Coverage Report

Created: 2023-11-19 06:40

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