Avoid error when running the tests twice.
[apps/pfixtools.git] / postlicyd / tst-filters.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 REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
20 /*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
21 /*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
22 /*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
23 /*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
24 /*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
25 /*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
26 /*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
27 /*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
28 /*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
29 /*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
30 /******************************************************************************/
31
32 /*
33  * Copyright © 2008 Florent Bruneau
34  */
35
36 #include "str.h"
37 #include "config.h"
38 #include "file.h"
39 #include <dirent.h>
40
41 static char *read_query(const char *basepath, const char *filename,
42                         char *buff, char **end, query_t *q)
43 {
44     char path[FILENAME_MAX];
45     snprintf(path, FILENAME_MAX, "%s%s", basepath, filename);
46     {
47         file_map_t map;
48         if (!file_map_open(&map, path, false)) {
49             UNIXERR("open");
50             return NULL;
51         }
52         if (map.end - map.map >= BUFSIZ) {
53             err("File too large for a testcase: %s", path);
54             file_map_close(&map);
55             return NULL;
56         }
57         memcpy(buff, map.map, map.end - map.map);
58         if (end != NULL) {
59             *end = buff + (map.end - map.map);
60             **end = '\0';
61         } else {
62             buff[map.end - map.map] = '\0';
63         }
64         file_map_close(&map);
65     }
66
67     char *eoq = strstr(buff, "\n\n");
68     if (eoq == NULL) {
69         return NULL;
70     }
71     if (!query_parse(q, buff)) {
72         err("Cannot parse query from file %s", filename);
73         return NULL;
74     }
75     return eoq + 2;
76 }
77
78 static bool run_testcase(const config_t *config, const char *basepath,
79                          const char *filename)
80 {
81     char buff[BUFSIZ];
82     char *end;
83     query_t query;
84     const char *eol = read_query(basepath, filename, buff, &end, &query);
85     if (eol == NULL) {
86         return false;
87     }
88
89     bool ok = true;
90     filter_context_t context;
91     filter_context_prepare(&context, NULL);
92
93     while (eol < end) {
94         char *neol = memchr(eol, '\n', end - eol);
95         if (neol == NULL) {
96             neol = end;
97         }
98         *neol = '\0';
99         char *sep = memchr(eol, '=', neol - eol);
100         if (sep == NULL) {
101             eol = neol + 1;
102             err("missing separator");
103             continue;
104         }
105         *sep = '\0';
106
107         int pos = filter_find_with_name(&config->filters, eol);
108         if (pos == -1) {
109             err("Unknown filter %s", eol);
110             eol = neol + 1;
111             continue;
112         }
113         ++sep;
114         filter_result_t result = hook_tokenize(sep, neol - sep);
115         if (result == HTK_UNKNOWN) {
116             err("Unknown filter result %.*s", neol - sep, sep);
117             eol = neol + 1;
118             continue;
119         }
120         filter_t *filter = array_ptr(config->filters, pos);
121
122 #define TEST(Name, Run)                                                        \
123         do {                                                                   \
124           bool __test = (Run);                                                 \
125           printf("  test %s: %s\n", Name, __test ? "SUCCESS" : "FAILED");      \
126           ok = ok && __test;                                                   \
127         } while (0)
128         TEST(filter->name, filter_test(filter, &query, &context, result));
129         eol = neol + 1;
130
131     }
132     filter_context_wipe(&context);
133     return ok;
134 }
135
136 static bool run_greylisttest(const config_t *config, const char *basepath)
137 {
138     char buff_q1[BUFSIZ];
139     char buff_q2[BUFSIZ];
140     char buff_q3[BUFSIZ];
141     query_t q1;
142     query_t q2;
143     query_t q3;
144     bool ok = true;
145
146     filter_t *greylist1;
147 //    filter_t *greylist2;
148
149 #define QUERY(Q)                                                               \
150     if (read_query(basepath, "greylist_" STR(Q), buff_##Q, NULL, &Q) == NULL) {    \
151         return false;                                                          \
152     }
153     QUERY(q1);
154     QUERY(q2);
155     QUERY(q3);
156 #undef QUERY
157
158 #define FILTER(F)                                                              \
159     do {                                                                       \
160       int __p = filter_find_with_name(&config->filters, STR(F));               \
161       if (__p < 0) {                                                           \
162           return false;                                                        \
163       }                                                                        \
164       F = array_ptr(config->filters, __p);                                     \
165     } while (0)
166     FILTER(greylist1);
167 //    FILTER(greylist2);
168 #undef FILTER
169
170     filter_context_t context;
171     filter_context_prepare(&context, NULL);
172
173     /* Test greylist */
174     TEST("greylisted", filter_test(greylist1, &q1, &context, HTK_GREYLIST));
175     TEST("too_fast", filter_test(greylist1, &q1, &context, HTK_GREYLIST));
176     sleep(5);
177     TEST("too_slow", filter_test(greylist1, &q1, &context, HTK_GREYLIST));
178     sleep(2);
179     TEST("whitelisted", filter_test(greylist1, &q1, &context, HTK_WHITELIST));
180     TEST("other_greylisted", filter_test(greylist1, &q2, &context, HTK_GREYLIST));
181     TEST("auto_whitelisted", filter_test(greylist1, &q1, &context, HTK_WHITELIST));
182     TEST("other_auto_whitelisted", filter_test(greylist1, &q2, &context, HTK_WHITELIST));
183     TEST("greylisted", filter_test(greylist1, &q3, &context, HTK_GREYLIST));
184     sleep(10);
185     TEST("cleanup", filter_test(greylist1, &q1, &context, HTK_GREYLIST));
186
187     filter_context_wipe(&context);
188     return ok;
189 }
190
191 int main(int argc, char *argv[])
192 {
193     char basepath[FILENAME_MAX];
194     char path[FILENAME_MAX];
195     char *p;
196
197     common_startup();
198     p = strrchr(argv[0], '/');
199     if (p == NULL) {
200         p = argv[0];
201     } else {
202         ++p;
203     }
204     snprintf(basepath, FILENAME_MAX, "%.*sdata/", p - argv[0], argv[0]);
205
206     /* Cleanup */
207     {
208 #define RM(File)                                                               \
209       snprintf(path, FILENAME_MAX, "%s/%s", basepath, File);                   \
210       unlink(path);
211       RM("test1_greylist.db");
212       RM("test1_whitelist.db");
213       RM("test2_greylist.db");
214       RM("test2_whitelist.db");
215 #undef RM
216     }
217
218     snprintf(path, FILENAME_MAX, "%s/test.conf", basepath);
219
220     config_t *config = config_read(path);
221     if (config == NULL) {
222         return 1;
223     }
224
225
226 #define RUN(Name, Test, ...)                                                   \
227     printf("Running %s:\n", Name);                                             \
228     printf("%s\n", run_##Test(config, basepath, ##__VA_ARGS__) ? "SUCCESS"     \
229                                                                : "FAILED");
230
231     /* Test stateless filters */
232     DIR *dir = opendir(basepath);
233     if (dir == NULL) {
234         UNIXERR("opendir");
235         return 1;
236     }
237     struct dirent *ent;
238     while ((ent = readdir(dir)) != NULL) {
239         if (strncmp("testcase_", ent->d_name, 9) == 0) {
240             RUN(ent->d_name, testcase, ent->d_name);
241         }
242     }
243     closedir(dir);
244
245     /* Test greylist */
246     RUN("greylist", greylisttest);
247
248
249 #undef RUN
250     return 0;
251 }