/*-
 * Copyright (c) 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_ctl.c,v 1.2.2.7 2007/07/20 09:53:41 simon Exp $";
#endif /* !lint */

#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <grp.h>
#include <pwd.h>
#include <unistd.h>

#include "ipa_mod.h"

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

#include "ipa_cmd.h"
#include "ipa_time.h"

#include "ipa_ac.h"
#include "ipa_db.h"
#include "ipa_ctl.h"
#include "ipa_log.h"
#include "ipa_main.h"
#include "ipa_rules.h"
#include "ipa_autorules.h"

#ifdef MSG_WAITALL
# define RECVMSG_FLAGS MSG_WAITALL
#else
# define RECVMSG_FLAGS 0
#endif

#ifndef PF_LOCAL
# ifdef PF_UNIX
#  define PF_LOCAL PF_UNIX
# else
#  ifdef AF_LOCAL
#   define PF_LOCAL AF_LOCAL
#  else
#   ifdef AF_UNIX
#    define PF_LOCAL AF_UNIX
#   else
#    error cannot define PF_LOCAL
#   endif
#  endif
# endif
#endif

#ifndef SUN_LEN
# define SUN_LEN(su) \
	(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
#endif

#define CTL_BACKLOG	5

int		ctl_enable;		/* ctl_enable parameter. */
char		*ctl_socket_path;	/* ctl_socket_path parameter. */
mode_t		ctl_socket_perm;	/* ctl_socket_perm parameter. */
u_int		ctl_timeout;		/* ctl_timeout parameter. */

char		*ctl_socket_path_default = IPA_CTL_SOCKET;

int		debug_ctl;		/* debug_ctl parameter. */

ipa_mem_type	*m_ctl;			/* Memory allocations for ctl. */

#ifdef CTL_CHECK_CREDS
const struct ctl_acl_class *ctl_dump_acl; /* ctl_dump_acl parameter. */
const struct ctl_acl_class *ctl_freeze_acl; /* ctl_freeze_acl parameter. */
const struct ctl_acl_class *ctl_stat_acl; /* ctl_stat_acl parameter. */
const struct ctl_acl_class *global_ctl_rule_acl; /* global { ctl_rule_acl } */
struct ctl_acl_classes ctl_acl_classes;	/* List of all ctl ACL classes. */
ipa_mzone	*ctl_acl_elem_mzone;	/* Mzone for all ctl ACL elements. */
ipa_mzone	*ctl_acl_class_mzone;	/* Mzone for all ctl ACL classes. */
#endif

int		ctl_listenfd = -1;	/* ctl listen socket descriptor. */

static int	ctl_connfd;		/* Connected socket descriptor. */
static int	ctl_socket_bound;	/* Non-zero if socket file was bound. */
static int	wait_answer;		/* Non-zero if client is waiting for answer. */

static struct ctl_cmd_query cmd_query;	/* Control command query. */
static struct ctl_cmd_answer cmd_answer;/* Control command answer. */

static void	*cmd_answer_aux;	/* Auxiliary answer. */
static size_t	cmd_answer_aux_size;	/* Auxiliary answer size. */
static int	cmd_answer_aux_allocated; /* Non-zero if cmd_answer_aux was allocated. */

static struct ctl_cmd_answer_status cmd_answer_status;
#ifdef WITH_ANY_LIMITS
static struct ctl_cmd_answer_set cmd_answer_set;
#endif

static struct msghdr msg_query;		/* Where to received command query. */
static struct iovec iov_query[1];	/* iovec with query. */

static struct iovec iov_answer[2];	/* iovec with answer. */
static int	iovcnt_answer;		/* Number of buffers in iov_answer. */

static struct rule *q_rule;		/* Pointer to rule with name cmd_query.name1. */

#ifdef WITH_LIMITS
static struct limit *q_limit;		/* Pointer to limit with name cmd_query.name2. */
#endif

#ifdef WITH_THRESHOLDS
static struct threshold *q_threshold;	/* Pointer to threshold with name cmd_query.name2. */
#endif

#ifdef CTL_CHECK_CREDS
# ifdef __FreeBSD__
static union {
	struct cmsghdr	cm;
	char	control[CMSG_SPACE(sizeof(struct cmsgcred))];
} query_control_un;
# endif /* _FreeBSD__ */
#endif /* CTL_CHECK_CREDS */

static sigjmp_buf env_alrm;

static const char * const ctl_cmd_msg[] = {
	"status",		/* 0 | CTL_CMD_STATUS	*/
	"memory",		/* 1 | CTL_CMD_MEMORY	*/
	"dump",			/* 2 | CTL_CMD_DUMP	*/
	"freeze",		/* 3 | CTL_CMD_FREEZE	*/
	"restart",		/* 4 | CTL_CMD_RESTART	*/
	"expire",		/* 5 | CTL_CMD_EXPIRE	*/
	"set"			/* 6 | CTL_CMD_SET	*/
};

/*
 * Several similar make_*_active() functions: if worktime is inactive,
 * then set rule, limit or threshold active or inactive, depending
 * on the active argument.
 */
static int
make_rule_active(struct rule *rule, int active)
{
	if (IS_INACTIVE(rule->worktime))
		if (mod_set_rule_active(rule, active) < 0) {
			logmsgx(IPA_LOG_ERR, "make_rule_active: mod_set_rule_active failed");
			return -1;
		}
	return 0;
}

#ifdef WITH_LIMITS
static int
make_limit_active(const struct rule *rule, struct limit *limit, int active)
{
	if (IS_INACTIVE(limit->worktime))
		if (mod_set_limit_active(rule, limit, active) < 0) {
			logmsgx(IPA_LOG_ERR, "make_limit_active: mod_set_limit_active failed");
			return -1;
		}
	return 0;
}
#endif

#ifdef WITH_THRESHOLDS
static int
make_threshold_active(const struct rule *rule, struct threshold *threshold,
    int active)
{
	if (IS_INACTIVE(threshold->worktime))
		if (mod_set_threshold_active(rule, threshold, active) < 0) {
			logmsgx(IPA_LOG_ERR, "make_threshold_active: mod_set_threshold_active failed");
			return -1;
		}
	return 0;
}
#endif

/*
 * SIGALRM handler.
 */
/* ARGSUSED */
void
sig_alrm(int signo ATTR_UNUSED)
{
	siglongjmp(env_alrm, 1);
}

/*
 * Create Unix domain socket and update relevant variables.
 */
int
init_ctl(void)
{
	struct sockaddr_un servaddr;
	mode_t oumask;
	int ret;

	logmsgx(IPA_LOG_INFO, "creating ctl socket %s", ctl_socket_path);

	if ( (ctl_listenfd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
		logmsg(IPA_LOG_ERR, "init_ctl: socket(PF_LOCAL, SOCK_STREAM, 0)");
		return -1;
	}

	memset(&servaddr, 0, sizeof servaddr);
	servaddr.sun_family = PF_LOCAL;
	strncpy(servaddr.sun_path, ctl_socket_path, sizeof(servaddr.sun_path) - 1);

	oumask = umask(S_IWUSR|S_IXUSR|S_IRWXG|S_IRWXO);
	ret = bind(ctl_listenfd, (struct sockaddr *)&servaddr, SUN_LEN(&servaddr));
	(void)umask(oumask);
	if (ret < 0) {
		logmsg(IPA_LOG_ERR, "init_ctl: bind(%s)", ctl_socket_path);
		ctl_socket_bound = 0;
		return -1;
	}
	ctl_socket_bound = 1;

	if (chmod(ctl_socket_path, ctl_socket_perm) < 0) {
		logmsg(IPA_LOG_ERR, "init_ctl: chmod(%s, 0%03o)", ctl_socket_path,
		    (u_int)ctl_socket_perm);
		return -1;
	}

	if (chown(ctl_socket_path, myuid, mygid) < 0) {
		logmsg(IPA_LOG_ERR, "init_ctl: chown(%s, %lu, %lu)", ctl_socket_path,
		    (u_long)myuid, (u_long)mygid);
		return -1;
	}

	if (set_nonblock(ctl_listenfd) < 0) {
		logmsgx(IPA_LOG_ERR, "init_ctl: set_nonblock failed");
		return -1;
	}

	/*
	 * Even if ctl_socket_path was created by bind() with incorrect,
	 * permission bits, all clients will get ``Connection refused'',
	 * because socket is not in SO_ACCEPTCONN state.
	 */
	if (listen(ctl_listenfd, CTL_BACKLOG) < 0) {
		logmsg(IPA_LOG_ERR, "init_ctl: listen(%s, %u)", ctl_socket_path, CTL_BACKLOG);
		return -1;
	}

	/* Init msg_query. */
	msg_query.msg_name = NULL;
	msg_query.msg_namelen = 0;

	iov_query[0].iov_base = (char *)&cmd_query;
	iov_query[0].iov_len = sizeof cmd_query;
	msg_query.msg_iov = iov_query;
	msg_query.msg_iovlen = 1;

#ifdef CTL_CHECK_CREDS
# ifdef __FreeBSD__
	msg_query.msg_control = query_control_un.control;
	msg_query.msg_controllen = sizeof query_control_un.control;
# endif
#else
	msg_query.msg_control = NULL;
	msg_query.msg_controllen = 0;
#endif /* CTL_CHECK_CREDS */

	/* Init part of iov_answer. */
	iov_answer[0].iov_base = (char *)&cmd_answer;
	iov_answer[0].iov_len = sizeof cmd_answer;

	return 0;
}

#ifdef CTL_CHECK_CREDS
/*
 * Release memory used by all ACLs.
 */
static int
free_ctl_acls(void)
{
	struct ctl_acl_elem *elem;
	const struct ctl_acl_class *class;

	STAILQ_FOREACH(class, &ctl_acl_classes, link) {
		STAILQ_FOREACH(elem, &class->list, link) {
			mem_free(elem->user, m_ctl);
			mem_free(elem->group, m_ctl);
		}
		mem_free(class->class_name, m_ctl);
	}

	mzone_deinit(ctl_acl_elem_mzone);
	mzone_deinit(ctl_acl_class_mzone);

	return 0;
}
#endif /* CTL_CHECK_CREDS */

/*
 * Close listen Unix domain socket and unlink it.
 */
int
deinit_ctl(void)
{
	int error = 0;

	if (ctl_listenfd >= 0) {
		if (close(ctl_listenfd) < 0) {
			logmsg(IPA_LOG_ERR, "deinit_ctl: close(%s)", ctl_socket_path);
			error = -1;
		}
		if (ctl_socket_bound) {
			logmsgx(IPA_LOG_INFO, "unlinking ctl socket %s", ctl_socket_path);
			if (unlink(ctl_socket_path) < 0) {
				logmsg(IPA_LOG_ERR, "deinit_ctl: unlink(%s)", ctl_socket_path);
				error = -1;
			}
		}
		ctl_listenfd = -1;
	}

	if (ctl_socket_path != ctl_socket_path_default)
		mem_free(ctl_socket_path, m_parser);

#ifdef CTL_CHECK_CREDS
	free_ctl_acls();
#endif

	return error;
}

/*
 * "status"
 */
static int
ctl_func_status(void)
{
#ifdef WITH_LIMITS
	const struct limit *limit;
#endif
#ifdef WITH_SUBLIMITS
	const struct sublimit *sublimit;
#endif
#ifdef WITH_THRESHOLDS
	const struct threshold *threshold;
#endif

	if (wait_answer) {
		if (q_rule == NULL) {
			cmd_answer_status.nac_mods = nac_mods;
			cmd_answer_status.ndb_mods = ndb_mods;
			cmd_answer_status.nautorules = nautorules;
			cmd_answer_status.nstatrules = nstatrules;
			cmd_answer_status.ndynrules = ndynrules;
			cmd_answer_status.nstatlimits = nstatlimits;
			cmd_answer_status.ndynlimits = ndynlimits;
			cmd_answer_status.nstatsublimits = nstatsublimits;
			cmd_answer_status.ndynsublimits = ndynsublimits;
			cmd_answer_status.nstatthresholds = nstatthresholds;
			cmd_answer_status.ndynthresholds = ndynthresholds;
		} else {
			cmd_answer_status.dynamic = RULE_IS_DYNAMIC(q_rule);
#ifdef WITH_LIMITS
			if (q_limit != NULL) {
				cmd_answer_status.active = q_limit->is_active;
				cmd_answer_status.nstatsublimits = 0;
# ifdef WITH_SUBLIMITS
				STAILQ_FOREACH(sublimit, &q_limit->sublimits, link)
					cmd_answer_status.nstatsublimits++;
# endif
				cmd_answer_status.reached = q_limit->is_reached;
				cmd_answer_status.tm.tm_year = 0;
				if (q_limit->is_reached) {
					if (q_limit->expire.expire.upto != TEXP_UPTO_NOTSET)
						cmd_answer_status.tm = q_limit->event_tm;
				} else {
					if (q_limit->restart.restart.upto != TEXP_UPTO_NOTSET)
						cmd_answer_status.tm = q_limit->event_tm;
				}
				cmd_answer_status.value_type = q_limit->cnt_type;
				cmd_answer_status.value1 = q_limit->lim;
				if (q_limit->cnt_neg == UINT64_C(0)) {
					cmd_answer_status.value2 = q_limit->cnt;
					cmd_answer_status.value2_sign = 0;
				} else {
					cmd_answer_status.value2 = q_limit->cnt_neg;
					cmd_answer_status.value2_sign = 1;
				}
			} else
#endif /* WITH_LIMITS */
#ifdef WITH_THRESHOLDS
			if (q_threshold != NULL) {
				cmd_answer_status.active = q_threshold->is_active;
				cmd_answer_status.value_type = q_threshold->cnt_type;
				cmd_answer_status.value1 = q_threshold->thr;
				if (q_threshold->cnt_neg == UINT64_C(0)) {
					cmd_answer_status.value2 = q_threshold->cnt;
					cmd_answer_status.value2_sign = 0;
				} else {
					cmd_answer_status.value2 = q_threshold->cnt_neg;
					cmd_answer_status.value2_sign = 1;
				}
			} else
#endif
			{
				cmd_answer_status.active = q_rule->is_active;
				cmd_answer_status.nstatlimits = 0;
				cmd_answer_status.nstatsublimits = 0;
				cmd_answer_status.nstatthresholds = 0;
#ifdef WITH_LIMITS
				STAILQ_FOREACH(limit, &q_rule->limits, link) {
					cmd_answer_status.nstatlimits++;
# ifdef WITH_SUBLIMITS
					STAILQ_FOREACH(sublimit, &limit->sublimits, link)
						cmd_answer_status.nstatsublimits++;
# endif
				}
#endif /* WITH_LIMITS */
#ifdef WITH_THRESHOLDS
				STAILQ_FOREACH(threshold, &q_rule->thresholds, link)
					cmd_answer_status.nstatthresholds++;
#endif
			}
		}
		cmd_answer_aux = &cmd_answer_status;
		cmd_answer_aux_size = sizeof cmd_answer_status;
	}
	return 1;
}

/*
 * "memory"
 */
static int
ctl_func_memory(void)
{
	if (wait_answer) {
		if (memfunc_get_stat(&cmd_answer_aux, &cmd_answer_aux_size) < 0) {
			logmsgx(IPA_LOG_ERR, "ctl_func_memory: memfunc_get_stat failed");
			cmd_answer.result = CTL_ANSWER_SYSTEM_ERROR;
			return 1;
		}
		cmd_answer_aux_allocated = 1;
	}
	return 1;
}

/*
 * "dump"
 */
static int
ctl_func_dump(void)
{
	need_check_flag = 1;
	set_rules_for_check();
	main_check_sec = 0;
	dump_flag = 1;
	logmsgx(IPA_LOG_INFO, "saving current statistics to database...");
	return 0;
}

/*
 * "freeze"
 */
static int
ctl_func_freeze(void)
{
	freeze_flag = 1;
	return 1;
}

#ifdef WITH_LIMITS
static int
do_ctl_func_expire(struct rule *rule, struct limit *limit, int need_answer)
{
	struct ipa_limit_state newstate;

	if (IS_NOTREACHED(limit)) {
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_expire: cannot expire limit, it is not reached",
		    rule->rule_name, limit->limit_name);
		if (need_answer)
			cmd_answer.result = CTL_ANSWER_CANNOT_EXPIRE;
		return 1;
	}

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

	if (make_limit_active(rule, limit, 1) < 0) {
		logmsgx(IPA_LOG_ERR, "do_ctl_func_expire: make_limit_active failed");
		return -1;
	}

	if (debug_ctl || rule->debug_limit || rule->debug_limit_init)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_expire: expiring limit",
		    rule->rule_name, limit->limit_name);

	newstate.lim = limit->lim;
	newstate.cnt = limit->cnt;
	newstate.event_date_set = limit->event_date_set;
	memcpy(newstate.event_date, limit->event_date, sizeof newstate.event_date);

	newstate.event_date_set |= IPA_LIMIT_EVENT_EXPIRE_SET;
	newstate.event_date[IPA_LIMIT_EVENT_EXPIRE] = curdate;

	if (db_set_limit_state(rule, limit, &newstate, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_expire: db_set_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}

	if (expire_limit(rule, limit) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_expire: expire_limit failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}

	if (make_limit_active(rule, limit, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "do_ctl_func_expire: make_limit_active failed");
		return -1;
	}

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

	return 1;
}

/*
 * "-l ... expire"
 */
static int
ctl_func_expire(void)
{
	return do_ctl_func_expire(q_rule, q_limit, wait_answer);
}
#else
# define ctl_func_expire NULL
#endif /* WITH_LIMITS */

#ifdef WITH_LIMITS
static int
do_ctl_func_restart(struct rule *rule, struct limit *limit, int need_answer)
{
	struct ipa_limit_state newstate;

	if (IS_REACHED(limit)) {
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_restart: cannot restart limit, it is reached",
		    rule->rule_name, limit->limit_name);
		if (need_answer)
			cmd_answer.result = CTL_ANSWER_CANNOT_RESTART;
		return 1;
	}

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

	if (make_limit_active(rule, limit, 1) < 0) {
		logmsgx(IPA_LOG_ERR, "do_ctl_func_restart: make_limit_active failed");
		return -1;
	}

	if (debug_ctl || rule->debug_limit || rule->debug_limit_init)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_restart: restarting limit",
		    rule->rule_name, limit->limit_name);

	newstate.lim = limit->lim;
	newstate.cnt = limit->cnt;
	newstate.event_date_set = limit->event_date_set;
	memcpy(newstate.event_date, limit->event_date, sizeof newstate.event_date);

	newstate.event_date_set |= IPA_LIMIT_EVENT_RESTART_SET;
	newstate.event_date[IPA_LIMIT_EVENT_RESTART] = curdate;

	if (db_set_limit_state(rule, limit, &newstate, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_restart: db_set_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}

	if (restart_limit(rule, limit) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_restart: restart_limit failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}

	if (make_limit_active(rule, limit, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "do_ctl_func_restart: make_limit_active failed");
		return -1;
	}

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

	return 1;
}

