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 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. #include <sys/types.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <glib.h>
  5. #include "main.h"
  6. #define CRLF "\r\n"
  7. /* Max line size as it is defined in rfc2822 */
  8. #define OUTBUFSIZ 1000
  9. /*
  10. * Just check if the passed message is spam or not and reply as
  11. * described below
  12. */
  13. #define MSG_CMD_CHECK "check"
  14. /*
  15. * Check if message is spam or not, and return score plus list
  16. * of symbols hit
  17. */
  18. #define MSG_CMD_SYMBOLS "symbols"
  19. /*
  20. * Check if message is spam or not, and return score plus report
  21. */
  22. #define MSG_CMD_REPORT "report"
  23. /*
  24. * Check if message is spam or not, and return score plus report
  25. * if the message is spam
  26. */
  27. #define MSG_CMD_REPORT_IFSPAM "report_ifspam"
  28. /*
  29. * Ignore this message -- client opened connection then changed
  30. */
  31. #define MSG_CMD_SKIP "skip"
  32. /*
  33. * Return a confirmation that spamd is alive
  34. */
  35. #define MSG_CMD_PING "ping"
  36. /*
  37. * Process this message as described above and return modified message
  38. */
  39. #define MSG_CMD_PROCESS "process"
  40. /*
  41. * spamassassin greeting:
  42. */
  43. #define SPAMC_GREETING "SPAMC"
  44. /*
  45. * rspamd greeting:
  46. */
  47. #define RSPAMC_GREETING "RSPAMC"
  48. /*
  49. * Headers
  50. */
  51. #define CONTENT_LENGTH_HEADER "Content-Length"
  52. #define HELO_HEADER "Helo"
  53. #define FROM_HEADER "From"
  54. #define IP_ADDR_HEADER "IP"
  55. #define NRCPT_HEADER "Recipient-Number"
  56. #define RCPT_HEADER "Rcpt"
  57. #define ERROR_HEADER "Error"
  58. /*
  59. * Reply messages
  60. */
  61. #define RSPAMD_REPLY_BANNER "RSPAMD/1.0"
  62. #define SPAMD_REPLY_BANNER "SPAMD/1.1"
  63. #define SPAMD_OK "EX_OK"
  64. /* XXX: try to convert rspamd errors to spamd errors */
  65. #define SPAMD_ERROR "EX_ERROR"
  66. static int
  67. parse_command (struct worker_task *task, char *line)
  68. {
  69. char *token;
  70. token = strsep (&line, " ");
  71. if (line == NULL || token == NULL) {
  72. msg_debug ("parse_command: bad comand: %s", token);
  73. return -1;
  74. }
  75. switch (token[0]) {
  76. case 'c':
  77. case 'C':
  78. /* check */
  79. if (strcasecmp (token + 1, MSG_CMD_CHECK + 1) == 0) {
  80. task->cmd = CMD_CHECK;
  81. }
  82. else {
  83. msg_debug ("parse_command: bad comand: %s", token);
  84. return -1;
  85. }
  86. break;
  87. case 's':
  88. case 'S':
  89. /* symbols, skip */
  90. if (strcasecmp (token + 1, MSG_CMD_SYMBOLS + 1) == 0) {
  91. task->cmd = CMD_SYMBOLS;
  92. }
  93. else if (strcasecmp (token + 1, MSG_CMD_SKIP + 1) == 0) {
  94. task->cmd = CMD_SKIP;
  95. }
  96. else {
  97. msg_debug ("parse_command: bad comand: %s", token);
  98. return -1;
  99. }
  100. break;
  101. case 'p':
  102. case 'P':
  103. /* ping, process */
  104. if (strcasecmp (token + 1, MSG_CMD_PING + 1) == 0) {
  105. task->cmd = CMD_PING;
  106. }
  107. else if (strcasecmp (token + 1, MSG_CMD_PROCESS + 1) == 0) {
  108. task->cmd = CMD_PROCESS;
  109. }
  110. else {
  111. msg_debug ("parse_command: bad comand: %s", token);
  112. return -1;
  113. }
  114. break;
  115. case 'r':
  116. case 'R':
  117. /* report, report_ifspam */
  118. if (strcasecmp (token + 1, MSG_CMD_REPORT + 1) == 0) {
  119. task->cmd = CMD_REPORT;
  120. }
  121. else if (strcasecmp (token + 1, MSG_CMD_REPORT_IFSPAM + 1) == 0) {
  122. task->cmd = CMD_REPORT_IFSPAM;
  123. }
  124. else {
  125. msg_debug ("parse_command: bad comand: %s", token);
  126. return -1;
  127. }
  128. break;
  129. default:
  130. msg_debug ("parse_command: bad comand: %s", token);
  131. return -1;
  132. }
  133. if (strncasecmp (line, RSPAMC_GREETING, sizeof (RSPAMC_GREETING) - 1) == 0) {
  134. task->proto = RSPAMC_PROTO;
  135. }
  136. else if (strncasecmp (line, SPAMC_GREETING, sizeof (SPAMC_GREETING) -1) == 0) {
  137. task->proto = SPAMC_PROTO;
  138. }
  139. else {
  140. msg_debug ("parse_command: bad protocol version: %s", line);
  141. return -1;
  142. }
  143. task->state = READ_HEADER;
  144. return 0;
  145. }
  146. static int
  147. parse_header (struct worker_task *task, char *line)
  148. {
  149. char *headern, *err, *tmp;
  150. /* Check end of headers */
  151. if (*line == '\0') {
  152. if (task->cmd == CMD_PING || task->cmd == CMD_SKIP) {
  153. task->state = WRITE_REPLY;
  154. }
  155. else {
  156. if (task->content_length > 0) {
  157. task->state = READ_MESSAGE;
  158. }
  159. else {
  160. task->last_error = "Unknown content length";
  161. task->error_code = RSPAMD_LENGTH_ERROR;
  162. task->state = WRITE_ERROR;
  163. }
  164. }
  165. return 0;
  166. }
  167. headern = strsep (&line, ":");
  168. if (line == NULL || headern == NULL) {
  169. return -1;
  170. }
  171. /* Eat whitespaces */
  172. g_strstrip (line);
  173. g_strstrip (headern);
  174. switch (headern[0]) {
  175. case 'c':
  176. case 'C':
  177. /* content-length */
  178. if (strncasecmp (headern, CONTENT_LENGTH_HEADER, sizeof (CONTENT_LENGTH_HEADER) - 1) == 0) {
  179. task->content_length = strtoul (line, &err, 10);
  180. task->msg = memory_pool_alloc (task->task_pool, sizeof (f_str_buf_t));
  181. task->msg->buf = fstralloc (task->task_pool, task->content_length);
  182. if (task->msg->buf == NULL) {
  183. msg_err ("read_socket: cannot allocate memory for message buffer");
  184. return -1;
  185. }
  186. }
  187. else {
  188. msg_info ("parse_header: wrong header: %s", headern);
  189. return -1;
  190. }
  191. break;
  192. case 'h':
  193. case 'H':
  194. /* helo */
  195. if (strncasecmp (headern, HELO_HEADER, sizeof (HELO_HEADER) - 1) == 0) {
  196. task->helo = memory_pool_strdup (task->task_pool, line);
  197. }
  198. else {
  199. msg_info ("parse_header: wrong header: %s", headern);
  200. return -1;
  201. }
  202. break;
  203. case 'f':
  204. case 'F':
  205. /* from */
  206. if (strncasecmp (headern, FROM_HEADER, sizeof (FROM_HEADER) - 1) == 0) {
  207. task->from = memory_pool_strdup (task->task_pool, line);
  208. }
  209. else {
  210. msg_info ("parse_header: wrong header: %s", headern);
  211. return -1;
  212. }
  213. break;
  214. case 'r':
  215. case 'R':
  216. /* rcpt */
  217. if (strncasecmp (headern, RCPT_HEADER, sizeof (RCPT_HEADER) - 1) == 0) {
  218. tmp = memory_pool_strdup (task->task_pool, line);
  219. task->rcpt = g_list_prepend (task->rcpt, tmp);
  220. }
  221. else {
  222. msg_info ("parse_header: wrong header: %s", headern);
  223. return -1;
  224. }
  225. break;
  226. case 'n':
  227. case 'N':
  228. /* nrcpt */
  229. if (strncasecmp (headern, NRCPT_HEADER, sizeof (NRCPT_HEADER) - 1) == 0) {
  230. task->nrcpt = strtoul (line, &err, 10);
  231. }
  232. else {
  233. msg_info ("parse_header: wrong header: %s", headern);
  234. return -1;
  235. }
  236. break;
  237. case 'i':
  238. case 'I':
  239. /* ip_addr */
  240. if (strncasecmp (headern, IP_ADDR_HEADER, sizeof (IP_ADDR_HEADER) - 1) == 0) {
  241. if (!inet_aton (line, &task->from_addr)) {
  242. msg_info ("parse_header: bad ip header: '%s'", line);
  243. return -1;
  244. }
  245. }
  246. else {
  247. msg_info ("parse_header: wrong header: %s", headern);
  248. return -1;
  249. }
  250. break;
  251. default:
  252. msg_info ("parse_header: wrong header: %s", headern);
  253. return -1;
  254. }
  255. return 0;
  256. }
  257. int
  258. read_rspamd_input_line (struct worker_task *task, char *line)
  259. {
  260. switch (task->state) {
  261. case READ_COMMAND:
  262. return parse_command (task, line);
  263. break;
  264. case READ_HEADER:
  265. return parse_header (task, line);
  266. break;
  267. }
  268. }
  269. static void
  270. show_url_header (struct worker_task *task)
  271. {
  272. int r = 0;
  273. char outbuf[OUTBUFSIZ], c;
  274. struct uri *url;
  275. f_str_t host;
  276. r = snprintf (outbuf, sizeof (outbuf), "Urls: ");
  277. TAILQ_FOREACH (url, &task->urls, next) {
  278. host.begin = url->host;
  279. host.len = url->hostlen;
  280. /* Skip long hosts to avoid protocol coollisions */
  281. if (host.len > OUTBUFSIZ) {
  282. continue;
  283. }
  284. /* Do header folding */
  285. if (host.len + r >= OUTBUFSIZ - 3) {
  286. outbuf[r ++] = '\r'; outbuf[r ++] = '\n'; outbuf[r] = ' ';
  287. bufferevent_write (task->bev, outbuf, r);
  288. r = 0;
  289. }
  290. /* Write url host to buf */
  291. if (TAILQ_NEXT (url, next) != NULL) {
  292. c = *(host.begin + host.len);
  293. *(host.begin + host.len) = '\0';
  294. r += snprintf (outbuf, sizeof (outbuf) - r, "%s, ", host.begin);
  295. *(host.begin + host.len) = c;
  296. }
  297. else {
  298. c = *(host.begin + host.len);
  299. *(host.begin + host.len) = '\0';
  300. r += snprintf (outbuf, sizeof (outbuf) - r, "%s" CRLF, host.begin);
  301. *(host.begin + host.len) = c;
  302. }
  303. }
  304. bufferevent_write (task->bev, outbuf, r);
  305. }
  306. static void
  307. show_metric_result (gpointer metric_name, gpointer metric_value, void *user_data)
  308. {
  309. struct worker_task *task = (struct worker_task *)user_data;
  310. int r;
  311. char outbuf[OUTBUFSIZ];
  312. struct metric_result *metric_res = (struct metric_result *)metric_value;
  313. int is_spam = 0;
  314. if (metric_res->score >= metric_res->metric->required_score) {
  315. is_spam = 1;
  316. }
  317. if (task->proto == SPAMC_PROTO) {
  318. r = snprintf (outbuf, sizeof (outbuf), "Spam: %s ; %.2f / %.2f" CRLF,
  319. (is_spam) ? "True" : "False", metric_res->score, metric_res->metric->required_score);
  320. }
  321. else {
  322. r = snprintf (outbuf, sizeof (outbuf), "%s: %s ; %.2f / %.2f" CRLF, (char *)metric_name,
  323. (is_spam) ? "True" : "False", metric_res->score, metric_res->metric->required_score);
  324. }
  325. bufferevent_write (task->bev, outbuf, r);
  326. }
  327. static int
  328. write_check_reply (struct worker_task *task)
  329. {
  330. int r;
  331. char outbuf[OUTBUFSIZ];
  332. struct metric_result *metric_res;
  333. r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER);
  334. bufferevent_write (task->bev, outbuf, r);
  335. if (task->proto == SPAMC_PROTO) {
  336. /* Ignore metrics, just write report for 'default' metric */
  337. metric_res = g_hash_table_lookup (task->results, "default");
  338. if (metric_res == NULL) {
  339. return -1;
  340. }
  341. else {
  342. show_metric_result ((gpointer)"default", (gpointer)metric_res, (void *)task);
  343. }
  344. }
  345. else {
  346. /* Write result for each metric separately */
  347. g_hash_table_foreach (task->results, show_metric_result, task);
  348. /* URL stat */
  349. show_url_header (task);
  350. }
  351. bufferevent_write (task->bev, CRLF, sizeof (CRLF) - 1);
  352. return 0;
  353. }
  354. static void
  355. show_metric_symbols (gpointer metric_name, gpointer metric_value, void *user_data)
  356. {
  357. struct worker_task *task = (struct worker_task *)user_data;
  358. int r = 0;
  359. char outbuf[OUTBUFSIZ];
  360. GList *symbols = NULL, *cur;
  361. struct metric_result *metric_res = (struct metric_result *)metric_value;
  362. if (task->proto == RSPAMC_PROTO) {
  363. r = snprintf (outbuf, sizeof (outbuf), "%s: ", (char *)metric_name);
  364. }
  365. symbols = g_hash_table_get_keys (metric_res->symbols);
  366. cur = symbols;
  367. while (cur) {
  368. if (g_list_next (cur) != NULL) {
  369. r += snprintf (outbuf + r, sizeof (outbuf) - r, "%s,", (char *)cur->data);
  370. }
  371. else {
  372. r += snprintf (outbuf + r, sizeof (outbuf) - r, "%s", (char *)cur->data);
  373. }
  374. cur = g_list_next (cur);
  375. }
  376. g_list_free (symbols);
  377. outbuf[r++] = '\r'; outbuf[r] = '\n';
  378. bufferevent_write (task->bev, outbuf, r);
  379. }
  380. static int
  381. write_symbols_reply (struct worker_task *task)
  382. {
  383. struct metric_result *metric_res;
  384. /* First of all write normal results by calling write_check_reply */
  385. if (write_check_reply (task) == -1) {
  386. return -1;
  387. }
  388. /* Now write symbols */
  389. if (task->proto == SPAMC_PROTO) {
  390. /* Ignore metrics, just write report for 'default' metric */
  391. metric_res = g_hash_table_lookup (task->results, "default");
  392. if (metric_res == NULL) {
  393. return -1;
  394. }
  395. else {
  396. show_metric_symbols ((gpointer)"default", (gpointer)metric_res, (void *)task);
  397. }
  398. }
  399. else {
  400. /* Write result for each metric separately */
  401. g_hash_table_foreach (task->results, show_metric_symbols, task);
  402. }
  403. return 0;
  404. }
  405. static int
  406. write_process_reply (struct worker_task *task)
  407. {
  408. int r;
  409. char outbuf[OUTBUFSIZ];
  410. r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF "Content-Length: %zd" CRLF CRLF,
  411. (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, task->msg->buf->len);
  412. bufferevent_write (task->bev, outbuf, r);
  413. bufferevent_write (task->bev, task->msg->buf->begin, task->msg->buf->len);
  414. return 0;
  415. }
  416. int
  417. write_reply (struct worker_task *task)
  418. {
  419. int r;
  420. char outbuf[OUTBUFSIZ];
  421. msg_debug ("write_reply: writing reply to client");
  422. if (task->error_code != 0) {
  423. /* Write error message and error code to reply */
  424. if (task->proto == SPAMC_PROTO) {
  425. r = snprintf (outbuf, sizeof (outbuf), "%s %d %s" CRLF CRLF, SPAMD_REPLY_BANNER, task->error_code, SPAMD_ERROR);
  426. msg_debug ("write_reply: writing error: %s", outbuf);
  427. }
  428. else {
  429. r = snprintf (outbuf, sizeof (outbuf), "%s %d %s" CRLF "%s: %s" CRLF CRLF, RSPAMD_REPLY_BANNER, task->error_code,
  430. SPAMD_ERROR, ERROR_HEADER, task->last_error);
  431. msg_debug ("write_reply: writing error: %s", outbuf);
  432. }
  433. /* Write to bufferevent error message */
  434. bufferevent_write (task->bev, outbuf, r);
  435. }
  436. else {
  437. switch (task->cmd) {
  438. case CMD_REPORT_IFSPAM:
  439. case CMD_REPORT:
  440. case CMD_CHECK:
  441. return write_check_reply (task);
  442. break;
  443. case CMD_SYMBOLS:
  444. return write_symbols_reply (task);
  445. break;
  446. case CMD_PROCESS:
  447. return write_process_reply (task);
  448. break;
  449. case CMD_SKIP:
  450. r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER,
  451. SPAMD_OK);
  452. bufferevent_write (task->bev, outbuf, r);
  453. break;
  454. case CMD_PING:
  455. r = snprintf (outbuf, sizeof (outbuf), "%s 0 PONG" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER);
  456. bufferevent_write (task->bev, outbuf, r);
  457. break;
  458. }
  459. }
  460. return 0;
  461. }