/*
 * fwd - OpenWrt firewall daemon - config parsing
 *
 *   Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org>
 *
 * The fwd program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * The fwd program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with the fwd program. If not, see http://www.gnu.org/licenses/.
 */


#include "fwd.h"
#include "fwd_addr.h"
#include "fwd_config.h"
#include "fwd_utils.h"

#include "ucix.h"


#define fwd_read_error(...) do { \
	fwd_log_err(__VA_ARGS__);    \
	return;                      \
} while(0)


/*
 * Parse helpers
 */
static int
fwd_read_policy(struct uci_context *uci, const char *s, const char *o)
{
	const char *val = ucix_get_option(uci, "firewall", s, o);

	if( val != NULL )
	{
		switch( val[0] )
		{
			case 'D':
			case 'd':
				return FWD_P_DROP;

			case 'R':
			case 'r':
				return FWD_P_REJECT;

			case 'A':
			case 'a':
				return FWD_P_ACCEPT;
		}
	}

	return FWD_P_UNSPEC;
}

static int
fwd_read_bool(struct uci_context *uci, const char *s, const char *o, int d)
{
	const char *val = ucix_get_option(uci, "firewall", s, o);

	if( val != NULL )
	{
		if( !strcmp(val, "yes") || !strcmp(val, "true") || !strcmp(val, "1") )
			return 1;
		else
			return 0;
	}

	return d;
}

static unsigned int
fwd_read_uint(struct uci_context *uci, const char *s, const char *o, unsigned int d)
{
	const char *val = ucix_get_option(uci, "firewall", s, o);

	if( val != NULL )
	{
		return atoi(val);
	}

	return d;
}

static int
fwd_read_cidr(struct uci_context *uci, const char *s, const char *o, struct fwd_cidr **c)
{
	const char *val = ucix_get_option(uci, "firewall", s, o);
	char ip[32], prefix[32];
	struct in_addr ina;

	memset(ip, 0, 32);
	memset(prefix, 0, 32);

	if( val == NULL )
	{
		return 0;
	}
	else if( (strlen(val) < 32) && (sscanf(val, "%[^/]/%s", ip, prefix) > 0) )
	{
		if( !(*c = fwd_alloc_ptr(struct fwd_cidr)) )
			goto inval;

		if( inet_aton(ip, &ina) )
		{
			(*c)->addr.s_addr = ina.s_addr;

			if( strchr(prefix, '.') )
			{
				if( inet_aton(prefix, &ina) )
				{
					(*c)->prefix = 32;
					ina.s_addr = ntohl(ina.s_addr);

					while( !(ina.s_addr & 1) )
					{
						ina.s_addr >>= 1;
						(*c)->prefix--;
					}
				}
				else
				{
					goto inval;
				}
			}
			else
			{
				(*c)->prefix = prefix[0] ? atoi(prefix) : 32;

				if( ((*c)->prefix < 0) || ((*c)->prefix > 32) )
				{
					goto inval;
				}
			}

			return 0;
		}
	}

	inval:
	fwd_free_ptr(*c);
	return -1;
}

static int
fwd_read_mac(struct uci_context *uci, const char *s, const char *o, struct fwd_mac **m)
{
	const char *val = ucix_get_option(uci, "firewall", s, o);

	if( val == NULL )
	{
		return 0;
	}
	else
	{
		if( (*m = fwd_alloc_ptr(struct fwd_mac)) != NULL )
		{
			unsigned int i1, i2, i3, i4, i5, i6;

			if( sscanf(val, "%2x:%2x:%2x:%2x:%2x:%2x",
				&i1, &i2, &i3, &i4, &i5, &i6) == 6
			) {
				(*m)->mac[0] = (unsigned char)i1;
				(*m)->mac[1] = (unsigned char)i2;
				(*m)->mac[2] = (unsigned char)i3;
				(*m)->mac[3] = (unsigned char)i4;
				(*m)->mac[4] = (unsigned char)i5;
				(*m)->mac[5] = (unsigned char)i6;
				return 0;
			}
		}
	}

	fwd_free_ptr(*m);
	return -1;
}