/*
 * "-l ... restart"
 */
static int
ctl_func_restart(void)
{
	return do_ctl_func_restart(q_rule, q_limit, wait_answer);
}
#else
# define ctl_func_restart NULL
#endif /* WITH_LIMITS */

#ifdef WITH_LIMITS
static int
do_ctl_func_set_limit(struct rule *rule, struct limit *limit, int need_answer,
    const struct ctl_cmd_query *query)
{
	int	debug_limit;
	u_int	flags;
	uint64_t value, lim;
	struct ipa_limit_state newstate;
#ifdef WITH_SUBLIMITS
	struct sublimit *sublimit;
#endif

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

	if (make_limit_active(rule, limit, 1) < 0) {
		logmsgx(IPA_LOG_ERR, "do_ctl_func_set_limit: make_limit_active failed");
		return -1;
	}

	flags = query->flags;
	value = query->value;

	debug_limit = debug_ctl || rule->debug_limit || rule->debug_limit_init;

	if (flags & CTL_CMD_FLAG_COUNTER) {
		if (flags & CTL_CMD_FLAG_INCREMENT) {
			if (add_chunk_to_limit(rule, limit, &value) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_set_limit: add_chunk_to_limit failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
		} else if (flags & CTL_CMD_FLAG_DECREMENT) {
			if (sub_chunk_from_limit(rule, limit, &value) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_set_limit: sub_chunk_from_limit failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
		} else {
			limit->cnt = value;
			limit->cnt_neg = UINT64_C(0);
		}
		if (debug_limit) {
			if (limit->cnt_neg == UINT64_C(0))
				logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_set_limit: counter was changed to %s",
				    rule->rule_name, limit->limit_name, cnt_to_buf(&limit->cnt, limit->cnt_type));
			else
				logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_set_limit: counter was changed to -%s",
				    rule->rule_name, limit->limit_name, cnt_to_buf(&limit->cnt_neg, limit->cnt_type));
		}
	} else {
		if (!limit->load_limit) {
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_set_limit: cannot change \"limit\" parameter, load_limit = no",
			    rule->rule_name, limit->limit_name);
			goto cannot_modify;
		}
		lim = limit->lim;
		if (flags & CTL_CMD_FLAG_INCREMENT) {
			if (lim <= UINT64_MAX - value)
				lim += value;
			else {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_set_limit: \"limit\" parameter overflowed",
				    rule->rule_name, limit->limit_name);
				goto cannot_modify;
			}
		} else if (flags & CTL_CMD_FLAG_DECREMENT) {
			if (lim > value)
				lim -= value;
			else {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_set_limit: cannot decrease \"limit\" parameter to zero",
				    rule->rule_name, limit->limit_name);
				goto cannot_modify;
			}
		} else {
			if (value == UINT64_C(0)) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_set_limit: cannot set \"limit\" parameter to zero",
				    rule->rule_name, limit->limit_name);
				goto cannot_modify;
			}
			lim = value;
		}
