/*-
 * Copyright (c) 2003 Andrey Simonenko
 * 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 "compat.h"

#ifndef lint
static const char rcsid[] ATTR_UNUSED =
  "@(#)$Id: ipa_ip6fw.c,v 1.5 2006/04/26 21:08:22 simon Exp $";
#endif /* !lint */

#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <regex.h>

#include <ipa_mod.h>

#include "ipa_ip6fw.h"
#include "kip6fw.h"
#include "version.h"

#if IPA_AC_MOD_API_VERSION != 1
# error This module supports only ipa_ac_mod API version 1
#endif

#define AC_MOD_API_VERSION 1		/* Supported ipa_ac_mod API. */

#if IPA_MEMFUNC_API_VERSION != 1
# error This module supports only ipa_memfunc API version 1
#endif

#define MEMFUNC_API_VERSION 1		/* Supported memfunc API. */

#ifdef WITH_PTHREAD
# define MOD_FLAGS IPA_MOD_FLAG_PTHREAD_SAFE
#else
# define MOD_FLAGS 0
#endif

#define MEMFLAG_OPTIMIZE IPA_MEMFUNC_FLAG_OPTIMIZE

#define MOD_NAME	"ipa_ip6fw"
#define MOD_AC_NAME	"ip6fw"
#define MOD_CONF_PREFIX	"ip6fw"

#define MARRAY_NAME(x)	MOD_NAME ":" #x
#define MZONE_NAME(x)	MOD_NAME ":" #x
#define MTYPE_NAME(x)   MOD_NAME ":m_" #x

extern struct ipa_ac_mod IPA_AC_MOD_ENTRY(ipa_ip6fw); /* Is defined at the end of this file. */

static const ipa_suppfunc *suppfunc;	/* Exported support functions. */

const ipa_memfunc *memfunc;		/* Exported memory manipulation functions. */

ipa_mem_type	*m_anon;		/* Anonymous memory allocations. */
ipa_mem_type	*m_buf;			/* Memory for buffers. */

void		*(*mem_realloc)(void *, size_t, ipa_mem_type *);
void		(*mem_free)(void *, ipa_mem_type *);

static ipa_marray *(*marray_init)(const char *, const char *, u_int, void **, size_t, u_int, u_int);
static void	(*marray_deinit)(ipa_marray *);
static int	(*marray_alloc)(ipa_marray *, u_int *, int);
static void	(*marray_minimize)(ipa_marray *);
static int	(*marray_check_index)(ipa_marray *, u_int);

static ipa_mzone *(*mzone_init)(const char *, const char *, u_int, size_t, u_int, u_int);
static void	(*mzone_deinit)(ipa_mzone *);
static void	*(*mzone_alloc)(ipa_mzone *);

static uint64_t	global_maxchunk;	/* global { ip6fw:maxchunk } */
static int	global_quiet;		/* global { ip6fw:quiet } */

/*
 * Struct ip6_fw{} has only a rule number for each rule.
 * An extra subnumber is introduced: if there are some rules
 * with the same number we will distinguish such rules
 * by subnumbers, first rule in the set has subnumber equal to 0.
 */
struct ip6fwac {
	struct kip6fw_rule *kip6fw_rule;/* Pointer to kip6fw_rule.	*/
	int		seen;		/* 1, if bcnt_old is known.	*/
	int		addition;	/* Non-zero if add.		*/
	u_short		number;		/* IP6FW rule number.		*/
	u_int		subnumber;	/* IP6FW rule subnumber.	*/
	uint64_t	bcnt_old;	/* Old value of counter.	*/
};

/*
 * Module's data for each rule.
 */
struct rule {
	const char	*rule_name;	/* Name of this rule.		*/
	uint64_t	maxchunk;	/* rule { ip6fw:maxchunk }	*/
	int		quiet;		/* rule { ip6fw:quiet }		*/
	struct ip6fwac	*ip6fwac;	/* rule { ip6fw:rules }		*/
	u_int		nip6fwac;	/* Number of accounting rules.	*/
	int		inited;		/* Nonzero, if rule was inited.	*/
	STAILQ_ENTRY(rule) link;	/* For list of static rules.	*/
};

#define LOG_IP6FW_OVERFLOW(r)	(!((r)->quiet & 0x1))
#define LOG_IP6FW_EVENTS(r)	(!((r)->quiet & 0x2))

