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