/*-
 * 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: ipactl.c,v 1.3.2.5 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <ctype.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <regex.h>

#include <sys/un.h>

#include "pathnames.h"

#include "ipa_mod.h"

#include "ipactl.h"

#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

#define IPACTL_NAME "ipactl"

#ifndef IPACTL_ANSWER_MAX
# define IPACTL_ANSWER_MAX	(100 * 1024)
#endif

/*
 * All strto_uintxx() functions get strings with positive decimal
 * integers, so it is not necessary to check '-' in the first character
 * of a string inside strto_uintxx() functions (remember, that strtoul()
 * and strtoull() work with negative values).
 */

static const char *envprogname;

static int	wait_answer = 1;	/* 0, if -n. */
static u_int	ctl_timeout = 0;	/* -w <timeout> */
static const char *ctl_socket_path = IPA_CTL_SOCKET; /* -s <socket> */

static const char *rule_name = NULL;	/* -r <rule> */

#ifdef WITH_LIMITS
# define OPTSTRING_LIMITS "l:"
static const char *limit_name = NULL;	/* -l <limit> */
#else
# define OPTSTRING_LIMITS ""
#endif

#ifdef WITH_THRESHOLDS
# define OPTSTRING_THRESHOLDS "t:"
static const char *threshold_name = NULL; /* -t <threshold> */
#else
# define OPTSTRING_THRESHOLDS ""
#endif

#define OPTSTRING ":hnvr:s:w:" OPTSTRING_LIMITS OPTSTRING_THRESHOLDS

static int	sockfd;			/* Socket descriptor. */

static struct ctl_cmd_query cmd_query;
static struct ctl_cmd_answer cmd_answer;
static struct ctl_cmd_answer_status cmd_answer_status;

#ifdef WITH_ANY_LIMITS
static struct ctl_cmd_answer_set cmd_answer_set;
#endif

static void	*cmd_answer_aux = NULL;	/* Auxiliary answer. */
static int	cmd_answer_aux_exp = 0;	/* Non-zero if auxiliary answer is expected. */
static size_t	cmd_answer_aux_size;	/* Auxiliary answer size. */

#define CTL_REQ_RULE	0x01
#ifdef WITH_LIMITS
# define CTL_REQ_LIMIT	0x02
#endif

struct ctl_cmd {
	const char	*name;		/* Name of option.		*/
	u_int		nargs;		/* Number of arguments.		*/
	int		req;		/* ORed CTL_REQ_xxx or -1.	*/
	u_int		cmd;		/* Command code.		*/
	int		(*parse)(int argc, char *argv[]); /* Parser.	*/
	int		(*check)(void);	/* Check returned result.	*/
};

static sigjmp_buf env_alrm;		/* Used if -w option is used. */

#define PAT_TIME		"^([[:digit:]]+[smh])+$"
#define PAT_BYTES		"^([[:digit:]]+[BKMGT])+$"

#define SECONDS_IN_MINUTE	(60)
#define SECONDS_IN_HOUR		(60 * SECONDS_IN_MINUTE)
#define SECONDS_IN_DAY		(24 * SECONDS_IN_HOUR)

#define KBYTE			(UINT64_C(1024))
#define MBYTE			(UINT64_C(1024) * KBYTE)
#define GBYTE			(UINT64_C(1024) * MBYTE)
#define TBYTE			(UINT64_C(1024) * GBYTE)

/* For reporting errors from regcomp(3) function. */
#define RE_ERRBUF_SIZ 100
static int	re_errcode;
static char	re_errbuf[RE_ERRBUF_SIZ];

static void	logmsg(const char *, ...) ATTR_FORMAT(printf, 1, 2);
static void	logmsgx(const char *, ...) ATTR_FORMAT(printf, 1, 2);
static void	vlogmsg(const char *, va_list) ATTR_FORMAT(printf, 1, 0);
static void	vlogmsgx(const char *, va_list) ATTR_FORMAT(printf, 1, 0);
static void	exit_errx(const char *, ...) ATTR_NORETURN ATTR_FORMAT(printf, 1, 2);

static void
vlogmsg(const char *format, va_list ap)
{
	int errno_save = errno;

	fflush(stdout);
	fprintf(stderr, "%s: ", envprogname);
	vfprintf(stderr, format, ap);
	if (errno_save != 0)
		fprintf(stderr, ": %s", strerror(errno_save));
	fprintf(stderr, "\n");
}

static void
vlogmsgx(const char *format, va_list ap)
{
	fflush(stdout);
	fprintf(stderr, "%s: ", envprogname);
	vfprintf(stderr, format, ap);
	fprintf(stderr, "\n");
}

/*
 * Output the program name, a message and an error message.
 */
static void
logmsg(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	vlogmsg(format, ap);
	va_end(ap);
}

/*
 * Output the program name and a message.
 */
static void
logmsgx(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	vlogmsgx(format, ap);
	va_end(ap);
}

/*
 * Output the program name, a message and exit.
 */
static void
exit_errx(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	vlogmsgx(format, ap);
	va_end(ap);
	exit(1);
}

/*
 * Convert string to u_int.
 * Assume that sizeof(u_long) >= sizeof(u_int).
 */
static int
strto_u_int(u_int *result, const char *nptr)
{
	char	*endptr;
	u_long	val_ul;

	if (isdigit(*nptr) == 0)
		goto failed;
	errno = 0;
	val_ul = strtoul(nptr, &endptr, 10);
	if (errno != 0) {
		logmsg("strtoul");
		return -1;
	}
	if (val_ul > UINT_MAX) {
		logmsgx("too big value for u_int");
		return -1;
	}
	if (nptr == endptr)
		goto failed;
	*result = (u_int)val_ul;
	return 0;

failed:
	logmsgx("wrong positive decimal number");
	return -1;
}

/*
 * Output version number (-v and -h switches).
 */
static void
show_version(void)
{
	printf(IPACTL_NAME ", version " PACKAGE_VERSION "\nSupports: rules"
#ifdef WITH_LIMITS
	", limits"
#endif
#ifdef WITH_THRESHOLDS
	", thresholds"
#endif
#ifdef CTL_CHECK_CREDS
	"; sending messages credentials is enabled"
#else
	"; sending messages credentials is disabled"
#endif
	".\n");
}

