/*- * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }