/* 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/fcntl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#if defined(__FreeBSD__)
#include <sys/capsicum.h>
#endif

#include <net/if.h>
#include <netinet/in.h>

#include <arpa/inet.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SYSLOG_NAMES 1
#include <syslog.h>
#include <unistd.h>

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

#define NUMBER_IS_PORT(PORT) \
	if (PORT < 1 || PORT > IPPORT_MAX) { \
		yyerror("'%d': not a valid port", PORT); \
		YYERROR; \
	}

#define RESET_ADDR_INFO() \
	struct mohawk_address_info *ai; \
	if (conf.default_vhost == NULL) { \
		yyerror("reset listen is forbidden inside default vhost"); \
		YYERROR; \
	} \
	while (!SLIST_EMPTY(&current_vh->address_infos)) { \
		ai = SLIST_FIRST(&current_vh->address_infos); \
		SLIST_REMOVE_HEAD(&current_vh->address_infos, entry); \
		free(ai); \
	}

#define SLIST_LAST(var, last, head, field) \
        for ((var) = SLIST_FIRST((head)); \
            (var); \
            (last) = (var), (var) = SLIST_NEXT((var), field))

#define SET_MOHAWK_LIST(ITEM, FIELD) \
	if (!SLIST_EMPTY(&current_vh->FIELD)) \
		SLIST_LAST(list_mohawk, list_last, &current_vh->FIELD, entry); \
	if ((list_mohawk = malloc(sizeof(struct mohawk_list))) == NULL) \
		errx(EXIT_FAILURE, "Unable to allocate memory"); \
	list_mohawk->item = ITEM; \
	if (SLIST_EMPTY(&current_vh->FIELD)) \
		SLIST_INSERT_HEAD(&current_vh->FIELD, list_mohawk, entry); \
	else \
		SLIST_INSERT_AFTER(list_last, list_mohawk, entry);

#define SET_MOHAWK_HASH(KEY, VALUE, FIELD) \
	if (!SLIST_EMPTY(&current_vh->FIELD)) \
		SLIST_LAST(hash_mohawk, hash_last, &current_vh->FIELD, entry); \
	if ((hash_mohawk = malloc(sizeof(struct mohawk_hash))) == NULL) \
		errx(EXIT_FAILURE, "Unable to allocate memory"); \
	hash_mohawk->key = KEY; \
	hash_mohawk->value = VALUE; \
	if (SLIST_EMPTY(&current_vh->FIELD)) \
		SLIST_INSERT_HEAD(&current_vh->FIELD, hash_mohawk, entry); \
	else \
		SLIST_INSERT_AFTER(hash_last, hash_mohawk, entry);

#define RESET_MOHAWK_HASH(FIELD) \
	while (!SLIST_EMPTY(&current_vh->FIELD)) { \
		hash_mohawk = SLIST_FIRST(&current_vh->FIELD); \
		SLIST_REMOVE_HEAD(&current_vh->FIELD, entry); \
		free(hash_mohawk); \
	} \
	SLIST_INIT(&current_vh->FIELD);

#define RESET_MOHAWK_LIST(FIELD) \
	while (!SLIST_EMPTY(&current_vh->FIELD)) { \
		list_mohawk = SLIST_FIRST(&current_vh->FIELD); \
		SLIST_REMOVE_HEAD(&current_vh->FIELD, entry); \
		free(list_mohawk); \
	} \
	SLIST_INIT(&current_vh->FIELD);

#define RESET_MOHAWK_NETWORK \
	while (!SLIST_EMPTY(&current_vh->grant_access)) { \
		network_last = SLIST_FIRST(&current_vh->grant_access); \
		SLIST_REMOVE_HEAD(&current_vh->grant_access, entry); \
		free(network_last); \
	} \
	SLIST_INIT(&current_vh->grant_access);

int yyparse(void);
void yyerror(const char *, ...);
int yywrap(void);

int word_is_dir(const char *, int);
int interface(const char *, const char *, int);
struct vhost * vhost_lookup(const char *);
void vhost_new(const char *, int);
void vhost_clone(const char *, const char *, int);
void clone(struct vhost *, struct vhost *, int);
void vhost_add(int);
struct vhost * location_lookup(const char *);

void parse_network(const char *);
static inline int fill_network_inet(struct mohawk_network *, struct addrinfo *, unsigned int, int);
static inline int fill_network_inet6(struct mohawk_network *, struct addrinfo *, unsigned int, int);

void parse_mime_type(const char *);
static inline void add_mime_type(char *, char *);

extern int yylineno;

static struct vhost *current_vh = NULL, *save_current_vh = NULL, *location_last = NULL;
static struct mohawk_hash *hash_mohawk = NULL, *hash_last = NULL;
static struct mohawk_list *list_mohawk = NULL, *list_last = NULL;
static struct mohawk_network *network_last = NULL;

void
yyerror(const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	vprintf(fmt, ap);
	printf(" at line %d in '%s'\n", yylineno, config_filename);
	va_end(ap);
	exit(EXIT_FAILURE);
}

int yywrap(void)
{
	return 1;
} 

int
word_is_dir(const char *word, int flag_yy)
{
	struct stat sb;
	char buf[PATH_MAX];
	const char *path = word;

	if (conf.chroot != NULL) {
		snprintf(buf, sizeof(buf), "%s%s", conf.chroot, word); 
		path = buf;
	}

	if (stat(path, &sb) != 0) {
		if (flag_yy)
			yyerror("'%s': can't stat", path);
		return 0;
	}
	if (S_ISDIR(sb.st_mode))
		return 1;

	if (flag_yy)
		yyerror("'%s': not a directory", path);

	return 0;
}

const char *
facility_lookup(const char *name, int *facility)
{
	int i = 0;
	while (facilitynames[i].c_name && strcmp(facilitynames[i].c_name, name))
		i++;

	if (facility && facilitynames[i].c_name)
		*facility = facilitynames[i].c_val;

	return facilitynames[i].c_name;
}

int
interface(const char *s, const char *service, int family)
{
	struct mohawk_address_info *addr_info;
	struct ifaddrs *ifap, *p;
	struct addrinfo *ai, *current_ai;
	struct addrinfo hints;
	const struct sockaddr_in6 *sin6 = NULL;
	char hostname[MAXHOSTNAMELEN];
	int error, ret = 0, sa_family = 0;
	void *fix_warnings;

	if (getifaddrs(&ifap) == -1)
		errx(EXIT_FAILURE, "getifaddrs() return -1");

	for (p = ifap; p != NULL; p = p->ifa_next) {
		if (p->ifa_addr == NULL)
			continue;
		if (!(p->ifa_flags & IFF_UP))
			continue;
		if (s != NULL && strcmp(s, p->ifa_name) != 0)
			continue;

		sa_family = p->ifa_addr->sa_family;
		if (sa_family != AF_INET && sa_family != AF_INET6)
			continue;
		if (family > 0 && sa_family != family)
			continue;

		// yes, we skip local link address
		if (sa_family == AF_INET6) {
			fix_warnings = p->ifa_addr;
			sin6 = (const struct sockaddr_in6 *)fix_warnings;
			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
				continue;
		}

		bzero(&hints, sizeof hints);
		hints.ai_family = PF_UNSPEC;
		hints.ai_flags = AI_PASSIVE;
		hints.ai_socktype = SOCK_STREAM;

		if ((error = getnameinfo(p->ifa_addr, p->ifa_addr->sa_len,
		    hostname, MAXHOSTNAMELEN, NULL, 0, NI_NUMERICHOST)) != 0) {
#ifdef USE_DEBUG
			warnx("warning only, getnameinfo(): %s", gai_strerror(error));
#endif
			ret = 0;
			break;
		}

		if ((error = getaddrinfo(hostname, service, &hints, &ai)) != 0) {
#ifdef USE_DEBUG
			warnx("warning only, interface - getaddrinfo('%s', '%s'): %s", hostname, service, gai_strerror(error));
#endif
			ret = 0;
			break;
		}

		for (current_ai = ai; current_ai != NULL; current_ai = current_ai->ai_next) {
			if ((addr_info = calloc(1, sizeof(struct mohawk_address_info))) == NULL)
				errx(EXIT_FAILURE, "Unable to allocate memory");
			addr_info->ai = current_ai;
			SLIST_INSERT_HEAD(&current_vh->address_infos, addr_info, entry);
			ret++;
		}
	}
	freeifaddrs(ifap);
	return ret;
}

int
host(const char *hostname, const char *service)
{
	struct mohawk_address_info *addr_info;
	struct addrinfo *ai, *current_ai;
	struct addrinfo hints;
	int error, ret = 0;

	if (strcmp(hostname, "0.0.0.0") == 0)
		return interface(NULL, service, AF_INET);
	if (strcmp(hostname, "::") == 0)
		return interface(NULL, service, AF_INET6);

	bzero(&hints, sizeof hints);
	hints.ai_family = PF_UNSPEC;
	hints.ai_flags = AI_PASSIVE;
	hints.ai_socktype = SOCK_STREAM;

	if ((error = getaddrinfo(hostname, service, &hints, &ai)) != 0) {
#ifdef USE_DEBUG
		warnx("warning only, host - getaddrinfo('%s', '%s'): %s", hostname, service, gai_strerror(error));
#endif
		return 0;
	}

	for (current_ai = ai; current_ai != NULL; current_ai = current_ai->ai_next) {
		if ((addr_info = calloc(1, sizeof(struct mohawk_address_info))) == NULL)
			errx(EXIT_FAILURE, "Unable to allocate memory");
		addr_info->ai = current_ai;
		SLIST_INSERT_HEAD(&current_vh->address_infos, addr_info, entry);
		ret++;
	}
	return ret;
}

void
vhost_new(const char *name, int is_vhost)
{
	struct vhost *vh;
	char buf[64]; //magic number. sorry :(

	if (is_vhost && strcmp(name, "*") == 0 && conf.default_vhost != NULL)
		errx(EXIT_FAILURE, "vhost 'default' is already defined");

	else if (is_vhost && vhost_lookup(name) != NULL)
		errx(EXIT_FAILURE, "vhost '%s' already defined", name);

	else if (!is_vhost && location_lookup(name) != NULL)
		errx(EXIT_FAILURE, "location '%s' already defined", name);

	if ((vh = malloc(sizeof(struct vhost))) == NULL)
		errx(EXIT_FAILURE, "Unable to allocate memory");

	SLIST_INIT(&vh->auth_patterns);
	SLIST_INIT(&vh->blacklist_patterns);
	SLIST_INIT(&vh->cgi_patterns);
	SLIST_INIT(&vh->index_names);
	SLIST_INIT(&vh->no_auth_patterns);
	SLIST_INIT(&vh->no_cgi_maps);
	SLIST_INIT(&vh->no_log_patterns);
	SLIST_INIT(&vh->cgi_env);
	SLIST_INIT(&vh->cgi_map);
	SLIST_INIT(&vh->grant_access);

	vh->email_admin = NULL;
	vh->auth_path = NULL;
	vh->charset = NULL;
	vh->dirlist_css_url = NULL;
	snprintf(buf, sizeof(buf), "%s/%s", getprogname(), MOHAWK_VERSION);
	vh->mohawk_name = strdup(buf);
	vh->rootdir = NULL;

	vh->authentication = false;
	vh->auth_blacklistd = false;
	vh->can_quick = true;
	vh->cgi_env_count = 0;
	vh->cgi_expose_mohawk_version = false;
	vh->dirlist = false;
	vh->external_symlinks = false;
	vh->hostname_in_rootdir = false;
	vh->maxage = 0;
	vh->x_forwarded_for = false;

	if (is_vhost) {
		SLIST_INIT(&vh->address_infos);
		SLIST_INIT(&vh->locations);
		vh->host = name;
		vh->status_url = NULL;
		if (strcmp(name, "*") == 0)
			conf.default_vhost = vh;
		save_current_vh = vh;
	}
	else {
		vh->location = name;
	}
	current_vh = vh;
}

struct vhost *
vhost_lookup(const char *hostname)
{
	struct vhost *vh;
	SLIST_FOREACH(vh, &conf.vhosts, entry) {
		if (strcmp(vh->host, hostname) == 0)
			return vh;
	}
	return NULL;
}

void
vhost_clone(const char *from, const char *to, int is_vhost)
{
	struct vhost *src, *dst;

	if (conf.default_vhost == NULL)
		errx(EXIT_FAILURE, "Can't proccess '%s': default vhost not yet exists", to);

	if (is_vhost) {
		src = conf.default_vhost;
		if (strcmp(from, "*") != 0)
			if ((src = vhost_lookup(from)) == NULL)
				errx(EXIT_FAILURE, "Can't clone vhost '%s': not found", from);
	}
	else {
		src = save_current_vh;
		if (strcmp(from, "*") != 0)
			if ((src = location_lookup(from)) == NULL)
				errx(EXIT_FAILURE, "Can't clone location '%s': not found", from);
	}
	vhost_new(to, is_vhost);
	dst = current_vh;

	clone(src, dst, is_vhost);
	if (!is_vhost)
		location_last = current_vh;
}

void
clone(struct vhost *from, struct vhost *to, int is_vhost)
{
	struct mohawk_list *from_list, *to_list;
	struct mohawk_hash *from_hash, *to_hash;
	struct mohawk_address_info *from_ai, *to_ai;
	struct mohawk_network *from_network, *to_network;

	to->rootdir = from->rootdir;
	to->auth_path = from->auth_path;
	to->charset = from->charset;
	to->dirlist_css_url = from->dirlist_css_url;
	to->email_admin = from->email_admin;
	to->mohawk_name = from->mohawk_name;

	to->authentication = from->authentication;
	to->auth_blacklistd = from->auth_blacklistd;
	to->can_quick = from->can_quick;
	to->cgi_expose_mohawk_version = from->cgi_expose_mohawk_version;
	to->dirlist = from->dirlist;
	to->external_symlinks = from->external_symlinks;
	to->hostname_in_rootdir = from->hostname_in_rootdir;
	to->maxage = from->maxage;
	to->x_forwarded_for = from->x_forwarded_for;

#define CLONE_LIST(FIELD) \
	SLIST_FOREACH(from_list, &from->FIELD, entry) { \
		if ((to_list = malloc(sizeof(struct mohawk_list))) == NULL) \
			errx(EXIT_FAILURE, "Unable to allocate memory"); \
		if ((to_list->item = strdup(from_list->item)) == NULL) \
			errx(EXIT_FAILURE, "Unable to allocate memory"); \
		if (SLIST_EMPTY(&to->FIELD)) \
			SLIST_INSERT_HEAD(&to->FIELD, to_list, entry); \
		else \
			SLIST_INSERT_AFTER(list_last, to_list, entry); \
		list_last = to_list; \
	} 

	CLONE_LIST(auth_patterns);
	CLONE_LIST(blacklist_patterns);
	CLONE_LIST(cgi_patterns);
	CLONE_LIST(index_names);
	CLONE_LIST(no_auth_patterns);
	CLONE_LIST(no_cgi_maps);
	CLONE_LIST(no_log_patterns);

#define CLONE_HASH(FIELD) \
	SLIST_FOREACH(from_hash, &from->FIELD, entry) { \
		if ((to_hash = malloc(sizeof(struct mohawk_hash))) == NULL) \
			errx(EXIT_FAILURE, "Unable to allocate memory"); \
		if ((to_hash->key = strdup(from_hash->key)) == NULL) \
			errx(EXIT_FAILURE, "Unable to allocate memory"); \
		if ((to_hash->value = strdup(from_hash->value)) == NULL) \
			errx(EXIT_FAILURE, "Unable to allocate memory"); \
		if (SLIST_EMPTY(&to->FIELD)) \
			SLIST_INSERT_HEAD(&to->FIELD, to_hash, entry); \
		else \
			SLIST_INSERT_AFTER(hash_last, to_hash, entry); \
		hash_last = to_hash; \
	} 

	CLONE_HASH(cgi_map);
	CLONE_HASH(cgi_env);
	to->cgi_env_count = from->cgi_env_count;

	SLIST_FOREACH(from_network, &from->grant_access, entry) {
		if ((to_network = malloc(sizeof(struct mohawk_network))) == NULL)
			errx(EXIT_FAILURE, "Unable to allocate memory");
		to_network->item = from_network->item;
		to_network->family = from_network->family;
		to_network->network = from_network->network;
		if (SLIST_EMPTY(&to->grant_access))
			SLIST_INSERT_HEAD(&to->grant_access, to_network, entry);
		else
			SLIST_INSERT_AFTER(network_last, to_network, entry);
		network_last = to_network;
	}

	if (!is_vhost) {
		to->host = from->host;
		return;
	}

	to->status_url = from->status_url;

	SLIST_FOREACH(from_ai, &from->address_infos, entry) {
		if ((to_ai = malloc(sizeof(struct mohawk_address_info))) == NULL)
			errx(EXIT_FAILURE, "Unable to allocate memory");
		to_ai->ai = from_ai->ai;
		SLIST_INSERT_HEAD(&to->address_infos, to_ai, entry);
	}
}

void
vhost_add(int is_vhost)
{
	int nb = 0;
	char buf[PATH_MAX];
	struct vhost *location, *last_location;

	if (is_vhost && strcmp(current_vh->host, "*") == 0) {
		if (SLIST_EMPTY(&current_vh->address_infos)) {
			nb += interface(NULL, DEFAULT_HTTP_PORT, AF_INET);
			nb += interface(NULL, DEFAULT_HTTP_PORT, AF_INET6);
			if (!nb)
				errx(EXIT_FAILURE, "Can't find any ip for listenning on '%s'. Hu ?", DEFAULT_HTTP_PORT);
		}
		if (current_vh->hostname_in_rootdir)
			errx(EXIT_FAILURE, "Can't add 'default' vhost: 'hostname_in_rootdir' is 'on'");
	}
	if (current_vh->rootdir == NULL)
		errx(EXIT_FAILURE, "Can't add '%s': no rootdir", current_vh->host);

	if (is_vhost && SLIST_EMPTY(&current_vh->address_infos))
		errx(EXIT_FAILURE, "Can't add vhost '%s': no listen", current_vh->host);

	if (current_vh->authentication) {
		if (current_vh->auth_path == NULL)
			errx(EXIT_FAILURE, "Can't add vhost '%s': authentication is 'on' but no auth_path defined", current_vh->host);
		
		if (strstr(current_vh->auth_path, current_vh->rootdir) != NULL)
			errx(EXIT_FAILURE, "Can't add vhost '%s': auth_path '%s' inside rootdir '%s'", current_vh->host, current_vh->auth_path, current_vh->rootdir);

		if (snprintf(buf, PATH_MAX, "%s/%s", current_vh->auth_path, DEFAULT_AUTH_FILE) >= PATH_MAX)
			errx(EXIT_FAILURE, "Can't add vhost '%s': auth_path filename too long", current_vh->host);

		if ((current_vh->auth_path = strdup(buf)) == NULL)
			errx(EXIT_FAILURE, "Can't strdup auth_path");
		current_vh->can_quick = false;
	}

	if (!SLIST_EMPTY(&current_vh->cgi_map))
		current_vh->can_quick = false;

#if defined(HAVE_BLACKLISTD)
	if (!SLIST_EMPTY(&current_vh->blacklist_patterns))
		current_vh->can_quick = false;
#endif

	if (is_vhost) {
		SLIST_INSERT_HEAD(&conf.vhosts, current_vh, entry);
	}
	else {
		save_current_vh->can_quick = false;
		if (SLIST_EMPTY(&save_current_vh->locations))
			SLIST_INSERT_HEAD(&save_current_vh->locations, current_vh, entry);
		else {
			SLIST_LAST(location, last_location, &save_current_vh->locations, entry);
			SLIST_INSERT_AFTER(last_location, current_vh, entry);
		}
		current_vh = save_current_vh;
	}
}

struct vhost *
location_lookup(const char *location)
{
	struct vhost *vh;
	SLIST_FOREACH(vh, &save_current_vh->locations, entry) {
		if (strcmp(vh->location, location) == 0)
			return vh;
	}
	return NULL;
}

void
parse_command_line(const char *listen, const char *port, const char *rootdir)
{
	int nb = 0;
	SLIST_INIT(&conf.mime_type);
	conf.default_vhost = NULL;
	vhost_new("*", 1);
	
	if ((nb = interface(listen, port, -1) + host(listen, port)) == 0)
		err(EXIT_FAILURE, "Can't listen on '%s' port '%s'", listen, port);

	if (!word_is_dir(rootdir, 0))
		err(EXIT_FAILURE, "Can't use '%s' as rootdir", rootdir);

	current_vh->rootdir = rootdir;
	vhost_add(1);
}

void
parse_config(const char *filename)
{
	int fd;
#if defined(__FreeBSD__)
	cap_rights_t rights;
#endif

	SLIST_INIT(&conf.mime_type);
	conf.default_vhost = NULL;

	if ((fd = open(filename, O_RDONLY)) == -1)
		err(EXIT_FAILURE, "%s", filename);

#if defined(__FreeBSD__)
	cap_rights_init(&rights, CAP_READ);
	if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS)
		err(1, "cap_rights_limit() failed");
#endif

	if (close(STDIN_FILENO) == -1)
		err(EXIT_FAILURE, "stdin");

	if (dup(fd) == -1)
		err(EXIT_FAILURE, "%s", filename);
	config_filename = filename;
	yyparse();
	close(fd);

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

void
parse_network(const char *s)
{
	struct mohawk_network *network_mohawk;
	struct addrinfo hints, *ai_mask;
	char *prefix, *netmask;
	int error, not = 0;
	unsigned int defmasklen, masklen;
	int (*fill_network)(struct mohawk_network *, struct addrinfo *, unsigned int, int);

	if ((prefix = strdup(s)) == NULL)
		errx(EXIT_FAILURE, "Unable to allocate memory");

	if ((netmask = strchr(prefix, '/')) != NULL) {
		*netmask = '\0';
		netmask++;
	}
	if (prefix[0] == '!') {
		not = 1;
		prefix++;
	}

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

	if ((error = getaddrinfo(prefix, NULL, &hints, &ai_mask)) != 0) {
		yyerror("'%s': %s", s, gai_strerror(error));
		return;
	}

	switch (ai_mask->ai_family) {
	case PF_INET:
		defmasklen = 32;
		fill_network = fill_network_inet;
		break;
	case PF_INET6:
		defmasklen = 128;
		fill_network = fill_network_inet6;
		break;
	default:
		yyerror("'%s': unexpected address family: %d", s, ai_mask->ai_family);
		return;
	}
	masklen = defmasklen;

	if (netmask != NULL && *netmask != '\0') {
		errno = 0;
		masklen = (unsigned int)strtoul(netmask, NULL, 10);
		if (errno == ERANGE || errno == EINVAL || masklen == 0 || masklen > defmasklen) {
			yyerror("'%s': invalid netmask '%s'", s, netmask);
			return;
		}
	}

	if ((network_mohawk = malloc(sizeof(struct mohawk_network))) == NULL)
		errx(EXIT_FAILURE, "Unable to allocate memory");

	network_mohawk->item = s;
	network_mohawk->family = ai_mask->ai_family;

	if (fill_network(network_mohawk, ai_mask, masklen, not)) {
		if (SLIST_EMPTY(&current_vh->grant_access))
			SLIST_INSERT_HEAD(&current_vh->grant_access, network_mohawk, entry);
		else
			SLIST_INSERT_AFTER(network_last, network_mohawk, entry);
		network_last = network_mohawk;
	}
	return;
}

/*
error: cast from 'struct sockaddr *' to 'struct sockaddr_in6 *'
increases required alignment from 1 to 4 [-Werror,-Wcast-align]
*/
static inline int
fill_network_inet(struct mohawk_network *network, struct addrinfo *ai, unsigned int masklen, int not)
{
	struct mohawk_network_inet *net;
	struct sockaddr_in *sin;
	void *fix_error;

	if ((net = malloc(sizeof(struct mohawk_network_inet))) == NULL)
		errx(EXIT_FAILURE, "Unable to allocate memory");

	fix_error = ai->ai_addr;
	net->mask = htonl(0xffffffff << (32 - masklen));
	sin = (struct sockaddr_in *)fix_error;
	net->prefix = sin->sin_addr.s_addr & net->mask;
	net->not = not;

	network->network = net;
	return 1;
}

