Coverage Report

Created: 2024-07-23 07:27

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