pfix-srsd: add a -I option
[apps/pfixtools.git] / postlicyd / strlist.c
1 /******************************************************************************/
2 /*          pfixtools: a collection of postfix related tools                  */
3 /*          ~~~~~~~~~                                                         */
4 /*  ________________________________________________________________________  */
5 /*                                                                            */
6 /*  Redistribution and use in source and binary forms, with or without        */
7 /*  modification, are permitted provided that the following conditions        */
8 /*  are met:                                                                  */
9 /*                                                                            */
10 /*  1. Redistributions of source code must retain the above copyright         */
11 /*     notice, this list of conditions and the following disclaimer.          */
12 /*  2. Redistributions in binary form must reproduce the above copyright      */
13 /*     notice, this list of conditions and the following disclaimer in the    */
14 /*     documentation and/or other materials provided with the distribution.   */
15 /*  3. The names of its contributors may not be used to endorse or promote    */
16 /*     products derived from this software without specific prior written     */
17 /*     permission.                                                            */
18 /*                                                                            */
19 /*  THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS   */
20 /*  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED         */
21 /*  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE    */
22 /*  DISCLAIMED.  IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY         */
23 /*  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL        */
24 /*  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS   */
25 /*  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)     */
26 /*  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,       */
27 /*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN  */
28 /*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
29 /*  POSSIBILITY OF SUCH DAMAGE.                                               */
30 /*                                                                            */
31 /*   Copyright (c) 2006-2008 the Authors                                      */
32 /*   see AUTHORS and source files for details                                 */
33 /******************************************************************************/
34
35 /*
36  * Copyright © 2008 Florent Bruneau
37  */
38
39 #include "filter.h"
40 #include "trie.h"
41 #include "file.h"
42 #include "str.h"
43 #include "rbl.h"
44 #include "policy_tokens.h"
45 #include "resources.h"
46
47 typedef struct strlist_local_t {
48     char     *filename;
49     trie_t   **db;
50     int      weight;
51     unsigned reverse     :1;
52     unsigned partial     :1;
53 } strlist_local_t;
54 ARRAY(strlist_local_t)
55
56 typedef struct strlist_resource_t {
57     off_t  size;
58     time_t mtime;
59     trie_t *trie1;
60     trie_t *trie2;
61 } strlist_resource_t;
62
63 typedef struct strlist_config_t {
64     A(strlist_local_t) locals;
65
66     A(char)     hosts;
67     A(int)      host_offsets;
68     A(int)      host_weights;
69
70     int soft_threshold;
71     int hard_threshold;
72
73     unsigned is_email         :1;
74     unsigned is_hostname      :1;
75
76     unsigned match_sender     :1;
77     unsigned match_recipient  :1;
78
79     unsigned match_helo       :1;
80     unsigned match_client     :1;
81     unsigned match_reverse    :1;
82 } strlist_config_t;
83
84 typedef struct strlist_async_data_t {
85     A(rbl_result_t) results;
86     int awaited;
87     uint32_t sum;
88     bool error;
89 } strlist_async_data_t;
90
91 static filter_type_t filter_type = FTK_UNKNOWN;
92
93
94 static void strlist_local_wipe(strlist_local_t *entry)
95 {
96     if (entry->filename != NULL) {
97         resource_release("strlist", entry->filename);
98         p_delete(&entry->filename);
99     }
100 }
101
102 static void strlist_resource_wipe(strlist_resource_t *res)
103 {
104     trie_delete(&res->trie1);
105     trie_delete(&res->trie2);
106     p_delete(&res);
107 }
108
109 static strlist_config_t *strlist_config_new(void)
110 {
111     return p_new(strlist_config_t, 1);
112 }
113
114 static void strlist_config_delete(strlist_config_t **config)
115 {
116     if (*config) {
117         array_deep_wipe((*config)->locals, strlist_local_wipe);
118         array_wipe((*config)->hosts);
119         array_wipe((*config)->host_offsets);
120         array_wipe((*config)->host_weights);
121         p_delete(config);
122     }
123 }
124
125 static inline void strlist_copy(char *dest, const char *str, ssize_t str_len,
126                                 bool reverse)
127 {
128     if (str_len > 0) {
129         if (reverse) {
130             for (const char *src = str + str_len - 1 ; src >= str ; --src) {
131                 *dest = ascii_tolower(*src);
132                 ++dest;
133             }
134         } else {
135             for (int i = 0 ; i < str_len ; ++i) {
136                 *dest = ascii_tolower(str[i]);
137                 ++dest;
138             }
139         }
140     }
141     *dest = '\0';
142 }
143
144
145 static bool strlist_create(strlist_local_t *local,
146                            const char *file, int weight,
147                            bool reverse, bool partial, bool lock)
148 {
149     file_map_t map;
150     const char *p, *end;
151     char line[BUFSIZ];
152     uint32_t count = 0;
153     time_t now = time(0);
154
155     if (!file_map_open(&map, file, false)) {
156         return false;
157     }
158     p   = map.map;
159     end = map.end;
160     while (end > p && end[-1] != '\n') {
161         --end;
162     }
163     if (end != map.end) {
164         warn("%s: final \\n missing, ignoring last line", file);
165     }
166
167     strlist_resource_t *res = resource_get("strlist", file);
168     if (res == NULL) {
169         res = p_new(strlist_resource_t, 1);
170         resource_set("strlist", file, res, (resource_destructor_t)strlist_resource_wipe);
171     } else if (res->trie2 != NULL) {
172         err("%s not loaded: the file is already used as a rbldns zone file", file);
173         resource_release("strlist", file);
174         file_map_close(&map);
175         return false;
176     }
177
178     p_clear(local, 1);
179     local->filename = m_strdup(file);
180     local->db      = &res->trie1;
181     local->weight  = weight;
182     local->reverse = reverse;
183     local->partial = partial;
184     if (res->size == map.st.st_size && res->mtime == map.st.st_mtime) {
185         info("%s loaded: already up-to-date", file);
186         file_map_close(&map);
187         return true;
188     }
189     trie_delete(&res->trie1);
190     res->trie1 = trie_new();
191     res->size  = map.st.st_size;
192     res->mtime = map.st.st_mtime;
193
194     while (p < end && p != NULL) {
195         const char *eol = (char *)memchr(p, '\n', end - p);
196         if (eol == NULL) {
197             eol = end;
198         }
199         if (eol - p >= BUFSIZ) {
200             err("%s not loaded: unreasonnable long line", file);
201             file_map_close(&map);
202             trie_delete(&res->trie1);
203             strlist_local_wipe(local);
204             return false;
205         }
206         if (*p != '#') {
207             const char *eos = eol;
208             while (p < eos && isspace(*p)) {
209                 ++p;
210             }
211             while (p < eos && isspace(eos[-1])) {
212                 --eos;
213             }
214             if (p < eos) {
215                 strlist_copy(line, p, eos - p, reverse);
216                 trie_insert(res->trie1, line);
217                 ++count;
218             }
219         }
220         p = eol + 1;
221     }
222     file_map_close(&map);
223     trie_compile(res->trie1, lock);
224     info("%s loaded: done in %us, %u entries", file, (uint32_t)(time(0) - now), count);
225     return true;
226 }
227
228 static bool strlist_create_from_rhbl(strlist_local_t *hosts, strlist_local_t *domains,
229                                      const char *file, int weight, bool lock)
230 {
231     uint32_t host_count, domain_count;
232     file_map_t map;
233     const char *p, *end;
234     char line[BUFSIZ];
235     time_t now = time(0);
236
237     if (!file_map_open(&map, file, false)) {
238         return false;
239     }
240     p   = map.map;
241     end = map.end;
242     while (end > p && end[-1] != '\n') {
243         --end;
244     }
245     if (end != map.end) {
246         warn("%s: final \\n missing, ignoring last line", file);
247     }
248
249
250     strlist_resource_t *res = resource_get("strlist", file);
251     if (res == NULL) {
252         res = p_new(strlist_resource_t, 1);
253         resource_set("strlist", file, res, (resource_destructor_t)strlist_resource_wipe);
254     } else if (res->trie2 == NULL) {
255         err("%s not loaded: the file is already used as a strlist-file parameter", file);
256         resource_release("strlist", file);
257         file_map_close(&map);
258         return false;
259     }
260
261     p_clear(hosts, 1);
262     hosts->filename = m_strdup(file);
263     hosts->db = &res->trie1;
264     hosts->weight = weight;
265     hosts->reverse    = true;
266     host_count = 0;
267
268     p_clear(domains, 1);
269     /* don't set filename */
270     domains->db = &res->trie2;
271     domains->weight = weight;
272     domains->reverse      = true;
273     domains->partial      = true;
274     domain_count = 0;
275
276     if (map.st.st_size == res->size && map.st.st_mtime == res->mtime) {
277         info("%s loaded: already up-to-date", file);
278         file_map_close(&map);
279         return true;
280     }
281
282     trie_delete(&res->trie1);
283     trie_delete(&res->trie2);
284     res->trie1 = trie_new();
285     res->trie2 = trie_new();
286     res->size  = map.st.st_size;
287     res->mtime = map.st.st_mtime;
288
289     while (p < end && p != NULL) {
290         const char *eol = (char *)memchr(p, '\n', end - p);
291         if (eol == NULL) {
292             eol = end;
293         }
294         if (eol - p >= BUFSIZ) {
295             err("%s not loaded: unreasonnable long line", file);
296             file_map_close(&map);
297             trie_delete(&res->trie1);
298             trie_delete(&res->trie2);
299             strlist_local_wipe(hosts);
300             return false;
301         }
302         if (*p != '#') {
303             const char *eos = eol;
304             while (p < eos && isspace(*p)) {
305                 ++p;
306             }
307             while (p < eos && isspace(eos[-1])) {
308                 --eos;
309             }
310             if (p < eos) {
311                 if (isalnum(*p)) {
312                     strlist_copy(line, p, eos - p, true);
313                     trie_insert(res->trie1, line);
314                     ++host_count;
315                 } else if (*p == '*') {
316                     ++p;
317                     strlist_copy(line, p, eos - p, true);
318                     trie_insert(res->trie2, line);
319                     ++domain_count;
320                 }
321             }
322         }
323         p = eol + 1;
324     }
325     file_map_close(&map);
326     if (host_count > 0) {
327         trie_compile(res->trie1, lock);
328     } else {
329         trie_delete(&res->trie1);
330     }
331     if (domain_count > 0) {
332         trie_compile(res->trie2, lock);
333     } else {
334         trie_delete(&res->trie2);
335     }
336     if (res->trie1 == NULL && res->trie2 == NULL) {
337         err("%s not loaded: no data found", file);
338         strlist_local_wipe(hosts);
339         return false;
340     }
341     info("%s loaded: done in %us, %u hosts, %u domains", file,
342          (uint32_t)(time(0) - now), host_count, domain_count);
343     return true;
344 }
345
346
347 static bool strlist_filter_constructor(filter_t *filter)
348 {
349     strlist_config_t *config = strlist_config_new();
350
351 #define PARSE_CHECK(Expr, Str, ...)                                            \
352     if (!(Expr)) {                                                             \
353         err(Str, ##__VA_ARGS__);                                               \
354         strlist_config_delete(&config);                                        \
355         return false;                                                          \
356     }
357
358     config->hard_threshold = 1;
359     config->soft_threshold = 1;
360     foreach (filter_param_t *param, filter->params) {
361         switch (param->type) {
362           /* file parameter is:
363            *  [no]lock:(partial-)(prefix|suffix):weight:filename
364            *  valid options are:
365            *    - lock:   memlock the database in memory.
366            *    - nolock: don't memlock the database in memory.
367            *    - prefix: perform "prefix" compression on storage.
368            *    - suffix  perform "suffix" compression on storage.
369            *    - \d+:    a number describing the weight to give to the match
370            *              the given list [mandatory]
371            *  the file pointed by filename MUST be a valid string list (one string per
372            *  line, empty lines and lines beginning with a '#' are ignored).
373            */
374           case ATK_FILE: {
375             bool lock = false;
376             int  weight = 0;
377             bool reverse = false;
378             bool partial = false;
379             const char *current = param->value;
380             const char *p = m_strchrnul(param->value, ':');
381             char *next = NULL;
382             for (int i = 0 ; i < 4 ; ++i) {
383                 PARSE_CHECK(i == 3 || *p,
384                             "file parameter must contains a locking state "
385                             "and a weight option");
386                 switch (i) {
387                   case 0:
388                     if ((p - current) == 4 && strncmp(current, "lock", 4) == 0) {
389                         lock = true;
390                     } else if ((p - current) == 6 && strncmp(current, "nolock", 6) == 0) {
391                         lock = false;
392                     } else {
393                         PARSE_CHECK(false, "illegal locking state %.*s",
394                                     (int)(p - current), current);
395                     }
396                     break;
397
398                   case 1:
399                     if (p - current > (ssize_t)strlen("partial-") 
400                         && strncmp(current, "partial-", strlen("partial-")) == 0) {
401                         partial = true;
402                         current += strlen("partial-");
403                     }
404                     if ((p - current) == 6 && strncmp(current, "suffix", 6) == 0) {
405                         reverse = true;
406                     } else if ((p - current) == 6 && strncmp(current, "prefix", 6) == 0) {
407                         reverse = false;
408                     } else {
409                         PARSE_CHECK(false, "illegal character order value %.*s",
410                                     (int)(p - current), current);
411                     }
412                     break;
413
414                   case 2:
415                     weight = strtol(current, &next, 10);
416                     PARSE_CHECK(next == p && weight >= 0 && weight <= 1024,
417                                 "illegal weight value %.*s",
418                                 (int)(p - current), current);
419                     break;
420
421                   case 3: {
422                     strlist_local_t entry;
423                     PARSE_CHECK(strlist_create(&entry, current, weight,
424                                                reverse, partial, lock),
425                                 "cannot load string list from %s", current);
426                     array_add(config->locals, entry);
427                   } break;
428                 }
429                 if (i != 3) {
430                     current = p + 1;
431                     p = m_strchrnul(current, ':');
432                 }
433             }
434           } break;
435
436           /* rbldns parameter is:
437            *  [no]lock::weight:filename
438            *  valid options are:
439            *    - lock:   memlock the database in memory.
440            *    - nolock: don't memlock the database in memory.
441            *    - \d+:    a number describing the weight to give to the match
442            *              the given list [mandatory]
443            *  directly import a file issued from a rhbl in rbldns format.
444            */
445           case ATK_RBLDNS: {
446             bool lock = false;
447             int  weight = 0;
448             const char *current = param->value;
449             const char *p = m_strchrnul(param->value, ':');
450             char *next = NULL;
451             for (int i = 0 ; i < 3 ; ++i) {
452                 PARSE_CHECK(i == 2 || *p,
453                             "file parameter must contains a locking state "
454                             "and a weight option");
455                 switch (i) {
456                   case 0:
457                     if ((p - current) == 4 && strncmp(current, "lock", 4) == 0) {
458                         lock = true;
459                     } else if ((p - current) == 6 && strncmp(current, "nolock", 6) == 0) {
460                         lock = false;
461                     } else {
462                         PARSE_CHECK(false, "illegal locking state %.*s",
463                                     (int)(p - current), current);
464                     }
465                     break;
466
467                   case 1:
468                     weight = strtol(current, &next, 10);
469                     PARSE_CHECK(next == p && weight >= 0 && weight <= 1024,
470                                 "illegal weight value %.*s",
471                                 (int)(p - current), current);
472                     break;
473
474                   case 2: {
475                     strlist_local_t trie_hosts, trie_domains;
476                     PARSE_CHECK(strlist_create_from_rhbl(&trie_hosts, &trie_domains,
477                                                          current, weight, lock),
478                                 "cannot load string list from rhbl %s", current);
479                     if (trie_hosts.db != NULL) {
480                         array_add(config->locals, trie_hosts);
481                     }
482                     if (trie_domains.db != NULL) {
483                         array_add(config->locals, trie_domains);
484                     }
485                     config->is_hostname = true;
486                   } break;
487                 }
488                 if (i != 2) {
489                     current = p + 1;
490                     p = m_strchrnul(current, ':');
491                 }
492             }
493           } break;
494
495           /* dns parameter.
496            *  weight:hostname.
497            * define a RBL to use through DNS resolution.
498            */
499           case ATK_DNS: {
500             int  weight = 0;
501             const char *current = param->value;
502             const char *p = m_strchrnul(param->value, ':');
503             char *next = NULL;
504             for (int i = 0 ; i < 2 ; ++i) {
505                 PARSE_CHECK(i == 1 || *p,
506                             "host parameter must contains a weight option");
507                 switch (i) {
508                   case 0:
509                     weight = strtol(current, &next, 10);
510                     PARSE_CHECK(next == p && weight >= 0 && weight <= 1024,
511                                 "illegal weight value %.*s",
512                                 (int)(p - current), current);
513                     break;
514
515                   case 1:
516                     array_add(config->host_offsets, array_len(config->hosts));
517                     array_append(config->hosts, current, strlen(current) + 1);
518                     array_add(config->host_weights, weight);
519                     break;
520                 }
521                 if (i != 1) {
522                     current = p + 1;
523                     p = m_strchrnul(current, ':');
524                 }
525             }
526           } break;
527
528           /* hard_threshold parameter is an integer.
529            *  If the matching score is greater or equal than this threshold,
530            *  the hook "hard_match" is called.
531            * hard_threshold = 1 means, that all matches are hard matches.
532            * default is 1;
533            */
534           FILTER_PARAM_PARSE_INT(HARD_THRESHOLD, config->hard_threshold);
535
536           /* soft_threshold parameter is an integer.
537            *  if the matching score is greater or equal than this threshold
538            *  and smaller or equal than the hard_threshold, the hook "soft_match"
539            *  is called.
540            * default is 1;
541            */
542           FILTER_PARAM_PARSE_INT(SOFT_THRESHOLD, config->soft_threshold);
543
544           /* fields to match againes:
545            *  fields = field_name(,field_name)*
546            *  field_names are
547            *    - hostname: helo_name,client_name,reverse_client_name
548            *    - email: sender,recipient
549            */
550           case ATK_FIELDS: {
551             const char *current = param->value;
552             const char *p = m_strchrnul(param->value, ',');
553             do {
554                 postlicyd_token tok = policy_tokenize(current, p - current);
555                 switch (tok) {
556 #define           CASE(Up, Low, Type)                                          \
557                   case PTK_ ## Up:                                             \
558                     config->match_ ## Low = true;                              \
559                     config->is_ ## Type = true;                                \
560                     break
561                   CASE(HELO_NAME, helo, hostname);
562                   CASE(CLIENT_NAME, client, hostname);
563                   CASE(REVERSE_CLIENT_NAME, reverse, hostname);
564                   CASE(SENDER_DOMAIN, sender, hostname);
565                   CASE(RECIPIENT_DOMAIN, recipient, hostname);
566                   CASE(SENDER, sender, email);
567                   CASE(RECIPIENT, recipient, email);
568 #undef CASE
569                   default:
570                     PARSE_CHECK(false, "unknown field %.*s", (int)(p - current), current);
571                     break;
572                 }
573                 if (!*p) {
574                     break;
575                 }
576                 current = p + 1;
577                 p = m_strchrnul(current, ',');
578             } while (true);
579           } break;
580
581           default: break;
582         }
583     }}
584
585     PARSE_CHECK(config->is_email != config->is_hostname,
586                 "matched field MUST be emails XOR hostnames");
587     PARSE_CHECK(config->locals.len || config->host_offsets.len,
588                 "no file parameter in the filter %s", filter->name);
589     filter->data = config;
590     return true;
591 }
592
593 static void strlist_filter_destructor(filter_t *filter)
594 {
595     strlist_config_t *config = filter->data;
596     strlist_config_delete(&config);
597     filter->data = config;
598 }
599
600 static void strlist_filter_async(rbl_result_t *result, void *arg)
601 {
602     filter_context_t   *context = arg;
603     const filter_t      *filter = context->current_filter;
604     const strlist_config_t *data = filter->data;
605     strlist_async_data_t  *async = context->contexts[filter_type];
606
607     if (*result != RBL_ERROR) {
608         async->error = false;
609     }
610     --async->awaited;
611
612     debug("got asynchronous request result for filter %s, rbl %d, still awaiting %d answers",
613           filter->name, (int)(result - array_ptr(async->results, 0)), async->awaited);
614
615     if (async->awaited == 0) {
616         filter_result_t res = HTK_FAIL;
617         if (async->error) {
618             res = HTK_ERROR;
619         } else {
620             uint32_t j = 0;
621 #define DO_SUM(Field)                                                          \
622         if (data->match_ ## Field) {                                           \
623             for (uint32_t i = 0 ; i < array_len(data->host_offsets) ; ++i) {   \
624                 int weight = array_elt(data->host_weights, i);                 \
625                                                                                \
626                 switch (array_elt(async->results, j)) {                        \
627                   case RBL_ASYNC:                                              \
628                     crit("no more awaited answer but result is ASYNC");        \
629                     abort();                                                   \
630                   case RBL_FOUND:                                              \
631                     async->sum += weight;                                      \
632                     break;                                                     \
633                   default:                                                     \
634                     break;                                                     \
635                 }                                                              \
636                 ++j;                                                           \
637             }                                                                  \
638         }
639             DO_SUM(helo);
640             DO_SUM(client);
641             DO_SUM(reverse);
642             DO_SUM(recipient);
643             DO_SUM(sender);
644 #undef DO_SUM
645             debug("score is %d", async->sum);
646             if (async->sum >= (uint32_t)data->hard_threshold) {
647                 res = HTK_HARD_MATCH;
648             } else if (async->sum >= (uint32_t)data->soft_threshold) {
649                 res = HTK_SOFT_MATCH;
650             }
651         }
652         debug("answering to filter %s", filter->name);
653         filter_post_async_result(context, res);
654     }
655 }
656
657
658 static filter_result_t strlist_filter(const filter_t *filter, const query_t *query,
659                                       filter_context_t *context)
660 {
661     char reverse[BUFSIZ];
662     char normal[BUFSIZ];
663     const strlist_config_t *config = filter->data;
664     strlist_async_data_t *async = context->contexts[filter_type];
665     int result_pos = 0;
666     async->sum = 0;
667     async->error = true;
668     array_ensure_exact_capacity(async->results, (config->match_client
669                                 + config->match_sender + config->match_helo
670                                 + config->match_recipient + config->match_reverse)
671                                 * array_len(config->host_offsets));
672     async->awaited = 0;
673
674
675     if (config->is_email && 
676         ((config->match_sender && query->state < SMTP_MAIL)
677         || (config->match_recipient && query->state != SMTP_RCPT))) {
678         warn("trying to match an email against a field that is not "
679              "available in current protocol state");
680         return HTK_ABORT;
681     } else if (config->is_hostname && config->match_helo && query->state < SMTP_HELO) {
682         warn("trying to match hostname against helo before helo is received");
683         return HTK_ABORT;
684     }
685 #define LOOKUP(Flag, Field)                                                    \
686     if (config->match_ ## Flag) {                                              \
687         const int len = query->Field.len;                                      \
688         strlist_copy(normal, query->Field.str, len, false);                    \
689         strlist_copy(reverse, query->Field.str, len, true);                    \
690         foreach (strlist_local_t *entry, config->locals) {                     \
691             if ((!entry->partial && trie_lookup(*(entry->db),                  \
692                                       entry->reverse ? reverse : normal))      \
693                 || (entry->partial && trie_prefix(*(entry->db),                \
694                                                   entry->reverse ? reverse : normal))) {      \
695                 async->sum += entry->weight;                                   \
696                 if (async->sum >= (uint32_t)config->hard_threshold) {          \
697                     return HTK_HARD_MATCH;                                     \
698                 }                                                              \
699             }                                                                  \
700             async->error = false;                                              \
701         }}                                                                     \
702     }
703 #define DNS(Flag, Field)                                                       \
704     if (config->match_ ## Flag) {                                              \
705         const int len = query->Field.len;                                      \
706         strlist_copy(normal, query->Field.str, len, false);                    \
707         for (uint32_t i = 0 ; len > 0 && i < config->host_offsets.len ; ++i) { \
708             const char *rbl = array_ptr(config->hosts,                         \
709                                         array_elt(config->host_offsets, i));   \
710             debug("running check of field %s (%s) against %s", STR(Field),     \
711                   normal, rbl);                                                \
712             if (rhbl_check(rbl, normal, array_ptr(async->results, result_pos), \
713                            strlist_filter_async, context)) {                   \
714                 async->error = false;                                          \
715                 ++async->awaited;                                              \
716             }                                                                  \
717             ++result_pos;                                                      \
718         }                                                                      \
719     }
720
721     if (config->is_email) {
722         LOOKUP(sender, sender);
723         LOOKUP(recipient, recipient);
724         DNS(sender, sender);
725         DNS(recipient, recipient);
726     } else if (config->is_hostname) {
727         LOOKUP(helo, helo_name);
728         LOOKUP(client, client_name);
729         LOOKUP(reverse, reverse_client_name);
730         LOOKUP(recipient, recipient_domain);
731         LOOKUP(sender, sender_domain);
732         DNS(helo, helo_name);
733         DNS(client, client_name);
734         DNS(reverse, reverse_client_name);
735         DNS(recipient, recipient_domain);
736         DNS(sender, sender_domain);
737     }
738 #undef  DNS
739 #undef  LOOKUP
740     if (async->awaited > 0) {
741         return HTK_ASYNC;
742     }
743     if (async->error) {
744         err("filter %s: all the rbls returned an error", filter->name);
745         return HTK_ERROR;
746     }
747     if (async->sum >= (uint32_t)config->hard_threshold) {
748         return HTK_HARD_MATCH;
749     } else if (async->sum >= (uint32_t)config->soft_threshold) {
750         return HTK_SOFT_MATCH;
751     } else {
752         return HTK_FAIL;
753     }
754 }
755
756 static void *strlist_context_constructor(void)
757 {
758     return p_new(strlist_async_data_t, 1);
759 }
760
761 static void strlist_context_destructor(void *data)
762 {
763     strlist_async_data_t *ctx = data;
764     array_wipe(ctx->results);
765     p_delete(&ctx);
766 }
767
768 static int strlist_init(void)
769 {
770     filter_type =  filter_register("strlist", strlist_filter_constructor,
771                                    strlist_filter_destructor, strlist_filter,
772                                    strlist_context_constructor,
773                                    strlist_context_destructor);
774     /* Hooks.
775      */
776     (void)filter_hook_register(filter_type, "abort");
777     (void)filter_hook_register(filter_type, "error");
778     (void)filter_hook_register(filter_type, "fail");
779     (void)filter_hook_register(filter_type, "hard_match");
780     (void)filter_hook_register(filter_type, "soft_match");
781
782     /* Parameters.
783      */
784     (void)filter_param_register(filter_type, "file");
785     (void)filter_param_register(filter_type, "rbldns");
786     (void)filter_param_register(filter_type, "dns");
787     (void)filter_param_register(filter_type, "hard_threshold");
788     (void)filter_param_register(filter_type, "soft_threshold");
789     (void)filter_param_register(filter_type, "fields");
790     return 0;
791 }
792 module_init(strlist_init);