You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

protocol.c 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. /*
  2. * Copyright (c) 2009, Rambler media
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY Rambler media ''AS IS'' AND ANY
  14. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  16. * DISCLAIMED. IN NO EVENT SHALL Rambler BE LIABLE FOR ANY
  17. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  18. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  19. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  20. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  21. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  22. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23. */
  24. #include "config.h"
  25. #include "main.h"
  26. #include "util.h"
  27. #include "cfg_file.h"
  28. #include "settings.h"
  29. #include "message.h"
  30. /* Max line size as it is defined in rfc2822 */
  31. #define OUTBUFSIZ 1000
  32. /*
  33. * Just check if the passed message is spam or not and reply as
  34. * described below
  35. */
  36. #define MSG_CMD_CHECK "check"
  37. /*
  38. * Check if message is spam or not, and return score plus list
  39. * of symbols hit
  40. */
  41. #define MSG_CMD_SYMBOLS "symbols"
  42. /*
  43. * Check if message is spam or not, and return score plus report
  44. */
  45. #define MSG_CMD_REPORT "report"
  46. /*
  47. * Check if message is spam or not, and return score plus report
  48. * if the message is spam
  49. */
  50. #define MSG_CMD_REPORT_IFSPAM "report_ifspam"
  51. /*
  52. * Ignore this message -- client opened connection then changed
  53. */
  54. #define MSG_CMD_SKIP "skip"
  55. /*
  56. * Return a confirmation that spamd is alive
  57. */
  58. #define MSG_CMD_PING "ping"
  59. /*
  60. * Process this message as described above and return modified message
  61. */
  62. #define MSG_CMD_PROCESS "process"
  63. /*
  64. * spamassassin greeting:
  65. */
  66. #define SPAMC_GREETING "SPAMC"
  67. /*
  68. * rspamd greeting:
  69. */
  70. #define RSPAMC_GREETING "RSPAMC"
  71. /*
  72. * Headers
  73. */
  74. #define CONTENT_LENGTH_HEADER "Content-Length"
  75. #define HELO_HEADER "Helo"
  76. #define FROM_HEADER "From"
  77. #define IP_ADDR_HEADER "IP"
  78. #define NRCPT_HEADER "Recipient-Number"
  79. #define RCPT_HEADER "Rcpt"
  80. #define SUBJECT_HEADER "Subject"
  81. #define QUEUE_ID_HEADER "Queue-ID"
  82. #define ERROR_HEADER "Error"
  83. #define USER_HEADER "User"
  84. #define PASS_HEADER "Pass"
  85. #define DELIVER_TO_HEADER "Deliver-To"
  86. static GList *custom_commands = NULL;
  87. /* For default metric, dirty hack, but much faster than hash lookup */
  88. static double default_score, default_required_score;
  89. static char *
  90. separate_command (f_str_t * in, char c)
  91. {
  92. int r = 0;
  93. char *p = in->begin, *b;
  94. b = p;
  95. while (r < in->len) {
  96. if (*p == c) {
  97. *p = '\0';
  98. in->begin = p + 1;
  99. in->len -= r + 1;
  100. return b;
  101. }
  102. p++;
  103. r++;
  104. }
  105. return NULL;
  106. }
  107. static int
  108. parse_command (struct worker_task *task, f_str_t * line)
  109. {
  110. char *token;
  111. struct custom_command *cmd;
  112. GList *cur;
  113. task->proto_ver = RSPAMC_PROTO_1_1;
  114. token = separate_command (line, ' ');
  115. if (line == NULL || token == NULL) {
  116. debug_task ("bad command: %s", token);
  117. return -1;
  118. }
  119. switch (token[0]) {
  120. case 'c':
  121. case 'C':
  122. /* check */
  123. if (g_ascii_strcasecmp (token + 1, MSG_CMD_CHECK + 1) == 0) {
  124. task->cmd = CMD_CHECK;
  125. }
  126. else {
  127. debug_task ("bad command: %s", token);
  128. return -1;
  129. }
  130. break;
  131. case 's':
  132. case 'S':
  133. /* symbols, skip */
  134. if (g_ascii_strcasecmp (token + 1, MSG_CMD_SYMBOLS + 1) == 0) {
  135. task->cmd = CMD_SYMBOLS;
  136. }
  137. else if (g_ascii_strcasecmp (token + 1, MSG_CMD_SKIP + 1) == 0) {
  138. task->cmd = CMD_SKIP;
  139. }
  140. else {
  141. debug_task ("bad command: %s", token);
  142. return -1;
  143. }
  144. break;
  145. case 'p':
  146. case 'P':
  147. /* ping, process */
  148. if (g_ascii_strcasecmp (token + 1, MSG_CMD_PING + 1) == 0) {
  149. task->cmd = CMD_PING;
  150. }
  151. else if (g_ascii_strcasecmp (token + 1, MSG_CMD_PROCESS + 1) == 0) {
  152. task->cmd = CMD_PROCESS;
  153. }
  154. else {
  155. debug_task ("bad command: %s", token);
  156. return -1;
  157. }
  158. break;
  159. case 'r':
  160. case 'R':
  161. /* report, report_ifspam */
  162. if (g_ascii_strcasecmp (token + 1, MSG_CMD_REPORT + 1) == 0) {
  163. task->cmd = CMD_REPORT;
  164. }
  165. else if (g_ascii_strcasecmp (token + 1, MSG_CMD_REPORT_IFSPAM + 1) == 0) {
  166. task->cmd = CMD_REPORT_IFSPAM;
  167. }
  168. else {
  169. debug_task ("bad command: %s", token);
  170. return -1;
  171. }
  172. break;
  173. default:
  174. cur = custom_commands;
  175. while (cur) {
  176. cmd = cur->data;
  177. if (g_ascii_strcasecmp (token, cmd->name) == 0) {
  178. task->cmd = CMD_OTHER;
  179. task->custom_cmd = cmd;
  180. break;
  181. }
  182. cur = g_list_next (cur);
  183. }
  184. if (cur == NULL) {
  185. debug_task ("bad command: %s", token);
  186. return -1;
  187. }
  188. break;
  189. }
  190. if (g_ascii_strncasecmp (line->begin, RSPAMC_GREETING, sizeof (RSPAMC_GREETING) - 1) == 0) {
  191. task->proto = RSPAMC_PROTO;
  192. task->proto_ver = RSPAMC_PROTO_1_0;
  193. if (*(line->begin + sizeof (RSPAMC_GREETING) - 1) == '/') {
  194. /* Extract protocol version */
  195. token = line->begin + sizeof (RSPAMC_GREETING);
  196. if (strncmp (token, RSPAMC_PROTO_1_1, sizeof (RSPAMC_PROTO_1_1) - 1) == 0) {
  197. task->proto_ver = RSPAMC_PROTO_1_1;
  198. }
  199. }
  200. }
  201. else if (g_ascii_strncasecmp (line->begin, SPAMC_GREETING, sizeof (SPAMC_GREETING) - 1) == 0) {
  202. task->proto = SPAMC_PROTO;
  203. }
  204. else {
  205. return -1;
  206. }
  207. task->state = READ_HEADER;
  208. return 0;
  209. }
  210. static int
  211. parse_header (struct worker_task *task, f_str_t * line)
  212. {
  213. char *headern, *err, *tmp;
  214. /* Check end of headers */
  215. if (line->len == 0) {
  216. debug_task ("got empty line, assume it as end of headers");
  217. if (task->cmd == CMD_PING || task->cmd == CMD_SKIP) {
  218. task->state = WRITE_REPLY;
  219. }
  220. else {
  221. if (task->content_length > 0) {
  222. rspamd_set_dispatcher_policy (task->dispatcher, BUFFER_CHARACTER, task->content_length);
  223. task->state = READ_MESSAGE;
  224. }
  225. else {
  226. task->last_error = "Unknown content length";
  227. task->error_code = RSPAMD_LENGTH_ERROR;
  228. task->state = WRITE_ERROR;
  229. return -1;
  230. }
  231. }
  232. return 0;
  233. }
  234. headern = separate_command (line, ':');
  235. if (line == NULL || headern == NULL) {
  236. return -1;
  237. }
  238. /* Eat whitespaces */
  239. g_strstrip (headern);
  240. fstrstrip (line);
  241. switch (headern[0]) {
  242. case 'c':
  243. case 'C':
  244. /* content-length */
  245. if (g_ascii_strncasecmp (headern, CONTENT_LENGTH_HEADER, sizeof (CONTENT_LENGTH_HEADER) - 1) == 0) {
  246. if (task->content_length == 0) {
  247. tmp = memory_pool_fstrdup (task->task_pool, line);
  248. task->content_length = strtoul (tmp, &err, 10);
  249. debug_task ("read Content-Length header, value: %lu", (unsigned long int)task->content_length);
  250. }
  251. }
  252. else {
  253. msg_info ("wrong header: %s", headern);
  254. return -1;
  255. }
  256. break;
  257. case 'd':
  258. case 'D':
  259. /* Deliver-To */
  260. if (g_ascii_strncasecmp (headern, DELIVER_TO_HEADER, sizeof (DELIVER_TO_HEADER) - 1) == 0) {
  261. task->deliver_to = memory_pool_fstrdup (task->task_pool, line);
  262. debug_task ("read deliver-to header, value: %s", task->deliver_to);
  263. }
  264. else {
  265. msg_info ("wrong header: %s", headern);
  266. return -1;
  267. }
  268. break;
  269. case 'h':
  270. case 'H':
  271. /* helo */
  272. if (g_ascii_strncasecmp (headern, HELO_HEADER, sizeof (HELO_HEADER) - 1) == 0) {
  273. task->helo = memory_pool_fstrdup (task->task_pool, line);
  274. debug_task ("read helo header, value: %s", task->helo);
  275. }
  276. else {
  277. msg_info ("wrong header: %s", headern);
  278. return -1;
  279. }
  280. break;
  281. case 'f':
  282. case 'F':
  283. /* from */
  284. if (g_ascii_strncasecmp (headern, FROM_HEADER, sizeof (FROM_HEADER) - 1) == 0) {
  285. task->from = memory_pool_fstrdup (task->task_pool, line);
  286. debug_task ("read from header, value: %s", task->from);
  287. }
  288. else {
  289. msg_info ("wrong header: %s", headern);
  290. return -1;
  291. }
  292. break;
  293. case 'q':
  294. case 'Q':
  295. /* Queue id */
  296. if (g_ascii_strncasecmp (headern, QUEUE_ID_HEADER, sizeof (QUEUE_ID_HEADER) - 1) == 0) {
  297. task->queue_id = memory_pool_fstrdup (task->task_pool, line);
  298. debug_task ("read queue_id header, value: %s", task->queue_id);
  299. }
  300. else {
  301. msg_info ("wrong header: %s", headern);
  302. return -1;
  303. }
  304. break;
  305. case 'r':
  306. case 'R':
  307. /* rcpt */
  308. if (g_ascii_strncasecmp (headern, RCPT_HEADER, sizeof (RCPT_HEADER) - 1) == 0) {
  309. tmp = memory_pool_fstrdup (task->task_pool, line);
  310. task->rcpt = g_list_prepend (task->rcpt, tmp);
  311. debug_task ("read rcpt header, value: %s", tmp);
  312. }
  313. else if (g_ascii_strncasecmp (headern, NRCPT_HEADER, sizeof (NRCPT_HEADER) - 1) == 0) {
  314. tmp = memory_pool_fstrdup (task->task_pool, line);
  315. task->nrcpt = strtoul (tmp, &err, 10);
  316. debug_task ("read rcpt header, value: %d", (int)task->nrcpt);
  317. }
  318. else {
  319. msg_info ("wrong header: %s", headern);
  320. return -1;
  321. }
  322. break;
  323. case 'i':
  324. case 'I':
  325. /* ip_addr */
  326. if (g_ascii_strncasecmp (headern, IP_ADDR_HEADER, sizeof (IP_ADDR_HEADER) - 1) == 0) {
  327. tmp = memory_pool_fstrdup (task->task_pool, line);
  328. if (!inet_aton (tmp, &task->from_addr)) {
  329. msg_info ("bad ip header: '%s'", tmp);
  330. return -1;
  331. }
  332. debug_task ("read IP header, value: %s", tmp);
  333. }
  334. else {
  335. msg_info ("wrong header: %s", headern);
  336. return -1;
  337. }
  338. break;
  339. case 'p':
  340. case 'P':
  341. /* Pass header */
  342. if (g_ascii_strncasecmp (headern, PASS_HEADER, sizeof (PASS_HEADER) - 1) == 0) {
  343. if (line->len == sizeof ("all") - 1 && g_ascii_strncasecmp (line->begin, "all", sizeof ("all") - 1) == 0) {
  344. task->pass_all_filters = TRUE;
  345. msg_info ("pass all filters");
  346. }
  347. }
  348. break;
  349. case 's':
  350. case 'S':
  351. if (g_ascii_strncasecmp (headern, SUBJECT_HEADER, sizeof (SUBJECT_HEADER) - 1) == 0) {
  352. task->subject = memory_pool_fstrdup (task->task_pool, line);
  353. }
  354. else {
  355. return -1;
  356. }
  357. break;
  358. case 'u':
  359. case 'U':
  360. if (g_ascii_strncasecmp (headern, USER_HEADER, sizeof (USER_HEADER) - 1) == 0) {
  361. /* XXX: use this header somehow */
  362. task->user = memory_pool_fstrdup (task->task_pool, line);
  363. }
  364. else {
  365. return -1;
  366. }
  367. break;
  368. default:
  369. msg_info ("wrong header: %s", headern);
  370. return -1;
  371. }
  372. return 0;
  373. }
  374. int
  375. read_rspamd_input_line (struct worker_task *task, f_str_t * line)
  376. {
  377. switch (task->state) {
  378. case READ_COMMAND:
  379. return parse_command (task, line);
  380. break;
  381. case READ_HEADER:
  382. return parse_header (task, line);
  383. break;
  384. default:
  385. return -1;
  386. }
  387. return -1;
  388. }
  389. struct metric_callback_data {
  390. struct worker_task *task;
  391. char *log_buf;
  392. int log_offset;
  393. int log_size;
  394. };
  395. static void
  396. write_hashes_to_log (struct worker_task *task, char *logbuf, int offset, int size)
  397. {
  398. GList *cur;
  399. struct mime_text_part *text_part;
  400. cur = task->text_parts;
  401. while (cur && offset < size) {
  402. text_part = cur->data;
  403. if (text_part->fuzzy) {
  404. if (cur->next != NULL) {
  405. offset += snprintf (logbuf + offset, size - offset, " part: %Xd,", text_part->fuzzy->h);
  406. }
  407. else {
  408. offset += snprintf (logbuf + offset, size - offset, " part: %Xd", text_part->fuzzy->h);
  409. }
  410. }
  411. cur = g_list_next (cur);
  412. }
  413. }
  414. static void
  415. show_url_header (struct worker_task *task)
  416. {
  417. int r = 0;
  418. char outbuf[OUTBUFSIZ], c;
  419. struct uri *url;
  420. GList *cur;
  421. f_str_t host;
  422. r = snprintf (outbuf, sizeof (outbuf), "Urls: ");
  423. cur = task->urls;
  424. while (cur) {
  425. url = cur->data;
  426. if (task->cfg->log_urls) {
  427. /* Write this url to log as well */
  428. msg_info ("url found: <%s>, score: [%.2f / %.2f]", struri (url), default_score, default_required_score);
  429. }
  430. host.begin = url->host;
  431. host.len = url->hostlen;
  432. /* Skip long hosts to avoid protocol coollisions */
  433. if (host.len > OUTBUFSIZ) {
  434. cur = g_list_next (cur);
  435. continue;
  436. }
  437. /* Do header folding */
  438. if (host.len + r >= OUTBUFSIZ - 3) {
  439. outbuf[r++] = '\r';
  440. outbuf[r++] = '\n';
  441. outbuf[r] = ' ';
  442. rspamd_dispatcher_write (task->dispatcher, outbuf, r, TRUE, FALSE);
  443. r = 0;
  444. }
  445. /* Write url host to buf */
  446. if (g_list_next (cur) != NULL) {
  447. c = *(host.begin + host.len);
  448. *(host.begin + host.len) = '\0';
  449. debug_task ("write url: %s", host.begin);
  450. r += snprintf (outbuf + r, sizeof (outbuf) - r, "%s, ", host.begin);
  451. *(host.begin + host.len) = c;
  452. }
  453. else {
  454. c = *(host.begin + host.len);
  455. *(host.begin + host.len) = '\0';
  456. debug_task ("write url: %s", host.begin);
  457. r += snprintf (outbuf + r, sizeof (outbuf) - r, "%s" CRLF, host.begin);
  458. *(host.begin + host.len) = c;
  459. }
  460. cur = g_list_next (cur);
  461. }
  462. rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE);
  463. }
  464. static void
  465. metric_symbols_callback (gpointer key, gpointer value, void *user_data)
  466. {
  467. struct metric_callback_data *cd = (struct metric_callback_data *)user_data;
  468. struct worker_task *task = cd->task;
  469. int r = 0;
  470. char outbuf[OUTBUFSIZ];
  471. struct symbol *s = (struct symbol *)value;
  472. GList *cur;
  473. if (s->options) {
  474. r = snprintf (outbuf, OUTBUFSIZ, "Symbol: %s; ", (char *)key);
  475. cur = s->options;
  476. while (cur) {
  477. if (g_list_next (cur)) {
  478. r += snprintf (outbuf + r, OUTBUFSIZ - r, "%s,", (char *)cur->data);
  479. }
  480. else {
  481. r += snprintf (outbuf + r, OUTBUFSIZ - r, "%s" CRLF, (char *)cur->data);
  482. }
  483. cur = g_list_next (cur);
  484. }
  485. /* End line with CRLF strictly */
  486. if (r >= OUTBUFSIZ - 1) {
  487. outbuf[OUTBUFSIZ - 2] = '\r';
  488. outbuf[OUTBUFSIZ - 1] = '\n';
  489. }
  490. }
  491. else {
  492. r = snprintf (outbuf, OUTBUFSIZ, "Symbol: %s" CRLF, (char *)key);
  493. }
  494. cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "%s,", (char *)key);
  495. rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE);
  496. }
  497. static void
  498. show_metric_symbols (struct metric_result *metric_res, struct metric_callback_data *cd)
  499. {
  500. int r = 0;
  501. GList *symbols, *cur;
  502. char outbuf[OUTBUFSIZ];
  503. if (cd->task->proto == SPAMC_PROTO) {
  504. symbols = g_hash_table_get_keys (metric_res->symbols);
  505. cur = symbols;
  506. while (cur) {
  507. if (g_list_next (cur) != NULL) {
  508. r += snprintf (outbuf + r, sizeof (outbuf) - r, "%s,", (char *)cur->data);
  509. }
  510. else {
  511. r += snprintf (outbuf + r, sizeof (outbuf) - r, "%s" CRLF, (char *)cur->data);
  512. }
  513. cur = g_list_next (cur);
  514. }
  515. g_list_free (symbols);
  516. rspamd_dispatcher_write (cd->task->dispatcher, outbuf, r, FALSE, FALSE);
  517. }
  518. else {
  519. g_hash_table_foreach (metric_res->symbols, metric_symbols_callback, cd);
  520. /* Remove last , from log buf */
  521. if (cd->log_buf[cd->log_offset - 1] == ',') {
  522. cd->log_buf[--cd->log_offset] = '\0';
  523. }
  524. }
  525. }
  526. static void
  527. show_metric_result (gpointer metric_name, gpointer metric_value, void *user_data)
  528. {
  529. struct metric_callback_data *cd = (struct metric_callback_data *)user_data;
  530. struct worker_task *task = cd->task;
  531. int r;
  532. char outbuf[OUTBUFSIZ];
  533. struct metric_result *metric_res = (struct metric_result *)metric_value;
  534. struct metric *m;
  535. int is_spam = 0;
  536. double ms = 0, rs = 0;
  537. if (metric_name == NULL || metric_value == NULL) {
  538. m = g_hash_table_lookup (task->cfg->metrics, DEFAULT_METRIC);
  539. default_required_score = m->required_score;
  540. default_score = 0;
  541. if (!check_metric_settings (task, m, &ms, &rs)) {
  542. ms = m->required_score;
  543. rs = m->reject_score;
  544. }
  545. if (task->proto == SPAMC_PROTO) {
  546. r = snprintf (outbuf, sizeof (outbuf), "Spam: False ; 0 / %.2f" CRLF, ms);
  547. }
  548. else {
  549. if (strcmp (task->proto_ver, RSPAMC_PROTO_1_1) == 0) {
  550. if (!task->is_skipped) {
  551. r = snprintf (outbuf, sizeof (outbuf), "Metric: default; False; 0 / %.2f / %.2f" CRLF, ms, rs);
  552. }
  553. else {
  554. r = snprintf (outbuf, sizeof (outbuf), "Metric: default; Skip; 0 / %.2f / %.2f" CRLF, ms, rs);
  555. }
  556. }
  557. else {
  558. r = snprintf (outbuf, sizeof (outbuf), "Metric: default; False; 0 / %.2f" CRLF, ms);
  559. }
  560. }
  561. if (!task->is_skipped) {
  562. cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "(%s: F: [0/%.2f/%.2f] [", "default", ms, rs);
  563. }
  564. else {
  565. cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "(%s: S: [0/%.2f/%.2f] [", "default", ms, rs);
  566. }
  567. }
  568. else {
  569. /* XXX: dirty hack */
  570. if (strcmp (metric_res->metric->name, DEFAULT_METRIC) == 0) {
  571. default_required_score = metric_res->metric->required_score;
  572. default_score = metric_res->score;
  573. }
  574. if (!check_metric_settings (task, metric_res->metric, &ms, &rs)) {
  575. ms = metric_res->metric->required_score;
  576. rs = metric_res->metric->reject_score;
  577. }
  578. if (metric_res->score >= ms) {
  579. is_spam = 1;
  580. }
  581. if (task->proto == SPAMC_PROTO) {
  582. r = snprintf (outbuf, sizeof (outbuf), "Spam: %s ; %.2f / %.2f" CRLF, (is_spam) ? "True" : "False", metric_res->score, ms);
  583. }
  584. else {
  585. if (strcmp (task->proto_ver, RSPAMC_PROTO_1_1) == 0) {
  586. if (!task->is_skipped) {
  587. r = snprintf (outbuf, sizeof (outbuf), "Metric: %s; %s; %.2f / %.2f / %.2f" CRLF,
  588. (char *)metric_name, (is_spam) ? "True" : "False", metric_res->score, ms, rs);
  589. }
  590. else {
  591. r = snprintf (outbuf, sizeof (outbuf), "Metric: %s; Skip; %.2f / %.2f / %.2f" CRLF,
  592. (char *)metric_name, metric_res->score, ms, rs);
  593. }
  594. }
  595. else {
  596. r = snprintf (outbuf, sizeof (outbuf), "Metric: %s; %s; %.2f / %.2f" CRLF,
  597. (char *)metric_name, (is_spam) ? "True" : "False", metric_res->score, ms);
  598. }
  599. }
  600. if (!task->is_skipped) {
  601. cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "(%s: %s: [%.2f/%.2f/%.2f] [",
  602. (char *)metric_name, is_spam ? "T" : "F", metric_res->score, ms, rs);
  603. }
  604. else {
  605. cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "(%s: %s: [%.2f/%.2f/%.2f] [",
  606. (char *)metric_name, "S", metric_res->score, ms, rs);
  607. }
  608. }
  609. if (task->cmd == CMD_PROCESS) {
  610. #ifndef GMIME24
  611. g_mime_message_add_header (task->message, "X-Spam-Status", outbuf);
  612. #else
  613. g_mime_object_append_header (GMIME_OBJECT (task->message), "X-Spam-Status", outbuf);
  614. #endif
  615. }
  616. else {
  617. rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE);
  618. if (task->cmd == CMD_SYMBOLS && metric_value != NULL) {
  619. show_metric_symbols (metric_res, cd);
  620. }
  621. }
  622. #ifdef HAVE_CLOCK_GETTIME
  623. cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "]), len: %ld, time: %sms",
  624. (long int)task->msg->len, calculate_check_time (&task->ts, task->cfg->clock_res));
  625. #else
  626. cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "]), len: %ld, time: %sms",
  627. (long int)task->msg->len, calculate_check_time (&task->tv, task->cfg->clock_res));
  628. #endif
  629. }
  630. static void
  631. show_messages (struct worker_task *task)
  632. {
  633. int r = 0;
  634. char outbuf[OUTBUFSIZ];
  635. GList *cur;
  636. cur = task->messages;
  637. while (cur) {
  638. r += snprintf (outbuf + r, sizeof (outbuf) - r, "Message: %s" CRLF, (char *)cur->data);
  639. cur = g_list_next (cur);
  640. }
  641. rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE);
  642. }
  643. static int
  644. write_check_reply (struct worker_task *task)
  645. {
  646. int r;
  647. char outbuf[OUTBUFSIZ], logbuf[OUTBUFSIZ];
  648. struct metric_result *metric_res;
  649. struct metric_callback_data cd;
  650. r = snprintf (outbuf, sizeof (outbuf), "%s/%s 0 %s" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER,
  651. task->proto_ver, "OK");
  652. rspamd_dispatcher_write (task->dispatcher, outbuf, r, TRUE, FALSE);
  653. cd.task = task;
  654. cd.log_buf = logbuf;
  655. cd.log_offset = snprintf (logbuf, sizeof (logbuf), "msg ok, id: <%s>, ", task->message_id);
  656. cd.log_size = sizeof (logbuf);
  657. if (task->proto == SPAMC_PROTO) {
  658. /* Ignore metrics, just write report for 'default' metric */
  659. metric_res = g_hash_table_lookup (task->results, "default");
  660. if (metric_res == NULL) {
  661. /* Implicit metric result */
  662. show_metric_result (NULL, NULL, (void *)&cd);
  663. }
  664. else {
  665. show_metric_result ((gpointer) "default", (gpointer) metric_res, (void *)&cd);
  666. }
  667. }
  668. else {
  669. /* Show default metric first */
  670. metric_res = g_hash_table_lookup (task->results, "default");
  671. if (metric_res == NULL) {
  672. /* Implicit metric result */
  673. show_metric_result (NULL, NULL, (void *)&cd);
  674. }
  675. else {
  676. show_metric_result ((gpointer) "default", (gpointer) metric_res, (void *)&cd);
  677. }
  678. g_hash_table_remove (task->results, "default");
  679. /* Write result for each metric separately */
  680. g_hash_table_foreach (task->results, show_metric_result, &cd);
  681. /* Messages */
  682. show_messages (task);
  683. /* URL stat */
  684. show_url_header (task);
  685. }
  686. write_hashes_to_log (task, logbuf, cd.log_offset, cd.log_size);
  687. msg_info ("%s", logbuf);
  688. rspamd_dispatcher_write (task->dispatcher, CRLF, sizeof (CRLF) - 1, FALSE, TRUE);
  689. task->worker->srv->stat->messages_scanned++;
  690. if (default_score >= default_required_score) {
  691. task->worker->srv->stat->messages_spam ++;
  692. }
  693. else {
  694. task->worker->srv->stat->messages_ham ++;
  695. }
  696. return 0;
  697. }
  698. static int
  699. write_process_reply (struct worker_task *task)
  700. {
  701. int r;
  702. char *outmsg;
  703. char outbuf[OUTBUFSIZ], logbuf[OUTBUFSIZ];
  704. struct metric_result *metric_res;
  705. struct metric_callback_data cd;
  706. r = snprintf (outbuf, sizeof (outbuf), "%s/%s 0 %s" CRLF "Content-Length: %zd" CRLF CRLF,
  707. (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER,
  708. task->proto_ver, "OK", task->msg->len);
  709. cd.task = task;
  710. cd.log_buf = logbuf;
  711. cd.log_offset = snprintf (logbuf, sizeof (logbuf), "msg ok, id: <%s>, ", task->message_id);
  712. cd.log_size = sizeof (logbuf);
  713. if (task->proto == SPAMC_PROTO) {
  714. /* Ignore metrics, just write report for 'default' metric */
  715. metric_res = g_hash_table_lookup (task->results, "default");
  716. if (metric_res == NULL) {
  717. /* Implicit metric result */
  718. show_metric_result (NULL, NULL, (void *)&cd);
  719. }
  720. else {
  721. show_metric_result ((gpointer) "default", (gpointer) metric_res, (void *)&cd);
  722. }
  723. }
  724. else {
  725. /* Show default metric first */
  726. metric_res = g_hash_table_lookup (task->results, "default");
  727. if (metric_res == NULL) {
  728. /* Implicit metric result */
  729. show_metric_result (NULL, NULL, (void *)&cd);
  730. }
  731. else {
  732. show_metric_result ((gpointer) "default", (gpointer) metric_res, (void *)&cd);
  733. }
  734. g_hash_table_remove (task->results, "default");
  735. /* Write result for each metric separately */
  736. g_hash_table_foreach (task->results, show_metric_result, &cd);
  737. /* Messages */
  738. show_messages (task);
  739. }
  740. write_hashes_to_log (task, logbuf, cd.log_offset, cd.log_size);
  741. msg_info ("%s", logbuf);
  742. outmsg = g_mime_object_to_string (GMIME_OBJECT (task->message));
  743. rspamd_dispatcher_write (task->dispatcher, outbuf, r, TRUE, FALSE);
  744. rspamd_dispatcher_write (task->dispatcher, outmsg, strlen (outmsg), FALSE, TRUE);
  745. task->worker->srv->stat->messages_scanned++;
  746. if (default_score >= default_required_score) {
  747. task->worker->srv->stat->messages_spam ++;
  748. }
  749. else {
  750. task->worker->srv->stat->messages_ham ++;
  751. }
  752. memory_pool_add_destructor (task->task_pool, (pool_destruct_func) g_free, outmsg);
  753. return 0;
  754. }
  755. int
  756. write_reply (struct worker_task *task)
  757. {
  758. int r;
  759. char outbuf[OUTBUFSIZ];
  760. debug_task ("writing reply to client");
  761. if (task->error_code != 0) {
  762. /* Write error message and error code to reply */
  763. if (task->proto == SPAMC_PROTO) {
  764. r = snprintf (outbuf, sizeof (outbuf), "%s/%s %d %s" CRLF CRLF,
  765. SPAMD_REPLY_BANNER, task->proto_ver, task->error_code, SPAMD_ERROR);
  766. debug_task ("writing error: %s", outbuf);
  767. }
  768. else {
  769. r = snprintf (outbuf, sizeof (outbuf), "%s/%s %d %s" CRLF "%s: %s" CRLF CRLF,
  770. RSPAMD_REPLY_BANNER, task->proto_ver, task->error_code, SPAMD_ERROR, ERROR_HEADER, task->last_error);
  771. debug_task ("writing error: %s", outbuf);
  772. }
  773. /* Write to bufferevent error message */
  774. rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE);
  775. }
  776. else {
  777. switch (task->cmd) {
  778. case CMD_REPORT_IFSPAM:
  779. case CMD_REPORT:
  780. case CMD_CHECK:
  781. case CMD_SYMBOLS:
  782. return write_check_reply (task);
  783. break;
  784. case CMD_PROCESS:
  785. return write_process_reply (task);
  786. break;
  787. case CMD_SKIP:
  788. r = snprintf (outbuf, sizeof (outbuf), "%s/%s 0 %s" CRLF,
  789. (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, task->proto_ver, SPAMD_OK);
  790. rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE);
  791. break;
  792. case CMD_PING:
  793. r = snprintf (outbuf, sizeof (outbuf), "%s/%s 0 PONG" CRLF,
  794. (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, task->proto_ver);
  795. rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE);
  796. break;
  797. case CMD_OTHER:
  798. return task->custom_cmd->func (task);
  799. }
  800. }
  801. return 0;
  802. }
  803. void
  804. register_protocol_command (const char *name, protocol_reply_func func)
  805. {
  806. struct custom_command *cmd;
  807. cmd = g_malloc (sizeof (struct custom_command));
  808. cmd->name = name;
  809. cmd->func = func;
  810. custom_commands = g_list_prepend (custom_commands, cmd);
  811. }