/*
 * Output the help message (-h switch).
 */
static void
usage(void)
{
	show_version();
	printf("\
Usage: %s [-hnv] [-s <socket>] [-w <time>]"
"\n              [-r <rule>"
#if defined(WITH_LIMITS) && defined(WITH_THRESHOLDS)
" [-l <limit>|-t <threshold>]"
#else
# ifdef WITH_LIMITS
" [-l <limit>]"
# endif
# ifdef WITH_THRESHOLDS
" [-t <threshold>]"
# endif
#endif /* WITH_LIMITS && WITH_THRESHOLDS */
"] <command> [<args>]\n"
" Control utility for ipa(8)\n\
 Options are:\n"
"  -r <rule>\t\tSpecify rule name\n"
#ifdef WITH_LIMITS
"  -l <limit>\t\tSpecify limit name\n"
#endif
#ifdef WITH_THRESHOLDS
"  -t <threshold>\tSpecify threshold name\n"
#endif
"  -s <socket>\t\tConnect to the given socket, by default connect\n\
\t\t\tto Unix domain socket "IPA_CTL_SOCKET"\n\
  -n\t\t\tDo not wait an answer from ipa\n\
  -h\t\t\tOutput this help message\n\
  -v\t\t\tShow version information and exit\n\
  -w <time>\t\tHow many seconds to wait for an answer from ipa,\n\
\t\t\tzero means infinite timeout, what is the default\n\
 Commands are (details in the manual page):\n\
  dump\t\t\tForce dumping statistics to database\n\
  freeze\t\tFreeze work of ipa\n\
  memory\t\tOutput information about used memory and about\n\
\t\t\tmzones and marrays\n\
  status\t\tOutput different status information, can be used\n\
\t\t\twith -r"
#ifdef WITH_LIMITS
", -l"
#endif
#ifdef WITH_THRESHOLDS
", -t"
#endif
#ifdef WITH_ANY_LIMITS
" options\n"
#else
" option\n"
#endif
#ifdef WITH_LIMITS
"  restart\t\tRestart limit, if it is currently not reached\n\
  expire\t\tExpire limit, if it was reached\n\
  set limit [+|-]<value>\n\
\t\t\tChange value of the \"limit\" parameter for limit\n"
#endif /* WITH_LIMITS */
#ifdef WITH_THRESHOLDS
"  set threshold [+|-]<value>\n\
\t\t\tChange value of the \"threshold\" parameter for\n\
\t\t\tthreshold\n"
#endif
#ifdef WITH_ANY_LIMITS
"  set counter [+|-]<value>\n"
# if defined(WITH_LIMITS) && defined(WITH_THRESHOLDS)
"\t\t\tChange rule's, limit's or threshold's counter. In all\n\
\t\t\tcommands `+' means increasing and `-' means decreasing\n\
\t\t\tof current value\n"
# else
#  ifdef WITH_LIMITS
"\t\t\tChange rule's or limit's counter. In all commands `+'\n\
\t\t\tmeans increasing and `-' means decreasing of current\n\
\t\t\tvalue\n"
#  endif
#  ifdef WITH_THRESHOLDS
"\t\t\tChange rule's or threshold's counter. In all\n\
\t\t\tcommands `+' means increasing and `-' means decreasing\n\
\t\t\tof current value\n"
#  endif
# endif /* WITH_LIMITS && WITH_THRESHOLDS */
#else
"  set counter [+|-]<value>\n"
"\t\t\tChange rule's counter. `+' means increasing and\n\
\t\t\t`-' means decreasing of current value\n"
#endif /* WITH_ANY_LIMITS */
	    , envprogname);
}

/*
 * Form an error message in re_errbuf according to
 * re_errcode with the help from the regerror() function.
 */
static void
re_form_errbuf(void)
{
	regerror(re_errcode, (regex_t *)NULL, re_errbuf, sizeof re_errbuf);
}

/*
 * Convert string to uint64_t.
 * Assume that sizeof(unsigned long long) >= sizeof(uint64_t).
 */
static int
strto_uint64(uint64_t *result, const char *nptr, char **endptr_ret)
{
	char	*endptr;
	unsigned long long val_ull;

	errno = 0;
	val_ull = strtoull(nptr, &endptr, 10);
	if (errno != 0) {
		logmsg("strtoull failed for value \"%s\"", nptr);
		return -1;
	}
	if (val_ull > UINT64_MAX) {
		logmsgx("too big value %llu for uint64_t type", val_ull);
		return -1;
	}
	if (nptr == endptr) {
		logmsgx("wrong number");
		return -1;
	}
	*result = (uint64_t)val_ull;
	if (endptr_ret != NULL)
		*endptr_ret = endptr;
	return 0;
}

/*
 * Parse bytes value.
 */
static int
parse_value_bytes(uint64_t *res, const char *arg)
{
	const char *ptr = arg;
	char	*endptr;
	int	level, error, overflow;
	uint64_t value, result;

	level = error = overflow = 0;
	result = UINT64_C(0);

	for (;;) {
		if (strto_uint64(&value, ptr, &endptr) < 0)
			return -1;
		ptr = endptr;
		switch (*ptr) {
		case 'T':
			if (level > 0)
				error = 1;
			else {
				if (value > UINT64_MAX / TBYTE)
					overflow = 1;
				else {
					level = 1;
					value *= TBYTE;
				}
			}
			break;
		case 'G':
			if (level > 1)
				error = 1;
			else {
				if (value > UINT64_MAX / GBYTE)
					overflow = 1;
				else {
					level = 2;
					value *= GBYTE;
				}
			}
			break;
		case 'M':
			if (level > 2)
				error = 1;
			else {
				if (value > UINT64_MAX / MBYTE)
					overflow = 1;
				else {
					level = 3;
					value *= MBYTE;
				}
			}
			break;
		case 'K':
			if (level > 3)
				error = 1;
			else {
				if (value > UINT64_MAX / KBYTE)
					overflow = 1;
				else {
					level = 4;
					value *= KBYTE;
				}
			}
			break;
		default: /* 'B' */
			if (level > 4)
				error = 1;
			else
				level = 5;
		}
		if (error) {
			logmsgx("wrong bytes format");
			return -1;
		}
		if (overflow || result > UINT64_MAX - value) {
			logmsgx("too big value for uint64_t");
			return -1;
		}
		result += value;
		if (*++ptr == '\0')
			break;	/* EOL */
	}

	*res = result;

	return 0;
}