#ifdef WITH_SUBLIMITS
		STAILQ_FOREACH(sublimit, &limit->sublimits, link)
			if (sublimit->lim_per_cent == 0 &&
			    sublimit->lim > lim) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s, sublimit %s: do_ctl_func_set_limit: sublimit will be greater than parent limit %s",
				    rule->rule_name, limit->limit_name, sublimit->sublimit_name,
				    cnt_to_buf(&lim, limit->cnt_type));
				goto cannot_modify;
			}
#endif
		limit->lim = lim;
		if (debug_limit)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_set_limit: limit parameter was changed to %s",
			    rule->rule_name, limit->limit_name, cnt_to_buf(&limit->lim, limit->cnt_type));
	}

	newstate.lim = limit->lim;
	newstate.cnt = limit->cnt;
	newstate.event_date_set = limit->event_date_set;
	memcpy(newstate.event_date, limit->event_date, sizeof newstate.event_date);

	if (IS_REACHED(limit)) {
		/* Limit is reached. */
		if (limit->cnt < limit->lim) {
			/* But with new settings it became not reached. */
			newstate.event_date_set &=
			    ~(IPA_LIMIT_EVENT_REACH_SET | IPA_LIMIT_EVENT_REACH_EXEC_SET |
			      IPA_LIMIT_EVENT_EXPIRE_SET);
			limit->is_reached = 0;
			if (limit->restart.restart.upto != TEXP_UPTO_NOTSET) {
				limit->event_tm = limit->event_date[IPA_LIMIT_EVENT_START];
				if (set_wday(&limit->event_tm) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_set_limit: set_wday failed",
					    rule->rule_name, limit->limit_name);
					return -1;
				}
				/* Following lines were copied from new_limit_state(). */
				ipa_tm_texp(&limit->event_tm, &limit->restart.restart);
				newstate.event_date_set |= IPA_LIMIT_EVENT_RESTART_SET;
				newstate.event_date[IPA_LIMIT_EVENT_RESTART] = limit->event_tm;
				limit_set_event_sec(limit);
				if (rule->debug_limit || rule->debug_limit_init)
					logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit will be restarted at %s",
					    rule->rule_name, limit->limit_name, ipa_tm_to_buf(&limit->event_tm));
			} else
				limit->event_sec = SECONDS_IN_WEEK;
			if (debug_limit)
				logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_set_limit: limit becomes not reached",
				    rule->rule_name, limit->limit_name);
		}
	}

	if (db_set_limit_state(rule, limit, &newstate, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_set_limit: db_set_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}

	if (IS_NOTREACHED(limit)) {
		/* Limit is not reached. */
		if (limit->cnt >= limit->lim) {
			/* But with new settings it became reached. */
			if (reach_limit(rule, limit) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: do_ctl_func_set_limit: reach_limit failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
		}
	}

#ifdef WITH_SUBLIMITS
	STAILQ_FOREACH(sublimit, &limit->sublimits, link) {
		if (!(flags & CTL_CMD_FLAG_COUNTER))
			if (sublimit->lim_per_cent != 0)
				sublimit->lim = uint64_per_cent(&limit->lim, sublimit->lim_per_cent);
		if (limit->cnt >= sublimit->lim) {
			if (IS_NOTREACHED(sublimit))
				/* Sublimit is not reached, but with new settings became reached. */
				if (reach_sublimit(rule, limit, sublimit) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s, limit %s, sublimit %s: do_ctl_func_set_limit: reach_sublimit failed",
					    rule->rule_name, limit->limit_name, sublimit->sublimit_name);
					return -1;
				}
		} else {
			if (IS_REACHED(sublimit))
				/* Sublimit is reached, but with new setting it became not reached. */
				sublimit->is_reached = 0;
		}
	}
#endif

	if (need_answer) {
		cmd_answer_set.value1 = limit->lim;
		if (limit->cnt_neg == UINT64_C(0)) {
			cmd_answer_set.value2 = limit->cnt;
			cmd_answer_set.value2_sign = 0;
		} else {
			cmd_answer_set.value2 = limit->cnt_neg;
			cmd_answer_set.value2_sign = 1;
		}
		cmd_answer_aux = &cmd_answer_set;
		cmd_answer_aux_size = sizeof cmd_answer_set;
	}

	if (IS_ACTIVE(limit->worktime))
		/* Limit is really active, update rule->check_sec and main_check_sec. */
		if (rule->check_sec > limit->event_sec) {
			rule->check_sec = limit->event_sec;
			if (main_check_sec > rule->check_sec)
				main_check_sec = rule->check_sec;
		}

	goto done;

cannot_modify:
	logmsgx(IPA_LOG_INFO, "rule %s, limit %s: do_ctl_func_set_limit: limit was not modified",
	    rule->rule_name, limit->limit_name);
	if (need_answer)
		cmd_answer.result = CTL_ANSWER_CANNOT_MODIFY;

done:
	if (make_limit_active(rule, limit, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "do_ctl_func_set_limit: make_limit_active failed");
		return -1;
	}

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

	return 1;
}