static inline int
fill_network_inet6(struct mohawk_network *network, struct addrinfo *ai, unsigned int masklen, int not)
{
	struct mohawk_network_inet6 *net6;
	struct sockaddr_in6 *sin6;
	unsigned int i, q;
	void *fix_error;

	if ((net6 = malloc(sizeof(struct mohawk_network_inet6))) == NULL)
		errx(EXIT_FAILURE, "Unable to allocate memory");

	q = masklen / 8;
	for (i = 0; i < q; i++)
		net6->mask[i] = 0xff;

	for (i = q + 1; i < 16; i++)
		net6->mask[i] = 0x00000000;

	if (q < 16) {
		net6->mask[q] = 0xff;
		net6->mask[q] <<= 8 - (masklen % 8);
	}

	fix_error = ai->ai_addr;
	sin6 = (struct sockaddr_in6 *)fix_error;
	memcpy(net6->prefix, &sin6->sin6_addr, sizeof sin6->sin6_addr);
	for (i = 0; i < 16; i++)
		net6->prefix[i] &= net6->mask[i];

	net6->not = not;
	network->network = net6;
	return 1;
}

/*
* nginx:
*     text/html                             html htm shtml;
* apache:
* application/octet-stream        bin dms lha lzh exe class so dll
*/
void
parse_mime_type(const char *filename)
{
	char buf[PATH_MAX], *start, *end, *mime;
	FILE *fp;
	int lines = 1;

	if ((fp = fopen(filename, "r")) == NULL)
		errx(EXIT_FAILURE, "Can't fopen '%s'", filename);

	while (fgets(buf, sizeof(buf), fp) != NULL) {
		if (!buf[0] || buf[0] == '\n' || buf[0] == '#')
			continue;

		start = buf;
		while (isspace((unsigned char)*start)) { start++; }
		if (!isalpha((unsigned char)*start)) { continue; }

		end = start;
		while (!isspace((unsigned char)*end)) { end++;}
		if (!isspace((unsigned char)*end)) { continue; }
		*end = '\0';
		mime = start;

		end++;
		if (*end == '\0') { continue; }

		start = end;
		while (isspace((unsigned char)*start)) { start++; }
		if (!isalpha((unsigned char)*start)) { goto next_line; }
		if ((end = strchr(start, '\n')) == NULL)
			errx(EXIT_FAILURE, "File '%s' line %d: line too long", filename, lines);
		*end = '\0';
		if ((end = strchr(start, ';')) != NULL)
			*end = '\0';

		add_mime_type(strdup(mime), strdup(start));

next_line:
		lines++;
	}

	fclose(fp);
}

