/*- * Copyright (c) 2003 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_limits.c,v 1.1.4.2 2007/05/11 16:29:59 simon Exp $"; #endif /* !lint */ #include #include #include #include #include #include #include #include "ipa_mod.h" #include "dlapi.h" #include "confcommon.h" #include "memfunc.h" #include "parser.h" #include "ipa_ac.h" #include "ipa_db.h" #include "ipa_cmd.h" #include "ipa_ctl.h" #include "ipa_time.h" #include "ipa_conf.h" #include "ipa_log.h" #include "ipa_main.h" #include "ipa_rules.h" #include "ipa_autorules.h" u_int nstatlimits; /* Number of static limits. */ u_int ndynlimits; /* Number of dynamic limits. */ u_int nstatsublimits; /* Number of static sublimits. */ u_int ndynsublimits; /* Number of dynamic sublimits. */ #ifdef WITH_LIMITS int global_debug_limit; /* global { debug_limit } */ int global_debug_limit_init;/* global { debug_limit_init } */ int global_load_limit; /* global { load_limit } */ ipa_mzone *limit_mzone; /* Mzone for all struct limit{}. */ #ifdef WITH_SUBLIMITS ipa_mzone *sublimit_mzone; /* Mzone for all struct sublimit{}. */ #endif #define set_limit_active(r, l) mod_set_limit_active((r), (l), ACTIVE_FLAG) const char *const limit_event_msg[] = { "START", /* 0 */ "RESTART", /* 1 */ "RESTART_EXEC", /* 2 */ "REACH", /* 3 */ "REACH_EXEC", /* 4 */ "EXPIRE", /* 5 */ "EXPIRE_EXEC", /* 6 */ "UPDATED", /* 7 */ "STARTUP_IF_REACHED", /* 8 */ "STARTUP_IF_NOT_REACHED", /* 9 */ "SHUTDOWN_IF_REACHED", /* 10 */ "SHUTDOWN_IF_NOT_REACHED" /* 11 */ }; /* * Set limit active or inactive in modules it uses. */ int mod_set_limit_active(const struct rule *rule, struct limit *limit, int active) { if (limit->is_active == active) { logmsgx(IPA_LOG_ERR, "internal error: mod_set_limit_active(%s, %s, %d): limit is already %s", rule->rule_name, limit->limit_name, active, active_msg[active]); return -1; } if (debug_worktime) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: set limit %s", rule->rule_name, limit->limit_name, active_msg[active]); limit->is_active = active; if (ac_set_limit_active(rule, limit, active) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: mod_set_limit_active: ac_set_limit_active(%d) failed", rule->rule_name, limit->limit_name, active); return -1; } if (db_set_limit_active(rule, limit, active) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: mod_set_limit_active: db_set_limit_active(%d) failed", rule->rule_name, limit->limit_name, active); return -1; } return 0; } /* * Set limit->event_sec from limit->event_tm. */ void limit_set_event_sec(struct limit *limit) { const ipa_tm *tm = &limit->event_tm; if (cmp_ipa_tm(tm, &curdate) > 0) { if (tm->tm_year == curdate.tm_year && tm->tm_mon == curdate.tm_mon && tm->tm_mday == curdate.tm_mday) limit->event_sec = TIME_TO_SEC(tm); else limit->event_sec = SECONDS_IN_WEEK; } else limit->event_sec = cursec; } /* * Add chunk to one limit, if positive counter overflows, then * return -1, since this means incorrect configuration. */ int add_chunk_to_limit(const struct rule *rule, struct limit *limit, const uint64_t *chunk_ptr) { uint64_t chunk; chunk = *chunk_ptr; if (limit->cnt_neg >= chunk) limit->cnt_neg -= chunk; else { chunk -= limit->cnt_neg; if (limit->cnt > UINT64_MAX - chunk) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: add_chunk_to_limit: positive counter overflowed", rule->rule_name, limit->limit_name); logmsgx(IPA_LOG_ERR, "rule %s, limit %s: add_chunk_to_limit: this means that something is wrong in configuration or in modules", rule->rule_name, limit->limit_name); return -1; } limit->cnt += chunk; limit->cnt_neg = UINT64_C(0); } return 0; } /* * Add chunk to every not reached and active rule's limit. */ int add_chunk_to_limits(const struct rule *rule, const uint64_t *chunk_ptr) { struct limit *limit; STAILQ_FOREACH(limit, &rule->limits, link) if (IS_NOTREACHED(limit) && IS_ACTIVE(limit)) if (add_chunk_to_limit(rule, limit, chunk_ptr) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: add_chunk_to_limits: add_chunk_to_limit failed", rule->rule_name, limit->limit_name); return -1; } return 0; } /* * Subtract chunk from one limit, if negative counter overflows, then * return -1, since this means incorrect configuration. */ int sub_chunk_from_limit(const struct rule *rule, struct limit *limit, const uint64_t *chunk_ptr) { uint64_t chunk; chunk = *chunk_ptr; if (limit->cnt >= chunk) limit->cnt -= chunk; else { chunk -= limit->cnt; if (limit->cnt_neg > UINT64_MAX - chunk) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: sub_chunk_from_limits: negative counter overflowed", rule->rule_name, limit->limit_name); logmsgx(IPA_LOG_ERR, "rule %s, limit %s: sub_chunk_to_limit: this means that something is wrong in configuration or in modules", rule->rule_name, limit->limit_name); return -1; } limit->cnt_neg += chunk; limit->cnt = UINT64_C(0); } return 0; } /* * Subtract chunk from every not reached and active rule's limit. */ int sub_chunk_from_limits(const struct rule *rule, const uint64_t *chunk_ptr) { struct limit *limit; STAILQ_FOREACH(limit, &rule->limits, link) if (IS_NOTREACHED(limit) && IS_ACTIVE(limit)) if (sub_chunk_from_limit(rule, limit, chunk_ptr) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: sub_chunk_from_limits: sub_chunk_from_limit failed", rule->rule_name, limit->limit_name); return -1; } return 0; } /* * Register a new state for the limit. */ static int new_limit_state(const struct rule *rule, struct limit *limit) { ipa_tm event_tm; struct ipa_limit_state newstate; #ifdef WITH_SUBLIMITS struct sublimit *sublimit; #endif if (rule->debug_limit_init || rule->debug_limit) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: register new limit state", rule->rule_name, limit->limit_name); event_tm = curdate; /* This function can be called at 24:00:00. */ if (newday_flag) fix_240000(&event_tm); /* Set limit status as not reached. */ limit->is_reached = 0; newstate.lim = limit->lim; /* Flush limit's counter. */ newstate.cnt = limit->cnt = UINT64_C(0); newstate.event_date_set = IPA_LIMIT_EVENT_START_SET; newstate.event_date[IPA_LIMIT_EVENT_START] = curdate; if (limit->restart.restart.upto != TEXP_UPTO_NOTSET) { limit->event_tm = event_tm; 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; #ifdef WITH_SUBLIMITS STAILQ_FOREACH(sublimit, &limit->sublimits, link) sublimit->is_reached = 0; #endif if (db_set_limit_state(rule, limit, &newstate, 1) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: new_limit_state: db_set_limit_state failed", rule->rule_name, limit->limit_name); return -1; } return 0; } /* * Restart a limit. */ int restart_limit(const struct rule *rule, struct limit *limit) { struct wpid *wpid; if (rule->debug_limit) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: restart limit", rule->rule_name, limit->limit_name); if (!STAILQ_EMPTY(&limit->restart.cmdl.list)) { /* restart { exec } */ if (rule->debug_exec) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit restarted: run commands (%s)", rule->rule_name, limit->limit_name, sync_exec_msg[limit->restart.cmdl.sync_exec]); wpid = &limit->wpid; if (wpid->pid != 0) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s (restart): previously run process with PID %ld has not exited yet", rule->rule_name, limit->limit_name, (long)wpid->pid); if (exec_cmd_list(rule, wpid, &limit->restart.cmdl, "rule %s, limit %s: restart", rule->rule_name, limit->limit_name) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: restart_limit: exec_cmd_list failed", rule->rule_name, limit->limit_name); return -1; } if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_RESTART_EXEC, &curdate) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: restart_limit: db_limit_event failed", rule->rule_name, limit->limit_name); return -1; } } if (ac_limit_event(rule, limit, IPA_LIMIT_EVENT_RESTART) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: restart_limit: ac_limit_event failed", rule->rule_name, limit->limit_name); return -1; } if (new_limit_state(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: restart_limit: new_limit_state failed", rule->rule_name, limit->limit_name); return -1; } return 0; } /* * Expire a limit. */ int expire_limit(const struct rule *rule, struct limit *limit) { struct wpid *wpid; if (rule->debug_limit) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit expired", rule->rule_name, limit->limit_name); if (!STAILQ_EMPTY(&limit->expire.cmdl.list)) { /* expire { exec } */ if (rule->debug_exec) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit expired: run commands (%s)", rule->rule_name, limit->limit_name, sync_exec_msg[limit->expire.cmdl.sync_exec]); wpid = &limit->wpid; if (wpid->pid != 0) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s (expire): previously run process with PID %ld has not exited yet", rule->rule_name, limit->limit_name, (long)wpid->pid); if (exec_cmd_list(rule, wpid, &limit->expire.cmdl, "rule %s, limit %s: expire", rule->rule_name, limit->limit_name) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: expire_limit: exec_cmd_list failed", rule->rule_name, limit->limit_name); return -1; } if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_EXPIRE_EXEC, &curdate) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: expire_limit: db_limit_event failed", rule->rule_name, limit->limit_name); return -1; } } if (ac_limit_event(rule, limit, IPA_LIMIT_EVENT_EXPIRE) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: expire_limit: ac_limit_event failed", rule->rule_name, limit->limit_name); return -1; } if (new_limit_state(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: expire_limit: new_limit_state failed", rule->rule_name, limit->limit_name); return -1; } return 0; } /* * A limit is reached. */ int reach_limit(const struct rule *rule, struct limit *limit) { ipa_tm event_tm; struct wpid *wpid; /* This function can be called at 24:00:00. */ event_tm = curdate; if (newday_flag) fix_240000(&event_tm); if (rule->debug_limit) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit reached, cnt %s", rule->rule_name, limit->limit_name, cnt_to_buf(&limit->cnt, limit->cnt_type)); /* Set limit status as reached. */ limit->is_reached = 1; if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_REACH, &curdate) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: db_limit_event failed", rule->rule_name, limit->limit_name); return -1; } if (!STAILQ_EMPTY(&limit->reach.list)) { /* reach { exec } */ if (rule->debug_exec) logmsgx(IPA_LOG_INFO, "rule %s, limit %s (reach): run commands (%s)", rule->rule_name, limit->limit_name, sync_exec_msg[limit->reach.sync_exec]); wpid = &limit->wpid; if (wpid->pid != 0) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s (reach): previously run process with PID %ld has not exited yet", rule->rule_name, limit->limit_name, (long)wpid->pid); if (exec_cmd_list(rule, wpid, &limit->reach, "rule %s, limit %s: reach", rule->rule_name, limit->limit_name) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: exec_cmd_list failed", rule->rule_name, limit->limit_name); return -1; } if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_REACH_EXEC, &curdate) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: db_limit_event failed", rule->rule_name, limit->limit_name); return -1; } } if (ac_limit_event(rule, limit, IPA_LIMIT_EVENT_REACH) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: ac_limit_event failed", rule->rule_name, limit->limit_name); return -1; } if (limit->expire.expire.upto != TEXP_UPTO_NOTSET) { /* limit { expire } */ limit->event_tm = event_tm; ipa_tm_texp(&limit->event_tm, &limit->expire.expire); limit_set_event_sec(limit); if (rule->debug_limit) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit will expire at %s", rule->rule_name, limit->limit_name, ipa_tm_to_buf(&limit->event_tm)); if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_EXPIRE, &limit->event_tm) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: db_limit_event failed", rule->rule_name, limit->limit_name); return -1; } if (limit->event_sec == cursec) { /* expire = 0s */ if (expire_limit(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: expire_limit failed", rule->rule_name, limit->limit_name); return -1; } } } else limit->event_sec = SECONDS_IN_WEEK; return 0; } #ifdef WITH_SUBLIMITS /* * A sublimit is reached. */ int reach_sublimit(const struct rule *rule, const struct limit *limit, struct sublimit *sublimit) { struct wpid *wpid; if (rule->debug_limit) logmsgx(IPA_LOG_INFO, "rule %s, limit %s, sublimit %s: sublimit reached, cnt %s", rule->rule_name, limit->limit_name, sublimit->sublimit_name, cnt_to_buf(&limit->cnt, limit->cnt_type)); sublimit->is_reached = 1; if (!STAILQ_EMPTY(&sublimit->reach.list)) { /* reach { exec } */ if (rule->debug_exec) logmsgx(IPA_LOG_INFO, "rule %s, limit %s, sublimit %s (reach): run commands (%s)", rule->rule_name, limit->limit_name, sublimit->sublimit_name, sync_exec_msg[sublimit->reach.sync_exec]); wpid = &sublimit->wpid; if (wpid->pid != 0) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s, sublimit %s (reach): previously run process with PID %ld has not exited yet", rule->rule_name, limit->limit_name, sublimit->sublimit_name, (long)wpid->pid); if (exec_cmd_list(rule, wpid, &sublimit->reach, "rule %s, limit %s, sublimit %s: reach", rule->rule_name, limit->limit_name, sublimit->sublimit_name) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s, sublimit %s: reach_sublimit: exec_cmd_list failed", rule->rule_name, limit->limit_name, sublimit->sublimit_name); return -1; } } return 0; } #endif /* WITH_SUBLIMITS */ /* * Check for rule's limits events. */ int check_limits_events(const struct rule *rule, u_int *check_secp) { u_int check_sec, tmp; struct limit *limit; #ifdef WITH_SUBLIMITS struct sublimit *sublimit; #endif const struct worktime *wt; check_sec = SECONDS_IN_WEEK; /* Hint. */ STAILQ_FOREACH(limit, &rule->limits, link) { wt = limit->worktime; if (IS_INACTIVE(limit)) { /* Limit is inactive. */ if (wt->active_sec <= cursec) { /* It's time to make limit active. */ if (set_limit_active(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "check_limits_events: set_limit_active failed"); return -1; } } else { if (check_sec > wt->active_sec) check_sec = wt->active_sec; continue; /* and don't check any time events for limit. */ } } /* Here limit is active. */ if (IS_REACHED(limit)) { /* Limit has been reached already. */ if (limit->event_sec <= cursec) { /* limit { expire } and it's time to expire limit. */ tmp = ipa_tm_diff(&curdate, &limit->event_tm); if (tmp > sensitive_time) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: expire limit too late: delta %s is greater than \"sensitive_time\" %u seconds", rule->rule_name, limit->limit_name, time_to_buf(tmp), sensitive_time); if (expire_limit(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: check_limits_events: expire_limit failed", rule->rule_name, limit->limit_name); return -1; } } } else { /* Limit is not reached. */ if (db_update_limit(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: check_limits_events: db_update_limit failed", rule->rule_name, limit->limit_name); return -1; } #ifdef WITH_SUBLIMITS STAILQ_FOREACH(sublimit, &limit->sublimits, link) if (IS_NOTREACHED(sublimit)) { /* Sublimit is not reached. */ if (limit->cnt >= sublimit->lim) { /* Sublimit has just been reached. */ if (reach_sublimit(rule, limit, sublimit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s, sublimit %s: check_limits_events: reach_sublimit failed", rule->rule_name, limit->limit_name, sublimit->sublimit_name); return -1; } } } #endif if (limit->cnt >= limit->lim) { /* Limit has just been reached. */ if (reach_limit(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: check_limits_events: reach_limit failed", rule->rule_name, limit->limit_name); return -1; } } else if (limit->event_sec <= cursec) { /* limit { restart } and it's time to restart limit. */ tmp = ipa_tm_diff(&curdate, &limit->event_tm); if (tmp > sensitive_time) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: restart limit too late: delta %s is greater than \"sensitive_time\" %u seconds", rule->rule_name, limit->limit_name, time_to_buf(tmp), sensitive_time); if (restart_limit(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: check_limits_events: restart_limit failed", rule->rule_name, limit->limit_name); return -1; } } } if (IS_INACTIVE(wt)) { /* Limit became inactive. */ if (!newday_flag) { if (set_limit_inactive(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "check_limits_events: set_limit_inactive failed"); return -1; } if (check_sec > wt->active_sec) check_sec = wt->active_sec; } } else { /* Limit is still active. */ if (check_sec > wt->inactive_sec) check_sec = wt->inactive_sec; if (check_sec > limit->event_sec) check_sec = limit->event_sec; } } *check_secp = check_sec; return 0; } /* * Init one limit. */ static int init_limit(const struct rule *rule, struct limit *limit) { const char *db_name; int i, error; char *buf; u_int bit; ipa_tm event_tm; struct ipa_limit_state oldstate, newstate; /* * If init_limit() is called second, third... time, * then limit can be inactive. */ if (IS_INACTIVE(limit)) if (set_limit_active(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "init_limit: set_limit_active failed"); return -1; } error = 0; limit->cnt_neg = UINT64_C(0); if (STAILQ_EMPTY(limit->db_list)) { /* "null" database is used. */ if (rule->debug_limit_init) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: create limit with no database", rule->rule_name, limit->limit_name); goto new_limit_state; } /* Get limit state from databases. */ if (db_get_limit_state(rule, limit, &oldstate, &db_name) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: db_get_limit_state failed", rule->rule_name, limit->limit_name); return -1; } if (db_name == NULL) { logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: any database used for this limit does not support db_get_limit_state function", rule->rule_name, limit->limit_name); goto new_limit_state; } if (rule->debug_limit_init) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: got limit state from database %s", rule->rule_name, limit->limit_name, db_name); /* Check if limit has state in database. */ if (oldstate.lim == UINT64_C(0)) { /* There isn't the old state of the limit. */ if (rule->debug_limit_init) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: database does not has limit state, probably this is a new limit", rule->rule_name, limit->limit_name); goto new_limit_state; } /* Check and init "limit" parameter. */ if (rule->debug_limit_init && limit->lim != oldstate.lim) { buf = mem_strdup(cnt_to_buf(&limit->lim, limit->cnt_type), m_tmp); logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: config limit %s, database limit %s (will use %s value)", rule->rule_name, limit->limit_name, buf != NULL ? buf : "(no memory)", cnt_to_buf(&oldstate.lim, limit->cnt_type), limit->load_limit ? "database" : "config"); mem_free(buf, m_tmp); } if (limit->load_limit) limit->lim = oldstate.lim; /* Check EVENT_START. */ if (!(oldstate.event_date_set & IPA_LIMIT_EVENT_START_SET)) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: cannot find EVENT_START", rule->rule_name, limit->limit_name); goto new_limit_state_error; } /* Check EVENT_UPDATED */ if (!(oldstate.event_date_set & IPA_LIMIT_EVENT_UPDATED_SET)) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: cannot find EVENT_UPDATED", rule->rule_name, limit->limit_name); goto new_limit_state_error; } /* Set limit status as unreached. */ limit->is_reached = 0; /* Check dates in event_date array. */ for (i = 0, bit = 1; i < IPA_LIMIT_EVENT_NUM; bit <<= 1, ++i) if (oldstate.event_date_set & bit) { event_tm = oldstate.event_date[i]; /* Some events could happen at 24:00:00. */ switch (i) { case IPA_LIMIT_EVENT_START: case IPA_LIMIT_EVENT_REACH: case IPA_LIMIT_EVENT_REACH_EXEC: case IPA_LIMIT_EVENT_UPDATED: fix_240000(&event_tm); } if (check_ipa_tm(&event_tm) < 0) { logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: wrong value of LIMIT_EVENT_%s date: %s", rule->rule_name, limit->limit_name, limit_event_msg[i], ipa_tm_to_buf(&event_tm)); goto new_limit_state_error; } switch (i) { case IPA_LIMIT_EVENT_START: case IPA_LIMIT_EVENT_RESTART_EXEC: case IPA_LIMIT_EVENT_REACH: case IPA_LIMIT_EVENT_REACH_EXEC: case IPA_LIMIT_EVENT_UPDATED: if (cmp_ipa_tm(&event_tm, &curdate) > 0) { logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: LIMIT_EVENT_%s %s is greater than current time", rule->rule_name, limit->limit_name, limit_event_msg[i], ipa_tm_to_buf(&event_tm)); goto new_limit_state_error; } } } if (cmp_ipa_tm(&oldstate.event_date[IPA_LIMIT_EVENT_START], &oldstate.event_date[IPA_LIMIT_EVENT_UPDATED]) > 0) { buf = mem_strdup(ipa_tm_to_buf(&oldstate.event_date[IPA_LIMIT_EVENT_START]), m_tmp); logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: EVENT_START %s is greater than EVENT_UPDATED %s", rule->rule_name, limit->limit_name, buf != NULL ? buf : "(no memory)", ipa_tm_to_buf(&oldstate.event_date[IPA_LIMIT_EVENT_UPDATED])); mem_free(buf, m_tmp); goto new_limit_state_error; } if (oldstate.event_date_set & IPA_LIMIT_EVENT_RESTART_EXEC_SET) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: limit has EVENT_RESTART_EXEC, this is wrong", rule->rule_name, limit->limit_name); goto new_limit_state_error; } if (oldstate.event_date_set & IPA_LIMIT_EVENT_EXPIRE_EXEC_SET) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: limit has EVENT_EXPIRE_EXEC, this is wrong", rule->rule_name, limit->limit_name); goto new_limit_state_error; } /* Set start date of limit. */ newstate.event_date_set = IPA_LIMIT_EVENT_START_SET; newstate.event_date[IPA_LIMIT_EVENT_START] = oldstate.event_date[IPA_LIMIT_EVENT_START]; if (oldstate.cnt < oldstate.lim || !(oldstate.event_date_set & IPA_LIMIT_EVENT_REACH_SET)) { /* Limit wasn't reached or wasn't mark as reached with old "limit" parameter. */ newstate.lim = limit->lim; if (oldstate.cnt >= oldstate.lim) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit was reached with old state, but EVENT_REACH was not registered", rule->rule_name, limit->limit_name); if (limit->restart.restart.upto != TEXP_UPTO_NOTSET) { /* The "restart" parameter is used. */ limit->event_tm = oldstate.event_date[IPA_LIMIT_EVENT_START]; fix_240000(&limit->event_tm); if (set_wday(&limit->event_tm) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: set_wday failed", rule->rule_name, limit->limit_name); return -1; } 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; if (!(oldstate.event_date_set & IPA_LIMIT_EVENT_RESTART_SET)) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit has not EVENT_RESTART in database, but it has \"restart\" parameter in config", rule->rule_name, limit->limit_name); else { if (cmp_ipa_tm(&limit->event_tm, &oldstate.event_date[IPA_LIMIT_EVENT_RESTART]) != 0) { buf = mem_strdup(ipa_tm_to_buf(&oldstate.event_date[IPA_LIMIT_EVENT_RESTART]), m_tmp); logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: restart time is changed %s -> %s", rule->rule_name, limit->limit_name, buf != NULL ? buf : "(no memory)", ipa_tm_to_buf(&limit->event_tm)); mem_free(buf, m_tmp); } } 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 { if (oldstate.event_date_set & IPA_LIMIT_EVENT_RESTART_SET) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit has EVENT_RESTART in database, but limit has not \"restart\" parameter in config: forgot about EVENT_RESTART", rule->rule_name, limit->limit_name); limit->event_sec = SECONDS_IN_WEEK; } } else { /* Limit was reached and marked as reached with old "limit" parameter. */ if (oldstate.event_date_set & IPA_LIMIT_EVENT_RESTART_SET) { /* If limit had restart time in previous state, then simply copy it. */ newstate.event_date_set |= IPA_LIMIT_EVENT_RESTART_SET; newstate.event_date[IPA_LIMIT_EVENT_RESTART] = oldstate.event_date[IPA_LIMIT_EVENT_RESTART]; } newstate.lim = oldstate.lim; limit->is_reached = 1; newstate.event_date_set |= IPA_LIMIT_EVENT_REACH_SET; newstate.event_date[IPA_LIMIT_EVENT_REACH] = oldstate.event_date[IPA_LIMIT_EVENT_REACH]; if (oldstate.event_date_set & IPA_LIMIT_EVENT_REACH_EXEC_SET) { /* If limit run any commands, then save this in the new state. */ newstate.event_date_set |= IPA_LIMIT_EVENT_REACH_EXEC_SET; newstate.event_date[IPA_LIMIT_EVENT_REACH_EXEC] = oldstate.event_date[IPA_LIMIT_EVENT_REACH_EXEC]; } if (limit->expire.expire.upto != TEXP_UPTO_NOTSET) { newstate.event_date_set |= IPA_LIMIT_EVENT_EXPIRE_SET; limit->event_tm = newstate.event_date[IPA_LIMIT_EVENT_REACH]; fix_240000(&limit->event_tm); if (set_wday(&limit->event_tm) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: set_wday failed", rule->rule_name, limit->limit_name); return -1; } ipa_tm_texp(&limit->event_tm, &limit->expire.expire); newstate.event_date[IPA_LIMIT_EVENT_EXPIRE] = limit->event_tm; if (!(oldstate.event_date_set & IPA_LIMIT_EVENT_EXPIRE_SET)) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit has not EVENT_EXPIRE in database, but limit has \"expire\" section in config", rule->rule_name, limit->limit_name); else { if (cmp_ipa_tm(&limit->event_tm, &oldstate.event_date[IPA_LIMIT_EVENT_EXPIRE]) != 0) { buf = mem_strdup(ipa_tm_to_buf(&oldstate.event_date[IPA_LIMIT_EVENT_EXPIRE]), m_tmp); logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: expire time is changed %s -> %s", rule->rule_name, limit->limit_name, buf != NULL ? buf : "(no memory)", ipa_tm_to_buf(&limit->event_tm)); mem_free(buf, m_tmp); } } if (rule->debug_limit || rule->debug_limit_init) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: reached limit will expire at %s", rule->rule_name, limit->limit_name, ipa_tm_to_buf(&limit->event_tm)); } else { if (oldstate.event_date_set & IPA_LIMIT_EVENT_EXPIRE_SET) logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit has EVENT_EXPIRE in database, but limit has not \"expire\" section in config: forgot about EVENT_EXPIRE", rule->rule_name, limit->limit_name); limit->event_sec = SECONDS_IN_WEEK; } } /* Copy counter value from the old state. */ limit->cnt = newstate.cnt = oldstate.cnt; if (rule->debug_limit_init) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: set limit state", rule->rule_name, limit->limit_name); if (db_set_limit_state(rule, limit, &newstate, 0) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: db_set_limit_state failed", rule->rule_name, limit->limit_name); return -1; } return 0; new_limit_state_error: error = 1; new_limit_state: if (rule->debug_limit_init || error) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: set new limit state %s", rule->rule_name, limit->limit_name, error ? "(fixing errors found in database)" : ""); if (new_limit_state(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: new_limit_state failed", rule->rule_name, limit->limit_name); return -1; } return 0; } #ifdef WITH_SUBLIMITS /* * Init all sublimits for one limit. */ static void init_sublimits(const struct rule *rule, const struct limit *limit) { struct sublimit *sublimit; STAILQ_FOREACH(sublimit, &limit->sublimits, link) { if (sublimit->lim_per_cent != 0) sublimit->lim = uint64_per_cent(&limit->lim, sublimit->lim_per_cent); /* * If limit is reached, then "limit" parameter is * not used and sublimit is always reached. */ sublimit->is_reached = IS_REACHED(limit) || limit->cnt >= sublimit->lim; } } void init_cmds_in_sublimit(struct sublimit *sublimit) { init_cmd_list(&sublimit->reach); init_cmds_limit(&sublimit->rc[RC_STARTUP]); init_cmds_limit(&sublimit->rc[RC_SHUTDOWN]); } static void set_sync_exec_in_sublimit(struct sublimit *sublimit) { if (sublimit->reach.sync_exec < 0) sublimit->reach.sync_exec = 0; set_sync_exec_cmds_limit(&sublimit->rc[RC_STARTUP]); set_sync_exec_cmds_limit(&sublimit->rc[RC_SHUTDOWN]); } #endif /* WITH_SUBLIMITS */ /* * Init all limits in one rule. */ int init_limits(const struct rule *rule) { struct limit *limit; STAILQ_FOREACH(limit, &rule->limits, link) { if (init_limit(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limits: init_limit failed", rule->rule_name, limit->limit_name); return -1; } #ifdef WITH_SUBLIMITS init_sublimits(rule, limit); #endif } return 0; } /* * Return pointer to limit with the given name. */ struct limit * limit_by_name(const struct rule *rule, const char *name) { struct limit *limit; STAILQ_FOREACH(limit, &rule->limits, link) if (strcmp(name, limit->limit_name) == 0) return limit; return NULL; } /* * This function is called from newday(). */ int limits_newday(struct rule *rule) { u_int check_sec; struct limit *limit; check_sec = SECONDS_IN_WEEK; /* Hint. */ STAILQ_FOREACH(limit, &rule->limits, link) { if (IS_REACHED(limit)) { /* Is reached. */ if (limit->expire.expire.upto != TEXP_UPTO_NOTSET) /* limit { expire } */ limit_set_event_sec(limit); } else { /* Is not reached. */ if (limit->restart.restart.upto != TEXP_UPTO_NOTSET) /* limit { restart } */ limit_set_event_sec(limit); } if (IS_INACTIVE(limit->worktime)) { if (IS_ACTIVE(limit)) if (set_limit_inactive(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "limits_newday: set_limit_inactive failed"); return -1; } if (check_sec > limit->worktime->active_sec) check_sec = limit->worktime->active_sec; } else { if (IS_INACTIVE(limit)) if (set_limit_active(rule, limit) < 0) { logmsgx(IPA_LOG_ERR, "limits_newday: set_limit_active failed"); return -1; } if (check_sec > limit->worktime->inactive_sec) check_sec = limit->worktime->inactive_sec; if (check_sec > limit->event_sec) check_sec = limit->event_sec; } if (rule->check_sec > check_sec) rule->check_sec = check_sec; } return 0; } /* * Copy all limits from list_src to rule. */ int copy_limits(struct rule *rule, const struct limits_list *list_src, int dyn_flag) { struct limit *limit_dst; const struct limit *limit_src; #ifdef WITH_SUBLIMITS struct sublimit *sublimit_dst; const struct sublimit *sublimit_src; #endif STAILQ_FOREACH(limit_src, list_src, link) { if ( (limit_dst = mzone_alloc(limit_mzone)) == NULL) { xlogmsgx(IPA_LOG_ERR, "copy_limits: mzone_alloc failed"); return -1; } /* Copy settings from source limit. */ *limit_dst = *limit_src; /* Init fields, which are pointers to memory, which can't be shared. */ init_cmds_in_limit(limit_dst); #ifdef WITH_SUBLIMITS STAILQ_INIT(&limit_dst->sublimits); #endif limit_dst->rule = rule; /* Link just allocated limit to rule. */ STAILQ_INSERT_TAIL(&rule->limits, limit_dst, link); if (dyn_flag) ++ndynlimits; else ++nstatlimits; /* * If something goes wrong with memory allocation, * previous settings will allow to deinit current rule. */ if (copy_cmd_list(rule, &limit_dst->restart.cmdl, &limit_src->restart.cmdl) < 0) return -1; if (copy_cmd_list(rule, &limit_dst->reach, &limit_src->reach) < 0) return -1; if (copy_cmd_list(rule, &limit_dst->expire.cmdl, &limit_src->expire.cmdl) < 0) return -1; if (copy_cmds_limit(rule, &limit_dst->rc[RC_STARTUP], &limit_src->rc[RC_STARTUP]) < 0) return -1; if (copy_cmds_limit(rule, &limit_dst->rc[RC_SHUTDOWN], &limit_src->rc[RC_SHUTDOWN]) < 0) return -1; #ifdef WITH_SUBLIMITS STAILQ_FOREACH(sublimit_src, &limit_src->sublimits, link) { if ( (sublimit_dst = mzone_alloc(sublimit_mzone)) == NULL) { xlogmsgx(IPA_LOG_ERR, "copy_limits: mzone_alloc failed"); return -1; } /* Copy settings from source sublimit. */ *sublimit_dst = *sublimit_src; /* Init fields, which are pointers to memory, which can't be shared. */ init_cmds_in_sublimit(sublimit_dst); sublimit_dst->limit = limit_dst; /* Link just allocated sublimit to rule. */ STAILQ_INSERT_TAIL(&limit_dst->sublimits, sublimit_dst, link); if (dyn_flag) ++ndynsublimits; else ++nstatsublimits; /* * If something goes wrong with memory allocation, * previous settings will allow to deinit current rule. */ if (copy_cmd_list(rule, &sublimit_dst->reach, &sublimit_src->reach) < 0) return -1; if (copy_cmds_limit(rule, &sublimit_dst->rc[RC_STARTUP], &sublimit_src->rc[RC_STARTUP]) < 0) return -1; if (copy_cmds_limit(rule, &sublimit_dst->rc[RC_SHUTDOWN], &sublimit_src->rc[RC_SHUTDOWN]) < 0) return -1; sublimit_dst->wpid.u.sublimit = sublimit_dst; } #endif /* WITH_SUBLIMITS */ limit_dst->wpid.u.limit = limit_dst; } return 0; } /* * Release memory used by a list of limits, free_mask determines * which part of limit{} structure to free. */ void free_limits(u_int free_mask, struct limits_list *limits, int dyn_flag) { struct limit *limit, *limit_next; #ifdef WITH_SUBLIMITS struct sublimit *sublimit, *sublimit_next; #endif for (limit = STAILQ_FIRST(limits); limit != NULL; limit = limit_next) { limit_next = STAILQ_NEXT(limit, link); if (dyn_flag) --ndynlimits; if (free_mask & RULE_FREE_LIMITS) { mem_free(limit->limit_name, m_anon); mem_free(limit->limit_info, m_parser); } free_cmd_list(&limit->restart.cmdl); free_cmd_list(&limit->reach); free_cmd_list(&limit->expire.cmdl); free_cmds_limit(&limit->rc[RC_STARTUP]); free_cmds_limit(&limit->rc[RC_SHUTDOWN]); # ifdef WITH_SUBLIMITS for (sublimit = STAILQ_FIRST(&limit->sublimits); sublimit != NULL; sublimit = sublimit_next) { sublimit_next = STAILQ_NEXT(sublimit, link); if (dyn_flag) --ndynsublimits; if (free_mask & RULE_FREE_LIMITS) mem_free(sublimit->sublimit_name, m_anon); free_cmd_list(&sublimit->reach); free_cmds_limit(&sublimit->rc[RC_STARTUP]); free_cmds_limit(&sublimit->rc[RC_SHUTDOWN]); mzone_free(sublimit_mzone, sublimit); } # endif /* WITH_SUBLIMITS */ mzone_free(limit_mzone, limit); } } void init_cmds_in_limit(struct limit *limit) { init_cmd_list(&limit->restart.cmdl); init_cmd_list(&limit->reach); init_cmd_list(&limit->expire.cmdl); init_cmds_limit(&limit->rc[RC_STARTUP]); init_cmds_limit(&limit->rc[RC_SHUTDOWN]); } void set_sync_exec_in_limit(struct limit *limit) { #ifdef WITH_SUBLIMITS struct sublimit *sublimit; #endif if (limit->restart.cmdl.sync_exec < 0) limit->restart.cmdl.sync_exec = 0; if (limit->reach.sync_exec < 0) limit->reach.sync_exec = 0; if (limit->expire.cmdl.sync_exec < 0) limit->expire.cmdl.sync_exec = 0; set_sync_exec_cmds_limit(&limit->rc[RC_STARTUP]); set_sync_exec_cmds_limit(&limit->rc[RC_SHUTDOWN]); #ifdef WITH_SUBLIMITS STAILQ_FOREACH(sublimit, &limit->sublimits, link) set_sync_exec_in_sublimit(sublimit); #endif } #endif /* WITH_LIMITS */