/*
 * "-r ... -l ... set ..."
 */
static int
ctl_func_set_limit(void)
{
	return do_ctl_func_set_limit(q_rule, q_limit, wait_answer, &cmd_query);
}
#endif /* WITH_LIMITS */

#ifdef WITH_THRESHOLDS
static int
do_ctl_func_set_threshold(struct rule *rule, struct threshold *threshold,
    int need_answer, const struct ctl_cmd_query *query)
{
	int	debug_threshold;
	u_int	flags;
	uint64_t value;
	struct ipa_threshold_state newstate;

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

	if (make_threshold_active(rule, threshold, 1) < 0) {
		logmsgx(IPA_LOG_ERR, "do_ctl_func_set_threshold: make_threshold_active failed");
		return -1;
	}

	flags = query->flags;
	value = query->value;

	debug_threshold = debug_ctl || rule->debug_threshold || rule->debug_threshold_init;

	if (flags & CTL_CMD_FLAG_COUNTER) {
		if (flags & CTL_CMD_FLAG_INCREMENT) {
			if (add_chunk_to_threshold(rule, threshold, &value) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: add_chunk_to_threshold failed",
				    rule->rule_name, threshold->threshold_name);
				return -1;
			}
		} else if (flags & CTL_CMD_FLAG_DECREMENT) {
			if (sub_chunk_from_threshold(rule, threshold, &value) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: sub_chunk_from_threshold failed",
				    rule->rule_name, threshold->threshold_name);
				return -1;
			}
		} else {
			if (threshold->cnt > UINT64_C(0) ||
			    threshold->cnt_neg == UINT64_C(0)) {
				if (threshold->cnt > value) {
					value = threshold->cnt - value;
					if (sub_chunk_from_threshold(rule, threshold, &value) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: sub_chunk_from_threshold failed",
						    rule->rule_name, threshold->threshold_name);
						return -1;
					}
				} else {
					value -= threshold->cnt;
					if (add_chunk_to_threshold(rule, threshold, &value) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: add_chunk_to_threshold failed",
						    rule->rule_name, threshold->threshold_name);
						return -1;
					}
				}
			} else {
				uint64_t value2;

				value2 = threshold->cnt_neg;
				if (add_chunk_to_threshold(rule, threshold, &value2) < 0 ||
				    add_chunk_to_threshold(rule, threshold, &value) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: add_chunk_to_threshold failed",
					    rule->rule_name, threshold->threshold_name);
					return -1;
				}
			}
		}
		if (debug_threshold) {
			if (threshold->cnt_neg == UINT64_C(0))
				logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: do_ctl_func_set_threshold: counter was changed to %s",
				    rule->rule_name, threshold->threshold_name, cnt_to_buf(&threshold->cnt, threshold->cnt_type));
			else
				logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: do_ctl_func_set_threshold: counter was changed to -%s",
				    rule->rule_name, threshold->threshold_name, cnt_to_buf(&threshold->cnt_neg, threshold->cnt_type));
		}
	} else {
		if (!threshold->load_threshold) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: cannot change \"threshold\" parameter, load_threshold = no",
			    rule->rule_name, threshold->threshold_name);
			goto cannot_modify;
		}
		if (flags & CTL_CMD_FLAG_INCREMENT) {
			if (threshold->thr <= UINT64_MAX - value)
				threshold->thr += value;
			else {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: \"threshold\" parameter overflowed",
				    rule->rule_name, threshold->threshold_name);
				goto cannot_modify;
			}
		} else if (flags & CTL_CMD_FLAG_DECREMENT) {
			if (threshold->thr > value)
				threshold->thr -= value;
			else {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: cannot decrease \"threshold\" parameter to zero",
				    rule->rule_name, threshold->threshold_name);
				goto cannot_modify;
			}
		} else {
			if (value == UINT64_C(0)) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: cannot set \"threshold\" parameter to zero",
				    rule->rule_name, threshold->threshold_name);
				goto cannot_modify;
			}
			threshold->thr = value;
		}
		if (debug_threshold)
			logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: do_ctl_func_set_threshold: threshold parameter was changed to %s",
			    rule->rule_name, threshold->threshold_name, cnt_to_buf(&threshold->thr, threshold->cnt_type));
	}

	set_thr_min_max(threshold);

	newstate.thr = threshold->thr;
	newstate.cnt = threshold->cnt;
	newstate.tm_from = threshold->tm_from;
	newstate.tm_updated = curdate;

	if (db_set_threshold_state(rule, threshold, &newstate) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: do_ctl_func_set_threshold: db_set_threshold_state failed",
		    rule->rule_name, threshold->threshold_name);
		return -1;
	}

	if (need_answer) {
		cmd_answer_set.value1 = threshold->thr;
		if (threshold->cnt_neg == UINT64_C(0)) {
			cmd_answer_set.value2 = threshold->cnt;
			cmd_answer_set.value2_sign = 0;
		} else {
			cmd_answer_set.value2 = threshold->cnt_neg;
			cmd_answer_set.value2_sign = 1;
		}
		cmd_answer_aux = &cmd_answer_set;
		cmd_answer_aux_size = sizeof cmd_answer_set;
	}

	goto done;