static struct rule **rules_ptr;		/* Array of pointers to rules. */
static u_int	nrules;			/* Number of rules. */
static u_int	nalloced;		/* Number of allocated rules. */
static u_int	ninited;		/* Number of inited rules. */
static ipa_mzone *rule_mzone;		/* Mzone for all rules. */
static ipa_marray *rules_ptr_marray;	/* Marray for array of pointers to rules. */

#define RULE(x) rules_ptr[x]		/* Pointer to rule by number. */

static struct rule *currule;		/* Current rule. */
static const char *currulename;		/* Name of current rule. */

static STAILQ_HEAD(, rule) rules_list;	/* List of static rules. */

#define RULE_NSIZE	30
#define RULE_NALLOC	20

static int	in_rule;		/* 1, if we are in rule{} section. */

static void	logconferr(const char *, ...) ATTR_PRINTFLIKE(1, 2);
static void	logconferrx(const char *, ...) ATTR_PRINTFLIKE(1, 2);
static void	print_param_args(const char *, ...) ATTR_PRINTFLIKE(1, 2);

#define KIP6FW_RULE_NSIZE	100
#define KIP6FW_RULE_NALLOC	20

static ipa_marray *kip6fw_rules_marray;	/* Marray for all kip6fw_rules. */

static struct kip6fw_rule *kip6fw_rules;/* Sorted array of all kip6fw_rules. */
static u_int	nkip6fw_rules;		/* Number of all kip6fw_rules. */

/*
 * A wrapper for imported logmsg() function.
 */
void
logmsg(int priority, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	suppfunc->logmsg(MOD_NAME, priority, errno, format, ap);
	va_end(ap);
}

/*
 * A wrapper for imported logmsg() function,
 * but error code is always zero here.
 */
void
logmsgx(int priority, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	suppfunc->logmsg(MOD_NAME, priority, 0, format, ap);
	va_end(ap);
}

/*
 * A wrapper for imported logconferr() function.
 */
static void
logconferr(const char *format, ...)
{
	va_list	ap;

	va_start(ap, format);
	suppfunc->logconferr(MOD_NAME, errno, format, ap);
	va_end(ap);
}

/*
 * A wrapper for imported logconferr() function,
 * but error code is always zero here.
 */
static void
logconferrx(const char *format, ...)
{
	va_list	ap;

	va_start(ap, format);
	suppfunc->logconferr(MOD_NAME, 0, format, ap);
	va_end(ap);
}

/*
 * A wrapper for imported print_param_args() function.
 */
static void
print_param_args(const char *format, ...)
{
	va_list	ap;

	va_start(ap, format);
	suppfunc->print_param_args(format, ap);
	va_end(ap);
}

/*
 * A wrapper for imported print_param_name() function.
 */
static void
print_param_name(const char *name)
{
	suppfunc->print_param_name(MOD_CONF_PREFIX, name);
}

/*
 * A wrapper for imported print_param_end() function.
 */
static void
print_param_end(void)
{
	suppfunc->print_param_end();
}

/*
 * Convert string to u_int.
 * Assumed that sizeof(u_long) >= sizeof(u_int).
 * Since value was parsed by regular expression, it is not
 * needed to check '-' in first character in a string (remember,
 * that strtoul() and strtoull() work with negative values).
 */
static int
strto_u_int(u_int *result, const char *nptr, char **endptr)
{
	u_long val_ul;

	errno = 0;
	val_ul = strtoul(nptr, endptr, 10);
	if (errno != 0) {
		logconferr("strtoul");
		return -1;
	}
	if (val_ul > UINT_MAX) {
		logconferrx("too big value for u_int type");
		return -1;
	}
	*result = (u_int)val_ul;
	return 0;
}

