1 #include "first.h" 2 3 #include "base.h" 4 #include "fdevent.h" 5 #include "log.h" 6 #include "buffer.h" 7 #include "http_header.h" 8 #include "stat_cache.h" 9 10 #include "plugin.h" 11 12 #include "response.h" 13 14 #include "mod_ssi.h" 15 16 #include "sys-socket.h" 17 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 #include "sys-strings.h" 21 #include <sys/wait.h> 22 23 #include <ctype.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <errno.h> 27 #include <fcntl.h> 28 #include <time.h> 29 #include <unistd.h> 30 31 #ifdef HAVE_PWD_H 32 # include <pwd.h> 33 #endif 34 35 #ifdef HAVE_SYS_FILIO_H 36 # include <sys/filio.h> 37 #endif 38 39 #include "etag.h" 40 41 static handler_ctx * handler_ctx_init(plugin_data *p) { 42 handler_ctx *hctx = calloc(1, sizeof(*hctx)); 43 force_assert(hctx); 44 hctx->timefmt = p->timefmt; 45 hctx->stat_fn = p->stat_fn; 46 hctx->ssi_vars = p->ssi_vars; 47 hctx->ssi_cgi_env = p->ssi_cgi_env; 48 memcpy(&hctx->conf, &p->conf, sizeof(plugin_config)); 49 return hctx; 50 } 51 52 static void handler_ctx_free(handler_ctx *hctx) { 53 free(hctx); 54 } 55 56 /* The newest modified time of included files for include statement */ 57 static volatile time_t include_file_last_mtime = 0; 58 59 /* init the plugin data */ 60 INIT_FUNC(mod_ssi_init) { 61 plugin_data *p; 62 63 p = calloc(1, sizeof(*p)); 64 65 p->timefmt = buffer_init(); 66 p->stat_fn = buffer_init(); 67 68 p->ssi_vars = array_init(); 69 p->ssi_cgi_env = array_init(); 70 71 return p; 72 } 73 74 /* detroy the plugin data */ 75 FREE_FUNC(mod_ssi_free) { 76 plugin_data *p = p_d; 77 UNUSED(srv); 78 79 if (!p) return HANDLER_GO_ON; 80 81 if (p->config_storage) { 82 size_t i; 83 for (i = 0; i < srv->config_context->used; i++) { 84 plugin_config *s = p->config_storage[i]; 85 86 if (NULL == s) continue; 87 88 array_free(s->ssi_extension); 89 buffer_free(s->content_type); 90 91 free(s); 92 } 93 free(p->config_storage); 94 } 95 96 array_free(p->ssi_vars); 97 array_free(p->ssi_cgi_env); 98 buffer_free(p->timefmt); 99 buffer_free(p->stat_fn); 100 101 free(p); 102 103 return HANDLER_GO_ON; 104 } 105 106 /* handle plugin config and check values */ 107 108 SETDEFAULTS_FUNC(mod_ssi_set_defaults) { 109 plugin_data *p = p_d; 110 size_t i = 0; 111 112 config_values_t cv[] = { 113 { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ 114 { "ssi.content-type", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ 115 { "ssi.conditional-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ 116 { "ssi.exec", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ 117 { "ssi.recursion-max", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ 118 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 119 }; 120 121 if (!p) return HANDLER_ERROR; 122 123 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *)); 124 125 for (i = 0; i < srv->config_context->used; i++) { 126 data_config const* config = (data_config const*)srv->config_context->data[i]; 127 plugin_config *s; 128 129 s = calloc(1, sizeof(plugin_config)); 130 s->ssi_extension = array_init(); 131 s->content_type = buffer_init(); 132 s->conditional_requests = 0; 133 s->ssi_exec = 1; 134 s->ssi_recursion_max = 0; 135 136 cv[0].destination = s->ssi_extension; 137 cv[1].destination = s->content_type; 138 cv[2].destination = &(s->conditional_requests); 139 cv[3].destination = &(s->ssi_exec); 140 cv[4].destination = &(s->ssi_recursion_max); 141 142 p->config_storage[i] = s; 143 144 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 145 return HANDLER_ERROR; 146 } 147 148 if (!array_is_vlist(s->ssi_extension)) { 149 log_error_write(srv, __FILE__, __LINE__, "s", 150 "unexpected value for ssi.extension; expected list of \"ext\""); 151 return HANDLER_ERROR; 152 } 153 } 154 155 return HANDLER_GO_ON; 156 } 157 158 159 static int ssi_env_add(void *venv, const char *key, size_t klen, const char *val, size_t vlen) { 160 array_insert_key_value((array *)venv, key, klen, val, vlen); 161 return 0; 162 } 163 164 static int build_ssi_cgi_vars(server *srv, connection *con, handler_ctx *p) { 165 http_cgi_opts opts = { 0, 0, NULL, NULL }; 166 /* temporarily remove Authorization from request headers 167 * so that Authorization does not end up in SSI environment */ 168 buffer *vb_auth = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization")); 169 buffer b_auth; 170 if (vb_auth) { 171 memcpy(&b_auth, vb_auth, sizeof(buffer)); 172 memset(vb_auth, 0, sizeof(buffer)); 173 } 174 175 array_reset_data_strings(p->ssi_cgi_env); 176 177 if (0 != http_cgi_headers(srv, con, &opts, ssi_env_add, p->ssi_cgi_env)) { 178 con->http_status = 400; 179 return -1; 180 } 181 182 if (vb_auth) { 183 memcpy(vb_auth, &b_auth, sizeof(buffer)); 184 } 185 186 return 0; 187 } 188 189 static int mod_ssi_process_file(server *srv, connection *con, handler_ctx *p, struct stat *st); 190 191 static int process_ssi_stmt(server *srv, connection *con, handler_ctx *p, const char **l, size_t n, struct stat *st) { 192 193 /** 194 * <!--#element attribute=value attribute=value ... --> 195 * 196 * config DONE 197 * errmsg -- missing 198 * sizefmt DONE 199 * timefmt DONE 200 * echo DONE 201 * var DONE 202 * encoding -- missing 203 * exec DONE 204 * cgi -- never 205 * cmd DONE 206 * fsize DONE 207 * file DONE 208 * virtual DONE 209 * flastmod DONE 210 * file DONE 211 * virtual DONE 212 * include DONE 213 * file DONE 214 * virtual DONE 215 * printenv DONE 216 * set DONE 217 * var DONE 218 * value DONE 219 * 220 * if DONE 221 * elif DONE 222 * else DONE 223 * endif DONE 224 * 225 * 226 * expressions 227 * AND, OR DONE 228 * comp DONE 229 * ${...} -- missing 230 * $... DONE 231 * '...' DONE 232 * ( ... ) DONE 233 * 234 * 235 * 236 * ** all DONE ** 237 * DATE_GMT 238 * The current date in Greenwich Mean Time. 239 * DATE_LOCAL 240 * The current date in the local time zone. 241 * DOCUMENT_NAME 242 * The filename (excluding directories) of the document requested by the user. 243 * DOCUMENT_URI 244 * 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. 245 * LAST_MODIFIED 246 * The last modification date of the document requested by the user. 247 * USER_NAME 248 * Contains the owner of the file which included it. 249 * 250 */ 251 252 size_t i, ssicmd = 0; 253 char buf[255]; 254 buffer *b = NULL; 255 256 static const struct { 257 const char *var; 258 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD, 259 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF, 260 SSI_ELSE, SSI_ENDIF, SSI_EXEC, SSI_COMMENT } type; 261 } ssicmds[] = { 262 { "echo", SSI_ECHO }, 263 { "include", SSI_INCLUDE }, 264 { "flastmod", SSI_FLASTMOD }, 265 { "fsize", SSI_FSIZE }, 266 { "config", SSI_CONFIG }, 267 { "printenv", SSI_PRINTENV }, 268 { "set", SSI_SET }, 269 { "if", SSI_IF }, 270 { "elif", SSI_ELIF }, 271 { "endif", SSI_ENDIF }, 272 { "else", SSI_ELSE }, 273 { "exec", SSI_EXEC }, 274 { "comment", SSI_COMMENT }, 275 276 { NULL, SSI_UNSET } 277 }; 278 279 for (i = 0; ssicmds[i].var; i++) { 280 if (0 == strcmp(l[1], ssicmds[i].var)) { 281 ssicmd = ssicmds[i].type; 282 break; 283 } 284 } 285 286 switch(ssicmd) { 287 case SSI_ECHO: { 288 /* echo */ 289 int var = 0; 290 /* int enc = 0; */ 291 const char *var_val = NULL; 292 293 static const struct { 294 const char *var; 295 enum { 296 SSI_ECHO_UNSET, 297 SSI_ECHO_DATE_GMT, 298 SSI_ECHO_DATE_LOCAL, 299 SSI_ECHO_DOCUMENT_NAME, 300 SSI_ECHO_DOCUMENT_URI, 301 SSI_ECHO_LAST_MODIFIED, 302 SSI_ECHO_USER_NAME, 303 SSI_ECHO_SCRIPT_URI, 304 SSI_ECHO_SCRIPT_URL, 305 } type; 306 } echovars[] = { 307 { "DATE_GMT", SSI_ECHO_DATE_GMT }, 308 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL }, 309 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME }, 310 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI }, 311 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED }, 312 { "USER_NAME", SSI_ECHO_USER_NAME }, 313 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI }, 314 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL }, 315 316 { NULL, SSI_ECHO_UNSET } 317 }; 318 319 /* 320 static const struct { 321 const char *var; 322 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type; 323 } encvars[] = { 324 { "url", SSI_ENC_URL }, 325 { "none", SSI_ENC_NONE }, 326 { "entity", SSI_ENC_ENTITY }, 327 328 { NULL, SSI_ENC_UNSET } 329 }; 330 */ 331 332 for (i = 2; i < n; i += 2) { 333 if (0 == strcmp(l[i], "var")) { 334 int j; 335 336 var_val = l[i+1]; 337 338 for (j = 0; echovars[j].var; j++) { 339 if (0 == strcmp(l[i+1], echovars[j].var)) { 340 var = echovars[j].type; 341 break; 342 } 343 } 344 } else if (0 == strcmp(l[i], "encoding")) { 345 /* 346 int j; 347 348 for (j = 0; encvars[j].var; j++) { 349 if (0 == strcmp(l[i+1], encvars[j].var)) { 350 enc = encvars[j].type; 351 break; 352 } 353 } 354 */ 355 } else { 356 log_error_write(srv, __FILE__, __LINE__, "sss", 357 "ssi: unknown attribute for ", 358 l[1], l[i]); 359 } 360 } 361 362 if (p->if_is_false) break; 363 364 if (!var_val) { 365 log_error_write(srv, __FILE__, __LINE__, "sss", 366 "ssi: ", 367 l[1], "var is missing"); 368 break; 369 } 370 371 switch(var) { 372 case SSI_ECHO_USER_NAME: { 373 struct passwd *pw; 374 375 b = srv->tmp_buf; 376 #ifdef HAVE_PWD_H 377 if (NULL == (pw = getpwuid(st->st_uid))) { 378 buffer_copy_int(b, st->st_uid); 379 } else { 380 buffer_copy_string(b, pw->pw_name); 381 } 382 #else 383 buffer_copy_int(b, st->st_uid); 384 #endif 385 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(b)); 386 break; 387 } 388 case SSI_ECHO_LAST_MODIFIED: { 389 time_t t = st->st_mtime; 390 391 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { 392 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)")); 393 } else { 394 chunkqueue_append_mem(con->write_queue, buf, strlen(buf)); 395 } 396 break; 397 } 398 case SSI_ECHO_DATE_LOCAL: { 399 time_t t = time(NULL); 400 401 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { 402 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)")); 403 } else { 404 chunkqueue_append_mem(con->write_queue, buf, strlen(buf)); 405 } 406 break; 407 } 408 case SSI_ECHO_DATE_GMT: { 409 time_t t = time(NULL); 410 411 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) { 412 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)")); 413 } else { 414 chunkqueue_append_mem(con->write_queue, buf, strlen(buf)); 415 } 416 break; 417 } 418 case SSI_ECHO_DOCUMENT_NAME: { 419 char *sl; 420 421 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) { 422 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->physical.path)); 423 } else { 424 chunkqueue_append_mem(con->write_queue, sl + 1, strlen(sl + 1)); 425 } 426 break; 427 } 428 case SSI_ECHO_DOCUMENT_URI: { 429 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.path)); 430 break; 431 } 432 case SSI_ECHO_SCRIPT_URI: { 433 if (!buffer_string_is_empty(con->uri.scheme) && !buffer_string_is_empty(con->uri.authority)) { 434 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.scheme)); 435 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("://")); 436 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.authority)); 437 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri)); 438 if (!buffer_string_is_empty(con->uri.query)) { 439 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?")); 440 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query)); 441 } 442 } 443 break; 444 } 445 case SSI_ECHO_SCRIPT_URL: { 446 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri)); 447 if (!buffer_string_is_empty(con->uri.query)) { 448 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?")); 449 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query)); 450 } 451 break; 452 } 453 default: { 454 data_string *ds; 455 /* check if it is a cgi-var or a ssi-var */ 456 457 if (NULL != (ds = (data_string *)array_get_element_klen(p->ssi_cgi_env, var_val, strlen(var_val))) || 458 NULL != (ds = (data_string *)array_get_element_klen(p->ssi_vars, var_val, strlen(var_val)))) { 459 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(ds->value)); 460 } else { 461 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)")); 462 } 463 464 break; 465 } 466 } 467 break; 468 } 469 case SSI_INCLUDE: 470 case SSI_FLASTMOD: 471 case SSI_FSIZE: { 472 const char * file_path = NULL, *virt_path = NULL; 473 struct stat stb; 474 char *sl; 475 476 for (i = 2; i < n; i += 2) { 477 if (0 == strcmp(l[i], "file")) { 478 file_path = l[i+1]; 479 } else if (0 == strcmp(l[i], "virtual")) { 480 virt_path = l[i+1]; 481 } else { 482 log_error_write(srv, __FILE__, __LINE__, "sss", 483 "ssi: unknown attribute for ", 484 l[1], l[i]); 485 } 486 } 487 488 if (!file_path && !virt_path) { 489 log_error_write(srv, __FILE__, __LINE__, "sss", 490 "ssi: ", 491 l[1], "file or virtual are missing"); 492 break; 493 } 494 495 if (file_path && virt_path) { 496 log_error_write(srv, __FILE__, __LINE__, "sss", 497 "ssi: ", 498 l[1], "only one of file and virtual is allowed here"); 499 break; 500 } 501 502 503 if (p->if_is_false) break; 504 505 if (file_path) { 506 /* current doc-root */ 507 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) { 508 buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/")); 509 } else { 510 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1); 511 } 512 513 buffer_copy_string(srv->tmp_buf, file_path); 514 buffer_urldecode_path(srv->tmp_buf); 515 if (!buffer_is_valid_UTF8(srv->tmp_buf)) { 516 log_error_write(srv, __FILE__, __LINE__, "sb", 517 "SSI invalid UTF-8 after url-decode:", srv->tmp_buf); 518 break; 519 } 520 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf); 521 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf); 522 } else { 523 /* virtual */ 524 size_t remain; 525 526 if (virt_path[0] == '/') { 527 buffer_copy_string(srv->tmp_buf, virt_path); 528 } else { 529 /* there is always a / */ 530 sl = strrchr(con->uri.path->ptr, '/'); 531 532 buffer_copy_string_len(srv->tmp_buf, con->uri.path->ptr, sl - con->uri.path->ptr + 1); 533 buffer_append_string(srv->tmp_buf, virt_path); 534 } 535 536 buffer_urldecode_path(srv->tmp_buf); 537 if (!buffer_is_valid_UTF8(srv->tmp_buf)) { 538 log_error_write(srv, __FILE__, __LINE__, "sb", 539 "SSI invalid UTF-8 after url-decode:", srv->tmp_buf); 540 break; 541 } 542 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf); 543 544 /* we have an uri */ 545 546 /* Destination physical path (similar to code in mod_webdav.c) 547 * src con->physical.path might have been remapped with mod_alias, mod_userdir. 548 * (but neither modifies con->physical.rel_path) 549 * Find matching prefix to support relative paths to current physical path. 550 * Aliasing of paths underneath current con->physical.basedir might not work. 551 * Likewise, mod_rewrite URL rewriting might thwart this comparison. 552 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir. 553 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir. 554 * (Related, use mod_auth to protect this basedir, but avoid attempting to 555 * use mod_auth on paths underneath this basedir, as target path is not 556 * validated with mod_auth) 557 */ 558 559 /* find matching URI prefix 560 * check if remaining con->physical.rel_path matches suffix 561 * of con->physical.basedir so that we can use it to 562 * remap Destination physical path */ 563 { 564 const char *sep, *sep2; 565 sep = con->uri.path->ptr; 566 sep2 = srv->tmp_buf->ptr; 567 for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ; 568 while (i != 0 && sep[--i] != '/') ; /* find matching directory path */ 569 } 570 if (con->conf.force_lowercase_filenames) { 571 buffer_to_lower(srv->tmp_buf); 572 } 573 remain = buffer_string_length(con->uri.path) - i; 574 if (!con->conf.force_lowercase_filenames 575 ? buffer_is_equal_right_len(con->physical.path, con->physical.rel_path, remain) 576 :(buffer_string_length(con->physical.path) >= remain 577 && 0 == strncasecmp(con->physical.path->ptr+buffer_string_length(con->physical.path)-remain, con->physical.rel_path->ptr+i, remain))) { 578 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, buffer_string_length(con->physical.path)-remain); 579 buffer_append_string_len(p->stat_fn, srv->tmp_buf->ptr+i, buffer_string_length(srv->tmp_buf)-i); 580 } else { 581 /* unable to perform physical path remap here; 582 * assume doc_root/rel_path and no remapping */ 583 buffer_copy_buffer(p->stat_fn, con->physical.doc_root); 584 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf); 585 } 586 } 587 588 if (!con->conf.follow_symlink 589 && 0 != stat_cache_path_contains_symlink(srv, p->stat_fn)) { 590 break; 591 } 592 593 int fd = stat_cache_open_rdonly_fstat(p->stat_fn, &stb, con->conf.follow_symlink); 594 if (fd > 0) { 595 time_t t = stb.st_mtime; 596 597 switch (ssicmd) { 598 case SSI_FSIZE: 599 b = srv->tmp_buf; 600 if (p->sizefmt) { 601 int j = 0; 602 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL }; 603 604 off_t s = stb.st_size; 605 606 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++); 607 608 buffer_copy_int(b, s); 609 buffer_append_string(b, abr[j]); 610 } else { 611 buffer_copy_int(b, stb.st_size); 612 } 613 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(b)); 614 break; 615 case SSI_FLASTMOD: 616 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { 617 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)")); 618 } else { 619 chunkqueue_append_mem(con->write_queue, buf, strlen(buf)); 620 } 621 break; 622 case SSI_INCLUDE: 623 /* Keep the newest mtime of included files */ 624 if (stb.st_mtime > include_file_last_mtime) 625 include_file_last_mtime = stb.st_mtime; 626 627 if (file_path || 0 == p->conf.ssi_recursion_max) { 628 /* don't process if #include file="..." is used */ 629 chunkqueue_append_file_fd(con->write_queue, p->stat_fn, fd, 0, stb.st_size); 630 fd = -1; 631 } else { 632 buffer *upsave, *ppsave, *prpsave; 633 634 /* only allow predefined recursion depth */ 635 if (p->ssi_recursion_depth >= p->conf.ssi_recursion_max) { 636 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(error: include directives recurse deeper than pre-defined ssi.recursion-max)")); 637 break; 638 } 639 640 /* prevents simple infinite loop */ 641 if (buffer_is_equal(con->physical.path, p->stat_fn)) { 642 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(error: include directives create an infinite loop)")); 643 break; 644 } 645 646 /* save and restore con->physical.path, con->physical.rel_path, and con->uri.path around include 647 * 648 * srv->tmp_buf contains url-decoded, path-simplified, and lowercased (if con->conf.force_lowercase) uri path of target. 649 * con->uri.path and con->physical.rel_path are set to the same since we only operate on filenames here, 650 * not full re-run of all modules for subrequest */ 651 upsave = con->uri.path; 652 ppsave = con->physical.path; 653 prpsave = con->physical.rel_path; 654 655 con->physical.path = p->stat_fn; 656 p->stat_fn = buffer_init(); 657 658 con->uri.path = con->physical.rel_path = buffer_init_buffer(srv->tmp_buf); 659 660 close(fd); 661 fd = -1; 662 663 /*(ignore return value; muddle along as best we can if error occurs)*/ 664 ++p->ssi_recursion_depth; 665 mod_ssi_process_file(srv, con, p, &stb); 666 --p->ssi_recursion_depth; 667 668 buffer_free(con->uri.path); 669 con->uri.path = upsave; 670 con->physical.rel_path = prpsave; 671 672 buffer_free(p->stat_fn); 673 p->stat_fn = con->physical.path; 674 con->physical.path = ppsave; 675 } 676 677 break; 678 } 679 680 if (fd > 0) close(fd); 681 } else { 682 log_error_write(srv, __FILE__, __LINE__, "sbs", 683 "ssi: stating failed ", 684 p->stat_fn, strerror(errno)); 685 } 686 break; 687 } 688 case SSI_SET: { 689 const char *key = NULL, *val = NULL; 690 for (i = 2; i < n; i += 2) { 691 if (0 == strcmp(l[i], "var")) { 692 key = l[i+1]; 693 } else if (0 == strcmp(l[i], "value")) { 694 val = l[i+1]; 695 } else { 696 log_error_write(srv, __FILE__, __LINE__, "sss", 697 "ssi: unknown attribute for ", 698 l[1], l[i]); 699 } 700 } 701 702 if (p->if_is_false) break; 703 704 if (key && val) { 705 array_insert_key_value(p->ssi_vars, key, strlen(key), val, strlen(val)); 706 } else if (key || val) { 707 log_error_write(srv, __FILE__, __LINE__, "sSSss", 708 "ssi: var and value have to be set in <!--#set", l[1], "=", l[2], "-->"); 709 } else { 710 log_error_write(srv, __FILE__, __LINE__, "s", 711 "ssi: var and value have to be set in <!--#set var=... value=... -->"); 712 } 713 break; 714 } 715 case SSI_CONFIG: 716 if (p->if_is_false) break; 717 718 for (i = 2; i < n; i += 2) { 719 if (0 == strcmp(l[i], "timefmt")) { 720 buffer_copy_string(p->timefmt, l[i+1]); 721 } else if (0 == strcmp(l[i], "sizefmt")) { 722 if (0 == strcmp(l[i+1], "abbrev")) { 723 p->sizefmt = 1; 724 } else if (0 == strcmp(l[i+1], "bytes")) { 725 p->sizefmt = 0; 726 } else { 727 log_error_write(srv, __FILE__, __LINE__, "sssss", 728 "ssi: unknown value for attribute '", 729 l[i], 730 "' for ", 731 l[1], l[i+1]); 732 } 733 } else { 734 log_error_write(srv, __FILE__, __LINE__, "sss", 735 "ssi: unknown attribute for ", 736 l[1], l[i]); 737 } 738 } 739 break; 740 case SSI_PRINTENV: 741 if (p->if_is_false) break; 742 743 b = srv->tmp_buf; 744 buffer_clear(b); 745 for (i = 0; i < p->ssi_vars->used; i++) { 746 data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]]; 747 748 buffer_append_string_buffer(b, ds->key); 749 buffer_append_string_len(b, CONST_STR_LEN("=")); 750 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML); 751 buffer_append_string_len(b, CONST_STR_LEN("\n")); 752 } 753 for (i = 0; i < p->ssi_cgi_env->used; i++) { 754 data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]]; 755 756 buffer_append_string_buffer(b, ds->key); 757 buffer_append_string_len(b, CONST_STR_LEN("=")); 758 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML); 759 buffer_append_string_len(b, CONST_STR_LEN("\n")); 760 } 761 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(b)); 762 break; 763 case SSI_EXEC: { 764 const char *cmd = NULL; 765 pid_t pid; 766 chunk *c; 767 char *args[4]; 768 769 if (!p->conf.ssi_exec) { /* <!--#exec ... --> disabled by config */ 770 break; 771 } 772 773 for (i = 2; i < n; i += 2) { 774 if (0 == strcmp(l[i], "cmd")) { 775 cmd = l[i+1]; 776 } else { 777 log_error_write(srv, __FILE__, __LINE__, "sss", 778 "ssi: unknown attribute for ", 779 l[1], l[i]); 780 } 781 } 782 783 if (p->if_is_false) break; 784 785 /* 786 * as exec is assumed evil it is implemented synchronously 787 */ 788 789 if (!cmd) break; 790 791 /* send cmd output to a temporary file */ 792 if (0 != chunkqueue_append_mem_to_tempfile(srv, con->write_queue, "", 0)) break; 793 c = con->write_queue->last; 794 795 *(const char **)&args[0] = "/bin/sh"; 796 *(const char **)&args[1] = "-c"; 797 *(const char **)&args[2] = cmd; 798 args[3] = NULL; 799 800 /*(expects STDIN_FILENO open to /dev/null)*/ 801 pid = fdevent_fork_execve(args[0], args, NULL, -1, c->file.fd, -1, -1); 802 if (-1 == pid) { 803 log_error_write(srv, __FILE__, __LINE__, "sss", "spawning exec failed:", strerror(errno), cmd); 804 } else { 805 struct stat stb; 806 int status; 807 808 /* wait for the client to end */ 809 /* NOTE: synchronous; blocks entire lighttpd server */ 810 811 /* 812 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it 813 */ 814 while (-1 == waitpid(pid, &status, 0)) { 815 if (errno != EINTR) { 816 log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno)); 817 break; 818 } 819 } 820 if (!WIFEXITED(status)) { 821 log_error_write(srv, __FILE__, __LINE__, "ss", "process exited abnormally:", cmd); 822 } 823 if (0 == fstat(c->file.fd, &stb)) { 824 c->file.length = stb.st_size; 825 } 826 } 827 828 break; 829 } 830 case SSI_IF: { 831 const char *expr = NULL; 832 833 for (i = 2; i < n; i += 2) { 834 if (0 == strcmp(l[i], "expr")) { 835 expr = l[i+1]; 836 } else { 837 log_error_write(srv, __FILE__, __LINE__, "sss", 838 "ssi: unknown attribute for ", 839 l[1], l[i]); 840 } 841 } 842 843 if (!expr) { 844 log_error_write(srv, __FILE__, __LINE__, "sss", 845 "ssi: ", 846 l[1], "expr missing"); 847 break; 848 } 849 850 if ((!p->if_is_false) && 851 ((p->if_is_false_level == 0) || 852 (p->if_level < p->if_is_false_level))) { 853 switch (ssi_eval_expr(srv, con, p, expr)) { 854 case -1: 855 case 0: 856 p->if_is_false = 1; 857 p->if_is_false_level = p->if_level; 858 break; 859 case 1: 860 p->if_is_false = 0; 861 break; 862 } 863 } 864 865 p->if_level++; 866 867 break; 868 } 869 case SSI_ELSE: 870 p->if_level--; 871 872 if (p->if_is_false) { 873 if ((p->if_level == p->if_is_false_level) && 874 (p->if_is_false_endif == 0)) { 875 p->if_is_false = 0; 876 } 877 } else { 878 p->if_is_false = 1; 879 880 p->if_is_false_level = p->if_level; 881 } 882 p->if_level++; 883 884 break; 885 case SSI_ELIF: { 886 const char *expr = NULL; 887 for (i = 2; i < n; i += 2) { 888 if (0 == strcmp(l[i], "expr")) { 889 expr = l[i+1]; 890 } else { 891 log_error_write(srv, __FILE__, __LINE__, "sss", 892 "ssi: unknown attribute for ", 893 l[1], l[i]); 894 } 895 } 896 897 if (!expr) { 898 log_error_write(srv, __FILE__, __LINE__, "sss", 899 "ssi: ", 900 l[1], "expr missing"); 901 break; 902 } 903 904 p->if_level--; 905 906 if (p->if_level == p->if_is_false_level) { 907 if ((p->if_is_false) && 908 (p->if_is_false_endif == 0)) { 909 switch (ssi_eval_expr(srv, con, p, expr)) { 910 case -1: 911 case 0: 912 p->if_is_false = 1; 913 p->if_is_false_level = p->if_level; 914 break; 915 case 1: 916 p->if_is_false = 0; 917 break; 918 } 919 } else { 920 p->if_is_false = 1; 921 p->if_is_false_level = p->if_level; 922 p->if_is_false_endif = 1; 923 } 924 } 925 926 p->if_level++; 927 928 break; 929 } 930 case SSI_ENDIF: 931 p->if_level--; 932 933 if (p->if_level == p->if_is_false_level) { 934 p->if_is_false = 0; 935 p->if_is_false_endif = 0; 936 } 937 938 break; 939 case SSI_COMMENT: 940 break; 941 default: 942 log_error_write(srv, __FILE__, __LINE__, "ss", 943 "ssi: unknown ssi-command:", 944 l[1]); 945 break; 946 } 947 948 return 0; 949 950 } 951 952 static int mod_ssi_parse_ssi_stmt_value(const char * const s, const int len) { 953 int n; 954 const int c = (s[0] == '"' ? '"' : s[0] == '\'' ? '\'' : 0); 955 if (0 != c) { 956 for (n = 1; n < len; ++n) { 957 if (s[n] == c) return n+1; 958 if (s[n] == '\\') { 959 if (n+1 == len) return 0; /* invalid */ 960 ++n; 961 } 962 } 963 return 0; /* invalid */ 964 } else { 965 for (n = 0; n < len; ++n) { 966 if (isspace(s[n])) return n; 967 if (s[n] == '\\') { 968 if (n+1 == len) return 0; /* invalid */ 969 ++n; 970 } 971 } 972 return n; 973 } 974 } 975 976 static int mod_ssi_parse_ssi_stmt_offlen(int o[10], const char * const s, const int len) { 977 978 /** 979 * <!--#element attribute=value attribute=value ... --> 980 */ 981 982 /* s must begin "<!--#" and must end with "-->" */ 983 int n = 5; 984 o[0] = n; 985 for (; light_isalpha(s[n]); ++n) ; /*(n = 5 to begin after "<!--#")*/ 986 o[1] = n - o[0]; 987 if (0 == o[1]) return -1; /* empty token */ 988 989 if (n+3 == len) return 2; /* token only; no params */ 990 if (!isspace(s[n])) return -1; 991 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */ 992 if (n+3 == len) return 2; /* token only; no params */ 993 994 o[2] = n; 995 for (; light_isalpha(s[n]); ++n) ; 996 o[3] = n - o[2]; 997 if (0 == o[3] || s[n++] != '=') return -1; 998 999 o[4] = n; 1000 o[5] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3); 1001 if (0 == o[5]) return -1; /* empty or invalid token */ 1002 n += o[5]; 1003 1004 if (n+3 == len) return 6; /* token and one param */ 1005 if (!isspace(s[n])) return -1; 1006 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */ 1007 if (n+3 == len) return 6; /* token and one param */ 1008 1009 o[6] = n; 1010 for (; light_isalpha(s[n]); ++n) ; 1011 o[7] = n - o[6]; 1012 if (0 == o[7] || s[n++] != '=') return -1; 1013 1014 o[8] = n; 1015 o[9] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3); 1016 if (0 == o[9]) return -1; /* empty or invalid token */ 1017 n += o[9]; 1018 1019 if (n+3 == len) return 10; /* token and two params */ 1020 if (!isspace(s[n])) return -1; 1021 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */ 1022 if (n+3 == len) return 10; /* token and two params */ 1023 return -1; 1024 } 1025 1026 static void mod_ssi_parse_ssi_stmt(server *srv, connection *con, handler_ctx *p, char *s, int len, struct stat *st) { 1027 1028 /** 1029 * <!--#element attribute=value attribute=value ... --> 1030 */ 1031 1032 int o[10]; 1033 int m; 1034 const int n = mod_ssi_parse_ssi_stmt_offlen(o, s, len); 1035 char *l[6] = { s, NULL, NULL, NULL, NULL, NULL }; 1036 if (-1 == n) { 1037 /* ignore <!--#comment ... --> */ 1038 if (len >= 16 1039 && 0 == memcmp(s+5, "comment", sizeof("comment")-1) 1040 && (s[12] == ' ' || s[12] == '\t')) 1041 return; 1042 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */ 1043 chunkqueue_append_mem(con->write_queue, s, len); /* append stmt as-is */ 1044 return; 1045 } 1046 1047 #if 0 1048 /* dup s and then modify s */ 1049 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/ 1050 l[0] = malloc((size_t)(len+1)); 1051 memcpy(l[0], s, (size_t)len); 1052 (l[0])[len] = '\0'; 1053 #endif 1054 1055 /* modify s in-place to split string into arg tokens */ 1056 for (m = 0; m < n; m += 2) { 1057 char *ptr = s+o[m]; 1058 switch (*ptr) { 1059 case '"': 1060 case '\'': (++ptr)[o[m+1]-2] = '\0'; break; 1061 default: ptr[o[m+1]] = '\0'; break; 1062 } 1063 l[1+(m>>1)] = ptr; 1064 if (m == 4 || m == 8) { 1065 /* XXX: removing '\\' escapes from param value would be 1066 * the right thing to do, but would potentially change 1067 * current behavior, e.g. <!--#exec cmd=... --> */ 1068 } 1069 } 1070 1071 process_ssi_stmt(srv, con, p, (const char **)l, 1+(n>>1), st); 1072 1073 #if 0 1074 free(l[0]); 1075 #endif 1076 } 1077 1078 static int mod_ssi_stmt_len(const char *s, const int len) { 1079 /* s must begin "<!--#" */ 1080 int n, sq = 0, dq = 0, bs = 0; 1081 for (n = 5; n < len; ++n) { /*(n = 5 to begin after "<!--#")*/ 1082 switch (s[n]) { 1083 default: 1084 break; 1085 case '-': 1086 if (!sq && !dq && n+2 < len && s[n+1] == '-' && s[n+2] == '>') return n+3; /* found end of stmt */ 1087 break; 1088 case '"': 1089 if (!sq && (!dq || !bs)) dq = !dq; 1090 break; 1091 case '\'': 1092 if (!dq && (!sq || !bs)) sq = !sq; 1093 break; 1094 case '\\': 1095 if (sq || dq) bs = !bs; 1096 break; 1097 } 1098 } 1099 return 0; /* incomplete directive "<!--#...-->" */ 1100 } 1101 1102 static void mod_ssi_read_fd(server *srv, connection *con, handler_ctx *p, struct stat *st, int fd) { 1103 ssize_t rd; 1104 size_t offset, pretag; 1105 size_t bufsz = 8192; 1106 char *buf = malloc(bufsz); /* allocate to reduce chance of stack exhaustion upon deep recursion */ 1107 force_assert(buf); 1108 1109 offset = 0; 1110 pretag = 0; 1111 while (0 < (rd = read(fd, buf+offset, bufsz-offset))) { 1112 char *s; 1113 size_t prelen = 0, len; 1114 offset += (size_t)rd; 1115 for (; (s = memchr(buf+prelen, '<', offset-prelen)); ++prelen) { 1116 prelen = s - buf; 1117 if (prelen + 5 <= offset) { /*("<!--#" is 5 chars)*/ 1118 if (0 != memcmp(s+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */ 1119 1120 if (prelen - pretag && !p->if_is_false) { 1121 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag); 1122 } 1123 1124 len = mod_ssi_stmt_len(buf+prelen, offset-prelen); 1125 if (len) { /* num of chars to be consumed */ 1126 mod_ssi_parse_ssi_stmt(srv, con, p, buf+prelen, len, st); 1127 prelen += (len - 1); /* offset to '>' at end of SSI directive; incremented at top of loop */ 1128 pretag = prelen + 1; 1129 if (pretag == offset) { 1130 offset = pretag = 0; 1131 break; 1132 } 1133 } else if (0 == prelen && offset == bufsz) { /*(full buf)*/ 1134 /* SSI statement is way too long 1135 * NOTE: skipping this buf will expose *the rest* of this SSI statement */ 1136 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("<!-- [an error occurred: directive too long] ")); 1137 /* check if buf ends with "-" or "--" which might be part of "-->" 1138 * (buf contains at least 5 chars for "<!--#") */ 1139 if (buf[offset-2] == '-' && buf[offset-1] == '-') { 1140 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("--")); 1141 } else if (buf[offset-1] == '-') { 1142 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("-")); 1143 } 1144 offset = pretag = 0; 1145 break; 1146 } else { /* incomplete directive "<!--#...-->" */ 1147 memmove(buf, buf+prelen, (offset -= prelen)); 1148 pretag = 0; 1149 break; 1150 } 1151 } else if (prelen + 1 == offset || 0 == memcmp(s+1, "!--", offset - prelen - 1)) { 1152 if (prelen - pretag && !p->if_is_false) { 1153 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag); 1154 } 1155 memcpy(buf, buf+prelen, (offset -= prelen)); 1156 pretag = 0; 1157 break; 1158 } 1159 /* loop to look for next '<' */ 1160 } 1161 if (offset == bufsz) { 1162 if (!p->if_is_false) { 1163 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag); 1164 } 1165 offset = pretag = 0; 1166 } 1167 } 1168 1169 if (0 != rd) { 1170 log_error_write(srv, __FILE__, __LINE__, "SsB", "read(): ", strerror(errno), con->physical.path); 1171 } 1172 1173 if (offset - pretag) { 1174 /* copy remaining data in buf */ 1175 if (!p->if_is_false) { 1176 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag); 1177 } 1178 } 1179 1180 free(buf); 1181 } 1182 1183 1184 static int mod_ssi_process_file(server *srv, connection *con, handler_ctx *p, struct stat *st) { 1185 int fd = fdevent_open_cloexec(con->physical.path->ptr, con->conf.follow_symlink, O_RDONLY, 0); 1186 if (-1 == fd) { 1187 log_error_write(srv, __FILE__, __LINE__, "SsB", "open(): ", 1188 strerror(errno), con->physical.path); 1189 return -1; 1190 } 1191 1192 if (0 != fstat(fd, st)) { 1193 log_error_write(srv, __FILE__, __LINE__, "SsB", "fstat(): ", 1194 strerror(errno), con->physical.path); 1195 close(fd); 1196 return -1; 1197 } 1198 1199 mod_ssi_read_fd(srv, con, p, st, fd); 1200 1201 close(fd); 1202 return 0; 1203 } 1204 1205 1206 static int mod_ssi_handle_request(server *srv, connection *con, handler_ctx *p) { 1207 struct stat st; 1208 1209 /* get a stream to the file */ 1210 1211 array_reset_data_strings(p->ssi_vars); 1212 array_reset_data_strings(p->ssi_cgi_env); 1213 buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z")); 1214 build_ssi_cgi_vars(srv, con, p); 1215 1216 /* Reset the modified time of included files */ 1217 include_file_last_mtime = 0; 1218 1219 if (mod_ssi_process_file(srv, con, p, &st)) return -1; 1220 1221 con->file_started = 1; 1222 con->file_finished = 1; 1223 1224 if (buffer_string_is_empty(p->conf.content_type)) { 1225 http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); 1226 } else { 1227 http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type)); 1228 } 1229 1230 if (p->conf.conditional_requests) { 1231 /* Generate "ETag" & "Last-Modified" headers */ 1232 buffer *mtime = NULL; 1233 1234 /* use most recently modified include file for ETag and Last-Modified */ 1235 if (st.st_mtime < include_file_last_mtime) 1236 st.st_mtime = include_file_last_mtime; 1237 1238 etag_create(con->physical.etag, &st, con->etag_flags); 1239 etag_mutate(con->physical.etag, con->physical.etag); 1240 http_header_response_set(con, HTTP_HEADER_ETAG, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); 1241 1242 mtime = strftime_cache_get(srv, st.st_mtime); 1243 http_header_response_set(con, HTTP_HEADER_LAST_MODIFIED, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); 1244 1245 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) { 1246 /* ok, the client already has our content, 1247 * no need to send it again */ 1248 1249 chunkqueue_reset(con->write_queue); 1250 } 1251 } 1252 1253 /* Reset the modified time of included files */ 1254 include_file_last_mtime = 0; 1255 1256 /* reset physical.path */ 1257 buffer_reset(con->physical.path); 1258 1259 return 0; 1260 } 1261 1262 #define PATCH(x) \ 1263 p->conf.x = s->x; 1264 static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) { 1265 size_t i, j; 1266 plugin_config *s = p->config_storage[0]; 1267 1268 PATCH(ssi_extension); 1269 PATCH(content_type); 1270 PATCH(conditional_requests); 1271 PATCH(ssi_exec); 1272 PATCH(ssi_recursion_max); 1273 1274 /* skip the first, the global context */ 1275 for (i = 1; i < srv->config_context->used; i++) { 1276 data_config *dc = (data_config *)srv->config_context->data[i]; 1277 s = p->config_storage[i]; 1278 1279 /* condition didn't match */ 1280 if (!config_check_cond(srv, con, dc)) continue; 1281 1282 /* merge config */ 1283 for (j = 0; j < dc->value->used; j++) { 1284 data_unset *du = dc->value->data[j]; 1285 1286 if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) { 1287 PATCH(ssi_extension); 1288 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) { 1289 PATCH(content_type); 1290 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.conditional-requests"))) { 1291 PATCH(conditional_requests); 1292 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.exec"))) { 1293 PATCH(ssi_exec); 1294 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.recursion-max"))) { 1295 PATCH(ssi_recursion_max); 1296 } 1297 } 1298 } 1299 1300 return 0; 1301 } 1302 #undef PATCH 1303 1304 URIHANDLER_FUNC(mod_ssi_physical_path) { 1305 plugin_data *p = p_d; 1306 1307 if (con->mode != DIRECT) return HANDLER_GO_ON; 1308 if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON; 1309 1310 mod_ssi_patch_connection(srv, con, p); 1311 1312 if (array_match_value_suffix(p->conf.ssi_extension, con->physical.path)) { 1313 con->plugin_ctx[p->id] = handler_ctx_init(p); 1314 con->mode = p->id; 1315 } 1316 1317 return HANDLER_GO_ON; 1318 } 1319 1320 SUBREQUEST_FUNC(mod_ssi_handle_subrequest) { 1321 plugin_data *p = p_d; 1322 handler_ctx *hctx = con->plugin_ctx[p->id]; 1323 if (NULL == hctx) return HANDLER_GO_ON; 1324 if (con->mode != p->id) return HANDLER_GO_ON; /* not my job */ 1325 /* 1326 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT, 1327 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars, 1328 * and hctx->ssi_cgi_env should be allocated and cleaned up per request. 1329 */ 1330 1331 /* handle ssi-request */ 1332 1333 if (mod_ssi_handle_request(srv, con, hctx)) { 1334 /* on error */ 1335 con->http_status = 500; 1336 con->mode = DIRECT; 1337 } 1338 1339 return HANDLER_FINISHED; 1340 } 1341 1342 static handler_t mod_ssi_connection_reset(server *srv, connection *con, void *p_d) { 1343 plugin_data *p = p_d; 1344 handler_ctx *hctx = con->plugin_ctx[p->id]; 1345 if (hctx) { 1346 handler_ctx_free(hctx); 1347 con->plugin_ctx[p->id] = NULL; 1348 } 1349 1350 UNUSED(srv); 1351 return HANDLER_GO_ON; 1352 } 1353 1354 /* this function is called at dlopen() time and inits the callbacks */ 1355 1356 int mod_ssi_plugin_init(plugin *p); 1357 int mod_ssi_plugin_init(plugin *p) { 1358 p->version = LIGHTTPD_VERSION_ID; 1359 p->name = buffer_init_string("ssi"); 1360 1361 p->init = mod_ssi_init; 1362 p->handle_subrequest_start = mod_ssi_physical_path; 1363 p->handle_subrequest = mod_ssi_handle_subrequest; 1364 p->connection_reset = mod_ssi_connection_reset; 1365 p->set_defaults = mod_ssi_set_defaults; 1366 p->cleanup = mod_ssi_free; 1367 1368 p->data = NULL; 1369 1370 return 0; 1371 } 1372