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