static inline void
add_mime_type(char *key, char *value)
{
	if (key == NULL || value == NULL)
		errx(EXIT_FAILURE, "Unable to allocate memory");
	if ((hash_mohawk = malloc(sizeof(struct mohawk_hash))) == NULL)
		errx(EXIT_FAILURE, "Unable to allocate memory");
	hash_mohawk->key = key;
	hash_mohawk->value = value;
	if (SLIST_EMPTY(&conf.mime_type))
		SLIST_INSERT_HEAD(&conf.mime_type, hash_mohawk, entry);
	else
		SLIST_INSERT_AFTER(hash_last, hash_mohawk, entry);
	hash_last = hash_mohawk;
}
%}

%{
int yylex(void);
%}

/* syntax */
%token EBRACE OBRACE PORT 

/* mohawk */
%token CHROOT MIME_TYPE MOHAWK_DEBUG PIDFILE SYSLOG_FACILITY USER 

/* vhost */
%token VHOST CLONE LOCATION

/* on/off */
%token AUTH_BLACKLISTD AUTHENTICATION CGI_EXPOSE_MOHAWK_VERSION DIRLIST EXTERNAL_SYMLINKS HOSTNAME_IN_ROOTDIR X_FORWARDED_FOR 

/* options */
%token AUTH_PATH CHARSET DIRLIST_CSS_URL EMAIL_ADMIN MAXAGE MOHAWK_NAME ROOTDIR STATUS_URL UNIX_SOCKET_PATH