static int
conf_init(void)
{
	suppfunc = IPA_AC_MOD_ENTRY(ipa_ip6fw).suppfunc;

	memfunc = IPA_AC_MOD_ENTRY(ipa_ip6fw).memfunc;
	if (memfunc->api_ver != MEMFUNC_API_VERSION) {
		logconferrx("conf_init: module understands memfunc API version %u, exported memfunc API version is %u",
		    MEMFUNC_API_VERSION, memfunc->api_ver);
		return -1;
	}

        if ((m_anon = memfunc->mem_type_new(MTYPE_NAME(anon), "Anonymous memory", 0)) == NULL ||
	    (m_buf = memfunc->mem_type_new(MTYPE_NAME(buf), "Statistics from kernel", 0)) == NULL) {
                logconferrx("conf_init: mem_type_new failed");
                return -1;
        }

	mem_realloc = memfunc->mem_realloc;
	mem_free = memfunc->mem_free;

	marray_init = memfunc->marray_init;
	marray_deinit = memfunc->marray_deinit;
	marray_alloc = memfunc->marray_alloc;
	marray_minimize = memfunc->marray_minimize;
	marray_check_index = memfunc->marray_check_index;

	mzone_init = memfunc->mzone_init;
	mzone_deinit = memfunc->mzone_deinit;
	mzone_alloc = memfunc->mzone_alloc;

	if ( (kip6fw_rules_marray = marray_init(MARRAY_NAME(kip6fw_rules), "Kernel IP6FW rules",
	    0, (void *)&kip6fw_rules, sizeof(struct kip6fw_rule),
	    KIP6FW_RULE_NSIZE, KIP6FW_RULE_NALLOC)) == NULL ||
	     (rules_ptr_marray = marray_init(MARRAY_NAME(rules_ptr), "Pointers to rules",
	    0, (void *)&rules_ptr, sizeof(struct rule *),
	    RULE_NSIZE, RULE_NALLOC)) == NULL) {
		logconferrx("conf_init: marray_init failed");
		return -1;
	}

	if ( (rule_mzone = mzone_init(MZONE_NAME(rule), "Rules", MEMFLAG_OPTIMIZE,
	    sizeof(struct rule), RULE_NSIZE, RULE_NALLOC)) == NULL) {
		logconferrx("conf_init: mzone_init failed");
		return -1;
	}
	nrules = nalloced = ninited = 0;
	in_rule = 0;

	nkip6fw_rules = 0;

	debug_ip6fw = global_quiet = -1;
	global_maxchunk = UINT64_C(0);

	STAILQ_INIT(&rules_list);

	return 0;
}

/*
 * Compare two struct ip6fwac{}.  This function is used in qsort(3).
 */
static int
cmp_ip6fwac(const void *p1, const void *p2)
{
	const struct ip6fwac *q1 = p1, *q2 = p2;

	if (q1->number > q2->number)
		return 1;
	if (q1->number < q2->number)
		return -1;
	return q1->subnumber > q2->subnumber ? 1 : -1;
}

/*
 * Compare two struct kip6fw_rule{}.  This function is used in qsort(3).
 */
static int
cmp_kip6fw_rule(const void *p1, const void *p2)
{
	const struct kip6fw_rule *q1 = p1, *q2 = p2;

	if (q1->number > q2->number)
		return 1;
	if (q1->number < q2->number)
		return -1;
	return q1->subnumber > q2->subnumber ? 1 : -1;
}

static int
conf_deinit(void)
{
	struct ip6fwac *fwac;
	struct kip6fw_rule *kip6fw_rule;
	const struct rule *rule;

	/* Try to minimize rules_ptr_marray. */
	if (nrules != 0)
		marray_minimize(rules_ptr_marray);

	/* Try to minimize kip6fw_rules_marray. */
	if (nkip6fw_rules != 0)
		marray_minimize(kip6fw_rules_marray);

	/* Sort kip6fw_rules. */
	qsort(kip6fw_rules, nkip6fw_rules, sizeof *kip6fw_rules, cmp_kip6fw_rule);

	/* Sort accounting rules in rule->ip6fwac. */
	STAILQ_FOREACH(rule, &rules_list, link) {
		if (rule->ip6fwac != NULL)
			qsort(rule->ip6fwac, rule->nip6fwac, sizeof *rule->ip6fwac, cmp_ip6fwac);
		for (kip6fw_rule = kip6fw_rules, fwac = rule->ip6fwac; fwac < rule->ip6fwac + rule->nip6fwac; ++fwac)
			for (; kip6fw_rule < kip6fw_rules + nkip6fw_rules; ++kip6fw_rule)
				if (kip6fw_rule->number == fwac->number && kip6fw_rule->subnumber == fwac->subnumber) {
					fwac->kip6fw_rule = kip6fw_rule;
					break;
				}
	}

	/* Just to prevent of using incorrect link in alloc_rule(). */
	currulename = NULL;

	return 0;
}

static int
conf_event(u_int event, u_int no, const void *arg)
{
	switch (event) {
	case IPA_CONF_EVENT_RULE_BEGIN:
		nrules = no + 1;
		in_rule = 1;
		currule = NULL;
		currulename = arg;
		break;
	case IPA_CONF_EVENT_RULE_END:
		in_rule = 0;
		break;
	}
	return 0;
}

static void
show_maxchunk(const uint64_t *val)
{
	if (*val > UINT64_C(0)) {
		print_param_name("maxchunk");
		suppfunc->print_bytes(val);
		print_param_end();
	}
}