/*
 * Parse time value.
 */
static int
parse_value_time(uint64_t *res, const char *arg)
{
	const char *ptr = arg;
	char	*endptr;
	int	level, error, overflow;
	uint64_t value, result;

	level = error = overflow = 0;
	result = UINT64_C(0);

	for (;;) {
		if (strto_uint64(&value, ptr, &endptr) < 0)
			return -1;
		ptr = endptr;
		switch (*ptr) {
		case 'h':
			if (level > 0)
				error = 1;
			else {
				if (value > UINT64_MAX / SECONDS_IN_HOUR)
					overflow = 1;
				else {
					level = 1;
					value *= SECONDS_IN_HOUR;
				}
			}
			break;
		case 'm':
			if (level > 1)
				error = 1;
			else {
				if (value > UINT64_MAX / SECONDS_IN_MINUTE)
					overflow = 1;
				else {
					level = 2;
					value *= SECONDS_IN_MINUTE;
				}
			}
			break;
		default: /* 's' */
			if (level > 2)
				error = 1;
			else
				level = 3;
		}
		if (error) {
			logmsgx("wrong time format");
			return -1;
		}
		if (overflow || result > UINT64_MAX - value) {
			logmsgx("too big value for uint64_t");
			return -1;
		}
		result += value;
		if (*++ptr == '\0')
			break;	/* EOL */
	}

	*res = result;

	return 0;
}

/*
 * Parse value (bytes, time or number) possibly prepended
 * with '+' or '-'.
 */
static int
parse_value(uint64_t *result, const char *arg)
{
	regex_t reg;

	if ( (re_errcode = regcomp(&reg, PAT_BYTES, REG_EXTENDED|REG_NOSUB)) != 0) {
		re_form_errbuf();
		logmsgx("regcomp(\"%s\"): %s", PAT_BYTES, re_errbuf);
		return -1;
	}
	if (regexec(&reg, arg, 0, (regmatch_t *)NULL, 0) == 0) {
		if (parse_value_bytes(result, arg) < 0)
			return -1;
	} else {
		regfree(&reg);
		if ( (re_errcode = regcomp(&reg, PAT_TIME, REG_EXTENDED|REG_NOSUB)) != 0) {
			re_form_errbuf();
			logmsgx("regcomp(\"%s\"): %s", PAT_TIME, re_errbuf);
			return -1;
		}
		if (regexec(&reg, arg, 0, (regmatch_t *)NULL, 0) == 0) {
			if (parse_value_time(result, arg) < 0)
				return -1;
		} else {
			const char *ptr;

			for (ptr = arg; *ptr != '\0'; ++ptr)
				if (isdigit(*ptr) == 0) {
					logmsgx("value should be bytes, time or number");
					return -1;
				}
			if (strto_uint64(result, arg, (char **)NULL) < 0)
				return -1;
		}
	}
	regfree(&reg);
	return 0;
}

#ifdef WITH_ANY_LIMITS

struct time_conv {
	uint64_t div;
	char	ch;
};

static const struct time_conv time_conv_tbl[] = {
	{ SECONDS_IN_HOUR,	'h'  },
	{ SECONDS_IN_MINUTE,	'm'  },
	{ 1,			's'  },
	{ 0,			'\0' }
};

static void
print_time(const uint64_t *ptr)
{
	uint64_t t, a;
	const struct time_conv *time_conv;
	int need_space;

	a = *ptr;
	if (a == UINT64_C(0)) {
		printf("0s");
		return;
	}

	need_space = 0;
	for (time_conv = time_conv_tbl; time_conv->div > UINT64_C(0); ++time_conv) {
		t = a / time_conv->div;
		if (t != 0) {
			if (need_space)
				printf(" ");
			printf("%"PRIu64"%c", t, time_conv->ch);
			need_space = 1;
			a -= t * time_conv->div;
		}
	}
}

struct byte_conv {
	uint64_t div;
	char	ch;
};

static const struct byte_conv byte_conv_tbl[] = {
	{ TBYTE, 'T'  },
	{ GBYTE, 'G'  },
	{ MBYTE, 'M'  },
	{ KBYTE, 'K'  },
	{ 1,	 'B'  },
	{ 0,	 '\0' }
};

static void
print_bytes(const uint64_t *bytes)
{
	int	need_space = 0;
	uint64_t t, a;
	const struct byte_conv *byte_conv;

	a = *bytes;

	if (a == UINT64_C(0)) {
		printf("0B");
		return;
	}
	for (byte_conv = byte_conv_tbl; byte_conv->div > UINT64_C(0); ++byte_conv) {
		t = a / byte_conv->div;
		if (t != 0) {
			if (need_space)
				printf(" ");
			printf("%"PRIu64"%c", t, byte_conv->ch);
			need_space = 1;
			a -= t * byte_conv->div;
		}
	}
}

static void
print_value(int sign, const uint64_t *value, u_int value_type)
{
	if (sign)
		printf("-");

	switch (value_type) {
	case IPA_CONF_TYPE_BYTES:
		print_bytes(value);
		break;
	case IPA_CONF_TYPE_TIME:
		print_time(value);
		break;
	default: /* IPA_CONF_TYPE_UINT64 */
		printf("%"PRIu64, *value);
		return;
	}

	printf(" / %s%"PRIu64, sign ? "-" : "", *value);
}
#endif /* WITH_ANY_LIMITS */

static int
print_line(size_t len)
{
	while (len--)
		printf("-");
	return 0;
}

