(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;
}