/*
* Copyright (c) 2009-2012, Vsevolod Stakhov
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "cfg_file.h"
#include "main.h"
#include "filter.h"
#include "settings.h"
#include "classifiers/classifiers.h"
#include "cfg_xml.h"
#include "lua/lua_common.h"
#include "kvstorage_config.h"
#include "map.h"
#include "dynamic_cfg.h"
#define DEFAULT_SCORE 10.0
#define DEFAULT_RLIMIT_NOFILE 2048
#define DEFAULT_RLIMIT_MAXCORE 0
#define DEFAULT_MAP_TIMEOUT 10
struct rspamd_ucl_map_cbdata {
struct config_file *cfg;
GString *buf;
};
static gchar* rspamd_ucl_read_cb (memory_pool_t * pool, gchar * chunk, gint len, struct map_cb_data *data);
static void rspamd_ucl_fin_cb (memory_pool_t * pool, struct map_cb_data *data);
static gboolean
parse_host_port_priority_strv (memory_pool_t *pool, gchar **tokens,
gchar **addr, guint16 *port, guint *priority, guint default_port)
{
gchar *err_str, portbuf[8];
const gchar *cur_tok, *cur_port;
struct addrinfo hints, *res;
guint port_parsed, priority_parsed, saved_errno = errno;
gint r;
union {
struct sockaddr_in v4;
struct sockaddr_in6 v6;
} addr_holder;
/* Now try to parse host and write address to ina */
memset (&hints, 0, sizeof (hints));
hints.ai_socktype = SOCK_STREAM; /* Type of the socket */
hints.ai_flags = AI_NUMERICSERV;
cur_tok = tokens[0];
if (strcmp (cur_tok, "*v6") == 0) {
hints.ai_family = AF_INET6;
hints.ai_flags |= AI_PASSIVE;
cur_tok = NULL;
}
else if (strcmp (cur_tok, "*v4") == 0) {
hints.ai_family = AF_INET;
hints.ai_flags |= AI_PASSIVE;
cur_tok = NULL;
}
else {
hints.ai_family = AF_UNSPEC;
}
if (tokens[1] != NULL) {
/* Port part */
rspamd_strlcpy (portbuf, tokens[1], sizeof (portbuf));
cur_port = portbuf;
if (port != NULL) {
errno = 0;
port_parsed = strtoul (tokens[1], &err_str, 10);
if (*err_str != '\0' || errno != 0) {
msg_warn ("cannot parse port: %s, at symbol %c, error: %s", tokens[1], *err_str, strerror (errno));
hints.ai_flags ^= AI_NUMERICSERV;
}
else if (port_parsed > G_MAXUINT16) {
errno = ERANGE;
msg_warn ("cannot parse port: %s, error: %s", tokens[1], *err_str, strerror (errno));
hints.ai_flags ^= AI_NUMERICSERV;
}
else {
*port = port_parsed;
}
}
if (priority != NULL) {
if (port != NULL) {
cur_tok = tokens[2];
}
else {
cur_tok = tokens[1];
}
if (cur_tok != NULL) {
/* Priority part */
errno = 0;
priority_parsed = strtoul (cur_tok, &err_str, 10);
if (*err_str != '\0' || errno != 0) {
msg_warn ("cannot parse priority: %s, at symbol %c, error: %s", tokens[1], *err_str, strerror (errno));
}
else {
*priority = priority_parsed;
}
}
}
}
else if (default_port != 0) {
rspamd_snprintf (portbuf, sizeof (portbuf), "%ud", default_port);
cur_port = portbuf;
}
else {
cur_port = NULL;
}
if ((r = getaddrinfo (cur_tok, cur_port, &hints, &res)) == 0) {
memcpy (&addr_holder, res->ai_addr, MIN (sizeof (addr_holder), res->ai_addrlen));
if (res->ai_family == AF_INET) {
if (pool != NULL) {
*addr = memory_pool_alloc (pool, INET_ADDRSTRLEN + 1);
}
inet_ntop (res->ai_family, &addr_holder.v4.sin_addr, *addr, INET_ADDRSTRLEN + 1);
}
else {
if (pool != NULL) {
*addr = memory_pool_alloc (pool, INET6_ADDRSTRLEN + 1);
}
inet_ntop (res->ai_family, &addr_holder.v6.sin6_addr, *addr, INET6_ADDRSTRLEN + 1);
}
freeaddrinfo (res);
}
else {
msg_err ("address resolution for %s failed: %s", tokens[0], gai_strerror (r));
goto err;
}
/* Restore errno */
errno = saved_errno;
return TRUE;
err:
errno = saved_errno;
return FALSE;
}
gboolean
parse_host_port_priority (memory_pool_t *pool, const gchar *str, gchar **addr, guint16 *port, guint *priority)
{
gchar **tokens;
gboolean ret;
tokens = g_strsplit_set (str, ":", 0);
if (!tokens || !tokens[0]) {
return FALSE;
}
ret = parse_host_port_priority_strv (pool, tokens, addr, port, priority, 0);
g_strfreev (tokens);
return ret;
}
gboolean
parse_host_port (memory_pool_t *pool, const gchar *str, gchar **addr, guint16 *port)
{
return parse_host_port_priority (pool, str, addr, port, NULL);
}
gboolean
parse_host_priority (memory_pool_t *pool, const gchar *str, gchar **addr, guint *priority)
{
return parse_host_port_priority (pool, str, addr, NULL, priority);
}
gboolean
parse_bind_line (struct config_file *cfg, struct worker_conf *cf, const gchar *str)
{
struct rspamd_worker_bind_conf *cnf;
gchar **tokens, *tmp, *err;
gboolean ret = TRUE;
if (str == NULL) {
return FALSE;
}
tokens = g_strsplit_set (str, ":", 0);
if (!tokens || !tokens[0]) {
return FALSE;
}
cnf = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_worker_bind_conf));
cnf->bind_port = DEFAULT_BIND_PORT;
cnf->bind_host = memory_pool_strdup (cfg->cfg_pool, str);
cnf->ai = AF_UNSPEC;
if (*tokens[0] == '/' || *tokens[0] == '.') {
cnf->ai = AF_UNIX;
LL_PREPEND (cf->bind_conf, cnf);
return TRUE;
}
else if (strcmp (tokens[0], "*") == 0) {
/* We need to add two listen entries: one for ipv4 and one for ipv6 */
tmp = tokens[0];
tokens[0] = "*v4";
cnf->ai = AF_INET;
if ((ret = parse_host_port_priority_strv (cfg->cfg_pool, tokens,
&cnf->bind_host, &cnf->bind_port, NULL, DEFAULT_BIND_PORT))) {
LL_PREPEND (cf->bind_conf, cnf);
}
cnf = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_worker_bind_conf));
cnf->bind_port = DEFAULT_BIND_PORT;
cnf->bind_host = memory_pool_strdup (cfg->cfg_pool, str);
cnf->ai = AF_INET6;
tokens[0] = "*v6";
if ((ret &= parse_host_port_priority_strv (cfg->cfg_pool, tokens,
&cnf->bind_host, &cnf->bind_port, NULL, DEFAULT_BIND_PORT))) {
LL_PREPEND (cf->bind_conf, cnf);
}
tokens[0] = tmp;
}
else if (strcmp (tokens[0], "systemd") == 0) {
/* The actual socket will be passed by systemd environment */
cnf->bind_host = memory_pool_strdup (cfg->cfg_pool, str);
cnf->ai = strtoul (tokens[1], &err, 10);
cnf->is_systemd = TRUE;
if (err == NULL || *err == '\0') {
LL_PREPEND (cf->bind_conf, cnf);
}
}
else {
if ((ret = parse_host_port_priority_strv (cfg->cfg_pool, tokens,
&cnf->bind_host, &cnf->bind_port, NULL, DEFAULT_BIND_PORT))) {
LL_PREPEND (cf->bind_conf, cnf);
}
}
g_strfreev (tokens);
return ret;
}
void
init_defaults (struct config_file *cfg)
{
cfg->memcached_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
cfg->memcached_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
cfg->memcached_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
cfg->memcached_protocol = TCP_TEXT;
cfg->dns_timeout = 1000;
cfg->dns_retransmits = 5;
/* After 20 errors do throttling for 10 seconds */
cfg->dns_throttling_errors = 20;
cfg->dns_throttling_time = 10000;
/* 16 sockets per DNS server */
cfg->dns_io_per_server = 16;
cfg->statfile_sync_interval = 60000;
cfg->statfile_sync_timeout = 20000;
/* 20 Kb */
cfg->max_diff = 20480;
cfg->metrics = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
cfg->c_modules = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
cfg->composite_symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
cfg->classifiers_symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
cfg->cfg_params = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
cfg->metrics_symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
cfg->map_timeout = DEFAULT_MAP_TIMEOUT;
cfg->log_level = G_LOG_LEVEL_WARNING;
cfg->log_extended = TRUE;
init_settings (cfg);
}
void
free_config (struct config_file *cfg)
{
GList *cur;
struct symbols_group *gr;
remove_all_maps (cfg);
ucl_obj_unref (cfg->rcl_obj);
g_hash_table_remove_all (cfg->metrics);
g_hash_table_unref (cfg->metrics);
g_hash_table_remove_all (cfg->c_modules);
g_hash_table_unref (cfg->c_modules);
g_hash_table_remove_all (cfg->composite_symbols);
g_hash_table_unref (cfg->composite_symbols);
g_hash_table_remove_all (cfg->cfg_params);
g_hash_table_unref (cfg->cfg_params);
g_hash_table_destroy (cfg->metrics_symbols);
g_hash_table_destroy (cfg->classifiers_symbols);
/* Free symbols groups */
cur = cfg->symbols_groups;
while (cur) {
gr = cur->data;
if (gr->symbols) {
g_list_free (gr->symbols);
}
cur = g_list_next (cur);
}
if (cfg->symbols_groups) {
g_list_free (cfg->symbols_groups);
}
if (cfg->checksum) {
g_free (cfg->checksum);
}
g_list_free (cfg->classifiers);
g_list_free (cfg->metrics_list);
memory_pool_delete (cfg->cfg_pool);
}
ucl_object_t *
get_module_opt (struct config_file *cfg, const gchar *module_name, const gchar *opt_name)
{
ucl_object_t *res = NULL, *sec;
sec = ucl_obj_get_key (cfg->rcl_obj, module_name);
if (sec != NULL) {
res = ucl_obj_get_key (sec, opt_name);
}
return res;
}
guint64
parse_limit (const gchar *limit, guint len)
{
guint64 result = 0;
const gchar *err_str;
if (!limit || *limit == '\0' || len == 0) {
return 0;
}
errno = 0;
result = strtoull (limit, (gchar **)&err_str, 10);
if (*err_str != '\0') {
/* Megabytes */
if (*err_str == 'm' || *err_str == 'M') {
result *= 1048576L;
}
/* Kilobytes */
else if (*err_str == 'k' || *err_str == 'K') {
result *= 1024;
}
/* Gigabytes */
else if (*err_str == 'g' || *err_str == 'G') {
result *= 1073741824L;
}
else if (len > 0 && err_str - limit != (gint)len) {
msg_warn ("invalid limit value '%s' at position '%s'", limit, err_str);
result = 0;
}
}
return result;
}
gchar
parse_flag (const gchar *str)
{
guint len;
gchar c;
if (!str || !*str) {
return -1;
}
len = strlen (str);
switch (len) {
case 1:
c = g_ascii_tolower (*str);
if (c == 'y' || c == '1') {
return 1;
}
else if (c == 'n' || c == '0') {
return 0;
}
break;
case 2:
if (g_ascii_strncasecmp (str, "no", len) == 0) {
return 0;
}
else if (g_ascii_strncasecmp (str, "on", len) == 0) {
return 1;
}
break;
case 3:
if (g_ascii_strncasecmp (str, "yes", len) == 0) {
return 1;
}
else if (g_ascii_strncasecmp (str, "off", len) == 0) {
return 0;
}
break;
case 4:
if (g_ascii_strncasecmp (str, "true", len) == 0) {
return 1;
}
break;
case 5:
if (g_ascii_strncasecmp (str, "false", len) == 0) {
return 0;
}
break;
}
return -1;
}
gboolean
get_config_checksum (struct config_file *cfg)
{
gint fd;
void *map;
struct stat st;
/* Compute checksum for config file that should be used by xml dumper */
if ((fd = open (cfg->cfg_name, O_RDONLY)) == -1) {
msg_err ("config file %s is no longer available, cannot calculate checksum");
return FALSE;
}
if (stat (cfg->cfg_name, &st) == -1) {
msg_err ("cannot stat %s: %s", cfg->cfg_name, strerror (errno));
return FALSE;
}
/* Now mmap this file to simplify reading process */
if ((map = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
msg_err ("cannot mmap %s: %s", cfg->cfg_name, strerror (errno));
close (fd);
return FALSE;
}
close (fd);
/* Get checksum for a file */
cfg->checksum = g_compute_checksum_for_string (G_CHECKSUM_MD5, map, st.st_size);
munmap (map, st.st_size);
return TRUE;
}
/*
* Perform post load actions
*/
void
post_load_config (struct config_file *cfg)
{
#ifdef HAVE_CLOCK_GETTIME
struct timespec ts;
#endif
struct metric *def_metric;
#ifdef HAVE_CLOCK_GETTIME
#ifdef HAVE_CLOCK_PROCESS_CPUTIME_ID
clock_getres (CLOCK_PROCESS_CPUTIME_ID, &ts);
# elif defined(HAVE_CLOCK_VIRTUAL)
clock_getres (CLOCK_VIRTUAL, &ts);
# else
clock_getres (CLOCK_REALTIME, &ts);
# endif
cfg->clock_res = (gint)log10 (1000000 / ts.tv_nsec);
if (cfg->clock_res < 0) {
cfg->clock_res = 0;
}
if (cfg->clock_res > 3) {
cfg->clock_res = 3;
}
#else
/* For gettimeofday */
cfg->clock_res = 1;
#endif
if ((def_metric = g_hash_table_lookup (cfg->metrics, DEFAULT_METRIC)) == NULL) {
def_metric = check_metric_conf (cfg, NULL);
def_metric->name = DEFAULT_METRIC;
def_metric->actions[METRIC_ACTION_REJECT].score = DEFAULT_SCORE;
cfg->metrics_list = g_list_prepend (cfg->metrics_list, def_metric);
g_hash_table_insert (cfg->metrics, DEFAULT_METRIC, def_metric);
}
cfg->default_metric = def_metric;
/* Lua options */
(void)lua_post_load_config (cfg);
init_dynamic_config (cfg);
}
#if 0
void
parse_err (const gchar *fmt, ...)
{
va_list aq;
gchar logbuf[BUFSIZ], readbuf[32];
gint r;
va_start (aq, fmt);
rspamd_strlcpy (readbuf, yytext, sizeof (readbuf));
r = snprintf (logbuf, sizeof (logbuf), "config file parse error! line: %d, text: %s, reason: ", yylineno, readbuf);
r += vsnprintf (logbuf + r, sizeof (logbuf) - r, fmt, aq);
va_end (aq);
g_critical ("%s", logbuf);
}
void
parse_warn (const gchar *fmt, ...)
{
va_list aq;
gchar logbuf[BUFSIZ], readbuf[32];
gint r;
va_start (aq, fmt);
rspamd_strlcpy (readbuf, yytext, sizeof (readbuf));
r = snprintf (logbuf, sizeof (logbuf), "config file parse warning! line: %d, text: %s, reason: ", yylineno, readbuf);
r += vsnprintf (logbuf + r, sizeof (logbuf) - r, fmt, aq);
va_end (aq);
g_warning ("%s", logbuf);
}
#endif
void
unescape_quotes (gchar *line)
{
gchar *c = line, *t;
while (*c) {
if (*c == '\\' && *(c + 1) == '"') {
t = c;
while (*t) {
*t = *(t + 1);
t++;
}
}
c++;
}
}
GList *
parse_comma_list (memory_pool_t * pool, const gchar *line)
{
GList *res = NULL;
const gchar *c, *p;
gchar *str;
c = line;
p = c;
while (*p) {
if (*p == ',' && *c != *p) {
str = memory_pool_alloc (pool, p - c + 1);
rspamd_strlcpy (str, c, p - c + 1);
res = g_list_prepend (res, str);
/* Skip spaces */
while (g_ascii_isspace (*(++p)));
c = p;
continue;
}
p++;
}
if (res != NULL) {
memory_pool_add_destructor (pool, (pool_destruct_func) g_list_free, res);
}
return res;
}
struct classifier_config *
check_classifier_conf (struct config_file *cfg, struct classifier_config *c)
{
if (c == NULL) {
c = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct classifier_config));
}
if (c->opts == NULL) {
c->opts = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func) g_hash_table_destroy, c->opts);
}
if (c->labels == NULL) {
c->labels = g_hash_table_new_full (rspamd_str_hash, rspamd_str_equal, NULL, (GDestroyNotify)g_list_free);
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func) g_hash_table_destroy, c->labels);
}
return c;
}
struct statfile*
check_statfile_conf (struct config_file *cfg, struct statfile *c)
{
if (c == NULL) {
c = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct statfile));
}
return c;
}
struct metric *
check_metric_conf (struct config_file *cfg, struct metric *c)
{
int i;
if (c == NULL) {
c = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct metric));
c->grow_factor = 1.0;
c->symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
c->descriptions = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i ++) {
c->actions[i].score = -1.0;
}
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func) g_hash_table_destroy, c->symbols);
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func) g_hash_table_destroy, c->descriptions);
}
return c;
}
struct worker_conf *
check_worker_conf (struct config_file *cfg, struct worker_conf *c)
{
if (c == NULL) {
c = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct worker_conf));
c->params = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
c->active_workers = g_queue_new ();
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)g_hash_table_destroy, c->params);
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)g_queue_free, c->active_workers);
#ifdef HAVE_SC_NPROCESSORS_ONLN
c->count = sysconf (_SC_NPROCESSORS_ONLN);
#else
c->count = DEFAULT_WORKERS_NUM;
#endif
c->rlimit_nofile = DEFAULT_RLIMIT_NOFILE;
c->rlimit_maxcore = DEFAULT_RLIMIT_MAXCORE;
}
return c;
}
static GMarkupParser xml_parser = {
.start_element = rspamd_xml_start_element,
.end_element = rspamd_xml_end_element,
.passthrough = NULL,
.text = rspamd_xml_text,
.error = rspamd_xml_error,
};
static const char*
get_filename_extension (const char *filename)
{
const char *dot_pos = strrchr (filename, '.');
if (dot_pos != NULL) {
return (dot_pos + 1);
}
return NULL;
}
static bool
rspamd_include_map_handler (const guchar *data, gsize len, void* ud)
{
struct config_file *cfg = (struct config_file *)ud;
struct rspamd_ucl_map_cbdata *cbdata, **pcbdata;
gchar *map_line;
map_line = memory_pool_alloc (cfg->cfg_pool, len + 1);
rspamd_strlcpy (map_line, data, len + 1);
cbdata = g_malloc (sizeof (struct rspamd_ucl_map_cbdata));
pcbdata = g_malloc (sizeof (struct rspamd_ucl_map_cbdata *));
cbdata->buf = NULL;
cbdata->cfg = cfg;
*pcbdata = cbdata;
return add_map (cfg, map_line, "ucl include", rspamd_ucl_read_cb, rspamd_ucl_fin_cb, (void **)pcbdata);
}
/*
* Variables:
* $CONFDIR - configuration directory
* $RUNDIR - local states directory
* $DBDIR - databases dir
* $LOGDIR - logs dir
* $PLUGINSDIR - pluggins dir
* $PREFIX - installation prefix
* $VERSION - rspamd version
*/
#define RSPAMD_CONFDIR_MACRO "CONFDIR"
#define RSPAMD_RUNDIR_MACRO "RUNDIR"
#define RSPAMD_DBDIR_MACRO "DBDIR"
#define RSPAMD_LOGDIR_MACRO "LOGDIR"
#define RSPAMD_PLUGINSDIR_MACRO "PLUGINSDIR"
#define RSPAMD_PREFIX_MACRO "PREFIX"
#define RSPAMD_VERSION_MACRO "VERSION"
static void
rspamd_ucl_add_conf_variables (struct ucl_parser *parser)
{
ucl_parser_register_variable (parser, RSPAMD_CONFDIR_MACRO, RSPAMD_CONFDIR);
ucl_parser_register_variable (parser, RSPAMD_RUNDIR_MACRO, RSPAMD_RUNDIR);
ucl_parser_register_variable (parser, RSPAMD_DBDIR_MACRO, RSPAMD_DBDIR);
ucl_parser_register_variable (parser, RSPAMD_LOGDIR_MACRO, RSPAMD_LOGDIR);
ucl_parser_register_variable (parser, RSPAMD_PLUGINSDIR_MACRO, RSPAMD_PLUGINSDIR);
ucl_parser_register_variable (parser, RSPAMD_PREFIX_MACRO, RSPAMD_PREFIX);
ucl_parser_register_variable (parser, RSPAMD_VERSION_MACRO, RVERSION);
}
static void
rspamd_ucl_add_conf_macros (struct ucl_parser *parser, struct config_file *cfg)
{
ucl_parser_register_macro (parser, "include_map", rspamd_include_map_handler, cfg);
}
gboolean
read_rspamd_config (struct config_file *cfg, const gchar *filename,
const gchar *convert_to, rspamd_rcl_section_fin_t logger_fin,
gpointer logger_ud)
{
struct stat st;
gint fd;
gchar *data, *rcl;
const gchar *ext;
GMarkupParseContext *ctx;
GError *err = NULL;
struct rspamd_rcl_section *top, *logger;
gboolean res, is_xml = FALSE;
struct rspamd_xml_userdata ud;
struct ucl_parser *parser;
if (stat (filename, &st) == -1) {
msg_err ("cannot stat %s: %s", filename, strerror (errno));
return FALSE;
}
if ((fd = open (filename, O_RDONLY)) == -1) {
msg_err ("cannot open %s: %s", filename, strerror (errno));
return FALSE;
}
/* Now mmap this file to simplify reading process */
if ((data = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
msg_err ("cannot mmap %s: %s", filename, strerror (errno));
close (fd);
return FALSE;
}
close (fd);
if (convert_to != NULL) {
is_xml = TRUE;
}
else {
ext = get_filename_extension (filename);
if (ext != NULL && strcmp (ext, "xml") == 0) {
is_xml = TRUE;
}
}
if (is_xml) {
/* Prepare xml parser */
memset (&ud, 0, sizeof (ud));
ud.cfg = cfg;
ud.state = 0;
ctx = g_markup_parse_context_new (&xml_parser, G_MARKUP_TREAT_CDATA_AS_TEXT, &ud, NULL);
res = g_markup_parse_context_parse (ctx, data, st.st_size, &err);
munmap (data, st.st_size);
}
else {
parser = ucl_parser_new (0);
rspamd_ucl_add_conf_variables (parser);
rspamd_ucl_add_conf_macros (parser, cfg);
if (!ucl_parser_add_chunk (parser, data, st.st_size)) {
msg_err ("ucl parser error: %s", ucl_parser_get_error (parser));
ucl_parser_free (parser);
munmap (data, st.st_size);
return FALSE;
}
munmap (data, st.st_size);
cfg->rcl_obj = ucl_parser_get_object (parser);
ucl_parser_free (parser);
res = TRUE;
}
if (!res) {
return FALSE;
}
if (is_xml && convert_to != NULL) {
/* Convert XML config to UCL */
rcl = ucl_object_emit (cfg->rcl_obj, UCL_EMIT_CONFIG);
if (rcl != NULL) {
fd = open (convert_to, O_CREAT|O_TRUNC|O_WRONLY, 00644);
if (fd == -1) {
msg_err ("cannot open %s: %s", convert_to, strerror (errno));
}
else if (write (fd, rcl, strlen (rcl)) == -1) {
msg_err ("cannot write rcl %s: %s", convert_to, strerror (errno));
}
else {
msg_info ("dumped xml configuration %s to ucl configuration %s",
filename, convert_to);
}
close (fd);
free (rcl);
}
}
top = rspamd_rcl_config_init ();
err = NULL;
HASH_FIND_STR(top, "logging", logger);
if (logger != NULL) {
logger->fin = logger_fin;
logger->fin_ud = logger_ud;
}
if (!rspamd_read_rcl_config (top, cfg, cfg->rcl_obj, &err)) {
msg_err ("rcl parse error: %s", err->message);
return FALSE;
}
return TRUE;
}
static void
symbols_classifiers_callback (gpointer key, gpointer value, gpointer ud)
{
struct config_file *cfg = ud;
register_virtual_symbol (&cfg->cache, key, 1.0);
}
void
insert_classifier_symbols (struct config_file *cfg)
{
g_hash_table_foreach (cfg->classifiers_symbols, symbols_classifiers_callback, cfg);
}
struct classifier_config*
find_classifier_conf (struct config_file *cfg, const gchar *name)
{
GList *cur;
struct classifier_config *cf;
if (name == NULL) {
return NULL;
}
cur = cfg->classifiers;
while (cur) {
cf = cur->data;
if (g_ascii_strcasecmp (cf->classifier->name, name) == 0) {
return cf;
}
cur = g_list_next (cur);
}
return NULL;
}
gboolean
check_classifier_statfiles (struct classifier_config *cf)
{
struct statfile *st;
gboolean has_other = FALSE, res = FALSE, cur_class;
GList *cur;
/* First check classes directly */
cur = cf->statfiles;
while (cur) {
st = cur->data;
if (!has_other) {
cur_class = st->is_spam;
has_other = TRUE;
}
else {
if (cur_class != st->is_spam) {
return TRUE;
}
}
cur = g_list_next (cur);
}
if (!has_other) {
/* We have only one statfile */
return FALSE;
}
/* We have not detected any statfile that has different class, so turn on euristic based on symbol's name */
has_other = FALSE;
cur = cf->statfiles;
while (cur) {
st = cur->data;
if (rspamd_strncasestr (st->symbol, "spam", -1) != NULL) {
st->is_spam = TRUE;
}
else if (rspamd_strncasestr (st->symbol, "ham", -1) != NULL) {
st->is_spam = FALSE;
}
if (!has_other) {
cur_class = st->is_spam;
has_other = TRUE;
}
else {
if (cur_class != st->is_spam) {
res = TRUE;
}
}
cur = g_list_next (cur);
}
return res;
}
static gchar*
rspamd_ucl_read_cb (memory_pool_t * pool, gchar * chunk, gint len, struct map_cb_data *data)
{
struct rspamd_ucl_map_cbdata *cbdata = data->cur_data, *prev;
if (cbdata == NULL) {
cbdata = g_malloc (sizeof (struct rspamd_ucl_map_cbdata));
prev = data->prev_data;
cbdata->buf = g_string_sized_new (BUFSIZ);
cbdata->cfg = prev->cfg;
data->cur_data = cbdata;
}
g_string_append_len (cbdata->buf, chunk, len);
/* Say not to copy any part of this buffer */
return NULL;
}
static void
rspamd_ucl_fin_cb (memory_pool_t * pool, struct map_cb_data *data)
{
struct rspamd_ucl_map_cbdata *cbdata = data->cur_data, *prev = data->prev_data;
ucl_object_t *obj;
struct ucl_parser *parser;
guint32 checksum;
if (prev != NULL) {
if (prev->buf != NULL) {
g_string_free (prev->buf, TRUE);
}
g_free (prev);
}
if (cbdata == NULL) {
msg_err ("map fin error: new data is NULL");
return;
}
checksum = murmur32_hash (cbdata->buf->str, cbdata->buf->len);
if (data->map->checksum != checksum) {
/* New data available */
parser = ucl_parser_new (0);
if (!ucl_parser_add_chunk (parser, cbdata->buf->str, cbdata->buf->len)) {
msg_err ("cannot parse map %s: %s", data->map->uri, ucl_parser_get_error (parser));
ucl_parser_free (parser);
}
else {
obj = ucl_parser_get_object (parser);
ucl_parser_free (parser);
/* XXX: add replace objects code */
ucl_object_unref (obj);
data->map->checksum = checksum;
}
}
else {
msg_info ("do not reload map %s, checksum is the same: %d", data->map->uri, checksum);
}
}
gboolean
rspamd_parse_ip_list (const gchar *ip_list, radix_tree_t **tree)
{
gchar **strvec, **cur;
struct in_addr ina;
guint32 mask;
strvec = g_strsplit_set (ip_list, ",", 0);
cur = strvec;
while (*cur != NULL) {
/* XXX: handle only ipv4 addresses */
if (parse_ipmask_v4 (*cur, &ina, &mask)) {
if (*tree == NULL) {
*tree = radix_tree_create ();
}
radix32tree_add (*tree, htonl (ina.s_addr), mask, 1);
}
cur ++;
}
return (*tree != NULL);
}
/*
* vi:ts=4
*/
table30
Nextcloud server, a safe home for all your data: https://github.com/nextcloud/server | www-data |
blob: 8d1c8b69c3fce7bea45c73efd06983e3c419a92f (
plain)