/*-
 * Copyright (c) 2003-2004 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 "config.h"

#ifndef lint
static const char rcsid[] ATTR_UNUSED =
  "@(#)$Id: ipa_autorules.c,v 1.2.2.3 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <regex.h>

#include "ipa_mod.h"

#include "ipa_log.h"

#include "dlapi.h"
#include "confcommon.h"
#include "memfunc.h"
#include "parser.h"

#include "ipa_ac.h"
#include "ipa_db.h"
#include "ipa_time.h"

#include "ipa_cmd.h"
#include "ipa_conf.h"
#include "ipa_ctl.h"
#include "ipa_main.h"
#include "ipa_rules.h"
#include "ipa_autorules.h"

u_int		nautorules;		/* Number of autorules. */
u_int		ndynrules;		/* Number of dynamic rules. */

#ifdef WITH_AUTORULES

int		debug_autorule;		/* debug_autorule parameter. */

struct autorule	*autorules;		/* Array of autorules. */
ipa_marray	*autorules_marray;	/* Marray of autorules. */

struct autorules_list autorules_list;	/* List of all autorules. */

#define AUTORULE(i) &autorules[i]	/* Pointer to autorule by number. */

static struct rule **rules_ptr;		/* Array of pointers to rules. */
ipa_marray *rules_ptr_marray;		/* Marray for array of pointers to rules. */

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

u_int		autorules_active_check_sec;	/* Time when to check active autorules. */
u_int		autorules_inactive_check_sec;	/* Time when to check inactive autorules. */
static struct autorules_queue autorules_active;	/* Active rules queue. */
static struct autorules_queue autorules_inactive; /* Inactive rules queue. */

/*
 * Set autorule active or inactive in modules it uses.
 */
static int
mod_set_autorule_active(struct autorule *autorule, int active)
{
	if (autorule->is_active == active) {
		logmsgx(IPA_LOG_ERR, "internal error: mod_set_autorule_active(%s, %d): autorule is already %s",
		    autorule->arule_name, active, active_msg[active]);
		return -1;
	}

	if (debug_worktime)
		logmsgx(IPA_LOG_INFO, "autorule %s: set autorule %s",
		    autorule->arule_name, active_msg[active]);

	autorule->is_active = active;

	if (ac_set_autorule_active(autorule, active) < 0) {
		logmsgx(IPA_LOG_INFO, "autorule %s: mod_set_autorule_active: ac_set_autorule_active failed",
		    autorule->arule_name);
		return -1;
	}

	return 0;
}

static void
show_inactive_autorules(void)
{
	const struct autorule *autorule;

	logmsgx(IPA_LOG_INFO, "inactive autorules after sorting:");
	TAILQ_FOREACH(autorule, &autorules_inactive, queue)
		logmsgx(IPA_LOG_INFO, "    active %s, autorule %s",
		    sec_to_buf(autorule->worktime->active_sec), autorule->arule_name);
}

/*
 * Move am autorule from the active autorules queue to the inactive
 * autorules queue.
 */
static int
set_autorule_inactive(struct autorule *autorule1)
{
	struct autorule *autorule2;

	/* Inform modules that autorule becomes inactive. */
	if (mod_set_autorule_active(autorule1, INACTIVE_FLAG) < 0)
		return -1;

	/* Remove the autorule from the active autorules queue. */
	TAILQ_REMOVE(&autorules_active, autorule1, queue);

	/* Add the autorule to the inactive autorules queue, keep that queue sorted. */
	TAILQ_FOREACH(autorule2, &autorules_inactive, queue)
		if (autorule1->worktime->active_sec < autorule2->worktime->active_sec) {
			TAILQ_INSERT_BEFORE(autorule2, autorule1, queue);
			goto out;
		}
	TAILQ_INSERT_TAIL(&autorules_inactive, autorule1, queue);

out:
	if (debug_worktime)
		show_inactive_autorules();

	autorules_inactive_check_sec = TAILQ_FIRST(&autorules_inactive)->worktime->active_sec;

	return 0;
}

