/* * Copyright (c) 2009, Rambler media * 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 Rambler media ''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 Rambler 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. */ /* * Read and write rspamd dynamic parameters from xml files */ #include "config.h" #include "cfg_xml.h" #include "main.h" #include "logger.h" #include "util.h" #include "classifiers/classifiers.h" #include "tokenizers/tokenizers.h" #include "view.h" #include "map.h" #include "expressions.h" #include "settings.h" #include "lua/lua_common.h" /* Maximum attributes for param */ #define MAX_PARAM 64 #define EOL "\n" #define NULL_ATTR \ { \ NULL, \ NULL, \ 0, \ NULL \ } \ #define NULL_DEF_ATTR \ { \ NULL, \ 0, \ NULL \ } \ enum xml_config_section { XML_SECTION_MAIN, XML_SECTION_OPTIONS, XML_SECTION_LOGGING, XML_SECTION_WORKER, XML_SECTION_METRIC, XML_SECTION_CLASSIFIER, XML_SECTION_STATFILE, XML_SECTION_MODULE, XML_SECTION_MODULES, XML_SECTION_VIEW }; struct xml_config_param { const gchar *name; element_handler_func handler; gint offset; gpointer user_data; }; struct xml_default_config_param { element_default_handler_func handler; gint offset; gpointer user_data; }; struct xml_parser_rule { enum xml_config_section section; struct xml_config_param params[MAX_PARAM]; struct xml_default_config_param default_param; }; struct xml_subparser { enum xml_read_state state; const GMarkupParser *parser; gpointer user_data; void (*fin_func)(gpointer ud); }; /* Here we describes our basic grammar */ static struct xml_parser_rule grammar[] = { { XML_SECTION_MAIN, { { "pidfile", xml_handle_string, G_STRUCT_OFFSET (struct config_file, pid_file), NULL }, { "lua", handle_lua, 0, NULL }, { "raw_mode", xml_handle_boolean, G_STRUCT_OFFSET (struct config_file, raw_mode), NULL }, { "check_attachements", xml_handle_boolean, G_STRUCT_OFFSET (struct config_file, check_text_attachements), NULL }, { "tempdir", xml_handle_string, G_STRUCT_OFFSET (struct config_file, temp_dir), NULL }, { "checksum", xml_handle_string, G_STRUCT_OFFSET (struct config_file, dump_checksum), NULL }, { "statfile_pool_size", xml_handle_size, G_STRUCT_OFFSET (struct config_file, max_statfile_size), NULL }, { "filters", xml_handle_string, G_STRUCT_OFFSET (struct config_file, filters_str), NULL }, { "variable", handle_variable, 0, NULL }, { "composite", handle_composite, 0, NULL }, { "user_settings", handle_user_settings, 0, NULL }, { "domain_settings", handle_domain_settings, 0, NULL }, { "cache_file", xml_handle_string, G_STRUCT_OFFSET (struct config_file, cache_filename), NULL }, { "dns_timeout", xml_handle_seconds, G_STRUCT_OFFSET (struct config_file, dns_timeout), NULL }, { "dns_retransmits", xml_handle_uint32, G_STRUCT_OFFSET (struct config_file, dns_retransmits), NULL }, { "dns_throttling_errors", xml_handle_uint32, G_STRUCT_OFFSET (struct config_file, dns_throttling_errors), NULL }, { "dns_throttling_time", xml_handle_seconds, G_STRUCT_OFFSET (struct config_file, dns_throttling_time), NULL }, NULL_ATTR }, NULL_DEF_ATTR }, { XML_SECTION_OPTIONS, { { "statfile_pool_size", xml_handle_size, G_STRUCT_OFFSET (struct config_file, max_statfile_size), NULL }, { "user_settings", handle_user_settings, 0, NULL }, { "domain_settings", handle_domain_settings, 0, NULL }, { "cache_file", xml_handle_string, G_STRUCT_OFFSET (struct config_file, cache_filename), NULL }, { "dns_timeout", xml_handle_seconds, G_STRUCT_OFFSET (struct config_file, dns_timeout), NULL }, { "dns_retransmits", xml_handle_uint32, G_STRUCT_OFFSET (struct config_file, dns_retransmits), NULL }, { "dns_nameserver", options_handle_nameserver, 0, NULL }, { "raw_mode", xml_handle_boolean, G_STRUCT_OFFSET (struct config_file, raw_mode), NULL }, { "one_shot", xml_handle_boolean, G_STRUCT_OFFSET (struct config_file, one_shot_mode), NULL }, { "check_attachements", xml_handle_boolean, G_STRUCT_OFFSET (struct config_file, check_text_attachements), NULL }, { "tempdir", xml_handle_string, G_STRUCT_OFFSET (struct config_file, temp_dir), NULL }, { "pidfile", xml_handle_string, G_STRUCT_OFFSET (struct config_file, pid_file), NULL }, { "filters", xml_handle_string, G_STRUCT_OFFSET (struct config_file, filters_str), NULL }, { "sync_interval", xml_handle_seconds, G_STRUCT_OFFSET (struct config_file, statfile_sync_interval), NULL }, { "sync_timeout", xml_handle_seconds, G_STRUCT_OFFSET (struct config_file, statfile_sync_timeout), NULL }, { "max_diff", xml_handle_size, G_STRUCT_OFFSET (struct config_file, max_diff), NULL }, NULL_ATTR }, NULL_DEF_ATTR }, { XML_SECTION_LOGGING, { { "type", handle_log_type, 0, NULL }, { "level", handle_log_level, 0, NULL }, { "log_urls", xml_handle_boolean, G_STRUCT_OFFSET (struct config_file, log_urls), NULL }, { "log_buffer", xml_handle_uint32, G_STRUCT_OFFSET (struct config_file, log_buf_size), NULL }, { "debug_ip", xml_handle_string, G_STRUCT_OFFSET (struct config_file, debug_ip_map), NULL }, { "debug_symbols", xml_handle_string_list, G_STRUCT_OFFSET (struct config_file, debug_symbols), NULL }, { "log_color", xml_handle_boolean, G_STRUCT_OFFSET (struct config_file, log_color), NULL }, NULL_ATTR }, NULL_DEF_ATTR }, { XML_SECTION_WORKER, { { "type", worker_handle_type, 0, NULL }, { "bind_socket", worker_handle_bind, 0, NULL }, { "count", xml_handle_uint16, G_STRUCT_OFFSET (struct worker_conf, count), NULL }, { "maxfiles", xml_handle_uint32, G_STRUCT_OFFSET (struct worker_conf, rlimit_nofile), NULL }, { "maxcore", xml_handle_uint32, G_STRUCT_OFFSET (struct worker_conf, rlimit_maxcore), NULL }, NULL_ATTR }, { worker_handle_param, 0, NULL } }, { XML_SECTION_METRIC, { { "name", xml_handle_string, G_STRUCT_OFFSET (struct metric, name), NULL }, { "grow_factor", xml_handle_double, G_STRUCT_OFFSET (struct metric, grow_factor), NULL }, { "required_score", xml_handle_double, G_STRUCT_OFFSET (struct metric, required_score), NULL }, { "reject_score", xml_handle_double, G_STRUCT_OFFSET (struct metric, reject_score), NULL }, { "subject", xml_handle_string, G_STRUCT_OFFSET (struct metric, subject), NULL }, { "symbol", handle_metric_symbol, 0, NULL }, { "action", handle_metric_action, 0, NULL }, NULL_ATTR }, NULL_DEF_ATTR }, { XML_SECTION_CLASSIFIER, { { "metric", xml_handle_string, G_STRUCT_OFFSET (struct classifier_config, metric), NULL }, { "tokenizer", handle_classifier_tokenizer, 0, NULL }, NULL_ATTR }, { handle_classifier_opt, 0, NULL } }, { XML_SECTION_STATFILE, { { "symbol", xml_handle_string, G_STRUCT_OFFSET (struct statfile, symbol), NULL }, { "path", xml_handle_string, G_STRUCT_OFFSET (struct statfile, path), NULL }, { "size", xml_handle_size, G_STRUCT_OFFSET (struct statfile, size), NULL }, { "spam", xml_handle_boolean, G_STRUCT_OFFSET (struct statfile, is_spam), NULL }, { "normalizer", handle_statfile_normalizer, 0, NULL }, { "binlog", handle_statfile_binlog, 0, NULL }, { "binlog_rotate", handle_statfile_binlog_rotate, 0, NULL }, { "binlog_master", handle_statfile_binlog_master, 0, NULL }, NULL_ATTR }, { handle_statfile_opt, 0, NULL } }, { XML_SECTION_MODULE, { NULL_ATTR }, { handle_module_opt, 0, NULL } }, { XML_SECTION_MODULES, { { "path", handle_module_path, 0, NULL }, NULL_ATTR }, NULL_DEF_ATTR }, { XML_SECTION_VIEW, { { "skip_check", xml_handle_boolean, G_STRUCT_OFFSET (struct rspamd_view, skip_check), NULL }, { "ip", handle_view_ip, 0, NULL }, { "client_ip", handle_view_client_ip, 0, NULL }, { "from", handle_view_from, 0, NULL }, { "rcpt", handle_view_rcpt, 0, NULL }, { "symbols", handle_view_symbols, 0, NULL }, NULL_ATTR }, NULL_DEF_ATTR }, }; GHashTable *module_options = NULL, *worker_options = NULL, *classifier_options = NULL, *subparsers = NULL; GQuark xml_error_quark (void) { return g_quark_from_static_string ("xml-error-quark"); } static inline const gchar * xml_state_to_string (struct rspamd_xml_userdata *ud) { switch (ud->state) { case XML_READ_START: return "read start tag"; case XML_READ_PARAM: return "read param"; case XML_READ_MODULE: return "read module section"; case XML_READ_OPTIONS: return "read options section"; case XML_READ_MODULES: return "read modules section"; case XML_READ_CLASSIFIER: return "read classifier section"; case XML_READ_STATFILE: return "read statfile section"; case XML_READ_METRIC: return "read metric section"; case XML_READ_WORKER: return "read worker section"; case XML_READ_VIEW: return "read view section"; case XML_READ_LOGGING: return "read logging section"; case XML_READ_VALUE: return "read value"; case XML_SKIP_ELEMENTS: return "skip if block"; case XML_ERROR: return "error occured"; case XML_END: return "read final tag"; case XML_SUBPARSER: return "subparser handle"; } /* Unreached */ return "unknown state"; } static inline gboolean extract_attr (const gchar *attr, const gchar **attribute_names, const gchar **attribute_values, gchar **res) { const gchar **cur_attr, **cur_value; cur_attr = attribute_names; cur_value = attribute_values; while (*cur_attr && *cur_value) { if (g_ascii_strcasecmp (*cur_attr, attr) == 0) { *res = (gchar *) *cur_value; return TRUE; } cur_attr ++; cur_value ++; } return FALSE; } static inline gchar* xml_asciiz_string (memory_pool_t *pool, const gchar *text, gsize len) { gchar *val; val = memory_pool_alloc (pool, len + 1); rspamd_strlcpy (val, text, len + 1); return val; } /* Find among attributes required ones and form new array of pairs attribute-value */ static GHashTable * process_attrs (struct config_file *cfg, const gchar **attribute_names, const gchar **attribute_values) { const gchar **attr, **value; GHashTable *res; if (*attribute_names == NULL) { /* No attributes required */ return NULL; } res = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal); attr = attribute_names; value = attribute_values; while (*attr) { /* Copy attributes to pool */ g_hash_table_insert (res, memory_pool_strdup (cfg->cfg_pool, *attr), memory_pool_strdup (cfg->cfg_pool, *value)); attr ++; value ++; } memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)g_hash_table_destroy, res); return res; } static gboolean call_param_handler (struct rspamd_xml_userdata *ctx, const gchar *name, gchar *value, gpointer dest_struct, enum xml_config_section section) { struct xml_parser_rule *rule; struct xml_config_param *param; guint i; /* First find required section */ for (i = 0; i < G_N_ELEMENTS (grammar); i ++) { rule = &grammar[i]; if (rule->section == section) { /* Now find attribute in section or call default handler */ param = &rule->params[0]; while (param && param->handler) { if (param->name && g_ascii_strcasecmp (param->name, name) == 0) { /* Call specified handler */ return param->handler (ctx->cfg, ctx, ctx->cur_attrs, value, param->user_data, dest_struct, param->offset); } param ++; } if (rule->default_param.handler != NULL) { /* Call default handler */ return rule->default_param.handler (ctx->cfg, ctx, name, ctx->cur_attrs, value, param->user_data, dest_struct, param->offset); } } } msg_err ("could not find handler for tag %s at section %d", name, section); return FALSE; } /* Handlers */ /* Specific handlers */ /* Logging section */ gboolean handle_log_type (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gchar *val; if (g_ascii_strcasecmp (data, "file") == 0) { /* Find filename attribute */ if (attrs == NULL || (val = g_hash_table_lookup (attrs, "filename")) == NULL) { msg_err ("cannot log to file that is not specified"); return FALSE; } cfg->log_type = RSPAMD_LOG_FILE; cfg->log_file = val; } else if (g_ascii_strcasecmp (data, "console") == 0) { cfg->log_type = RSPAMD_LOG_CONSOLE; } else if (g_ascii_strcasecmp (data, "syslog") == 0) { if (attrs == NULL || (val = g_hash_table_lookup (attrs, "facility")) == NULL) { msg_err ("cannot log to syslog when facility is not specified"); return FALSE; } cfg->log_type = RSPAMD_LOG_SYSLOG; /* Rather ugly check */ if (g_ascii_strncasecmp (val, "LOG_AUTH", sizeof ("LOG_AUTH") - 1) == 0 || g_ascii_strncasecmp (val, "auth", sizeof ("auth") - 1) == 0 ) { cfg->log_facility = LOG_AUTH; } else if (g_ascii_strncasecmp (val, "LOG_CRON", sizeof ("LOG_CRON") - 1) == 0 || g_ascii_strncasecmp (val, "cron", sizeof ("cron") - 1) == 0 ) { cfg->log_facility = LOG_CRON; } else if (g_ascii_strncasecmp (val, "LOG_DAEMON", sizeof ("LOG_DAEMON") - 1) == 0 || g_ascii_strncasecmp (val, "daemon", sizeof ("daemon") - 1) == 0 ) { cfg->log_facility = LOG_DAEMON; } else if (g_ascii_strncasecmp (val, "LOG_MAIL", sizeof ("LOG_MAIL") - 1) == 0 || g_ascii_strncasecmp (val, "mail", sizeof ("mail") - 1) == 0) { cfg->log_facility = LOG_MAIL; } else if (g_ascii_strncasecmp (val, "LOG_USER", sizeof ("LOG_USER") - 1) == 0 || g_ascii_strncasecmp (val, "user", sizeof ("user") - 1) == 0 ) { cfg->log_facility = LOG_USER; } else if (g_ascii_strncasecmp (val, "LOG_LOCAL0", sizeof ("LOG_LOCAL0") - 1) == 0 || g_ascii_strncasecmp (val, "local0", sizeof ("local0") - 1) == 0) { cfg->log_facility = LOG_LOCAL0; } else if (g_ascii_strncasecmp (val, "LOG_LOCAL1", sizeof ("LOG_LOCAL1") - 1) == 0 || g_ascii_strncasecmp (val, "local1", sizeof ("local1") - 1) == 0) { cfg->log_facility = LOG_LOCAL1; } else if (g_ascii_strncasecmp (val, "LOG_LOCAL2", sizeof ("LOG_LOCAL2") - 1) == 0 || g_ascii_strncasecmp (val, "local2", sizeof ("local2") - 1) == 0) { cfg->log_facility = LOG_LOCAL2; } else if (g_ascii_strncasecmp (val, "LOG_LOCAL3", sizeof ("LOG_LOCAL3") - 1) == 0 || g_ascii_strncasecmp (val, "local3", sizeof ("local3") - 1) == 0) { cfg->log_facility = LOG_LOCAL3; } else if (g_ascii_strncasecmp (val, "LOG_LOCAL4", sizeof ("LOG_LOCAL4") - 1) == 0 || g_ascii_strncasecmp (val, "local4", sizeof ("local4") - 1) == 0) { cfg->log_facility = LOG_LOCAL4; } else if (g_ascii_strncasecmp (val, "LOG_LOCAL5", sizeof ("LOG_LOCAL5") - 1) == 0 || g_ascii_strncasecmp (val, "local5", sizeof ("local5") - 1) == 0) { cfg->log_facility = LOG_LOCAL5; } else if (g_ascii_strncasecmp (val, "LOG_LOCAL6", sizeof ("LOG_LOCAL6") - 1) == 0 || g_ascii_strncasecmp (val, "local6", sizeof ("local6") - 1) == 0) { cfg->log_facility = LOG_LOCAL6; } else if (g_ascii_strncasecmp (val, "LOG_LOCAL7", sizeof ("LOG_LOCAL7") - 1) == 0 || g_ascii_strncasecmp (val, "local7", sizeof ("local7") - 1) == 0) { cfg->log_facility = LOG_LOCAL7; } else { msg_err ("invalid logging facility: %s", val); return FALSE; } } else { msg_err ("invalid logging type: %s", data); return FALSE; } return TRUE; } gboolean handle_log_level (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { if (g_ascii_strcasecmp (data, "error") == 0) { cfg->log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL; } else if (g_ascii_strcasecmp (data, "warning") == 0) { cfg->log_level = G_LOG_LEVEL_WARNING; } else if (g_ascii_strcasecmp (data, "info") == 0) { cfg->log_level = G_LOG_LEVEL_INFO | G_LOG_LEVEL_MESSAGE; } else if (g_ascii_strcasecmp (data, "debug") == 0) { cfg->log_level = G_LOG_LEVEL_DEBUG; } else { msg_err ("unknown log level: %s", data); return FALSE; } return TRUE; } gboolean options_handle_nameserver (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { cfg->nameservers = g_list_prepend (cfg->nameservers, memory_pool_strdup (cfg->cfg_pool, data)); return TRUE; } /* Worker section */ struct wrk_cbdata { struct worker_conf *wrk; struct config_file *cfg; struct rspamd_xml_userdata *ctx; }; struct wrk_param { gboolean is_list; union { gchar *param; GList *list; } d; }; static void worker_foreach_callback (gpointer k, gpointer v, gpointer ud) { struct wrk_cbdata *cd = ud; struct wrk_param *param = v; struct xml_config_param *cparam; GList *cur; GHashTable *worker_config; if (!worker_options || (worker_config = g_hash_table_lookup (worker_options, &cd->wrk->type)) == NULL || (cparam = g_hash_table_lookup (worker_config, k)) == NULL) { msg_warn ("unregistered worker attribute '%s' for worker %s", k, g_quark_to_string (cd->wrk->type)); } else { if (cd->wrk->ctx != NULL) { if (param->is_list) { cur = param->d.list; while (cur) { cparam->handler (cd->cfg, cd->ctx, NULL, cur->data, NULL, cd->wrk->ctx, cparam->offset); cur = g_list_next (cur); } } else { cparam->handler (cd->cfg, cd->ctx, NULL, param->d.param, NULL, cd->wrk->ctx, cparam->offset); } } else { msg_err ("Bad error detected: worker %s has not initialized its context", g_quark_to_string (cd->wrk->type)); } } } gboolean worker_handle_param (struct config_file *cfg, struct rspamd_xml_userdata *ctx, const gchar *tag, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct worker_conf *wrk = ctx->section_pointer; const gchar *name, *tmp; struct wrk_param *param; if (g_ascii_strcasecmp (tag, "option") == 0 || g_ascii_strcasecmp (tag, "param") == 0) { if (attrs == NULL || (name = g_hash_table_lookup (attrs, "name")) == NULL) { msg_err ("worker param tag must have \"name\" attribute"); return FALSE; } } else { name = memory_pool_strdup (cfg->cfg_pool, tag); } if ((param = g_hash_table_lookup (wrk->params, name)) == NULL) { param = memory_pool_alloc (cfg->cfg_pool, sizeof (struct wrk_param)); param->is_list = FALSE; param->d.param = memory_pool_strdup (cfg->cfg_pool, data); g_hash_table_insert (wrk->params, (char *)name, param); } else { if (param->is_list) { param->d.list = g_list_append (param->d.list, memory_pool_strdup (cfg->cfg_pool, data)); } else { /* Convert to list */ param->is_list = TRUE; tmp = param->d.param; param->d.list = g_list_prepend (NULL, (gpointer)tmp); memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)g_list_free, param->d.list); } } return TRUE; } gboolean worker_handle_type (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct worker_conf *wrk = ctx->section_pointer; GQuark type; type = g_quark_try_string (data); if (type != 0) { wrk->worker = get_worker_by_type (type); if (wrk->worker == NULL) { msg_err ("unknown worker type: %s", data); return FALSE; } wrk->type = type; if (wrk->worker->worker_init_func) { wrk->ctx = wrk->worker->worker_init_func (); } } else { msg_err ("unknown worker type: %s", data); return FALSE; } return TRUE; } gboolean worker_handle_bind (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct worker_conf *wrk = ctx->section_pointer; if (!parse_bind_line (cfg, wrk, data)) { msg_err ("cannot parse bind_socket: %s", data); return FALSE; } return TRUE; } gboolean handle_metric_action (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct metric *metric = ctx->section_pointer; gchar *p, *errstr; gint res; struct metric_action *action; /* First of all check whether we have data with weight (reject:50 for example) */ if ((p = strchr (data, ':')) == NULL) { if (check_action_str (data, &res)) { metric->action = res; return TRUE; } return FALSE; } else { if (!check_action_str (data, &res)) { return FALSE; } else { action = memory_pool_alloc (cfg->cfg_pool, sizeof (struct metric_action)); action->action = res; errno = 0; action->score = strtod (p + 1, &errstr); if (errno != 0 || (errstr != NULL && *errstr != '\0')) { msg_err ("invalid double value: %s", data); return FALSE; } metric->actions = g_list_prepend (metric->actions, action); } } return TRUE; } gboolean handle_metric_symbol (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gchar *strval, *err, *desc; double *value; GList *metric_list; struct metric *metric = ctx->section_pointer; value = memory_pool_alloc (cfg->cfg_pool, sizeof (double)); if (attrs == NULL || (strval = g_hash_table_lookup (attrs, "weight")) == NULL) { msg_info ("symbol tag should have \"weight\" attribute, assume weight 1.0"); *value = 1.0; } else { errno = 0; *value = strtod (strval, &err); if (errno != 0 || (err != NULL && *err != 0)) { msg_err ("invalid number: %s, %s", strval, strerror (errno)); return FALSE; } } if (attrs != NULL) { desc = g_hash_table_lookup (attrs, "description"); if (desc) { g_hash_table_insert (metric->descriptions, data, memory_pool_strdup (cfg->cfg_pool, desc)); } } g_hash_table_insert (metric->symbols, data, value); if ((metric_list = g_hash_table_lookup (cfg->metrics_symbols, data)) == NULL) { metric_list = g_list_prepend (NULL, metric); memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)g_list_free, metric_list); g_hash_table_insert (cfg->metrics_symbols, data, metric_list); } else { /* Slow but keep start element of list in safe */ if (!g_list_find (metric_list, metric)) { metric_list = g_list_append (metric_list, metric); } } return TRUE; } /* Modules section */ gboolean handle_module_opt (struct config_file *cfg, struct rspamd_xml_userdata *ctx, const gchar *tag, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gchar *val; struct module_opt *cur; gboolean is_lua = FALSE; const gchar *name; if (g_ascii_strcasecmp (tag, "option") == 0 || g_ascii_strcasecmp (tag, "param") == 0) { if ((name = g_hash_table_lookup (attrs, "name")) == NULL) { msg_err ("worker param tag must have \"name\" attribute"); return FALSE; } } else { name = memory_pool_strdup (cfg->cfg_pool, tag); } /* Check for lua */ if (attrs != NULL && (val = g_hash_table_lookup (attrs, "lua")) != NULL) { if (g_ascii_strcasecmp (val, "yes") == 0) { is_lua = TRUE; } } /* * XXX: in fact we cannot check for lua modules and need to do it in post-config procedure * so just insert any options provided and try to handle them in further process */ /* Insert option */ cur = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct module_opt)); cur->param = (char *)name; cur->value = data; cur->is_lua = is_lua; ctx->section_pointer = g_list_prepend (ctx->section_pointer, cur); return TRUE; } static void set_lua_globals (struct config_file *cfg, lua_State *L) { struct config_file **pcfg; /* First check for global variable 'config' */ lua_getglobal (L, "config"); if (lua_isnil (L, -1)) { /* Assign global table to set up attributes */ lua_newtable (L); lua_setglobal (L, "config"); } lua_getglobal (L, "metrics"); if (lua_isnil (L, -1)) { lua_newtable (L); lua_setglobal (L, "metrics"); } lua_getglobal (L, "composites"); if (lua_isnil (L, -1)) { lua_newtable (L); lua_setglobal (L, "composites"); } lua_getglobal (L, "classifiers"); if (lua_isnil (L, -1)) { lua_newtable (L); lua_setglobal (L, "classifiers"); } pcfg = lua_newuserdata (L, sizeof (struct config_file *)); lua_setclass (L, "rspamd{config}", -1); *pcfg = cfg; lua_setglobal (L, "rspamd_config"); /* Clear stack from globals */ lua_pop (L, 4); } /* Handle lua tag */ gboolean handle_lua (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gchar *val, *cur_dir, *lua_dir, *lua_file, *tmp1, *tmp2; lua_State *L = cfg->lua_state; /* Now config tables can be used for configuring rspamd */ /* First check "src" attribute */ if (attrs != NULL && (val = g_hash_table_lookup (attrs, "src")) != NULL) { /* Chdir */ tmp1 = g_strdup (val); tmp2 = g_strdup (val); lua_dir = dirname (tmp1); lua_file = basename (tmp2); if (lua_dir && lua_file) { cur_dir = g_malloc (PATH_MAX); if (getcwd (cur_dir, PATH_MAX) != NULL && chdir (lua_dir) != -1) { /* Load file */ if (luaL_loadfile (L, lua_file) != 0) { msg_err ("cannot load lua file %s: %s", val, lua_tostring (L, -1)); if (chdir (cur_dir) == -1) { msg_err ("cannot chdir to %s: %s", cur_dir, strerror (errno));; } g_free (cur_dir); g_free (tmp1); g_free (tmp2); return FALSE; } set_lua_globals (cfg, L); /* Now do it */ if (lua_pcall (L, 0, LUA_MULTRET, 0) != 0) { msg_err ("init of %s failed: %s", val, lua_tostring (L, -1)); if (chdir (cur_dir) == -1) { msg_err ("cannot chdir to %s: %s", cur_dir, strerror (errno));; } g_free (cur_dir); g_free (tmp1); g_free (tmp2); return FALSE; } } else { msg_err ("cannot chdir to %s: %s", lua_dir, strerror (errno));; if (chdir (cur_dir) == -1) { msg_err ("cannot chdir to %s: %s", cur_dir, strerror (errno));; } g_free (cur_dir); g_free (tmp1); g_free (tmp2); return FALSE; } if (chdir (cur_dir) == -1) { msg_err ("cannot chdir to %s: %s", cur_dir, strerror (errno));; } g_free (cur_dir); g_free (tmp1); g_free (tmp2); } else { msg_err ("directory for file %s does not exists", val); } } else if (data != NULL && *data != '\0') { /* Try to load a string */ if (luaL_loadstring (L, data) != 0) { msg_err ("cannot load lua chunk: %s", lua_tostring (L, -1)); return FALSE; } set_lua_globals (cfg, L); /* Now do it */ if (lua_pcall (L, 0, LUA_MULTRET, 0) != 0) { msg_err ("init of lua chunk failed: %s", lua_tostring (L, -1)); return FALSE; } } return TRUE; } /* Modules section */ gboolean handle_module_path (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct stat st; struct script_module *cur; glob_t globbuf; gchar *pattern; size_t len; guint i; if (stat (data, &st) == -1) { msg_err ("cannot stat path %s, %s", data, strerror (errno)); return FALSE; } /* Handle directory */ if (S_ISDIR (st.st_mode)) { globbuf.gl_offs = 0; len = strlen (data) + sizeof ("*.lua"); pattern = g_malloc (len); snprintf (pattern, len, "%s%s", data, "*.lua"); if (glob (pattern, GLOB_DOOFFS, NULL, &globbuf) == 0) { for (i = 0; i < globbuf.gl_pathc; i ++) { cur = memory_pool_alloc (cfg->cfg_pool, sizeof (struct script_module)); cur->path = memory_pool_strdup (cfg->cfg_pool, globbuf.gl_pathv[i]); cfg->script_modules = g_list_prepend (cfg->script_modules, cur); } globfree (&globbuf); g_free (pattern); } else { msg_err ("glob failed: %s", strerror (errno)); g_free (pattern); return FALSE; } } else { /* Handle single file */ cur = memory_pool_alloc (cfg->cfg_pool, sizeof (struct script_module)); cur->path = memory_pool_strdup (cfg->cfg_pool, data); cfg->script_modules = g_list_prepend (cfg->script_modules, cur); } return TRUE; } /* Variables and composites */ gboolean handle_variable (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gchar *val; if (attrs == NULL || (val = g_hash_table_lookup (attrs, "name")) == NULL) { msg_err ("'name' attribute is required for tag 'variable'"); return FALSE; } g_hash_table_insert (cfg->variables, val, memory_pool_strdup (cfg->cfg_pool, data)); return TRUE; } gboolean handle_composite (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gchar *val; struct expression *expr; if (attrs == NULL || (val = g_hash_table_lookup (attrs, "name")) == NULL) { msg_err ("'name' attribute is required for tag 'composite'"); return FALSE; } if ((expr = parse_expression (cfg->cfg_pool, data)) == NULL) { msg_err ("cannot parse composite expression: %s", data); return FALSE; } g_hash_table_insert (cfg->composite_symbols, val, expr); register_virtual_symbol (&cfg->cache, val, 1); return TRUE; } /* View section */ gboolean handle_view_ip (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct rspamd_view *view = ctx->section_pointer; if (!add_view_ip (view, data)) { msg_err ("invalid ip line in view definition: ip = '%s'", data); return FALSE; } return TRUE; } gboolean handle_view_client_ip (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct rspamd_view *view = ctx->section_pointer; if (!add_view_client_ip (view, data)) { msg_err ("invalid ip line in view definition: ip = '%s'", data); return FALSE; } return TRUE; } gboolean handle_view_from (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct rspamd_view *view = ctx->section_pointer; if (!add_view_from (view, data)) { msg_err ("invalid from line in view definition: from = '%s'", data); return FALSE; } return TRUE; } gboolean handle_view_rcpt (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct rspamd_view *view = ctx->section_pointer; if (!add_view_rcpt (view, data)) { msg_err ("invalid from line in view definition: rcpt = '%s'", data); return FALSE; } return TRUE; } gboolean handle_view_symbols (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct rspamd_view *view = ctx->section_pointer; if (!add_view_symbols (view, data)) { msg_err ("invalid symbols line in view definition: symbols = '%s'", data); return FALSE; } cfg->domain_settings_str = memory_pool_strdup (cfg->cfg_pool, data); return TRUE; } /* Settings */ gboolean handle_user_settings (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { if (!read_settings (data, cfg, cfg->user_settings)) { msg_err ("cannot read settings %s", data); return FALSE; } cfg->user_settings_str = memory_pool_strdup (cfg->cfg_pool, data); return TRUE; } gboolean handle_domain_settings (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { if (!read_settings (data, cfg, cfg->domain_settings)) { msg_err ("cannot read settings %s", data); return FALSE; } return TRUE; } /* Classifier */ gboolean handle_classifier_tokenizer (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct classifier_config *ccf = ctx->section_pointer; if ((ccf->tokenizer = get_tokenizer (data)) == NULL) { msg_err ("unknown tokenizer %s", data); return FALSE; } return TRUE; } gboolean handle_classifier_opt (struct config_file *cfg, struct rspamd_xml_userdata *ctx, const gchar *tag, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct classifier_config *ccf = ctx->section_pointer; const gchar *name; struct xml_config_param *cparam; GHashTable *classifier_config; if (g_ascii_strcasecmp (tag, "option") == 0 || g_ascii_strcasecmp (tag, "param") == 0) { if (attrs == NULL || (name = g_hash_table_lookup (attrs, "name")) == NULL) { msg_err ("worker param tag must have \"name\" attribute"); return FALSE; } } else { name = memory_pool_strdup (cfg->cfg_pool, tag); } if (!classifier_options || (classifier_config = g_hash_table_lookup (classifier_options, ccf->classifier->name)) == NULL || (cparam = g_hash_table_lookup (classifier_config, name)) == NULL) { msg_warn ("unregistered classifier attribute '%s' for classifier %s", name, ccf->classifier->name); return FALSE; } else { g_hash_table_insert (ccf->opts, (char *)name, memory_pool_strdup (cfg->cfg_pool, data)); } return TRUE; } /* Statfile */ gboolean handle_statfile_normalizer (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { msg_info ("normalizer option is now not available as rspamd always use internal normalizer for winnow (hyperbolic tanhent)"); return TRUE; } gboolean handle_statfile_binlog (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct statfile *st = ctx->section_pointer; if (st->binlog == NULL) { st->binlog = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct statfile_binlog_params)); } if (g_ascii_strcasecmp (data, "master") == 0) { st->binlog->affinity = AFFINITY_MASTER; } else if (g_ascii_strcasecmp (data, "slave") == 0) { st->binlog->affinity = AFFINITY_SLAVE; } else { st->binlog->affinity = AFFINITY_NONE; } return TRUE; } gboolean handle_statfile_binlog_rotate (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct statfile *st = ctx->section_pointer; if (st->binlog == NULL) { st->binlog = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct statfile_binlog_params)); } st->binlog->rotate_time = cfg_parse_time (data, TIME_SECONDS); return TRUE; } gboolean handle_statfile_binlog_master (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct statfile *st = ctx->section_pointer; if (st->binlog == NULL) { st->binlog = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct statfile_binlog_params)); } if (!parse_host_port (data, &st->binlog->master_addr, &st->binlog->master_port)) { msg_err ("cannot parse master address: %s", data); return FALSE; } return TRUE; } gboolean handle_statfile_opt (struct config_file *cfg, struct rspamd_xml_userdata *ctx, const gchar *tag, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { struct statfile *st = ctx->section_pointer; const gchar *name; if (g_ascii_strcasecmp (tag, "option") == 0 || g_ascii_strcasecmp (tag, "param") == 0) { if (attrs == NULL || (name = g_hash_table_lookup (attrs, "name")) == NULL) { msg_err ("worker param tag must have \"name\" attribute"); return FALSE; } } else { name = memory_pool_strdup (cfg->cfg_pool, tag); } g_hash_table_insert (st->opts, (char *)name, memory_pool_strdup (cfg->cfg_pool, data)); return TRUE; } /* Common handlers */ gboolean xml_handle_string (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { /* Simply assign pointer to pointer */ gchar **dest; dest = (gchar **)G_STRUCT_MEMBER_P (dest_struct, offset); *dest = memory_pool_strdup (cfg->cfg_pool, data); return TRUE; } gboolean xml_handle_string_list (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { GList **dest; gchar **tokens, **cur; dest = (GList **)G_STRUCT_MEMBER_P (dest_struct, offset); *dest = NULL; tokens = g_strsplit_set (data, ";,", 0); if (!tokens || !tokens[0]) { return FALSE; } memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)g_strfreev, tokens); cur = tokens; while (*cur) { *dest = g_list_prepend (*dest, *cur); cur ++; } return TRUE; } gboolean xml_handle_list (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { /* Simply assign pointer to pointer */ GList **dest; dest = (GList **)G_STRUCT_MEMBER_P (dest_struct, offset); *dest = g_list_prepend (*dest, memory_pool_strdup (cfg->cfg_pool, data)); return TRUE; } gboolean xml_handle_size (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gsize *dest; dest = (gsize *)G_STRUCT_MEMBER_P (dest_struct, offset); *dest = (gsize)parse_limit (data, -1); return TRUE; } /* Guint64 variant */ gboolean xml_handle_size_64 (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { guint64 *dest; dest = (guint64 *)G_STRUCT_MEMBER_P (dest_struct, offset); *dest = parse_limit (data, -1); return TRUE; } gboolean xml_handle_seconds (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { guint32 *dest; dest = (guint32 *)G_STRUCT_MEMBER_P (dest_struct, offset); *dest = cfg_parse_time (data, TIME_SECONDS); return TRUE; } gboolean xml_handle_boolean (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gboolean *dest; dest = (gboolean *)G_STRUCT_MEMBER_P (dest_struct, offset); *dest = parse_flag (data); /* gchar -> gboolean */ if (*dest == -1) { msg_err ("bad boolean: %s", data); return FALSE; } else if (*dest == 1) { *dest = TRUE; } return TRUE; } gboolean xml_handle_double (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { double *dest; gchar *err = NULL; dest = (double *)G_STRUCT_MEMBER_P (dest_struct, offset); errno = 0; *dest = strtod (data, &err); if (errno != 0 || (err != NULL && *err != 0)) { msg_err ("invalid number: %s, %s", data, strerror (errno)); return FALSE; } return TRUE; } gboolean xml_handle_int (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { gint *dest; gchar *err = NULL; dest = (gint *)G_STRUCT_MEMBER_P (dest_struct, offset); errno = 0; *dest = strtol (data, &err, 10); if (errno != 0 || (err != NULL && *err != 0)) { msg_err ("invalid number: %s, %s", data, strerror (errno)); return FALSE; } return TRUE; } gboolean xml_handle_uint32 (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { guint32 *dest; gchar *err = NULL; dest = (guint32 *)G_STRUCT_MEMBER_P (dest_struct, offset); errno = 0; *dest = strtoul (data, &err, 10); if (errno != 0 || (err != NULL && *err != 0)) { msg_err ("invalid number: %s, %s", data, strerror (errno)); return FALSE; } return TRUE; } gboolean xml_handle_uint16 (struct config_file *cfg, struct rspamd_xml_userdata *ctx, GHashTable *attrs, gchar *data, gpointer user_data, gpointer dest_struct, gint offset) { guint16 *dest; gchar *err = NULL; dest = (guint16 *)G_STRUCT_MEMBER_P (dest_struct, offset); errno = 0; *dest = strtoul (data, &err, 10); if (errno != 0 || (err != NULL && *err != 0)) { msg_err ("invalid number: %s, %s", data, strerror (errno)); return FALSE; } return TRUE; } /* XML callbacks */ void rspamd_xml_start_element (GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { struct rspamd_xml_userdata *ud = user_data; struct xml_subparser *subparser; struct classifier_config *ccf; gchar *res, *condition; if (g_ascii_strcasecmp (element_name, "if") == 0) { /* Push current state to queue */ g_queue_push_head (ud->if_stack, GSIZE_TO_POINTER ((gsize)ud->state)); /* Now get attributes */ ud->cur_attrs = process_attrs (ud->cfg, attribute_names, attribute_values); if (ud->cur_attrs == NULL || (condition = g_hash_table_lookup (ud->cur_attrs, "condition")) == NULL) { msg_err ("unknown condition attribute for if tag"); *error = g_error_new (xml_error_quark (), XML_PARAM_MISSING, "param 'condition' is required for tag 'if'"); ud->state = XML_ERROR; } else if (! lua_check_condition (ud->cfg, condition)) { ud->state = XML_SKIP_ELEMENTS; } return; } switch (ud->state) { case XML_READ_START: if (g_ascii_strcasecmp (element_name, "rspamd") != 0) { /* Invalid XML, it must contains root element */ *error = g_error_new (xml_error_quark (), XML_START_MISSING, "start element is missing"); ud->state = XML_ERROR; } else { ud->state = XML_READ_PARAM; } break; case XML_READ_PARAM: /* Read parameter name and try to find among list of known parameters */ if (g_ascii_strcasecmp (element_name, "module") == 0) { /* Read module data */ if (extract_attr ("name", attribute_names, attribute_values, &res)) { ud->parent_pointer = memory_pool_strdup (ud->cfg->cfg_pool, res); /* Empty list */ ud->section_pointer = NULL; ud->state = XML_READ_MODULE; } else { *error = g_error_new (xml_error_quark (), XML_PARAM_MISSING, "param 'name' is required for tag 'module'"); ud->state = XML_ERROR; } } else if (g_ascii_strcasecmp (element_name, "modules") == 0) { ud->state = XML_READ_MODULES; } else if (g_ascii_strcasecmp (element_name, "options") == 0) { ud->state = XML_READ_OPTIONS; } else if (g_ascii_strcasecmp (element_name, "logging") == 0) { ud->state = XML_READ_LOGGING; } else if (g_ascii_strcasecmp (element_name, "metric") == 0) { ud->state = XML_READ_METRIC; /* Create object */ ud->section_pointer = check_metric_conf (ud->cfg, NULL); } else if (g_ascii_strcasecmp (element_name, "classifier") == 0) { if (extract_attr ("type", attribute_names, attribute_values, &res)) { ud->state = XML_READ_CLASSIFIER; /* Create object */ ccf = check_classifier_conf (ud->cfg, NULL); if ((ccf->classifier = get_classifier (res)) == NULL) { *error = g_error_new (xml_error_quark (), XML_INVALID_ATTR, "invalid classifier type: %s", res); ud->state = XML_ERROR; } else { ud->section_pointer = ccf; } } else { *error = g_error_new (xml_error_quark (), XML_PARAM_MISSING, "param 'type' is required for tag 'classifier'"); ud->state = XML_ERROR; } } else if (g_ascii_strcasecmp (element_name, "worker") == 0) { ud->state = XML_READ_WORKER; /* Create object */ ud->section_pointer = check_worker_conf (ud->cfg, NULL); } else if (g_ascii_strcasecmp (element_name, "lua") == 0) { rspamd_strlcpy (ud->section_name, element_name, sizeof (ud->section_name)); ud->cur_attrs = process_attrs (ud->cfg, attribute_names, attribute_values); if (! handle_lua (ud->cfg, ud, ud->cur_attrs, NULL, NULL, ud->cfg, 0)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s'", ud->section_name); ud->state = XML_ERROR; } else { ud->state = XML_READ_VALUE; } } else if (g_ascii_strcasecmp (element_name, "view") == 0) { ud->state = XML_READ_VIEW; /* Create object */ ud->section_pointer = init_view (ud->cfg->cfg_pool); } #if GLIB_MINOR_VERSION >= 18 else if (subparsers != NULL && (subparser = g_hash_table_lookup (subparsers, element_name)) != NULL) { ud->state = XML_SUBPARSER; g_markup_parse_context_push (context, subparser->parser, subparser->user_data); rspamd_strlcpy (ud->section_name, element_name, sizeof (ud->section_name)); } #endif else { /* Extract other tags */ rspamd_strlcpy (ud->section_name, element_name, sizeof (ud->section_name)); ud->cur_attrs = process_attrs (ud->cfg, attribute_names, attribute_values); ud->state = XML_READ_VALUE; } break; case XML_READ_CLASSIFIER: if (g_ascii_strcasecmp (element_name, "statfile") == 0) { ud->state = XML_READ_STATFILE; /* Now section pointer is statfile and parent pointer is classifier */ ud->parent_pointer = ud->section_pointer; ud->section_pointer = check_statfile_conf (ud->cfg, NULL); } else { rspamd_strlcpy (ud->section_name, element_name, sizeof (ud->section_name)); /* Save attributes */ ud->cur_attrs = process_attrs (ud->cfg, attribute_names, attribute_values); } break; case XML_SKIP_ELEMENTS: /* Do nothing */ return; case XML_READ_MODULE: case XML_READ_METRIC: case XML_READ_MODULES: case XML_READ_STATFILE: case XML_READ_WORKER: case XML_READ_LOGGING: case XML_READ_VIEW: case XML_READ_OPTIONS: rspamd_strlcpy (ud->section_name, element_name, sizeof (ud->section_name)); /* Save attributes */ ud->cur_attrs = process_attrs (ud->cfg, attribute_names, attribute_values); break; case XML_SUBPARSER: /* Recursive call of this function but with other state */ ud->state = XML_READ_PARAM; rspamd_xml_start_element (context, element_name, attribute_names, attribute_values, user_data, error); break; default: if (*error == NULL) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "element %s is unexpected in this state %s", element_name, xml_state_to_string (ud)); } break; } } #define CHECK_TAG(x, required) \ do { \ if (g_ascii_strcasecmp (element_name, (x)) == 0) { \ ud->state = XML_READ_PARAM; \ res = TRUE; \ } \ else { \ res = FALSE; \ if ((required) == TRUE) { \ if (*error == NULL) *error = g_error_new (xml_error_quark (), XML_UNMATCHED_TAG, "element %s is unexpected in this state, expected %s", element_name, (x)); \ ud->state = XML_ERROR; \ } \ } \ } while (0) void rspamd_xml_end_element (GMarkupParseContext *context, const gchar *element_name, gpointer user_data, GError **error) { struct rspamd_xml_userdata *ud = user_data; struct metric *m; struct classifier_config *ccf; struct statfile *st; gboolean res; gpointer tptr; struct wrk_cbdata wcd; struct xml_subparser *subparser; if (g_ascii_strcasecmp (element_name, "if") == 0) { tptr = g_queue_pop_head (ud->if_stack); if (tptr == NULL) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "element %s is umatched", element_name); ud->state = XML_ERROR; } /* Restore state */ if (ud->state == XML_SKIP_ELEMENTS) { ud->state = GPOINTER_TO_SIZE (tptr); } /* Skip processing */ return; } switch (ud->state) { case XML_READ_MODULE: CHECK_TAG ("module", FALSE); if (res) { if (ud->section_pointer != NULL) { g_hash_table_insert (ud->cfg->modules_opts, ud->parent_pointer, ud->section_pointer); ud->parent_pointer = NULL; ud->section_pointer = NULL; } } break; case XML_READ_CLASSIFIER: CHECK_TAG ("classifier", FALSE); if (res) { ccf = ud->section_pointer; if (ccf->statfiles == NULL || !check_classifier_statfiles (ccf)) { *error = g_error_new (xml_error_quark (), XML_PARAM_MISSING, "classifier cannot contains no statfiles or statfiles of the same class"); ud->state = XML_ERROR; return; } ud->cfg->classifiers = g_list_prepend (ud->cfg->classifiers, ccf); } break; case XML_READ_STATFILE: CHECK_TAG ("statfile", FALSE); if (res) { ccf = ud->parent_pointer; st = ud->section_pointer; /* Check statfile and insert it into classifier */ if (st->path == NULL || st->size == 0 || st->symbol == NULL) { *error = g_error_new (xml_error_quark (), XML_PARAM_MISSING, "not enough arguments in statfile definition"); ud->state = XML_ERROR; return; } ccf->statfiles = g_list_prepend (ccf->statfiles, st); ud->cfg->statfiles = g_list_prepend (ud->cfg->statfiles, st); g_hash_table_insert (ud->cfg->classifiers_symbols, st->symbol, ccf); ud->section_pointer = ccf; ud->parent_pointer = NULL; ud->state = XML_READ_CLASSIFIER; } break; case XML_READ_MODULES: CHECK_TAG ("modules", FALSE); break; case XML_READ_OPTIONS: CHECK_TAG ("options", FALSE); break; case XML_READ_METRIC: CHECK_TAG ("metric", FALSE); if (res) { m = ud->section_pointer; if (m->name == NULL) { *error = g_error_new (xml_error_quark (), XML_PARAM_MISSING, "metric attribute \"name\" is required but missing"); ud->state = XML_ERROR; return; } g_hash_table_insert (ud->cfg->metrics, m->name, m); ud->cfg->metrics_list = g_list_prepend (ud->cfg->metrics_list, m); } break; case XML_READ_WORKER: CHECK_TAG ("worker", FALSE); if (res) { /* Parse params */ wcd.wrk = ud->section_pointer; wcd.cfg = ud->cfg; wcd.ctx = ud; g_hash_table_foreach (wcd.wrk->params, worker_foreach_callback, &wcd); /* Insert object to list */ ud->cfg->workers = g_list_prepend (ud->cfg->workers, ud->section_pointer); } break; case XML_READ_VIEW: CHECK_TAG ("view", FALSE); if (res) { /* Insert object to list */ ud->cfg->views = g_list_prepend (ud->cfg->views, ud->section_pointer); } break; case XML_READ_VALUE: /* Check tags parity */ CHECK_TAG (ud->section_name, TRUE); break; case XML_READ_LOGGING: CHECK_TAG ("logging", FALSE); break; case XML_READ_PARAM: if (g_ascii_strcasecmp (element_name, "rspamd") == 0) { /* End of document */ ud->state = XML_END; } else { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "element %s is umatched", element_name); ud->state = XML_ERROR; } break; case XML_SKIP_ELEMENTS: return; #if GLIB_MINOR_VERSION >= 18 case XML_SUBPARSER: CHECK_TAG (ud->section_name, TRUE); if (subparsers != NULL && (subparser = g_hash_table_lookup (subparsers, element_name)) != NULL) { if (subparser->fin_func) { subparser->fin_func (g_markup_parse_context_pop (context)); } else { g_markup_parse_context_pop (context); } } ud->state = XML_READ_PARAM; break; #endif default: ud->state = XML_ERROR; break; } } #undef CHECK_TAG void rspamd_xml_text (GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, GError **error) { struct rspamd_xml_userdata *ud = user_data; gchar *val; struct config_file *cfg = ud->cfg; /* Strip space symbols */ while (*text && g_ascii_isspace (*text)) { text ++; } if (*text == '\0') { /* Skip empty text */ return; } val = xml_asciiz_string (cfg->cfg_pool, text, text_len); switch (ud->state) { case XML_READ_MODULE: if (!call_param_handler (ud, ud->section_name, val, ud->section_pointer, XML_SECTION_MODULE)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_MODULES: if (!call_param_handler (ud, ud->section_name, val, cfg, XML_SECTION_MODULES)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_OPTIONS: if (!call_param_handler (ud, ud->section_name, val, cfg, XML_SECTION_OPTIONS)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_CLASSIFIER: if (!call_param_handler (ud, ud->section_name, val, ud->section_pointer, XML_SECTION_CLASSIFIER)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_STATFILE: if (!call_param_handler (ud, ud->section_name, val, ud->section_pointer, XML_SECTION_STATFILE)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_METRIC: if (!call_param_handler (ud, ud->section_name, val, ud->section_pointer, XML_SECTION_METRIC)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_WORKER: if (!call_param_handler (ud, ud->section_name, val, ud->section_pointer, XML_SECTION_WORKER)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_VIEW: if (!call_param_handler (ud, ud->section_name, val, ud->section_pointer, XML_SECTION_VIEW)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_VALUE: if (!call_param_handler (ud, ud->section_name, val, cfg, XML_SECTION_MAIN)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_READ_LOGGING: if (!call_param_handler (ud, ud->section_name, val, cfg, XML_SECTION_LOGGING)) { *error = g_error_new (xml_error_quark (), XML_EXTRA_ELEMENT, "cannot parse tag '%s' data: %s", ud->section_name, val); ud->state = XML_ERROR; } break; case XML_SKIP_ELEMENTS: /* Do nothing */ return; default: ud->state = XML_ERROR; break; } } void rspamd_xml_error (GMarkupParseContext *context, GError *error, gpointer user_data) { struct rspamd_xml_userdata *ud = user_data; msg_err ("xml parser error: %s, at state \"%s\"", error->message, xml_state_to_string (ud)); } /* Register handlers for specific parts of config */ /* Checker for module options */ struct option_callback_data { const gchar *optname; gboolean res; struct xml_config_param *param; }; static void module_option_callback (gpointer key, gpointer value, gpointer ud) { const gchar *optname = key; static gchar rebuf[512]; struct option_callback_data *cd = ud; GRegex *re; GError *err = NULL; gsize relen; if (*optname == '/') { relen = strcspn (optname + 1, "/"); if (relen > sizeof (rebuf)) { relen = sizeof (rebuf); } rspamd_strlcpy (rebuf, optname + 1, relen); /* This is a regexp so compile and check it */ re = g_regex_new (rebuf, G_REGEX_CASELESS, 0, &err); if (err != NULL) { msg_err ("failed to compile regexp for option '%s', error was: %s, regexp was: %s", cd->optname, err->message, rebuf); return; } if (g_regex_match (re, cd->optname, 0, NULL)) { cd->res = TRUE; cd->param = value; } } return; } gboolean check_module_option (const gchar *mname, const gchar *optname, const gchar *data) { struct xml_config_param *param; enum module_opt_type type; GHashTable *module; gchar *err_str; struct option_callback_data cd; union { gint i; gdouble d; guint ui; } t; if (module_options == NULL) { msg_warn ("no module options registered while checking option %s for module %s", mname, optname); return FALSE; } if ((module = g_hash_table_lookup (module_options, mname)) == NULL) { msg_warn ("module %s has not registered any options while checking for option %s", mname, optname); return FALSE; } if ((param = g_hash_table_lookup (module, optname)) == NULL) { /* Try to handle regexp options */ cd.optname = optname; cd.res = FALSE; g_hash_table_foreach (module, module_option_callback, &cd); if (!cd.res) { msg_warn ("module %s has not registered option %s", mname, optname); return FALSE; } param = cd.param; } type = param->offset; /* Now handle option of each type */ switch (type) { case MODULE_OPT_TYPE_STRING: case MODULE_OPT_TYPE_ANY: /* Allways OK */ return TRUE; case MODULE_OPT_TYPE_INT: t.i = strtol (data, &err_str, 10); if (*err_str != '\0') { msg_warn ("non-numeric data for option: '%s' for module: '%s' at position: '%s'", optname, mname, err_str); return FALSE; } (void)t.i; break; case MODULE_OPT_TYPE_UINT: t.ui = strtoul (data, &err_str, 10); if (*err_str != '\0') { msg_warn ("non-numeric data for option: '%s' for module: '%s' at position: '%s'", optname, mname, err_str); return FALSE; } (void)t.ui; break; case MODULE_OPT_TYPE_DOUBLE: t.d = strtod (data, &err_str); if (*err_str != '\0') { msg_warn ("non-numeric data for option: '%s' for module: '%s' at position: '%s'", optname, mname, err_str); return FALSE; } (void)t.d; break; case MODULE_OPT_TYPE_TIME: t.ui = cfg_parse_time (data, TIME_SECONDS); if (errno != 0) { msg_warn ("non-numeric data for option: '%s' for module: '%s': %s", optname, mname, strerror (errno)); return FALSE; } (void)t.ui; break; case MODULE_OPT_TYPE_SIZE: t.ui = parse_limit (data, -1); if (errno != 0) { msg_warn ("non-numeric data for option: '%s' for module: '%s': %s", optname, mname, strerror (errno)); return FALSE; } (void)t.ui; break; case MODULE_OPT_TYPE_MAP: if (!check_map_proto (data, NULL, NULL)) { return FALSE; } break; } return TRUE; } /* Register new module option */ void register_module_opt (const gchar *mname, const gchar *optname, enum module_opt_type type) { struct xml_config_param *param; GHashTable *module; if (module_options == NULL) { module_options = g_hash_table_new (g_str_hash, g_str_equal); } if ((module = g_hash_table_lookup (module_options, mname)) == NULL) { module = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (module_options, (char *)mname, module); } if ((param = g_hash_table_lookup (module, optname)) == NULL) { /* Register new param */ param = g_malloc (sizeof (struct xml_config_param)); param->handler = NULL; param->offset = type; param->name = optname; g_hash_table_insert (module, (char *)optname, param); } else { /* Param already exists replace it */ msg_warn ("replace old handler for param '%s'", optname); g_free (param); param = g_malloc (sizeof (struct xml_config_param)); param->handler = NULL; param->offset = type; param->name = optname; g_hash_table_insert (module, (char *)optname, param); } } /* Register new worker's options */ void register_worker_opt (gint wtype, const gchar *optname, element_handler_func func, gpointer dest_struct, gint offset) { struct xml_config_param *param; GHashTable *worker; gint *new_key; if (worker_options == NULL) { worker_options = g_hash_table_new (g_int_hash, g_int_equal); } if ((worker = g_hash_table_lookup (worker_options, &wtype)) == NULL) { worker = g_hash_table_new (g_str_hash, g_str_equal); new_key = g_malloc (sizeof (gint)); *new_key = wtype; g_hash_table_insert (worker_options, new_key, worker); } if ((param = g_hash_table_lookup (worker, optname)) == NULL) { /* Register new param */ param = g_malloc (sizeof (struct xml_config_param)); param->handler = func; param->user_data = dest_struct; param->offset = offset; param->name = optname; g_hash_table_insert (worker, (char *)optname, param); } } /* Register new classifier option */ void register_classifier_opt (const gchar *ctype, const gchar *optname) { struct xml_config_param *param; GHashTable *classifier; if (classifier_options == NULL) { classifier_options = g_hash_table_new (g_str_hash, g_str_equal); } if ((classifier = g_hash_table_lookup (classifier_options, ctype)) == NULL) { classifier = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (classifier_options, (char *)ctype, classifier); } if ((param = g_hash_table_lookup (classifier, optname)) == NULL) { /* Register new param */ param = g_malloc (sizeof (struct xml_config_param)); param->handler = NULL; param->user_data = NULL; param->offset = 0; param->name = optname; g_hash_table_insert (classifier, (char *)optname, param); } else { /* Param already exists replace it */ msg_warn ("replace old handler for param '%s'", optname); g_free (param); param = g_malloc (sizeof (struct xml_config_param)); param->handler = NULL; param->user_data = NULL; param->offset = 0; param->name = optname; g_hash_table_insert (classifier, (char *)optname, param); } } void register_subparser (const gchar *tag, enum xml_read_state state, const GMarkupParser *parser, void (*fin_func)(gpointer ud), gpointer user_data) { struct xml_subparser *subparser; if (subparsers == NULL) { subparsers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } subparser = g_malloc (sizeof (struct xml_subparser)); subparser->parser = parser; subparser->state = state; subparser->user_data = user_data; subparser->fin_func = fin_func; g_hash_table_replace (subparsers, g_strdup (tag), subparser); } /* Dumper part */ /* Dump specific sections */ /* Dump main section variables */ static gboolean xml_dump_main (struct config_file *cfg, FILE *f) { gchar *escaped_str; /* Print header comment */ rspamd_fprintf (f, "" EOL); escaped_str = g_markup_escape_text (cfg->temp_dir, -1); rspamd_fprintf (f, "%s" EOL, escaped_str); g_free (escaped_str); escaped_str = g_markup_escape_text (cfg->pid_file, -1); rspamd_fprintf (f, "%s" EOL, escaped_str); g_free (escaped_str); escaped_str = g_markup_escape_text (cfg->filters_str, -1); rspamd_fprintf (f, "%s" EOL, escaped_str); g_free (escaped_str); if (cfg->user_settings_str) { escaped_str = g_markup_escape_text (cfg->user_settings_str, -1); rspamd_fprintf (f, "%s" EOL, escaped_str); g_free (escaped_str); } if (cfg->domain_settings_str) { escaped_str = g_markup_escape_text (cfg->domain_settings_str, -1); rspamd_fprintf (f, "%s" EOL, escaped_str); g_free (escaped_str); } rspamd_fprintf (f, "%z" EOL, cfg->max_statfile_size); if (cfg->checksum) { escaped_str = g_markup_escape_text (cfg->checksum, -1); rspamd_fprintf (f, "%s" EOL, escaped_str); g_free (escaped_str); } rspamd_fprintf (f, "%s" EOL, cfg->raw_mode ? "yes" : "no"); /* Print footer comment */ rspamd_fprintf (f, "" EOL EOL); return TRUE; } /* Dump variables section */ static void xml_variable_callback (gpointer key, gpointer value, gpointer user_data) { FILE *f = user_data; gchar *escaped_key, *escaped_value; escaped_key = g_markup_escape_text (key, -1); escaped_value = g_markup_escape_text (value, -1); rspamd_fprintf (f, "%s" EOL, escaped_key, escaped_value); g_free (escaped_key); g_free (escaped_value); } static gboolean xml_dump_variables (struct config_file *cfg, FILE *f) { /* Print header comment */ rspamd_fprintf (f, "" EOL); /* Iterate through variables */ g_hash_table_foreach (cfg->variables, xml_variable_callback, (gpointer)f); /* Print footer comment */ rspamd_fprintf (f, "" EOL EOL); return TRUE; } /* Composites section */ static void xml_composite_callback (gpointer key, gpointer value, gpointer user_data) { FILE *f = user_data; struct expression *expr; gchar *escaped_key, *escaped_value; expr = value; escaped_key = g_markup_escape_text (key, -1); escaped_value = g_markup_escape_text (expr->orig, -1); rspamd_fprintf (f, "%s" EOL, escaped_key, escaped_value); g_free (escaped_key); g_free (escaped_value); } static gboolean xml_dump_composites (struct config_file *cfg, FILE *f) { /* Print header comment */ rspamd_fprintf (f, "" EOL); /* Iterate through variables */ g_hash_table_foreach (cfg->composite_symbols, xml_composite_callback, (gpointer)f); /* Print footer comment */ rspamd_fprintf (f, "" EOL EOL); return TRUE; } /* Workers */ static void xml_worker_param_callback (gpointer key, gpointer value, gpointer user_data) { FILE *f = user_data; gchar *escaped_key, *escaped_value; escaped_key = g_markup_escape_text (key, -1); escaped_value = g_markup_escape_text (value, -1); rspamd_fprintf (f, " %s" EOL, escaped_key, escaped_value); g_free (escaped_key); g_free (escaped_value); } static gboolean xml_dump_workers (struct config_file *cfg, FILE *f) { GList *cur; struct worker_conf *wrk; gchar *escaped_str; /* Print header comment */ rspamd_fprintf (f, "" EOL); /* Iterate through list */ cur = g_list_first (cfg->workers); while (cur) { wrk = cur->data; rspamd_fprintf (f, "" EOL); rspamd_fprintf (f, " %s" EOL, g_quark_to_string (wrk->type)); escaped_str = g_markup_escape_text (wrk->bind_host, -1); rspamd_fprintf (f, " %s" EOL, escaped_str); g_free (escaped_str); rspamd_fprintf (f, " %ud" EOL, wrk->count); rspamd_fprintf (f, " %ud" EOL, wrk->rlimit_nofile); rspamd_fprintf (f, " %ud" EOL, wrk->rlimit_maxcore); /* Now dump other attrs */ rspamd_fprintf (f, "" EOL); g_hash_table_foreach (wrk->params, xml_worker_param_callback, f); rspamd_fprintf (f, "" EOL); cur = g_list_next (cur); } /* Print footer comment */ rspamd_fprintf (f, "" EOL EOL); return TRUE; } /* Modules dump */ static void xml_module_callback (gpointer key, gpointer value, gpointer user_data) { FILE *f = user_data; gchar *escaped_key, *escaped_value; GList *cur; struct module_opt *opt; escaped_key = g_markup_escape_text (key, -1); rspamd_fprintf (f, "" EOL, escaped_key); rspamd_fprintf (f, "" EOL, escaped_key); g_free (escaped_key); cur = g_list_first (value); while (cur) { opt = cur->data; escaped_key = g_markup_escape_text (opt->param, -1); escaped_value = g_markup_escape_text (opt->value, -1); rspamd_fprintf (f, " " EOL, escaped_key, escaped_value); g_free (escaped_key); g_free (escaped_value); cur = g_list_next (cur); } rspamd_fprintf (f, "" EOL EOL); } static gboolean xml_dump_modules (struct config_file *cfg, FILE *f) { /* Print header comment */ rspamd_fprintf (f, "" EOL); /* Iterate through variables */ g_hash_table_foreach (cfg->modules_opts, xml_module_callback, (gpointer)f); /* Print footer comment */ rspamd_fprintf (f, "" EOL EOL); return TRUE; } /* Classifiers dump */ static void xml_classifier_callback (gpointer key, gpointer value, gpointer user_data) { FILE *f = user_data; gchar *escaped_key, *escaped_value; escaped_key = g_markup_escape_text (key, -1); escaped_value = g_markup_escape_text (value, -1); rspamd_fprintf (f, " " EOL, escaped_key, escaped_value); g_free (escaped_key); g_free (escaped_value); } static gboolean xml_dump_classifiers (struct config_file *cfg, FILE *f) { GList *cur, *cur_st; struct classifier_config *ccf; struct statfile *st; /* Print header comment */ rspamd_fprintf (f, "" EOL); /* Iterate through classifiers */ cur = g_list_first (cfg->classifiers); while (cur) { ccf = cur->data; rspamd_fprintf (f, "" EOL, ccf->classifier->name); rspamd_fprintf (f, " %s" EOL, ccf->tokenizer->name); rspamd_fprintf (f, " %s" EOL, ccf->metric); g_hash_table_foreach (ccf->opts, xml_classifier_callback, f); /* Statfiles */ cur_st = g_list_first (ccf->statfiles); while (cur_st) { st = cur_st->data; rspamd_fprintf (f, " " EOL); rspamd_fprintf (f, " %s" EOL " %z" EOL " %s" EOL, st->symbol, st->size, st->path); rspamd_fprintf (f, " %s" EOL, st->normalizer_str); /* Binlog */ if (st->binlog) { if (st->binlog->affinity == AFFINITY_MASTER) { rspamd_fprintf (f, " master" EOL); } else if (st->binlog->affinity == AFFINITY_SLAVE) { rspamd_fprintf (f, " slave" EOL); rspamd_fprintf (f, " %s:%d" EOL, inet_ntoa (st->binlog->master_addr), (gint)ntohs (st->binlog->master_port)); } rspamd_fprintf (f, " %T" EOL, st->binlog->rotate_time); } rspamd_fprintf (f, " " EOL); cur_st = g_list_next (cur_st); } rspamd_fprintf (f, "" EOL); cur = g_list_next (cur); } /* Print footer comment */ rspamd_fprintf (f, "" EOL EOL); return TRUE; } /* Logging section */ static gboolean xml_dump_logging (struct config_file *cfg, FILE *f) { gchar *escaped_value; /* Print header comment */ rspamd_fprintf (f, "" EOL); rspamd_fprintf (f, "" EOL); /* Level */ if (cfg->log_level < G_LOG_LEVEL_WARNING) { rspamd_fprintf (f, " error" EOL); } else if (cfg->log_level < G_LOG_LEVEL_MESSAGE) { rspamd_fprintf (f, " warning" EOL); } else if (cfg->log_level < G_LOG_LEVEL_DEBUG) { rspamd_fprintf (f, " info" EOL); } else { rspamd_fprintf (f, " debug" EOL); } /* Other options */ rspamd_fprintf (f, " %s" EOL, cfg->log_urls ? "yes" : "no"); if (cfg->log_buf_size != 0) { rspamd_fprintf (f, " %ud" EOL, (guint)cfg->log_buf_size); } if (cfg->debug_ip_map != NULL) { escaped_value = g_markup_escape_text (cfg->debug_ip_map, -1); rspamd_fprintf (f, " %s" EOL, escaped_value); g_free (escaped_value); } /* Handle type */ if (cfg->log_type == RSPAMD_LOG_FILE) { escaped_value = g_markup_escape_text (cfg->log_file, -1); rspamd_fprintf (f, " file" EOL, escaped_value); g_free (escaped_value); } else if (cfg->log_type == RSPAMD_LOG_CONSOLE) { rspamd_fprintf (f, " console" EOL); } else if (cfg->log_type == RSPAMD_LOG_SYSLOG) { escaped_value = NULL; switch (cfg->log_facility) { case LOG_AUTH: escaped_value = "LOG_AUTH"; break; case LOG_CRON: escaped_value = "LOG_CRON"; break; case LOG_DAEMON: escaped_value = "LOG_DAEMON"; break; case LOG_MAIL: escaped_value = "LOG_MAIL"; break; case LOG_USER: escaped_value = "LOG_USER"; break; case LOG_LOCAL0: escaped_value = "LOG_LOCAL0"; break; case LOG_LOCAL1: escaped_value = "LOG_LOCAL1"; break; case LOG_LOCAL2: escaped_value = "LOG_LOCAL2"; break; case LOG_LOCAL3: escaped_value = "LOG_LOCAL3"; break; case LOG_LOCAL4: escaped_value = "LOG_LOCAL4"; break; case LOG_LOCAL5: escaped_value = "LOG_LOCAL5"; break; case LOG_LOCAL6: escaped_value = "LOG_LOCAL6"; break; case LOG_LOCAL7: escaped_value = "LOG_LOCAL7"; break; } rspamd_fprintf (f, " syslog" EOL, escaped_value); } rspamd_fprintf (f, "" EOL); /* Print footer comment */ rspamd_fprintf (f, "" EOL EOL); return TRUE; } /* Modules */ static gboolean xml_dump_modules_paths (struct config_file *cfg, FILE *f) { GList *cur; gchar *escaped_value; struct script_module *module; /* Print header comment */ rspamd_fprintf (f, "" EOL); rspamd_fprintf (f, "" EOL); cur = cfg->script_modules; while (cur) { module = cur->data; escaped_value = g_markup_escape_text (module->path, -1); rspamd_fprintf (f, " %s" EOL, escaped_value); g_free (escaped_value); cur = g_list_next (cur); } rspamd_fprintf (f, "" EOL); /* Print footer comment */ rspamd_fprintf (f, "" EOL EOL); return TRUE; } #define CHECK_RES do { if (!res) { fclose (f); return FALSE; } } while (0) gboolean xml_dump_config (struct config_file *cfg, const gchar *filename) { FILE *f; gboolean res = FALSE; f = fopen (filename, "w"); if (f == NULL) { msg_err ("cannot open file '%s': %s", filename, strerror (errno)); return FALSE; } /* Header */ rspamd_fprintf (f, "" EOL "" EOL); /* Now dump all parts of config */ res = xml_dump_main (cfg, f); CHECK_RES; res = xml_dump_logging (cfg, f); CHECK_RES; res = xml_dump_variables (cfg, f); CHECK_RES; res = xml_dump_composites (cfg, f); CHECK_RES; res = xml_dump_workers (cfg, f); CHECK_RES; res = xml_dump_modules (cfg, f); CHECK_RES; res = xml_dump_classifiers (cfg, f); CHECK_RES; res = xml_dump_modules_paths (cfg, f); CHECK_RES; /* Footer */ rspamd_fprintf (f, "" EOL); fclose (f); return TRUE; }