static void
show_quiet(int val)
{
	if (val >= 0) {
		print_param_name("quiet");
		print_param_args("%d", val);
		print_param_end();
	}
}

static int
conf_mimic_real(void)
{
	struct rule *rule;

	if (global_quiet < 0)
		global_quiet = 0;
	if (debug_ip6fw < 0)
		debug_ip6fw = 0;

	STAILQ_FOREACH(rule, &rules_list, link) {
		if (rule->quiet < 0)
			rule->quiet = global_quiet;
		if (rule->maxchunk == UINT64_C(0))
			rule->maxchunk = global_maxchunk;
	}

	return 0;
}

static void
conf_show(u_int sect_id, u_int no)
{
	const struct rule *rule;

	switch (sect_id) {
	case IPA_CONF_SECT_ROOT:
		if (debug_ip6fw >= 0) {
			print_param_name("debug_ip6fw");
			print_param_args("%d", debug_ip6fw);
			print_param_end();
		}
		break;
	case IPA_CONF_SECT_GLOBAL:
		show_maxchunk(&global_maxchunk);
		show_quiet(global_quiet);
		break;
	case IPA_CONF_SECT_RULE:
		if (marray_check_index(rules_ptr_marray, no)) {
			rule = RULE(no);
			if (rule->ip6fwac != NULL) {
				const struct ip6fwac *fwac;

				print_param_name("rules");
				for (fwac = rule->ip6fwac; fwac < rule->ip6fwac + rule->nip6fwac; ++fwac) {
					suppfunc->print_space();
					print_param_args("%s%hu", fwac->addition ? "" : "-",
					    fwac->number);
					if (fwac->subnumber != 0)
						print_param_args(".%u", fwac->subnumber);
				}
				print_param_end();
			}
			show_maxchunk(&rule->maxchunk);
			show_quiet(rule->quiet);
		}
		break;
	}
}

static struct rule *
alloc_rule(u_int ruleno)
{
	struct rule *rule;

	if ( (rule = mzone_alloc(rule_mzone)) == NULL) {
		logconferrx("alloc_rule: mzone_alloc failed");
		return NULL;
	}
	rule->rule_name = currulename;
	rule->maxchunk = UINT64_C(0);
	rule->quiet = -1;
	rule->ip6fwac = NULL;
	rule->nip6fwac = 0;
	rule->inited = 0;

	if (marray_alloc(rules_ptr_marray, &ruleno, 1) < 0) {
		logconferrx("alloc_rule: marray_alloc failed");
		return NULL;
	}
	RULE(ruleno) = rule;

	++nalloced;

	STAILQ_INSERT_TAIL(&rules_list, rule, link);

	return rule;
}

static struct rule *
alloc_currule(void)
{
	return currule = alloc_rule(nrules - 1);
}

/*
 * Parse "rules" parameter.
 */
