1 /*
2  * Copyright (C) 1997-2004, Michael Jennings
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to
6  * deal in the Software without restriction, including without limitation the
7  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8  * sell copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies of the Software, its documentation and marketing & publicity
13  * materials, and acknowledgment shall be given in the documentation, materials
14  * and software packages that this Software was used.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23 
24 /**
25  * @file conf.c
26  * Config File Parser Source File
27  *
28  * This file contains the functions which comprise the config file
29  * parser.
30  *
31  * @author Michael Jennings <mej@eterm.org>
32  */
33 
34 static const char __attribute__((unused)) cvs_ident[] = "$Id: conf.c,v 1.26 2005/03/07 22:29:07 mej Exp $";
35 
36 #ifdef HAVE_CONFIG_H
37 # include <config.h>
38 #endif
39 
40 #include <libast_internal.h>
41 
42 static spifconf_var_t *spifconf_new_var(void);
43 static void spifconf_free_var(spifconf_var_t *);
44 static spif_charptr_t spifconf_get_var(const spif_charptr_t);
45 static void spifconf_put_var(spif_charptr_t, spif_charptr_t);
46 static spif_charptr_t builtin_random(spif_charptr_t);
47 static spif_charptr_t builtin_exec(spif_charptr_t);
48 static spif_charptr_t builtin_get(spif_charptr_t);
49 static spif_charptr_t builtin_put(spif_charptr_t);
50 static spif_charptr_t builtin_dirscan(spif_charptr_t);
51 static spif_charptr_t builtin_version(spif_charptr_t);
52 static spif_charptr_t builtin_appname(spif_charptr_t);
53 static void *parse_null(spif_charptr_t, void *);
54 
55 static ctx_t *context;
56 static ctx_state_t *ctx_state;
57 static spifconf_func_t *builtins;
58 static unsigned char ctx_cnt, ctx_idx, ctx_state_idx, ctx_state_cnt, fstate_cnt, builtin_cnt, builtin_idx;
59 static spifconf_var_t *spifconf_vars = NULL;
60 
61 const char *true_vals[] = { "1", "on", "true", "yes" };
62 const char *false_vals[] = { "0", "off", "false", "no" };
63 
64 fstate_t *fstate = NULL;
65 unsigned char fstate_idx = 0;
66 
67 /***** The Config File Section *****/
68 /* This function must be called before any other spifconf_*() function.
69    Otherwise you will be bitten by dragons.  That's life. */
70 void
spifconf_init_subsystem(void)71 spifconf_init_subsystem(void)
72 {
73     /* Initialize the context list and establish a catch-all "null" context */
74     ctx_cnt = 20;
75     ctx_idx = 0;
76     context = (ctx_t *) MALLOC(sizeof(ctx_t) * ctx_cnt);
77     MEMSET(context, 0, sizeof(ctx_t) * ctx_cnt);
78     context[0].name = SPIF_CAST(charptr) STRDUP("null");
79     context[0].handler = parse_null;
80 
81     /* Initialize the context state stack and set the current context to "null" */
82     ctx_state_cnt = 20;
83     ctx_state_idx = 0;
84     ctx_state = (ctx_state_t *) MALLOC(sizeof(ctx_state_t) * ctx_state_cnt);
85     MEMSET(ctx_state, 0, sizeof(ctx_state_t) * ctx_state_cnt);
86 
87     /* Initialize the file state stack */
88     fstate_cnt = 10;
89     fstate_idx = 0;
90     fstate = (fstate_t *) MALLOC(sizeof(fstate_t) * fstate_cnt);
91     MEMSET(fstate, 0, sizeof(fstate_t) * fstate_cnt);
92 
93     /* Initialize the builtin function table */
94     builtin_cnt = 10;
95     builtin_idx = 0;
96     builtins = (spifconf_func_t *) MALLOC(sizeof(spifconf_func_t) * builtin_cnt);
97     MEMSET(builtins, 0, sizeof(spifconf_func_t) * builtin_cnt);
98 
99     /* Register the omni-present builtin functions */
100     spifconf_register_builtin("appname", builtin_appname);
101     spifconf_register_builtin("version", builtin_version);
102     spifconf_register_builtin("exec", builtin_exec);
103     spifconf_register_builtin("random", builtin_random);
104     spifconf_register_builtin("get", builtin_get);
105     spifconf_register_builtin("put", builtin_put);
106     spifconf_register_builtin("dirscan", builtin_dirscan);
107 }
108 
109 /* Register a new config file context */
110 unsigned char
spifconf_register_context(spif_charptr_t name,ctx_handler_t handler)111 spifconf_register_context(spif_charptr_t name, ctx_handler_t handler)
112 {
113     ASSERT_RVAL(!SPIF_PTR_ISNULL(name), SPIF_CAST_C(unsigned char) -1);
114     ASSERT_RVAL(!SPIF_PTR_ISNULL(handler), SPIF_CAST_C(unsigned char) -1);
115 
116     if (strcasecmp(SPIF_CAST_C(char *) name, "null")) {
117         if (++ctx_idx == ctx_cnt) {
118             ctx_cnt *= 2;
119             context = (ctx_t *) REALLOC(context, sizeof(ctx_t) * ctx_cnt);
120         }
121     } else {
122         FREE(context[0].name);
123     }
124     context[ctx_idx].name = SPIF_CAST(charptr) STRDUP(name);
125     context[ctx_idx].handler = handler;
126     D_CONF(("Added context \"%s\" with ID %d and handler 0x%08x\n", context[ctx_idx].name, ctx_idx, context[ctx_idx].handler));
127     return (ctx_idx);
128 }
129 
130 /* Register a new file state structure */
131 unsigned char
spifconf_register_fstate(FILE * fp,spif_charptr_t path,spif_charptr_t outfile,unsigned long line,unsigned char flags)132 spifconf_register_fstate(FILE * fp, spif_charptr_t path, spif_charptr_t outfile, unsigned long line, unsigned char flags)
133 {
134     ASSERT_RVAL(!SPIF_PTR_ISNULL(fp), SPIF_CAST_C(unsigned char) -1);
135     ASSERT_RVAL(!SPIF_PTR_ISNULL(path), SPIF_CAST_C(unsigned char) -1);
136 
137     if (++fstate_idx == fstate_cnt) {
138         fstate_cnt *= 2;
139         fstate = (fstate_t *) REALLOC(fstate, sizeof(fstate_t) * fstate_cnt);
140     }
141     fstate[fstate_idx].fp = fp;
142     fstate[fstate_idx].path = path;
143     fstate[fstate_idx].outfile = outfile;
144     fstate[fstate_idx].line = line;
145     fstate[fstate_idx].flags = flags;
146     return (fstate_idx);
147 }
148 
149 /* Register a new builtin function */
150 unsigned char
spifconf_register_builtin(char * name,spifconf_func_ptr_t ptr)151 spifconf_register_builtin(char *name, spifconf_func_ptr_t ptr)
152 {
153     ASSERT_RVAL(!SPIF_PTR_ISNULL(name), SPIF_CAST_C(unsigned char) -1);
154 
155     builtins[builtin_idx].name = SPIF_CAST(charptr) STRDUP(name);
156     builtins[builtin_idx].ptr = ptr;
157     if (++builtin_idx == builtin_cnt) {
158         builtin_cnt *= 2;
159         builtins = (spifconf_func_t *) REALLOC(builtins, sizeof(spifconf_func_t) * builtin_cnt);
160     }
161     return (builtin_idx - 1);
162 }
163 
164 /* Register a new config file context */
165 unsigned char
spifconf_register_context_state(unsigned char ctx_id)166 spifconf_register_context_state(unsigned char ctx_id)
167 {
168     if (++ctx_state_idx == ctx_state_cnt) {
169         ctx_state_cnt *= 2;
170         ctx_state = (ctx_state_t *) REALLOC(ctx_state, sizeof(ctx_state_t) * ctx_state_cnt);
171     }
172     ctx_state[ctx_state_idx].ctx_id = ctx_id;
173     ctx_state[ctx_state_idx].state = NULL;
174     return (ctx_state_idx);
175 }
176 
177 void
spifconf_free_subsystem(void)178 spifconf_free_subsystem(void)
179 {
180     spifconf_var_t *v, *tmp;
181     unsigned long i;
182 
183     for (v = spifconf_vars; v;) {
184         tmp = v;
185         v = v->next;
186         spifconf_free_var(tmp);
187     }
188     for (i = 0; i < builtin_idx; i++) {
189         FREE(builtins[i].name);
190     }
191     for (i = 0; i <= ctx_idx; i++) {
192         FREE(context[i].name);
193     }
194     FREE(ctx_state);
195     FREE(builtins);
196     FREE(fstate);
197     FREE(context);
198 }
199 
200 static spifconf_var_t *
spifconf_new_var(void)201 spifconf_new_var(void)
202 {
203     spifconf_var_t *v;
204 
205     v = (spifconf_var_t *) MALLOC(sizeof(spifconf_var_t));
206     MEMSET(v, 0, sizeof(spifconf_var_t));
207     return v;
208 }
209 
210 static void
spifconf_free_var(spifconf_var_t * v)211 spifconf_free_var(spifconf_var_t *v)
212 {
213     ASSERT(!SPIF_PTR_ISNULL(v));
214     if (v->var) {
215         FREE(v->var);
216     }
217     if (v->value) {
218         FREE(v->value);
219     }
220     FREE(v);
221 }
222 
223 static spif_charptr_t
spifconf_get_var(const spif_charptr_t var)224 spifconf_get_var(const spif_charptr_t var)
225 {
226     spifconf_var_t *v;
227 
228     ASSERT_RVAL(!SPIF_PTR_ISNULL(var), SPIF_NULL_TYPE_C(spif_charptr_t));
229     D_CONF(("var == \"%s\"\n", var));
230     for (v = spifconf_vars; v; v = v->next) {
231         if (!strcmp(SPIF_CAST_C(char *) v->var, SPIF_CAST_C(char *) var)) {
232             D_CONF(("Found it at %10p:  \"%s\" == \"%s\"\n", v, v->var, v->value));
233             return (v->value);
234         }
235     }
236     D_CONF(("Not found.\n"));
237     return NULL;
238 }
239 
240 static void
spifconf_put_var(spif_charptr_t var,spif_charptr_t val)241 spifconf_put_var(spif_charptr_t var, spif_charptr_t val)
242 {
243     spifconf_var_t *v, *loc = NULL, *tmp;
244 
245     ASSERT(var != NULL);
246     D_CONF(("var == \"%s\", val == \"%s\"\n", var, val));
247 
248     for (v = spifconf_vars; v; loc = v, v = v->next) {
249         int n;
250 
251         n = strcmp(SPIF_CAST_C(char *) var, SPIF_CAST_C(char *) v->var);
252         D_CONF(("Comparing at %10p:  \"%s\" -> \"%s\", n == %d\n", v, v->var, v->value, n));
253         if (n == 0) {
254             FREE(v->value);
255             if (val) {
256                 v->value = val;
257                 D_CONF(("Variable already defined.  Replacing its value with \"%s\"\n", v->value));
258             } else {
259                 D_CONF(("Variable already defined.  Deleting it.\n"));
260                 if (loc) {
261                     loc->next = v->next;
262                 } else {
263                     spifconf_vars = v->next;
264                 }
265                 spifconf_free_var(v);
266             }
267             return;
268         } else if (n < 0) {
269             break;
270         }
271     }
272     if (!val) {
273         D_CONF(("Empty value given for non-existant variable \"%s\".  Aborting.\n", var));
274         return;
275     }
276     D_CONF(("Inserting new var/val pair between \"%s\" and \"%s\"\n",
277             ((loc) ? (loc->var) : (SPIF_CAST(charptr) "-beginning-")),
278             ((v) ? (v->var) : (SPIF_CAST(charptr) "-end-"))));
279     tmp = spifconf_new_var();
280     if (loc == NULL) {
281         tmp->next = spifconf_vars;
282         spifconf_vars = tmp;
283     } else {
284         tmp->next = loc->next;
285         loc->next = tmp;
286     }
287     tmp->var = var;
288     tmp->value = val;
289 }
290 
291 static spif_charptr_t
builtin_random(spif_charptr_t param)292 builtin_random(spif_charptr_t param)
293 {
294     unsigned long n, index;
295     static unsigned int rseed = 0;
296 
297     REQUIRE_RVAL(!SPIF_PTR_ISNULL(param), SPIF_NULL_TYPE_C(spif_charptr_t));
298     D_PARSE(("builtin_random(%s) called\n", NONULL(param)));
299 
300     if (rseed == 0) {
301         rseed = (unsigned int) (getpid() * time(NULL) % ((unsigned int) -1));
302         srand(rseed);
303     }
304     n = spiftool_num_words(param);
305     index = (int) (n * ((float) rand()) / (RAND_MAX + 1.0)) + 1;
306     D_PARSE(("random index == %lu\n", index));
307 
308     return (spiftool_get_word(index, param));
309 }
310 
311 static spif_charptr_t
builtin_exec(spif_charptr_t param)312 builtin_exec(spif_charptr_t param)
313 {
314     spif_uint32_t fsize, maxlen;
315     spif_charptr_t Command, Output = NULL;
316     spif_char_t OutFile[256];
317     FILE *fp;
318     int fd;
319 
320     REQUIRE_RVAL(!SPIF_PTR_ISNULL(param), SPIF_NULL_TYPE_C(spif_charptr_t));
321     D_PARSE(("builtin_exec(%s) called\n", NONULL(param)));
322 
323     Command = (spif_charptr_t) MALLOC(CONFIG_BUFF);
324     strcpy(SPIF_CAST_C(char *) OutFile, "Eterm-exec-");
325     fd = spiftool_temp_file(OutFile, sizeof(OutFile));
326     if ((fd < 0) || fchmod(fd, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
327         libast_print_error("Unable to create unique temporary file for \"%s\" -- %s\n", param, strerror(errno));
328         return ((spif_charptr_t) NULL);
329     }
330 
331     maxlen = strlen(SPIF_CAST_C(char *) param) + strlen(SPIF_CAST_C(char *) OutFile) + 8;
332     if (maxlen > CONFIG_BUFF) {
333         libast_print_error("Parse error in file %s, line %lu:  Cannot execute command, line too long\n",
334                            file_peek_path(), file_peek_line());
335         return ((spif_charptr_t) NULL);
336     }
337     strcpy(SPIF_CAST_C(char *) Command, SPIF_CAST_C(char *) param);
338     strcat(SPIF_CAST_C(char *) Command, " >");
339     strcat(SPIF_CAST_C(char *) Command, SPIF_CAST_C(char *) OutFile);
340     system(SPIF_CAST_C(char *) Command);
341     if ((fp = fdopen(fd, "rb")) != NULL) {
342         fseek(fp, 0, SEEK_END);
343         fsize = ftell(fp);
344         rewind(fp);
345         if (fsize) {
346             Output = (spif_charptr_t) MALLOC(fsize + 1);
347             fread(Output, fsize, 1, fp);
348             Output[fsize] = 0;
349             fclose(fp);
350             remove(SPIF_CAST_C(char *) OutFile);
351             Output = spiftool_condense_whitespace(Output);
352         } else {
353             libast_print_warning("Command at line %lu of file %s returned no output.\n",
354                                  file_peek_line(), file_peek_path());
355         }
356     } else {
357         libast_print_warning("Output file %s could not be created.  (line %lu of file %s)\n", NONULL(OutFile), file_peek_line(), file_peek_path());
358     }
359     FREE(Command);
360 
361     return (Output);
362 }
363 
364 static spif_charptr_t
builtin_get(spif_charptr_t param)365 builtin_get(spif_charptr_t param)
366 {
367     spif_charptr_t s, f, v;
368     unsigned short n;
369 
370     if (!param || ((n = spiftool_num_words(param)) > 2)) {
371         libast_print_error("Parse error in file %s, line %lu:  Invalid syntax for %get().  Syntax is:  %get(variable)\n", file_peek_path(),
372                     file_peek_line());
373         return NULL;
374     }
375 
376     D_PARSE(("builtin_get(%s) called\n", param));
377     s = spiftool_get_word(1, param);
378     if (n == 2) {
379         f = spiftool_get_word(2, param);
380     } else {
381         f = NULL;
382     }
383     v = spifconf_get_var(s);
384     FREE(s);
385     if (v) {
386         if (f) {
387             FREE(f);
388         }
389         return (SPIF_CAST(charptr) STRDUP(v));
390     } else if (f) {
391         return f;
392     } else {
393         return NULL;
394     }
395 }
396 
397 static spif_charptr_t
builtin_put(spif_charptr_t param)398 builtin_put(spif_charptr_t param)
399 {
400     spif_charptr_t var, val;
401 
402     if (!param || (spiftool_num_words(param) != 2)) {
403         libast_print_error("Parse error in file %s, line %lu:  Invalid syntax for %put().  Syntax is:  %put(variable value)\n", file_peek_path(),
404                     file_peek_line());
405         return NULL;
406     }
407 
408     D_PARSE(("builtin_put(%s) called\n", param));
409     var = spiftool_get_word(1, param);
410     val = spiftool_get_word(2, param);
411     spifconf_put_var(var, val);
412     return NULL;
413 }
414 
415 static spif_charptr_t
builtin_dirscan(spif_charptr_t param)416 builtin_dirscan(spif_charptr_t param)
417 {
418     int i;
419     unsigned long n;
420     DIR *dirp;
421     struct dirent *dp;
422     struct stat filestat;
423     spif_charptr_t dir, buff;
424 
425     if (!param || (spiftool_num_words(param) != 1)) {
426         libast_print_error("Parse error in file %s, line %lu:  Invalid syntax for %dirscan().  Syntax is:  %dirscan(directory)\n",
427                     file_peek_path(), file_peek_line());
428         return NULL;
429     }
430     D_PARSE(("builtin_dirscan(%s)\n", param));
431     dir = spiftool_get_word(1, param);
432     dirp = opendir(SPIF_CAST_C(char *) dir);
433     if (!dirp) {
434         return NULL;
435     }
436     buff = (spif_charptr_t) MALLOC(CONFIG_BUFF);
437     *buff = 0;
438     n = CONFIG_BUFF;
439 
440     for (i = 0; (dp = readdir(dirp)) != NULL;) {
441         spif_char_t fullname[PATH_MAX];
442 
443         snprintf(SPIF_CAST_C(char *) fullname, sizeof(fullname), "%s/%s", dir, dp->d_name);
444         if (stat(SPIF_CAST_C(char *) fullname, &filestat)) {
445             D_PARSE((" -> Couldn't stat() file %s -- %s\n", fullname, strerror(errno)));
446         } else {
447             if (S_ISREG(filestat.st_mode)) {
448                 unsigned long len;
449 
450                 len = strlen(dp->d_name);
451                 if (len < n) {
452                     strcat(SPIF_CAST_C(char *) buff, dp->d_name);
453                     strcat(SPIF_CAST_C(char *) buff, " ");
454                     n -= len + 1;
455                 }
456             }
457         }
458         if (n < 2) {
459             break;
460         }
461     }
462     closedir(dirp);
463     return buff;
464 }
465 
466 static spif_charptr_t
builtin_version(spif_charptr_t param)467 builtin_version(spif_charptr_t param)
468 {
469     USE_VAR(param);
470     D_PARSE(("builtin_version(%s) called\n", NONULL(param)));
471 
472     return (SPIF_CAST(charptr) STRDUP(libast_program_version));
473 }
474 
475 static spif_charptr_t
builtin_appname(spif_charptr_t param)476 builtin_appname(spif_charptr_t param)
477 {
478     char buff[256];
479 
480     USE_VAR(param);
481     D_PARSE(("builtin_appname(%s) called\n", NONULL(param)));
482 
483     snprintf(buff, sizeof(buff), "%s-%s", libast_program_name, libast_program_version);
484     return (SPIF_CAST(charptr) STRDUP(buff));
485 }
486 
487 /* spifconf_shell_expand() takes care of shell variable expansion, quote conventions,
488    calling of built-in functions, etc.                                -- mej */
489 spif_charptr_t
spifconf_shell_expand(spif_charptr_t s)490 spifconf_shell_expand(spif_charptr_t s)
491 {
492     register spif_charptr_t tmp;
493     register spif_charptr_t pbuff = s, tmp1;
494     register spif_uint32_t j, k, l = 0;
495     spif_char_t newbuff[CONFIG_BUFF];
496     spif_uint8_t in_single = 0, in_double = 0;
497     spif_uint32_t cnt1 = 0, cnt2 = 0;
498     const spif_uint32_t max = CONFIG_BUFF - 1;
499     spif_charptr_t Command, Output, EnvVar;
500 
501     ASSERT_RVAL(s != NULL, SPIF_NULL_TYPE(charptr));
502 
503 #if 0
504     newbuff = (spif_charptr_t) MALLOC(CONFIG_BUFF);
505 #endif
506 
507     for (j = 0; *pbuff && j < max; pbuff++, j++) {
508         switch (*pbuff) {
509           case '~':
510               D_CONF(("Tilde detected.\n"));
511               EnvVar = SPIF_CAST(charptr) getenv("HOME");
512               if (!in_single && !in_double && EnvVar && *EnvVar) {
513                   spiftool_safe_strncpy(newbuff + j, EnvVar, max - j);
514                   cnt1 = strlen(SPIF_CAST_C(char *) EnvVar) - 1;
515                   cnt2 = max - j - 1;
516                   j += MIN(cnt1, cnt2);
517               } else {
518                   newbuff[j] = *pbuff;
519               }
520               break;
521           case '\\':
522               D_CONF(("Escape sequence detected.\n"));
523               if (!in_single || (in_single && *(pbuff + 1) == '\'')) {
524                   switch (tolower(*(++pbuff))) {
525                     case 'n':
526                         newbuff[j] = '\n';
527                         break;
528                     case 'r':
529                         newbuff[j] = '\r';
530                         break;
531                     case 't':
532                         newbuff[j] = '\t';
533                         break;
534                     case 'b':
535                         newbuff[j] = '\b';
536                         break;
537                     case 'f':
538                         newbuff[j] = '\f';
539                         break;
540                     case 'a':
541                         newbuff[j] = '\a';
542                         break;
543                     case 'v':
544                         newbuff[j] = '\v';
545                         break;
546                     case 'e':
547                         newbuff[j] = '\033';
548                         break;
549                     default:
550                         newbuff[j] = *pbuff;
551                         break;
552                   }
553               } else {
554                   newbuff[j++] = *(pbuff++);
555                   newbuff[j] = *pbuff;
556               }
557               break;
558           case '%':
559               D_CONF(("%% detected.\n"));
560               for (k = 0, pbuff++; builtins[k].name != NULL; k++) {
561                   D_PARSE(("Checking for function %%%s, pbuff == \"%s\"\n", builtins[k].name, pbuff));
562                   l = strlen(SPIF_CAST_C(char *) builtins[k].name);
563                   if (!strncasecmp(SPIF_CAST_C(char *) builtins[k].name, SPIF_CAST_C(char *) pbuff, l)
564                       && ((pbuff[l] == '(')
565                           || (pbuff[l] == ' '
566                               && pbuff[l + 1] == ')'))) {
567                       break;
568                   }
569               }
570               if (builtins[k].name == NULL) {
571                   newbuff[j] = *pbuff;
572               } else {
573                   D_CONF(("Call to built-in function %s detected.\n", builtins[k].name));
574                   Command = SPIF_CAST(charptr) MALLOC(CONFIG_BUFF);
575                   pbuff += l;
576                   if (*pbuff != '(')
577                       pbuff++;
578                   for (tmp1 = Command, pbuff++, l = 1; l && *pbuff; pbuff++, tmp1++) {
579                       switch (*pbuff) {
580                         case '(':
581                             l++;
582                             *tmp1 = *pbuff;
583                             break;
584                         case ')':
585                             l--;
586                         default:
587                             *tmp1 = *pbuff;
588                             break;
589                       }
590                   }
591                   *(--tmp1) = 0;
592                   if (l) {
593                       libast_print_error("parse error in file %s, line %lu:  Mismatched parentheses\n", file_peek_path(), file_peek_line());
594                       return SPIF_NULL_TYPE(charptr);
595                   }
596                   Command = spifconf_shell_expand(Command);
597                   Output = SPIF_CAST(charptr) (builtins[k].ptr) (Command);
598                   FREE(Command);
599                   if (Output) {
600                       if (*Output) {
601                           spiftool_safe_strncpy(newbuff + j, Output, max - j);
602                           l = strlen(SPIF_CAST_C(char *) Output) - 1;
603                           cnt2 = max - j - 1;
604                           j += MIN(l, cnt2);
605                       } else {
606                           j--;
607                       }
608                       FREE(Output);
609                   } else {
610                       j--;
611                   }
612                   pbuff--;
613               }
614               break;
615           case '`':
616 #if ALLOW_BACKQUOTE_EXEC
617               D_CONF(("Backquotes detected.  Evaluating expression.\n"));
618               if (!in_single) {
619                   Command = SPIF_CAST(charptr) MALLOC(CONFIG_BUFF);
620                   l = 0;
621                   for (pbuff++; *pbuff && *pbuff != '`' && l < max; pbuff++, l++) {
622                       Command[l] = *pbuff;
623                   }
624                   ASSERT_RVAL(l < CONFIG_BUFF, NULL);
625                   Command[l] = 0;
626                   Command = spifconf_shell_expand(Command);
627                   Output = builtin_exec(Command);
628                   FREE(Command);
629                   if (Output) {
630                       if (*Output) {
631                           spiftool_safe_strncpy(newbuff + j, Output, max - j);
632                           l = strlen(SPIF_CAST_C(char *) Output) - 1;
633                           cnt2 = max - j - 1;
634                           j += MIN(l, cnt2);
635                       } else {
636                           j--;
637                       }
638                       FREE(Output);
639                   } else {
640                       j--;
641                   }
642               } else {
643                   newbuff[j] = *pbuff;
644               }
645 #else
646               libast_print_warning("Backquote execution support not compiled in, ignoring\n");
647               newbuff[j] = *pbuff;
648 #endif
649               break;
650           case '$':
651               D_CONF(("Environment variable detected.  Evaluating.\n"));
652               if (!in_single) {
653                   EnvVar = SPIF_CAST(charptr) MALLOC(128);
654                   switch (*(++pbuff)) {
655                     case '{':
656                         for (pbuff++, k = 0; *pbuff != '}' && k < 127; k++, pbuff++)
657                             EnvVar[k] = *pbuff;
658                         break;
659                     case '(':
660                         for (pbuff++, k = 0; *pbuff != ')' && k < 127; k++, pbuff++)
661                             EnvVar[k] = *pbuff;
662                         break;
663                     default:
664                         for (k = 0; (isalnum(*pbuff) || *pbuff == '_') && k < 127; k++, pbuff++)
665                             EnvVar[k] = *pbuff;
666                         break;
667                   }
668                   EnvVar[k] = 0;
669                   tmp = SPIF_CAST(charptr) getenv(SPIF_CAST_C(char *) EnvVar);
670                   if (tmp && *tmp) {
671                       spiftool_safe_strncpy(newbuff, tmp, max - j);
672                       cnt1 = strlen(SPIF_CAST_C(char *) tmp) - 1;
673                       cnt2 = max - j - 1;
674                       j += MIN(cnt1, cnt2);
675                   }
676                   pbuff--;
677               } else {
678                   newbuff[j] = *pbuff;
679               }
680               break;
681           case '\"':
682               D_CONF(("Double quotes detected.\n"));
683               if (!in_single) {
684                   if (in_double) {
685                       in_double = 0;
686                   } else {
687                       in_double = 1;
688                   }
689               }
690               newbuff[j] = *pbuff;
691               break;
692 
693           case '\'':
694               D_CONF(("Single quotes detected.\n"));
695               if (in_single) {
696                   in_single = 0;
697               } else {
698                   in_single = 1;
699               }
700               newbuff[j] = *pbuff;
701               break;
702 
703           default:
704               newbuff[j] = *pbuff;
705         }
706     }
707     ASSERT_RVAL(j < CONFIG_BUFF, NULL);
708     newbuff[j] = 0;
709 
710     D_PARSE(("spifconf_shell_expand(%s) returning \"%s\"\n", s, newbuff));
711     D_PARSE((" strlen(s) == %lu, strlen(newbuff) == %lu, j == %lu\n",
712              strlen(SPIF_CAST_C(char *) s),
713              strlen(SPIF_CAST_C(char *) newbuff), j));
714 
715     strcpy(SPIF_CAST_C(char *) s, SPIF_CAST_C(char *) newbuff);
716 #if 0
717     FREE(newbuff);
718 #endif
719     return (s);
720 }
721 
722 /* The config file reader.  This looks for the config file by searching CONFIG_SEARCH_PATH.
723    If it can't find a config file, it displays a warning but continues. -- mej */
724 spif_charptr_t
spifconf_find_file(const spif_charptr_t file,const spif_charptr_t dir,const spif_charptr_t pathlist)725 spifconf_find_file(const spif_charptr_t file, const spif_charptr_t dir, const spif_charptr_t pathlist)
726 {
727     static spif_char_t name[PATH_MAX], full_path[PATH_MAX];
728     spif_charptr_t path, p;
729     spif_int32_t len, maxpathlen;
730     struct stat fst;
731 
732     REQUIRE_RVAL(file != NULL, NULL);
733 
734     getcwd(SPIF_CAST_C(char *) name, PATH_MAX);
735     D_CONF(("spifconf_find_file(\"%s\", \"%s\", \"%s\") called from directory \"%s\".\n",
736             file, NONULL(dir), NONULL(pathlist), name));
737 
738     /* Make sure our supplied settings don't overflow. */
739     len = strlen(SPIF_CAST_C(char *) file) + ((dir) ? (strlen(SPIF_CAST_C(char *) dir)) : (0)) + 2;
740     if ((len > SPIF_CAST(int32) sizeof(name)) || (len <= 0)) {
741         D_CONF(("Too big.  I lose. :(\n"));
742         return ((spif_charptr_t) NULL);
743     }
744 
745     if (dir) {
746         strcpy(SPIF_CAST_C(char *) name, SPIF_CAST_C(char *) dir);
747         strcat(SPIF_CAST_C(char *) name, "/");
748         strcat(SPIF_CAST_C(char *) name, SPIF_CAST_C(char *) file);
749     } else {
750         strcpy(SPIF_CAST_C(char *) name, SPIF_CAST_C(char *) file);
751     }
752     len = strlen(SPIF_CAST_C(char *) name);
753     D_CONF(("Checking for file \"%s\"\n", name));
754     if ((!access(SPIF_CAST_C(char *) name, R_OK))
755         && (!stat(SPIF_CAST_C(char *) name, &fst))
756         && (!S_ISDIR(fst.st_mode))) {
757         D_CONF(("Found \"%s\"\n", name));
758         return ((spif_charptr_t) name);
759     }
760 
761     /* maxpathlen is the longest possible path we can stuff into name[].  The - 2 saves room for
762        an additional / and the trailing null. */
763     if ((maxpathlen = sizeof(name) - len - 2) <= 0) {
764         D_CONF(("Too big.  I lose. :(\n"));
765         return ((spif_charptr_t) NULL);
766     }
767 
768     for (path = pathlist; path != NULL && *path != '\0'; path = p) {
769         short n;
770 
771         /* Calculate the length of the next directory in the path */
772         if ((p = SPIF_CAST(charptr) strchr(SPIF_CAST_C(char *) path, ':')) != NULL) {
773             n = p++ - path;
774         } else {
775             n = strlen(SPIF_CAST_C(char *) path);
776         }
777 
778         /* Don't try if it's too long */
779         if (n > 0 && n <= maxpathlen) {
780             /* Compose the /path/file combo */
781             memcpy(full_path, path, n);
782             if (full_path[n - 1] != '/') {
783                 full_path[n++] = '/';
784             }
785             full_path[n] = '\0';
786             strcat(SPIF_CAST_C(char *) full_path, SPIF_CAST_C(char *) name);
787 
788             D_CONF(("Checking for file \"%s\"\n", full_path));
789             if ((!access(SPIF_CAST_C(char *) full_path, R_OK))
790                 && (!stat(SPIF_CAST_C(char *) full_path, &fst))
791                 && (!S_ISDIR(fst.st_mode))) {
792                 D_CONF(("Found \"%s\"\n", full_path));
793                 return ((spif_charptr_t) full_path);
794             }
795         }
796     }
797     D_CONF(("spifconf_find_file():  File \"%s\" not found in path.\n", name));
798     return ((spif_charptr_t) NULL);
799 }
800 
801 FILE *
spifconf_open_file(spif_charptr_t name)802 spifconf_open_file(spif_charptr_t name)
803 {
804     FILE *fp;
805     spif_cmp_t ver;
806     spif_str_t ver_str;
807     spif_char_t buff[256], test[30];
808     spif_charptr_t begin_ptr, end_ptr;
809     spif_stridx_t testlen;
810 
811     ASSERT_RVAL(name != NULL, NULL);
812 
813     snprintf(SPIF_CAST_C(char *) test, sizeof(test), "<%s-", libast_program_name);
814     testlen = SPIF_CAST(stridx) strlen(SPIF_CAST_C(char *) test);
815 
816     /* Read first line from config file.  Using spif_str_new_from_fp() would read the
817      * whole file, so we don't do that here. */
818     fp = fopen(SPIF_CAST_C(char *) name, "rt");
819     REQUIRE_RVAL(fp != NULL, NULL);
820     fgets(SPIF_CAST_C(char *) buff, 256, fp);
821     ver_str = spif_str_new_from_ptr(buff);
822 
823     /* Check for magic string. */
824     if (spif_str_ncasecmp_with_ptr(ver_str, test, testlen)) {
825         libast_print_warning("%s exists but does not contain the proper magic string (<%s-%s>)\n",
826                              name, libast_program_name, libast_program_version);
827         fclose(fp);
828         spif_str_del(ver_str);
829         return NULL;
830     }
831 
832     /* Check version number against current application version. */
833     begin_ptr = SPIF_STR_STR(ver_str) + spif_str_index(ver_str, SPIF_CAST(char) '-') + 1;
834     end_ptr = SPIF_STR_STR(ver_str) + spif_str_index(ver_str, SPIF_CAST(char) '>');
835     D_CONF(("Begin pointer is %10p (%s), end pointer is %10p (%s), length is %d, buffer size is %d\n",
836             begin_ptr, begin_ptr, end_ptr, end_ptr, SPIF_CAST_C(int) (end_ptr - begin_ptr), sizeof(buff)));
837     if (SPIF_PTR_ISNULL(end_ptr)) {
838         spiftool_safe_strncpy(buff, begin_ptr, sizeof(buff));
839     } else {
840         testlen = MIN(SPIF_CAST_C(int) sizeof(buff), SPIF_CAST_C(int) (end_ptr - begin_ptr + 1));
841         spiftool_safe_strncpy(buff, begin_ptr, testlen);
842     }
843     ver = spiftool_version_compare(buff, libast_program_version);
844     if (SPIF_CMP_IS_GREATER(ver)) {
845         libast_print_warning("Config file is designed for a newer version of %s\n",
846                              libast_program_name);
847     }
848     spif_str_del(ver_str);
849     return (fp);
850 }
851 
852 #define SPIFCONF_PARSE_RET()  do {if (!fp) {file_pop(); ctx_end();} return;} while (0)
853 void
spifconf_parse_line(FILE * fp,spif_charptr_t buff)854 spifconf_parse_line(FILE * fp, spif_charptr_t buff)
855 {
856     register unsigned long i = 0;
857     unsigned char id;
858     void *state = NULL;
859 
860     ASSERT(buff != NULL);
861 
862     if (!(*buff) || *buff == '\n' || *buff == '#' || *buff == '<') {
863         SPIFCONF_PARSE_RET();
864     }
865     if (fp == NULL) {
866         file_push(NULL, SPIF_CAST(charptr) "<argv>", NULL, 0, 0);
867         ctx_begin(1);
868         buff = spiftool_get_pword(2, buff);
869         if (!buff) {
870             SPIFCONF_PARSE_RET();
871         }
872     }
873     id = ctx_peek_id();
874     spiftool_chomp(buff);
875     D_CONF(("Parsing line #%lu of file %s\n", file_peek_line(), file_peek_path()));
876     switch (*buff) {
877       case '#':
878       case '\0':
879           SPIFCONF_PARSE_RET();
880       case '%':
881           if (!BEG_STRCASECMP(spiftool_get_pword(1, buff + 1), "include ")) {
882               spif_charptr_t path;
883               FILE *fp;
884 
885               spifconf_shell_expand(SPIF_CAST(charptr) buff);
886               path = spiftool_get_word(2, buff + 1);
887               if ((fp = spifconf_open_file(path)) == NULL) {
888                   libast_print_error("Parsing file %s, line %lu:  Unable to locate %%included config file %s (%s), continuing\n", file_peek_path(),
889                               file_peek_line(), path, strerror(errno));
890               } else {
891                   file_push(fp, path, NULL, 1, 0);
892               }
893           } else if (!BEG_STRCASECMP(spiftool_get_pword(1, buff + 1), "preproc ")) {
894               spif_char_t cmd[PATH_MAX], fname[PATH_MAX];
895               spif_charptr_t outfile;
896               int fd;
897               FILE *fp;
898 
899               if (file_peek_preproc()) {
900                   SPIFCONF_PARSE_RET();
901               }
902               strcpy(SPIF_CAST_C(char *) fname, "Eterm-preproc-");
903               fd = spiftool_temp_file(fname, PATH_MAX);
904               outfile = SPIF_CAST(charptr) STRDUP(fname);
905               snprintf(SPIF_CAST_C(char *) cmd, PATH_MAX, "%s < %s > %s",
906                        spiftool_get_pword(2, buff), file_peek_path(), fname);
907               system(SPIF_CAST_C(char *) cmd);
908               fp = fdopen(fd, "rt");
909               if (fp != NULL) {
910                   fclose(file_peek_fp());
911                   file_poke_fp(fp);
912                   file_poke_preproc(1);
913                   file_poke_outfile(outfile);
914               }
915           } else {
916               if (file_peek_skip()) {
917                   SPIFCONF_PARSE_RET();
918               }
919               spifconf_shell_expand(SPIF_CAST(charptr) buff);
920           }
921           break;
922       case 'b':
923           if (file_peek_skip()) {
924               SPIFCONF_PARSE_RET();
925           }
926           if (!BEG_STRCASECMP(buff, "begin ")) {
927               ctx_begin(2);
928               break;
929           }
930           /* Intentional pass-through */
931       case 'e':
932           if (!BEG_STRCASECMP(buff, "end ") || !strcasecmp(SPIF_CAST_C(char *) buff, "end")) {
933               ctx_end();
934               break;
935           }
936           /* Intentional pass-through */
937       default:
938           if (file_peek_skip()) {
939               SPIFCONF_PARSE_RET();
940           }
941           spifconf_shell_expand(SPIF_CAST(charptr) buff);
942           ctx_poke_state((*ctx_id_to_func(id)) (buff, ctx_peek_state()));
943     }
944     SPIFCONF_PARSE_RET();
945 }
946 
947 #undef SPIFCONF_PARSE_RET
948 
949 spif_charptr_t
spifconf_parse(spif_charptr_t conf_name,const spif_charptr_t dir,const spif_charptr_t path)950 spifconf_parse(spif_charptr_t conf_name, const spif_charptr_t dir, const spif_charptr_t path)
951 {
952     FILE *fp;
953     spif_charptr_t name = NULL, p = SPIF_CAST(charptr) ".";
954     spif_char_t buff[CONFIG_BUFF], orig_dir[PATH_MAX];
955 
956     REQUIRE_RVAL(conf_name != NULL, 0);
957 
958     *orig_dir = 0;
959     if (path) {
960         if ((name = spifconf_find_file(conf_name, dir, path)) != NULL) {
961             if ((p = SPIF_CAST(charptr) strrchr(SPIF_CAST_C(char *) name, '/')) != NULL) {
962                 getcwd(SPIF_CAST_C(char *) orig_dir, PATH_MAX);
963                 *p = 0;
964                 p = name;
965                 chdir(SPIF_CAST_C(char *) name);
966             } else {
967                 p = SPIF_CAST(charptr) ".";
968             }
969         } else {
970             return NULL;
971         }
972     }
973     if ((fp = spifconf_open_file(conf_name)) == NULL) {
974         return NULL;
975     }
976 	/* Line count starts at 1 because spifconf_open_file() parses the first line. */
977     file_push(fp, conf_name, NULL, 1, 0);
978 
979     for (; fstate_idx > 0;) {
980         for (; fgets(SPIF_CAST_C(char *) buff, CONFIG_BUFF, file_peek_fp());) {
981             file_inc_line();
982             if (!strchr(SPIF_CAST_C(char *) buff, '\n')) {
983                 libast_print_error("Parse error in file %s, line %lu:  line too long\n",
984                                    file_peek_path(), file_peek_line());
985                 for (; fgets(SPIF_CAST_C(char *) buff, CONFIG_BUFF, file_peek_fp())
986                        && !strrchr(SPIF_CAST_C(char *) buff, '\n'););
987                 continue;
988             }
989             spifconf_parse_line(fp, buff);
990         }
991         fclose(file_peek_fp());
992         if (file_peek_preproc()) {
993             remove(SPIF_CAST_C(char *) file_peek_outfile());
994             FREE(file_peek_outfile());
995         }
996         file_pop();
997     }
998     if (*orig_dir) {
999         chdir(SPIF_CAST_C(char *) orig_dir);
1000     }
1001     D_CONF(("Returning \"%s\"\n", p));
1002     return (SPIF_CAST(charptr) STRDUP(p));
1003 }
1004 
1005 static void *
parse_null(spif_charptr_t buff,void * state)1006 parse_null(spif_charptr_t buff, void *state)
1007 {
1008     ASSERT_RVAL(!SPIF_PTR_ISNULL(buff), SPIF_NULL_TYPE(ptr));
1009     if (*buff == SPIFCONF_BEGIN_CHAR) {
1010         return (NULL);
1011     } else if (*buff == SPIFCONF_END_CHAR) {
1012         return (NULL);
1013     } else {
1014         libast_print_error("Parse error in file %s, line %lu:  Not allowed in \"null\" context:  \"%s\"\n", file_peek_path(), file_peek_line(),
1015                     buff);
1016         return (state);
1017     }
1018 }
1019 
1020 /**
1021  * @defgroup DOXGRP_CONF Configuration File Parser
1022  *
1023  * This group of functions/defines/macros comprises the configuration
1024  * file parsing engine.
1025  *
1026  *
1027  * A small sample program demonstrating some of these routines can be
1028  * found @link conf_example.c here @endlink.
1029  */
1030 
1031 /**
1032  * @example conf_example.c
1033  * Example code for using the config file parser.
1034  *
1035  */
1036 
1037 /**
1038  * @defgroup DOXGRP_CONF_FSS File State Stack
1039  * @ingroup DOXGRP_CONF
1040  *
1041  * @note An understanding of the inner workings of the file state
1042  * stack is not necessary to use the config file parser.  If you
1043  * aren't interested in understanding the LibAST internals, you should
1044  * skip most of this section and simply study the examples.
1045  *
1046  * Parsers must keep track of various state-related information when
1047  * parsing files, things like file name, line number, etc.  And since
1048  * LibAST's config file parser supports the inclusion of sub-files via
1049  * its %include directive, it must keep track of multiple instances of
1050  * this information, one for each file.  LibAST uses a structure array
1051  * called the file state stack.
1052  *
1053  * When config file parsing is initiated by a call to spifconf_parse(),
1054  * the information for that file is pushed onto the empty stack.  For
1055  * monolithic config files, the stack retains its height throughout
1056  * the parsing cycle.  However, if an @c %include directive is
1057  * encountered (and the file is successfully opened), a new set of
1058  * data is placed atop the stack via file_push().  The new file is
1059  * then parsed in its entirety, including any sub-files that it may
1060  * itself include, before its information is popped off the stack and
1061  * parsing of the original file can continue.
1062  *
1063  * Client programs should not need to modify the stack in any way.
1064  * However, use of the file_peek_path() and file_peek_line() macros
1065  * are encouraged, specifically for printing error/warning messages.
1066  * Many of the file state stack manipulation routines should probably
1067  * never be called by client programs (and are therefore marked as
1068  * internal); they are, however, made available on the off chance that
1069  * someone may get super-creative and do something neat with them.
1070  * Just don't blame LibAST if your (ab)use of internal functions
1071  * breaks the parser!
1072  */
1073 
1074 /**
1075  * @defgroup DOXGRP_CONF_CTX Context Handling
1076  * @ingroup DOXGRP_CONF
1077  *
1078  * LibAST-style configuration files are organized into logical units
1079  * called "contexts."  A begin/end pair is used to surround groups of
1080  * directives which should be evaluated in a given context.  The begin
1081  * keyword is followed by the name of the context that will follow.
1082  * The end keyword may stand alone; anything after it is ignored.
1083  *
1084  * The parser starts out in a pseudo-context called @c null for which
1085  * LibAST employs a built-in handler that rejects any unexpected
1086  * directives with an error message.  Any other context must be dealt
1087  * with by a client-specified context handler.
1088  *
1089  * Context handlers defined by the client program must conform to the
1090  * following specification:
1091  * - Accept two parameters as follows:
1092  *    -# A spif_charptr_t containing the line of text to be parsed
1093  *    -# A void * containing optional state information, or NULL
1094  * - Return a void * containing optional state information, or NULL
1095  *
1096  * Although nothing else is strictly @em required by LibAST, if you
1097  * want your parser to actually work, it needs to handle the LibAST
1098  * context handler calling conventions.  The following is a
1099  * step-by-step walk-through of how LibAST calls parser functions:
1100  *
1101  * -# When LibAST encounters a @c begin keyword followed by a one or
1102  *    more additional words (words are separated by whitespace
1103  *    according to shell conventions), the word immediately following
1104  *    the @c begin keyword is interpreted as the context name.
1105  * -# LibAST checks its list of registered context handlers for one
1106  *    that matches the given context name.  If none is found, an error
1107  *    is printed, and the parser skips the entire context (until the
1108  *    next @c end keyword).  Otherwise, go to the next step.
1109  * -# The registered context handler function is called.  The value
1110  *    #SPIFCONF_BEGIN_STRING is passed as the first parameter (which I'll
1111  *    call @a buff ), and NULL is passed as the second parameter
1112  *    (which I'll call @a state ).
1113  * -# The context handler should handle this using a statement similar
1114  *    to the following:
1115  *     @code
1116  *     if (*buff == SPIFCONF_BEGIN_CHAR) {
1117  *     @endcode
1118  *    (The value of #SPIFCONF_BEGIN_CHAR is such that it should never
1119  *    occur in normal config file text.)
1120  *    If the handler does not require any persistent state information
1121  *    to be kept between calls, it may simply return NULL here.
1122  *    Otherwise, this portion of the handler should perform any
1123  *    initialization required for the state information and return a
1124  *    pointer to that information.
1125  * -# The value returned by the context handler is stored by LibAST
1126  *    for later use, and parsing of the config file continues with the
1127  *    next line.
1128  * -# Each subsequent line encountered in the config file which does
1129  *    not start with the keyword @c end is passed to the context
1130  *    handler function as the first parameter.  The second parameter
1131  *    will contain the handler's previous return value, the persistent
1132  *    state information pointer.  The handler, of course, should
1133  *    continue returning the state information pointer.
1134  * -# Once the @c end keyword is encountered, the context handler is
1135  *    called with #SPIFCONF_END_STRING as the first parameter and the
1136  *    state information pointer as the second parameter.  This
1137  *    situation should be caught by some code like this:
1138  *     @code
1139  *     if (*buff == SPIFCONF_END_CHAR) {
1140  *     @endcode
1141  *    Again, the handler should simply return NULL if no state
1142  *    information is being kept.  Otherwise, any post-processing or
1143  *    cleanup needed should be done, possibly including the freeing of
1144  *    the state pointer, etc.  The handler should then return NULL.
1145  * -# LibAST reverts to the aforementioned @c null context and
1146  *    continues parsing as above.
1147  *
1148  * A sample implementation of context handlers which demonstrate use
1149  * of this mechanism can be found in the @link conf_example.c config
1150  * file parser example @endlink.
1151  */
1152 
1153