static int
fwd_read_portrange(struct uci_context *uci, const char *s, const char *o, struct fwd_portrange **p)
{
	const char *val = ucix_get_option(uci, "firewall", s, o);
	int min = -1;
	int max = -1;
	unsigned int tmp;

	if( val == NULL )
	{
		return 0;
	}
	else if( sscanf(val, "%u%*[:-]%u", &min, &max) > 0 )
	{
		if( max == -1 )
		{
			max = min;
		}
		else if( min > max )
		{
			tmp = max;
			max = min;
			min = tmp;
		}

		if( (min >= 0) && (min <= 65535) && (max >= 0) && (max <= 65535) )
		{
			if( (*p = fwd_alloc_ptr(struct fwd_portrange)) != NULL )
			{
				(*p)->min = min;
				(*p)->max = max;
				return 0;
			}
		}
	}

	fwd_free_ptr(*p);
	return -1;
}

static int
fwd_read_proto(struct uci_context *uci, const char *s, const char *o, struct fwd_proto **p)
{
	const char *val = ucix_get_option(uci, "firewall", s, o);
	int proto;

	if( val == NULL )
	{
		return 0;
	}
	else
	{
		if( (*p = fwd_alloc_ptr(struct fwd_proto)) != NULL )
		{
			proto = atoi(val);

			if( !strcasecmp(val, "all") )
			{
				(*p)->type  = FWD_PR_ALL;
				(*p)->proto = 0;
			}
			else if( !strcasecmp(val, "icmp") )
			{
				(*p)->type  = FWD_PR_ICMP;
				(*p)->proto = 0;
			}
			else if( !strcasecmp(val, "udp") )
			{
				(*p)->type  = FWD_PR_UDP;
				(*p)->proto = 0;
			}
			else if( !strcasecmp(val, "tcp") )
			{
				(*p)->type  = FWD_PR_TCP;
				(*p)->proto = 0;
			}
			else if( !strcasecmp(val, "tcpudp") )
			{
				(*p)->type  = FWD_PR_TCPUDP;
				(*p)->proto = 0;
			}
			else if( proto > 0 )
			{
				(*p)->type  = FWD_PR_CUSTOM;
				(*p)->proto = proto;
			}
			else
			{
				goto inval;
			}

			return 0;
		}
	}

	inval:
	fwd_free_ptr(*p);
	return -1;
}

static int
fwd_read_icmptype(struct uci_context *uci, const char *s, const char *o, struct fwd_icmptype **i)
{
	const char *val = ucix_get_option(uci, "firewall", s, o);
	unsigned int type, code;

	if( val == NULL )
	{
		return 0;
	}
	else
	{
		if( (*i = fwd_alloc_ptr(struct fwd_icmptype)) != NULL )
		{
			if( sscanf(val, "%u/%u", &type, &code) == 2 )
			{
				if( (type > 255) || (code > 255) )
					goto inval;

				(*i)->type = type;
				(*i)->code = code;

				return 0;
			}

			else if( sscanf(val, "%u", &type) == 1 )
			{
				if( type > 255 )
					goto inval;

				(*i)->type = type;
				(*i)->code = -1;

				return 0;
			}

			/* XXX: no validity check here but I do not want to
			        duplicate libipt_icmp.c ... */
			else if( sscanf(val, "%31s", (*i)->name) == 1 )
			{
				return 0;
			}
		}
	}

	inval:
	fwd_free_ptr(*i);
	return -1;
}

static const char *
fwd_read_string(struct uci_context *uci, const char *s, const char *o)
{
	return ucix_get_option(uci, "firewall", s, o);
}


static void
fwd_append_config(struct fwd_data *h, struct fwd_data *a)
{
	while( h->next )
		h = h->next;

	h->next = a;
}


