Coverage Report

Created: 2026-05-26 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/krb5/src/util/profile/prof_parse.c
Line
Count
Source
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
2.49k
#define STATE_INIT_COMMENT      1
19
24.4k
#define STATE_STD_LINE          2
20
1.20k
#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
56.1k
{
35
57.5k
    while (*cp && isspace((int) (*cp)))
36
1.45k
        cp++;
37
56.1k
    return cp;
38
56.1k
}
39
40
static void strip_line(char *line)
41
22.4k
{
42
22.4k
    char *p = line + strlen(line);
43
43.8k
    while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
44
21.4k
        *--p = 0;
45
22.4k
}
46
47
static void parse_quoted_string(char *str)
48
1.69k
{
49
1.69k
    char *to, *from;
50
51
4.58k
    for (to = from = str; *from && *from != '"'; to++, from++) {
52
2.88k
        if (*from == '\\' && *(from + 1) != '\0') {
53
986
            from++;
54
986
            switch (*from) {
55
304
            case 'n':
56
304
                *to = '\n';
57
304
                break;
58
210
            case 't':
59
210
                *to = '\t';
60
210
                break;
61
194
            case 'b':
62
194
                *to = '\b';
63
194
                break;
64
278
            default:
65
278
                *to = *from;
66
986
            }
67
986
            continue;
68
986
        }
69
1.89k
        *to = *from;
70
1.89k
    }
71
1.69k
    *to = '\0';
72
1.69k
}
73
74
75
static errcode_t parse_std_line(char *line, struct parse_state *state)
76
22.9k
{
77
22.9k
    char    *cp, ch, *tag, *value;
78
22.9k
    char    *p;
79
22.9k
    errcode_t retval;
80
22.9k
    struct profile_node     *node;
81
22.9k
    int do_subsection = 0;
82
83
22.9k
    if (*line == 0)
84
199
        return 0;
85
22.8k
    cp = skip_over_blanks(line);
86
22.8k
    if (cp[0] == ';' || cp[0] == '#')
87
392
        return 0;
88
22.4k
    strip_line(cp);
89
22.4k
    ch = *cp;
90
22.4k
    if (ch == 0)
91
405
        return 0;
92
22.0k
    if (ch == '[') {
93
2.69k
        if (state->group_level > 1)
94
7
            return PROF_SECTION_NOTOP;
95
2.69k
        cp++;
96
2.69k
        p = strchr(cp, ']');
97
2.69k
        if (p == NULL)
98
34
            return PROF_SECTION_SYNTAX;
99
2.65k
        *p = '\0';
100
2.65k
        retval = profile_add_node(state->root_section, cp, NULL, 0,
101
2.65k
                                  &state->current_section);
102
2.65k
        if (retval)
103
0
            return retval;
104
2.65k
        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
2.65k
        state->discard = profile_is_node_final(state->current_section) ?
108
2.44k
            1 : 0;
109
110
        /*
111
         * Finish off the rest of the line.
112
         */
113
2.65k
        cp = p+1;
114
2.65k
        if (*cp == '*') {
115
228
            profile_make_node_final(state->current_section);
116
228
            cp++;
117
228
        }
118
        /*
119
         * A space after ']' should not be fatal
120
         */
121
2.65k
        cp = skip_over_blanks(cp);
122
2.65k
        if (*cp)
123
17
            return PROF_SECTION_SYNTAX;
124
2.64k
        return 0;
125
2.65k
    }
126
19.3k
    if (ch == '}') {
127
1.49k
        if (state->group_level < 2)
128
3
            return PROF_EXTRA_CBRACE;
129
1.49k
        if (*(cp+1) == '*')
130
448
            profile_make_node_final(state->current_section);
131
1.49k
        state->group_level--;
132
        /* Check if we are done discarding values from a subsection. */
133
1.49k
        if (state->group_level < state->discard)
134
698
            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
1.49k
        if (!state->discard) {
138
1.22k
            retval = profile_get_node_parent(state->current_section,
139
1.22k
                                             &state->current_section);
140
1.22k
            if (retval)
141
0
                return retval;
142
1.22k
        }
143
1.49k
        return 0;
144
1.49k
    }
145
    /*
146
     * Parse the relations
147
     */
148
17.8k
    tag = cp;
149
17.8k
    cp = strchr(cp, '=');
150
17.8k
    if (!cp)
151
36
        return PROF_RELATION_SYNTAX;
152
17.7k
    if (cp == tag)
153
1
        return PROF_RELATION_SYNTAX;
154
17.7k
    *cp = '\0';
155
17.7k
    p = tag;
156
    /* Look for whitespace on left-hand side.  */
157
36.1k
    while (p < cp && !isspace((int)*p))
158
18.3k
        p++;
159
17.7k
    if (p < cp) {
160
        /* Found some sort of whitespace.  */
161
260
        *p++ = 0;
162
        /* If we have more non-whitespace, it's an error.  */
163
454
        while (p < cp) {
164
195
            if (!isspace((int)*p))
165
1
                return PROF_RELATION_SYNTAX;
166
194
            p++;
167
194
        }
168
260
    }
169
17.7k
    cp = skip_over_blanks(cp+1);
170
17.7k
    value = cp;
171
17.7k
    if (value[0] == '"') {
172
1.69k
        value++;
173
1.69k
        parse_quoted_string(value);
174
16.0k
    } else if (value[0] == 0) {
175
718
        do_subsection++;
176
718
        state->state = STATE_GET_OBRACE;
177
15.3k
    } else if (value[0] == '{' && *(skip_over_blanks(value+1)) == 0)
178
12.1k
        do_subsection++;
179
3.16k
    else {
180
3.16k
        cp = value + strlen(value) - 1;
181
3.36k
        while ((cp > value) && isspace((int) (*cp)))
182
194
            *cp-- = 0;
183
3.16k
    }
184
17.7k
    if (do_subsection) {
185
12.9k
        p = strchr(tag, '*');
186
12.9k
        if (p)
187
5.69k
            *p = '\0';
188
12.9k
        state->group_level++;
189
12.9k
        if (!state->discard) {
190
12.3k
            retval = profile_add_node(state->current_section, tag, NULL, 0,
191
12.3k
                                      &state->current_section);
192
12.3k
            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
12.3k
            if (profile_is_node_final(state->current_section))
197
733
                state->discard = state->group_level;
198
12.3k
            if (p)
199
5.66k
                profile_make_node_final(state->current_section);
200
12.3k
        }
201
12.9k
        return 0;
202
12.9k
    }
203
4.86k
    p = strchr(tag, '*');
204
4.86k
    if (p)
205
729
        *p = '\0';
206
4.86k
    if (!state->discard) {
207
4.61k
        profile_add_node(state->current_section, tag, value, 1, &node);
208
4.61k
        if (p && node)
209
320
            profile_make_node_final(node);
210
4.61k
    }
211
4.86k
    return 0;
212
17.7k
}
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
23.9k
{
293
23.9k
    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
23.9k
    switch (state->state) {
308
1.44k
    case STATE_INIT_COMMENT:
309
1.44k
        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
2
            if (ret_modspec) {
318
0
                cp = skip_over_blanks(line + 6);
319
0
                strip_line(cp);
320
0
                *ret_modspec = strdup(cp);
321
0
                if (!*ret_modspec)
322
0
                    return ENOMEM;
323
0
            }
324
2
            return PROF_MODULE;
325
2
        }
326
1.44k
        if (line[0] != '[')
327
474
            return 0;
328
971
        state->state = STATE_STD_LINE;
329
22.9k
    case STATE_STD_LINE:
330
22.9k
        return parse_std_line(line, state);
331
484
    case STATE_GET_OBRACE:
332
484
        cp = skip_over_blanks(line);
333
484
        if (*cp != '{')
334
30
            return PROF_MISSING_OBRACE;
335
454
        state->state = STATE_STD_LINE;
336
23.9k
    }