/* list */
%token AUTH_PATTERNS BLACKLIST_PATTERNS CGI_PATTERNS GRANT_ACCESS INDEX_NAMES NO_AUTH_PATTERNS NO_CGI_MAPS NO_LOG_PATTERNS
%token UNIX_SOCKET_PATTERNS NO_UNIX_SOCKET_PATTERNS

/* hash */
%token CGI_ENV CGI_MAP 

/* command */
%token LISTEN RESET 

/* values */
%token DEFAULT WORD NUMBER WORDS STATE

%union 
{
	unsigned int number;
	char *string;
	bool state;
}

%token <state>  STATE
%token <number> NUMBER
%token <string> WORD
%token <string> WORDS

%%
commands: /* empty */
        | commands command
        ;

command: chroot | debug | mime_type | pidfile | syslog_facility | user | vhost ;

chroot: CHROOT WORD {
	if (!word_is_dir($2, 1))
		YYERROR;
	conf.chroot = $2;
};

debug: MOHAWK_DEBUG STATE {
	conf.debug = $2;
};

mime_type: MIME_TYPE WORD { parse_mime_type($2); }
         | MIME_TYPE mime_type_content { };

mime_type_content: OBRACE mime_type_items EBRACE { };

mime_type_items: | mime_type_items mime_type_item ;

