Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/util/profile/prof_parse.c
34889 views
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
33
static char *skip_over_blanks(char *cp)
34
{
35
while (*cp && isspace((int) (*cp)))
36
cp++;
37
return cp;
38
}
39
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
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
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. */
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. */
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
*/
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
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
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
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
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
*/
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
*/
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! */
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
585
static void dump_profile_to_file_cb(const char *str, void *data)
586
{
587
fputs(str, data);
588
}
589
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
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
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
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
648