337
454
    return 0;
338
23.9k
}
339
340
static errcode_t parse_file(FILE *f, struct parse_state *state,
341
                            char **ret_modspec)
342
1.04k
{
343
25.0k
#define BUF_SIZE        2048
344
1.04k
    char *bptr;
345
1.04k
    errcode_t retval;
346
347
1.04k
    bptr = malloc (BUF_SIZE);
348
1.04k
    if (!bptr)
349
0
        return ENOMEM;
350
351
24.8k
    while (!feof(f)) {
352
23.9k
        if (fgets(bptr, BUF_SIZE, f) == NULL)
353
11
            break;
354
23.9k
#ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
355
23.9k
        retval = parse_line(bptr, state, ret_modspec);
356
23.9k
        if (retval) {
357
131
            free (bptr);
358
131
            return retval;
359
131
        }
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
23.9k
    }
410
411
913
    free (bptr);
412
913
    return 0;
413
1.04k
}
414
415
errcode_t profile_parse_file(FILE *f, struct profile_node **root,
416
                             char **ret_modspec)
417
1.04k
{
418
1.04k
    struct parse_state state;
419
1.04k
    errcode_t retval;
420
421
1.04k
    *root = NULL;
422
423
    /* Initialize parsing state with a new root node. */
424
1.04k
    state.state = STATE_INIT_COMMENT;
425
1.04k
    state.group_level = state.discard = 0;
426
1.04k
    state.current_section = NULL;
427
1.04k
    retval = profile_create_node("(root)", 0, &state.root_section);
428
1.04k
    if (retval)
429
0
        return retval;
430
431
1.04k
    retval = parse_file(f, &state, ret_modspec);
432
1.04k
    if (retval) {
433
131
        profile_free_node(state.root_section);
434
131
        return retval;
435
131
    }
436
913
    *root = state.root_section;
437
913
    return 0;
438
1.04k
}
439
440
errcode_t profile_process_directory(const char *dirname,
441
                                    struct profile_node **root)