/*
 * Move an autorule from the inactive autorules queue to the
 * active autorules queue.
 */
static int
set_autorule_active(struct autorule *autorule)
{
	/* Inform modules that autorule becomes active. */
	if (mod_set_autorule_active(autorule, ACTIVE_FLAG) < 0)
		return -1;

	/* Remove the autorule from the inactive autorules queue. */
	TAILQ_REMOVE(&autorules_inactive, autorule, queue);

	/* Add the rule to the active autorules queue. */
	TAILQ_INSERT_TAIL(&autorules_active, autorule, queue);

	/* Get new value of autorules_inactive_check_sec. */
	autorules_inactive_check_sec = TAILQ_EMPTY(&autorules_inactive) ?
	    SECONDS_IN_WEEK : TAILQ_FIRST(&autorules_inactive)->worktime->active_sec;

	/* Check if rules_active_check_sec should be modified. */
	if (autorules_active_check_sec > autorule->update_tevent->event_sec)
		autorules_active_check_sec = autorule->update_tevent->event_sec;
	if (autorules_active_check_sec > autorule->worktime->inactive_sec)
		autorules_active_check_sec = autorule->worktime->inactive_sec;

	return 0;
}

/*
 * Sort inactive autorules.
 */
static void
sort_inactive_autorules(void)
{
	struct autorule *autorule1, *autorule1_next, *autorule2;

	if (TAILQ_EMPTY(&autorules_inactive))
		autorules_inactive_check_sec = SECONDS_IN_WEEK;
	else {
		autorule1 = TAILQ_FIRST(&autorules_inactive);
		TAILQ_INIT(&autorules_inactive);
		for (; autorule1 != NULL; autorule1 = autorule1_next) {
			autorule1_next = TAILQ_NEXT(autorule1, queue);
			if (autorule1->worktime->active_sec != SECONDS_IN_WEEK) {
				/* Will be active in current day. */
				TAILQ_FOREACH(autorule2, &autorules_inactive, queue)
					if (autorule1->worktime->active_sec <= autorule2->worktime->active_sec) {
						TAILQ_INSERT_BEFORE(autorule2, autorule1, queue);
						goto next;
					}
			}
			TAILQ_INSERT_TAIL(&autorules_inactive, autorule1, queue);
next:
			;
		}
		autorules_inactive_check_sec = TAILQ_FIRST(&autorules_inactive)->worktime->active_sec;
	}

	if (debug_worktime)
		show_inactive_autorules();
}

/*
 * Make initialization for all autorules.
 */
int
init_autorules(void)
{
	struct rule *rule;
	struct autorule *autorule;

	/* Init autorules queues. */
	TAILQ_INIT(&autorules_inactive);
	TAILQ_INIT(&autorules_active);

	/* Flush number of dynamic rules. */
	ndynrules = 0;

	if (nautorules != 0) {
		/* Init each autorule in accounting modules. */
		STAILQ_FOREACH(autorule, &autorules_list, link) {
			if (ac_init_autorule(autorule) < 0) {
				logmsgx(IPA_LOG_ERR, "autorule %s: init_autorules: ac_init_autorule failed",
				    autorule->arule_name);
				return -1;
			}
			TAILQ_INSERT_TAIL(&autorules_active, autorule, queue);
		}

		/* Create rules_ptr_marray. */
		if ( (rules_ptr_marray = marray_init(MARRAY_NAME(rules_ptr),
		    "Pointers to rules", 0, (void *)&rules_ptr, sizeof(struct rule *),
		    nstatrules + RULE_NSIZE, RULE_NALLOC)) == NULL) {
			logmsgx(IPA_LOG_ERR, "init_autorules: marray_init failed");
			return -1;
		}

		/* Mark entries in rules_ptr for static rules as used. */
		TAILQ_FOREACH(rule, &rules_list, list)
			if (marray_alloc(rules_ptr_marray, &rule->ruleno, 1) < 0) {
				logmsgx(IPA_LOG_ERR, "init_autorules: marray_alloc failed");
				return -1;
			}
	}

	return 0;
}

/*
 * Deinit one autorule.
 */