/*
 * config defaults
 */
static void fwd_read_defaults_cb(
	struct uci_context *uci,
	const char *s, struct fwd_defaults *d
) {
	d->input        = fwd_read_policy(uci, s, "input");
	d->forward      = fwd_read_policy(uci, s, "forward");
	d->output       = fwd_read_policy(uci, s, "output");
	d->syn_flood    = fwd_read_bool(uci, s, "syn_flood", 1);
	d->syn_rate     = fwd_read_uint(uci, s, "syn_rate", 25);
	d->syn_burst    = fwd_read_uint(uci, s, "syn_burst", 50);
	d->drop_invalid = fwd_read_bool(uci, s, "drop_invalid", 1);
}

static struct fwd_data *
fwd_read_defaults(struct uci_context *uci)
{
	struct fwd_data *dt;
	struct fwd_defaults d;

	if( (dt = fwd_alloc_ptr(struct fwd_data)) != NULL )
	{
		memset(&d, 0, sizeof(d));

		ucix_for_each_section_type(uci, "firewall", "defaults",
			(void *)fwd_read_defaults_cb, &d);

		memcpy(&dt->section.defaults, &d, sizeof(d));

		dt->type = FWD_S_DEFAULTS;
		dt->next = NULL;

		return dt;
	}

	return NULL;
}


/*
 * config zone
 */
static void fwd_read_zone_networks_cb(
	const char *net, struct fwd_network **np
) {
	struct fwd_network *nn;

	if( (nn = fwd_alloc_ptr(struct fwd_network)) != NULL )
	{
		nn->name = strdup(net);
		nn->next = *np;
		*np = nn;
	}
}

static void fwd_read_zones_cb(
	struct uci_context *uci,
	const char *s, struct fwd_data_conveyor *cv
) {
	struct fwd_data *dtn;
	struct fwd_network *net = NULL;
	const char *name;

	if( !(name = fwd_read_string(uci, s, "name")) )
		fwd_read_error("section '%s' is missing 'name' option!", s);

	if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL )
	{
		dtn->section.zone.name      = strdup(name);
		dtn->section.zone.masq      = fwd_read_bool(uci, s, "masq", 0);
		dtn->section.zone.mtu_fix   = fwd_read_bool(uci, s, "mtu_fix", 0);
		dtn->section.zone.conntrack = fwd_read_bool(uci, s, "conntrack", 0);

		dtn->section.zone.input     = fwd_read_policy(uci, s, "input")
			?: cv->head->section.defaults.input   ?: FWD_P_DROP;

		dtn->section.zone.forward   = fwd_read_policy(uci, s, "forward")
			?: cv->head->section.defaults.forward ?: FWD_P_DROP;

		dtn->section.zone.output    = fwd_read_policy(uci, s, "output")
			?: cv->head->section.defaults.output  ?: FWD_P_DROP;

		/* try to parse option/list network ... */
		if( ucix_for_each_list(uci, "firewall", s, "network",
			(void *)&fwd_read_zone_networks_cb, &net) < 0 )
		{
			/* ... didn't work, fallback to option name */
			fwd_read_zone_networks_cb(name, &net);
		}

		dtn->section.zone.networks = net;
		dtn->type = FWD_S_ZONE;
		dtn->next = cv->cursor;
		cv->cursor = dtn;
	}
}

static struct fwd_data *
fwd_read_zones(struct uci_context *uci, struct fwd_data *def)
{
	struct fwd_data_conveyor cv;

	cv.cursor = NULL;
	cv.head = def;

	ucix_for_each_section_type(uci, "firewall", "zone",
		(void *)fwd_read_zones_cb, &cv);

	return cv.cursor;
}


/*
 * config forwarding
 */