static int
parse_rules(void *arg)
{
	char	*ptr = *(char **)arg, *endptr;
	u_int	number, subnumber;
	int	addition;
	struct ip6fwac *fwac;
	struct kip6fw_rule *kip6fw_rule;

	if (currule == NULL)
		if (alloc_currule() == NULL)
			return -1;
	for (;;) {
		/* Parse one argument. */
		if (*ptr == '-') {
			addition = 0;
			++ptr;
		} else
			addition = 1;
		if (strto_u_int(&number, ptr, &endptr) < 0)
			return -1;
		if (number > IP6FW_NUMBER_MAX || number < IP6FW_NUMBER_MIN) {
			logconferrx("IP6FW rule number should be greater than %u and less than or equal to %u",
			    IP6FW_NUMBER_MIN, IP6FW_NUMBER_MAX);
			return -1;
		}
		ptr = endptr;
		subnumber = 0;
		if (*ptr == '.') {
			if (strto_u_int(&subnumber, ++ptr, &endptr) < 0) {
				logconferrx("IP6FW rule subnumber should be less than %u",
				    UINT_MAX);
				return -1;
			}
			if (number == IP6FW_NUMBER_MAX && subnumber != 0) {
				logconferrx("subnumbers for IP6FW rule %u are not allowed",
				    IP6FW_NUMBER_MAX);
				return -1;
			}
			ptr = endptr;
		}

		/* Check for dups. */
		for (fwac = currule->ip6fwac; fwac < currule->ip6fwac + currule->nip6fwac; ++fwac)
			if (fwac->number == number && fwac->subnumber == subnumber) {
				if (fwac->addition != addition)
					logconferrx("IP6FW rules %u.%u and -%u.%u have no effect",
					    number, subnumber, number, subnumber);
				else
					logconferrx("duplicated IP6FW rule %u.%u",
					    number, subnumber);
				return -1;
			}

		/* Allocate new kip6fw_rule if there isn't number.subnumber. */
		for (kip6fw_rule = kip6fw_rules; kip6fw_rule < kip6fw_rules + nkip6fw_rules; ++kip6fw_rule)
			if (kip6fw_rule->number == number && kip6fw_rule->subnumber == subnumber)
				break;
		if (kip6fw_rule == kip6fw_rules + nkip6fw_rules) {
			if (marray_alloc(kip6fw_rules_marray, &nkip6fw_rules, 1) < 0) {
				logconferrx("marray_alloc failed");
				return -1;
			}
			kip6fw_rule = kip6fw_rules + nkip6fw_rules;
			kip6fw_rule->ref_count = 0;
			kip6fw_rule->number = (u_short)number;
			kip6fw_rule->subnumber = subnumber;
			++nkip6fw_rules;
		}
		kip6fw_rule->ref_count++;

		/* Add a new ip6fwac for rule. */
		if ( (fwac = mem_realloc(currule->ip6fwac, (currule->nip6fwac + 1) * sizeof *fwac, m_anon)) == NULL) {
			logconferrx("mem_realloc failed");
			return -1;
		}
		currule->ip6fwac = fwac;
		fwac += currule->nip6fwac;
		++currule->nip6fwac;
		fwac->number = (u_short)number;
		fwac->subnumber = subnumber;
		fwac->seen = 0;
		fwac->addition = addition;

		if (*ptr == '\0')
			break;	/* EOL */
		++ptr;
	}
	return 0;
}

/*
 * Parse "debug_ip6fw" parameter.
 */
static int
parse_debug_ip6fw(void *arg)
{
	debug_ip6fw = (int)(*(uint32_t *)arg);
	if (debug_ip6fw > 1) {
		logconferrx("too big debug level, max level is 1");
		return -1;
	}
	return 0;
}

/*
 * Parse "maxchunk" parameter.
 */
static int
parse_maxchunk(void *arg)
{
	const uint64_t maxchunk = *(uint64_t *)arg;

	if (maxchunk == UINT64_C(0)) {
		logconferrx("should be greater than zero");
		return -1;
	}
	if (in_rule) {
		if (currule == NULL)
			if (alloc_currule() == NULL)
				return -1;
		currule->maxchunk = maxchunk;
	} else
		global_maxchunk = maxchunk;
	return 0;
}

/*
 * Parse "quiet" parameter.
 */
static int
parse_quiet(void *arg)
{
	const u_int quiet = *(uint32_t *)arg;

	if (quiet > 3) {
		logconferrx("such value is not allowed, max allowed value is 3");
		return -1;
	}
	if (in_rule) {
		if (currule == NULL)
			if (alloc_currule() == NULL)
				return -1;
		currule->quiet = (int)quiet;
	} else
		global_quiet = (int)quiet;
	return 0;
}

static int
ac_pre_init(void)
{
	struct kip6fw_rule *kip6fw_rule;

	logmsgx(IPA_LOG_INFO, "initing accounting system for IPv6 Firewall, version "IPA_IP6FW_VERSION);

	if (global_quiet < 0)
		global_quiet = 0;
	if (debug_ip6fw < 0)
		debug_ip6fw = 0;

	if (kip6fw_init() < 0) {
		logmsgx(IPA_LOG_ERR, "ac_pre_init: kipfw_init failed");
		return -1;
	}

	TAILQ_INIT(&kip6fw_rules_active);
	for (kip6fw_rule = kip6fw_rules; kip6fw_rule < kip6fw_rules + nkip6fw_rules; ++kip6fw_rule)
		TAILQ_INSERT_TAIL(&kip6fw_rules_active, kip6fw_rule, link);

	return 0;
}

static int
ac_init(void)
{
	const struct rule *rule;

	if (nalloced != ninited)
		STAILQ_FOREACH(rule, &rules_list, link)
			if (rule->inited == 0)
				logmsgx(IPA_LOG_WARNING, "rule %s: this rule has some module's parameter, but it does not use my accounting system",
				    rule->rule_name);

	return 0;
}