cannot_modify:
	logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: do_ctl_func_set_threshold: threshold parameter was not modified",
		    rule->rule_name, threshold->threshold_name);
	if (need_answer)
		cmd_answer.result = CTL_ANSWER_CANNOT_MODIFY;

done:
	if (make_threshold_active(rule, threshold, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "do_ctl_func_set_threshold: make_threshold_active failed");
		return -1;
	}

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

	return 1;
}

/*
 * "-r ... -t ... set ..."
 */
static int
ctl_func_set_threshold(void)
{
	return do_ctl_func_set_threshold(q_rule, q_threshold, wait_answer, &cmd_query);
}
#endif /* WITH_THRESHOLDS */

static int
do_ctl_func_set_rule(struct rule *rule, const struct ctl_cmd_query *query)
{
	u_int	flags;
	uint64_t value;

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

	flags = query->flags;
	value = query->value;

	/*
	 * If a rule is really inactive, then a new record should be
	 * appended, as it is defined in ipa_mod(3).
	 */
	if (IS_INACTIVE(rule->worktime)) {
		/* Append new record for the rule. */
		if (db_append_rule(rule, &uint64_zero, 1) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: do_ctl_func_set_rule: db_append_rule failed",
			    rule->rule_name);
			return -1;
		}
	}

	if (flags & CTL_CMD_FLAG_INCREMENT) {
		if (add_chunk_to_rule(rule, &value) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: do_ctl_func_set_rule: add_chunk_to_rule failed",
			    rule->rule_name);
			return -1;
		}
	} else { /* CTL_CMD_FLAG_DECREMENT */
		if (sub_chunk_from_rule(rule, &value) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: do_ctl_func_set_rule: sub_chunk_to_rule failed",
			    rule->rule_name);
			return -1;
		}
	}

	/*
	 * If a rule is really inactive, then update its
	 * statistics right now.
	 */
	if (IS_INACTIVE(rule->worktime)) {
		if (db_update_rule(rule, &rule->cnt) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: do_ctl_func_set_rule: db_update_rule failed",
			    rule->rule_name);
			return -1;
		}
	} else {
#ifdef WITH_LIMITS
		if (!STAILQ_EMPTY(&rule->limits)) {
			/* Rule has limits and it is really active. */
			main_check_sec = rule->check_sec = 0;
		}
#endif
	}

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

	return 1;
}

/*
 * "-r ... set ..."
 */
static int
ctl_func_set_rule(void)
{
	return do_ctl_func_set_rule(q_rule, &cmd_query);
}

/*
 * "set ..."
 */
static int
ctl_func_set(void)
{
#ifdef WITH_LIMITS
	if (q_limit != NULL)
		return ctl_func_set_limit();
	else
#endif

#ifdef WITH_THRESHOLDS
	if (q_threshold != NULL)
		return ctl_func_set_threshold();
	else
#endif
		return ctl_func_set_rule();
}