static void fwd_read_forwards_cb(
	struct uci_context *uci,
	const char *s, struct fwd_data_conveyor *cv
) {
	const char *src, *dest;
	struct fwd_data *dtn;
	struct fwd_zone *zsrc  = NULL;
	struct fwd_zone *zdest = NULL;

	if( !(src = fwd_read_string(uci, s, "src")) )
		fwd_read_error("section '%s' is missing 'src' option!", s);
	else if( !(zsrc = fwd_lookup_zone(cv->head, src)) )
		fwd_read_error("section '%s' references unknown src zone '%s'!", s, src);
	else if( !(dest = fwd_read_string(uci, s, "dest")) )
		fwd_read_error("section '%s' is missing 'dest' option!", s);
	else if( !(zdest = fwd_lookup_zone(cv->head, dest)) )
		fwd_read_error("section '%s' references unknown dest zone '%s'!", s, dest);
	
	if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL )
	{
		dtn->section.forwarding.src = zsrc;
		dtn->section.forwarding.dest = zdest;
		dtn->section.forwarding.mtu_fix = fwd_read_bool(uci, s, "mtu_fix", 0);
		dtn->section.forwarding.masq = fwd_read_bool(uci, s, "masq", 0);

		dtn->type = FWD_S_FORWARD;

		if( zsrc )
		{
			dtn->next = zsrc->forwardings;
			zsrc->forwardings = dtn;
		}
		else
		{
			dtn->next = cv->cursor;
			cv->cursor = dtn;
		}
	}
	else
	{
		fwd_read_error("out of memory while parsing config!");
	}
}

static struct fwd_data *
fwd_read_forwards(struct uci_context *uci, struct fwd_data *zones)
{
	struct fwd_data_conveyor cv;

	cv.cursor = NULL;
	cv.head = zones;

	ucix_for_each_section_type(uci, "firewall", "forwarding",
		(void *)fwd_read_forwards_cb, &cv);

	return cv.cursor;
}


/*
 * config redirect
 */
static void fwd_read_redirects_cb(
	struct uci_context *uci,
	const char *s, struct fwd_data_conveyor *cv
) {
	const char *src;
	struct fwd_data *dtn  = NULL;
	struct fwd_data *dtn2 = NULL;
	struct fwd_zone *zsrc = NULL;

	/* check zone */
	if( !(src = fwd_read_string(uci, s, "src")) )
		fwd_read_error(
			"section '%s' is missing 'src' option!",
			s
		);

	else if( !(zsrc = fwd_lookup_zone(cv->head, src)) )
		fwd_read_error(
			"section '%s' references unknown src zone '%s'!",
			s, src
		);

	/* uci context, section, name, type */
	fwd_check_option(uci, s, src_ip, cidr);
	fwd_check_option(uci, s, src_mac, mac);
	fwd_check_option(uci, s, src_port, portrange);
	fwd_check_option(uci, s, src_dport, portrange);
	fwd_check_option(uci, s, dest_ip, cidr);
	fwd_check_option(uci, s, dest_port, portrange);
	fwd_check_option(uci, s, proto, proto);
	
	if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL )
	{
		dtn->section.redirect.proto     = proto;
		dtn->section.redirect.src       = zsrc;
		dtn->section.redirect.src_ip    = src_ip;
		dtn->section.redirect.src_mac   = src_mac;
		dtn->section.redirect.src_port  = src_port;
		dtn->section.redirect.src_dport = src_dport;
		dtn->section.redirect.dest_ip   = dest_ip;
		dtn->section.redirect.dest_port = dest_port;

		dtn->type = FWD_S_REDIRECT;
		dtn->next = zsrc->redirects;
		zsrc->redirects = dtn;

		if( (proto != NULL) && (proto->type == FWD_PR_TCPUDP) )
		{
			if( !(dtn2 = fwd_alloc_ptr(struct fwd_data)) ||
			    !(dtn2->section.redirect.proto = fwd_alloc_ptr(struct fwd_proto))
			) {
				fwd_free_ptr(dtn2);
				fwd_read_error("out of memory while parsing config!");
			}

			dtn->section.redirect.proto->type = FWD_PR_UDP;
			dtn2->section.redirect.proto->type = FWD_PR_TCP;

			dtn2->section.redirect.src       = zsrc;
			dtn2->section.redirect.src_ip    = src_ip;
			dtn2->section.redirect.src_mac   = src_mac;
			dtn2->section.redirect.src_port  = src_port;
			dtn2->section.redirect.src_dport = src_dport;
			dtn2->section.redirect.dest_ip   = dest_ip;
			dtn2->section.redirect.dest_port = dest_port;
			dtn2->section.redirect.clone     = 1;

			dtn2->type = FWD_S_REDIRECT;
			dtn2->next = zsrc->redirects;
			zsrc->redirects = dtn2;
		}
	}
	else
	{
		fwd_read_error("out of memory while parsing config!");
	}
}