static int
ac_init_statrule(u_int ruleno, const char *rule_name)
{
	struct rule *rule;

	if (!marray_check_index(rules_ptr_marray, ruleno)) {
		if ( (rule = alloc_rule(ruleno)) == NULL) {
			logmsgx(IPA_LOG_ERR, "ac_init_statrule: alloc_rule failed");
			return -1;
		}
		rule->rule_name = rule_name;
		rule->maxchunk = global_maxchunk;
		rule->quiet = global_quiet;
	} else {
		rule = RULE(ruleno);
		if (rule->quiet < 0)
			rule->quiet = global_quiet;
		if (rule->maxchunk == UINT64_C(0))
			rule->maxchunk = global_maxchunk;
	}

	if (rule->ip6fwac == NULL)
		logmsgx(IPA_LOG_WARNING, "ac_init_statrule: rule %s does not have \"rules\" parameter",
		    rule_name);
	else if (rule->maxchunk == UINT64_C(0)) {
		logmsgx(IPA_LOG_ERR, "ac_init_statrule: rule %s does not have \"maxchunk\" parameter, set it!",
		    rule_name);
		return -1;
	}

	rule->inited = 1;
	++ninited;

	return 0;
}

/*
 * Deinit a rule, simply free memory used by a rule.
 */
static int
ac_deinit_rule(u_int ruleno)
{
	struct rule *rule;

	if (marray_check_index(rules_ptr_marray, ruleno)) {
		rule = RULE(ruleno);
		mem_free(rule->ip6fwac, m_anon);
		rule->ip6fwac = NULL;
		--nalloced;
	}
	return 0;
}

/*
 * Deinit accounting system.
 */
static int
ac_deinit(void)
{
	struct rule *rule;

	logmsgx(IPA_LOG_INFO, "deiniting IPv6 Firewall support");

	if (nalloced != 0) {
		/*
		 * Very uncommon case (if no error occurred during
		 * initialization), when some parameter is set for rule,
		 * but rule does not use this accounting module.
		 */
		STAILQ_FOREACH(rule, &rules_list, link)
			mem_free(rule->ip6fwac, m_anon);
	}

	marray_deinit(kip6fw_rules_marray);
	marray_deinit(rules_ptr_marray);
	mzone_deinit(rule_mzone);

	if (kip6fw_deinit() < 0) {
		logmsgx(IPA_LOG_ERR, "ac_deinit: kip6fw_deinit failed");
		return -1;
	}

	return 0;
}

