/*-
* 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(®, PAT_BYTES, REG_EXTENDED|REG_NOSUB)) != 0) {
re_form_errbuf();
logmsgx("regcomp(\"%s\"): %s", PAT_BYTES, re_errbuf);
return -1;
}
if (regexec(®, arg, 0, (regmatch_t *)NULL, 0) == 0) {
if (parse_value_bytes(result, arg) < 0)
return -1;
} else {
regfree(®);
if ( (re_errcode = regcomp(®, PAT_TIME, REG_EXTENDED|REG_NOSUB)) != 0) {
re_form_errbuf();
logmsgx("regcomp(\"%s\"): %s", PAT_TIME, re_errbuf);
return -1;
}
if (regexec(®, 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(®);
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