1 /*-
2 * Copyright (c) 2002-2015 Devin Teske <dteske@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27 #include <sys/param.h>
28
29 #include <ctype.h>
30 #include <fcntl.h>
31 #include <fnmatch.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #include "figpar.h"
37 #include "string_m.h"
38
39 struct figpar_config figpar_dummy_config = {0, NULL, {0}, NULL};
40
41 /*
42 * Search for config option (struct figpar_config) in the array of config
43 * options, returning the struct whose directive matches the given parameter.
44 * If no match is found, a pointer to the static dummy array (above) is
45 * returned.
46 *
47 * This is to eliminate dependency on the index position of an item in the
48 * array, since the index position is more apt to be changed as code grows.
49 */
50 struct figpar_config *
get_config_option(struct figpar_config options[],const char * directive)51 get_config_option(struct figpar_config options[], const char *directive)
52 {
53 uint32_t n;
54
55 /* Check arguments */
56 if (options == NULL || directive == NULL)
57 return (&figpar_dummy_config);
58
59 /* Loop through the array, return the index of the first match */
60 for (n = 0; options[n].directive != NULL; n++)
61 if (strcmp(options[n].directive, directive) == 0)
62 return (&(options[n]));
63
64 /* Re-initialize the dummy variable in case it was written to */
65 figpar_dummy_config.directive = NULL;
66 figpar_dummy_config.type = 0;
67 figpar_dummy_config.action = NULL;
68 figpar_dummy_config.value.u_num = 0;
69
70 return (&figpar_dummy_config);
71 }
72
73 /*
74 * Parse the configuration file at `path' and execute the `action' call-back
75 * functions for any directives defined by the array of config options (first
76 * argument).
77 *
78 * For unknown directives that are encountered, you can optionally pass a
79 * call-back function for the third argument to be called for unknowns.
80 *
81 * Returns zero on success; otherwise returns -1 and errno should be consulted.
82 */
83 int
parse_config(struct figpar_config options[],const char * path,int (* unknown)(struct figpar_config * option,uint32_t line,char * directive,char * value),uint16_t processing_options)84 parse_config(struct figpar_config options[], const char *path,
85 int (*unknown)(struct figpar_config *option, uint32_t line,
86 char *directive, char *value), uint16_t processing_options)
87 {
88 uint8_t bequals;
89 uint8_t bsemicolon;
90 uint8_t case_sensitive;
91 uint8_t comment = 0;
92 uint8_t end;
93 uint8_t found;
94 uint8_t have_equals = 0;
95 uint8_t quote;
96 uint8_t require_equals;
97 uint8_t strict_equals;
98 char p[2];
99 char *directive;
100 char *t;
101 char *value;
102 int error;
103 int fd;
104 ssize_t r = 1;
105 uint32_t dsize;
106 uint32_t line = 1;
107 uint32_t n;
108 uint32_t vsize;
109 uint32_t x;
110 off_t charpos;
111 off_t curpos;
112 char rpath[PATH_MAX];
113
114 /* Sanity check: if no options and no unknown function, return */
115 if (options == NULL && unknown == NULL)
116 return (-1);
117
118 /* Processing options */
119 bequals = (processing_options & FIGPAR_BREAK_ON_EQUALS) == 0 ? 0 : 1;
120 bsemicolon =
121 (processing_options & FIGPAR_BREAK_ON_SEMICOLON) == 0 ? 0 : 1;
122 case_sensitive =
123 (processing_options & FIGPAR_CASE_SENSITIVE) == 0 ? 0 : 1;
124 require_equals =
125 (processing_options & FIGPAR_REQUIRE_EQUALS) == 0 ? 0 : 1;
126 strict_equals =
127 (processing_options & FIGPAR_STRICT_EQUALS) == 0 ? 0 : 1;
128
129 /* Initialize strings */
130 directive = value = 0;
131 vsize = dsize = 0;
132
133 /* Resolve the file path */
134 if (realpath(path, rpath) == 0)
135 return (-1);
136
137 /* Open the file */
138 if ((fd = open(rpath, O_RDONLY)) < 0)
139 return (-1);
140
141 /* Read the file until EOF */
142 while (r != 0) {
143 r = read(fd, p, 1);
144
145 /* skip to the beginning of a directive */
146 while (r != 0 && (isspace(*p) || *p == '#' || comment ||
147 (bsemicolon && *p == ';'))) {
148 if (*p == '#')
149 comment = 1;
150 else if (*p == '\n') {
151 comment = 0;
152 line++;
153 }
154 r = read(fd, p, 1);
155 }
156 /* Test for EOF; if EOF then no directive was found */
157 if (r == 0) {
158 close(fd);
159 return (0);
160 }
161
162 /* Get the current offset */
163 if ((curpos = lseek(fd, 0, SEEK_CUR)) == -1) {
164 close(fd);
165 return (-1);
166 }
167 curpos--;
168
169 /* Find the length of the directive */
170 for (n = 0; r != 0; n++) {
171 if (isspace(*p))
172 break;
173 if (bequals && *p == '=') {
174 have_equals = 1;
175 break;
176 }
177 if (bsemicolon && *p == ';')
178 break;
179 r = read(fd, p, 1);
180 }
181
182 /* Test for EOF, if EOF then no directive was found */
183 if (n == 0 && r == 0) {
184 close(fd);
185 return (0);
186 }
187
188 /* Go back to the beginning of the directive */
189 if (lseek(fd, curpos, SEEK_SET) == -1) {
190 close(fd);
191 return (-1);
192 }
193
194 /* Allocate and read the directive into memory */
195 if (n > dsize) {
196 if ((directive = realloc(directive, n + 1)) == NULL) {
197 close(fd);
198 return (-1);
199 }
200 dsize = n;
201 }
202 r = read(fd, directive, n);
203
204 /* Advance beyond the equals sign if appropriate/desired */
205 if (bequals && *p == '=') {
206 if (lseek(fd, 1, SEEK_CUR) != -1)
207 r = read(fd, p, 1);
208 if (strict_equals && isspace(*p))
209 *p = '\n';
210 }
211
212 /* Terminate the string */
213 directive[n] = '\0';
214
215 /* Convert directive to lower case before comparison */
216 if (!case_sensitive)
217 strtolower(directive);
218
219 /* Move to what may be the start of the value */
220 if (!(bsemicolon && *p == ';') &&
221 !(strict_equals && *p == '=')) {
222 while (r != 0 && isspace(*p) && *p != '\n')
223 r = read(fd, p, 1);
224 }
225
226 /* An equals sign may have stopped us, should we eat it? */
227 if (r != 0 && bequals && *p == '=' && !strict_equals) {
228 have_equals = 1;
229 r = read(fd, p, 1);
230 while (r != 0 && isspace(*p) && *p != '\n')
231 r = read(fd, p, 1);
232 }
233
234 /* If no value, allocate a dummy value and jump to action */
235 if (r == 0 || *p == '\n' || *p == '#' ||
236 (bsemicolon && *p == ';')) {
237 /* Initialize the value if not already done */
238 if (value == NULL && (value = malloc(1)) == NULL) {
239 close(fd);
240 return (-1);
241 }
242 value[0] = '\0';
243 goto call_function;
244 }
245
246 /* Get the current offset */
247 if ((curpos = lseek(fd, 0, SEEK_CUR)) == -1) {
248 close(fd);
249 return (-1);
250 }
251 curpos--;
252
253 /* Find the end of the value */
254 quote = 0;
255 end = 0;
256 while (r != 0 && end == 0) {
257 /* Advance to the next character if we know we can */
258 if (*p != '\"' && *p != '#' && *p != '\n' &&
259 (!bsemicolon || *p != ';')) {
260 r = read(fd, p, 1);
261 continue;
262 }
263
264 /*
265 * If we get this far, we've hit an end-key
266 */
267
268 /* Get the current offset */
269 if ((charpos = lseek(fd, 0, SEEK_CUR)) == -1) {
270 close(fd);
271 return (-1);
272 }
273 charpos--;
274
275 /*
276 * Go back so we can read the character before the key
277 * to check if the character is escaped (which means we
278 * should continue).
279 */
280 if (lseek(fd, -2, SEEK_CUR) == -1) {
281 close(fd);
282 return (-1);
283 }
284 r = read(fd, p, 1);
285
286 /*
287 * Count how many backslashes there are (an odd number
288 * means the key is escaped, even means otherwise).
289 */
290 for (n = 1; *p == '\\'; n++) {
291 /* Move back another offset to read */
292 if (lseek(fd, -2, SEEK_CUR) == -1) {
293 close(fd);
294 return (-1);
295 }
296 r = read(fd, p, 1);
297 }
298
299 /* Move offset back to the key and read it */
300 if (lseek(fd, charpos, SEEK_SET) == -1) {
301 close(fd);
302 return (-1);
303 }
304 r = read(fd, p, 1);
305
306 /*
307 * If an even number of backslashes was counted meaning
308 * key is not escaped, we should evaluate what to do.
309 */
310 if ((n & 1) == 1) {
311 switch (*p) {
312 case '\"':
313 /*
314 * Flag current sequence of characters
315 * to follow as being quoted (hashes
316 * are not considered comments).
317 */
318 quote = !quote;
319 break;
320 case '#':
321 /*
322 * If we aren't in a quoted series, we
323 * just hit an inline comment and have
324 * found the end of the value.
325 */
326 if (!quote)
327 end = 1;
328 break;
329 case '\n':
330 /*
331 * Newline characters must always be
332 * escaped, whether inside a quoted
333 * series or not, otherwise they
334 * terminate the value.
335 */
336 end = 1;
337 case ';':
338 if (!quote && bsemicolon)
339 end = 1;
340 break;
341 }
342 } else if (*p == '\n')
343 /* Escaped newline character. increment */
344 line++;
345
346 /* Advance to the next character */
347 r = read(fd, p, 1);
348 }
349
350 /* Get the current offset */
351 if ((charpos = lseek(fd, 0, SEEK_CUR)) == -1) {
352 close(fd);
353 return (-1);
354 }
355
356 /* Get the length of the value */
357 n = (uint32_t)(charpos - curpos);
358 if (r != 0) /* more to read, but don't read ending key */
359 n--;
360
361 /* Move offset back to the beginning of the value */
362 if (lseek(fd, curpos, SEEK_SET) == -1) {
363 close(fd);
364 return (-1);
365 }
366
367 /* Allocate and read the value into memory */
368 if (n > vsize) {
369 if ((value = realloc(value, n + 1)) == NULL) {
370 close(fd);
371 return (-1);
372 }
373 vsize = n;
374 }
375 r = read(fd, value, n);
376
377 /* Terminate the string */
378 value[n] = '\0';
379
380 /* Cut trailing whitespace off by termination */
381 t = value + n;
382 while (isspace(*--t))
383 *t = '\0';
384
385 /* Escape the escaped quotes (replaceall is in string_m.c) */
386 x = strcount(value, "\\\""); /* in string_m.c */
387 if (x != 0 && (n + x) > vsize) {
388 if ((value = realloc(value, n + x + 1)) == NULL) {
389 close(fd);
390 return (-1);
391 }
392 vsize = n + x;
393 }
394 if (replaceall(value, "\\\"", "\\\\\"") < 0) {
395 /* Replace operation failed for some unknown reason */
396 close(fd);
397 return (-1);
398 }
399
400 /* Remove all new line characters */
401 if (replaceall(value, "\\\n", "") < 0) {
402 /* Replace operation failed for some unknown reason */
403 close(fd);
404 return (-1);
405 }
406
407 /* Resolve escape sequences */
408 strexpand(value); /* in string_m.c */
409
410 call_function:
411 /* Abort if we're seeking only assignments */
412 if (require_equals && !have_equals)
413 return (-1);
414
415 found = have_equals = 0; /* reset */
416
417 /* If there are no options defined, call unknown and loop */
418 if (options == NULL && unknown != NULL) {
419 error = unknown(NULL, line, directive, value);
420 if (error != 0) {
421 close(fd);
422 return (error);
423 }
424 continue;
425 }
426
427 /* Loop through the array looking for a match for the value */
428 for (n = 0; options[n].directive != NULL; n++) {
429 error = fnmatch(options[n].directive, directive,
430 FNM_NOESCAPE);
431 if (error == 0) {
432 found = 1;
433 /* Call function for array index item */
434 if (options[n].action != NULL) {
435 error = options[n].action(
436 &options[n],
437 line, directive, value);
438 if (error != 0) {
439 close(fd);
440 return (error);
441 }
442 }
443 } else if (error != FNM_NOMATCH) {
444 /* An error has occurred */
445 close(fd);
446 return (-1);
447 }
448 }
449 if (!found && unknown != NULL) {
450 /*
451 * No match was found for the value we read from the
452 * file; call function designated for unknown values.
453 */
454 error = unknown(NULL, line, directive, value);
455 if (error != 0) {
456 close(fd);
457 return (error);
458 }
459 }
460 }
461
462 close(fd);
463 return (0);
464 }
465