static int
deinit_autorule(struct autorule *autorule)
{
	int error = 0;

	/* Deinit in modules. */
	if (ac_deinit_autorule(autorule) < 0) {
		logmsgx(IPA_LOG_ERR, "autorule %s: deinit_autorule: ac_deinit_autorule failed",
		    autorule->arule_name);
		error = -1;
	}

	/* Decrement ref_count for accounting systems. */
	if (IS_ACTIVE(autorule))
		if (ac_dec_ref_count(autorule->ac_list) < 0) {
			logmsgx(IPA_LOG_ERR, "autorule %s: deinit_autorule: ac_dec_ref_count failed",
			    autorule->arule_name);
			error = -1;
		}

	/* And free all memory used by a autorule. */

	mem_free(autorule->arule_name, m_anon);
	free_cmds_rule(&autorule->rc[RC_STARTUP]);
	free_cmds_rule(&autorule->rc[RC_SHUTDOWN]);

#ifdef WITH_LIMITS
	free_limits(RULE_FREE_LIMITS, &autorule->limits, 0);
#endif

#ifdef WITH_THRESHOLDS
	free_thresholds(RULE_FREE_THRESHOLDS, &autorule->thresholds, 0);
#endif

	return error;
}

/*
 * Make deinitialization for all autorules.
 */
int
deinit_autorules(void)
{
	int	error;
	u_int	n;
	struct autorule *autorule;

	error = 0;

	if (nautorules != 0) {
		STAILQ_FOREACH(autorule, &autorules_list, link)
			if (deinit_autorule(autorule) < 0) {
				logmsgx(IPA_LOG_ERR, "deinit_autorules: deinit_autorule failed");
				error = -1;
			}

		marray_deinit(autorules_marray);

		if (ndynrules != 0) {
			logmsgx(IPA_LOG_ERR, "internal error: deinit_autorules: ndynrules is not zero: %u", ndynrules);
			error = -1;
		}

		if (rules_ptr_marray != NULL) {
			if ( (n = marray_nused(rules_ptr_marray)) != 0) {
				logmsgx(IPA_LOG_ERR, "internal error: deinit_autorules: rules_ptr_marray is not empty: %u", n);
				error = -1;
			}
			marray_deinit(rules_ptr_marray);
		}
	}

	return error;
}

/*
 * Inherit settings for dynamic rule.
 */
static int
dyn_rule_inherit(const struct autorule *autorule, struct rule *rule)
{
	struct rulepat *rulepat;
#ifdef WITH_LIMITS
	struct limit *limit;
#endif
#ifdef WITH_THRESHOLDS
	struct threshold *threshold;
#endif

	/* Inherit settings from autorule{}. */

	/* Dynamic rule always inherits non-NULL update_tevent from autorule. */
	rule->update_tevent = autorule->update_tevent;

	rule->append_tevent = autorule->append_tevent;

	rule->worktime = autorule->worktime_rule;

	/* Dynamic rule always inherits non-NULL ac_list from autorule. */
	rule->ac_list = autorule->ac_list;
	ac_inc_ref_count(rule->ac_list);

	rule->db_list = autorule->db_list;

#ifdef WITH_RULES
	/* Dynamic rules can't have ac_gather_* parameters. */
	rule->ac_gather_add_pat = rule->ac_gather_sub_pat = NULL;
	SLIST_INIT(&rule->ac_gather_rev);
#endif

	rule->debug_exec = autorule->debug_exec;

	init_cmds_in_rule(rule);

#ifdef WITH_LIMITS
	rule->debug_limit = autorule->debug_limit;
	rule->debug_limit_init = autorule->debug_limit_init;
	STAILQ_INIT(&rule->limits);
#endif

#ifdef WITH_THRESHOLDS
	rule->debug_threshold = autorule->debug_threshold;
	rule->debug_threshold_init = autorule->debug_threshold_init;
	STAILQ_INIT(&rule->thresholds);
#endif

	/*
	 * free_mask is always zero for dynamic rules, since copy_limits()
	 * and copy_thresholds() always are called for dynamic rules.
	 */
	rule->free_mask = 0;

#ifdef CTL_CHECK_CREDS
	rule->ctl_rule_acl = autorule->ctl_rule_acl;
#endif

	/* Add rule to rules list. */
	TAILQ_INSERT_TAIL(&rules_list, rule, list);

	/*
	 * At this point everything is ready in rule for possible
	 * deinit_dyn_rule() invocation.
	 */

	/* Inherit other settings from autorule, and we can fail here. */

	if (autorule->rc[RC_STARTUP].set != 0) {
		if (copy_cmds_rule(rule, &rule->rc[RC_STARTUP], &autorule->rc[RC_STARTUP]) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule_inherit: cannot copy all commands from autorule %s { startup {}}",
			    rule->rule_name, autorule->arule_name);
			return -1;
		}
		rule->rc[RC_STARTUP].set = 1;
	}

	if (autorule->rc[RC_SHUTDOWN].set != 0) {
		if (copy_cmds_rule(rule, &rule->rc[RC_SHUTDOWN], &autorule->rc[RC_SHUTDOWN]) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule_inherit: cannot copy all commands from autorule %s { shutdown {}}",
			    rule->rule_name, autorule->arule_name);
			return -1;
		}
		rule->rc[RC_SHUTDOWN].set = 1;
	}

