/* mohawk - small HTTP server
**
** Copyright © 2010 by Baptiste Daroussin <bapt@FreeBSD.org>,
** Copyright © 2010 by Freddy Dissaux <freddy.dsx@free.fr>,
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.

*/

#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#if defined(__FreeBSD__)
#include <sys/capsicum.h>
#include <osreldate.h>
#endif
#include <netinet/in.h> /* b64_pton */

#if defined(HAVE_BLACKLISTD)
#include <blacklist.h>
#endif
#if defined(HAVE_BLOCKLISTD)
#include <blocklist.h>
#endif
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <libgen.h>
#include <limits.h>
#include <netdb.h>
#include <pwd.h>
#include <resolv.h> /* b64_pton */
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#if defined(__OpenBSD__)
#include <util.h>
#endif

#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/http_struct.h>
#include <event2/keyvalq_struct.h>
#include <event2/util.h>

#include "mohawk.h"
#include "dump.h"

#ifndef CGI_PATH
#define CGI_PATH "/bin:/usr/bin:/usr/local/bin"
#endif /* CGI_PATH */
#ifndef CGI_LD_LIBRARY_PATH
#define CGI_LD_LIBRARY_PATH "/usr/lib:/usr/local/lib"
#endif /* CGI_LD_LIBRARY_PATH */

#define HTTP_UNAUTHORIZED 401
#define HTTP_ACCESS_DENIED 403
#define MAX_CONTENT_TYPE 128
#define MAX_SIZE_INT 16

struct mohawk_conf conf;

static const char *methods[] = {
	NULL,
	"GET",
	"POST",
	"PUT",
	"HEAD",
	"DELETE",
	"OPTIONS",
	"TRACE",
	"CONNECT",
	"PATH"
};

struct cgi_callback_data {
	struct evhttp_request *req;
	struct vhost *vhost;
};

#define MAX_PATHS 3
struct mohawk_path {
	char *paths[MAX_PATHS];
	size_t offset; /* for location */
	int nb;
};

#if defined(HAVE_BLACKLISTD)
static struct blacklist *blstate;
#endif
#if defined(HAVE_BLOCKLISTD)
static struct blocklist *blstate;
#endif

static int access_granted(struct evhttp_request *, struct callback_data *); 
static inline int access_granted_inet(struct addrinfo *, struct mohawk_network_inet *);
static inline int access_granted_inet6(struct addrinfo *, struct mohawk_network_inet6 *);

static int auth_check(struct evhttp_request *, struct vhost *);
static int auth_check_lookup(FILE *, const char *, const char *);
static bool auth_lookup(struct vhost *, struct mohawk_path *);
#if defined(HAVE_BLACKLISTD) || defined(HAVE_BLOCKLISTD)
static int blacklist_lookup(struct vhost *, struct mohawk_path *);
#endif

static void cgi_event_cb(struct bufferevent *, short, void *);
static char * cgi_map_lookup(struct vhost *, struct mohawk_path *);
static void cgi_read_cb(struct bufferevent *, void *);

#if defined(HAVE_BLACKLISTD) || defined(HAVE_BLOCKLISTD)
static void do_blacklist(struct evhttp_request *, int);
#endif
static void do_cgi(struct evhttp_request *, void *, const char *, struct stat *);
#ifdef USE_DEBUG
static void do_cgi_debug(struct evhttp_request *, size_t);
#endif
static char ** do_cgi_env(struct evhttp_request *, void *, const char *, size_t);
#ifdef USE_DEBUG
static void do_cgi_env_debug(struct vhost *, char **);
#endif

static void do_error(struct evhttp_request *, struct vhost *, int, const char *, const char *, ...);

static const char * do_fs_content_type_lookup(const char *);
static void do_fs_dir(struct evhttp_request *, struct vhost *, const char *, const char *);
static void do_fs_file(struct evhttp_request *, void *, const char *, struct stat *);
static int do_fs_file_details(struct evhttp_request *, struct vhost *, struct evbuffer *, const char *, const char *, const char *);

static void do_location(struct evhttp_request *, const char *, const char *);
static void do_log(struct evhttp_request *, struct vhost *, unsigned long long size);
static void do_request(struct evhttp_request *, void *);
static void do_status(struct evhttp_request *, void *);
static int do_vhost(struct event_base *, struct vhost *, bool);

static struct evhttp_uri * get_clean_uri(struct evhttp_request *);
static int get_fs_path(struct evhttp_request *, struct vhost *, struct mohawk_path *, char *, struct stat *);
static void handle_sigterm(int sig);
static void handle_sigchld(int sig);
static void mohawk_event_log_cb(int, const char *);
static time_t parse_date(const char *);

static void request_decode_paths(struct mohawk_path *);
static int request_index_lookup(struct vhost *, const char *, char **, struct stat *);
static struct vhost * request_location_lookup(struct vhost *, struct mohawk_path *);

static void usage(int);

static int
access_granted(struct evhttp_request *req, struct callback_data *cb)
{
	struct mohawk_network *network_mohawk;
	struct addrinfo hints, *ai;
	int error, ret = 0;
	struct vhost *vh = cb->vh;

	if (conf.debug) {
		fprintf(stderr, "\nDebug: connection from '%s' port '%d'\n", req->remote_host, req->remote_port);
		fprintf(stderr, "Debug: connecting to '%s' port '%d'\n", cb->host, cb->port);
		fprintf(stderr, "Debug: vhost '%s' rootdir '%s'\n", vh->host, vh->rootdir);
		fprintf(stderr, "Debug: host '%s'\n", evhttp_request_get_host(req));
	}

	if (SLIST_EMPTY(&vh->grant_access))
		return 1;

    bzero(&hints, sizeof hints);
    hints.ai_family = PF_UNSPEC;
    hints.ai_flags = AI_NUMERICHOST;

	if ((error = getaddrinfo(req->remote_host, NULL, &hints, &ai)) != 0) {
		warnx("getaddrinfo '%s': %s", req->remote_host, gai_strerror(error));
		goto done;
	}

	SLIST_FOREACH(network_mohawk, &vh->grant_access, entry) {
		if (ai->ai_family != network_mohawk->family)
			continue;

		switch (ai->ai_family) {
		case PF_INET:
			if ((ret = access_granted_inet(ai, (struct mohawk_network_inet *)network_mohawk->network)) > -1) {
				goto done;
			}
			break;
		case PF_INET6:
			if ((ret = access_granted_inet6(ai, (struct mohawk_network_inet6 *)network_mohawk->network)) > -1) {
				goto done;
			}
			break;
		default: /* Hu ? */
			warnx("Unexpected address family '%s': %d", req->remote_host, ai->ai_family);
		}
	}

done:
	if (ai)
		freeaddrinfo(ai);

	if (ret != 1)
		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "access_granted - '%s' access denied %s",
			req->remote_host, error ? gai_strerror(error) : "");
	return ret == 1;
}

static inline int 
access_granted_inet(struct addrinfo *ai, struct mohawk_network_inet *network) 
{ 
    void *fix_error = ai->ai_addr;
    struct sockaddr_in *sa = (struct sockaddr_in*)fix_error; 
 
    if ((sa->sin_addr.s_addr & network->mask) != network->prefix) 
        return -1;

    return !network->not;
}
 
static inline int 
access_granted_inet6(struct addrinfo *ai, struct mohawk_network_inet6 *network) 
{ 
    int i; 
    void *fix_error = ai->ai_addr;
    struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)fix_error;
 
    for (i = 0; i < 16; i++) { 
        if ((sa6->sin6_addr.s6_addr[i] & network->mask[i]) != network->prefix[i])
            return -1;
    }

    return !network->not;
}

static int
auth_check(struct evhttp_request *req, struct vhost *vh)
{
	const char *header_auth;
	char auth_buf[PATH_MAX];
	char *auth_pass, *colon;
	ssize_t l;
	FILE *fp;
	struct stat st_auth;
	int ret;
#if defined(__FreeBSD__)
	int fd;
	cap_rights_t rights;
#endif

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: auth_check '%s'\n", vh->auth_path);
#endif

	if (stat(vh->auth_path, &st_auth) < 0) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "auth_check - '%s': file not found", vh->auth_path);
		return 0;
	}

	/* We need a Authentification header */
	if ((header_auth = evhttp_find_header(req->input_headers, "Authorization")) == NULL)
		goto send_auth;
	if (strncmp(header_auth, "Basic ", 6) != 0)
		goto send_auth;

	l = b64_pton(header_auth + 6, (u_char *)auth_buf, sizeof(auth_buf));
	auth_buf[l] = '\0';
	if ((auth_pass = strchr(auth_buf, ':')) == NULL) {
		/* TODO malformed , log it ? */
		goto send_auth;
	}
	*auth_pass++ = '\0';
	if ((colon = strchr(auth_pass, ':')) != NULL)
		*colon = '\0';

#if defined(__FreeBSD__)
	if ((fd = open(vh->auth_path, O_RDONLY)) == -1) {
		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "auth_check - can't open '%s'", vh->auth_path);
		return 0;
	}
	cap_rights_init(&rights, CAP_FCNTL, CAP_READ); /* CAP_FCNTL is required by fdopen */
	if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS) {
		close(fd);
		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "auth_check - can't cap_rights_limit '%s'", vh->auth_path);
		return 0;
	}
	if ((fp = fdopen(fd, "r")) == NULL) {
		close(fd);
		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "auth_check - can't fdopen '%s'", vh->auth_path);
		return 0;
	}