442
0
{
443
0
    errcode_t retval;
444
0
    struct profile_node *node;
445
446
0
    *root = NULL;
447
0
    retval = profile_create_node("(root)", 0, &node);
448
0
    if (retval)
449
0
        return retval;
450
0
    retval = parse_include_dir(dirname, node);
451
0
    if (retval) {
452
0
        profile_free_node(node);
453
0
        return retval;
454
0
    }
455
0
    *root = node;
456
0
    return 0;
457
0
}
458
459
/*
460
 * Return TRUE if the string begins or ends with whitespace
461
 */
462
static int need_double_quotes(char *str)
463
4.21k
{
464
4.21k
    if (!str)
465
0
        return 0;
466
4.21k
    if (str[0] == '\0')
467
774
        return 1;
468
3.44k
    if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))
469
468
        return 1;
470
2.97k
    if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b'))
471
1.24k
        return 1;
472
1.72k
    return 0;
473
2.97k
}
474
475
/*
476
 * Output a string with double quotes, doing appropriate backquoting
477
 * of characters as necessary.
478
 */
479
static void output_quoted_string(char *str, void (*cb)(const char *,void *),
480
                                 void *data)
481
2.48k
{
482
2.48k
    char    ch;
483
2.48k
    char buf[2];
484
485
2.48k
    cb("\"", data);
486
2.48k
    if (!str) {
487
0
        cb("\"", data);
488
0
        return;
489
0
    }
490
2.48k
    buf[1] = 0;
491
6.78k
    while ((ch = *str++)) {
492
4.29k
        switch (ch) {
493
336
        case '\\':
494
336
            cb("\\\\", data);
495
336
            break;
496
292
        case '\n':
497
292
            cb("\\n", data);
498
292
            break;
499
815
        case '\t':
500
815
            cb("\\t", data);
501
815
            break;
502
1.33k
        case '\b':
503
1.33k
            cb("\\b", data);
504
1.33k
            break;
505
1.52k
        default:
506
            /* This would be a lot faster if we scanned
507
               forward for the next "interesting"
508
               character.  */
509
1.52k
            buf[0] = ch;
510
1.52k
            cb(buf, data);
511
1.52k
            break;
512
4.29k
        }
513
4.29k
    }
514
2.48k
    cb("\"", data);
515
2.48k
}
516
517
518
519
#if defined(_WIN32)
520
#define EOL "\r\n"
521
#endif
522
523
#ifndef EOL
524
29.4k
#define EOL "\n"
525
#endif
526
527
/* Errors should be returned, not ignored!  */
528
static void dump_profile(struct profile_node *root, int level,
529
                         void (*cb)(const char *, void *), void *data)
