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