#ifdef WITH_LIMITS
	if (!STAILQ_EMPTY(&autorule->limits))
		if (copy_limits(rule, &autorule->limits, 1) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule_inherit: cannot copy all limits from autorule %s",
			    rule->rule_name, autorule->arule_name);
			return -1;
		}
#endif

#ifdef WITH_THRESHOLDS
	if (!STAILQ_EMPTY(&autorule->thresholds))
		if (copy_thresholds(rule, &autorule->thresholds, 1) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule: cannot copy all thresholds from autorule %s",
			    rule->rule_name, autorule->arule_name);
			return -1;
		}
#endif

	STAILQ_FOREACH(rulepat, &rulepats_list, link)
		if (regexec(&rulepat->reg, rule->rule_name, 0, (regmatch_t *)NULL, 0) == 0) {
			/* Inherit some settings for dynamic rule from rulepat{}. */
			if (rule->append_tevent == NULL)
				rule->append_tevent = rulepat->append_tevent;
			if (rule->worktime == NULL)
				rule->worktime = rulepat->worktime;
			if (rule->db_list == NULL)
				rule->db_list = rulepat->db_list;
			if (rule->debug_exec < 0)
				rule->debug_exec = rulepat->debug_exec;
			if (rule->rc[RC_STARTUP].set == 0 && rulepat->rc[RC_STARTUP].set != 0)
				if (copy_cmds_rule(rule, &rule->rc[RC_STARTUP], &rulepat->rc[RC_STARTUP]) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule_inherit: cannot copy all commands from rulepat %s { startup {}}",
					    rule->rule_name, parser_buf_to_string(rulepat->pat));
					return -1;
				}
			if (rule->rc[RC_SHUTDOWN].set == 0 && rulepat->rc[RC_SHUTDOWN].set != 0)
				if (copy_cmds_rule(rule, &rule->rc[RC_SHUTDOWN], &rulepat->rc[RC_SHUTDOWN]) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule_inherit: cannot copy all commands from rulepat %s { shutdown {}}",
					    rule->rule_name, parser_buf_to_string(rulepat->pat));
					return -1;
				}
#ifdef WITH_LIMITS
			if (rule->debug_limit < 0)
				rule->debug_limit = rulepat->debug_limit;
			if (rule->debug_limit_init < 0)
				rule->debug_limit_init = rulepat->debug_limit_init;
			if (STAILQ_EMPTY(&rule->limits) && !STAILQ_EMPTY(&rulepat->limits))
				if (copy_limits(rule, &rulepat->limits, 1) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule_inherit: cannot copy all limits from rulepat %s",
					    rule->rule_name, parser_buf_to_string(rulepat->pat));
					return -1;
				}