530
13.5k
{
531
13.5k
    int i, final;
532
13.5k
    struct profile_node *p;
533
13.5k
    void *iter;
534
13.5k
    long retval;
535
13.5k
    char *name, *value;
536
537
13.5k
    iter = 0;
538
17.0k
    do {
539
17.0k
        retval = profile_find_node_relation(root, 0, &iter,
540
17.0k
                                            &name, &value, &final);
541
17.0k
        if (retval)
542
12.8k
            break;
543
25.5k
        for (i=0; i < level; i++)
544
21.3k
            cb("\t", data);
545
4.21k
        cb(name, data);
546
4.21k
        cb(final ? "*" : "", data);
547
4.21k
        cb(" = ", data);
548
4.21k
        if (need_double_quotes(value))
549
2.48k
            output_quoted_string(value, cb, data);
550
1.72k
        else
551
1.72k
            cb(value, data);
552
4.21k
        cb(EOL, data);
553
4.21k
    } while (iter != 0);
554
555
13.5k
    iter = 0;
556
14.7k
    do {
557
14.7k
        retval = profile_find_node_subsection(root, 0, &iter,
558
14.7k
                                              &name, &p);
559
14.7k
        if (retval)
560
2.11k
            break;
561
12.6k
        if (level == 0) { /* [xxx] */
562
1.83k
            cb("[", data);
563
1.83k
            cb(name, data);
564
1.83k
            cb("]", data);
565
1.83k
            cb(profile_is_node_final(p) ? "*" : "", data);
566
1.83k
            cb(EOL, data);
567
1.83k
            dump_profile(p, level+1, cb, data);
568
1.83k
            cb(EOL, data);
569
10.8k
        } else {        /* xxx = { ... } */
570
722k
            for (i=0; i < level; i++)
571
712k
                cb("\t", data);
572
10.8k
            cb(name, data);
573
10.8k
            cb(" = {", data);
574
10.8k
            cb(EOL, data);
575
10.8k
            dump_profile(p, level+1, cb, data);
576
722k
            for (i=0; i < level; i++)
577
712k
                cb("\t", data);
578
10.8k
            cb("}", data);
579
10.8k
            cb(profile_is_node_final(p) ? "*" : "", data);
580
10.8k
            cb(EOL, data);
581
10.8k
        }
582
12.6k
    } while (iter != 0);
583
13.5k
}
584
585
static void dump_profile_to_file_cb(const char *str, void *data)
586
0
{
587
0
    fputs(str, data);
588
0
}
589
590
errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)
591
0
{
592
0
    dump_profile(root, 0, dump_profile_to_file_cb, dstfile);
593
0
    return 0;
594
0
}
595
596
struct prof_buf {
597
    char *base;
598
    size_t cur, max;
599
    int err;
600
};
601
602
static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)
603
1.55M
{
604
1.55M
    if (b->err)
605
0
        return;
606
1.55M
    if (b->max - b->cur < len) {
607
1.27k
        size_t newsize;
608
1.27k
        char *newptr;
609
610
1.27k
        newsize = b->max + (b->max >> 1) + len + 1024;
611
1.27k
        newptr = realloc(b->base, newsize);
612
1.27k
        if (newptr == NULL) {
613
0
            b->err = 1;
614
0
            return;
615
0
        }
616
1.27k
        b->base = newptr;
617
1.27k
        b->max = newsize;
618
1.27k
    }
619
1.55M
    memcpy(b->base + b->cur, d, len);
620
1.55M
    b->cur += len;          /* ignore overflow */
621
1.55M
}
622
623
static void dump_profile_to_buffer_cb(const char *str, void *data)
624
1.54M
{
625
1.54M
    add_data_to_buffer((struct prof_buf *)data, str, strlen(str));
626
1.54M
}
627
628
errcode_t profile_write_tree_to_buffer(struct profile_node *root,
629
                                       char **buf)
630
913
{
631
913
    struct prof_buf prof_buf = { 0, 0, 0, 0 };
632
633
913
    dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);
634
913
    if (prof_buf.err) {
635
0
        *buf = NULL;
636
0
        return ENOMEM;
637
0
    }
638
913
    add_data_to_buffer(&prof_buf, "", 1); /* append nul */
639
913
    if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {
640
883
        char *newptr = realloc(prof_buf.base, prof_buf.cur);
641
883
        if (newptr)
642
883
            prof_buf.base = newptr;
643
883
    }
644
913
    *buf = prof_buf.base;
645
913
    return 0;
646
913
}