static int
ctl_xxx_check(void)
{
	const char * const answer_msg[] = {
		"command was successfully done",	/*  0 | CTL_ANSWER_DONE */
		"some system error occurred",		/*  1 | CTL_ANSWER_SYSTEM_ERROR */
		"unknown command or flag",		/*  2 | CTL_ANSWER_UNKNOWN_COMMAND */
		"illegal name in control query",	/*  3 | CTL_ANSWER_INVALID_NAME */
		"command was denied",			/*  4 | CTL_ANSWER_DENIED */
		"unknown rule name",			/*  5 | CTL_ANSWER_UNKNOWN_RULE */
		"unknown limit name",			/*  6 | CTL_ANSWER_UNKNOWN_LIMIT */
		"unknown threshold name",		/*  7 | CTL_ANSWER_UNKNOWN_THRESHOLD */
		"cannot modify value",			/*  8 | CTL_ANSWER_CANNOT_MODIFY */
		"cannot expire limit (it is not reached)", /*  9 | CTL_ANSWER_CANNOT_EXPIRE */
		"cannot restart limit (it is reached)"	/* 10 | CTL_ANSWER_CANNOT_RESTART */
	};

	if (cmd_answer.result == CTL_ANSWER_DONE) {
		printf("\nResult: %s\n\n", answer_msg[CTL_ANSWER_DONE]);
		return 0;
	}
	if (cmd_answer.result <= CTL_ANSWER_MAX)
		logmsgx("Result: %s", answer_msg[cmd_answer.result]);
	else
		logmsgx("Result: unknown result code %u", cmd_answer.result);
	return -1;
}

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

/*
 * Send control command query.
 */
