rbl filter implementation.
[apps/pfixtools.git] / postlicyd / config.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 "file.h"
37 #include "filter.h"
38 #include "config.h"
39 #include "str.h"
40
41 struct config_t {
42     A(filter_t)        filters;
43     A(filter_params_t) params;
44     int entry_point;
45 };
46
47 static inline config_t *config_new(void)
48 {
49     config_t *config = p_new(config_t, 1);
50     config->entry_point = -1;
51     return config;
52 }
53
54 void config_delete(config_t **config)
55 {
56     if (*config) {
57         array_deep_wipe((*config)->filters, filter_wipe);
58         array_deep_wipe((*config)->params, filter_params_wipe);
59         p_delete(config);
60     }
61 }
62
63
64 static bool config_second_pass(config_t *config)
65 {
66     bool ok = true;
67     if (config->filters.len > 0) {
68 #       define QSORT_TYPE filter_t
69 #       define QSORT_BASE config->filters.data
70 #       define QSORT_NELT config->filters.len
71 #       define QSORT_LT(a,b) strcmp(a->name, b->name) < 0
72 #       include "qsort.c"
73     }
74
75     foreach (filter_t *filter, config->filters) {
76         if (!filter_update_references(filter, &config->filters)) {
77             ok = false;
78             break;
79         }
80     }}
81
82     return ok;
83 }
84
85 config_t *config_read(const char *file)
86 {
87     config_t *config;
88     filter_t filter;
89     file_map_t map;
90     const char *p;
91     int line = 0;
92     const char *linep;
93
94     char key[BUFSIZ];
95     char value[BUFSIZ];
96     ssize_t key_len, value_len;
97
98     if (!file_map_open(&map, file, false)) {
99         return false;
100     }
101
102     config = config_new();
103     filter_init(&filter);
104     linep = p = map.map;
105
106 #define READ_ERROR(Fmt, ...)                                                   \
107     do {                                                                       \
108         syslog(LOG_ERR, "config file %s:%d:%d: " Fmt, file, line + 1,          \
109                p - linep + 1, ##__VA_ARGS__);                                  \
110         goto error;                                                            \
111     } while (0)
112 #define ADD_IN_BUFFER(Buffer, Len, Char)                                       \
113     do {                                                                       \
114         if ((Len) >= BUFSIZ - 1) {                                             \
115             READ_ERROR("unreasonnable long line");                             \
116         }                                                                      \
117         (Buffer)[(Len)++] = (Char);                                            \
118         (Buffer)[(Len)]   = '\0';                                              \
119     } while (0)
120 #define READ_NEXT(OnEOF)                                                       \
121     do {                                                                       \
122         if (*p == '\n') {                                                      \
123             ++line;                                                            \
124             linep = p + 1;                                                     \
125         }                                                                      \
126         if (++p >= map.end) {                                                  \
127             OnEOF;                                                             \
128         }                                                                      \
129     } while (0)
130 #define READ_BLANK(OnEOF)                                                      \
131     do {                                                                       \
132         bool in_comment = false;                                               \
133         while (in_comment || isspace(*p) || *p == '#') {                       \
134             if (*p == '\n') {                                                  \
135                 in_comment = false;                                            \
136             } else if (*p == '#') {                                            \
137                 in_comment = true;                                             \
138             }                                                                  \
139             READ_NEXT(OnEOF);                                                  \
140         }                                                                      \
141     } while (0)
142 #define READ_TOKEN(Name, Buffer, Len)                                          \
143     do {                                                                       \
144         (Len) = 0;                                                             \
145         (Buffer)[0] = '\0';                                                    \
146         if (!isalpha(*p)) {                                                    \
147             READ_ERROR("invalid %s, unexpected character '%c'", Name, *p);     \
148         }                                                                      \
149         do {                                                                   \
150             ADD_IN_BUFFER(Buffer, Len, *p);                                    \
151             READ_NEXT(goto badeof);                                            \
152         } while (isalnum(*p) || *p == '_');                                    \
153     } while (0)
154 #define READ_STRING(Name, Buffer, Len, OnEOF)                                  \
155     do {                                                                       \
156         (Len) = 0;                                                             \
157         (Buffer)[0] = '\0';                                                    \
158         if (*p == '"') {                                                       \
159             bool escaped = false;                                              \
160             while (*p == '"') {                                                \
161                 READ_NEXT(goto badeof);                                        \
162                 while (true) {                                                 \
163                     if (*p == '\n') {                                          \
164                         READ_ERROR("string must not contain EOL");             \
165                     } else if (escaped) {                                      \
166                         ADD_IN_BUFFER(Buffer, Len, *p);                        \
167                         escaped = false;                                       \
168                     } else if (*p == '\\') {                                   \
169                         escaped = true;                                        \
170                     } else if (*p == '"') {                                    \
171                         READ_NEXT(goto badeof);                                \
172                         break;                                                 \
173                     } else {                                                   \
174                         ADD_IN_BUFFER(Buffer, Len, *p);                        \
175                     }                                                          \
176                     READ_NEXT(goto badeof);                                    \
177                 }                                                              \
178                 READ_BLANK(goto badeof);                                       \
179             }                                                                  \
180             if (*p != ';') {                                                   \
181                 READ_ERROR("%s must end with a ';'", Name);                    \
182             }                                                                  \
183         } else {                                                               \
184             bool escaped = false;                                              \
185             while (*p != ';' && isascii(*p) && (isprint(*p) || isspace(*p))) { \
186                 if (escaped) {                                                 \
187                     if (*p == '\r' || *p == '\n') {                            \
188                         READ_BLANK(goto badeof);                               \
189                     } else {                                                   \
190                         ADD_IN_BUFFER(Buffer, Len, '\\');                      \
191                     }                                                          \
192                     escaped = false;                                           \
193                 }                                                              \
194                 if (*p == '\\') {                                              \
195                     escaped = true;                                            \
196                 } else if (*p == '\r' || *p == '\n') {                         \
197                     READ_ERROR("%s must not contain EOL", Name);               \
198                 } else {                                                       \
199                     ADD_IN_BUFFER(Buffer, Len, *p);                            \
200                 }                                                              \
201                 READ_NEXT(goto badeof);                                        \
202             }                                                                  \
203             if (escaped) {                                                     \
204                 ADD_IN_BUFFER(Buffer, Len, '\\');                              \
205             }                                                                  \
206         }                                                                      \
207         READ_NEXT(OnEOF);                                                      \
208     } while(0)
209
210
211 read_section:
212     if (p >= map.end) {
213         goto ok;
214     }
215
216     value[0] = key[0] = '\0';
217     value_len = key_len = 0;
218
219     READ_BLANK(goto ok);
220     READ_TOKEN("section name", key, key_len);
221     READ_BLANK(goto badeof);
222     switch (*p) {
223       case '=':
224         READ_NEXT(goto badeof);
225         goto read_param_value;
226       case '{':
227         READ_NEXT(goto badeof);
228         goto read_filter;
229       default:
230         READ_ERROR("invalid character '%c', expected '=' or '{'", *p);
231     }
232
233 read_param_value:
234     READ_BLANK(goto badeof);
235     READ_STRING("parameter value", value, value_len, ;);
236     {
237         filter_params_t param;
238         param.name = m_strdup(key);
239         param.value = m_strdup(value);
240         array_add(config->params, param);
241     }
242     goto read_section;
243
244 read_filter:
245     filter_set_name(&filter, key, key_len);
246     READ_BLANK(goto badeof);
247     while (*p != '}') {
248         READ_TOKEN("filter parameter name", key, key_len);
249         READ_BLANK(goto badeof);
250         if (*p != '=') {
251             READ_ERROR("invalid character '%c', expected '='", *p);
252         }
253         READ_NEXT(goto badeof);
254         READ_BLANK(goto badeof);
255         READ_STRING("filter parameter value", value, value_len, goto badeof);
256         READ_BLANK(goto badeof);
257         if (strcmp(key, "type") == 0) {
258             if (!filter_set_type(&filter, value, value_len)) {
259                 READ_ERROR("unknow filter type (%s) for filter %s",
260                            value, filter.name);
261             }
262         } else if (key_len > 3 && strncmp(key, "on_", 3) == 0) {
263             if (!filter_add_hook(&filter, key + 3, key_len - 3,
264                                  value, value_len)) {
265                 READ_ERROR("hook %s not supported by filter %s",
266                            key + 3, filter.name);
267             }
268         } else {
269             if (!filter_add_param(&filter, key, key_len, value, value_len)) {
270                 goto error;
271             }
272         }
273     }
274     READ_NEXT(;);
275     if (!filter_build(&filter)) {
276         READ_ERROR("invalid filter %s", filter.name);
277     }
278     array_add(config->filters, filter);
279     filter_init(&filter);
280     goto read_section;
281
282 ok:
283     if (!config_second_pass(config)) {
284         goto error;
285     }
286     file_map_close(&map);
287     return config;
288
289 badeof:
290     syslog(LOG_ERR, "Unexpected end of file");
291
292 error:
293     filter_wipe(&filter);
294     config_delete(&config);
295     file_map_close(&map);
296     return NULL;
297 }