filter.c
images.c
message.c
- protocol.c
smtp_utils.c
smtp_proto.c)
+++ /dev/null
-/*
- * 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);
-}
+++ /dev/null
-/**
- * @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
dynamic_cfg.c
events.c
html.c
+ protocol.c
proxy.c
roll_history.c
settings.c
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/**
+ * @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