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.

surbl.c 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /***MODULE:surbl
  2. * rspamd module that implements SURBL url checking
  3. */
  4. #include <sys/types.h>
  5. #include <sys/time.h>
  6. #include <sys/wait.h>
  7. #include <sys/param.h>
  8. #include <netinet/in.h>
  9. #include <arpa/inet.h>
  10. #include <netdb.h>
  11. #include <syslog.h>
  12. #include <fcntl.h>
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include <evdns.h>
  16. #include "../config.h"
  17. #include "../main.h"
  18. #include "../modules.h"
  19. #include "../cfg_file.h"
  20. #include "../memcached.h"
  21. #define DEFAULT_REDIRECTOR_PORT 8080
  22. #define DEFAULT_SURBL_WEIGHT 10
  23. #define DEFAULT_REDIRECTOR_CONNECT_TIMEOUT 1000
  24. #define DEFAULT_REDIRECTOR_READ_TIMEOUT 5000
  25. #define DEFAULT_SURBL_MAX_URLS 1000
  26. #define DEFAULT_SURBL_URL_EXPIRE 86400
  27. #define DEFAULT_SURBL_SYMBOL "SURBL_DNS"
  28. #define DEFAULT_SURBL_SUFFIX "multi.surbl.org"
  29. struct surbl_ctx {
  30. int (*header_filter)(struct worker_task *task);
  31. int (*mime_filter)(struct worker_task *task);
  32. int (*message_filter)(struct worker_task *task);
  33. int (*url_filter)(struct worker_task *task);
  34. struct in_addr redirector_addr;
  35. uint16_t redirector_port;
  36. uint16_t weight;
  37. unsigned int connect_timeout;
  38. unsigned int read_timeout;
  39. unsigned int max_urls;
  40. unsigned int url_expire;
  41. char *suffix;
  42. char *symbol;
  43. char *metric;
  44. GHashTable *hosters;
  45. GHashTable *whitelist;
  46. unsigned use_redirector;
  47. memory_pool_t *surbl_pool;
  48. };
  49. struct redirector_param {
  50. struct uri *url;
  51. struct worker_task *task;
  52. enum {
  53. STATE_CONNECT,
  54. STATE_READ,
  55. } state;
  56. struct event ev;
  57. int sock;
  58. };
  59. struct memcached_param {
  60. struct uri *url;
  61. struct worker_task *task;
  62. memcached_ctx_t *ctx;
  63. };
  64. static char *hash_fill = "1";
  65. struct surbl_ctx *surbl_module_ctx;
  66. GRegex *extract_hoster_regexp, *extract_normal_regexp, *extract_numeric_regexp;
  67. static int surbl_test_url (struct worker_task *task);
  68. int
  69. surbl_module_init (struct config_file *cfg, struct module_ctx **ctx)
  70. {
  71. GError *err = NULL;
  72. surbl_module_ctx = g_malloc (sizeof (struct surbl_ctx));
  73. surbl_module_ctx->header_filter = NULL;
  74. surbl_module_ctx->mime_filter = NULL;
  75. surbl_module_ctx->message_filter = NULL;
  76. surbl_module_ctx->url_filter = surbl_test_url;
  77. surbl_module_ctx->use_redirector = 0;
  78. surbl_module_ctx->surbl_pool = memory_pool_new (1024);
  79. surbl_module_ctx->hosters = g_hash_table_new (g_str_hash, g_str_equal);
  80. /* Register destructors */
  81. memory_pool_add_destructor (surbl_module_ctx->surbl_pool, (pool_destruct_func)g_hash_table_remove_all, surbl_module_ctx->hosters);
  82. surbl_module_ctx->whitelist = g_hash_table_new (g_str_hash, g_str_equal);
  83. /* Register destructors */
  84. memory_pool_add_destructor (surbl_module_ctx->surbl_pool, (pool_destruct_func)g_hash_table_remove_all, surbl_module_ctx->whitelist);
  85. /* Init matching regexps */
  86. extract_hoster_regexp = g_regex_new ("([^.]+)\\.([^.]+)\\.([^.]+)$", G_REGEX_RAW, 0, &err);
  87. extract_normal_regexp = g_regex_new ("([^.]+)\\.([^.]+)$", G_REGEX_RAW, 0, &err);
  88. extract_numeric_regexp = g_regex_new ("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$", G_REGEX_RAW, 0, &err);
  89. *ctx = (struct module_ctx *)surbl_module_ctx;
  90. return 0;
  91. }
  92. int
  93. surbl_module_config (struct config_file *cfg)
  94. {
  95. struct hostent *hent;
  96. char *value, *cur_tok, *str;
  97. evdns_init ();
  98. if ((value = get_module_opt (cfg, "surbl", "redirector")) != NULL) {
  99. str = memory_pool_strdup (surbl_module_ctx->surbl_pool, value);
  100. cur_tok = strsep (&str, ":");
  101. if (!inet_aton (cur_tok, &surbl_module_ctx->redirector_addr)) {
  102. /* Try to call gethostbyname */
  103. hent = gethostbyname (cur_tok);
  104. if (hent != NULL) {
  105. memcpy((char *)&surbl_module_ctx->redirector_addr, hent->h_addr, sizeof(struct in_addr));
  106. if (str != NULL) {
  107. surbl_module_ctx->redirector_port = (uint16_t)strtoul (str, NULL, 10);
  108. }
  109. else {
  110. surbl_module_ctx->redirector_port = DEFAULT_REDIRECTOR_PORT;
  111. }
  112. surbl_module_ctx->use_redirector = 1;
  113. }
  114. }
  115. /* Free cur_tok as it is actually initial str after strsep */
  116. free (cur_tok);
  117. }
  118. if ((value = get_module_opt (cfg, "surbl", "weight")) != NULL) {
  119. surbl_module_ctx->weight = atoi (value);
  120. }
  121. else {
  122. surbl_module_ctx->weight = DEFAULT_SURBL_WEIGHT;
  123. }
  124. if ((value = get_module_opt (cfg, "surbl", "url_expire")) != NULL) {
  125. surbl_module_ctx->url_expire = atoi (value);
  126. }
  127. else {
  128. surbl_module_ctx->url_expire = DEFAULT_SURBL_URL_EXPIRE;
  129. }
  130. if ((value = get_module_opt (cfg, "surbl", "redirector_connect_timeout")) != NULL) {
  131. surbl_module_ctx->connect_timeout = parse_seconds (value);
  132. }
  133. else {
  134. surbl_module_ctx->connect_timeout = DEFAULT_REDIRECTOR_CONNECT_TIMEOUT;
  135. }
  136. if ((value = get_module_opt (cfg, "surbl", "redirector_read_timeout")) != NULL) {
  137. surbl_module_ctx->read_timeout = parse_seconds (value);
  138. }
  139. else {
  140. surbl_module_ctx->read_timeout = DEFAULT_REDIRECTOR_READ_TIMEOUT;
  141. }
  142. if ((value = get_module_opt (cfg, "surbl", "max_urls")) != NULL) {
  143. surbl_module_ctx->max_urls = atoi (value);
  144. }
  145. else {
  146. surbl_module_ctx->max_urls = DEFAULT_SURBL_MAX_URLS;
  147. }
  148. if ((value = get_module_opt (cfg, "surbl", "suffix")) != NULL) {
  149. surbl_module_ctx->suffix = memory_pool_strdup (surbl_module_ctx->surbl_pool, value);
  150. g_free (value);
  151. }
  152. else {
  153. surbl_module_ctx->suffix = DEFAULT_SURBL_SUFFIX;
  154. }
  155. if ((value = get_module_opt (cfg, "surbl", "symbol")) != NULL) {
  156. surbl_module_ctx->symbol = memory_pool_strdup (surbl_module_ctx->surbl_pool, value);
  157. g_free (value);
  158. }
  159. else {
  160. surbl_module_ctx->symbol = DEFAULT_SURBL_SYMBOL;
  161. }
  162. if ((value = get_module_opt (cfg, "surbl", "metric")) != NULL) {
  163. surbl_module_ctx->metric = memory_pool_strdup (surbl_module_ctx->surbl_pool, value);
  164. g_free (value);
  165. }
  166. else {
  167. surbl_module_ctx->metric = DEFAULT_METRIC;
  168. }
  169. if ((value = get_module_opt (cfg, "surbl", "hostings")) != NULL) {
  170. char comment_flag = 0;
  171. str = value;
  172. while (*value ++) {
  173. if (*value == '#') {
  174. comment_flag = 1;
  175. }
  176. if (*value == '\r' || *value == '\n' || *value == ',') {
  177. if (!comment_flag && str != value) {
  178. g_hash_table_insert (surbl_module_ctx->hosters, g_strstrip(str), hash_fill);
  179. str = value + 1;
  180. }
  181. else if (*value != ',') {
  182. comment_flag = 0;
  183. str = value + 1;
  184. }
  185. }
  186. }
  187. }
  188. if ((value = get_module_opt (cfg, "surbl", "whitelist")) != NULL) {
  189. char comment_flag = 0;
  190. str = value;
  191. while (*value ++) {
  192. if (*value == '#') {
  193. comment_flag = 1;
  194. }
  195. if (*value == '\r' || *value == '\n' || *value == ',') {
  196. if (!comment_flag && str != value) {
  197. g_hash_table_insert (surbl_module_ctx->whitelist, g_strstrip(str), hash_fill);
  198. str = value + 1;
  199. }
  200. else if (*value != ',') {
  201. comment_flag = 0;
  202. str = value + 1;
  203. }
  204. }
  205. }
  206. }
  207. }
  208. int
  209. surbl_module_reconfig (struct config_file *cfg)
  210. {
  211. memory_pool_delete (surbl_module_ctx->surbl_pool);
  212. surbl_module_ctx->surbl_pool = memory_pool_new (1024);
  213. return surbl_module_config (cfg);
  214. }
  215. static char *
  216. format_surbl_request (char *hostname)
  217. {
  218. GMatchInfo *info;
  219. char *result;
  220. result = g_malloc (strlen (hostname) + strlen (surbl_module_ctx->suffix) + 1);
  221. /* First try to match numeric expression */
  222. if (g_regex_match (extract_numeric_regexp, hostname, 0, &info) == TRUE) {
  223. gchar *octet1, *octet2, *octet3, *octet4;
  224. octet1 = g_match_info_fetch (info, 0);
  225. g_match_info_next (info, NULL);
  226. octet2 = g_match_info_fetch (info, 0);
  227. g_match_info_next (info, NULL);
  228. octet3 = g_match_info_fetch (info, 0);
  229. g_match_info_next (info, NULL);
  230. octet4 = g_match_info_fetch (info, 0);
  231. g_match_info_free (info);
  232. sprintf (result, "%s.%s.%s.%s.%s", octet4, octet3, octet2, octet1, surbl_module_ctx->suffix);
  233. g_free (octet1);
  234. g_free (octet2);
  235. g_free (octet3);
  236. g_free (octet4);
  237. return result;
  238. }
  239. g_match_info_free (info);
  240. /* Try to match normal domain */
  241. if (g_regex_match (extract_normal_regexp, hostname, 0, &info) == TRUE) {
  242. gchar *part1, *part2;
  243. part1 = g_match_info_fetch (info, 0);
  244. g_match_info_next (info, NULL);
  245. part2 = g_match_info_fetch (info, 0);
  246. g_match_info_free (info);
  247. sprintf (result, "%s.%s", part1, part2);
  248. if (g_hash_table_lookup (surbl_module_ctx->hosters, result) != NULL) {
  249. /* Match additional part for hosters */
  250. g_free (part1);
  251. g_free (part2);
  252. if (g_regex_match (extract_hoster_regexp, hostname, 0, &info) == TRUE) {
  253. gchar *hpart1, *hpart2, *hpart3;
  254. hpart1 = g_match_info_fetch (info, 0);
  255. g_match_info_next (info, NULL);
  256. hpart2 = g_match_info_fetch (info, 0);
  257. g_match_info_next (info, NULL);
  258. hpart3 = g_match_info_fetch (info, 0);
  259. g_match_info_free (info);
  260. sprintf (result, "%s.%s.%s.%s", hpart1, hpart2, hpart3, surbl_module_ctx->suffix);
  261. g_free (hpart1);
  262. g_free (hpart2);
  263. g_free (hpart3);
  264. return result;
  265. }
  266. return NULL;
  267. }
  268. g_free (part1);
  269. g_free (part2);
  270. return result;
  271. }
  272. return NULL;
  273. }
  274. static void
  275. dns_callback (int result, char type, int count, int ttl, void *addresses, void *data)
  276. {
  277. struct memcached_param *param = (struct memcached_param *)data;
  278. /* If we have result from DNS server, this url exists in SURBL, so increase score */
  279. if (result != DNS_ERR_NONE || type != DNS_IPv4_A) {
  280. msg_info ("surbl_check: url %s is in surbl %s", param->url->host, surbl_module_ctx->suffix);
  281. insert_result (param->task, surbl_module_ctx->metric, surbl_module_ctx->symbol, 1);
  282. }
  283. else {
  284. insert_result (param->task, surbl_module_ctx->metric, surbl_module_ctx->symbol, 0);
  285. }
  286. param->task->save.saved --;
  287. if (param->task->save.saved == 0) {
  288. /* Call other filters */
  289. param->task->save.saved = 1;
  290. process_filters (param->task);
  291. }
  292. g_free (param->ctx->param->buf);
  293. g_free (param->ctx->param);
  294. g_free (param->ctx);
  295. g_free (param);
  296. }
  297. static void
  298. memcached_callback (memcached_ctx_t *ctx, memc_error_t error, void *data)
  299. {
  300. struct memcached_param *param = (struct memcached_param *)data;
  301. int *url_count;
  302. char *surbl_req;
  303. switch (ctx->op) {
  304. case CMD_CONNECT:
  305. if (error != OK) {
  306. msg_info ("memcached_callback: memcached returned error %s on CONNECT stage");
  307. memc_close_ctx (param->ctx);
  308. param->task->save.saved --;
  309. if (param->task->save.saved == 0) {
  310. /* Call other filters */
  311. param->task->save.saved = 1;
  312. process_filters (param->task);
  313. }
  314. g_free (param->ctx->param->buf);
  315. g_free (param->ctx->param);
  316. g_free (param->ctx);
  317. g_free (param);
  318. }
  319. else {
  320. memc_get (param->ctx, param->ctx->param);
  321. }
  322. break;
  323. case CMD_READ:
  324. if (error != OK) {
  325. msg_info ("memcached_callback: memcached returned error %s on READ stage");
  326. memc_close_ctx (param->ctx);
  327. param->task->save.saved --;
  328. if (param->task->save.saved == 0) {
  329. /* Call other filters */
  330. param->task->save.saved = 1;
  331. process_filters (param->task);
  332. }
  333. g_free (param->ctx->param->buf);
  334. g_free (param->ctx->param);
  335. g_free (param->ctx);
  336. g_free (param);
  337. }
  338. else {
  339. url_count = (int *)param->ctx->param->buf;
  340. /* Do not check DNS for urls that have count more than max_urls */
  341. if (*url_count > surbl_module_ctx->max_urls) {
  342. msg_info ("memcached_callback: url '%s' has count %d, max: %d", struri (param->url), *url_count, surbl_module_ctx->max_urls);
  343. insert_result (param->task, surbl_module_ctx->metric, surbl_module_ctx->symbol, 1);
  344. }
  345. (*url_count) ++;
  346. memc_set (param->ctx, param->ctx->param, surbl_module_ctx->url_expire);
  347. }
  348. break;
  349. case CMD_WRITE:
  350. if (error != OK) {
  351. msg_info ("memcached_callback: memcached returned error %s on WRITE stage");
  352. }
  353. memc_close_ctx (param->ctx);
  354. param->task->save.saved --;
  355. if (param->task->save.saved == 0) {
  356. /* Call other filters */
  357. param->task->save.saved = 1;
  358. process_filters (param->task);
  359. }
  360. if ((surbl_req = format_surbl_request (param->url->host)) != NULL) {
  361. param->task->save.saved ++;
  362. evdns_resolve_ipv4 (surbl_req, DNS_QUERY_NO_SEARCH, dns_callback, (void *)param);
  363. return;
  364. }
  365. g_free (param->ctx->param->buf);
  366. g_free (param->ctx->param);
  367. g_free (param->ctx);
  368. g_free (param);
  369. break;
  370. }
  371. }
  372. static void
  373. register_memcached_call (struct uri *url, struct worker_task *task)
  374. {
  375. struct memcached_param *param;
  376. struct memcached_server *selected;
  377. memcached_param_t *cur_param;
  378. gchar *sum_str;
  379. int *url_count;
  380. param = g_malloc (sizeof (struct memcached_param));
  381. cur_param = g_malloc (sizeof (memcached_param_t));
  382. url_count = g_malloc (sizeof (int));
  383. param->url = url;
  384. param->task = task;
  385. param->ctx = g_malloc (sizeof (memcached_ctx_t));
  386. bzero (param->ctx, sizeof (memcached_ctx_t));
  387. bzero (cur_param, sizeof (memcached_param_t));
  388. cur_param->buf = (u_char *)url_count;
  389. cur_param->bufsize = sizeof (int);
  390. sum_str = g_compute_checksum_for_string (G_CHECKSUM_MD5, struri (url), -1);
  391. strlcpy (cur_param->key, sum_str, sizeof (cur_param->key));
  392. g_free (sum_str);
  393. selected = (struct memcached_server *) get_upstream_by_hash ((void *)task->cfg->memcached_servers,
  394. task->cfg->memcached_servers_num, sizeof (struct memcached_server),
  395. time (NULL), task->cfg->memcached_error_time, task->cfg->memcached_dead_time, task->cfg->memcached_maxerrors,
  396. cur_param->key, strlen(cur_param->key));
  397. param->ctx->callback = memcached_callback;
  398. param->ctx->callback_data = (void *)param;
  399. param->ctx->protocol = task->cfg->memcached_protocol;
  400. memcpy(&param->ctx->addr, &selected->addr, sizeof (struct in_addr));
  401. param->ctx->port = selected->port;
  402. param->ctx->timeout.tv_sec = task->cfg->memcached_connect_timeout / 1000;
  403. param->ctx->timeout.tv_sec = task->cfg->memcached_connect_timeout - param->ctx->timeout.tv_sec * 1000;
  404. param->ctx->sock = -1;
  405. #ifdef WITH_DEBUG
  406. param->ctx->options = MEMC_OPT_DEBUG;
  407. #else
  408. param->ctx->options = 0;
  409. #endif
  410. param->ctx->param = cur_param;
  411. memc_init_ctx (param->ctx);
  412. }
  413. static void
  414. redirector_callback (int fd, short what, void *arg)
  415. {
  416. struct redirector_param *param = (struct redirector_param *)arg;
  417. char url_buf[1024];
  418. int r;
  419. struct timeval timeout;
  420. char *p, *c;
  421. switch (param->state) {
  422. case STATE_CONNECT:
  423. /* We have write readiness after connect call, so reinit event */
  424. if (what == EV_WRITE) {
  425. timeout.tv_sec = surbl_module_ctx->connect_timeout / 1000;
  426. timeout.tv_usec = surbl_module_ctx->connect_timeout - timeout.tv_sec * 1000;
  427. event_del (&param->ev);
  428. event_set (&param->ev, param->sock, EV_READ | EV_PERSIST | EV_TIMEOUT, redirector_callback, (void *)param);
  429. event_add (&param->ev, &timeout);
  430. r = snprintf (url_buf, sizeof (url_buf), "GET %s HTTP/1.0\r\n\r\n", struri (param->url));
  431. write (param->sock, url_buf, r);
  432. param->state = STATE_READ;
  433. }
  434. else {
  435. event_del (&param->ev);
  436. msg_info ("redirector_callback: connection to redirector timed out");
  437. param->task->save.saved --;
  438. if (param->task->save.saved == 0) {
  439. /* Call other filters */
  440. param->task->save.saved = 1;
  441. process_filters (param->task);
  442. }
  443. g_free (param);
  444. }
  445. break;
  446. case STATE_READ:
  447. if (what == EV_READ) {
  448. r = read (param->sock, url_buf, sizeof (url_buf));
  449. if ((p = strstr (url_buf, "Uri: ")) != NULL) {
  450. p += sizeof ("Uri: ") - 1;
  451. c = p;
  452. while (p++ < url_buf + sizeof (url_buf) - 1) {
  453. if (*p == '\r' || *p == '\n') {
  454. *p = '\0';
  455. break;
  456. }
  457. }
  458. if (*p == '\0') {
  459. msg_info ("redirector_callback: got reply from redirector: '%s' -> '%s'", struri (param->url), c);
  460. parse_uri (param->url, c, param->task->task_pool);
  461. register_memcached_call (param->url, param->task);
  462. param->task->save.saved ++;
  463. }
  464. }
  465. event_del (&param->ev);
  466. param->task->save.saved --;
  467. if (param->task->save.saved == 0) {
  468. /* Call other filters */
  469. param->task->save.saved = 1;
  470. process_filters (param->task);
  471. }
  472. g_free (param);
  473. }
  474. else {
  475. event_del (&param->ev);
  476. msg_info ("redirector_callback: reading redirector timed out");
  477. param->task->save.saved --;
  478. if (param->task->save.saved == 0) {
  479. /* Call other filters */
  480. param->task->save.saved = 1;
  481. process_filters (param->task);
  482. }
  483. g_free (param);
  484. }
  485. break;
  486. }
  487. }
  488. static void
  489. register_redirector_call (struct uri *url, struct worker_task *task)
  490. {
  491. struct sockaddr_in sc;
  492. int ofl, r, s;
  493. struct redirector_param *param;
  494. struct timeval timeout;
  495. bzero (&sc, sizeof (struct sockaddr_in *));
  496. sc.sin_family = AF_INET;
  497. sc.sin_port = surbl_module_ctx->redirector_port;
  498. memcpy (&sc.sin_addr, &surbl_module_ctx->redirector_addr, sizeof (struct in_addr));
  499. s = socket (PF_INET, SOCK_STREAM, 0);
  500. if (s == -1) {
  501. msg_info ("register_redirector_call: socket() failed: %m");
  502. return;
  503. }
  504. /* set nonblocking */
  505. ofl = fcntl(s, F_GETFL, 0);
  506. fcntl(s, F_SETFL, ofl | O_NONBLOCK);
  507. if ((r = connect (s, (struct sockaddr*)&sc, sizeof (struct sockaddr_in))) == -1) {
  508. if (errno != EINPROGRESS) {
  509. close (s);
  510. msg_info ("register_redirector_call: connect() failed: %m");
  511. }
  512. }
  513. param = g_malloc (sizeof (struct redirector_param));
  514. param->url = url;
  515. param->task = task;
  516. param->state = STATE_READ;
  517. param->sock = s;
  518. timeout.tv_sec = surbl_module_ctx->connect_timeout / 1000;
  519. timeout.tv_usec = surbl_module_ctx->connect_timeout - timeout.tv_sec * 1000;
  520. event_set (&param->ev, s, EV_WRITE | EV_TIMEOUT, redirector_callback, (void *)param);
  521. event_add (&param->ev, &timeout);
  522. }
  523. static int
  524. surbl_test_url (struct worker_task *task)
  525. {
  526. struct uri *url;
  527. TAILQ_FOREACH (url, &task->urls, next) {
  528. if (surbl_module_ctx->use_redirector) {
  529. register_redirector_call (url, task);
  530. }
  531. else {
  532. register_memcached_call (url, task);
  533. }
  534. task->save.saved++;
  535. }
  536. return 0;
  537. }
  538. /*
  539. * vi:ts=4
  540. */