/*
 * Table of functions, which implement execution of control
 * messages.  Return codes are following:
 *  0 -- everything is Ok, send answer from ipa_main();
 *  1 -- everything is Ok, send answer immediately;
 * -1 -- some error occurred, send answer with SYSTEM_ERROR immediately.
 */
static int (* const ctl_func_tbl[])(void) = {
	ctl_func_status,	/* 0 | CTL_CMD_STATUS	*/
	ctl_func_memory,	/* 1 | CTL_CMD_MEMORY	*/
	ctl_func_dump,		/* 2 | CTL_CMD_DUMP	*/
	ctl_func_freeze,	/* 3 | CTL_CMD_FREEZE	*/
	ctl_func_restart,	/* 4 | CTL_CMD_RESTART	*/
	ctl_func_expire,	/* 5 | CTL_CMD_EXPIRE	*/
	ctl_func_set		/* 6 | CTL_CMD_SET	*/
};

/*
 * Check if the given string with maximum allowed length len
 * has '\0' byte.
 */
static int
validate_string(const char *str, u_int len)
{
	u_int	i;
	const char *ptr;

	for (i = 0, ptr = str; i < len; ++ptr, ++i)
		if (*ptr == '\0')
			return 0;
	return -1;
}

#ifdef CTL_CHECK_CREDS
/*
 * Check credentials in received message.
 * Return:
 *   0 -- deny access;
 *   1 -- allow access;
 *  -1 -- some error occurred.
 */
# ifdef __FreeBSD__

# ifdef WITH_PTHREAD
/*
 * Since sysconf() with _SC_GETPW_R_SIZE_MAX and _SC_GETGR_R_SIZE_MAX
 * do not work on FreeBSD, need to define these macro variables.
 */

#ifndef GETPWNAM_R_BUFSIZE
# define GETPWNAM_R_BUFSIZE 1024
#endif

#ifndef GETGRNAM_R_BUFSIZE
# define GETGRNAM_R_BUFSIZE 1024
#endif

# endif /* WITH_PTHREAD */

static int
ctl_check_creds(const struct ctl_acl_class *acl_class)
{
	int	i;
	const struct cmsghdr *cmptr;
	const struct cmsgcred *cmcredptr;
	const struct ctl_acl_elem *elem;
	const struct group *grp_res;
	const struct passwd *pwd_res;
#ifdef WITH_PTHREAD
	char	pwd_buf[GETPWNAM_R_BUFSIZE];
	char	grp_buf[GETGRNAM_R_BUFSIZE];
	struct group grp;
	struct passwd pwd;
#endif

	if (acl_class == NULL) {
		/* Deny by default. */
		goto deny;
	}

	if (msg_query.msg_flags & MSG_CTRUNC) {
		logmsgx(IPA_LOG_WARNING, "ctl_check_creds: control data was truncated");
		return -1;
	}

	if (msg_query.msg_controllen <= sizeof(struct cmsghdr)) {
		logmsgx(IPA_LOG_WARNING, "ctl_check_creds: sender's credentials are not returned: msg_controllen %lu <= %lu",
		    (u_long)msg_query.msg_controllen, (u_long)sizeof(struct cmsghdr));
		return -1;
	}

	cmptr = CMSG_FIRSTHDR(&msg_query);

	if (cmptr->cmsg_len != CMSG_LEN(sizeof(struct cmsgcred))) {
		logmsgx(IPA_LOG_WARNING, "ctl_check_creds: control len = %lu, should be = %lu",
		    (u_long)cmptr->cmsg_len, (u_long)CMSG_LEN(sizeof(struct cmsgcred)));
		return -1;
	}

	if (cmptr->cmsg_level != SOL_SOCKET) {
		logmsgx(IPA_LOG_WARNING, "ctl_check_creds: control level %d != SOL_SOCKET",
		    cmptr->cmsg_level);
		return -1;
	}

	if (cmptr->cmsg_type != SCM_CREDS) {
		logmsgx(IPA_LOG_WARNING, "ctl_check_creds: control type %d != SCM_CREDS",
		    cmptr->cmsg_type);
		return -1;
	}

	cmcredptr = (const struct cmsgcred *)CMSG_DATA(cmptr);

	STAILQ_FOREACH(elem, &acl_class->list, link)
		if (elem->user != NULL) {
			/* Check real UID. */
#ifdef WITH_PTHREAD
			if ( (errno = getpwnam_r(elem->user, &pwd, pwd_buf, sizeof pwd_buf, &pwd_res)) == 0) {
				if (pwd_res != NULL) {
					if (pwd.pw_uid == cmcredptr->cmcred_uid)
						goto done;
				} else {
					logmsgx(IPA_LOG_WARNING, "ctl_check_creds: cannot find user %s in users database",
					    elem->user);
					goto deny;
				}
			} else {
				logmsg(IPA_LOG_ERR, "ctl_check_creds: getpwnam_r(%s)",
				    elem->user);
				return -1;
			}
#else
			errno = 0;
			if ( (pwd_res = getpwnam(elem->user)) != NULL) {
				if (pwd_res->pw_uid == cmcredptr->cmcred_uid)
					goto done;
			} else {
				if (errno != 0) {
					logmsg(IPA_LOG_ERR, "ctl_check_creds: getpwnam(%s)",
					    elem->user);
					return -1;
				} else {
					logmsgx(IPA_LOG_WARNING, "ctl_check_creds: cannot find user %s in users database",
					    elem->user);
					goto deny;
				}
			}
#endif /* WITH_PTHREAD */
		} else {
			/* Check real GID. */
#ifdef WITH_PTHREAD
			if ( (errno = getgrnam_r(elem->group, &grp, grp_buf, sizeof grp_buf, &grp_res)) == 0) {
				if (grp_res != NULL) {
					/* Is there standard that cmcred_groups should contain cmcred_gid? */
					if (cmcredptr->cmcred_gid == grp.gr_gid)
						goto done;
					/* Start with first group, since zero group is EGID. */
					for (i = 1; i < cmcredptr->cmcred_ngroups; ++i)
						if (cmcredptr->cmcred_groups[i] == grp.gr_gid)
							goto done;
				} else {
					logmsgx(IPA_LOG_WARNING, "ctl_check_creds: cannot find group %s in groups database",
					    elem->group);
					goto deny;
				}
			} else {
				logmsg(IPA_LOG_ERR, "ctl_check_creds: getgrnam(%s)",
				    elem->group);
				return -1;
			}
#else
			errno = 0;
			if ( (grp_res = getgrnam(elem->group)) != NULL) {
				/* Is there standard that cmcred_groups should contain cmcred_gid? */
				if (cmcredptr->cmcred_gid == grp_res->gr_gid)
					goto done;
				/* Start with first group, since zero group is EGID. */
				for (i = 1; i < cmcredptr->cmcred_ngroups; ++i)
					if (cmcredptr->cmcred_groups[i] == grp_res->gr_gid)
						goto done;
			} else {
				if (errno != 0) {
					logmsg(IPA_LOG_ERR, "ctl_check_creds: getgrnam(%s)",
					    elem->group);
					return -1;
				} else {
					logmsgx(IPA_LOG_WARNING, "ctl_check_creds: cannot find group %s in groups database",
					    elem->group);
					goto deny;
				}
			}
#endif /* WITH_PTHREAD */
		}

done:
	if (elem != NULL) {
		if (debug_ctl) {
			const char *what;

			what = elem->allowed ? "accept" : "deny";
			if (elem->user != NULL) {
				if (q_rule != NULL)
					logmsgx(IPA_LOG_INFO, "ctl_check_creds: %s command \"%s\" for user %s for rule %s",
					    what, ctl_cmd_msg[cmd_query.cmd], elem->user, q_rule->rule_name);
				else
					logmsgx(IPA_LOG_INFO, "ctl_check_creds: %s command \"%s\" for user %s",
					    what, ctl_cmd_msg[cmd_query.cmd], elem->user);
			} else {
				if (q_rule != NULL)
					logmsgx(IPA_LOG_INFO, "ctl_check_creds: %s command \"%s\" for group %s for rule %s",
					    what, ctl_cmd_msg[cmd_query.cmd], elem->group, q_rule->rule_name);
				else
					logmsgx(IPA_LOG_INFO, "ctl_check_creds: %s command \"%s\" for group %s",
					    what, ctl_cmd_msg[cmd_query.cmd], elem->group);
			}
		}
		return elem->allowed;
	}

deny:
	if (debug_ctl) {
		if (q_rule != NULL)
			logmsgx(IPA_LOG_INFO, "ctl_check_creds: deny command \"%s\" for rule %s",
			    ctl_cmd_msg[cmd_query.cmd], q_rule->rule_name);
		else
			logmsgx(IPA_LOG_INFO, "ctl_check_creds: deny command \"%s\"",
			    ctl_cmd_msg[cmd_query.cmd]);
	}

	return 0;
}
# endif /* __FreeBSD__ */
#endif /* CTL_CHECK_CREDS */