mime_type_item: WORD WORD {
	if (strchr($1, '/'))
		add_mime_type($1, $2);
	else
		add_mime_type($2, $1);
}
              | WORD WORDS { add_mime_type($1, $2); };

pidfile: PIDFILE WORD {
	if (conf.pidfile == NULL)
		conf.pidfile = $2;
};

syslog_facility: SYSLOG_FACILITY WORD {
	if ((conf.syslog_facility = facility_lookup($2, NULL)) == NULL) {
		yyerror("'%s': not a valid syslog facility name", $2);
		YYERROR;
	}
};

user: USER WORD {
	if (getpwnam($2) == NULL) {
		yyerror("'%s': not a valid user", $2);
		YYERROR;
	}
	conf.user = $2;
};

vhost: VHOST DEFAULT { vhost_new("*", 1); } OBRACE vhost_content { vhost_add(1); } 
     | VHOST WORD { vhost_clone("*", $2, 1); } OBRACE vhost_content { vhost_add(1); }
     | VHOST WORD CLONE WORD { vhost_clone($4, $2, 1); } OBRACE vhost_content { vhost_add(1); };

vhost_content: vhost_items EBRACE { };

vhost_items: | vhost_items vhost_item;

vhost_item: authentication | auth_blacklistd | cgi_expose_mohawk_version | dirlist | hostname_in_rootdir | x_forwarded_for
          | auth_path | charset | dirlist_css_url | email_admin | external_symlinks | maxage | mohawk_name | rootdir | status_url
          | auth_patterns | blacklist_patterns | cgi_patterns | grant_access | index_names | no_auth_patterns | no_cgi_maps | no_log_patterns
          | cgi_env | cgi_map
          | location
          | listen | reset

