1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150 |
- /*-
- * Copyright 2016 Vsevolod Stakhov
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #include "config.h"
- #include "rspamd.h"
- #include "util.h"
- #include "cfg_file.h"
- #include "cfg_rcl.h"
- #include "message.h"
- #include "utlist.h"
- #include "http.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 URLS_HEADER "URL-Format"
- #define PASS_HEADER "Pass"
- #define JSON_HEADER "Json"
- #define HOSTNAME_HEADER "Hostname"
- #define DELIVER_TO_HEADER "Deliver-To"
- #define NO_LOG_HEADER "Log"
- #define MLEN_HEADER "Message-Length"
-
- static GList *custom_commands = NULL;
-
- static GQuark
- rspamd_protocol_quark (void)
- {
- return g_quark_from_static_string ("protocol-error");
- }
-
- /*
- * Remove <> from the fixed string and copy it to the pool
- */
- static gchar *
- rspamd_protocol_escape_braces (struct rspamd_task *task, rspamd_fstring_t *in)
- {
- guint nchars = 0;
- const gchar *p;
- rspamd_ftok_t tok;
-
- g_assert (in != NULL);
- g_assert (in->len > 0);
-
- p = in->str;
-
- while ((g_ascii_isspace (*p) || *p == '<') && nchars < in->len) {
- p++;
- nchars ++;
- }
-
- tok.begin = p;
-
- p = in->str + in->len - 1;
- tok.len = in->len - nchars;
-
- while ((!g_ascii_isspace (*p) && *p !=
- '>') && tok.len > 0) {
- p--;
- tok.len --;
- }
-
- return rspamd_mempool_ftokdup (task->task_pool, &tok);
- }
-
- static gboolean
- rspamd_protocol_handle_url (struct rspamd_task *task,
- struct rspamd_http_message *msg)
- {
- GList *cur;
- GHashTable *query_args;
- GHashTableIter it;
- struct custom_command *cmd;
- struct http_parser_url u;
- const gchar *p;
- gsize pathlen;
- rspamd_ftok_t *key, *value;
- gpointer k, v;
-
- if (msg->url == NULL || msg->url->len == 0) {
- g_set_error (&task->err, rspamd_protocol_quark(), 400, "missing command");
- return FALSE;
- }
-
- if (http_parser_parse_url (msg->url->str, msg->url->len, 0, &u) != 0) {
- g_set_error (&task->err, rspamd_protocol_quark(), 400, "bad request URL");
-
- return FALSE;
- }
-
- if (!(u.field_set & (1 << UF_PATH))) {
- g_set_error (&task->err, rspamd_protocol_quark(), 400,
- "bad request URL: missing path");
-
- return FALSE;
- }
-
- p = msg->url->str + u.field_data[UF_PATH].off;
- pathlen = u.field_data[UF_PATH].len;
-
- if (*p == '/') {
- p ++;
- pathlen --;
- }
-
- switch (*p) {
- case 'c':
- case 'C':
- /* check */
- if (g_ascii_strncasecmp (p, MSG_CMD_CHECK, pathlen) == 0) {
- task->cmd = CMD_CHECK;
- }
- else {
- goto err;
- }
- break;
- case 's':
- case 'S':
- /* symbols, skip */
- if (g_ascii_strncasecmp (p, MSG_CMD_SYMBOLS, pathlen) == 0) {
- task->cmd = CMD_SYMBOLS;
- }
- else if (g_ascii_strncasecmp (p, MSG_CMD_SKIP, pathlen) == 0) {
- task->cmd = CMD_SKIP;
- }
- else {
- goto err;
- }
- break;
- case 'p':
- case 'P':
- /* ping, process */
- if (g_ascii_strncasecmp (p, MSG_CMD_PING, pathlen) == 0) {
- task->cmd = CMD_PING;
- }
- else if (g_ascii_strncasecmp (p, MSG_CMD_PROCESS, pathlen) == 0) {
- task->cmd = CMD_PROCESS;
- }
- else {
- goto err;
- }
- break;
- case 'r':
- case 'R':
- /* report, report_ifspam */
- if (g_ascii_strncasecmp (p, MSG_CMD_REPORT, pathlen) == 0) {
- task->cmd = CMD_REPORT;
- }
- else if (g_ascii_strncasecmp (p, MSG_CMD_REPORT_IFSPAM,
- pathlen) == 0) {
- task->cmd = CMD_REPORT_IFSPAM;
- }
- else {
- goto err;
- }
- break;
- default:
- cur = custom_commands;
- while (cur) {
- cmd = cur->data;
- if (g_ascii_strncasecmp (p, cmd->name, pathlen) == 0) {
- task->cmd = CMD_OTHER;
- task->custom_cmd = cmd;
- break;
- }
- cur = g_list_next (cur);
- }
-
- if (cur == NULL) {
- goto err;
- }
- break;
- }
-
- if (u.field_set & (1 << UF_QUERY)) {
- /* In case if we have a query, we need to store it somewhere */
- query_args = rspamd_http_message_parse_query (msg);
-
- /* Insert the rest of query params as HTTP headers */
- g_hash_table_iter_init (&it, query_args);
-
- while (g_hash_table_iter_next (&it, &k, &v)) {
- key = k;
- value = v;
- /* Steal strings */
- g_hash_table_iter_steal (&it);
- g_hash_table_replace (task->request_headers, key, value);
- msg_debug_task ("added header \"%T\" -> \"%T\" from HTTP query",
- key, value);
- }
-
- g_hash_table_unref (query_args);
- }
-
- return TRUE;
-
- err:
- g_set_error (&task->err, rspamd_protocol_quark(), 400, "invalid command: %*.s",
- (gint)pathlen, p);
-
- return FALSE;
- }
-
- #define IF_HEADER(name) \
- srch.begin = (name); \
- srch.len = sizeof (name) - 1; \
- if (rspamd_ftok_casecmp (hn_tok, &srch) == 0)
-
- gboolean
- rspamd_protocol_handle_headers (struct rspamd_task *task,
- struct rspamd_http_message *msg)
- {
- rspamd_fstring_t *hn, *hv;
- rspamd_ftok_t *hn_tok, *hv_tok, srch;
- gboolean fl, has_ip = FALSE;
- struct rspamd_http_header *h;
-
- LL_FOREACH (msg->headers, h)
- {
- hn = rspamd_fstring_new_init (h->name->begin, h->name->len);
- hv = rspamd_fstring_new_init (h->value->begin, h->value->len);
- hn_tok = rspamd_ftok_map (hn);
- hv_tok = rspamd_ftok_map (hv);
-
- g_hash_table_replace (task->request_headers, hn_tok, hv_tok);
-
- switch (*hn_tok->begin) {
- case 'd':
- case 'D':
- IF_HEADER (DELIVER_TO_HEADER) {
- task->deliver_to = rspamd_protocol_escape_braces (task, hv);
- debug_task ("read deliver-to header, value: %s",
- task->deliver_to);
- }
- else {
- debug_task ("wrong header: %V", hn);
- }
- break;
- case 'h':
- case 'H':
- IF_HEADER (HELO_HEADER) {
- task->helo = rspamd_mempool_ftokdup (task->task_pool, hv_tok);
- debug_task ("read helo header, value: %s", task->helo);
- }
- IF_HEADER (HOSTNAME_HEADER) {
- task->hostname = rspamd_mempool_ftokdup (task->task_pool,
- hv_tok);
- debug_task ("read hostname header, value: %s", task->hostname);
- }
- break;
- case 'f':
- case 'F':
- IF_HEADER (FROM_HEADER) {
- if (!rspamd_task_add_sender (task,
- rspamd_mempool_ftokdup (task->task_pool, hv_tok))) {
- msg_err_task ("bad from header: '%V'", hv);
- }
- }
- else {
- debug_task ("wrong header: %V", hn);
- }
- break;
- case 'j':
- case 'J':
- IF_HEADER (JSON_HEADER) {
- fl = rspamd_config_parse_flag (hv->str, hv->len);
- if (fl) {
- task->flags |= RSPAMD_TASK_FLAG_JSON;
- }
- else {
- task->flags &= ~RSPAMD_TASK_FLAG_JSON;
- }
- }
- else {
- debug_task ("wrong header: %V", hn);
- }
- break;
- case 'q':
- case 'Q':
- IF_HEADER (QUEUE_ID_HEADER) {
- task->queue_id = rspamd_mempool_ftokdup (task->task_pool,
- hv_tok);
- debug_task ("read queue_id header, value: %s", task->queue_id);
- }
- else {
- debug_task ("wrong header: %V", hn);
- }
- break;
- case 'r':
- case 'R':
- IF_HEADER (RCPT_HEADER) {
- if (!rspamd_task_add_recipient (task,
- rspamd_mempool_ftokdup (task->task_pool, hv_tok))) {
- msg_err_task ("bad from header: '%T'", h->value);
- }
- debug_task ("read rcpt header, value: %V", hv);
- }
- else {
- debug_task ("wrong header: %V", hn);
- }
- break;
- case 'i':
- case 'I':
- IF_HEADER (IP_ADDR_HEADER) {
- if (!rspamd_parse_inet_address (&task->from_addr, hv->str, hv->len)) {
- msg_err_task ("bad ip header: '%V'", hv);
- return FALSE;
- }
- debug_task ("read IP header, value: %V", hv);
- has_ip = TRUE;
- }
- else {
- debug_task ("wrong header: %V", hn);
- }
- break;
- case 'p':
- case 'P':
- IF_HEADER (PASS_HEADER) {
- srch.begin = "all";
- srch.len = 3;
-
- if (rspamd_ftok_casecmp (hv_tok, &srch) == 0) {
- task->flags |= RSPAMD_TASK_FLAG_PASS_ALL;
- debug_task ("pass all filters");
- }
- }
- break;
- case 's':
- case 'S':
- IF_HEADER (SUBJECT_HEADER) {
- task->subject = rspamd_mempool_ftokdup (task->task_pool, hv_tok);
- }
- break;
- case 'u':
- case 'U':
- IF_HEADER (USER_HEADER) {
- /*
- * We must ignore User header in case of spamc, as SA has
- * different meaning of this header
- */
- if (!RSPAMD_TASK_IS_SPAMC (task)) {
- task->user = rspamd_mempool_ftokdup (task->task_pool,
- hv_tok);
- }
- }
- IF_HEADER (URLS_HEADER) {
- srch.begin = "extended";
- srch.len = 8;
-
- if (rspamd_ftok_casecmp (hv_tok, &srch) == 0) {
- task->flags |= RSPAMD_TASK_FLAG_EXT_URLS;
- debug_task ("extended urls information");
- }
- }
- break;
- case 'l':
- case 'L':
- IF_HEADER (NO_LOG_HEADER) {
- srch.begin = "no";
- srch.len = 2;
-
- if (rspamd_ftok_casecmp (hv_tok, &srch) == 0) {
- task->flags |= RSPAMD_TASK_FLAG_NO_LOG;
- }
- }
- break;
- case 'm':
- case 'M':
- IF_HEADER (MLEN_HEADER) {
- if (!rspamd_strtoul (hv_tok->begin,
- hv_tok->len,
- &task->message_len)) {
- msg_err_task ("Invalid message length header: %V", hv);
- }
- else {
- task->flags |= RSPAMD_TASK_FLAG_HAS_CONTROL;
- }
- }
- break;
- default:
- debug_task ("unknown header: %V", hn);
- break;
- }
- }
-
- if (task->hostname == NULL || task->hostname[0] == '\0') {
- /* We assume that hostname is either "unknown" or existing */
- task->hostname = rspamd_mempool_strdup (task->task_pool, "unknown");
- }
-
- if (!has_ip) {
- task->flags |= RSPAMD_TASK_FLAG_NO_IP;
- }
-
- return TRUE;
- }
-
- #define BOOL_TO_FLAG(val, flags, flag) do { \
- if ((val)) (flags) |= (flag); \
- else (flags) &= ~(flag); \
- } while(0)
-
- gboolean
- rspamd_protocol_parse_task_flags (rspamd_mempool_t *pool,
- const ucl_object_t *obj,
- gpointer ud,
- struct rspamd_rcl_section *section,
- GError **err)
- {
- struct rspamd_rcl_struct_parser *pd = ud;
- gint *target;
- const gchar *key;
- gboolean value;
-
- target = (gint *)(((gchar *)pd->user_struct) + pd->offset);
- key = ucl_object_key (obj);
- value = ucl_object_toboolean (obj);
-
- if (key != NULL) {
- if (g_ascii_strcasecmp (key, "pass_all") == 0) {
- BOOL_TO_FLAG (value, *target, RSPAMD_TASK_FLAG_PASS_ALL);
- }
- else if (g_ascii_strcasecmp (key, "no_log") == 0) {
- BOOL_TO_FLAG (value, *target, RSPAMD_TASK_FLAG_NO_LOG);
- }
- }
-
- return TRUE;
- }
-
- static struct rspamd_rcl_section *control_parser = NULL;
-
- static void
- rspamd_protocol_control_parser_init (void)
- {
- struct rspamd_rcl_section *sub;
-
- if (control_parser == NULL) {
- sub = rspamd_rcl_add_section (&control_parser,
- "*",
- NULL,
- NULL,
- UCL_OBJECT,
- FALSE,
- TRUE);
- /* Default handlers */
- rspamd_rcl_add_default_handler (sub,
- "ip",
- rspamd_rcl_parse_struct_addr,
- G_STRUCT_OFFSET (struct rspamd_task, from_addr),
- 0,
- NULL);
- rspamd_rcl_add_default_handler (sub,
- "from",
- rspamd_rcl_parse_struct_mime_addr,
- G_STRUCT_OFFSET (struct rspamd_task, from_envelope),
- 0,
- NULL);
- rspamd_rcl_add_default_handler (sub,
- "rcpt",
- rspamd_rcl_parse_struct_mime_addr,
- G_STRUCT_OFFSET (struct rspamd_task, rcpt_envelope),
- 0,
- NULL);
- rspamd_rcl_add_default_handler (sub,
- "helo",
- rspamd_rcl_parse_struct_string,
- G_STRUCT_OFFSET (struct rspamd_task, helo),
- 0,
- NULL);
- rspamd_rcl_add_default_handler (sub,
- "user",
- rspamd_rcl_parse_struct_string,
- G_STRUCT_OFFSET (struct rspamd_task, user),
- 0,
- NULL);
- rspamd_rcl_add_default_handler (sub,
- "pass_all",
- rspamd_protocol_parse_task_flags,
- G_STRUCT_OFFSET (struct rspamd_task, flags),
- 0,
- NULL);
- rspamd_rcl_add_default_handler (sub,
- "json",
- rspamd_protocol_parse_task_flags,
- G_STRUCT_OFFSET (struct rspamd_task, flags),
- 0,
- NULL);
- }
- }
-
- gboolean
- rspamd_protocol_handle_control (struct rspamd_task *task,
- const ucl_object_t *control)
- {
- GError *err = NULL;
-
- rspamd_protocol_control_parser_init ();
-
- if (!rspamd_rcl_parse (control_parser, task, task->task_pool,
- control, &err)) {
- msg_warn_task ("cannot parse control block: %e", err);
- g_error_free (err);
-
- 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->flags &= ~RSPAMD_TASK_FLAG_JSON;
- }
- else if (msg->method == HTTP_CHECK) {
- task->cmd = CMD_CHECK;
- task->flags &= ~RSPAMD_TASK_FLAG_JSON;
- }
- else {
- task->flags |= RSPAMD_TASK_FLAG_JSON;
- ret = rspamd_protocol_handle_url (task, msg);
- }
-
- if (msg->flags & RSPAMD_HTTP_FLAG_SPAMC) {
- task->flags &= ~RSPAMD_TASK_FLAG_JSON;
- task->flags |= RSPAMD_TASK_FLAG_SPAMC;
- }
-
- return ret;
- }
-
- /* Structure for writing tree data */
- struct tree_cb_data {
- ucl_object_t *top;
- struct rspamd_task *task;
- };
-
- /*
- * Callback for writing urls
- */
- static void
- urls_protocol_cb (gpointer key, gpointer value, gpointer ud)
- {
- struct tree_cb_data *cb = ud;
- struct rspamd_url *url = value;
- ucl_object_t *obj, *elt;
- struct rspamd_task *task = cb->task;
- const gchar *user_field = "unknown";
- gboolean has_user = FALSE;
-
- if (!(task->flags & RSPAMD_TASK_FLAG_EXT_URLS)) {
- obj = ucl_object_fromlstring (url->string, url->urllen);
- }
- else {
- obj = ucl_object_typed_new (UCL_OBJECT);
-
- elt = ucl_object_fromlstring (url->string, url->urllen);
- ucl_object_insert_key (obj, elt, "url", 0, false);
-
- if (url->surbllen > 0) {
- elt = ucl_object_fromlstring (url->surbl, url->surbllen);
- ucl_object_insert_key (obj, elt, "surbl", 0, false);
- }
- if (url->hostlen > 0) {
- elt = ucl_object_fromlstring (url->host, url->hostlen);
- ucl_object_insert_key (obj, elt, "host", 0, false);
- }
-
- elt = ucl_object_frombool (url->flags & RSPAMD_URL_FLAG_PHISHED);
- ucl_object_insert_key (obj, elt, "phished", 0, false);
- }
-
- ucl_array_append (cb->top, obj);
-
- if (cb->task->cfg->log_urls) {
- if (task->user) {
- user_field = task->user;
- has_user = TRUE;
- }
- else if (task->from_envelope) {
- InternetAddress *ia;
-
- ia = internet_address_list_get_address (task->from_envelope, 0);
-
- if (ia && INTERNET_ADDRESS_IS_MAILBOX (ia)) {
- InternetAddressMailbox *iamb = INTERNET_ADDRESS_MAILBOX (ia);
-
- user_field = iamb->addr;
- }
- }
-
- msg_info_task ("<%s> %s: %s; ip: %s; URL: %*s",
- task->message_id,
- has_user ? "user" : "from",
- user_field,
- rspamd_inet_address_to_string (task->from_addr),
- url->urllen, url->string);
- }
- }
-
- static ucl_object_t *
- rspamd_urls_tree_ucl (GHashTable *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_hash_table_foreach (input, urls_protocol_cb, &cb);
-
- return obj;
- }
-
- static void
- emails_protocol_cb (gpointer key, gpointer value, gpointer ud)
- {
- struct tree_cb_data *cb = ud;
- struct rspamd_url *url = value;
- ucl_object_t *obj;
-
- if (url->userlen > 0 && url->hostlen > 0 &&
- url->host == url->user + url->userlen + 1) {
- obj = ucl_object_fromlstring (url->user,
- url->userlen + url->hostlen + 1);
- ucl_array_append (cb->top, obj);
- }
- }
-
- static ucl_object_t *
- rspamd_emails_tree_ucl (GHashTable *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_hash_table_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, *res;
- const gchar *s, *c;
-
- end = p + sizeof(subj_buf);
- c = metric->subject;
- if (c == NULL) {
- c = SPAM_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);
- ucl_array_append (top, 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)
- {
- ucl_object_t *obj = NULL;
- const gchar *description = NULL;
-
- if (sym->def != NULL) {
- description = sym->def->description;
- }
-
- 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)
- {
- 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;;
- gpointer h, v;
- const gchar *subject;
-
- m = mres->metric;
- mres->action = rspamd_check_action_metric (task, mres->score,
- &mres->required_score, m);
-
- action = mres->action;
- is_spam = (action == METRIC_ACTION_REJECT);
-
- 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 (RSPAMD_TASK_IS_SKIPPED (task)),
- "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 (mres->required_score),
- "required_score", 0, false);
- ucl_object_insert_key (obj,
- ucl_object_fromstring (rspamd_action_to_str (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);
- ucl_object_insert_key (obj, sobj, h, 0, false);
- }
-
- return obj;
- }
-
- static void
- rspamd_ucl_torspamc_output (struct rspamd_task *task,
- ucl_object_t *top,
- rspamd_fstring_t **out)
- {
- const ucl_object_t *metric, *score,
- *required_score, *is_spam, *elt, *cur;
- 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");
- rspamd_printf_fstring (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) {
- rspamd_printf_fstring (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");
- rspamd_printf_fstring (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) {
- rspamd_printf_fstring (out, "Subject: %s\r\n",
- ucl_object_tostring (elt));
- }
- }
-
- elt = ucl_object_find_key (top, "messages");
- if (elt != NULL) {
- iter = NULL;
- while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) {
- if (cur->type == UCL_STRING) {
- rspamd_printf_fstring (out, "Message: %s\r\n",
- ucl_object_tostring (cur));
- }
- }
- }
-
- rspamd_printf_fstring (out, "Message-ID: %s\r\n", task->message_id);
- }
-
- static void
- rspamd_ucl_tospamc_output (struct rspamd_task *task,
- ucl_object_t *top,
- rspamd_fstring_t **out)
- {
- const ucl_object_t *metric, *score,
- *required_score, *is_spam, *elt;
- ucl_object_iter_t iter = NULL;
- rspamd_fstring_t *f;
-
- 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");
- rspamd_printf_fstring (out,
- "Spam: %s ; %.2f / %.2f\r\n\r\n",
- ucl_object_toboolean (is_spam) ? "True" : "False",
- ucl_object_todouble (score),
- ucl_object_todouble (required_score));
-
- while ((elt = ucl_iterate_object (metric, &iter, true)) != NULL) {
- if (elt->type == UCL_OBJECT) {
- rspamd_printf_fstring (out, "%s,",
- ucl_object_key (elt));
- }
- }
- /* Ugly hack, but the whole spamc is ugly */
- f = *out;
- if (f->str[f->len - 1] == ',') {
- f->len --;
-
- *out = rspamd_fstring_append (*out, CRLF, 2);
- }
- }
- }
-
- ucl_object_t *
- rspamd_protocol_write_ucl (struct rspamd_task *task)
- {
- struct metric_result *metric_res;
- ucl_object_t *top = NULL, *obj;
- GHashTableIter hiter;
- gpointer h, v;
-
- 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);
- 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 (task->cfg->log_urls || (task->flags & RSPAMD_TASK_FLAG_EXT_URLS)) {
- if (g_hash_table_size (task->urls) > 0) {
- ucl_object_insert_key (top, rspamd_urls_tree_ucl (task->urls,
- task), "urls", 0, false);
- }
- if (g_hash_table_size (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);
-
- return top;
- }
-
- void
- rspamd_protocol_http_reply (struct rspamd_http_message *msg,
- struct rspamd_task *task)
- {
- struct metric_result *metric_res;
- GHashTableIter hiter;
- const struct rspamd_re_cache_stat *restat;
- gpointer h, v;
- ucl_object_t *top = NULL;
- gdouble required_score;
- gint action;
-
- /* Write custom headers */
- g_hash_table_iter_init (&hiter, task->reply_headers);
- while (g_hash_table_iter_next (&hiter, &h, &v)) {
- rspamd_ftok_t *hn = h, *hv = v;
-
- rspamd_http_message_add_header (msg, hn->begin, hv->begin);
- }
-
- top = rspamd_protocol_write_ucl (task);
-
- if (!(task->flags & RSPAMD_TASK_FLAG_NO_LOG)) {
- rspamd_roll_history_update (task->worker->srv->history, task);
- }
-
- rspamd_task_write_log (task);
-
- if (task->cfg->log_re_cache) {
- restat = rspamd_re_cache_get_stat (task->re_rt);
- g_assert (restat != NULL);
- msg_info_task (
- "regexp statistics: %ud pcre regexps scanned, %ud regexps matched,"
- " %HL bytes scanned using pcre, %HL bytes scanned total",
- restat->regexp_checked,
- restat->regexp_matched,
- restat->bytes_scanned_pcre,
- restat->bytes_scanned);
- }
-
- msg->body = rspamd_fstring_sized_new (1000);
-
- if (msg->method < HTTP_SYMBOLS && !RSPAMD_TASK_IS_SPAMC (task)) {
- rspamd_ucl_emit_fstring (top, UCL_EMIT_JSON_COMPACT, &msg->body);
- }
- else {
- if (RSPAMD_TASK_IS_SPAMC (task)) {
- rspamd_ucl_tospamc_output (task, top, &msg->body);
- }
- else {
- rspamd_ucl_torspamc_output (task, top, &msg->body);
- }
- }
-
- ucl_object_unref (top);
-
- if (!(task->flags & RSPAMD_TASK_FLAG_NO_STAT)) {
- /* Update stat for default metric */
- metric_res = g_hash_table_lookup (task->results, DEFAULT_METRIC);
- if (metric_res != NULL) {
- action = rspamd_check_action_metric (task, metric_res->score, &required_score,
- metric_res->metric);
- if (action <= METRIC_ACTION_NOACTION) {
- #ifndef HAVE_ATOMIC_BUILTINS
- task->worker->srv->stat->actions_stat[action]++;
- #else
- __atomic_add_fetch (&task->worker->srv->stat->actions_stat[action],
- 1, __ATOMIC_RELEASE);
- #endif
- }
- }
-
- /* Increase counters */
- #ifndef HAVE_ATOMIC_BUILTINS
- task->worker->srv->stat->messages_scanned++;
- #else
- __atomic_add_fetch (&task->worker->srv->stat->messages_scanned,
- 1, __ATOMIC_RELEASE);
- #endif
- }
- }
-
- void
- rspamd_protocol_write_reply (struct rspamd_task *task)
- {
- struct rspamd_http_message *msg;
- const gchar *ctype = "application/json";
-
- msg = rspamd_http_new_message (HTTP_RESPONSE);
-
- if (rspamd_http_connection_is_encrypted (task->http_conn)) {
- msg_info_task ("<%s> writing encrypted reply", task->message_id);
- }
-
- if (!RSPAMD_TASK_IS_JSON (task)) {
- /* Turn compatibility on */
- msg->method = HTTP_SYMBOLS;
- }
- if (RSPAMD_TASK_IS_SPAMC (task)) {
- msg->flags |= RSPAMD_HTTP_FLAG_SPAMC;
- }
-
- msg->date = time (NULL);
-
-
- debug_task ("writing reply to client");
- if (task->err != NULL) {
- ucl_object_t *top = NULL;
-
- top = ucl_object_typed_new (UCL_OBJECT);
- msg->code = 500 + task->err->code % 100;
- msg->status = rspamd_fstring_new_init (task->err->message,
- strlen (task->err->message));
- ucl_object_insert_key (top, ucl_object_fromstring (task->err->message),
- "error", 0, false);
- ucl_object_insert_key (top,
- ucl_object_fromstring (g_quark_to_string (task->err->domain)),
- "error_domain", 0, false);
- msg->body = rspamd_fstring_sized_new (256);
- rspamd_ucl_emit_fstring (top, UCL_EMIT_JSON_COMPACT, &msg->body);
- ucl_object_unref (top);
- }
- else {
- msg->status = rspamd_fstring_new_init ("OK", 2);
-
- 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 = rspamd_fstring_new_init ("pong" CRLF, 6);
- ctype = "text/plain";
- break;
- case CMD_OTHER:
- msg_err_task ("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);
-
- task->processed_stages |= RSPAMD_TASK_STAGE_REPLIED;
- }
-
- 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);
- }
|