static int
ac_get_rule_stat(u_int stat_generation, int newstat, u_int ruleno,
    int *addition, uint64_t *ret_chunk)
{
	static u_int cur_stat_generation = 0;

	uint64_t chunk, chunk_sum, chunk_sub_sum;
	struct ip6fwac *fwac;
	const struct ip6fwac *fwac_end;
	const struct rule *rule;
	const struct ip6_fw *fw;

	if (cur_stat_generation != stat_generation) {
		if (kip6fw_get_stat() < 0) {
			logmsgx(IPA_LOG_ERR, "ac_get_rule_stat: kip6fw_get_stat failed");
			return -1;
		}
		cur_stat_generation = stat_generation;
	}

	rule = RULE(ruleno);

	chunk_sum = chunk_sub_sum = UINT64_C(0);

	fwac_end = rule->ip6fwac + rule->nip6fwac;
	for (fwac = rule->ip6fwac; fwac < fwac_end; ++fwac) {
		if (newstat)
			fwac->seen = 0;
		fw = fwac->kip6fw_rule->ip6_fw;
		if (fw != NULL) {
			/*
			 * At this point we know that rule with given
			 * number and subnumber exists and fw points to
			 * needed struct ip6_fw{}.
			 */
			if (fwac->seen == 0) {
				fwac->seen = 1;
				fwac->bcnt_old = IP6FWP_BCNT(fw);
				if (!newstat && LOG_IP6FW_EVENTS(rule))
					logmsgx(IPA_LOG_WARNING, "rule %s: ac_get_rule_stat: %hu.%u was added to IP6FW kernel table",
					    rule->rule_name, fwac->number, fwac->subnumber);
			} else {
				if (fwac->bcnt_old != IP6FWP_BCNT(fw)) {
					if (fwac->bcnt_old < IP6FWP_BCNT(fw))
						chunk = IP6FWP_BCNT(fw) - fwac->bcnt_old;
					else {
						chunk = kip6fw_bcnt_max - (fwac->bcnt_old - IP6FWP_BCNT(fw));
						if (LOG_IP6FW_OVERFLOW(rule))
							logmsgx(IPA_LOG_WARNING, "rule %s: ac_get_rule_stat: IP6FW counter %hu.%u was overflowed, old value = %"PRIu64" bytes, current value = %"PRIu64" bytes",
							    rule->rule_name, fwac->number, fwac->subnumber, fwac->bcnt_old, (uint64_t)IP6FWP_BCNT(fw));
						if (chunk > rule->maxchunk) {
							if (LOG_IP6FW_OVERFLOW(rule)) {
								logmsgx(IPA_LOG_WARNING, "rule %s: ac_get_rule_stat: was IP6FW rule %hu.%u flushed? chunk = %"PRIu64" bytes, maxchunk = %"PRIu64" bytes",
								    rule->rule_name, fwac->number, fwac->subnumber,
								    chunk, rule->maxchunk);
								logmsgx(IPA_LOG_INFO, "rule %s: ac_get_rule_stat: use absolute value %"PRIu64" bytes of counter of IP6FW %hu.%u rule",
								    rule->rule_name, (uint64_t)IP6FWP_BCNT(fw), fwac->number, fwac->subnumber);
							}
							chunk = IP6FWP_BCNT(fw);
						}
					}
				} else
					continue; /* Counter wasn't changed. */

				/* At this point chunk is know. */
				fwac->bcnt_old = IP6FWP_BCNT(fw);
				if (fwac->addition) {
					if (chunk_sub_sum >= chunk)
						chunk_sub_sum -= chunk;
					else {
						chunk -= chunk_sub_sum;
						chunk_sub_sum = UINT64_C(0);
						if (chunk_sum <= UINT64_MAX - chunk)
							chunk_sum += chunk;
						else {
							logmsgx(IPA_LOG_ERR, "rule %s: ac_get_rule_stat: internal positive counter overflowed",
							    rule->rule_name);
							logmsgx(IPA_LOG_ERR, "rule %s: ac_get_rule_stat: this means that something is wrong in configuration",
							    rule->rule_name);
							return -1;
						}
					}
				} else /* fwac->addition == 0 */ {
					if (chunk_sum >= chunk)
						chunk_sum -= chunk;
					else {
						chunk -= chunk_sum;
						chunk_sum = UINT64_C(0);
						if (chunk_sub_sum <= UINT64_MAX - chunk)
							chunk_sub_sum += chunk;
						else {
							logmsgx(IPA_LOG_ERR, "rule %s: ac_get_rule_stat: internal negative counter overflowed",
							    rule->rule_name);
							logmsgx(IPA_LOG_ERR, "rule %s: ac_get_rule_stat: this means that something is wrong in configuration",
							    rule->rule_name);
							return -1;
						}
					}
				}
			}
		} else {
			/* Not found in kernel IP6FW table. */
			if (fwac->seen) {
				fwac->seen = 0;
				if (LOG_IP6FW_EVENTS(rule))
					logmsgx(IPA_LOG_WARNING, "rule %s: ac_get_rule_stat: %hu.%u was deleted from IP6FW kernel table",
					    rule->rule_name, fwac->number, fwac->subnumber);
			} else if (newstat) {
				if (LOG_IP6FW_EVENTS(rule))
					logmsgx(IPA_LOG_WARNING, "rule %s: ac_get_rule_stat: %hu.%u does not exist in IP6FW kernel table",
					    rule->rule_name, fwac->number, fwac->subnumber);
			}
		}
	}
	if (chunk_sum > UINT64_C(0)) {
		*addition = 1;
		*ret_chunk = chunk_sum;
	} else {
		*addition = 0;
		*ret_chunk = chunk_sub_sum;
	}
	return 0;
}

/*
 * This module always takes whole firewall table from the kernel
 * (because of the nature of the kernel IP6FW API), but we can save
 * some time in kip6fw_read_table() function by removing not needed
 * rules from the kip6fw_rules_active list.
 */
