From e6ddee95a731f41a9cca17fe5082bfcda9623924 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Fri, 2 May 2014 12:16:43 +0100 Subject: Move protocol functions to libserver. --- src/libmime/CMakeLists.txt | 1 - src/libmime/protocol.c | 825 ------------------------------------------- src/libmime/protocol.h | 53 --- src/libserver/CMakeLists.txt | 1 + src/libserver/protocol.c | 825 +++++++++++++++++++++++++++++++++++++++++++ src/libserver/protocol.h | 53 +++ 6 files changed, 879 insertions(+), 879 deletions(-) delete mode 100644 src/libmime/protocol.c delete mode 100644 src/libmime/protocol.h create mode 100644 src/libserver/protocol.c create mode 100644 src/libserver/protocol.h diff --git a/src/libmime/CMakeLists.txt b/src/libmime/CMakeLists.txt index e612dce19..08efdba09 100644 --- a/src/libmime/CMakeLists.txt +++ b/src/libmime/CMakeLists.txt @@ -4,7 +4,6 @@ SET(LIBRSPAMDMIMESRC filter.c images.c message.c - protocol.c smtp_utils.c smtp_proto.c) diff --git a/src/libmime/protocol.c b/src/libmime/protocol.c deleted file mode 100644 index 8f42a08ce..000000000 --- a/src/libmime/protocol.c +++ /dev/null @@ -1,825 +0,0 @@ -/* - * 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 "main.h" -#include "util.h" -#include "cfg_file.h" -#include "settings.h" -#include "message.h" - -/* Max line size */ -#define OUTBUFSIZ BUFSIZ -/* - * Just check if the passed message is spam or not and reply as - * described below - */ -#define MSG_CMD_CHECK "check" -/* - * Check if message is spam or not, and return score plus list - * of symbols hit - */ -#define MSG_CMD_SYMBOLS "symbols" -/* - * Check if message is spam or not, and return score plus report - */ -#define MSG_CMD_REPORT "report" -/* - * Check if message is spam or not, and return score plus report - * if the message is spam - */ -#define MSG_CMD_REPORT_IFSPAM "report_ifspam" -/* - * Ignore this message -- client opened connection then changed - */ -#define MSG_CMD_SKIP "skip" -/* - * Return a confirmation that spamd is alive - */ -#define MSG_CMD_PING "ping" -/* - * Process this message as described above and return modified message - */ -#define MSG_CMD_PROCESS "process" - -/* - * Learn specified statfile using message - */ -#define MSG_CMD_LEARN "learn" - -/* - * spamassassin greeting: - */ -#define SPAMC_GREETING "SPAMC" -/* - * rspamd greeting: - */ -#define RSPAMC_GREETING "RSPAMC" -/* - * Headers - */ -#define CONTENT_LENGTH_HEADER "Content-length" -#define HELO_HEADER "Helo" -#define FROM_HEADER "From" -#define IP_ADDR_HEADER "IP" -#define NRCPT_HEADER "Recipient-Number" -#define RCPT_HEADER "Rcpt" -#define SUBJECT_HEADER "Subject" -#define STATFILE_HEADER "Statfile" -#define QUEUE_ID_HEADER "Queue-ID" -#define ERROR_HEADER "Error" -#define USER_HEADER "User" -#define PASS_HEADER "Pass" -#define JSON_HEADER "Json" -#define HOSTNAME_HEADER "Hostname" -#define DELIVER_TO_HEADER "Deliver-To" -#define NO_LOG_HEADER "Log" - -static GList *custom_commands = NULL; - - -/* - * Remove <> from the fixed string and copy it to the pool - */ -static gchar * -rspamd_protocol_escape_braces (GString *in) -{ - gint len = 0; - gchar *orig, *p; - - orig = in->str; - while ((g_ascii_isspace (*orig) || *orig == '<') && orig - in->str < (gint)in->len) { - orig ++; - } - - g_string_erase (in, 0, orig - in->str); - - p = in->str; - while ((!g_ascii_isspace (*p) && *p != '>') && p - in->str < (gint)in->len) { - p ++; - len ++; - } - - g_string_truncate (in, len); - - return in->str; -} - -static gboolean -rspamd_protocol_handle_url (struct rspamd_task *task, struct rspamd_http_message *msg) -{ - GList *cur; - struct custom_command *cmd; - const gchar *p; - - if (msg->url == NULL || msg->url->len == 0) { - task->last_error = "command is absent"; - task->error_code = 400; - return FALSE; - } - - if (msg->url->str[0] == '/') { - p = &msg->url->str[1]; - } - else { - p = msg->url->str; - } - - switch (*p) { - case 'c': - case 'C': - /* check */ - if (g_ascii_strcasecmp (p + 1, MSG_CMD_CHECK + 1) == 0) { - task->cmd = CMD_CHECK; - } - else { - goto err; - } - break; - case 's': - case 'S': - /* symbols, skip */ - if (g_ascii_strcasecmp (p + 1, MSG_CMD_SYMBOLS + 1) == 0) { - task->cmd = CMD_SYMBOLS; - } - else if (g_ascii_strcasecmp (p + 1, MSG_CMD_SKIP + 1) == 0) { - task->cmd = CMD_SKIP; - } - else { - goto err; - } - break; - case 'p': - case 'P': - /* ping, process */ - if (g_ascii_strcasecmp (p + 1, MSG_CMD_PING + 1) == 0) { - task->cmd = CMD_PING; - } - else if (g_ascii_strcasecmp (p + 1, MSG_CMD_PROCESS + 1) == 0) { - task->cmd = CMD_PROCESS; - } - else { - goto err; - } - break; - case 'r': - case 'R': - /* report, report_ifspam */ - if (g_ascii_strcasecmp (p + 1, MSG_CMD_REPORT + 1) == 0) { - task->cmd = CMD_REPORT; - } - else if (g_ascii_strcasecmp (p + 1, MSG_CMD_REPORT_IFSPAM + 1) == 0) { - task->cmd = CMD_REPORT_IFSPAM; - } - else { - goto err; - } - break; - default: - cur = custom_commands; - while (cur) { - cmd = cur->data; - if (g_ascii_strcasecmp (p, cmd->name) == 0) { - task->cmd = CMD_OTHER; - task->custom_cmd = cmd; - break; - } - cur = g_list_next (cur); - } - - if (cur == NULL) { - goto err; - } - break; - } - - return TRUE; - -err: - debug_task ("bad command: %s", p); - task->last_error = "invalid command"; - task->error_code = 400; - return FALSE; -} - -static gboolean -rspamd_protocol_handle_headers (struct rspamd_task *task, struct rspamd_http_message *msg) -{ - gchar *headern, *err, *tmp; - gboolean res = TRUE; - struct rspamd_http_header *h; - - LL_FOREACH (msg->headers, h) { - headern = h->name->str; - - switch (headern[0]) { - case 'd': - case 'D': - if (g_ascii_strcasecmp (headern, DELIVER_TO_HEADER) == 0) { - task->deliver_to = rspamd_protocol_escape_braces (h->value); - debug_task ("read deliver-to header, value: %s", task->deliver_to); - } - else { - debug_task ("wrong header: %s", headern); - res = FALSE; - } - break; - case 'h': - case 'H': - if (g_ascii_strcasecmp (headern, HELO_HEADER) == 0) { - task->helo = h->value->str; - debug_task ("read helo header, value: %s", task->helo); - } - else if (g_ascii_strcasecmp (headern, HOSTNAME_HEADER) == 0) { - task->hostname = h->value->str; - debug_task ("read hostname header, value: %s", task->hostname); - } - else { - debug_task ("wrong header: %s", headern); - res = FALSE; - } - break; - case 'f': - case 'F': - if (g_ascii_strcasecmp (headern, FROM_HEADER) == 0) { - task->from = rspamd_protocol_escape_braces (h->value); - debug_task ("read from header, value: %s", task->from); - } - else { - debug_task ("wrong header: %s", headern); - res = FALSE; - } - break; - case 'j': - case 'J': - if (g_ascii_strcasecmp (headern, JSON_HEADER) == 0) { - task->is_json = rspamd_config_parse_flag (h->value->str); - } - else { - debug_task ("wrong header: %s", headern); - res = FALSE; - } - break; - case 'q': - case 'Q': - if (g_ascii_strcasecmp (headern, QUEUE_ID_HEADER) == 0) { - task->queue_id = h->value->str; - debug_task ("read queue_id header, value: %s", task->queue_id); - } - else { - debug_task ("wrong header: %s", headern); - res = FALSE; - } - break; - case 'r': - case 'R': - if (g_ascii_strcasecmp (headern, RCPT_HEADER) == 0) { - tmp = rspamd_protocol_escape_braces (h->value); - task->rcpt = g_list_prepend (task->rcpt, tmp); - debug_task ("read rcpt header, value: %s", tmp); - } - else if (g_ascii_strcasecmp (headern, NRCPT_HEADER) == 0) { - task->nrcpt = strtoul (h->value->str, &err, 10); - debug_task ("read rcpt header, value: %d", (gint)task->nrcpt); - } - else { - msg_info ("wrong header: %s", headern); - res = FALSE; - } - break; - case 'i': - case 'I': - if (g_ascii_strcasecmp (headern, IP_ADDR_HEADER) == 0) { - tmp = h->value->str; - if (!rspamd_parse_inet_address (&task->from_addr, tmp)) { - msg_err ("bad ip header: '%s'", tmp); - return FALSE; - } - debug_task ("read IP header, value: %s", tmp); - } - else { - debug_task ("wrong header: %s", headern); - res = FALSE; - } - break; - case 'p': - case 'P': - if (g_ascii_strcasecmp (headern, PASS_HEADER) == 0) { - if (h->value->len == sizeof ("all") - 1 && - g_ascii_strcasecmp (h->value->str, "all") == 0) { - task->pass_all_filters = TRUE; - debug_task ("pass all filters"); - } - } - else { - res = FALSE; - } - break; - case 's': - case 'S': - if (g_ascii_strcasecmp (headern, SUBJECT_HEADER) == 0) { - task->subject = h->value->str; - } - else { - res = FALSE; - } - break; - case 'u': - case 'U': - if (g_ascii_strcasecmp (headern, USER_HEADER) == 0) { - task->user = h->value->str; - } - else { - res = FALSE; - } - break; - case 'l': - case 'L': - if (g_ascii_strcasecmp (headern, NO_LOG_HEADER) == 0) { - if (g_ascii_strcasecmp (h->value->str, "no") == 0) { - task->no_log = TRUE; - } - } - else { - res = FALSE; - } - break; - default: - debug_task ("wrong header: %s", headern); - res = FALSE; - break; - } - } - - if (!res && task->cfg->strict_protocol_headers) { - msg_err ("deny processing of a request with incorrect or unknown headers"); - task->last_error = "invalid header"; - task->error_code = 400; - return FALSE; - } - - return TRUE; -} - -gboolean -rspamd_protocol_handle_request (struct rspamd_task *task, - struct rspamd_http_message *msg) -{ - gboolean ret = TRUE; - - if (msg->method == HTTP_SYMBOLS) { - task->cmd = CMD_SYMBOLS; - task->is_json = FALSE; - } - else if (msg->method == HTTP_CHECK) { - task->cmd = CMD_CHECK; - task->is_json = FALSE; - } - else { - task->is_json = TRUE; - ret = rspamd_protocol_handle_url (task, msg); - } - - if (ret) { - ret = rspamd_protocol_handle_headers (task, msg); - } - - return ret; -} - -static void -write_hashes_to_log (struct rspamd_task *task, GString *logbuf) -{ - GList *cur; - struct mime_text_part *text_part; - - cur = task->text_parts; - - while (cur) { - text_part = cur->data; - if (text_part->fuzzy) { - if (cur->next != NULL) { - rspamd_printf_gstring (logbuf, " part: %Xd,", text_part->fuzzy->h); - } - else { - rspamd_printf_gstring (logbuf, " part: %Xd", text_part->fuzzy->h); - } - } - cur = g_list_next (cur); - } -} - - -/* Structure for writing tree data */ -struct tree_cb_data { - ucl_object_t *top; - struct rspamd_task *task; -}; - -/* - * Callback for writing urls - */ -static gboolean -urls_protocol_cb (gpointer key, gpointer value, gpointer ud) -{ - struct tree_cb_data *cb = ud; - struct uri *url = value; - ucl_object_t *obj; - - obj = ucl_object_fromlstring (url->host, url->hostlen); - DL_APPEND (cb->top->value.av, obj); - - if (cb->task->cfg->log_urls) { - msg_info ("<%s> URL: %s - %s: %s", cb->task->message_id, cb->task->user ? - cb->task->user : (cb->task->from ? cb->task->from : "unknown"), - rspamd_inet_address_to_string (&cb->task->from_addr), - struri (url)); - } - - return FALSE; -} - -static ucl_object_t * -rspamd_urls_tree_ucl (GTree *input, struct rspamd_task *task) -{ - struct tree_cb_data cb; - ucl_object_t *obj; - - obj = ucl_object_typed_new (UCL_ARRAY); - cb.top = obj; - cb.task = task; - - g_tree_foreach (input, urls_protocol_cb, &cb); - - return obj; -} - -static gboolean -emails_protocol_cb (gpointer key, gpointer value, gpointer ud) -{ - struct tree_cb_data *cb = ud; - struct uri *url = value; - ucl_object_t *obj; - - obj = ucl_object_fromlstring (url->user, url->userlen + url->hostlen + 1); - DL_APPEND (cb->top->value.av, obj); - - return FALSE; -} - -static ucl_object_t * -rspamd_emails_tree_ucl (GTree *input, struct rspamd_task *task) -{ - struct tree_cb_data cb; - ucl_object_t *obj; - - obj = ucl_object_typed_new (UCL_ARRAY); - cb.top = obj; - cb.task = task; - - g_tree_foreach (input, emails_protocol_cb, &cb); - - return obj; -} - - -/* Write new subject */ -static const gchar * -make_rewritten_subject (struct metric *metric, struct rspamd_task *task) -{ - static gchar subj_buf[1024]; - gchar *p = subj_buf, *end, *c, *res; - const gchar *s; - - end = p + sizeof(subj_buf); - c = metric->subject; - s = g_mime_message_get_subject (task->message); - - while (p < end) { - if (*c == '\0') { - *p = '\0'; - break; - } - else if (*c == '%' && *(c + 1) == 's') { - p += rspamd_strlcpy (p, (s != NULL) ? s : "", end - p); - c += 2; - } - else { - *p = *c ++; - } - p ++; - } - res = g_mime_utils_header_encode_text (subj_buf); - - rspamd_mempool_add_destructor (task->task_pool, (rspamd_mempool_destruct_t)g_free, res); - - return res; -} - -static ucl_object_t * -rspamd_str_list_ucl (GList *str_list) -{ - ucl_object_t *top = NULL, *obj; - GList *cur; - - top = ucl_object_typed_new (UCL_ARRAY); - cur = str_list; - while (cur) { - obj = ucl_object_fromstring (cur->data); - DL_APPEND (top->value.av, obj); - cur = g_list_next (cur); - } - - return top; -} - -static ucl_object_t * -rspamd_metric_symbol_ucl (struct rspamd_task *task, struct metric *m, - struct symbol *sym, GString *logbuf) -{ - ucl_object_t *obj = NULL; - const gchar *description = NULL; - - rspamd_printf_gstring (logbuf, "%s,", sym->name); - description = g_hash_table_lookup (m->descriptions, sym->name); - - obj = ucl_object_typed_new (UCL_OBJECT); - ucl_object_insert_key (obj, ucl_object_fromstring (sym->name), "name", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble (sym->score), "score", 0, false); - if (description) { - ucl_object_insert_key (obj, ucl_object_fromstring (description), "description", 0, false); - } - if (sym->options != NULL) { - ucl_object_insert_key (obj, rspamd_str_list_ucl (sym->options), "options", 0, false); - } - - return obj; -} - -static ucl_object_t * -rspamd_metric_result_ucl (struct rspamd_task *task, struct metric_result *mres, GString *logbuf) -{ - GHashTableIter hiter; - struct symbol *sym; - struct metric *m; - gboolean is_spam; - enum rspamd_metric_action action = METRIC_ACTION_NOACTION; - ucl_object_t *obj = NULL, *sobj; - gdouble required_score; - gpointer h, v; - const gchar *subject; - gchar action_char; - - m = mres->metric; - - /* XXX: handle settings */ - required_score = m->actions[METRIC_ACTION_REJECT].score; - is_spam = (mres->score >= required_score); - action = check_metric_action (mres->score, required_score, m); - if (task->is_skipped) { - action_char = 'S'; - } - else if (is_spam) { - action_char = 'T'; - } - else { - action_char = 'F'; - } - rspamd_printf_gstring (logbuf, "(%s: %c (%s): [%.2f/%.2f] [", - m->name, action_char, - str_action_metric (action), - mres->score, required_score); - - obj = ucl_object_typed_new (UCL_OBJECT); - ucl_object_insert_key (obj, ucl_object_frombool (is_spam), - "is_spam", 0, false); - ucl_object_insert_key (obj, ucl_object_frombool (task->is_skipped), - "is_skipped", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble (mres->score), - "score", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble (required_score), - "required_score", 0, false); - ucl_object_insert_key (obj, ucl_object_fromstring (str_action_metric (action)), - "action", 0, false); - - if (action == METRIC_ACTION_REWRITE_SUBJECT) { - subject = make_rewritten_subject (m, task); - ucl_object_insert_key (obj, ucl_object_fromstring (subject), - "subject", 0, false); - } - /* Now handle symbols */ - g_hash_table_iter_init (&hiter, mres->symbols); - while (g_hash_table_iter_next (&hiter, &h, &v)) { - sym = (struct symbol *)v; - sobj = rspamd_metric_symbol_ucl (task, m, sym, logbuf); - ucl_object_insert_key (obj, sobj, h, 0, false); - } - - /* Cut the trailing comma if needed */ - if (logbuf->str[logbuf->len - 1] == ',') { - logbuf->len --; - } - -#ifdef HAVE_CLOCK_GETTIME - rspamd_printf_gstring (logbuf, "]), len: %z, time: %s, dns req: %d,", - task->msg->len, calculate_check_time (&task->tv, &task->ts, - task->cfg->clock_res, &task->scan_milliseconds), task->dns_requests); -#else - rspamd_printf_gstring (logbuf, "]), len: %z, time: %s, dns req: %d,", - task->msg->len, - calculate_check_time (&task->tv, task->cfg->clock_res, &task->scan_milliseconds), - task->dns_requests); -#endif - - return obj; -} - -static void -rspamd_ucl_tolegacy_output (struct rspamd_task *task, ucl_object_t *top, GString *out) -{ - const ucl_object_t *metric, *score, - *required_score, *is_spam, *elt; - ucl_object_iter_t iter = NULL; - - metric = ucl_object_find_key (top, DEFAULT_METRIC); - if (metric != NULL) { - score = ucl_object_find_key (metric, "score"); - required_score = ucl_object_find_key (metric, "required_score"); - is_spam = ucl_object_find_key (metric, "is_spam"); - g_string_append_printf (out, "Metric: default; %s; %.2f / %.2f / 0.0\r\n", - ucl_object_toboolean (is_spam) ? "True" : "False", - ucl_object_todouble (score), - ucl_object_todouble (required_score)); - elt = ucl_object_find_key (metric, "action"); - if (elt != NULL) { - g_string_append_printf (out, "Action: %s\r\n", - ucl_object_tostring (elt)); - } - - iter = NULL; - while ((elt = ucl_iterate_object (metric, &iter, true)) != NULL) { - if (elt->type == UCL_OBJECT) { - const ucl_object_t *sym_score; - sym_score = ucl_object_find_key (elt, "score"); - g_string_append_printf (out, "Symbol: %s(%.2f)\r\n", - ucl_object_key (elt), - ucl_object_todouble (sym_score)); - } - } - - elt = ucl_object_find_key (metric, "subject"); - if (elt != NULL) { - g_string_append_printf (out, "Subject: %s\r\n", - ucl_object_tostring (elt)); - } - } - g_string_append_printf (out, "Message-ID: %s\r\n", task->message_id); -} - -void -rspamd_protocol_http_reply (struct rspamd_http_message *msg, struct rspamd_task *task) -{ - GString *logbuf; - struct metric_result *metric_res; - GHashTableIter hiter; - gpointer h, v; - ucl_object_t *top = NULL, *obj; - - /* Output the first line - check status */ - logbuf = g_string_sized_new (BUFSIZ); - rspamd_printf_gstring (logbuf, "id: <%s>, qid: <%s>, ", task->message_id, task->queue_id); - - if (task->user) { - rspamd_printf_gstring (logbuf, "user: %s, ", task->user); - } - - if (!task->no_log) { - rspamd_roll_history_update (task->worker->srv->history, task); - } - g_hash_table_iter_init (&hiter, task->results); - - top = ucl_object_typed_new (UCL_OBJECT); - /* Convert results to an ucl object */ - while (g_hash_table_iter_next (&hiter, &h, &v)) { - metric_res = (struct metric_result *)v; - obj = rspamd_metric_result_ucl (task, metric_res, logbuf); - ucl_object_insert_key (top, obj, h, 0, false); - } - - if (task->messages != NULL) { - ucl_object_insert_key (top, rspamd_str_list_ucl (task->messages), "messages", 0, false); - } - if (g_tree_nnodes (task->urls) > 0) { - ucl_object_insert_key (top, rspamd_urls_tree_ucl (task->urls, task), "urls", 0, false); - } - if (g_tree_nnodes (task->emails) > 0) { - ucl_object_insert_key (top, rspamd_emails_tree_ucl (task->emails, task), - "emails", 0, false); - } - - ucl_object_insert_key (top, ucl_object_fromstring (task->message_id), - "message-id", 0, false); - - write_hashes_to_log (task, logbuf); - if (!task->no_log) { - msg_info ("%v", logbuf); - } - g_string_free (logbuf, TRUE); - - msg->body = g_string_sized_new (BUFSIZ); - - if (msg->method < HTTP_SYMBOLS) { - rspamd_ucl_emit_gstring (top, UCL_EMIT_JSON_COMPACT, msg->body); - } - else { - rspamd_ucl_tolegacy_output (task, top, msg->body); - } - ucl_object_unref (top); - - /* Increase counters */ - task->worker->srv->stat->messages_scanned++; -} - -void -rspamd_protocol_write_reply (struct rspamd_task *task) -{ - struct rspamd_http_message *msg; - const gchar *ctype = "application/json"; - ucl_object_t *top = NULL; - - msg = rspamd_http_new_message (HTTP_RESPONSE); - if (!task->is_json) { - /* Turn compatibility on */ - msg->method = HTTP_SYMBOLS; - } - msg->date = time (NULL); - - task->state = CLOSING_CONNECTION; - - top = ucl_object_typed_new (UCL_OBJECT); - debug_task ("writing reply to client"); - if (task->error_code != 0) { - msg->code = 500 + task->error_code % 100; - msg->status = g_string_new (task->last_error); - ucl_object_insert_key (top, ucl_object_fromstring (task->last_error), - "error", 0, false); - msg->body = g_string_sized_new (256); - rspamd_ucl_emit_gstring (top, UCL_EMIT_JSON_COMPACT, msg->body); - ucl_object_unref (top); - } - else { - switch (task->cmd) { - case CMD_REPORT_IFSPAM: - case CMD_REPORT: - case CMD_CHECK: - case CMD_SYMBOLS: - case CMD_PROCESS: - case CMD_SKIP: - rspamd_protocol_http_reply (msg, task); - break; - case CMD_PING: - msg->body = g_string_new ("pong"); - break; - case CMD_OTHER: - msg_err ("BROKEN"); - break; - } - } - - rspamd_http_connection_reset (task->http_conn); - rspamd_http_connection_write_message (task->http_conn, msg, NULL, - ctype, task, task->sock, &task->tv, task->ev_base); -} - -void -register_protocol_command (const gchar *name, protocol_reply_func func) -{ - struct custom_command *cmd; - - cmd = g_malloc (sizeof (struct custom_command)); - cmd->name = name; - cmd->func = func; - - custom_commands = g_list_prepend (custom_commands, cmd); -} diff --git a/src/libmime/protocol.h b/src/libmime/protocol.h deleted file mode 100644 index b3643ac7a..000000000 --- a/src/libmime/protocol.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @file protocol.h - * Rspamd protocol definition - */ - -#ifndef RSPAMD_PROTOCOL_H -#define RSPAMD_PROTOCOL_H - -#include "config.h" -#include "filter.h" -#include "http.h" -#include "task.h" - -#define RSPAMD_BASE_ERROR 500 -#define RSPAMD_FILTER_ERROR RSPAMD_BASE_ERROR + 1 -#define RSPAMD_NETWORK_ERROR RSPAMD_BASE_ERROR + 2 -#define RSPAMD_PROTOCOL_ERROR RSPAMD_BASE_ERROR + 3 -#define RSPAMD_LENGTH_ERROR RSPAMD_BASE_ERROR + 4 -#define RSPAMD_STATFILE_ERROR RSPAMD_BASE_ERROR + 5 - -struct metric; - -/** - * Process HTTP request to the task structure - * @param task - * @param msg - * @return - */ -gboolean rspamd_protocol_handle_request (struct rspamd_task *task, struct rspamd_http_message *msg); - -/** - * Write task results to http message - * @param msg - * @param task - */ -void rspamd_protocol_http_reply (struct rspamd_http_message *msg, struct rspamd_task *task); - -/** - * Write reply for specified task command - * @param task task object - * @return 0 if we wrote reply and -1 if there was some error - */ -void rspamd_protocol_write_reply (struct rspamd_task *task); - - -/** - * Register custom fucntion to extend protocol - * @param name symbolic name of custom function - * @param func callback function for writing reply - */ -void register_protocol_command (const gchar *name, protocol_reply_func func); - -#endif diff --git a/src/libserver/CMakeLists.txt b/src/libserver/CMakeLists.txt index 99a4debb2..b46a88ffd 100644 --- a/src/libserver/CMakeLists.txt +++ b/src/libserver/CMakeLists.txt @@ -9,6 +9,7 @@ SET(LIBRSPAMDSERVERSRC dynamic_cfg.c events.c html.c + protocol.c proxy.c roll_history.c settings.c diff --git a/src/libserver/protocol.c b/src/libserver/protocol.c new file mode 100644 index 000000000..8f42a08ce --- /dev/null +++ b/src/libserver/protocol.c @@ -0,0 +1,825 @@ +/* + * 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 "main.h" +#include "util.h" +#include "cfg_file.h" +#include "settings.h" +#include "message.h" + +/* Max line size */ +#define OUTBUFSIZ BUFSIZ +/* + * Just check if the passed message is spam or not and reply as + * described below + */ +#define MSG_CMD_CHECK "check" +/* + * Check if message is spam or not, and return score plus list + * of symbols hit + */ +#define MSG_CMD_SYMBOLS "symbols" +/* + * Check if message is spam or not, and return score plus report + */ +#define MSG_CMD_REPORT "report" +/* + * Check if message is spam or not, and return score plus report + * if the message is spam + */ +#define MSG_CMD_REPORT_IFSPAM "report_ifspam" +/* + * Ignore this message -- client opened connection then changed + */ +#define MSG_CMD_SKIP "skip" +/* + * Return a confirmation that spamd is alive + */ +#define MSG_CMD_PING "ping" +/* + * Process this message as described above and return modified message + */ +#define MSG_CMD_PROCESS "process" + +/* + * Learn specified statfile using message + */ +#define MSG_CMD_LEARN "learn" + +/* + * spamassassin greeting: + */ +#define SPAMC_GREETING "SPAMC" +/* + * rspamd greeting: + */ +#define RSPAMC_GREETING "RSPAMC" +/* + * Headers + */ +#define CONTENT_LENGTH_HEADER "Content-length" +#define HELO_HEADER "Helo" +#define FROM_HEADER "From" +#define IP_ADDR_HEADER "IP" +#define NRCPT_HEADER "Recipient-Number" +#define RCPT_HEADER "Rcpt" +#define SUBJECT_HEADER "Subject" +#define STATFILE_HEADER "Statfile" +#define QUEUE_ID_HEADER "Queue-ID" +#define ERROR_HEADER "Error" +#define USER_HEADER "User" +#define PASS_HEADER "Pass" +#define JSON_HEADER "Json" +#define HOSTNAME_HEADER "Hostname" +#define DELIVER_TO_HEADER "Deliver-To" +#define NO_LOG_HEADER "Log" + +static GList *custom_commands = NULL; + + +/* + * Remove <> from the fixed string and copy it to the pool + */ +static gchar * +rspamd_protocol_escape_braces (GString *in) +{ + gint len = 0; + gchar *orig, *p; + + orig = in->str; + while ((g_ascii_isspace (*orig) || *orig == '<') && orig - in->str < (gint)in->len) { + orig ++; + } + + g_string_erase (in, 0, orig - in->str); + + p = in->str; + while ((!g_ascii_isspace (*p) && *p != '>') && p - in->str < (gint)in->len) { + p ++; + len ++; + } + + g_string_truncate (in, len); + + return in->str; +} + +static gboolean +rspamd_protocol_handle_url (struct rspamd_task *task, struct rspamd_http_message *msg) +{ + GList *cur; + struct custom_command *cmd; + const gchar *p; + + if (msg->url == NULL || msg->url->len == 0) { + task->last_error = "command is absent"; + task->error_code = 400; + return FALSE; + } + + if (msg->url->str[0] == '/') { + p = &msg->url->str[1]; + } + else { + p = msg->url->str; + } + + switch (*p) { + case 'c': + case 'C': + /* check */ + if (g_ascii_strcasecmp (p + 1, MSG_CMD_CHECK + 1) == 0) { + task->cmd = CMD_CHECK; + } + else { + goto err; + } + break; + case 's': + case 'S': + /* symbols, skip */ + if (g_ascii_strcasecmp (p + 1, MSG_CMD_SYMBOLS + 1) == 0) { + task->cmd = CMD_SYMBOLS; + } + else if (g_ascii_strcasecmp (p + 1, MSG_CMD_SKIP + 1) == 0) { + task->cmd = CMD_SKIP; + } + else { + goto err; + } + break; + case 'p': + case 'P': + /* ping, process */ + if (g_ascii_strcasecmp (p + 1, MSG_CMD_PING + 1) == 0) { + task->cmd = CMD_PING; + } + else if (g_ascii_strcasecmp (p + 1, MSG_CMD_PROCESS + 1) == 0) { + task->cmd = CMD_PROCESS; + } + else { + goto err; + } + break; + case 'r': + case 'R': + /* report, report_ifspam */ + if (g_ascii_strcasecmp (p + 1, MSG_CMD_REPORT + 1) == 0) { + task->cmd = CMD_REPORT; + } + else if (g_ascii_strcasecmp (p + 1, MSG_CMD_REPORT_IFSPAM + 1) == 0) { + task->cmd = CMD_REPORT_IFSPAM; + } + else { + goto err; + } + break; + default: + cur = custom_commands; + while (cur) { + cmd = cur->data; + if (g_ascii_strcasecmp (p, cmd->name) == 0) { + task->cmd = CMD_OTHER; + task->custom_cmd = cmd; + break; + } + cur = g_list_next (cur); + } + + if (cur == NULL) { + goto err; + } + break; + } + + return TRUE; + +err: + debug_task ("bad command: %s", p); + task->last_error = "invalid command"; + task->error_code = 400; + return FALSE; +} + +static gboolean +rspamd_protocol_handle_headers (struct rspamd_task *task, struct rspamd_http_message *msg) +{ + gchar *headern, *err, *tmp; + gboolean res = TRUE; + struct rspamd_http_header *h; + + LL_FOREACH (msg->headers, h) { + headern = h->name->str; + + switch (headern[0]) { + case 'd': + case 'D': + if (g_ascii_strcasecmp (headern, DELIVER_TO_HEADER) == 0) { + task->deliver_to = rspamd_protocol_escape_braces (h->value); + debug_task ("read deliver-to header, value: %s", task->deliver_to); + } + else { + debug_task ("wrong header: %s", headern); + res = FALSE; + } + break; + case 'h': + case 'H': + if (g_ascii_strcasecmp (headern, HELO_HEADER) == 0) { + task->helo = h->value->str; + debug_task ("read helo header, value: %s", task->helo); + } + else if (g_ascii_strcasecmp (headern, HOSTNAME_HEADER) == 0) { + task->hostname = h->value->str; + debug_task ("read hostname header, value: %s", task->hostname); + } + else { + debug_task ("wrong header: %s", headern); + res = FALSE; + } + break; + case 'f': + case 'F': + if (g_ascii_strcasecmp (headern, FROM_HEADER) == 0) { + task->from = rspamd_protocol_escape_braces (h->value); + debug_task ("read from header, value: %s", task->from); + } + else { + debug_task ("wrong header: %s", headern); + res = FALSE; + } + break; + case 'j': + case 'J': + if (g_ascii_strcasecmp (headern, JSON_HEADER) == 0) { + task->is_json = rspamd_config_parse_flag (h->value->str); + } + else { + debug_task ("wrong header: %s", headern); + res = FALSE; + } + break; + case 'q': + case 'Q': + if (g_ascii_strcasecmp (headern, QUEUE_ID_HEADER) == 0) { + task->queue_id = h->value->str; + debug_task ("read queue_id header, value: %s", task->queue_id); + } + else { + debug_task ("wrong header: %s", headern); + res = FALSE; + } + break; + case 'r': + case 'R': + if (g_ascii_strcasecmp (headern, RCPT_HEADER) == 0) { + tmp = rspamd_protocol_escape_braces (h->value); + task->rcpt = g_list_prepend (task->rcpt, tmp); + debug_task ("read rcpt header, value: %s", tmp); + } + else if (g_ascii_strcasecmp (headern, NRCPT_HEADER) == 0) { + task->nrcpt = strtoul (h->value->str, &err, 10); + debug_task ("read rcpt header, value: %d", (gint)task->nrcpt); + } + else { + msg_info ("wrong header: %s", headern); + res = FALSE; + } + break; + case 'i': + case 'I': + if (g_ascii_strcasecmp (headern, IP_ADDR_HEADER) == 0) { + tmp = h->value->str; + if (!rspamd_parse_inet_address (&task->from_addr, tmp)) { + msg_err ("bad ip header: '%s'", tmp); + return FALSE; + } + debug_task ("read IP header, value: %s", tmp); + } + else { + debug_task ("wrong header: %s", headern); + res = FALSE; + } + break; + case 'p': + case 'P': + if (g_ascii_strcasecmp (headern, PASS_HEADER) == 0) { + if (h->value->len == sizeof ("all") - 1 && + g_ascii_strcasecmp (h->value->str, "all") == 0) { + task->pass_all_filters = TRUE; + debug_task ("pass all filters"); + } + } + else { + res = FALSE; + } + break; + case 's': + case 'S': + if (g_ascii_strcasecmp (headern, SUBJECT_HEADER) == 0) { + task->subject = h->value->str; + } + else { + res = FALSE; + } + break; + case 'u': + case 'U': + if (g_ascii_strcasecmp (headern, USER_HEADER) == 0) { + task->user = h->value->str; + } + else { + res = FALSE; + } + break; + case 'l': + case 'L': + if (g_ascii_strcasecmp (headern, NO_LOG_HEADER) == 0) { + if (g_ascii_strcasecmp (h->value->str, "no") == 0) { + task->no_log = TRUE; + } + } + else { + res = FALSE; + } + break; + default: + debug_task ("wrong header: %s", headern); + res = FALSE; + break; + } + } + + if (!res && task->cfg->strict_protocol_headers) { + msg_err ("deny processing of a request with incorrect or unknown headers"); + task->last_error = "invalid header"; + task->error_code = 400; + return FALSE; + } + + return TRUE; +} + +gboolean +rspamd_protocol_handle_request (struct rspamd_task *task, + struct rspamd_http_message *msg) +{ + gboolean ret = TRUE; + + if (msg->method == HTTP_SYMBOLS) { + task->cmd = CMD_SYMBOLS; + task->is_json = FALSE; + } + else if (msg->method == HTTP_CHECK) { + task->cmd = CMD_CHECK; + task->is_json = FALSE; + } + else { + task->is_json = TRUE; + ret = rspamd_protocol_handle_url (task, msg); + } + + if (ret) { + ret = rspamd_protocol_handle_headers (task, msg); + } + + return ret; +} + +static void +write_hashes_to_log (struct rspamd_task *task, GString *logbuf) +{ + GList *cur; + struct mime_text_part *text_part; + + cur = task->text_parts; + + while (cur) { + text_part = cur->data; + if (text_part->fuzzy) { + if (cur->next != NULL) { + rspamd_printf_gstring (logbuf, " part: %Xd,", text_part->fuzzy->h); + } + else { + rspamd_printf_gstring (logbuf, " part: %Xd", text_part->fuzzy->h); + } + } + cur = g_list_next (cur); + } +} + + +/* Structure for writing tree data */ +struct tree_cb_data { + ucl_object_t *top; + struct rspamd_task *task; +}; + +/* + * Callback for writing urls + */ +static gboolean +urls_protocol_cb (gpointer key, gpointer value, gpointer ud) +{ + struct tree_cb_data *cb = ud; + struct uri *url = value; + ucl_object_t *obj; + + obj = ucl_object_fromlstring (url->host, url->hostlen); + DL_APPEND (cb->top->value.av, obj); + + if (cb->task->cfg->log_urls) { + msg_info ("<%s> URL: %s - %s: %s", cb->task->message_id, cb->task->user ? + cb->task->user : (cb->task->from ? cb->task->from : "unknown"), + rspamd_inet_address_to_string (&cb->task->from_addr), + struri (url)); + } + + return FALSE; +} + +static ucl_object_t * +rspamd_urls_tree_ucl (GTree *input, struct rspamd_task *task) +{ + struct tree_cb_data cb; + ucl_object_t *obj; + + obj = ucl_object_typed_new (UCL_ARRAY); + cb.top = obj; + cb.task = task; + + g_tree_foreach (input, urls_protocol_cb, &cb); + + return obj; +} + +static gboolean +emails_protocol_cb (gpointer key, gpointer value, gpointer ud) +{ + struct tree_cb_data *cb = ud; + struct uri *url = value; + ucl_object_t *obj; + + obj = ucl_object_fromlstring (url->user, url->userlen + url->hostlen + 1); + DL_APPEND (cb->top->value.av, obj); + + return FALSE; +} + +static ucl_object_t * +rspamd_emails_tree_ucl (GTree *input, struct rspamd_task *task) +{ + struct tree_cb_data cb; + ucl_object_t *obj; + + obj = ucl_object_typed_new (UCL_ARRAY); + cb.top = obj; + cb.task = task; + + g_tree_foreach (input, emails_protocol_cb, &cb); + + return obj; +} + + +/* Write new subject */ +static const gchar * +make_rewritten_subject (struct metric *metric, struct rspamd_task *task) +{ + static gchar subj_buf[1024]; + gchar *p = subj_buf, *end, *c, *res; + const gchar *s; + + end = p + sizeof(subj_buf); + c = metric->subject; + s = g_mime_message_get_subject (task->message); + + while (p < end) { + if (*c == '\0') { + *p = '\0'; + break; + } + else if (*c == '%' && *(c + 1) == 's') { + p += rspamd_strlcpy (p, (s != NULL) ? s : "", end - p); + c += 2; + } + else { + *p = *c ++; + } + p ++; + } + res = g_mime_utils_header_encode_text (subj_buf); + + rspamd_mempool_add_destructor (task->task_pool, (rspamd_mempool_destruct_t)g_free, res); + + return res; +} + +static ucl_object_t * +rspamd_str_list_ucl (GList *str_list) +{ + ucl_object_t *top = NULL, *obj; + GList *cur; + + top = ucl_object_typed_new (UCL_ARRAY); + cur = str_list; + while (cur) { + obj = ucl_object_fromstring (cur->data); + DL_APPEND (top->value.av, obj); + cur = g_list_next (cur); + } + + return top; +} + +static ucl_object_t * +rspamd_metric_symbol_ucl (struct rspamd_task *task, struct metric *m, + struct symbol *sym, GString *logbuf) +{ + ucl_object_t *obj = NULL; + const gchar *description = NULL; + + rspamd_printf_gstring (logbuf, "%s,", sym->name); + description = g_hash_table_lookup (m->descriptions, sym->name); + + obj = ucl_object_typed_new (UCL_OBJECT); + ucl_object_insert_key (obj, ucl_object_fromstring (sym->name), "name", 0, false); + ucl_object_insert_key (obj, ucl_object_fromdouble (sym->score), "score", 0, false); + if (description) { + ucl_object_insert_key (obj, ucl_object_fromstring (description), "description", 0, false); + } + if (sym->options != NULL) { + ucl_object_insert_key (obj, rspamd_str_list_ucl (sym->options), "options", 0, false); + } + + return obj; +} + +static ucl_object_t * +rspamd_metric_result_ucl (struct rspamd_task *task, struct metric_result *mres, GString *logbuf) +{ + GHashTableIter hiter; + struct symbol *sym; + struct metric *m; + gboolean is_spam; + enum rspamd_metric_action action = METRIC_ACTION_NOACTION; + ucl_object_t *obj = NULL, *sobj; + gdouble required_score; + gpointer h, v; + const gchar *subject; + gchar action_char; + + m = mres->metric; + + /* XXX: handle settings */ + required_score = m->actions[METRIC_ACTION_REJECT].score; + is_spam = (mres->score >= required_score); + action = check_metric_action (mres->score, required_score, m); + if (task->is_skipped) { + action_char = 'S'; + } + else if (is_spam) { + action_char = 'T'; + } + else { + action_char = 'F'; + } + rspamd_printf_gstring (logbuf, "(%s: %c (%s): [%.2f/%.2f] [", + m->name, action_char, + str_action_metric (action), + mres->score, required_score); + + obj = ucl_object_typed_new (UCL_OBJECT); + ucl_object_insert_key (obj, ucl_object_frombool (is_spam), + "is_spam", 0, false); + ucl_object_insert_key (obj, ucl_object_frombool (task->is_skipped), + "is_skipped", 0, false); + ucl_object_insert_key (obj, ucl_object_fromdouble (mres->score), + "score", 0, false); + ucl_object_insert_key (obj, ucl_object_fromdouble (required_score), + "required_score", 0, false); + ucl_object_insert_key (obj, ucl_object_fromstring (str_action_metric (action)), + "action", 0, false); + + if (action == METRIC_ACTION_REWRITE_SUBJECT) { + subject = make_rewritten_subject (m, task); + ucl_object_insert_key (obj, ucl_object_fromstring (subject), + "subject", 0, false); + } + /* Now handle symbols */ + g_hash_table_iter_init (&hiter, mres->symbols); + while (g_hash_table_iter_next (&hiter, &h, &v)) { + sym = (struct symbol *)v; + sobj = rspamd_metric_symbol_ucl (task, m, sym, logbuf); + ucl_object_insert_key (obj, sobj, h, 0, false); + } + + /* Cut the trailing comma if needed */ + if (logbuf->str[logbuf->len - 1] == ',') { + logbuf->len --; + } + +#ifdef HAVE_CLOCK_GETTIME + rspamd_printf_gstring (logbuf, "]), len: %z, time: %s, dns req: %d,", + task->msg->len, calculate_check_time (&task->tv, &task->ts, + task->cfg->clock_res, &task->scan_milliseconds), task->dns_requests); +#else + rspamd_printf_gstring (logbuf, "]), len: %z, time: %s, dns req: %d,", + task->msg->len, + calculate_check_time (&task->tv, task->cfg->clock_res, &task->scan_milliseconds), + task->dns_requests); +#endif + + return obj; +} + +static void +rspamd_ucl_tolegacy_output (struct rspamd_task *task, ucl_object_t *top, GString *out) +{ + const ucl_object_t *metric, *score, + *required_score, *is_spam, *elt; + ucl_object_iter_t iter = NULL; + + metric = ucl_object_find_key (top, DEFAULT_METRIC); + if (metric != NULL) { + score = ucl_object_find_key (metric, "score"); + required_score = ucl_object_find_key (metric, "required_score"); + is_spam = ucl_object_find_key (metric, "is_spam"); + g_string_append_printf (out, "Metric: default; %s; %.2f / %.2f / 0.0\r\n", + ucl_object_toboolean (is_spam) ? "True" : "False", + ucl_object_todouble (score), + ucl_object_todouble (required_score)); + elt = ucl_object_find_key (metric, "action"); + if (elt != NULL) { + g_string_append_printf (out, "Action: %s\r\n", + ucl_object_tostring (elt)); + } + + iter = NULL; + while ((elt = ucl_iterate_object (metric, &iter, true)) != NULL) { + if (elt->type == UCL_OBJECT) { + const ucl_object_t *sym_score; + sym_score = ucl_object_find_key (elt, "score"); + g_string_append_printf (out, "Symbol: %s(%.2f)\r\n", + ucl_object_key (elt), + ucl_object_todouble (sym_score)); + } + } + + elt = ucl_object_find_key (metric, "subject"); + if (elt != NULL) { + g_string_append_printf (out, "Subject: %s\r\n", + ucl_object_tostring (elt)); + } + } + g_string_append_printf (out, "Message-ID: %s\r\n", task->message_id); +} + +void +rspamd_protocol_http_reply (struct rspamd_http_message *msg, struct rspamd_task *task) +{ + GString *logbuf; + struct metric_result *metric_res; + GHashTableIter hiter; + gpointer h, v; + ucl_object_t *top = NULL, *obj; + + /* Output the first line - check status */ + logbuf = g_string_sized_new (BUFSIZ); + rspamd_printf_gstring (logbuf, "id: <%s>, qid: <%s>, ", task->message_id, task->queue_id); + + if (task->user) { + rspamd_printf_gstring (logbuf, "user: %s, ", task->user); + } + + if (!task->no_log) { + rspamd_roll_history_update (task->worker->srv->history, task); + } + g_hash_table_iter_init (&hiter, task->results); + + top = ucl_object_typed_new (UCL_OBJECT); + /* Convert results to an ucl object */ + while (g_hash_table_iter_next (&hiter, &h, &v)) { + metric_res = (struct metric_result *)v; + obj = rspamd_metric_result_ucl (task, metric_res, logbuf); + ucl_object_insert_key (top, obj, h, 0, false); + } + + if (task->messages != NULL) { + ucl_object_insert_key (top, rspamd_str_list_ucl (task->messages), "messages", 0, false); + } + if (g_tree_nnodes (task->urls) > 0) { + ucl_object_insert_key (top, rspamd_urls_tree_ucl (task->urls, task), "urls", 0, false); + } + if (g_tree_nnodes (task->emails) > 0) { + ucl_object_insert_key (top, rspamd_emails_tree_ucl (task->emails, task), + "emails", 0, false); + } + + ucl_object_insert_key (top, ucl_object_fromstring (task->message_id), + "message-id", 0, false); + + write_hashes_to_log (task, logbuf); + if (!task->no_log) { + msg_info ("%v", logbuf); + } + g_string_free (logbuf, TRUE); + + msg->body = g_string_sized_new (BUFSIZ); + + if (msg->method < HTTP_SYMBOLS) { + rspamd_ucl_emit_gstring (top, UCL_EMIT_JSON_COMPACT, msg->body); + } + else { + rspamd_ucl_tolegacy_output (task, top, msg->body); + } + ucl_object_unref (top); + + /* Increase counters */ + task->worker->srv->stat->messages_scanned++; +} + +void +rspamd_protocol_write_reply (struct rspamd_task *task) +{ + struct rspamd_http_message *msg; + const gchar *ctype = "application/json"; + ucl_object_t *top = NULL; + + msg = rspamd_http_new_message (HTTP_RESPONSE); + if (!task->is_json) { + /* Turn compatibility on */ + msg->method = HTTP_SYMBOLS; + } + msg->date = time (NULL); + + task->state = CLOSING_CONNECTION; + + top = ucl_object_typed_new (UCL_OBJECT); + debug_task ("writing reply to client"); + if (task->error_code != 0) { + msg->code = 500 + task->error_code % 100; + msg->status = g_string_new (task->last_error); + ucl_object_insert_key (top, ucl_object_fromstring (task->last_error), + "error", 0, false); + msg->body = g_string_sized_new (256); + rspamd_ucl_emit_gstring (top, UCL_EMIT_JSON_COMPACT, msg->body); + ucl_object_unref (top); + } + else { + switch (task->cmd) { + case CMD_REPORT_IFSPAM: + case CMD_REPORT: + case CMD_CHECK: + case CMD_SYMBOLS: + case CMD_PROCESS: + case CMD_SKIP: + rspamd_protocol_http_reply (msg, task); + break; + case CMD_PING: + msg->body = g_string_new ("pong"); + break; + case CMD_OTHER: + msg_err ("BROKEN"); + break; + } + } + + rspamd_http_connection_reset (task->http_conn); + rspamd_http_connection_write_message (task->http_conn, msg, NULL, + ctype, task, task->sock, &task->tv, task->ev_base); +} + +void +register_protocol_command (const gchar *name, protocol_reply_func func) +{ + struct custom_command *cmd; + + cmd = g_malloc (sizeof (struct custom_command)); + cmd->name = name; + cmd->func = func; + + custom_commands = g_list_prepend (custom_commands, cmd); +} diff --git a/src/libserver/protocol.h b/src/libserver/protocol.h new file mode 100644 index 000000000..b3643ac7a --- /dev/null +++ b/src/libserver/protocol.h @@ -0,0 +1,53 @@ +/** + * @file protocol.h + * Rspamd protocol definition + */ + +#ifndef RSPAMD_PROTOCOL_H +#define RSPAMD_PROTOCOL_H + +#include "config.h" +#include "filter.h" +#include "http.h" +#include "task.h" + +#define RSPAMD_BASE_ERROR 500 +#define RSPAMD_FILTER_ERROR RSPAMD_BASE_ERROR + 1 +#define RSPAMD_NETWORK_ERROR RSPAMD_BASE_ERROR + 2 +#define RSPAMD_PROTOCOL_ERROR RSPAMD_BASE_ERROR + 3 +#define RSPAMD_LENGTH_ERROR RSPAMD_BASE_ERROR + 4 +#define RSPAMD_STATFILE_ERROR RSPAMD_BASE_ERROR + 5 + +struct metric; + +/** + * Process HTTP request to the task structure + * @param task + * @param msg + * @return + */ +gboolean rspamd_protocol_handle_request (struct rspamd_task *task, struct rspamd_http_message *msg); + +/** + * Write task results to http message + * @param msg + * @param task + */ +void rspamd_protocol_http_reply (struct rspamd_http_message *msg, struct rspamd_task *task); + +/** + * Write reply for specified task command + * @param task task object + * @return 0 if we wrote reply and -1 if there was some error + */ +void rspamd_protocol_write_reply (struct rspamd_task *task); + + +/** + * Register custom fucntion to extend protocol + * @param name symbolic name of custom function + * @param func callback function for writing reply + */ +void register_protocol_command (const gchar *name, protocol_reply_func func); + +#endif -- cgit v1.2.3