1 /*
2  *	Configuration file parsing
3  *
4  *	(c) Tomi Manninen <tomi.manninen@hut.fi>
5  *
6  *	This program is licensed under the BSD license, which can be found
7  *	in the file LICENSE.
8  *
9  */
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <ctype.h>
14 #include <string.h>
15 #include <errno.h>
16 
17 #include "cfgfile.h"
18 #include "hmalloc.h"
19 #include "hlog.h"
20 
21  /* ***************************************************************** */
22 
23 /*
24  *	Convert string to long long
25  */
26 
hatoll(char * s)27 long long hatoll(char *s)
28 {
29 	long long res = 0LL;
30 
31 	while (isdigit((int)*s))
32 		res = res * 10 + (*s++ - '0');
33 
34 	return res;
35 }
36 
37  /* ***************************************************************** */
38 
39 /*
40  *	String to lower case
41  */
42 
strlwr(char * s)43 extern char *strlwr(char *s)
44 {
45 	char *c;
46 	for (c = s; (*c); c++) {
47 		*c = tolower(*c);
48 	}
49 	return s;
50 }
51 
52  /* ***************************************************************** */
53 
do_string(char ** dest,int argc,char ** argv)54 int do_string(char **dest, int argc, char **argv)
55 {
56 	if (argc < 2)
57 		return -1;
58 	if (*dest)
59 		hfree(*dest);
60 	*dest = hstrdup(argv[1]);
61 	return 0;
62 }
63 
do_string_array(char *** dest,int argc,char ** argv)64 int do_string_array(char ***dest, int argc, char **argv)
65 {
66 	int i, n;
67 	char **vars;
68 
69 	if (argc < 2)
70 		return -1;
71 
72 	if (*dest) // TODO free the actual referenced strings
73 		hfree(*dest);
74 
75 	n = argc - 1;
76 
77 	vars = hmalloc(sizeof(char *) * (n+1));
78 	for (i = 0; i < n; i++)
79 		vars[i] = hstrdup(argv[i + 1]);
80 
81 	vars[i] = NULL;
82 
83 	*dest = vars;
84 
85 	return 0;
86 }
87 
free_string_array(char ** dest)88 extern void free_string_array(char **dest)
89 {
90 	int i;
91 
92 	for (i = 0; (dest[i]); i++)
93 		hfree(dest[i]);
94 
95 	hfree(dest);
96 }
97 
do_char(char * dest,int argc,char ** argv)98 int do_char(char *dest, int argc, char **argv)
99 {
100 	if (argc != 2)
101 		return -1;
102 
103 	if (strlen(argv[1]) != 1)
104 		return -1;
105 
106 	*dest = *argv[1];
107 	return 0;
108 }
109 
do_int(int * dest,int argc,char ** argv)110 int do_int(int *dest, int argc, char **argv)
111 {
112 	if (argc < 2)
113 		return -1;
114 	*dest = atoi(argv[1]);
115 	return 0;
116 }
117 
do_boolean(int * dest,int argc,char ** argv)118 int do_boolean(int *dest, int argc, char **argv)
119 {
120 	if (argc < 2)
121 		return -1;
122 
123 	if (strcasecmp(argv[1], "yes") == 0
124 		|| strcasecmp(argv[1], "true") == 0
125 		|| strcasecmp(argv[1], "on") == 0
126 		|| strcasecmp(argv[1], "enable") == 0
127 		|| strcasecmp(argv[1], "enabled") == 0) {
128 			*dest = 1;
129 			return 0;
130 	}
131 
132 	if (strcasecmp(argv[1], "no") == 0
133 		|| strcasecmp(argv[1], "false") == 0
134 		|| strcasecmp(argv[1], "off") == 0
135 		|| strcasecmp(argv[1], "disable") == 0
136 		|| strcasecmp(argv[1], "disabled") == 0) {
137 			*dest = 0;
138 			return 0;
139 	}
140 
141 	return -1;
142 }
143 
144  /* ***************************************************************** */
145 
146 /*
147  *	Parse c-style escapes (neat to have!)
148  */
149 
parse_string(char * str)150 static char *parse_string(char *str)
151 {
152 	char *cp = str;
153 	unsigned long num;
154 
155 	while (*str != '\0' && *str != '\"') {
156 		if (*str == '\\') {
157 			str++;
158 			switch (*str++) {
159 			case 'n':
160 				*cp++ = '\n';
161 				break;
162 			case 't':
163 				*cp++ = '\t';
164 				break;
165 			case 'v':
166 				*cp++ = '\v';
167 				break;
168 			case 'b':
169 				*cp++ = '\b';
170 				break;
171 			case 'r':
172 				*cp++ = '\r';
173 				break;
174 			case 'f':
175 				*cp++ = '\f';
176 				break;
177 			case 'a':
178 				*cp++ = '\007';
179 				break;
180 			case '\\':
181 				*cp++ = '\\';
182 				break;
183 			case '\"':
184 				*cp++ = '\"';
185 				break;
186 			case 'x':
187 				str--;
188 				num = strtoul(str, &str, 16);
189 				*cp++ = (char) num;
190 				break;
191 			case '0':
192 			case '1':
193 			case '2':
194 			case '3':
195 			case '4':
196 			case '5':
197 			case '6':
198 			case '7':
199 				str--;
200 				num = strtoul(str, &str, 8);
201 				*cp++ = (char) num;
202 				break;
203 			case '\0':
204 				return NULL;
205 			default:
206 				*cp++ = *(str - 1);
207 				break;
208 			};
209 		} else {
210 			*cp++ = *str++;
211 		}
212 	}
213 	if (*str == '\"')
214 		str++;		/* skip final quote */
215 	*cp = '\0';		/* terminate string */
216 	return str;
217 }
218 
219 /*
220  *	Parse command line to argv, honoring quotes and such
221  */
222 
parse_args(char * argv[],char * cmd)223 int parse_args(char *argv[],char *cmd)
224 {
225 	int ct = 0;
226 	int quoted;
227 
228 	while (ct < 255)
229 	{
230 		quoted = 0;
231 		while (*cmd && isspace((int)*cmd))
232 			cmd++;
233 		if (*cmd == 0)
234 			break;
235 		if (*cmd == '"') {
236 			quoted++;
237 			cmd++;
238 		}
239 		argv[ct++] = cmd;
240 		if (quoted) {
241 			if ((cmd = parse_string(cmd)) == NULL)
242 				return 0;
243 		} else {
244 			while (*cmd && !isspace((int)*cmd))
245 				cmd++;
246 		}
247 		if (*cmd)
248 			*cmd++ = 0;
249 	}
250 	argv[ct] = NULL;
251 	return ct;
252 }
253 
254 /*
255  *	Combine arguments back to a string
256  */
257 
argstr(int arg,int argc,char ** argv)258 char *argstr(int arg, int argc, char **argv)
259 {
260 	static char s[CFGLINE_LEN+1];
261 	int i;
262 
263 	s[0] = '\0';
264 
265 	for (i = arg; i < argc; i++) {
266 		strncat(s, argv[i], CFGLINE_LEN - strlen(s));
267 		strncat(s, " ", CFGLINE_LEN - strlen(s));
268 	}
269 
270 	if ((i = strlen(s)) > 0)
271 		s[i-1] = '\0';
272 
273 	return s;
274 }
275 
276 /*
277  *	Find the command from the command table and execute it.
278  */
279 
cmdparse(struct cfgcmd * cmds,char * cmdline)280 int cmdparse(struct cfgcmd *cmds, char *cmdline)
281 {
282 	struct cfgcmd *cmdp;
283 	int argc;
284 	char *argv[256];
285 
286 	if ((argc = parse_args(argv, cmdline)) == 0 || *argv[0] == '#')
287 		return 0;
288 	strlwr(argv[0]);
289 	for (cmdp = cmds; cmdp->function != NULL; cmdp++)
290 		if (strncasecmp(cmdp->name, argv[0], strlen(argv[0])) == 0 && strlen(argv[0]) == strlen(cmdp->name))
291 			break;
292 	if (cmdp->function == NULL) {
293 		hlog(LOG_ERR, "No such configuration file directive: %s", argv[0]);
294 		return -1;
295 	}
296 	return (*cmdp->function)(cmdp->dest, argc, argv);
297 }
298 
299  /* ***************************************************************** */
300 
301 
fgets_multiline(char * buf,int buflen,FILE * fp)302 char *fgets_multiline(char *buf, int buflen, FILE *fp)
303 {
304 	char *p = buf;
305 	int left = buflen;
306 	char *s;
307 	int len;
308 
309 	while (1) {
310 		s = fgets(p, left, fp);
311 		if (!s)
312 			return NULL;
313 
314 		len = strlen(s);
315 
316 		/* trim newlines and whitespace */
317 		while (len > 1 && (s[len-1] == '\r' || s[len-1] == '\n' || s[len-1] == ' ' || s[len-1] == '\t'))
318 			len--;
319 
320 		/* nul-terminate */
321 		s[len] = 0;
322 
323 		/* If the string does not end with a '\', return it */
324 		if (s[len-1] != '\\')
325 			return buf;
326 
327 		/* ahh, line continues... remove the \ and read next line. */
328 		len--;
329 		s[len] = 0;
330 		p = s + len;
331 		left = buflen - len;
332 	}
333 }
334 
335 /*
336  *	Read configuration
337  */
338 
read_cfgfile(char * f,struct cfgcmd * cmds)339 int read_cfgfile(char *f, struct cfgcmd *cmds)
340 {
341 	FILE *fp;
342 	char line[CFGLINE_LEN];
343 	int ret, n = 0;
344 
345 	if ((fp = fopen(f, "r")) == NULL) {
346 		hlog(LOG_ERR, "Cannot open %s: %s", f, strerror(errno));
347 		return 1;
348 	}
349 
350 	while (fgets_multiline(line, CFGLINE_LEN, fp) != NULL) {
351 		n++;
352 
353 		ret = cmdparse(cmds, line);
354 		if (ret < 0) {
355 			hlog(LOG_ERR, "Problem in %s at line %d: %s", f, n, line);
356 			fclose(fp);
357 			return 2;
358 		}
359 	}
360 	fclose(fp);
361 
362 	return 0;
363 }
364 
365