#endif
#ifdef WITH_THRESHOLDS
			if (rule->debug_threshold < 0)
				rule->debug_threshold = rulepat->debug_threshold;
			if (rule->debug_threshold_init < 0)
				rule->debug_threshold_init = rulepat->debug_threshold_init;
			if (STAILQ_EMPTY(&rule->thresholds) && !STAILQ_EMPTY(&rulepat->thresholds))
				if (copy_thresholds(rule, &rulepat->thresholds, 1) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule_inherit: cannot copy all thresholds from rulepat %s",
					    rule->rule_name, parser_buf_to_string(rulepat->pat));
					return -1;
				}
#endif
#ifdef CTL_CHECK_CREDS
			if (rule->ctl_rule_acl == NULL)
				rule->ctl_rule_acl = rulepat->ctl_rule_acl;
#endif
			if (mod_conf_inherit(rulepat, rule) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s: dyn_rule_inherit: mod_conf_inherit failed",
				    rule->rule_name);
				return -1;
			}
			if (rulepat->check_next_rulepat == 0)
				break;
		}

	if (rulepat == NULL) {
		/* Inherit some settings for dynamic rule from global{}. */
		if (rule->append_tevent == NULL)
			rule->append_tevent = global_append_tevent;
		if (rule->worktime == NULL)
			rule->worktime = global_worktime;
		if (rule->db_list == NULL)
			rule->db_list = global_db_list;
		if (rule->debug_exec < 0)
			rule->debug_exec = global_debug_exec;
#ifdef WITH_LIMITS
		if (rule->debug_limit < 0)
			rule->debug_limit = global_debug_limit;
		if (rule->debug_limit_init < 0)
			rule->debug_limit_init = global_debug_limit_init;
#endif
#ifdef WITH_THRESHOLDS
		if (rule->debug_threshold < 0)
			rule->debug_threshold = global_debug_threshold;
		if (rule->debug_threshold_init)
			rule->debug_threshold_init = global_debug_threshold_init;
#endif
#ifdef CTL_CHECK_CREDS
		if (rule->ctl_rule_acl == NULL)
			rule->ctl_rule_acl = global_ctl_rule_acl;
#endif
	}