#else
	if ((fp = fopen(vh->auth_path, "r")) == NULL) {
		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "auth_check - can't fopen '%s'", vh->auth_path);
		return 0;
	}
#endif

	if ((ret = auth_check_lookup(fp, auth_buf, auth_pass)) == 1) {
		evhttp_add_header(req->input_headers, "Mohawk-Authenticate-User", auth_buf);
		return 1;
	}
	if (ret == -1) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "auth_check_lookup - unable to allocate memory");
		return 0;
	}

send_auth:
	evhttp_add_header(req->output_headers, "WWW-Authenticate", "Basic realm=\"mohawk\"");
	evhttp_send_reply(req, HTTP_UNAUTHORIZED, NULL, NULL);

	return 0;
}

static int
auth_check_lookup(FILE *fp, const char *user, const char *pass)
{
	char *buf, *password;
	ssize_t len;
	int found = 0;
	size_t linecap = 0;

	buf = NULL;
	while ((len = getline(&buf, &linecap, fp)) > 0) {
		if (buf[len - 1] == '\n')
			buf[len - 1] = '\0';
		if (buf[0] == '#')
			continue;

		if ((password = strchr(buf, ':')) == NULL)
			continue;

		*password++ = '\0';
		if (strcmp(buf, user) == 0) {
			if (strcmp(crypt(pass, password), password) == 0) {
				found = 1;
				break;
			}
		}
	}
	fclose(fp);
	free(buf);

	return found;
}

static bool
auth_lookup(struct vhost *vh, struct mohawk_path *paths)
{
	struct mohawk_list *item;
	int i;
	const char *uri;

	for (i = 0; i < paths->nb; i++) {
		uri = paths->paths[i] + paths->offset;
		SLIST_FOREACH(item, &vh->no_auth_patterns, entry) {
			if (fnmatch(item->item, uri, 0) != FNM_NOMATCH)
				return false;
		}

		SLIST_FOREACH(item, &vh->auth_patterns, entry) {
			if (fnmatch(item->item, uri, 0) != FNM_NOMATCH)
				return true;
		}
	}
	return vh->authentication;
}

#if defined(HAVE_BLACKLISTD) || defined(HAVE_BLOCKLISTD)
static int
blacklist_lookup(struct vhost *vh, struct mohawk_path *paths)
{
	struct mohawk_list *item;
	int i;
	const char *uri;

	for (i = 0; i < paths->nb; i++) {
		uri = paths->paths[i] + paths->offset;
		SLIST_FOREACH(item, &vh->blacklist_patterns, entry) {
			if (fnmatch(item->item, uri, 0) != FNM_NOMATCH)
				return 1;
		}
	}
	return 0;
}
#endif

static void
cgi_event_cb(struct bufferevent *bev, short what, void *cgi_cb_data)
{
	char *header, *pos;
	struct cgi_callback_data *cb = cgi_cb_data;
	struct evhttp_request *req = cb->req;
	struct vhost *vh = cb->vhost;
	size_t len;

	if (what & BEV_EVENT_EOF) {
#ifdef USE_DEBUG
		if (conf.debug) {
			fprintf(stderr, "Debug: cgi_event_cb - %zd to send\n", evbuffer_get_length(req->output_buffer));
		}
#endif
		while ((header = evbuffer_readln(req->output_buffer, &len, EVBUFFER_EOL_CRLF)) != NULL) {
			if (!len) /* empty line, end of headers */
				break;

			if ((pos = strchr(header, ':')) != NULL) {
				if (header + len > pos + 2) {
					*pos = '\0';
			
					evhttp_add_header(req->output_headers, header, pos + 2);
					*pos = ':';
					free(header);
				}
			}
		}
		bufferevent_free(bev);
		len = evbuffer_get_length(req->output_buffer);
		evhttp_send_reply(req, HTTP_OK, "OK", NULL);
		do_log(req, vh, len);
	}
	return;
}

static char *
cgi_map_lookup(struct vhost *vh, struct mohawk_path *paths)
{
	struct mohawk_hash *cgi_map;
	struct mohawk_list *no_cgi_map;
	int i;
	const char *uri;

	for (i = 0; i < paths->nb; i++) {
		uri = paths->paths[i] + paths->offset;
		/* script_name match cgi-map ? */
		SLIST_FOREACH(cgi_map, &vh->cgi_map, entry) {
			if (fnmatch(cgi_map->key, uri, 0) != FNM_NOMATCH) {
#ifdef USE_DEBUG
				if (conf.debug)
					fprintf(stderr, "Debug: '%s' match '%s'\n", uri, cgi_map->key);
#endif
				/* check if match no_cgi_map */
				SLIST_FOREACH(no_cgi_map, &vh->no_cgi_maps, entry) {
					if (fnmatch(no_cgi_map->item, uri, 0) != FNM_NOMATCH) {
#ifdef USE_DEBUG
						if (conf.debug)
							fprintf(stderr, "Debug: but '%s' match '%s'\n", uri, no_cgi_map->item);
#endif
						return NULL;
					}
				}
				return cgi_map->value;
			}
		}
	}
	return NULL;
}

static void
cgi_read_cb(struct bufferevent *bev, void *cgi_cb_data)
{
	struct cgi_callback_data *cb = cgi_cb_data;
	struct evhttp_request *req = cb->req;

	evbuffer_add_buffer(req->output_buffer, bufferevent_get_input(bev));
	return;
}

#if defined(HAVE_BLACKLISTD) || defined(HAVE_BLOCKLISTD)
static void
do_blacklist(struct evhttp_request *req, int action)
{
	/*
 	 * WARNING: ugly hack
	 * https://stackoverflow.com/questions/39179676/forward-declaration-of-struct-while-retrieving-libevent-connection-information
	 * https://github.com/libevent/libevent/issues/120
	 * TL;DR libevent hide the file descriptor required by blacklistd
	 * struct evhttp_connection *con = evhttp_request_get_connection(req)
	 * struct evhttp_connection {
	 *   TAILQ_ENTRY(evhttp_connection) next;
	 *   evutil_socket_t fd;
	 * }
	 * #define evutil_socket_t int
	*/
	int ret;
	struct ugly_hack_struct {
		TAILQ_ENTRY(evhttp_connection) next;
		evutil_socket_t fd;
	};
	struct ugly_hack_struct *ugly_hack_pointer = (struct ugly_hack_struct *)evhttp_request_get_connection(req);
	if (ugly_hack_pointer == NULL) { return; }

#ifdef USE_DEBUG
	if (conf.debug) {
		fprintf(stderr, "Debug: req fd: %d\n", ugly_hack_pointer->fd);
		fprintf(stderr, "Debug: blacklist %s\n", req->remote_host);
	}
#endif
#if defined(HAVE_BLACKLISTD)
	if (!conf.debug && (ret = blacklist_r(blstate, action, ugly_hack_pointer->fd, "mohawk")) != 0) {
#ifdef USE_DEBUG
		if (conf.debug)
			fprintf(stderr, "Debug: blacklist_r %d return: %d\n", action, ret);
#endif
		return;
	}
#endif
#if defined(HAVE_BLOCKLISTD)
	if (!conf.debug && (ret = blocklist_r(blstate, action, ugly_hack_pointer->fd, "mohawk")) != 0) {
#ifdef USE_DEBUG
		if (conf.debug)
			fprintf(stderr, "Debug: blocklist_r %d return: %d\n", action, ret);
#endif
		return;
	}
#endif
	return;
}
#endif

static void
do_cgi(struct evhttp_request *req, void *cb_data, const char *fs_path, struct stat *st)
{
	char *argp[2];
	evutil_socket_t fds[2];
	pid_t pid;
	struct bufferevent *bev;
	struct cgi_callback_data *cgi_cb_data;
	struct callback_data *cb = cb_data;
	struct vhost *vh = cb->vh;
	char **envp = NULL;
	size_t size = evbuffer_get_length(req->input_buffer);

#ifdef USE_DEBUG
	if (conf.debug) {
		fprintf(stderr, "Debug: do_cgi '%s'\n", fs_path);
		do_cgi_debug(req, size);
	}
#endif

	/* not executable */
	if (!(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi - '%s' not executable", fs_path);
		return;
	}

	if ((cgi_cb_data = malloc(sizeof (struct cgi_callback_data))) == NULL) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi - unable to allocate memory");
		return;
	}

	cgi_cb_data->req = req;
	cgi_cb_data->vhost = vh;

	if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
		free(cgi_cb_data);
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi - evutil_socketpair != 0");
		return;
	}

	if ((pid = fork()) < 0) {
		free(cgi_cb_data);
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi - fork < 0");
		/* clean evutil_socketpair / fds ? */
		return;
	}
	else if (pid == 0) { /* child */
		if ((envp = do_cgi_env(req, cb_data, fs_path, size)) == NULL)
			return;

#ifdef USE_DEBUG
		if (conf.debug) {
			fprintf(stderr, "Debug: do_cgi_env\n");
			do_cgi_env_debug(vh, envp);
		}
#endif
		argp[0] = strrchr(fs_path, '/');
		argp[1] = NULL;

		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);
		close(fds[0]);
		dup2(fds[1], STDIN_FILENO);
		dup2(fds[1], STDOUT_FILENO);
		dup2(fds[1], STDERR_FILENO);

		execve(fs_path, argp, envp);
		exit(EXIT_SUCCESS);
	}

	/* parent */
	close(fds[1]);
	if (evbuffer_get_length(req->input_buffer))
		evbuffer_write(req->input_buffer, fds[0]);
	bev = bufferevent_socket_new(evhttp_connection_get_base(evhttp_request_get_connection(req)), fds[0], BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
	bufferevent_setcb(bev, cgi_read_cb, NULL, cgi_event_cb, cgi_cb_data);
	bufferevent_enable(bev, EV_READ);

	return;
}

