(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 <rspamd></rspamd> */ *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; case MODULE_OPT_TYPE_FLAG: if (parse_flag (data) == -1) { 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, "<!-- Main section -->" EOL); escaped_str = g_markup_escape_text (cfg->temp_dir, -1); rspamd_fprintf (f, "<tempdir>%s</tempdir>" EOL, escaped_str); g_free (escaped_str); escaped_str = g_markup_escape_text (cfg->pid_file, -1); rspamd_fprintf (f, "<pidfile>%s</pidfile>" EOL, escaped_str); g_free (escaped_str); escaped_str = g_markup_escape_text (cfg->filters_str, -1); rspamd_fprintf (f, "<filters>%s</filters>" 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, "<user_settings>%s</user_settings>" 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, "<domain_settings>%s</domain_settings>" EOL, escaped_str); g_free (escaped_str); } rspamd_fprintf (f, "<statfile_pool_size>%z</statfile_pool_size>" EOL, cfg->max_statfile_size); if (cfg->checksum) { escaped_str = g_markup_escape_text (cfg->checksum, -1); rspamd_fprintf (f, "<checksum>%s</checksum>" EOL, escaped_str); g_free (escaped_str); } rspamd_fprintf (f, "<raw_mode>%s</raw_mode>" EOL, cfg->raw_mode ? "yes" : "no"); /* Print footer comment */ rspamd_fprintf (f, "<!-- End of main section -->" 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, "<variable name=\"%s\">%s</variable>" 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, "<!-- Variables section -->" EOL); /* Iterate through variables */ g_hash_table_foreach (cfg->variables, xml_variable_callback, (gpointer)f); /* Print footer comment */ rspamd_fprintf (f, "<!-- End of variables section -->" 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, "<composite name=\"%s\">%s</composite>" 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, "<!-- Composites section -->" EOL); /* Iterate through variables */ g_hash_table_foreach (cfg->composite_symbols, xml_composite_callback, (gpointer)f); /* Print footer comment */ rspamd_fprintf (f, "<!-- End of composites section -->" 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, " <param name=\"%s\">%s</param>" 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, "<!-- Workers section -->" EOL); /* Iterate through list */ cur = g_list_first (cfg->workers); while (cur) { wrk = cur->data; rspamd_fprintf (f, "<worker>" EOL); rspamd_fprintf (f, " <type>%s</type>" EOL, g_quark_to_string (wrk->type)); escaped_str = g_markup_escape_text (wrk->bind_host, -1); rspamd_fprintf (f, " <bind_socket>%s</bind_socket>" EOL, escaped_str); g_free (escaped_str); rspamd_fprintf (f, " <count>%ud</count>" EOL, wrk->count); rspamd_fprintf (f, " <maxfiles>%ud</maxfiles>" EOL, wrk->rlimit_nofile); rspamd_fprintf (f, " <maxcore>%ud</maxcore>" EOL, wrk->rlimit_maxcore); /* Now dump other attrs */ rspamd_fprintf (f, "<!-- Other params -->" EOL); g_hash_table_foreach (wrk->params, xml_worker_param_callback, f); rspamd_fprintf (f, "</worker>" EOL); cur = g_list_next (cur); } /* Print footer comment */ rspamd_fprintf (f, "<!-- End of workers section -->" 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, "<!-- %s -->" EOL, escaped_key); rspamd_fprintf (f, "<module name=\"%s\">" 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, " <option name=\"%s\">%s</option>" EOL, escaped_key, escaped_value); g_free (escaped_key); g_free (escaped_value); cur = g_list_next (cur); } rspamd_fprintf (f, "</module>" EOL EOL); } static gboolean xml_dump_modules (struct config_file *cfg, FILE *f) { /* Print header comment */ rspamd_fprintf (f, "<!-- Modules section -->" EOL); /* Iterate through variables */ g_hash_table_foreach (cfg->modules_opts, xml_module_callback, (gpointer)f); /* Print footer comment */ rspamd_fprintf (f, "<!-- End of modules section -->" 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, " <option name=\"%s\">%s</option>" 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, "<!-- Classifiers section -->" EOL); /* Iterate through classifiers */ cur = g_list_first (cfg->classifiers); while (cur) { ccf = cur->data; rspamd_fprintf (f, "<classifier type=\"%s\">" EOL, ccf->classifier->name); rspamd_fprintf (f, " <tokenizer>%s</tokenizer>" EOL, ccf->tokenizer->name); rspamd_fprintf (f, " <metric>%s</metric>" 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, " <statfile>" EOL); rspamd_fprintf (f, " <symbol>%s</symbol>" EOL " <size>%z</size>" EOL " <path>%s</path>" EOL, st->symbol, st->size, st->path); rspamd_fprintf (f, " <normalizer>%s</normalizer>" EOL, st->normalizer_str); /* Binlog */ if (st->binlog) { if (st->binlog->affinity == AFFINITY_MASTER) { rspamd_fprintf (f, " <binlog>master</binlog>" EOL); } else if (st->binlog->affinity == AFFINITY_SLAVE) { rspamd_fprintf (f, " <binlog>slave</binlog>" EOL); rspamd_fprintf (f, " <binlog_master>%s:%d</binlog_master>" EOL, inet_ntoa (st->binlog->master_addr), (gint)ntohs (st->binlog->master_port)); } rspamd_fprintf (f, " <binlog_rotate>%T</binlog_rotate>" EOL, st->binlog->rotate_time); } rspamd_fprintf (f, " </statfile>" EOL); cur_st = g_list_next (cur_st); } rspamd_fprintf (f, "</classifier>" EOL); cur = g_list_next (cur); } /* Print footer comment */ rspamd_fprintf (f, "<!-- End of classifiers section -->" 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, "<!-- Logging section -->" EOL); rspamd_fprintf (f, "<logging>" EOL); /* Level */ if (cfg->log_level < G_LOG_LEVEL_WARNING) { rspamd_fprintf (f, " <level>error</level>" EOL); } else if (cfg->log_level < G_LOG_LEVEL_MESSAGE) { rspamd_fprintf (f, " <level>warning</level>" EOL); } else if (cfg->log_level < G_LOG_LEVEL_DEBUG) { rspamd_fprintf (f, " <level>info</level>" EOL); } else { rspamd_fprintf (f, " <level>debug</level>" EOL); } /* Other options */ rspamd_fprintf (f, " <log_urls>%s</log_urls>" EOL, cfg->log_urls ? "yes" : "no"); if (cfg->log_buf_size != 0) { rspamd_fprintf (f, " <log_buffer>%ud</log_buffer>" 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, " <debug_ip>%s</debug_ip>" 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, " <type filename=\"%s\">file</type>" EOL, escaped_value); g_free (escaped_value); } else if (cfg->log_type == RSPAMD_LOG_CONSOLE) { rspamd_fprintf (f, " <type>console</type>" 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, " <type facility=\"%s\">syslog</type>" EOL, escaped_value); } rspamd_fprintf (f, "</logging>" EOL); /* Print footer comment */ rspamd_fprintf (f, "<!-- End of logging section -->" 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, "<!-- Modules section -->" EOL); rspamd_fprintf (f, "<modules>" EOL); cur = cfg->script_modules; while (cur) { module = cur->data; escaped_value = g_markup_escape_text (module->path, -1); rspamd_fprintf (f, " <path>%s</path>" EOL, escaped_value); g_free (escaped_value); cur = g_list_next (cur); } rspamd_fprintf (f, "</modules>" EOL); /* Print footer comment */ rspamd_fprintf (f, "<!-- End of modules section -->" 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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" EOL "<rspamd>" 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, "</rspamd>" EOL); fclose (f); return TRUE; }