#ifdef WITH_LIMITS
	STAILQ_FOREACH(limit, &rule->limits, link) {
		if (check_worktime_subset(rule->worktime, limit->worktime) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: dyn_rule_inherit: limit's worktime must be a subset of rule's worktime",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
		if (limit->worktime == NULL)
			limit->worktime = rule->worktime;
		if (limit->db_list == NULL)
			limit->db_list = rule->db_list;
	}
#endif /* WITH_LIMITS */

#ifdef WITH_THRESHOLDS
	STAILQ_FOREACH(threshold, &rule->thresholds, link) {
		if (check_worktime_subset(rule->worktime, threshold->worktime) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: dyn_rule_inherit: threshold's worktime must be a subset of rule's worktime",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}
		if (threshold->worktime == NULL)
			threshold->worktime = rule->worktime;
		if (threshold->db_list == NULL)
			threshold->db_list = rule->db_list;
	}
#endif /* WITH_THRESHOLDS */

	return 0;
}

/*
 * Deinit one dynamic rule.
 */
int
deinit_dyn_rule(struct rule *rule)
{
	/* Mark rule as unused. */
	marray_free(rules_ptr_marray, rule->ruleno);

	/* Decrement number of dynamic rules. */
	--ndynrules;

	if (deinit_rule(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "deinit_dyn_rule: deinit_rule failed");
		return -1;
	}

	return 0;
}

/*
 * Init one dynamic rule.
 */
static int
init_dyn_rule(const struct autorule *autorule, struct rule *rule)
{
	rule->newstat = 0;

	if (rule->append_tevent == NULL)
		rule->append_sec = SECONDS_IN_WEEK;

	if (ac_init_dynrule(autorule, rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: init_dyn_rule: ac_init_dynrule failed",
		    rule->rule_name);
		return -1;
	}

	if (db_init_dynrule(autorule, rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: init_dyn_rule: db_init_dynrule failed",
		    rule->rule_name);
		return -1;
	}

	return 0;
}

/*
 * Create a dynamic rule from the autorule, this function is invoked
 * from the ipa_ac_mod's ac_get_stat() function.
 */
int
create_rule(const char *mod_name, u_int autoruleno,
    const char *rule_name, const char *rule_info)
{
	u_int	ruleno;
	struct rule *rule;
	struct autorule *autorule;
#ifdef WITH_LIMITS
	struct limit *limit;
#endif
#ifdef WITH_THRESHOLDS
	struct threshold *threshold;
#endif

	/* Validate aruleno. */
	if (autoruleno >= nautorules) {
		logmsgx(IPA_LOG_ERR, "create_rule: module %s tries to create dynamic rule from autorule with number %u and such autorule does not exist",
		    mod_name, autoruleno);
		goto failed;
	}

	autorule = AUTORULE(autoruleno);

	if (debug_autorule)
		logmsgx(IPA_LOG_INFO, "create_rule: module %s creates rule %s from autorule %s",
		    mod_name, rule_name, autorule->arule_name);

	/* Check if there is already rule with the given name. */
	if ( (rule = rule_by_name(rule_name)) != NULL) {
		logmsgx(IPA_LOG_WARNING, "autorule %s: create_rule: module %s is trying to create duplicated rule %s",
		    autorule->arule_name, mod_name, rule_name);
		return -2;
	}

	/* Find first unused ruleno. */
	if (marray_alloc(rules_ptr_marray, &ruleno, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: create_rule: marray_alloc failed",
		    rule_name);
		goto failed;
	}

	/* Create rule. */
	if ( (rule = mzone_alloc(rule_mzone)) == NULL) {
		logmsgx(IPA_LOG_ERR, "rule %s: create_rule: mzone_alloc failed",
		    rule_name);
		marray_free(rules_ptr_marray, ruleno);
		goto failed;
	}

	/* Copy rule_name. */
	if ( (rule->rule_name = mem_strdup(rule_name, m_anon)) == NULL) {
		logmsgx(IPA_LOG_ERR, "rule %s: create_rule: mem_strdup for rule_name failed",
		    rule_name);
		mzone_free(rule_mzone, rule);
		marray_free(rules_ptr_marray, ruleno);
		goto failed;
	}

	RULE(ruleno) = rule;
	rule->ruleno = ruleno;
	++ndynrules;

	/* Add rule to rules hash. */
	add_rule_to_hash(rule);

	rule->rule_info = NULL;

	/*
	 * Flush negative counter, positive counter we will flush
	 * in db_append_rule().
	 */
	rule->cnt_neg = UINT64_C(0);

	/* By default any new rule is active. */
	rule->is_active = ACTIVE_FLAG;
	queue_active_rule(rule);

	/* Inherit settings for dynamic rule. */
	if (dyn_rule_inherit(autorule, rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: create_rule: cannot inherit all settings for dynamic rule",
		    rule_name);
		goto failed_deinit;
	}

#ifdef WITH_RULES
	if (has_ac_gather)
    		if (init_ac_gather_rev(rule) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: create_rule: init_ac_gather_rev failed",
			    rule_name);
	    		goto failed_deinit;
		}
#endif

	/* Copy rule_info if it exist. */
	if (rule_info != NULL)
		if ( (rule->rule_info = mem_strdup(rule_info, m_anon)) == NULL) {
			logmsgx(IPA_LOG_ERR, "rule %s: create_rule: mem_strdup for rule_info failed",
			    rule_name);
			goto failed_deinit;
		}

	if (init_dyn_rule(autorule, rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: create_rule: init_dyn_rule failed",
		    rule_name);
		goto failed_deinit;
	}

#ifdef WITH_LIMITS
	if (init_limits(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: create_rule: init_limits failed",
		    rule_name);
		goto failed_deinit;
	}
#endif

#ifdef WITH_THRESHOLDS
	if (init_thresholds(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: create_rule: init_thresholds failed",
		    rule_name);
		goto failed_deinit;
	}
#endif

	if (run_rule_cmds(rule, RC_STARTUP) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: create_rule: run_rule_cmds failed",
		    rule_name);
		goto failed_deinit;
	}

	/*
	 * By default any new rule is active as its limits and thresholds
	 * are.  First ac_get_rule_stat() for the new rule returns statistics,
	 * mark rule inactive even if newday_flag is set on.
	 */

