1274116Sdteske/*-
2293619Sdteske * Copyright (c) 2002-2015 Devin Teske <dteske@FreeBSD.org>
3274116Sdteske * All rights reserved.
4274116Sdteske *
5274116Sdteske * Redistribution and use in source and binary forms, with or without
6274116Sdteske * modification, are permitted provided that the following conditions
7274116Sdteske * are met:
8274116Sdteske * 1. Redistributions of source code must retain the above copyright
9274116Sdteske *    notice, this list of conditions and the following disclaimer.
10274116Sdteske * 2. Redistributions in binary form must reproduce the above copyright
11274116Sdteske *    notice, this list of conditions and the following disclaimer in the
12274116Sdteske *    documentation and/or other materials provided with the distribution.
13274116Sdteske *
14274116Sdteske * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15274116Sdteske * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16274116Sdteske * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17274116Sdteske * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18274116Sdteske * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19274116Sdteske * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20274116Sdteske * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21274116Sdteske * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22274116Sdteske * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23274116Sdteske * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24274116Sdteske * SUCH DAMAGE.
25274116Sdteske */
26274116Sdteske
27274116Sdteske#include <sys/cdefs.h>
28274116Sdteske__FBSDID("$FreeBSD: releng/10.3/lib/libfigpar/figpar.c 293619 2016-01-09 23:33:44Z dteske $");
29274116Sdteske
30274116Sdteske#include <sys/param.h>
31274116Sdteske
32274116Sdteske#include <ctype.h>
33274116Sdteske#include <errno.h>
34274116Sdteske#include <fcntl.h>
35274116Sdteske#include <fnmatch.h>
36274116Sdteske#include <stdlib.h>
37274116Sdteske#include <string.h>
38274116Sdteske#include <unistd.h>
39274116Sdteske
40274116Sdteske#include "figpar.h"
41274116Sdteske#include "string_m.h"
42274116Sdteske
43293619Sdteskestruct figpar_config figpar_dummy_config = {0, NULL, {0}, NULL};
44274116Sdteske
45274116Sdteske/*
46293619Sdteske * Search for config option (struct figpar_config) in the array of config
47293619Sdteske * options, returning the struct whose directive matches the given parameter.
48293619Sdteske * If no match is found, a pointer to the static dummy array (above) is
49293619Sdteske * returned.
50274116Sdteske *
51274116Sdteske * This is to eliminate dependency on the index position of an item in the
52274116Sdteske * array, since the index position is more apt to be changed as code grows.
53274116Sdteske */
54293619Sdteskestruct figpar_config *
55293619Sdteskeget_config_option(struct figpar_config options[], const char *directive)
56274116Sdteske{
57274116Sdteske	uint32_t n;
58274116Sdteske
59274116Sdteske	/* Check arguments */
60274116Sdteske	if (options == NULL || directive == NULL)
61293619Sdteske		return (&figpar_dummy_config);
62274116Sdteske
63274116Sdteske	/* Loop through the array, return the index of the first match */
64274116Sdteske	for (n = 0; options[n].directive != NULL; n++)
65274116Sdteske		if (strcmp(options[n].directive, directive) == 0)
66274116Sdteske			return (&(options[n]));
67274116Sdteske
68274116Sdteske	/* Re-initialize the dummy variable in case it was written to */
69293619Sdteske	figpar_dummy_config.directive	= NULL;
70293619Sdteske	figpar_dummy_config.type	= 0;
71293619Sdteske	figpar_dummy_config.action	= NULL;
72293619Sdteske	figpar_dummy_config.value.u_num	= 0;
73274116Sdteske
74293619Sdteske	return (&figpar_dummy_config);
75274116Sdteske}
76274116Sdteske
77274116Sdteske/*
78274116Sdteske * Parse the configuration file at `path' and execute the `action' call-back
79274116Sdteske * functions for any directives defined by the array of config options (first
80274116Sdteske * argument).
81274116Sdteske *
82274116Sdteske * For unknown directives that are encountered, you can optionally pass a
83274116Sdteske * call-back function for the third argument to be called for unknowns.
84274116Sdteske *
85274116Sdteske * Returns zero on success; otherwise returns -1 and errno should be consulted.
86274116Sdteske*/
87274116Sdteskeint
88293619Sdteskeparse_config(struct figpar_config options[], const char *path,
89293619Sdteske    int (*unknown)(struct figpar_config *option, uint32_t line,
90293619Sdteske    char *directive, char *value), uint16_t processing_options)
91274116Sdteske{
92274116Sdteske	uint8_t bequals;
93274116Sdteske	uint8_t bsemicolon;
94274116Sdteske	uint8_t case_sensitive;
95274116Sdteske	uint8_t comment = 0;
96274116Sdteske	uint8_t end;
97274116Sdteske	uint8_t found;
98274116Sdteske	uint8_t have_equals = 0;
99274116Sdteske	uint8_t quote;
100274116Sdteske	uint8_t require_equals;
101274116Sdteske	uint8_t strict_equals;
102274116Sdteske	char p[2];
103274116Sdteske	char *directive;
104274116Sdteske	char *t;
105274116Sdteske	char *value;
106274116Sdteske	int error;
107274116Sdteske	int fd;
108274116Sdteske	ssize_t r = 1;
109274116Sdteske	uint32_t dsize;
110274116Sdteske	uint32_t line = 1;
111274116Sdteske	uint32_t n;
112274116Sdteske	uint32_t vsize;
113274116Sdteske	uint32_t x;
114274116Sdteske	off_t charpos;
115274116Sdteske	off_t curpos;
116274116Sdteske	char rpath[PATH_MAX];
117274116Sdteske
118274116Sdteske	/* Sanity check: if no options and no unknown function, return */
119274116Sdteske	if (options == NULL && unknown == NULL)
120274116Sdteske		return (-1);
121274116Sdteske
122274116Sdteske	/* Processing options */
123293619Sdteske	bequals = (processing_options & FIGPAR_BREAK_ON_EQUALS) == 0 ? 0 : 1;
124293619Sdteske	bsemicolon =
125293619Sdteske		(processing_options & FIGPAR_BREAK_ON_SEMICOLON) == 0 ? 0 : 1;
126293619Sdteske	case_sensitive =
127293619Sdteske		(processing_options & FIGPAR_CASE_SENSITIVE) == 0 ? 0 : 1;
128293619Sdteske	require_equals =
129293619Sdteske		(processing_options & FIGPAR_REQUIRE_EQUALS) == 0 ? 0 : 1;
130293619Sdteske	strict_equals =
131293619Sdteske		(processing_options & FIGPAR_STRICT_EQUALS) == 0 ? 0 : 1;
132274116Sdteske
133274116Sdteske	/* Initialize strings */
134274116Sdteske	directive = value = 0;
135274116Sdteske	vsize = dsize = 0;
136274116Sdteske
137274116Sdteske	/* Resolve the file path */
138274116Sdteske	if (realpath(path, rpath) == 0)
139274116Sdteske		return (-1);
140274116Sdteske
141274116Sdteske	/* Open the file */
142274116Sdteske	if ((fd = open(rpath, O_RDONLY)) < 0)
143274116Sdteske		return (-1);
144274116Sdteske
145274116Sdteske	/* Read the file until EOF */
146274116Sdteske	while (r != 0) {
147274116Sdteske		r = read(fd, p, 1);
148274116Sdteske
149274116Sdteske		/* skip to the beginning of a directive */
150274116Sdteske		while (r != 0 && (isspace(*p) || *p == '#' || comment ||
151274116Sdteske		    (bsemicolon && *p == ';'))) {
152274116Sdteske			if (*p == '#')
153274116Sdteske				comment = 1;
154274116Sdteske			else if (*p == '\n') {
155274116Sdteske				comment = 0;
156274116Sdteske				line++;
157274116Sdteske			}
158274116Sdteske			r = read(fd, p, 1);
159274116Sdteske		}
160274116Sdteske		/* Test for EOF; if EOF then no directive was found */
161274116Sdteske		if (r == 0) {
162274116Sdteske			close(fd);
163274116Sdteske			return (0);
164274116Sdteske		}
165274116Sdteske
166274116Sdteske		/* Get the current offset */
167274116Sdteske		curpos = lseek(fd, 0, SEEK_CUR) - 1;
168274116Sdteske		if (curpos == -1) {
169274116Sdteske			close(fd);
170274116Sdteske			return (-1);
171274116Sdteske		}
172274116Sdteske
173274116Sdteske		/* Find the length of the directive */
174274116Sdteske		for (n = 0; r != 0; n++) {
175274116Sdteske			if (isspace(*p))
176274116Sdteske				break;
177274116Sdteske			if (bequals && *p == '=') {
178274116Sdteske				have_equals = 1;
179274116Sdteske				break;
180274116Sdteske			}
181274116Sdteske			if (bsemicolon && *p == ';')
182274116Sdteske				break;
183274116Sdteske			r = read(fd, p, 1);
184274116Sdteske		}
185274116Sdteske
186274116Sdteske		/* Test for EOF, if EOF then no directive was found */
187274116Sdteske		if (n == 0 && r == 0) {
188274116Sdteske			close(fd);
189274116Sdteske			return (0);
190274116Sdteske		}
191274116Sdteske
192274116Sdteske		/* Go back to the beginning of the directive */
193274116Sdteske		error = (int)lseek(fd, curpos, SEEK_SET);
194274116Sdteske		if (error == (curpos - 1)) {
195274116Sdteske			close(fd);
196274116Sdteske			return (-1);
197274116Sdteske		}
198274116Sdteske
199274116Sdteske		/* Allocate and read the directive into memory */
200274116Sdteske		if (n > dsize) {
201274116Sdteske			if ((directive = realloc(directive, n + 1)) == NULL) {
202274116Sdteske				close(fd);
203274116Sdteske				return (-1);
204274116Sdteske			}
205274116Sdteske			dsize = n;
206274116Sdteske		}
207274116Sdteske		r = read(fd, directive, n);
208274116Sdteske
209274116Sdteske		/* Advance beyond the equals sign if appropriate/desired */
210274116Sdteske		if (bequals && *p == '=') {
211274116Sdteske			if (lseek(fd, 1, SEEK_CUR) != -1)
212274116Sdteske				r = read(fd, p, 1);
213274116Sdteske			if (strict_equals && isspace(*p))
214274116Sdteske				*p = '\n';
215274116Sdteske		}
216274116Sdteske
217274116Sdteske		/* Terminate the string */
218274116Sdteske		directive[n] = '\0';
219274116Sdteske
220274116Sdteske		/* Convert directive to lower case before comparison */
221274116Sdteske		if (!case_sensitive)
222274116Sdteske			strtolower(directive);
223274116Sdteske
224274116Sdteske		/* Move to what may be the start of the value */
225274116Sdteske		if (!(bsemicolon && *p == ';') &&
226274116Sdteske		    !(strict_equals && *p == '=')) {
227274116Sdteske			while (r != 0 && isspace(*p) && *p != '\n')
228274116Sdteske				r = read(fd, p, 1);
229274116Sdteske		}
230274116Sdteske
231274116Sdteske		/* An equals sign may have stopped us, should we eat it? */
232274116Sdteske		if (r != 0 && bequals && *p == '=' && !strict_equals) {
233274116Sdteske			have_equals = 1;
234274116Sdteske			r = read(fd, p, 1);
235274116Sdteske			while (r != 0 && isspace(*p) && *p != '\n')
236274116Sdteske				r = read(fd, p, 1);
237274116Sdteske		}
238274116Sdteske
239274116Sdteske		/* If no value, allocate a dummy value and jump to action */
240274116Sdteske		if (r == 0 || *p == '\n' || *p == '#' ||
241274116Sdteske		    (bsemicolon && *p == ';')) {
242274116Sdteske			/* Initialize the value if not already done */
243274116Sdteske			if (value == NULL && (value = malloc(1)) == NULL) {
244274116Sdteske				close(fd);
245274116Sdteske				return (-1);
246274116Sdteske			}
247274116Sdteske			value[0] = '\0';
248274116Sdteske			goto call_function;
249274116Sdteske		}
250274116Sdteske
251274116Sdteske		/* Get the current offset */
252274116Sdteske		curpos = lseek(fd, 0, SEEK_CUR) - 1;
253274116Sdteske		if (curpos == -1) {
254274116Sdteske			close(fd);
255274116Sdteske			return (-1);
256274116Sdteske		}
257274116Sdteske
258274116Sdteske		/* Find the end of the value */
259274116Sdteske		quote = 0;
260274116Sdteske		end = 0;
261274116Sdteske		while (r != 0 && end == 0) {
262274116Sdteske			/* Advance to the next character if we know we can */
263274116Sdteske			if (*p != '\"' && *p != '#' && *p != '\n' &&
264274116Sdteske			    (!bsemicolon || *p != ';')) {
265274116Sdteske				r = read(fd, p, 1);
266274116Sdteske				continue;
267274116Sdteske			}
268274116Sdteske
269274116Sdteske			/*
270274116Sdteske			 * If we get this far, we've hit an end-key
271274116Sdteske			 */
272274116Sdteske
273274116Sdteske			/* Get the current offset */
274274116Sdteske			charpos = lseek(fd, 0, SEEK_CUR) - 1;
275274116Sdteske			if (charpos == -1) {
276274116Sdteske				close(fd);
277274116Sdteske				return (-1);
278274116Sdteske			}
279274116Sdteske
280274116Sdteske			/*
281274116Sdteske			 * Go back so we can read the character before the key
282274116Sdteske			 * to check if the character is escaped (which means we
283274116Sdteske			 * should continue).
284274116Sdteske			 */
285274116Sdteske			error = (int)lseek(fd, -2, SEEK_CUR);
286274116Sdteske			if (error == -3) {
287274116Sdteske				close(fd);
288274116Sdteske				return (-1);
289274116Sdteske			}
290274116Sdteske			r = read(fd, p, 1);
291274116Sdteske
292274116Sdteske			/*
293274116Sdteske			 * Count how many backslashes there are (an odd number
294274116Sdteske			 * means the key is escaped, even means otherwise).
295274116Sdteske			 */
296274116Sdteske			for (n = 1; *p == '\\'; n++) {
297274116Sdteske				/* Move back another offset to read */
298274116Sdteske				error = (int)lseek(fd, -2, SEEK_CUR);
299274116Sdteske				if (error == -3) {
300274116Sdteske					close(fd);
301274116Sdteske					return (-1);
302274116Sdteske				}
303274116Sdteske				r = read(fd, p, 1);
304274116Sdteske			}
305274116Sdteske
306274116Sdteske			/* Move offset back to the key and read it */
307274116Sdteske			error = (int)lseek(fd, charpos, SEEK_SET);
308274116Sdteske			if (error == (charpos - 1)) {
309274116Sdteske				close(fd);
310274116Sdteske				return (-1);
311274116Sdteske			}
312274116Sdteske			r = read(fd, p, 1);
313274116Sdteske
314274116Sdteske			/*
315274116Sdteske			 * If an even number of backslashes was counted meaning
316274116Sdteske			 * key is not escaped, we should evaluate what to do.
317274116Sdteske			 */
318274116Sdteske			if ((n & 1) == 1) {
319274116Sdteske				switch (*p) {
320274116Sdteske				case '\"':
321274116Sdteske					/*
322274116Sdteske				 	 * Flag current sequence of characters
323274116Sdteske					 * to follow as being quoted (hashes
324274116Sdteske					 * are not considered comments).
325274116Sdteske					 */
326274116Sdteske					quote = !quote;
327274116Sdteske					break;
328274116Sdteske				case '#':
329274116Sdteske					/*
330274116Sdteske					 * If we aren't in a quoted series, we
331274116Sdteske					 * just hit an inline comment and have
332274116Sdteske					 * found the end of the value.
333274116Sdteske					 */
334274116Sdteske					if (!quote)
335274116Sdteske						end = 1;
336274116Sdteske					break;
337274116Sdteske				case '\n':
338274116Sdteske					/*
339274116Sdteske					 * Newline characters must always be
340274116Sdteske					 * escaped, whether inside a quoted
341274116Sdteske					 * series or not, otherwise they
342274116Sdteske					 * terminate the value.
343274116Sdteske					 */
344274116Sdteske					end = 1;
345274116Sdteske				case ';':
346274116Sdteske					if (!quote && bsemicolon)
347274116Sdteske						end = 1;
348274116Sdteske					break;
349274116Sdteske				}
350274116Sdteske			} else if (*p == '\n')
351274116Sdteske				/* Escaped newline character. increment */
352274116Sdteske				line++;
353274116Sdteske
354274116Sdteske			/* Advance to the next character */
355274116Sdteske			r = read(fd, p, 1);
356274116Sdteske		}
357274116Sdteske
358274116Sdteske		/* Get the current offset */
359274116Sdteske		charpos = lseek(fd, 0, SEEK_CUR) - 1;
360274116Sdteske		if (charpos == -1) {
361274116Sdteske			close(fd);
362274116Sdteske			return (-1);
363274116Sdteske		}
364274116Sdteske
365274116Sdteske		/* Get the length of the value */
366274116Sdteske		n = (uint32_t)(charpos - curpos);
367274116Sdteske		if (r != 0) /* more to read, but don't read ending key */
368274116Sdteske			n--;
369274116Sdteske
370274116Sdteske		/* Move offset back to the beginning of the value */
371274116Sdteske		error = (int)lseek(fd, curpos, SEEK_SET);
372274116Sdteske		if (error == (curpos - 1)) {
373274116Sdteske			close(fd);
374274116Sdteske			return (-1);
375274116Sdteske		}
376274116Sdteske
377274116Sdteske		/* Allocate and read the value into memory */
378274116Sdteske		if (n > vsize) {
379274116Sdteske			if ((value = realloc(value, n + 1)) == NULL) {
380274116Sdteske				close(fd);
381274116Sdteske				return (-1);
382274116Sdteske			}
383274116Sdteske			vsize = n;
384274116Sdteske		}
385274116Sdteske		r = read(fd, value, n);
386274116Sdteske
387274116Sdteske		/* Terminate the string */
388274116Sdteske		value[n] = '\0';
389274116Sdteske
390274116Sdteske		/* Cut trailing whitespace off by termination */
391274116Sdteske		t = value + n;
392274116Sdteske		while (isspace(*--t))
393274116Sdteske			*t = '\0';
394274116Sdteske
395274116Sdteske		/* Escape the escaped quotes (replaceall is in string_m.c) */
396274116Sdteske		x = strcount(value, "\\\""); /* in string_m.c */
397274116Sdteske		if (x != 0 && (n + x) > vsize) {
398274116Sdteske			if ((value = realloc(value, n + x + 1)) == NULL) {
399274116Sdteske				close(fd);
400274116Sdteske				return (-1);
401274116Sdteske			}
402274116Sdteske			vsize = n + x;
403274116Sdteske		}
404274116Sdteske		if (replaceall(value, "\\\"", "\\\\\"") < 0) {
405274116Sdteske			/* Replace operation failed for some unknown reason */
406274116Sdteske			close(fd);
407274116Sdteske			return (-1);
408274116Sdteske		}
409274116Sdteske
410274116Sdteske		/* Remove all new line characters */
411274116Sdteske		if (replaceall(value, "\\\n", "") < 0) {
412274116Sdteske			/* Replace operation failed for some unknown reason */
413274116Sdteske			close(fd);
414274116Sdteske			return (-1);
415274116Sdteske		}
416274116Sdteske
417274116Sdteske		/* Resolve escape sequences */
418274116Sdteske		strexpand(value); /* in string_m.c */
419274116Sdteske
420274116Sdteskecall_function:
421274116Sdteske		/* Abort if we're seeking only assignments */
422274116Sdteske		if (require_equals && !have_equals)
423274116Sdteske			return (-1);
424274116Sdteske
425274116Sdteske		found = have_equals = 0; /* reset */
426274116Sdteske
427274116Sdteske		/* If there are no options defined, call unknown and loop */
428274116Sdteske		if (options == NULL && unknown != NULL) {
429274116Sdteske			error = unknown(NULL, line, directive, value);
430274116Sdteske			if (error != 0) {
431274116Sdteske				close(fd);
432274116Sdteske				return (error);
433274116Sdteske			}
434274116Sdteske			continue;
435274116Sdteske		}
436274116Sdteske
437274116Sdteske		/* Loop through the array looking for a match for the value */
438274116Sdteske		for (n = 0; options[n].directive != NULL; n++) {
439274116Sdteske			error = fnmatch(options[n].directive, directive,
440274116Sdteske			    FNM_NOESCAPE);
441274116Sdteske			if (error == 0) {
442274116Sdteske				found = 1;
443274116Sdteske				/* Call function for array index item */
444274116Sdteske				if (options[n].action != NULL) {
445274116Sdteske					error = options[n].action(
446274116Sdteske					    &options[n],
447274116Sdteske					    line, directive, value);
448274116Sdteske					if (error != 0) {
449274116Sdteske						close(fd);
450274116Sdteske						return (error);
451274116Sdteske					}
452274116Sdteske				}
453274116Sdteske			} else if (error != FNM_NOMATCH) {
454274116Sdteske				/* An error has occurred */
455274116Sdteske				close(fd);
456274116Sdteske				return (-1);
457274116Sdteske			}
458274116Sdteske		}
459274116Sdteske		if (!found && unknown != NULL) {
460274116Sdteske			/*
461274116Sdteske			 * No match was found for the value we read from the
462274116Sdteske			 * file; call function designated for unknown values.
463274116Sdteske			 */
464274116Sdteske			error = unknown(NULL, line, directive, value);
465274116Sdteske			if (error != 0) {
466274116Sdteske				close(fd);
467274116Sdteske				return (error);
468274116Sdteske			}
469274116Sdteske		}
470274116Sdteske	}
471274116Sdteske
472274116Sdteske	close(fd);
473274116Sdteske	return (0);
474274116Sdteske}
475