1 /* mohawk - small HTTP server
2 **
3 ** Copyright © 2010 by Baptiste Daroussin <bapt@FreeBSD.org>,
4 ** Copyright © 2010 by Freddy Dissaux <freddy.dsx@free.fr>,
5 ** All rights reserved.
6 **
7 ** Redistribution and use in source and binary forms, with or without
8 ** modification, are permitted provided that the following conditions
9 ** are met:
10 ** 1. Redistributions of source code must retain the above copyright
11 **    notice, this list of conditions and the following disclaimer.
12 ** 2. Redistributions in binary form must reproduce the above copyright
13 **    notice, this list of conditions and the following disclaimer in the
14 **    documentation and/or other materials provided with the distribution.
15 **
16 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 ** SUCH DAMAGE.
27 
28 */
29 
30 #include <sys/resource.h>
31 #include <sys/socket.h>
32 #include <sys/stat.h>
33 #include <sys/time.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #if defined(__FreeBSD__)
37 #include <sys/capsicum.h>
38 #include <osreldate.h>
39 #endif
40 #include <netinet/in.h> /* b64_pton */
41 
42 #if defined(HAVE_BLACKLISTD)
43 #include <blacklist.h>
44 #endif
45 #include <dirent.h>
46 #include <err.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <fnmatch.h>
50 #include <libgen.h>
51 #include <limits.h>
52 #include <netdb.h>
53 #include <pwd.h>
54 #include <resolv.h> /* b64_pton */
55 #include <signal.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <syslog.h>
60 #include <unistd.h>
61 #if defined(__OpenBSD__)
62 #include <util.h>
63 #endif
64 
65 #include <event2/buffer.h>
66 #include <event2/bufferevent.h>
67 #include <event2/event.h>
68 #include <event2/http.h>
69 #include <event2/http_struct.h>
70 #include <event2/keyvalq_struct.h>
71 #include <event2/util.h>
72 
73 #include "mohawk.h"
74 #include "dump.h"
75 
76 #ifndef CGI_PATH
77 #define CGI_PATH "/bin:/usr/bin:/usr/local/bin"
78 #endif /* CGI_PATH */
79 #ifndef CGI_LD_LIBRARY_PATH
80 #define CGI_LD_LIBRARY_PATH "/usr/lib:/usr/local/lib"
81 #endif /* CGI_LD_LIBRARY_PATH */
82 
83 #define HTTP_UNAUTHORIZED 401
84 #define HTTP_ACCESS_DENIED 403
85 #define MAX_SIZE_INT 16
86 
87 struct mohawk_conf conf;
88 
89 static const char *methods[] = {
90 	NULL,
91 	"GET",
92 	"POST",
93 	"PUT",
94 	"HEAD",
95 	"DELETE",
96 	"OPTIONS",
97 	"TRACE",
98 	"CONNECT",
99 	"PATH"
100 };
101 
102 struct cgi_callback_data {
103 	struct evhttp_request *req;
104 	struct vhost *vhost;
105 };
106 
107 #define MAX_PATHS 3
108 struct mohawk_path {
109 	char *paths[MAX_PATHS];
110 	size_t offset; /* for location */
111 	int nb;
112 };
113 
114 #if defined(HAVE_BLACKLISTD)
115 static struct blacklist *blstate;
116 #endif
117 
118 static int access_granted(struct evhttp_request *, struct callback_data *);
119 static inline int access_granted_inet(struct addrinfo *, struct mohawk_network_inet *);
120 static inline int access_granted_inet6(struct addrinfo *, struct mohawk_network_inet6 *);
121 
122 static int auth_check(struct evhttp_request *, struct vhost *);
123 static int auth_check_lookup(FILE *, const char *, const char *);
124 static int auth_lookup(struct vhost *, struct mohawk_path *);
125 #if defined(HAVE_BLACKLISTD)
126 static int blacklist_lookup(struct vhost *, struct mohawk_path *);
127 #endif
128 
129 static void cgi_event_cb(struct bufferevent *, short, void *);
130 static char * cgi_map_lookup(struct vhost *, struct mohawk_path *);
131 static void cgi_read_cb(struct bufferevent *, void *);
132 
133 #if defined(HAVE_BLACKLISTD)
134 static void do_blacklist(struct evhttp_request *, int);
135 #endif
136 static void do_cgi(struct evhttp_request *, void *, const char *, struct stat *);
137 #ifdef USE_DEBUG
138 static void do_cgi_debug(struct evhttp_request *, size_t);
139 #endif
140 static char ** do_cgi_env(struct evhttp_request *, void *, const char *, size_t);
141 #ifdef USE_DEBUG
142 static void do_cgi_env_debug(char **);
143 #endif
144 
145 static void do_error(struct evhttp_request *, struct vhost *, int, const char *, const char *, ...);
146 
147 static const char * do_fs_content_type_lookup(const char *);
148 static void do_fs_dir(struct evhttp_request *, struct vhost *, const char *, const char *);
149 static void do_fs_file(struct evhttp_request *, void *, const char *, struct stat *);
150 static int do_fs_file_details(struct evhttp_request *, struct vhost *, struct evbuffer *, const char *, const char *, const char *);
151 
152 static void do_location(struct evhttp_request *, struct callback_data *, const char *, const char *);
153 static void do_log(struct evhttp_request *, struct vhost *, unsigned long long size);
154 static void do_request(struct evhttp_request *, void *);
155 static void do_status(struct evhttp_request *, void *);
156 static int do_vhost(struct event_base *, struct vhost *, int);
157 
158 static struct evhttp_uri * get_clean_uri(struct evhttp_request *);
159 static int get_fs_path(struct evhttp_request *, struct vhost *, struct mohawk_path *, char *, struct stat *);
160 static void handle_sigterm(int sig);
161 static void mohawk_event_log_cb(int, const char *);
162 static time_t parse_date(const char *);
163 
164 static void request_decode_paths(struct mohawk_path *);
165 static int request_index_lookup(struct vhost *, const char *, char **, struct stat *);
166 static struct vhost * request_location_lookup(struct vhost *, struct mohawk_path *);
167 
168 static void usage(int);
169 
170 static int
access_granted(struct evhttp_request * req,struct callback_data * cb)171 access_granted(struct evhttp_request *req, struct callback_data *cb)
172 {
173 	struct mohawk_network *network_mohawk;
174 	struct addrinfo hints, *ai;
175 	int error, ret = 0;
176 	struct vhost *vh = cb->vh;
177 
178 	if (conf.debug) {
179 		fprintf(stderr, "\nDebug: connection from '%s' port '%d'\n", req->remote_host, req->remote_port);
180 		fprintf(stderr, "Debug: connecting to '%s' port '%d'\n", cb->host, cb->port);
181 		fprintf(stderr, "Debug: vhost '%s' rootdir '%s'\n", vh->host, vh->rootdir);
182 		fprintf(stderr, "Debug: host '%s'\n", evhttp_request_get_host(req));
183 	}
184 
185 	if (SLIST_EMPTY(&vh->grant_access))
186 		return 1;
187 
188     bzero(&hints, sizeof hints);
189     hints.ai_family = PF_UNSPEC;
190     hints.ai_flags = AI_NUMERICHOST;
191 
192 	if ((error = getaddrinfo(req->remote_host, NULL, &hints, &ai)) != 0) {
193 		warnx("getaddrinfo '%s': %s", req->remote_host, gai_strerror(error));
194 		goto done;
195 	}
196 
197 	SLIST_FOREACH(network_mohawk, &vh->grant_access, entry) {
198 		if (ai->ai_family != network_mohawk->family)
199 			continue;
200 
201 		switch (ai->ai_family) {
202 		case PF_INET:
203 			if ((ret = access_granted_inet(ai, (struct mohawk_network_inet *)network_mohawk->network)) > -1) {
204 				goto done;
205 			}
206 			break;
207 		case PF_INET6:
208 			if ((ret = access_granted_inet6(ai, (struct mohawk_network_inet6 *)network_mohawk->network)) > -1) {
209 				goto done;
210 			}
211 			break;
212 		default: /* Hu ? */
213 			warnx("Unexpected address family '%s': %d", req->remote_host, ai->ai_family);
214 		}
215 	}
216 
217 done:
218 	if (ai)
219 		freeaddrinfo(ai);
220 
221 	if (ret != 1)
222 		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "access_granted - '%s' access denied %s",
223 			req->remote_host, error ? gai_strerror(error) : "");
224 	return ret == 1;
225 }
226 
227 static inline int
access_granted_inet(struct addrinfo * ai,struct mohawk_network_inet * network)228 access_granted_inet(struct addrinfo *ai, struct mohawk_network_inet *network)
229 {
230     void *fix_error = ai->ai_addr;
231     struct sockaddr_in *sa = (struct sockaddr_in*)fix_error;
232 
233     if ((sa->sin_addr.s_addr & network->mask) != network->prefix)
234         return -1;
235 
236     return !network->not;
237 }
238 
239 static inline int
access_granted_inet6(struct addrinfo * ai,struct mohawk_network_inet6 * network)240 access_granted_inet6(struct addrinfo *ai, struct mohawk_network_inet6 *network)
241 {
242     int i;
243     void *fix_error = ai->ai_addr;
244     struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)fix_error;
245 
246     for (i = 0; i < 16; i++) {
247         if ((sa6->sin6_addr.s6_addr[i] & network->mask[i]) != network->prefix[i])
248             return -1;
249     }
250 
251     return !network->not;
252 }
253 
254 static int
auth_check(struct evhttp_request * req,struct vhost * vh)255 auth_check(struct evhttp_request *req, struct vhost *vh)
256 {
257 	const char *header_auth;
258 	char auth_buf[PATH_MAX];
259 	char *auth_pass, *colon;
260 	ssize_t l;
261 	FILE *fp;
262 	struct stat st_auth;
263 	int ret;
264 #if defined(__FreeBSD__)
265 	int fd;
266 	cap_rights_t rights;
267 #endif
268 
269 #ifdef USE_DEBUG
270 	if (conf.debug)
271 		fprintf(stderr, "Debug: auth_check '%s'\n", vh->auth_path);
272 #endif
273 
274 	if (stat(vh->auth_path, &st_auth) < 0) {
275 		do_error(req, vh, HTTP_INTERNAL, NULL, "auth_check - '%s': file not found", vh->auth_path);
276 		return 0;
277 	}
278 
279 	/* We need a Authentification header */
280 	if ((header_auth = evhttp_find_header(req->input_headers, "Authorization")) == NULL)
281 		goto send_auth;
282 	if (strncmp(header_auth, "Basic ", 6) != 0)
283 		goto send_auth;
284 
285 	l = b64_pton(header_auth + 6, (u_char *)auth_buf, sizeof(auth_buf));
286 	auth_buf[l] = '\0';
287 	if ((auth_pass = strchr(auth_buf, ':')) == NULL) {
288 		/* TODO malformed , log it ? */
289 		goto send_auth;
290 	}
291 	*auth_pass++ = '\0';
292 	if ((colon = strchr(auth_pass, ':')) != NULL)
293 		*colon = '\0';
294 
295 #if defined(__FreeBSD__)
296 	if ((fd = open(vh->auth_path, O_RDONLY)) == -1) {
297 		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "auth_check - can't open '%s'", vh->auth_path);
298 		return 0;
299 	}
300 	cap_rights_init(&rights, CAP_FCNTL, CAP_READ); /* CAP_FCNTL is required by fdopen */
301 	if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS) {
302 		close(fd);
303 		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "auth_check - can't cap_rights_limit '%s'", vh->auth_path);
304 		return 0;
305 	}
306 	if ((fp = fdopen(fd, "r")) == NULL) {
307 		close(fd);
308 		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "auth_check - can't fdopen '%s'", vh->auth_path);
309 		return 0;
310 	}
311 #else
312 	if ((fp = fopen(vh->auth_path, "r")) == NULL) {
313 		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "auth_check - can't fopen '%s'", vh->auth_path);
314 		return 0;
315 	}
316 #endif
317 
318 	if ((ret = auth_check_lookup(fp, auth_buf, auth_pass)) == 1) {
319 		evhttp_add_header(req->input_headers, "Mohawk-Authenticate-User", auth_buf);
320 		return 1;
321 	}
322 	if (ret == -1) {
323 		do_error(req, vh, HTTP_INTERNAL, NULL, "auth_check_lookup - unable to allocate memory");
324 		return 0;
325 	}
326 
327 send_auth:
328 	evhttp_add_header(req->output_headers, "WWW-Authenticate", "Basic realm=\"mohawk\"");
329 	evhttp_send_reply(req, HTTP_UNAUTHORIZED, NULL, NULL);
330 
331 	return 0;
332 }
333 
334 static int
auth_check_lookup(FILE * fp,const char * user,const char * pass)335 auth_check_lookup(FILE *fp, const char *user, const char *pass)
336 {
337 	char *buf, *lbuf, *password;
338 	size_t len;
339 	int found = 0;
340 
341 	lbuf = NULL;
342 	while ((buf = fgetln(fp, &len))) {
343 		if (buf[len - 1] == '\n')
344 			buf[len - 1] = '\0';
345 		else {
346 			if ((lbuf = malloc(len + 1)) == NULL) {
347 				found = -1;
348 				goto done;
349 			}
350 			memcpy(lbuf, buf, len);
351 			lbuf[len] = '\0';
352 			free(buf);
353 			buf = lbuf;
354 		}
355 		if (buf[0] == '#')
356 			continue;
357 
358 		if ((password = strchr(buf, ':')) == NULL)
359 			continue;
360 
361 		*password++ = '\0';
362 		if (strcmp(buf, user) == 0) {
363 			if (strcmp(crypt(pass, password), password) == 0) {
364 				found = 1;
365 				break;
366 			}
367 		}
368 
369 		if (lbuf != NULL) {
370 			free(lbuf);
371 			lbuf = NULL;
372 		}
373 	}
374 
375 done:
376 	fclose(fp);
377 	if (lbuf != NULL)
378 		free(lbuf);
379 
380 	return found;
381 }
382 
383 static int
auth_lookup(struct vhost * vh,struct mohawk_path * paths)384 auth_lookup(struct vhost *vh, struct mohawk_path *paths)
385 {
386 	struct mohawk_list *item;
387 	int i;
388 	const char *uri;
389 
390 	for (i = 0; i < paths->nb; i++) {
391 		uri = paths->paths[i] + paths->offset;
392 		SLIST_FOREACH(item, &vh->no_auth_patterns, entry) {
393 			if (fnmatch(item->item, uri, 0) != FNM_NOMATCH)
394 				return 0;
395 		}
396 
397 		SLIST_FOREACH(item, &vh->auth_patterns, entry) {
398 			if (fnmatch(item->item, uri, 0) != FNM_NOMATCH)
399 				return 1;
400 		}
401 	}
402 	return vh->authentication == 1;
403 }
404 
405 #if defined(HAVE_BLACKLISTD)
406 static int
blacklist_lookup(struct vhost * vh,struct mohawk_path * paths)407 blacklist_lookup(struct vhost *vh, struct mohawk_path *paths)
408 {
409 	struct mohawk_list *item;
410 	int i;
411 	const char *uri;
412 
413 	for (i = 0; i < paths->nb; i++) {
414 		uri = paths->paths[i] + paths->offset;
415 		SLIST_FOREACH(item, &vh->blacklist_patterns, entry) {
416 			if (fnmatch(item->item, uri, 0) != FNM_NOMATCH)
417 				return 1;
418 		}
419 	}
420 	return 0;
421 }
422 #endif
423 
424 static void
cgi_event_cb(struct bufferevent * bev,short what,void * cgi_cb_data)425 cgi_event_cb(struct bufferevent *bev, short what, void *cgi_cb_data)
426 {
427 	char *header, *pos;
428 	struct cgi_callback_data *cb = cgi_cb_data;
429 	struct evhttp_request *req = cb->req;
430 	struct vhost *vh = cb->vhost;
431 	size_t len;
432 
433 	if (what & BEV_EVENT_EOF) {
434 #ifdef USE_DEBUG
435 		if (conf.debug) {
436 			fprintf(stderr, "Debug: cgi_event_cb - %zd to send\n", evbuffer_get_length(req->output_buffer));
437 		}
438 #endif
439 		while ((header = evbuffer_readln(req->output_buffer, &len, EVBUFFER_EOL_CRLF)) != NULL) {
440 			if (!len) /* empty line, end of headers */
441 				break;
442 
443 			if ((pos = strchr(header, ':')) != NULL) {
444 				if (header + len > pos + 2) {
445 					*pos = '\0';
446 
447 					evhttp_add_header(req->output_headers, header, pos + 2);
448 					*pos = ':';
449 					free(header);
450 				}
451 			}
452 		}
453 		bufferevent_free(bev);
454 		len = evbuffer_get_length(req->output_buffer);
455 		evhttp_send_reply(req, HTTP_OK, "OK", NULL);
456 		do_log(req, vh, len);
457 	}
458 	return;
459 }
460 
461 static char *
cgi_map_lookup(struct vhost * vh,struct mohawk_path * paths)462 cgi_map_lookup(struct vhost *vh, struct mohawk_path *paths)
463 {
464 	struct mohawk_hash *cgi_map;
465 	struct mohawk_list *no_cgi_map;
466 	int i;
467 	const char *uri;
468 
469 	for (i = 0; i < paths->nb; i++) {
470 		uri = paths->paths[i] + paths->offset;
471 		/* script_name match cgi-map ? */
472 		SLIST_FOREACH(cgi_map, &vh->cgi_map, entry) {
473 			if (fnmatch(cgi_map->key, uri, 0) != FNM_NOMATCH) {
474 #ifdef USE_DEBUG
475 				if (conf.debug)
476 					fprintf(stderr, "Debug: '%s' match '%s'\n", uri, cgi_map->key);
477 #endif
478 				/* check if match no_cgi_map */
479 				SLIST_FOREACH(no_cgi_map, &vh->no_cgi_maps, entry) {
480 					if (fnmatch(no_cgi_map->item, uri, 0) != FNM_NOMATCH) {
481 #ifdef USE_DEBUG
482 						if (conf.debug)
483 							fprintf(stderr, "Debug: but '%s' match '%s'\n", uri, no_cgi_map->item);
484 #endif
485 						return NULL;
486 					}
487 				}
488 				return cgi_map->value;
489 			}
490 		}
491 	}
492 	return NULL;
493 }
494 
495 static void
cgi_read_cb(struct bufferevent * bev,void * cgi_cb_data)496 cgi_read_cb(struct bufferevent *bev, void *cgi_cb_data)
497 {
498 	struct cgi_callback_data *cb = cgi_cb_data;
499 	struct evhttp_request *req = cb->req;
500 
501 	evbuffer_add_buffer(req->output_buffer, bufferevent_get_input(bev));
502 	return;
503 }
504 
505 #if defined(HAVE_BLACKLISTD)
506 static void
do_blacklist(struct evhttp_request * req,int action)507 do_blacklist(struct evhttp_request *req, int action)
508 {
509 	/*
510  	 * WARNING: ugly hack
511 	 * https://stackoverflow.com/questions/39179676/forward-declaration-of-struct-while-retrieving-libevent-connection-information
512 	 * https://github.com/libevent/libevent/issues/120
513 	 * TL;DR libevent hide the file descriptor required by blacklistd
514 	 * struct evhttp_connection *con = evhttp_request_get_connection(req)
515 	 * struct evhttp_connection {
516 	 *   TAILQ_ENTRY(evhttp_connection) next;
517 	 *   evutil_socket_t fd;
518 	 * }
519 	 * #define evutil_socket_t int
520 	*/
521 	int ret;
522 	struct ugly_hack_struct {
523 		TAILQ_ENTRY(evhttp_connection) next;
524 		evutil_socket_t fd;
525 	};
526 	struct ugly_hack_struct *ugly_hack_pointer = (struct ugly_hack_struct *)evhttp_request_get_connection(req);
527 	if (ugly_hack_pointer == NULL) { return; }
528 
529 #ifdef USE_DEBUG
530 	if (conf.debug) {
531 		fprintf(stderr, "Debug: req fd: %d\n", ugly_hack_pointer->fd);
532 		fprintf(stderr, "Debug: blacklist %s\n", req->remote_host);
533 	}
534 #endif
535 	if (!conf.debug && (ret = blacklist_r(blstate, action, ugly_hack_pointer->fd, "mohawk")) != 0) {
536 #ifdef USE_DEBUG
537 		if (conf.debug)
538 			fprintf(stderr, "Debug: blacklist_r %d return: %d\n", action, ret);
539 #endif
540 		return;
541 	}
542 	return;
543 }
544 #endif
545 
546 static void
do_cgi(struct evhttp_request * req,void * cb_data,const char * fs_path,struct stat * st)547 do_cgi(struct evhttp_request *req, void *cb_data, const char *fs_path, struct stat *st)
548 {
549 	char *argp[2];
550 	evutil_socket_t fds[2];
551 	pid_t pid;
552 	struct bufferevent *bev;
553 	struct cgi_callback_data *cgi_cb_data;
554 	struct callback_data *cb = cb_data;
555 	struct vhost *vh = cb->vh;
556 	char **envp = NULL;
557 	size_t size = evbuffer_get_length(req->input_buffer);
558 
559 #ifdef USE_DEBUG
560 	if (conf.debug) {
561 		fprintf(stderr, "Debug: do_cgi '%s'\n", fs_path);
562 		do_cgi_debug(req, size);
563 	}
564 #endif
565 
566 	/* not executable */
567 	if (!(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
568 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi - '%s' not executable", fs_path);
569 		return;
570 	}
571 
572 	if ((cgi_cb_data = malloc(sizeof (struct cgi_callback_data))) == NULL) {
573 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi - unable to allocate memory");
574 		return;
575 	}
576 
577 	cgi_cb_data->req = req;
578 	cgi_cb_data->vhost = vh;
579 
580 	if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
581 		free(cgi_cb_data);
582 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi - evutil_socketpair != 0");
583 		return;
584 	}
585 
586 	if ((pid = fork()) < 0) {
587 		free(cgi_cb_data);
588 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi - fork < 0");
589 		/* clean evutil_socketpair / fds ? */
590 		return;
591 	}
592 	else if (pid == 0) { /* child */
593 		if ((envp = do_cgi_env(req, cb_data, fs_path, size)) == NULL)
594 			return;
595 
596 #ifdef USE_DEBUG
597 		if (conf.debug) {
598 			fprintf(stderr, "Debug: do_cgi_env\n");
599 			do_cgi_env_debug(envp);
600 		}
601 #endif
602 		argp[0] = strrchr(fs_path, '/');
603 		argp[1] = NULL;
604 
605 		close(STDIN_FILENO);
606 		close(STDOUT_FILENO);
607 		close(STDERR_FILENO);
608 		close(fds[0]);
609 		dup2(fds[1], STDIN_FILENO);
610 		dup2(fds[1], STDOUT_FILENO);
611 		dup2(fds[1], STDERR_FILENO);
612 
613 		execve(fs_path, argp, envp);
614 		exit(EXIT_SUCCESS);
615 	}
616 
617 	/* parent */
618 	close(fds[1]);
619 	if (evbuffer_get_length(req->input_buffer))
620 		evbuffer_write(req->input_buffer, fds[0]);
621 	bev = bufferevent_socket_new(evhttp_connection_get_base(evhttp_request_get_connection(req)), fds[0], BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
622 	bufferevent_setcb(bev, cgi_read_cb, NULL, cgi_event_cb, cgi_cb_data);
623 	bufferevent_enable(bev, EV_READ);
624 
625 	return;
626 }
627 
628 #ifdef USE_DEBUG
629 #define DEBUG_MAX_POSTDATA 256
630 static void
do_cgi_debug(struct evhttp_request * req,size_t size)631 do_cgi_debug(struct evhttp_request *req, size_t size)
632 {
633 	char *postdata;
634 
635 	if ((postdata = malloc(size + 1)) == NULL) {
636 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
637 		fprintf(stderr, "Debug: do_cgi - can't malloc %zd\n", size);
638 #else
639 		fprintf(stderr, "Debug: do_cgi - can't malloc %" PRId64 "\n", size);
640 #endif
641 		return;
642 	}
643 
644 	/* TODO: what's append if evbuffer_copyout < size ? */
645 	if (evbuffer_copyout(req->input_buffer, postdata, size) < 0) {
646 		free(postdata);
647 		fprintf(stderr, "Debug: do_cgi_debug - copyout return -1\n");
648 		return;
649 	}
650 	postdata[size] = '\0';
651 
652 	fprintf(stderr, "\n[%.*s%s]\n",
653 		size <= DEBUG_MAX_POSTDATA ? (int)size : DEBUG_MAX_POSTDATA,
654 		postdata,
655 		size <= DEBUG_MAX_POSTDATA ? "" : "...");
656 
657 	return;
658 }
659 #endif
660 
661 enum var_env {
662 	CONTENT_LENGTH,    // * size of data
663 	DOCUMENT_ROOT,     // * The root directory of your server
664 	GATEWAY_INTERFACE, // * CGI/1.1
665 	HTTP_COOKIE,       // * The visitor's cookie, if one is set
666 	HTTP_HOST,         // * The hostname of the page being attempted
667 	HTTP_REFERER,      // * The URL of the page that called your program
668 	HTTP_USER_AGENT,   // * The browser type of the visitor
669 	HTTPS,             // * "on" if the program is being called through a secure server
670 	LD_LIBRARY_PATH,   // *
671 	PATH,              // * The system path your server is running under
672 	QUERY_STRING,      // * The query string (see GET, below)
673 	REMOTE_ADDR,       // * The IP address of the visitor
674 	REMOTE_HOST,       // * The hostname of the visitor (if your server has reverse-name-lookups on; otherwise this is the IP address again)
675 	REMOTE_PORT,       // * The port the visitor is connected to on the web server
676 	REMOTE_USER,       // * The visitor's username (for .htaccess-protected pages)
677 	REQUEST_METHOD,    // * GET or POST
678 	REQUEST_URI,       // * The interpreted pathname of the requested document or CGI (relative to the document root)
679 	SCRIPT_FILENAME,   // * The full pathname of the current CGI
680 	SCRIPT_NAME,       // * The interpreted pathname of the current CGI (relative to the document root)
681 	SERVER_ADDR,       // *
682 	SERVER_ADMIN,      // * The email address for your server's webmaster
683 	SERVER_NAME,       // * Your server's fully qualified domain name (e.g. www.cgi101.com)
684 	SERVER_PORT,       // * The port number your server is listening on
685 	SERVER_SOFTWARE,   // * The server software you're using (e.g. Apache 1.3)
686 	TZ,                // * timezone, if defined
687 	VERSION_MOHAWK,    // *
688 	NB_VAR_ENV         // *
689 };
690 
691 static char **
do_cgi_env(struct evhttp_request * req,void * cb_data,const char * filepath,size_t size)692 do_cgi_env(struct evhttp_request *req, void *cb_data, const char *filepath, size_t size)
693 {
694 	const char *header, *user;
695 	char buf[PATH_MAX];
696 	char **envp;
697 	int i;
698 	struct evhttp_uri *uri;
699 	struct callback_data *cb = cb_data;
700 	struct vhost *vh = cb->vh;
701 
702 	if ((envp = calloc(vh->cgi_env_count + NB_VAR_ENV, sizeof(char *))) == NULL) {
703 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi_env - can't calloc");
704 		return NULL;
705 	}
706 
707 	if ((uri = get_clean_uri(req)) == NULL) {
708 		do_error(req, vh, 400, NULL, "do_cgi_env - can't clean uri");
709 		return NULL;
710 	}
711 
712 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
713 	snprintf(buf, PATH_MAX, "CONTENT_LENGTH=%zd", size);
714 #else
715 	snprintf(buf, PATH_MAX, "CONTENT_LENGTH=%" PRId64 "", size);
716 #endif
717 	if ((envp[CONTENT_LENGTH] = strdup(buf)) == NULL) goto done;
718 
719 	snprintf(buf, PATH_MAX, "DOCUMENT_ROOT=%s", vh->rootdir);
720 	if ((envp[DOCUMENT_ROOT] = strdup(buf)) == NULL) goto done;
721 
722 	if ((envp[GATEWAY_INTERFACE] = strdup("GATEWAY_INTERFACE=CGI/1.1")) == NULL) goto done;
723 
724 	if ((header = evhttp_find_header(req->input_headers, "Cookie")) != NULL) {
725 		snprintf(buf, PATH_MAX, "HTTP_COOKIE=%s", header);
726 		if ((envp[HTTP_COOKIE] = strdup(buf)) == NULL) goto done;
727 	}
728 	else
729 		if ((envp[HTTP_COOKIE] = strdup("HTTP_COOKIE=")) == NULL) goto done;
730 
731 	snprintf(buf, PATH_MAX, "HTTP_HOST=%s", evhttp_request_get_host(req));
732 	if ((envp[HTTP_HOST] = strdup(buf)) == NULL) goto done;
733 
734 	if ((header = evhttp_find_header(req->input_headers, "Referer")) != NULL) {
735 		snprintf(buf, PATH_MAX, "HTTP_REFERER=%s", header);
736 		if ((envp[HTTP_REFERER] = strdup(buf)) == NULL) goto done;
737 	}
738 	else
739 		if ((envp[HTTP_REFERER] = strdup("HTTP_REFERER=")) == NULL) goto done;
740 
741 	if ((header = evhttp_find_header(req->input_headers, "User-Agent")) != NULL) {
742 		snprintf(buf, PATH_MAX, "HTTP_USER_AGENT=%s", header);
743 		if ((envp[HTTP_USER_AGENT] = strdup(buf)) == NULL) goto done;
744 	}
745 	else
746 		if ((envp[HTTP_USER_AGENT] = strdup("HTTP_USER_AGENT=")) == NULL) goto done;
747 
748 	if ((envp[HTTPS] = strdup("HTTPS=off")) == NULL) goto done;
749 
750 	snprintf(buf, PATH_MAX, "LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH);
751 	if ((envp[LD_LIBRARY_PATH] = strdup(buf)) == NULL) goto done;
752 
753 	snprintf(buf, PATH_MAX, "PATH=%s", CGI_PATH);
754 	if ((envp[PATH] = strdup(buf)) == NULL) goto done;
755 
756 	if (evhttp_uri_get_query(uri)) {
757 		snprintf(buf, PATH_MAX, "QUERY_STRING=%s", evhttp_uri_get_query(uri));
758 		if ((envp[QUERY_STRING] = strdup(buf)) == NULL) goto done;
759 	}
760 	else
761 		if ((envp[QUERY_STRING] = strdup("QUERY_STRING=")) == NULL) goto done;
762 
763 	snprintf(buf, PATH_MAX, "REMOTE_ADDR=%s", req->remote_host);
764 	if ((envp[REMOTE_ADDR] = strdup(buf)) == NULL) goto done;
765 
766 	snprintf(buf, PATH_MAX, "REMOTE_HOST=%s", req->remote_host);
767 	if ((envp[REMOTE_HOST] = strdup(buf)) == NULL) goto done;
768 
769 	snprintf(buf, PATH_MAX, "REMOTE_PORT=%d", req->remote_port);
770 	if ((envp[REMOTE_PORT] = strdup(buf)) == NULL) goto done;
771 
772 	user = evhttp_find_header(req->input_headers, "Mohawk-Authenticate-User");
773 	snprintf(buf, PATH_MAX, "REMOTE_USER=%s", user ? user : "");
774 	if ((envp[REMOTE_USER] = strdup(buf)) == NULL) goto done;
775 
776 	snprintf(buf, PATH_MAX, "REQUEST_METHOD=%s", methods[evhttp_request_get_command(req)]);
777 	if ((envp[REQUEST_METHOD] = strdup(buf)) == NULL) goto done;
778 
779 	/* TODO: evhttp_request_get_uri is not interpreted */
780 	snprintf(buf, PATH_MAX, "REQUEST_URI=%s", evhttp_request_get_uri(req));
781 	if ((envp[REQUEST_URI] = strdup(buf)) == NULL) goto done;
782 
783 	snprintf(buf, PATH_MAX, "SCRIPT_FILENAME=%s", filepath);
784 	if ((envp[SCRIPT_FILENAME] = strdup(buf)) == NULL) goto done;
785 
786 	snprintf(buf, PATH_MAX, "SCRIPT_NAME=%s", evhttp_uri_get_path(uri));
787 	if ((envp[SCRIPT_NAME] = strdup(buf)) == NULL) goto done;
788 
789 	snprintf(buf, PATH_MAX, "SERVER_ADMIN=%s", vh->email_admin ? vh->email_admin : "");
790 	if ((envp[SERVER_ADMIN] = strdup(buf)) == NULL) goto done;
791 
792 	snprintf(buf, PATH_MAX, "SERVER_ADDR=%s", cb->host);
793 	if ((envp[SERVER_ADDR] = strdup(buf)) == NULL) goto done;
794 
795 	snprintf(buf, PATH_MAX, "SERVER_NAME=%s", evhttp_request_get_host(req));
796 	if ((envp[SERVER_NAME] = strdup(buf)) == NULL) goto done;
797 
798 	snprintf(buf, PATH_MAX, "SERVER_PORT=%d", cb->port);
799 	if ((envp[SERVER_PORT] = strdup(buf)) == NULL) goto done;
800 
801 	snprintf(buf, PATH_MAX, "SERVER_SOFTWARE=%s", vh->mohawk_name);
802 	if ((envp[SERVER_SOFTWARE] = strdup(buf)) == NULL) goto done;
803 
804 	if (getenv("TZ")) {
805 		snprintf(buf, PATH_MAX, "TZ=%s", getenv("TZ"));
806 		if ((envp[TZ] = strdup(buf)) == NULL) goto done;
807 	}
808 	else
809 		if ((envp[TZ] = strdup("TZ=")) == NULL) goto done;
810 
811 	if (vh->cgi_expose_mohawk_version) {
812 		snprintf(buf, PATH_MAX, "MOHAWK_VERSION=%s", MOHAWK_VERSION);
813 		if ((envp[VERSION_MOHAWK] = strdup(buf)) == NULL) goto done;
814 	}
815 	else
816 		if ((envp[VERSION_MOHAWK] = strdup("MOHAWK_VERSION=")) == NULL) goto done;
817 
818 	envp[NB_VAR_ENV] = (char*)0;
819 
820 	return envp;
821 
822 done:
823 	for (i = 0; i < NB_VAR_ENV; i++)
824 		if (envp[i])
825 			free(envp[i]);
826 
827 	if (envp)
828 		free(envp);
829 
830 	evhttp_uri_free(uri);
831 	do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi_env - can't strdup");
832 	return NULL;
833 }
834 
835 #ifdef USE_DEBUG
836 static void
do_cgi_env_debug(char ** envp)837 do_cgi_env_debug(char **envp)
838 {
839 	int i = 0;
840 	while(i < NB_VAR_ENV)
841 		fprintf(stderr, "  %s\n", envp[i++]);
842 	if (i != NB_VAR_ENV)
843 		fprintf(stderr, "  loose %d\n", NB_VAR_ENV - i);
844 
845 	return;
846 }
847 #endif
848 
849 static void
do_error(struct evhttp_request * req,struct vhost * vh,int code,const char * reason,const char * fmt,...)850 do_error(struct evhttp_request *req, struct vhost *vh, int code, const char *reason, const char *fmt, ...)
851 {
852 	va_list ap;
853 
854 	va_start(ap, fmt);
855 
856 	if (conf.debug) {
857 #ifdef USE_DEBUG
858 		fprintf(stderr, "Debug: do_error %d '%s' -> ", code, reason ? reason : "");
859 		vfprintf(stderr, fmt, ap);
860 		fprintf(stderr, "\n");
861 #else
862 		vsyslog(LOG_ERR, fmt, ap);
863 #endif
864 	}
865 
866 	va_end(ap);
867 
868 	evhttp_send_error(req, code, reason);
869 	do_log(req, vh, 0);
870 
871 	return;
872 }
873 
874 static const char *
do_fs_content_type_lookup(const char * filepath)875 do_fs_content_type_lookup(const char *filepath)
876 {
877 	struct mohawk_hash *mime_type;
878 	char *ext, *slash;
879 
880 	if ((slash = strrchr(filepath, '/')) == NULL)
881 		goto done;
882 	if ((ext = strrchr(slash, '.')) == NULL)
883 		goto done;
884 
885 	ext++;
886 	if (*ext == '\0')
887 		goto done;
888 
889 	SLIST_FOREACH(mime_type, &conf.mime_type, entry) {
890 #ifdef USE_DEBUG
891 		fprintf(stderr, "Debug: search '%s' in '%s'\n", ext, mime_type->value);
892 #endif
893 		if (strcasestr(mime_type->value, ext) != NULL)
894 			return mime_type->key;
895 	}
896 
897 done:
898 	return "application/octet-stream";
899 }
900 
901 #define FMT_PARENT "<tr><td class='n'><a href='%s'>..</a></td><td class='m'>-</td><td class='s'>-</td><td class='t'>Directory</td></tr>\n"
902 
903 #define FMT_HEAD "<html>\n\
904 <head><title>Index of %s</title>\n"
905 
906 #define FMT_BODY "<body>\n\
907 <h2>Index of %s</h2>\n\
908 <div class='list'>\n\
909 <table summary='Directory Listing' cellpadding='0' cellspacing='0'>\n\
910 <thead><tr><th class='n'>Name</th><th class='m'>Last Modified</th><th class='s'>Size</th><th class='t'>Type</th></tr></thead>\n\
911 <tbody>\n"
912 
913 #define FMT_FOOTER "</tbody>\n\
914 </table>\n\
915 </div>\n\
916 <div class='foot'>%s/%s</div>\n\
917 </body>\n\
918 </html>\n"
919 
920 static void
do_fs_dir(struct evhttp_request * req,struct vhost * vh,const char * fs_path,const char * uri_path)921 do_fs_dir(struct evhttp_request *req, struct vhost *vh, const char *fs_path, const char *uri_path)
922 {
923 	int command, i, len = 0, len_item = 0, n;
924 	char buf[MAX_SIZE_INT];
925 	char parent_dir[PATH_MAX];
926 	char *pos;
927 	struct dirent **dl;
928 	struct evbuffer *evb;
929 
930 #ifdef USE_DEBUG
931 	if (conf.debug)
932 		fprintf(stderr, "Debug: do_fs_dir '%s' '%s'\n", fs_path, uri_path);
933 #endif
934 
935 	if (vh->dirlist < 1) {
936 		do_error(req, vh, HTTP_NOTFOUND, NULL, "do_fs_dir - but dirlist < 1");
937 		return;
938 	}
939 
940 	if ((evb = evbuffer_new()) == NULL) {
941 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_dir - unable to allocate memory");
942 		return;
943 	}
944 
945 	n = scandir(fs_path, &dl, NULL, alphasort);
946 	if (n < 0) {
947 		evbuffer_free(evb);
948 		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "do_fs_dir - scandir '%s' return %d", fs_path, n);
949 		return;
950 	}
951 
952 	len += evbuffer_add_printf(evb, FMT_HEAD, uri_path);
953 	if (vh->dirlist_css_url) {
954 		len += evbuffer_add_printf(evb, "<link href='%s' rel='stylesheet' type='text/css' />\n", vh->dirlist_css_url);
955 	}
956 	len += evbuffer_add_printf(evb, FMT_BODY, uri_path);
957 
958 	for (i = 0; i < n; ++i) {
959 		if (strcmp(dl[i]->d_name, ".") == 0)
960 			continue;
961 		if (strcmp(dl[i]->d_name, "..") == 0) {
962 			if (strcmp("/", uri_path) == 0)
963 				continue;
964 
965 			/* parent directory, strip the last relevant '/' */
966 			if (strlcpy(parent_dir, uri_path, PATH_MAX) > sizeof(parent_dir)) {
967 				evbuffer_free(evb);
968 				do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_dir - strlcpy");
969 				return;
970 			}
971 			pos = strrchr(parent_dir, '/');
972 			if (*(pos + 1) == '\0') {
973 				*pos = '\0';
974 				pos = strrchr(parent_dir, '/');
975 			}
976 			if (pos == parent_dir)
977 				parent_dir[1] = '\0';
978 			else
979 				*pos = '\0';
980 			len += evbuffer_add_printf(evb, FMT_PARENT, parent_dir);
981 			continue;
982 		}
983 
984 		if ((len_item = do_fs_file_details(req, vh, evb, fs_path, dl[i]->d_name, uri_path)) < 0)
985 			goto done;
986 
987 		len += len_item;
988 	}
989 
990 	len += evbuffer_add_printf(evb, FMT_FOOTER, vh->mohawk_name ? "" : getprogname(), vh->mohawk_name ? vh->mohawk_name : MOHAWK_VERSION);
991 	snprintf(buf, sizeof(buf), "%d", len);
992 	evhttp_add_header(req->output_headers, "Content-Length", buf);
993 	command = evhttp_request_get_command(req);
994 	if (command == EVHTTP_REQ_HEAD)
995 		evhttp_add_header(req->output_headers, "Content-Type", "text/html");
996 	evhttp_send_reply(req, HTTP_OK, "OK", command == EVHTTP_REQ_HEAD ? NULL : evb);
997 	do_log(req, vh, command == EVHTTP_REQ_HEAD ? 0 : len); // s/0/sizeof headers/
998 
999 done:
1000 	evbuffer_free(evb);
1001 	for (i = 0; i < n; ++i)
1002 		free(dl[i]);
1003 	free(dl);
1004 	return;
1005 }
1006 
1007 static void
do_fs_file(struct evhttp_request * req,void * cb_data,const char * filepath,struct stat * st)1008 do_fs_file(struct evhttp_request *req, void *cb_data, const char *filepath, struct stat *st)
1009 {
1010 	char buf[PATH_MAX];
1011 	time_t t;
1012 	struct mohawk_list *cgi_pattern;
1013 	struct callback_data *cb = cb_data;
1014 	struct vhost *vh = cb->vh;
1015 	char *content_type = NULL;
1016 	struct evbuffer *evb = NULL;
1017 	int fd, command = evhttp_request_get_command(req);
1018 #if defined(__FreeBSD__)
1019 	cap_rights_t rights;
1020 #endif
1021 
1022 #ifdef USE_DEBUG
1023 	if (conf.debug)
1024 		fprintf(stderr, "Debug: do_fs_file '%s'\n", filepath);
1025 #endif
1026 
1027 	SLIST_FOREACH(cgi_pattern, &vh->cgi_patterns, entry) {
1028 		if (fnmatch(cgi_pattern->item, filepath, FNM_CASEFOLD) == 0) {
1029 #ifdef USE_DEBUG
1030 			if (conf.debug)
1031 				fprintf(stderr, "Debug: do_fs_file cgi_pattern match '%s'\n", cgi_pattern->item);
1032 #endif
1033 			if (command == EVHTTP_REQ_HEAD) {
1034 				evhttp_send_reply(req, HTTP_BADMETHOD, "Bad method", NULL);
1035 			}
1036 			else {
1037 				do_cgi(req, cb_data, filepath, st);
1038 			}
1039 			return;
1040 		}
1041 	}
1042 
1043 	if ((fd = open(filepath, O_RDONLY)) < 0) {
1044 		do_error(req, vh, HTTP_NOTFOUND, NULL, "do_fs_file - open '%s' return < 0", filepath);
1045 		return;
1046 	}
1047 #if defined(__FreeBSD__)
1048 	cap_rights_init(&rights, CAP_READ, CAP_MMAP_R);
1049 	if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS) {
1050 		close(fd);
1051 		return;
1052 	}
1053 #endif
1054 
1055 	if ((evb = evbuffer_new()) == NULL) {
1056 		close(fd);
1057 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_file - unable to allocate memory");
1058 		return;
1059 	}
1060 #define MAX_CONTENT_TYPE 128
1061 	if ((content_type = malloc(MAX_CONTENT_TYPE)) == NULL) {
1062 		close(fd);
1063 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_file - unable to allocate memory");
1064 		goto done;
1065 	}
1066 
1067 #define FMT_RFC1123 "%a, %d %b %Y %H:%M:%S GMT"
1068 	strftime(buf, PATH_MAX, FMT_RFC1123, gmtime(&st->st_mtime));
1069 	evhttp_add_header(req->output_headers, "Last-Modified", buf);
1070 	if (vh->maxage >= 0) {
1071 		t = time((time_t*) 0) + vh->maxage;
1072 		strftime(buf, PATH_MAX, FMT_RFC1123, gmtime(&t));
1073 		evhttp_add_header(req->output_headers, "Expires", buf);
1074 		snprintf(buf, PATH_MAX, "max-age=%d", vh->maxage);
1075 		evhttp_add_header(req->output_headers, "Cache-Control", buf);
1076 	}
1077 
1078 #if defined(__FreeBSD__) || defined(__DragonFly__)
1079 	snprintf(buf, PATH_MAX, "%jd", (intmax_t)st->st_size);
1080 #elif defined(__OpenBSD__)
1081 	snprintf(buf, PATH_MAX, "%lld", st->st_size);
1082 #else
1083 	snprintf(buf, PATH_MAX, "%" PRId64 "", st->st_size);
1084 #endif
1085 	evhttp_add_header(req->output_headers, "Content-Length", buf);
1086 
1087 	if (vh->charset)
1088 		snprintf(content_type, MAX_CONTENT_TYPE, "%s; charset=%s",
1089 			do_fs_content_type_lookup(filepath), vh->charset);
1090 	else
1091 		snprintf(content_type, MAX_CONTENT_TYPE, "%s", do_fs_content_type_lookup(filepath));
1092 
1093 	evhttp_add_header(req->output_headers, "Content-Type", content_type);
1094 
1095 	if (parse_date(evhttp_find_header(req->input_headers, "If-Modified-Since")) >= st->st_mtime) {
1096 		close(fd);
1097 		evhttp_send_reply(req, HTTP_NOTMODIFIED, "Not Modified", NULL);
1098 		do_log(req, vh, 0);
1099 		goto done;
1100 	}
1101 
1102 	/* do not send file if size == 0 or method == HEAD */
1103 	if (st->st_size && evhttp_request_get_command(req) != EVHTTP_REQ_HEAD)
1104 		evbuffer_add_file(evb, fd, 0, st->st_size);
1105 	else {
1106 		if (fd)
1107 			close(fd);
1108 	}
1109 
1110 	evhttp_send_reply(req, HTTP_OK, "OK", evb);
1111 
1112 	/*
1113 	 * WARNING:
1114 	 * request is logged before sending full data.
1115 	 * client can close the socket before receive full data
1116 	 */
1117 
1118 	do_log(req, vh, st->st_size);
1119 
1120 done:
1121 	if (evb)
1122 		evbuffer_free(evb);
1123 	if (content_type)
1124 		free(content_type);
1125 
1126 	return;
1127 }
1128 
1129 #define FMT_DIRECTORY "<tr><td class='n'><a href='%s%s/'>%s/</a></td><td class='m'>%s</td><td class='s'>-</td><td class='t'>Directory</td></tr>\n"
1130 #define FMT_FILE "<tr><td class='n'><a href='%s%s'>%s</a></td><td class='m'>%s</td><td class='s'>%s</td><td class='t'>%s</td></tr>\n"
1131 static int
do_fs_file_details(struct evhttp_request * req,struct vhost * vh,struct evbuffer * evb,const char * path,const char * name,const char * uri_path)1132 do_fs_file_details(struct evhttp_request *req, struct vhost *vh, struct evbuffer *evb, const char *path, const char *name, const char *uri_path)
1133 {
1134 	struct stat st;
1135 	char f_time[20];
1136 	char entry[PATH_MAX];
1137 #if defined(__OpenBSD__)
1138 	char sz[FMT_SCALED_STRSIZE];
1139 #else
1140 	char sz[6];
1141 #endif
1142 	const char *mime = NULL;
1143 	char *name_encoded = NULL;
1144 	int nb;
1145 
1146 	if ((nb = snprintf(entry, PATH_MAX, "%s/%s", path, name)) >= PATH_MAX) {
1147 		do_error(req, vh, HTTP_INTERNAL, "filename too long", " do_fs_file_details - '%s' + '%s' return %d (>= %d)", path, name, nb, PATH_MAX);
1148 		return -1;
1149 	}
1150 
1151 	if (lstat(entry, &st) < 0) {
1152 		return 0;
1153 	}
1154 
1155 	strftime(f_time, sizeof(f_time), "%d-%b-%Y %H:%M", localtime(&st.st_mtime));
1156 #if defined(__OpenBSD__)
1157 	fmt_scaled(st.st_size, sz);
1158 #else
1159 	humanize_number(sz, 6, st.st_size, "", HN_AUTOSCALE, HN_B | HN_DECIMAL | HN_NOSPACE);
1160 #endif
1161 
1162 	mime = do_fs_content_type_lookup(name);
1163 	name_encoded = evhttp_encode_uri(name);
1164 
1165 	if (st.st_mode & S_IFDIR)
1166 		nb = evbuffer_add_printf(evb, FMT_DIRECTORY, uri_path, name_encoded, name, f_time);
1167 	else
1168 		nb = evbuffer_add_printf(evb, FMT_FILE, uri_path, name_encoded, name, f_time, sz, mime);
1169 
1170 	free(name_encoded);
1171 
1172 	return nb;
1173 }
1174 
1175 static void
do_location(struct evhttp_request * req,struct callback_data * cb_data,const char * path,const char * query)1176 do_location(struct evhttp_request *req, struct callback_data *cb_data, const char *path, const char *query)
1177 {
1178 	char location[PATH_MAX];
1179 
1180 #ifdef USE_DEBUG
1181 	if (conf.debug)
1182 		fprintf(stderr, "Debug: do_location '%s'\n", path);
1183 #endif
1184 
1185 	/*
1186 	 * Build location: http$0://$1$2$3$4/$5$6
1187 	 * $0 == ssl ? "s" : ""
1188 	 * $1 == evhttp_request_get_host(req) || cb_data->host
1189 	 * $2 cb_data->port == DEFAULT_HTTP_PORT ? "" : ":"
1190 	 * $3 cb_data->port == DEFAULT_HTTP_PORT ? "" : cb_data->port
1191 	 * $4 path
1192 	 * $5 uri ? "?" : ""
1193 	 * $6 uri ? query : ""
1194 	 */
1195 #define FMT_LOCATION "http%s://%s%s%s%s/%s%s"
1196 
1197 	/*
1198 	 * defaul_http_port is a string, not a integer.
1199 	 * cb_data->port is a integer, cb_data->service is a string.
1200 	 * sorry
1201 	 */
1202 	snprintf(location, PATH_MAX, FMT_LOCATION,
1203 		"",
1204 		evhttp_request_get_host(req) ? evhttp_request_get_host(req) : cb_data->host,
1205 		strcmp(cb_data->service, DEFAULT_HTTP_PORT) == 0 ? "" : ":",
1206 		strcmp(cb_data->service, DEFAULT_HTTP_PORT) == 0 ? "" : cb_data->service,
1207 		path,
1208 		query ? "?" : "",
1209 		query ? query : "");
1210 
1211 #ifdef USE_DEBUG
1212 	if (conf.debug)
1213 		fprintf(stderr, "Debug: do_location '%s'\n", location);
1214 #endif
1215 
1216 	evhttp_add_header(req->output_headers, "Location", location);
1217 	evhttp_send_reply(req, HTTP_MOVEPERM, "Moved Permanently", NULL);
1218 
1219 	/* FIXME no do_log here ? */
1220 	return;
1221 }
1222 
1223 static void
do_log(struct evhttp_request * req,struct vhost * vh,unsigned long long size)1224 do_log(struct evhttp_request *req, struct vhost *vh, unsigned long long size)
1225 {
1226 	const char *user, *x_forwarded_for;
1227 	struct mohawk_list *no_log_pattern;
1228 
1229 	const char *uri = evhttp_request_get_uri(req);
1230 
1231 #ifdef USE_DEBUG
1232 	if (conf.debug)
1233 		fprintf(stderr, "Debug: do_log\n");
1234 #endif
1235 
1236 	/* FMT_LOG host remote_addr remote_user "method url protocol" status size */
1237 #define FMT_LOG "%s %s %s \"%s %s HTTP/%d.%d\" %d %llu"
1238 
1239 	if (!conf.debug) {
1240 		SLIST_FOREACH(no_log_pattern, &vh->no_log_patterns, entry) {
1241 			if (fnmatch(no_log_pattern->item, uri, 0) != FNM_NOMATCH) {
1242 				return;
1243 			}
1244 		}
1245 	}
1246 
1247 	x_forwarded_for = req->remote_host;
1248 	if (vh->x_forwarded_for) {
1249 		x_forwarded_for = evhttp_find_header(req->input_headers, "X-Forwarded-For");
1250 		if (!x_forwarded_for)
1251 			x_forwarded_for = req->remote_host;
1252 	}
1253 
1254 	user = evhttp_find_header(req->input_headers, "Mohawk-Authenticate-User");
1255 
1256 	if (!conf.debug)
1257 		syslog(LOG_INFO, FMT_LOG,
1258 			evhttp_request_get_host(req) ? evhttp_request_get_host(req) : "Unknow",
1259 			x_forwarded_for,
1260 			user ? user : "-",
1261 			methods[evhttp_request_get_command(req)],
1262 			uri,
1263 			req->major, req->minor,
1264 			evhttp_request_get_response_code(req),
1265 			size);
1266 	else
1267 		fprintf(stderr, "Debug: " FMT_LOG "\n",
1268 			evhttp_request_get_host(req) ? evhttp_request_get_host(req) : "Unknow",
1269 			x_forwarded_for,
1270 			user ? user : "-",
1271 			methods[evhttp_request_get_command(req)],
1272 			uri,
1273 			req->major, req->minor,
1274 			evhttp_request_get_response_code(req),
1275 			size);
1276 	return;
1277 }
1278 
1279 static void
do_request(struct evhttp_request * req,void * cb_data)1280 do_request(struct evhttp_request *req, void *cb_data)
1281 {
1282 	struct evhttp_uri *uri;
1283 	struct mohawk_path paths, cgi_paths;
1284 	struct evkeyval *header;
1285 	char *indexfile, *filename;
1286 	char fs_path[PATH_MAX];
1287 	struct stat st, st_index;
1288 	size_t len;
1289 	struct callback_data *cb = cb_data, *cb_data_location = NULL;
1290 	struct vhost *vh = cb->vh;
1291 	int command, i, ret = -2;
1292 
1293 	if (!access_granted(req, cb))
1294 		return; /* access denied */
1295 
1296 	paths.nb = 0;
1297 	if ((uri = get_clean_uri(req)) == NULL) {
1298 		do_error(req, cb->vh, HTTP_BADREQUEST, NULL, "do_request - can't clean uri");
1299 		goto done;
1300 	}
1301 
1302 	if ((paths.paths[0] = strdup(evhttp_uri_get_path(uri))) == NULL) {
1303 		do_error(req, cb->vh, HTTP_INTERNAL, NULL, "do_request - unable to allocate memory");
1304 		goto done;
1305 	}
1306 
1307 	paths.nb = 1;
1308 
1309 	request_decode_paths(&paths);
1310 	paths.offset = 0;
1311 
1312 	if (conf.debug) {
1313 		fprintf(stderr, "Debug: uri '%s'\n", evhttp_request_get_uri(req));
1314 		for (i = 0; i < paths.nb; i++) {
1315 			fprintf(stderr, "Debug paths '%s'\n", paths.paths[i]);
1316 		}
1317 		if (evhttp_uri_get_query(uri))
1318 			fprintf(stderr, "Debug: query '%s'\n", evhttp_uri_get_query(uri));
1319 
1320 		for (header = req->input_headers->tqh_first; header; header = header->next.tqe_next) {
1321 			fprintf(stderr, "  %s: %s\n", header->key, header->value);
1322 		}
1323 	}
1324 
1325 	/* may be 'usual case': head/get, no cgi_map, no authentication, no location, no blacklist_patterns, uri exists and it's a file */
1326 	command = evhttp_request_get_command(req);
1327 	if (command == EVHTTP_REQ_GET || command == EVHTTP_REQ_HEAD) {
1328 		if (vh->can_quick) {
1329 			if ((ret = get_fs_path(req, vh, &paths, fs_path, &st)) > -1) {
1330 				if (!S_ISDIR(st.st_mode)) {
1331 #ifdef USE_DEBUG
1332 					if (conf.debug)
1333 						fprintf(stderr, "Debug: quick do_fs_file '%s'\n", paths.paths[ret]);
1334 #endif
1335 					do_fs_file(req, cb_data, fs_path, &st);
1336 					goto done;
1337 				}
1338 			}
1339 		}
1340 	}
1341 
1342 #if defined(HAVE_BLACKLISTD)
1343 	if (!SLIST_EMPTY(&vh->blacklist_patterns)) {
1344 		if (blacklist_lookup(vh, &paths)) {
1345 #ifdef USE_DEBUG
1346 			if (conf.debug)
1347 				fprintf(stderr, "Debug: uri blacklisted\n");
1348 #endif
1349 			do_blacklist(req, BLACKLIST_ABUSIVE_BEHAVIOR);
1350 			do_error(req, cb->vh, HTTP_INTERNAL, NULL, "do_request - blacklist");
1351 			goto done;
1352 		}
1353 	}
1354 #endif
1355 
1356 	if (!SLIST_EMPTY(&vh->locations)) {
1357 		vh = request_location_lookup(vh, &paths);
1358 		if (vh == NULL) {
1359 			/* No location match, restore vh */
1360 			vh = cb->vh;
1361 		}
1362 		else {
1363 			if ((cb_data_location = malloc(sizeof(struct callback_data))) == NULL) {
1364 				do_error(req, cb->vh, HTTP_INTERNAL, NULL, "do_request - unable to allocate memory");
1365 				goto done;
1366 			}
1367 			cb_data_location->httpd = cb->httpd;
1368 			cb_data_location->vh = vh;
1369 			cb_data_location->host = cb->host;
1370 			cb_data_location->service = cb->service;
1371 			cb_data_location->port = cb->port;
1372 
1373 			cb = cb_data_location;
1374 			paths.offset = strlen(vh->location);
1375 #ifdef USE_DEBUG
1376 			if (conf.debug) {
1377 				for (i = 0; i < paths.nb; i++)
1378 					fprintf(stderr, "Debug: location '%s' paths '%s'\n", vh->location, paths.paths[i]);
1379 			}
1380 #endif
1381 			if (!access_granted(req, cb))
1382 				goto done; /* access denied */
1383 		}
1384 	}
1385 
1386 	if (vh->authentication > 0 && auth_lookup(vh, &paths) && !auth_check(req, vh))
1387 		goto done;
1388 
1389 	if ((filename = cgi_map_lookup(vh, &paths)) != NULL) {
1390 		if (command == EVHTTP_REQ_HEAD) {
1391 			evhttp_send_reply(req, HTTP_BADMETHOD, "Bad method", NULL);
1392 			goto done;
1393 		}
1394 		cgi_paths.offset = paths.offset;
1395 		cgi_paths.nb = 1;
1396 		cgi_paths.paths[0] = filename;
1397 		if (get_fs_path(req, vh, &cgi_paths, fs_path, &st) == -1) {
1398 			evhttp_send_reply(req, HTTP_NOTFOUND, "Not found", NULL);
1399 			goto done;
1400 		}
1401 
1402 		do_cgi(req, (void *)cb, fs_path, &st);
1403 		goto done;
1404 	}
1405 
1406 	if (ret == -1)
1407 		goto done;
1408 
1409 	if (ret == -2 && get_fs_path(req, vh, &paths, fs_path, &st) == -1)
1410 		goto done;
1411 
1412 	/* if fs_path is dir and paths.paths[0] don't end with / -> 301 */
1413 	len = strlen(paths.paths[0]);
1414 	if (S_ISDIR(st.st_mode) && paths.paths[0][len - 1] != '/') {
1415 		do_location(req, (void *)cb, paths.paths[0], evhttp_uri_get_query(uri));
1416 		goto done;
1417 	}
1418 
1419 	if (!S_ISDIR(st.st_mode)) {
1420 		do_fs_file(req, (void *)cb, fs_path, &st);
1421 		goto done;
1422 	}
1423 
1424 	/* If no index file in this directory */
1425 	if ((ret = request_index_lookup(vh, fs_path, &indexfile, &st_index)) == 0) {
1426 		do_fs_dir(req, vh, fs_path, paths.paths[0]);
1427 		goto done;
1428 	}
1429 	/* request_index_lookup can return -1 */
1430 	if (ret == -1) {
1431 		do_error(req, vh, HTTP_INTERNAL, NULL, "request_index_lookup - unable to allocate memory");
1432 		goto done;
1433 	}
1434 
1435 	do_fs_file(req, (void *)cb, indexfile, &st_index);
1436 	free(indexfile);
1437 
1438 done:
1439 	if (paths.nb != 0) {
1440 		for (i = 0; i > paths.nb; i++)
1441 			if (paths.paths[i])
1442 				free(paths.paths[i]);
1443 	}
1444 	if (cb_data_location)
1445 		free(cb_data_location);
1446 
1447 	if (uri)
1448 		evhttp_uri_free(uri);
1449 	return;
1450 }
1451 
1452 static void
do_status(struct evhttp_request * req,void * cb_data)1453 do_status(struct evhttp_request *req, void *cb_data)
1454 {
1455 	int len;
1456 	char buf[MAX_SIZE_INT + 1];
1457 	struct rusage ru;
1458 	struct evbuffer *evb;
1459 	struct callback_data *cb = cb_data;
1460 	struct vhost *vh = cb->vh;
1461 
1462 #ifdef USE_DEBUG
1463 	if (conf.debug)
1464 		fprintf(stderr, "Debug: do_status\n");
1465 #endif
1466 
1467 	if (!access_granted(req, cb))
1468 		return; /* access denied */
1469 
1470 	if ((evb = evbuffer_new()) == NULL) {
1471 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_status - unable to allocate memory");
1472 		return;
1473 	}
1474 
1475 	if (getrusage(RUSAGE_SELF, &ru) != 0) {
1476 		do_error(req, vh, HTTP_INTERNAL, NULL, "do_status - getrusage error");
1477 		goto done;
1478 	}
1479 
1480 #define STATUS_HEADER "stime\tutime\tmaxrss\tixrss\tidrss\tisrss\tminflt\tmajflt\tnswap\tinblock\toublock\tmsgsnd\tmsgrcv\tnsignals\tnvcsw\tnivcsw"
1481 
1482 #define FMT_STATUS "%s\n%lld.%06ld\t%lld.%06ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\n"
1483 
1484 	len = evbuffer_add_printf(evb, FMT_STATUS, STATUS_HEADER,
1485 			(long long int)ru.ru_stime.tv_sec, (long int)ru.ru_stime.tv_usec,
1486 			(long long int)ru.ru_utime.tv_sec, (long int)ru.ru_utime.tv_usec,
1487 			ru.ru_maxrss, ru.ru_ixrss, ru.ru_idrss, ru.ru_isrss, ru.ru_minflt, ru.ru_majflt,
1488 			ru.ru_nswap,ru.ru_inblock, ru.ru_oublock, ru.ru_msgsnd, ru.ru_msgrcv,
1489 			ru.ru_nsignals, ru.ru_nvcsw, ru.ru_nivcsw
1490 			);
1491 	snprintf(buf, sizeof(buf), "%d", len);
1492 	evhttp_add_header(req->output_headers, "Content-Length", buf);
1493 	evhttp_add_header(req->output_headers, "Content-Type", "text/plain");
1494 	evhttp_send_reply(req, HTTP_OK, "OK", evb);
1495 
1496 done:
1497 	evbuffer_free(evb);
1498 
1499 	return;
1500 }
1501 
1502 static int
do_vhost(struct event_base * eb,struct vhost * vh,int flag_keep)1503 do_vhost(struct event_base *eb, struct vhost *vh, int flag_keep)
1504 {
1505 	struct evhttp *httpd;
1506 	struct callback_data *cb_data, *item;
1507 	struct mohawk_address_info *ai;
1508 	char hostname[NI_MAXHOST];
1509 	char service[NI_MAXSERV];
1510 	int error, nb_listen = 0;
1511 
1512 #ifdef USE_DEBUG
1513 	if (conf.debug)
1514 		fprintf(stderr, "Debug: processing vhost '%s'\n", vh->host);
1515 #endif
1516 
1517 	SLIST_FOREACH(ai, &vh->address_infos, entry) {
1518 		if ((error = getnameinfo(ai->ai->ai_addr, ai->ai->ai_addrlen,
1519 			                     hostname, sizeof(hostname),
1520 			                     service, sizeof(service),
1521 			                     NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
1522 			warnx("getnameinfo(): %s", gai_strerror(error));
1523 		}
1524 
1525 		httpd = evhttp_new(eb);
1526 		evhttp_set_allowed_methods(httpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_HEAD);
1527 		if ((cb_data = malloc(sizeof(struct callback_data))) == NULL) {
1528 			errx(EXIT_FAILURE, "do_vhost - unable to allocate memory");
1529 		}
1530 		cb_data->httpd = httpd;
1531 		cb_data->vh = vh;
1532 		cb_data->host = strdup(hostname);
1533 		cb_data->service = strdup(service);
1534 		cb_data->port = parse_number(service, 1, IPPORT_MAX);
1535 
1536 		if (cb_data->port < IPPORT_RESERVED && flag_keep)
1537 			warnx("Listen on port < %d with flag_keep", IPPORT_RESERVED);
1538 
1539 		if (vh->status_url != NULL)
1540 			evhttp_set_cb(httpd, vh->status_url, do_status, cb_data);
1541 		evhttp_set_gencb(httpd, do_request, cb_data);
1542 
1543 		if (evhttp_bind_socket(cb_data->httpd, cb_data->host, cb_data->port) == 0) {
1544 			SLIST_INSERT_HEAD(&conf.cb_datas, cb_data, entry);
1545 #ifdef USE_DEBUG
1546 			if (conf.debug)
1547 				fprintf(stderr, "Debug: vhost '%s' listen on '%s' '%s'\n", vh->host, hostname, service);
1548 #endif
1549 			nb_listen++;
1550 		}
1551 		else {
1552 			/* Check if hostname/service is not listenning by another vhost */
1553 			error = 1;
1554 			SLIST_FOREACH(item, &conf.cb_datas, entry) {
1555 				if (item->port == cb_data->port && strcmp(item->host, cb_data->host) == 0) {
1556 					evhttp_add_virtual_host(item->httpd, vh->host, httpd);
1557 #ifdef USE_DEBUG
1558 					if (conf.debug)
1559 						fprintf(stderr, "Debug: add vhost '%s' to '%s' '%d'\n", vh->host, item->host, item->port);
1560 #endif
1561 					error = 0;
1562 					break;
1563 				}
1564 			}
1565 			if (error) {
1566 				free(cb_data->host);
1567 				free(cb_data->service);
1568 				free(cb_data);
1569 			}
1570 		}
1571 	}
1572 
1573 	return nb_listen;
1574 }
1575 
1576 static struct evhttp_uri *
get_clean_uri(struct evhttp_request * req)1577 get_clean_uri(struct evhttp_request *req)
1578 {
1579 	const char *uri = evhttp_request_get_uri(req);
1580 	const char *c;
1581 
1582 #ifdef USE_DEBUG
1583 	if (conf.debug)
1584 		fprintf(stderr, "Debug: from '%s' method '%s' url '%s'\n",
1585 			req->remote_host,  methods[evhttp_request_get_command(req)], uri);
1586 #endif
1587 	c = uri;
1588 	if (uri[1] == '/') {
1589 		c++;
1590 		while (*c != '\0' && *c == '/') {
1591 			c++;
1592 		}
1593 		c--;
1594 	}
1595 #ifdef USE_DEBUG
1596 	if (conf.debug)
1597 		fprintf(stderr, "Debug: get_clean_uri '%s'\n", c);
1598 #endif
1599 
1600 	return evhttp_uri_parse_with_flags(c, 0);
1601 }
1602 
1603 static int
get_fs_path(struct evhttp_request * req,struct vhost * vh,struct mohawk_path * paths,char * fs_path,struct stat * st)1604 get_fs_path(struct evhttp_request *req, struct vhost *vh, struct mohawk_path *paths, char *fs_path, struct stat *st)
1605 {
1606 	int i, nb;
1607 	char buf[PATH_MAX];
1608 	char *uri;
1609 
1610 	for (i = 0; i < paths->nb; i++) {
1611 		uri =  paths->paths[i] + paths->offset;
1612 		if (strcmp(vh->host, "*") == 0 || vh->hostname_in_rootdir != 1)
1613 			nb = snprintf(buf, PATH_MAX, "%s%s",  vh->rootdir, uri);
1614 		else
1615 			nb = snprintf(buf, PATH_MAX, "%s/%s%s", vh->rootdir, evhttp_request_get_host(req), uri);
1616 
1617 #ifdef USE_DEBUG
1618 		if (conf.debug)
1619 			fprintf(stderr, "Debug: get_fs_path '%s'\n", buf);
1620 #endif
1621 
1622 		if (nb >= PATH_MAX) {
1623 #define FMT_TOO_LONG "get_fs_path - '%s' + '%s' == %d (>= %d)"
1624 			do_error(req, vh, HTTP_INTERNAL, "filename too long", FMT_TOO_LONG, vh->host, uri, nb, PATH_MAX);
1625 			return -1;
1626 		}
1627 
1628 		if (realpath(buf, fs_path) == NULL) {
1629 #ifdef USE_DEBUG
1630 			if (conf.debug)
1631 				fprintf(stderr, "Debug: realpath '%s' == NULL\n", fs_path);
1632 #endif
1633 			continue;
1634 		}
1635 
1636 #ifdef USE_DEBUG
1637 		if (conf.debug)
1638 			fprintf(stderr, "Debug: fs_path '%s'\n", fs_path);
1639 #endif
1640 
1641 		if (strcmp(vh->host, "*") == 0 || vh->hostname_in_rootdir != 1)
1642 			snprintf(buf, PATH_MAX, "%s",  vh->rootdir);
1643 		else
1644 			snprintf(buf, PATH_MAX, "%s/%s", vh->rootdir, evhttp_request_get_host(req));
1645 
1646 		if (strstr(fs_path, buf) == NULL) {
1647 #ifdef USE_DEBUG
1648 			if (conf.debug)
1649 				fprintf(stderr, "Debug: can't find '%s' in '%s'\n", buf, fs_path);
1650 #endif
1651 			continue;
1652 		}
1653 
1654 		if (stat(fs_path, st) == 0)
1655 			return i;
1656 	}
1657 
1658 	do_error(req, vh, HTTP_NOTFOUND, NULL, "get_fs_path - '%s'", paths->paths[0]);
1659 	return -1;
1660 }
1661 
1662 static void
handle_sigterm(int sig)1663 handle_sigterm(int sig)
1664 {
1665 	syslog(LOG_NOTICE, "exiting due to signal %d", sig);
1666 	closelog();
1667 
1668 	errx(EXIT_FAILURE, "exiting due to signal %d", sig);
1669 }
1670 
1671 static void
mohawk_event_log_cb(int severity,const char * msg)1672 mohawk_event_log_cb(int severity, const char *msg)
1673 {
1674 	if (conf.debug)
1675 		fprintf(stderr, "Debug: mohawk_event_log_cb libevent log severity %d msg: '%s'\n", severity, msg);
1676 	else
1677 		syslog(LOG_ERR, "libevent log severity %d msg: '%s'", severity, msg);
1678 }
1679 
1680 #define DATE_1123 "%a, %d %b %Y %H:%M:%S"
1681 #define DATE_1036 "%A, %d-%b-%y %H:%M:%S"
1682 #define DATE_ANSI "%a %b %d %H:%M:%S %Y"
1683 
1684 static time_t
parse_date(const char * s)1685 parse_date(const char *s)
1686 {
1687 	struct tm t;
1688 	if (!s)
1689 		return -1;
1690 	t.tm_isdst = 0;
1691 
1692 	if (strptime(s, DATE_1123, &t) != NULL)
1693 		return timegm(&t);
1694 
1695 	if (strptime(s, DATE_1036, &t) != NULL)
1696 		return timegm(&t);
1697 
1698 	if (strptime(s, DATE_ANSI, &t) != NULL)
1699 		return timegm(&t);
1700 
1701 	return -1;
1702 }
1703 
1704 static void
request_decode_paths(struct mohawk_path * paths)1705 request_decode_paths(struct mohawk_path *paths)
1706 {
1707 	char *percent = NULL, *plus = NULL;
1708 
1709 	plus = index(paths->paths[0], '+');
1710 	percent = index(paths->paths[0], '%');
1711 
1712 	if (!percent && !plus)
1713 		return;
1714 
1715 	if (percent && !plus) {
1716 		paths->paths[paths->nb++] = evhttp_uridecode(paths->paths[0], 0, NULL);
1717 		return;
1718 	}
1719 
1720 	paths->paths[paths->nb++] = evhttp_uridecode(paths->paths[0], 1, NULL);
1721 	return;
1722 }
1723 
1724 static int
request_index_lookup(struct vhost * vh,const char * dir,char ** file,struct stat * st)1725 request_index_lookup(struct vhost *vh, const char *dir, char **file, struct stat *st)
1726 {
1727 	struct mohawk_list *index_name;
1728 
1729 	if ((*file = malloc(PATH_MAX)) == NULL)
1730 		return -1;
1731 
1732 	SLIST_FOREACH(index_name, &vh->index_names, entry) {
1733 		snprintf(*file, PATH_MAX, "%s/%s", dir, index_name->item);
1734 		if (stat(*file, st) >= 0)
1735 			if (st->st_mode & S_IFREG)
1736 				return 1;
1737 	}
1738 
1739 	free(*file);
1740 
1741 	return 0;
1742 }
1743 
1744 static struct vhost *
request_location_lookup(struct vhost * vhost,struct mohawk_path * paths)1745 request_location_lookup(struct vhost *vhost, struct mohawk_path *paths)
1746 {
1747 	struct vhost *vh;
1748 	int i;
1749 
1750 	for (i = 0; i < paths->nb; i++) {
1751 		SLIST_FOREACH(vh, &vhost->locations, entry) {
1752 			if (strncmp(vh->location, paths->paths[i], strlen(vh->location)) == 0)
1753 				return vh;
1754 		}
1755 	}
1756 	return NULL;
1757 }
1758 
1759 static void
usage(int ret)1760 usage(int ret)
1761 {
1762 	fprintf(stderr, "\
1763 usage: %s [-c configfile | [[ -L listen ] [ -P port ] [ -R rootdir ]] [-d] [-D] [-k] [-h] [-n] [-p pidfile] [-v]\n\
1764 \t-c configfile (incompatible with -L/-P/-R)\n\
1765 \t-d active debug mode (if %s is compiled with -DUSE_DEBUG)\n\
1766 \t-D dump config file (more verbose with -d)\n\
1767 \t-h this message\n\
1768 \t-k keep identity (don't setuid)\n\
1769 \t-L listen (incompatible with -c) default %s\n\
1770 \t-n check config file\n\
1771 \t-p pidfile\n\
1772 \t-P port (incompatible with -c) default %s\n\
1773 \t-R rootdir (incompatible with -c) default %s\n\
1774 \t-v display version\n\
1775 ", getprogname(), getprogname(), DEFAULT_LISTEN, DEFAULT_HTTP_PORT, DEFAULT_ROOTDIR);
1776 
1777 	exit(ret);
1778 }
1779 
1780 int
main(int argc,char ** argv)1781 main(int argc, char **argv)
1782 {
1783 	int ch, flag_command_line = 0, flag_check_config = 0, flag_config = 0, flag_dump = 0, flag_keep = 0, nb_listen = 0, syslog_facility, ret;
1784 	const char *listen = DEFAULT_LISTEN, *port = DEFAULT_HTTP_PORT, *rootdir = DEFAULT_ROOTDIR;
1785 	struct event_base *eb;
1786 	struct vhost *vh;
1787 	struct passwd* pwd = NULL;
1788 #if defined(__FreeBSD__)
1789 	pid_t otherpid;
1790 #endif
1791 	uid_t uid = 65534;
1792 	gid_t gid = 65534;
1793 	struct sigaction act_pipe, act_handler;
1794 
1795 	/* configuration initialization */
1796 #if defined(HAVE_BLACKLISTD)
1797 	conf.blacklistd = 0;
1798 #endif
1799 	conf.chroot = NULL;
1800 	conf.debug = -1;
1801 	conf.pidfile = NULL;
1802 	conf.syslog_facility = DEFAULT_SYSLOG_FACILITY;
1803 	conf.user = DEFAULT_USER;
1804 
1805 	while ((ch = getopt(argc, argv, "vc:dDkL:np:P:R:h")) != -1) {
1806 		switch (ch) {
1807 		case 'v':
1808 			printf("%s/%s\n", getprogname(), MOHAWK_VERSION );
1809 			exit(EXIT_SUCCESS);
1810 		case 'c':
1811 			if (!flag_command_line) {
1812 				parse_config(optarg);
1813 				flag_config = 1;
1814 			}
1815 			else
1816 				warnx("'-c' option is incompatible with '-L/-P/-R' option");
1817 			break;
1818 		case 'd':
1819 			conf.debug = 1;
1820 			break;
1821 		case 'D':
1822 			flag_dump = 1;
1823 			break;
1824 		case 'k':
1825 			flag_keep = 1;
1826 			break;
1827 		case 'L':
1828 			if (!flag_config) {
1829 				listen = optarg;
1830 				flag_command_line = 1;
1831 			}
1832 			else
1833 				warnx("'-L' option is incompatible with '-c' option");
1834 			break;
1835 		case 'n':
1836 			flag_check_config = 1;
1837 			break;
1838 		case 'p':
1839 			conf.pidfile = optarg;
1840 			break;
1841 		case 'P':
1842 			if (!flag_config) {
1843 				port = optarg;
1844 				flag_command_line = 1;
1845 			}
1846 			else
1847 				warnx("'-P' option is incompatible with '-c' option");
1848 			break;
1849 		case 'R':
1850 			if (!flag_config) {
1851 				rootdir = optarg;
1852 				flag_command_line = 1;
1853 			}
1854 			else
1855 				warnx("'-R' option is incompatible with '-c' option");
1856 			break;
1857 		case 'h':
1858 		default:
1859 			usage(EXIT_SUCCESS);
1860 		}
1861 	}
1862 	argc -= optind;
1863 	argv += optind;
1864 
1865 	if (conf.debug < 0)
1866 		conf.debug = 0;
1867 
1868 	if (!flag_config && !flag_command_line)
1869 		usage(EXIT_FAILURE);
1870 
1871 	if (flag_command_line)
1872 		parse_command_line(listen, port, rootdir);
1873 
1874 	if (conf.default_vhost == NULL)
1875 		errx(EXIT_FAILURE, "No 'default' vhost found");
1876 
1877 	if (flag_check_config)
1878 		exit(EXIT_SUCCESS);
1879 
1880 	if (conf.pidfile == NULL)
1881 		conf.pidfile = DEFAULT_PIDFILE;
1882 #if defined (__OpenBSD__)
1883 	else if (strchr(conf.pidfile, '/')) {
1884 		errx(EXIT_FAILURE, "'%s': bad pid basename. Don't use absolute or relative pathname. See pidfile(3).", conf.pidfile);
1885 	}
1886 #endif
1887 
1888 	if (flag_dump) {
1889 		dump_config();
1890 		exit(EXIT_SUCCESS);
1891 	}
1892 
1893 #ifdef USE_DEBUG
1894 	if (conf.debug)
1895 		fprintf(stderr, "Debug: starting debug mode\n");
1896 #endif
1897 
1898 #ifdef USE_DEBUG
1899 	if (conf.debug && flag_keep)
1900 		fprintf(stderr, "Debug: keep my identity\n");
1901 #endif
1902 
1903 	if (!flag_keep && getuid() != 0)
1904 		errx(EXIT_FAILURE, "\
1905 For security reason, only root can run %s.\n\
1906 chroot and setuid need root privilege.\n\
1907 By default, chroot is optional and %s run as %s.\
1908 ", getprogname(), getprogname(), DEFAULT_USER);
1909 
1910 
1911 	if (facility_lookup(conf.syslog_facility, &syslog_facility) == NULL)
1912 		errx(EXIT_FAILURE, "Can't use '%s' as syslog facility", conf.syslog_facility);
1913 	openlog(getprogname(), LOG_NDELAY|LOG_PID, syslog_facility);
1914 
1915 	if (!conf.debug) {
1916 		if (daemon(1, 0) < 0) {
1917 			syslog(LOG_CRIT, "daemon - %m");
1918 			closelog();
1919 			err(EXIT_FAILURE, "daemon");
1920 		}
1921 	}
1922 
1923 	if (!flag_keep) {
1924 		/* We can't not seriously run as root */
1925 		if ((pwd = getpwnam(conf.user)) == NULL) {
1926 			syslog(LOG_CRIT, "unknown user - '%s'", conf.user);
1927 			errx(EXIT_FAILURE, "unknown user - '%s'", conf.user);
1928 		}
1929 		uid = pwd->pw_uid;
1930 		gid = pwd->pw_gid;
1931 	}
1932 	/* configure libevent */
1933 	event_set_log_callback(mohawk_event_log_cb);
1934 
1935 	if ((eb = event_base_new()) == NULL)
1936 		err(EXIT_FAILURE, "event_base_new return NULL");
1937 
1938 	/* Read zone info now, in case we chroot(). */
1939 	tzset();
1940 
1941 #if defined(HAVE_BLACKLISTD)
1942 	if (conf.blacklistd) {
1943 		if ((blstate = blacklist_open()) == NULL) {
1944 			syslog(LOG_ERR, "blacklist_open - %m");
1945 			warn("Cannot open blacklist");
1946 		}
1947 #ifdef USE_DEBUG
1948 		if (conf.debug)
1949 			fprintf(stderr, "Debug: open blacklist\n");
1950 #endif
1951 	}
1952 #endif
1953 
1954 	/* Chroot if requested. */
1955 	if (conf.chroot != NULL) {
1956 		if (chroot(conf.chroot) < 0) {
1957 			syslog(LOG_CRIT, "chroot - %m");
1958 			err(EXIT_FAILURE, "chroot");
1959 		}
1960 
1961 		/* Always chdir to / after a chroot. */
1962 		if (chdir("/") < 0) {
1963 			syslog(LOG_CRIT, "chroot chdir - %m");
1964 			err(EXIT_FAILURE, "chroot chdir");
1965 		}
1966 	}
1967 
1968 	/* First, vhost 'default' */
1969 	SLIST_INIT(&conf.cb_datas);
1970 	nb_listen += do_vhost(eb, conf.default_vhost, flag_keep);
1971 	SLIST_FOREACH(vh, &conf.vhosts, entry) {
1972 		if (strcmp(vh->host, "*") == 0)
1973 			break;
1974 		nb_listen += do_vhost(eb, vh, flag_keep);
1975 	}
1976 
1977 #ifdef USE_DEBUG
1978 	if (conf.debug)
1979 		fprintf(stderr, "Debug: listening on '%d' ips\n", nb_listen);
1980 #endif
1981 
1982 	if (nb_listen == 0)
1983 		errx(EXIT_FAILURE, "No listen, may be another mohawk proccess is running ?");
1984 
1985 	/*
1986 	 * Don't search pidfile_remove'function. We will lose root privilege
1987 	 * and may be chroot.
1988 	 * We will have no more access to the pidfile.
1989 	 */
1990 	if (!conf.debug) {
1991 #if defined(__FreeBSD__)
1992 		if ((conf.pfh = pidfile_open(conf.pidfile, 0644, &otherpid)) == NULL) {
1993 			if (errno == EEXIST)
1994 				errx(EXIT_FAILURE, "Daemon already running, pid: %jd.", (intmax_t)otherpid);
1995 			warn("Cannot open or create pidfile '%s'", conf.pidfile);
1996 		}
1997 #endif
1998 #if defined(__NetBSD__) || defined(__OpenBSD__)
1999 		if (pidfile(conf.pidfile) < 0)
2000 			warn("Cannot open or create pidfile '%s'", conf.pidfile);
2001 #endif
2002 	}
2003 
2004 #ifdef USE_DEBUG
2005 	if (conf.debug)
2006 		fprintf(stderr, "Debug: pidfile '%s'\n", conf.pidfile);
2007 #endif
2008 
2009 	if (!flag_keep) {
2010 		/* We are root, start becoming someone else. */
2011 		/* Set aux groups to null. */
2012 		if (setgroups(0, (gid_t*) 0) < 0) {
2013 			syslog(LOG_CRIT, "setgroups - %m");
2014 			err(EXIT_FAILURE, "setgroups");
2015 		}
2016 		/* Set primary group. */
2017 		if (setgid(gid) < 0) {
2018 			syslog(LOG_CRIT, "setgid - %m");
2019 			err(EXIT_FAILURE, "setgid");
2020 		}
2021 		/* Try setting aux groups correctly - not critical if this fails. */
2022 		if (initgroups(conf.user, gid) < 0) {
2023 			syslog(LOG_ERR, "initgroups - %m");
2024 			warn("initgroups");
2025 		}
2026 		/* Set login name. */
2027 		setlogin(conf.user);
2028 	}
2029 
2030 
2031 	if (!flag_keep) {
2032 		/* Bye bye root privilege */
2033 		if (setuid(uid) < 0) {
2034 			syslog(LOG_CRIT, "setuid - %m");
2035 			err(EXIT_FAILURE, "setuid");
2036 		}
2037 	}
2038 
2039 #ifdef USE_DEBUG
2040 	if (conf.debug)
2041 		fprintf(stderr, "Debug: start dispatching\n");
2042 #endif
2043 
2044 #if defined(__FreeBSD__)
2045 	if (!conf.debug)
2046 		pidfile_write(conf.pfh);
2047 #endif
2048 
2049 	act_handler.sa_handler = handle_sigterm;
2050 	sigemptyset (&act_handler.sa_mask);
2051 	act_handler.sa_flags = 0;
2052 	sigaction(SIGINT, &act_handler, NULL);
2053 	sigaction(SIGTERM, &act_handler, NULL);
2054 	sigaction(SIGUSR1, &act_handler, NULL);
2055 
2056 	/* without, if client close the socket, our daemon die */
2057 	act_pipe.sa_handler = SIG_IGN;
2058 	act_pipe.sa_flags = 0;
2059 	sigemptyset(&act_pipe.sa_mask);
2060 	sigaction(SIGPIPE, &act_pipe, NULL);
2061 
2062 	/* if error errno is set */
2063 	ret = event_base_dispatch(eb);
2064 
2065 	/* TODO: free each http virtual host */
2066 	err(EXIT_FAILURE, "end mohawk, event_base_dispatch return %d", ret);
2067 }
2068 
2069 /* TODO ssl: http://www.wangafu.net/~nickm/libevent-book/Ref6a_advanced_bufferevents.html */
2070 /* vim: set sw=4 sts=4 ts=4 : */
2071