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