13 #include "inet_ntop_cache.h"
15 #include "sys-socket.h"
17 #include <sys/types.h>
34 # include <sys/wait.h>
37 #ifdef HAVE_SYS_FILIO_H
38 # include <sys/filio.h>
43 static handler_ctx
* handler_ctx_init(plugin_data
*p
) {
44 handler_ctx
*hctx
= calloc(1, sizeof(*hctx
));
46 hctx
->timefmt
= p
->timefmt
;
47 hctx
->stat_fn
= p
->stat_fn
;
48 hctx
->ssi_vars
= p
->ssi_vars
;
49 hctx
->ssi_cgi_env
= p
->ssi_cgi_env
;
50 memcpy(&hctx
->conf
, &p
->conf
, sizeof(plugin_config
));
54 static void handler_ctx_free(handler_ctx
*hctx
) {
58 /* The newest modified time of included files for include statement */
59 static volatile time_t include_file_last_mtime
= 0;
61 /* init the plugin data */
62 INIT_FUNC(mod_ssi_init
) {
65 p
= calloc(1, sizeof(*p
));
67 p
->timefmt
= buffer_init();
68 p
->stat_fn
= buffer_init();
70 p
->ssi_vars
= array_init();
71 p
->ssi_cgi_env
= array_init();
76 /* detroy the plugin data */
77 FREE_FUNC(mod_ssi_free
) {
81 if (!p
) return HANDLER_GO_ON
;
83 if (p
->config_storage
) {
85 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
86 plugin_config
*s
= p
->config_storage
[i
];
88 if (NULL
== s
) continue;
90 array_free(s
->ssi_extension
);
91 buffer_free(s
->content_type
);
95 free(p
->config_storage
);
98 array_free(p
->ssi_vars
);
99 array_free(p
->ssi_cgi_env
);
100 buffer_free(p
->timefmt
);
101 buffer_free(p
->stat_fn
);
105 return HANDLER_GO_ON
;
108 /* handle plugin config and check values */
110 SETDEFAULTS_FUNC(mod_ssi_set_defaults
) {
111 plugin_data
*p
= p_d
;
114 config_values_t cv
[] = {
115 { "ssi.extension", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
116 { "ssi.content-type", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
117 { "ssi.conditional-requests", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
118 { "ssi.exec", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
119 { "ssi.recursion-max", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
120 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
123 if (!p
) return HANDLER_ERROR
;
125 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
127 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
128 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
131 s
= calloc(1, sizeof(plugin_config
));
132 s
->ssi_extension
= array_init();
133 s
->content_type
= buffer_init();
134 s
->conditional_requests
= 0;
136 s
->ssi_recursion_max
= 0;
138 cv
[0].destination
= s
->ssi_extension
;
139 cv
[1].destination
= s
->content_type
;
140 cv
[2].destination
= &(s
->conditional_requests
);
141 cv
[3].destination
= &(s
->ssi_exec
);
142 cv
[4].destination
= &(s
->ssi_recursion_max
);
144 p
->config_storage
[i
] = s
;
146 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
147 return HANDLER_ERROR
;
151 return HANDLER_GO_ON
;
155 static int ssi_env_add(void *venv
, const char *key
, size_t klen
, const char *val
, size_t vlen
) {
159 /* array_set_key_value() w/o extra lookup to see if key already exists */
160 if (NULL
== (ds
= (data_string
*)array_get_unused_element(env
, TYPE_STRING
))) {
161 ds
= data_string_init();
163 buffer_copy_string_len(ds
->key
, key
, klen
);
164 buffer_copy_string_len(ds
->value
, val
, vlen
);
166 array_insert_unique(env
, (data_unset
*)ds
);
171 static int build_ssi_cgi_vars(server
*srv
, connection
*con
, handler_ctx
*p
) {
172 http_cgi_opts opts
= { 0, 0, NULL
, NULL
};
173 /* temporarily remove Authorization from request headers
174 * so that Authorization does not end up in SSI environment */
175 data_string
*ds_auth
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
176 buffer
*b_auth
= NULL
;
178 b_auth
= ds_auth
->value
;
179 ds_auth
->value
= NULL
;
182 array_reset(p
->ssi_cgi_env
);
184 if (0 != http_cgi_headers(srv
, con
, &opts
, ssi_env_add
, p
->ssi_cgi_env
)) {
185 con
->http_status
= 400;
190 ds_auth
->value
= b_auth
;
196 static int mod_ssi_process_file(server
*srv
, connection
*con
, handler_ctx
*p
, struct stat
*st
);
198 static int process_ssi_stmt(server
*srv
, connection
*con
, handler_ctx
*p
, const char **l
, size_t n
, struct stat
*st
) {
201 * <!--#element attribute=value attribute=value ... -->
209 * encoding -- missing
245 * The current date in Greenwich Mean Time.
247 * The current date in the local time zone.
249 * The filename (excluding directories) of the document requested by the user.
251 * 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.
253 * The last modification date of the document requested by the user.
255 * Contains the owner of the file which included it.
259 size_t i
, ssicmd
= 0;
263 static const struct {
265 enum { SSI_UNSET
, SSI_ECHO
, SSI_FSIZE
, SSI_INCLUDE
, SSI_FLASTMOD
,
266 SSI_CONFIG
, SSI_PRINTENV
, SSI_SET
, SSI_IF
, SSI_ELIF
,
267 SSI_ELSE
, SSI_ENDIF
, SSI_EXEC
} type
;
269 { "echo", SSI_ECHO
},
270 { "include", SSI_INCLUDE
},
271 { "flastmod", SSI_FLASTMOD
},
272 { "fsize", SSI_FSIZE
},
273 { "config", SSI_CONFIG
},
274 { "printenv", SSI_PRINTENV
},
277 { "elif", SSI_ELIF
},
278 { "endif", SSI_ENDIF
},
279 { "else", SSI_ELSE
},
280 { "exec", SSI_EXEC
},
285 for (i
= 0; ssicmds
[i
].var
; i
++) {
286 if (0 == strcmp(l
[1], ssicmds
[i
].var
)) {
287 ssicmd
= ssicmds
[i
].type
;
297 const char *var_val
= NULL
;
299 static const struct {
305 SSI_ECHO_DOCUMENT_NAME
,
306 SSI_ECHO_DOCUMENT_URI
,
307 SSI_ECHO_LAST_MODIFIED
,
313 { "DATE_GMT", SSI_ECHO_DATE_GMT
},
314 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL
},
315 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME
},
316 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI
},
317 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED
},
318 { "USER_NAME", SSI_ECHO_USER_NAME
},
319 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI
},
320 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL
},
322 { NULL
, SSI_ECHO_UNSET
}
326 static const struct {
328 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
330 { "url", SSI_ENC_URL },
331 { "none", SSI_ENC_NONE },
332 { "entity", SSI_ENC_ENTITY },
334 { NULL, SSI_ENC_UNSET }
338 for (i
= 2; i
< n
; i
+= 2) {
339 if (0 == strcmp(l
[i
], "var")) {
344 for (j
= 0; echovars
[j
].var
; j
++) {
345 if (0 == strcmp(l
[i
+1], echovars
[j
].var
)) {
346 var
= echovars
[j
].type
;
350 } else if (0 == strcmp(l
[i
], "encoding")) {
354 for (j = 0; encvars[j].var; j++) {
355 if (0 == strcmp(l[i+1], encvars[j].var)) {
356 enc = encvars[j].type;
362 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
363 "ssi: unknown attribute for ",
368 if (p
->if_is_false
) break;
371 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
373 l
[1], "var is missing");
378 case SSI_ECHO_USER_NAME
: {
383 if (NULL
== (pw
= getpwuid(st
->st_uid
))) {
384 buffer_copy_int(b
, st
->st_uid
);
386 buffer_copy_string(b
, pw
->pw_name
);
389 buffer_copy_int(b
, st
->st_uid
);
391 chunkqueue_append_buffer(con
->write_queue
, b
);
395 case SSI_ECHO_LAST_MODIFIED
: {
396 time_t t
= st
->st_mtime
;
398 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
399 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
401 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
405 case SSI_ECHO_DATE_LOCAL
: {
406 time_t t
= time(NULL
);
408 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
409 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
411 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
415 case SSI_ECHO_DATE_GMT
: {
416 time_t t
= time(NULL
);
418 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, gmtime(&t
))) {
419 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
421 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
425 case SSI_ECHO_DOCUMENT_NAME
: {
428 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
429 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->physical
.path
));
431 chunkqueue_append_mem(con
->write_queue
, sl
+ 1, strlen(sl
+ 1));
435 case SSI_ECHO_DOCUMENT_URI
: {
436 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.path
));
439 case SSI_ECHO_SCRIPT_URI
: {
440 if (!buffer_string_is_empty(con
->uri
.scheme
) && !buffer_string_is_empty(con
->uri
.authority
)) {
441 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.scheme
));
442 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("://"));
443 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.authority
));
444 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
445 if (!buffer_string_is_empty(con
->uri
.query
)) {
446 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
447 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
452 case SSI_ECHO_SCRIPT_URL
: {
453 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
454 if (!buffer_string_is_empty(con
->uri
.query
)) {
455 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
456 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
462 /* check if it is a cgi-var or a ssi-var */
464 if (NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_cgi_env
, var_val
)) ||
465 NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_vars
, var_val
))) {
466 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(ds
->value
));
468 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
479 const char * file_path
= NULL
, *virt_path
= NULL
;
483 for (i
= 2; i
< n
; i
+= 2) {
484 if (0 == strcmp(l
[i
], "file")) {
486 } else if (0 == strcmp(l
[i
], "virtual")) {
489 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
490 "ssi: unknown attribute for ",
495 if (!file_path
&& !virt_path
) {
496 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
498 l
[1], "file or virtual are missing");
502 if (file_path
&& virt_path
) {
503 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
505 l
[1], "only one of file and virtual is allowed here");
510 if (p
->if_is_false
) break;
513 /* current doc-root */
514 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
515 buffer_copy_string_len(p
->stat_fn
, CONST_STR_LEN("/"));
517 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, sl
- con
->physical
.path
->ptr
+ 1);
520 buffer_copy_string(srv
->tmp_buf
, file_path
);
521 buffer_urldecode_path(srv
->tmp_buf
);
522 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
523 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
528 if (virt_path
[0] == '/') {
529 buffer_copy_string(p
->stat_fn
, virt_path
);
531 /* there is always a / */
532 sl
= strrchr(con
->uri
.path
->ptr
, '/');
534 buffer_copy_string_len(p
->stat_fn
, con
->uri
.path
->ptr
, sl
- con
->uri
.path
->ptr
+ 1);
535 buffer_append_string(p
->stat_fn
, virt_path
);
538 buffer_urldecode_path(p
->stat_fn
);
539 buffer_path_simplify(srv
->tmp_buf
, p
->stat_fn
);
543 /* Destination physical path (similar to code in mod_webdav.c)
544 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
545 * (but neither modifies con->physical.rel_path)
546 * Find matching prefix to support relative paths to current physical path.
547 * Aliasing of paths underneath current con->physical.basedir might not work.
548 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
549 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
550 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
551 * (Related, use mod_auth to protect this basedir, but avoid attempting to
552 * use mod_auth on paths underneath this basedir, as target path is not
553 * validated with mod_auth)
556 /* find matching URI prefix
557 * check if remaining con->physical.rel_path matches suffix
558 * of con->physical.basedir so that we can use it to
559 * remap Destination physical path */
561 const char *sep
, *sep2
;
562 sep
= con
->uri
.path
->ptr
;
563 sep2
= srv
->tmp_buf
->ptr
;
564 for (i
= 0; sep
[i
] && sep
[i
] == sep2
[i
]; ++i
) ;
565 while (i
!= 0 && sep
[--i
] != '/') ; /* find matching directory path */
567 if (con
->conf
.force_lowercase_filenames
) {
568 buffer_to_lower(srv
->tmp_buf
);
570 remain
= buffer_string_length(con
->uri
.path
) - i
;
571 if (!con
->conf
.force_lowercase_filenames
572 ? buffer_is_equal_right_len(con
->physical
.path
, con
->physical
.rel_path
, remain
)
573 :(buffer_string_length(con
->physical
.path
) >= remain
574 && 0 == strncasecmp(con
->physical
.path
->ptr
+buffer_string_length(con
->physical
.path
)-remain
, con
->physical
.rel_path
->ptr
+i
, remain
))) {
575 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, buffer_string_length(con
->physical
.path
)-remain
);
576 buffer_append_string_len(p
->stat_fn
, srv
->tmp_buf
->ptr
+i
, buffer_string_length(srv
->tmp_buf
)-i
);
578 /* unable to perform physical path remap here;
579 * assume doc_root/rel_path and no remapping */
580 buffer_copy_buffer(p
->stat_fn
, con
->physical
.doc_root
);
581 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
585 if (0 == stat(p
->stat_fn
->ptr
, &stb
)) {
586 time_t t
= stb
.st_mtime
;
593 const char *abr
[] = { " B", " kB", " MB", " GB", " TB", NULL
};
595 off_t s
= stb
.st_size
;
597 for (j
= 0; s
> 1024 && abr
[j
+1]; s
/= 1024, j
++);
599 buffer_copy_int(b
, s
);
600 buffer_append_string(b
, abr
[j
]);
602 buffer_copy_int(b
, stb
.st_size
);
604 chunkqueue_append_buffer(con
->write_queue
, b
);
608 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
609 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
611 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
615 /* Keep the newest mtime of included files */
616 if (stb
.st_mtime
> include_file_last_mtime
)
617 include_file_last_mtime
= stb
.st_mtime
;
619 if (file_path
|| 0 == p
->conf
.ssi_recursion_max
) {
620 /* don't process if #include file="..." is used */
621 chunkqueue_append_file(con
->write_queue
, p
->stat_fn
, 0, stb
.st_size
);
623 buffer
*upsave
, *ppsave
, *prpsave
;
625 /* only allow predefined recursion depth */
626 if (p
->ssi_recursion_depth
>= p
->conf
.ssi_recursion_max
) {
627 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(error: include directives recurse deeper than pre-defined ssi.recursion-max)"));
631 /* prevents simple infinite loop */
632 if (buffer_is_equal(con
->physical
.path
, p
->stat_fn
)) {
633 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(error: include directives create an infinite loop)"));
637 /* save and restore con->physical.path, con->physical.rel_path, and con->uri.path around include
639 * srv->tmp_buf contains url-decoded, path-simplified, and lowercased (if con->conf.force_lowercase) uri path of target.
640 * con->uri.path and con->physical.rel_path are set to the same since we only operate on filenames here,
641 * not full re-run of all modules for subrequest */
642 upsave
= con
->uri
.path
;
643 ppsave
= con
->physical
.path
;
644 prpsave
= con
->physical
.rel_path
;
646 con
->physical
.path
= p
->stat_fn
;
647 p
->stat_fn
= buffer_init();
649 con
->uri
.path
= con
->physical
.rel_path
= buffer_init_buffer(srv
->tmp_buf
);
651 /*(ignore return value; muddle along as best we can if error occurs)*/
652 ++p
->ssi_recursion_depth
;
653 mod_ssi_process_file(srv
, con
, p
, &stb
);
654 --p
->ssi_recursion_depth
;
656 buffer_free(con
->uri
.path
);
657 con
->uri
.path
= upsave
;
658 con
->physical
.rel_path
= prpsave
;
660 buffer_free(p
->stat_fn
);
661 p
->stat_fn
= con
->physical
.path
;
662 con
->physical
.path
= ppsave
;
668 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
669 "ssi: stating failed ",
670 p
->stat_fn
, strerror(errno
));
675 const char *key
= NULL
, *val
= NULL
;
676 for (i
= 2; i
< n
; i
+= 2) {
677 if (0 == strcmp(l
[i
], "var")) {
679 } else if (0 == strcmp(l
[i
], "value")) {
682 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
683 "ssi: unknown attribute for ",
688 if (p
->if_is_false
) break;
693 if (NULL
== (ds
= (data_string
*)array_get_unused_element(p
->ssi_vars
, TYPE_STRING
))) {
694 ds
= data_string_init();
696 buffer_copy_string(ds
->key
, key
);
697 buffer_copy_string(ds
->value
, val
);
699 array_insert_unique(p
->ssi_vars
, (data_unset
*)ds
);
700 } else if (key
|| val
) {
701 log_error_write(srv
, __FILE__
, __LINE__
, "sSSss",
702 "ssi: var and value have to be set in <!--#set", l
[1], "=", l
[2], "-->");
704 log_error_write(srv
, __FILE__
, __LINE__
, "s",
705 "ssi: var and value have to be set in <!--#set var=... value=... -->");
710 if (p
->if_is_false
) break;
712 for (i
= 2; i
< n
; i
+= 2) {
713 if (0 == strcmp(l
[i
], "timefmt")) {
714 buffer_copy_string(p
->timefmt
, l
[i
+1]);
715 } else if (0 == strcmp(l
[i
], "sizefmt")) {
716 if (0 == strcmp(l
[i
+1], "abbrev")) {
718 } else if (0 == strcmp(l
[i
+1], "bytes")) {
721 log_error_write(srv
, __FILE__
, __LINE__
, "sssss",
722 "ssi: unknown value for attribute '",
728 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
729 "ssi: unknown attribute for ",
735 if (p
->if_is_false
) break;
738 for (i
= 0; i
< p
->ssi_vars
->used
; i
++) {
739 data_string
*ds
= (data_string
*)p
->ssi_vars
->data
[p
->ssi_vars
->sorted
[i
]];
741 buffer_append_string_buffer(b
, ds
->key
);
742 buffer_append_string_len(b
, CONST_STR_LEN("="));
743 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
744 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
746 for (i
= 0; i
< p
->ssi_cgi_env
->used
; i
++) {
747 data_string
*ds
= (data_string
*)p
->ssi_cgi_env
->data
[p
->ssi_cgi_env
->sorted
[i
]];
749 buffer_append_string_buffer(b
, ds
->key
);
750 buffer_append_string_len(b
, CONST_STR_LEN("="));
751 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
752 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
754 chunkqueue_append_buffer(con
->write_queue
, b
);
759 const char *cmd
= NULL
;
761 int from_exec_fds
[2];
763 if (!p
->conf
.ssi_exec
) { /* <!--#exec ... --> disabled by config */
767 for (i
= 2; i
< n
; i
+= 2) {
768 if (0 == strcmp(l
[i
], "cmd")) {
771 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
772 "ssi: unknown attribute for ",
777 if (p
->if_is_false
) break;
779 /* create a return pipe and send output to the html-page
781 * as exec is assumed evil it is implemented synchronously
786 if (pipe(from_exec_fds
)) {
787 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
788 "pipe failed: ", strerror(errno
));
793 switch (pid
= fork()) {
795 /* move stdout to from_rrdtool_fd[1] */
796 close(STDOUT_FILENO
);
797 dup2(from_exec_fds
[1], STDOUT_FILENO
);
798 close(from_exec_fds
[1]);
800 close(from_exec_fds
[0]);
805 execl("/bin/sh", "sh", "-c", cmd
, (char *)NULL
);
807 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "spawing exec failed:", strerror(errno
), cmd
);
815 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "fork failed:", strerror(errno
));
821 int was_interrupted
= 0;
823 close(from_exec_fds
[1]);
825 /* wait for the client to end */
828 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
831 if (-1 == waitpid(pid
, &status
, 0)) {
832 if (errno
== EINTR
) {
836 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "waitpid failed:", strerror(errno
));
838 } else if (WIFEXITED(status
)) {
840 /* read everything from client and paste it into the output */
844 if (ioctl(from_exec_fds
[0], FIONREAD
, &toread
)) {
845 log_error_write(srv
, __FILE__
, __LINE__
, "s",
846 "unexpected end-of-file (perhaps the ssi-exec process died)");
854 chunkqueue_get_memory(con
->write_queue
, &mem
, &mem_len
, 0, toread
);
855 r
= read(from_exec_fds
[0], mem
, mem_len
);
856 chunkqueue_use_memory(con
->write_queue
, r
> 0 ? r
: 0);
858 if (r
< 0) break; /* read failed */
865 log_error_write(srv
, __FILE__
, __LINE__
, "s", "process exited abnormally");
867 } while (was_interrupted
> 0 && was_interrupted
< 4); /* if waitpid() gets interrupted, retry, but max 4 times */
869 close(from_exec_fds
[0]);
882 const char *expr
= NULL
;
884 for (i
= 2; i
< n
; i
+= 2) {
885 if (0 == strcmp(l
[i
], "expr")) {
888 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
889 "ssi: unknown attribute for ",
895 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
897 l
[1], "expr missing");
901 if ((!p
->if_is_false
) &&
902 ((p
->if_is_false_level
== 0) ||
903 (p
->if_level
< p
->if_is_false_level
))) {
904 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
908 p
->if_is_false_level
= p
->if_level
;
923 if (p
->if_is_false
) {
924 if ((p
->if_level
== p
->if_is_false_level
) &&
925 (p
->if_is_false_endif
== 0)) {
931 p
->if_is_false_level
= p
->if_level
;
937 const char *expr
= NULL
;
938 for (i
= 2; i
< n
; i
+= 2) {
939 if (0 == strcmp(l
[i
], "expr")) {
942 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
943 "ssi: unknown attribute for ",
949 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
951 l
[1], "expr missing");
957 if (p
->if_level
== p
->if_is_false_level
) {
958 if ((p
->if_is_false
) &&
959 (p
->if_is_false_endif
== 0)) {
960 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
964 p
->if_is_false_level
= p
->if_level
;
972 p
->if_is_false_level
= p
->if_level
;
973 p
->if_is_false_endif
= 1;
984 if (p
->if_level
== p
->if_is_false_level
) {
986 p
->if_is_false_endif
= 0;
991 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
992 "ssi: unknown ssi-command:",
1001 static int mod_ssi_parse_ssi_stmt_value(const char * const s
, const int len
) {
1003 const int c
= (s
[0] == '"' ? '"' : s
[0] == '\'' ? '\'' : 0);
1005 for (n
= 1; n
< len
; ++n
) {
1006 if (s
[n
] == c
) return n
+1;
1008 if (n
+1 == len
) return 0; /* invalid */
1012 return 0; /* invalid */
1014 for (n
= 0; n
< len
; ++n
) {
1015 if (isspace(s
[n
])) return n
;
1017 if (n
+1 == len
) return 0; /* invalid */
1025 static int mod_ssi_parse_ssi_stmt_offlen(int o
[10], const char * const s
, const int len
) {
1028 * <!--#element attribute=value attribute=value ... -->
1031 /* s must begin "<!--#" and must end with "-->" */
1034 for (; light_isalpha(s
[n
]); ++n
) ; /*(n = 5 to begin after "<!--#")*/
1036 if (0 == o
[1]) return -1; /* empty token */
1038 if (n
+3 == len
) return 2; /* token only; no params */
1039 if (!isspace(s
[n
])) return -1;
1040 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1041 if (n
+3 == len
) return 2; /* token only; no params */
1044 for (; light_isalpha(s
[n
]); ++n
) ;
1046 if (0 == o
[3] || s
[n
++] != '=') return -1;
1049 o
[5] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1050 if (0 == o
[5]) return -1; /* empty or invalid token */
1053 if (n
+3 == len
) return 6; /* token and one param */
1054 if (!isspace(s
[n
])) return -1;
1055 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1056 if (n
+3 == len
) return 6; /* token and one param */
1059 for (; light_isalpha(s
[n
]); ++n
) ;
1061 if (0 == o
[7] || s
[n
++] != '=') return -1;
1064 o
[9] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1065 if (0 == o
[9]) return -1; /* empty or invalid token */
1068 if (n
+3 == len
) return 10; /* token and two params */
1069 if (!isspace(s
[n
])) return -1;
1070 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1071 if (n
+3 == len
) return 10; /* token and two params */
1075 static void mod_ssi_parse_ssi_stmt(server
*srv
, connection
*con
, handler_ctx
*p
, char *s
, int len
, struct stat
*st
) {
1078 * <!--#element attribute=value attribute=value ... -->
1083 const int n
= mod_ssi_parse_ssi_stmt_offlen(o
, s
, len
);
1084 char *l
[6] = { s
, NULL
, NULL
, NULL
, NULL
, NULL
};
1086 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1087 chunkqueue_append_mem(con
->write_queue
, s
, len
); /* append stmt as-is */
1092 /* dup s and then modify s */
1093 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1094 l
[0] = malloc((size_t)(len
+1));
1095 memcpy(l
[0], s
, (size_t)len
);
1099 /* modify s in-place to split string into arg tokens */
1100 for (m
= 0; m
< n
; m
+= 2) {
1104 case '\'': (++ptr
)[o
[m
+1]-2] = '\0'; break;
1105 default: ptr
[o
[m
+1]] = '\0'; break;
1108 if (m
== 4 || m
== 8) {
1109 /* XXX: removing '\\' escapes from param value would be
1110 * the right thing to do, but would potentially change
1111 * current behavior, e.g. <!--#exec cmd=... --> */
1115 process_ssi_stmt(srv
, con
, p
, (const char **)l
, 1+(n
>>1), st
);
1122 static int mod_ssi_stmt_len(const char *s
, const int len
) {
1123 /* s must begin "<!--#" */
1124 int n
, sq
= 0, dq
= 0, bs
= 0;
1125 for (n
= 5; n
< len
; ++n
) { /*(n = 5 to begin after "<!--#")*/
1130 if (!sq
&& !dq
&& n
+2 < len
&& s
[n
+1] == '-' && s
[n
+2] == '>') return n
+3; /* found end of stmt */
1133 if (!sq
&& (!dq
|| !bs
)) dq
= !dq
;
1136 if (!dq
&& (!sq
|| !bs
)) sq
= !sq
;
1139 if (sq
|| dq
) bs
= !bs
;
1143 return 0; /* incomplete directive "<!--#...-->" */
1146 static void mod_ssi_read_fd(server
*srv
, connection
*con
, handler_ctx
*p
, struct stat
*st
, int fd
) {
1148 size_t offset
, pretag
;
1149 size_t bufsz
= 8192;
1150 char *buf
= malloc(bufsz
); /* allocate to reduce chance of stack exhaustion upon deep recursion */
1155 while (0 < (rd
= read(fd
, buf
+offset
, bufsz
-offset
))) {
1157 size_t prelen
= 0, len
;
1158 offset
+= (size_t)rd
;
1159 for (; (s
= memchr(buf
+prelen
, '<', offset
-prelen
)); ++prelen
) {
1161 if (prelen
+ 5 <= offset
) { /*("<!--#" is 5 chars)*/
1162 if (0 != memcmp(s
+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1164 if (prelen
- pretag
&& !p
->if_is_false
) {
1165 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1168 len
= mod_ssi_stmt_len(buf
+prelen
, offset
-prelen
);
1169 if (len
) { /* num of chars to be consumed */
1170 mod_ssi_parse_ssi_stmt(srv
, con
, p
, buf
+prelen
, len
, st
);
1171 prelen
+= (len
- 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1172 pretag
= prelen
+ 1;
1173 if (pretag
== offset
) {
1174 offset
= pretag
= 0;
1177 } else if (0 == prelen
&& offset
== bufsz
) { /*(full buf)*/
1178 /* SSI statement is way too long
1179 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1180 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1181 /* check if buf ends with "-" or "--" which might be part of "-->"
1182 * (buf contains at least 5 chars for "<!--#") */
1183 if (buf
[offset
-2] == '-' && buf
[offset
-1] == '-') {
1184 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("--"));
1185 } else if (buf
[offset
-1] == '-') {
1186 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("-"));
1188 offset
= pretag
= 0;
1190 } else { /* incomplete directive "<!--#...-->" */
1191 memmove(buf
, buf
+prelen
, (offset
-= prelen
));
1195 } else if (prelen
+ 1 == offset
|| 0 == memcmp(s
+1, "!--", offset
- prelen
- 1)) {
1196 if (prelen
- pretag
&& !p
->if_is_false
) {
1197 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1199 memcpy(buf
, buf
+prelen
, (offset
-= prelen
));
1203 /* loop to look for next '<' */
1205 if (offset
== bufsz
) {
1206 if (!p
->if_is_false
) {
1207 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1209 offset
= pretag
= 0;
1214 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "read(): ", strerror(errno
), con
->physical
.path
);
1217 if (offset
- pretag
) {
1218 /* copy remaining data in buf */
1219 if (!p
->if_is_false
) {
1220 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1228 /* don't want to block when open()ing a fifo */
1229 #if defined(O_NONBLOCK)
1230 # define FIFO_NONBLOCK O_NONBLOCK
1232 # define FIFO_NONBLOCK 0
1235 static int mod_ssi_process_file(server
*srv
, connection
*con
, handler_ctx
*p
, struct stat
*st
) {
1236 int fd
= open(con
->physical
.path
->ptr
, O_RDONLY
| FIFO_NONBLOCK
);
1238 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "open(): ",
1239 strerror(errno
), con
->physical
.path
);
1243 if (0 != fstat(fd
, st
)) {
1244 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "fstat(): ",
1245 strerror(errno
), con
->physical
.path
);
1250 mod_ssi_read_fd(srv
, con
, p
, st
, fd
);
1257 static int mod_ssi_handle_request(server
*srv
, connection
*con
, handler_ctx
*p
) {
1260 /* get a stream to the file */
1262 array_reset(p
->ssi_vars
);
1263 array_reset(p
->ssi_cgi_env
);
1264 buffer_copy_string_len(p
->timefmt
, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1265 build_ssi_cgi_vars(srv
, con
, p
);
1267 /* Reset the modified time of included files */
1268 include_file_last_mtime
= 0;
1270 mod_ssi_process_file(srv
, con
, p
, &st
);
1272 con
->file_started
= 1;
1273 con
->file_finished
= 1;
1275 if (buffer_string_is_empty(p
->conf
.content_type
)) {
1276 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1278 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->conf
.content_type
));
1281 if (p
->conf
.conditional_requests
) {
1282 /* Generate "ETag" & "Last-Modified" headers */
1283 buffer
*mtime
= NULL
;
1285 /* use most recently modified include file for ETag and Last-Modified */
1286 if (st
.st_mtime
< include_file_last_mtime
)
1287 st
.st_mtime
= include_file_last_mtime
;
1289 etag_create(con
->physical
.etag
, &st
, con
->etag_flags
);
1290 response_header_overwrite(srv
, con
, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con
->physical
.etag
));
1292 mtime
= strftime_cache_get(srv
, st
.st_mtime
);
1293 response_header_overwrite(srv
, con
, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime
));
1295 if (HANDLER_FINISHED
== http_response_handle_cachable(srv
, con
, mtime
)) {
1296 /* ok, the client already has our content,
1297 * no need to send it again */
1299 chunkqueue_reset(con
->write_queue
);
1303 /* Reset the modified time of included files */
1304 include_file_last_mtime
= 0;
1306 /* reset physical.path */
1307 buffer_reset(con
->physical
.path
);
1314 static int mod_ssi_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
1316 plugin_config
*s
= p
->config_storage
[0];
1318 PATCH(ssi_extension
);
1319 PATCH(content_type
);
1320 PATCH(conditional_requests
);
1322 PATCH(ssi_recursion_max
);
1324 /* skip the first, the global context */
1325 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
1326 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
1327 s
= p
->config_storage
[i
];
1329 /* condition didn't match */
1330 if (!config_check_cond(srv
, con
, dc
)) continue;
1333 for (j
= 0; j
< dc
->value
->used
; j
++) {
1334 data_unset
*du
= dc
->value
->data
[j
];
1336 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.extension"))) {
1337 PATCH(ssi_extension
);
1338 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.content-type"))) {
1339 PATCH(content_type
);
1340 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.conditional-requests"))) {
1341 PATCH(conditional_requests
);
1342 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.exec"))) {
1344 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.recursion-max"))) {
1345 PATCH(ssi_recursion_max
);
1354 URIHANDLER_FUNC(mod_ssi_physical_path
) {
1355 plugin_data
*p
= p_d
;
1358 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
1360 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
1362 mod_ssi_patch_connection(srv
, con
, p
);
1364 for (k
= 0; k
< p
->conf
.ssi_extension
->used
; k
++) {
1365 data_string
*ds
= (data_string
*)p
->conf
.ssi_extension
->data
[k
];
1367 if (buffer_is_empty(ds
->value
)) continue;
1369 if (buffer_is_equal_right_len(con
->physical
.path
, ds
->value
, buffer_string_length(ds
->value
))) {
1370 con
->plugin_ctx
[p
->id
] = handler_ctx_init(p
);
1376 return HANDLER_GO_ON
;
1379 SUBREQUEST_FUNC(mod_ssi_handle_subrequest
) {
1380 plugin_data
*p
= p_d
;
1381 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1382 if (NULL
== hctx
) return HANDLER_GO_ON
;
1383 if (con
->mode
!= p
->id
) return HANDLER_GO_ON
; /* not my job */
1385 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT,
1386 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars,
1387 * and hctx->ssi_cgi_env should be allocated and cleaned up per request.
1390 /* handle ssi-request */
1392 if (mod_ssi_handle_request(srv
, con
, hctx
)) {
1394 con
->http_status
= 500;
1398 return HANDLER_FINISHED
;
1401 static handler_t
mod_ssi_connection_reset(server
*srv
, connection
*con
, void *p_d
) {
1402 plugin_data
*p
= p_d
;
1403 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1405 handler_ctx_free(hctx
);
1406 con
->plugin_ctx
[p
->id
] = NULL
;
1410 return HANDLER_GO_ON
;
1413 /* this function is called at dlopen() time and inits the callbacks */
1415 int mod_ssi_plugin_init(plugin
*p
);
1416 int mod_ssi_plugin_init(plugin
*p
) {
1417 p
->version
= LIGHTTPD_VERSION_ID
;
1418 p
->name
= buffer_init_string("ssi");
1420 p
->init
= mod_ssi_init
;
1421 p
->handle_subrequest_start
= mod_ssi_physical_path
;
1422 p
->handle_subrequest
= mod_ssi_handle_subrequest
;
1423 p
->connection_reset
= mod_ssi_connection_reset
;
1424 p
->set_defaults
= mod_ssi_set_defaults
;
1425 p
->cleanup
= mod_ssi_free
;