From: Vsevolod Stakhov Date: Wed, 30 Apr 2014 16:23:37 +0000 (+0100) Subject: Place modules.c in BINDIR not SRCDIR. X-Git-Tag: 0.7.0~256 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7256893004d0027de0b81410e7b379a03cdbe719;p=thirdparty%2Frspamd.git Place modules.c in BINDIR not SRCDIR. --- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 332adbe1f6..1d04476f88 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,9 @@ MACRO(_AddModulesForced MLIST WLIST) # Generate unique string for this build STRING(RANDOM LENGTH 8 _MODULES_ID) SET(MODULES_ID ${_MODULES_ID} CACHE INTERNAL "Modules ID" FORCE) - FILE(WRITE "modules.c" "/* ${MODULES_ID} */\n#include \"config.h\"\n") + SET(MODULES_C "${CMAKE_CURRENT_BINARY_DIR}/modules.c") + FILE(WRITE "${MODULES_C}" + "/* ${MODULES_ID} */\n#include \"config.h\"\n") # Handle even old cmake LIST(LENGTH ${MLIST} MLIST_COUNT) @@ -12,30 +14,30 @@ MACRO(_AddModulesForced MLIST WLIST) FOREACH(MOD_IDX RANGE ${MLIST_MAX}) LIST(GET ${MLIST} ${MOD_IDX} MOD) - FILE(APPEND "modules.c" "extern module_t ${MOD}_module;\n") + FILE(APPEND "${MODULES_C}" "extern module_t ${MOD}_module;\n") ENDFOREACH(MOD_IDX RANGE ${MLIST_MAX}) - FILE(APPEND "modules.c" "\n\nmodule_t *modules[] = {\n") + FILE(APPEND "${MODULES_C}" "\n\nmodule_t *modules[] = {\n") FOREACH(MOD_IDX RANGE ${MLIST_MAX}) LIST(GET ${MLIST} ${MOD_IDX} MOD) - FILE(APPEND "modules.c" "&${MOD}_module,\n") + FILE(APPEND "${MODULES_C}" "&${MOD}_module,\n") ENDFOREACH(MOD_IDX RANGE ${MLIST_MAX}) - FILE(APPEND "modules.c" "NULL\n};\n") + FILE(APPEND "${MODULES_C}" "NULL\n};\n") FOREACH(MOD_IDX RANGE ${WLIST_MAX}) LIST(GET ${WLIST} ${MOD_IDX} WRK) - FILE(APPEND "modules.c" "extern worker_t ${WRK}_worker;\n") + FILE(APPEND "${MODULES_C}" "extern worker_t ${WRK}_worker;\n") ENDFOREACH(MOD_IDX RANGE ${WLIST_MAX}) - FILE(APPEND "modules.c" "\n\nworker_t *workers[] = {\n") + FILE(APPEND "${MODULES_C}" "\n\nworker_t *workers[] = {\n") FOREACH(MOD_IDX RANGE ${WLIST_MAX}) LIST(GET ${WLIST} ${MOD_IDX} WRK) - FILE(APPEND "modules.c" "&${WRK}_worker,\n") + FILE(APPEND "${MODULES_C}" "&${WRK}_worker,\n") ENDFOREACH(MOD_IDX RANGE ${WLIST_MAX}) - FILE(APPEND "modules.c" "NULL\n};\n") + FILE(APPEND "${MODULES_C}" "NULL\n};\n") ENDMACRO(_AddModulesForced MLIST WLIST) MACRO(AddModules MLIST WLIST) diff --git a/src/controller.c b/src/controller.c index 05b1794cd9..aa009f9e56 100644 --- a/src/controller.c +++ b/src/controller.c @@ -1,16 +1,15 @@ -/* - * Copyright (c) 2009-2012, Vsevolod Stakhov +/* Copyright (c) 2010-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. + * * 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 + * THIS SOFTWARE IS PROVIDED ''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 @@ -22,9 +21,11 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include "config.h" #include "util.h" #include "main.h" +#include "http.h" #include "message.h" #include "protocol.h" #include "upstream.h" @@ -33,1915 +34,1646 @@ #include "dns.h" #include "tokenizers/tokenizers.h" #include "classifiers/classifiers.h" -#include "binlog.h" -#include "statfile_sync.h" -#include "lua/lua_common.h" #include "dynamic_cfg.h" #include "rrd.h" -#define END "END" CRLF - -/* 120 seconds for controller's IO */ -#define CONTROLLER_IO_TIMEOUT 120 - -/* RRD macroes */ -/* Write data each minute */ -#define CONTROLLER_RRD_STEP 60 - -/* Init functions */ -gpointer init_controller (struct rspamd_config *cfg); -void start_controller (struct rspamd_worker *worker); +#ifdef WITH_GPERF_TOOLS +# include +#endif -worker_t controller_worker = { - "controller", /* Name */ - init_controller, /* Init function */ - start_controller, /* Start function */ - TRUE, /* Has socket */ - FALSE, /* Non unique */ - FALSE, /* Non threaded */ - TRUE, /* Killable */ - SOCK_STREAM /* TCP socket */ +/* 60 seconds for worker's IO */ +#define DEFAULT_WORKER_IO_TIMEOUT 60000 + +/* HTTP paths */ +#define PATH_AUTH "/auth" +#define PATH_SYMBOLS "/symbols" +#define PATH_ACTIONS "/actions" +#define PATH_MAPS "/maps" +#define PATH_GET_MAP "/getmap" +#define PATH_GRAPH "/graph" +#define PATH_PIE_CHART "/pie" +#define PATH_HISTORY "/history" +#define PATH_LEARN_SPAM "/learnspam" +#define PATH_LEARN_HAM "/learnham" +#define PATH_SAVE_ACTIONS "/saveactions" +#define PATH_SAVE_SYMBOLS "/savesymbols" +#define PATH_SAVE_MAP "/savemap" +#define PATH_SCAN "/scan" +#define PATH_STAT "/stat" +#define PATH_STAT_RESET "/statreset" +#define PATH_COUNTERS "/counters" + +/* Graph colors */ +#define COLOR_CLEAN "#58A458" +#define COLOR_PROBABLE_SPAM "#D67E7E" +#define COLOR_GREYLIST "#A0A0A0" +#define COLOR_REJECT "#CB4B4B" +#define COLOR_TOTAL "#9440ED" + +gpointer init_webui_worker (struct rspamd_config *cfg); +void start_webui_worker (struct rspamd_worker *worker); + +worker_t webui_worker = { + "webui", /* Name */ + init_webui_worker, /* Init function */ + start_webui_worker, /* Start function */ + TRUE, /* Has socket */ + TRUE, /* Non unique */ + FALSE, /* Non threaded */ + TRUE, /* Killable */ + SOCK_STREAM /* TCP socket */ }; - -enum command_type { - COMMAND_PASSWORD, - COMMAND_QUIT, - COMMAND_RELOAD, - COMMAND_STAT, - COMMAND_STAT_RESET, - COMMAND_SHUTDOWN, - COMMAND_UPTIME, - COMMAND_LEARN, - COMMAND_LEARN_SPAM, - COMMAND_LEARN_HAM, - COMMAND_HELP, - COMMAND_COUNTERS, - COMMAND_SYNC, - COMMAND_WEIGHTS, - COMMAND_GET, - COMMAND_POST, - COMMAND_ADD_SYMBOL, - COMMAND_ADD_ACTION +/* + * Worker's context + */ +struct rspamd_webui_worker_ctx { + guint32 timeout; + struct timeval io_tv; + /* DNS resolver */ + struct rspamd_dns_resolver *resolver; + /* Events base */ + struct event_base *ev_base; + /* Whether we use ssl for this server */ + gboolean use_ssl; + /* Webui password */ + gchar *password; + /* Privilleged password */ + gchar *enable_password; + /* HTTP server */ + struct rspamd_http_connection_router *http; + /* Server's start time */ + time_t start_time; + /* Main server */ + struct rspamd_main *srv; + /* Configuration */ + struct rspamd_config *cfg; + /* SSL cert */ + gchar *ssl_cert; + /* SSL private key */ + gchar *ssl_key; + /* A map of secure IP */ + gchar *secure_ip; + radix_tree_t *secure_map; + + /* Static files dir */ + gchar *static_files_dir; + + /* Custom commands registered by plugins */ + GHashTable *custom_commands; + + /* Worker */ + struct rspamd_worker *worker; }; -struct controller_command { - gchar *command; - gboolean privilleged; - enum command_type type; +struct rspamd_webui_session { + struct rspamd_webui_worker_ctx *ctx; + rspamd_mempool_t *pool; + struct rspamd_task *task; + struct rspamd_classifier_config *cl; + rspamd_inet_addr_t from_addr; + gboolean is_spam; }; -struct custom_controller_command { - const gchar *command; - gboolean privilleged; - gboolean require_message; - controller_func_t handler; -}; +sig_atomic_t wanna_die = 0; -struct rspamd_controller_ctx { - char *password; - guint32 timeout; - struct rspamd_dns_resolver *resolver; - struct event_base *ev_base; - struct event rrd_event; - struct rspamd_rrd_file *rrd_file; - struct rspamd_main *srv; -}; +/* Check for password if it is required by configuration */ +static gboolean +rspamd_webui_check_password (struct rspamd_http_connection_entry *entry, + struct rspamd_webui_session *session, struct rspamd_http_message *msg, + gboolean is_enable) +{ + const gchar *password, *check; + struct rspamd_webui_worker_ctx *ctx = session->ctx; + gboolean ret = TRUE; -static struct controller_command commands[] = { - {"password", FALSE, COMMAND_PASSWORD}, - {"quit", FALSE, COMMAND_QUIT}, - {"reload", TRUE, COMMAND_RELOAD}, - {"stat", FALSE, COMMAND_STAT}, - {"stat_reset", TRUE, COMMAND_STAT_RESET}, - {"shutdown", TRUE, COMMAND_SHUTDOWN}, - {"uptime", FALSE, COMMAND_UPTIME}, - {"learn", TRUE, COMMAND_LEARN}, - {"weights", FALSE, COMMAND_WEIGHTS}, - {"help", FALSE, COMMAND_HELP}, - {"counters", FALSE, COMMAND_COUNTERS}, - {"sync", FALSE, COMMAND_SYNC}, - {"learn_spam", TRUE, COMMAND_LEARN_SPAM}, - {"learn_ham", TRUE, COMMAND_LEARN_HAM}, - {"get", FALSE, COMMAND_GET}, - {"post", FALSE, COMMAND_POST}, - {"add_symbol", TRUE, COMMAND_ADD_SYMBOL}, - {"add_action", TRUE, COMMAND_ADD_ACTION} -}; + /* Access list logic */ + if (!session->from_addr.af == AF_UNIX) { + msg_info ("allow unauthorized connection from a unix socket"); + return TRUE; + } + else if (ctx->secure_map && radix32_tree_find_addr (ctx->secure_map, + &session->from_addr) != RADIX_NO_VALUE) { + msg_info ("allow unauthorized connection from a trusted IP %s", + rspamd_inet_address_to_string (&session->from_addr)); + return TRUE; + } + + /* Password logic */ + if (is_enable) { + /* For privileged commands we strictly require enable password */ + password = rspamd_http_message_find_header (msg, "Password"); + if (ctx->enable_password == NULL) { + /* Use just a password (legacy mode) */ + msg_info ("using password as enable_password for a privileged command"); + check = ctx->password; + } + if (check != NULL) { + if (password == NULL || strcmp (password, check) != 0) { + msg_info ("incorrect or absent password has been specified"); + ret = FALSE; + } + } + else { + msg_warn ("no password to check while executing a privileged command"); + if (ctx->secure_map) { + msg_info ("deny unauthorized connection"); + ret = FALSE; + } + } + } + else { + password = rspamd_http_message_find_header (msg, "Password"); + if (ctx->password != NULL) { + /* Accept both normal and enable passwords */ + check = ctx->password; + if (password == NULL) { + msg_info ("absent password has been specified"); + ret = FALSE; + } + else if (strcmp (password, check) != 0) { + if (ctx->enable_password != NULL) { + check = ctx->enable_password; + if (strcmp (password, check) != 0) { + msg_info ("incorrect password has been specified"); + ret = FALSE; + } + } + else { + msg_info ("incorrect or absent password has been specified"); + ret = FALSE; + } + } + } + /* No password specified, allowing this command */ + } -static GList *custom_commands = NULL; -static time_t start_time; + if (!ret) { + rspamd_controller_send_error (entry, 403, "Unauthorized"); + } -static gchar greetingbuf[1024]; + return ret; +} -static gboolean controller_write_socket (void *arg); +/* Command handlers */ -static void -free_session (void *ud) +/* + * Auth command handler: + * request: /auth + * headers: Password + * reply: json {"auth": "ok", "version": "0.5.2", "uptime": "some uptime", "error": "none"} + */ +static int +rspamd_webui_handle_auth (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - GList *part; - struct mime_part *p; - struct controller_session *session = ud; - - msg_debug ("freeing session %p", session); + struct rspamd_webui_session *session = conn_ent->ud; + struct rspamd_stat *st; + int64_t uptime; + gulong data[4]; + ucl_object_t *obj; - while ((part = g_list_first (session->parts))) { - session->parts = g_list_remove_link (session->parts, part); - p = (struct mime_part *)part->data; - g_byte_array_free (p->content, FALSE); - g_list_free_1 (part); + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - rspamd_remove_dispatcher (session->dispatcher); - if (session->kwargs) { - g_hash_table_destroy (session->kwargs); - } + obj = ucl_object_typed_new (UCL_OBJECT); + st = session->ctx->srv->stat; + data[0] = st->actions_stat[METRIC_ACTION_NOACTION]; + data[1] = st->actions_stat[METRIC_ACTION_ADD_HEADER] + st->actions_stat[METRIC_ACTION_REWRITE_SUBJECT]; + data[2] = st->actions_stat[METRIC_ACTION_GREYLIST]; + data[3] = st->actions_stat[METRIC_ACTION_REJECT]; + + /* Get uptime */ + uptime = time (NULL) - session->ctx->start_time; + + ucl_object_insert_key (obj, ucl_object_fromstring (RVERSION), "version", 0, false); + ucl_object_insert_key (obj, ucl_object_fromstring ("ok"), "auth", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (uptime), "uptime", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (data[0]), "clean", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (data[1]), "probable", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (data[2]), "greylist", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (data[3]), "reject", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (st->messages_scanned), "scanned", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (st->messages_learned), "learned", 0, false); - close (session->sock); + rspamd_controller_send_ucl (conn_ent, obj); + ucl_object_unref (obj); - rspamd_mempool_delete (session->session_pool); - g_slice_free1 (sizeof (struct controller_session), session); + return 0; } -static gboolean -restful_write_reply (gint error_code, const gchar *err_message, - const gchar *buf, gsize buflen, rspamd_io_dispatcher_t *d) +/* + * Symbols command handler: + * request: /symbols + * reply: json [{ + * "name": "group_name", + * "symbols": [ + * { + * "name": "name", + * "weight": 0.1, + * "description": "description of symbol" + * }, + * {...} + * }, + * {...}] + */ +static int +rspamd_webui_handle_symbols (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - static gchar hbuf[256]; - gint r; + struct rspamd_webui_session *session = conn_ent->ud; + GList *cur_gr, *cur_sym; + struct rspamd_symbols_group *gr; + struct rspamd_symbol_def *sym; + ucl_object_t *obj, *top, *sym_obj; - r = rspamd_snprintf (hbuf, sizeof (hbuf), - "HTTP/1.0 %d %s" CRLF "Version: " RVERSION CRLF, - error_code, err_message ? err_message : "OK"); - if (buflen > 0) { - r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, "Content-Length: %z" CRLF, buflen); + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, CRLF); - if (buf != NULL) { - if (!rspamd_dispatcher_write (d, hbuf, r, TRUE, TRUE)) { - return FALSE; - } - return rspamd_dispatcher_write (d, buf, buflen, FALSE, FALSE); - } - else { - if (!rspamd_dispatcher_write (d, hbuf, r, FALSE, TRUE)) { - return FALSE; + top = ucl_object_typed_new (UCL_ARRAY); + + /* Go through all symbols groups */ + cur_gr = session->ctx->cfg->symbols_groups; + while (cur_gr) { + gr = cur_gr->data; + obj = ucl_object_typed_new (UCL_OBJECT); + ucl_object_insert_key (obj, ucl_object_fromstring (gr->name), "group", 0, false); + /* Iterate through all symbols */ + cur_sym = gr->symbols; + while (cur_sym) { + sym_obj = ucl_object_typed_new (UCL_OBJECT); + sym = cur_sym->data; + + ucl_object_insert_key (sym_obj, ucl_object_fromstring (sym->name), + "symbol", 0, false); + ucl_object_insert_key (sym_obj, ucl_object_fromdouble (*sym->weight_ptr), + "weight", 0, false); + if (sym->description) { + ucl_object_insert_key (sym_obj, ucl_object_fromstring (sym->description), + "description", 0, false); + } + + ucl_object_insert_key (obj, sym_obj, "rules", 0, false); + cur_sym = g_list_next (cur_sym); } + cur_gr = g_list_next (cur_gr); + ucl_array_append (top, obj); } - return TRUE; + rspamd_controller_send_ucl (conn_ent, top); + ucl_object_unref (top); + + return 0; } -static gboolean -restful_write_reply_string (gint error_code, const gchar *err_message, - GString *buf, rspamd_io_dispatcher_t *d) +/* + * Actions command handler: + * request: /actions + * reply: json [{ + * "action": "no action", + * "value": 1.1 + * }, + * {...}] + */ +static int +rspamd_webui_handle_actions (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - static gchar hbuf[256]; - gint r; + struct rspamd_webui_session *session = conn_ent->ud; + struct metric *metric; + struct metric_action *act; + gint i; + ucl_object_t *obj, *top; - r = rspamd_snprintf (hbuf, sizeof (hbuf), - "HTTP/1.0 %d %s" CRLF "Version: " RVERSION CRLF, - error_code, err_message ? err_message : "OK"); - if (buf->len > 0) { - r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, "Content-Length: %z" CRLF, buf->len); + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, CRLF); - if (buf != NULL) { - if (!rspamd_dispatcher_write (d, hbuf, r, TRUE, TRUE)) { - return FALSE; - } - return rspamd_dispatcher_write_string (d, buf, FALSE, TRUE); - } - else { - if (!rspamd_dispatcher_write (d, hbuf, r, FALSE, TRUE)) { - return FALSE; + top = ucl_object_typed_new (UCL_ARRAY); + + /* Get actions for default metric */ + metric = g_hash_table_lookup (session->ctx->cfg->metrics, DEFAULT_METRIC); + if (metric != NULL) { + for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i ++) { + act = &metric->actions[i]; + if (act->score > 0) { + obj = ucl_object_typed_new (UCL_OBJECT); + ucl_object_insert_key (obj, + ucl_object_fromstring (str_action_metric (act->action)), "action", 0, false); + ucl_object_insert_key (obj, ucl_object_fromdouble (act->score), "value", 0, false); + ucl_array_append (top, obj); + } } } - return TRUE; -} + rspamd_controller_send_ucl (conn_ent, top); + ucl_object_unref (top); -static gint -check_auth (struct controller_command *cmd, struct controller_session *session) + return 0; +} +/* + * Maps command handler: + * request: /maps + * headers: Password + * reply: json [ + * { + * "map": "name", + * "description": "description", + * "editable": true + * }, + * {...} + * ] + */ +static int +rspamd_webui_handle_maps (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - gchar out_buf[128]; - gint r; + struct rspamd_webui_session *session = conn_ent->ud; + GList *cur, *tmp = NULL; + struct rspamd_map *map; + gboolean editable; + ucl_object_t *obj, *top; - if (cmd->privilleged && !session->authorized) { - if (!session->restful) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "not authorized" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return 0; + + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; + } + + top = ucl_object_typed_new (UCL_ARRAY); + /* Iterate over all maps */ + cur = session->ctx->cfg->maps; + while (cur) { + map = cur->data; + if (map->protocol == MAP_PROTO_FILE && map->description != NULL) { + if (access (map->uri, R_OK) == 0) { + tmp = g_list_prepend (tmp, map); } } - else { - (void)restful_write_reply (403, "Not authorized", NULL, 0, session->dispatcher); - } - return 0; + cur = g_list_next (cur); + } + /* Iterate over selected maps */ + cur = tmp; + while (cur) { + map = cur->data; + editable = (access (map->uri, W_OK) == 0); + + obj = ucl_object_typed_new (UCL_OBJECT); + ucl_object_insert_key (obj, ucl_object_fromint (map->id), + "map", 0, false); + ucl_object_insert_key (obj, ucl_object_fromstring (map->description), + "description", 0, false); + ucl_object_insert_key (obj, ucl_object_frombool (editable), + "editable", 0, false); + ucl_array_append (top, obj); + + cur = g_list_next (cur); + } + + if (tmp) { + g_list_free (tmp); } - return 1; + rspamd_controller_send_ucl (conn_ent, top); + ucl_object_unref (top); + + return 0; } -static gboolean -write_whole_statfile (struct controller_session *session, gchar *symbol, struct rspamd_classifier_config *ccf) +/* + * Get map command handler: + * request: /getmap + * headers: Password, Map + * reply: plain-text + */ +static int +rspamd_webui_handle_get_map (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - stat_file_t *statfile; - struct rspamd_statfile_config *st; - gchar out_buf[BUFSIZ]; - guint i; - guint64 rev, ti, len, pos, blocks; - gchar *out; - struct rspamd_binlog_element log_elt; - struct stat_file_block *stat_elt; - - statfile = get_statfile_by_symbol (session->worker->srv->statfile_pool, ccf, - symbol, &st, FALSE); - if (statfile == NULL) { - return FALSE; - } - - /* Begin to copy all blocks into array */ - statfile_get_revision (statfile, &rev, (time_t *)&ti); - if (ti == 0) { - /* Not tracked file */ - ti = time (NULL); - statfile_set_revision (statfile, rev, ti); - } - msg_info ("send a whole statfile %s with version %uL to slave", symbol, rev); - - blocks = statfile_get_total_blocks (statfile); - len = blocks * sizeof (struct rspamd_binlog_element); - out = rspamd_mempool_alloc (session->session_pool, len); - - for (i = 0, pos = 0; i < blocks; i ++) { - stat_elt = (struct stat_file_block *)((u_char *)statfile->map + statfile->seek_pos + i * sizeof (struct stat_file_block)); - if (fabs (stat_elt->value) > 0.001) { - /* Write only those values which value is not 0 */ - log_elt.h1 = stat_elt->hash1; - log_elt.h2 = stat_elt->hash2; - log_elt.value = stat_elt->value; - - memcpy (out + pos, &log_elt, sizeof (log_elt)); - pos += sizeof (struct rspamd_binlog_element); - } + struct rspamd_webui_session *session = conn_ent->ud; + GList *cur; + struct rspamd_map *map; + const gchar *idstr; + gchar *errstr; + struct stat st; + gint fd; + guint32 id; + gboolean found = FALSE; + struct rspamd_http_message *reply; + + + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - i = rspamd_snprintf (out_buf, sizeof (out_buf), "%uL %uL %uL" CRLF, rev, ti, pos); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, i, TRUE, FALSE)) { - return FALSE; + + idstr = rspamd_http_message_find_header (msg, "Map"); + + if (idstr == NULL) { + msg_info ("absent map id"); + rspamd_controller_send_error (conn_ent, 400, "400 id header missing"); + return 0; } - if (!rspamd_dispatcher_write (session->dispatcher, out, pos, TRUE, TRUE)) { - return FALSE; + id = strtoul (idstr, &errstr, 10); + if (*errstr != '\0') { + msg_info ("invalid map id"); + rspamd_controller_send_error (conn_ent, 400, "400 invalid map id"); + return 0; } - - return TRUE; -} -static gboolean -process_sync_command (struct controller_session *session, gchar **args) -{ - gchar out_buf[BUFSIZ], *arg, *err_str, *symbol; - gint r; - guint64 rev, time; - struct rspamd_statfile_config *st = NULL; - struct rspamd_classifier_config *ccf; - GList *cur; - struct rspamd_binlog *binlog; - GByteArray *data = NULL; - - arg = *args; - if (!arg || *arg == '\0') { - msg_info ("bad arguments to sync command, need symbol"); - return FALSE; - } - symbol = arg; - arg = *(args + 1); - if (!arg || *arg == '\0') { - msg_info ("bad arguments to sync command, need revision"); - return FALSE; - } - rev = strtoull (arg, &err_str, 10); - if (err_str && *err_str != 0) { - msg_info ("bad arguments to sync command: %s", arg); - return FALSE; - } - arg = *(args + 2); - if (!arg || *arg == '\0') { - msg_info ("bad arguments to sync command, need time"); - return FALSE; - } - time = strtoull (arg, &err_str, 10); - if (err_str && *err_str != 0) { - msg_info ("bad arguments to sync command: %s", arg); - return FALSE; - } - - ccf = g_hash_table_lookup (session->cfg->classifiers_symbols, symbol); - if (ccf == NULL) { - msg_info ("bad symbol: %s", symbol); - return FALSE; - } - - cur = g_list_first (ccf->statfiles); + /* Now let's be sure that we have map defined in configuration */ + cur = session->ctx->cfg->maps; while (cur) { - st = cur->data; - if (strcmp (symbol, st->symbol) == 0) { + map = cur->data; + if (map->id == id && map->protocol == MAP_PROTO_FILE) { + found = TRUE; break; } - st = NULL; cur = g_list_next (cur); } - if (st == NULL) { - msg_info ("bad symbol: %s", symbol); - return FALSE; - } - - binlog = get_binlog_by_statfile (st); - if (binlog == NULL) { - msg_info ("cannot open binlog: %s", symbol); - return FALSE; - } - - while (binlog_sync (binlog, rev, &time, &data)) { - rev ++; - r = rspamd_snprintf (out_buf, sizeof (out_buf), "%uL %uL %z" CRLF, rev, time, data->len); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - if (data != NULL) { - g_free (data); - } - return FALSE; - } - if (data->data != NULL) { - if (!rspamd_dispatcher_write (session->dispatcher, data->data, data->len, TRUE, FALSE)) { - if (data != NULL) { - g_free (data); - } - return FALSE; - } - } + + if (!found) { + msg_info ("map not found"); + rspamd_controller_send_error (conn_ent, 404, "404 map not found"); + return 0; } - if (time == 0) { - if (data != NULL) { - g_free (data); - } - return write_whole_statfile (session, symbol, ccf); + if (stat (map->uri, &st) == -1 || (fd = open (map->uri, O_RDONLY)) == -1) { + msg_err ("cannot open map %s: %s", map->uri, strerror (errno)); + rspamd_controller_send_error (conn_ent, 500, "500 map open error"); + return 0; } - if (data != NULL) { - g_free (data); + reply = rspamd_http_new_message (HTTP_RESPONSE); + reply->date = time (NULL); + reply->code = 200; + reply->body = g_string_sized_new (st.st_size); + + /* Read the whole buffer */ + if (read (fd, msg->body->str, st.st_size) == -1) { + rspamd_http_message_free (reply); + msg_err ("cannot read map %s: %s", map->uri, strerror (errno)); + rspamd_controller_send_error (conn_ent, 500, "500 map read error"); + return 0; } - return TRUE; + reply->body->len = st.st_size; + reply->body->str[reply->body->len] = '\0'; + + close (fd); + + rspamd_http_connection_reset (conn_ent->conn); + rspamd_http_connection_write_message (conn_ent->conn, reply, NULL, + "text/plain", conn_ent, conn_ent->conn->fd, + conn_ent->rt->ptv, conn_ent->rt->ev_base); + conn_ent->is_reply = TRUE; + + return 0; } -static gboolean -process_counters_command (struct controller_session *session) +#if 0 +/* + * Graph command handler: + * request: /graph + * headers: Password + * reply: json [ + * { label: "Foo", data: [ [10, 1], [17, -14], [30, 5] ] }, + * { label: "Bar", data: [ [10, 1], [17, -14], [30, 5] ] }, + * {...} + * ] + */ +/* XXX: now this function returns only random data */ +static void +rspamd_webui_handle_graph (struct evhttp_request *req, gpointer arg) { - GList *cur; - struct cache_item *item; - struct symbols_cache *cache; - GString *out; - - cache = session->cfg->cache; - out = g_string_sized_new (BUFSIZ); + struct rspamd_webui_worker_ctx *ctx = arg; + struct evbuffer *evb; + gint i, seed; + time_t now, t; + double vals[5][100]; - if (!session->restful) { - rspamd_printf_gstring (out, "Rspamd counters:" CRLF); + if (!http_check_password (ctx, req)) { + return; } - if (cache != NULL) { - cur = cache->negative_items; - while (cur) { - item = cur->data; - if (!item->is_callback) { - rspamd_printf_gstring (out, "%s %.2f %d %.3f" CRLF, - item->s->symbol, item->s->weight, - item->s->frequency, item->s->avg_time); - } - cur = g_list_next (cur); - } - cur = cache->static_items; - while (cur) { - item = cur->data; - if (!item->is_callback) { - rspamd_printf_gstring (out, "%s %.2f %d %.3f" CRLF, - item->s->symbol, item->s->weight, - item->s->frequency, item->s->avg_time); - } - cur = g_list_next (cur); - } + evb = evbuffer_new (); + if (!evb) { + msg_err ("cannot allocate evbuffer for reply"); + evhttp_send_reply (req, HTTP_INTERNAL, "500 insufficient memory", NULL); + return; } - if (!session->restful) { - return rspamd_dispatcher_write_string (session->dispatcher, out, FALSE, TRUE); - } - else { - return restful_write_reply_string (200, NULL, out, session->dispatcher); + /* Trailer */ + evbuffer_add (evb, "[", 1); + + /* XXX: simple and stupid set */ + seed = g_random_int (); + for (i = 0; i < 100; i ++, seed ++) { + vals[0][i] = fabs ((sin (seed * 0.1 * M_PI_2) + 1) * 40. + ((gint)(g_random_int () % 2) - 1)); + vals[1][i] = vals[0][i] * 0.5; + vals[2][i] = vals[0][i] * 0.1; + vals[3][i] = vals[0][i] * 0.3; + vals[4][i] = vals[0][i] * 1.9; } -} -static gboolean -process_stat_command (struct controller_session *session, gboolean do_reset) -{ - GString *out; - gint i; - guint64 used, total, rev, ham = 0, spam = 0; - time_t ti; - rspamd_mempool_stat_t mem_st; - struct rspamd_classifier_config *ccf; - stat_file_t *statfile; - struct rspamd_statfile_config *st; - GList *cur_cl, *cur_st; - struct rspamd_stat *stat, stat_copy; + now = time (NULL); - rspamd_mempool_stat (&mem_st); - memcpy (&stat_copy, session->worker->srv->stat, sizeof (stat_copy)); - stat = &stat_copy; - out = g_string_sized_new (BUFSIZ); - rspamd_printf_gstring (out, "Messages scanned: %ud" CRLF, stat->messages_scanned); - if (stat->messages_scanned > 0) { - for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i ++) { - rspamd_printf_gstring (out, "Messages with action %s: %ud, %.2f%%" CRLF, - str_action_metric (i), stat->actions_stat[i], - (double)stat->actions_stat[i] / (double)stat->messages_scanned * 100.); - if (i < METRIC_ACTION_GREYLIST) { - spam += stat->actions_stat[i]; - } - else { - ham += stat->actions_stat[i]; - } - if (do_reset) { - session->worker->srv->stat->actions_stat[i] = 0; - } - } - rspamd_printf_gstring (out, "Messages treated as spam: %ud, %.2f%%" CRLF, spam, - (double)spam / (double)stat->messages_scanned * 100.); - rspamd_printf_gstring (out, "Messages treated as ham: %ud, %.2f%%" CRLF, ham, - (double)ham / (double)stat->messages_scanned * 100.); - } - rspamd_printf_gstring (out, "Messages learned: %ud" CRLF, stat->messages_learned); - rspamd_printf_gstring (out, "Connections count: %ud" CRLF, stat->connections_count); - rspamd_printf_gstring (out, "Control connections count: %ud" CRLF, stat->control_connections_count); - rspamd_printf_gstring (out, "Pools allocated: %z" CRLF, mem_st.pools_allocated); - rspamd_printf_gstring (out, "Pools freed: %z" CRLF, mem_st.pools_freed); - rspamd_printf_gstring (out, "Bytes allocated: %z" CRLF, mem_st.bytes_allocated); - rspamd_printf_gstring (out, "Memory chunks allocated: %z" CRLF, mem_st.chunks_allocated); - rspamd_printf_gstring (out, "Shared chunks allocated: %z" CRLF, mem_st.shared_chunks_allocated); - rspamd_printf_gstring (out, "Chunks freed: %z" CRLF, mem_st.chunks_freed); - rspamd_printf_gstring (out, "Oversized chunks: %z" CRLF, mem_st.oversized_chunks); - rspamd_printf_gstring (out, "Fuzzy hashes stored: %ud" CRLF, stat->fuzzy_hashes); - rspamd_printf_gstring (out, "Fuzzy hashes expired: %ud" CRLF, stat->fuzzy_hashes_expired); - /* Now write statistics for each statfile */ - cur_cl = g_list_first (session->cfg->classifiers); - while (cur_cl) { - ccf = cur_cl->data; - cur_st = g_list_first (ccf->statfiles); - while (cur_st) { - st = cur_st->data; - if ((statfile = statfile_pool_is_open (session->worker->srv->statfile_pool, st->path)) == NULL) { - statfile = statfile_pool_open (session->worker->srv->statfile_pool, st->path, st->size, FALSE); - } - if (statfile) { - used = statfile_get_used_blocks (statfile); - total = statfile_get_total_blocks (statfile); - statfile_get_revision (statfile, &rev, &ti); - if (total != (guint64)-1 && used != (guint64)-1) { - if (st->label) { - rspamd_printf_gstring (out, - "Statfile: %s <%s> (version %uL); length: %Hz; free blocks: %uL; total blocks: %uL; free: %.2f%%" CRLF, - st->symbol, st->label, rev, st->size, - (total - used), total, - (double)((double)(total - used) / (double)total) * 100.); - } - else { - rspamd_printf_gstring (out, - "Statfile: %s (version %uL); length: %Hz; free blocks: %uL; total blocks: %uL; free: %.2f%%" CRLF, - st->symbol, rev, st->size, - (total - used), total, - (double)((double)(total - used) / (double)total) * 100.); - } - } - } - cur_st = g_list_next (cur_st); - } - cur_cl = g_list_next (cur_cl); + /* Ham label */ + t = now - 6000; + evbuffer_add_printf (evb, "{\"label\": \"Clean messages\", \"lines\": {\"fill\": false}, \"color\": \"" + COLOR_CLEAN "\", \"data\":["); + for (i = 0; i < 100; i ++, t += 60) { + evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[0][i], i == 99 ? "]" : "],"); } + evbuffer_add (evb, "]},", 3); - if (do_reset) { - session->worker->srv->stat->messages_scanned = 0; - session->worker->srv->stat->messages_learned = 0; - session->worker->srv->stat->connections_count = 0; - session->worker->srv->stat->control_connections_count = 0; + /* Probable spam label */ + t = now - 6000; + evbuffer_add_printf (evb, "{\"label\": \"Probable spam messages\", \"lines\": {\"fill\": false}, \"color\": \"" + COLOR_PROBABLE_SPAM "\", \"data\":["); + for (i = 0; i < 100; i ++, t += 60) { + evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[1][i], i == 99 ? "]" : "],"); } + evbuffer_add (evb, "]},", 3); - if (!session->restful) { - return rspamd_dispatcher_write_string (session->dispatcher, out, FALSE, TRUE); + /* Greylist label */ + t = now - 6000; + evbuffer_add_printf (evb, "{\"label\": \"Greylisted messages\", \"lines\": {\"fill\": false}, \"color\": \"" + COLOR_GREYLIST "\", \"data\":["); + for (i = 0; i < 100; i ++, t += 60) { + evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[2][i], i == 99 ? "]" : "],"); } - else { - return restful_write_reply_string (200, NULL, out, session->dispatcher); + evbuffer_add (evb, "]},", 3); + + /* Reject label */ + t = now - 6000; + evbuffer_add_printf (evb, "{\"label\": \"Rejected messages\", \"lines\": {\"fill\": false}, \"color\": \"" + COLOR_REJECT "\", \"data\":["); + for (i = 0; i < 100; i ++, t += 60) { + evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[3][i], i == 99 ? "]" : "],"); } -} + evbuffer_add (evb, "]},", 3); -static gboolean -process_dynamic_conf_command (gchar **cmd_args, struct controller_session *session, gboolean is_action) + /* Total label */ + t = now - 6000; + evbuffer_add_printf (evb, "{\"label\": \"Total messages\", \"lines\": {\"fill\": false}, \"color\": \"" + COLOR_TOTAL "\", \"data\":["); + for (i = 0; i < 100; i ++, t += 60) { + evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[4][i], i == 99 ? "]" : "],"); + } + evbuffer_add (evb, "]}", 2); + + evbuffer_add (evb, "]" CRLF, 3); + + evhttp_add_header (req->output_headers, "Connection", "close"); + http_calculate_content_length (evb, req); + + evhttp_send_reply (req, HTTP_OK, "OK", evb); + evbuffer_free (evb); +} +#endif +/* + * Pie chart command handler: + * request: /pie + * headers: Password + * reply: json [ + * { label: "Foo", data: 11 }, + * { label: "Bar", data: 20 }, + * {...} + * ] + */ +static int +rspamd_webui_handle_pie_chart (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - struct rspamd_config *cfg = session->cfg; - gchar *arg, *metric, *name, *err_str; - gdouble value; - gboolean res; - guint real_act; - - if (cfg->dynamic_conf == NULL) { - if (!session->restful) { - return rspamd_dispatcher_write (session->dispatcher, "dynamic config is not specified" CRLF, 0, FALSE, TRUE); - } - else { - return restful_write_reply (500, "dynamic config is not specified", NULL, 0, session->dispatcher); - } + struct rspamd_webui_session *session = conn_ent->ud; + struct rspamd_webui_worker_ctx *ctx; + gdouble data[4], total; + ucl_object_t *top, *obj; + + ctx = session->ctx; + + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - if (session->restful) { - if ((arg = g_hash_table_lookup (session->kwargs, "metric")) == NULL) { - metric = DEFAULT_METRIC; - } - else { - metric = arg; - } - if ((arg = g_hash_table_lookup (session->kwargs, "name")) == NULL) { - goto invalid_arguments; - } - name = arg; - if ((arg = g_hash_table_lookup (session->kwargs, "value")) == NULL) { - goto invalid_arguments; - } - value = strtod (arg, &err_str); - if (err_str && *err_str != '\0') { - msg_info ("double value is invalid: %s", arg); - goto invalid_arguments; - } + top = ucl_object_typed_new (UCL_ARRAY); + total = ctx->srv->stat->messages_scanned; + if (total != 0) { + obj = ucl_object_typed_new (UCL_ARRAY); + + data[0] = ctx->srv->stat->actions_stat[METRIC_ACTION_NOACTION] / total * 100.; + data[1] = (ctx->srv->stat->actions_stat[METRIC_ACTION_ADD_HEADER] + + ctx->srv->stat->actions_stat[METRIC_ACTION_REWRITE_SUBJECT]) / total * 100.; + data[2] = ctx->srv->stat->actions_stat[METRIC_ACTION_GREYLIST] / total * 100.; + data[3] = ctx->srv->stat->actions_stat[METRIC_ACTION_REJECT] / total * 100.; + + ucl_array_append (obj, ucl_object_fromstring ("Clean messages")); + ucl_array_append (obj, ucl_object_fromdouble (data[0])); + ucl_array_append (top, obj); + ucl_array_append (obj, ucl_object_fromstring ("Probable spam messages")); + ucl_array_append (obj, ucl_object_fromdouble (data[1])); + ucl_array_append (top, obj); + ucl_array_append (obj, ucl_object_fromstring ("Greylisted messages")); + ucl_array_append (obj, ucl_object_fromdouble (data[2])); + ucl_array_append (top, obj); + ucl_array_append (obj, ucl_object_fromstring ("Rejected messages")); + ucl_array_append (obj, ucl_object_fromdouble (data[3])); + ucl_array_append (top, obj); } else { - if (cmd_args[0] == NULL || cmd_args[1] == NULL) { - goto invalid_arguments; - } - if (cmd_args[2] == NULL) { - metric = DEFAULT_METRIC; - name = cmd_args[0]; - arg = cmd_args[1]; - value = strtod (arg, &err_str); - if (err_str && *err_str != '\0') { - msg_info ("double value is invalid: %s", arg); - goto invalid_arguments; - } - } - else { - metric = cmd_args[0]; - name = cmd_args[1]; - arg = cmd_args[2]; - value = strtod (arg, &err_str); - if (err_str && *err_str != '\0') { - msg_info ("double value is invalid: %s", arg); - goto invalid_arguments; - } - } - } + obj = ucl_object_typed_new (UCL_ARRAY); - if (is_action) { - if (!check_action_str (name, &real_act)) { - msg_info ("invalid action string: %s", name); - res = FALSE; - } - else { - res = add_dynamic_action (cfg, metric, real_act, value); - } + ucl_array_append (obj, ucl_object_fromstring ("Scanned messages")); + ucl_array_append (obj, ucl_object_fromdouble (0)); + ucl_array_append (top, obj); } - else { - res = add_dynamic_symbol (cfg, metric, name, value); + + rspamd_controller_send_ucl (conn_ent, top); + ucl_object_unref (top); + + return 0; +} + +/* + * History command handler: + * request: /history + * headers: Password + * reply: json [ + * { label: "Foo", data: 11 }, + * { label: "Bar", data: 20 }, + * {...} + * ] + */ +static int +rspamd_webui_handle_history (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) +{ + struct rspamd_webui_session *session = conn_ent->ud; + struct rspamd_webui_worker_ctx *ctx; + struct roll_history_row *row; + struct roll_history copied_history; + gint i, rows_proc, row_num; + struct tm *tm; + gchar timebuf[32]; + gchar ip_buf[INET6_ADDRSTRLEN]; + ucl_object_t *top, *obj; + + ctx = session->ctx; + + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - if (res) { + top = ucl_object_typed_new (UCL_ARRAY); - res = dump_dynamic_config (cfg); - if (res) { - if (!session->restful) { - return rspamd_dispatcher_write (session->dispatcher, "OK" CRLF, 0, FALSE, TRUE); - } - else { - return restful_write_reply (200, "OK", NULL, 0, session->dispatcher); - } + /* Set lock on history */ + rspamd_mempool_lock_mutex (ctx->srv->history->mtx); + ctx->srv->history->need_lock = TRUE; + /* Copy locked */ + memcpy (&copied_history, ctx->srv->history, sizeof (copied_history)); + rspamd_mempool_unlock_mutex (ctx->srv->history->mtx); + + /* Go through all rows */ + row_num = copied_history.cur_row; + for (i = 0, rows_proc = 0; i < HISTORY_MAX_ROWS; i ++, row_num ++) { + if (row_num == HISTORY_MAX_ROWS) { + row_num = 0; } - else { - if (!session->restful) { - return rspamd_dispatcher_write (session->dispatcher, "Error dumping dynamic config" CRLF, 0, FALSE, TRUE); + row = &copied_history.rows[row_num]; + /* Get only completed rows */ + if (row->completed) { + tm = localtime (&row->tv.tv_sec); + strftime (timebuf, sizeof (timebuf), "%F %H:%M:%S", tm); +#ifdef HAVE_INET_PTON + if (row->from_addr.ipv6) { + inet_ntop (AF_INET6, &row->from_addr.d.in6, ip_buf, sizeof (ip_buf)); } else { - return restful_write_reply (500, "Error dumping dynamic config", NULL, 0, session->dispatcher); + inet_ntop (AF_INET, &row->from_addr.d.in4, ip_buf, sizeof (ip_buf)); } - } - } - else { - if (!session->restful) { - return rspamd_dispatcher_write (session->dispatcher, "Cannot add dynamic rule" CRLF, 0, FALSE, TRUE); - } - else { - return restful_write_reply (500, "Cannot add dynamic rule", NULL, 0, session->dispatcher); +#else + rspamd_strlcpy (ip_buf, inet_ntoa (task->from_addr), sizeof (ip_buf)); +#endif + obj = ucl_object_typed_new (UCL_OBJECT); + ucl_object_insert_key (obj, ucl_object_fromstring (timebuf), "time", 0, false); + ucl_object_insert_key (obj, ucl_object_fromstring (row->message_id), "id", 0, false); + ucl_object_insert_key (obj, ucl_object_fromstring (ip_buf), "ip", 0, false); + ucl_object_insert_key (obj, + ucl_object_fromstring (str_action_metric (row->action)), "action", 0, false); + ucl_object_insert_key (obj, ucl_object_fromdouble (row->score), "score", 0, false); + ucl_object_insert_key (obj, ucl_object_fromdouble (row->required_score), "required_score", 0, false); + ucl_object_insert_key (obj, ucl_object_fromstring (row->symbols), "symbols", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (row->len), "size", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (row->scan_time), "scan_time", 0, false); + if (row->user[0] != '\0') { + ucl_object_insert_key (obj, ucl_object_fromstring (row->user), "user", 0, false); + } + ucl_array_append (top, obj); + rows_proc ++; } } -invalid_arguments: - if (!session->restful) { - return rspamd_dispatcher_write (session->dispatcher, "Invalid arguments" CRLF, 0, FALSE, TRUE); - } - else { - return restful_write_reply (500, "Invalid arguments", NULL, 0, session->dispatcher); + rspamd_controller_send_ucl (conn_ent, top); + ucl_object_unref (top); + + return 0; +} + +static gboolean +rspamd_webui_learn_fin_task (void *ud) +{ + struct rspamd_task *task = ud; + struct rspamd_webui_session *session; + struct rspamd_http_connection_entry *conn_ent; + GError *err = NULL; + + conn_ent = task->fin_arg; + session = conn_ent->ud; + + if (!learn_task_spam (session->cl, task, session->is_spam, &err)) { + rspamd_controller_send_error (conn_ent, 500 + err->code, err->message); + rspamd_http_connection_unref (conn_ent->conn); + return TRUE; } + /* Successful learn */ + rspamd_controller_send_string (conn_ent, "{\"success\":true}"); + rspamd_http_connection_unref (conn_ent->conn); + + return TRUE; } static gboolean -process_command (struct controller_command *cmd, gchar **cmd_args, struct controller_session *session) +rspamd_webui_check_fin_task (void *ud) { - gchar out_buf[BUFSIZ], *arg, *err_str; - gint r = 0, days, hours, minutes; - time_t uptime; - guint32 size = 0; - struct rspamd_classifier_config *cl; - struct rspamd_controller_ctx *ctx = session->worker->ctx; - - switch (cmd->type) { - case COMMAND_GET: - case COMMAND_POST: - session->restful = TRUE; - session->state = STATE_HEADER; - session->kwargs = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal); - break; - case COMMAND_PASSWORD: - arg = *cmd_args; - if (!arg || *arg == '\0') { - msg_debug ("empty password passed"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "password command requires one argument" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - if (ctx->password == NULL) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "password command disabled in config, authorized access granted" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - if (strncmp (arg, ctx->password, strlen (arg)) == 0) { - session->authorized = 1; - r = rspamd_snprintf (out_buf, sizeof (out_buf), "password accepted" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - session->authorized = 0; - r = rspamd_snprintf (out_buf, sizeof (out_buf), "password NOT accepted" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - break; - case COMMAND_QUIT: - session->state = STATE_QUIT; - break; - case COMMAND_RELOAD: - if (check_auth (cmd, session)) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "reload request sent" CRLF); - if (!session->restful) { - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (! restful_write_reply (200, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - } - kill (getppid (), SIGHUP); - } - break; - case COMMAND_STAT: - if (check_auth (cmd, session)) { - return process_stat_command (session, FALSE); - } - break; - case COMMAND_STAT_RESET: - if (check_auth (cmd, session)) { - return process_stat_command (session, TRUE); - } - break; - case COMMAND_SHUTDOWN: - if (check_auth (cmd, session)) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "shutdown request sent" CRLF); - if (!session->restful) { - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (! restful_write_reply (200, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - } - kill (getppid (), SIGTERM); - } - break; - case COMMAND_UPTIME: - if (check_auth (cmd, session)) { - uptime = time (NULL) - start_time; - /* If uptime more than 2 hours, print as a number of days. */ - if (uptime >= 2 * 3600) { - days = uptime / 86400; - hours = uptime / 3600 - days * 24; - minutes = uptime / 60 - hours * 60 - days * 1440; - r = rspamd_snprintf (out_buf, sizeof (out_buf), "%d day%s %d hour%s %d minute%s" CRLF, days, days > 1 ? "s" : " ", hours, hours > 1 ? "s" : " ", minutes, minutes > 1 ? "s" : " "); - } - /* If uptime is less than 1 minute print only seconds */ - else if (uptime / 60 == 0) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "%d second%s" CRLF, (gint)uptime, (gint)uptime > 1 ? "s" : " "); - } - /* Else print the minutes and seconds. */ - else { - hours = uptime / 3600; - minutes = uptime / 60 - hours * 60; - uptime -= hours * 3600 + minutes * 60; - r = rspamd_snprintf (out_buf, sizeof (out_buf), "%d hour%s %d minute%s %d second%s" CRLF, hours, hours > 1 ? "s" : " ", minutes, minutes > 1 ? "s" : " ", (gint)uptime, uptime > 1 ? "s" : " "); - } - if (!session->restful) { - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (! restful_write_reply (200, NULL, out_buf, r, session->dispatcher)) { - return FALSE; - } - } - } - break; - case COMMAND_LEARN_SPAM: - if (check_auth (cmd, session)) { - if (!session->restful) { - arg = *cmd_args; - if (!arg || *arg == '\0') { - msg_debug ("no statfile specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - arg = *(cmd_args + 1); - if (arg == NULL || *arg == '\0') { - msg_debug ("no message size specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - size = strtoul (arg, &err_str, 10); - if (err_str && *err_str != '\0') { - msg_debug ("message size is invalid: %s", arg); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - cl = rspamd_config_find_classifier (session->cfg, *cmd_args); - } - else { - if ((arg = g_hash_table_lookup (session->kwargs, "classifier")) == NULL) { - msg_debug ("no classifier specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - return TRUE; - } - else { - cl = rspamd_config_find_classifier (session->cfg, arg); - } - if ((arg = g_hash_table_lookup (session->kwargs, "content-length")) == NULL) { - msg_debug ("no size specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - } - else { - size = strtoul (arg, &err_str, 10); - if (err_str && *err_str != '\0') { - msg_debug ("message size is invalid: %s", arg); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - return TRUE; - } - } - } + struct rspamd_task *task = ud; + struct rspamd_http_connection_entry *conn_ent; - session->learn_classifier = cl; + conn_ent = task->fin_arg; + task->http_conn = conn_ent->conn; + rspamd_protocol_write_reply (task); + conn_ent->is_reply = TRUE; + rspamd_http_connection_unref (conn_ent->conn); - /* By default learn positive */ - session->in_class = TRUE; - rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_CHARACTER, size); - session->state = STATE_LEARN_SPAM_PRE; - } - break; - case COMMAND_LEARN_HAM: - if (check_auth (cmd, session)) { - if (!session->restful) { - arg = *cmd_args; - if (!arg || *arg == '\0') { - msg_debug ("no statfile specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - arg = *(cmd_args + 1); - if (arg == NULL || *arg == '\0') { - msg_debug ("no message size specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - size = strtoul (arg, &err_str, 10); - if (err_str && *err_str != '\0') { - msg_debug ("message size is invalid: %s", arg); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - cl = rspamd_config_find_classifier (session->cfg, *cmd_args); - } - else { - if ((arg = g_hash_table_lookup (session->kwargs, "classifier")) == NULL) { - msg_debug ("no classifier specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - return TRUE; - } - else { - cl = rspamd_config_find_classifier (session->cfg, arg); - } - if ((arg = g_hash_table_lookup (session->kwargs, "content-length")) == NULL) { - msg_debug ("no size specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - } - else { - size = strtoul (arg, &err_str, 10); - if (err_str && *err_str != '\0') { - msg_debug ("message size is invalid: %s", arg); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - return TRUE; - } - } - } + return TRUE; +} - session->learn_classifier = cl; +static int +rspamd_webui_handle_learn_common (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg, gboolean is_spam) +{ + struct rspamd_webui_session *session = conn_ent->ud; + struct rspamd_webui_worker_ctx *ctx; + struct rspamd_classifier_config *cl; + struct rspamd_task *task; + const gchar *classifier; - /* By default learn positive */ - session->in_class = FALSE; - rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_CHARACTER, size); - session->state = STATE_LEARN_SPAM_PRE; - } - break; - case COMMAND_LEARN: - if (check_auth (cmd, session)) { - /* TODO: remove this command as currenly it should not be used anywhere */ - arg = *cmd_args; - if (!arg || *arg == '\0') { - msg_debug ("no statfile specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn command requires at least two arguments: stat filename and its size" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - arg = *(cmd_args + 1); - if (arg == NULL || *arg == '\0') { - msg_debug ("no message size specified in learn command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn command requires at least two arguments: symbol and message size" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - size = strtoul (arg, &err_str, 10); - if (err_str && *err_str != '\0') { - msg_debug ("message size is invalid: %s", arg); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } + ctx = session->ctx; - session->learn_symbol = rspamd_mempool_strdup (session->session_pool, *cmd_args); - cl = g_hash_table_lookup (session->cfg->classifiers_symbols, *cmd_args); - - session->learn_classifier = cl; - - /* By default learn positive */ - session->in_class = 1; - session->learn_multiplier = 1.; - /* Get all arguments */ - while (*cmd_args++) { - arg = *cmd_args; - if (arg && *arg == '-') { - switch (*(arg + 1)) { - case 'r': - arg = *(cmd_args + 1); - if (!arg || *arg == '\0') { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "recipient is not defined" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - session->learn_rcpt = rspamd_mempool_strdup (session->session_pool, arg); - break; - case 'f': - arg = *(cmd_args + 1); - if (!arg || *arg == '\0') { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "from is not defined" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - session->learn_from = rspamd_mempool_strdup (session->session_pool, arg); - break; - case 'n': - session->in_class = 0; - break; - case 'm': - arg = *(cmd_args + 1); - if (!arg || *arg == '\0') { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "multiplier is not defined" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - session->learn_multiplier = strtod (arg, NULL); - } - break; - default: - r = rspamd_snprintf (out_buf, sizeof (out_buf), "tokenizer is not defined" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - } - } - rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_CHARACTER, size); - session->state = STATE_LEARN; - } - break; - - case COMMAND_WEIGHTS: - /* TODO: remove this command as currenly it should not be used anywhere */ - arg = *cmd_args; - if (!arg || *arg == '\0') { - msg_debug ("no statfile specified in weights command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "weights command requires two arguments: statfile and message size" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - arg = *(cmd_args + 1); - if (arg == NULL || *arg == '\0') { - msg_debug ("no message size specified in weights command"); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "weights command requires two arguments: statfile and message size" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - size = strtoul (arg, &err_str, 10); - if (err_str && *err_str != '\0') { - msg_debug ("message size is invalid: %s", arg); - r = rspamd_snprintf (out_buf, sizeof (out_buf), "message size is invalid" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } + if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { + return 0; + } - cl = g_hash_table_lookup (session->cfg->classifiers_symbols, *cmd_args); - if (cl == NULL) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "statfile %s is not defined" CRLF, *cmd_args); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; + if (msg->body == NULL || msg->body->len == 0) { + msg_err ("got zero length body, cannot continue"); + rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); + return 0; + } - } - session->learn_classifier = cl; - - rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_CHARACTER, size); - session->state = STATE_WEIGHTS; - break; - case COMMAND_SYNC: - if (!process_sync_command (session, cmd_args)) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "FAIL" CRLF); - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return TRUE; - } - break; - case COMMAND_HELP: - r = rspamd_snprintf (out_buf, sizeof (out_buf), - "Rspamd CLI commands (* - privileged command):" CRLF - " help - this help message" CRLF - "(*) learn [-r recipient] [-m multiplier] [-f from] [-n] - learn message to specified statfile" CRLF - " quit - quit CLI session" CRLF - " password - authenticate yourself for privileged commands" CRLF - "(*) reload - reload rspamd" CRLF - "(*) shutdown - shutdown rspamd" CRLF - " stat - show different rspamd stat" CRLF - " sync - run synchronization of statfiles" CRLF - " counters - show rspamd counters" CRLF - " uptime - rspamd uptime" CRLF - " weights - weight of message in all statfiles" CRLF); - if (!session->restful) { - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (! restful_write_reply (200, NULL, out_buf, r, session->dispatcher)) { - return FALSE; - } - } - break; - case COMMAND_COUNTERS: - process_counters_command (session); - break; - case COMMAND_ADD_ACTION: - if (check_auth (cmd, session)) { - return process_dynamic_conf_command (cmd_args, session, TRUE); - } - break; - case COMMAND_ADD_SYMBOL: - if (check_auth (cmd, session)) { - return process_dynamic_conf_command (cmd_args, session, FALSE); - } - break; + if ((classifier = rspamd_http_message_find_header (msg, "Classifier")) == NULL) { + classifier = "bayes"; } - return TRUE; + + cl = rspamd_config_find_classifier (ctx->cfg, classifier); + if (cl == NULL) { + rspamd_controller_send_error (conn_ent, 400, "Classifier not found"); + return 0; + } + + task = rspamd_task_new (session->ctx->worker); + task->ev_base = session->ctx->ev_base; + task->msg = msg->body; + + task->resolver = ctx->resolver; + task->ev_base = ctx->ev_base; + + rspamd_http_connection_ref (conn_ent->conn); + task->s = new_async_session (session->pool, rspamd_webui_learn_fin_task, NULL, + rspamd_task_free_hard, task); + task->s->wanna_die = TRUE; + task->fin_arg = conn_ent; + + if (!rspamd_task_process (task, msg, NULL, FALSE)) { + msg_warn ("filters cannot be processed for %s", task->message_id); + rspamd_controller_send_error (conn_ent, 500, task->last_error); + destroy_session (task->s); + return 0; + } + + session->task = task; + session->cl = cl; + session->is_spam = is_spam; + + return 0; } -static controller_func_t -parse_custom_command (gchar *line, gchar **cmd_args, struct controller_session *session, gsize len) +/* + * Learn spam command handler: + * request: /learnspam + * headers: Password + * input: plaintext data + * reply: json {"success":true} or {"error":"error message"} + */ +static int +rspamd_webui_handle_learnspam (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - GList *cur; - struct custom_controller_command *cmd; + return rspamd_webui_handle_learn_common (conn_ent, msg, TRUE); +} +/* + * Learn ham command handler: + * request: /learnham + * headers: Password + * input: plaintext data + * reply: json {"success":true} or {"error":"error message"} + */ +static int +rspamd_webui_handle_learnham (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) +{ + return rspamd_webui_handle_learn_common (conn_ent, msg, TRUE); +} - if (len == 0) { - len = strlen (line); +/* + * Scan command handler: + * request: /scan + * headers: Password + * input: plaintext data + * reply: json {scan data} or {"error":"error message"} + */ +static int +rspamd_webui_handle_scan (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) +{ + struct rspamd_webui_session *session = conn_ent->ud; + struct rspamd_webui_worker_ctx *ctx; + struct rspamd_task *task; + + ctx = session->ctx; + + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - cur = custom_commands; - while (cur) { - cmd = cur->data; - if (g_ascii_strncasecmp (cmd->command, line, len) == 0) { - return cmd->handler; - } - cur = g_list_next (cur); + + if (msg->body == NULL || msg->body->len == 0) { + msg_err ("got zero length body, cannot continue"); + rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); + return 0; } - return NULL; + task = rspamd_task_new (session->ctx->worker); + task->ev_base = session->ctx->ev_base; + task->msg = msg->body; + + task->resolver = ctx->resolver; + task->ev_base = ctx->ev_base; + + rspamd_http_connection_ref (conn_ent->conn); + task->s = new_async_session (session->pool, rspamd_webui_check_fin_task, NULL, + rspamd_task_free_hard, task); + task->s->wanna_die = TRUE; + task->fin_arg = conn_ent; + + if (!rspamd_task_process (task, msg, NULL, FALSE)) { + msg_warn ("filters cannot be processed for %s", task->message_id); + rspamd_controller_send_error (conn_ent, 500, task->last_error); + destroy_session (task->s); + return 0; + } + + session->task = task; + + return 0; } -static struct controller_command * -parse_normal_command (const gchar *line, gsize len) +/* + * Save actions command handler: + * request: /saveactions + * headers: Password + * input: json array [,,] + * reply: json {"success":true} or {"error":"error message"} + */ +static int +rspamd_webui_handle_saveactions (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - guint i; - struct controller_command *c; + struct rspamd_webui_session *session = conn_ent->ud; + struct ucl_parser *parser; + struct metric *metric; + ucl_object_t *obj, *cur; + struct rspamd_webui_worker_ctx *ctx; + const gchar *error; + gdouble score; + gint i; + enum rspamd_metric_action act; + + ctx = session->ctx; + + if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { + return 0; + } - if (len == 0) { - len = strlen (line); + if (msg->body == NULL || msg->body->len == 0) { + msg_err ("got zero length body, cannot continue"); + rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); + return 0; } - for (i = 0; i < G_N_ELEMENTS (commands); i ++) { - c = &commands[i]; - if (g_ascii_strncasecmp (line, c->command, len) == 0) { - return c; - } + + metric = g_hash_table_lookup (ctx->cfg->metrics, DEFAULT_METRIC); + if (metric == NULL) { + msg_err ("cannot find default metric"); + rspamd_controller_send_error (conn_ent, 500, "Default metric is absent"); + return 0; } - return NULL; -} + /* Now check for dynamic config */ + if (!ctx->cfg->dynamic_conf) { + msg_err ("dynamic conf has not been defined"); + rspamd_controller_send_error (conn_ent, 500, "No dynamic_rules setting defined"); + return 0; + } -static gboolean -process_header (f_str_t *line, struct controller_session *session) -{ - gchar *headern; - struct controller_command *command; - struct rspamd_controller_ctx *ctx = session->worker->ctx; - controller_func_t custom_handler; - - /* XXX: temporary workaround */ - headern = NULL; - - if (line == NULL || headern == NULL) { - msg_warn ("bad header: %V", line); - return FALSE; - } - /* Eat whitespaces */ - g_strstrip (headern); - fstrstrip (line); - - if (*headern == 'c' || *headern == 'C') { - if (g_ascii_strcasecmp (headern, "command") == 0) { - /* This header is actually command */ - command = parse_normal_command (line->begin, line->len); - if (command == NULL) { - if ((custom_handler = parse_custom_command (line->begin, NULL, session, line->len)) == NULL) { - msg_info ("bad command header: %V", line); - return FALSE; - } - else { - session->custom_handler = custom_handler; - } - } - session->cmd = command; - return TRUE; - } + parser = ucl_parser_new (0); + ucl_parser_add_chunk (parser, msg->body->str, msg->body->len); + + if ((error = ucl_parser_get_error (parser)) != NULL) { + msg_err ("cannot parse input: %s", error); + rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); + ucl_parser_free (parser); + return 0; } - else if (*headern == 'p' || *headern == 'P') { - /* Password header */ - if (g_ascii_strcasecmp (headern, "password") == 0) { - if (ctx->password == NULL || - (line->len == strlen (ctx->password) - && memcmp (line->begin, ctx->password, line->len) == 0)) { - session->authorized = TRUE; - } - else { - msg_info ("wrong password in controller command"); - } - return TRUE; + + obj = ucl_parser_get_object (parser); + ucl_parser_free (parser); + + if (obj->type != UCL_ARRAY || obj->len != 3) { + msg_err ("input is not an array of 3 elements"); + rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); + ucl_object_unref (obj); + return 0; + } + + cur = obj->value.av; + for (i = 0; i < 3 && cur != NULL; i ++, cur = cur->next) { + switch(i) { + case 0: + act = METRIC_ACTION_REJECT; + break; + case 1: + act = METRIC_ACTION_ADD_HEADER; + break; + case 2: + act = METRIC_ACTION_GREYLIST; + break; + } + score = ucl_object_todouble (cur); + if (metric->actions[act].score != score) { + add_dynamic_action (ctx->cfg, DEFAULT_METRIC, act, score); } } - g_hash_table_insert (session->kwargs, headern, fstrcstr (line, session->session_pool)); + dump_dynamic_config (ctx->cfg); - return TRUE; + rspamd_controller_send_string (conn_ent, "{\"success\":true}"); + + return 0; } /* - * Called if all filters are processed, non-threaded and simple version + * Save symbols command handler: + * request: /savesymbols + * headers: Password + * input: json data + * reply: json {"success":true} or {"error":"error message"} */ -static gboolean -fin_learn_task (void *arg) +static int +rspamd_webui_handle_savesymbols (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - struct rspamd_task *task = (struct rspamd_task *) arg; + struct rspamd_webui_session *session = conn_ent->ud; + struct ucl_parser *parser; + struct metric *metric; + ucl_object_t *obj; + const ucl_object_t *cur, *jname, *jvalue; + ucl_object_iter_t iter = NULL; + struct rspamd_webui_worker_ctx *ctx; + const gchar *error; + gdouble val; + struct symbol *sym; + + ctx = session->ctx; + + if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { + return 0; + } - /* XXX: needs to be reworked */ + if (msg->body == NULL || msg->body->len == 0) { + msg_err ("got zero length body, cannot continue"); + rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); + return 0; + } - return TRUE; + metric = g_hash_table_lookup (ctx->cfg->metrics, DEFAULT_METRIC); + if (metric == NULL) { + msg_err ("cannot find default metric"); + rspamd_controller_send_error (conn_ent, 500, "Default metric is absent"); + return 0; + } + + /* Now check for dynamic config */ + if (!ctx->cfg->dynamic_conf) { + msg_err ("dynamic conf has not been defined"); + rspamd_controller_send_error (conn_ent, 500, "No dynamic_rules setting defined"); + return 0; + } + + parser = ucl_parser_new (0); + ucl_parser_add_chunk (parser, msg->body->str, msg->body->len); + + if ((error = ucl_parser_get_error (parser)) != NULL) { + msg_err ("cannot parse input: %s", error); + rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); + ucl_parser_free (parser); + return 0; + } + + obj = ucl_parser_get_object (parser); + ucl_parser_free (parser); + + if (obj->type != UCL_ARRAY || obj->len != 3) { + msg_err ("input is not an array of 3 elements"); + rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); + ucl_object_unref (obj); + return 0; + } + + while ((cur = ucl_iterate_object (obj, &iter, true))) { + if (cur->type != UCL_OBJECT) { + msg_err ("json array data error"); + rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); + ucl_object_unref (obj); + return 0; + } + jname = ucl_object_find_key (cur, "name"); + jvalue = ucl_object_find_key (cur, "value"); + val = ucl_object_todouble (jvalue); + sym = g_hash_table_lookup (metric->symbols, ucl_object_tostring (jname)); + if (sym && fabs (sym->score - val) > 0.01) { + if (!add_dynamic_symbol (ctx->cfg, DEFAULT_METRIC, + ucl_object_tostring (jname), val)) { + msg_err ("add symbol failed for %s", ucl_object_tostring (jname)); + rspamd_controller_send_error (conn_ent, 506, "Add symbol failed"); + ucl_object_unref (obj); + return 0; + } + } + } + + dump_dynamic_config (ctx->cfg); + + rspamd_controller_send_string (conn_ent, "{\"success\":true}"); + + return 0; } /* - * Called if session was restored inside fin callback + * Save map command handler: + * request: /savemap + * headers: Password, Map + * input: plaintext data + * reply: json {"success":true} or {"error":"error message"} */ -static void -restore_learn_task (void *arg) +static int +rspamd_webui_handle_savemap (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - struct rspamd_task *task = (struct rspamd_task *) arg; + struct rspamd_webui_session *session = conn_ent->ud; + GList *cur; + struct rspamd_map *map; + struct rspamd_webui_worker_ctx *ctx; + const gchar *idstr; + gchar *errstr; + guint32 id; + gboolean found = FALSE; + gint fd; + + ctx = session->ctx; + + if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { + return 0; + } - /* Special state */ -} + if (msg->body == NULL || msg->body->len == 0) { + msg_err ("got zero length body, cannot continue"); + rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); + return 0; + } -static gboolean -controller_read_socket (f_str_t * in, void *arg) -{ - struct controller_session *session = (struct controller_session *)arg; - struct classifier_ctx *cls_ctx; - gint len, i, r; - gchar *s, **params, *cmd, out_buf[128]; - struct controller_command *command; - struct rspamd_task *task; - struct mime_text_part *part; - GList *cur = NULL; - GTree *tokens = NULL; - GError *err = NULL; - f_str_t c; - controller_func_t custom_handler; - - switch (session->state) { - case STATE_COMMAND: - s = fstrcstr (in, session->session_pool); - params = g_strsplit_set (s, " ", -1); - - rspamd_mempool_add_destructor (session->session_pool, (rspamd_mempool_destruct_t) g_strfreev, params); - - len = g_strv_length (params); - if (len > 0) { - cmd = g_strstrip (params[0]); - - command = parse_normal_command (cmd, 0); - if (command != NULL) { - if (! process_command (command, ¶ms[1], session)) { - return FALSE; - } - } - else { - if ((custom_handler = parse_custom_command (cmd, ¶ms[1], session, 0)) == NULL) { - msg_debug ("'%s'", cmd); - i = rspamd_snprintf (out_buf, sizeof (out_buf), "Unknown command" CRLF); - if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { - return FALSE; - } - } - else { - custom_handler (¶ms[1], session); - } - } - } - if (session->state != STATE_LEARN && session->state != STATE_LEARN_SPAM_PRE - && session->state != STATE_WEIGHTS && session->state != STATE_OTHER && session->state != STATE_HEADER) { - if (!rspamd_dispatcher_write (session->dispatcher, END, sizeof (END) - 1, FALSE, TRUE)) { - return FALSE; - } - if (session->state == STATE_QUIT) { - destroy_session (session->s); - return FALSE; - } - } + idstr = rspamd_http_message_find_header (msg, "Map"); - break; - case STATE_HEADER: - if (in->len == 0) { - /* End of headers */ - if (session->cmd == NULL && session->custom_handler == NULL) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Bad command" CRLF CRLF); - if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { - return FALSE; - } - destroy_session (session->s); - return FALSE; - } - /* Perform command */ - else if (session->cmd != NULL) { - if (! process_command (session->cmd, NULL, session)) { - msg_debug ("process command failed"); - return FALSE; - } - } - else { - session->custom_handler (NULL, session); - } - if (session->state != STATE_LEARN && session->state != STATE_LEARN_SPAM_PRE - && session->state != STATE_WEIGHTS && session->state != STATE_OTHER) { - msg_debug ("closing restful connection"); - destroy_session (session->s); - return FALSE; - } - } - else if (!process_header (in, session)) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Bad header" CRLF CRLF); - if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { - return FALSE; - } - destroy_session (session->s); - return FALSE; - } - break; - case STATE_LEARN: - session->learn_buf = in; - task = rspamd_task_new (session->worker); - - task->msg = g_string_new_len (in->begin, in->len); - task->ev_base = session->ev_base; - - r = process_message (task); - if (r == -1) { - msg_warn ("processing of message failed"); - rspamd_task_free (task, FALSE); - session->state = STATE_REPLY; - if (session->restful) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot process message" CRLF CRLF); - } - else { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF); - } - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - return FALSE; - } + if (idstr == NULL) { + msg_info ("absent map id"); + rspamd_controller_send_error (conn_ent, 400, "Map id not specified"); + return 0; + } - if (!learn_task (session->learn_symbol, task, &err)) { - rspamd_task_free (task, FALSE); - if (err) { - if (session->restful) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: %s" CRLF CRLF, err->message); - } - else { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: %s" CRLF END, err->message); - } - g_error_free (err); - } - else { - if (session->restful) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: unknown" CRLF CRLF); - } - else { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, unknown learn classifier error" CRLF END); - } - } + id = strtoul (idstr, &errstr, 10); + if (*errstr != '\0') { + msg_info ("invalid map id"); + rspamd_controller_send_error (conn_ent, 400, "Map id is invalid"); + return 0; + } - if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { - return FALSE; - } - session->state = STATE_REPLY; - return TRUE; + /* Now let's be sure that we have map defined in configuration */ + cur = ctx->cfg->maps; + while (cur) { + map = cur->data; + if (map->id == id && map->protocol == MAP_PROTO_FILE) { + found = TRUE; + break; } + cur = g_list_next (cur); + } - rspamd_task_free (task, FALSE); - if (session->restful) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 200 Learn OK" CRLF CRLF); - } - else { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn ok" CRLF END); - } - session->state = STATE_REPLY; - if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { - return FALSE; - } - break; - case STATE_LEARN_SPAM_PRE: - session->learn_buf = in; - task = rspamd_task_new (session->worker); - - task->msg = g_string_new_len (in->begin, in->len); - - task->resolver = session->resolver; - task->ev_base = session->ev_base; - - r = process_message (task); - if (r == -1) { - msg_warn ("processing of message failed"); - session->state = STATE_REPLY; - if (session->restful) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot process message" CRLF CRLF); - } - else { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF); - } - if (!session->restful) { - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - } - return FALSE; - } - /* Set up async session */ - task->s = new_async_session (task->task_pool, fin_learn_task, restore_learn_task, rspamd_task_free_hard, task); - session->learn_task = task; - session->state = STATE_LEARN_SPAM; - rspamd_dispatcher_pause (session->dispatcher); - r = process_filters (task); - if (r == -1) { - rspamd_dispatcher_restore (session->dispatcher); - session->state = STATE_REPLY; - if (session->restful) { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot process message" CRLF CRLF); - } - else { - r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF); - } - destroy_session (task->s); - if (!session->restful) { - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - } - } - break; - case STATE_WEIGHTS: - session->learn_buf = in; - task = rspamd_task_new (session->worker); - - task->msg = g_string_new_len (in->begin, in->len); - task->ev_base = session->ev_base; - - r = process_message (task); - if (r == -1) { - msg_warn ("processing of message failed"); - rspamd_task_free (task, FALSE); - session->state = STATE_REPLY; - r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF END); - if (!session->restful) { - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - } - return FALSE; - } + if (!found) { + msg_info ("map not found: %d", id); + rspamd_controller_send_error (conn_ent, 404, "Map id not found"); + return 0; + } - cur = g_list_first (task->text_parts); - while (cur) { - part = cur->data; - if (part->is_empty) { - cur = g_list_next (cur); - continue; - } + if (g_atomic_int_get (map->locked)) { + msg_info ("map locked: %s", map->uri); + rspamd_controller_send_error (conn_ent, 404, "Map is locked"); + return 0; + } - c.begin = part->content->data; - c.len = part->content->len; - if (!session->learn_classifier->tokenizer->tokenize_func (session->learn_classifier->tokenizer, - session->session_pool, &c, &tokens, FALSE, part->is_utf, part->urls_offset)) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "weights failed, tokenizer error" CRLF END); - rspamd_task_free (task, FALSE); - if (!session->restful) { - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { - return FALSE; - } - } - session->state = STATE_REPLY; - return TRUE; - } - cur = g_list_next (cur); - } - - /* Handle messages without text */ - if (tokens == NULL) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "weights failed, no tokens can be extracted (no text data)" CRLF END); - rspamd_task_free (task, FALSE); - if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { - return FALSE; - } - session->state = STATE_REPLY; - return TRUE; - } - - - /* Init classifier */ - cls_ctx = session->learn_classifier->classifier->init_func (session->session_pool, session->learn_classifier); - - cur = session->learn_classifier->classifier->weights_func (cls_ctx, session->worker->srv->statfile_pool, - tokens, task); - i = 0; - struct classify_weight *w; + /* Set lock */ + g_atomic_int_set (map->locked, 1); + fd = open (map->uri, O_WRONLY | O_TRUNC); + if (fd == -1) { + g_atomic_int_set (map->locked, 0); + msg_info ("map %s open error: %s", map->uri, strerror (errno)); + rspamd_controller_send_error (conn_ent, 404, "Map id not found"); + return 0; + } - while (cur) { - w = cur->data; - i += rspamd_snprintf (out_buf + i, sizeof (out_buf) - i, "%s: %G" CRLF, w->name, w->weight); - cur = g_list_next (cur); - } - i += rspamd_snprintf (out_buf + i, sizeof (out_buf) - i, END); - if (i != 0) { - if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { - return FALSE; - } - } - else { - if (!rspamd_dispatcher_write (session->dispatcher, "weights failed: classifier error" CRLF END, 0, FALSE, TRUE)) { - return FALSE; - } - } + if (write (fd, msg->body->str, msg->body->len) == -1) { + msg_info ("map %s write error: %s", map->uri, strerror (errno)); + close (fd); + g_atomic_int_set (map->locked, 0); + rspamd_controller_send_error (conn_ent, 500, "Map write error"); + return 0; + } - rspamd_task_free (task, FALSE); + /* Close and unlock */ + close (fd); + g_atomic_int_set (map->locked, 0); - session->state = STATE_REPLY; - break; - case STATE_OTHER: - rspamd_dispatcher_pause (session->dispatcher); - if (session->other_handler) { - if (!session->other_handler (session, in)) { - return FALSE; - } - } - break; - case STATE_WAIT: - rspamd_dispatcher_pause (session->dispatcher); - break; - default: - msg_debug ("unknown state while reading %d", session->state); - break; - } - - if (session->state == STATE_REPLY || session->state == STATE_QUIT) { - /* In case of normal session we restore read state, for restful session we need to terminate immediately */ - if (!session->restful) { - rspamd_dispatcher_restore (session->dispatcher); - } - else { - return FALSE; - } - } - return TRUE; + rspamd_controller_send_string (conn_ent, "{\"success\":true}"); + + return 0; } -static gboolean -controller_write_socket (void *arg) +/* + * Stat command handler: + * request: /stat (/resetstat) + * headers: Password + * reply: json data + */ +static int +rspamd_webui_handle_stat_common (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg, gboolean do_reset) { - struct controller_session *session = (struct controller_session *)arg; - gint i; - gchar out_buf[1024]; - GError *err = NULL; - - if (session->state == STATE_QUIT) { - /* Free buffers */ - destroy_session (session->s); - return FALSE; - } - else if (session->state == STATE_LEARN_SPAM) { - /* Perform actual learn here */ - if (session->learn_classifier == NULL) { - if (session->restful) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: %s" CRLF CRLF, "unknown classifier"); + struct rspamd_webui_session *session = conn_ent->ud; + ucl_object_t *top, *sub; + gint i; + guint64 used, total, rev, ham = 0, spam = 0; + time_t ti; + rspamd_mempool_stat_t mem_st; + struct rspamd_classifier_config *ccf; + stat_file_t *statfile; + struct rspamd_statfile_config *st; + GList *cur_cl, *cur_st; + struct rspamd_stat *stat, stat_copy; + + rspamd_mempool_stat (&mem_st); + memcpy (&stat_copy, session->ctx->worker->srv->stat, sizeof (stat_copy)); + stat = &stat_copy; + top = ucl_object_typed_new (UCL_OBJECT); + + ucl_object_insert_key (top, ucl_object_fromint (stat->messages_scanned), "scanned", 0, false); + if (stat->messages_scanned > 0) { + sub = ucl_object_typed_new (UCL_OBJECT); + for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i ++) { + ucl_object_insert_key (top, + ucl_object_fromint (stat->actions_stat[i]), + str_action_metric (i), 0, false); + if (i < METRIC_ACTION_GREYLIST) { + spam += stat->actions_stat[i]; } else { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: %s" CRLF END, "unknown classifier"); + ham += stat->actions_stat[i]; } - } - else { - if (! learn_task_spam (session->learn_classifier, session->learn_task, session->in_class, &err)) { - if (err) { - if (session->restful) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: %s" CRLF CRLF, err->message); - } - else { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: %s" CRLF END, err->message); - } - g_error_free (err); - } - else { - if (session->restful) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: unknown" CRLF CRLF); - } - else { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: unknown" CRLF END); - } - } + if (do_reset) { + session->ctx->worker->srv->stat->actions_stat[i] = 0; + } + } + ucl_object_insert_key (top, sub, "actions", 0, false); + } + + ucl_object_insert_key (top, ucl_object_fromint (spam), "spam_count", 0, false); + ucl_object_insert_key (top, ucl_object_fromint (ham), "ham_count", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (stat->messages_learned), "learned", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (stat->connections_count), "connections", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (stat->control_connections_count), + "control_connections", 0, false); + + ucl_object_insert_key (top, + ucl_object_fromint (mem_st.pools_allocated), "pools_allocated", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (mem_st.pools_freed), "pools_freed", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (mem_st.bytes_allocated), "bytes_allocated", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (mem_st.chunks_allocated), "chunks_allocated", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (mem_st.shared_chunks_allocated), + "shared_chunks_allocated", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (mem_st.chunks_freed), "chunks_freed", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (mem_st.oversized_chunks), "chunks_oversized", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (stat->fuzzy_hashes), "fuzzy_stored", 0, false); + ucl_object_insert_key (top, + ucl_object_fromint (stat->fuzzy_hashes_expired), "fuzzy_expired", 0, false); + + /* Now write statistics for each statfile */ + cur_cl = g_list_first (session->ctx->cfg->classifiers); + sub = ucl_object_typed_new (UCL_ARRAY); + while (cur_cl) { + ccf = cur_cl->data; + cur_st = g_list_first (ccf->statfiles); + while (cur_st) { + st = cur_st->data; + if ((statfile = statfile_pool_is_open (session->ctx->srv->statfile_pool, + st->path)) == NULL) { + statfile = statfile_pool_open (session->ctx->srv->statfile_pool, + st->path, st->size, FALSE); } - else { - if (session->restful) { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 200 Learn OK" CRLF CRLF); - } - else { - i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn ok" CRLF END); + if (statfile) { + ucl_object_t *obj = ucl_object_typed_new (UCL_OBJECT); + + used = statfile_get_used_blocks (statfile); + total = statfile_get_total_blocks (statfile); + statfile_get_revision (statfile, &rev, &ti); + ucl_object_insert_key (obj, + ucl_object_fromstring (st->symbol), "symbol", 0, false); + if (st->label != NULL) { + ucl_object_insert_key (obj, + ucl_object_fromstring (st->label), "label", 0, false); } + ucl_object_insert_key (obj, ucl_object_fromint (rev), "revision", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (used), "used", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (total), "total", 0, false); + + ucl_array_append (sub, obj); } + cur_st = g_list_next (cur_st); } - destroy_session (session->learn_task->s); - session->state = STATE_REPLY; - if (! rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { - return FALSE; - } - if (session->restful) { - destroy_session (session->s); - return FALSE; - } - return TRUE; + cur_cl = g_list_next (cur_cl); } - else if (session->state == STATE_REPLY) { - if (session->restful) { - destroy_session (session->s); - return FALSE; - } - else { - session->state = STATE_COMMAND; - rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_LINE, BUFSIZ); - } + + ucl_object_insert_key (top, sub, "statfiles", 0, false); + + if (do_reset) { + session->ctx->srv->stat->messages_scanned = 0; + session->ctx->srv->stat->messages_learned = 0; + session->ctx->srv->stat->connections_count = 0; + session->ctx->srv->stat->control_connections_count = 0; + rspamd_mempool_stat_reset (); } - rspamd_dispatcher_restore (session->dispatcher); - return TRUE; + + rspamd_controller_send_ucl (conn_ent, top); + ucl_object_unref (top); + + return 0; } -static void -controller_err_socket (GError * err, void *arg) +static int +rspamd_webui_handle_stat (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - struct controller_session *session = (struct controller_session *)arg; + struct rspamd_webui_session *session = conn_ent->ud; - if (err->code != EOF) { - msg_info ("abnormally closing control connection, error: %s", err->message); + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - g_error_free (err); - /* Free buffers */ - destroy_session (session->s); + + return rspamd_webui_handle_stat_common (conn_ent, msg, FALSE); } -static void -accept_socket (gint fd, short what, void *arg) +static int +rspamd_webui_handle_statreset (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) { - struct rspamd_worker *worker = (struct rspamd_worker *)arg; - union sa_union su; - struct controller_session *new_session; - struct timeval *io_tv; - gint nfd; - struct rspamd_controller_ctx *ctx; - rspamd_inet_addr_t addr; + struct rspamd_webui_session *session = conn_ent->ud; - ctx = worker->ctx; - - if ((nfd = rspamd_accept_from_socket (fd, &addr)) == -1) { - return; + if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { + return 0; } - new_session = g_slice_alloc0 (sizeof (struct controller_session)); - if (new_session == NULL) { - msg_err ("cannot allocate memory for task, %s", strerror (errno)); - return; - } + return rspamd_webui_handle_stat_common (conn_ent, msg, TRUE); +} + +static ucl_object_t * +rspamd_webui_cache_item_to_ucl (struct cache_item *item) +{ + ucl_object_t *obj; + + obj = ucl_object_typed_new (UCL_OBJECT); + ucl_object_insert_key (obj, ucl_object_fromstring (item->s->symbol), + "symbol", 0, false); + ucl_object_insert_key (obj, ucl_object_fromdouble (item->s->weight), + "weight", 0, false); + ucl_object_insert_key (obj, ucl_object_fromint (item->s->frequency), + "frequency", 0, false); + ucl_object_insert_key (obj, ucl_object_fromdouble (item->s->avg_time), + "time", 0, false); + + return obj; +} - new_session->worker = worker; - new_session->sock = nfd; - new_session->cfg = worker->srv->cfg; - new_session->state = STATE_COMMAND; - new_session->session_pool = rspamd_mempool_new (rspamd_mempool_suggest_size () - 1); - new_session->resolver = ctx->resolver; - new_session->ev_base = ctx->ev_base; - if (ctx->password == NULL) { - new_session->authorized = TRUE; +/* + * Counters command handler: + * request: /counters + * headers: Password + * reply: json array of all counters + */ +static int +rspamd_webui_handle_counters (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) +{ + struct rspamd_webui_session *session = conn_ent->ud; + ucl_object_t *top; + GList *cur; + struct cache_item *item; + struct symbols_cache *cache; + + if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { + return 0; } - worker->srv->stat->control_connections_count++; - /* Set up dispatcher */ - io_tv = rspamd_mempool_alloc (new_session->session_pool, sizeof (struct timeval)); - io_tv->tv_sec = ctx->timeout / 1000; - io_tv->tv_usec = ctx->timeout - io_tv->tv_sec * 1000; + cache = session->ctx->cfg->cache; + top = ucl_object_typed_new (UCL_ARRAY); + if (cache != NULL) { + cur = cache->negative_items; + while (cur) { + item = cur->data; + if (!item->is_callback) { + ucl_array_append (top, rspamd_webui_cache_item_to_ucl (item)); + } + cur = g_list_next (cur); + } + cur = cache->static_items; + while (cur) { + item = cur->data; + if (!item->is_callback) { + ucl_array_append (top, rspamd_webui_cache_item_to_ucl (item)); + } + cur = g_list_next (cur); + } + } + rspamd_controller_send_ucl (conn_ent, top); + ucl_object_unref (top); - new_session->s = new_async_session (new_session->session_pool, NULL, NULL, free_session, new_session); + return 0; +} - new_session->dispatcher = rspamd_create_dispatcher (ctx->ev_base, nfd, BUFFER_LINE, controller_read_socket, - controller_write_socket, controller_err_socket, io_tv, (void *)new_session); +static int +rspamd_webui_handle_custom (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) +{ + struct rspamd_webui_session *session = conn_ent->ud; + struct rspamd_custom_controller_command *cmd; - if (su.ss.ss_family == AF_UNIX) { - msg_info ("accepted connection from unix socket"); - new_session->dispatcher->peer_addr = INADDR_LOOPBACK; + cmd = g_hash_table_lookup (session->ctx->custom_commands, msg->url->str); + if (cmd == NULL || cmd->handler == NULL) { + msg_err ("custom command %V has not been found", msg->url); + rspamd_controller_send_error (conn_ent, 404, "No command associated"); + return 0; } - else if (su.ss.ss_family == AF_INET) { - msg_info ("accepted connection from %s port %d", - inet_ntoa (su.s4.sin_addr), ntohs (su.s4.sin_port)); - memcpy (&new_session->dispatcher->peer_addr, &su.s4.sin_addr, - sizeof (guint32)); + + if (!rspamd_webui_check_password (conn_ent, session, msg, cmd->privilleged)) { + return 0; } -#if 0 - /* As we support now restful HTTP like connections, this should not be printed anymore */ - if (! rspamd_dispatcher_write (new_session->dispatcher, greetingbuf, strlen (greetingbuf), FALSE, FALSE)) { - msg_warn ("cannot write greeting"); + if (cmd->require_message && (msg->body == NULL || msg->body->len == 0)) { + msg_err ("got zero length body, cannot continue"); + rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); + return 0; } -#endif + + return cmd->handler (conn_ent, msg, cmd->ctx); } -static gboolean -create_rrd_file (const gchar *filename, struct rspamd_controller_ctx *ctx) +static void +rspamd_webui_error_handler (struct rspamd_http_connection_entry *conn_ent, GError *err) +{ + msg_err ("http error occurred: %s", err->message); +} + +static void +rspamd_webui_finish_handler (struct rspamd_http_connection_entry *conn_ent) { - GError *err = NULL; - GArray ar; - struct rrd_rra_def rra[5]; - struct rrd_ds_def ds[4]; - - /* - * DS: - * 1) reject as spam - * 2) mark as spam - * 3) greylist - * 4) pass - */ - /* - * RRA: - * 1) per minute AVERAGE - * 2) per 5 minutes AVERAGE - * 3) per 30 minutes AVERAGE - * 4) per 2 hours AVERAGE - * 5) per day AVERAGE - */ - ctx->rrd_file = rspamd_rrd_create (filename, 4, 5, CONTROLLER_RRD_STEP, &err); - if (ctx->rrd_file == NULL) { - msg_err ("cannot create rrd file %s, error: %s", filename, err->message); - g_error_free (err); - return FALSE; - } - - /* Add all ds and rra */ - rrd_make_default_ds ("spam", CONTROLLER_RRD_STEP, &ds[0]); - rrd_make_default_ds ("possible spam", CONTROLLER_RRD_STEP, &ds[1]); - rrd_make_default_ds ("greylist", CONTROLLER_RRD_STEP, &ds[2]); - rrd_make_default_ds ("ham", CONTROLLER_RRD_STEP, &ds[3]); - - rrd_make_default_rra ("AVERAGE", 1, 600, &rra[0]); - rrd_make_default_rra ("AVERAGE", 5, 600, &rra[1]); - rrd_make_default_rra ("AVERAGE", 30, 700, &rra[2]); - rrd_make_default_rra ("AVERAGE", 120, 775, &rra[3]); - rrd_make_default_rra ("AVERAGE", 1440, 797, &rra[4]); - - ar.data = (gchar *)ds; - ar.len = sizeof (ds); - if (!rspamd_rrd_add_ds (ctx->rrd_file, &ar, &err)) { - msg_err ("cannot create rrd file %s, error: %s", filename, err->message); - g_error_free (err); - rspamd_rrd_close (ctx->rrd_file); - return FALSE; - } - - ar.data = (gchar *)rra; - ar.len = sizeof (rra); - if (!rspamd_rrd_add_rra (ctx->rrd_file, &ar, &err)) { - msg_err ("cannot create rrd file %s, error: %s", filename, err->message); - g_error_free (err); - rspamd_rrd_close (ctx->rrd_file); - return FALSE; - } - - /* Finalize */ - if (!rspamd_rrd_finalize (ctx->rrd_file, &err)) { - msg_err ("cannot create rrd file %s, error: %s", filename, err->message); - g_error_free (err); - rspamd_rrd_close (ctx->rrd_file); - return FALSE; + struct rspamd_webui_session *session = conn_ent->ud; + + if (session->pool) { + rspamd_mempool_delete (session->pool); + } + if (session->task != NULL) { + destroy_session (session->task->s); } - return TRUE; + g_slice_free1 (sizeof (struct rspamd_webui_session), session); } static void -controller_update_rrd (gint fd, short what, void *arg) +rspamd_webui_accept_socket (gint fd, short what, void *arg) { - struct rspamd_controller_ctx *ctx = arg; - struct timeval tv; - GArray ar; - gdouble data[4]; - GError *err = NULL; - - /* - * Data: - * 1) reject as spam - * 2) mark as spam - * 3) greylist - * 4) pass - */ - - tv.tv_sec = CONTROLLER_RRD_STEP; - tv.tv_usec = 0; - - /* Fill data */ - data[0] = ctx->srv->stat->actions_stat[METRIC_ACTION_REJECT]; - data[1] = ctx->srv->stat->actions_stat[METRIC_ACTION_ADD_HEADER] + ctx->srv->stat->actions_stat[METRIC_ACTION_REWRITE_SUBJECT]; - data[2] = ctx->srv->stat->actions_stat[METRIC_ACTION_GREYLIST]; - data[3] = ctx->srv->stat->actions_stat[METRIC_ACTION_NOACTION]; - - ar.data = (gchar *)data; - ar.len = sizeof (data); - if (!rspamd_rrd_add_record (ctx->rrd_file, &ar, &err)) { - msg_err ("cannot add record to rrd database: %s, stop rrd update", err->message); - g_error_free (err); + struct rspamd_worker *worker = (struct rspamd_worker *) arg; + struct rspamd_webui_worker_ctx *ctx; + struct rspamd_webui_session *nsession; + rspamd_inet_addr_t addr; + gint nfd; + + ctx = worker->ctx; + + if ((nfd = + rspamd_accept_from_socket (fd, &addr)) == -1) { + msg_warn ("accept failed: %s", strerror (errno)); + return; } - else { - evtimer_add (&ctx->rrd_event, &tv); + /* Check for EAGAIN */ + if (nfd == 0) { + return; } + + msg_info ("accepted connection from %s port %d", + rspamd_inet_address_to_string (&addr), + rspamd_inet_address_get_port (&addr)); + + nsession = g_slice_alloc0 (sizeof (struct rspamd_webui_session)); + nsession->pool = rspamd_mempool_new (rspamd_mempool_suggest_size ()); + nsession->ctx = ctx; + + memcpy (&nsession->from_addr, &addr, sizeof (addr)); + + rspamd_http_router_handle_socket (ctx->http, nfd, nsession); } gpointer -init_controller (struct rspamd_config *cfg) +init_webui_worker (struct rspamd_config *cfg) { - struct rspamd_controller_ctx *ctx; + struct rspamd_webui_worker_ctx *ctx; GQuark type; - type = g_quark_try_string ("controller"); - ctx = g_malloc0 (sizeof (struct rspamd_controller_ctx)); + type = g_quark_try_string ("webui"); + + ctx = g_malloc0 (sizeof (struct rspamd_webui_worker_ctx)); - ctx->timeout = CONTROLLER_IO_TIMEOUT * 1000; + ctx->timeout = DEFAULT_WORKER_IO_TIMEOUT; rspamd_rcl_register_worker_option (cfg, type, "password", rspamd_rcl_parse_struct_string, ctx, - G_STRUCT_OFFSET (struct rspamd_controller_ctx, password), 0); + G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, password), 0); + + rspamd_rcl_register_worker_option (cfg, type, "enable_password", + rspamd_rcl_parse_struct_string, ctx, + G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, password), 0); + + rspamd_rcl_register_worker_option (cfg, type, "ssl", + rspamd_rcl_parse_struct_boolean, ctx, + G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, use_ssl), 0); + + rspamd_rcl_register_worker_option (cfg, type, "ssl_cert", + rspamd_rcl_parse_struct_string, ctx, + G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, ssl_cert), 0); + rspamd_rcl_register_worker_option (cfg, type, "ssl_key", + rspamd_rcl_parse_struct_string, ctx, + G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, ssl_key), 0); rspamd_rcl_register_worker_option (cfg, type, "timeout", rspamd_rcl_parse_struct_time, ctx, - G_STRUCT_OFFSET (struct rspamd_controller_ctx, timeout), RSPAMD_CL_FLAG_TIME_UINT_32); + G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, timeout), RSPAMD_CL_FLAG_TIME_INTEGER); + + rspamd_rcl_register_worker_option (cfg, type, "secure_ip", + rspamd_rcl_parse_struct_string, ctx, + G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, secure_ip), 0); + + rspamd_rcl_register_worker_option (cfg, type, "static_dir", + rspamd_rcl_parse_struct_string, ctx, + G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, static_files_dir), 0); return ctx; } +/* + * Start worker process + */ void -start_controller (struct rspamd_worker *worker) +start_webui_worker (struct rspamd_worker *worker) { - gchar *hostbuf; - gsize hostmax; - struct rspamd_controller_ctx *ctx = worker->ctx; - GError *err = NULL; - struct timeval tv; + struct rspamd_webui_worker_ctx *ctx = worker->ctx; + GList *cur; + struct filter *f; + struct module_ctx *mctx; + GHashTableIter iter; + gpointer key, value; - ctx->ev_base = rspamd_prepare_worker (worker, "controller", accept_socket); - g_mime_init (0); - start_time = time (NULL); + ctx->ev_base = rspamd_prepare_worker (worker, "controller", rspamd_webui_accept_socket); + msec_to_tv (ctx->timeout, &ctx->io_tv); - /* Start statfile synchronization */ - if (!start_statfile_sync (worker->srv->statfile_pool, worker->srv->cfg, ctx->ev_base)) { - msg_info ("cannot start statfile synchronization, statfiles would not be synchronized"); - } - - /* Check for rrd */ - tv.tv_sec = CONTROLLER_RRD_STEP; - tv.tv_usec = 0; + ctx->start_time = time (NULL); + ctx->worker = worker; + ctx->cfg = worker->srv->cfg; ctx->srv = worker->srv; - if (worker->srv->cfg->rrd_file) { - ctx->rrd_file = rspamd_rrd_open (worker->srv->cfg->rrd_file, &err); - if (ctx->rrd_file == NULL) { - msg_info ("cannot open rrd file: %s, error: %s, trying to create", worker->srv->cfg->rrd_file, err->message); - g_error_free (err); - /* Try to create rrd file */ - if (create_rrd_file (worker->srv->cfg->rrd_file, ctx)) { - evtimer_set (&ctx->rrd_event, controller_update_rrd, ctx); - event_base_set (ctx->ev_base, &ctx->rrd_event); - evtimer_add (&ctx->rrd_event, &tv); - } - } - else { - evtimer_set (&ctx->rrd_event, controller_update_rrd, ctx); - event_base_set (ctx->ev_base, &ctx->rrd_event); - evtimer_add (&ctx->rrd_event, &tv); + ctx->custom_commands = g_hash_table_new (rspamd_strcase_hash, + rspamd_strcase_equal); + if (ctx->secure_ip != NULL) { + if (!add_map (worker->srv->cfg, ctx->secure_ip, "Allow webui access from the specified IP", + read_radix_list, fin_radix_list, (void **)&ctx->secure_map)) { + if (!rspamd_config_parse_ip_list (ctx->secure_ip, &ctx->secure_map)) { + msg_warn ("cannot load or parse ip list from '%s'", ctx->secure_ip); + } + } + } + /* Accept event */ + ctx->http = rspamd_http_router_new (rspamd_webui_error_handler, + rspamd_webui_finish_handler, &ctx->io_tv, ctx->ev_base, + ctx->static_files_dir); + + /* Add callbacks for different methods */ + rspamd_http_router_add_path (ctx->http, PATH_AUTH, rspamd_webui_handle_auth); + rspamd_http_router_add_path (ctx->http, PATH_SYMBOLS, rspamd_webui_handle_symbols); + rspamd_http_router_add_path (ctx->http, PATH_ACTIONS, rspamd_webui_handle_actions); + rspamd_http_router_add_path (ctx->http, PATH_MAPS, rspamd_webui_handle_maps); + rspamd_http_router_add_path (ctx->http, PATH_GET_MAP, rspamd_webui_handle_get_map); + rspamd_http_router_add_path (ctx->http, PATH_PIE_CHART, rspamd_webui_handle_pie_chart); + rspamd_http_router_add_path (ctx->http, PATH_HISTORY, rspamd_webui_handle_history); + rspamd_http_router_add_path (ctx->http, PATH_LEARN_SPAM, rspamd_webui_handle_learnspam); + rspamd_http_router_add_path (ctx->http, PATH_LEARN_HAM, rspamd_webui_handle_learnham); + rspamd_http_router_add_path (ctx->http, PATH_SAVE_ACTIONS, rspamd_webui_handle_saveactions); + rspamd_http_router_add_path (ctx->http, PATH_SAVE_SYMBOLS, rspamd_webui_handle_savesymbols); + rspamd_http_router_add_path (ctx->http, PATH_SAVE_MAP, rspamd_webui_handle_savemap); + rspamd_http_router_add_path (ctx->http, PATH_SCAN, rspamd_webui_handle_scan); + rspamd_http_router_add_path (ctx->http, PATH_STAT, rspamd_webui_handle_stat); + rspamd_http_router_add_path (ctx->http, PATH_STAT_RESET, rspamd_webui_handle_statreset); + rspamd_http_router_add_path (ctx->http, PATH_COUNTERS, rspamd_webui_handle_counters); + + /* Attach plugins */ + cur = g_list_first (ctx->cfg->filters); + while (cur) { + f = cur->data; + mctx = g_hash_table_lookup (ctx->cfg->c_modules, f->module->name); + if (mctx != NULL && f->module->module_init_func != NULL) { + f->module->module_attach_controller_func (mctx, ctx->custom_commands); } + cur = g_list_next (cur); } - /* Fill hostname buf */ - hostmax = sysconf (_SC_HOST_NAME_MAX) + 1; - hostbuf = alloca (hostmax); - gethostname (hostbuf, hostmax); - hostbuf[hostmax - 1] = '\0'; - rspamd_snprintf (greetingbuf, sizeof (greetingbuf), "Rspamd version %s is running on %s" CRLF, RVERSION, hostbuf); + g_hash_table_iter_init (&iter, ctx->custom_commands); + while (g_hash_table_iter_next (&iter, &key, &value)) { + rspamd_http_router_add_path (ctx->http, key, rspamd_webui_handle_custom); + } + +#if 0 + rspamd_http_router_add_path (ctx->http, PATH_GRAPH, rspamd_webui_handle_graph, ctx); +#endif - start_map_watch (worker->srv->cfg, ctx->ev_base); ctx->resolver = dns_resolver_init (worker->srv->logger, ctx->ev_base, worker->srv->cfg); - event_base_loop (ctx->ev_base, 0); + /* Maps events */ + start_map_watch (worker->srv->cfg, ctx->ev_base); - close_log (worker->srv->logger); - if (ctx->rrd_file) { - rspamd_rrd_close (ctx->rrd_file); - } + event_base_loop (ctx->ev_base, 0); + g_mime_shutdown (); + close_log (rspamd_main->logger); exit (EXIT_SUCCESS); } - -void -register_custom_controller_command (const gchar *name, controller_func_t handler, gboolean privilleged, gboolean require_message) -{ - struct custom_controller_command *cmd; - - cmd = g_malloc (sizeof (struct custom_controller_command)); - cmd->command = name; - cmd->handler = handler; - cmd->privilleged = privilleged; - cmd->require_message = require_message; - - custom_commands = g_list_prepend (custom_commands, cmd); -} - -/* - * vi:ts=4 - */ diff --git a/src/legacy_controller.c b/src/legacy_controller.c new file mode 100644 index 0000000000..05b1794cd9 --- /dev/null +++ b/src/legacy_controller.c @@ -0,0 +1,1947 @@ +/* + * 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 "util.h" +#include "main.h" +#include "message.h" +#include "protocol.h" +#include "upstream.h" +#include "cfg_file.h" +#include "map.h" +#include "dns.h" +#include "tokenizers/tokenizers.h" +#include "classifiers/classifiers.h" +#include "binlog.h" +#include "statfile_sync.h" +#include "lua/lua_common.h" +#include "dynamic_cfg.h" +#include "rrd.h" + +#define END "END" CRLF + +/* 120 seconds for controller's IO */ +#define CONTROLLER_IO_TIMEOUT 120 + +/* RRD macroes */ +/* Write data each minute */ +#define CONTROLLER_RRD_STEP 60 + +/* Init functions */ +gpointer init_controller (struct rspamd_config *cfg); +void start_controller (struct rspamd_worker *worker); + +worker_t controller_worker = { + "controller", /* Name */ + init_controller, /* Init function */ + start_controller, /* Start function */ + TRUE, /* Has socket */ + FALSE, /* Non unique */ + FALSE, /* Non threaded */ + TRUE, /* Killable */ + SOCK_STREAM /* TCP socket */ +}; + +enum command_type { + COMMAND_PASSWORD, + COMMAND_QUIT, + COMMAND_RELOAD, + COMMAND_STAT, + COMMAND_STAT_RESET, + COMMAND_SHUTDOWN, + COMMAND_UPTIME, + COMMAND_LEARN, + COMMAND_LEARN_SPAM, + COMMAND_LEARN_HAM, + COMMAND_HELP, + COMMAND_COUNTERS, + COMMAND_SYNC, + COMMAND_WEIGHTS, + COMMAND_GET, + COMMAND_POST, + COMMAND_ADD_SYMBOL, + COMMAND_ADD_ACTION +}; + +struct controller_command { + gchar *command; + gboolean privilleged; + enum command_type type; +}; + +struct custom_controller_command { + const gchar *command; + gboolean privilleged; + gboolean require_message; + controller_func_t handler; +}; + +struct rspamd_controller_ctx { + char *password; + guint32 timeout; + struct rspamd_dns_resolver *resolver; + struct event_base *ev_base; + struct event rrd_event; + struct rspamd_rrd_file *rrd_file; + struct rspamd_main *srv; +}; + +static struct controller_command commands[] = { + {"password", FALSE, COMMAND_PASSWORD}, + {"quit", FALSE, COMMAND_QUIT}, + {"reload", TRUE, COMMAND_RELOAD}, + {"stat", FALSE, COMMAND_STAT}, + {"stat_reset", TRUE, COMMAND_STAT_RESET}, + {"shutdown", TRUE, COMMAND_SHUTDOWN}, + {"uptime", FALSE, COMMAND_UPTIME}, + {"learn", TRUE, COMMAND_LEARN}, + {"weights", FALSE, COMMAND_WEIGHTS}, + {"help", FALSE, COMMAND_HELP}, + {"counters", FALSE, COMMAND_COUNTERS}, + {"sync", FALSE, COMMAND_SYNC}, + {"learn_spam", TRUE, COMMAND_LEARN_SPAM}, + {"learn_ham", TRUE, COMMAND_LEARN_HAM}, + {"get", FALSE, COMMAND_GET}, + {"post", FALSE, COMMAND_POST}, + {"add_symbol", TRUE, COMMAND_ADD_SYMBOL}, + {"add_action", TRUE, COMMAND_ADD_ACTION} +}; + +static GList *custom_commands = NULL; + +static time_t start_time; + +static gchar greetingbuf[1024]; + +static gboolean controller_write_socket (void *arg); + +static void +free_session (void *ud) +{ + GList *part; + struct mime_part *p; + struct controller_session *session = ud; + + msg_debug ("freeing session %p", session); + + while ((part = g_list_first (session->parts))) { + session->parts = g_list_remove_link (session->parts, part); + p = (struct mime_part *)part->data; + g_byte_array_free (p->content, FALSE); + g_list_free_1 (part); + } + rspamd_remove_dispatcher (session->dispatcher); + + if (session->kwargs) { + g_hash_table_destroy (session->kwargs); + } + + close (session->sock); + + rspamd_mempool_delete (session->session_pool); + g_slice_free1 (sizeof (struct controller_session), session); +} + +static gboolean +restful_write_reply (gint error_code, const gchar *err_message, + const gchar *buf, gsize buflen, rspamd_io_dispatcher_t *d) +{ + static gchar hbuf[256]; + gint r; + + r = rspamd_snprintf (hbuf, sizeof (hbuf), + "HTTP/1.0 %d %s" CRLF "Version: " RVERSION CRLF, + error_code, err_message ? err_message : "OK"); + if (buflen > 0) { + r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, "Content-Length: %z" CRLF, buflen); + } + r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, CRLF); + + if (buf != NULL) { + if (!rspamd_dispatcher_write (d, hbuf, r, TRUE, TRUE)) { + return FALSE; + } + return rspamd_dispatcher_write (d, buf, buflen, FALSE, FALSE); + } + else { + if (!rspamd_dispatcher_write (d, hbuf, r, FALSE, TRUE)) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +restful_write_reply_string (gint error_code, const gchar *err_message, + GString *buf, rspamd_io_dispatcher_t *d) +{ + static gchar hbuf[256]; + gint r; + + r = rspamd_snprintf (hbuf, sizeof (hbuf), + "HTTP/1.0 %d %s" CRLF "Version: " RVERSION CRLF, + error_code, err_message ? err_message : "OK"); + if (buf->len > 0) { + r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, "Content-Length: %z" CRLF, buf->len); + } + r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, CRLF); + + if (buf != NULL) { + if (!rspamd_dispatcher_write (d, hbuf, r, TRUE, TRUE)) { + return FALSE; + } + return rspamd_dispatcher_write_string (d, buf, FALSE, TRUE); + } + else { + if (!rspamd_dispatcher_write (d, hbuf, r, FALSE, TRUE)) { + return FALSE; + } + } + + return TRUE; +} + +static gint +check_auth (struct controller_command *cmd, struct controller_session *session) +{ + gchar out_buf[128]; + gint r; + + if (cmd->privilleged && !session->authorized) { + if (!session->restful) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "not authorized" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return 0; + } + } + else { + (void)restful_write_reply (403, "Not authorized", NULL, 0, session->dispatcher); + } + return 0; + } + + return 1; +} + +static gboolean +write_whole_statfile (struct controller_session *session, gchar *symbol, struct rspamd_classifier_config *ccf) +{ + stat_file_t *statfile; + struct rspamd_statfile_config *st; + gchar out_buf[BUFSIZ]; + guint i; + guint64 rev, ti, len, pos, blocks; + gchar *out; + struct rspamd_binlog_element log_elt; + struct stat_file_block *stat_elt; + + statfile = get_statfile_by_symbol (session->worker->srv->statfile_pool, ccf, + symbol, &st, FALSE); + if (statfile == NULL) { + return FALSE; + } + + /* Begin to copy all blocks into array */ + statfile_get_revision (statfile, &rev, (time_t *)&ti); + if (ti == 0) { + /* Not tracked file */ + ti = time (NULL); + statfile_set_revision (statfile, rev, ti); + } + msg_info ("send a whole statfile %s with version %uL to slave", symbol, rev); + + blocks = statfile_get_total_blocks (statfile); + len = blocks * sizeof (struct rspamd_binlog_element); + out = rspamd_mempool_alloc (session->session_pool, len); + + for (i = 0, pos = 0; i < blocks; i ++) { + stat_elt = (struct stat_file_block *)((u_char *)statfile->map + statfile->seek_pos + i * sizeof (struct stat_file_block)); + if (fabs (stat_elt->value) > 0.001) { + /* Write only those values which value is not 0 */ + log_elt.h1 = stat_elt->hash1; + log_elt.h2 = stat_elt->hash2; + log_elt.value = stat_elt->value; + + memcpy (out + pos, &log_elt, sizeof (log_elt)); + pos += sizeof (struct rspamd_binlog_element); + } + } + + i = rspamd_snprintf (out_buf, sizeof (out_buf), "%uL %uL %uL" CRLF, rev, ti, pos); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, i, TRUE, FALSE)) { + return FALSE; + } + + if (!rspamd_dispatcher_write (session->dispatcher, out, pos, TRUE, TRUE)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +process_sync_command (struct controller_session *session, gchar **args) +{ + gchar out_buf[BUFSIZ], *arg, *err_str, *symbol; + gint r; + guint64 rev, time; + struct rspamd_statfile_config *st = NULL; + struct rspamd_classifier_config *ccf; + GList *cur; + struct rspamd_binlog *binlog; + GByteArray *data = NULL; + + arg = *args; + if (!arg || *arg == '\0') { + msg_info ("bad arguments to sync command, need symbol"); + return FALSE; + } + symbol = arg; + arg = *(args + 1); + if (!arg || *arg == '\0') { + msg_info ("bad arguments to sync command, need revision"); + return FALSE; + } + rev = strtoull (arg, &err_str, 10); + if (err_str && *err_str != 0) { + msg_info ("bad arguments to sync command: %s", arg); + return FALSE; + } + arg = *(args + 2); + if (!arg || *arg == '\0') { + msg_info ("bad arguments to sync command, need time"); + return FALSE; + } + time = strtoull (arg, &err_str, 10); + if (err_str && *err_str != 0) { + msg_info ("bad arguments to sync command: %s", arg); + return FALSE; + } + + ccf = g_hash_table_lookup (session->cfg->classifiers_symbols, symbol); + if (ccf == NULL) { + msg_info ("bad symbol: %s", symbol); + return FALSE; + } + + cur = g_list_first (ccf->statfiles); + while (cur) { + st = cur->data; + if (strcmp (symbol, st->symbol) == 0) { + break; + } + st = NULL; + cur = g_list_next (cur); + } + if (st == NULL) { + msg_info ("bad symbol: %s", symbol); + return FALSE; + } + + binlog = get_binlog_by_statfile (st); + if (binlog == NULL) { + msg_info ("cannot open binlog: %s", symbol); + return FALSE; + } + + while (binlog_sync (binlog, rev, &time, &data)) { + rev ++; + r = rspamd_snprintf (out_buf, sizeof (out_buf), "%uL %uL %z" CRLF, rev, time, data->len); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + if (data != NULL) { + g_free (data); + } + return FALSE; + } + if (data->data != NULL) { + if (!rspamd_dispatcher_write (session->dispatcher, data->data, data->len, TRUE, FALSE)) { + if (data != NULL) { + g_free (data); + } + return FALSE; + } + } + } + + if (time == 0) { + if (data != NULL) { + g_free (data); + } + return write_whole_statfile (session, symbol, ccf); + } + + if (data != NULL) { + g_free (data); + } + + return TRUE; +} + +static gboolean +process_counters_command (struct controller_session *session) +{ + GList *cur; + struct cache_item *item; + struct symbols_cache *cache; + GString *out; + + cache = session->cfg->cache; + out = g_string_sized_new (BUFSIZ); + + if (!session->restful) { + rspamd_printf_gstring (out, "Rspamd counters:" CRLF); + } + + if (cache != NULL) { + cur = cache->negative_items; + while (cur) { + item = cur->data; + if (!item->is_callback) { + rspamd_printf_gstring (out, "%s %.2f %d %.3f" CRLF, + item->s->symbol, item->s->weight, + item->s->frequency, item->s->avg_time); + } + cur = g_list_next (cur); + } + cur = cache->static_items; + while (cur) { + item = cur->data; + if (!item->is_callback) { + rspamd_printf_gstring (out, "%s %.2f %d %.3f" CRLF, + item->s->symbol, item->s->weight, + item->s->frequency, item->s->avg_time); + } + cur = g_list_next (cur); + } + } + + if (!session->restful) { + return rspamd_dispatcher_write_string (session->dispatcher, out, FALSE, TRUE); + } + else { + return restful_write_reply_string (200, NULL, out, session->dispatcher); + } +} + +static gboolean +process_stat_command (struct controller_session *session, gboolean do_reset) +{ + GString *out; + gint i; + guint64 used, total, rev, ham = 0, spam = 0; + time_t ti; + rspamd_mempool_stat_t mem_st; + struct rspamd_classifier_config *ccf; + stat_file_t *statfile; + struct rspamd_statfile_config *st; + GList *cur_cl, *cur_st; + struct rspamd_stat *stat, stat_copy; + + rspamd_mempool_stat (&mem_st); + memcpy (&stat_copy, session->worker->srv->stat, sizeof (stat_copy)); + stat = &stat_copy; + out = g_string_sized_new (BUFSIZ); + rspamd_printf_gstring (out, "Messages scanned: %ud" CRLF, stat->messages_scanned); + if (stat->messages_scanned > 0) { + for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i ++) { + rspamd_printf_gstring (out, "Messages with action %s: %ud, %.2f%%" CRLF, + str_action_metric (i), stat->actions_stat[i], + (double)stat->actions_stat[i] / (double)stat->messages_scanned * 100.); + if (i < METRIC_ACTION_GREYLIST) { + spam += stat->actions_stat[i]; + } + else { + ham += stat->actions_stat[i]; + } + if (do_reset) { + session->worker->srv->stat->actions_stat[i] = 0; + } + } + rspamd_printf_gstring (out, "Messages treated as spam: %ud, %.2f%%" CRLF, spam, + (double)spam / (double)stat->messages_scanned * 100.); + rspamd_printf_gstring (out, "Messages treated as ham: %ud, %.2f%%" CRLF, ham, + (double)ham / (double)stat->messages_scanned * 100.); + } + rspamd_printf_gstring (out, "Messages learned: %ud" CRLF, stat->messages_learned); + rspamd_printf_gstring (out, "Connections count: %ud" CRLF, stat->connections_count); + rspamd_printf_gstring (out, "Control connections count: %ud" CRLF, stat->control_connections_count); + rspamd_printf_gstring (out, "Pools allocated: %z" CRLF, mem_st.pools_allocated); + rspamd_printf_gstring (out, "Pools freed: %z" CRLF, mem_st.pools_freed); + rspamd_printf_gstring (out, "Bytes allocated: %z" CRLF, mem_st.bytes_allocated); + rspamd_printf_gstring (out, "Memory chunks allocated: %z" CRLF, mem_st.chunks_allocated); + rspamd_printf_gstring (out, "Shared chunks allocated: %z" CRLF, mem_st.shared_chunks_allocated); + rspamd_printf_gstring (out, "Chunks freed: %z" CRLF, mem_st.chunks_freed); + rspamd_printf_gstring (out, "Oversized chunks: %z" CRLF, mem_st.oversized_chunks); + rspamd_printf_gstring (out, "Fuzzy hashes stored: %ud" CRLF, stat->fuzzy_hashes); + rspamd_printf_gstring (out, "Fuzzy hashes expired: %ud" CRLF, stat->fuzzy_hashes_expired); + /* Now write statistics for each statfile */ + cur_cl = g_list_first (session->cfg->classifiers); + while (cur_cl) { + ccf = cur_cl->data; + cur_st = g_list_first (ccf->statfiles); + while (cur_st) { + st = cur_st->data; + if ((statfile = statfile_pool_is_open (session->worker->srv->statfile_pool, st->path)) == NULL) { + statfile = statfile_pool_open (session->worker->srv->statfile_pool, st->path, st->size, FALSE); + } + if (statfile) { + used = statfile_get_used_blocks (statfile); + total = statfile_get_total_blocks (statfile); + statfile_get_revision (statfile, &rev, &ti); + if (total != (guint64)-1 && used != (guint64)-1) { + if (st->label) { + rspamd_printf_gstring (out, + "Statfile: %s <%s> (version %uL); length: %Hz; free blocks: %uL; total blocks: %uL; free: %.2f%%" CRLF, + st->symbol, st->label, rev, st->size, + (total - used), total, + (double)((double)(total - used) / (double)total) * 100.); + } + else { + rspamd_printf_gstring (out, + "Statfile: %s (version %uL); length: %Hz; free blocks: %uL; total blocks: %uL; free: %.2f%%" CRLF, + st->symbol, rev, st->size, + (total - used), total, + (double)((double)(total - used) / (double)total) * 100.); + } + } + } + cur_st = g_list_next (cur_st); + } + cur_cl = g_list_next (cur_cl); + } + + if (do_reset) { + session->worker->srv->stat->messages_scanned = 0; + session->worker->srv->stat->messages_learned = 0; + session->worker->srv->stat->connections_count = 0; + session->worker->srv->stat->control_connections_count = 0; + } + + if (!session->restful) { + return rspamd_dispatcher_write_string (session->dispatcher, out, FALSE, TRUE); + } + else { + return restful_write_reply_string (200, NULL, out, session->dispatcher); + } +} + +static gboolean +process_dynamic_conf_command (gchar **cmd_args, struct controller_session *session, gboolean is_action) +{ + struct rspamd_config *cfg = session->cfg; + gchar *arg, *metric, *name, *err_str; + gdouble value; + gboolean res; + guint real_act; + + if (cfg->dynamic_conf == NULL) { + if (!session->restful) { + return rspamd_dispatcher_write (session->dispatcher, "dynamic config is not specified" CRLF, 0, FALSE, TRUE); + } + else { + return restful_write_reply (500, "dynamic config is not specified", NULL, 0, session->dispatcher); + } + } + + if (session->restful) { + if ((arg = g_hash_table_lookup (session->kwargs, "metric")) == NULL) { + metric = DEFAULT_METRIC; + } + else { + metric = arg; + } + if ((arg = g_hash_table_lookup (session->kwargs, "name")) == NULL) { + goto invalid_arguments; + } + name = arg; + if ((arg = g_hash_table_lookup (session->kwargs, "value")) == NULL) { + goto invalid_arguments; + } + value = strtod (arg, &err_str); + if (err_str && *err_str != '\0') { + msg_info ("double value is invalid: %s", arg); + goto invalid_arguments; + } + } + else { + if (cmd_args[0] == NULL || cmd_args[1] == NULL) { + goto invalid_arguments; + } + if (cmd_args[2] == NULL) { + metric = DEFAULT_METRIC; + name = cmd_args[0]; + arg = cmd_args[1]; + value = strtod (arg, &err_str); + if (err_str && *err_str != '\0') { + msg_info ("double value is invalid: %s", arg); + goto invalid_arguments; + } + } + else { + metric = cmd_args[0]; + name = cmd_args[1]; + arg = cmd_args[2]; + value = strtod (arg, &err_str); + if (err_str && *err_str != '\0') { + msg_info ("double value is invalid: %s", arg); + goto invalid_arguments; + } + } + } + + if (is_action) { + if (!check_action_str (name, &real_act)) { + msg_info ("invalid action string: %s", name); + res = FALSE; + } + else { + res = add_dynamic_action (cfg, metric, real_act, value); + } + } + else { + res = add_dynamic_symbol (cfg, metric, name, value); + } + + if (res) { + + res = dump_dynamic_config (cfg); + if (res) { + if (!session->restful) { + return rspamd_dispatcher_write (session->dispatcher, "OK" CRLF, 0, FALSE, TRUE); + } + else { + return restful_write_reply (200, "OK", NULL, 0, session->dispatcher); + } + } + else { + if (!session->restful) { + return rspamd_dispatcher_write (session->dispatcher, "Error dumping dynamic config" CRLF, 0, FALSE, TRUE); + } + else { + return restful_write_reply (500, "Error dumping dynamic config", NULL, 0, session->dispatcher); + } + } + } + else { + if (!session->restful) { + return rspamd_dispatcher_write (session->dispatcher, "Cannot add dynamic rule" CRLF, 0, FALSE, TRUE); + } + else { + return restful_write_reply (500, "Cannot add dynamic rule", NULL, 0, session->dispatcher); + } + } + +invalid_arguments: + if (!session->restful) { + return rspamd_dispatcher_write (session->dispatcher, "Invalid arguments" CRLF, 0, FALSE, TRUE); + } + else { + return restful_write_reply (500, "Invalid arguments", NULL, 0, session->dispatcher); + } +} + +static gboolean +process_command (struct controller_command *cmd, gchar **cmd_args, struct controller_session *session) +{ + gchar out_buf[BUFSIZ], *arg, *err_str; + gint r = 0, days, hours, minutes; + time_t uptime; + guint32 size = 0; + struct rspamd_classifier_config *cl; + struct rspamd_controller_ctx *ctx = session->worker->ctx; + + switch (cmd->type) { + case COMMAND_GET: + case COMMAND_POST: + session->restful = TRUE; + session->state = STATE_HEADER; + session->kwargs = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal); + break; + case COMMAND_PASSWORD: + arg = *cmd_args; + if (!arg || *arg == '\0') { + msg_debug ("empty password passed"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "password command requires one argument" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + if (ctx->password == NULL) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "password command disabled in config, authorized access granted" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + if (strncmp (arg, ctx->password, strlen (arg)) == 0) { + session->authorized = 1; + r = rspamd_snprintf (out_buf, sizeof (out_buf), "password accepted" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + session->authorized = 0; + r = rspamd_snprintf (out_buf, sizeof (out_buf), "password NOT accepted" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + break; + case COMMAND_QUIT: + session->state = STATE_QUIT; + break; + case COMMAND_RELOAD: + if (check_auth (cmd, session)) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "reload request sent" CRLF); + if (!session->restful) { + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (! restful_write_reply (200, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + } + kill (getppid (), SIGHUP); + } + break; + case COMMAND_STAT: + if (check_auth (cmd, session)) { + return process_stat_command (session, FALSE); + } + break; + case COMMAND_STAT_RESET: + if (check_auth (cmd, session)) { + return process_stat_command (session, TRUE); + } + break; + case COMMAND_SHUTDOWN: + if (check_auth (cmd, session)) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "shutdown request sent" CRLF); + if (!session->restful) { + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (! restful_write_reply (200, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + } + kill (getppid (), SIGTERM); + } + break; + case COMMAND_UPTIME: + if (check_auth (cmd, session)) { + uptime = time (NULL) - start_time; + /* If uptime more than 2 hours, print as a number of days. */ + if (uptime >= 2 * 3600) { + days = uptime / 86400; + hours = uptime / 3600 - days * 24; + minutes = uptime / 60 - hours * 60 - days * 1440; + r = rspamd_snprintf (out_buf, sizeof (out_buf), "%d day%s %d hour%s %d minute%s" CRLF, days, days > 1 ? "s" : " ", hours, hours > 1 ? "s" : " ", minutes, minutes > 1 ? "s" : " "); + } + /* If uptime is less than 1 minute print only seconds */ + else if (uptime / 60 == 0) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "%d second%s" CRLF, (gint)uptime, (gint)uptime > 1 ? "s" : " "); + } + /* Else print the minutes and seconds. */ + else { + hours = uptime / 3600; + minutes = uptime / 60 - hours * 60; + uptime -= hours * 3600 + minutes * 60; + r = rspamd_snprintf (out_buf, sizeof (out_buf), "%d hour%s %d minute%s %d second%s" CRLF, hours, hours > 1 ? "s" : " ", minutes, minutes > 1 ? "s" : " ", (gint)uptime, uptime > 1 ? "s" : " "); + } + if (!session->restful) { + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (! restful_write_reply (200, NULL, out_buf, r, session->dispatcher)) { + return FALSE; + } + } + } + break; + case COMMAND_LEARN_SPAM: + if (check_auth (cmd, session)) { + if (!session->restful) { + arg = *cmd_args; + if (!arg || *arg == '\0') { + msg_debug ("no statfile specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + arg = *(cmd_args + 1); + if (arg == NULL || *arg == '\0') { + msg_debug ("no message size specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + size = strtoul (arg, &err_str, 10); + if (err_str && *err_str != '\0') { + msg_debug ("message size is invalid: %s", arg); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + cl = rspamd_config_find_classifier (session->cfg, *cmd_args); + } + else { + if ((arg = g_hash_table_lookup (session->kwargs, "classifier")) == NULL) { + msg_debug ("no classifier specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + return TRUE; + } + else { + cl = rspamd_config_find_classifier (session->cfg, arg); + } + if ((arg = g_hash_table_lookup (session->kwargs, "content-length")) == NULL) { + msg_debug ("no size specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + } + else { + size = strtoul (arg, &err_str, 10); + if (err_str && *err_str != '\0') { + msg_debug ("message size is invalid: %s", arg); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + return TRUE; + } + } + } + + session->learn_classifier = cl; + + /* By default learn positive */ + session->in_class = TRUE; + rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_CHARACTER, size); + session->state = STATE_LEARN_SPAM_PRE; + } + break; + case COMMAND_LEARN_HAM: + if (check_auth (cmd, session)) { + if (!session->restful) { + arg = *cmd_args; + if (!arg || *arg == '\0') { + msg_debug ("no statfile specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + arg = *(cmd_args + 1); + if (arg == NULL || *arg == '\0') { + msg_debug ("no message size specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + size = strtoul (arg, &err_str, 10); + if (err_str && *err_str != '\0') { + msg_debug ("message size is invalid: %s", arg); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + cl = rspamd_config_find_classifier (session->cfg, *cmd_args); + } + else { + if ((arg = g_hash_table_lookup (session->kwargs, "classifier")) == NULL) { + msg_debug ("no classifier specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + return TRUE; + } + else { + cl = rspamd_config_find_classifier (session->cfg, arg); + } + if ((arg = g_hash_table_lookup (session->kwargs, "content-length")) == NULL) { + msg_debug ("no size specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF); + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + } + else { + size = strtoul (arg, &err_str, 10); + if (err_str && *err_str != '\0') { + msg_debug ("message size is invalid: %s", arg); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + return TRUE; + } + } + } + + session->learn_classifier = cl; + + /* By default learn positive */ + session->in_class = FALSE; + rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_CHARACTER, size); + session->state = STATE_LEARN_SPAM_PRE; + } + break; + case COMMAND_LEARN: + if (check_auth (cmd, session)) { + /* TODO: remove this command as currenly it should not be used anywhere */ + arg = *cmd_args; + if (!arg || *arg == '\0') { + msg_debug ("no statfile specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn command requires at least two arguments: stat filename and its size" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + arg = *(cmd_args + 1); + if (arg == NULL || *arg == '\0') { + msg_debug ("no message size specified in learn command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn command requires at least two arguments: symbol and message size" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + size = strtoul (arg, &err_str, 10); + if (err_str && *err_str != '\0') { + msg_debug ("message size is invalid: %s", arg); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + + session->learn_symbol = rspamd_mempool_strdup (session->session_pool, *cmd_args); + cl = g_hash_table_lookup (session->cfg->classifiers_symbols, *cmd_args); + + session->learn_classifier = cl; + + /* By default learn positive */ + session->in_class = 1; + session->learn_multiplier = 1.; + /* Get all arguments */ + while (*cmd_args++) { + arg = *cmd_args; + if (arg && *arg == '-') { + switch (*(arg + 1)) { + case 'r': + arg = *(cmd_args + 1); + if (!arg || *arg == '\0') { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "recipient is not defined" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + session->learn_rcpt = rspamd_mempool_strdup (session->session_pool, arg); + break; + case 'f': + arg = *(cmd_args + 1); + if (!arg || *arg == '\0') { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "from is not defined" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + session->learn_from = rspamd_mempool_strdup (session->session_pool, arg); + break; + case 'n': + session->in_class = 0; + break; + case 'm': + arg = *(cmd_args + 1); + if (!arg || *arg == '\0') { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "multiplier is not defined" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + session->learn_multiplier = strtod (arg, NULL); + } + break; + default: + r = rspamd_snprintf (out_buf, sizeof (out_buf), "tokenizer is not defined" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + } + } + rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_CHARACTER, size); + session->state = STATE_LEARN; + } + break; + + case COMMAND_WEIGHTS: + /* TODO: remove this command as currenly it should not be used anywhere */ + arg = *cmd_args; + if (!arg || *arg == '\0') { + msg_debug ("no statfile specified in weights command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "weights command requires two arguments: statfile and message size" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + arg = *(cmd_args + 1); + if (arg == NULL || *arg == '\0') { + msg_debug ("no message size specified in weights command"); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "weights command requires two arguments: statfile and message size" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + size = strtoul (arg, &err_str, 10); + if (err_str && *err_str != '\0') { + msg_debug ("message size is invalid: %s", arg); + r = rspamd_snprintf (out_buf, sizeof (out_buf), "message size is invalid" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + + cl = g_hash_table_lookup (session->cfg->classifiers_symbols, *cmd_args); + if (cl == NULL) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "statfile %s is not defined" CRLF, *cmd_args); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + + } + session->learn_classifier = cl; + + rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_CHARACTER, size); + session->state = STATE_WEIGHTS; + break; + case COMMAND_SYNC: + if (!process_sync_command (session, cmd_args)) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "FAIL" CRLF); + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return TRUE; + } + break; + case COMMAND_HELP: + r = rspamd_snprintf (out_buf, sizeof (out_buf), + "Rspamd CLI commands (* - privileged command):" CRLF + " help - this help message" CRLF + "(*) learn [-r recipient] [-m multiplier] [-f from] [-n] - learn message to specified statfile" CRLF + " quit - quit CLI session" CRLF + " password - authenticate yourself for privileged commands" CRLF + "(*) reload - reload rspamd" CRLF + "(*) shutdown - shutdown rspamd" CRLF + " stat - show different rspamd stat" CRLF + " sync - run synchronization of statfiles" CRLF + " counters - show rspamd counters" CRLF + " uptime - rspamd uptime" CRLF + " weights - weight of message in all statfiles" CRLF); + if (!session->restful) { + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (! restful_write_reply (200, NULL, out_buf, r, session->dispatcher)) { + return FALSE; + } + } + break; + case COMMAND_COUNTERS: + process_counters_command (session); + break; + case COMMAND_ADD_ACTION: + if (check_auth (cmd, session)) { + return process_dynamic_conf_command (cmd_args, session, TRUE); + } + break; + case COMMAND_ADD_SYMBOL: + if (check_auth (cmd, session)) { + return process_dynamic_conf_command (cmd_args, session, FALSE); + } + break; + } + return TRUE; +} + +static controller_func_t +parse_custom_command (gchar *line, gchar **cmd_args, struct controller_session *session, gsize len) +{ + GList *cur; + struct custom_controller_command *cmd; + + if (len == 0) { + len = strlen (line); + } + cur = custom_commands; + while (cur) { + cmd = cur->data; + if (g_ascii_strncasecmp (cmd->command, line, len) == 0) { + return cmd->handler; + } + cur = g_list_next (cur); + } + + return NULL; +} + +static struct controller_command * +parse_normal_command (const gchar *line, gsize len) +{ + guint i; + struct controller_command *c; + + if (len == 0) { + len = strlen (line); + } + for (i = 0; i < G_N_ELEMENTS (commands); i ++) { + c = &commands[i]; + if (g_ascii_strncasecmp (line, c->command, len) == 0) { + return c; + } + } + + return NULL; +} + +static gboolean +process_header (f_str_t *line, struct controller_session *session) +{ + gchar *headern; + struct controller_command *command; + struct rspamd_controller_ctx *ctx = session->worker->ctx; + controller_func_t custom_handler; + + /* XXX: temporary workaround */ + headern = NULL; + + if (line == NULL || headern == NULL) { + msg_warn ("bad header: %V", line); + return FALSE; + } + /* Eat whitespaces */ + g_strstrip (headern); + fstrstrip (line); + + if (*headern == 'c' || *headern == 'C') { + if (g_ascii_strcasecmp (headern, "command") == 0) { + /* This header is actually command */ + command = parse_normal_command (line->begin, line->len); + if (command == NULL) { + if ((custom_handler = parse_custom_command (line->begin, NULL, session, line->len)) == NULL) { + msg_info ("bad command header: %V", line); + return FALSE; + } + else { + session->custom_handler = custom_handler; + } + } + session->cmd = command; + return TRUE; + } + } + else if (*headern == 'p' || *headern == 'P') { + /* Password header */ + if (g_ascii_strcasecmp (headern, "password") == 0) { + if (ctx->password == NULL || + (line->len == strlen (ctx->password) + && memcmp (line->begin, ctx->password, line->len) == 0)) { + session->authorized = TRUE; + } + else { + msg_info ("wrong password in controller command"); + } + return TRUE; + } + } + + g_hash_table_insert (session->kwargs, headern, fstrcstr (line, session->session_pool)); + + return TRUE; +} + +/* + * Called if all filters are processed, non-threaded and simple version + */ +static gboolean +fin_learn_task (void *arg) +{ + struct rspamd_task *task = (struct rspamd_task *) arg; + + /* XXX: needs to be reworked */ + + return TRUE; +} + +/* + * Called if session was restored inside fin callback + */ +static void +restore_learn_task (void *arg) +{ + struct rspamd_task *task = (struct rspamd_task *) arg; + + /* Special state */ +} + +static gboolean +controller_read_socket (f_str_t * in, void *arg) +{ + struct controller_session *session = (struct controller_session *)arg; + struct classifier_ctx *cls_ctx; + gint len, i, r; + gchar *s, **params, *cmd, out_buf[128]; + struct controller_command *command; + struct rspamd_task *task; + struct mime_text_part *part; + GList *cur = NULL; + GTree *tokens = NULL; + GError *err = NULL; + f_str_t c; + controller_func_t custom_handler; + + switch (session->state) { + case STATE_COMMAND: + s = fstrcstr (in, session->session_pool); + params = g_strsplit_set (s, " ", -1); + + rspamd_mempool_add_destructor (session->session_pool, (rspamd_mempool_destruct_t) g_strfreev, params); + + len = g_strv_length (params); + if (len > 0) { + cmd = g_strstrip (params[0]); + + command = parse_normal_command (cmd, 0); + if (command != NULL) { + if (! process_command (command, ¶ms[1], session)) { + return FALSE; + } + } + else { + if ((custom_handler = parse_custom_command (cmd, ¶ms[1], session, 0)) == NULL) { + msg_debug ("'%s'", cmd); + i = rspamd_snprintf (out_buf, sizeof (out_buf), "Unknown command" CRLF); + if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { + return FALSE; + } + } + else { + custom_handler (¶ms[1], session); + } + } + } + if (session->state != STATE_LEARN && session->state != STATE_LEARN_SPAM_PRE + && session->state != STATE_WEIGHTS && session->state != STATE_OTHER && session->state != STATE_HEADER) { + if (!rspamd_dispatcher_write (session->dispatcher, END, sizeof (END) - 1, FALSE, TRUE)) { + return FALSE; + } + if (session->state == STATE_QUIT) { + destroy_session (session->s); + return FALSE; + } + } + + break; + case STATE_HEADER: + if (in->len == 0) { + /* End of headers */ + if (session->cmd == NULL && session->custom_handler == NULL) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Bad command" CRLF CRLF); + if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { + return FALSE; + } + destroy_session (session->s); + return FALSE; + } + /* Perform command */ + else if (session->cmd != NULL) { + if (! process_command (session->cmd, NULL, session)) { + msg_debug ("process command failed"); + return FALSE; + } + } + else { + session->custom_handler (NULL, session); + } + if (session->state != STATE_LEARN && session->state != STATE_LEARN_SPAM_PRE + && session->state != STATE_WEIGHTS && session->state != STATE_OTHER) { + msg_debug ("closing restful connection"); + destroy_session (session->s); + return FALSE; + } + } + else if (!process_header (in, session)) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Bad header" CRLF CRLF); + if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { + return FALSE; + } + destroy_session (session->s); + return FALSE; + } + break; + case STATE_LEARN: + session->learn_buf = in; + task = rspamd_task_new (session->worker); + + task->msg = g_string_new_len (in->begin, in->len); + task->ev_base = session->ev_base; + + r = process_message (task); + if (r == -1) { + msg_warn ("processing of message failed"); + rspamd_task_free (task, FALSE); + session->state = STATE_REPLY; + if (session->restful) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot process message" CRLF CRLF); + } + else { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF); + } + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + return FALSE; + } + + if (!learn_task (session->learn_symbol, task, &err)) { + rspamd_task_free (task, FALSE); + if (err) { + if (session->restful) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: %s" CRLF CRLF, err->message); + } + else { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: %s" CRLF END, err->message); + } + g_error_free (err); + } + else { + if (session->restful) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: unknown" CRLF CRLF); + } + else { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, unknown learn classifier error" CRLF END); + } + } + + if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { + return FALSE; + } + session->state = STATE_REPLY; + return TRUE; + } + + rspamd_task_free (task, FALSE); + if (session->restful) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 200 Learn OK" CRLF CRLF); + } + else { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn ok" CRLF END); + } + session->state = STATE_REPLY; + if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { + return FALSE; + } + break; + case STATE_LEARN_SPAM_PRE: + session->learn_buf = in; + task = rspamd_task_new (session->worker); + + task->msg = g_string_new_len (in->begin, in->len); + + task->resolver = session->resolver; + task->ev_base = session->ev_base; + + r = process_message (task); + if (r == -1) { + msg_warn ("processing of message failed"); + session->state = STATE_REPLY; + if (session->restful) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot process message" CRLF CRLF); + } + else { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF); + } + if (!session->restful) { + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + } + return FALSE; + } + /* Set up async session */ + task->s = new_async_session (task->task_pool, fin_learn_task, restore_learn_task, rspamd_task_free_hard, task); + session->learn_task = task; + session->state = STATE_LEARN_SPAM; + rspamd_dispatcher_pause (session->dispatcher); + r = process_filters (task); + if (r == -1) { + rspamd_dispatcher_restore (session->dispatcher); + session->state = STATE_REPLY; + if (session->restful) { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot process message" CRLF CRLF); + } + else { + r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF); + } + destroy_session (task->s); + if (!session->restful) { + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + } + } + break; + case STATE_WEIGHTS: + session->learn_buf = in; + task = rspamd_task_new (session->worker); + + task->msg = g_string_new_len (in->begin, in->len); + task->ev_base = session->ev_base; + + r = process_message (task); + if (r == -1) { + msg_warn ("processing of message failed"); + rspamd_task_free (task, FALSE); + session->state = STATE_REPLY; + r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF END); + if (!session->restful) { + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + } + return FALSE; + } + + cur = g_list_first (task->text_parts); + while (cur) { + part = cur->data; + if (part->is_empty) { + cur = g_list_next (cur); + continue; + } + + c.begin = part->content->data; + c.len = part->content->len; + if (!session->learn_classifier->tokenizer->tokenize_func (session->learn_classifier->tokenizer, + session->session_pool, &c, &tokens, FALSE, part->is_utf, part->urls_offset)) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "weights failed, tokenizer error" CRLF END); + rspamd_task_free (task, FALSE); + if (!session->restful) { + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (! restful_write_reply (500, out_buf, NULL, 0, session->dispatcher)) { + return FALSE; + } + } + session->state = STATE_REPLY; + return TRUE; + } + cur = g_list_next (cur); + } + + /* Handle messages without text */ + if (tokens == NULL) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "weights failed, no tokens can be extracted (no text data)" CRLF END); + rspamd_task_free (task, FALSE); + if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { + return FALSE; + } + session->state = STATE_REPLY; + return TRUE; + } + + + /* Init classifier */ + cls_ctx = session->learn_classifier->classifier->init_func (session->session_pool, session->learn_classifier); + + cur = session->learn_classifier->classifier->weights_func (cls_ctx, session->worker->srv->statfile_pool, + tokens, task); + i = 0; + struct classify_weight *w; + + while (cur) { + w = cur->data; + i += rspamd_snprintf (out_buf + i, sizeof (out_buf) - i, "%s: %G" CRLF, w->name, w->weight); + cur = g_list_next (cur); + } + i += rspamd_snprintf (out_buf + i, sizeof (out_buf) - i, END); + if (i != 0) { + if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { + return FALSE; + } + } + else { + if (!rspamd_dispatcher_write (session->dispatcher, "weights failed: classifier error" CRLF END, 0, FALSE, TRUE)) { + return FALSE; + } + } + + rspamd_task_free (task, FALSE); + + session->state = STATE_REPLY; + break; + case STATE_OTHER: + rspamd_dispatcher_pause (session->dispatcher); + if (session->other_handler) { + if (!session->other_handler (session, in)) { + return FALSE; + } + } + break; + case STATE_WAIT: + rspamd_dispatcher_pause (session->dispatcher); + break; + default: + msg_debug ("unknown state while reading %d", session->state); + break; + } + + if (session->state == STATE_REPLY || session->state == STATE_QUIT) { + /* In case of normal session we restore read state, for restful session we need to terminate immediately */ + if (!session->restful) { + rspamd_dispatcher_restore (session->dispatcher); + } + else { + return FALSE; + } + } + return TRUE; +} + +static gboolean +controller_write_socket (void *arg) +{ + struct controller_session *session = (struct controller_session *)arg; + gint i; + gchar out_buf[1024]; + GError *err = NULL; + + if (session->state == STATE_QUIT) { + /* Free buffers */ + destroy_session (session->s); + return FALSE; + } + else if (session->state == STATE_LEARN_SPAM) { + /* Perform actual learn here */ + if (session->learn_classifier == NULL) { + if (session->restful) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: %s" CRLF CRLF, "unknown classifier"); + } + else { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: %s" CRLF END, "unknown classifier"); + } + } + else { + if (! learn_task_spam (session->learn_classifier, session->learn_task, session->in_class, &err)) { + if (err) { + if (session->restful) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: %s" CRLF CRLF, err->message); + } + else { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: %s" CRLF END, err->message); + } + g_error_free (err); + } + else { + if (session->restful) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn classifier error: unknown" CRLF CRLF); + } + else { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: unknown" CRLF END); + } + } + } + else { + if (session->restful) { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 200 Learn OK" CRLF CRLF); + } + else { + i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn ok" CRLF END); + } + } + } + destroy_session (session->learn_task->s); + session->state = STATE_REPLY; + if (! rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) { + return FALSE; + } + if (session->restful) { + destroy_session (session->s); + return FALSE; + } + return TRUE; + } + else if (session->state == STATE_REPLY) { + if (session->restful) { + destroy_session (session->s); + return FALSE; + } + else { + session->state = STATE_COMMAND; + rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_LINE, BUFSIZ); + } + } + rspamd_dispatcher_restore (session->dispatcher); + return TRUE; +} + +static void +controller_err_socket (GError * err, void *arg) +{ + struct controller_session *session = (struct controller_session *)arg; + + if (err->code != EOF) { + msg_info ("abnormally closing control connection, error: %s", err->message); + } + g_error_free (err); + /* Free buffers */ + destroy_session (session->s); +} + +static void +accept_socket (gint fd, short what, void *arg) +{ + struct rspamd_worker *worker = (struct rspamd_worker *)arg; + union sa_union su; + struct controller_session *new_session; + struct timeval *io_tv; + gint nfd; + struct rspamd_controller_ctx *ctx; + rspamd_inet_addr_t addr; + + ctx = worker->ctx; + + if ((nfd = rspamd_accept_from_socket (fd, &addr)) == -1) { + return; + } + + new_session = g_slice_alloc0 (sizeof (struct controller_session)); + if (new_session == NULL) { + msg_err ("cannot allocate memory for task, %s", strerror (errno)); + return; + } + + new_session->worker = worker; + new_session->sock = nfd; + new_session->cfg = worker->srv->cfg; + new_session->state = STATE_COMMAND; + new_session->session_pool = rspamd_mempool_new (rspamd_mempool_suggest_size () - 1); + new_session->resolver = ctx->resolver; + new_session->ev_base = ctx->ev_base; + if (ctx->password == NULL) { + new_session->authorized = TRUE; + } + worker->srv->stat->control_connections_count++; + + /* Set up dispatcher */ + io_tv = rspamd_mempool_alloc (new_session->session_pool, sizeof (struct timeval)); + io_tv->tv_sec = ctx->timeout / 1000; + io_tv->tv_usec = ctx->timeout - io_tv->tv_sec * 1000; + + new_session->s = new_async_session (new_session->session_pool, NULL, NULL, free_session, new_session); + + new_session->dispatcher = rspamd_create_dispatcher (ctx->ev_base, nfd, BUFFER_LINE, controller_read_socket, + controller_write_socket, controller_err_socket, io_tv, (void *)new_session); + + if (su.ss.ss_family == AF_UNIX) { + msg_info ("accepted connection from unix socket"); + new_session->dispatcher->peer_addr = INADDR_LOOPBACK; + } + else if (su.ss.ss_family == AF_INET) { + msg_info ("accepted connection from %s port %d", + inet_ntoa (su.s4.sin_addr), ntohs (su.s4.sin_port)); + memcpy (&new_session->dispatcher->peer_addr, &su.s4.sin_addr, + sizeof (guint32)); + } +#if 0 + /* As we support now restful HTTP like connections, this should not be printed anymore */ + if (! rspamd_dispatcher_write (new_session->dispatcher, greetingbuf, strlen (greetingbuf), FALSE, FALSE)) { + msg_warn ("cannot write greeting"); + } +#endif +} + +static gboolean +create_rrd_file (const gchar *filename, struct rspamd_controller_ctx *ctx) +{ + GError *err = NULL; + GArray ar; + struct rrd_rra_def rra[5]; + struct rrd_ds_def ds[4]; + + /* + * DS: + * 1) reject as spam + * 2) mark as spam + * 3) greylist + * 4) pass + */ + /* + * RRA: + * 1) per minute AVERAGE + * 2) per 5 minutes AVERAGE + * 3) per 30 minutes AVERAGE + * 4) per 2 hours AVERAGE + * 5) per day AVERAGE + */ + ctx->rrd_file = rspamd_rrd_create (filename, 4, 5, CONTROLLER_RRD_STEP, &err); + if (ctx->rrd_file == NULL) { + msg_err ("cannot create rrd file %s, error: %s", filename, err->message); + g_error_free (err); + return FALSE; + } + + /* Add all ds and rra */ + rrd_make_default_ds ("spam", CONTROLLER_RRD_STEP, &ds[0]); + rrd_make_default_ds ("possible spam", CONTROLLER_RRD_STEP, &ds[1]); + rrd_make_default_ds ("greylist", CONTROLLER_RRD_STEP, &ds[2]); + rrd_make_default_ds ("ham", CONTROLLER_RRD_STEP, &ds[3]); + + rrd_make_default_rra ("AVERAGE", 1, 600, &rra[0]); + rrd_make_default_rra ("AVERAGE", 5, 600, &rra[1]); + rrd_make_default_rra ("AVERAGE", 30, 700, &rra[2]); + rrd_make_default_rra ("AVERAGE", 120, 775, &rra[3]); + rrd_make_default_rra ("AVERAGE", 1440, 797, &rra[4]); + + ar.data = (gchar *)ds; + ar.len = sizeof (ds); + if (!rspamd_rrd_add_ds (ctx->rrd_file, &ar, &err)) { + msg_err ("cannot create rrd file %s, error: %s", filename, err->message); + g_error_free (err); + rspamd_rrd_close (ctx->rrd_file); + return FALSE; + } + + ar.data = (gchar *)rra; + ar.len = sizeof (rra); + if (!rspamd_rrd_add_rra (ctx->rrd_file, &ar, &err)) { + msg_err ("cannot create rrd file %s, error: %s", filename, err->message); + g_error_free (err); + rspamd_rrd_close (ctx->rrd_file); + return FALSE; + } + + /* Finalize */ + if (!rspamd_rrd_finalize (ctx->rrd_file, &err)) { + msg_err ("cannot create rrd file %s, error: %s", filename, err->message); + g_error_free (err); + rspamd_rrd_close (ctx->rrd_file); + return FALSE; + } + + return TRUE; +} + +static void +controller_update_rrd (gint fd, short what, void *arg) +{ + struct rspamd_controller_ctx *ctx = arg; + struct timeval tv; + GArray ar; + gdouble data[4]; + GError *err = NULL; + + /* + * Data: + * 1) reject as spam + * 2) mark as spam + * 3) greylist + * 4) pass + */ + + tv.tv_sec = CONTROLLER_RRD_STEP; + tv.tv_usec = 0; + + /* Fill data */ + data[0] = ctx->srv->stat->actions_stat[METRIC_ACTION_REJECT]; + data[1] = ctx->srv->stat->actions_stat[METRIC_ACTION_ADD_HEADER] + ctx->srv->stat->actions_stat[METRIC_ACTION_REWRITE_SUBJECT]; + data[2] = ctx->srv->stat->actions_stat[METRIC_ACTION_GREYLIST]; + data[3] = ctx->srv->stat->actions_stat[METRIC_ACTION_NOACTION]; + + ar.data = (gchar *)data; + ar.len = sizeof (data); + if (!rspamd_rrd_add_record (ctx->rrd_file, &ar, &err)) { + msg_err ("cannot add record to rrd database: %s, stop rrd update", err->message); + g_error_free (err); + } + else { + evtimer_add (&ctx->rrd_event, &tv); + } +} + +gpointer +init_controller (struct rspamd_config *cfg) +{ + struct rspamd_controller_ctx *ctx; + GQuark type; + + type = g_quark_try_string ("controller"); + ctx = g_malloc0 (sizeof (struct rspamd_controller_ctx)); + + ctx->timeout = CONTROLLER_IO_TIMEOUT * 1000; + + rspamd_rcl_register_worker_option (cfg, type, "password", + rspamd_rcl_parse_struct_string, ctx, + G_STRUCT_OFFSET (struct rspamd_controller_ctx, password), 0); + + rspamd_rcl_register_worker_option (cfg, type, "timeout", + rspamd_rcl_parse_struct_time, ctx, + G_STRUCT_OFFSET (struct rspamd_controller_ctx, timeout), RSPAMD_CL_FLAG_TIME_UINT_32); + + return ctx; +} + +void +start_controller (struct rspamd_worker *worker) +{ + gchar *hostbuf; + gsize hostmax; + struct rspamd_controller_ctx *ctx = worker->ctx; + GError *err = NULL; + struct timeval tv; + + ctx->ev_base = rspamd_prepare_worker (worker, "controller", accept_socket); + g_mime_init (0); + + start_time = time (NULL); + + /* Start statfile synchronization */ + if (!start_statfile_sync (worker->srv->statfile_pool, worker->srv->cfg, ctx->ev_base)) { + msg_info ("cannot start statfile synchronization, statfiles would not be synchronized"); + } + + /* Check for rrd */ + tv.tv_sec = CONTROLLER_RRD_STEP; + tv.tv_usec = 0; + ctx->srv = worker->srv; + if (worker->srv->cfg->rrd_file) { + ctx->rrd_file = rspamd_rrd_open (worker->srv->cfg->rrd_file, &err); + if (ctx->rrd_file == NULL) { + msg_info ("cannot open rrd file: %s, error: %s, trying to create", worker->srv->cfg->rrd_file, err->message); + g_error_free (err); + /* Try to create rrd file */ + if (create_rrd_file (worker->srv->cfg->rrd_file, ctx)) { + evtimer_set (&ctx->rrd_event, controller_update_rrd, ctx); + event_base_set (ctx->ev_base, &ctx->rrd_event); + evtimer_add (&ctx->rrd_event, &tv); + } + } + else { + evtimer_set (&ctx->rrd_event, controller_update_rrd, ctx); + event_base_set (ctx->ev_base, &ctx->rrd_event); + evtimer_add (&ctx->rrd_event, &tv); + } + } + + /* Fill hostname buf */ + hostmax = sysconf (_SC_HOST_NAME_MAX) + 1; + hostbuf = alloca (hostmax); + gethostname (hostbuf, hostmax); + hostbuf[hostmax - 1] = '\0'; + rspamd_snprintf (greetingbuf, sizeof (greetingbuf), "Rspamd version %s is running on %s" CRLF, RVERSION, hostbuf); + + start_map_watch (worker->srv->cfg, ctx->ev_base); + ctx->resolver = dns_resolver_init (worker->srv->logger, ctx->ev_base, worker->srv->cfg); + + event_base_loop (ctx->ev_base, 0); + + close_log (worker->srv->logger); + if (ctx->rrd_file) { + rspamd_rrd_close (ctx->rrd_file); + } + + exit (EXIT_SUCCESS); +} + +void +register_custom_controller_command (const gchar *name, controller_func_t handler, gboolean privilleged, gboolean require_message) +{ + struct custom_controller_command *cmd; + + cmd = g_malloc (sizeof (struct custom_controller_command)); + cmd->command = name; + cmd->handler = handler; + cmd->privilleged = privilleged; + cmd->require_message = require_message; + + custom_commands = g_list_prepend (custom_commands, cmd); +} + +/* + * vi:ts=4 + */ diff --git a/src/webui.c b/src/webui.c deleted file mode 100644 index aa009f9e56..0000000000 --- a/src/webui.c +++ /dev/null @@ -1,1679 +0,0 @@ -/* Copyright (c) 2010-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 ''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 "util.h" -#include "main.h" -#include "http.h" -#include "message.h" -#include "protocol.h" -#include "upstream.h" -#include "cfg_file.h" -#include "map.h" -#include "dns.h" -#include "tokenizers/tokenizers.h" -#include "classifiers/classifiers.h" -#include "dynamic_cfg.h" -#include "rrd.h" - -#ifdef WITH_GPERF_TOOLS -# include -#endif - -/* 60 seconds for worker's IO */ -#define DEFAULT_WORKER_IO_TIMEOUT 60000 - -/* HTTP paths */ -#define PATH_AUTH "/auth" -#define PATH_SYMBOLS "/symbols" -#define PATH_ACTIONS "/actions" -#define PATH_MAPS "/maps" -#define PATH_GET_MAP "/getmap" -#define PATH_GRAPH "/graph" -#define PATH_PIE_CHART "/pie" -#define PATH_HISTORY "/history" -#define PATH_LEARN_SPAM "/learnspam" -#define PATH_LEARN_HAM "/learnham" -#define PATH_SAVE_ACTIONS "/saveactions" -#define PATH_SAVE_SYMBOLS "/savesymbols" -#define PATH_SAVE_MAP "/savemap" -#define PATH_SCAN "/scan" -#define PATH_STAT "/stat" -#define PATH_STAT_RESET "/statreset" -#define PATH_COUNTERS "/counters" - -/* Graph colors */ -#define COLOR_CLEAN "#58A458" -#define COLOR_PROBABLE_SPAM "#D67E7E" -#define COLOR_GREYLIST "#A0A0A0" -#define COLOR_REJECT "#CB4B4B" -#define COLOR_TOTAL "#9440ED" - -gpointer init_webui_worker (struct rspamd_config *cfg); -void start_webui_worker (struct rspamd_worker *worker); - -worker_t webui_worker = { - "webui", /* Name */ - init_webui_worker, /* Init function */ - start_webui_worker, /* Start function */ - TRUE, /* Has socket */ - TRUE, /* Non unique */ - FALSE, /* Non threaded */ - TRUE, /* Killable */ - SOCK_STREAM /* TCP socket */ -}; -/* - * Worker's context - */ -struct rspamd_webui_worker_ctx { - guint32 timeout; - struct timeval io_tv; - /* DNS resolver */ - struct rspamd_dns_resolver *resolver; - /* Events base */ - struct event_base *ev_base; - /* Whether we use ssl for this server */ - gboolean use_ssl; - /* Webui password */ - gchar *password; - /* Privilleged password */ - gchar *enable_password; - /* HTTP server */ - struct rspamd_http_connection_router *http; - /* Server's start time */ - time_t start_time; - /* Main server */ - struct rspamd_main *srv; - /* Configuration */ - struct rspamd_config *cfg; - /* SSL cert */ - gchar *ssl_cert; - /* SSL private key */ - gchar *ssl_key; - /* A map of secure IP */ - gchar *secure_ip; - radix_tree_t *secure_map; - - /* Static files dir */ - gchar *static_files_dir; - - /* Custom commands registered by plugins */ - GHashTable *custom_commands; - - /* Worker */ - struct rspamd_worker *worker; -}; - -struct rspamd_webui_session { - struct rspamd_webui_worker_ctx *ctx; - rspamd_mempool_t *pool; - struct rspamd_task *task; - struct rspamd_classifier_config *cl; - rspamd_inet_addr_t from_addr; - gboolean is_spam; -}; - -sig_atomic_t wanna_die = 0; - -/* Check for password if it is required by configuration */ -static gboolean -rspamd_webui_check_password (struct rspamd_http_connection_entry *entry, - struct rspamd_webui_session *session, struct rspamd_http_message *msg, - gboolean is_enable) -{ - const gchar *password, *check; - struct rspamd_webui_worker_ctx *ctx = session->ctx; - gboolean ret = TRUE; - - /* Access list logic */ - if (!session->from_addr.af == AF_UNIX) { - msg_info ("allow unauthorized connection from a unix socket"); - return TRUE; - } - else if (ctx->secure_map && radix32_tree_find_addr (ctx->secure_map, - &session->from_addr) != RADIX_NO_VALUE) { - msg_info ("allow unauthorized connection from a trusted IP %s", - rspamd_inet_address_to_string (&session->from_addr)); - return TRUE; - } - - /* Password logic */ - if (is_enable) { - /* For privileged commands we strictly require enable password */ - password = rspamd_http_message_find_header (msg, "Password"); - if (ctx->enable_password == NULL) { - /* Use just a password (legacy mode) */ - msg_info ("using password as enable_password for a privileged command"); - check = ctx->password; - } - if (check != NULL) { - if (password == NULL || strcmp (password, check) != 0) { - msg_info ("incorrect or absent password has been specified"); - ret = FALSE; - } - } - else { - msg_warn ("no password to check while executing a privileged command"); - if (ctx->secure_map) { - msg_info ("deny unauthorized connection"); - ret = FALSE; - } - } - } - else { - password = rspamd_http_message_find_header (msg, "Password"); - if (ctx->password != NULL) { - /* Accept both normal and enable passwords */ - check = ctx->password; - if (password == NULL) { - msg_info ("absent password has been specified"); - ret = FALSE; - } - else if (strcmp (password, check) != 0) { - if (ctx->enable_password != NULL) { - check = ctx->enable_password; - if (strcmp (password, check) != 0) { - msg_info ("incorrect password has been specified"); - ret = FALSE; - } - } - else { - msg_info ("incorrect or absent password has been specified"); - ret = FALSE; - } - } - } - /* No password specified, allowing this command */ - } - - - if (!ret) { - rspamd_controller_send_error (entry, 403, "Unauthorized"); - } - - return ret; -} - -/* Command handlers */ - -/* - * Auth command handler: - * request: /auth - * headers: Password - * reply: json {"auth": "ok", "version": "0.5.2", "uptime": "some uptime", "error": "none"} - */ -static int -rspamd_webui_handle_auth (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct rspamd_stat *st; - int64_t uptime; - gulong data[4]; - ucl_object_t *obj; - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - obj = ucl_object_typed_new (UCL_OBJECT); - st = session->ctx->srv->stat; - data[0] = st->actions_stat[METRIC_ACTION_NOACTION]; - data[1] = st->actions_stat[METRIC_ACTION_ADD_HEADER] + st->actions_stat[METRIC_ACTION_REWRITE_SUBJECT]; - data[2] = st->actions_stat[METRIC_ACTION_GREYLIST]; - data[3] = st->actions_stat[METRIC_ACTION_REJECT]; - - /* Get uptime */ - uptime = time (NULL) - session->ctx->start_time; - - ucl_object_insert_key (obj, ucl_object_fromstring (RVERSION), "version", 0, false); - ucl_object_insert_key (obj, ucl_object_fromstring ("ok"), "auth", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (uptime), "uptime", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (data[0]), "clean", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (data[1]), "probable", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (data[2]), "greylist", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (data[3]), "reject", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (st->messages_scanned), "scanned", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (st->messages_learned), "learned", 0, false); - - rspamd_controller_send_ucl (conn_ent, obj); - ucl_object_unref (obj); - - return 0; -} - -/* - * Symbols command handler: - * request: /symbols - * reply: json [{ - * "name": "group_name", - * "symbols": [ - * { - * "name": "name", - * "weight": 0.1, - * "description": "description of symbol" - * }, - * {...} - * }, - * {...}] - */ -static int -rspamd_webui_handle_symbols (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - GList *cur_gr, *cur_sym; - struct rspamd_symbols_group *gr; - struct rspamd_symbol_def *sym; - ucl_object_t *obj, *top, *sym_obj; - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - top = ucl_object_typed_new (UCL_ARRAY); - - /* Go through all symbols groups */ - cur_gr = session->ctx->cfg->symbols_groups; - while (cur_gr) { - gr = cur_gr->data; - obj = ucl_object_typed_new (UCL_OBJECT); - ucl_object_insert_key (obj, ucl_object_fromstring (gr->name), "group", 0, false); - /* Iterate through all symbols */ - cur_sym = gr->symbols; - while (cur_sym) { - sym_obj = ucl_object_typed_new (UCL_OBJECT); - sym = cur_sym->data; - - ucl_object_insert_key (sym_obj, ucl_object_fromstring (sym->name), - "symbol", 0, false); - ucl_object_insert_key (sym_obj, ucl_object_fromdouble (*sym->weight_ptr), - "weight", 0, false); - if (sym->description) { - ucl_object_insert_key (sym_obj, ucl_object_fromstring (sym->description), - "description", 0, false); - } - - ucl_object_insert_key (obj, sym_obj, "rules", 0, false); - cur_sym = g_list_next (cur_sym); - } - cur_gr = g_list_next (cur_gr); - ucl_array_append (top, obj); - } - - rspamd_controller_send_ucl (conn_ent, top); - ucl_object_unref (top); - - return 0; -} - -/* - * Actions command handler: - * request: /actions - * reply: json [{ - * "action": "no action", - * "value": 1.1 - * }, - * {...}] - */ -static int -rspamd_webui_handle_actions (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct metric *metric; - struct metric_action *act; - gint i; - ucl_object_t *obj, *top; - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - top = ucl_object_typed_new (UCL_ARRAY); - - /* Get actions for default metric */ - metric = g_hash_table_lookup (session->ctx->cfg->metrics, DEFAULT_METRIC); - if (metric != NULL) { - for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i ++) { - act = &metric->actions[i]; - if (act->score > 0) { - obj = ucl_object_typed_new (UCL_OBJECT); - ucl_object_insert_key (obj, - ucl_object_fromstring (str_action_metric (act->action)), "action", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble (act->score), "value", 0, false); - ucl_array_append (top, obj); - } - } - } - - rspamd_controller_send_ucl (conn_ent, top); - ucl_object_unref (top); - - return 0; -} -/* - * Maps command handler: - * request: /maps - * headers: Password - * reply: json [ - * { - * "map": "name", - * "description": "description", - * "editable": true - * }, - * {...} - * ] - */ -static int -rspamd_webui_handle_maps (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - GList *cur, *tmp = NULL; - struct rspamd_map *map; - gboolean editable; - ucl_object_t *obj, *top; - - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - top = ucl_object_typed_new (UCL_ARRAY); - /* Iterate over all maps */ - cur = session->ctx->cfg->maps; - while (cur) { - map = cur->data; - if (map->protocol == MAP_PROTO_FILE && map->description != NULL) { - if (access (map->uri, R_OK) == 0) { - tmp = g_list_prepend (tmp, map); - } - } - cur = g_list_next (cur); - } - /* Iterate over selected maps */ - cur = tmp; - while (cur) { - map = cur->data; - editable = (access (map->uri, W_OK) == 0); - - obj = ucl_object_typed_new (UCL_OBJECT); - ucl_object_insert_key (obj, ucl_object_fromint (map->id), - "map", 0, false); - ucl_object_insert_key (obj, ucl_object_fromstring (map->description), - "description", 0, false); - ucl_object_insert_key (obj, ucl_object_frombool (editable), - "editable", 0, false); - ucl_array_append (top, obj); - - cur = g_list_next (cur); - } - - if (tmp) { - g_list_free (tmp); - } - - rspamd_controller_send_ucl (conn_ent, top); - ucl_object_unref (top); - - return 0; -} - -/* - * Get map command handler: - * request: /getmap - * headers: Password, Map - * reply: plain-text - */ -static int -rspamd_webui_handle_get_map (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - GList *cur; - struct rspamd_map *map; - const gchar *idstr; - gchar *errstr; - struct stat st; - gint fd; - guint32 id; - gboolean found = FALSE; - struct rspamd_http_message *reply; - - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - - idstr = rspamd_http_message_find_header (msg, "Map"); - - if (idstr == NULL) { - msg_info ("absent map id"); - rspamd_controller_send_error (conn_ent, 400, "400 id header missing"); - return 0; - } - - id = strtoul (idstr, &errstr, 10); - if (*errstr != '\0') { - msg_info ("invalid map id"); - rspamd_controller_send_error (conn_ent, 400, "400 invalid map id"); - return 0; - } - - /* Now let's be sure that we have map defined in configuration */ - cur = session->ctx->cfg->maps; - while (cur) { - map = cur->data; - if (map->id == id && map->protocol == MAP_PROTO_FILE) { - found = TRUE; - break; - } - cur = g_list_next (cur); - } - - if (!found) { - msg_info ("map not found"); - rspamd_controller_send_error (conn_ent, 404, "404 map not found"); - return 0; - } - - if (stat (map->uri, &st) == -1 || (fd = open (map->uri, O_RDONLY)) == -1) { - msg_err ("cannot open map %s: %s", map->uri, strerror (errno)); - rspamd_controller_send_error (conn_ent, 500, "500 map open error"); - return 0; - } - - reply = rspamd_http_new_message (HTTP_RESPONSE); - reply->date = time (NULL); - reply->code = 200; - reply->body = g_string_sized_new (st.st_size); - - /* Read the whole buffer */ - if (read (fd, msg->body->str, st.st_size) == -1) { - rspamd_http_message_free (reply); - msg_err ("cannot read map %s: %s", map->uri, strerror (errno)); - rspamd_controller_send_error (conn_ent, 500, "500 map read error"); - return 0; - } - - reply->body->len = st.st_size; - reply->body->str[reply->body->len] = '\0'; - - close (fd); - - rspamd_http_connection_reset (conn_ent->conn); - rspamd_http_connection_write_message (conn_ent->conn, reply, NULL, - "text/plain", conn_ent, conn_ent->conn->fd, - conn_ent->rt->ptv, conn_ent->rt->ev_base); - conn_ent->is_reply = TRUE; - - return 0; -} - -#if 0 -/* - * Graph command handler: - * request: /graph - * headers: Password - * reply: json [ - * { label: "Foo", data: [ [10, 1], [17, -14], [30, 5] ] }, - * { label: "Bar", data: [ [10, 1], [17, -14], [30, 5] ] }, - * {...} - * ] - */ -/* XXX: now this function returns only random data */ -static void -rspamd_webui_handle_graph (struct evhttp_request *req, gpointer arg) -{ - struct rspamd_webui_worker_ctx *ctx = arg; - struct evbuffer *evb; - gint i, seed; - time_t now, t; - double vals[5][100]; - - if (!http_check_password (ctx, req)) { - return; - } - - evb = evbuffer_new (); - if (!evb) { - msg_err ("cannot allocate evbuffer for reply"); - evhttp_send_reply (req, HTTP_INTERNAL, "500 insufficient memory", NULL); - return; - } - - /* Trailer */ - evbuffer_add (evb, "[", 1); - - /* XXX: simple and stupid set */ - seed = g_random_int (); - for (i = 0; i < 100; i ++, seed ++) { - vals[0][i] = fabs ((sin (seed * 0.1 * M_PI_2) + 1) * 40. + ((gint)(g_random_int () % 2) - 1)); - vals[1][i] = vals[0][i] * 0.5; - vals[2][i] = vals[0][i] * 0.1; - vals[3][i] = vals[0][i] * 0.3; - vals[4][i] = vals[0][i] * 1.9; - } - - now = time (NULL); - - /* Ham label */ - t = now - 6000; - evbuffer_add_printf (evb, "{\"label\": \"Clean messages\", \"lines\": {\"fill\": false}, \"color\": \"" - COLOR_CLEAN "\", \"data\":["); - for (i = 0; i < 100; i ++, t += 60) { - evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[0][i], i == 99 ? "]" : "],"); - } - evbuffer_add (evb, "]},", 3); - - /* Probable spam label */ - t = now - 6000; - evbuffer_add_printf (evb, "{\"label\": \"Probable spam messages\", \"lines\": {\"fill\": false}, \"color\": \"" - COLOR_PROBABLE_SPAM "\", \"data\":["); - for (i = 0; i < 100; i ++, t += 60) { - evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[1][i], i == 99 ? "]" : "],"); - } - evbuffer_add (evb, "]},", 3); - - /* Greylist label */ - t = now - 6000; - evbuffer_add_printf (evb, "{\"label\": \"Greylisted messages\", \"lines\": {\"fill\": false}, \"color\": \"" - COLOR_GREYLIST "\", \"data\":["); - for (i = 0; i < 100; i ++, t += 60) { - evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[2][i], i == 99 ? "]" : "],"); - } - evbuffer_add (evb, "]},", 3); - - /* Reject label */ - t = now - 6000; - evbuffer_add_printf (evb, "{\"label\": \"Rejected messages\", \"lines\": {\"fill\": false}, \"color\": \"" - COLOR_REJECT "\", \"data\":["); - for (i = 0; i < 100; i ++, t += 60) { - evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[3][i], i == 99 ? "]" : "],"); - } - evbuffer_add (evb, "]},", 3); - - /* Total label */ - t = now - 6000; - evbuffer_add_printf (evb, "{\"label\": \"Total messages\", \"lines\": {\"fill\": false}, \"color\": \"" - COLOR_TOTAL "\", \"data\":["); - for (i = 0; i < 100; i ++, t += 60) { - evbuffer_add_printf (evb, "[%llu,%.2f%s", (long long unsigned)t * 1000, vals[4][i], i == 99 ? "]" : "],"); - } - evbuffer_add (evb, "]}", 2); - - evbuffer_add (evb, "]" CRLF, 3); - - evhttp_add_header (req->output_headers, "Connection", "close"); - http_calculate_content_length (evb, req); - - evhttp_send_reply (req, HTTP_OK, "OK", evb); - evbuffer_free (evb); -} -#endif -/* - * Pie chart command handler: - * request: /pie - * headers: Password - * reply: json [ - * { label: "Foo", data: 11 }, - * { label: "Bar", data: 20 }, - * {...} - * ] - */ -static int -rspamd_webui_handle_pie_chart (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct rspamd_webui_worker_ctx *ctx; - gdouble data[4], total; - ucl_object_t *top, *obj; - - ctx = session->ctx; - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - top = ucl_object_typed_new (UCL_ARRAY); - total = ctx->srv->stat->messages_scanned; - if (total != 0) { - obj = ucl_object_typed_new (UCL_ARRAY); - - data[0] = ctx->srv->stat->actions_stat[METRIC_ACTION_NOACTION] / total * 100.; - data[1] = (ctx->srv->stat->actions_stat[METRIC_ACTION_ADD_HEADER] + - ctx->srv->stat->actions_stat[METRIC_ACTION_REWRITE_SUBJECT]) / total * 100.; - data[2] = ctx->srv->stat->actions_stat[METRIC_ACTION_GREYLIST] / total * 100.; - data[3] = ctx->srv->stat->actions_stat[METRIC_ACTION_REJECT] / total * 100.; - - ucl_array_append (obj, ucl_object_fromstring ("Clean messages")); - ucl_array_append (obj, ucl_object_fromdouble (data[0])); - ucl_array_append (top, obj); - ucl_array_append (obj, ucl_object_fromstring ("Probable spam messages")); - ucl_array_append (obj, ucl_object_fromdouble (data[1])); - ucl_array_append (top, obj); - ucl_array_append (obj, ucl_object_fromstring ("Greylisted messages")); - ucl_array_append (obj, ucl_object_fromdouble (data[2])); - ucl_array_append (top, obj); - ucl_array_append (obj, ucl_object_fromstring ("Rejected messages")); - ucl_array_append (obj, ucl_object_fromdouble (data[3])); - ucl_array_append (top, obj); - } - else { - obj = ucl_object_typed_new (UCL_ARRAY); - - ucl_array_append (obj, ucl_object_fromstring ("Scanned messages")); - ucl_array_append (obj, ucl_object_fromdouble (0)); - ucl_array_append (top, obj); - } - - rspamd_controller_send_ucl (conn_ent, top); - ucl_object_unref (top); - - return 0; -} - -/* - * History command handler: - * request: /history - * headers: Password - * reply: json [ - * { label: "Foo", data: 11 }, - * { label: "Bar", data: 20 }, - * {...} - * ] - */ -static int -rspamd_webui_handle_history (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct rspamd_webui_worker_ctx *ctx; - struct roll_history_row *row; - struct roll_history copied_history; - gint i, rows_proc, row_num; - struct tm *tm; - gchar timebuf[32]; - gchar ip_buf[INET6_ADDRSTRLEN]; - ucl_object_t *top, *obj; - - ctx = session->ctx; - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - top = ucl_object_typed_new (UCL_ARRAY); - - /* Set lock on history */ - rspamd_mempool_lock_mutex (ctx->srv->history->mtx); - ctx->srv->history->need_lock = TRUE; - /* Copy locked */ - memcpy (&copied_history, ctx->srv->history, sizeof (copied_history)); - rspamd_mempool_unlock_mutex (ctx->srv->history->mtx); - - /* Go through all rows */ - row_num = copied_history.cur_row; - for (i = 0, rows_proc = 0; i < HISTORY_MAX_ROWS; i ++, row_num ++) { - if (row_num == HISTORY_MAX_ROWS) { - row_num = 0; - } - row = &copied_history.rows[row_num]; - /* Get only completed rows */ - if (row->completed) { - tm = localtime (&row->tv.tv_sec); - strftime (timebuf, sizeof (timebuf), "%F %H:%M:%S", tm); -#ifdef HAVE_INET_PTON - if (row->from_addr.ipv6) { - inet_ntop (AF_INET6, &row->from_addr.d.in6, ip_buf, sizeof (ip_buf)); - } - else { - inet_ntop (AF_INET, &row->from_addr.d.in4, ip_buf, sizeof (ip_buf)); - } -#else - rspamd_strlcpy (ip_buf, inet_ntoa (task->from_addr), sizeof (ip_buf)); -#endif - obj = ucl_object_typed_new (UCL_OBJECT); - ucl_object_insert_key (obj, ucl_object_fromstring (timebuf), "time", 0, false); - ucl_object_insert_key (obj, ucl_object_fromstring (row->message_id), "id", 0, false); - ucl_object_insert_key (obj, ucl_object_fromstring (ip_buf), "ip", 0, false); - ucl_object_insert_key (obj, - ucl_object_fromstring (str_action_metric (row->action)), "action", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble (row->score), "score", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble (row->required_score), "required_score", 0, false); - ucl_object_insert_key (obj, ucl_object_fromstring (row->symbols), "symbols", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (row->len), "size", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (row->scan_time), "scan_time", 0, false); - if (row->user[0] != '\0') { - ucl_object_insert_key (obj, ucl_object_fromstring (row->user), "user", 0, false); - } - ucl_array_append (top, obj); - rows_proc ++; - } - } - - rspamd_controller_send_ucl (conn_ent, top); - ucl_object_unref (top); - - return 0; -} - -static gboolean -rspamd_webui_learn_fin_task (void *ud) -{ - struct rspamd_task *task = ud; - struct rspamd_webui_session *session; - struct rspamd_http_connection_entry *conn_ent; - GError *err = NULL; - - conn_ent = task->fin_arg; - session = conn_ent->ud; - - if (!learn_task_spam (session->cl, task, session->is_spam, &err)) { - rspamd_controller_send_error (conn_ent, 500 + err->code, err->message); - rspamd_http_connection_unref (conn_ent->conn); - return TRUE; - } - /* Successful learn */ - rspamd_controller_send_string (conn_ent, "{\"success\":true}"); - rspamd_http_connection_unref (conn_ent->conn); - - return TRUE; -} - -static gboolean -rspamd_webui_check_fin_task (void *ud) -{ - struct rspamd_task *task = ud; - struct rspamd_http_connection_entry *conn_ent; - - conn_ent = task->fin_arg; - task->http_conn = conn_ent->conn; - rspamd_protocol_write_reply (task); - conn_ent->is_reply = TRUE; - rspamd_http_connection_unref (conn_ent->conn); - - return TRUE; -} - -static int -rspamd_webui_handle_learn_common (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg, gboolean is_spam) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct rspamd_webui_worker_ctx *ctx; - struct rspamd_classifier_config *cl; - struct rspamd_task *task; - const gchar *classifier; - - ctx = session->ctx; - - if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { - return 0; - } - - if (msg->body == NULL || msg->body->len == 0) { - msg_err ("got zero length body, cannot continue"); - rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); - return 0; - } - - if ((classifier = rspamd_http_message_find_header (msg, "Classifier")) == NULL) { - classifier = "bayes"; - } - - cl = rspamd_config_find_classifier (ctx->cfg, classifier); - if (cl == NULL) { - rspamd_controller_send_error (conn_ent, 400, "Classifier not found"); - return 0; - } - - task = rspamd_task_new (session->ctx->worker); - task->ev_base = session->ctx->ev_base; - task->msg = msg->body; - - task->resolver = ctx->resolver; - task->ev_base = ctx->ev_base; - - rspamd_http_connection_ref (conn_ent->conn); - task->s = new_async_session (session->pool, rspamd_webui_learn_fin_task, NULL, - rspamd_task_free_hard, task); - task->s->wanna_die = TRUE; - task->fin_arg = conn_ent; - - if (!rspamd_task_process (task, msg, NULL, FALSE)) { - msg_warn ("filters cannot be processed for %s", task->message_id); - rspamd_controller_send_error (conn_ent, 500, task->last_error); - destroy_session (task->s); - return 0; - } - - session->task = task; - session->cl = cl; - session->is_spam = is_spam; - - return 0; -} - -/* - * Learn spam command handler: - * request: /learnspam - * headers: Password - * input: plaintext data - * reply: json {"success":true} or {"error":"error message"} - */ -static int -rspamd_webui_handle_learnspam (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - return rspamd_webui_handle_learn_common (conn_ent, msg, TRUE); -} -/* - * Learn ham command handler: - * request: /learnham - * headers: Password - * input: plaintext data - * reply: json {"success":true} or {"error":"error message"} - */ -static int -rspamd_webui_handle_learnham (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - return rspamd_webui_handle_learn_common (conn_ent, msg, TRUE); -} - -/* - * Scan command handler: - * request: /scan - * headers: Password - * input: plaintext data - * reply: json {scan data} or {"error":"error message"} - */ -static int -rspamd_webui_handle_scan (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct rspamd_webui_worker_ctx *ctx; - struct rspamd_task *task; - - ctx = session->ctx; - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - if (msg->body == NULL || msg->body->len == 0) { - msg_err ("got zero length body, cannot continue"); - rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); - return 0; - } - - task = rspamd_task_new (session->ctx->worker); - task->ev_base = session->ctx->ev_base; - task->msg = msg->body; - - task->resolver = ctx->resolver; - task->ev_base = ctx->ev_base; - - rspamd_http_connection_ref (conn_ent->conn); - task->s = new_async_session (session->pool, rspamd_webui_check_fin_task, NULL, - rspamd_task_free_hard, task); - task->s->wanna_die = TRUE; - task->fin_arg = conn_ent; - - if (!rspamd_task_process (task, msg, NULL, FALSE)) { - msg_warn ("filters cannot be processed for %s", task->message_id); - rspamd_controller_send_error (conn_ent, 500, task->last_error); - destroy_session (task->s); - return 0; - } - - session->task = task; - - return 0; -} - -/* - * Save actions command handler: - * request: /saveactions - * headers: Password - * input: json array [,,] - * reply: json {"success":true} or {"error":"error message"} - */ -static int -rspamd_webui_handle_saveactions (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct ucl_parser *parser; - struct metric *metric; - ucl_object_t *obj, *cur; - struct rspamd_webui_worker_ctx *ctx; - const gchar *error; - gdouble score; - gint i; - enum rspamd_metric_action act; - - ctx = session->ctx; - - if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { - return 0; - } - - if (msg->body == NULL || msg->body->len == 0) { - msg_err ("got zero length body, cannot continue"); - rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); - return 0; - } - - metric = g_hash_table_lookup (ctx->cfg->metrics, DEFAULT_METRIC); - if (metric == NULL) { - msg_err ("cannot find default metric"); - rspamd_controller_send_error (conn_ent, 500, "Default metric is absent"); - return 0; - } - - /* Now check for dynamic config */ - if (!ctx->cfg->dynamic_conf) { - msg_err ("dynamic conf has not been defined"); - rspamd_controller_send_error (conn_ent, 500, "No dynamic_rules setting defined"); - return 0; - } - - parser = ucl_parser_new (0); - ucl_parser_add_chunk (parser, msg->body->str, msg->body->len); - - if ((error = ucl_parser_get_error (parser)) != NULL) { - msg_err ("cannot parse input: %s", error); - rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); - ucl_parser_free (parser); - return 0; - } - - obj = ucl_parser_get_object (parser); - ucl_parser_free (parser); - - if (obj->type != UCL_ARRAY || obj->len != 3) { - msg_err ("input is not an array of 3 elements"); - rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); - ucl_object_unref (obj); - return 0; - } - - cur = obj->value.av; - for (i = 0; i < 3 && cur != NULL; i ++, cur = cur->next) { - switch(i) { - case 0: - act = METRIC_ACTION_REJECT; - break; - case 1: - act = METRIC_ACTION_ADD_HEADER; - break; - case 2: - act = METRIC_ACTION_GREYLIST; - break; - } - score = ucl_object_todouble (cur); - if (metric->actions[act].score != score) { - add_dynamic_action (ctx->cfg, DEFAULT_METRIC, act, score); - } - } - - dump_dynamic_config (ctx->cfg); - - rspamd_controller_send_string (conn_ent, "{\"success\":true}"); - - return 0; -} - -/* - * Save symbols command handler: - * request: /savesymbols - * headers: Password - * input: json data - * reply: json {"success":true} or {"error":"error message"} - */ -static int -rspamd_webui_handle_savesymbols (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct ucl_parser *parser; - struct metric *metric; - ucl_object_t *obj; - const ucl_object_t *cur, *jname, *jvalue; - ucl_object_iter_t iter = NULL; - struct rspamd_webui_worker_ctx *ctx; - const gchar *error; - gdouble val; - struct symbol *sym; - - ctx = session->ctx; - - if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { - return 0; - } - - if (msg->body == NULL || msg->body->len == 0) { - msg_err ("got zero length body, cannot continue"); - rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); - return 0; - } - - metric = g_hash_table_lookup (ctx->cfg->metrics, DEFAULT_METRIC); - if (metric == NULL) { - msg_err ("cannot find default metric"); - rspamd_controller_send_error (conn_ent, 500, "Default metric is absent"); - return 0; - } - - /* Now check for dynamic config */ - if (!ctx->cfg->dynamic_conf) { - msg_err ("dynamic conf has not been defined"); - rspamd_controller_send_error (conn_ent, 500, "No dynamic_rules setting defined"); - return 0; - } - - parser = ucl_parser_new (0); - ucl_parser_add_chunk (parser, msg->body->str, msg->body->len); - - if ((error = ucl_parser_get_error (parser)) != NULL) { - msg_err ("cannot parse input: %s", error); - rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); - ucl_parser_free (parser); - return 0; - } - - obj = ucl_parser_get_object (parser); - ucl_parser_free (parser); - - if (obj->type != UCL_ARRAY || obj->len != 3) { - msg_err ("input is not an array of 3 elements"); - rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); - ucl_object_unref (obj); - return 0; - } - - while ((cur = ucl_iterate_object (obj, &iter, true))) { - if (cur->type != UCL_OBJECT) { - msg_err ("json array data error"); - rspamd_controller_send_error (conn_ent, 400, "Cannot parse input"); - ucl_object_unref (obj); - return 0; - } - jname = ucl_object_find_key (cur, "name"); - jvalue = ucl_object_find_key (cur, "value"); - val = ucl_object_todouble (jvalue); - sym = g_hash_table_lookup (metric->symbols, ucl_object_tostring (jname)); - if (sym && fabs (sym->score - val) > 0.01) { - if (!add_dynamic_symbol (ctx->cfg, DEFAULT_METRIC, - ucl_object_tostring (jname), val)) { - msg_err ("add symbol failed for %s", ucl_object_tostring (jname)); - rspamd_controller_send_error (conn_ent, 506, "Add symbol failed"); - ucl_object_unref (obj); - return 0; - } - } - } - - dump_dynamic_config (ctx->cfg); - - rspamd_controller_send_string (conn_ent, "{\"success\":true}"); - - return 0; -} - -/* - * Save map command handler: - * request: /savemap - * headers: Password, Map - * input: plaintext data - * reply: json {"success":true} or {"error":"error message"} - */ -static int -rspamd_webui_handle_savemap (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - GList *cur; - struct rspamd_map *map; - struct rspamd_webui_worker_ctx *ctx; - const gchar *idstr; - gchar *errstr; - guint32 id; - gboolean found = FALSE; - gint fd; - - ctx = session->ctx; - - if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { - return 0; - } - - if (msg->body == NULL || msg->body->len == 0) { - msg_err ("got zero length body, cannot continue"); - rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); - return 0; - } - - idstr = rspamd_http_message_find_header (msg, "Map"); - - if (idstr == NULL) { - msg_info ("absent map id"); - rspamd_controller_send_error (conn_ent, 400, "Map id not specified"); - return 0; - } - - id = strtoul (idstr, &errstr, 10); - if (*errstr != '\0') { - msg_info ("invalid map id"); - rspamd_controller_send_error (conn_ent, 400, "Map id is invalid"); - return 0; - } - - /* Now let's be sure that we have map defined in configuration */ - cur = ctx->cfg->maps; - while (cur) { - map = cur->data; - if (map->id == id && map->protocol == MAP_PROTO_FILE) { - found = TRUE; - break; - } - cur = g_list_next (cur); - } - - if (!found) { - msg_info ("map not found: %d", id); - rspamd_controller_send_error (conn_ent, 404, "Map id not found"); - return 0; - } - - if (g_atomic_int_get (map->locked)) { - msg_info ("map locked: %s", map->uri); - rspamd_controller_send_error (conn_ent, 404, "Map is locked"); - return 0; - } - - /* Set lock */ - g_atomic_int_set (map->locked, 1); - fd = open (map->uri, O_WRONLY | O_TRUNC); - if (fd == -1) { - g_atomic_int_set (map->locked, 0); - msg_info ("map %s open error: %s", map->uri, strerror (errno)); - rspamd_controller_send_error (conn_ent, 404, "Map id not found"); - return 0; - } - - if (write (fd, msg->body->str, msg->body->len) == -1) { - msg_info ("map %s write error: %s", map->uri, strerror (errno)); - close (fd); - g_atomic_int_set (map->locked, 0); - rspamd_controller_send_error (conn_ent, 500, "Map write error"); - return 0; - } - - /* Close and unlock */ - close (fd); - g_atomic_int_set (map->locked, 0); - - rspamd_controller_send_string (conn_ent, "{\"success\":true}"); - - return 0; -} - -/* - * Stat command handler: - * request: /stat (/resetstat) - * headers: Password - * reply: json data - */ -static int -rspamd_webui_handle_stat_common (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg, gboolean do_reset) -{ - struct rspamd_webui_session *session = conn_ent->ud; - ucl_object_t *top, *sub; - gint i; - guint64 used, total, rev, ham = 0, spam = 0; - time_t ti; - rspamd_mempool_stat_t mem_st; - struct rspamd_classifier_config *ccf; - stat_file_t *statfile; - struct rspamd_statfile_config *st; - GList *cur_cl, *cur_st; - struct rspamd_stat *stat, stat_copy; - - rspamd_mempool_stat (&mem_st); - memcpy (&stat_copy, session->ctx->worker->srv->stat, sizeof (stat_copy)); - stat = &stat_copy; - top = ucl_object_typed_new (UCL_OBJECT); - - ucl_object_insert_key (top, ucl_object_fromint (stat->messages_scanned), "scanned", 0, false); - if (stat->messages_scanned > 0) { - sub = ucl_object_typed_new (UCL_OBJECT); - for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i ++) { - ucl_object_insert_key (top, - ucl_object_fromint (stat->actions_stat[i]), - str_action_metric (i), 0, false); - if (i < METRIC_ACTION_GREYLIST) { - spam += stat->actions_stat[i]; - } - else { - ham += stat->actions_stat[i]; - } - if (do_reset) { - session->ctx->worker->srv->stat->actions_stat[i] = 0; - } - } - ucl_object_insert_key (top, sub, "actions", 0, false); - } - - ucl_object_insert_key (top, ucl_object_fromint (spam), "spam_count", 0, false); - ucl_object_insert_key (top, ucl_object_fromint (ham), "ham_count", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (stat->messages_learned), "learned", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (stat->connections_count), "connections", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (stat->control_connections_count), - "control_connections", 0, false); - - ucl_object_insert_key (top, - ucl_object_fromint (mem_st.pools_allocated), "pools_allocated", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (mem_st.pools_freed), "pools_freed", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (mem_st.bytes_allocated), "bytes_allocated", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (mem_st.chunks_allocated), "chunks_allocated", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (mem_st.shared_chunks_allocated), - "shared_chunks_allocated", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (mem_st.chunks_freed), "chunks_freed", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (mem_st.oversized_chunks), "chunks_oversized", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (stat->fuzzy_hashes), "fuzzy_stored", 0, false); - ucl_object_insert_key (top, - ucl_object_fromint (stat->fuzzy_hashes_expired), "fuzzy_expired", 0, false); - - /* Now write statistics for each statfile */ - cur_cl = g_list_first (session->ctx->cfg->classifiers); - sub = ucl_object_typed_new (UCL_ARRAY); - while (cur_cl) { - ccf = cur_cl->data; - cur_st = g_list_first (ccf->statfiles); - while (cur_st) { - st = cur_st->data; - if ((statfile = statfile_pool_is_open (session->ctx->srv->statfile_pool, - st->path)) == NULL) { - statfile = statfile_pool_open (session->ctx->srv->statfile_pool, - st->path, st->size, FALSE); - } - if (statfile) { - ucl_object_t *obj = ucl_object_typed_new (UCL_OBJECT); - - used = statfile_get_used_blocks (statfile); - total = statfile_get_total_blocks (statfile); - statfile_get_revision (statfile, &rev, &ti); - ucl_object_insert_key (obj, - ucl_object_fromstring (st->symbol), "symbol", 0, false); - if (st->label != NULL) { - ucl_object_insert_key (obj, - ucl_object_fromstring (st->label), "label", 0, false); - } - ucl_object_insert_key (obj, ucl_object_fromint (rev), "revision", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (used), "used", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (total), "total", 0, false); - - ucl_array_append (sub, obj); - } - cur_st = g_list_next (cur_st); - } - cur_cl = g_list_next (cur_cl); - } - - ucl_object_insert_key (top, sub, "statfiles", 0, false); - - if (do_reset) { - session->ctx->srv->stat->messages_scanned = 0; - session->ctx->srv->stat->messages_learned = 0; - session->ctx->srv->stat->connections_count = 0; - session->ctx->srv->stat->control_connections_count = 0; - rspamd_mempool_stat_reset (); - } - - rspamd_controller_send_ucl (conn_ent, top); - ucl_object_unref (top); - - return 0; -} - -static int -rspamd_webui_handle_stat (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - return rspamd_webui_handle_stat_common (conn_ent, msg, FALSE); -} - -static int -rspamd_webui_handle_statreset (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - - if (!rspamd_webui_check_password (conn_ent, session, msg, TRUE)) { - return 0; - } - - return rspamd_webui_handle_stat_common (conn_ent, msg, TRUE); -} - -static ucl_object_t * -rspamd_webui_cache_item_to_ucl (struct cache_item *item) -{ - ucl_object_t *obj; - - obj = ucl_object_typed_new (UCL_OBJECT); - ucl_object_insert_key (obj, ucl_object_fromstring (item->s->symbol), - "symbol", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble (item->s->weight), - "weight", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint (item->s->frequency), - "frequency", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble (item->s->avg_time), - "time", 0, false); - - return obj; -} - -/* - * Counters command handler: - * request: /counters - * headers: Password - * reply: json array of all counters - */ -static int -rspamd_webui_handle_counters (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - ucl_object_t *top; - GList *cur; - struct cache_item *item; - struct symbols_cache *cache; - - if (!rspamd_webui_check_password (conn_ent, session, msg, FALSE)) { - return 0; - } - - cache = session->ctx->cfg->cache; - top = ucl_object_typed_new (UCL_ARRAY); - if (cache != NULL) { - cur = cache->negative_items; - while (cur) { - item = cur->data; - if (!item->is_callback) { - ucl_array_append (top, rspamd_webui_cache_item_to_ucl (item)); - } - cur = g_list_next (cur); - } - cur = cache->static_items; - while (cur) { - item = cur->data; - if (!item->is_callback) { - ucl_array_append (top, rspamd_webui_cache_item_to_ucl (item)); - } - cur = g_list_next (cur); - } - } - rspamd_controller_send_ucl (conn_ent, top); - ucl_object_unref (top); - - return 0; -} - -static int -rspamd_webui_handle_custom (struct rspamd_http_connection_entry *conn_ent, - struct rspamd_http_message *msg) -{ - struct rspamd_webui_session *session = conn_ent->ud; - struct rspamd_custom_controller_command *cmd; - - cmd = g_hash_table_lookup (session->ctx->custom_commands, msg->url->str); - if (cmd == NULL || cmd->handler == NULL) { - msg_err ("custom command %V has not been found", msg->url); - rspamd_controller_send_error (conn_ent, 404, "No command associated"); - return 0; - } - - if (!rspamd_webui_check_password (conn_ent, session, msg, cmd->privilleged)) { - return 0; - } - if (cmd->require_message && (msg->body == NULL || msg->body->len == 0)) { - msg_err ("got zero length body, cannot continue"); - rspamd_controller_send_error (conn_ent, 400, "Empty body is not permitted"); - return 0; - } - - return cmd->handler (conn_ent, msg, cmd->ctx); -} - -static void -rspamd_webui_error_handler (struct rspamd_http_connection_entry *conn_ent, GError *err) -{ - msg_err ("http error occurred: %s", err->message); -} - -static void -rspamd_webui_finish_handler (struct rspamd_http_connection_entry *conn_ent) -{ - struct rspamd_webui_session *session = conn_ent->ud; - - if (session->pool) { - rspamd_mempool_delete (session->pool); - } - if (session->task != NULL) { - destroy_session (session->task->s); - } - - g_slice_free1 (sizeof (struct rspamd_webui_session), session); -} - -static void -rspamd_webui_accept_socket (gint fd, short what, void *arg) -{ - struct rspamd_worker *worker = (struct rspamd_worker *) arg; - struct rspamd_webui_worker_ctx *ctx; - struct rspamd_webui_session *nsession; - rspamd_inet_addr_t addr; - gint nfd; - - ctx = worker->ctx; - - if ((nfd = - rspamd_accept_from_socket (fd, &addr)) == -1) { - msg_warn ("accept failed: %s", strerror (errno)); - return; - } - /* Check for EAGAIN */ - if (nfd == 0) { - return; - } - - msg_info ("accepted connection from %s port %d", - rspamd_inet_address_to_string (&addr), - rspamd_inet_address_get_port (&addr)); - - nsession = g_slice_alloc0 (sizeof (struct rspamd_webui_session)); - nsession->pool = rspamd_mempool_new (rspamd_mempool_suggest_size ()); - nsession->ctx = ctx; - - memcpy (&nsession->from_addr, &addr, sizeof (addr)); - - rspamd_http_router_handle_socket (ctx->http, nfd, nsession); -} - -gpointer -init_webui_worker (struct rspamd_config *cfg) -{ - struct rspamd_webui_worker_ctx *ctx; - GQuark type; - - type = g_quark_try_string ("webui"); - - ctx = g_malloc0 (sizeof (struct rspamd_webui_worker_ctx)); - - ctx->timeout = DEFAULT_WORKER_IO_TIMEOUT; - - rspamd_rcl_register_worker_option (cfg, type, "password", - rspamd_rcl_parse_struct_string, ctx, - G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, password), 0); - - rspamd_rcl_register_worker_option (cfg, type, "enable_password", - rspamd_rcl_parse_struct_string, ctx, - G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, password), 0); - - rspamd_rcl_register_worker_option (cfg, type, "ssl", - rspamd_rcl_parse_struct_boolean, ctx, - G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, use_ssl), 0); - - rspamd_rcl_register_worker_option (cfg, type, "ssl_cert", - rspamd_rcl_parse_struct_string, ctx, - G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, ssl_cert), 0); - - rspamd_rcl_register_worker_option (cfg, type, "ssl_key", - rspamd_rcl_parse_struct_string, ctx, - G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, ssl_key), 0); - rspamd_rcl_register_worker_option (cfg, type, "timeout", - rspamd_rcl_parse_struct_time, ctx, - G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, timeout), RSPAMD_CL_FLAG_TIME_INTEGER); - - rspamd_rcl_register_worker_option (cfg, type, "secure_ip", - rspamd_rcl_parse_struct_string, ctx, - G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, secure_ip), 0); - - rspamd_rcl_register_worker_option (cfg, type, "static_dir", - rspamd_rcl_parse_struct_string, ctx, - G_STRUCT_OFFSET (struct rspamd_webui_worker_ctx, static_files_dir), 0); - - return ctx; -} - -/* - * Start worker process - */ -void -start_webui_worker (struct rspamd_worker *worker) -{ - struct rspamd_webui_worker_ctx *ctx = worker->ctx; - GList *cur; - struct filter *f; - struct module_ctx *mctx; - GHashTableIter iter; - gpointer key, value; - - - ctx->ev_base = rspamd_prepare_worker (worker, "controller", rspamd_webui_accept_socket); - msec_to_tv (ctx->timeout, &ctx->io_tv); - - ctx->start_time = time (NULL); - ctx->worker = worker; - ctx->cfg = worker->srv->cfg; - ctx->srv = worker->srv; - ctx->custom_commands = g_hash_table_new (rspamd_strcase_hash, - rspamd_strcase_equal); - if (ctx->secure_ip != NULL) { - if (!add_map (worker->srv->cfg, ctx->secure_ip, "Allow webui access from the specified IP", - read_radix_list, fin_radix_list, (void **)&ctx->secure_map)) { - if (!rspamd_config_parse_ip_list (ctx->secure_ip, &ctx->secure_map)) { - msg_warn ("cannot load or parse ip list from '%s'", ctx->secure_ip); - } - } - } - /* Accept event */ - ctx->http = rspamd_http_router_new (rspamd_webui_error_handler, - rspamd_webui_finish_handler, &ctx->io_tv, ctx->ev_base, - ctx->static_files_dir); - - /* Add callbacks for different methods */ - rspamd_http_router_add_path (ctx->http, PATH_AUTH, rspamd_webui_handle_auth); - rspamd_http_router_add_path (ctx->http, PATH_SYMBOLS, rspamd_webui_handle_symbols); - rspamd_http_router_add_path (ctx->http, PATH_ACTIONS, rspamd_webui_handle_actions); - rspamd_http_router_add_path (ctx->http, PATH_MAPS, rspamd_webui_handle_maps); - rspamd_http_router_add_path (ctx->http, PATH_GET_MAP, rspamd_webui_handle_get_map); - rspamd_http_router_add_path (ctx->http, PATH_PIE_CHART, rspamd_webui_handle_pie_chart); - rspamd_http_router_add_path (ctx->http, PATH_HISTORY, rspamd_webui_handle_history); - rspamd_http_router_add_path (ctx->http, PATH_LEARN_SPAM, rspamd_webui_handle_learnspam); - rspamd_http_router_add_path (ctx->http, PATH_LEARN_HAM, rspamd_webui_handle_learnham); - rspamd_http_router_add_path (ctx->http, PATH_SAVE_ACTIONS, rspamd_webui_handle_saveactions); - rspamd_http_router_add_path (ctx->http, PATH_SAVE_SYMBOLS, rspamd_webui_handle_savesymbols); - rspamd_http_router_add_path (ctx->http, PATH_SAVE_MAP, rspamd_webui_handle_savemap); - rspamd_http_router_add_path (ctx->http, PATH_SCAN, rspamd_webui_handle_scan); - rspamd_http_router_add_path (ctx->http, PATH_STAT, rspamd_webui_handle_stat); - rspamd_http_router_add_path (ctx->http, PATH_STAT_RESET, rspamd_webui_handle_statreset); - rspamd_http_router_add_path (ctx->http, PATH_COUNTERS, rspamd_webui_handle_counters); - - /* Attach plugins */ - cur = g_list_first (ctx->cfg->filters); - while (cur) { - f = cur->data; - mctx = g_hash_table_lookup (ctx->cfg->c_modules, f->module->name); - if (mctx != NULL && f->module->module_init_func != NULL) { - f->module->module_attach_controller_func (mctx, ctx->custom_commands); - } - cur = g_list_next (cur); - } - - g_hash_table_iter_init (&iter, ctx->custom_commands); - while (g_hash_table_iter_next (&iter, &key, &value)) { - rspamd_http_router_add_path (ctx->http, key, rspamd_webui_handle_custom); - } - -#if 0 - rspamd_http_router_add_path (ctx->http, PATH_GRAPH, rspamd_webui_handle_graph, ctx); -#endif - - ctx->resolver = dns_resolver_init (worker->srv->logger, ctx->ev_base, worker->srv->cfg); - - /* Maps events */ - start_map_watch (worker->srv->cfg, ctx->ev_base); - - event_base_loop (ctx->ev_base, 0); - - g_mime_shutdown (); - close_log (rspamd_main->logger); - exit (EXIT_SUCCESS); -}