/*
 * Send a message to a client.
 */
static int
ctl_send_msg(void)
{
	size_t	msg_size;
	ssize_t	nsent;

	if (debug_ctl)
		logmsgx(IPA_LOG_INFO, "ctl_send_msg: sending message with answer");

	/* Catch timeout from writev. */
	if (sigsetjmp(env_alrm, 1) != 0) {
		logmsgx(IPA_LOG_WARNING, "ctl_send_msg: cannot send answer during %u seconds",
		    ctl_timeout);
		return 0;
	}

	/* Set alarm. */
	(void)alarm(ctl_timeout);

	nsent = writev(ctl_connfd, iov_answer, iovcnt_answer);

	/* Release alarm. */
	(void)alarm(0);

	msg_size = cmd_answer_aux_size + sizeof cmd_answer;
	if (nsent != msg_size) {
		if (nsent < 0) {
			logmsg(IPA_LOG_ERR, "ctl_send_msg: writev");
			switch (errno) {
			case EBADF:
			case EINVAL:
				return -1;
			}
		} else
			logmsgx(IPA_LOG_WARNING, "ctl_send_msg: writev: short send (%ld of %lu bytes)",
			    (long)nsent, (u_long)msg_size);
	}
	return 0;
}

/*
 * Send answer to connected socket and close it.
 */
int
ctl_send_answer(void)
{
	if (wait_answer) {
		if (cmd_answer_aux != NULL) {
			cmd_answer.size = cmd_answer_aux_size;
			iovcnt_answer = 2;
			iov_answer[1].iov_base = (char *)cmd_answer_aux;
			iov_answer[1].iov_len = cmd_answer_aux_size;
		} else {
			cmd_answer.size = 0;
			iovcnt_answer = 1;
		}
		if (ctl_send_msg() < 0) {
			logmsgx(IPA_LOG_ERR, "ctl_send_answer: ctl_send_msg failed");
			goto failed;
		}
		if (close(ctl_connfd) < 0) {
			logmsg(IPA_LOG_ERR, "ctl_send_answer: close");
			if (errno == EBADF)
				goto failed;
		}
		if (cmd_answer_aux_allocated)
			mem_free(cmd_answer_aux, m_ctl);
		wait_answer = 0;
	}
	return 0;

failed:
	if (cmd_answer_aux_allocated)
		mem_free(cmd_answer_aux, m_ctl);
	return -1;
}

/*
 * Make descriptor blockable.
 */
static int
set_block(int fd)
{
	int val;

	if ( (val = fcntl(fd, F_GETFL, 0)) < 0) {
		logmsg(IPA_LOG_ERR, "set_block: fcntl(%d, F_GETFL)", fd);
		return -1;
	}
	if (fcntl(fd, F_SETFL, val & ~O_NONBLOCK) < 0) {
		logmsg(IPA_LOG_ERR, "set_block: fcntl(%d, F_SETFL)", fd);
		return -1;
	}
	return 0;
}

/*
 * Receive a message from a client.
 * Return codes are following:
 *  -1 -- error occurred;
 *   0 -- nothing was received (timeout, short message);
 *   1 -- message was received.
 */
static int
ctl_recv_msg(void)
{
	ssize_t nread;

	/*
	 * Accept connection, since ctl_listenfd is in a non-blocking
	 * state, then accept() will not block, but it can return
	 * EWOULDBLOCK, if client closed connection before accept()
	 * invocation.
	 */
	if ( (ctl_connfd = accept(ctl_listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL)) < 0) {
		switch (errno) {
		case EWOULDBLOCK:
#ifdef ECONNABORTED
		case ECONNABORTED:
#endif
#ifdef EPROTO
		case EPROTO:
#endif
			/* Ignore this connection, it was interrupted by a client. */
			logmsg(IPA_LOG_WARNING, "ctl_recv_msg: accept");
			return 0;
		default:
			logmsg(IPA_LOG_ERR, "ctl_recv_msg: accept");
			return -1;
		}
	}

	if (set_block(ctl_connfd) < 0) {
		logmsgx(IPA_LOG_ERR, "ctl_recv_msg: cannot make connected socket blockable");
		return -1;
	}

	if (debug_ctl)
		logmsgx(IPA_LOG_INFO, "ctl_recv_msg: receiving control message");

	/* Catch timeout for recvmsg. */
	if (sigsetjmp(env_alrm, 1) != 0) {
		logmsgx(IPA_LOG_WARNING, "ctl_recv_msg: cannot receive command during %u seconds",
		    ctl_timeout);
		goto failed;
	}

	/* Set alarm. */
	(void)alarm(ctl_timeout);

	nread = recvmsg(ctl_connfd, &msg_query, RECVMSG_FLAGS);

	/* Release alarm. */
	(void)alarm(0);

	if (nread < 0) {
		logmsg(IPA_LOG_ERR, "ctl_recv_msg: recvmsg");
		switch (errno) {
		case EBADF:
		case EINVAL:
		case EMSGSIZE:
		case ENOTSOCK:
			return -1;
		}
		goto failed;
	}

	if (nread != sizeof cmd_query) {
		logmsgx(IPA_LOG_WARNING, "ctl_recv_msg: recvmsg: short read (%ld of %lu bytes)",
		    (long)nread, (u_long)sizeof(cmd_query));
		goto failed;
	}

	return 1;

failed:
	if (close(ctl_connfd) < 0) {
		logmsg(IPA_LOG_ERR, "ctl_recv_msg: close");
		if (errno == EBADF)
			return -1;
	}
	return 0;
}

