1 #include "base.h"
2 #include "log.h"
3 #include "buffer.h"
4 #include "stat_cache.h"
5
6 #include "plugin.h"
7 #include "stream.h"
8
9 #include "response.h"
10
11 #include "mod_ssi.h"
12
13 #include "inet_ntop_cache.h"
14
15 #include "sys-socket.h"
16
17 #include <sys/types.h>
18
19 #include <ctype.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <time.h>
25 #include <unistd.h>
26
27 #ifdef HAVE_PWD_H
28 # include <pwd.h>
29 #endif
30
31 #ifdef HAVE_FORK
32 # include <sys/wait.h>
33 #endif
34
35 #ifdef HAVE_SYS_FILIO_H
36 # include <sys/filio.h>
37 #endif
38
39 #include "etag.h"
40 #include "version.h"
41
42 /* The newest modified time of included files for include statement */
43 static volatile time_t include_file_last_mtime = 0;
44
45 /* init the plugin data */
INIT_FUNC(mod_ssi_init)46 INIT_FUNC(mod_ssi_init) {
47 plugin_data *p;
48
49 p = calloc(1, sizeof(*p));
50
51 p->timefmt = buffer_init();
52 p->stat_fn = buffer_init();
53
54 p->ssi_vars = array_init();
55 p->ssi_cgi_env = array_init();
56
57 return p;
58 }
59
60 /* detroy the plugin data */
FREE_FUNC(mod_ssi_free)61 FREE_FUNC(mod_ssi_free) {
62 plugin_data *p = p_d;
63 UNUSED(srv);
64
65 if (!p) return HANDLER_GO_ON;
66
67 if (p->config_storage) {
68 size_t i;
69 for (i = 0; i < srv->config_context->used; i++) {
70 plugin_config *s = p->config_storage[i];
71
72 array_free(s->ssi_extension);
73 buffer_free(s->content_type);
74
75 free(s);
76 }
77 free(p->config_storage);
78 }
79
80 array_free(p->ssi_vars);
81 array_free(p->ssi_cgi_env);
82 #ifdef HAVE_PCRE_H
83 pcre_free(p->ssi_regex);
84 #endif
85 buffer_free(p->timefmt);
86 buffer_free(p->stat_fn);
87
88 free(p);
89
90 return HANDLER_GO_ON;
91 }
92
93 /* handle plugin config and check values */
94
SETDEFAULTS_FUNC(mod_ssi_set_defaults)95 SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
96 plugin_data *p = p_d;
97 size_t i = 0;
98 #ifdef HAVE_PCRE_H
99 const char *errptr;
100 int erroff;
101 #endif
102
103 config_values_t cv[] = {
104 { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
105 { "ssi.content-type", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
106 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
107 };
108
109 if (!p) return HANDLER_ERROR;
110
111 p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
112
113 for (i = 0; i < srv->config_context->used; i++) {
114 plugin_config *s;
115
116 s = calloc(1, sizeof(plugin_config));
117 s->ssi_extension = array_init();
118 s->content_type = buffer_init();
119
120 cv[0].destination = s->ssi_extension;
121 cv[1].destination = s->content_type;
122
123 p->config_storage[i] = s;
124
125 if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
126 return HANDLER_ERROR;
127 }
128 }
129
130 #ifdef HAVE_PCRE_H
131 /* allow 2 params */
132 if (NULL == (p->ssi_regex = pcre_compile("<!--#([a-z]+)\\s+(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?-->", 0, &errptr, &erroff, NULL))) {
133 log_error_write(srv, __FILE__, __LINE__, "sds",
134 "ssi: pcre ",
135 erroff, errptr);
136 return HANDLER_ERROR;
137 }
138 #else
139 log_error_write(srv, __FILE__, __LINE__, "s",
140 "mod_ssi: pcre support is missing, please recompile with pcre support or remove mod_ssi from the list of modules");
141 return HANDLER_ERROR;
142 #endif
143
144 return HANDLER_GO_ON;
145 }
146
ssi_env_add(array * env,const char * key,const char * val)147 static int ssi_env_add(array *env, const char *key, const char *val) {
148 data_string *ds;
149
150 if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
151 ds = data_string_init();
152 }
153 buffer_copy_string(ds->key, key);
154 buffer_copy_string(ds->value, val);
155
156 array_insert_unique(env, (data_unset *)ds);
157
158 return 0;
159 }
160
161 /**
162 *
163 * the next two functions are take from fcgi.c
164 *
165 */
166
ssi_env_add_request_headers(server * srv,connection * con,plugin_data * p)167 static int ssi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
168 size_t i;
169
170 for (i = 0; i < con->request.headers->used; i++) {
171 data_string *ds;
172
173 ds = (data_string *)con->request.headers->data[i];
174
175 if (ds->value->used && ds->key->used) {
176 size_t j;
177 buffer_reset(srv->tmp_buf);
178
179 /* don't forward the Authorization: Header */
180 if (0 == strcasecmp(ds->key->ptr, "AUTHORIZATION")) {
181 continue;
182 }
183
184 if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
185 buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("HTTP_"));
186 srv->tmp_buf->used--;
187 }
188
189 buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
190 for (j = 0; j < ds->key->used - 1; j++) {
191 char c = '_';
192 if (light_isalpha(ds->key->ptr[j])) {
193 /* upper-case */
194 c = ds->key->ptr[j] & ~32;
195 } else if (light_isdigit(ds->key->ptr[j])) {
196 /* copy */
197 c = ds->key->ptr[j];
198 }
199 srv->tmp_buf->ptr[srv->tmp_buf->used++] = c;
200 }
201 srv->tmp_buf->ptr[srv->tmp_buf->used] = '\0';
202
203 ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
204 }
205 }
206
207 for (i = 0; i < con->environment->used; i++) {
208 data_string *ds;
209
210 ds = (data_string *)con->environment->data[i];
211
212 if (ds->value->used && ds->key->used) {
213 size_t j;
214
215 buffer_reset(srv->tmp_buf);
216 buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
217
218 for (j = 0; j < ds->key->used - 1; j++) {
219 char c = '_';
220 if (light_isalpha(ds->key->ptr[j])) {
221 /* upper-case */
222 c = ds->key->ptr[j] & ~32;
223 } else if (light_isdigit(ds->key->ptr[j])) {
224 /* copy */
225 c = ds->key->ptr[j];
226 }
227 srv->tmp_buf->ptr[srv->tmp_buf->used++] = c;
228 }
229 srv->tmp_buf->ptr[srv->tmp_buf->used] = '\0';
230
231 ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
232 }
233 }
234
235 return 0;
236 }
237
build_ssi_cgi_vars(server * srv,connection * con,plugin_data * p)238 static int build_ssi_cgi_vars(server *srv, connection *con, plugin_data *p) {
239 char buf[32];
240
241 server_socket *srv_sock = con->srv_socket;
242
243 #ifdef HAVE_IPV6
244 char b2[INET6_ADDRSTRLEN + 1];
245 #endif
246
247 #define CONST_STRING(x) \
248 x
249
250 array_reset(p->ssi_cgi_env);
251
252 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_DESC);
253 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_NAME"),
254 #ifdef HAVE_IPV6
255 inet_ntop(srv_sock->addr.plain.sa_family,
256 srv_sock->addr.plain.sa_family == AF_INET6 ?
257 (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
258 (const void *) &(srv_sock->addr.ipv4.sin_addr),
259 b2, sizeof(b2)-1)
260 #else
261 inet_ntoa(srv_sock->addr.ipv4.sin_addr)
262 #endif
263 );
264 ssi_env_add(p->ssi_cgi_env, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1");
265
266 LI_ltostr(buf,
267 #ifdef HAVE_IPV6
268 ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
269 #else
270 ntohs(srv_sock->addr.ipv4.sin_port)
271 #endif
272 );
273
274 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PORT"), buf);
275
276 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_ADDR"),
277 inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
278
279 if (con->authed_user->used) {
280 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_USER"),
281 con->authed_user->ptr);
282 }
283
284 if (con->request.content_length > 0) {
285 /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
286
287 /* request.content_length < SSIZE_MAX, see request.c */
288 LI_ltostr(buf, con->request.content_length);
289 ssi_env_add(p->ssi_cgi_env, CONST_STRING("CONTENT_LENGTH"), buf);
290 }
291
292 /*
293 * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
294 * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
295 * (6.1.14, 6.1.6, 6.1.7)
296 */
297
298 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_NAME"), con->uri.path->ptr);
299 ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), "");
300
301 /*
302 * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
303 * http://www.php.net/manual/en/reserved.variables.php
304 * treatment of PATH_TRANSLATED is different from the one of CGI specs.
305 * TODO: this code should be checked against cgi.fix_pathinfo php
306 * parameter.
307 */
308
309 if (con->request.pathinfo->used) {
310 ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), con->request.pathinfo->ptr);
311 }
312
313 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_FILENAME"), con->physical.path->ptr);
314 ssi_env_add(p->ssi_cgi_env, CONST_STRING("DOCUMENT_ROOT"), con->physical.doc_root->ptr);
315
316 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_URI"), con->request.uri->ptr);
317 ssi_env_add(p->ssi_cgi_env, CONST_STRING("QUERY_STRING"), con->uri.query->used ? con->uri.query->ptr : "");
318 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con->request.http_method));
319 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REDIRECT_STATUS"), "200");
320 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version));
321
322 ssi_env_add_request_headers(srv, con, p);
323
324 return 0;
325 }
326
process_ssi_stmt(server * srv,connection * con,plugin_data * p,const char ** l,size_t n)327 static int process_ssi_stmt(server *srv, connection *con, plugin_data *p,
328 const char **l, size_t n) {
329 size_t i, ssicmd = 0;
330 char buf[255];
331 buffer *b = NULL;
332
333 struct {
334 const char *var;
335 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
336 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
337 SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
338 } ssicmds[] = {
339 { "echo", SSI_ECHO },
340 { "include", SSI_INCLUDE },
341 { "flastmod", SSI_FLASTMOD },
342 { "fsize", SSI_FSIZE },
343 { "config", SSI_CONFIG },
344 { "printenv", SSI_PRINTENV },
345 { "set", SSI_SET },
346 { "if", SSI_IF },
347 { "elif", SSI_ELIF },
348 { "endif", SSI_ENDIF },
349 { "else", SSI_ELSE },
350 { "exec", SSI_EXEC },
351
352 { NULL, SSI_UNSET }
353 };
354
355 for (i = 0; ssicmds[i].var; i++) {
356 if (0 == strcmp(l[1], ssicmds[i].var)) {
357 ssicmd = ssicmds[i].type;
358 break;
359 }
360 }
361
362 switch(ssicmd) {
363 case SSI_ECHO: {
364 /* echo */
365 int var = 0;
366 /* int enc = 0; */
367 const char *var_val = NULL;
368 stat_cache_entry *sce = NULL;
369
370 struct {
371 const char *var;
372 enum { SSI_ECHO_UNSET, SSI_ECHO_DATE_GMT, SSI_ECHO_DATE_LOCAL, SSI_ECHO_DOCUMENT_NAME, SSI_ECHO_DOCUMENT_URI,
373 SSI_ECHO_LAST_MODIFIED, SSI_ECHO_USER_NAME } type;
374 } echovars[] = {
375 { "DATE_GMT", SSI_ECHO_DATE_GMT },
376 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
377 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
378 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
379 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
380 { "USER_NAME", SSI_ECHO_USER_NAME },
381
382 { NULL, SSI_ECHO_UNSET }
383 };
384
385 /*
386 struct {
387 const char *var;
388 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
389 } encvars[] = {
390 { "url", SSI_ENC_URL },
391 { "none", SSI_ENC_NONE },
392 { "entity", SSI_ENC_ENTITY },
393
394 { NULL, SSI_ENC_UNSET }
395 };
396 */
397
398 for (i = 2; i < n; i += 2) {
399 if (0 == strcmp(l[i], "var")) {
400 int j;
401
402 var_val = l[i+1];
403
404 for (j = 0; echovars[j].var; j++) {
405 if (0 == strcmp(l[i+1], echovars[j].var)) {
406 var = echovars[j].type;
407 break;
408 }
409 }
410 } else if (0 == strcmp(l[i], "encoding")) {
411 /*
412 int j;
413
414 for (j = 0; encvars[j].var; j++) {
415 if (0 == strcmp(l[i+1], encvars[j].var)) {
416 enc = encvars[j].type;
417 break;
418 }
419 }
420 */
421 } else {
422 log_error_write(srv, __FILE__, __LINE__, "sss",
423 "ssi: unknow attribute for ",
424 l[1], l[i]);
425 }
426 }
427
428 if (p->if_is_false) break;
429
430 if (!var_val) {
431 log_error_write(srv, __FILE__, __LINE__, "sss",
432 "ssi: ",
433 l[1], "var is missing");
434 break;
435 }
436
437 stat_cache_get_entry(srv, con, con->physical.path, &sce);
438
439 switch(var) {
440 case SSI_ECHO_USER_NAME: {
441 struct passwd *pw;
442
443 b = chunkqueue_get_append_buffer(con->write_queue);
444 #ifdef HAVE_PWD_H
445 if (NULL == (pw = getpwuid(sce->st.st_uid))) {
446 buffer_copy_long(b, sce->st.st_uid);
447 } else {
448 buffer_copy_string(b, pw->pw_name);
449 }
450 #else
451 buffer_copy_long(b, sce->st.st_uid);
452 #endif
453 break;
454 }
455 case SSI_ECHO_LAST_MODIFIED: {
456 time_t t = sce->st.st_mtime;
457
458 b = chunkqueue_get_append_buffer(con->write_queue);
459 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
460 buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
461 } else {
462 buffer_copy_string(b, buf);
463 }
464 break;
465 }
466 case SSI_ECHO_DATE_LOCAL: {
467 time_t t = time(NULL);
468
469 b = chunkqueue_get_append_buffer(con->write_queue);
470 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
471 buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
472 } else {
473 buffer_copy_string(b, buf);
474 }
475 break;
476 }
477 case SSI_ECHO_DATE_GMT: {
478 time_t t = time(NULL);
479
480 b = chunkqueue_get_append_buffer(con->write_queue);
481 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
482 buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
483 } else {
484 buffer_copy_string(b, buf);
485 }
486 break;
487 }
488 case SSI_ECHO_DOCUMENT_NAME: {
489 char *sl;
490
491 b = chunkqueue_get_append_buffer(con->write_queue);
492 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
493 buffer_copy_string_buffer(b, con->physical.path);
494 } else {
495 buffer_copy_string(b, sl + 1);
496 }
497 break;
498 }
499 case SSI_ECHO_DOCUMENT_URI: {
500 b = chunkqueue_get_append_buffer(con->write_queue);
501 buffer_copy_string_buffer(b, con->uri.path);
502 break;
503 }
504 default: {
505 data_string *ds;
506 /* check if it is a cgi-var */
507
508 b = chunkqueue_get_append_buffer(con->write_queue);
509
510 if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val))) {
511 buffer_copy_string_buffer(b, ds->value);
512 } else {
513 buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
514 }
515
516 break;
517 }
518 }
519 break;
520 }
521 case SSI_INCLUDE:
522 case SSI_FLASTMOD:
523 case SSI_FSIZE: {
524 const char * file_path = NULL, *virt_path = NULL;
525 struct stat st;
526 char *sl;
527
528 for (i = 2; i < n; i += 2) {
529 if (0 == strcmp(l[i], "file")) {
530 file_path = l[i+1];
531 } else if (0 == strcmp(l[i], "virtual")) {
532 virt_path = l[i+1];
533 } else {
534 log_error_write(srv, __FILE__, __LINE__, "sss",
535 "ssi: unknow attribute for ",
536 l[1], l[i]);
537 }
538 }
539
540 if (!file_path && !virt_path) {
541 log_error_write(srv, __FILE__, __LINE__, "sss",
542 "ssi: ",
543 l[1], "file or virtual are missing");
544 break;
545 }
546
547 if (file_path && virt_path) {
548 log_error_write(srv, __FILE__, __LINE__, "sss",
549 "ssi: ",
550 l[1], "only one of file and virtual is allowed here");
551 break;
552 }
553
554
555 if (p->if_is_false) break;
556
557 if (file_path) {
558 /* current doc-root */
559 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
560 buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
561 } else {
562 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
563 }
564
565 buffer_copy_string(srv->tmp_buf, file_path);
566 buffer_urldecode_path(srv->tmp_buf);
567 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
568 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
569 } else {
570 /* virtual */
571
572 if (virt_path[0] == '/') {
573 buffer_copy_string(p->stat_fn, virt_path);
574 } else {
575 /* there is always a / */
576 sl = strrchr(con->uri.path->ptr, '/');
577
578 buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
579 buffer_append_string(p->stat_fn, virt_path);
580 }
581
582 buffer_urldecode_path(p->stat_fn);
583 buffer_path_simplify(srv->tmp_buf, p->stat_fn);
584
585 /* we have an uri */
586
587 buffer_copy_string_buffer(p->stat_fn, con->physical.doc_root);
588 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
589 }
590
591 if (0 == stat(p->stat_fn->ptr, &st)) {
592 time_t t = st.st_mtime;
593
594 switch (ssicmd) {
595 case SSI_FSIZE:
596 b = chunkqueue_get_append_buffer(con->write_queue);
597 if (p->sizefmt) {
598 int j = 0;
599 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
600
601 off_t s = st.st_size;
602
603 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
604
605 buffer_copy_off_t(b, s);
606 buffer_append_string(b, abr[j]);
607 } else {
608 buffer_copy_off_t(b, st.st_size);
609 }
610 break;
611 case SSI_FLASTMOD:
612 b = chunkqueue_get_append_buffer(con->write_queue);
613 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
614 buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
615 } else {
616 buffer_copy_string(b, buf);
617 }
618 break;
619 case SSI_INCLUDE:
620 chunkqueue_append_file(con->write_queue, p->stat_fn, 0, st.st_size);
621
622 /* Keep the newest mtime of included files */
623 if (st.st_mtime > include_file_last_mtime)
624 include_file_last_mtime = st.st_mtime;
625
626 break;
627 }
628 } else {
629 log_error_write(srv, __FILE__, __LINE__, "sbs",
630 "ssi: stating failed ",
631 p->stat_fn, strerror(errno));
632 }
633 break;
634 }
635 case SSI_SET: {
636 const char *key = NULL, *val = NULL;
637 for (i = 2; i < n; i += 2) {
638 if (0 == strcmp(l[i], "var")) {
639 key = l[i+1];
640 } else if (0 == strcmp(l[i], "value")) {
641 val = l[i+1];
642 } else {
643 log_error_write(srv, __FILE__, __LINE__, "sss",
644 "ssi: unknow attribute for ",
645 l[1], l[i]);
646 }
647 }
648
649 if (p->if_is_false) break;
650
651 if (key && val) {
652 data_string *ds;
653
654 if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
655 ds = data_string_init();
656 }
657 buffer_copy_string(ds->key, key);
658 buffer_copy_string(ds->value, val);
659
660 array_insert_unique(p->ssi_vars, (data_unset *)ds);
661 } else {
662 log_error_write(srv, __FILE__, __LINE__, "sss",
663 "ssi: var and value have to be set in",
664 l[0], l[1]);
665 }
666 break;
667 }
668 case SSI_CONFIG:
669 if (p->if_is_false) break;
670
671 for (i = 2; i < n; i += 2) {
672 if (0 == strcmp(l[i], "timefmt")) {
673 buffer_copy_string(p->timefmt, l[i+1]);
674 } else if (0 == strcmp(l[i], "sizefmt")) {
675 if (0 == strcmp(l[i+1], "abbrev")) {
676 p->sizefmt = 1;
677 } else if (0 == strcmp(l[i+1], "abbrev")) {
678 p->sizefmt = 0;
679 } else {
680 log_error_write(srv, __FILE__, __LINE__, "sssss",
681 "ssi: unknow value for attribute '",
682 l[i],
683 "' for ",
684 l[1], l[i+1]);
685 }
686 } else {
687 log_error_write(srv, __FILE__, __LINE__, "sss",
688 "ssi: unknow attribute for ",
689 l[1], l[i]);
690 }
691 }
692 break;
693 case SSI_PRINTENV:
694 if (p->if_is_false) break;
695
696 b = chunkqueue_get_append_buffer(con->write_queue);
697 for (i = 0; i < p->ssi_vars->used; i++) {
698 data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
699
700 buffer_append_string_buffer(b, ds->key);
701 buffer_append_string_len(b, CONST_STR_LEN("="));
702 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
703 buffer_append_string_len(b, CONST_STR_LEN("\n"));
704 }
705 for (i = 0; i < p->ssi_cgi_env->used; i++) {
706 data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]];
707
708 buffer_append_string_buffer(b, ds->key);
709 buffer_append_string_len(b, CONST_STR_LEN("="));
710 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
711 buffer_append_string_len(b, CONST_STR_LEN("\n"));
712 }
713
714 break;
715 case SSI_EXEC: {
716 const char *cmd = NULL;
717 pid_t pid;
718 int from_exec_fds[2];
719
720 for (i = 2; i < n; i += 2) {
721 if (0 == strcmp(l[i], "cmd")) {
722 cmd = l[i+1];
723 } else {
724 log_error_write(srv, __FILE__, __LINE__, "sss",
725 "ssi: unknow attribute for ",
726 l[1], l[i]);
727 }
728 }
729
730 if (p->if_is_false) break;
731
732 /* create a return pipe and send output to the html-page
733 *
734 * as exec is assumed evil it is implemented synchronously
735 */
736
737 if (!cmd) break;
738 #ifdef HAVE_FORK
739 if (pipe(from_exec_fds)) {
740 log_error_write(srv, __FILE__, __LINE__, "ss",
741 "pipe failed: ", strerror(errno));
742 return -1;
743 }
744
745 /* fork, execve */
746 switch (pid = fork()) {
747 case 0: {
748 /* move stdout to from_rrdtool_fd[1] */
749 close(STDOUT_FILENO);
750 dup2(from_exec_fds[1], STDOUT_FILENO);
751 close(from_exec_fds[1]);
752 /* not needed */
753 close(from_exec_fds[0]);
754
755 /* close stdin */
756 close(STDIN_FILENO);
757
758 execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
759
760 log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
761
762 /* */
763 SEGFAULT();
764 break;
765 }
766 case -1:
767 /* error */
768 log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
769 break;
770 default: {
771 /* father */
772 int status;
773 ssize_t r;
774 int was_interrupted = 0;
775
776 close(from_exec_fds[1]);
777
778 /* wait for the client to end */
779
780 /*
781 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
782 */
783 do {
784 if (-1 == waitpid(pid, &status, 0)) {
785 if (errno == EINTR) {
786 was_interrupted++;
787 } else {
788 was_interrupted = 0;
789 log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
790 }
791 } else if (WIFEXITED(status)) {
792 int toread;
793 /* read everything from client and paste it into the output */
794 was_interrupted = 0;
795
796 while(1) {
797 if (ioctl(from_exec_fds[0], FIONREAD, &toread)) {
798 log_error_write(srv, __FILE__, __LINE__, "s",
799 "unexpected end-of-file (perhaps the ssi-exec process died)");
800 return -1;
801 }
802
803 if (toread > 0) {
804 b = chunkqueue_get_append_buffer(con->write_queue);
805
806 buffer_prepare_copy(b, toread + 1);
807
808 if ((r = read(from_exec_fds[0], b->ptr, b->size - 1)) < 0) {
809 /* read failed */
810 break;
811 } else {
812 b->used = r;
813 b->ptr[b->used++] = '\0';
814 }
815 } else {
816 break;
817 }
818 }
819 } else {
820 was_interrupted = 0;
821 log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
822 }
823 } while (was_interrupted > 0 && was_interrupted < 4); /* if waitpid() gets interrupted, retry, but max 4 times */
824
825 close(from_exec_fds[0]);
826
827 break;
828 }
829 }
830 #else
831
832 return -1;
833 #endif
834
835 break;
836 }
837 case SSI_IF: {
838 const char *expr = NULL;
839
840 for (i = 2; i < n; i += 2) {
841 if (0 == strcmp(l[i], "expr")) {
842 expr = l[i+1];
843 } else {
844 log_error_write(srv, __FILE__, __LINE__, "sss",
845 "ssi: unknow attribute for ",
846 l[1], l[i]);
847 }
848 }
849
850 if (!expr) {
851 log_error_write(srv, __FILE__, __LINE__, "sss",
852 "ssi: ",
853 l[1], "expr missing");
854 break;
855 }
856
857 if ((!p->if_is_false) &&
858 ((p->if_is_false_level == 0) ||
859 (p->if_level < p->if_is_false_level))) {
860 switch (ssi_eval_expr(srv, con, p, expr)) {
861 case -1:
862 case 0:
863 p->if_is_false = 1;
864 p->if_is_false_level = p->if_level;
865 break;
866 case 1:
867 p->if_is_false = 0;
868 break;
869 }
870 }
871
872 p->if_level++;
873
874 break;
875 }
876 case SSI_ELSE:
877 p->if_level--;
878
879 if (p->if_is_false) {
880 if ((p->if_level == p->if_is_false_level) &&
881 (p->if_is_false_endif == 0)) {
882 p->if_is_false = 0;
883 }
884 } else {
885 p->if_is_false = 1;
886
887 p->if_is_false_level = p->if_level;
888 }
889 p->if_level++;
890
891 break;
892 case SSI_ELIF: {
893 const char *expr = NULL;
894 for (i = 2; i < n; i += 2) {
895 if (0 == strcmp(l[i], "expr")) {
896 expr = l[i+1];
897 } else {
898 log_error_write(srv, __FILE__, __LINE__, "sss",
899 "ssi: unknow attribute for ",
900 l[1], l[i]);
901 }
902 }
903
904 if (!expr) {
905 log_error_write(srv, __FILE__, __LINE__, "sss",
906 "ssi: ",
907 l[1], "expr missing");
908 break;
909 }
910
911 p->if_level--;
912
913 if (p->if_level == p->if_is_false_level) {
914 if ((p->if_is_false) &&
915 (p->if_is_false_endif == 0)) {
916 switch (ssi_eval_expr(srv, con, p, expr)) {
917 case -1:
918 case 0:
919 p->if_is_false = 1;
920 p->if_is_false_level = p->if_level;
921 break;
922 case 1:
923 p->if_is_false = 0;
924 break;
925 }
926 } else {
927 p->if_is_false = 1;
928 p->if_is_false_level = p->if_level;
929 p->if_is_false_endif = 1;
930 }
931 }
932
933 p->if_level++;
934
935 break;
936 }
937 case SSI_ENDIF:
938 p->if_level--;
939
940 if (p->if_level == p->if_is_false_level) {
941 p->if_is_false = 0;
942 p->if_is_false_endif = 0;
943 }
944
945 break;
946 default:
947 log_error_write(srv, __FILE__, __LINE__, "ss",
948 "ssi: unknow ssi-command:",
949 l[1]);
950 break;
951 }
952
953 return 0;
954
955 }
956
mod_ssi_handle_request(server * srv,connection * con,plugin_data * p)957 static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) {
958 stream s;
959 #ifdef HAVE_PCRE_H
960 int i, n;
961
962 #define N 10
963 int ovec[N * 3];
964 #endif
965
966 /* get a stream to the file */
967
968 array_reset(p->ssi_vars);
969 array_reset(p->ssi_cgi_env);
970 buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
971 p->sizefmt = 0;
972 build_ssi_cgi_vars(srv, con, p);
973 p->if_is_false = 0;
974
975 /* Reset the modified time of included files */
976 include_file_last_mtime = 0;
977
978 if (-1 == stream_open(&s, con->physical.path)) {
979 log_error_write(srv, __FILE__, __LINE__, "sb",
980 "stream-open: ", con->physical.path);
981 return -1;
982 }
983
984
985 /**
986 * <!--#element attribute=value attribute=value ... -->
987 *
988 * config DONE
989 * errmsg -- missing
990 * sizefmt DONE
991 * timefmt DONE
992 * echo DONE
993 * var DONE
994 * encoding -- missing
995 * exec DONE
996 * cgi -- never
997 * cmd DONE
998 * fsize DONE
999 * file DONE
1000 * virtual DONE
1001 * flastmod DONE
1002 * file DONE
1003 * virtual DONE
1004 * include DONE
1005 * file DONE
1006 * virtual DONE
1007 * printenv DONE
1008 * set DONE
1009 * var DONE
1010 * value DONE
1011 *
1012 * if DONE
1013 * elif DONE
1014 * else DONE
1015 * endif DONE
1016 *
1017 *
1018 * expressions
1019 * AND, OR DONE
1020 * comp DONE
1021 * ${...} -- missing
1022 * $... DONE
1023 * '...' DONE
1024 * ( ... ) DONE
1025 *
1026 *
1027 *
1028 * ** all DONE **
1029 * DATE_GMT
1030 * The current date in Greenwich Mean Time.
1031 * DATE_LOCAL
1032 * The current date in the local time zone.
1033 * DOCUMENT_NAME
1034 * The filename (excluding directories) of the document requested by the user.
1035 * DOCUMENT_URI
1036 * 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.
1037 * LAST_MODIFIED
1038 * The last modification date of the document requested by the user.
1039 * USER_NAME
1040 * Contains the owner of the file which included it.
1041 *
1042 */
1043 #ifdef HAVE_PCRE_H
1044 for (i = 0; (n = pcre_exec(p->ssi_regex, NULL, s.start, s.size, i, 0, ovec, N * 3)) > 0; i = ovec[1]) {
1045 const char **l;
1046 /* take everything from last offset to current match pos */
1047
1048 if (!p->if_is_false) chunkqueue_append_file(con->write_queue, con->physical.path, i, ovec[0] - i);
1049
1050 pcre_get_substring_list(s.start, ovec, n, &l);
1051 process_ssi_stmt(srv, con, p, l, n);
1052 pcre_free_substring_list(l);
1053 }
1054
1055 switch(n) {
1056 case PCRE_ERROR_NOMATCH:
1057 /* copy everything/the rest */
1058 chunkqueue_append_file(con->write_queue, con->physical.path, i, s.size - i);
1059
1060 break;
1061 default:
1062 log_error_write(srv, __FILE__, __LINE__, "sd",
1063 "execution error while matching: ", n);
1064 break;
1065 }
1066 #endif
1067
1068
1069 stream_close(&s);
1070
1071 con->file_started = 1;
1072 con->file_finished = 1;
1073 con->mode = p->id;
1074
1075 if (p->conf.content_type->used <= 1) {
1076 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1077 } else {
1078 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type));
1079 }
1080
1081 {
1082 /* Generate "ETag" & "Last-Modified" headers */
1083
1084 stat_cache_entry *sce = NULL;
1085 time_t lm_time = 0;
1086 buffer *mtime = NULL;
1087
1088 stat_cache_get_entry(srv, con, con->physical.path, &sce);
1089
1090 etag_mutate(con->physical.etag, sce->etag);
1091 response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
1092
1093 if (sce->st.st_mtime > include_file_last_mtime)
1094 lm_time = sce->st.st_mtime;
1095 else
1096 lm_time = include_file_last_mtime;
1097
1098 mtime = strftime_cache_get(srv, lm_time);
1099 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
1100 }
1101
1102 /* Reset the modified time of included files */
1103 include_file_last_mtime = 0;
1104
1105 /* reset physical.path */
1106 buffer_reset(con->physical.path);
1107
1108 return 0;
1109 }
1110
1111 #define PATCH(x) \
1112 p->conf.x = s->x;
mod_ssi_patch_connection(server * srv,connection * con,plugin_data * p)1113 static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
1114 size_t i, j;
1115 plugin_config *s = p->config_storage[0];
1116
1117 PATCH(ssi_extension);
1118 PATCH(content_type);
1119
1120 /* skip the first, the global context */
1121 for (i = 1; i < srv->config_context->used; i++) {
1122 data_config *dc = (data_config *)srv->config_context->data[i];
1123 s = p->config_storage[i];
1124
1125 /* condition didn't match */
1126 if (!config_check_cond(srv, con, dc)) continue;
1127
1128 /* merge config */
1129 for (j = 0; j < dc->value->used; j++) {
1130 data_unset *du = dc->value->data[j];
1131
1132 if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
1133 PATCH(ssi_extension);
1134 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) {
1135 PATCH(content_type);
1136 }
1137 }
1138 }
1139
1140 return 0;
1141 }
1142 #undef PATCH
1143
URIHANDLER_FUNC(mod_ssi_physical_path)1144 URIHANDLER_FUNC(mod_ssi_physical_path) {
1145 plugin_data *p = p_d;
1146 size_t k;
1147
1148 if (con->mode != DIRECT) return HANDLER_GO_ON;
1149
1150 if (con->physical.path->used == 0) return HANDLER_GO_ON;
1151
1152 mod_ssi_patch_connection(srv, con, p);
1153
1154 for (k = 0; k < p->conf.ssi_extension->used; k++) {
1155 data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
1156
1157 if (ds->value->used == 0) continue;
1158
1159 if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) {
1160 /* handle ssi-request */
1161
1162 if (mod_ssi_handle_request(srv, con, p)) {
1163 /* on error */
1164 con->http_status = 500;
1165 con->mode = DIRECT;
1166 }
1167
1168 return HANDLER_FINISHED;
1169 }
1170 }
1171
1172 /* not found */
1173 return HANDLER_GO_ON;
1174 }
1175
1176 /* this function is called at dlopen() time and inits the callbacks */
1177
1178 int mod_ssi_plugin_init(plugin *p);
mod_ssi_plugin_init(plugin * p)1179 int mod_ssi_plugin_init(plugin *p) {
1180 p->version = LIGHTTPD_VERSION_ID;
1181 p->name = buffer_init_string("ssi");
1182
1183 p->init = mod_ssi_init;
1184 p->handle_subrequest_start = mod_ssi_physical_path;
1185 p->set_defaults = mod_ssi_set_defaults;
1186 p->cleanup = mod_ssi_free;
1187
1188 p->data = NULL;
1189
1190 return 0;
1191 }
1192