static struct fwd_data *
fwd_read_redirects(struct uci_context *uci, struct fwd_data *zones)
{
	struct fwd_data_conveyor cv;

	cv.cursor = NULL;
	cv.head = zones;

	ucix_for_each_section_type(uci, "firewall", "redirect",
		(void *)fwd_read_redirects_cb, &cv);

	return cv.cursor;
}


/*
 * config rule
 */
static void fwd_read_rules_cb(
	struct uci_context *uci,
	const char *s, struct fwd_data_conveyor *cv
) {
	const char *src, *dest;
	struct fwd_data *dtn   = NULL;
	struct fwd_data *dtn2  = NULL;
	struct fwd_zone *zsrc  = NULL;
	struct fwd_zone *zdest = NULL;

	/* check zones */
	if( !(src = fwd_read_string(uci, s, "src")) )
		fwd_read_error(
			"section '%s' is missing 'src' option!",
			s
		);

	else if( !(zsrc = fwd_lookup_zone(cv->head, src)) )
		fwd_read_error(
			"section '%s' references unknown src zone '%s'!",
			s, src
		);

	if( (dest = fwd_read_string(uci, s, "dest")) != NULL )
		if( !(zdest = fwd_lookup_zone(cv->head, dest)) )
			fwd_read_error(
				"section '%s' references unknown dest zone '%s'!",
				s, dest
			);

	/* uci context, section, name, type */
	fwd_check_option(uci, s, src_ip, cidr);
	fwd_check_option(uci, s, src_mac, mac);
	fwd_check_option(uci, s, src_port, portrange);
	fwd_check_option(uci, s, dest_ip, cidr);
	fwd_check_option(uci, s, dest_port, portrange);
	fwd_check_option(uci, s, proto, proto);
	fwd_check_option(uci, s, icmptype, icmptype);
	
	if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL )
	{
		dtn->section.rule.proto     = proto;
		dtn->section.rule.icmp_type = icmptype;
		dtn->section.rule.src       = zsrc;
		dtn->section.rule.src_ip    = src_ip;
		dtn->section.rule.src_mac   = src_mac;
		dtn->section.rule.src_port  = src_port;
		dtn->section.rule.dest      = zdest;
		dtn->section.rule.dest_ip   = dest_ip;
		dtn->section.rule.dest_port = dest_port;
		dtn->section.rule.target    = fwd_read_policy(uci, s, "target");

		dtn->type = FWD_S_RULE;
		dtn->next = zsrc->rules;
		zsrc->rules = dtn;

		if( (proto != NULL) && (proto->type == FWD_PR_TCPUDP) )
		{
			if( !(dtn2 = fwd_alloc_ptr(struct fwd_data)) ||
			    !(dtn2->section.rule.proto = fwd_alloc_ptr(struct fwd_proto))
			) {
				fwd_free_ptr(dtn2);
				fwd_read_error("out of memory while parsing config!");
			}

			dtn->section.rule.proto->type = FWD_PR_UDP;
			dtn2->section.rule.proto->type = FWD_PR_TCP;

			dtn2->section.rule.src       = zsrc;
			dtn2->section.rule.src_ip    = src_ip;
			dtn2->section.rule.src_mac   = src_mac;
			dtn2->section.rule.src_port  = src_port;
			dtn2->section.rule.dest      = zdest;
			dtn2->section.rule.dest_ip   = dest_ip;
			dtn2->section.rule.dest_port = dest_port;
			dtn2->section.rule.target    = dtn->section.rule.target;
			dtn2->section.rule.clone     = 1;

			dtn2->type = FWD_S_RULE;
			dtn2->next = zsrc->rules;
			zsrc->rules = dtn2;
		}
	}
	else
	{
		fwd_read_error("out of memory while parsing config!");
	}
}