#ifdef USE_DEBUG
#define DEBUG_MAX_POSTDATA 256
static void
do_cgi_debug(struct evhttp_request *req, size_t size)
{
	char *postdata;

	if ((postdata = malloc(size + 1)) == NULL) {
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
		fprintf(stderr, "Debug: do_cgi - can't malloc %zd\n", size);
#else
		fprintf(stderr, "Debug: do_cgi - can't malloc %" PRId64 "\n", size);
#endif
		return;
	}

	/* TODO: what's append if evbuffer_copyout < size ? */
	if (evbuffer_copyout(req->input_buffer, postdata, size) < 0) {
		free(postdata);
		fprintf(stderr, "Debug: do_cgi_debug - copyout return -1\n");
		return;
	}
	postdata[size] = '\0';

	fprintf(stderr, "\n[%.*s%s]\n",
		size <= DEBUG_MAX_POSTDATA ? (int)size : DEBUG_MAX_POSTDATA,
		postdata,
		size <= DEBUG_MAX_POSTDATA ? "" : "...");

	return;
}
#endif

enum var_env {
	CONTENT_LENGTH,    // * size of data
	DOCUMENT_ROOT,     // * The root directory of your server
	GATEWAY_INTERFACE, // * CGI/1.1
	HTTP_COOKIE,       // * The visitor's cookie, if one is set
	HTTP_HOST,         // * The hostname of the page being attempted
	HTTP_REFERER,      // * The URL of the page that called your program
	HTTP_USER_AGENT,   // * The browser type of the visitor
	HTTPS,             // * "on" if the program is being called through a secure server
	LD_LIBRARY_PATH,   // *
	PATH,              // * The system path your server is running under
	QUERY_STRING,      // * The query string (see GET, below)
	REMOTE_ADDR,       // * The IP address of the visitor
	REMOTE_HOST,       // * The hostname of the visitor (if your server has reverse-name-lookups on; otherwise this is the IP address again)
	REMOTE_PORT,       // * The port the visitor is connected to on the web server
	REMOTE_USER,       // * The visitor's username (for .htaccess-protected pages)
	REQUEST_METHOD,    // * GET or POST
	REQUEST_URI,       // * The interpreted pathname of the requested document or CGI (relative to the document root)
	SCRIPT_FILENAME,   // * The full pathname of the current CGI
	SCRIPT_NAME,       // * The interpreted pathname of the current CGI (relative to the document root)
	SERVER_ADDR,       // *
	SERVER_ADMIN,      // * The email address for your server's webmaster
	SERVER_NAME,       // * Your server's fully qualified domain name (e.g. www.cgi101.com)
	SERVER_PORT,       // * The port number your server is listening on
	SERVER_SOFTWARE,   // * The server software you're using (e.g. Apache 1.3) 
	TZ,                // * timezone, if defined
	VERSION_MOHAWK,    // *
	NB_VAR_ENV         // * 
};

static char **
do_cgi_env(struct evhttp_request *req, void *cb_data, const char *filepath, size_t size)
{
	const char *header, *user;
	char buf[PATH_MAX];
	char **envp;
	int i, max = NB_VAR_ENV;
	struct evhttp_uri *uri;
	struct callback_data *cb = cb_data;
	struct vhost *vh = cb->vh;
	struct mohawk_hash *cgi_env;

	if ((envp = calloc(vh->cgi_env_count + NB_VAR_ENV, sizeof(char *))) == NULL) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi_env - can't calloc");
		return NULL;
	}

	if ((uri = get_clean_uri(req)) == NULL) {
		do_error(req, vh, 400, NULL, "do_cgi_env - can't clean uri");
		return NULL;
	}

#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
	snprintf(buf, PATH_MAX, "CONTENT_LENGTH=%zd", size);
#else
	snprintf(buf, PATH_MAX, "CONTENT_LENGTH=%" PRId64 "", size);
