1 #include <ctype.h>
2 #include <errno.h>
3 #include <limits.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 
8 #include "simpleconf.h"
9 
10 #ifndef SC_MAX_ARG_LENGTH
11 # define SC_MAX_ARG_LENGTH 65536
12 #endif
13 #ifndef SC_MAX_RECURSION
14 # define SC_MAX_RECURSION  16
15 #endif
16 
17 typedef enum State_ {
18     STATE_UNDEFINED,
19     STATE_PROPNAME,
20     STATE_AFTERPROPNAME,
21     STATE_AFTERPROPNAME2,
22     STATE_AFTERPROPNAMESEP,
23     STATE_RCHAR,
24     STATE_MATCH_ALPHA,
25     STATE_MATCH_ALNUM,
26     STATE_MATCH_DIGITS,
27     STATE_MATCH_XDIGITS,
28     STATE_MATCH_NOSPACE,
29     STATE_MATCH_ANY,
30     STATE_MATCH_ANY_WITHINQUOTES,
31     STATE_MATCH_ANY_AFTERQUOTES,
32     STATE_MATCH_ANY_WITHOUTQUOTES,
33     STATE_MATCH_ANY_UNQUOTED,
34     STATE_MATCH_SPACES,
35     STATE_MATCH_BOOLEAN,
36 
37     STATE_TEMPLATE_UNDEFINED,
38     STATE_TEMPLATE_RCHAR,
39     STATE_TEMPLATE_SUBST_ESC
40 } State;
41 
42 typedef struct Match_ {
43     const char *str;
44     size_t      str_len;
45 } Match;
46 
47 typedef enum EntryResult_ {
48     ENTRYRESULT_UNDEFINED,
49     ENTRYRESULT_OK,
50     ENTRYRESULT_IGNORE,
51     ENTRYRESULT_PROPNOTFOUND,
52     ENTRYRESULT_MISMATCH,
53     ENTRYRESULT_SYNTAX,
54     ENTRYRESULT_INVALID_ENTRY,
55     ENTRYRESULT_INTERNAL,
56     ENTRYRESULT_E2BIG,
57     ENTRYRESULT_SPECIAL
58 } EntryResult;
59 
60 static const char *
skip_spaces(const char * str)61 skip_spaces(const char *str)
62 {
63     while (*str != 0 && isspace((unsigned char)*str)) {
64         str++;
65     }
66     return str;
67 }
68 
69 static int
prefix_match(const char ** str,const char * prefix)70 prefix_match(const char **str, const char *prefix)
71 {
72     size_t prefix_len = strlen(prefix);
73     size_t str_len    = strlen(*str);
74     size_t i;
75     int    x = 0;
76 
77     if (str_len < prefix_len) {
78         return 0;
79     }
80     for (i = 0; i < prefix_len; i++) {
81         x |= tolower((unsigned char)(*str)[i]) ^
82              tolower((unsigned char)prefix[i]);
83     }
84     if (x == 0) {
85         *str += prefix_len;
86         return 1;
87     }
88     return 0;
89 }
90 
91 static int
add_to_matches(Match * const matches,size_t * matches_len,const char * start,const char * end)92 add_to_matches(Match *const matches, size_t *matches_len,
93                const char *start, const char *end)
94 {
95     size_t len;
96 
97     start = skip_spaces(start);
98     len   = (size_t)(end - start);
99     while (len > 0 && isspace((unsigned char)start[len - 1])) {
100         len--;
101     }
102     if (len == 0) {
103         return -1;
104     }
105     matches[*matches_len].str     = start;
106     matches[*matches_len].str_len = len;
107     (*matches_len)++;
108 
109     return 0;
110 }
111 
112 static EntryResult
err_mismatch(const char ** err_p,const char * line_pnt,const char * line)113 err_mismatch(const char **err_p, const char *line_pnt, const char *line)
114 {
115     *err_p = *line_pnt != 0 ? line_pnt : line;
116 
117     return ENTRYRESULT_MISMATCH;
118 }
119 
120 static EntryResult
err_syntax(const char ** err_p,const char * line_pnt,const char * line)121 err_syntax(const char **err_p, const char *line_pnt, const char *line)
122 {
123     *err_p = *line_pnt != 0 ? line_pnt : line;
124 
125     return ENTRYRESULT_SYNTAX;
126 }
127 
128 static EntryResult
try_entry(const SimpleConfEntry * const entry,const char * line,char ** arg_p,const char ** err_p)129 try_entry(const SimpleConfEntry *const entry, const char *line,
130           char **arg_p, const char **err_p)
131 {
132     const char *in_pnt;
133     const char *line_pnt;
134     const char *match_start = NULL;
135     const char *out_pnt;
136     const char *prop_name      = NULL;
137     const char *wildcard_start = NULL;
138     char *      arg;
139     Match       matches[10];
140     size_t      arg_len;
141     size_t      matches_len;
142     size_t      wildcard_len;
143     State       state = STATE_UNDEFINED;
144     int         expect_char;
145     int         is_boolean;
146     int         is_enabled;
147     int         is_special;
148     int         c = 0;
149     int         d = 0;
150 
151     *err_p = NULL;
152     *arg_p = NULL;
153     line   = skip_spaces(line);
154     if (*line == 0 || *line == '#') {
155         return ENTRYRESULT_IGNORE;
156     }
157     in_pnt      = entry->in;
158     line_pnt    = line;
159     prop_name   = in_pnt;
160     matches_len = 0;
161     expect_char = 0;
162     is_boolean  = 0;
163     is_enabled  = 0;
164     is_special  = 0;
165     state       = STATE_PROPNAME;
166     while (*in_pnt != 0 || *line_pnt != 0) {
167         c = *(const unsigned char *)line_pnt;
168         d = *(const unsigned char *)in_pnt;
169         switch (state) {
170         case STATE_PROPNAME:
171             if (isspace(d)) {
172                 in_pnt++;
173                 state = STATE_AFTERPROPNAME;
174             } else if (d == '?' && is_boolean == 0) {
175                 is_boolean = 1;
176                 in_pnt++;
177             } else if (d == '!' && is_special == 0) {
178                 is_special = 1;
179                 in_pnt++;
180             } else if (c != 0 && d != 0 && tolower(c) == tolower(d)) {
181                 in_pnt++;
182                 line_pnt++;
183             } else {
184                 return ENTRYRESULT_PROPNOTFOUND;
185             }
186             continue;
187         case STATE_AFTERPROPNAME:
188             if (c == '=' || c == ':') {
189                 state = STATE_AFTERPROPNAMESEP;
190                 line_pnt++;
191             } else if (isspace(c)) {
192                 state = STATE_AFTERPROPNAME2;
193                 line_pnt++;
194             } else {
195                 return err_syntax(err_p, line_pnt, line);
196             }
197             continue;
198         case STATE_AFTERPROPNAME2:
199             if (c == '=' || c == ':') {
200                 state = STATE_AFTERPROPNAMESEP;
201                 line_pnt++;
202             } else if (isspace(c)) {
203                 line_pnt++;
204             } else {
205                 state = STATE_AFTERPROPNAMESEP;
206             }
207             continue;
208         case STATE_AFTERPROPNAMESEP:
209             if (c == '=' || c == ':') {
210                 return err_syntax(err_p, line_pnt, line);
211             } else if (isspace(c)) {
212                 line_pnt++;
213             } else {
214                 if (*in_pnt == 0 || in_pnt == prop_name) {
215                     return err_syntax(err_p, line_pnt, line);
216                 }
217                 wildcard_start = line_pnt;
218                 state = STATE_RCHAR;
219             }
220             continue;
221         case STATE_RCHAR:
222             if (d == 0) {
223                 return err_mismatch(err_p, line_pnt, line);
224             } else if (d == '(') {
225                 match_start = line_pnt;
226                 in_pnt++;
227             } else if (d == ')') {
228                 if (match_start == NULL ||
229                     matches_len >= (sizeof matches) / (sizeof matches[0]) ||
230                     add_to_matches(matches, &matches_len, match_start,
231                                    line_pnt) != 0) {
232                     return err_mismatch(err_p, line, line);
233                 }
234                 in_pnt++;
235             } else if (prefix_match(&in_pnt, "<alpha>")) {
236                 expect_char = 1;
237                 state = STATE_MATCH_ALPHA;
238             } else if (prefix_match(&in_pnt, "<alnum>")) {
239                 expect_char = 1;
240                 state = STATE_MATCH_ALNUM;
241             } else if (prefix_match(&in_pnt, "<digits>")) {
242                 expect_char = 1;
243                 state = STATE_MATCH_DIGITS;
244             } else if (prefix_match(&in_pnt, "<xdigits>")) {
245                 expect_char = 1;
246                 state = STATE_MATCH_XDIGITS;
247             } else if (prefix_match(&in_pnt, "<nospace>")) {
248                 expect_char = 1;
249                 state = STATE_MATCH_NOSPACE;
250             } else if (prefix_match(&in_pnt, "<any>")) {
251                 expect_char = 1;
252                 state = STATE_MATCH_ANY;
253             } else if (prefix_match(&in_pnt, "<any*>")) {
254                 expect_char = 1;
255                 state = STATE_MATCH_ANY_UNQUOTED;
256             } else if (prefix_match(&in_pnt, "<bool>")) {
257                 if (is_enabled) {
258                     return ENTRYRESULT_INVALID_ENTRY;
259                 }
260                 state = STATE_MATCH_BOOLEAN;
261             } else if (d == '<') {
262                 return ENTRYRESULT_INVALID_ENTRY;
263             } else if (isspace(d)) {
264                 in_pnt++;
265                 expect_char = 1;
266                 state = STATE_MATCH_SPACES;
267             } else if (isgraph(d)) {
268                 if (c == d) {
269                     in_pnt++;
270                     line_pnt++;
271                 } else {
272                     return err_mismatch(err_p, line_pnt, line);
273                 }
274             } else {
275                 return err_mismatch(err_p, line_pnt, line);
276             }
277             continue;
278         case STATE_MATCH_ALPHA:
279             if (isalpha(c)) {
280                 expect_char = 0;
281                 line_pnt++;
282             } else {
283                 state = STATE_RCHAR;
284             }
285             continue;
286         case STATE_MATCH_ALNUM:
287             if (isalnum(c)) {
288                 expect_char = 0;
289                 line_pnt++;
290             } else {
291                 state = STATE_RCHAR;
292             }
293             continue;
294         case STATE_MATCH_DIGITS:
295             if (isdigit(c)) {
296                 expect_char = 0;
297                 line_pnt++;
298             } else {
299                 state = STATE_RCHAR;
300             }
301             continue;
302         case STATE_MATCH_XDIGITS:
303             if (isxdigit(c)) {
304                 line_pnt++;
305             } else {
306                 state = STATE_RCHAR;
307             }
308             continue;
309         case STATE_MATCH_NOSPACE:
310             if (isgraph(c)) {
311                 expect_char = 0;
312                 line_pnt++;
313             } else {
314                 state = STATE_RCHAR;
315             }
316             continue;
317         case STATE_MATCH_ANY:
318             if (c == '"') {
319                 if (match_start == line_pnt) {
320                     match_start++;
321                 } else if (match_start != NULL) {
322                     return ENTRYRESULT_INVALID_ENTRY;
323                 }
324                 line_pnt++;
325                 state = STATE_MATCH_ANY_WITHINQUOTES;
326             } else if (isgraph(c)) {
327                 expect_char = 0;
328                 line_pnt++;
329                 state = STATE_MATCH_ANY_WITHOUTQUOTES;
330             } else {
331                 state = STATE_RCHAR;
332             }
333             continue;
334         case STATE_MATCH_ANY_WITHINQUOTES:
335             if (c == '"') {
336                 state = STATE_MATCH_ANY_AFTERQUOTES;
337             } else if (isprint(c)) {
338                 expect_char = 0;
339                 line_pnt++;
340             } else {
341                 return err_syntax(err_p, line_pnt, line);
342             }
343             continue;
344         case STATE_MATCH_ANY_AFTERQUOTES:
345             if (d == ')') {
346                 if (match_start == NULL ||
347                     matches_len >= (sizeof matches) / (sizeof matches[0]) ||
348                     add_to_matches(matches, &matches_len, match_start,
349                                    line_pnt) != 0) {
350                     return err_mismatch(err_p, line, line);
351                 }
352                 match_start = NULL;
353                 line_pnt++;
354                 in_pnt++;
355             }
356             state = STATE_RCHAR;
357             continue;
358         case STATE_MATCH_ANY_UNQUOTED:
359             if (isprint(c)) {
360                 expect_char = 0;
361                 line_pnt++;
362             } else {
363                 state = STATE_RCHAR;
364             }
365             continue;
366         case STATE_MATCH_ANY_WITHOUTQUOTES:
367             if (isgraph(c)) {
368                 expect_char = 0;
369                 line_pnt++;
370             } else {
371                 state = STATE_RCHAR;
372             }
373             continue;
374         case STATE_MATCH_SPACES:
375             if (isspace(c)) {
376                 expect_char = 0;
377                 line_pnt++;
378             } else {
379                 state = STATE_RCHAR;
380             }
381             continue;
382         case STATE_MATCH_BOOLEAN: {
383             if (prefix_match(&line_pnt, "yes")  || prefix_match(&line_pnt, "on") ||
384                 prefix_match(&line_pnt, "true") || prefix_match(&line_pnt, "1")) {
385                 is_enabled = 1;
386                 state      = STATE_RCHAR;
387             } else if (prefix_match(&line_pnt, "no")    || prefix_match(&line_pnt, "off") ||
388                        prefix_match(&line_pnt, "false") || prefix_match(&line_pnt, "0")) {
389                 is_enabled = 0;
390                 state      = STATE_RCHAR;
391             } else {
392                 return err_syntax(err_p, line_pnt, line);
393             }
394             continue;
395         }
396         default:
397             return ENTRYRESULT_INVALID_ENTRY;
398         }
399     }
400     switch (state) {
401     case STATE_RCHAR:
402     case STATE_MATCH_ALPHA:
403     case STATE_MATCH_ALNUM:
404     case STATE_MATCH_DIGITS:
405     case STATE_MATCH_XDIGITS:
406     case STATE_MATCH_NOSPACE:
407     case STATE_MATCH_ANY:
408     case STATE_MATCH_ANY_AFTERQUOTES:
409     case STATE_MATCH_ANY_WITHOUTQUOTES:
410     case STATE_MATCH_ANY_UNQUOTED:
411     case STATE_MATCH_SPACES:
412     case STATE_MATCH_BOOLEAN:
413         break;
414     default:
415         return err_mismatch(err_p, line_pnt, line);
416     }
417     if (expect_char != 0) {
418         return err_mismatch(err_p, line_pnt, line);
419     }
420     if (*in_pnt == ')') {
421         if (match_start == NULL ||
422             matches_len >= (sizeof matches) / (sizeof matches[0]) ||
423             add_to_matches(matches, &matches_len, match_start, line_pnt) != 0) {
424             return ENTRYRESULT_SYNTAX;
425         }
426         match_start = NULL;
427     }
428     if (is_boolean != 0 && is_enabled == 0) {
429         return ENTRYRESULT_IGNORE;
430     }
431     if (wildcard_start == NULL) {
432         wildcard_len = 0;
433     } else {
434         wildcard_len = (size_t) (line_pnt - wildcard_start);
435     }
436     out_pnt = entry->out;
437     if ((arg = malloc(SC_MAX_ARG_LENGTH + 1)) == NULL) {
438         return ENTRYRESULT_INTERNAL;
439     }
440     arg_len = 0;
441     state   = STATE_TEMPLATE_RCHAR;
442     while (arg_len < SC_MAX_ARG_LENGTH && *out_pnt != 0) {
443         d = *(const unsigned char *)out_pnt;
444         switch (state) {
445         case STATE_TEMPLATE_RCHAR:
446             if (d == '$') {
447                 out_pnt++;
448                 state = STATE_TEMPLATE_SUBST_ESC;
449             } else {
450                 arg[arg_len] = (char)d;
451                 arg_len++;
452                 out_pnt++;
453             }
454             continue;
455         case STATE_TEMPLATE_SUBST_ESC:
456             if (d == '*') {
457                 size_t i = 0;
458 
459                 while (arg_len < SC_MAX_ARG_LENGTH && i < wildcard_len) {
460                     arg[arg_len++] = wildcard_start[i++];
461                 }
462                 out_pnt++;
463                 state = STATE_TEMPLATE_RCHAR;
464             } else if (d >= '0' && d <= '9') {
465                 size_t match_id = (size_t)(d - '0');
466                 size_t i        = 0;
467 
468                 if (match_id >= matches_len) {
469                     return ENTRYRESULT_INVALID_ENTRY;
470                 }
471                 while (
472                     arg_len < SC_MAX_ARG_LENGTH && i < matches[match_id].str_len) {
473                     arg[arg_len++] = matches[match_id].str[i++];
474                 }
475                 out_pnt++;
476                 state = STATE_TEMPLATE_RCHAR;
477             } else {
478                 free(arg);
479                 return ENTRYRESULT_INVALID_ENTRY;
480             }
481             continue;
482         default:
483             abort();
484         }
485     }
486     if (arg_len >= SC_MAX_ARG_LENGTH) {
487         free(arg);
488         errno = E2BIG;
489         return ENTRYRESULT_E2BIG;
490     }
491     arg[arg_len] = 0;
492     *arg_p       = arg;
493 
494     if (is_special) {
495         return ENTRYRESULT_SPECIAL;
496     }
497     return ENTRYRESULT_OK;
498 }
499 
500 static char *
chomp(char * str)501 chomp(char *str)
502 {
503     size_t i = strlen(str);
504     int    c;
505 
506     if (i == 0) {
507         return str;
508     }
509     do {
510         i--;
511         c = (unsigned char) str[i];
512         if (isspace(c)) {
513             str[i] = 0;
514         } else {
515             break;
516         }
517     } while (i != 0);
518 
519     return str;
520 }
521 
522 void
sc_argv_free(int argc,char * argv[])523 sc_argv_free(int argc, char *argv[])
524 {
525     int i;
526 
527     for (i = 0; i < argc; i++) {
528         free(argv[i]);
529     }
530     free(argv);
531 }
532 
533 static int
append_to_command_line_from_file(const char * file_name,const SimpleConfConfig * config,const SimpleConfEntry entries[],size_t entries_count,int * argc_p,char *** argv_p,unsigned int depth)534 append_to_command_line_from_file(const char *file_name,
535                                  const SimpleConfConfig *config,
536                                  const SimpleConfEntry entries[],
537                                  size_t entries_count,
538                                  int *argc_p, char ***argv_p,
539                                  unsigned int depth)
540 {
541     char          line[SC_MAX_ARG_LENGTH];
542     FILE         *fp = NULL;
543     char         *arg;
544     char        **argv_tmp;
545     const char   *err = NULL;
546     const char   *err_tmp;
547     size_t        i;
548     unsigned int  line_count = 0;
549     int           try_next   = 1;
550 
551     if (depth >= SC_MAX_RECURSION) {
552         fprintf(stderr, "[%s]: too many levels of recursion\n", file_name);
553         return -1;
554     }
555     if ((fp = fopen(file_name, "r")) == NULL) {
556         fprintf(stderr, "Unable to open [%s]: %s\n", file_name, strerror(errno));
557         return -1;
558     }
559     while (fgets(line, (int)(sizeof line), fp) != NULL) {
560         chomp(line);
561         line_count++;
562         for (i = 0; i < entries_count; i++) {
563             try_next = 1;
564             switch (try_entry(&entries[i], line, &arg, &err_tmp)) {
565             case ENTRYRESULT_IGNORE:
566                 try_next = 0;
567                 break;
568             case ENTRYRESULT_PROPNOTFOUND:
569                 break;
570             case ENTRYRESULT_E2BIG:
571                 fclose(fp);
572                 return -1;
573             case ENTRYRESULT_INVALID_ENTRY:
574                 fprintf(stderr, "Bogus rule: [%s]\n", entries[i].in);
575                 abort();
576             case ENTRYRESULT_INTERNAL:
577                 fclose(fp);
578                 return -1;
579             case ENTRYRESULT_MISMATCH:
580                 err = err_tmp;
581                 continue;
582             case ENTRYRESULT_SYNTAX: {
583                 err = err_tmp;
584                 if (err != NULL && *err != 0) {
585                     fprintf(stderr, "%s:%u:%u: syntax error line %u: [%s].\n",
586                         file_name, line_count, (unsigned int)(err - line + 1),
587                         line_count, err);
588                 } else {
589                     fprintf(stderr, "%s:%u:1: syntax error line %u: [%s].\n",
590                         file_name, line_count, line_count, line);
591                 }
592                 fclose(fp);
593                 return -1;
594             }
595             case ENTRYRESULT_OK:
596                 try_next = 0;
597                 if (arg == NULL || *arg == 0) {
598                     free(arg);
599                     break;
600                 }
601                 if (*argc_p >= INT_MAX / (int)(sizeof *arg)) {
602                     abort();
603                 }
604                 if ((argv_tmp = realloc(*argv_p, (sizeof arg) *
605                                         ((size_t) *argc_p + 1))) == NULL) {
606                     free(arg);
607                     fclose(fp);
608                     return -1;
609                 }
610                 *argv_p = argv_tmp;
611                 (*argv_p)[(*argc_p)++] = arg;
612                 break;
613             case ENTRYRESULT_SPECIAL: {
614                 char                           *output = NULL;
615                 SimpleConfSpecialHandlerResult  special_result;
616 
617                 try_next = 0;
618                 if (config == NULL || config->special_handler == NULL) {
619                     fprintf(stderr, "Undefined handler for special keywords\n");
620                     abort();
621                 }
622                 special_result = config->special_handler((void **) &output, arg,
623                                                          config->user_data);
624                 if (special_result == SC_SPECIAL_HANDLER_RESULT_NEXT) {
625                     free(arg);
626                     break;
627                 } else if (special_result == SC_SPECIAL_HANDLER_RESULT_ERROR) {
628                     free(arg);
629                     fclose(fp);
630                     return -1;
631                 } else if (special_result == SC_SPECIAL_HANDLER_RESULT_INCLUDE) {
632                     const int ret = append_to_command_line_from_file
633                         ((const char *) output, config, entries, entries_count,
634                          argc_p, argv_p, depth + 1U);
635                     free(output);
636                     free(arg);
637                     if (ret != 0) {
638                         fclose(fp);
639                         return -1;
640                     }
641                     break;
642                 }
643                 abort();
644             }
645             default:
646                 abort();
647             }
648             if (try_next == 0) {
649                 break;
650             }
651         }
652         if (try_next != 0 && i >= entries_count) {
653             if (err != NULL && *err != 0) {
654                 fprintf(stderr, "%s:%u:%u: syntax error line %u: [%s].\n",
655                     file_name, line_count, (unsigned int)(err - line + 1),
656                     line_count, err);
657             } else {
658                 fprintf(stderr, "%s:%u:1: property not found line %u: [%s].\n",
659                     file_name, line_count, line_count, line);
660             }
661             fclose(fp);
662             return -1;
663         }
664     }
665     (void) fclose(fp);
666 
667     return 0;
668 }
669 
670 int
sc_build_command_line_from_file(const char * file_name,const SimpleConfConfig * config,const SimpleConfEntry entries[],size_t entries_count,char * app_name,int * argc_p,char *** argv_p)671 sc_build_command_line_from_file(const char *file_name,
672                                 const SimpleConfConfig *config,
673                                 const SimpleConfEntry entries[],
674                                 size_t entries_count, char *app_name,
675                                 int *argc_p, char ***argv_p)
676 {
677     char **argv = NULL;
678     int    argc = 0;
679 
680     *argc_p = 0;
681     *argv_p = NULL;
682     if ((argv = malloc(sizeof *argv)) == NULL ||
683         (app_name = strdup(app_name)) == NULL) {
684         sc_argv_free(argc, argv);
685         return -1;
686     }
687     argv[argc++] = app_name;
688     if (append_to_command_line_from_file(file_name, config,
689                                          entries, entries_count,
690                                          &argc, &argv, 0U) != 0) {
691         sc_argv_free(argc, argv);
692         return -1;
693     }
694     *argc_p = argc;
695     *argv_p = argv;
696 
697     return 0;
698 }
699