static struct fwd_data *
fwd_read_rules(struct uci_context *uci, struct fwd_data *zones)
{
	struct fwd_data_conveyor cv;

	cv.cursor = NULL;
	cv.head = zones;

	ucix_for_each_section_type(uci, "firewall", "rule",
		(void *)fwd_read_rules_cb, &cv);

	return cv.cursor;
}


/*
 * config include
 */
static void fwd_read_includes_cb(
	struct uci_context *uci,
	const char *s, struct fwd_data_conveyor *cv
) {
	const char *path = fwd_read_string(uci, s, "path");
	struct fwd_data *dtn = NULL;

	if( path != NULL )
	{
		if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL )
		{
			dtn->section.include.path = strdup(path);

			dtn->type = FWD_S_INCLUDE;
			dtn->next = cv->cursor;
			cv->cursor = dtn;
		}
		else
		{
			fwd_read_error("out of memory while parsing config!");
		}
	}
}

static struct fwd_data *
fwd_read_includes(struct uci_context *uci)
{
	struct fwd_data_conveyor cv;

	cv.cursor = NULL;
	cv.head   = NULL;

	ucix_for_each_section_type(uci, "firewall", "include",
		(void *)fwd_read_includes_cb, &cv);

	return cv.cursor;
}


/*
 * config interface
 */
static void fwd_read_network_data(
	struct uci_context *uci, struct fwd_network *net
) {
	struct fwd_network *e;
	const char *type, *ifname;

	for( e = net; e; e = e->next )
	{
		if( (type = ucix_get_option(uci, "network", e->name, NULL)) != NULL )
		{
			if( !(ifname = ucix_get_option(uci, "network", e->name, "ifname")) )
				fwd_read_error(
					"section '%s' is missing 'ifname' option!",
					e->name
				);

			e->isalias = (strcmp(type, "alias") ? 0 : 1);
			e->ifname  = strdup(ifname);
		}
	}
}

static void fwd_read_networks(
	struct uci_context *uci, struct fwd_data *zones
) {
	struct fwd_data *e;

	for( e = zones; e; e = e->next )
		if( e->type == FWD_S_ZONE )
			fwd_read_network_data(uci, e->section.zone.networks);
}

static void fwd_free_networks(struct fwd_network *h)
{
	struct fwd_network *e = h;

	while( h != NULL )
	{
		e = h->next;

		fwd_free_ptr(h->name);
		fwd_free_ptr(h->ifname);
		fwd_free_ptr(h->addr);

		free(h);
		h = e;
	}

	e = h = NULL;
}

static struct fwd_cidr * fwd_alloc_cidr(struct fwd_cidr *addr)
{
	struct fwd_cidr *cidr;

	if( (cidr = fwd_alloc_ptr(struct fwd_cidr)) != NULL )
	{
		if( addr != NULL )
		{
			cidr->addr.s_addr = addr->addr.s_addr;
			cidr->prefix = addr->prefix;
		}

		return cidr;
	}

	return NULL;
}



struct fwd_data * fwd_read_config(struct fwd_handle *h)
{
	struct uci_context *ctx;
	struct fwd_data *defaults, *zones, *e;
	struct fwd_addr *addrs;
	struct fwd_network *net;
	struct fwd_zone *zone;