#ifdef WITH_LIMITS
	STAILQ_FOREACH(limit, &rule->limits, link)
		if (IS_INACTIVE(limit->worktime))
			if (set_limit_inactive(rule, limit) < 0) {
				logmsgx(IPA_LOG_ERR, "create_rule: set_limit_inactive failed");
				goto failed_deinit;
			}
#endif

#ifdef WITH_THRESHOLDS
	STAILQ_FOREACH(threshold, &rule->thresholds, link)
		if (IS_INACTIVE(threshold->worktime))
			if (set_threshold_inactive(rule, threshold) < 0) {
				logmsgx(IPA_LOG_ERR, "create_rule: set_threshold_inactive failed");
				goto failed_deinit;
			}
#endif

	if (IS_INACTIVE(rule->worktime)) {
		if (set_rule_inactive(rule) < 0) {
			logmsgx(IPA_LOG_ERR, "create_rule: set_rule_inactive failed");
			goto failed_deinit;
		}
	} else {
		/* If a rule is active, then check it immediately. */
		rule->check_sec = 0;
		if (db_append_rule(rule, &uint64_zero, 1) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: create_rule: db_append_rule failed",
			    rule_name);
			goto failed_deinit;
		}
	}

	if (debug_autorule)
		logmsgx(IPA_LOG_INFO, "create_rule: dynamic rule %s with number %u has been created",
		    rule_name, ruleno);

	return 0;

failed_deinit:
	if (deinit_dyn_rule(rule) < 0)
		logmsgx(IPA_LOG_ERR, "create_rule: deinit_dyn_rule failed");
	free_rule(rule);

failed:
	return -1;
}

/*
 * Delete a dynamic rule previously created from an autorule,
 * this function is invoked from the ipa_ac_mod.
 */
int
delete_rule(const char *mod_name, u_int ruleno)
{
	int	error;
	struct rule *rule;

	/* Validate ruleno. */
	if (ruleno < nstatrules) {
		logmsgx(IPA_LOG_ERR, "delete_rule: module %s tries to delete rule with number %u and this rule is not dynamic",
		    mod_name, ruleno);
		goto failed;
	}

	if (!marray_check_index(rules_ptr_marray, ruleno)) {
		logmsgx(IPA_LOG_ERR, "delete_rule: module %s tries to delete rule with number %u and this rule does not exist",
		    mod_name, ruleno);
		goto failed;
	}

	rule = RULE(ruleno);

	if (debug_autorule)
		logmsgx(IPA_LOG_INFO, "delete_rule: module %s tries to delete rule %s with number %u",
		    mod_name, rule->rule_name, ruleno);

	error = 0;

	if (run_rule_cmds(rule, RC_SHUTDOWN) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: delete_rule: run_rule_cmds failed",
		    rule->rule_name);
		error = 1;
	}

	if (deinit_dyn_rule(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "delete_rule: deinit_dyn_rule failed");
		error = 1;
	}

	free_rule(rule);

	if (debug_autorule)
		logmsgx(IPA_LOG_INFO, "delete_rule: rule with number %u has been deleted",
		    ruleno);

	if (error)
		goto failed;

	return 0;

failed:
	return -1;
}

/*
 * This function is called from newday().
 */
