Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/sudoers/cvtsudoers_ldif.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2018-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 <sudo_ldap.h>
32
#include <redblack.h>
33
#include <cvtsudoers.h>
34
#include <sudo_lbuf.h>
35
#include <gram.h>
36
37
struct seen_user {
38
const char *name;
39
unsigned long count;
40
};
41
42
static struct rbtree *seen_users;
43
44
static bool printf_attribute_ldif(FILE *fp, const char *name, const char * restrict fmt, ...) sudo_printflike(3, 4);
45
46
static int
47
seen_user_compare(const void *aa, const void *bb)
48
{
49
const struct seen_user *a = aa;
50
const struct seen_user *b = bb;
51
52
return strcasecmp(a->name, b->name);
53
}
54
55
static void
56
seen_user_free(void *v)
57
{
58
struct seen_user *su = v;
59
60
free((void *)su->name);
61
free(su);
62
}
63
64
static bool
65
safe_string(const char *str)
66
{
67
const unsigned char *ustr = (const unsigned char *)str;
68
unsigned char ch = *ustr++;
69
debug_decl(safe_string, SUDOERS_DEBUG_UTIL);
70
71
/* Initial char must be <= 127 and not LF, CR, SPACE, ':', '<' */
72
switch (ch) {
73
case '\0':
74
debug_return_bool(true);
75
case '\n':
76
case '\r':
77
case ' ':
78
case ':':
79
case '<':
80
debug_return_bool(false);
81
default:
82
if (ch > 127)
83
debug_return_bool(false);
84
}
85
86
/* Any value <= 127 decimal except NUL, LF, and CR is safe */
87
while ((ch = *ustr++) != '\0') {
88
if (ch > 127 || ch == '\n' || ch == '\r')
89
debug_return_bool(false);
90
}
91
92
debug_return_bool(true);
93
}
94
95
static bool
96
print_attribute_ldif(FILE *fp, const char *name, const char *value)
97
{
98
const unsigned char *uvalue = (unsigned char *)value;
99
char *encoded = NULL;
100
size_t esize;
101
debug_decl(print_attribute_ldif, SUDOERS_DEBUG_UTIL);
102
103
if (!safe_string(value)) {
104
const size_t vlen = strlen(value);
105
esize = ((vlen + 2) / 3 * 4) + 1;
106
if ((encoded = malloc(esize)) == NULL) {
107
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
108
debug_return_bool(false);
109
}
110
if (sudo_base64_encode(uvalue, vlen, encoded, esize) == (size_t)-1) {
111
sudo_warnx(U_("unable to base64 encode value \"%s\""), value);
112
free(encoded);
113
debug_return_bool(false);
114
}
115
fprintf(fp, "%s:: %s\n", name, encoded);
116
free(encoded);
117
} else if (*value != '\0') {
118
fprintf(fp, "%s: %s\n", name, value);
119
} else {
120
fprintf(fp, "%s:\n", name);
121
}
122
123
debug_return_bool(!ferror(fp));
124
}
125
126
static bool
127
printf_attribute_ldif(FILE *fp, const char *name, const char * restrict fmt, ...)
128
{
129
char *attr_val;
130
va_list ap;
131
bool ret;
132
int len;
133
debug_decl(printf_attribute_ldif, SUDOERS_DEBUG_UTIL);
134
135
va_start(ap, fmt);
136
len = vasprintf(&attr_val, fmt, ap);
137
va_end(ap);
138
if (len == -1) {
139
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
140
debug_return_bool(false);
141
}
142
143
ret = print_attribute_ldif(fp, name, attr_val);
144
free(attr_val);
145
146
debug_return_bool(ret);
147
}
148
149
/*
150
* Print sudoOptions from a defaults_list.
151
*/
152
static bool
153
print_options_ldif(FILE *fp, const struct defaults_list *options)
154
{
155
struct defaults *opt;
156
bool ok;
157
debug_decl(print_options_ldif, SUDOERS_DEBUG_UTIL);
158
159
TAILQ_FOREACH(opt, options, entries) {
160
if (opt->type != DEFAULTS)
161
continue; /* don't support bound defaults */
162
163
if (opt->val != NULL) {
164
/* There is no need to double quote values here. */
165
ok = printf_attribute_ldif(fp, "sudoOption", "%s%s%s", opt->var,
166
opt->op == '+' ? "+=" : opt->op == '-' ? "-=" : "=", opt->val);
167
} else {
168
/* Boolean flag. */
169
ok = printf_attribute_ldif(fp, "sudoOption", "%s%s",
170
opt->op == false ? "!" : "", opt->var);
171
}
172
if (!ok)
173
debug_return_bool(false);
174
}
175
176
debug_return_bool(!ferror(fp));
177
}
178
179
/*
180
* Print global Defaults in a single sudoRole object.
181
*/
182
static bool
183
print_global_defaults_ldif(FILE *fp,
184
const struct sudoers_parse_tree *parse_tree, struct cvtsudoers_config *conf)
185
{
186
unsigned int count = 0;
187
struct sudo_lbuf lbuf;
188
struct defaults *opt;
189
bool ret = false;
190
debug_decl(print_global_defaults_ldif, SUDOERS_DEBUG_UTIL);
191
192
sudo_lbuf_init(&lbuf, NULL, 0, NULL, 80);
193
194
TAILQ_FOREACH(opt, &parse_tree->defaults, entries) {
195
/* Skip bound Defaults (unsupported). */
196
if (opt->type == DEFAULTS) {
197
count++;
198
} else {
199
lbuf.len = 0;
200
if (!sudo_lbuf_append(&lbuf, "# "))
201
goto done;
202
if (!sudoers_format_default_line(&lbuf, parse_tree, opt, NULL, true))
203
goto done;
204
fprintf(fp, "# Unable to translate %s:%d:%d:\n%s\n",
205
opt->file, opt->line, opt->column, lbuf.buf);
206
}
207
}
208
sudo_lbuf_destroy(&lbuf);
209
210
if (count == 0)
211
debug_return_bool(true);
212
213
if (!printf_attribute_ldif(fp, "dn", "cn=defaults,%s", conf->sudoers_base) ||
214
!print_attribute_ldif(fp, "objectClass", "top") ||
215
!print_attribute_ldif(fp, "objectClass", "sudoRole") ||
216
!print_attribute_ldif(fp, "cn", "defaults") ||
217
!print_attribute_ldif(fp, "description", "Default sudoOption's go here")) {
218
goto done;
219
}
220
if (!print_options_ldif(fp, &parse_tree->defaults))
221
goto done;
222
putc('\n', fp);
223
if (!ferror(fp))
224
ret = true;
225
226
done:
227
debug_return_bool(ret);
228
}
229
230
/*
231
* Format a sudo_command as a string.
232
* Returns the formatted, dynamically allocated string or dies on error.
233
*/
234
static char *
235
format_cmnd(struct sudo_command *c, bool negated)
236
{
237
struct command_digest *digest;
238
char *buf, *cp, *cmnd;
239
size_t bufsiz;
240
int len;
241
debug_decl(format_cmnd, SUDOERS_DEBUG_UTIL);
242
243
cmnd = c->cmnd ? c->cmnd : (char *)"ALL";
244
bufsiz = negated + strlen(cmnd) + 1;
245
if (c->args != NULL)
246
bufsiz += 1 + strlen(c->args);
247
TAILQ_FOREACH(digest, &c->digests, entries) {
248
bufsiz += strlen(digest_type_to_name(digest->digest_type)) + 1 +
249
strlen(digest->digest_str) + 1;
250
if (TAILQ_NEXT(digest, entries) != NULL)
251
bufsiz += 2;
252
}
253
254
if ((buf = malloc(bufsiz)) == NULL) {
255
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
256
debug_return_ptr(NULL);
257
}
258
259
cp = buf;
260
TAILQ_FOREACH(digest, &c->digests, entries) {
261
len = snprintf(cp, bufsiz - (size_t)(cp - buf), "%s:%s%s ",
262
digest_type_to_name(digest->digest_type), digest->digest_str,
263
TAILQ_NEXT(digest, entries) ? "," : "");
264
if (len < 0 || len >= (int)bufsiz - (cp - buf))
265
sudo_fatalx(U_("internal error, %s overflow"), __func__);
266
cp += len;
267
}
268
269
len = snprintf(cp, bufsiz - (size_t)(cp - buf), "%s%s%s%s",
270
negated ? "!" : "", cmnd, c->args ? " " : "", c->args ? c->args : "");
271
if (len < 0 || len >= (int)bufsiz - (cp - buf))
272
sudo_fatalx(U_("internal error, %s overflow"), __func__);
273
274
debug_return_str(buf);
275
}
276
277
/*
278
* Print struct member in LDIF format as the specified attribute.
279
* See print_member_int() in parse.c.
280
*/
281
static bool
282
print_member_ldif(FILE *fp, const struct sudoers_parse_tree *parse_tree,
283
char *name, int type, bool negated, short alias_type,
284
const char *attr_name)
285
{
286
struct alias *a;
287
struct member *m;
288
char *attr_val;
289
debug_decl(print_member_ldif, SUDOERS_DEBUG_UTIL);
290
291
switch (type) {
292
case MYSELF:
293
/* Only valid for sudoRunasUser */
294
if (!print_attribute_ldif(fp, attr_name, ""))
295
debug_return_bool(false);
296
break;
297
case ALL:
298
if (name == NULL) {
299
if (!print_attribute_ldif(fp, attr_name, negated ? "!ALL" : "ALL"))
300
debug_return_bool(false);
301
break;
302
}
303
FALLTHROUGH;
304
case COMMAND:
305
attr_val = format_cmnd((struct sudo_command *)name, negated);
306
if (attr_val == NULL) {
307
debug_return_bool(false);
308
}
309
if (!print_attribute_ldif(fp, attr_name, attr_val)) {
310
free(attr_val);
311
debug_return_bool(false);
312
}
313
free(attr_val);
314
break;
315
case ALIAS:
316
if ((a = alias_get(parse_tree, name, alias_type)) != NULL) {
317
TAILQ_FOREACH(m, &a->members, entries) {
318
if (!print_member_ldif(fp, parse_tree, m->name, m->type,
319
negated ? !m->negated : m->negated, alias_type,
320
attr_name)) {
321
debug_return_bool(false);
322
}
323
}
324
alias_put(a);
325
break;
326
}
327
FALLTHROUGH;
328
default:
329
if (!printf_attribute_ldif(fp, attr_name, "%s%s", negated ? "!" : "",
330
name)) {
331
debug_return_bool(false);
332
}
333
break;
334
}
335
336
debug_return_bool(true);
337
}
338
339
/*
340
* Print a Cmnd_Spec in LDIF format.
341
* A pointer to the next Cmnd_Spec is passed in to make it possible to
342
* merge adjacent entries that are identical in all but the command.
343
*/
344
static bool
345
print_cmndspec_ldif(FILE *fp, const struct sudoers_parse_tree *parse_tree,
346
struct cmndspec *cs, struct cmndspec **nextp, struct defaults_list *options)
347
{
348
char timebuf[sizeof("20120727121554Z")];
349
struct cmndspec *next = *nextp;
350
struct member *m;
351
struct tm gmt;
352
bool last_one;
353
size_t len;
354
debug_decl(print_cmndspec_ldif, SUDOERS_DEBUG_UTIL);
355
356
/* Print runasuserlist as sudoRunAsUser attributes */
357
if (cs->runasuserlist != NULL) {
358
TAILQ_FOREACH(m, cs->runasuserlist, entries) {
359
if (!print_member_ldif(fp, parse_tree, m->name, m->type, m->negated,
360
RUNASALIAS, "sudoRunAsUser")) {
361
debug_return_bool(false);
362
}
363
}
364
}
365
366
/* Print runasgrouplist as sudoRunAsGroup attributes */
367
if (cs->runasgrouplist != NULL) {
368
TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
369
if (!print_member_ldif(fp, parse_tree, m->name, m->type, m->negated,
370
RUNASALIAS, "sudoRunAsGroup")) {
371
debug_return_bool(false);
372
}
373
}
374
}
375
376
/* Print sudoNotBefore and sudoNotAfter attributes */
377
if (cs->notbefore != UNSPEC) {
378
if (gmtime_r(&cs->notbefore, &gmt) == NULL) {
379
sudo_warn("%s", U_("unable to get GMT time"));
380
} else {
381
timebuf[sizeof(timebuf) - 1] = '\0';
382
len = strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", &gmt);
383
if (len == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
384
sudo_warnx("%s", U_("unable to format timestamp"));
385
} else {
386
if (!print_attribute_ldif(fp, "sudoNotBefore", timebuf))
387
debug_return_bool(false);
388
}
389
}
390
}
391
if (cs->notafter != UNSPEC) {
392
if (gmtime_r(&cs->notafter, &gmt) == NULL) {
393
sudo_warn("%s", U_("unable to get GMT time"));
394
} else {
395
timebuf[sizeof(timebuf) - 1] = '\0';
396
len = strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", &gmt);
397
if (len == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
398
sudo_warnx("%s", U_("unable to format timestamp"));
399
} else {
400
if (!print_attribute_ldif(fp, "sudoNotAfter", timebuf))
401
debug_return_bool(false);
402
}
403
}
404
}
405
406
/* Print timeout as a sudoOption. */
407
if (cs->timeout > 0) {
408
if (!printf_attribute_ldif(fp, "sudoOption", "command_timeout=%d",
409
cs->timeout)) {
410
debug_return_bool(false);
411
}
412
}
413
414
/* Print tags as sudoOption attributes */
415
if (TAGS_SET(cs->tags)) {
416
struct cmndtag tag = cs->tags;
417
418
if (tag.nopasswd != UNSPEC) {
419
if (!print_attribute_ldif(fp, "sudoOption",
420
tag.nopasswd ? "!authenticate" : "authenticate")) {
421
debug_return_bool(false);
422
}
423
}
424
if (tag.noexec != UNSPEC) {
425
if (!print_attribute_ldif(fp, "sudoOption",
426
tag.noexec ? "noexec" : "!noexec")) {
427
debug_return_bool(false);
428
}
429
}
430
if (tag.intercept != UNSPEC) {
431
if (!print_attribute_ldif(fp, "sudoOption",
432
tag.intercept ? "intercept" : "!intercept")) {
433
debug_return_bool(false);
434
}
435
}
436
if (tag.send_mail != UNSPEC) {
437
if (tag.send_mail) {
438
if (!print_attribute_ldif(fp, "sudoOption", "mail_all_cmnds")) {
439
debug_return_bool(false);
440
}
441
} else {
442
if (!print_attribute_ldif(fp, "sudoOption", "!mail_all_cmnds") ||
443
!print_attribute_ldif(fp, "sudoOption", "!mail_always") ||
444
!print_attribute_ldif(fp, "sudoOption", "!mail_no_perms")) {
445
debug_return_bool(false);
446
}
447
}
448
}
449
if (tag.setenv != UNSPEC && tag.setenv != IMPLIED) {
450
if (!print_attribute_ldif(fp, "sudoOption",
451
tag.setenv ? "setenv" : "!setenv")) {
452
debug_return_bool(false);
453
}
454
}
455
if (tag.follow != UNSPEC) {
456
if (!print_attribute_ldif(fp, "sudoOption",
457
tag.follow ? "sudoedit_follow" : "!sudoedit_follow")) {
458
debug_return_bool(false);
459
}
460
}
461
if (tag.log_input != UNSPEC) {
462
if (!print_attribute_ldif(fp, "sudoOption",
463
tag.log_input ? "log_input" : "!log_input")) {
464
debug_return_bool(false);
465
}
466
}
467
if (tag.log_output != UNSPEC) {
468
if (!print_attribute_ldif(fp, "sudoOption",
469
tag.log_output ? "log_output" : "!log_output")) {
470
debug_return_bool(false);
471
}
472
}
473
}
474
if (!print_options_ldif(fp, options))
475
debug_return_bool(false);
476
477
/* Print runchroot and runcwd. */
478
if (cs->runchroot != NULL) {
479
if (!printf_attribute_ldif(fp, "sudoOption", "runchroot=%s",
480
cs->runchroot)) {
481
debug_return_bool(false);
482
}
483
}
484
if (cs->runcwd != NULL) {
485
if (!printf_attribute_ldif(fp, "sudoOption", "runcwd=%s", cs->runcwd)) {
486
debug_return_bool(false);
487
}
488
}
489
490
/* Print SELinux role/type */
491
if (cs->role != NULL && cs->type != NULL) {
492
if (!printf_attribute_ldif(fp, "sudoOption", "role=%s", cs->role) ||
493
!printf_attribute_ldif(fp, "sudoOption", "type=%s", cs->type)) {
494
debug_return_bool(false);
495
}
496
}
497
498
/* Print AppArmor profile */
499
if (cs->apparmor_profile != NULL) {
500
if (!printf_attribute_ldif(fp, "sudoOption", "apparmor_profile=%s",
501
cs->apparmor_profile)) {
502
debug_return_bool(false);
503
}
504
}
505
506
/* Print Solaris privs/limitprivs */
507
if (cs->privs != NULL || cs->limitprivs != NULL) {
508
if (cs->privs != NULL) {
509
if (!printf_attribute_ldif(fp, "sudoOption", "privs=%s",
510
cs->privs)) {
511
debug_return_bool(false);
512
}
513
}
514
if (cs->limitprivs != NULL) {
515
if (!printf_attribute_ldif(fp, "sudoOption", "limitprivs=%s",
516
cs->limitprivs)) {
517
debug_return_bool(false);
518
}
519
}
520
}
521
522
/*
523
* Merge adjacent commands with matching tags, runas, SELinux
524
* role/type and Solaris priv settings.
525
*/
526
for (;;) {
527
/* Does the next entry differ only in the command itself? */
528
/* XXX - move into a function that returns bool */
529
/* XXX - TAG_SET does not account for implied SETENV */
530
last_one = next == NULL ||
531
RUNAS_CHANGED(cs, next) || TAGS_CHANGED(cs->tags, next->tags)
532
|| cs->privs != next->privs || cs->limitprivs != next->limitprivs
533
|| cs->role != next->role || cs->type != next->type
534
|| cs->runchroot != next->runchroot || cs->runcwd != next->runcwd;
535
536
if (!print_member_ldif(fp, parse_tree, cs->cmnd->name, cs->cmnd->type,
537
cs->cmnd->negated, CMNDALIAS, "sudoCommand")) {
538
debug_return_bool(false);
539
}
540
if (last_one)
541
break;
542
cs = next;
543
next = TAILQ_NEXT(cs, entries);
544
}
545
546
*nextp = next;
547
548
debug_return_bool(true);
549
}
550
551
/*
552
* Convert user name to cn, avoiding duplicates and quoting as needed.
553
* See http://www.faqs.org/rfcs/rfc2253.html
554
*/
555
static char *
556
user_to_cn(const char *user)
557
{
558
struct seen_user key, *su = NULL;
559
struct rbnode *node;
560
const char *src;
561
char *cn, *dst;
562
size_t size;
563
debug_decl(user_to_cn, SUDOERS_DEBUG_UTIL);
564
565
/* Allocate as much as we could possibly need. */
566
size = (2 * strlen(user)) + 64 + 1;
567
if ((cn = malloc(size)) == NULL) {
568
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
569
goto bad;
570
}
571
572
/*
573
* Increment the number of times we have seen this user.
574
*/
575
key.name = user;
576
node = rbfind(seen_users, &key);
577
if (node != NULL) {
578
su = node->data;
579
} else {
580
if ((su = malloc(sizeof(*su))) == NULL) {
581
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
582
goto bad;
583
}
584
su->count = 0;
585
if ((su->name = strdup(user)) == NULL) {
586
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
587
goto bad;
588
}
589
if (rbinsert(seen_users, su, NULL) != 0) {
590
sudo_warnx(U_("internal error, unable insert user %s"), user);
591
goto bad;
592
}
593
}
594
595
/* Build cn, quoting special chars as needed (we allocated 2 x len). */
596
for (src = user, dst = cn; *src != '\0'; src++) {
597
switch (*src) {
598
case ',':
599
case '+':
600
case '"':
601
case '\\':
602
case '<':
603
case '>':
604
case '#':
605
case ';':
606
*dst++ = '\\'; /* always escape */
607
break;
608
case ' ':
609
if (src == user || src[1] == '\0')
610
*dst++ = '\\'; /* only escape at beginning or end of string */
611
break;
612
default:
613
break;
614
}
615
*dst++ = *src;
616
}
617
*dst = '\0';
618
619
/* Append count if there are duplicate users (cn must be unique). */
620
if (su->count != 0) {
621
size -= (size_t)(dst - cn);
622
if ((size_t)snprintf(dst, size, "_%lu", su->count) >= size) {
623
sudo_warnx(U_("internal error, %s overflow"), __func__);
624
goto bad;
625
}
626
}
627
su->count++;
628
629
debug_return_str(cn);
630
bad:
631
if (su != NULL && su->count == 0)
632
seen_user_free(su);
633
free(cn);
634
debug_return_str(NULL);
635
}
636
637
/*
638
* Print a single User_Spec.
639
*/
640
static bool
641
print_userspec_ldif(FILE *fp, const struct sudoers_parse_tree *parse_tree,
642
struct userspec *us, struct cvtsudoers_config *conf)
643
{
644
struct privilege *priv;
645
struct member *m;
646
struct cmndspec *cs, *next;
647
debug_decl(print_userspec_ldif, SUDOERS_DEBUG_UTIL);
648
649
/*
650
* Each userspec struct may contain multiple privileges for
651
* the user. We export each privilege as a separate sudoRole
652
* object for simplicity's sake.
653
*/
654
TAILQ_FOREACH(priv, &us->privileges, entries) {
655
TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) {
656
const char *base = conf->sudoers_base;
657
char *cn;
658
659
/*
660
* Increment the number of times we have seen this user.
661
* If more than one user is listed, just use the first one.
662
*/
663
m = TAILQ_FIRST(&us->users);
664
cn = user_to_cn(m->type == ALL ? "ALL" : m->name);
665
if (cn == NULL)
666
debug_return_bool(false);
667
668
if (!printf_attribute_ldif(fp, "dn", "cn=%s,%s", cn, base) ||
669
!print_attribute_ldif(fp, "objectClass", "top") ||
670
!print_attribute_ldif(fp, "objectClass", "sudoRole") ||
671
!print_attribute_ldif(fp, "cn", cn)) {
672
free(cn);
673
debug_return_bool(false);
674
}
675
free(cn);
676
677
TAILQ_FOREACH(m, &us->users, entries) {
678
if (!print_member_ldif(fp, parse_tree, m->name, m->type,
679
m->negated, USERALIAS, "sudoUser")) {
680
debug_return_bool(false);
681
}
682
}
683
684
TAILQ_FOREACH(m, &priv->hostlist, entries) {
685
if (!print_member_ldif(fp, parse_tree, m->name, m->type,
686
m->negated, HOSTALIAS, "sudoHost")) {
687
debug_return_bool(false);
688
}
689
}
690
691
if (!print_cmndspec_ldif(fp, parse_tree, cs, &next, &priv->defaults))
692
debug_return_bool(false);
693
694
if (conf->sudo_order != 0) {
695
char numbuf[STRLEN_MAX_UNSIGNED(conf->sudo_order) + 1];
696
if (conf->order_max != 0 && conf->sudo_order > conf->order_max) {
697
sudo_warnx(U_("too many sudoers entries, maximum %u"),
698
conf->order_padding);
699
debug_return_bool(false);
700
}
701
(void)snprintf(numbuf, sizeof(numbuf), "%u", conf->sudo_order);
702
if (!print_attribute_ldif(fp, "sudoOrder", numbuf))
703
debug_return_bool(false);
704
putc('\n', fp);
705
conf->sudo_order += conf->order_increment;
706
}
707
}
708
}
709
710
debug_return_bool(!ferror(fp));
711
}
712
713
/*
714
* Print User_Specs.
715
*/
716
static bool
717
print_userspecs_ldif(FILE *fp, const struct sudoers_parse_tree *parse_tree,
718
struct cvtsudoers_config *conf)
719
{
720
struct userspec *us;
721
debug_decl(print_userspecs_ldif, SUDOERS_DEBUG_UTIL);
722
723
TAILQ_FOREACH(us, &parse_tree->userspecs, entries) {
724
if (!print_userspec_ldif(fp, parse_tree, us, conf))
725
debug_return_bool(false);
726
}
727
debug_return_bool(true);
728
}
729
730
/*
731
* Export the parsed sudoers file in LDIF format.
732
*/
733
bool
734
convert_sudoers_ldif(const struct sudoers_parse_tree *parse_tree,
735
const char *output_file, struct cvtsudoers_config *conf)
736
{
737
bool ret = false;
738
FILE *output_fp = stdout;
739
debug_decl(convert_sudoers_ldif, SUDOERS_DEBUG_UTIL);
740
741
if (conf->sudoers_base == NULL) {
742
sudo_warnx("%s", U_("the SUDOERS_BASE environment variable is not set and the -b option was not specified."));
743
debug_return_bool(false);
744
}
745
746
if (output_file != NULL && strcmp(output_file, "-") != 0) {
747
if ((output_fp = fopen(output_file, "w")) == NULL) {
748
sudo_warn(U_("unable to open %s"), output_file);
749
debug_return_bool(false);
750
}
751
}
752
753
/* Create a dictionary of already-seen users. */
754
seen_users = rbcreate(seen_user_compare);
755
if (seen_users == NULL) {
756
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
757
goto cleanup;
758
}
759
760
/* Dump global Defaults in LDIF format. */
761
if (!ISSET(conf->suppress, SUPPRESS_DEFAULTS)) {
762
if (!print_global_defaults_ldif(output_fp, parse_tree, conf))
763
goto cleanup;
764
}
765
766
/* Dump User_Specs in LDIF format, expanding Aliases. */
767
if (!ISSET(conf->suppress, SUPPRESS_PRIVS)) {
768
if (!print_userspecs_ldif(output_fp, parse_tree, conf))
769
goto cleanup;
770
}
771
772
ret = true;
773
774
cleanup:
775
if (seen_users != NULL)
776
rbdestroy(seen_users, seen_user_free);
777
778
(void)fflush(output_fp);
779
if (ferror(output_fp)) {
780
sudo_warn("%s", output_file);
781
ret = false;
782
}
783
if (output_fp != stdout)
784
fclose(output_fp);
785
786
debug_return_bool(ret);
787
}
788
789