123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- #include <sys/types.h>
- #include <string.h>
- #include <stdlib.h>
- #include <glib.h>
- #include "main.h"
-
- #define CRLF "\r\n"
- /* Max line size as it is defined in rfc2822 */
- #define OUTBUFSIZ 1000
- /*
- * 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"
-
- /*
- * 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 ERROR_HEADER "Error"
- /*
- * Reply messages
- */
- #define RSPAMD_REPLY_BANNER "RSPAMD/1.0"
- #define SPAMD_REPLY_BANNER "SPAMD/1.1"
- #define SPAMD_OK "EX_OK"
- /* XXX: try to convert rspamd errors to spamd errors */
- #define SPAMD_ERROR "EX_ERROR"
-
- static int
- parse_command (struct worker_task *task, char *line)
- {
- char *token;
-
- token = strsep (&line, " ");
- if (line == NULL || token == NULL) {
- msg_debug ("parse_command: bad comand: %s", token);
- return -1;
- }
-
- switch (token[0]) {
- case 'c':
- case 'C':
- /* check */
- if (strcasecmp (token + 1, MSG_CMD_CHECK + 1) == 0) {
- task->cmd = CMD_CHECK;
- }
- else {
- msg_debug ("parse_command: bad comand: %s", token);
- return -1;
- }
- break;
- case 's':
- case 'S':
- /* symbols, skip */
- if (strcasecmp (token + 1, MSG_CMD_SYMBOLS + 1) == 0) {
- task->cmd = CMD_SYMBOLS;
- }
- else if (strcasecmp (token + 1, MSG_CMD_SKIP + 1) == 0) {
- task->cmd = CMD_SKIP;
- }
- else {
- msg_debug ("parse_command: bad comand: %s", token);
- return -1;
- }
- break;
- case 'p':
- case 'P':
- /* ping, process */
- if (strcasecmp (token + 1, MSG_CMD_PING + 1) == 0) {
- task->cmd = CMD_PING;
- }
- else if (strcasecmp (token + 1, MSG_CMD_PROCESS + 1) == 0) {
- task->cmd = CMD_PROCESS;
- }
- else {
- msg_debug ("parse_command: bad comand: %s", token);
- return -1;
- }
- break;
- case 'r':
- case 'R':
- /* report, report_ifspam */
- if (strcasecmp (token + 1, MSG_CMD_REPORT + 1) == 0) {
- task->cmd = CMD_REPORT;
- }
- else if (strcasecmp (token + 1, MSG_CMD_REPORT_IFSPAM + 1) == 0) {
- task->cmd = CMD_REPORT_IFSPAM;
- }
- else {
- msg_debug ("parse_command: bad comand: %s", token);
- return -1;
- }
- break;
- default:
- msg_debug ("parse_command: bad comand: %s", token);
- return -1;
- }
-
- if (strncasecmp (line, RSPAMC_GREETING, sizeof (RSPAMC_GREETING) - 1) == 0) {
- task->proto = RSPAMC_PROTO;
- }
- else if (strncasecmp (line, SPAMC_GREETING, sizeof (SPAMC_GREETING) -1) == 0) {
- task->proto = SPAMC_PROTO;
- }
- else {
- msg_debug ("parse_command: bad protocol version: %s", line);
- return -1;
- }
- task->state = READ_HEADER;
- return 0;
- }
-
- static int
- parse_header (struct worker_task *task, char *line)
- {
- char *headern, *err, *tmp;
-
- /* Check end of headers */
- if (*line == '\0') {
- if (task->cmd == CMD_PING || task->cmd == CMD_SKIP) {
- task->state = WRITE_REPLY;
- }
- else {
- if (task->content_length > 0) {
- task->state = READ_MESSAGE;
- }
- else {
- task->last_error = "Unknown content length";
- task->error_code = RSPAMD_LENGTH_ERROR;
- task->state = WRITE_ERROR;
- }
- }
- return 0;
- }
-
- headern = strsep (&line, ":");
-
- if (line == NULL || headern == NULL) {
- return -1;
- }
- /* Eat whitespaces */
- g_strstrip (line);
- g_strstrip (headern);
-
- switch (headern[0]) {
- case 'c':
- case 'C':
- /* content-length */
- if (strncasecmp (headern, CONTENT_LENGTH_HEADER, sizeof (CONTENT_LENGTH_HEADER) - 1) == 0) {
- task->content_length = strtoul (line, &err, 10);
- task->msg = memory_pool_alloc (task->task_pool, sizeof (f_str_buf_t));
- task->msg->buf = fstralloc (task->task_pool, task->content_length);
- if (task->msg->buf == NULL) {
- msg_err ("read_socket: cannot allocate memory for message buffer");
- return -1;
- }
- }
- else {
- msg_info ("parse_header: wrong header: %s", headern);
- return -1;
- }
- break;
- case 'h':
- case 'H':
- /* helo */
- if (strncasecmp (headern, HELO_HEADER, sizeof (HELO_HEADER) - 1) == 0) {
- task->helo = memory_pool_strdup (task->task_pool, line);
- }
- else {
- msg_info ("parse_header: wrong header: %s", headern);
- return -1;
- }
- break;
- case 'f':
- case 'F':
- /* from */
- if (strncasecmp (headern, FROM_HEADER, sizeof (FROM_HEADER) - 1) == 0) {
- task->from = memory_pool_strdup (task->task_pool, line);
- }
- else {
- msg_info ("parse_header: wrong header: %s", headern);
- return -1;
- }
- break;
- case 'r':
- case 'R':
- /* rcpt */
- if (strncasecmp (headern, RCPT_HEADER, sizeof (RCPT_HEADER) - 1) == 0) {
- tmp = memory_pool_strdup (task->task_pool, line);
- task->rcpt = g_list_prepend (task->rcpt, tmp);
- }
- else {
- msg_info ("parse_header: wrong header: %s", headern);
- return -1;
- }
- break;
- case 'n':
- case 'N':
- /* nrcpt */
- if (strncasecmp (headern, NRCPT_HEADER, sizeof (NRCPT_HEADER) - 1) == 0) {
- task->nrcpt = strtoul (line, &err, 10);
- }
- else {
- msg_info ("parse_header: wrong header: %s", headern);
- return -1;
- }
- break;
- case 'i':
- case 'I':
- /* ip_addr */
- if (strncasecmp (headern, IP_ADDR_HEADER, sizeof (IP_ADDR_HEADER) - 1) == 0) {
- if (!inet_aton (line, &task->from_addr)) {
- msg_info ("parse_header: bad ip header: '%s'", line);
- return -1;
- }
- }
- else {
- msg_info ("parse_header: wrong header: %s", headern);
- return -1;
- }
- break;
- default:
- msg_info ("parse_header: wrong header: %s", headern);
- return -1;
- }
-
- return 0;
- }
-
- int
- read_rspamd_input_line (struct worker_task *task, char *line)
- {
- switch (task->state) {
- case READ_COMMAND:
- return parse_command (task, line);
- break;
- case READ_HEADER:
- return parse_header (task, line);
- break;
- }
- }
-
- static void
- show_url_header (struct worker_task *task)
- {
- int r = 0;
- char outbuf[OUTBUFSIZ], c;
- struct uri *url;
- f_str_t host;
-
- r = snprintf (outbuf, sizeof (outbuf), "Urls: ");
- TAILQ_FOREACH (url, &task->urls, next) {
- host.begin = url->host;
- host.len = url->hostlen;
- /* Skip long hosts to avoid protocol coollisions */
- if (host.len > OUTBUFSIZ) {
- continue;
- }
- /* Do header folding */
- if (host.len + r >= OUTBUFSIZ - 3) {
- outbuf[r ++] = '\r'; outbuf[r ++] = '\n'; outbuf[r] = ' ';
- bufferevent_write (task->bev, outbuf, r);
- r = 0;
- }
- /* Write url host to buf */
- if (TAILQ_NEXT (url, next) != NULL) {
- c = *(host.begin + host.len);
- *(host.begin + host.len) = '\0';
- r += snprintf (outbuf, sizeof (outbuf) - r, "%s, ", host.begin);
- *(host.begin + host.len) = c;
- }
- else {
- c = *(host.begin + host.len);
- *(host.begin + host.len) = '\0';
- r += snprintf (outbuf, sizeof (outbuf) - r, "%s" CRLF, host.begin);
- *(host.begin + host.len) = c;
- }
- }
- bufferevent_write (task->bev, outbuf, r);
- }
-
- static void
- show_metric_result (gpointer metric_name, gpointer metric_value, void *user_data)
- {
- struct worker_task *task = (struct worker_task *)user_data;
- int r;
- char outbuf[OUTBUFSIZ];
- struct metric_result *metric_res = (struct metric_result *)metric_value;
- int is_spam = 0;
-
- if (metric_res->score >= metric_res->metric->required_score) {
- is_spam = 1;
- }
- if (task->proto == SPAMC_PROTO) {
- r = snprintf (outbuf, sizeof (outbuf), "Spam: %s ; %.2f / %.2f" CRLF,
- (is_spam) ? "True" : "False", metric_res->score, metric_res->metric->required_score);
- }
- else {
- r = snprintf (outbuf, sizeof (outbuf), "%s: %s ; %.2f / %.2f" CRLF, (char *)metric_name,
- (is_spam) ? "True" : "False", metric_res->score, metric_res->metric->required_score);
- }
- bufferevent_write (task->bev, outbuf, r);
- }
-
- static int
- write_check_reply (struct worker_task *task)
- {
- int r;
- char outbuf[OUTBUFSIZ];
- struct metric_result *metric_res;
-
- r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER);
- bufferevent_write (task->bev, outbuf, r);
- if (task->proto == SPAMC_PROTO) {
- /* Ignore metrics, just write report for 'default' metric */
- metric_res = g_hash_table_lookup (task->results, "default");
- if (metric_res == NULL) {
- return -1;
- }
- else {
- show_metric_result ((gpointer)"default", (gpointer)metric_res, (void *)task);
- }
- }
- else {
- /* Write result for each metric separately */
- g_hash_table_foreach (task->results, show_metric_result, task);
- /* URL stat */
- show_url_header (task);
- }
- bufferevent_write (task->bev, CRLF, sizeof (CRLF) - 1);
-
- return 0;
- }
-
- static void
- show_metric_symbols (gpointer metric_name, gpointer metric_value, void *user_data)
- {
- struct worker_task *task = (struct worker_task *)user_data;
- int r = 0;
- char outbuf[OUTBUFSIZ];
- GList *symbols = NULL, *cur;
- struct metric_result *metric_res = (struct metric_result *)metric_value;
-
- if (task->proto == RSPAMC_PROTO) {
- r = snprintf (outbuf, sizeof (outbuf), "%s: ", (char *)metric_name);
- }
-
- symbols = g_hash_table_get_keys (metric_res->symbols);
- cur = symbols;
- while (cur) {
- if (g_list_next (cur) != NULL) {
- r += snprintf (outbuf + r, sizeof (outbuf) - r, "%s,", (char *)cur->data);
- }
- else {
- r += snprintf (outbuf + r, sizeof (outbuf) - r, "%s", (char *)cur->data);
- }
- cur = g_list_next (cur);
- }
- g_list_free (symbols);
- outbuf[r++] = '\r'; outbuf[r] = '\n';
- bufferevent_write (task->bev, outbuf, r);
- }
-
- static int
- write_symbols_reply (struct worker_task *task)
- {
- struct metric_result *metric_res;
-
- /* First of all write normal results by calling write_check_reply */
- if (write_check_reply (task) == -1) {
- return -1;
- }
- /* Now write symbols */
- if (task->proto == SPAMC_PROTO) {
- /* Ignore metrics, just write report for 'default' metric */
- metric_res = g_hash_table_lookup (task->results, "default");
- if (metric_res == NULL) {
- return -1;
- }
- else {
- show_metric_symbols ((gpointer)"default", (gpointer)metric_res, (void *)task);
- }
- }
- else {
- /* Write result for each metric separately */
- g_hash_table_foreach (task->results, show_metric_symbols, task);
- }
- return 0;
- }
-
- static int
- write_process_reply (struct worker_task *task)
- {
- int r;
- char outbuf[OUTBUFSIZ];
-
- r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF "Content-Length: %zd" CRLF CRLF,
- (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, task->msg->buf->len);
- bufferevent_write (task->bev, outbuf, r);
- bufferevent_write (task->bev, task->msg->buf->begin, task->msg->buf->len);
-
- return 0;
- }
-
- int
- write_reply (struct worker_task *task)
- {
- int r;
- char outbuf[OUTBUFSIZ];
-
- msg_debug ("write_reply: writing reply to client");
- if (task->error_code != 0) {
- /* Write error message and error code to reply */
- if (task->proto == SPAMC_PROTO) {
- r = snprintf (outbuf, sizeof (outbuf), "%s %d %s" CRLF CRLF, SPAMD_REPLY_BANNER, task->error_code, SPAMD_ERROR);
- msg_debug ("write_reply: writing error: %s", outbuf);
- }
- else {
- r = snprintf (outbuf, sizeof (outbuf), "%s %d %s" CRLF "%s: %s" CRLF CRLF, RSPAMD_REPLY_BANNER, task->error_code,
- SPAMD_ERROR, ERROR_HEADER, task->last_error);
- msg_debug ("write_reply: writing error: %s", outbuf);
- }
- /* Write to bufferevent error message */
- bufferevent_write (task->bev, outbuf, r);
- }
- else {
- switch (task->cmd) {
- case CMD_REPORT_IFSPAM:
- case CMD_REPORT:
- case CMD_CHECK:
- return write_check_reply (task);
- break;
- case CMD_SYMBOLS:
- return write_symbols_reply (task);
- break;
- case CMD_PROCESS:
- return write_process_reply (task);
- break;
- case CMD_SKIP:
- r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER,
- SPAMD_OK);
- bufferevent_write (task->bev, outbuf, r);
- break;
- case CMD_PING:
- r = snprintf (outbuf, sizeof (outbuf), "%s 0 PONG" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER);
- bufferevent_write (task->bev, outbuf, r);
- break;
- }
- }
-
- return 0;
- }
|