xref: /freebsd/crypto/krb5/src/util/profile/prof_parse.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "prof_int.h"
3 
4 #include <sys/types.h>
5 #include <stdio.h>
6 #include <string.h>
7 #ifdef HAVE_STDLIB_H
8 #include <stdlib.h>
9 #endif
10 #include <errno.h>
11 #include <ctype.h>
12 #ifndef _WIN32
13 #include <dirent.h>
14 #endif
15 
16 #define SECTION_SEP_CHAR '/'
17 
18 #define STATE_INIT_COMMENT      1
19 #define STATE_STD_LINE          2
20 #define STATE_GET_OBRACE        3
21 
22 struct parse_state {
23     int state;
24     int group_level;
25     int discard;                /* group_level of a final-flagged section */
26     struct profile_node *root_section;
27     struct profile_node *current_section;
28 };
29 
30 static errcode_t parse_file(FILE *f, struct parse_state *state,
31                             char **ret_modspec);
32 
skip_over_blanks(char * cp)33 static char *skip_over_blanks(char *cp)
34 {
35     while (*cp && isspace((int) (*cp)))
36         cp++;
37     return cp;
38 }
39 
strip_line(char * line)40 static void strip_line(char *line)
41 {
42     char *p = line + strlen(line);
43     while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
44         *--p = 0;
45 }
46 
parse_quoted_string(char * str)47 static void parse_quoted_string(char *str)
48 {
49     char *to, *from;
50 
51     for (to = from = str; *from && *from != '"'; to++, from++) {
52         if (*from == '\\' && *(from + 1) != '\0') {
53             from++;
54             switch (*from) {
55             case 'n':
56                 *to = '\n';
57                 break;
58             case 't':
59                 *to = '\t';
60                 break;
61             case 'b':
62                 *to = '\b';
63                 break;
64             default:
65                 *to = *from;
66             }
67             continue;
68         }
69         *to = *from;
70     }
71     *to = '\0';
72 }
73 
74 
parse_std_line(char * line,struct parse_state * state)75 static errcode_t parse_std_line(char *line, struct parse_state *state)
76 {
77     char    *cp, ch, *tag, *value;
78     char    *p;
79     errcode_t retval;
80     struct profile_node     *node;
81     int do_subsection = 0;
82 
83     if (*line == 0)
84         return 0;
85     cp = skip_over_blanks(line);
86     if (cp[0] == ';' || cp[0] == '#')
87         return 0;
88     strip_line(cp);
89     ch = *cp;
90     if (ch == 0)
91         return 0;
92     if (ch == '[') {
93         if (state->group_level > 1)
94             return PROF_SECTION_NOTOP;
95         cp++;
96         p = strchr(cp, ']');
97         if (p == NULL)
98             return PROF_SECTION_SYNTAX;
99         *p = '\0';
100         retval = profile_add_node(state->root_section, cp, NULL, 0,
101                                   &state->current_section);
102         if (retval)
103             return retval;
104         state->group_level = 1;
105         /* If we previously saw this section name with the final flag,
106          * discard values until the next top-level section. */
107         state->discard = profile_is_node_final(state->current_section) ?
108             1 : 0;
109 
110         /*
111          * Finish off the rest of the line.
112          */
113         cp = p+1;
114         if (*cp == '*') {
115             profile_make_node_final(state->current_section);
116             cp++;
117         }
118         /*
119          * A space after ']' should not be fatal
120          */
121         cp = skip_over_blanks(cp);
122         if (*cp)
123             return PROF_SECTION_SYNTAX;
124         return 0;
125     }
126     if (ch == '}') {
127         if (state->group_level < 2)
128             return PROF_EXTRA_CBRACE;
129         if (*(cp+1) == '*')
130             profile_make_node_final(state->current_section);
131         state->group_level--;
132         /* Check if we are done discarding values from a subsection. */
133         if (state->group_level < state->discard)
134             state->discard = 0;
135         /* Ascend to the current node's parent, unless the subsection we ended
136          * was discarded (in which case we never descended). */
137         if (!state->discard) {
138             retval = profile_get_node_parent(state->current_section,
139                                              &state->current_section);
140             if (retval)
141                 return retval;
142         }
143         return 0;
144     }
145     /*
146      * Parse the relations
147      */
148     tag = cp;
149     cp = strchr(cp, '=');
150     if (!cp)
151         return PROF_RELATION_SYNTAX;
152     if (cp == tag)
153         return PROF_RELATION_SYNTAX;
154     *cp = '\0';
155     p = tag;
156     /* Look for whitespace on left-hand side.  */
157     while (p < cp && !isspace((int)*p))
158         p++;
159     if (p < cp) {
160         /* Found some sort of whitespace.  */
161         *p++ = 0;
162         /* If we have more non-whitespace, it's an error.  */
163         while (p < cp) {
164             if (!isspace((int)*p))
165                 return PROF_RELATION_SYNTAX;
166             p++;
167         }
168     }
169     cp = skip_over_blanks(cp+1);
170     value = cp;
171     if (value[0] == '"') {
172         value++;
173         parse_quoted_string(value);
174     } else if (value[0] == 0) {
175         do_subsection++;
176         state->state = STATE_GET_OBRACE;
177     } else if (value[0] == '{' && *(skip_over_blanks(value+1)) == 0)
178         do_subsection++;
179     else {
180         cp = value + strlen(value) - 1;
181         while ((cp > value) && isspace((int) (*cp)))
182             *cp-- = 0;
183     }
184     if (do_subsection) {
185         p = strchr(tag, '*');
186         if (p)
187             *p = '\0';
188         state->group_level++;
189         if (!state->discard) {
190             retval = profile_add_node(state->current_section, tag, NULL, 0,
191                                       &state->current_section);
192             if (retval)
193                 return retval;
194             /* If we previously saw this subsection with the final flag,
195              * discard values until the subsection is done. */
196             if (profile_is_node_final(state->current_section))
197                 state->discard = state->group_level;
198             if (p)
199                 profile_make_node_final(state->current_section);
200         }
201         return 0;
202     }
203     p = strchr(tag, '*');
204     if (p)
205         *p = '\0';
206     if (!state->discard) {
207         profile_add_node(state->current_section, tag, value, 1, &node);
208         if (p && node)
209             profile_make_node_final(node);
210     }
211     return 0;
212 }
213 
214 /* Open and parse an included profile file. */
parse_include_file(const char * filename,struct profile_node * root_section)215 static errcode_t parse_include_file(const char *filename,
216                                     struct profile_node *root_section)
217 {
218     FILE    *fp;
219     errcode_t retval = 0;
220     struct parse_state state;
221 
222     /* Create a new state so that fragments are syntactically independent but
223      * share a root section. */
224     state.state = STATE_INIT_COMMENT;
225     state.group_level = state.discard = 0;
226     state.root_section = root_section;
227     state.current_section = NULL;
228 
229     fp = fopen(filename, "r");
230     if (fp == NULL)
231         return PROF_FAIL_INCLUDE_FILE;
232     retval = parse_file(fp, &state, NULL);
233     fclose(fp);
234     return retval;
235 }
236 
237 /* Return non-zero if filename contains only alphanumeric characters, dashes,
238  * and underscores, or if the filename ends in ".conf" and is not a dotfile. */
valid_name(const char * filename)239 static int valid_name(const char *filename)
240 {
241     const char *p;
242     size_t len = strlen(filename);
243 
244     /* Ignore dotfiles, which might be editor or filesystem artifacts. */
245     if (*filename == '.')
246         return 0;
247 
248     if (len >= 5 && !strcmp(filename + len - 5, ".conf"))
249         return 1;
250 
251     for (p = filename; *p != '\0'; p++) {
252         if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_')
253             return 0;
254     }
255     return 1;
256 }
257 
258 /*
259  * Include files within dirname.  Only files with names ending in ".conf", or
260  * consisting entirely of alphanumeric characters, dashes, and underscores are
261  * included.  This restriction avoids including editor backup files, .rpmsave
262  * files, and the like.  Files are processed in alphanumeric order.
263  */
parse_include_dir(const char * dirname,struct profile_node * root_section)264 static errcode_t parse_include_dir(const char *dirname,
265                                    struct profile_node *root_section)
266 {
267     errcode_t retval = 0;
268     char **fnames, *pathname;
269     int i;
270 
271     if (k5_dir_filenames(dirname, &fnames) != 0)
272         return PROF_FAIL_INCLUDE_DIR;
273 
274     for (i = 0; fnames != NULL && fnames[i] != NULL; i++) {
275         if (!valid_name(fnames[i]))
276             continue;
277         if (asprintf(&pathname, "%s/%s", dirname, fnames[i]) < 0) {
278             retval = ENOMEM;
279             break;
280         }
281         retval = parse_include_file(pathname, root_section);
282         free(pathname);
283         if (retval)
284             break;
285     }
286     k5_free_filenames(fnames);
287     return retval;
288 }
289 
parse_line(char * line,struct parse_state * state,char ** ret_modspec)290 static errcode_t parse_line(char *line, struct parse_state *state,
291                             char **ret_modspec)
292 {
293     char    *cp;
294 
295 #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
296     if (strncmp(line, "include", 7) == 0 && isspace(line[7])) {
297         cp = skip_over_blanks(line + 7);
298         strip_line(cp);
299         return parse_include_file(cp, state->root_section);
300     }
301     if (strncmp(line, "includedir", 10) == 0 && isspace(line[10])) {
302         cp = skip_over_blanks(line + 10);
303         strip_line(cp);
304         return parse_include_dir(cp, state->root_section);
305     }
306 #endif
307     switch (state->state) {
308     case STATE_INIT_COMMENT:
309         if (strncmp(line, "module", 6) == 0 && isspace(line[6])) {
310             /*
311              * If we are expecting a module declaration, fill in *ret_modspec
312              * and return PROF_MODULE, which will cause parsing to abort and
313              * the module to be loaded instead.  If we aren't expecting a
314              * module declaration, return PROF_MODULE without filling in
315              * *ret_modspec, which will be treated as an ordinary error.
316              */
317             if (ret_modspec) {
318                 cp = skip_over_blanks(line + 6);
319                 strip_line(cp);
320                 *ret_modspec = strdup(cp);
321                 if (!*ret_modspec)
322                     return ENOMEM;
323             }
324             return PROF_MODULE;
325         }
326         if (line[0] != '[')
327             return 0;
328         state->state = STATE_STD_LINE;
329     case STATE_STD_LINE:
330         return parse_std_line(line, state);
331     case STATE_GET_OBRACE:
332         cp = skip_over_blanks(line);
333         if (*cp != '{')
334             return PROF_MISSING_OBRACE;
335         state->state = STATE_STD_LINE;
336     }
337     return 0;
338 }
339 
parse_file(FILE * f,struct parse_state * state,char ** ret_modspec)340 static errcode_t parse_file(FILE *f, struct parse_state *state,
341                             char **ret_modspec)
342 {
343 #define BUF_SIZE        2048
344     char *bptr;
345     errcode_t retval;
346 
347     bptr = malloc (BUF_SIZE);
348     if (!bptr)
349         return ENOMEM;
350 
351     while (!feof(f)) {
352         if (fgets(bptr, BUF_SIZE, f) == NULL)
353             break;
354 #ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
355         retval = parse_line(bptr, state, ret_modspec);
356         if (retval) {
357             free (bptr);
358             return retval;
359         }
360 #else
361         {
362             char *p, *end;
363 
364             if (strlen(bptr) >= BUF_SIZE - 1) {
365                 /* The string may have foreign newlines and
366                    gotten chopped off on a non-newline
367                    boundary.  Seek backwards to the last known
368                    newline.  */
369                 long offset;
370                 char *c = bptr + strlen (bptr);
371                 for (offset = 0; offset > -BUF_SIZE; offset--) {
372                     if (*c == '\r' || *c == '\n') {
373                         *c = '\0';
374                         fseek (f, offset, SEEK_CUR);
375                         break;
376                     }
377                     c--;
378                 }
379             }
380 
381             /* First change all newlines to \n */
382             for (p = bptr; *p != '\0'; p++) {
383                 if (*p == '\r')
384                     *p = '\n';
385             }
386             /* Then parse all lines */
387             p = bptr;
388             end = bptr + strlen (bptr);
389             while (p < end) {
390                 char* newline;
391                 char* newp;
392 
393                 newline = strchr (p, '\n');
394                 if (newline != NULL)
395                     *newline = '\0';
396 
397                 /* parse_line modifies contents of p */
398                 newp = p + strlen (p) + 1;
399                 retval = parse_line (p, state, ret_modspec);
400                 if (retval) {
401                     free (bptr);
402                     return retval;
403                 }
404 
405                 p = newp;
406             }
407         }
408 #endif
409     }
410 
411     free (bptr);
412     return 0;
413 }
414 
profile_parse_file(FILE * f,struct profile_node ** root,char ** ret_modspec)415 errcode_t profile_parse_file(FILE *f, struct profile_node **root,
416                              char **ret_modspec)
417 {
418     struct parse_state state;
419     errcode_t retval;
420 
421     *root = NULL;
422 
423     /* Initialize parsing state with a new root node. */
424     state.state = STATE_INIT_COMMENT;
425     state.group_level = state.discard = 0;
426     state.current_section = NULL;
427     retval = profile_create_node("(root)", 0, &state.root_section);
428     if (retval)
429         return retval;
430 
431     retval = parse_file(f, &state, ret_modspec);
432     if (retval) {
433         profile_free_node(state.root_section);
434         return retval;
435     }
436     *root = state.root_section;
437     return 0;
438 }
439 
profile_process_directory(const char * dirname,struct profile_node ** root)440 errcode_t profile_process_directory(const char *dirname,
441                                     struct profile_node **root)
442 {
443     errcode_t retval;
444     struct profile_node *node;
445 
446     *root = NULL;
447     retval = profile_create_node("(root)", 0, &node);
448     if (retval)
449         return retval;
450     retval = parse_include_dir(dirname, node);
451     if (retval) {
452         profile_free_node(node);
453         return retval;
454     }
455     *root = node;
456     return 0;
457 }
458 
459 /*
460  * Return TRUE if the string begins or ends with whitespace
461  */
need_double_quotes(char * str)462 static int need_double_quotes(char *str)
463 {
464     if (!str)
465         return 0;
466     if (str[0] == '\0')
467         return 1;
468     if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))
469         return 1;
470     if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b'))
471         return 1;
472     return 0;
473 }
474 
475 /*
476  * Output a string with double quotes, doing appropriate backquoting
477  * of characters as necessary.
478  */
output_quoted_string(char * str,void (* cb)(const char *,void *),void * data)479 static void output_quoted_string(char *str, void (*cb)(const char *,void *),
480                                  void *data)
481 {
482     char    ch;
483     char buf[2];
484 
485     cb("\"", data);
486     if (!str) {
487         cb("\"", data);
488         return;
489     }
490     buf[1] = 0;
491     while ((ch = *str++)) {
492         switch (ch) {
493         case '\\':
494             cb("\\\\", data);
495             break;
496         case '\n':
497             cb("\\n", data);
498             break;
499         case '\t':
500             cb("\\t", data);
501             break;
502         case '\b':
503             cb("\\b", data);
504             break;
505         default:
506             /* This would be a lot faster if we scanned
507                forward for the next "interesting"
508                character.  */
509             buf[0] = ch;
510             cb(buf, data);
511             break;
512         }
513     }
514     cb("\"", data);
515 }
516 
517 
518 
519 #if defined(_WIN32)
520 #define EOL "\r\n"
521 #endif
522 
523 #ifndef EOL
524 #define EOL "\n"
525 #endif
526 
527 /* Errors should be returned, not ignored!  */
dump_profile(struct profile_node * root,int level,void (* cb)(const char *,void *),void * data)528 static void dump_profile(struct profile_node *root, int level,
529                          void (*cb)(const char *, void *), void *data)
530 {
531     int i, final;
532     struct profile_node *p;
533     void *iter;
534     long retval;
535     char *name, *value;
536 
537     iter = 0;
538     do {
539         retval = profile_find_node_relation(root, 0, &iter,
540                                             &name, &value, &final);
541         if (retval)
542             break;
543         for (i=0; i < level; i++)
544             cb("\t", data);
545         cb(name, data);
546         cb(final ? "*" : "", data);
547         cb(" = ", data);
548         if (need_double_quotes(value))
549             output_quoted_string(value, cb, data);
550         else
551             cb(value, data);
552         cb(EOL, data);
553     } while (iter != 0);
554 
555     iter = 0;
556     do {
557         retval = profile_find_node_subsection(root, 0, &iter,
558                                               &name, &p);
559         if (retval)
560             break;
561         if (level == 0) { /* [xxx] */
562             cb("[", data);
563             cb(name, data);
564             cb("]", data);
565             cb(profile_is_node_final(p) ? "*" : "", data);
566             cb(EOL, data);
567             dump_profile(p, level+1, cb, data);
568             cb(EOL, data);
569         } else {        /* xxx = { ... } */
570             for (i=0; i < level; i++)
571                 cb("\t", data);
572             cb(name, data);
573             cb(" = {", data);
574             cb(EOL, data);
575             dump_profile(p, level+1, cb, data);
576             for (i=0; i < level; i++)
577                 cb("\t", data);
578             cb("}", data);
579             cb(profile_is_node_final(p) ? "*" : "", data);
580             cb(EOL, data);
581         }
582     } while (iter != 0);
583 }
584 
dump_profile_to_file_cb(const char * str,void * data)585 static void dump_profile_to_file_cb(const char *str, void *data)
586 {
587     fputs(str, data);
588 }
589 
profile_write_tree_file(struct profile_node * root,FILE * dstfile)590 errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)
591 {
592     dump_profile(root, 0, dump_profile_to_file_cb, dstfile);
593     return 0;
594 }
595 
596 struct prof_buf {
597     char *base;
598     size_t cur, max;
599     int err;
600 };
601 
add_data_to_buffer(struct prof_buf * b,const void * d,size_t len)602 static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)
603 {
604     if (b->err)
605         return;
606     if (b->max - b->cur < len) {
607         size_t newsize;
608         char *newptr;
609 
610         newsize = b->max + (b->max >> 1) + len + 1024;
611         newptr = realloc(b->base, newsize);
612         if (newptr == NULL) {
613             b->err = 1;
614             return;
615         }
616         b->base = newptr;
617         b->max = newsize;
618     }
619     memcpy(b->base + b->cur, d, len);
620     b->cur += len;          /* ignore overflow */
621 }
622 
dump_profile_to_buffer_cb(const char * str,void * data)623 static void dump_profile_to_buffer_cb(const char *str, void *data)
624 {
625     add_data_to_buffer((struct prof_buf *)data, str, strlen(str));
626 }
627 
profile_write_tree_to_buffer(struct profile_node * root,char ** buf)628 errcode_t profile_write_tree_to_buffer(struct profile_node *root,
629                                        char **buf)
630 {
631     struct prof_buf prof_buf = { 0, 0, 0, 0 };
632 
633     dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);
634     if (prof_buf.err) {
635         *buf = NULL;
636         return ENOMEM;
637     }
638     add_data_to_buffer(&prof_buf, "", 1); /* append nul */
639     if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {
640         char *newptr = realloc(prof_buf.base, prof_buf.cur);
641         if (newptr)
642             prof_buf.base = newptr;
643     }
644     *buf = prof_buf.base;
645     return 0;
646 }
647