aboutsummaryrefslogtreecommitdiffstats
path: root/src/libserver/cfg_utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libserver/cfg_utils.c')
-rw-r--r--src/libserver/cfg_utils.c969
1 files changed, 969 insertions, 0 deletions
diff --git a/src/libserver/cfg_utils.c b/src/libserver/cfg_utils.c
new file mode 100644
index 000000000..2ca846ebd
--- /dev/null
+++ b/src/libserver/cfg_utils.c
@@ -0,0 +1,969 @@
+/*
+ * Copyright (c) 2009-2012, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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 AUTHOR ''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 AUTHOR 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"
+
+#include "cfg_file.h"
+#include "main.h"
+#include "filter.h"
+#include "settings.h"
+#include "classifiers/classifiers.h"
+#include "lua/lua_common.h"
+#include "kvstorage_config.h"
+#include "map.h"
+#include "dynamic_cfg.h"
+
+#define DEFAULT_SCORE 10.0
+
+#define DEFAULT_RLIMIT_NOFILE 2048
+#define DEFAULT_RLIMIT_MAXCORE 0
+#define DEFAULT_MAP_TIMEOUT 10
+
+struct rspamd_ucl_map_cbdata {
+ struct config_file *cfg;
+ GString *buf;
+};
+static gchar* rspamd_ucl_read_cb (rspamd_mempool_t * pool, gchar * chunk, gint len, struct map_cb_data *data);
+static void rspamd_ucl_fin_cb (rspamd_mempool_t * pool, struct map_cb_data *data);
+
+static gboolean
+parse_host_port_priority_strv (rspamd_mempool_t *pool, gchar **tokens,
+ gchar **addr, guint16 *port, guint *priority, guint default_port)
+{
+ gchar *err_str, portbuf[8];
+ const gchar *cur_tok, *cur_port;
+ struct addrinfo hints, *res;
+ guint port_parsed, priority_parsed, saved_errno = errno;
+ gint r;
+ union {
+ struct sockaddr_in v4;
+ struct sockaddr_in6 v6;
+ } addr_holder;
+
+ /* Now try to parse host and write address to ina */
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_socktype = SOCK_STREAM; /* Type of the socket */
+ hints.ai_flags = AI_NUMERICSERV;
+
+ cur_tok = tokens[0];
+
+ if (strcmp (cur_tok, "*v6") == 0) {
+ hints.ai_family = AF_INET6;
+ hints.ai_flags |= AI_PASSIVE;
+ cur_tok = NULL;
+ }
+ else if (strcmp (cur_tok, "*v4") == 0) {
+ hints.ai_family = AF_INET;
+ hints.ai_flags |= AI_PASSIVE;
+ cur_tok = NULL;
+ }
+ else {
+ hints.ai_family = AF_UNSPEC;
+ }
+
+ if (tokens[1] != NULL) {
+ /* Port part */
+ rspamd_strlcpy (portbuf, tokens[1], sizeof (portbuf));
+ cur_port = portbuf;
+ if (port != NULL) {
+ errno = 0;
+ port_parsed = strtoul (tokens[1], &err_str, 10);
+ if (*err_str != '\0' || errno != 0) {
+ msg_warn ("cannot parse port: %s, at symbol %c, error: %s", tokens[1], *err_str, strerror (errno));
+ hints.ai_flags ^= AI_NUMERICSERV;
+ }
+ else if (port_parsed > G_MAXUINT16) {
+ errno = ERANGE;
+ msg_warn ("cannot parse port: %s, error: %s", tokens[1], *err_str, strerror (errno));
+ hints.ai_flags ^= AI_NUMERICSERV;
+ }
+ else {
+ *port = port_parsed;
+ }
+ }
+ if (priority != NULL) {
+ if (port != NULL) {
+ cur_tok = tokens[2];
+ }
+ else {
+ cur_tok = tokens[1];
+ }
+ if (cur_tok != NULL) {
+ /* Priority part */
+ errno = 0;
+ priority_parsed = strtoul (cur_tok, &err_str, 10);
+ if (*err_str != '\0' || errno != 0) {
+ msg_warn ("cannot parse priority: %s, at symbol %c, error: %s", tokens[1], *err_str, strerror (errno));
+ }
+ else {
+ *priority = priority_parsed;
+ }
+ }
+ }
+ }
+ else if (default_port != 0) {
+ rspamd_snprintf (portbuf, sizeof (portbuf), "%ud", default_port);
+ cur_port = portbuf;
+ }
+ else {
+ cur_port = NULL;
+ }
+
+ if ((r = getaddrinfo (cur_tok, cur_port, &hints, &res)) == 0) {
+ memcpy (&addr_holder, res->ai_addr, MIN (sizeof (addr_holder), res->ai_addrlen));
+ if (res->ai_family == AF_INET) {
+ if (pool != NULL) {
+ *addr = rspamd_mempool_alloc (pool, INET_ADDRSTRLEN + 1);
+ }
+ inet_ntop (res->ai_family, &addr_holder.v4.sin_addr, *addr, INET_ADDRSTRLEN + 1);
+ }
+ else {
+ if (pool != NULL) {
+ *addr = rspamd_mempool_alloc (pool, INET6_ADDRSTRLEN + 1);
+ }
+ inet_ntop (res->ai_family, &addr_holder.v6.sin6_addr, *addr, INET6_ADDRSTRLEN + 1);
+ }
+ freeaddrinfo (res);
+ }
+ else {
+ msg_err ("address resolution for %s failed: %s", tokens[0], gai_strerror (r));
+ goto err;
+ }
+
+ /* Restore errno */
+ errno = saved_errno;
+ return TRUE;
+
+err:
+ errno = saved_errno;
+ return FALSE;
+}
+
+gboolean
+parse_host_port_priority (rspamd_mempool_t *pool, const gchar *str, gchar **addr, guint16 *port, guint *priority)
+{
+ gchar **tokens;
+ gboolean ret;
+
+ tokens = g_strsplit_set (str, ":", 0);
+ if (!tokens || !tokens[0]) {
+ return FALSE;
+ }
+
+ ret = parse_host_port_priority_strv (pool, tokens, addr, port, priority, 0);
+
+ g_strfreev (tokens);
+
+ return ret;
+}
+
+gboolean
+parse_host_port (rspamd_mempool_t *pool, const gchar *str, gchar **addr, guint16 *port)
+{
+ return parse_host_port_priority (pool, str, addr, port, NULL);
+}
+
+gboolean
+parse_host_priority (rspamd_mempool_t *pool, const gchar *str, gchar **addr, guint *priority)
+{
+ return parse_host_port_priority (pool, str, addr, NULL, priority);
+}
+
+gboolean
+parse_bind_line (struct config_file *cfg, struct worker_conf *cf, const gchar *str)
+{
+ struct rspamd_worker_bind_conf *cnf;
+ gchar **tokens, *tmp, *err;
+ gboolean ret = TRUE;
+
+ if (str == NULL) {
+ return FALSE;
+ }
+
+ tokens = g_strsplit_set (str, ":", 0);
+ if (!tokens || !tokens[0]) {
+ return FALSE;
+ }
+
+ cnf = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_worker_bind_conf));
+ cnf->bind_port = DEFAULT_BIND_PORT;
+ cnf->bind_host = rspamd_mempool_strdup (cfg->cfg_pool, str);
+ cnf->ai = AF_UNSPEC;
+
+ if (*tokens[0] == '/' || *tokens[0] == '.') {
+ cnf->ai = AF_UNIX;
+ LL_PREPEND (cf->bind_conf, cnf);
+ return TRUE;
+ }
+ else if (strcmp (tokens[0], "*") == 0) {
+ /* We need to add two listen entries: one for ipv4 and one for ipv6 */
+ tmp = tokens[0];
+ tokens[0] = "*v4";
+ cnf->ai = AF_INET;
+ if ((ret = parse_host_port_priority_strv (cfg->cfg_pool, tokens,
+ &cnf->bind_host, &cnf->bind_port, NULL, DEFAULT_BIND_PORT))) {
+ LL_PREPEND (cf->bind_conf, cnf);
+ }
+ cnf = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_worker_bind_conf));
+ cnf->bind_port = DEFAULT_BIND_PORT;
+ cnf->bind_host = rspamd_mempool_strdup (cfg->cfg_pool, str);
+ cnf->ai = AF_INET6;
+ tokens[0] = "*v6";
+ if ((ret &= parse_host_port_priority_strv (cfg->cfg_pool, tokens,
+ &cnf->bind_host, &cnf->bind_port, NULL, DEFAULT_BIND_PORT))) {
+ LL_PREPEND (cf->bind_conf, cnf);
+ }
+ tokens[0] = tmp;
+ }
+ else if (strcmp (tokens[0], "systemd") == 0) {
+ /* The actual socket will be passed by systemd environment */
+ cnf->bind_host = rspamd_mempool_strdup (cfg->cfg_pool, str);
+ cnf->ai = strtoul (tokens[1], &err, 10);
+ cnf->is_systemd = TRUE;
+ if (err == NULL || *err == '\0') {
+ LL_PREPEND (cf->bind_conf, cnf);
+ }
+ }
+ else {
+ if ((ret = parse_host_port_priority_strv (cfg->cfg_pool, tokens,
+ &cnf->bind_host, &cnf->bind_port, NULL, DEFAULT_BIND_PORT))) {
+ LL_PREPEND (cf->bind_conf, cnf);
+ }
+ }
+
+ g_strfreev (tokens);
+
+ return ret;
+}
+
+void
+init_defaults (struct config_file *cfg)
+{
+
+ cfg->memcached_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
+ cfg->memcached_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
+ cfg->memcached_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
+ cfg->memcached_protocol = TCP_TEXT;
+
+ cfg->dns_timeout = 1000;
+ cfg->dns_retransmits = 5;
+ /* After 20 errors do throttling for 10 seconds */
+ cfg->dns_throttling_errors = 20;
+ cfg->dns_throttling_time = 10000;
+ /* 16 sockets per DNS server */
+ cfg->dns_io_per_server = 16;
+
+ cfg->statfile_sync_interval = 60000;
+ cfg->statfile_sync_timeout = 20000;
+
+ /* 20 Kb */
+ cfg->max_diff = 20480;
+
+ cfg->metrics = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ cfg->c_modules = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ cfg->composite_symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ cfg->classifiers_symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ cfg->cfg_params = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ cfg->metrics_symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+
+ cfg->map_timeout = DEFAULT_MAP_TIMEOUT;
+
+ cfg->log_level = G_LOG_LEVEL_WARNING;
+ cfg->log_extended = TRUE;
+
+ init_settings (cfg);
+
+}
+
+void
+free_config (struct config_file *cfg)
+{
+ GList *cur;
+ struct symbols_group *gr;
+
+ remove_all_maps (cfg);
+ ucl_obj_unref (cfg->rcl_obj);
+ g_hash_table_remove_all (cfg->metrics);
+ g_hash_table_unref (cfg->metrics);
+ g_hash_table_remove_all (cfg->c_modules);
+ g_hash_table_unref (cfg->c_modules);
+ g_hash_table_remove_all (cfg->composite_symbols);
+ g_hash_table_unref (cfg->composite_symbols);
+ g_hash_table_remove_all (cfg->cfg_params);
+ g_hash_table_unref (cfg->cfg_params);
+ g_hash_table_destroy (cfg->metrics_symbols);
+ g_hash_table_destroy (cfg->classifiers_symbols);
+ /* Free symbols groups */
+ cur = cfg->symbols_groups;
+ while (cur) {
+ gr = cur->data;
+ if (gr->symbols) {
+ g_list_free (gr->symbols);
+ }
+ cur = g_list_next (cur);
+ }
+ if (cfg->symbols_groups) {
+ g_list_free (cfg->symbols_groups);
+ }
+
+ if (cfg->checksum) {
+ g_free (cfg->checksum);
+ }
+ g_list_free (cfg->classifiers);
+ g_list_free (cfg->metrics_list);
+ rspamd_mempool_delete (cfg->cfg_pool);
+}
+
+const ucl_object_t *
+get_module_opt (struct config_file *cfg, const gchar *module_name, const gchar *opt_name)
+{
+ const ucl_object_t *res = NULL, *sec;
+
+ sec = ucl_obj_get_key (cfg->rcl_obj, module_name);
+ if (sec != NULL) {
+ res = ucl_obj_get_key (sec, opt_name);
+ }
+
+ return res;
+}
+
+guint64
+parse_limit (const gchar *limit, guint len)
+{
+ guint64 result = 0;
+ const gchar *err_str;
+
+ if (!limit || *limit == '\0' || len == 0) {
+ return 0;
+ }
+
+ errno = 0;
+ result = strtoull (limit, (gchar **)&err_str, 10);
+
+ if (*err_str != '\0') {
+ /* Megabytes */
+ if (*err_str == 'm' || *err_str == 'M') {
+ result *= 1048576L;
+ }
+ /* Kilobytes */
+ else if (*err_str == 'k' || *err_str == 'K') {
+ result *= 1024;
+ }
+ /* Gigabytes */
+ else if (*err_str == 'g' || *err_str == 'G') {
+ result *= 1073741824L;
+ }
+ else if (len > 0 && err_str - limit != (gint)len) {
+ msg_warn ("invalid limit value '%s' at position '%s'", limit, err_str);
+ result = 0;
+ }
+ }
+
+ return result;
+}
+
+gchar
+parse_flag (const gchar *str)
+{
+ guint len;
+ gchar c;
+
+ if (!str || !*str) {
+ return -1;
+ }
+
+ len = strlen (str);
+
+ switch (len) {
+ case 1:
+ c = g_ascii_tolower (*str);
+ if (c == 'y' || c == '1') {
+ return 1;
+ }
+ else if (c == 'n' || c == '0') {
+ return 0;
+ }
+ break;
+ case 2:
+ if (g_ascii_strncasecmp (str, "no", len) == 0) {
+ return 0;
+ }
+ else if (g_ascii_strncasecmp (str, "on", len) == 0) {
+ return 1;
+ }
+ break;
+ case 3:
+ if (g_ascii_strncasecmp (str, "yes", len) == 0) {
+ return 1;
+ }
+ else if (g_ascii_strncasecmp (str, "off", len) == 0) {
+ return 0;
+ }
+ break;
+ case 4:
+ if (g_ascii_strncasecmp (str, "true", len) == 0) {
+ return 1;
+ }
+ break;
+ case 5:
+ if (g_ascii_strncasecmp (str, "false", len) == 0) {
+ return 0;
+ }
+ break;
+ }
+
+ return -1;
+}
+
+gboolean
+get_config_checksum (struct config_file *cfg)
+{
+ gint fd;
+ void *map;
+ struct stat st;
+
+ /* Compute checksum for config file that should be used by xml dumper */
+ if ((fd = open (cfg->cfg_name, O_RDONLY)) == -1) {
+ msg_err ("config file %s is no longer available, cannot calculate checksum");
+ return FALSE;
+ }
+ if (stat (cfg->cfg_name, &st) == -1) {
+ msg_err ("cannot stat %s: %s", cfg->cfg_name, strerror (errno));
+ return FALSE;
+ }
+
+ /* Now mmap this file to simplify reading process */
+ if ((map = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ msg_err ("cannot mmap %s: %s", cfg->cfg_name, strerror (errno));
+ close (fd);
+ return FALSE;
+ }
+ close (fd);
+
+ /* Get checksum for a file */
+ cfg->checksum = g_compute_checksum_for_string (G_CHECKSUM_MD5, map, st.st_size);
+ munmap (map, st.st_size);
+
+ return TRUE;
+}
+/*
+ * Perform post load actions
+ */
+void
+post_load_config (struct config_file *cfg)
+{
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec ts;
+#endif
+ struct metric *def_metric;
+
+#ifdef HAVE_CLOCK_GETTIME
+#ifdef HAVE_CLOCK_PROCESS_CPUTIME_ID
+ clock_getres (CLOCK_PROCESS_CPUTIME_ID, &ts);
+# elif defined(HAVE_CLOCK_VIRTUAL)
+ clock_getres (CLOCK_VIRTUAL, &ts);
+# else
+ clock_getres (CLOCK_REALTIME, &ts);
+# endif
+
+ cfg->clock_res = (gint)log10 (1000000 / ts.tv_nsec);
+ if (cfg->clock_res < 0) {
+ cfg->clock_res = 0;
+ }
+ if (cfg->clock_res > 3) {
+ cfg->clock_res = 3;
+ }
+#else
+ /* For gettimeofday */
+ cfg->clock_res = 1;
+#endif
+
+ if ((def_metric = g_hash_table_lookup (cfg->metrics, DEFAULT_METRIC)) == NULL) {
+ def_metric = check_metric_conf (cfg, NULL);
+ def_metric->name = DEFAULT_METRIC;
+ def_metric->actions[METRIC_ACTION_REJECT].score = DEFAULT_SCORE;
+ cfg->metrics_list = g_list_prepend (cfg->metrics_list, def_metric);
+ g_hash_table_insert (cfg->metrics, DEFAULT_METRIC, def_metric);
+ }
+
+ cfg->default_metric = def_metric;
+
+ /* Lua options */
+ (void)lua_post_load_config (cfg);
+ init_dynamic_config (cfg);
+}
+
+#if 0
+void
+parse_err (const gchar *fmt, ...)
+{
+ va_list aq;
+ gchar logbuf[BUFSIZ], readbuf[32];
+ gint r;
+
+ va_start (aq, fmt);
+ rspamd_strlcpy (readbuf, yytext, sizeof (readbuf));
+
+ r = snprintf (logbuf, sizeof (logbuf), "config file parse error! line: %d, text: %s, reason: ", yylineno, readbuf);
+ r += vsnprintf (logbuf + r, sizeof (logbuf) - r, fmt, aq);
+
+ va_end (aq);
+ g_critical ("%s", logbuf);
+}
+
+void
+parse_warn (const gchar *fmt, ...)
+{
+ va_list aq;
+ gchar logbuf[BUFSIZ], readbuf[32];
+ gint r;
+
+ va_start (aq, fmt);
+ rspamd_strlcpy (readbuf, yytext, sizeof (readbuf));
+
+ r = snprintf (logbuf, sizeof (logbuf), "config file parse warning! line: %d, text: %s, reason: ", yylineno, readbuf);
+ r += vsnprintf (logbuf + r, sizeof (logbuf) - r, fmt, aq);
+
+ va_end (aq);
+ g_warning ("%s", logbuf);
+}
+#endif
+
+void
+unescape_quotes (gchar *line)
+{
+ gchar *c = line, *t;
+
+ while (*c) {
+ if (*c == '\\' && *(c + 1) == '"') {
+ t = c;
+ while (*t) {
+ *t = *(t + 1);
+ t++;
+ }
+ }
+ c++;
+ }
+}
+
+GList *
+parse_comma_list (rspamd_mempool_t * pool, const gchar *line)
+{
+ GList *res = NULL;
+ const gchar *c, *p;
+ gchar *str;
+
+ c = line;
+ p = c;
+
+ while (*p) {
+ if (*p == ',' && *c != *p) {
+ str = rspamd_mempool_alloc (pool, p - c + 1);
+ rspamd_strlcpy (str, c, p - c + 1);
+ res = g_list_prepend (res, str);
+ /* Skip spaces */
+ while (g_ascii_isspace (*(++p)));
+ c = p;
+ continue;
+ }
+ p++;
+ }
+ if (res != NULL) {
+ rspamd_mempool_add_destructor (pool, (rspamd_mempool_destruct_t) g_list_free, res);
+ }
+
+ return res;
+}
+
+struct classifier_config *
+check_classifier_conf (struct config_file *cfg, struct classifier_config *c)
+{
+ if (c == NULL) {
+ c = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct classifier_config));
+ }
+ if (c->opts == NULL) {
+ c->opts = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) g_hash_table_destroy, c->opts);
+ }
+ if (c->labels == NULL) {
+ c->labels = g_hash_table_new_full (rspamd_str_hash, rspamd_str_equal, NULL, (GDestroyNotify)g_list_free);
+ rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) g_hash_table_destroy, c->labels);
+ }
+
+ return c;
+}
+
+struct statfile*
+check_statfile_conf (struct config_file *cfg, struct statfile *c)
+{
+ if (c == NULL) {
+ c = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct statfile));
+ }
+
+ return c;
+}
+
+struct metric *
+check_metric_conf (struct config_file *cfg, struct metric *c)
+{
+ int i;
+ if (c == NULL) {
+ c = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct metric));
+ c->grow_factor = 1.0;
+ c->symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ c->descriptions = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i ++) {
+ c->actions[i].score = -1.0;
+ }
+ rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) g_hash_table_destroy, c->symbols);
+ rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) g_hash_table_destroy, c->descriptions);
+ }
+
+ return c;
+}
+
+struct worker_conf *
+check_worker_conf (struct config_file *cfg, struct worker_conf *c)
+{
+ if (c == NULL) {
+ c = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct worker_conf));
+ c->params = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
+ c->active_workers = g_queue_new ();
+ rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)g_hash_table_destroy, c->params);
+ rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)g_queue_free, c->active_workers);
+#ifdef HAVE_SC_NPROCESSORS_ONLN
+ c->count = sysconf (_SC_NPROCESSORS_ONLN);
+#else
+ c->count = DEFAULT_WORKERS_NUM;
+#endif
+ c->rlimit_nofile = DEFAULT_RLIMIT_NOFILE;
+ c->rlimit_maxcore = DEFAULT_RLIMIT_MAXCORE;
+ }
+
+ return c;
+}
+
+
+static bool
+rspamd_include_map_handler (const guchar *data, gsize len, void* ud)
+{
+ struct config_file *cfg = (struct config_file *)ud;
+ struct rspamd_ucl_map_cbdata *cbdata, **pcbdata;
+ gchar *map_line;
+
+ map_line = rspamd_mempool_alloc (cfg->cfg_pool, len + 1);
+ rspamd_strlcpy (map_line, data, len + 1);
+
+ cbdata = g_malloc (sizeof (struct rspamd_ucl_map_cbdata));
+ pcbdata = g_malloc (sizeof (struct rspamd_ucl_map_cbdata *));
+ cbdata->buf = NULL;
+ cbdata->cfg = cfg;
+ *pcbdata = cbdata;
+
+ return add_map (cfg, map_line, "ucl include", rspamd_ucl_read_cb, rspamd_ucl_fin_cb, (void **)pcbdata);
+}
+
+/*
+ * Variables:
+ * $CONFDIR - configuration directory
+ * $RUNDIR - local states directory
+ * $DBDIR - databases dir
+ * $LOGDIR - logs dir
+ * $PLUGINSDIR - pluggins dir
+ * $PREFIX - installation prefix
+ * $VERSION - rspamd version
+ */
+
+#define RSPAMD_CONFDIR_MACRO "CONFDIR"
+#define RSPAMD_RUNDIR_MACRO "RUNDIR"
+#define RSPAMD_DBDIR_MACRO "DBDIR"
+#define RSPAMD_LOGDIR_MACRO "LOGDIR"
+#define RSPAMD_PLUGINSDIR_MACRO "PLUGINSDIR"
+#define RSPAMD_PREFIX_MACRO "PREFIX"
+#define RSPAMD_VERSION_MACRO "VERSION"
+
+static void
+rspamd_ucl_add_conf_variables (struct ucl_parser *parser)
+{
+ ucl_parser_register_variable (parser, RSPAMD_CONFDIR_MACRO, RSPAMD_CONFDIR);
+ ucl_parser_register_variable (parser, RSPAMD_RUNDIR_MACRO, RSPAMD_RUNDIR);
+ ucl_parser_register_variable (parser, RSPAMD_DBDIR_MACRO, RSPAMD_DBDIR);
+ ucl_parser_register_variable (parser, RSPAMD_LOGDIR_MACRO, RSPAMD_LOGDIR);
+ ucl_parser_register_variable (parser, RSPAMD_PLUGINSDIR_MACRO, RSPAMD_PLUGINSDIR);
+ ucl_parser_register_variable (parser, RSPAMD_PREFIX_MACRO, RSPAMD_PREFIX);
+ ucl_parser_register_variable (parser, RSPAMD_VERSION_MACRO, RVERSION);
+}
+
+static void
+rspamd_ucl_add_conf_macros (struct ucl_parser *parser, struct config_file *cfg)
+{
+ ucl_parser_register_macro (parser, "include_map", rspamd_include_map_handler, cfg);
+}
+
+gboolean
+read_rspamd_config (struct config_file *cfg, const gchar *filename,
+ const gchar *convert_to, rspamd_rcl_section_fin_t logger_fin,
+ gpointer logger_ud)
+{
+ struct stat st;
+ gint fd;
+ gchar *data;
+ GError *err = NULL;
+ struct rspamd_rcl_section *top, *logger;
+ gboolean res;
+ struct ucl_parser *parser;
+
+ if (stat (filename, &st) == -1) {
+ msg_err ("cannot stat %s: %s", filename, strerror (errno));
+ return FALSE;
+ }
+ if ((fd = open (filename, O_RDONLY)) == -1) {
+ msg_err ("cannot open %s: %s", filename, strerror (errno));
+ return FALSE;
+
+ }
+ /* Now mmap this file to simplify reading process */
+ if ((data = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ msg_err ("cannot mmap %s: %s", filename, strerror (errno));
+ close (fd);
+ return FALSE;
+ }
+ close (fd);
+
+ parser = ucl_parser_new (0);
+ rspamd_ucl_add_conf_variables (parser);
+ rspamd_ucl_add_conf_macros (parser, cfg);
+ if (!ucl_parser_add_chunk (parser, data, st.st_size)) {
+ msg_err ("ucl parser error: %s", ucl_parser_get_error (parser));
+ ucl_parser_free (parser);
+ munmap (data, st.st_size);
+ return FALSE;
+ }
+ munmap (data, st.st_size);
+ cfg->rcl_obj = ucl_parser_get_object (parser);
+ ucl_parser_free (parser);
+ res = TRUE;
+
+ if (!res) {
+ return FALSE;
+ }
+
+ top = rspamd_rcl_config_init ();
+ err = NULL;
+
+ HASH_FIND_STR(top, "logging", logger);
+ if (logger != NULL) {
+ logger->fin = logger_fin;
+ logger->fin_ud = logger_ud;
+ }
+
+ if (!rspamd_read_rcl_config (top, cfg, cfg->rcl_obj, &err)) {
+ msg_err ("rcl parse error: %s", err->message);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+symbols_classifiers_callback (gpointer key, gpointer value, gpointer ud)
+{
+ struct config_file *cfg = ud;
+
+ register_virtual_symbol (&cfg->cache, key, 1.0);
+}
+
+void
+insert_classifier_symbols (struct config_file *cfg)
+{
+ g_hash_table_foreach (cfg->classifiers_symbols, symbols_classifiers_callback, cfg);
+}
+
+struct classifier_config*
+find_classifier_conf (struct config_file *cfg, const gchar *name)
+{
+ GList *cur;
+ struct classifier_config *cf;
+
+ if (name == NULL) {
+ return NULL;
+ }
+
+ cur = cfg->classifiers;
+ while (cur) {
+ cf = cur->data;
+
+ if (g_ascii_strcasecmp (cf->classifier->name, name) == 0) {
+ return cf;
+ }
+
+ cur = g_list_next (cur);
+ }
+
+ return NULL;
+}
+
+gboolean
+check_classifier_statfiles (struct classifier_config *cf)
+{
+ struct statfile *st;
+ gboolean has_other = FALSE, res = FALSE, cur_class;
+ GList *cur;
+
+ /* First check classes directly */
+ cur = cf->statfiles;
+ while (cur) {
+ st = cur->data;
+ if (!has_other) {
+ cur_class = st->is_spam;
+ has_other = TRUE;
+ }
+ else {
+ if (cur_class != st->is_spam) {
+ return TRUE;
+ }
+ }
+
+ cur = g_list_next (cur);
+ }
+
+ if (!has_other) {
+ /* We have only one statfile */
+ return FALSE;
+ }
+ /* We have not detected any statfile that has different class, so turn on euristic based on symbol's name */
+ has_other = FALSE;
+ cur = cf->statfiles;
+ while (cur) {
+ st = cur->data;
+ if (rspamd_strncasestr (st->symbol, "spam", -1) != NULL) {
+ st->is_spam = TRUE;
+ }
+ else if (rspamd_strncasestr (st->symbol, "ham", -1) != NULL) {
+ st->is_spam = FALSE;
+ }
+
+ if (!has_other) {
+ cur_class = st->is_spam;
+ has_other = TRUE;
+ }
+ else {
+ if (cur_class != st->is_spam) {
+ res = TRUE;
+ }
+ }
+
+ cur = g_list_next (cur);
+ }
+
+ return res;
+}
+
+static gchar*
+rspamd_ucl_read_cb (rspamd_mempool_t * pool, gchar * chunk, gint len, struct map_cb_data *data)
+{
+ struct rspamd_ucl_map_cbdata *cbdata = data->cur_data, *prev;
+
+ if (cbdata == NULL) {
+ cbdata = g_malloc (sizeof (struct rspamd_ucl_map_cbdata));
+ prev = data->prev_data;
+ cbdata->buf = g_string_sized_new (BUFSIZ);
+ cbdata->cfg = prev->cfg;
+ data->cur_data = cbdata;
+ }
+ g_string_append_len (cbdata->buf, chunk, len);
+
+ /* Say not to copy any part of this buffer */
+ return NULL;
+}
+
+static void
+rspamd_ucl_fin_cb (rspamd_mempool_t * pool, struct map_cb_data *data)
+{
+ struct rspamd_ucl_map_cbdata *cbdata = data->cur_data, *prev = data->prev_data;
+ ucl_object_t *obj;
+ struct ucl_parser *parser;
+ guint32 checksum;
+
+ if (prev != NULL) {
+ if (prev->buf != NULL) {
+ g_string_free (prev->buf, TRUE);
+ }
+ g_free (prev);
+ }
+
+ if (cbdata == NULL) {
+ msg_err ("map fin error: new data is NULL");
+ return;
+ }
+
+ checksum = murmur32_hash (cbdata->buf->str, cbdata->buf->len);
+ if (data->map->checksum != checksum) {
+ /* New data available */
+ parser = ucl_parser_new (0);
+ if (!ucl_parser_add_chunk (parser, cbdata->buf->str, cbdata->buf->len)) {
+ msg_err ("cannot parse map %s: %s", data->map->uri, ucl_parser_get_error (parser));
+ ucl_parser_free (parser);
+ }
+ else {
+ obj = ucl_parser_get_object (parser);
+ ucl_parser_free (parser);
+ /* XXX: add replace objects code */
+ ucl_object_unref (obj);
+ data->map->checksum = checksum;
+ }
+ }
+ else {
+ msg_info ("do not reload map %s, checksum is the same: %d", data->map->uri, checksum);
+ }
+}
+
+gboolean
+rspamd_parse_ip_list (const gchar *ip_list, radix_tree_t **tree)
+{
+ gchar **strvec, **cur;
+ struct in_addr ina;
+ guint32 mask;
+
+ strvec = g_strsplit_set (ip_list, ",", 0);
+ cur = strvec;
+
+ while (*cur != NULL) {
+ /* XXX: handle only ipv4 addresses */
+ if (parse_ipmask_v4 (*cur, &ina, &mask)) {
+ if (*tree == NULL) {
+ *tree = radix_tree_create ();
+ }
+ radix32tree_add (*tree, htonl (ina.s_addr), mask, 1);
+ }
+ cur ++;
+ }
+
+ return (*tree != NULL);
+}
+
+/*
+ * vi:ts=4
+ */