/*-
* 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