#endif
	if ((envp[CONTENT_LENGTH] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "DOCUMENT_ROOT=%s", vh->rootdir);
	if ((envp[DOCUMENT_ROOT] = strdup(buf)) == NULL) goto done;

	if ((envp[GATEWAY_INTERFACE] = strdup("GATEWAY_INTERFACE=CGI/1.1")) == NULL) goto done;

	if ((header = evhttp_find_header(req->input_headers, "Cookie")) != NULL) {
		snprintf(buf, PATH_MAX, "HTTP_COOKIE=%s", header);
		if ((envp[HTTP_COOKIE] = strdup(buf)) == NULL) goto done;
	}
	else
		if ((envp[HTTP_COOKIE] = strdup("HTTP_COOKIE=")) == NULL) goto done;

	snprintf(buf, PATH_MAX, "HTTP_HOST=%s", evhttp_request_get_host(req));
	if ((envp[HTTP_HOST] = strdup(buf)) == NULL) goto done;

	if ((header = evhttp_find_header(req->input_headers, "Referer")) != NULL) {
		snprintf(buf, PATH_MAX, "HTTP_REFERER=%s", header);
		if ((envp[HTTP_REFERER] = strdup(buf)) == NULL) goto done;
	}
	else
		if ((envp[HTTP_REFERER] = strdup("HTTP_REFERER=")) == NULL) goto done;

	if ((header = evhttp_find_header(req->input_headers, "User-Agent")) != NULL) {
		snprintf(buf, PATH_MAX, "HTTP_USER_AGENT=%s", header);
		if ((envp[HTTP_USER_AGENT] = strdup(buf)) == NULL) goto done;
	}
	else
		if ((envp[HTTP_USER_AGENT] = strdup("HTTP_USER_AGENT=")) == NULL) goto done;

	if ((envp[HTTPS] = strdup("HTTPS=off")) == NULL) goto done;
	
	snprintf(buf, PATH_MAX, "LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH);
	if ((envp[LD_LIBRARY_PATH] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "PATH=%s", CGI_PATH);
	if ((envp[PATH] = strdup(buf)) == NULL) goto done;

	if (evhttp_uri_get_query(uri)) {
		snprintf(buf, PATH_MAX, "QUERY_STRING=%s", evhttp_uri_get_query(uri));
		if ((envp[QUERY_STRING] = strdup(buf)) == NULL) goto done;
	}
	else
		if ((envp[QUERY_STRING] = strdup("QUERY_STRING=")) == NULL) goto done;

	snprintf(buf, PATH_MAX, "REMOTE_ADDR=%s", req->remote_host);
	if ((envp[REMOTE_ADDR] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "REMOTE_HOST=%s", req->remote_host);
	if ((envp[REMOTE_HOST] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "REMOTE_PORT=%d", req->remote_port);
	if ((envp[REMOTE_PORT] = strdup(buf)) == NULL) goto done;

	user = evhttp_find_header(req->input_headers, "Mohawk-Authenticate-User");
	snprintf(buf, PATH_MAX, "REMOTE_USER=%s", user ? user : "");
	if ((envp[REMOTE_USER] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "REQUEST_METHOD=%s", methods[evhttp_request_get_command(req)]);
	if ((envp[REQUEST_METHOD] = strdup(buf)) == NULL) goto done;

	/* TODO: evhttp_request_get_uri is not interpreted */
	snprintf(buf, PATH_MAX, "REQUEST_URI=%s", evhttp_request_get_uri(req));
	if ((envp[REQUEST_URI] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "SCRIPT_FILENAME=%s", filepath);
	if ((envp[SCRIPT_FILENAME] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "SCRIPT_NAME=%s", evhttp_uri_get_path(uri));
	if ((envp[SCRIPT_NAME] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "SERVER_ADMIN=%s", vh->email_admin ? vh->email_admin : "");
	if ((envp[SERVER_ADMIN] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "SERVER_ADDR=%s", cb->host);
	if ((envp[SERVER_ADDR] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "SERVER_NAME=%s", evhttp_request_get_host(req));
	if ((envp[SERVER_NAME] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "SERVER_PORT=%d", cb->port);
	if ((envp[SERVER_PORT] = strdup(buf)) == NULL) goto done;

	snprintf(buf, PATH_MAX, "SERVER_SOFTWARE=%s", vh->mohawk_name);
	if ((envp[SERVER_SOFTWARE] = strdup(buf)) == NULL) goto done;

	if (getenv("TZ")) {
		snprintf(buf, PATH_MAX, "TZ=%s", getenv("TZ"));
		if ((envp[TZ] = strdup(buf)) == NULL) goto done;
	}
	else
		if ((envp[TZ] = strdup("TZ=")) == NULL) goto done;

	if (vh->cgi_expose_mohawk_version) {
		snprintf(buf, PATH_MAX, "MOHAWK_VERSION=%s", MOHAWK_VERSION);
		if ((envp[VERSION_MOHAWK] = strdup(buf)) == NULL) goto done;
	}
	else
		if ((envp[VERSION_MOHAWK] = strdup("MOHAWK_VERSION=")) == NULL) goto done;

	if (vh->cgi_env_count) {
		SLIST_FOREACH(cgi_env, &vh->cgi_env, entry) {
			snprintf(buf, PATH_MAX, "%s=%s", cgi_env->key, cgi_env->value);
			if ((envp[max++] = strdup(buf)) == NULL) goto done;
		}
	}

	envp[max] = (char*)0;

	return envp;

done:
	for (i = 0; i < max; i++)
		if (envp[i])
			free(envp[i]);

	if (envp)
		free(envp);

	evhttp_uri_free(uri);
	do_error(req, vh, HTTP_INTERNAL, NULL, "do_cgi_env - can't strdup");
	return NULL;
}

#ifdef USE_DEBUG
static void
do_cgi_env_debug(struct vhost *vh, char **envp)
{
	int i = 0, max = vh->cgi_env_count + NB_VAR_ENV;
	while (i < max && envp[i])
		fprintf(stderr, "  %s\n", envp[i++]);
	if (i != max)
		fprintf(stderr, "  loose %d\n", max - i);

	return;
}
#endif

static void
do_error(struct evhttp_request *req, struct vhost *vh, int code, const char *reason, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);

	if (conf.debug) {
#ifdef USE_DEBUG
		fprintf(stderr, "Debug: do_error %d '%s' -> ", code, reason ? reason : "");
		vfprintf(stderr, fmt, ap);
		fprintf(stderr, "\n");
#else
		vsyslog(LOG_ERR, fmt, ap);
#endif
	}

	va_end(ap);

	evhttp_send_error(req, code, reason);
	do_log(req, vh, 0);

	return;
}

static const char *
do_fs_content_type_lookup(const char *filepath)
{
	struct mohawk_hash *mime_type;
	char *ext, *slash;

	if ((slash = strrchr(filepath, '/')) == NULL)
		goto done;
	if ((ext = strrchr(slash, '.')) == NULL)
		goto done;

	ext++;
	if (*ext == '\0')
		goto done;

	SLIST_FOREACH(mime_type, &conf.mime_type, entry) {
#ifdef USE_DEBUG
		fprintf(stderr, "Debug: search '%s' in '%s'\n", ext, mime_type->value);
#endif
		if (strcasestr(mime_type->value, ext) != NULL)
			return mime_type->key;
	}

done:
	return "application/octet-stream";
}

#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"

#define FMT_HEAD "<!DOCTYPE html>\n\
<html lang='en'>\n\
<head>\n\
<meta name='viewport' content='width=device-width, initial-scale=1.0'>\n\
<title>Index of %s</title>\n"

#define FMT_CSS "<style>\n\
:root {\n\
  --background-color: #f5f5f5;\n\
  --text-color: #333;\n\
  --link-color: #007bff;\n\
  --link-hover-color: #0056b3;\n\
  --table-header-bg: #f2f2f2;\n\
  --footer-color: #666;\n\
  --tr-hover-color: #e0f7fa;\n\
}\n\
\n\
body {\n\
  margin: 0;\n\
  padding: 0;\n\
  background-color: var(--background-color);\n\
  color: var(--text-color);\n\
}\n\
\n\
header, main, footer {\n\
  max-width: 1200px;\n\
  margin: 0 auto;\n\
  padding: 20px;\n\
}\n\
\n\
h1 {\n\
  margin-top: 0;\n\
}\n\
\n\
.list {\n\
  overflow-x: auto;\n\
}\n\
\n\
table {\n\
  width: 100%%;\n\
  border-collapse: collapse;\n\
}\n\
\n\
th, td {\n\
  padding: 10px;\n\
  text-align: left;\n\
  border-bottom: 1px solid #ddd;\n\
}\n\
\n\
td:nth-child(2) { font-family: monospace }\n\
\n\
td:nth-child(3) { font-family: monospace }\n\
\n\
th {\n\
  background-color: var(--table-header-bg);\n\
}\n\
\n\
a {\n\
  color: var(--link-color);\n\
  text-decoration: none;\n\
}\n\
\n\
a:hover {\n\
  text-decoration: underline;\n\
  color: var(--link-hover-color);\n\
}\n\
\n\
\n\
tr:hover {\n\
    background-color: var(--tr-hover-color);\n\
}\n\
\n\
footer.foot {\n\
  text-align: right;\n\
  font-size: 14px;\n\
  color: var(--footer-color);\n\
  margin-top: 20px;\n\
}\n\
\n\
/* responsive */\n\
@media (max-width: 768px) {\n\
  header, main, footer {\n\
    padding: 10px;\n\
  }\n\
\n\
  th, td {\n\
    padding: 5px;\n\
  }\n\
}\n\
\n\
@media (prefers-color-scheme: dark) {\n\
  :root {\n\
    --background-color: #333;\n\
    --text-color: #f5f5f5;\n\
    --link-color: #66b3ff;\n\
    --link-hover-color: #99ccff;\n\
    --table-header-bg: #444;\n\
    --footer-color: #aaa;\n\
    --tr-hover-color: #555;\n\
  }\n\
}\n\
</style>"

#define FMT_BODY "</head>\n\
<body>\n\
<header><h1>Index of %s</h1></header>\n\
<main>\n\
<div class='list'>\n\
<table>\n\
<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\
<tbody>\n"

#define FMT_FOOTER "</tbody>\n\
</table>\n\
</div>\n\
</main>\n\
<footer class='foot'><p>%s/%s</p></footer>\n\
</body>\n\
</html>\n"

static void
do_fs_dir(struct evhttp_request *req, struct vhost *vh, const char *fs_path, const char *uri_path)
{
	int i, len_item = 0, n, ret;
	unsigned int command, len = 0;
	char buf[MAX_SIZE_INT];
	char content_type[MAX_CONTENT_TYPE];
	char parent_dir[PATH_MAX];
	char *pos, *uri_path_dec;
	struct dirent **dl;
	struct evbuffer *evb;

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: do_fs_dir '%s' '%s'\n", fs_path, uri_path);
#endif

	if (!vh->dirlist) {
		do_error(req, vh, HTTP_NOTFOUND, NULL, "do_fs_dir - but !dirlist");
		return;
	}

	if ((evb = evbuffer_new()) == NULL) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_dir - unable to allocate memory");
		return;
	}

	n = scandir(fs_path, &dl, NULL, alphasort);
	if (n < 0) {
		evbuffer_free(evb);
		do_error(req, vh, HTTP_ACCESS_DENIED, NULL, "do_fs_dir - scandir '%s' return %d", fs_path, n);
		return;
	}

	uri_path_dec = evhttp_uridecode(uri_path, 0, NULL);
	if ((ret = evbuffer_add_printf(evb, FMT_HEAD, uri_path_dec)) < 0)
		goto evbuffer_add_printf_error;
	len += (unsigned int)ret;

	if (vh->dirlist_css_url) {
		if ((ret = evbuffer_add_printf(evb, "<link href='%s' rel='stylesheet' type='text/css'>\n", vh->dirlist_css_url)) < 0)
			goto evbuffer_add_printf_error;
	} else {
		if ((ret = evbuffer_add_printf(evb, FMT_CSS)) < 0)
			goto evbuffer_add_printf_error;
	}
	len += (unsigned int)ret;

	if (vh->charset) {
		if ((ret = evbuffer_add_printf(evb, "<meta charset='%s'>\n", vh->charset)) < 0)
			goto evbuffer_add_printf_error;
	}
	len += (unsigned int)ret;

	if ((ret = evbuffer_add_printf(evb, FMT_BODY, uri_path_dec)) < 0)
		goto evbuffer_add_printf_error;
	len += (unsigned int)ret;

	free(uri_path_dec);

	for (i = 0; i < n; ++i) {
		if (strcmp(dl[i]->d_name, ".") == 0)
			continue;
		if (strcmp(dl[i]->d_name, "..") == 0) {
			if (strcmp("/", uri_path) == 0)
				continue;

			/* parent directory, strip the last relevant '/' */
			if (strlcpy(parent_dir, uri_path, PATH_MAX) > sizeof(parent_dir)) {
				evbuffer_free(evb);
				do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_dir - strlcpy");
				return;
			}
			pos = strrchr(parent_dir, '/');
			if (*(pos + 1) == '\0') {
				*pos = '\0';
				pos = strrchr(parent_dir, '/');
			}
			if (pos == parent_dir)
				parent_dir[1] = '\0';
			else
				*pos = '\0';
			if ((ret = evbuffer_add_printf(evb, FMT_PARENT, parent_dir)) < 0)
				goto evbuffer_add_printf_error;
			len += (unsigned int)ret;
			continue;
		}

		if ((len_item = do_fs_file_details(req, vh, evb, fs_path, dl[i]->d_name, uri_path)) < 0)
			goto done;
		
		len += (unsigned int)len_item;
	}

	if ((ret = evbuffer_add_printf(evb, FMT_FOOTER, vh->mohawk_name ? "" : getprogname(), vh->mohawk_name ? vh->mohawk_name : MOHAWK_VERSION)) < 0)
		goto evbuffer_add_printf_error;
	len += (unsigned int)ret;
	snprintf(buf, sizeof(buf), "%d", len);
	evhttp_add_header(req->output_headers, "Content-Length", buf);
	command = evhttp_request_get_command(req);
	if (vh->charset)
		snprintf(content_type, MAX_CONTENT_TYPE, "text/html; charset=%s", vh->charset);
	else
		snprintf(content_type, MAX_CONTENT_TYPE, "text/html");
	evhttp_add_header(req->output_headers, "Content-Type", content_type);
	evhttp_send_reply(req, HTTP_OK, "OK", command == EVHTTP_REQ_HEAD ? NULL : evb);
	do_log(req, vh, command == EVHTTP_REQ_HEAD ? 0 : len); // s/0/sizeof headers/

done:
	evbuffer_free(evb);
	for (i = 0; i < n; ++i)
		free(dl[i]);
	free(dl);
	return;

evbuffer_add_printf_error:
	do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_dir - evbuffer_add_printf");
	goto done;
}

static void
do_fs_file(struct evhttp_request *req, void *cb_data, const char *filepath, struct stat *st)
{
	char buf[PATH_MAX];
	time_t t;
	struct mohawk_list *cgi_pattern;
	struct callback_data *cb = cb_data;
	struct vhost *vh = cb->vh;
	char *content_type = NULL;
	struct evbuffer *evb = NULL;
	int fd, command = evhttp_request_get_command(req);
#if defined(__FreeBSD__)
	cap_rights_t rights;
#endif
 
#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: do_fs_file '%s'\n", filepath);
#endif

	SLIST_FOREACH(cgi_pattern, &vh->cgi_patterns, entry) {
		if (fnmatch(cgi_pattern->item, filepath, FNM_CASEFOLD) == 0) {
#ifdef USE_DEBUG
			if (conf.debug)
				fprintf(stderr, "Debug: do_fs_file cgi_pattern match '%s'\n", cgi_pattern->item);
#endif
			if (command == EVHTTP_REQ_HEAD) {
				evhttp_send_reply(req, HTTP_BADMETHOD, "Bad method", NULL);
			}
			else {
				do_cgi(req, cb_data, filepath, st);
			}
			return;
		}
	}

	if ((fd = open(filepath, O_RDONLY)) < 0) {
		do_error(req, vh, HTTP_NOTFOUND, NULL, "do_fs_file - open '%s' return < 0", filepath);
		return;
	}
#if defined(__FreeBSD__)
	cap_rights_init(&rights, CAP_READ, CAP_MMAP_R);
	if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS) {
		close(fd);
		return;
	}
#endif

	if ((evb = evbuffer_new()) == NULL) {
		close(fd);
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_file - unable to allocate memory");
		return;
	}
	if ((content_type = malloc(MAX_CONTENT_TYPE)) == NULL) {
		close(fd);
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_fs_file - unable to allocate memory");
		goto done;
	}

#define FMT_RFC1123 "%a, %d %b %Y %H:%M:%S GMT"
	strftime(buf, PATH_MAX, FMT_RFC1123, gmtime(&st->st_mtime));
	evhttp_add_header(req->output_headers, "Last-Modified", buf);
	if (vh->maxage) {
		t = time((time_t*) 0) + vh->maxage;
		strftime(buf, PATH_MAX, FMT_RFC1123, gmtime(&t));
		evhttp_add_header(req->output_headers, "Expires", buf);
		snprintf(buf, PATH_MAX, "max-age=%d", vh->maxage);
		evhttp_add_header(req->output_headers, "Cache-Control", buf);
	}

#if defined(__FreeBSD__) || defined(__DragonFly__)
	snprintf(buf, PATH_MAX, "%jd", (intmax_t)st->st_size);
#elif defined(__OpenBSD__)
	snprintf(buf, PATH_MAX, "%lld", st->st_size);
#else
	snprintf(buf, PATH_MAX, "%" PRId64 "", st->st_size);
#endif
	evhttp_add_header(req->output_headers, "Content-Length", buf);

	if (vh->charset)
		snprintf(content_type, MAX_CONTENT_TYPE, "%s; charset=%s",
			do_fs_content_type_lookup(filepath), vh->charset);
	else
		snprintf(content_type, MAX_CONTENT_TYPE, "%s", do_fs_content_type_lookup(filepath));

	evhttp_add_header(req->output_headers, "Content-Type", content_type);

	if (parse_date(evhttp_find_header(req->input_headers, "If-Modified-Since")) >= st->st_mtime) {
		close(fd);
		evhttp_send_reply(req, HTTP_NOTMODIFIED, "Not Modified", NULL);
		do_log(req, vh, 0);
		goto done;
	}

	/* do not send file if size == 0 or method == HEAD */
	if (st->st_size && evhttp_request_get_command(req) != EVHTTP_REQ_HEAD)
		evbuffer_add_file(evb, fd, 0, st->st_size);
	else {
		if (fd)
			close(fd);
	}

	evhttp_send_reply(req, HTTP_OK, "OK", evb);

	/*
	 * WARNING:
	 * request is logged before sending full data.
	 * client can close the socket before receive full data
	 */

	do_log(req, vh, st->st_size > 0 ? (long long unsigned int)st->st_size : 0);

done:
	if (evb)
		evbuffer_free(evb);
	if (content_type)
		free(content_type);

	return;
}

#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"
#define FMT_LNK "<tr><td class='n'><a href='%s%s'>%s</a></td><td class='m'>%s</td><td class='s'>-</td><td class='t'>Link</td></tr>\n"
#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"
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)
{
	struct stat st;
	char f_time[20];
	char entry[PATH_MAX];
#if defined(__OpenBSD__)
	char sz[FMT_SCALED_STRSIZE];
#else
	char sz[7];
#endif
	const char *mime = NULL;
	char *name_encoded = NULL;
	int nb;

	if ((nb = snprintf(entry, PATH_MAX, "%s/%s", path, name)) >= PATH_MAX) {
		do_error(req, vh, HTTP_INTERNAL, "filename too long", " do_fs_file_details - '%s' + '%s' return %d (>= %d)", path, name, nb, PATH_MAX);
		return -1;
	}

	if (lstat(entry, &st) < 0) {
		return 0;
	}

	strftime(f_time, sizeof(f_time), "%d-%b-%Y %H:%M", localtime(&st.st_mtime));
#if defined(__OpenBSD__)
	fmt_scaled(st.st_size, sz);
#elif defined(__NetBSD__)
	humanize_number(sz, sizeof(sz), st.st_size, "B", HN_AUTOSCALE, HN_DECIMAL|HN_NOSPACE);
#else
	humanize_number(sz, sizeof(sz), st.st_size, "B", HN_AUTOSCALE, HN_IEC_PREFIXES|HN_DECIMAL|HN_NOSPACE);
#endif

	mime = do_fs_content_type_lookup(entry);
	name_encoded = evhttp_encode_uri(name);

	if (S_ISDIR(st.st_mode))
		nb = evbuffer_add_printf(evb, FMT_DIRECTORY, uri_path, name_encoded, name, f_time);
	else if (S_ISLNK(st.st_mode))
		nb = evbuffer_add_printf(evb, FMT_LNK, uri_path, name_encoded, name, f_time);
	else
		nb = evbuffer_add_printf(evb, FMT_FILE, uri_path, name_encoded, name, f_time, sz, mime);

	free(name_encoded);

	return nb;
}

static void
do_location(struct evhttp_request *req, const char *path, const char *query)
{
	char location[PATH_MAX];
	
#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: do_location '%s'\n", path);
#endif

	/*
	 * Build location: $0/$1$2
	 * $0 path
	 * $1 uri ? "?" : ""
	 * $2 uri ? query : ""
	 */
#define FMT_LOCATION "%s/%s%s"

	snprintf(location, PATH_MAX, FMT_LOCATION,
		path,
		query ? "?" : "",
		query ? query : "");

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: do_location '%s'\n", location);
#endif

	evhttp_add_header(req->output_headers, "Location", location);
	evhttp_send_reply(req, HTTP_MOVEPERM, "Moved Permanently", NULL);

	/* FIXME no do_log here ? */
	return;
}

static void
do_log(struct evhttp_request *req, struct vhost *vh, unsigned long long size)
{
	const char *user, *x_forwarded_for;
	struct mohawk_list *no_log_pattern;

	const char *uri = evhttp_request_get_uri(req);

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: do_log\n");
#endif

	/* FMT_LOG host remote_addr remote_user "method url protocol" status size */
#define FMT_LOG "%s %s %s \"%s %s HTTP/%d.%d\" %d %llu"

	if (!conf.debug) {
		SLIST_FOREACH(no_log_pattern, &vh->no_log_patterns, entry) {
			if (fnmatch(no_log_pattern->item, uri, 0) != FNM_NOMATCH) {
				return;
			}
		}
	}

	x_forwarded_for = req->remote_host;
	if (vh->x_forwarded_for) {
		x_forwarded_for = evhttp_find_header(req->input_headers, "X-Forwarded-For");
		if (!x_forwarded_for)
			x_forwarded_for = req->remote_host;
	}

	user = evhttp_find_header(req->input_headers, "Mohawk-Authenticate-User");

	if (!conf.debug)
		syslog(LOG_INFO, FMT_LOG,
			evhttp_request_get_host(req) ? evhttp_request_get_host(req) : "Unknow",
			x_forwarded_for,
			user ? user : "-",
			methods[evhttp_request_get_command(req)],
			uri,
			req->major, req->minor,
			evhttp_request_get_response_code(req),
			size);
	else
		fprintf(stderr, "Debug: " FMT_LOG "\n",
			evhttp_request_get_host(req) ? evhttp_request_get_host(req) : "Unknow",
			x_forwarded_for,
			user ? user : "-",
			methods[evhttp_request_get_command(req)],
			uri,
			req->major, req->minor,
			evhttp_request_get_response_code(req),
			size);
	return;
}

static void
do_request(struct evhttp_request *req, void *cb_data)
{
	struct evhttp_uri *uri;
	struct mohawk_path paths, cgi_paths;
	struct evkeyval *header;
	char *indexfile, *filename;
	char fs_path[PATH_MAX];
	struct stat st, st_index;
	size_t len;
	struct callback_data *cb = cb_data, *cb_data_location = NULL;
	struct vhost *vh = cb->vh;
	int command, i, ret = -2;

	if (!access_granted(req, cb))
		return; /* access denied */

	paths.nb = 0;
	if ((uri = get_clean_uri(req)) == NULL) {
		do_error(req, cb->vh, HTTP_BADREQUEST, NULL, "do_request - can't clean uri");
		goto done;
	}

	if ((paths.paths[0] = strdup(evhttp_uri_get_path(uri))) == NULL) {
		do_error(req, cb->vh, HTTP_INTERNAL, NULL, "do_request - unable to allocate memory");
		goto done;
	}

	paths.nb = 1;

	request_decode_paths(&paths);
	paths.offset = 0;

	if (conf.debug) {
		fprintf(stderr, "Debug: uri '%s'\n", evhttp_request_get_uri(req));
		for (i = 0; i < paths.nb; i++) {
			fprintf(stderr, "Debug paths '%s'\n", paths.paths[i]);
		}
		if (evhttp_uri_get_query(uri))
			fprintf(stderr, "Debug: query '%s'\n", evhttp_uri_get_query(uri));

		for (header = req->input_headers->tqh_first; header; header = header->next.tqe_next) {
			fprintf(stderr, "  %s: %s\n", header->key, header->value);
		}
	}

	/* may be 'usual case': head/get, no cgi_map, no authentication, no location, no blacklist_patterns, uri exists and it's a file */
	command = evhttp_request_get_command(req);
	if (command == EVHTTP_REQ_GET || command == EVHTTP_REQ_HEAD) {
		if (vh->can_quick) {
			if ((ret = get_fs_path(req, vh, &paths, fs_path, &st)) > -1) {
				if (!S_ISDIR(st.st_mode)) {
#ifdef USE_DEBUG
					if (conf.debug)
						fprintf(stderr, "Debug: quick do_fs_file '%s'\n", paths.paths[ret]);
#endif
					do_fs_file(req, cb_data, fs_path, &st);
					goto done;
				}
			}
		}
	}

#if defined(HAVE_BLACKLISTD) || defined(HAVE_BLOCKLISTD)
	if (!SLIST_EMPTY(&vh->blacklist_patterns)) {
		if (blacklist_lookup(vh, &paths)) {
#ifdef USE_DEBUG
			if (conf.debug)
				fprintf(stderr, "Debug: uri blacklisted\n");
#endif
#if defined(HAVE_BLACKLISTD)
			do_blacklist(req, BLACKLIST_ABUSIVE_BEHAVIOR);
#endif
#if defined(HAVE_BLOCKLISTD)
			do_blacklist(req, BLOCKLIST_ABUSIVE_BEHAVIOR);
#endif
			do_error(req, cb->vh, HTTP_INTERNAL, NULL, "do_request - blacklist");
			goto done;
		}
	}
#endif

	if (!SLIST_EMPTY(&vh->locations)) {
		vh = request_location_lookup(vh, &paths);
		if (vh == NULL) {
			/* No location match, restore vh */
			vh = cb->vh;
		}
		else {
			if ((cb_data_location = malloc(sizeof(struct callback_data))) == NULL) {
				do_error(req, cb->vh, HTTP_INTERNAL, NULL, "do_request - unable to allocate memory");
				goto done;
			}
			cb_data_location->httpd = cb->httpd;
			cb_data_location->vh = vh;
			cb_data_location->host = cb->host;
			cb_data_location->service = cb->service;
			cb_data_location->port = cb->port;

			cb = cb_data_location;
			paths.offset = strlen(vh->location);
#ifdef USE_DEBUG
			if (conf.debug) {
				for (i = 0; i < paths.nb; i++)
					fprintf(stderr, "Debug: location '%s' paths '%s'\n", vh->location, paths.paths[i]);
			}
#endif
			if (!access_granted(req, cb))
				goto done; /* access denied */
		}
	}

	if (vh->authentication && auth_lookup(vh, &paths) && !auth_check(req, vh))
		goto done;

	if ((filename = cgi_map_lookup(vh, &paths)) != NULL) {
		if (command == EVHTTP_REQ_HEAD) {
			evhttp_send_reply(req, HTTP_BADMETHOD, "Bad method", NULL);
			goto done;
		}
		cgi_paths.offset = paths.offset;
		cgi_paths.nb = 1;
		cgi_paths.paths[0] = filename;
		if (get_fs_path(req, vh, &cgi_paths, fs_path, &st) == -1) {
			evhttp_send_reply(req, HTTP_NOTFOUND, "Not found", NULL);
			goto done;
		}

		do_cgi(req, (void *)cb, fs_path, &st);
		goto done;
	}

	if (ret == -1)
		goto done;

	if (ret == -2 && get_fs_path(req, vh, &paths, fs_path, &st) == -1)
		goto done;

	/* if fs_path is dir and paths.paths[0] don't end with / -> 301 */
	len = strlen(paths.paths[0]);
	if (S_ISDIR(st.st_mode) && paths.paths[0][len - 1] != '/') {
		do_location(req, paths.paths[0], evhttp_uri_get_query(uri));
		goto done;
	}

	if (!S_ISDIR(st.st_mode)) {
		do_fs_file(req, (void *)cb, fs_path, &st);
		goto done;
	}

	/* If no index file in this directory */
	if ((ret = request_index_lookup(vh, fs_path, &indexfile, &st_index)) == 0) {
		do_fs_dir(req, vh, fs_path, paths.paths[0]);
		goto done;
	}
	/* request_index_lookup can return -1 */
	if (ret == -1) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "request_index_lookup - unable to allocate memory");
		goto done;
	}

	do_fs_file(req, (void *)cb, indexfile, &st_index);
	free(indexfile);

done:
	if (paths.nb != 0) {
		for (i = 0; i > paths.nb; i++)
			if (paths.paths[i])
				free(paths.paths[i]);
	}
	if (cb_data_location)
		free(cb_data_location);

	if (uri)
		evhttp_uri_free(uri);
	return;
}

static void
do_status(struct evhttp_request *req, void *cb_data)
{
	int len;
	char buf[MAX_SIZE_INT + 1];
	struct rusage ru;
	struct evbuffer *evb;
	struct callback_data *cb = cb_data;
	struct vhost *vh = cb->vh;

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: do_status\n");
#endif

	if (!access_granted(req, cb))
		return; /* access denied */

	if ((evb = evbuffer_new()) == NULL) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_status - unable to allocate memory");
		return;
	}

	if (getrusage(RUSAGE_SELF, &ru) != 0) {
		do_error(req, vh, HTTP_INTERNAL, NULL, "do_status - getrusage error");
		goto done;
	}

#define STATUS_HEADER "stime\tutime\tmaxrss\tixrss\tidrss\tisrss\tminflt\tmajflt\tnswap\tinblock\toublock\tmsgsnd\tmsgrcv\tnsignals\tnvcsw\tnivcsw"

#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"

	len = evbuffer_add_printf(evb, FMT_STATUS, STATUS_HEADER,
			(long long int)ru.ru_stime.tv_sec, (long int)ru.ru_stime.tv_usec,
			(long long int)ru.ru_utime.tv_sec, (long int)ru.ru_utime.tv_usec,
			ru.ru_maxrss, ru.ru_ixrss, ru.ru_idrss, ru.ru_isrss, ru.ru_minflt, ru.ru_majflt,
			ru.ru_nswap,ru.ru_inblock, ru.ru_oublock, ru.ru_msgsnd, ru.ru_msgrcv,
			ru.ru_nsignals, ru.ru_nvcsw, ru.ru_nivcsw
			);
	snprintf(buf, sizeof(buf), "%d", len);
	evhttp_add_header(req->output_headers, "Content-Length", buf);
	evhttp_add_header(req->output_headers, "Content-Type", "text/plain");
	evhttp_send_reply(req, HTTP_OK, "OK", evb);

done:
	evbuffer_free(evb);

	return;
}

static int
do_vhost(struct event_base *eb, struct vhost *vh, bool flag_keep)
{
	struct evhttp *httpd;
	struct callback_data *cb_data, *item;
	struct mohawk_address_info *ai;
	char hostname[NI_MAXHOST];
	char service[NI_MAXSERV];
	int error, nb_listen = 0;

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: processing vhost '%s'\n", vh->host);
#endif

	SLIST_FOREACH(ai, &vh->address_infos, entry) {
		if ((error = getnameinfo(ai->ai->ai_addr, ai->ai->ai_addrlen,
			                     hostname, sizeof(hostname),
			                     service, sizeof(service),
			                     NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
			warnx("getnameinfo(): %s", gai_strerror(error));
		}

		httpd = evhttp_new(eb);
		evhttp_set_allowed_methods(httpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_HEAD);
		if ((cb_data = malloc(sizeof(struct callback_data))) == NULL) {
			errx(EXIT_FAILURE, "do_vhost - unable to allocate memory");
		}
		cb_data->httpd = httpd;
		cb_data->vh = vh;
		cb_data->host = strdup(hostname);
		cb_data->service = strdup(service);
		cb_data->port = parse_number(service, 1, IPPORT_MAX);

		if (cb_data->port < IPPORT_RESERVED && flag_keep)
			warnx("Listen on port < %d with flag_keep", IPPORT_RESERVED);

		if (vh->status_url != NULL)
			evhttp_set_cb(httpd, vh->status_url, do_status, cb_data);
		evhttp_set_gencb(httpd, do_request, cb_data);

		if (evhttp_bind_socket(cb_data->httpd, cb_data->host, (uint16_t)cb_data->port) == 0) {
			SLIST_INSERT_HEAD(&conf.cb_datas, cb_data, entry);
#ifdef USE_DEBUG
			if (conf.debug)
				fprintf(stderr, "Debug: vhost '%s' listen on '%s' '%s'\n", vh->host, hostname, service);
#endif
			nb_listen++;
		}
		else {
			/* Check if hostname/service is not listenning by another vhost */
			error = 1;
			SLIST_FOREACH(item, &conf.cb_datas, entry) {
				if (item->port == cb_data->port && strcmp(item->host, cb_data->host) == 0) {
					evhttp_add_virtual_host(item->httpd, vh->host, httpd);
#ifdef USE_DEBUG
					if (conf.debug)
						fprintf(stderr, "Debug: add vhost '%s' to '%s' '%d'\n", vh->host, item->host, item->port);
#endif
					error = 0;
					break;
				}
			}
			if (error) {
				free(cb_data->host);
				free(cb_data->service);
				free(cb_data);
			}
		}
	}

	return nb_listen;
}

static struct evhttp_uri *
get_clean_uri(struct evhttp_request *req)
{
	const char *uri = evhttp_request_get_uri(req);
	const char *c;

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: from '%s' method '%s' url '%s'\n",
			req->remote_host,  methods[evhttp_request_get_command(req)], uri);
#endif
	c = uri;
	if (uri[1] == '/') {
		c++;
		while (*c != '\0' && *c == '/') {
			c++;
		}
		c--;
	}
#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: get_clean_uri '%s'\n", c);
#endif

	return evhttp_uri_parse_with_flags(c, 0);
}

static int
get_fs_path(struct evhttp_request *req, struct vhost *vh, struct mohawk_path *paths, char *fs_path, struct stat *st)
{
	int i, nb;
	char buf[PATH_MAX];
	char *uri = NULL;

	for (i = 0; i < paths->nb; i++) {
		free(uri);
		uri =  evhttp_uridecode(paths->paths[i] + paths->offset, 0, NULL);
		if (strcmp(vh->host, "*") == 0 || !vh->hostname_in_rootdir)
			nb = snprintf(buf, PATH_MAX, "%s%s",  vh->rootdir, uri);
		else
			nb = snprintf(buf, PATH_MAX, "%s/%s%s", vh->rootdir, evhttp_request_get_host(req), uri);

#ifdef USE_DEBUG
		if (conf.debug)
			fprintf(stderr, "Debug: get_fs_path '%s'\n", buf);
#endif

		if (nb >= PATH_MAX) {
#define FMT_TOO_LONG "get_fs_path - '%s' + '%s' == %d (>= %d)"
			do_error(req, vh, HTTP_INTERNAL, "filename too long", FMT_TOO_LONG, vh->host, uri, nb, PATH_MAX);
			free(uri);
			return -1;
		}

		if (realpath(buf, fs_path) == NULL) {
#ifdef USE_DEBUG
			if (conf.debug)
				fprintf(stderr, "Debug: realpath '%s' == NULL\n", fs_path);
#endif
			continue;
		}

#ifdef USE_DEBUG
		if (conf.debug)
			fprintf(stderr, "Debug: fs_path '%s'\n", fs_path);
#endif

		if (strcmp(vh->host, "*") == 0 || !vh->hostname_in_rootdir)
			snprintf(buf, PATH_MAX, "%s",  vh->rootdir);
		else
			snprintf(buf, PATH_MAX, "%s/%s", vh->rootdir, evhttp_request_get_host(req));

		if (strstr(fs_path, buf) == NULL && !vh->external_symlinks) {
#ifdef USE_DEBUG
			if (conf.debug)
				fprintf(stderr, "Debug: can't find '%s' in '%s'\n", buf, fs_path);
#endif
			continue;
		}

		if (stat(fs_path, st) == 0) {
			free(uri);
			return i;
		}
	}

	do_error(req, vh, HTTP_NOTFOUND, NULL, "get_fs_path - '%s'", paths->paths[0]);
	free(uri);
	return -1;
}

static void
handle_sigterm(int sig)
{
	syslog(LOG_NOTICE, "exiting due to signal %d", sig);
	closelog();

	errx(EXIT_FAILURE, "exiting due to signal %d", sig);
}

static void
handle_sigchld(int sig)
{
	(void)sig;
	pid_t pid;
	int status;

	while((pid = waitpid(-1, &status, WNOHANG)) > 0);
}

static void
mohawk_event_log_cb(int severity, const char *msg)
{
	if (conf.debug)
		fprintf(stderr, "Debug: mohawk_event_log_cb libevent log severity %d msg: '%s'\n", severity, msg);
	else
		syslog(LOG_ERR, "libevent log severity %d msg: '%s'", severity, msg);
}

#define DATE_1123 "%a, %d %b %Y %H:%M:%S"
#define DATE_1036 "%A, %d-%b-%y %H:%M:%S"
#define DATE_ANSI "%a %b %d %H:%M:%S %Y"

static time_t
parse_date(const char *s)
{
	struct tm t;
	if (!s)
		return -1;
	t.tm_isdst = 0;

	if (strptime(s, DATE_1123, &t) != NULL)
		return timegm(&t);

	if (strptime(s, DATE_1036, &t) != NULL)
		return timegm(&t);

	if (strptime(s, DATE_ANSI, &t) != NULL)
		return timegm(&t);

	return -1;
}

static void
request_decode_paths(struct mohawk_path *paths)
{
	char *percent = NULL, *plus = NULL;

	plus = index(paths->paths[0], '+');
	percent = index(paths->paths[0], '%');
		
	if (!percent && !plus)
		return;

	if (percent && !plus) {
		paths->paths[paths->nb++] = evhttp_uridecode(paths->paths[0], 0, NULL);
		return;
	}

	paths->paths[paths->nb++] = evhttp_uridecode(paths->paths[0], 1, NULL);
	return;
}

static int
request_index_lookup(struct vhost *vh, const char *dir, char **file, struct stat *st)
{
	struct mohawk_list *index_name;

	if ((*file = malloc(PATH_MAX)) == NULL)
		return -1;

	SLIST_FOREACH(index_name, &vh->index_names, entry) {
		if (snprintf(*file, PATH_MAX, "%s/%s", dir, index_name->item) >= PATH_MAX) {
#if defined(USE_DEBUG)
			if (conf.debug)
				fprintf(stderr, "Debug: skip file '%s' (>= PATH_MAX)\n", index_name->item);
#endif
			continue;
		}
		if (stat(*file, st) >= 0)
			if (st->st_mode & S_IFREG)
				return 1;
	}

	free(*file);

	return 0;
}

static struct vhost *
request_location_lookup(struct vhost *vhost, struct mohawk_path *paths)
{
	struct vhost *vh;
	int i;

	for (i = 0; i < paths->nb; i++) {
		SLIST_FOREACH(vh, &vhost->locations, entry) {
			if (strncmp(vh->location, paths->paths[i], strlen(vh->location)) == 0)
				return vh;
		}
	}
	return NULL;
}

static void
usage(int ret)
{
	fprintf(stderr, "\
usage: %s [-c configfile | [[ -L listen ] [ -P port ] [ -R rootdir ]] [-d] [-D] [-k] [-h] [-n] [-p pidfile] [-v]\n\
\t-c configfile (incompatible with -L/-P/-R)\n\
\t-d active debug mode (if %s is compiled with -DUSE_DEBUG)\n\
\t-D dump config file (more verbose with -d)\n\
\t-h this message\n\
\t-k keep identity (don't setuid)\n\
\t-L listen (incompatible with -c) default %s\n\
\t-n check config file\n\
\t-p pidfile\n\
\t-P port (incompatible with -c) default %s\n\
\t-R rootdir (incompatible with -c) default %s\n\
\t-v display version\n\
", getprogname(), getprogname(), DEFAULT_LISTEN, DEFAULT_HTTP_PORT, DEFAULT_ROOTDIR);

	exit(ret);
}

int
main(int argc, char **argv)
{
	bool flag_command_line = false, flag_check_config = false, flag_config = false, flag_dump = false, flag_keep = false;
	int ch, nb_listen = 0, syslog_facility, ret;
	const char *listen = DEFAULT_LISTEN, *port = DEFAULT_HTTP_PORT, *rootdir = DEFAULT_ROOTDIR;
	struct event_base *eb;
	struct vhost *vh;
	struct passwd* pwd = NULL;
#if defined(__FreeBSD__)
	pid_t otherpid;
#endif
	uid_t uid = 65534;
	gid_t gid = 65534;
	struct sigaction act_pipe, act_handler, act_chld;

	/* configuration initialization */
#if defined(HAVE_BLACKLISTD) || defined(HAVE_BLOCKLISTD)
	conf.blacklistd = false;
#endif
	conf.chroot = NULL;
	conf.debug = false;
	conf.pidfile = NULL;
	conf.syslog_facility = DEFAULT_SYSLOG_FACILITY;
	conf.user = DEFAULT_USER;

	while ((ch = getopt(argc, argv, "vc:dDkL:np:P:R:h")) != -1) {
		switch (ch) {
		case 'v':
			printf("%s/%s\n", getprogname(), MOHAWK_VERSION );
			exit(EXIT_SUCCESS);
		case 'c':
			if (!flag_command_line) {
				parse_config(optarg);
				flag_config = true;
			}
			else
				warnx("'-c' option is incompatible with '-L/-P/-R' option");
			break;
		case 'd':
			conf.debug = true;
			break;
		case 'D':
			flag_dump = true;
			break;
		case 'k':
			flag_keep = true;
			break;
		case 'L':
			if (!flag_config) {
				listen = optarg;
				flag_command_line = true;
			}
			else
				warnx("'-L' option is incompatible with '-c' option");
			break;
		case 'n':
			flag_check_config = true;
			break;
		case 'p':
			conf.pidfile = optarg;
			break;
		case 'P':
			if (!flag_config) {
				port = optarg;
				flag_command_line = true;
			}
			else
				warnx("'-P' option is incompatible with '-c' option");
			break;
		case 'R':
			if (!flag_config) {
				rootdir = optarg;
				flag_command_line = true;
			}
			else
				warnx("'-R' option is incompatible with '-c' option");
			break;
		case 'h':
		default:
			usage(EXIT_SUCCESS);
		}
	}
	argc -= optind;
	argv += optind;

	if (!flag_config && !flag_command_line)
		usage(EXIT_FAILURE);

	if (flag_command_line)
		parse_command_line(listen, port, rootdir);

	if (conf.default_vhost == NULL)
		errx(EXIT_FAILURE, "No 'default' vhost found");

	if (flag_check_config)
		exit(EXIT_SUCCESS);

	if (conf.pidfile == NULL)
		conf.pidfile = DEFAULT_PIDFILE;
#if defined (__OpenBSD__)
	else if (strchr(conf.pidfile, '/')) {
		errx(EXIT_FAILURE, "'%s': bad pid basename. Don't use absolute or relative pathname. See pidfile(3).", conf.pidfile);
	}
#endif

	if (flag_dump) {
		dump_config();
		exit(EXIT_SUCCESS);
	}

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: starting debug mode\n");
#endif

#ifdef USE_DEBUG
	if (conf.debug && flag_keep)
		fprintf(stderr, "Debug: keep my identity\n");
#endif

	if (!flag_keep && getuid() != 0)
		errx(EXIT_FAILURE, "\
For security reason, only root can run %s.\n\
chroot and setuid need root privilege.\n\
By default, chroot is optional and %s run as %s.\
", getprogname(), getprogname(), DEFAULT_USER);


	if (facility_lookup(conf.syslog_facility, &syslog_facility) == NULL)
		errx(EXIT_FAILURE, "Can't use '%s' as syslog facility", conf.syslog_facility);
	openlog(getprogname(), LOG_NDELAY|LOG_PID, syslog_facility);

	if (!conf.debug) {
		if (daemon(1, 0) < 0) {
			syslog(LOG_CRIT, "daemon - %m");
			closelog();
			err(EXIT_FAILURE, "daemon");
		}
	}

	if (!flag_keep) {
		/* We can't not seriously run as root */
		if ((pwd = getpwnam(conf.user)) == NULL) {
			syslog(LOG_CRIT, "unknown user - '%s'", conf.user);
			errx(EXIT_FAILURE, "unknown user - '%s'", conf.user);
		}
		uid = pwd->pw_uid;
		gid = pwd->pw_gid;
	}
	/* configure libevent */
	event_set_log_callback(mohawk_event_log_cb);

	if ((eb = event_base_new()) == NULL)
		err(EXIT_FAILURE, "event_base_new return NULL");

	/* Read zone info now, in case we chroot(). */
	tzset();

#if defined(HAVE_BLACKLISTD) || defined(HAVE_BLOCKLISTD)
	if (conf.blacklistd) {
#if defined(HAVE_BLACKLISTD)
		if ((blstate = blacklist_open()) == NULL) {
			syslog(LOG_ERR, "blacklist_open - %m");
			warn("Cannot open blacklist");
		}
#endif
#if defined(HAVE_BLOCKLISTD)
		if ((blstate = blocklist_open()) == NULL) {
			syslog(LOG_ERR, "blocklist_open - %m");
			warn("Cannot open blocklist");
		}
#endif
#ifdef USE_DEBUG
		if (conf.debug)
			fprintf(stderr, "Debug: open blacklist\n");
#endif
	}
#endif

	/* Chroot if requested. */
	if (conf.chroot != NULL) {
		if (chroot(conf.chroot) < 0) {
			syslog(LOG_CRIT, "chroot - %m");
			err(EXIT_FAILURE, "chroot");
		}

		/* Always chdir to / after a chroot. */
		if (chdir("/") < 0) {
			syslog(LOG_CRIT, "chroot chdir - %m");
			err(EXIT_FAILURE, "chroot chdir");
		}
	}

	/* First, vhost 'default' */
	SLIST_INIT(&conf.cb_datas);
	nb_listen += do_vhost(eb, conf.default_vhost, flag_keep);
	SLIST_FOREACH(vh, &conf.vhosts, entry) {
		if (strcmp(vh->host, "*") == 0)
			break;
		nb_listen += do_vhost(eb, vh, flag_keep);
	}

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: listening on '%d' ips\n", nb_listen);
#endif

	if (nb_listen == 0)
		errx(EXIT_FAILURE, "No listen, may be another mohawk proccess is running ?");

	/*
	 * Don't search pidfile_remove'function. We will lose root privilege
	 * and may be chroot.
	 * We will have no more access to the pidfile.
	 */
	if (!conf.debug) {
#if defined(__FreeBSD__)
		if ((conf.pfh = pidfile_open(conf.pidfile, 0644, &otherpid)) == NULL) {
			if (errno == EEXIST)
				errx(EXIT_FAILURE, "Daemon already running, pid: %jd.", (intmax_t)otherpid);
			warn("Cannot open or create pidfile '%s'", conf.pidfile);
		}
#endif
#if defined(__NetBSD__) || defined(__OpenBSD__)
		if (pidfile(conf.pidfile) < 0)
			warn("Cannot open or create pidfile '%s'", conf.pidfile);
#endif
	}

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: pidfile '%s'\n", conf.pidfile);
#endif

	if (!flag_keep) {
		/* We are root, start becoming someone else. */
		/* Set aux groups to null. */
		if (setgroups(0, (gid_t*) 0) < 0) {
			syslog(LOG_CRIT, "setgroups - %m");
			err(EXIT_FAILURE, "setgroups");
		}
		/* Set primary group. */
		if (setgid(gid) < 0) {
			syslog(LOG_CRIT, "setgid - %m");
			err(EXIT_FAILURE, "setgid");
		}
		/* Try setting aux groups correctly - not critical if this fails. */
		if (initgroups(conf.user, gid) < 0) {
			syslog(LOG_ERR, "initgroups - %m");
			warn("initgroups");
		}
		/* Set login name. */
		setlogin(conf.user);
	}


	if (!flag_keep) {
		/* Bye bye root privilege */
		if (setuid(uid) < 0) {
			syslog(LOG_CRIT, "setuid - %m");
			err(EXIT_FAILURE, "setuid");
		}
	}

#ifdef USE_DEBUG
	if (conf.debug)
		fprintf(stderr, "Debug: start dispatching\n");
#endif

#if defined(__FreeBSD__)
	if (!conf.debug)
		pidfile_write(conf.pfh);
#endif
	
	act_handler.sa_handler = handle_sigterm;
	sigemptyset (&act_handler.sa_mask);
	act_handler.sa_flags = 0;
	sigaction(SIGINT, &act_handler, NULL);
	sigaction(SIGTERM, &act_handler, NULL);
	sigaction(SIGUSR1, &act_handler, NULL);

	/* without, if client close the socket, our daemon die */
	act_pipe.sa_handler = SIG_IGN;
	act_pipe.sa_flags = 0;
	sigemptyset(&act_pipe.sa_mask);
	sigaction(SIGPIPE, &act_pipe, NULL);

	/* Handle SIGCHLD properly for cgi */
	sigemptyset(&act_chld.sa_mask);
	act_chld.sa_flags = 0;
	act_chld.sa_handler = handle_sigchld;
	sigaction(SIGCHLD, &act_chld, NULL);


	/* if error errno is set */
	ret = event_base_dispatch(eb);

	/* TODO: free each http virtual host */
	err(EXIT_FAILURE, "end mohawk, event_base_dispatch return %d", ret);
}

/* TODO ssl: http://www.wangafu.net/~nickm/libevent-book/Ref6a_advanced_bufferevents.html */
/* vim: set sw=4 sts=4 ts=4 : */