	if( (ctx = ucix_init("firewall")) != NULL )
	{
		if( !(defaults = fwd_read_defaults(ctx)) )
			goto error;

		if( !(zones = fwd_read_zones(ctx, defaults)) )
			goto error;

		fwd_append_config(defaults, zones);
		fwd_append_config(defaults, fwd_read_forwards(ctx, zones));
		fwd_append_config(defaults, fwd_read_redirects(ctx, zones));
		fwd_append_config(defaults, fwd_read_rules(ctx, zones));
		fwd_append_config(defaults, fwd_read_includes(ctx));

		ucix_cleanup(ctx);

		if( (ctx = ucix_init("network")) != NULL )
		{
			fwd_read_networks(ctx, zones);
			ucix_cleanup(ctx);

			if( !(addrs = fwd_get_addrs(h->rtnl_socket, AF_INET)) )
				goto error;

			for( e = zones; e && (zone = &e->section.zone); e = e->next )
			{
				if( e->type != FWD_S_ZONE )
					break;

				for( net = zone->networks; net; net = net->next )
				{
					net->addr = fwd_alloc_cidr(
						fwd_lookup_addr(addrs, net->ifname)
					);
				}
			}

			fwd_free_addrs(addrs);
			return defaults;
		}
	}

	error:
	if( ctx ) ucix_cleanup(ctx);
	fwd_free_config(defaults);
	fwd_free_config(zones);
	return NULL;	
}


void fwd_free_config(struct fwd_data *h)
{
	struct fwd_data *e = h;

	while( h != NULL )
	{
		e = h->next;

		switch(h->type)
		{
			case FWD_S_INCLUDE:
				fwd_free_ptr(h->section.include.path);
				break;

			case FWD_S_ZONE:
				fwd_free_ptr(h->section.zone.name);
				fwd_free_networks(h->section.zone.networks);
				fwd_free_config(h->section.zone.rules);
				fwd_free_config(h->section.zone.redirects);
				fwd_free_config(h->section.zone.forwardings);
				break;

			case FWD_S_REDIRECT:
				/* Clone rules share all pointers except proto.
                   Prevent a double-free here */          
				if( ! h->section.redirect.clone )
				{
					fwd_free_ptr(h->section.redirect.src_ip);
					fwd_free_ptr(h->section.redirect.src_mac);
					fwd_free_ptr(h->section.redirect.src_port);
					fwd_free_ptr(h->section.redirect.src_dport);
					fwd_free_ptr(h->section.redirect.dest_ip);
					fwd_free_ptr(h->section.redirect.dest_port);
				}
				fwd_free_ptr(h->section.redirect.proto);
				break;

			case FWD_S_RULE:
				/* Clone rules share all pointers except proto.
                   Prevent a double-free here */          
				if( ! h->section.rule.clone )
				{
					fwd_free_ptr(h->section.rule.src_ip);
					fwd_free_ptr(h->section.rule.src_mac);
					fwd_free_ptr(h->section.rule.src_port);
					fwd_free_ptr(h->section.rule.dest_ip);
					fwd_free_ptr(h->section.rule.dest_port);
					fwd_free_ptr(h->section.rule.icmp_type);
				}
				fwd_free_ptr(h->section.rule.proto);
				break;

			case FWD_S_DEFAULTS:
			case FWD_S_FORWARD:
				/* Make gcc happy */
				break;
		}

		fwd_free_ptr(h);
		h = e;
	}

	e = h = NULL;
}


struct fwd_zone *
fwd_lookup_zone(struct fwd_data *h, const char *n)
{
	struct fwd_data *e;

	if( n != NULL )
	{
		for( e = h; e; e = e->next )
		{
			if( (e->type = FWD_S_ZONE) && !strcmp(e->section.zone.name, n) )
				return &e->section.zone;
		}
	}

	return NULL;
}