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 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 /******************************************************************************/
33 * Copyright © 2008 Florent Bruneau
40 #define config_param_register(Param)
42 /* Filter to execute on "CONNECT"
44 config_param_register("client_filter");
46 /* Filter to execute on "MAIL FROM"
48 config_param_register("sender_filter");
50 /* Filter to execute on "RCPT TO"
52 config_param_register("recipient_filter");
54 /* Filter to execute on "DATA"
56 config_param_register("data_filter");
58 /* Filter to execute on "END-OF-DATA"
60 config_param_register("end_of_data_filter");
62 /* Filter to execute on "ETRN"
64 config_param_register("etrn_filter");
66 /* Filter to execute on "HELO"
68 config_param_register("helo_filter");
69 config_param_register("ehlo_filter");
71 /* Filter to execute on "VRFY"
73 config_param_register("verify_filter");
75 static inline config_t *config_new(void)
77 config_t *config = p_new(config_t, 1);
78 for (int i = 0 ; i < SMTP_count ; ++i) {
79 config->entry_points[i] = -1;
84 void config_delete(config_t **config)
87 array_deep_wipe((*config)->filters, filter_wipe);
88 array_deep_wipe((*config)->params, filter_params_wipe);
94 static bool config_second_pass(config_t *config)
97 if (config->filters.len > 0) {
98 # define QSORT_TYPE filter_t
99 # define QSORT_BASE config->filters.data
100 # define QSORT_NELT config->filters.len
101 # define QSORT_LT(a,b) strcmp(a->name, b->name) < 0
105 foreach (filter_t *filter, config->filters) {
106 if (!filter_update_references(filter, &config->filters)) {
116 foreach (filter_param_t *param, config->params) {
117 switch (param->type) {
118 #define CASE(Param, State) \
119 case ATK_ ## Param ## _FILTER: \
121 config->entry_points[SMTP_ ## State] \
122 = filter_find_with_name(&config->filters, param->value); \
124 CASE(CLIENT, CONNECT)
128 CASE(RECIPIENT, RCPT)
130 CASE(END_OF_DATA, END_OF_MESSAGE)
137 array_deep_wipe(config->params, filter_params_wipe);
140 syslog(LOG_ERR, "no entry point defined");
146 config_t *config_read(const char *file)
157 ssize_t key_len, value_len;
159 if (!file_map_open(&map, file, false)) {
163 config = config_new();
164 filter_init(&filter);
167 #define READ_ERROR(Fmt, ...) \
169 syslog(LOG_ERR, "config file %s:%d:%d: " Fmt, file, line + 1, \
170 p - linep + 1, ##__VA_ARGS__); \
173 #define ADD_IN_BUFFER(Buffer, Len, Char) \
175 if ((Len) >= BUFSIZ - 1) { \
176 READ_ERROR("unreasonnable long line"); \
178 (Buffer)[(Len)++] = (Char); \
179 (Buffer)[(Len)] = '\0'; \
181 #define READ_NEXT(OnEOF) \
187 if (++p >= map.end) { \
191 #define READ_BLANK(OnEOF) \
193 bool in_comment = false; \
194 while (in_comment || isspace(*p) || *p == '#') { \
196 in_comment = false; \
197 } else if (*p == '#') { \
203 #define READ_TOKEN(Name, Buffer, Len) \
206 (Buffer)[0] = '\0'; \
207 if (!isalpha(*p)) { \
208 READ_ERROR("invalid %s, unexpected character '%c'", Name, *p); \
211 ADD_IN_BUFFER(Buffer, Len, *p); \
212 READ_NEXT(goto badeof); \
213 } while (isalnum(*p) || *p == '_'); \
215 #define READ_STRING(Name, Buffer, Len, OnEOF) \
218 (Buffer)[0] = '\0'; \
220 bool escaped = false; \
221 while (*p == '"') { \
222 READ_NEXT(goto badeof); \
225 READ_ERROR("string must not contain EOL"); \
226 } else if (escaped) { \
227 ADD_IN_BUFFER(Buffer, Len, *p); \
229 } else if (*p == '\\') { \
231 } else if (*p == '"') { \
232 READ_NEXT(goto badeof); \
235 ADD_IN_BUFFER(Buffer, Len, *p); \
237 READ_NEXT(goto badeof); \
239 READ_BLANK(goto badeof); \
242 READ_ERROR("%s must end with a ';'", Name); \
245 bool escaped = false; \
246 while (*p != ';' && isascii(*p) && (isprint(*p) || isspace(*p))) { \
248 if (*p == '\r' || *p == '\n') { \
249 READ_BLANK(goto badeof); \
251 ADD_IN_BUFFER(Buffer, Len, '\\'); \
257 } else if (*p == '\r' || *p == '\n') { \
258 READ_ERROR("%s must not contain EOL", Name); \
260 ADD_IN_BUFFER(Buffer, Len, *p); \
262 READ_NEXT(goto badeof); \
265 ADD_IN_BUFFER(Buffer, Len, '\\'); \
277 value[0] = key[0] = '\0';
278 value_len = key_len = 0;
281 READ_TOKEN("section name", key, key_len);
282 READ_BLANK(goto badeof);
285 READ_NEXT(goto badeof);
286 goto read_param_value;
288 READ_NEXT(goto badeof);
291 READ_ERROR("invalid character '%c', expected '=' or '{'", *p);
295 READ_BLANK(goto badeof);
296 READ_STRING("parameter value", value, value_len, ;);
298 filter_param_t param;
299 param.type = param_tokenize(key, key_len);
300 if (param.type != ATK_UNKNOWN) {
301 param.value = m_strdup(value);
302 array_add(config->params, param);
308 filter_set_name(&filter, key, key_len);
309 READ_BLANK(goto badeof);
311 READ_TOKEN("filter parameter name", key, key_len);
312 READ_BLANK(goto badeof);
314 READ_ERROR("invalid character '%c', expected '='", *p);
316 READ_NEXT(goto badeof);
317 READ_BLANK(goto badeof);
318 READ_STRING("filter parameter value", value, value_len, goto badeof);
319 READ_BLANK(goto badeof);
320 if (strcmp(key, "type") == 0) {
321 if (!filter_set_type(&filter, value, value_len)) {
322 READ_ERROR("unknow filter type (%s) for filter %s",
325 } else if (key_len > 3 && strncmp(key, "on_", 3) == 0) {
326 if (!filter_add_hook(&filter, key + 3, key_len - 3,
328 READ_ERROR("hook %s not supported by filter %s",
329 key + 3, filter.name);
332 /* filter_add_param failure mean unknown type or unsupported type.
333 * this are non-fatal errors.
335 (void)filter_add_param(&filter, key, key_len, value, value_len);
339 if (!filter_build(&filter)) {
340 READ_ERROR("invalid filter %s", filter.name);
342 array_add(config->filters, filter);
343 filter_init(&filter);
347 if (!config_second_pass(config)) {
350 file_map_close(&map);
354 syslog(LOG_ERR, "Unexpected end of file");
358 filter_wipe(&filter);
360 config_delete(&config);
361 file_map_close(&map);