Initial version of the parser.
[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
40 struct config_t {
41     filter_t *filters;
42     int filters_len;
43     int filters_size;
44
45     int entry_point;
46 };
47
48 static inline config_t *config_new(void)
49 {
50     config_t *config = p_new(config_t, 1);
51     config->entry_point = -1;
52     return config;
53 }
54
55 void config_delete(config_t **config)
56 {
57     if (*config) {
58         for (int i = 0 ; i < (*config)->filters_len ; ++i) {
59             filter_wipe((*config)->filters + i);
60         }
61         p_delete(&(*config)->filters);
62     }
63 }
64
65 config_t *config_read(const char *file)
66 {
67     config_t *config;
68     //filter_t *filter = NULL;
69     file_map_t map;
70     const char *p;
71     int line = 0;
72
73     char key[BUFSIZ];
74     char value[BUFSIZ];
75     ssize_t key_len, value_len;
76
77     if (!file_map_open(&map, file, false)) {
78         return false;
79     }
80
81     config = config_new();
82     p = map.map;
83
84 #define READ_ERROR(Fmt, ...)                                                   \
85     syslog(LOG_ERR, "config file %s:%d: " Fmt, file, line, ##__VA_ARGS__)
86 #define ADD_IN_BUFFER(Buffer, Len, Char)                                       \
87     if ((Len) >= BUFSIZ - 1) {                                                 \
88         READ_ERROR("unreasonnable long line");                                 \
89         goto error;                                                            \
90     }                                                                          \
91     (Buffer)[(Len)++] = (Char);                                                \
92     (Buffer)[(Len)]   = '\0';
93
94 #define READ_NEXT(OnEOF)                                                       \
95     if (++p >= map.end) {                                                      \
96         OnEOF;                                                                 \
97     }
98 #define READ_BLANK(OnEOF)                                                      \
99     while (isblank(*p)) {                                                      \
100         if (*p == '\n') {                                                      \
101             ++line;                                                            \
102         }                                                                      \
103         READ_NEXT(OnEOF);                                                      \
104     }
105 #define READ_TOKEN(Name, Buffer, Len)                                          \
106     do {                                                                       \
107         (Len) = 0;                                                             \
108         (Buffer)[0] = '\0';                                                    \
109         if (!isalpha(*p)) {                                                    \
110             READ_ERROR("invalid %s, unexpected character '%c'", Name, *p);     \
111             goto error;                                                        \
112         }                                                                      \
113         do {                                                                   \
114             ADD_IN_BUFFER(Buffer, Len, *p);                                    \
115             if ((Len) >= BUFSIZ - 1) {                                         \
116                 READ_ERROR("unreasonnable long token");                        \
117                 goto error;                                                    \
118             }                                                                  \
119             (Buffer)[(Len)++] = *p;                                            \
120             READ_NEXT(goto badeof)                                             \
121         } while (isalnum(*p) || *p == '_');                                    \
122     } while (0)
123 #define READ_STRING(Name, Buffer, Len, OnEOF)                                  \
124     do {                                                                       \
125         (Len) = 0;                                                             \
126         (Buffer)[0] = '\0';                                                    \
127         if (*p == '"') {                                                       \
128             bool escaped = false;                                              \
129             while (*p == '"') {                                                \
130                 READ_NEXT(goto badeof);                                        \
131                 while (true) {                                                 \
132                     if (*p == '\n') {                                          \
133                         READ_ERROR("string must not contain EOL");             \
134                         goto error;                                            \
135                     } else if (escaped) {                                      \
136                         ADD_IN_BUFFER(Buffer, Len, *p);                        \
137                         escaped = false;                                       \
138                     } else if (*p == '\\') {                                   \
139                         escaped = true;                                        \
140                     } else if (*p == '"') {                                    \
141                         READ_NEXT(goto badeof);                                \
142                         break;                                                 \
143                     } else {                                                   \
144                         ADD_IN_BUFFER(Buffer, Len, *p);                        \
145                     }                                                          \
146                     READ_NEXT(goto badeof);                                    \
147                 }                                                              \
148                 READ_BLANK(goto badeof);                                       \
149             }                                                                  \
150             if (*p != ';') {                                                   \
151                 READ_ERROR("%s must end with a ';'", Name);                    \
152                 goto error;                                                    \
153             }                                                                  \
154         } else {                                                               \
155             bool escaped = false;                                              \
156             READ_NEXT(goto badeof);                                            \
157             while (*p != ';' && isascii(*p) && isprint(*p)) {                  \
158                 if (escaped) {                                                 \
159                     if (*p == '\r' || *p == '\n') {                            \
160                         READ_BLANK(goto badeof);                               \
161                     } else {                                                   \
162                         ADD_IN_BUFFER(Buffer, Len, '\\');                      \
163                     }                                                          \
164                     escaped = false;                                           \
165                 }                                                              \
166                 if (*p == '\\') {                                              \
167                     escaped = true;                                            \
168                 } else if (*p == '\r' || *p == '\n') {                         \
169                     READ_ERROR("%s must not contain EOL", Name);               \
170                 } else {                                                       \
171                     ADD_IN_BUFFER(Buffer, Len, *p);                            \
172                 }                                                              \
173                 READ_NEXT(goto badeof);                                        \
174             }                                                                  \
175             if (escaped) {                                                     \
176                 ADD_IN_BUFFER(Buffer, Len, '\\');                              \
177             }                                                                  \
178         }                                                                      \
179         READ_NEXT(OnEOF)                                                       \
180     } while(0)
181
182
183 read_section:
184     if (p >= map.end) {
185         goto ok;
186     }
187
188     value[0] = key[0] = '\0';
189     value_len = key_len = 0;
190
191     READ_BLANK(goto ok);
192     READ_TOKEN("section name", key, key_len);
193     READ_BLANK(goto badeof);
194     switch (*p) {
195       case '=':
196         READ_NEXT(goto badeof)
197         goto read_param_value;
198       case '{':
199         READ_NEXT(goto badeof)
200         goto read_filter;
201       default:
202         READ_ERROR("invalid character '%c', expected '=' or '{'", *p);
203         goto error;
204     }
205
206 read_param_value:
207     READ_BLANK(goto badeof);
208     READ_STRING("parameter value", value, value_len, ;);
209     /* TODO: Insert parameter in the configuration.
210      */
211     goto read_section;
212
213 read_filter:
214     /* TODO: Create a filter with the given name.
215      */
216     READ_BLANK(goto badeof);
217     while (*p != '}') {
218         READ_TOKEN("filter parameter name", key, key_len);
219         READ_BLANK(goto badeof);
220         READ_STRING("filter parameter value", value, value_len, goto badeof);
221         /* TODO: Insert parameter in the filter.
222          */
223     }
224     /* TODO: Check the filter.
225      */
226     goto read_section;
227
228 ok:
229     return config;
230
231 badeof:
232     syslog(LOG_ERR, "Unexpected end of file");
233
234 error:
235     config_delete(&config);
236     return NULL;
237 }