Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/sudoers/cvtsudoers_csv.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2021-2024 Todd C. Miller <[email protected]>
5
*
6
* Permission to use, copy, modify, and distribute this software for any
7
* purpose with or without fee is hereby granted, provided that the above
8
* copyright notice and this permission notice appear in all copies.
9
*
10
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
*/
18
19
#include <config.h>
20
21
#include <stdio.h>
22
#include <stdlib.h>
23
#include <string.h>
24
#ifdef HAVE_STRINGS_H
25
# include <strings.h>
26
#endif /* HAVE_STRINGS_H */
27
#include <unistd.h>
28
#include <stdarg.h>
29
30
#include <sudoers.h>
31
#include <cvtsudoers.h>
32
#include <gram.h>
33
34
static bool print_member_list_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree, struct member_list *members, bool negated, short alias_type, bool expand_aliases);
35
36
/*
37
* Print sudoOptions from a defaults_list.
38
*/
39
static bool
40
print_options_csv(FILE *fp, struct defaults_list *options, bool need_comma)
41
{
42
struct defaults *opt;
43
debug_decl(print_options_csv, SUDOERS_DEBUG_UTIL);
44
45
TAILQ_FOREACH(opt, options, entries) {
46
if (opt->val != NULL) {
47
/* There is no need to double quote values here. */
48
fprintf(fp, "%s%s%s%s", need_comma ? "," : "", opt->var,
49
opt->op == '+' ? "+=" : opt->op == '-' ? "-=" : "=", opt->val);
50
} else {
51
/* Boolean flag. */
52
fprintf(fp, "%s%s%s%s", need_comma ? "," : "", opt->var,
53
opt->op == false ? "!" : "", opt->var);
54
}
55
need_comma = true;
56
}
57
58
debug_return_bool(!ferror(fp));
59
}
60
61
/*
62
* Map a Defaults type to string.
63
*/
64
static const char *
65
defaults_type_to_string(int defaults_type)
66
{
67
switch (defaults_type) {
68
case DEFAULTS:
69
return "defaults";
70
case DEFAULTS_CMND:
71
return "defaults_command";
72
case DEFAULTS_HOST:
73
return "defaults_host";
74
case DEFAULTS_RUNAS:
75
return "defaults_runas";
76
case DEFAULTS_USER:
77
return "defaults_user";
78
default:
79
sudo_fatalx_nodebug("unexpected defaults type %d", defaults_type);
80
}
81
}
82
83
/*
84
* Map a Defaults type to an alias type.
85
*/
86
static short
87
defaults_to_alias_type(int defaults_type)
88
{
89
switch (defaults_type) {
90
case DEFAULTS_CMND:
91
return CMNDALIAS;
92
case DEFAULTS_HOST:
93
return HOSTALIAS;
94
case DEFAULTS_RUNAS:
95
return RUNASALIAS;
96
case DEFAULTS_USER:
97
return USERALIAS;
98
default:
99
sudo_fatalx_nodebug("unexpected defaults type %d", defaults_type);
100
}
101
}
102
103
/*
104
* Print a string, performing quoting as needed.
105
* If a field includes a comma it must be double-quoted.
106
* Double quotes are replaced by a pair of double-quotes.
107
* XXX - rewrite this
108
*/
109
static bool
110
print_csv_string(FILE * restrict fp, const char * restrict str, bool quoted)
111
{
112
const char *src = str;
113
char *dst, *newstr;
114
size_t len, newsize;
115
bool quote_it = false;
116
bool ret = true;
117
debug_decl(print_csv_string, SUDOERS_DEBUG_UTIL);
118
119
len = strcspn(str, quoted ? "\"" : "\",");
120
if (str[len] == '\0') {
121
/* nothing to escape */
122
debug_return_bool(fputs(str, fp) != EOF);
123
}
124
125
if (!quoted && strchr(str + len, ',') != NULL)
126
quote_it = true;
127
128
/* String includes characters we need to escape. */
129
newsize = len + 2 + (strlen(len + str) * 2) + 1;
130
if ((newstr = malloc(newsize)) == NULL)
131
debug_return_bool(false);
132
dst = newstr;
133
134
if (quote_it)
135
*dst++ = '"';
136
while (*src != '\0') {
137
if (*src == '"')
138
*dst++ = '"';
139
*dst++ = *src++;
140
}
141
if (quote_it)
142
*dst++ = '"';
143
*dst = '\0';
144
145
if (fputs(newstr, fp) == EOF)
146
ret = false;
147
free(newstr);
148
149
debug_return_bool(ret);
150
}
151
152
/*
153
* Format a sudo_command as a string.
154
* Returns the formatted, dynamically allocated string or dies on error.
155
*/
156
static char *
157
format_cmnd(struct sudo_command *c, bool negated)
158
{
159
struct command_digest *digest;
160
char *buf, *cp, *cmnd;
161
size_t bufsiz;
162
int len;
163
debug_decl(format_cmnd, SUDOERS_DEBUG_UTIL);
164
165
cmnd = c->cmnd ? c->cmnd : (char *)"ALL";
166
bufsiz = negated + strlen(cmnd) + 1;
167
if (c->args != NULL)
168
bufsiz += 1 + strlen(c->args);
169
TAILQ_FOREACH(digest, &c->digests, entries) {
170
bufsiz += strlen(digest_type_to_name(digest->digest_type)) + 1 +
171
strlen(digest->digest_str) + 1;
172
if (TAILQ_NEXT(digest, entries) != NULL)
173
bufsiz += 2;
174
}
175
176
if ((buf = malloc(bufsiz)) == NULL) {
177
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
178
debug_return_ptr(NULL);
179
}
180
181
cp = buf;
182
TAILQ_FOREACH(digest, &c->digests, entries) {
183
len = snprintf(cp, bufsiz - (size_t)(cp - buf), "%s:%s%s ",
184
digest_type_to_name(digest->digest_type), digest->digest_str,
185
TAILQ_NEXT(digest, entries) ? "," : "");
186
if (len < 0 || len >= (int)bufsiz - (cp - buf))
187
sudo_fatalx(U_("internal error, %s overflow"), __func__);
188
cp += len;
189
}
190
191
len = snprintf(cp, bufsiz - (size_t)(cp - buf), "%s%s%s%s",
192
negated ? "!" : "", cmnd, c->args ? " " : "", c->args ? c->args : "");
193
if (len < 0 || len >= (int)bufsiz - (cp - buf))
194
sudo_fatalx(U_("internal error, %s overflow"), __func__);
195
196
debug_return_str(buf);
197
}
198
199
/*
200
* Print struct member in CSV format as the specified attribute.
201
* See print_member_int() in parse.c.
202
*/
203
static bool
204
print_member_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
205
char *name, int type, bool negated, bool quoted, short alias_type,
206
bool expand_aliases)
207
{
208
struct alias *a;
209
char *str;
210
int len;
211
debug_decl(print_member_csv, SUDOERS_DEBUG_UTIL);
212
213
switch (type) {
214
case MYSELF:
215
/* Only valid for sudoRunasUser */
216
break;
217
case ALL:
218
if (name == NULL) {
219
fputs(negated ? "!ALL" : "ALL", fp);
220
break;
221
}
222
FALLTHROUGH;
223
case COMMAND:
224
str = format_cmnd((struct sudo_command *)name, negated);
225
if (str == NULL) {
226
debug_return_bool(false);
227
}
228
if (!print_csv_string(fp, str, quoted)) {
229
free(str);
230
debug_return_bool(false);
231
}
232
free(str);
233
break;
234
case ALIAS:
235
if (expand_aliases) {
236
if ((a = alias_get(parse_tree, name, alias_type)) != NULL) {
237
if (!print_member_list_csv(fp, parse_tree, &a->members, negated,
238
alias_type, expand_aliases)) {
239
alias_put(a);
240
debug_return_bool(false);
241
}
242
alias_put(a);
243
break;
244
}
245
}
246
FALLTHROUGH;
247
default:
248
len = asprintf(&str, "%s%s", negated ? "!" : "", name);
249
if (len == -1) {
250
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
251
debug_return_bool(false);
252
}
253
if (!print_csv_string(fp, str, quoted)) {
254
free(str);
255
debug_return_bool(false);
256
}
257
free(str);
258
break;
259
}
260
261
debug_return_bool(!ferror(fp));
262
}
263
264
/*
265
* Print list of struct member in CSV format as the specified attribute.
266
* See print_member_int() in parse.c.
267
*/
268
static bool
269
print_member_list_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
270
struct member_list *members, bool negated, short alias_type,
271
bool expand_aliases)
272
{
273
struct member *m, *next;
274
debug_decl(print_member_list_csv, SUDOERS_DEBUG_UTIL);
275
276
if (TAILQ_EMPTY(members))
277
debug_return_bool(true);
278
279
if (TAILQ_FIRST(members) != TAILQ_LAST(members, member_list))
280
putc('"', fp);
281
TAILQ_FOREACH_SAFE(m, members, entries, next) {
282
if (!print_member_csv(fp, parse_tree, m->name, m->type,
283
negated ? !m->negated : m->negated, true, alias_type,
284
expand_aliases)) {
285
debug_return_bool(false);
286
}
287
if (next != NULL)
288
putc(',', fp);
289
}
290
if (TAILQ_FIRST(members) != TAILQ_LAST(members, member_list))
291
putc('"', fp);
292
293
debug_return_bool(!ferror(fp));
294
}
295
296
/*
297
* Print the binding for a Defaults entry of the specified type.
298
*/
299
static bool
300
print_defaults_binding_csv(FILE *fp,
301
const struct sudoers_parse_tree *parse_tree,
302
struct defaults_binding *binding, int type, bool expand_aliases)
303
{
304
short alias_type;
305
debug_decl(print_defaults_binding_csv, SUDOERS_DEBUG_UTIL);
306
307
if (type != DEFAULTS) {
308
/* Print each member object in binding. */
309
alias_type = defaults_to_alias_type(type);
310
if (!print_member_list_csv(fp, parse_tree, &binding->members, false,
311
alias_type, expand_aliases)) {
312
debug_return_bool(false);
313
}
314
}
315
316
debug_return_bool(true);
317
}
318
319
/*
320
* Print all Defaults in CSV format:
321
*
322
* defaults,binding,name,operator,value
323
*
324
* where "operator" is one of +=, -=, or =
325
* and boolean flags use true/false for the value.
326
*/
327
static bool
328
print_defaults_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
329
bool expand_aliases)
330
{
331
struct defaults *def;
332
debug_decl(print_defaults_csv, SUDOERS_DEBUG_UTIL);
333
334
if (TAILQ_EMPTY(&parse_tree->defaults))
335
debug_return_bool(true);
336
337
/* Heading line. */
338
fputs("defaults_type,binding,name,operator,value\n", fp);
339
340
TAILQ_FOREACH(def, &parse_tree->defaults, entries) {
341
const char *operator;
342
343
/* Print operator */
344
switch (def->op) {
345
case '+':
346
operator = "+=";
347
break;
348
case '-':
349
operator = "-=";
350
break;
351
case true:
352
case false:
353
operator = "=";
354
break;
355
default:
356
sudo_warnx("internal error: unexpected defaults op %d", def->op);
357
continue;
358
}
359
360
/*
361
* For CSV we use a separate entry for each Defaults setting,
362
* even if they were on the same line in sudoers.
363
*/
364
fprintf(fp, "%s,", defaults_type_to_string(def->type));
365
366
/* Print binding (if any), which could be a list. */
367
print_defaults_binding_csv(fp, parse_tree, def->binding, def->type,
368
expand_aliases);
369
370
/* Print Defaults name + operator. */
371
fprintf(fp, ",%s,%s,", def->var, operator);
372
373
/* Print defaults value. */
374
/* XXX - differentiate between lists and single values? */
375
if (def->val == NULL) {
376
fputs(def->op == true ? "true" : "false", fp);
377
} else {
378
/* Does not handle lists specially. */
379
if (!print_csv_string(fp, def->val, false))
380
debug_return_bool(false);
381
}
382
putc('\n', fp);
383
}
384
putc('\n', fp);
385
fflush(fp);
386
387
debug_return_bool(!ferror(fp));
388
}
389
390
/*
391
* Callback for alias_apply() to print an alias entry.
392
*/
393
static int
394
print_alias_csv(struct sudoers_parse_tree *parse_tree, struct alias *a, void *v)
395
{
396
FILE *fp = v;
397
const char *title;
398
debug_decl(print_alias_csv, SUDOERS_DEBUG_UTIL);
399
400
title = alias_type_to_string(a->type);
401
if (title == NULL) {
402
sudo_warnx("unexpected alias type %d", a->type);
403
debug_return_int(-1);
404
}
405
406
fprintf(fp, "%s,%s,", title, a->name);
407
if (!print_member_list_csv(fp, parse_tree, &a->members, false, a->type,
408
false)) {
409
debug_return_int(-1);
410
}
411
putc('\n', fp);
412
debug_return_int(ferror(fp));
413
}
414
415
/*
416
* Print all aliases in CSV format:
417
*/
418
static bool
419
print_aliases_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree)
420
{
421
debug_decl(print_aliases_csv, SUDOERS_DEBUG_UTIL);
422
423
if (TAILQ_EMPTY(&parse_tree->defaults))
424
debug_return_bool(true);
425
426
/* Heading line. */
427
fputs("alias_type,alias_name,members\n", fp);
428
429
/* print_alias_csv() does not modify parse_tree. */
430
if (!alias_apply((struct sudoers_parse_tree *)parse_tree, print_alias_csv,
431
fp)) {
432
debug_return_bool(false);
433
}
434
putc('\n', fp);
435
436
debug_return_bool(!ferror(fp));
437
}
438
439
/*
440
* Print a Cmnd_Spec in CSV format.
441
*/
442
static bool
443
print_cmndspec_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
444
struct cmndspec *cs, struct cmndspec **nextp,
445
struct defaults_list *options, bool expand_aliases)
446
{
447
char timebuf[sizeof("20120727121554Z")];
448
struct cmndspec *next = *nextp;
449
bool need_comma = false;
450
struct member *m;
451
struct tm gmt;
452
bool last_one, quoted = false;
453
size_t len;
454
debug_decl(print_cmndspec_csv, SUDOERS_DEBUG_UTIL);
455
456
if (cs->runasuserlist != NULL) {
457
if (!print_member_list_csv(fp, parse_tree, cs->runasuserlist, false,
458
RUNASALIAS, expand_aliases)) {
459
debug_return_bool(false);
460
}
461
}
462
putc(',', fp);
463
464
if (cs->runasgrouplist != NULL) {
465
if (!print_member_list_csv(fp, parse_tree, cs->runasgrouplist, false,
466
RUNASALIAS, expand_aliases)) {
467
debug_return_bool(false);
468
}
469
}
470
putc(',', fp);
471
472
/* We don't know how many options there will be so always quote it. */
473
putc('"', fp);
474
if (cs->notbefore != UNSPEC) {
475
if (gmtime_r(&cs->notbefore, &gmt) == NULL) {
476
sudo_warn("%s", U_("unable to get GMT time"));
477
} else {
478
timebuf[sizeof(timebuf) - 1] = '\0';
479
len = strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", &gmt);
480
if (len == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
481
sudo_warnx("%s", U_("unable to format timestamp"));
482
} else {
483
fprintf(fp, "%snotbefore=%s", need_comma ? "," : "", timebuf); // -V547
484
need_comma = true;
485
}
486
}
487
}
488
if (cs->notafter != UNSPEC) {
489
if (gmtime_r(&cs->notafter, &gmt) == NULL) {
490
sudo_warn("%s", U_("unable to get GMT time"));
491
} else {
492
timebuf[sizeof(timebuf) - 1] = '\0';
493
len = strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", &gmt);
494
if (len == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
495
sudo_warnx("%s", U_("unable to format timestamp"));
496
} else {
497
fprintf(fp, "%snotafter=%s", need_comma ? "," : "", timebuf);
498
need_comma = true;
499
}
500
}
501
}
502
503
if (cs->timeout > 0) {
504
fprintf(fp, "%scommand_timeout=%d", need_comma ? "," : "", cs->timeout);
505
need_comma = true;
506
}
507
508
/* Print tags as options */
509
if (TAGS_SET(cs->tags)) {
510
struct cmndtag tag = cs->tags;
511
512
if (tag.nopasswd != UNSPEC) {
513
fprintf(fp, "%s%s", need_comma ? "," : "",
514
tag.nopasswd ? "!authenticate" : "authenticate");
515
need_comma = true;
516
}
517
if (tag.noexec != UNSPEC) {
518
fprintf(fp, "%s%s", need_comma ? "," : "",
519
tag.noexec ? "noexec" : "!noexec");
520
need_comma = true;
521
}
522
if (tag.intercept != UNSPEC) {
523
fprintf(fp, "%s%s", need_comma ? "," : "",
524
tag.intercept ? "intercept" : "!intercept");
525
need_comma = true;
526
}
527
if (tag.send_mail != UNSPEC) {
528
if (tag.send_mail) {
529
fprintf(fp, "%smail_all_cmnds", need_comma ? "," : "");
530
} else {
531
fprintf(fp, "%s!mail_all_cmnds,!mail_always,!mail_no_perms",
532
need_comma ? "," : "");
533
}
534
need_comma = true;
535
}
536
if (tag.setenv != UNSPEC && tag.setenv != IMPLIED) {
537
fprintf(fp, "%s%s", need_comma ? "," : "",
538
tag.setenv ? "setenv" : "!setenv");
539
need_comma = true;
540
}
541
if (tag.follow != UNSPEC) {
542
fprintf(fp, "%s%s", need_comma ? "," : "",
543
tag.follow ? "sudoedit_follow" : "!sudoedit_follow");
544
need_comma = true;
545
}
546
if (tag.log_input != UNSPEC) {
547
fprintf(fp, "%s%s", need_comma ? "," : "",
548
tag.follow ? "log_input" : "!log_input");
549
need_comma = true;
550
}
551
if (tag.log_output != UNSPEC) {
552
fprintf(fp, "%s%s", need_comma ? "," : "",
553
tag.follow ? "log_output" : "!log_output");
554
need_comma = true;
555
}
556
}
557
if (!print_options_csv(fp, options, need_comma))
558
debug_return_bool(false);
559
if (!TAILQ_EMPTY(options))
560
need_comma = true;
561
562
/* Print runchroot and runcwd. */
563
if (cs->runchroot != NULL) {
564
fprintf(fp, "%srunchroot=%s", need_comma ? "," : "", cs->runchroot);
565
need_comma = true;
566
}
567
if (cs->runcwd != NULL) {
568
fprintf(fp, "%sruncwd=%s", need_comma ? "," : "", cs->runcwd);
569
need_comma = true;
570
}
571
572
/* Print SELinux role/type */
573
if (cs->role != NULL && cs->type != NULL) {
574
fprintf(fp, "%srole=%s,type=%s", need_comma ? "," : "",
575
cs->role, cs->type);
576
need_comma = true;
577
}
578
579
if (cs->apparmor_profile != NULL) {
580
fprintf(fp, "%sapparmor_profile=%s,", need_comma ? "," : "",
581
cs->apparmor_profile);
582
need_comma = true;
583
}
584
585
/* Print Solaris privs/limitprivs */
586
if (cs->privs != NULL || cs->limitprivs != NULL) {
587
if (cs->privs != NULL) {
588
fprintf(fp, "%sprivs=%s", need_comma ? "," : "", cs->privs);
589
need_comma = true;
590
}
591
if (cs->limitprivs != NULL) {
592
fprintf(fp, "%slimitprivs=%s", need_comma ? "," : "", cs->limitprivs);
593
need_comma = true;
594
}
595
}
596
#ifdef __clang_analyzer__
597
(void)&need_comma;
598
#endif
599
putc('"', fp);
600
putc(',', fp);
601
602
/*
603
* Merge adjacent commands with matching tags, runas, SELinux
604
* role/type, AppArmor profiles and Solaris priv settings.
605
*/
606
for (;;) {
607
/* Does the next entry differ only in the command itself? */
608
/* XXX - move into a function that returns bool */
609
/* XXX - TAG_SET does not account for implied SETENV */
610
last_one = next == NULL ||
611
RUNAS_CHANGED(cs, next) || TAGS_CHANGED(cs->tags, next->tags)
612
|| cs->privs != next->privs || cs->limitprivs != next->limitprivs
613
|| cs->role != next->role || cs->type != next->type
614
|| cs->apparmor_profile != next->apparmor_profile
615
|| cs->runchroot != next->runchroot || cs->runcwd != next->runcwd;
616
617
if (!quoted && !last_one) {
618
quoted = true;
619
putc('"', fp);
620
}
621
m = cs->cmnd;
622
if (!print_member_csv(fp, parse_tree, m->name, m->type, m->negated,
623
quoted, CMNDALIAS, expand_aliases)) {
624
debug_return_bool(false);
625
}
626
if (last_one)
627
break;
628
putc(',', fp);
629
cs = next;
630
next = TAILQ_NEXT(cs, entries);
631
}
632
if (quoted)
633
putc('"', fp);
634
635
*nextp = next;
636
637
debug_return_bool(!ferror(fp));
638
}
639
640
/*
641
* Print a single User_Spec.
642
*/
643
static bool
644
print_userspec_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
645
struct userspec *us, bool expand_aliases)
646
{
647
struct privilege *priv;
648
struct cmndspec *cs, *next;
649
debug_decl(print_userspec_csv, SUDOERS_DEBUG_UTIL);
650
651
/*
652
* Each userspec struct may contain multiple privileges for the user.
653
*/
654
TAILQ_FOREACH(priv, &us->privileges, entries) {
655
TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) {
656
fputs("rule,", fp);
657
if (!print_member_list_csv(fp, parse_tree, &us->users, false,
658
USERALIAS, expand_aliases)) {
659
debug_return_bool(false);
660
}
661
putc(',', fp);
662
663
if (!print_member_list_csv(fp, parse_tree, &priv->hostlist, false,
664
HOSTALIAS, expand_aliases)) {
665
debug_return_bool(false);
666
}
667
putc(',', fp);
668
669
if (!print_cmndspec_csv(fp, parse_tree, cs, &next, &priv->defaults,
670
expand_aliases)) {
671
debug_return_bool(false);
672
}
673
putc('\n', fp);
674
}
675
}
676
677
debug_return_bool(!ferror(fp));
678
}
679
680
/*
681
* Print User_Specs.
682
*/
683
static bool
684
print_userspecs_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
685
bool expand_aliases)
686
{
687
struct userspec *us;
688
debug_decl(print_userspecs_csv, SUDOERS_DEBUG_UTIL);
689
690
if (TAILQ_EMPTY(&parse_tree->userspecs))
691
debug_return_bool(true);
692
693
/* Heading line. */
694
if (fputs("rule,user,host,runusers,rungroups,options,command\n", fp) == EOF)
695
debug_return_bool(false);
696
697
TAILQ_FOREACH(us, &parse_tree->userspecs, entries) {
698
if (!print_userspec_csv(fp, parse_tree, us, expand_aliases))
699
debug_return_bool(false);
700
}
701
debug_return_bool(true);
702
}
703
704
/*
705
* Export the parsed sudoers file in CSV format.
706
*/
707
bool
708
convert_sudoers_csv(const struct sudoers_parse_tree *parse_tree,
709
const char *output_file, struct cvtsudoers_config *conf)
710
{
711
bool ret = true;
712
FILE *output_fp = stdout;
713
debug_decl(convert_sudoers_csv, SUDOERS_DEBUG_UTIL);
714
715
if (output_file != NULL && strcmp(output_file, "-") != 0) {
716
if ((output_fp = fopen(output_file, "w")) == NULL) {
717
sudo_warn(U_("unable to open %s"), output_file);
718
debug_return_bool(false);
719
}
720
}
721
722
/* Dump Defaults in CSV format. */
723
if (!ISSET(conf->suppress, SUPPRESS_DEFAULTS)) {
724
if (!print_defaults_csv(output_fp, parse_tree, conf->expand_aliases)) {
725
goto cleanup;
726
}
727
}
728
729
/* Dump Aliases in CSV format. */
730
if (!conf->expand_aliases && !ISSET(conf->suppress, SUPPRESS_ALIASES)) {
731
if (!print_aliases_csv(output_fp, parse_tree)) {
732
goto cleanup;
733
}
734
}
735
736
/* Dump User_Specs in CSV format. */
737
if (!ISSET(conf->suppress, SUPPRESS_PRIVS)) {
738
if (!print_userspecs_csv(output_fp, parse_tree, conf->expand_aliases)) {
739
goto cleanup;
740
}
741
}
742
743
cleanup:
744
(void)fflush(output_fp);
745
if (ferror(output_fp)) {
746
sudo_warn("%s", output_file);
747
ret = false;
748
}
749
if (output_fp != stdout)
750
fclose(output_fp);
751
752
debug_return_bool(ret);
753
}
754
755