authentication: AUTHENTICATION STATE {
	current_vh->authentication = $2;
};

auth_blacklistd: AUTH_BLACKLISTD STATE {
	current_vh->auth_blacklistd = $2;
	conf.blacklistd = $2;
};

cgi_expose_mohawk_version: CGI_EXPOSE_MOHAWK_VERSION STATE {
	current_vh->cgi_expose_mohawk_version = $2;
};

dirlist: DIRLIST STATE {
	current_vh->dirlist = $2;
};

external_symlinks: EXTERNAL_SYMLINKS STATE {
	current_vh->external_symlinks = $2;
};

hostname_in_rootdir: HOSTNAME_IN_ROOTDIR STATE {
	current_vh->hostname_in_rootdir = $2;
};

x_forwarded_for: X_FORWARDED_FOR STATE {
	current_vh->x_forwarded_for = $2;
};

auth_path: AUTH_PATH WORD {
	if (!word_is_dir($2, 1))
		YYERROR;
	current_vh->auth_path = $2;
};

charset: CHARSET WORD {
	current_vh->charset = $2;
};

dirlist_css_url: DIRLIST_CSS_URL WORD {
	current_vh->dirlist_css_url = $2;
};

email_admin: EMAIL_ADMIN WORD {
	current_vh->email_admin = $2;
};

