fix gcc 6.1.1 compiler warn misleading-indentation
[lighttpd.git] / src / mod_ssi.c
blob94b5aebdc060d1945e0d18a1042cc504ba8e66e8
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(array *env, const char *key, const char *val) {
139 data_string *ds;
141 if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
142 ds = data_string_init();
144 buffer_copy_string(ds->key, key);
145 buffer_copy_string(ds->value, val);
147 array_insert_unique(env, (data_unset *)ds);
149 return 0;
154 * the next two functions are take from fcgi.c
158 static int ssi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
159 size_t i;
161 for (i = 0; i < con->request.headers->used; i++) {
162 data_string *ds;
164 ds = (data_string *)con->request.headers->data[i];
166 if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) {
167 /* don't forward the Authorization: Header */
168 if (0 == strcasecmp(ds->key->ptr, "AUTHORIZATION")) {
169 continue;
172 buffer_copy_string_encoded_cgi_varnames(srv->tmp_buf, CONST_BUF_LEN(ds->key), 1);
174 ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
178 for (i = 0; i < con->environment->used; i++) {
179 data_string *ds;
181 ds = (data_string *)con->environment->data[i];
183 if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) {
184 buffer_copy_string_encoded_cgi_varnames(srv->tmp_buf, CONST_BUF_LEN(ds->key), 0);
186 ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
190 return 0;
193 static int build_ssi_cgi_vars(server *srv, connection *con, plugin_data *p) {
194 char buf[LI_ITOSTRING_LENGTH];
196 server_socket *srv_sock = con->srv_socket;
198 #ifdef HAVE_IPV6
199 char b2[INET6_ADDRSTRLEN + 1];
200 #endif
202 #define CONST_STRING(x) \
205 array_reset(p->ssi_cgi_env);
207 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), con->conf.server_tag->ptr);
208 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_NAME"),
209 #ifdef HAVE_IPV6
210 inet_ntop(srv_sock->addr.plain.sa_family,
211 srv_sock->addr.plain.sa_family == AF_INET6 ?
212 (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
213 (const void *) &(srv_sock->addr.ipv4.sin_addr),
214 b2, sizeof(b2)-1)
215 #else
216 inet_ntoa(srv_sock->addr.ipv4.sin_addr)
217 #endif
219 ssi_env_add(p->ssi_cgi_env, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1");
221 li_utostrn(buf, sizeof(buf),
222 #ifdef HAVE_IPV6
223 ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
224 #else
225 ntohs(srv_sock->addr.ipv4.sin_port)
226 #endif
229 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PORT"), buf);
231 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_ADDR"),
232 inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
234 if (con->request.content_length > 0) {
235 /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
237 li_itostrn(buf, sizeof(buf), con->request.content_length);
238 ssi_env_add(p->ssi_cgi_env, CONST_STRING("CONTENT_LENGTH"), buf);
242 * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
243 * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
244 * (6.1.14, 6.1.6, 6.1.7)
247 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_NAME"), con->uri.path->ptr);
248 ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), "");
251 * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
252 * http://www.php.net/manual/en/reserved.variables.php
253 * treatment of PATH_TRANSLATED is different from the one of CGI specs.
254 * TODO: this code should be checked against cgi.fix_pathinfo php
255 * parameter.
258 if (!buffer_string_is_empty(con->request.pathinfo)) {
259 ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), con->request.pathinfo->ptr);
262 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_FILENAME"), con->physical.path->ptr);
263 ssi_env_add(p->ssi_cgi_env, CONST_STRING("DOCUMENT_ROOT"), con->physical.basedir->ptr);
265 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_URI"), con->request.uri->ptr);
267 if (!buffer_string_is_empty(con->uri.scheme)) {
268 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_SCHEME"), con->uri.scheme->ptr);
271 ssi_env_add(p->ssi_cgi_env, CONST_STRING("QUERY_STRING"), buffer_is_empty(con->uri.query) ? "" : con->uri.query->ptr);
272 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con->request.http_method));
273 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version));
274 /* set REDIRECT_STATUS for php compiled with --force-redirect
275 * (if REDIRECT_STATUS has not already been set by error handler) */
276 if (0 == con->error_handler_saved_status) {
277 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REDIRECT_STATUS"), "200");
280 ssi_env_add_request_headers(srv, con, p);
282 return 0;
285 static int process_ssi_stmt(server *srv, connection *con, plugin_data *p, const char **l, size_t n, struct stat *st) {
288 * <!--#element attribute=value attribute=value ... -->
290 * config DONE
291 * errmsg -- missing
292 * sizefmt DONE
293 * timefmt DONE
294 * echo DONE
295 * var DONE
296 * encoding -- missing
297 * exec DONE
298 * cgi -- never
299 * cmd DONE
300 * fsize DONE
301 * file DONE
302 * virtual DONE
303 * flastmod DONE
304 * file DONE
305 * virtual DONE
306 * include DONE
307 * file DONE
308 * virtual DONE
309 * printenv DONE
310 * set DONE
311 * var DONE
312 * value DONE
314 * if DONE
315 * elif DONE
316 * else DONE
317 * endif DONE
320 * expressions
321 * AND, OR DONE
322 * comp DONE
323 * ${...} -- missing
324 * $... DONE
325 * '...' DONE
326 * ( ... ) DONE
330 * ** all DONE **
331 * DATE_GMT
332 * The current date in Greenwich Mean Time.
333 * DATE_LOCAL
334 * The current date in the local time zone.
335 * DOCUMENT_NAME
336 * The filename (excluding directories) of the document requested by the user.
337 * DOCUMENT_URI
338 * 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.
339 * LAST_MODIFIED
340 * The last modification date of the document requested by the user.
341 * USER_NAME
342 * Contains the owner of the file which included it.
346 size_t i, ssicmd = 0;
347 char buf[255];
348 buffer *b = NULL;
350 struct {
351 const char *var;
352 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
353 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
354 SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
355 } ssicmds[] = {
356 { "echo", SSI_ECHO },
357 { "include", SSI_INCLUDE },
358 { "flastmod", SSI_FLASTMOD },
359 { "fsize", SSI_FSIZE },
360 { "config", SSI_CONFIG },
361 { "printenv", SSI_PRINTENV },
362 { "set", SSI_SET },
363 { "if", SSI_IF },
364 { "elif", SSI_ELIF },
365 { "endif", SSI_ENDIF },
366 { "else", SSI_ELSE },
367 { "exec", SSI_EXEC },
369 { NULL, SSI_UNSET }
372 for (i = 0; ssicmds[i].var; i++) {
373 if (0 == strcmp(l[1], ssicmds[i].var)) {
374 ssicmd = ssicmds[i].type;
375 break;
379 switch(ssicmd) {
380 case SSI_ECHO: {
381 /* echo */
382 int var = 0;
383 /* int enc = 0; */
384 const char *var_val = NULL;
386 struct {
387 const char *var;
388 enum {
389 SSI_ECHO_UNSET,
390 SSI_ECHO_DATE_GMT,
391 SSI_ECHO_DATE_LOCAL,
392 SSI_ECHO_DOCUMENT_NAME,
393 SSI_ECHO_DOCUMENT_URI,
394 SSI_ECHO_LAST_MODIFIED,
395 SSI_ECHO_USER_NAME,
396 SSI_ECHO_SCRIPT_URI,
397 SSI_ECHO_SCRIPT_URL,
398 } type;
399 } echovars[] = {
400 { "DATE_GMT", SSI_ECHO_DATE_GMT },
401 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
402 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
403 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
404 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
405 { "USER_NAME", SSI_ECHO_USER_NAME },
406 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI },
407 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL },
409 { NULL, SSI_ECHO_UNSET }
413 struct {
414 const char *var;
415 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
416 } encvars[] = {
417 { "url", SSI_ENC_URL },
418 { "none", SSI_ENC_NONE },
419 { "entity", SSI_ENC_ENTITY },
421 { NULL, SSI_ENC_UNSET }
425 for (i = 2; i < n; i += 2) {
426 if (0 == strcmp(l[i], "var")) {
427 int j;
429 var_val = l[i+1];
431 for (j = 0; echovars[j].var; j++) {
432 if (0 == strcmp(l[i+1], echovars[j].var)) {
433 var = echovars[j].type;
434 break;
437 } else if (0 == strcmp(l[i], "encoding")) {
439 int j;
441 for (j = 0; encvars[j].var; j++) {
442 if (0 == strcmp(l[i+1], encvars[j].var)) {
443 enc = encvars[j].type;
444 break;
448 } else {
449 log_error_write(srv, __FILE__, __LINE__, "sss",
450 "ssi: unknown attribute for ",
451 l[1], l[i]);
455 if (p->if_is_false) break;
457 if (!var_val) {
458 log_error_write(srv, __FILE__, __LINE__, "sss",
459 "ssi: ",
460 l[1], "var is missing");
461 break;
464 switch(var) {
465 case SSI_ECHO_USER_NAME: {
466 struct passwd *pw;
468 b = buffer_init();
469 #ifdef HAVE_PWD_H
470 if (NULL == (pw = getpwuid(st->st_uid))) {
471 buffer_copy_int(b, st->st_uid);
472 } else {
473 buffer_copy_string(b, pw->pw_name);
475 #else
476 buffer_copy_int(b, st->st_uid);
477 #endif
478 chunkqueue_append_buffer(con->write_queue, b);
479 buffer_free(b);
480 break;
482 case SSI_ECHO_LAST_MODIFIED: {
483 time_t t = st->st_mtime;
485 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
486 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
487 } else {
488 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
490 break;
492 case SSI_ECHO_DATE_LOCAL: {
493 time_t t = time(NULL);
495 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
496 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
497 } else {
498 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
500 break;
502 case SSI_ECHO_DATE_GMT: {
503 time_t t = time(NULL);
505 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
506 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
507 } else {
508 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
510 break;
512 case SSI_ECHO_DOCUMENT_NAME: {
513 char *sl;
515 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
516 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->physical.path));
517 } else {
518 chunkqueue_append_mem(con->write_queue, sl + 1, strlen(sl + 1));
520 break;
522 case SSI_ECHO_DOCUMENT_URI: {
523 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.path));
524 break;
526 case SSI_ECHO_SCRIPT_URI: {
527 if (!buffer_string_is_empty(con->uri.scheme) && !buffer_string_is_empty(con->uri.authority)) {
528 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.scheme));
529 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("://"));
530 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.authority));
531 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
532 if (!buffer_string_is_empty(con->uri.query)) {
533 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
534 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
537 break;
539 case SSI_ECHO_SCRIPT_URL: {
540 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
541 if (!buffer_string_is_empty(con->uri.query)) {
542 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
543 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
545 break;
547 default: {
548 data_string *ds;
549 /* check if it is a cgi-var or a ssi-var */
551 if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val)) ||
552 NULL != (ds = (data_string *)array_get_element(p->ssi_vars, var_val))) {
553 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(ds->value));
554 } else {
555 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
558 break;
561 break;
563 case SSI_INCLUDE:
564 case SSI_FLASTMOD:
565 case SSI_FSIZE: {
566 const char * file_path = NULL, *virt_path = NULL;
567 struct stat stb;
568 char *sl;
570 for (i = 2; i < n; i += 2) {
571 if (0 == strcmp(l[i], "file")) {
572 file_path = l[i+1];
573 } else if (0 == strcmp(l[i], "virtual")) {
574 virt_path = l[i+1];
575 } else {
576 log_error_write(srv, __FILE__, __LINE__, "sss",
577 "ssi: unknown attribute for ",
578 l[1], l[i]);
582 if (!file_path && !virt_path) {
583 log_error_write(srv, __FILE__, __LINE__, "sss",
584 "ssi: ",
585 l[1], "file or virtual are missing");
586 break;
589 if (file_path && virt_path) {
590 log_error_write(srv, __FILE__, __LINE__, "sss",
591 "ssi: ",
592 l[1], "only one of file and virtual is allowed here");
593 break;
597 if (p->if_is_false) break;
599 if (file_path) {
600 /* current doc-root */
601 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
602 buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
603 } else {
604 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
607 buffer_copy_string(srv->tmp_buf, file_path);
608 buffer_urldecode_path(srv->tmp_buf);
609 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
610 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
611 } else {
612 /* virtual */
613 size_t remain;
615 if (virt_path[0] == '/') {
616 buffer_copy_string(p->stat_fn, virt_path);
617 } else {
618 /* there is always a / */
619 sl = strrchr(con->uri.path->ptr, '/');
621 buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
622 buffer_append_string(p->stat_fn, virt_path);
625 buffer_urldecode_path(p->stat_fn);
626 buffer_path_simplify(srv->tmp_buf, p->stat_fn);
628 /* we have an uri */
630 /* Destination physical path (similar to code in mod_webdav.c)
631 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
632 * (but neither modifies con->physical.rel_path)
633 * Find matching prefix to support relative paths to current physical path.
634 * Aliasing of paths underneath current con->physical.basedir might not work.
635 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
636 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
637 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
638 * (Related, use mod_auth to protect this basedir, but avoid attempting to
639 * use mod_auth on paths underneath this basedir, as target path is not
640 * validated with mod_auth)
643 /* find matching URI prefix
644 * check if remaining con->physical.rel_path matches suffix
645 * of con->physical.basedir so that we can use it to
646 * remap Destination physical path */
648 const char *sep, *sep2;
649 sep = con->uri.path->ptr;
650 sep2 = srv->tmp_buf->ptr;
651 for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ;
652 while (i != 0 && sep[--i] != '/') ; /* find matching directory path */
654 if (con->conf.force_lowercase_filenames) {
655 buffer_to_lower(srv->tmp_buf);
657 remain = buffer_string_length(con->uri.path) - i;
658 if (!con->conf.force_lowercase_filenames
659 ? buffer_is_equal_right_len(con->physical.path, con->physical.rel_path, remain)
660 :(buffer_string_length(con->physical.path) >= remain
661 && 0 == strncasecmp(con->physical.path->ptr+buffer_string_length(con->physical.path)-remain, con->physical.rel_path->ptr+i, remain))) {
662 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, buffer_string_length(con->physical.path)-remain);
663 buffer_append_string_len(p->stat_fn, srv->tmp_buf->ptr+i, buffer_string_length(srv->tmp_buf)-i);
664 } else {
665 /* unable to perform physical path remap here;
666 * assume doc_root/rel_path and no remapping */
667 buffer_copy_buffer(p->stat_fn, con->physical.doc_root);
668 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
672 if (0 == stat(p->stat_fn->ptr, &stb)) {
673 time_t t = stb.st_mtime;
675 switch (ssicmd) {
676 case SSI_FSIZE:
677 b = buffer_init();
678 if (p->sizefmt) {
679 int j = 0;
680 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
682 off_t s = stb.st_size;
684 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
686 buffer_copy_int(b, s);
687 buffer_append_string(b, abr[j]);
688 } else {
689 buffer_copy_int(b, stb.st_size);
691 chunkqueue_append_buffer(con->write_queue, b);
692 buffer_free(b);
693 break;
694 case SSI_FLASTMOD:
695 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
696 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
697 } else {
698 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
700 break;
701 case SSI_INCLUDE:
702 chunkqueue_append_file(con->write_queue, p->stat_fn, 0, stb.st_size);
704 /* Keep the newest mtime of included files */
705 if (stb.st_mtime > include_file_last_mtime)
706 include_file_last_mtime = stb.st_mtime;
708 break;
710 } else {
711 log_error_write(srv, __FILE__, __LINE__, "sbs",
712 "ssi: stating failed ",
713 p->stat_fn, strerror(errno));
715 break;
717 case SSI_SET: {
718 const char *key = NULL, *val = NULL;
719 for (i = 2; i < n; i += 2) {
720 if (0 == strcmp(l[i], "var")) {
721 key = l[i+1];
722 } else if (0 == strcmp(l[i], "value")) {
723 val = l[i+1];
724 } else {
725 log_error_write(srv, __FILE__, __LINE__, "sss",
726 "ssi: unknown attribute for ",
727 l[1], l[i]);
731 if (p->if_is_false) break;
733 if (key && val) {
734 data_string *ds;
736 if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
737 ds = data_string_init();
739 buffer_copy_string(ds->key, key);
740 buffer_copy_string(ds->value, val);
742 array_insert_unique(p->ssi_vars, (data_unset *)ds);
743 } else if (key || val) {
744 log_error_write(srv, __FILE__, __LINE__, "sSSss",
745 "ssi: var and value have to be set in <!--#set", l[1], "=", l[2], "-->");
746 } else {
747 log_error_write(srv, __FILE__, __LINE__, "s",
748 "ssi: var and value have to be set in <!--#set var=... value=... -->");
750 break;
752 case SSI_CONFIG:
753 if (p->if_is_false) break;
755 for (i = 2; i < n; i += 2) {
756 if (0 == strcmp(l[i], "timefmt")) {
757 buffer_copy_string(p->timefmt, l[i+1]);
758 } else if (0 == strcmp(l[i], "sizefmt")) {
759 if (0 == strcmp(l[i+1], "abbrev")) {
760 p->sizefmt = 1;
761 } else if (0 == strcmp(l[i+1], "abbrev")) {
762 p->sizefmt = 0;
763 } else {
764 log_error_write(srv, __FILE__, __LINE__, "sssss",
765 "ssi: unknown value for attribute '",
766 l[i],
767 "' for ",
768 l[1], l[i+1]);
770 } else {
771 log_error_write(srv, __FILE__, __LINE__, "sss",
772 "ssi: unknown attribute for ",
773 l[1], l[i]);
776 break;
777 case SSI_PRINTENV:
778 if (p->if_is_false) break;
780 b = buffer_init();
781 for (i = 0; i < p->ssi_vars->used; i++) {
782 data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
784 buffer_append_string_buffer(b, ds->key);
785 buffer_append_string_len(b, CONST_STR_LEN("="));
786 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
787 buffer_append_string_len(b, CONST_STR_LEN("\n"));
789 for (i = 0; i < p->ssi_cgi_env->used; i++) {
790 data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]];
792 buffer_append_string_buffer(b, ds->key);
793 buffer_append_string_len(b, CONST_STR_LEN("="));
794 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
795 buffer_append_string_len(b, CONST_STR_LEN("\n"));
797 chunkqueue_append_buffer(con->write_queue, b);
798 buffer_free(b);
800 break;
801 case SSI_EXEC: {
802 const char *cmd = NULL;
803 pid_t pid;
804 int from_exec_fds[2];
806 if (!p->conf.ssi_exec) { /* <!--#exec ... --> disabled by config */
807 break;
810 for (i = 2; i < n; i += 2) {
811 if (0 == strcmp(l[i], "cmd")) {
812 cmd = l[i+1];
813 } else {
814 log_error_write(srv, __FILE__, __LINE__, "sss",
815 "ssi: unknown attribute for ",
816 l[1], l[i]);
820 if (p->if_is_false) break;
822 /* create a return pipe and send output to the html-page
824 * as exec is assumed evil it is implemented synchronously
827 if (!cmd) break;
828 #ifdef HAVE_FORK
829 if (pipe(from_exec_fds)) {
830 log_error_write(srv, __FILE__, __LINE__, "ss",
831 "pipe failed: ", strerror(errno));
832 return -1;
835 /* fork, execve */
836 switch (pid = fork()) {
837 case 0: {
838 /* move stdout to from_rrdtool_fd[1] */
839 close(STDOUT_FILENO);
840 dup2(from_exec_fds[1], STDOUT_FILENO);
841 close(from_exec_fds[1]);
842 /* not needed */
843 close(from_exec_fds[0]);
845 /* close stdin */
846 close(STDIN_FILENO);
848 execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
850 log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
852 /* */
853 SEGFAULT();
854 break;
856 case -1:
857 /* error */
858 log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
859 break;
860 default: {
861 /* father */
862 int status;
863 ssize_t r;
864 int was_interrupted = 0;
866 close(from_exec_fds[1]);
868 /* wait for the client to end */
871 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
873 do {
874 if (-1 == waitpid(pid, &status, 0)) {
875 if (errno == EINTR) {
876 was_interrupted++;
877 } else {
878 was_interrupted = 0;
879 log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
881 } else if (WIFEXITED(status)) {
882 int toread;
883 /* read everything from client and paste it into the output */
884 was_interrupted = 0;
886 while(1) {
887 if (ioctl(from_exec_fds[0], FIONREAD, &toread)) {
888 log_error_write(srv, __FILE__, __LINE__, "s",
889 "unexpected end-of-file (perhaps the ssi-exec process died)");
890 return -1;
893 if (toread > 0) {
894 char *mem;
895 size_t mem_len;
897 chunkqueue_get_memory(con->write_queue, &mem, &mem_len, 0, toread);
898 r = read(from_exec_fds[0], mem, mem_len);
899 chunkqueue_use_memory(con->write_queue, r > 0 ? r : 0);
901 if (r < 0) break; /* read failed */
902 } else {
903 break;
906 } else {
907 was_interrupted = 0;
908 log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
910 } while (was_interrupted > 0 && was_interrupted < 4); /* if waitpid() gets interrupted, retry, but max 4 times */
912 close(from_exec_fds[0]);
914 break;
917 #else
919 return -1;
920 #endif
922 break;
924 case SSI_IF: {
925 const char *expr = NULL;
927 for (i = 2; i < n; i += 2) {
928 if (0 == strcmp(l[i], "expr")) {
929 expr = l[i+1];
930 } else {
931 log_error_write(srv, __FILE__, __LINE__, "sss",
932 "ssi: unknown attribute for ",
933 l[1], l[i]);
937 if (!expr) {
938 log_error_write(srv, __FILE__, __LINE__, "sss",
939 "ssi: ",
940 l[1], "expr missing");
941 break;
944 if ((!p->if_is_false) &&
945 ((p->if_is_false_level == 0) ||
946 (p->if_level < p->if_is_false_level))) {
947 switch (ssi_eval_expr(srv, con, p, expr)) {
948 case -1:
949 case 0:
950 p->if_is_false = 1;
951 p->if_is_false_level = p->if_level;
952 break;
953 case 1:
954 p->if_is_false = 0;
955 break;
959 p->if_level++;
961 break;
963 case SSI_ELSE:
964 p->if_level--;
966 if (p->if_is_false) {
967 if ((p->if_level == p->if_is_false_level) &&
968 (p->if_is_false_endif == 0)) {
969 p->if_is_false = 0;
971 } else {
972 p->if_is_false = 1;
974 p->if_is_false_level = p->if_level;
976 p->if_level++;
978 break;
979 case SSI_ELIF: {
980 const char *expr = NULL;
981 for (i = 2; i < n; i += 2) {
982 if (0 == strcmp(l[i], "expr")) {
983 expr = l[i+1];
984 } else {
985 log_error_write(srv, __FILE__, __LINE__, "sss",
986 "ssi: unknown attribute for ",
987 l[1], l[i]);
991 if (!expr) {
992 log_error_write(srv, __FILE__, __LINE__, "sss",
993 "ssi: ",
994 l[1], "expr missing");
995 break;
998 p->if_level--;
1000 if (p->if_level == p->if_is_false_level) {
1001 if ((p->if_is_false) &&
1002 (p->if_is_false_endif == 0)) {
1003 switch (ssi_eval_expr(srv, con, p, expr)) {
1004 case -1:
1005 case 0:
1006 p->if_is_false = 1;
1007 p->if_is_false_level = p->if_level;
1008 break;
1009 case 1:
1010 p->if_is_false = 0;
1011 break;
1013 } else {
1014 p->if_is_false = 1;
1015 p->if_is_false_level = p->if_level;
1016 p->if_is_false_endif = 1;
1020 p->if_level++;
1022 break;
1024 case SSI_ENDIF:
1025 p->if_level--;
1027 if (p->if_level == p->if_is_false_level) {
1028 p->if_is_false = 0;
1029 p->if_is_false_endif = 0;
1032 break;
1033 default:
1034 log_error_write(srv, __FILE__, __LINE__, "ss",
1035 "ssi: unknown ssi-command:",
1036 l[1]);
1037 break;
1040 return 0;
1044 static int mod_ssi_parse_ssi_stmt_value(const char * const s, const int len) {
1045 int n;
1046 const int c = (s[0] == '"' ? '"' : s[0] == '\'' ? '\'' : 0);
1047 if (0 != c) {
1048 for (n = 1; n < len; ++n) {
1049 if (s[n] == c) return n+1;
1050 if (s[n] == '\\') {
1051 if (n+1 == len) return 0; /* invalid */
1052 ++n;
1055 return 0; /* invalid */
1056 } else {
1057 for (n = 0; n < len; ++n) {
1058 if (isspace(s[n])) return n;
1059 if (s[n] == '\\') {
1060 if (n+1 == len) return 0; /* invalid */
1061 ++n;
1064 return n;
1068 static int mod_ssi_parse_ssi_stmt_offlen(int o[10], const char * const s, const int len) {
1071 * <!--#element attribute=value attribute=value ... -->
1074 /* s must begin "<!--#" and must end with "-->" */
1075 int n = 5;
1076 o[0] = n;
1077 for (; light_isalpha(s[n]); ++n) ; /*(n = 5 to begin after "<!--#")*/
1078 o[1] = n - o[0];
1079 if (0 == o[1]) return -1; /* empty token */
1081 if (n+3 == len) return 2; /* token only; no params */
1082 if (!isspace(s[n])) return -1;
1083 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1084 if (n+3 == len) return 2; /* token only; no params */
1086 o[2] = n;
1087 for (; light_isalpha(s[n]); ++n) ;
1088 o[3] = n - o[2];
1089 if (0 == o[3] || s[n++] != '=') return -1;
1091 o[4] = n;
1092 o[5] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1093 if (0 == o[5]) return -1; /* empty or invalid token */
1094 n += o[5];
1096 if (n+3 == len) return 6; /* token and one param */
1097 if (!isspace(s[n])) return -1;
1098 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1099 if (n+3 == len) return 6; /* token and one param */
1101 o[6] = n;
1102 for (; light_isalpha(s[n]); ++n) ;
1103 o[7] = n - o[6];
1104 if (0 == o[7] || s[n++] != '=') return -1;
1106 o[8] = n;
1107 o[9] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1108 if (0 == o[9]) return -1; /* empty or invalid token */
1109 n += o[9];
1111 if (n+3 == len) return 10; /* token and two params */
1112 if (!isspace(s[n])) return -1;
1113 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1114 if (n+3 == len) return 10; /* token and two params */
1115 return -1;
1118 static void mod_ssi_parse_ssi_stmt(server *srv, connection *con, plugin_data *p, char *s, int len, struct stat *st) {
1121 * <!--#element attribute=value attribute=value ... -->
1124 int o[10];
1125 int m;
1126 const int n = mod_ssi_parse_ssi_stmt_offlen(o, s, len);
1127 char *l[6] = { s, NULL, NULL, NULL, NULL, NULL };
1128 if (-1 == n) {
1129 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1130 chunkqueue_append_mem(con->write_queue, s, len); /* append stmt as-is */
1131 return;
1134 #if 0
1135 /* dup s and then modify s */
1136 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1137 l[0] = malloc((size_t)(len+1));
1138 memcpy(l[0], s, (size_t)len);
1139 (l[0])[len] = '\0';
1140 #endif
1142 /* modify s in-place to split string into arg tokens */
1143 for (m = 0; m < n; m += 2) {
1144 char *ptr = s+o[m];
1145 switch (*ptr) {
1146 case '"':
1147 case '\'': (++ptr)[o[m+1]-2] = '\0'; break;
1148 default: ptr[o[m+1]] = '\0'; break;
1150 l[1+(m>>1)] = ptr;
1151 if (m == 4 || m == 8) {
1152 /* XXX: removing '\\' escapes from param value would be
1153 * the right thing to do, but would potentially change
1154 * current behavior, e.g. <!--#exec cmd=... --> */
1158 process_ssi_stmt(srv, con, p, (const char **)l, 1+(n>>1), st);
1160 #if 0
1161 free(l[0]);
1162 #endif
1165 static int mod_ssi_stmt_len(const char *s, const int len) {
1166 /* s must begin "<!--#" */
1167 int n, sq = 0, dq = 0, bs = 0;
1168 for (n = 5; n < len; ++n) { /*(n = 5 to begin after "<!--#")*/
1169 switch (s[n]) {
1170 default:
1171 break;
1172 case '-':
1173 if (!sq && !dq && n+2 < len && s[n+1] == '-' && s[n+2] == '>') return n+3; /* found end of stmt */
1174 break;
1175 case '"':
1176 if (!sq && (!dq || !bs)) dq = !dq;
1177 break;
1178 case '\'':
1179 if (!dq && (!sq || !bs)) sq = !sq;
1180 break;
1181 case '\\':
1182 if (sq || dq) bs = !bs;
1183 break;
1186 return 0; /* incomplete directive "<!--#...-->" */
1189 static void mod_ssi_read_fd(server *srv, connection *con, plugin_data *p, int fd, struct stat *st) {
1190 ssize_t rd;
1191 size_t offset, pretag;
1192 char buf[8192];
1194 offset = 0;
1195 pretag = 0;
1196 while (0 < (rd = read(fd, buf+offset, sizeof(buf)-offset))) {
1197 char *s;
1198 size_t prelen = 0, len;
1199 offset += (size_t)rd;
1200 for (; (s = memchr(buf+prelen, '<', offset-prelen)); ++prelen) {
1201 prelen = s - buf;
1202 if (prelen + 5 <= offset) { /*("<!--#" is 5 chars)*/
1203 if (0 != memcmp(s+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1205 if (prelen - pretag && !p->if_is_false) {
1206 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1209 len = mod_ssi_stmt_len(buf+prelen, offset-prelen);
1210 if (len) { /* num of chars to be consumed */
1211 mod_ssi_parse_ssi_stmt(srv, con, p, buf+prelen, len, st);
1212 prelen += (len - 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1213 pretag = prelen + 1;
1214 if (pretag == offset) {
1215 offset = pretag = 0;
1216 break;
1218 } else if (0 == prelen && offset == sizeof(buf)) { /*(full buf)*/
1219 /* SSI statement is way too long
1220 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1221 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1222 /* check if buf ends with "-" or "--" which might be part of "-->"
1223 * (buf contains at least 5 chars for "<!--#") */
1224 if (buf[offset-2] == '-' && buf[offset-1] == '-') {
1225 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("--"));
1226 } else if (buf[offset-1] == '-') {
1227 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("-"));
1229 offset = pretag = 0;
1230 break;
1231 } else { /* incomplete directive "<!--#...-->" */
1232 memmove(buf, buf+prelen, (offset -= prelen));
1233 pretag = 0;
1234 break;
1236 } else if (prelen + 1 == offset || 0 == memcmp(s+1, "!--", offset - prelen - 1)) {
1237 if (prelen - pretag && !p->if_is_false) {
1238 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1240 memcpy(buf, buf+prelen, (offset -= prelen));
1241 pretag = 0;
1242 break;
1244 /* loop to look for next '<' */
1246 if (offset == sizeof(buf)) {
1247 if (!p->if_is_false) {
1248 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1250 offset = pretag = 0;
1254 if (0 != rd) {
1255 log_error_write(srv, __FILE__, __LINE__, "SsB", "read(): ", strerror(errno), con->physical.path);
1258 if (offset - pretag) {
1259 /* copy remaining data in buf */
1260 if (!p->if_is_false) {
1261 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1267 /* don't want to block when open()ing a fifo */
1268 #if defined(O_NONBLOCK)
1269 # define FIFO_NONBLOCK O_NONBLOCK
1270 #else
1271 # define FIFO_NONBLOCK 0
1272 #endif
1274 static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) {
1275 int fd;
1276 struct stat st;
1278 /* get a stream to the file */
1280 array_reset(p->ssi_vars);
1281 array_reset(p->ssi_cgi_env);
1282 buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1283 p->sizefmt = 0;
1284 build_ssi_cgi_vars(srv, con, p);
1285 p->if_is_false = 0;
1287 /* Reset the modified time of included files */
1288 include_file_last_mtime = 0;
1290 if (-1 == (fd = open(con->physical.path->ptr, O_RDONLY | FIFO_NONBLOCK))) {
1291 log_error_write(srv, __FILE__, __LINE__, "sb",
1292 "open: ", con->physical.path);
1293 return -1;
1296 if (0 != fstat(fd, &st)) {
1297 log_error_write(srv, __FILE__, __LINE__, "SB", "fstat failed: ", con->physical.path);
1298 close(fd);
1299 return -1;
1302 mod_ssi_read_fd(srv, con, p, fd, &st);
1304 close(fd);
1305 con->file_started = 1;
1306 con->file_finished = 1;
1307 con->mode = p->id;
1309 if (buffer_string_is_empty(p->conf.content_type)) {
1310 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1311 } else {
1312 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type));
1315 if (p->conf.conditional_requests) {
1316 /* Generate "ETag" & "Last-Modified" headers */
1317 buffer *mtime = NULL;
1319 /* use most recently modified include file for ETag and Last-Modified */
1320 if (st.st_mtime < include_file_last_mtime)
1321 st.st_mtime = include_file_last_mtime;
1323 etag_create(con->physical.etag, &st, con->etag_flags);
1324 response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
1326 mtime = strftime_cache_get(srv, st.st_mtime);
1327 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
1329 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
1330 /* ok, the client already has our content,
1331 * no need to send it again */
1333 chunkqueue_reset(con->write_queue);
1337 /* Reset the modified time of included files */
1338 include_file_last_mtime = 0;
1340 /* reset physical.path */
1341 buffer_reset(con->physical.path);
1343 return 0;
1346 #define PATCH(x) \
1347 p->conf.x = s->x;
1348 static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
1349 size_t i, j;
1350 plugin_config *s = p->config_storage[0];
1352 PATCH(ssi_extension);
1353 PATCH(content_type);
1354 PATCH(conditional_requests);
1355 PATCH(ssi_exec);
1357 /* skip the first, the global context */
1358 for (i = 1; i < srv->config_context->used; i++) {
1359 data_config *dc = (data_config *)srv->config_context->data[i];
1360 s = p->config_storage[i];
1362 /* condition didn't match */
1363 if (!config_check_cond(srv, con, dc)) continue;
1365 /* merge config */
1366 for (j = 0; j < dc->value->used; j++) {
1367 data_unset *du = dc->value->data[j];
1369 if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
1370 PATCH(ssi_extension);
1371 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) {
1372 PATCH(content_type);
1373 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.conditional-requests"))) {
1374 PATCH(conditional_requests);
1375 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.exec"))) {
1376 PATCH(ssi_exec);
1381 return 0;
1383 #undef PATCH
1385 URIHANDLER_FUNC(mod_ssi_physical_path) {
1386 plugin_data *p = p_d;
1387 size_t k;
1389 if (con->mode != DIRECT) return HANDLER_GO_ON;
1391 if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
1393 mod_ssi_patch_connection(srv, con, p);
1395 for (k = 0; k < p->conf.ssi_extension->used; k++) {
1396 data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
1398 if (buffer_is_empty(ds->value)) continue;
1400 if (buffer_is_equal_right_len(con->physical.path, ds->value, buffer_string_length(ds->value))) {
1401 /* handle ssi-request */
1403 if (mod_ssi_handle_request(srv, con, p)) {
1404 /* on error */
1405 con->http_status = 500;
1406 con->mode = DIRECT;
1409 return HANDLER_FINISHED;
1413 /* not found */
1414 return HANDLER_GO_ON;
1417 /* this function is called at dlopen() time and inits the callbacks */
1419 int mod_ssi_plugin_init(plugin *p);
1420 int mod_ssi_plugin_init(plugin *p) {
1421 p->version = LIGHTTPD_VERSION_ID;
1422 p->name = buffer_init_string("ssi");
1424 p->init = mod_ssi_init;
1425 p->handle_subrequest_start = mod_ssi_physical_path;
1426 p->set_defaults = mod_ssi_set_defaults;
1427 p->cleanup = mod_ssi_free;
1429 p->data = NULL;
1431 return 0;