/*
 * Receive command query from a client and process it if possible.
 */
int
ctl_recv_query(void)
{
	int	ret;
	u_int	cmd, flags;
#ifdef CTL_CHECK_CREDS
	const struct ctl_acl_class *acl_class;
#endif

	if ( (ret = ctl_recv_msg()) != 1)
		return ret;

	if (cmd_query.ver != CTL_QUERY_VERSION) {
		logmsgx(IPA_LOG_ERR, "ctl_recv_query: incorrect format version of query %u, my version %u",
		    cmd_query.ver, CTL_QUERY_VERSION);
		if (close(ctl_connfd) < 0) {
			logmsg(IPA_LOG_ERR, "ctl_recv_query: close");
			if (errno == EBADF)
				return -1;
		}
		return 0;
	}

	cmd = cmd_query.cmd;
	flags = cmd_query.flags;

	wait_answer = flags & CTL_CMD_FLAG_WAIT;

	cmd_answer.result = CTL_ANSWER_DONE;
	cmd_answer_aux = NULL;
	cmd_answer_aux_size = 0;
	cmd_answer_aux_allocated = 0;

	if (cmd > CTL_CMD_MAX) {
		logmsgx(IPA_LOG_WARNING, "ctl_recv_query: received unknown control command number %u",
		    cmd);
		cmd_answer.result = CTL_ANSWER_UNKNOWN_COMMAND;
		goto send_answer;
	}

	if (debug_ctl)
		logmsgx(IPA_LOG_INFO, "ctl_recv_query: received \"%s\" control command",
		    ctl_cmd_msg[cmd]);

	if (validate_string(cmd_query.name1, sizeof cmd_query.name1) < 0 ||
	    validate_string(cmd_query.name2, sizeof cmd_query.name2) < 0) {
		logmsgx(IPA_LOG_WARNING, "ctl_recv_query: received \"%s\" control command with illegal name1 or name2",
		    ctl_cmd_msg[cmd]);
		cmd_answer.result = CTL_ANSWER_INVALID_NAME;
		goto send_answer;
	}

	if (cmd_query.name1[0] != '\0') {
		if ( (q_rule = rule_by_name(cmd_query.name1)) == NULL) {
			cmd_answer.result = CTL_ANSWER_UNKNOWN_RULE;
			goto send_answer;
		}
	} else
		q_rule = NULL;

	switch (cmd) {
	case CTL_CMD_STATUS:
		if ((flags & (CTL_CMD_FLAG_LIMIT|CTL_CMD_FLAG_THRESHOLD)) != 0)
			if (q_rule == NULL) {
				cmd_answer.result = CTL_ANSWER_UNKNOWN_RULE;
				goto send_answer;
			}
#ifdef CTL_CHECK_CREDS
		if (q_rule != NULL)
			acl_class = q_rule->ctl_rule_acl;
		else
			acl_class = ctl_stat_acl;
#endif
		break;
#ifdef CTL_CHECK_CREDS
	case CTL_CMD_MEMORY:
		acl_class = ctl_stat_acl;
		break;
	case CTL_CMD_DUMP:
		acl_class = ctl_dump_acl;
		break;
	case CTL_CMD_FREEZE:
		acl_class = ctl_freeze_acl;
		break;
#endif
	case CTL_CMD_RESTART:
	case CTL_CMD_EXPIRE:
	case CTL_CMD_SET:
		if (q_rule == NULL) {
			cmd_answer.result = CTL_ANSWER_UNKNOWN_RULE;
			goto send_answer;
		}
#ifdef CTL_CHECK_CREDS
		acl_class = q_rule->ctl_rule_acl;
#endif
		break;
	}

#ifdef CTL_CHECK_CREDS
	switch (ctl_check_creds(acl_class)) {
	case 0:
		cmd_answer.result = CTL_ANSWER_DENIED;
		goto send_answer;
	case -1:
		goto system_error;
	}
#endif

	if (((cmd == CTL_CMD_RESTART || cmd == CTL_CMD_EXPIRE) && (flags & CTL_CMD_FLAG_LIMIT) == 0) ||
	    (cmd == CTL_CMD_SET && (flags & (CTL_CMD_FLAG_LIMIT|CTL_CMD_FLAG_THRESHOLD|CTL_CMD_FLAG_INCREMENT|CTL_CMD_FLAG_DECREMENT)) == 0)) {
		logmsgx(IPA_LOG_WARNING, "ctl_recv_query: received \"%s\" control command with illegal combinations of flags = 0x%08x",
		    ctl_cmd_msg[cmd], flags);
		cmd_answer.result = CTL_ANSWER_UNKNOWN_COMMAND;
		goto send_answer;
	}

	if (flags & CTL_CMD_FLAG_LIMIT) {
#ifdef WITH_LIMITS
		if ( (q_limit = limit_by_name(q_rule, cmd_query.name2)) == NULL) {
			cmd_answer.result = CTL_ANSWER_UNKNOWN_LIMIT;
			goto send_answer;
		}
#else
		cmd_answer.result = CTL_ANSWER_UNKNOWN_LIMIT;
		goto send_answer;
#endif
	} else {
#ifdef WITH_LIMITS
		q_limit = NULL;
#endif
	}

	if (flags & CTL_CMD_FLAG_THRESHOLD) {
#ifdef WITH_THRESHOLDS
		if ( (q_threshold = threshold_by_name(q_rule, cmd_query.name2)) == NULL) {
			cmd_answer.result = CTL_ANSWER_UNKNOWN_THRESHOLD;
			goto send_answer;
		}
#else
		cmd_answer.result = CTL_ANSWER_UNKNOWN_THRESHOLD;
		goto send_answer;
#endif
	} else {
#ifdef WITH_THRESHOLDS
		q_threshold = NULL;
#endif
	}

	switch (ctl_func_tbl[cmd]()) {
	case 0:
		return 0;
	case -1:
		logmsgx(IPA_LOG_ERR, "ctl_recv_query: execution of control command failed");
		goto system_error;
	}

send_answer:
	if (ctl_send_answer() < 0) {
		logmsgx(IPA_LOG_ERR, "ctl_recv_query: ctl_send_answer failed");
		return -1;
	}
	return 0;

system_error:
	if (cmd_answer_aux_allocated)
		mem_free(cmd_answer_aux, m_ctl);
	cmd_answer.result = CTL_ANSWER_SYSTEM_ERROR;
	if (ctl_send_answer() < 0)
		logmsgx(IPA_LOG_ERR, "ctl_recv_query: ctl_send_answer failed");
	return -1;
}


syntax highlighted by Code2HTML, v. 0.9.1