maxage: MAXAGE NUMBER {
	current_vh->maxage = $2;
};

mohawk_name: MOHAWK_NAME WORD { current_vh->mohawk_name = $2; }
           | MOHAWK_NAME WORDS { current_vh->mohawk_name = $2; };

rootdir: ROOTDIR WORD {
	if (!word_is_dir($2, 1))
		YYERROR;
	current_vh->rootdir = $2;
};

status_url: STATUS_URL WORD {
	current_vh->status_url = $2;
};

auth_patterns: AUTH_PATTERNS auth_patterns_content { };

auth_patterns_content: OBRACE auth_patterns_items EBRACE { };

auth_patterns_items: | auth_patterns_items auth_patterns_item ;

auth_patterns_item: WORD { SET_MOHAWK_LIST($1, auth_patterns); };

blacklist_patterns: BLACKLIST_PATTERNS blacklist_patterns_content { };

blacklist_patterns_content: OBRACE blacklist_patterns_items EBRACE { };

blacklist_patterns_items: | blacklist_patterns_items blacklist_patterns_item ;

blacklist_patterns_item: WORD { SET_MOHAWK_LIST($1, blacklist_patterns); conf.blacklistd = 1; };

cgi_patterns: CGI_PATTERNS cgi_patterns_content { };

cgi_patterns_content: OBRACE cgi_patterns_items EBRACE { };

cgi_patterns_items: | cgi_patterns_items cgi_patterns_item ;

cgi_patterns_item: WORD { SET_MOHAWK_LIST($1, cgi_patterns); };

index_names: INDEX_NAMES index_names_content { };

index_names_content: OBRACE index_names_items EBRACE { };

index_names_items: | index_names_items index_names_item ;

index_names_item: WORD { SET_MOHAWK_LIST($1, index_names); };

no_auth_patterns: NO_AUTH_PATTERNS no_auth_patterns_content { };

no_auth_patterns_content: OBRACE no_auth_patterns_items EBRACE { };

no_auth_patterns_items: | no_auth_patterns_items no_auth_patterns_item ;

no_auth_patterns_item: WORD { SET_MOHAWK_LIST($1, no_auth_patterns); };

no_cgi_maps: NO_CGI_MAPS no_cgi_maps_content { };

no_cgi_maps_content: OBRACE no_cgi_maps_items EBRACE { };

no_cgi_maps_items: | no_cgi_maps_items no_cgi_maps_item ;

no_cgi_maps_item: WORD { SET_MOHAWK_LIST($1, no_cgi_maps); };

no_log_patterns: NO_LOG_PATTERNS no_log_patterns_content { };

no_log_patterns_content: OBRACE no_log_patterns_items EBRACE { };

no_log_patterns_items: | no_log_patterns_items no_log_patterns_item ;

no_log_patterns_item: WORD { SET_MOHAWK_LIST($1, no_log_patterns); };

grant_access: GRANT_ACCESS grant_access_content { };

grant_access_content: OBRACE grant_access_items EBRACE { };

grant_access_items: | grant_access_items grant_access_item;

grant_access_item: WORD { parse_network($1); };

cgi_env: CGI_ENV cgi_env_content { };

cgi_env_content: OBRACE cgi_env_items EBRACE { };

cgi_env_items: | cgi_env_items cgi_env_item ;

cgi_env_item: WORD WORD { SET_MOHAWK_HASH($1, $2, cgi_env); current_vh->cgi_env_count++; }
            | WORD WORDS { SET_MOHAWK_HASH($1, $2, cgi_env); current_vh->cgi_env_count++; };

