[mod_cgi] FreeBSD 9.3/MacOSX does not have pipe2() (fixes #2765)
[lighttpd.git] / src / mod_ssi.c
blob81b386d5b6221b886aa603654aeb229937b51b3d
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
7 #include "plugin.h"
8 #include "stream.h"
10 #include "response.h"
12 #include "mod_ssi.h"
14 #include "inet_ntop_cache.h"
16 #include "sys-socket.h"
18 #include <sys/types.h>
20 #include <ctype.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <strings.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <time.h>
28 #include <unistd.h>
30 #ifdef HAVE_PWD_H
31 # include <pwd.h>
32 #endif
34 #ifdef HAVE_FORK
35 # include <sys/wait.h>
36 #endif
38 #ifdef HAVE_SYS_FILIO_H
39 # include <sys/filio.h>
40 #endif
42 #include "etag.h"
44 /* The newest modified time of included files for include statement */
45 static volatile time_t include_file_last_mtime = 0;
47 /* init the plugin data */
48 INIT_FUNC(mod_ssi_init) {
49 plugin_data *p;
51 p = calloc(1, sizeof(*p));
53 p->timefmt = buffer_init();
54 p->stat_fn = buffer_init();
56 p->ssi_vars = array_init();
57 p->ssi_cgi_env = array_init();
59 return p;
62 /* detroy the plugin data */
63 FREE_FUNC(mod_ssi_free) {
64 plugin_data *p = p_d;
65 UNUSED(srv);
67 if (!p) return HANDLER_GO_ON;
69 if (p->config_storage) {
70 size_t i;
71 for (i = 0; i < srv->config_context->used; i++) {
72 plugin_config *s = p->config_storage[i];
74 if (NULL == s) continue;
76 array_free(s->ssi_extension);
77 buffer_free(s->content_type);
79 free(s);
81 free(p->config_storage);
84 array_free(p->ssi_vars);
85 array_free(p->ssi_cgi_env);
86 buffer_free(p->timefmt);
87 buffer_free(p->stat_fn);
89 free(p);
91 return HANDLER_GO_ON;
94 /* handle plugin config and check values */
96 SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
97 plugin_data *p = p_d;
98 size_t i = 0;
100 config_values_t cv[] = {
101 { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
102 { "ssi.content-type", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
103 { "ssi.conditional-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
104 { "ssi.exec", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
105 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
108 if (!p) return HANDLER_ERROR;
110 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
112 for (i = 0; i < srv->config_context->used; i++) {
113 data_config const* config = (data_config const*)srv->config_context->data[i];
114 plugin_config *s;
116 s = calloc(1, sizeof(plugin_config));
117 s->ssi_extension = array_init();
118 s->content_type = buffer_init();
119 s->conditional_requests = 0;
120 s->ssi_exec = 1;
122 cv[0].destination = s->ssi_extension;
123 cv[1].destination = s->content_type;
124 cv[2].destination = &(s->conditional_requests);
125 cv[3].destination = &(s->ssi_exec);
127 p->config_storage[i] = s;
129 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
130 return HANDLER_ERROR;
134 return HANDLER_GO_ON;
138 static int ssi_env_add(void *venv, const char *key, size_t klen, const char *val, size_t vlen) {
139 array *env = venv;
140 data_string *ds;
142 /* array_set_key_value() w/o extra lookup to see if key already exists */
143 if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
144 ds = data_string_init();
146 buffer_copy_string_len(ds->key, key, klen);
147 buffer_copy_string_len(ds->value, val, vlen);
149 array_insert_unique(env, (data_unset *)ds);
151 return 0;
154 static int build_ssi_cgi_vars(server *srv, connection *con, plugin_data *p) {
155 http_cgi_opts opts = { 0, 0, NULL, NULL };
156 /* temporarily remove Authorization from request headers
157 * so that Authorization does not end up in SSI environment */
158 data_string *ds_auth = (data_string *)array_get_element(con->request.headers, "Authorization");
159 buffer *b_auth = NULL;
160 if (ds_auth) {
161 b_auth = ds_auth->value;
162 ds_auth->value = NULL;
165 array_reset(p->ssi_cgi_env);
167 if (0 != http_cgi_headers(srv, con, &opts, ssi_env_add, p->ssi_cgi_env)) {
168 con->http_status = 400;
169 return -1;
172 if (ds_auth) {
173 ds_auth->value = b_auth;
176 return 0;
179 static int process_ssi_stmt(server *srv, connection *con, plugin_data *p, const char **l, size_t n, struct stat *st) {
182 * <!--#element attribute=value attribute=value ... -->
184 * config DONE
185 * errmsg -- missing
186 * sizefmt DONE
187 * timefmt DONE
188 * echo DONE
189 * var DONE
190 * encoding -- missing
191 * exec DONE
192 * cgi -- never
193 * cmd DONE
194 * fsize DONE
195 * file DONE
196 * virtual DONE
197 * flastmod DONE
198 * file DONE
199 * virtual DONE
200 * include DONE
201 * file DONE
202 * virtual DONE
203 * printenv DONE
204 * set DONE
205 * var DONE
206 * value DONE
208 * if DONE
209 * elif DONE
210 * else DONE
211 * endif DONE
214 * expressions
215 * AND, OR DONE
216 * comp DONE
217 * ${...} -- missing
218 * $... DONE
219 * '...' DONE
220 * ( ... ) DONE
224 * ** all DONE **
225 * DATE_GMT
226 * The current date in Greenwich Mean Time.
227 * DATE_LOCAL
228 * The current date in the local time zone.
229 * DOCUMENT_NAME
230 * The filename (excluding directories) of the document requested by the user.
231 * DOCUMENT_URI
232 * The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document.
233 * LAST_MODIFIED
234 * The last modification date of the document requested by the user.
235 * USER_NAME
236 * Contains the owner of the file which included it.
240 size_t i, ssicmd = 0;
241 char buf[255];
242 buffer *b = NULL;
244 struct {
245 const char *var;
246 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
247 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
248 SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
249 } ssicmds[] = {
250 { "echo", SSI_ECHO },
251 { "include", SSI_INCLUDE },
252 { "flastmod", SSI_FLASTMOD },
253 { "fsize", SSI_FSIZE },
254 { "config", SSI_CONFIG },
255 { "printenv", SSI_PRINTENV },
256 { "set", SSI_SET },
257 { "if", SSI_IF },
258 { "elif", SSI_ELIF },
259 { "endif", SSI_ENDIF },
260 { "else", SSI_ELSE },
261 { "exec", SSI_EXEC },
263 { NULL, SSI_UNSET }
266 for (i = 0; ssicmds[i].var; i++) {
267 if (0 == strcmp(l[1], ssicmds[i].var)) {
268 ssicmd = ssicmds[i].type;
269 break;
273 switch(ssicmd) {
274 case SSI_ECHO: {
275 /* echo */
276 int var = 0;
277 /* int enc = 0; */
278 const char *var_val = NULL;
280 struct {
281 const char *var;
282 enum {
283 SSI_ECHO_UNSET,
284 SSI_ECHO_DATE_GMT,
285 SSI_ECHO_DATE_LOCAL,
286 SSI_ECHO_DOCUMENT_NAME,
287 SSI_ECHO_DOCUMENT_URI,
288 SSI_ECHO_LAST_MODIFIED,
289 SSI_ECHO_USER_NAME,
290 SSI_ECHO_SCRIPT_URI,
291 SSI_ECHO_SCRIPT_URL,
292 } type;
293 } echovars[] = {
294 { "DATE_GMT", SSI_ECHO_DATE_GMT },
295 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
296 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
297 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
298 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
299 { "USER_NAME", SSI_ECHO_USER_NAME },
300 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI },
301 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL },
303 { NULL, SSI_ECHO_UNSET }
307 struct {
308 const char *var;
309 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
310 } encvars[] = {
311 { "url", SSI_ENC_URL },
312 { "none", SSI_ENC_NONE },
313 { "entity", SSI_ENC_ENTITY },
315 { NULL, SSI_ENC_UNSET }
319 for (i = 2; i < n; i += 2) {
320 if (0 == strcmp(l[i], "var")) {
321 int j;
323 var_val = l[i+1];
325 for (j = 0; echovars[j].var; j++) {
326 if (0 == strcmp(l[i+1], echovars[j].var)) {
327 var = echovars[j].type;
328 break;
331 } else if (0 == strcmp(l[i], "encoding")) {
333 int j;
335 for (j = 0; encvars[j].var; j++) {
336 if (0 == strcmp(l[i+1], encvars[j].var)) {
337 enc = encvars[j].type;
338 break;
342 } else {
343 log_error_write(srv, __FILE__, __LINE__, "sss",
344 "ssi: unknown attribute for ",
345 l[1], l[i]);
349 if (p->if_is_false) break;
351 if (!var_val) {
352 log_error_write(srv, __FILE__, __LINE__, "sss",
353 "ssi: ",
354 l[1], "var is missing");
355 break;
358 switch(var) {
359 case SSI_ECHO_USER_NAME: {
360 struct passwd *pw;
362 b = buffer_init();
363 #ifdef HAVE_PWD_H
364 if (NULL == (pw = getpwuid(st->st_uid))) {
365 buffer_copy_int(b, st->st_uid);
366 } else {
367 buffer_copy_string(b, pw->pw_name);
369 #else
370 buffer_copy_int(b, st->st_uid);
371 #endif
372 chunkqueue_append_buffer(con->write_queue, b);
373 buffer_free(b);
374 break;
376 case SSI_ECHO_LAST_MODIFIED: {
377 time_t t = st->st_mtime;
379 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
380 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
381 } else {
382 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
384 break;
386 case SSI_ECHO_DATE_LOCAL: {
387 time_t t = time(NULL);
389 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
390 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
391 } else {
392 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
394 break;
396 case SSI_ECHO_DATE_GMT: {
397 time_t t = time(NULL);
399 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
400 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
401 } else {
402 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
404 break;
406 case SSI_ECHO_DOCUMENT_NAME: {
407 char *sl;
409 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
410 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->physical.path));
411 } else {
412 chunkqueue_append_mem(con->write_queue, sl + 1, strlen(sl + 1));
414 break;
416 case SSI_ECHO_DOCUMENT_URI: {
417 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.path));
418 break;
420 case SSI_ECHO_SCRIPT_URI: {
421 if (!buffer_string_is_empty(con->uri.scheme) && !buffer_string_is_empty(con->uri.authority)) {
422 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.scheme));
423 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("://"));
424 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.authority));
425 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
426 if (!buffer_string_is_empty(con->uri.query)) {
427 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
428 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
431 break;
433 case SSI_ECHO_SCRIPT_URL: {
434 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
435 if (!buffer_string_is_empty(con->uri.query)) {
436 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
437 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
439 break;
441 default: {
442 data_string *ds;
443 /* check if it is a cgi-var or a ssi-var */
445 if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val)) ||
446 NULL != (ds = (data_string *)array_get_element(p->ssi_vars, var_val))) {
447 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(ds->value));
448 } else {
449 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
452 break;
455 break;
457 case SSI_INCLUDE:
458 case SSI_FLASTMOD:
459 case SSI_FSIZE: {
460 const char * file_path = NULL, *virt_path = NULL;
461 struct stat stb;
462 char *sl;
464 for (i = 2; i < n; i += 2) {
465 if (0 == strcmp(l[i], "file")) {
466 file_path = l[i+1];
467 } else if (0 == strcmp(l[i], "virtual")) {
468 virt_path = l[i+1];
469 } else {
470 log_error_write(srv, __FILE__, __LINE__, "sss",
471 "ssi: unknown attribute for ",
472 l[1], l[i]);
476 if (!file_path && !virt_path) {
477 log_error_write(srv, __FILE__, __LINE__, "sss",
478 "ssi: ",
479 l[1], "file or virtual are missing");
480 break;
483 if (file_path && virt_path) {
484 log_error_write(srv, __FILE__, __LINE__, "sss",
485 "ssi: ",
486 l[1], "only one of file and virtual is allowed here");
487 break;
491 if (p->if_is_false) break;
493 if (file_path) {
494 /* current doc-root */
495 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
496 buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
497 } else {
498 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
501 buffer_copy_string(srv->tmp_buf, file_path);
502 buffer_urldecode_path(srv->tmp_buf);
503 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
504 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
505 } else {
506 /* virtual */
507 size_t remain;
509 if (virt_path[0] == '/') {
510 buffer_copy_string(p->stat_fn, virt_path);
511 } else {
512 /* there is always a / */
513 sl = strrchr(con->uri.path->ptr, '/');
515 buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
516 buffer_append_string(p->stat_fn, virt_path);
519 buffer_urldecode_path(p->stat_fn);
520 buffer_path_simplify(srv->tmp_buf, p->stat_fn);
522 /* we have an uri */
524 /* Destination physical path (similar to code in mod_webdav.c)
525 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
526 * (but neither modifies con->physical.rel_path)
527 * Find matching prefix to support relative paths to current physical path.
528 * Aliasing of paths underneath current con->physical.basedir might not work.
529 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
530 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
531 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
532 * (Related, use mod_auth to protect this basedir, but avoid attempting to
533 * use mod_auth on paths underneath this basedir, as target path is not
534 * validated with mod_auth)
537 /* find matching URI prefix
538 * check if remaining con->physical.rel_path matches suffix
539 * of con->physical.basedir so that we can use it to
540 * remap Destination physical path */
542 const char *sep, *sep2;
543 sep = con->uri.path->ptr;
544 sep2 = srv->tmp_buf->ptr;
545 for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ;
546 while (i != 0 && sep[--i] != '/') ; /* find matching directory path */
548 if (con->conf.force_lowercase_filenames) {
549 buffer_to_lower(srv->tmp_buf);
551 remain = buffer_string_length(con->uri.path) - i;
552 if (!con->conf.force_lowercase_filenames
553 ? buffer_is_equal_right_len(con->physical.path, con->physical.rel_path, remain)
554 :(buffer_string_length(con->physical.path) >= remain
555 && 0 == strncasecmp(con->physical.path->ptr+buffer_string_length(con->physical.path)-remain, con->physical.rel_path->ptr+i, remain))) {
556 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, buffer_string_length(con->physical.path)-remain);
557 buffer_append_string_len(p->stat_fn, srv->tmp_buf->ptr+i, buffer_string_length(srv->tmp_buf)-i);
558 } else {
559 /* unable to perform physical path remap here;
560 * assume doc_root/rel_path and no remapping */
561 buffer_copy_buffer(p->stat_fn, con->physical.doc_root);
562 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
566 if (0 == stat(p->stat_fn->ptr, &stb)) {
567 time_t t = stb.st_mtime;
569 switch (ssicmd) {
570 case SSI_FSIZE:
571 b = buffer_init();
572 if (p->sizefmt) {
573 int j = 0;
574 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
576 off_t s = stb.st_size;
578 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
580 buffer_copy_int(b, s);
581 buffer_append_string(b, abr[j]);
582 } else {
583 buffer_copy_int(b, stb.st_size);
585 chunkqueue_append_buffer(con->write_queue, b);
586 buffer_free(b);
587 break;
588 case SSI_FLASTMOD:
589 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
590 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
591 } else {
592 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
594 break;
595 case SSI_INCLUDE:
596 chunkqueue_append_file(con->write_queue, p->stat_fn, 0, stb.st_size);
598 /* Keep the newest mtime of included files */
599 if (stb.st_mtime > include_file_last_mtime)
600 include_file_last_mtime = stb.st_mtime;
602 break;
604 } else {
605 log_error_write(srv, __FILE__, __LINE__, "sbs",
606 "ssi: stating failed ",
607 p->stat_fn, strerror(errno));
609 break;
611 case SSI_SET: {
612 const char *key = NULL, *val = NULL;
613 for (i = 2; i < n; i += 2) {
614 if (0 == strcmp(l[i], "var")) {
615 key = l[i+1];
616 } else if (0 == strcmp(l[i], "value")) {
617 val = l[i+1];
618 } else {
619 log_error_write(srv, __FILE__, __LINE__, "sss",
620 "ssi: unknown attribute for ",
621 l[1], l[i]);
625 if (p->if_is_false) break;
627 if (key && val) {
628 data_string *ds;
630 if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
631 ds = data_string_init();
633 buffer_copy_string(ds->key, key);
634 buffer_copy_string(ds->value, val);
636 array_insert_unique(p->ssi_vars, (data_unset *)ds);
637 } else if (key || val) {
638 log_error_write(srv, __FILE__, __LINE__, "sSSss",
639 "ssi: var and value have to be set in <!--#set", l[1], "=", l[2], "-->");
640 } else {
641 log_error_write(srv, __FILE__, __LINE__, "s",
642 "ssi: var and value have to be set in <!--#set var=... value=... -->");
644 break;
646 case SSI_CONFIG:
647 if (p->if_is_false) break;
649 for (i = 2; i < n; i += 2) {
650 if (0 == strcmp(l[i], "timefmt")) {
651 buffer_copy_string(p->timefmt, l[i+1]);
652 } else if (0 == strcmp(l[i], "sizefmt")) {
653 if (0 == strcmp(l[i+1], "abbrev")) {
654 p->sizefmt = 1;
655 } else if (0 == strcmp(l[i+1], "bytes")) {
656 p->sizefmt = 0;
657 } else {
658 log_error_write(srv, __FILE__, __LINE__, "sssss",
659 "ssi: unknown value for attribute '",
660 l[i],
661 "' for ",
662 l[1], l[i+1]);
664 } else {
665 log_error_write(srv, __FILE__, __LINE__, "sss",
666 "ssi: unknown attribute for ",
667 l[1], l[i]);
670 break;
671 case SSI_PRINTENV:
672 if (p->if_is_false) break;
674 b = buffer_init();
675 for (i = 0; i < p->ssi_vars->used; i++) {
676 data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
678 buffer_append_string_buffer(b, ds->key);
679 buffer_append_string_len(b, CONST_STR_LEN("="));
680 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
681 buffer_append_string_len(b, CONST_STR_LEN("\n"));
683 for (i = 0; i < p->ssi_cgi_env->used; i++) {
684 data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]];
686 buffer_append_string_buffer(b, ds->key);
687 buffer_append_string_len(b, CONST_STR_LEN("="));
688 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
689 buffer_append_string_len(b, CONST_STR_LEN("\n"));
691 chunkqueue_append_buffer(con->write_queue, b);
692 buffer_free(b);
694 break;
695 case SSI_EXEC: {
696 const char *cmd = NULL;
697 pid_t pid;
698 int from_exec_fds[2];
700 if (!p->conf.ssi_exec) { /* <!--#exec ... --> disabled by config */
701 break;
704 for (i = 2; i < n; i += 2) {
705 if (0 == strcmp(l[i], "cmd")) {
706 cmd = l[i+1];
707 } else {
708 log_error_write(srv, __FILE__, __LINE__, "sss",
709 "ssi: unknown attribute for ",
710 l[1], l[i]);
714 if (p->if_is_false) break;
716 /* create a return pipe and send output to the html-page
718 * as exec is assumed evil it is implemented synchronously
721 if (!cmd) break;
722 #ifdef HAVE_FORK
723 if (pipe(from_exec_fds)) {
724 log_error_write(srv, __FILE__, __LINE__, "ss",
725 "pipe failed: ", strerror(errno));
726 return -1;
729 /* fork, execve */
730 switch (pid = fork()) {
731 case 0: {
732 /* move stdout to from_rrdtool_fd[1] */
733 close(STDOUT_FILENO);
734 dup2(from_exec_fds[1], STDOUT_FILENO);
735 close(from_exec_fds[1]);
736 /* not needed */
737 close(from_exec_fds[0]);
739 /* close stdin */
740 close(STDIN_FILENO);
742 execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
744 log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
746 /* */
747 SEGFAULT();
748 break;
750 case -1:
751 /* error */
752 log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
753 break;
754 default: {
755 /* father */
756 int status;
757 ssize_t r;
758 int was_interrupted = 0;
760 close(from_exec_fds[1]);
762 /* wait for the client to end */
765 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
767 do {
768 if (-1 == waitpid(pid, &status, 0)) {
769 if (errno == EINTR) {
770 was_interrupted++;
771 } else {
772 was_interrupted = 0;
773 log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
775 } else if (WIFEXITED(status)) {
776 int toread;
777 /* read everything from client and paste it into the output */
778 was_interrupted = 0;
780 while(1) {
781 if (ioctl(from_exec_fds[0], FIONREAD, &toread)) {
782 log_error_write(srv, __FILE__, __LINE__, "s",
783 "unexpected end-of-file (perhaps the ssi-exec process died)");
784 return -1;
787 if (toread > 0) {
788 char *mem;
789 size_t mem_len;
791 chunkqueue_get_memory(con->write_queue, &mem, &mem_len, 0, toread);
792 r = read(from_exec_fds[0], mem, mem_len);
793 chunkqueue_use_memory(con->write_queue, r > 0 ? r : 0);
795 if (r < 0) break; /* read failed */
796 } else {
797 break;
800 } else {
801 was_interrupted = 0;
802 log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
804 } while (was_interrupted > 0 && was_interrupted < 4); /* if waitpid() gets interrupted, retry, but max 4 times */
806 close(from_exec_fds[0]);
808 break;
811 #else
813 return -1;
814 #endif
816 break;
818 case SSI_IF: {
819 const char *expr = NULL;
821 for (i = 2; i < n; i += 2) {
822 if (0 == strcmp(l[i], "expr")) {
823 expr = l[i+1];
824 } else {
825 log_error_write(srv, __FILE__, __LINE__, "sss",
826 "ssi: unknown attribute for ",
827 l[1], l[i]);
831 if (!expr) {
832 log_error_write(srv, __FILE__, __LINE__, "sss",
833 "ssi: ",
834 l[1], "expr missing");
835 break;
838 if ((!p->if_is_false) &&
839 ((p->if_is_false_level == 0) ||
840 (p->if_level < p->if_is_false_level))) {
841 switch (ssi_eval_expr(srv, con, p, expr)) {
842 case -1:
843 case 0:
844 p->if_is_false = 1;
845 p->if_is_false_level = p->if_level;
846 break;
847 case 1:
848 p->if_is_false = 0;
849 break;
853 p->if_level++;
855 break;
857 case SSI_ELSE:
858 p->if_level--;
860 if (p->if_is_false) {
861 if ((p->if_level == p->if_is_false_level) &&
862 (p->if_is_false_endif == 0)) {
863 p->if_is_false = 0;
865 } else {
866 p->if_is_false = 1;
868 p->if_is_false_level = p->if_level;
870 p->if_level++;
872 break;
873 case SSI_ELIF: {
874 const char *expr = NULL;
875 for (i = 2; i < n; i += 2) {
876 if (0 == strcmp(l[i], "expr")) {
877 expr = l[i+1];
878 } else {
879 log_error_write(srv, __FILE__, __LINE__, "sss",
880 "ssi: unknown attribute for ",
881 l[1], l[i]);
885 if (!expr) {
886 log_error_write(srv, __FILE__, __LINE__, "sss",
887 "ssi: ",
888 l[1], "expr missing");
889 break;
892 p->if_level--;
894 if (p->if_level == p->if_is_false_level) {
895 if ((p->if_is_false) &&
896 (p->if_is_false_endif == 0)) {
897 switch (ssi_eval_expr(srv, con, p, expr)) {
898 case -1:
899 case 0:
900 p->if_is_false = 1;
901 p->if_is_false_level = p->if_level;
902 break;
903 case 1:
904 p->if_is_false = 0;
905 break;
907 } else {
908 p->if_is_false = 1;
909 p->if_is_false_level = p->if_level;
910 p->if_is_false_endif = 1;
914 p->if_level++;
916 break;
918 case SSI_ENDIF:
919 p->if_level--;
921 if (p->if_level == p->if_is_false_level) {
922 p->if_is_false = 0;
923 p->if_is_false_endif = 0;
926 break;
927 default:
928 log_error_write(srv, __FILE__, __LINE__, "ss",
929 "ssi: unknown ssi-command:",
930 l[1]);
931 break;
934 return 0;
938 static int mod_ssi_parse_ssi_stmt_value(const char * const s, const int len) {
939 int n;
940 const int c = (s[0] == '"' ? '"' : s[0] == '\'' ? '\'' : 0);
941 if (0 != c) {
942 for (n = 1; n < len; ++n) {
943 if (s[n] == c) return n+1;
944 if (s[n] == '\\') {
945 if (n+1 == len) return 0; /* invalid */
946 ++n;
949 return 0; /* invalid */
950 } else {
951 for (n = 0; n < len; ++n) {
952 if (isspace(s[n])) return n;
953 if (s[n] == '\\') {
954 if (n+1 == len) return 0; /* invalid */
955 ++n;
958 return n;
962 static int mod_ssi_parse_ssi_stmt_offlen(int o[10], const char * const s, const int len) {
965 * <!--#element attribute=value attribute=value ... -->
968 /* s must begin "<!--#" and must end with "-->" */
969 int n = 5;
970 o[0] = n;
971 for (; light_isalpha(s[n]); ++n) ; /*(n = 5 to begin after "<!--#")*/
972 o[1] = n - o[0];
973 if (0 == o[1]) return -1; /* empty token */
975 if (n+3 == len) return 2; /* token only; no params */
976 if (!isspace(s[n])) return -1;
977 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
978 if (n+3 == len) return 2; /* token only; no params */
980 o[2] = n;
981 for (; light_isalpha(s[n]); ++n) ;
982 o[3] = n - o[2];
983 if (0 == o[3] || s[n++] != '=') return -1;
985 o[4] = n;
986 o[5] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
987 if (0 == o[5]) return -1; /* empty or invalid token */
988 n += o[5];
990 if (n+3 == len) return 6; /* token and one param */
991 if (!isspace(s[n])) return -1;
992 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
993 if (n+3 == len) return 6; /* token and one param */
995 o[6] = n;
996 for (; light_isalpha(s[n]); ++n) ;
997 o[7] = n - o[6];
998 if (0 == o[7] || s[n++] != '=') return -1;
1000 o[8] = n;
1001 o[9] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1002 if (0 == o[9]) return -1; /* empty or invalid token */
1003 n += o[9];
1005 if (n+3 == len) return 10; /* token and two params */
1006 if (!isspace(s[n])) return -1;
1007 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1008 if (n+3 == len) return 10; /* token and two params */
1009 return -1;
1012 static void mod_ssi_parse_ssi_stmt(server *srv, connection *con, plugin_data *p, char *s, int len, struct stat *st) {
1015 * <!--#element attribute=value attribute=value ... -->
1018 int o[10];
1019 int m;
1020 const int n = mod_ssi_parse_ssi_stmt_offlen(o, s, len);
1021 char *l[6] = { s, NULL, NULL, NULL, NULL, NULL };
1022 if (-1 == n) {
1023 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1024 chunkqueue_append_mem(con->write_queue, s, len); /* append stmt as-is */
1025 return;
1028 #if 0
1029 /* dup s and then modify s */
1030 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1031 l[0] = malloc((size_t)(len+1));
1032 memcpy(l[0], s, (size_t)len);
1033 (l[0])[len] = '\0';
1034 #endif
1036 /* modify s in-place to split string into arg tokens */
1037 for (m = 0; m < n; m += 2) {
1038 char *ptr = s+o[m];
1039 switch (*ptr) {
1040 case '"':
1041 case '\'': (++ptr)[o[m+1]-2] = '\0'; break;
1042 default: ptr[o[m+1]] = '\0'; break;
1044 l[1+(m>>1)] = ptr;
1045 if (m == 4 || m == 8) {
1046 /* XXX: removing '\\' escapes from param value would be
1047 * the right thing to do, but would potentially change
1048 * current behavior, e.g. <!--#exec cmd=... --> */
1052 process_ssi_stmt(srv, con, p, (const char **)l, 1+(n>>1), st);
1054 #if 0
1055 free(l[0]);
1056 #endif
1059 static int mod_ssi_stmt_len(const char *s, const int len) {
1060 /* s must begin "<!--#" */
1061 int n, sq = 0, dq = 0, bs = 0;
1062 for (n = 5; n < len; ++n) { /*(n = 5 to begin after "<!--#")*/
1063 switch (s[n]) {
1064 default:
1065 break;
1066 case '-':
1067 if (!sq && !dq && n+2 < len && s[n+1] == '-' && s[n+2] == '>') return n+3; /* found end of stmt */
1068 break;
1069 case '"':
1070 if (!sq && (!dq || !bs)) dq = !dq;
1071 break;
1072 case '\'':
1073 if (!dq && (!sq || !bs)) sq = !sq;
1074 break;
1075 case '\\':
1076 if (sq || dq) bs = !bs;
1077 break;
1080 return 0; /* incomplete directive "<!--#...-->" */
1083 static void mod_ssi_read_fd(server *srv, connection *con, plugin_data *p, int fd, struct stat *st) {
1084 ssize_t rd;
1085 size_t offset, pretag;
1086 char buf[8192];
1088 offset = 0;
1089 pretag = 0;
1090 while (0 < (rd = read(fd, buf+offset, sizeof(buf)-offset))) {
1091 char *s;
1092 size_t prelen = 0, len;
1093 offset += (size_t)rd;
1094 for (; (s = memchr(buf+prelen, '<', offset-prelen)); ++prelen) {
1095 prelen = s - buf;
1096 if (prelen + 5 <= offset) { /*("<!--#" is 5 chars)*/
1097 if (0 != memcmp(s+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1099 if (prelen - pretag && !p->if_is_false) {
1100 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1103 len = mod_ssi_stmt_len(buf+prelen, offset-prelen);
1104 if (len) { /* num of chars to be consumed */
1105 mod_ssi_parse_ssi_stmt(srv, con, p, buf+prelen, len, st);
1106 prelen += (len - 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1107 pretag = prelen + 1;
1108 if (pretag == offset) {
1109 offset = pretag = 0;
1110 break;
1112 } else if (0 == prelen && offset == sizeof(buf)) { /*(full buf)*/
1113 /* SSI statement is way too long
1114 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1115 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1116 /* check if buf ends with "-" or "--" which might be part of "-->"
1117 * (buf contains at least 5 chars for "<!--#") */
1118 if (buf[offset-2] == '-' && buf[offset-1] == '-') {
1119 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("--"));
1120 } else if (buf[offset-1] == '-') {
1121 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("-"));
1123 offset = pretag = 0;
1124 break;
1125 } else { /* incomplete directive "<!--#...-->" */
1126 memmove(buf, buf+prelen, (offset -= prelen));
1127 pretag = 0;
1128 break;
1130 } else if (prelen + 1 == offset || 0 == memcmp(s+1, "!--", offset - prelen - 1)) {
1131 if (prelen - pretag && !p->if_is_false) {
1132 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1134 memcpy(buf, buf+prelen, (offset -= prelen));
1135 pretag = 0;
1136 break;
1138 /* loop to look for next '<' */
1140 if (offset == sizeof(buf)) {
1141 if (!p->if_is_false) {
1142 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1144 offset = pretag = 0;
1148 if (0 != rd) {
1149 log_error_write(srv, __FILE__, __LINE__, "SsB", "read(): ", strerror(errno), con->physical.path);
1152 if (offset - pretag) {
1153 /* copy remaining data in buf */
1154 if (!p->if_is_false) {
1155 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1161 /* don't want to block when open()ing a fifo */
1162 #if defined(O_NONBLOCK)
1163 # define FIFO_NONBLOCK O_NONBLOCK
1164 #else
1165 # define FIFO_NONBLOCK 0
1166 #endif
1168 static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) {
1169 int fd;
1170 struct stat st;
1172 /* get a stream to the file */
1174 array_reset(p->ssi_vars);
1175 array_reset(p->ssi_cgi_env);
1176 buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1177 p->sizefmt = 0;
1178 build_ssi_cgi_vars(srv, con, p);
1179 p->if_is_false = 0;
1181 /* Reset the modified time of included files */
1182 include_file_last_mtime = 0;
1184 if (-1 == (fd = open(con->physical.path->ptr, O_RDONLY | FIFO_NONBLOCK))) {
1185 log_error_write(srv, __FILE__, __LINE__, "sb",
1186 "open: ", con->physical.path);
1187 return -1;
1190 if (0 != fstat(fd, &st)) {
1191 log_error_write(srv, __FILE__, __LINE__, "SB", "fstat failed: ", con->physical.path);
1192 close(fd);
1193 return -1;
1196 mod_ssi_read_fd(srv, con, p, fd, &st);
1198 close(fd);
1199 con->file_started = 1;
1200 con->file_finished = 1;
1201 con->mode = p->id;
1203 if (buffer_string_is_empty(p->conf.content_type)) {
1204 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1205 } else {
1206 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type));
1209 if (p->conf.conditional_requests) {
1210 /* Generate "ETag" & "Last-Modified" headers */
1211 buffer *mtime = NULL;
1213 /* use most recently modified include file for ETag and Last-Modified */
1214 if (st.st_mtime < include_file_last_mtime)
1215 st.st_mtime = include_file_last_mtime;
1217 etag_create(con->physical.etag, &st, con->etag_flags);
1218 response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
1220 mtime = strftime_cache_get(srv, st.st_mtime);
1221 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
1223 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
1224 /* ok, the client already has our content,
1225 * no need to send it again */
1227 chunkqueue_reset(con->write_queue);
1231 /* Reset the modified time of included files */
1232 include_file_last_mtime = 0;
1234 /* reset physical.path */
1235 buffer_reset(con->physical.path);
1237 return 0;
1240 #define PATCH(x) \
1241 p->conf.x = s->x;
1242 static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
1243 size_t i, j;
1244 plugin_config *s = p->config_storage[0];
1246 PATCH(ssi_extension);
1247 PATCH(content_type);
1248 PATCH(conditional_requests);
1249 PATCH(ssi_exec);
1251 /* skip the first, the global context */
1252 for (i = 1; i < srv->config_context->used; i++) {
1253 data_config *dc = (data_config *)srv->config_context->data[i];
1254 s = p->config_storage[i];
1256 /* condition didn't match */
1257 if (!config_check_cond(srv, con, dc)) continue;
1259 /* merge config */
1260 for (j = 0; j < dc->value->used; j++) {
1261 data_unset *du = dc->value->data[j];
1263 if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
1264 PATCH(ssi_extension);
1265 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) {
1266 PATCH(content_type);
1267 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.conditional-requests"))) {
1268 PATCH(conditional_requests);
1269 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.exec"))) {
1270 PATCH(ssi_exec);
1275 return 0;
1277 #undef PATCH
1279 URIHANDLER_FUNC(mod_ssi_physical_path) {
1280 plugin_data *p = p_d;
1281 size_t k;
1283 if (con->mode != DIRECT) return HANDLER_GO_ON;
1285 if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
1287 mod_ssi_patch_connection(srv, con, p);
1289 for (k = 0; k < p->conf.ssi_extension->used; k++) {
1290 data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
1292 if (buffer_is_empty(ds->value)) continue;
1294 if (buffer_is_equal_right_len(con->physical.path, ds->value, buffer_string_length(ds->value))) {
1295 /* handle ssi-request */
1297 if (mod_ssi_handle_request(srv, con, p)) {
1298 /* on error */
1299 con->http_status = 500;
1300 con->mode = DIRECT;
1303 return HANDLER_FINISHED;
1307 /* not found */
1308 return HANDLER_GO_ON;
1311 /* this function is called at dlopen() time and inits the callbacks */
1313 int mod_ssi_plugin_init(plugin *p);
1314 int mod_ssi_plugin_init(plugin *p) {
1315 p->version = LIGHTTPD_VERSION_ID;
1316 p->name = buffer_init_string("ssi");
1318 p->init = mod_ssi_init;
1319 p->handle_subrequest_start = mod_ssi_physical_path;
1320 p->set_defaults = mod_ssi_set_defaults;
1321 p->cleanup = mod_ssi_free;
1323 p->data = NULL;
1325 return 0;