1 /******************************************************************************/
2 /* pfixtools: a collection of postfix related tools */
4 /* ________________________________________________________________________ */
6 /* Redistribution and use in source and binary forms, with or without */
7 /* modification, are permitted provided that the following conditions */
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 */
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. */
31 /* Copyright (c) 2006-2008 the Authors */
32 /* see AUTHORS and source files for details */
33 /******************************************************************************/
36 * Copyright © 2008 Florent Bruneau
42 #include "resources.h"
44 #define config_param_register(Param)
46 /* Filter to execute on "CONNECT"
48 config_param_register("client_filter");
50 /* Filter to execute on "MAIL FROM"
52 config_param_register("sender_filter");
54 /* Filter to execute on "RCPT TO"
56 config_param_register("recipient_filter");
58 /* Filter to execute on "DATA"
60 config_param_register("data_filter");
62 /* Filter to execute on "END-OF-DATA"
64 config_param_register("end_of_data_filter");
66 /* Filter to execute on "ETRN"
68 config_param_register("etrn_filter");
70 /* Filter to execute on "HELO"
72 config_param_register("helo_filter");
73 config_param_register("ehlo_filter");
75 /* Filter to execute on "VRFY"
77 config_param_register("verify_filter");
80 /* Where to bind the server.
82 config_param_register("port");
85 /* Format of the log message.
86 * The message exact format is $log: "reply"
88 config_param_register("log_format");
91 static config_t *global_config = NULL;
93 static inline config_t *config_new(void)
95 config_t *config = p_new(config_t, 1);
96 global_config = config;
100 static void config_close(config_t *config)
102 for (int i = 0 ; i < SMTP_count ; ++i) {
103 config->entry_points[i] = -1;
105 array_deep_wipe(config->filters, filter_wipe);
106 array_deep_wipe(config->params, filter_params_wipe);
107 p_delete(&config->log_format);
110 void config_delete(config_t **config)
113 config_close(*config);
115 global_config = NULL;
119 static void config_exit()
122 config_delete(&global_config);
125 module_exit(config_exit);
128 static bool config_parse(config_t *config)
135 bool in_section = false;
136 bool end_of_section = false;
140 int key_len, value_len;
142 if (!file_map_open(&map, config->filename, false)) {
146 filter_init(&filter);
149 #define READ_LOG(Lev, Fmt, ...) \
150 __log(LOG_ ## Lev, "config file %s:%d:%d: " Fmt, config->filename, \
151 line + 1, (int)(p - linep + 1), ##__VA_ARGS__)
152 #define READ_ERROR(Fmt, ...) \
154 READ_LOG(ERR, Fmt, ##__VA_ARGS__); \
157 #define ADD_IN_BUFFER(Buffer, Len, Char) \
159 if ((Len) >= BUFSIZ - 1) { \
160 READ_ERROR("unreasonnable long line"); \
162 (Buffer)[(Len)++] = (Char); \
163 (Buffer)[(Len)] = '\0'; \
171 if (++p >= map.end) { \
172 if (!end_of_section) { \
183 bool in_comment = false; \
184 while (in_comment || isspace(*p) || *p == '#') { \
186 in_comment = false; \
187 } else if (*p == '#') { \
193 #define READ_TOKEN(Name, Buffer, Len) \
196 (Buffer)[0] = '\0'; \
197 if (!isalpha(*p)) { \
198 READ_ERROR("invalid %s, unexpected character '%c'", Name, *p); \
201 ADD_IN_BUFFER(Buffer, Len, *p); \
203 } while (isalnum(*p) || *p == '_'); \
205 #define READ_STRING(Name, Buffer, Len, Ignore) \
208 (Buffer)[0] = '\0'; \
210 bool escaped = false; \
211 while (*p == '"') { \
215 READ_ERROR("string must not contain EOL"); \
216 } else if (escaped) { \
217 ADD_IN_BUFFER(Buffer, Len, *p); \
219 } else if (*p == '\\') { \
221 } else if (*p == '"') { \
225 ADD_IN_BUFFER(Buffer, Len, *p); \
232 READ_ERROR("%s must end with a ';'", Name); \
235 bool escaped = false; \
236 while (*p != ';' && isascii(*p) && (isprint(*p) || isspace(*p))) { \
238 if (*p == '\r' || *p == '\n') { \
241 ADD_IN_BUFFER(Buffer, Len, '\\'); \
247 } else if (*p == '\r' || *p == '\n') { \
248 READ_ERROR("%s must not contain EOL", Name); \
250 ADD_IN_BUFFER(Buffer, Len, *p); \
255 ADD_IN_BUFFER(Buffer, Len, '\\'); \
257 while ((Len) > 0 && isspace((Buffer)[(Len) - 1])) { \
258 (Buffer)[--(Len)] = '\0'; \
261 end_of_section = Ignore; \
271 value[0] = key[0] = '\0';
272 value_len = key_len = 0;
274 in_section = end_of_section = false;
277 READ_TOKEN("section name", key, key_len);
282 goto read_param_value;
287 READ_ERROR("invalid character '%c', expected '=' or '{'", *p);
292 READ_STRING("parameter value", value, value_len, true);
294 filter_param_t param;
295 param.type = param_tokenize(key, key_len);
296 if (param.type != ATK_UNKNOWN) {
297 param.value = p_dupstr(value, value_len);
298 param.value_len = value_len;
299 array_add(config->params, param);
301 READ_LOG(INFO, "unknown parameter %.*s", key_len, key);
307 filter_set_name(&filter, key, key_len);
310 READ_TOKEN("filter parameter name", key, key_len);
313 READ_ERROR("invalid character '%c', expected '='", *p);
317 READ_STRING("filter parameter value", value, value_len, false);
319 if (strcmp(key, "type") == 0) {
320 if (!filter_set_type(&filter, value, value_len)) {
321 READ_ERROR("unknow filter type (%s) for filter %s",
324 } else if (key_len > 3 && strncmp(key, "on_", 3) == 0) {
325 if (!filter_add_hook(&filter, key + 3, key_len - 3,
327 READ_ERROR("hook %s not supported by filter %s",
328 key + 3, filter.name);
331 /* filter_add_param failure mean unknown type or unsupported type.
332 * this are non-fatal errors.
334 (void)filter_add_param(&filter, key, key_len, value, value_len);
337 end_of_section = true;
339 array_add(config->filters, filter);
340 filter_init(&filter);
344 file_map_close(&map);
348 err("Unexpected end of file");
352 filter_wipe(&filter);
354 file_map_close(&map);
358 static bool config_build_structure(config_t *config)
361 if (config->filters.len > 0) {
362 # define QSORT_TYPE filter_t
363 # define QSORT_BASE config->filters.data
364 # define QSORT_NELT config->filters.len
365 # define QSORT_LT(a,b) strcmp(a->name, b->name) < 0
369 foreach (filter_t *filter, config->filters) {
370 if (!filter_update_references(filter, &config->filters)) {
378 if (!filter_check_safety(&config->filters)) {
383 #define PARSE_CHECK(Expr, Fmt, ...) \
385 err(Fmt, ##__VA_ARGS__); \
388 foreach (filter_param_t *param, config->params) {
389 switch (param->type) {
390 #define CASE(Param, State) \
391 case ATK_ ## Param ## _FILTER: \
393 config->entry_points[SMTP_ ## State] \
394 = filter_find_with_name(&config->filters, param->value); \
395 PARSE_CHECK(config->entry_points[SMTP_ ## State] >= 0, \
396 "invalid filter name %s", param->value); \
398 CASE(CLIENT, CONNECT)
402 CASE(RECIPIENT, RCPT)
404 CASE(END_OF_DATA, END_OF_MESSAGE)
408 FILTER_PARAM_PARSE_INT(PORT, config->port);
409 FILTER_PARAM_PARSE_STRING(LOG_FORMAT, config->log_format, true);
413 array_deep_wipe(config->params, filter_params_wipe);
415 if (config->log_format && !query_format_check(config->log_format)) {
416 err("invalid log format: \"%s\"", config->log_format);
421 err("no entry point defined");
426 static bool config_build_filters(config_t *config)
428 foreach (filter_t *filter, config->filters) {
429 if (!filter_build(filter)) {
437 static bool config_load(config_t *config) {
438 config_close(config);
440 if (!config_parse(config)) {
441 err("Invalid configuration: cannot parse configuration file \"%s\"", config->filename);
444 if (!config_build_structure(config)) {
445 err("Invalid configuration: inconsistent filter structure");
448 if (!config_build_filters(config)) {
449 err("Invalid configuration: invalid filter");
453 resource_garbage_collect();
457 bool config_reload(config_t *config)
459 return config_load(config);
462 config_t *config_read(const char *file)
464 config_t *config = config_new();
465 config->filename = file;
466 if (!config_reload(config)) {
467 config_delete(&config);
473 bool config_check(const char *file)
475 config_t *config = config_new();
476 config->filename = file;
478 bool ret = config_parse(config) && config_build_structure(config);
480 config_delete(&config);