cgi_map: CGI_MAP cgi_map_content { };

cgi_map_content: OBRACE cgi_map_items EBRACE { };

cgi_map_items: | cgi_map_items cgi_map_item ;

cgi_map_item: WORD WORD { SET_MOHAWK_HASH($1, $2, cgi_map); }
            | WORD WORDS { SET_MOHAWK_HASH($1, $2, cgi_map); };

location: LOCATION WORD { vhost_clone("*", $2, 0); } OBRACE location_content { vhost_add(0); }
        | LOCATION WORD CLONE WORD { vhost_clone($4, $2, 0); } OBRACE location_content { vhost_add(0); }

location_content: location_items EBRACE { };

location_items: | location_items location_item;

location_item: authentication | auth_blacklistd | cgi_expose_mohawk_version | dirlist | hostname_in_rootdir | x_forwarded_for
          | auth_path | charset | dirlist_css_url | email_admin | external_symlinks | maxage | mohawk_name | rootdir
          | auth_patterns | cgi_patterns | grant_access | index_names | no_auth_patterns | no_cgi_maps | no_log_patterns
          | cgi_env | cgi_map
          | reset

listen: LISTEN STATE WORD PORT NUMBER {
			char buf[6];
			int nb = 0;
			NUMBER_IS_PORT($5);
			snprintf(buf, sizeof(buf), "%d", $5);
			nb = interface($3, buf, -1) + host($3, buf);
			if (!nb) {
				yylineno--;
				yyerror("invalid virtual ip or interface or hostname: '%s' '%d'", $3, $5);
				YYERROR;
			}
        }
      | LISTEN STATE WORD PORT WORD {
			int nb = 0;
			nb = interface($3, $5, -1) + host($3, $5);
			if (!nb) {
				yylineno--;
				yyerror("invalid virtual ip or interface or hostname: '%s' '%s'", $3, $5);
				YYERROR;
			}
        }
      | LISTEN STATE PORT NUMBER {
			char buf[6];
			int nb = 0;
			NUMBER_IS_PORT($4);
			snprintf(buf, sizeof(buf), "%d", $4);
			nb = interface(NULL, buf, AF_INET) + interface(NULL, buf, AF_INET6);
			if (!nb) {
				yylineno--;
				yyerror("invalid port: '%d'", $4);
				YYERROR;
			}
        }
      | LISTEN STATE PORT WORD {
			int nb = 0;
			nb = interface(NULL, $4, AF_INET) + interface(NULL, $4, AF_INET6);
			if (!nb) {
				yylineno--;
				yyerror("invalid port: '%s'", $4);
				YYERROR;
			}
        }
      | LISTEN STATE WORD {
			int nb = 0;
			nb = interface($3, DEFAULT_HTTP_PORT, -1) + host($3, DEFAULT_HTTP_PORT);
			if (!nb) {
				yylineno--;
				yyerror("invalid virtual ip or interface or hostname: '%s'", $3);
				YYERROR;
			}
        };

reset: RESET AUTH_PATTERNS { RESET_MOHAWK_LIST(auth_patterns); }
     | RESET BLACKLIST_PATTERNS { RESET_MOHAWK_LIST(blacklist_patterns);}
     | RESET CGI_PATTERNS { RESET_MOHAWK_LIST(cgi_patterns);}
     | RESET INDEX_NAMES { RESET_MOHAWK_LIST(index_names);}
     | RESET NO_CGI_MAPS { RESET_MOHAWK_LIST(no_cgi_maps);}
     | RESET NO_AUTH_PATTERNS { RESET_MOHAWK_LIST(no_auth_patterns);}
     | RESET NO_LOG_PATTERNS { RESET_MOHAWK_LIST(no_log_patterns);}

     | RESET CGI_ENV { RESET_MOHAWK_HASH(cgi_env); current_vh->cgi_env_count = 0;}
     | RESET CGI_MAP { RESET_MOHAWK_HASH(cgi_map);}

     | RESET GRANT_ACCESS{ RESET_MOHAWK_NETWORK;}

     | RESET AUTH_PATH { current_vh->auth_path = NULL;}
     | RESET CHARSET { current_vh->charset = NULL;}
     | RESET DIRLIST_CSS_URL { current_vh->dirlist_css_url = NULL;}
     | RESET EMAIL_ADMIN { current_vh->email_admin = NULL;}
     | RESET MOHAWK_NAME { current_vh->mohawk_name = NULL;}
     | RESET STATUS_URL { current_vh->status_url = NULL;}

     | RESET AUTHENTICATION { current_vh->authentication = false; }
     | RESET AUTH_BLACKLISTD { current_vh->auth_blacklistd = false; }
     | RESET CGI_EXPOSE_MOHAWK_VERSION { current_vh->cgi_expose_mohawk_version = false; }
     | RESET HOSTNAME_IN_ROOTDIR { current_vh->hostname_in_rootdir = false; }
     | RESET MAXAGE { current_vh->maxage = 0; }
     | RESET X_FORWARDED_FOR { current_vh->x_forwarded_for = false; }

     | RESET LISTEN { RESET_ADDR_INFO(); };

%%

/* vim: set sw=4 sts=4 ts=4 : */
