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