]> source.dussan.org Git - rspamd.git/commitdiff
Move protocol functions to libserver.
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Fri, 2 May 2014 11:16:43 +0000 (12:16 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Fri, 2 May 2014 11:16:43 +0000 (12:16 +0100)
src/libmime/CMakeLists.txt
src/libmime/protocol.c [deleted file]
src/libmime/protocol.h [deleted file]
src/libserver/CMakeLists.txt
src/libserver/protocol.c [new file with mode: 0644]
src/libserver/protocol.h [new file with mode: 0644]

index e612dce192f62c6e94c5fe4ea1380ad0662e0672..08efdba095d3b8e01d274e0d945fe4a40700f457 100644 (file)
@@ -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 (file)
index 8f42a08..0000000
+++ /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 (file)
index b3643ac..0000000
+++ /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
index 99a4debb2bf818920290625f615a9ce86827ab6a..b46a88ffdb4b282d4b4c1d5563e7427d38b915f6 100644 (file)
@@ -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 (file)
index 0000000..8f42a08
--- /dev/null
@@ -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 (file)
index 0000000..b3643ac
--- /dev/null
@@ -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