int
autorules_newday(void)
{
	struct autorule *autorule;

	autorules_active_check_sec = SECONDS_IN_WEEK; /* Hint. */

	STAILQ_FOREACH(autorule, &autorules_list, link) {
		if (IS_INACTIVE(autorule->worktime)) {
			if (IS_ACTIVE(autorule))
				if (set_autorule_inactive(autorule) < 0) {
					logmsgx(IPA_LOG_ERR, "autorules_newday: set_autorule_inactive failed");
					return -1;
				}
		} else {
			if (IS_INACTIVE(autorule))
				if (set_autorule_active(autorule) < 0) {
					logmsgx(IPA_LOG_ERR, "autorules_newday: set_autorule_active failed");
					return -1;
				}
			if (autorules_active_check_sec > autorule->update_tevent->event_sec)
				autorules_active_check_sec = autorule->update_tevent->event_sec;
			if (autorules_active_check_sec > autorule->worktime->inactive_sec)
				autorules_active_check_sec = autorule->worktime->inactive_sec;
		}
	}

	sort_inactive_autorules();

	return 0;
}

/*
 * Check if time to make some autorule active come.  This function
 * is called when autorules_inactive_check_sec <= cursec.
 */
int
check_inactive_autorules(void)
{
	struct autorule *autorule, *autorule_next;

	for (autorule = TAILQ_FIRST(&autorules_inactive); autorule != NULL; autorule = autorule_next) {
		if (autorule->worktime->inactive_sec <= cursec) {
			/* It's time to make autorule active. */
			autorule_next = TAILQ_NEXT(autorule, queue);
			if (set_autorule_active(autorule) < 0) {
				logmsgx(IPA_LOG_ERR, "check_inactive_autorules: set_autorule_active failed");
				return -1;
			}
		} else {
			/* Set next time of invocation of this function. */
			autorules_inactive_check_sec = autorule->worktime->active_sec;
			return 0;
		}
	}

	/* No more inactive autorules. */
	autorules_inactive_check_sec = SECONDS_IN_WEEK;
	return 0;
}

/*
 * This function is called from do_ac() and it is very simple,
 * since usually there are not a lot of autorules.  This function
 * is called when autorules_active_check_sec <= cursec.
 */
int
check_active_autorules(void)
{
	struct autorule *autorule;

	autorules_active_check_sec = SECONDS_IN_WEEK; /* Hint. */

	TAILQ_FOREACH(autorule, &autorules_active, queue) {
		if (IS_INACTIVE(autorule->worktime)) {
			/* Autorule became inactive. */
			if (!newday_flag)
				if (set_autorule_inactive(autorule) < 0) {
					logmsgx(IPA_LOG_ERR, "check_active_autorules: set_autorule_inactive failed");
					return -1;
				}
		}  else {
			/* Autorule is still active. */
			if (autorules_active_check_sec > autorule->worktime->inactive_sec)
				autorules_active_check_sec = autorule->worktime->inactive_sec;
			if (autorules_active_check_sec > autorule->update_tevent->event_sec)
				autorules_active_check_sec = autorule->update_tevent->event_sec;
		}
	}

	return 0;
}

void
init_cmds_in_autorule(struct autorule *autorule)
{
	init_cmds_rule(&autorule->rc[RC_STARTUP]);
	init_cmds_rule(&autorule->rc[RC_SHUTDOWN]);
}

void
set_sync_exec_in_autorule(struct autorule *autorule)
{
	set_sync_exec_cmds_rule(&autorule->rc[RC_STARTUP]);
	set_sync_exec_cmds_rule(&autorule->rc[RC_SHUTDOWN]);
}

#else /* !WITH_AUTORULES */

/* ARGSUSED3 */
int
create_rule(const char *mod_name, u_int autoruleno,
    const char *rule_name, const char *rule_info ATTR_UNUSED)
{
	logmsgx(IPA_LOG_WARNING, "create_rule: module %s tries to create rule %s from autorule %u",
	    mod_name, rule_name, autoruleno);
	logmsgx(IPA_LOG_WARNING, "create_rule: autorules support was removed");
	return -1;
}

int
delete_rule(const char *mod_name, u_int ruleno)
{
	logmsgx(IPA_LOG_ERR, "delete_rule: module %s tries to delete rule %u",
	    mod_name, ruleno);
	logmsgx(IPA_LOG_ERR, "delete_rule: autorules support was removed");
	return -1;
}
#endif /* WITH_AUTORULES */


syntax highlighted by Code2HTML, v. 0.9.1