static int
ac_set_rule_active(u_int ruleno, int active)
{
	struct kip6fw_rule *kip6fw_rule1, *kip6fw_rule2;
	const struct ip6fwac *fwac;
	const struct rule *rule;

	rule = RULE(ruleno);

	for (fwac = rule->ip6fwac; fwac < rule->ip6fwac + rule->nip6fwac; ++fwac) {
		kip6fw_rule1 = fwac->kip6fw_rule;
		if (active == 0) {
			/* Set inactive. */
			kip6fw_rule1->ref_count--;
			if (kip6fw_rule1->ref_count == 0)
				TAILQ_REMOVE(&kip6fw_rules_active, kip6fw_rule1, link);
		} else {
			/* Set active. */
			if (kip6fw_rule1->ref_count == 0) {
				/*
				 * Have to make linear search to find previous
				 * active rule in list, can use kip6fw_rules_active,
				 * but I think that following code will be faster.
				 */
				if (kip6fw_rule1 != kip6fw_rules) {
					/* Not the first rule in array of kip6fw_rules. */
					kip6fw_rule2 = kip6fw_rule1;
					do {
						kip6fw_rule2--;
						if (kip6fw_rule2->ref_count != 0) {
							/* There is previous active rule. */
							TAILQ_INSERT_AFTER(&kip6fw_rules_active, kip6fw_rule2, kip6fw_rule1, link);
							goto inserted;
						}
					} while (kip6fw_rule2 != kip6fw_rules);
				}
				/*
				 * No previous active rule or this is the first
				 * rule, add it to the head of the list.
				 */
				TAILQ_INSERT_HEAD(&kip6fw_rules_active, kip6fw_rule1, link);
			}
inserted:
			kip6fw_rule1->ref_count++;
		}
	}
	return 0;
}

static ipa_conf_sect conf_sect_tbl[] = {
	{ NULL,		0,	0,	NULL,	NULL,	IPA_CONF_TYPE_MISC, 0, NULL }
};

#define PAT_RULE	"-?[[:digit:]]+(\\.[[:digit:]]+)?"
#define PAT_RULES	"^"PAT_RULE"( "PAT_RULE")*$"

static regex_t	reg_rules;

static const u_int sect_rule[] = { IPA_CONF_SECT_RULE, 0 };
static const u_int sect_root[] = { IPA_CONF_SECT_ROOT, 0 };
static const u_int sect_global_rule[] = { IPA_CONF_SECT_GLOBAL, IPA_CONF_SECT_RULE, 0 };

static ipa_conf_param conf_param_tbl[] = {
	{ "rules",	-1,	PAT_RULES,	&reg_rules,	IPA_CONF_TYPE_MISC,	sect_rule, parse_rules },
	{ "maxchunk",	-1,	NULL,		NULL,		IPA_CONF_TYPE_BYTES,	sect_global_rule, parse_maxchunk },
	{ "quiet",	 1,	NULL,		NULL,		IPA_CONF_TYPE_UINT32,	sect_global_rule, parse_quiet },
	{ "debug_ip6fw", 1,	NULL,		NULL,		IPA_CONF_TYPE_UINT32,	sect_root, parse_debug_ip6fw },
	{ NULL,		 0,	NULL,		NULL,		IPA_CONF_TYPE_MISC,	0, NULL }
};

struct ipa_ac_mod IPA_AC_MOD_ENTRY(ipa_ip6fw) = {
	AC_MOD_API_VERSION,	/* api_ver			*/
	MOD_FLAGS,		/* mod_flags			*/
	MOD_AC_NAME,		/* ac_name			*/
	NULL,			/* suppfunc			*/
	NULL,			/* memfunc			*/
	MOD_CONF_PREFIX,	/* conf_prefix			*/
	conf_sect_tbl,		/* conf_sect_tbl		*/
	conf_param_tbl,		/* conf_param_tbl		*/
	conf_init,		/* conf_init			*/
	conf_deinit,		/* conf_deinit			*/
	conf_event,		/* conf_event			*/
	conf_mimic_real,	/* conf_mimic_real		*/
	NULL,			/* conf_inherit			*/
	conf_show,		/* conf_show			*/
	ac_pre_init,		/* ac_pre_init			*/
	NULL,			/* ac_init_autorule		*/
	NULL,			/* ac_init_dynrule		*/
	ac_init_statrule,	/* ac_init_statrule		*/
	ac_init,		/* ac_init			*/
	ac_deinit_rule,		/* ac_deinit_rule		*/
	NULL,			/* ac_deinit_autorule		*/
	ac_deinit,		/* ac_deinit			*/
	NULL,			/* ac_get_stat			*/
	ac_get_rule_stat,	/* ac_get_rule_stat		*/
	NULL,			/* ac_set_autorule_active	*/
	ac_set_rule_active,	/* ac_set_rule_active		*/
	NULL,			/* ac_set_limit_active		*/
	NULL,			/* ac_set_threshold_active	*/
	NULL,			/* ac_limit_event		*/
	NULL,			/* ac_threshold_event		*/
	NULL,			/* ac_create_rule		*/
	NULL			/* ac_delete_rule		*/
};


syntax highlighted by Code2HTML, v. 0.9.1