static int
send_cmd_query(void)
{
	int	ret;
	ssize_t	nsent;
	struct iovec iov[1];
	struct msghdr msg;
	struct sockaddr_un servaddr;
#ifdef CTL_CHECK_CREDS
# ifdef __FreeBSD__
	struct cmsghdr *cmptr;
	union {
		struct cmsghdr	cm;
		char		control[CMSG_SPACE(sizeof(struct cmsgcred))];
	} control_un;
# endif
#endif /* CTL_CHECK_CREDS */

	if (strlen(ctl_socket_path) + 1 > sizeof servaddr.sun_path) {
		logmsgx("too long path for ctl socket");
		return -1;
	}

	if ( (sockfd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
		logmsg("socket(PF_LOCAL, SOCK_STREAM)");
		return -1;
	}

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

	memset(&msg, 0, sizeof msg);

	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	cmd_query.ver = CTL_QUERY_VERSION;

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

#ifdef CTL_CHECK_CREDS
# ifdef __FreeBSD__
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof control_un.control;
	cmptr = CMSG_FIRSTHDR(&msg);
	cmptr->cmsg_len = CMSG_LEN(sizeof(struct cmsgcred));
	cmptr->cmsg_level = SOL_SOCKET;
	cmptr->cmsg_type= SCM_CREDS;
# endif /* __FreeBSD__ */
#else
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
#endif /* CTL_CHECK_CREDS */

	if (ctl_timeout != 0) {
		/* Catch timeout for connect. */
		if (sigsetjmp(env_alrm, 1) != 0) {
			logmsgx("cannot connect to %s during %u seconds",
			    ctl_socket_path, ctl_timeout);
			return -1;
		}
		/* Set alarm. */
		(void)alarm(ctl_timeout);
	}

	ret = connect(sockfd, (struct sockaddr *)&servaddr, sizeof servaddr);

	if (ctl_timeout != 0)
		/* Release alarm. */
		(void)alarm(0);

	if (ret < 0) {
		logmsg("connect(%s)", ctl_socket_path);
		return -1;
	}

	printf("Sending command...");
	fflush(stdout);

	if (ctl_timeout != 0) {
		/* Catch timeout for sendmsg. */
		if (sigsetjmp(env_alrm, 1) != 0) {
			logmsgx("cannot send message to %s during %u seconds",
			    ctl_socket_path, ctl_timeout);
			return -1;
		}
		/* Set alarm. */
		(void)alarm(ctl_timeout);
	}

	nsent = sendmsg(sockfd, &msg, 0);

	if (ctl_timeout != 0)
		/* Release alarm. */
		(void)alarm(0);

	if (nsent < 0) {
		logmsg("sendmsg(%s)", ctl_socket_path);
		return -1;
	}

	if (nsent != sizeof cmd_query) {
		logmsgx("sendmsg: short send (%ld of %lu bytes)",
		    (long)nsent, (u_long)sizeof(cmd_query));
		return -1;
	}

	if (!wait_answer)
		if (close(sockfd) < 0) {
			logmsg("close");
			return -1;
		}

	printf(" done\n");
	fflush(stdout);

	return 0;
}

/*
 * Read from socket nbytes bytes to buf, with timeout if needed.
 */
static int
read_sock_timeout(void *buf, size_t nbytes)
{
	ssize_t	nread;

	if (ctl_timeout != 0) {
		if (sigsetjmp(env_alrm, 1) != 0) {
			logmsgx("cannot receive message from %s during %u seconds",
			    ctl_socket_path, ctl_timeout);
			return -1;
		}
		/* Set alarm. */
		(void)alarm(ctl_timeout);
	}

	nread = read(sockfd, buf, nbytes);

	if (ctl_timeout != 0)
		/* Release alarm. */
		(void)alarm(0);

	if (nread < 0) {
		logmsg("read");
		return -1;
	}

	if (nread != nbytes) {
		logmsgx("read: short read (%ld of %lu bytes)",
		    (long)nread, (u_long)nbytes);
		return -1;
	}

	return 0;
}

/*
 * Receive control command answer.
 */
static int
recv_cmd_answer(void)
{
	printf("Receiving answer...");
	fflush(stdout);

	/* Receive generic answer. */
	if (read_sock_timeout(&cmd_answer, sizeof cmd_answer) < 0) {
		logmsgx("cannot receive generic answer");
		return -1;
	}

	/* Receive auxiliary answer, if needed. */
	if (cmd_answer.size != 0) {
		if (cmd_answer_aux == NULL) {
			if (!cmd_answer_aux_exp) {
				logmsgx("auxiliary answer is returned, but not expected");
				return -1;
			}
			cmd_answer_aux_size = cmd_answer.size;
			if (cmd_answer_aux_size > IPACTL_ANSWER_MAX) {
				logmsgx("returned size %lu of auxiliary answer is greater than %u",
				    (u_long)cmd_answer_aux_size, IPACTL_ANSWER_MAX);
				return -1;
			}
			if ( (cmd_answer_aux = malloc(cmd_answer_aux_size)) == NULL) {
				logmsg("malloc(%lu bytes): cannot allocate memory for auxiliary answer",
				    (u_long)cmd_answer_aux_size);
				return -1;
			}
		} else if (cmd_answer.size != cmd_answer_aux_size) {
			logmsgx("returned size of auxiliary answer %lu is not equal to expected size %lu",
			    (u_long)cmd_answer.size, (u_long)cmd_answer_aux_size);
			return -1;
		}
		if (read_sock_timeout(cmd_answer_aux, cmd_answer_aux_size) < 0) {
			logmsgx("cannot receive auxiliary answer");
			return -1;
		}
	}

	if (close(sockfd) < 0) {
		logmsg("close");
		return -1;
	}

	printf(" done\n");
	fflush(stdout);

	return 0;
}

/*
 * Parse for "status" command.
 */
/* ARGSUSED */
static int
ctl_status_parse(int agrc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
{
	if (rule_name != NULL) {
		/* -r <rule> */
		strcpy(cmd_query.name1, rule_name);

#ifdef WITH_LIMITS
		if (limit_name != NULL) {
			/* -r <rule> -l <limit> */
			strcpy(cmd_query.name2, limit_name);
			cmd_query.flags |= CTL_CMD_FLAG_LIMIT;
		}
#endif

#ifdef WITH_THRESHOLDS
		if (threshold_name != NULL) {
			/* -r <rule> -t <threshold> */
			strcpy(cmd_query.name2, threshold_name);
			cmd_query.flags |= CTL_CMD_FLAG_THRESHOLD;
		}
#endif
	}

	cmd_answer_aux = &cmd_answer_status;
	cmd_answer_aux_size = sizeof cmd_answer_status;
	cmd_answer_aux_exp = 1;

	return 0;
}

/*
 * Check for "status" command.
 */
static int
ctl_status_check(void)
{
#ifdef WITH_ANY_LIMITS
	u_int	value_type;
#endif
#ifdef WITH_LIMITS
	const ipa_tm *tm;
#endif

	if (rule_name == NULL) {
		printf("General status:\n");
		printf("  Ac_mods:\t%u\n  Db_mods:\t%u\n",
		    cmd_answer_status.nac_mods, cmd_answer_status.ndb_mods);
		printf("  Autorules:\t%u\n",
		    cmd_answer_status.nautorules);
		printf("  Rules:\t%u (%u static + %u dynamic)\n",
		    cmd_answer_status.nstatrules + cmd_answer_status.ndynrules,
		    cmd_answer_status.nstatrules, cmd_answer_status.ndynrules);
		printf("  Limits:\t%u (%u static + %u dynamic)\n",
		    cmd_answer_status.nstatlimits + cmd_answer_status.ndynlimits,
		    cmd_answer_status.nstatlimits, cmd_answer_status.ndynlimits);
		printf("  Sublimits:\t%u (%u static + %u dynamic)\n",
		    cmd_answer_status.nstatsublimits + cmd_answer_status.ndynsublimits,
		    cmd_answer_status.nstatsublimits, cmd_answer_status.ndynsublimits);
		printf("  Thresholds:\t%u (%u static + %u dynamic)\n",
		    cmd_answer_status.nstatthresholds + cmd_answer_status.ndynthresholds,
		    cmd_answer_status.nstatthresholds, cmd_answer_status.ndynthresholds);
	} else {
#ifdef WITH_ANY_LIMITS
		if (cmd_query.name2[0] != '\0') {
# ifdef WITH_LIMITS
			if (limit_name != NULL)
				printf("Limit status:\n  Limit:\t");
# endif
# ifdef WITH_THRESHOLDS
			if (threshold_name != NULL)
				printf("Threshold status:\n  Threshold:\t");
# endif
			value_type = cmd_answer_status.value_type;
			print_value(0, &cmd_answer_status.value1, value_type);
			printf("\n  Counter:\t");
			print_value(cmd_answer_status.value2_sign, &cmd_answer_status.value2, value_type);
			printf("\n  Active:\t%s\n",
			    cmd_answer_status.active ? "yes" : "no");
# ifdef WITH_LIMITS
			if (limit_name != NULL) {
				printf("  Sublimits:\t%u\n",
				     cmd_answer_status.nstatsublimits);
				printf("  State:\t");
				if (cmd_answer_status.reached)
					printf("reached\n  Expire:\t");
				else
					printf("not reached\n  Restart:\t");
				tm = &cmd_answer_status.tm;
				if (tm->tm_year != 0)
					printf("%d.%02d.%02d/%02d:%02d:%02d\n",
					    tm->tm_year, tm->tm_mon, tm->tm_mday,
					    tm->tm_hour, tm->tm_min, tm->tm_sec);
				else
					printf("never\n");
			}
# endif
		} else
#endif /* WITH_ANY_LIMITS */
		{
			printf("Rule status:\n");
			printf("  Limits:\t%u\n  Sublimits:\t%u\n  Thresholds:\t%u\n",
			    cmd_answer_status.nstatlimits,
			    cmd_answer_status.nstatsublimits,
			    cmd_answer_status.nstatthresholds);
			printf("  Active:\t%s\n",
			    cmd_answer_status.active ? "yes" : "no");
		}
		printf("  Dynamic:\t%s\n",
		    cmd_answer_status.dynamic ? "yes" : "no");
	}
	return 0;
}

/*
 * Parse for "memory" command.
 */
/* ARGSUSED */
static int
ctl_memory_parse(int agrc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
{
	cmd_answer_aux_exp = 1;
	return 0;
}

/*
 * Check for "memory" command.
 */
static int
ctl_memory_check(void)
{
	u_int	i;
	size_t	len, name_width, desc_width;
	const struct ctl_cmd_answer_memory *cmd_answer_memory;
	const struct ctl_cmd_answer_mzone *cmd_answer_mzone;
	const struct ctl_cmd_answer_marray *cmd_answer_marray;
	const struct ctl_cmd_answer_mem_type *cmd_answer_mem_type;

	if (cmd_answer_aux_size < sizeof cmd_answer_memory) {
		logmsgx("too small auxiliary answer (%lu of %lu bytes)",
		    (u_long)cmd_answer_aux_size, (u_long)sizeof(cmd_answer_memory));
		return -1;
	}

	cmd_answer_memory = (struct ctl_cmd_answer_memory *)cmd_answer_aux;

	if (cmd_answer_memory->nmem_type * sizeof(struct ctl_cmd_answer_mem_type) +
	    cmd_answer_memory->nmzones * sizeof(struct ctl_cmd_answer_mzone) +
	    cmd_answer_memory->nmarrays * sizeof(struct ctl_cmd_answer_marray) +
	    sizeof *cmd_answer_memory != cmd_answer_aux_size) {
		logmsgx("incorrect size for auxiliary answer (%lu bytes)",
		    (u_long)cmd_answer_aux_size);
		return -1;
	}

	printf("Memory status:\n");
	printf("  Allocated:\t%lu\n  Mem_types:\t%u\n  Mzones:\t%u\n  Marrays:\t%u\n",
	    (u_long)cmd_answer_memory->size_cnt,
	    cmd_answer_memory->nmem_type,
	    cmd_answer_memory->nmzones,
	    cmd_answer_memory->nmarrays);

	name_width = 8;  /* strlen("Mem_type") */
	desc_width = 11; /* strlen("Description") */

	cmd_answer_mem_type = (const struct ctl_cmd_answer_mem_type *)((const char *)cmd_answer_memory + sizeof *cmd_answer_memory);
	if (cmd_answer_memory->nmem_type != 0) {
		for (i = 0; i < cmd_answer_memory->nmem_type; ++i, ++cmd_answer_mem_type) {
			len = strlen(cmd_answer_mem_type->name);
			if (name_width < len)
				name_width = len;
			len = strlen(cmd_answer_mem_type->desc);
			if (desc_width < len)
				desc_width = len;
		}
	}

	cmd_answer_mzone = (const struct ctl_cmd_answer_mzone *)((const char *)cmd_answer_mem_type);
	if (cmd_answer_memory->nmzones != 0) {
		for (i = 0; i < cmd_answer_memory->nmzones; ++i, ++cmd_answer_mzone) {
			len = strlen(cmd_answer_mzone->name);
			if (name_width < len)
				name_width = len;
			len = strlen(cmd_answer_mzone->desc);
			if (desc_width < len)
				desc_width = len;
		}
	}

	if (cmd_answer_memory->nmarrays != 0) {
		cmd_answer_marray = (const struct ctl_cmd_answer_marray *)cmd_answer_mzone;
		for (i = 0; i < cmd_answer_memory->nmarrays; ++i, ++cmd_answer_marray) {
			len = strlen(cmd_answer_marray->name);
			if (name_width < len)
				name_width = len;
			len = strlen(cmd_answer_marray->desc);
			if (desc_width < len)
				desc_width = len;
		}
	}

	cmd_answer_mem_type = (const struct ctl_cmd_answer_mem_type *)((const char *)cmd_answer_memory + sizeof *cmd_answer_memory);
	if (cmd_answer_memory->nmem_type != 0) {
		printf("\nMem_type status:\n");
		printf("  %-*s | Allocated |  Requests | Description\n  ",
		    (int)name_width, "Mem_type");
		print_line(name_width);
		printf("-+-----------+-----------+-");
		print_line(desc_width);
		printf("\n");
		for (i = 0; i < cmd_answer_memory->nmem_type; ++i, ++cmd_answer_mem_type)
			printf("  %-*s | %9lu | %9u | %s\n", (int)name_width,
			    cmd_answer_mem_type->name,
			    (u_long)cmd_answer_mem_type->size,
			    cmd_answer_mem_type->reqs,
			    cmd_answer_mem_type->desc);
		printf("  ");
		print_line(name_width);
		printf("-+-----------+-----------+-");
		print_line(desc_width);
		printf("\n");
	}

	cmd_answer_mzone = (const struct ctl_cmd_answer_mzone *)((const char *)cmd_answer_mem_type);
	if (cmd_answer_memory->nmzones != 0) {
		printf("\nMzones status:\n");
		printf("  %-*s | Allocated |  Requests | Description\n  ",
		    (int)name_width, "Mzone");
		print_line(name_width);
		printf("-+-----------+-----------+-");
		print_line(desc_width);
		printf("\n");
		for (i = 0; i < cmd_answer_memory->nmzones; ++i, ++cmd_answer_mzone)
			printf("  %-*s | %9lu | %9u | %s\n", (int)name_width,
			    cmd_answer_mzone->name,
			    (u_long)cmd_answer_mzone->pools_size,
			    cmd_answer_mzone->reqs,
			    cmd_answer_mzone->desc);
		printf("  ");
		print_line(name_width);
		printf("-+-----------+-----------+-");
		print_line(desc_width);
		printf("\n");
	}

	if (cmd_answer_memory->nmarrays != 0) {
		printf("\nMarrays status:\n");
		printf("  %-*s | Allocated |  Requests | Description\n  ",
		    (int)name_width, "Mzone");
		print_line(name_width);
		printf("-+-----------+-----------+-");
		print_line(desc_width);
		printf("\n");
		cmd_answer_marray = (const struct ctl_cmd_answer_marray *)cmd_answer_mzone;
		for (i = 0; i < cmd_answer_memory->nmarrays; ++i, ++cmd_answer_marray)
			printf("  %-*s | %9lu | %9u | %s\n", (int)name_width,
			    cmd_answer_marray->name,
			    (u_long)(cmd_answer_marray->arr_size + cmd_answer_marray->bitmap_size),
			    cmd_answer_marray->reqs,
			    cmd_answer_marray->desc);
		printf("  ");
		print_line(name_width);
		printf("-+-----------+-----------+-");
		print_line(desc_width);
		printf("\n");
	}

	cmd_answer_mzone = (const struct ctl_cmd_answer_mzone *)((const char *)cmd_answer_mem_type);
	if (cmd_answer_memory->nmzones != 0) {
		printf("\nMzones details:\n");
		printf("  %-*s |  Isize |   Nused |   Nfree\n  ",
		    (int)name_width, "Mzone");
		print_line(name_width);
		printf("-+--------+---------+--------\n");
		for (i = 0; i < cmd_answer_memory->nmzones; ++i, ++cmd_answer_mzone)
			printf("  %-*s | %6lu | %7u | %7u\n", (int)name_width,
			    cmd_answer_mzone->name,
			    (u_long)cmd_answer_mzone->isize,
			    cmd_answer_mzone->nused,
			    cmd_answer_mzone->nfree);
		printf("  ");
		print_line(name_width);
		printf("-+--------+---------+--------\n");
	}

	if (cmd_answer_memory->nmarrays != 0) {
		printf("\nMarrays details:\n");
		printf("  %-*s |  Isize |   Nused |   Nfree |  Arr_size |  Map_size\n  ",
		    (int)name_width, "Marray");
		print_line(name_width);
		printf("-+--------+---------+---------+-----------+-----------\n");
		cmd_answer_marray = (const struct ctl_cmd_answer_marray *)cmd_answer_mzone;
		for (i = 0; i < cmd_answer_memory->nmarrays; ++i, ++cmd_answer_marray)
			printf("  %-*s | %6lu | %7u | %7u | %9lu | %9lu\n",
			    (int)name_width,
			    cmd_answer_marray->name,
			    (u_long)cmd_answer_marray->isize,
			    cmd_answer_marray->nused,
			    cmd_answer_marray->nfree,
			    (u_long)cmd_answer_marray->arr_size,
			    (u_long)cmd_answer_marray->bitmap_size);
		printf("  ");
		print_line(name_width);
		printf("-+--------+---------+---------+-----------+-----------\n");
	}

	return 0;
}

#ifdef WITH_LIMITS
/*
 * Parse for "restart" and "expire" command.
 */
/* ARGSUSED */
static int
ctl_restart_expire_parse(int argc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
{
	cmd_query.flags |= CTL_CMD_FLAG_LIMIT;
	strcpy(cmd_query.name1, rule_name);
	strcpy(cmd_query.name2, limit_name);
	return 0;
}
#endif

/*
 * Parse for "set" command.
 */
static int
ctl_set_parse(int argc ATTR_UNUSED, char *argv[])
{
	int	counter_flag;
	const char *argument;

	argument = argv[optind];

	if (strcmp(argument, "counter") == 0) {
		counter_flag = 1;
		cmd_query.flags |= CTL_CMD_FLAG_COUNTER;
	} else
		counter_flag = 0;

	strcpy(cmd_query.name1, rule_name);

#ifdef WITH_LIMITS
	if (limit_name != NULL) {
		if (!counter_flag && strcmp(argument, "limit") != 0) {
			logmsgx("wrong or unknown argument");
			return -1;
		}
		cmd_query.flags |= CTL_CMD_FLAG_LIMIT;
		strcpy(cmd_query.name2, limit_name);
	}
#endif

#ifdef WITH_THRESHOLDS
	if (threshold_name != NULL) {
		if (!counter_flag && strcmp(argument, "threshold") != 0) {
			logmsgx("wrong or unknown argument");
			return -1;
		}
		cmd_query.flags |= CTL_CMD_FLAG_THRESHOLD;
		strcpy(cmd_query.name2, threshold_name);
	}
#endif

	if (cmd_query.name2[0] == '\0' && !counter_flag) {
		logmsgx("wrong or unknown argument");
		return -1;
	}

	argument = argv[++optind];

	switch (*argument) {
	case '+':
		cmd_query.flags |= CTL_CMD_FLAG_INCREMENT;
		++argument;
		break;
	case '-':
		cmd_query.flags |= CTL_CMD_FLAG_DECREMENT;
		++argument;
		break;
	default:
		if (cmd_query.name2[0] == '\0' &&
		    (cmd_query.flags & (CTL_CMD_FLAG_LIMIT|CTL_CMD_FLAG_THRESHOLD)) == 0) {
			logmsgx("it is only allowed to increment or decrement statistics with the -r option");
			return -1;
		}
	}

	if (parse_value(&cmd_query.value, argument) < 0)
		return -1;

#ifdef WITH_ANY_LIMITS
	if (cmd_query.name2[0] != '\0') {
		cmd_answer_aux = &cmd_answer_set;
		cmd_answer_aux_size = sizeof cmd_answer_set;
		cmd_answer_aux_exp = 1;
	}
#endif

	return 0;
}

#ifdef WITH_ANY_LIMITS
/*
 * Check for "set" command.
 */
static int
ctl_set_check(void)
{
	u_int value_type;

	if (cmd_query.name2[0] != '\0') {
		value_type = cmd_answer_set.value_type;
#ifdef WITH_LIMITS
		if (limit_name != NULL)
			printf("Limit was changed:\n  Limit:\t");
#endif
#ifdef WITH_THRESHOLDS
		if (threshold_name != NULL)
			printf("Threshold was changed:\n  Threshold:\t");
#endif
		print_value(0, &cmd_answer_set.value1, value_type);
		printf("\n  Counter:\t");
		print_value(cmd_answer_set.value2_sign, &cmd_answer_set.value2, value_type);
		printf("\n");
	}
	return 0;
}
#else
# define ctl_set_check NULL
#endif /* WITH_ANY_LIMITS */

/*
 * Ignore SIGPIPE and set signal handler for SIGALRM
 * if timeout is enabled.
 */
static int
sig_init(void)
{
	struct sigaction sa;

	/* Ignore SIGPIPE. */
	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	if (sigaction(SIGPIPE, &sa, (struct sigaction *)NULL) < 0) {
		logmsg("sigaction(SIGPIPE)");
		return -1;
	}

	/* Init handler for SIGALRM. */
	if (ctl_timeout) {
		sa.sa_handler = sig_alrm;
		if (sigaction(SIGALRM, &sa, (struct sigaction *)NULL) < 0) {
			logmsg("sigaction(SIGALRM)");
			return -1;
		}
	}

	return 0;
}

static struct ctl_cmd ctl_cmd_tbl[] = {
	{ "status",	0,	-1,	CTL_CMD_STATUS, ctl_status_parse, ctl_status_check },
	{ "memory",	0,	 0,	CTL_CMD_MEMORY, ctl_memory_parse, ctl_memory_check },
	{ "dump",	0,	 0,	CTL_CMD_DUMP, NULL, NULL },
	{ "freeze",	0,	 0,	CTL_CMD_FREEZE, NULL, NULL },
#ifdef WITH_LIMITS
	{ "restart",	0,	 CTL_REQ_RULE|CTL_REQ_LIMIT, CTL_CMD_RESTART, ctl_restart_expire_parse, NULL },
	{ "expire",	0,	 CTL_REQ_RULE|CTL_REQ_LIMIT, CTL_CMD_EXPIRE, ctl_restart_expire_parse, NULL },
#endif
	{ "set",	2,	 CTL_REQ_RULE, CTL_CMD_SET, ctl_set_parse, ctl_set_check },
	{ NULL,		0,	 0,	0, NULL, NULL }
};

int
main(int argc, char *argv[])
{
	int	opt;
	const char *cmd_name;
	const struct ctl_cmd *ctl_cmd;

	/* Save the program name. */
	if ( (envprogname = strrchr(argv[0], '/')) != NULL)
		++envprogname;
	else
		envprogname = argv[0];

	opterr = 0;
	while ( (opt = getopt(argc, argv, OPTSTRING)) != -1)
		switch (opt) {
		case 'r':
			rule_name = optarg;
			if (strlen(rule_name) >= CTL_NAME1_LEN)
				exit_errx("too long name (>=%u) for rule", CTL_NAME1_LEN);
			break;
#ifdef WITH_LIMITS
		case 'l':
			limit_name = optarg;
			if (strlen(limit_name) >= CTL_NAME2_LEN)
				exit_errx("too long name (>=%u) for limit", CTL_NAME2_LEN);
			break;
#endif
#ifdef WITH_THRESHOLDS
		case 't':
			threshold_name = optarg;
			if (strlen(threshold_name) >= CTL_NAME2_LEN)
				exit_errx("too long name (>=%u) for threshold", CTL_NAME2_LEN);
			break;
#endif
		case 's':
			ctl_socket_path = optarg;
			break;
		case 'n':
			wait_answer = 0;
			break;
		case 'w':
			if (strto_u_int(&ctl_timeout, optarg) < 0) {
				logmsgx("wrong argument for the -w option");
				return -1;
			}
			break;
		case 'v':
			show_version();
			return 0;
		case 'h':
			usage();
			return 0;
		case ':':
			exit_errx("option requires an argument -- %c", optopt);
			/* NOTREACHED */
		case '?':
			exit_errx("illegal option -- %c", optopt);
			/* NOTREACHED */
		default:
			exit_errx("unexpected option -- %c", optopt);
		}

	if (optind == argc)
		exit_errx("command is missed");

#if defined(WITH_LIMITS) && defined(WITH_THRESHOLDS)
	if (limit_name != NULL && threshold_name != NULL)
		exit_errx("-l and -t options cannot be used together");
#endif

#ifdef WITH_LIMITS
	if (limit_name != NULL && rule_name == NULL)
		exit_errx("-l option should be used with -r option");
#endif

#ifdef WITH_THRESHOLDS
	if (threshold_name != NULL && rule_name == NULL)
		exit_errx("-t option should be used with -r option");
#endif

	cmd_name = argv[optind++];

	cmd_query.flags = wait_answer ? CTL_CMD_FLAG_WAIT : 0;
	cmd_query.name1[0] = cmd_query.name2[0] = '\0';

	for (ctl_cmd = ctl_cmd_tbl; ctl_cmd->name != NULL; ++ctl_cmd)
		if (strcmp(ctl_cmd->name, cmd_name) == 0) {
			/* Validate options (not everything). */
			if (ctl_cmd->nargs != argc - optind)
				exit_errx("wrong number of arguments for this command");
			switch (ctl_cmd->req) {
			case -1:
				break;
			case 0:
				if (rule_name != NULL)
					exit_errx("given command does not require -r option");
				break;
			default:
				if ((ctl_cmd->req & CTL_REQ_RULE) && rule_name == NULL)
					exit_errx("given command requires -r option");
#ifdef WITH_LIMITS
				if ((ctl_cmd->req & CTL_REQ_LIMIT) && limit_name == NULL)
					exit_errx("given command requires -l option");
#endif
			}

			/* Parse command. */
			if (ctl_cmd->parse != NULL)
				if (ctl_cmd->parse(argc, argv) < 0)
					exit_errx("cannot parse command line");

			if (sig_init() < 0)
				exit_errx("cannot init signal handlers");

			cmd_query.cmd = ctl_cmd->cmd;

			/* Send control command query. */
			if (send_cmd_query() < 0)
				exit_errx("cannot send control command query");

			/* Receive control command answer. */
			if (wait_answer) {
				if (recv_cmd_answer() < 0)
					exit_errx("cannot receive control command answer");
				if (ctl_xxx_check() < 0)
					return 2;
				if (ctl_cmd->check != NULL)
					if (ctl_cmd->check() < 0)
						return 2;
			}

			return 0;
		}

	exit_errx("unknown command \"%s\"", cmd_name);
	/* NOTREACHED */
}


syntax highlighted by Code2HTML, v. 0.9.1