Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/sudoers/cvtsudoers_merge.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 <stdarg.h>
24
#include <string.h>
25
#include <unistd.h>
26
#include <ctype.h>
27
#include <errno.h>
28
29
#include <sudoers.h>
30
#include <redblack.h>
31
#include <cvtsudoers.h>
32
#include <gram.h>
33
34
static struct member *
35
new_member(const char *name, short type)
36
{
37
struct member *m;
38
debug_decl(digest_list_equivalent, SUDOERS_DEBUG_PARSER);
39
40
m = calloc(1, sizeof(struct member));
41
if (m == NULL)
42
goto oom;
43
if (name != NULL) {
44
m->name = strdup(name);
45
if (m->name == NULL)
46
goto oom;
47
}
48
m->type = type;
49
50
debug_return_ptr(m);
51
oom:
52
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
53
free(m);
54
debug_return_ptr(NULL);
55
}
56
57
/*
58
* Compare two digest lists.
59
* Returns true if they are the same, else false.
60
* XXX - should not care about order
61
*/
62
static bool
63
digest_list_equivalent(struct command_digest_list *cdl1,
64
struct command_digest_list *cdl2)
65
{
66
struct command_digest *cd1 = TAILQ_FIRST(cdl1);
67
struct command_digest *cd2 = TAILQ_FIRST(cdl2);
68
debug_decl(digest_list_equivalent, SUDOERS_DEBUG_PARSER);
69
70
while (cd1 != NULL && cd2 != NULL) {
71
if (cd1->digest_type != cd2->digest_type)
72
debug_return_bool(false);
73
if (strcmp(cd1->digest_str, cd2->digest_str) != 0)
74
debug_return_bool(false);
75
cd1 = TAILQ_NEXT(cd1, entries);
76
cd2 = TAILQ_NEXT(cd2, entries);
77
}
78
79
if (cd1 != NULL || cd2 != NULL)
80
debug_return_bool(false);
81
debug_return_bool(true);
82
}
83
84
/*
85
* Compare two members.
86
* Returns true if they are the same, else false.
87
*/
88
static bool
89
member_equivalent(struct member *m1, struct member *m2)
90
{
91
debug_decl(member_equivalent, SUDOERS_DEBUG_PARSER);
92
93
if (m1->type != m2->type || m1->negated != m2->negated)
94
debug_return_bool(false);
95
96
if (m1->type == COMMAND) {
97
struct sudo_command *c1 = (struct sudo_command *)m1->name;
98
struct sudo_command *c2 = (struct sudo_command *)m2->name;
99
if (c1->cmnd != NULL && c2->cmnd != NULL) {
100
if (strcmp(c1->cmnd, c2->cmnd) != 0)
101
debug_return_bool(false);
102
} else if (c1->cmnd != c2->cmnd) {
103
debug_return_bool(false);
104
}
105
106
if (c1->args != NULL && c2->args != NULL) {
107
if (strcmp(c1->args, c2->args) != 0)
108
debug_return_bool(false);
109
} else if (c1->args != c2->args) {
110
debug_return_bool(false);
111
}
112
113
if (!digest_list_equivalent(&c1->digests, &c2->digests)) {
114
debug_return_bool(false);
115
}
116
} else {
117
if (m1->name != NULL && m2->name != NULL) {
118
if (strcmp(m1->name, m2->name) != 0)
119
debug_return_bool(false);
120
} else if (m1->name != m2->name) {
121
debug_return_bool(false);
122
}
123
}
124
125
debug_return_bool(true);
126
}
127
128
/*
129
* Compare two members, m1 and m2.
130
* Returns true if m2 overrides m1, else false.
131
*/
132
static bool
133
member_overridden(struct member *m1, struct member *m2, bool check_negated)
134
{
135
debug_decl(member_overridden, SUDOERS_DEBUG_PARSER);
136
137
if (check_negated && m1->negated != m2->negated)
138
debug_return_bool(false);
139
140
/* "ALL" always wins (modulo digest). */
141
if (m2->type == ALL) {
142
if (m2->name != NULL) {
143
struct sudo_command *c1 = (struct sudo_command *)m1->name;
144
struct sudo_command *c2 = (struct sudo_command *)m2->name;
145
debug_return_bool(digest_list_equivalent(&c1->digests, &c2->digests));
146
}
147
debug_return_bool(true);
148
}
149
150
if (m1->type != m2->type)
151
debug_return_bool(false);
152
153
if (m1->type == COMMAND) {
154
struct sudo_command *c1 = (struct sudo_command *)m1->name;
155
struct sudo_command *c2 = (struct sudo_command *)m2->name;
156
if (strcmp(c1->cmnd, c2->cmnd) != 0)
157
debug_return_bool(false);
158
159
if (c1->args != NULL && c2->args != NULL) {
160
if (strcmp(c1->args, c2->args) != 0)
161
debug_return_bool(false);
162
} else if (c1->args != c2->args) {
163
debug_return_bool(false);
164
}
165
166
if (!digest_list_equivalent(&c1->digests, &c2->digests)) {
167
debug_return_bool(false);
168
}
169
} else {
170
if (strcmp(m1->name, m2->name) != 0)
171
debug_return_bool(false);
172
}
173
174
debug_return_bool(true);
175
}
176
177
/*
178
* Given two member lists, ml1 and ml2.
179
* Returns true if the every element of ml1 is overridden by ml2, else false.
180
*/
181
static bool
182
member_list_override(struct member_list *ml1, struct member_list *ml2,
183
bool check_negated)
184
{
185
struct member *m1, *m2;
186
debug_decl(member_list_override, SUDOERS_DEBUG_PARSER);
187
188
/* An empty member_list only overrides another empty list. */
189
if (TAILQ_EMPTY(ml2)) {
190
debug_return_bool(TAILQ_EMPTY(ml1));
191
}
192
193
/* Check whether each element of ml1 is also covered by ml2. */
194
TAILQ_FOREACH_REVERSE(m1, ml1, member_list, entries) {
195
bool overridden = false;
196
TAILQ_FOREACH_REVERSE(m2, ml2, member_list, entries) {
197
if (member_overridden(m1, m2, check_negated)) {
198
overridden = true;
199
break;
200
}
201
}
202
if (!overridden)
203
debug_return_bool(false);
204
}
205
206
debug_return_bool(true);
207
}
208
209
/*
210
* Compare two member lists.
211
* Returns true if they are the same, else false.
212
* XXX - should not care about order if things are not negated.
213
*/
214
static bool
215
member_list_equivalent(struct member_list *ml1, struct member_list *ml2)
216
{
217
struct member *m1 = TAILQ_FIRST(ml1);
218
struct member *m2 = TAILQ_FIRST(ml2);
219
debug_decl(member_list_equivalent, SUDOERS_DEBUG_PARSER);
220
221
while (m1 != NULL && m2 != NULL) {
222
if (!member_equivalent(m1, m2))
223
debug_return_bool(false);
224
m1 = TAILQ_NEXT(m1, entries);
225
m2 = TAILQ_NEXT(m2, entries);
226
}
227
228
if (m1 != NULL || m2 != NULL)
229
debug_return_bool(false);
230
debug_return_bool(true);
231
}
232
233
/*
234
* Attempt to simplify a host list.
235
* If a host list contains all hosts in bound_hosts, replace them with
236
* "ALL". Also prune hosts on either side of "ALL" when possible.
237
*/
238
static bool
239
simplify_host_list(struct member_list *hosts, const char *file, int line,
240
int column, struct member_list *bound_hosts)
241
{
242
struct member *m, *n, *next;
243
bool logged = false;
244
debug_decl(simplify_host_list, SUDOERS_DEBUG_PARSER);
245
246
/*
247
* If all sudoers sources have an associated host, replace a
248
* list of those hosts with "ALL".
249
*/
250
if (!TAILQ_EMPTY(bound_hosts)) {
251
TAILQ_FOREACH_REVERSE(n, bound_hosts, member_list, entries) {
252
TAILQ_FOREACH_REVERSE(m, hosts, member_list, entries) {
253
if (m->negated) {
254
/* Don't try to handled negated entries. */
255
m = NULL;
256
break;
257
}
258
if (m->type == n->type && strcmp(m->name, n->name) == 0) {
259
/* match */
260
break;
261
}
262
}
263
if (m == NULL) {
264
/* no match */
265
break;
266
}
267
}
268
if (n == NULL) {
269
/* found all hosts */
270
log_warnx(U_("%s:%d:%d: converting host list to ALL"),
271
file, line, column);
272
logged = true;
273
274
TAILQ_FOREACH_REVERSE(n, bound_hosts, member_list, entries) {
275
TAILQ_FOREACH_REVERSE_SAFE(m, hosts, member_list, entries, next) {
276
if (m->negated) {
277
/* Don't try to handled negated entries. */
278
m = NULL;
279
break;
280
}
281
if (m->type == n->type && strcmp(m->name, n->name) == 0) {
282
/* remove matching host */
283
TAILQ_REMOVE(hosts, m, entries);
284
free_member(m);
285
break;
286
}
287
}
288
}
289
m = new_member(NULL, ALL);
290
if (m == NULL)
291
debug_return_bool(false);
292
TAILQ_INSERT_TAIL(hosts, m, entries);
293
}
294
}
295
296
/*
297
* A host list that contains ALL with no negated entries past it
298
* is equivalent to a list containing just "ALL".
299
*/
300
TAILQ_FOREACH_REVERSE(m, hosts, member_list, entries) {
301
if (m->negated) {
302
/* Don't try to handled negated entries. */
303
break;
304
}
305
if (m->type == ALL) {
306
/* Replace member list with a single ALL entry. */
307
if (!logged) {
308
log_warnx(U_("%s:%d:%d: converting host list to ALL"),
309
file, line, column);
310
}
311
TAILQ_REMOVE(hosts, m, entries);
312
free_members(hosts);
313
TAILQ_INSERT_TAIL(hosts, m, entries);
314
break;
315
}
316
}
317
318
debug_return_bool(true);
319
}
320
321
/*
322
* Generate a unique name from old_name that is not used in parse_tree,
323
* subsequent parse_trees or merged_tree.
324
*/
325
static char *
326
alias_make_unique(const char *old_name, short type,
327
struct sudoers_parse_tree *parse_tree0,
328
struct sudoers_parse_tree *merged_tree)
329
{
330
struct sudoers_parse_tree *parse_tree;
331
char *cp, *new_name = NULL;
332
struct alias *a;
333
long long suffix;
334
size_t namelen;
335
debug_decl(alias_make_unique, SUDOERS_DEBUG_ALIAS);
336
337
/* If old_name already has a suffix, increment it, else start with "_1". */
338
suffix = 0;
339
namelen = strlen(old_name);
340
cp = strrchr(old_name, '_');
341
if (cp != NULL && isdigit((unsigned char)cp[1])) {
342
suffix = sudo_strtonum(cp + 1, 0, LLONG_MAX, NULL);
343
if (suffix != 0) {
344
namelen = (size_t)(cp - old_name);
345
}
346
}
347
348
for (;;) {
349
suffix++;
350
free(new_name);
351
if (asprintf(&new_name, "%.*s_%lld", (int)namelen, old_name, suffix) == -1) {
352
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
353
debug_return_ptr(NULL);
354
}
355
/* Make sure new_name is not already in use. */
356
a = alias_get(merged_tree, new_name, type);
357
if (a != NULL) {
358
alias_put(a);
359
continue;
360
}
361
parse_tree = parse_tree0;
362
while ((parse_tree = TAILQ_NEXT(parse_tree, entries)) != NULL) {
363
a = alias_get(parse_tree, new_name, type);
364
if (a != NULL) {
365
alias_put(a);
366
break;
367
}
368
}
369
if (a == NULL) {
370
/* Must be unique. */
371
break;
372
}
373
}
374
375
debug_return_ptr(new_name);
376
}
377
378
struct alias_rename_closure {
379
const char *old_name;
380
const char *new_name;
381
int type;
382
};
383
384
static int
385
alias_rename_members(struct sudoers_parse_tree *parse_tree, struct alias *a,
386
void *v)
387
{
388
struct alias_rename_closure *closure = v;
389
struct member *m;
390
debug_decl(alias_rename_members, SUDOERS_DEBUG_ALIAS);
391
392
if (a->type != closure->type)
393
debug_return_int(0);
394
395
/* Replace old_name in member list, if present. */
396
TAILQ_FOREACH(m, &a->members, entries) {
397
if (m->type == ALIAS && strcmp(m->name, closure->old_name) == 0) {
398
char *copy = strdup(closure->new_name);
399
if (copy == NULL) {
400
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
401
debug_return_int(-1);
402
}
403
free(m->name);
404
m->name = copy;
405
}
406
}
407
408
debug_return_int(0);
409
}
410
411
static bool
412
alias_rename_defaults(const char *old_name, const char *new_name,
413
short alias_type, struct defaults_list *defaults)
414
{
415
struct defaults *def, *def_next;
416
struct member *m;
417
debug_decl(alias_rename_defaults, SUDOERS_DEBUG_ALIAS);
418
419
TAILQ_FOREACH_SAFE(def, defaults, entries, def_next) {
420
/* Consecutive Defaults can share the same binding. */
421
if (def_next != NULL && def->binding == def_next->binding)
422
continue;
423
424
switch (def->type) {
425
case DEFAULTS_USER:
426
if (alias_type != USERALIAS)
427
continue;
428
break;
429
case DEFAULTS_RUNAS:
430
if (alias_type != RUNASALIAS)
431
continue;
432
break;
433
case DEFAULTS_HOST:
434
if (alias_type != HOSTALIAS)
435
continue;
436
break;
437
default:
438
continue;
439
}
440
441
/* Rename matching aliases in the binding's member_list. */
442
TAILQ_FOREACH(m, &def->binding->members, entries) {
443
if (m->type != ALIAS)
444
continue;
445
if (strcmp(m->name, old_name) == 0) {
446
char *copy = strdup(new_name);
447
if (copy == NULL) {
448
sudo_warnx(U_("%s: %s"), __func__,
449
U_("unable to allocate memory"));
450
debug_return_bool(false);
451
}
452
free(m->name);
453
m->name = copy;
454
}
455
}
456
}
457
458
debug_return_bool(true);
459
}
460
461
static bool
462
alias_rename_member(const char *old_name, const char *new_name,
463
struct member *m)
464
{
465
debug_decl(alias_rename_member, SUDOERS_DEBUG_ALIAS);
466
467
if (m->type == ALIAS && strcmp(m->name, old_name) == 0) {
468
char *copy = strdup(new_name);
469
if (copy == NULL) {
470
sudo_warnx(U_("%s: %s"), __func__,
471
U_("unable to allocate memory"));
472
debug_return_bool(false);
473
}
474
free(m->name);
475
m->name = copy;
476
}
477
478
debug_return_bool(true);
479
}
480
481
static bool
482
alias_rename_member_list(const char *old_name, const char *new_name,
483
struct member_list *members)
484
{
485
struct member *m;
486
debug_decl(alias_rename_member_list, SUDOERS_DEBUG_ALIAS);
487
488
TAILQ_FOREACH(m, members, entries) {
489
if (!alias_rename_member(old_name, new_name, m))
490
debug_return_bool(false);
491
}
492
493
debug_return_bool(true);
494
}
495
496
static bool
497
alias_rename_userspecs(const char *old_name, const char *new_name,
498
short alias_type, struct userspec_list *userspecs)
499
{
500
struct privilege *priv;
501
struct cmndspec *cs;
502
struct userspec *us;
503
debug_decl(alias_rename_userspecs, SUDOERS_DEBUG_ALIAS);
504
505
TAILQ_FOREACH(us, userspecs, entries) {
506
if (alias_type == USERALIAS) {
507
if (!alias_rename_member_list(old_name, new_name, &us->users)) {
508
debug_return_bool(false);
509
}
510
}
511
TAILQ_FOREACH(priv, &us->privileges, entries) {
512
if (!alias_rename_defaults(old_name, new_name, alias_type, &priv->defaults)) {
513
debug_return_bool(false);
514
}
515
if (alias_type == HOSTALIAS) {
516
if (!alias_rename_member_list(old_name, new_name, &priv->hostlist)) {
517
debug_return_bool(false);
518
}
519
continue;
520
}
521
TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
522
if (alias_type == CMNDALIAS) {
523
if (!alias_rename_member(old_name, new_name, cs->cmnd)) {
524
debug_return_bool(false);
525
}
526
continue;
527
}
528
if (alias_type == RUNASALIAS) {
529
if (cs->runasuserlist != NULL) {
530
if (!alias_rename_member_list(old_name, new_name, cs->runasuserlist)) {
531
debug_return_bool(false);
532
}
533
}
534
if (cs->runasgrouplist != NULL) {
535
if (!alias_rename_member_list(old_name, new_name, cs->runasgrouplist)) {
536
debug_return_bool(false);
537
}
538
}
539
}
540
}
541
}
542
}
543
544
debug_return_bool(true);
545
}
546
547
/*
548
* Rename an alias in parse_tree and all the places where it is used.
549
* Takes ownership if new_name which must not be freed by the caller.
550
*/
551
static bool
552
alias_rename(const char *old_name, char *new_name, short alias_type,
553
struct sudoers_parse_tree *parse_tree)
554
{
555
struct alias_rename_closure closure = { old_name, new_name, alias_type };
556
struct alias *a;
557
debug_decl(alias_rename, SUDOERS_DEBUG_ALIAS);
558
559
/* Remove under old name and add via new to maintain tree properties. */
560
a = alias_remove(parse_tree, old_name, alias_type);
561
if (a == NULL) {
562
/* Should not happen. */
563
sudo_warnx(U_("unable to find alias %s"), old_name);
564
free(new_name);
565
debug_return_bool(false);
566
}
567
log_warnx(U_("%s:%d:%d: renaming alias %s to %s"),
568
a->file, a->line, a->column, a->name, new_name);
569
free(a->name);
570
a->name = new_name;
571
switch (rbinsert(parse_tree->aliases, a, NULL)) {
572
case 0:
573
/* success */
574
break;
575
case 1:
576
/* Already present, should not happen. */
577
errno = EEXIST;
578
sudo_warn(U_("%s: %s"), __func__, a->name);
579
alias_free(a);
580
debug_return_bool(false);
581
break;
582
default:
583
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
584
alias_free(a);
585
debug_return_bool(false);
586
}
587
588
/* Rename it in the aliases tree itself (aliases can be nested). */
589
if (!alias_apply(parse_tree, alias_rename_members, &closure))
590
debug_return_bool(false);
591
592
/* Rename it in the Defaults list. */
593
if (!alias_rename_defaults(old_name, new_name, alias_type, &parse_tree->defaults))
594
debug_return_bool(false);
595
596
/* Rename it in the userspecs list. */
597
if (!alias_rename_userspecs(old_name, new_name, alias_type, &parse_tree->userspecs))
598
debug_return_bool(false);
599
600
debug_return_bool(true);
601
}
602
603
static int
604
alias_resolve_conflicts(struct sudoers_parse_tree *parse_tree0, struct alias *a,
605
void *v)
606
{
607
struct sudoers_parse_tree *parse_tree = parse_tree0;
608
struct sudoers_parse_tree *merged_tree = v;
609
char *new_name;
610
int ret;
611
debug_decl(alias_resolve_conflicts, SUDOERS_DEBUG_ALIAS);
612
613
/*
614
* Check for conflicting alias names in the subsequent sudoers files.
615
* Duplicates are removed and conflicting aliases are renamed.
616
* We cannot modify the alias tree that we are traversing.
617
*/
618
while ((parse_tree = TAILQ_NEXT(parse_tree, entries)) != NULL) {
619
struct alias *b = alias_get(parse_tree, a->name, a->type);
620
if (b == NULL)
621
continue;
622
623
/* If alias 'b' is equivalent, remove it. */
624
alias_put(b);
625
if (member_list_equivalent(&a->members, &b->members)) {
626
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
627
"removing duplicate alias %s from %p", a->name, parse_tree);
628
b = alias_remove(parse_tree, a->name, a->type);
629
log_warnx(U_("%s:%d:%d: removing duplicate alias %s"),
630
b->file, b->line, b->column, b->name);
631
alias_free(b);
632
continue;
633
}
634
635
/* Rename alias 'b' to avoid a naming conflict. */
636
new_name = alias_make_unique(a->name, a->type, parse_tree, merged_tree);
637
if (new_name == NULL)
638
debug_return_int(-1);
639
if (!alias_rename(a->name, new_name, a->type, parse_tree))
640
debug_return_int(-1);
641
}
642
643
/*
644
* The alias will exist in both the original and merged trees.
645
* This is not a problem as the caller will delete the old trees
646
* (without freeing the data).
647
*/
648
ret = rbinsert(merged_tree->aliases, a, NULL);
649
switch (ret) {
650
case 0:
651
/* success */
652
break;
653
case 1:
654
/* already present, should not happen. */
655
errno = EEXIST;
656
sudo_warn(U_("%s: %s"), __func__, a->name);
657
debug_return_int(-1);
658
default:
659
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
660
debug_return_int(-1);
661
}
662
663
debug_return_int(0);
664
}
665
666
static bool
667
merge_aliases(struct sudoers_parse_tree_list *parse_trees,
668
struct sudoers_parse_tree *merged_tree)
669
{
670
struct sudoers_parse_tree *parse_tree;
671
debug_decl(merge_aliases, SUDOERS_DEBUG_ALIAS);
672
673
/*
674
* For each parse_tree, check for collisions with alias names
675
* in subsequent parse trees. On collision, add a numbered
676
* suffix (e.g. ALIAS_1) to make the name unique and rename
677
* any uses of that alias in the affected parse_tree.
678
*/
679
TAILQ_FOREACH(parse_tree, parse_trees, entries) {
680
if (parse_tree->aliases == NULL)
681
continue;
682
683
/*
684
* Resolve any conflicts in alias names, renaming aliases as
685
* needed and eliminating duplicates.
686
*/
687
if (!alias_apply(parse_tree, alias_resolve_conflicts, merged_tree))
688
debug_return_bool(false);
689
690
/*
691
* Destroy the old alias tree without freeing the alias data
692
* which has been copied to merged_tree.
693
*/
694
rbdestroy(parse_tree->aliases, NULL);
695
parse_tree->aliases = NULL;
696
}
697
698
debug_return_bool(true);
699
}
700
701
/*
702
* Compare two defaults structs but not their actual value.
703
* Returns true if they refer to the same Defaults variable and binding.
704
* Also sets mergeable if they only differ in the binding.
705
*/
706
static bool
707
defaults_var_matches(struct defaults *d1, struct defaults *d2,
708
bool *mergeable)
709
{
710
debug_decl(defaults_var_matches, SUDOERS_DEBUG_DEFAULTS);
711
712
if (strcmp(d1->var, d2->var) != 0)
713
debug_return_bool(false);
714
if (d1->type != d2->type) {
715
if ((d1->type == DEFAULTS && d2->type == DEFAULTS_HOST) ||
716
(d1->type == DEFAULTS_HOST && d2->type == DEFAULTS)) {
717
/* We can merge host and global bindings. */
718
if (mergeable != NULL)
719
*mergeable = true;
720
}
721
debug_return_bool(false);
722
}
723
if (d1->type != DEFAULTS) {
724
if (!member_list_equivalent(&d1->binding->members, &d2->binding->members)) {
725
if (mergeable != NULL)
726
*mergeable = true;
727
debug_return_bool(false);
728
}
729
}
730
731
debug_return_bool(true);
732
}
733
734
/*
735
* Compare the values of two defaults structs, which must be of the same type.
736
* Returns true if the value and operator match, else false.
737
*/
738
static bool
739
defaults_val_matches(struct defaults *d1, struct defaults *d2)
740
{
741
debug_decl(defaults_val_matches, SUDOERS_DEBUG_DEFAULTS);
742
743
/* XXX - what about list operators? */
744
if (d1->op != d2->op)
745
debug_return_bool(false);
746
747
/* Either both must be NULL or both non-NULL _and_ matching. */
748
if (d1->val != NULL && d2->val != NULL) {
749
if (strcmp(d1->val, d2->val) != 0)
750
debug_return_bool(false);
751
} else {
752
if (d1->val != NULL || d2->val != NULL)
753
debug_return_bool(false);
754
}
755
756
debug_return_bool(true);
757
}
758
759
/*
760
* Returns true if d1 is equivalent to d2, else false.
761
*/
762
static bool
763
defaults_equivalent(struct defaults *d1, struct defaults *d2)
764
{
765
debug_decl(defaults_equivalent, SUDOERS_DEBUG_DEFAULTS);
766
767
if (!defaults_var_matches(d1, d2, NULL))
768
debug_return_bool(false);
769
debug_return_bool(defaults_val_matches(d1, d2));
770
}
771
772
/*
773
* Returns true if dl1 is equivalent to dl2, else false.
774
*/
775
static bool
776
defaults_list_equivalent(struct defaults_list *dl1, struct defaults_list *dl2)
777
{
778
struct defaults *d1 = TAILQ_FIRST(dl1);
779
struct defaults *d2 = TAILQ_FIRST(dl2);
780
debug_decl(defaults_list_equivalent, SUDOERS_DEBUG_DEFAULTS);
781
782
while (d1 != NULL && d2 != NULL) {
783
if (!defaults_equivalent(d1, d2))
784
debug_return_bool(false);
785
d1 = TAILQ_NEXT(d1, entries);
786
d2 = TAILQ_NEXT(d2, entries);
787
}
788
789
if (d1 != NULL || d2 != NULL)
790
debug_return_bool(false);
791
debug_return_bool(true);
792
}
793
794
enum cvtsudoers_conflict {
795
CONFLICT_NONE,
796
CONFLICT_RESOLVED,
797
CONFLICT_UNRESOLVED,
798
CONFLICT_ERROR
799
};
800
801
/*
802
* Check for duplicate and conflicting Defaults entries in later sudoers files.
803
* Returns true if we find a conflict or duplicate, else false.
804
*/
805
static enum cvtsudoers_conflict
806
defaults_check_conflict(struct defaults *def,
807
struct sudoers_parse_tree *parse_tree0)
808
{
809
struct sudoers_parse_tree *parse_tree = parse_tree0;
810
struct defaults *d;
811
debug_decl(defaults_check_conflict, SUDOERS_DEBUG_DEFAULTS);
812
813
while ((parse_tree = TAILQ_NEXT(parse_tree, entries)) != NULL) {
814
TAILQ_FOREACH_REVERSE(d, &parse_tree->defaults, defaults_list, entries) {
815
bool mergeable = false;
816
817
/*
818
* We currently only merge host-based Defaults but could do
819
* others as well. Lists in Defaults entries can be harder
820
* to read, especially command lists.
821
*/
822
if (!defaults_var_matches(def, d, &mergeable)) {
823
if (!mergeable || (def->type != DEFAULTS && def->type != DEFAULTS_HOST))
824
continue;
825
}
826
if (defaults_val_matches(def, d)) {
827
/* Duplicate Defaults entry (may need to merge binding). */
828
if (mergeable) {
829
if (d->type != def->type &&
830
(d->type == DEFAULTS || def->type == DEFAULTS)) {
831
/*
832
* To be able to merge two Defaults, they both must
833
* have the same binding type. Convert a global
834
* Defaults to one bound to single "ALL" member.
835
*/
836
if (d->type == DEFAULTS) {
837
struct member *m = new_member(NULL, ALL);
838
if (m == NULL)
839
debug_return_int(CONFLICT_ERROR);
840
TAILQ_INSERT_TAIL(&d->binding->members, m, entries);
841
d->type = def->type;
842
}
843
if (def->type == DEFAULTS) {
844
struct member *m = new_member(NULL, ALL);
845
if (m == NULL)
846
debug_return_int(CONFLICT_ERROR);
847
TAILQ_INSERT_TAIL(&def->binding->members, m, entries);
848
def->type = d->type;
849
}
850
}
851
852
/* Prepend def binding to d (hence double concat). */
853
TAILQ_CONCAT(&def->binding->members, &d->binding->members, entries);
854
TAILQ_CONCAT(&d->binding->members, &def->binding->members, entries);
855
}
856
debug_return_int(CONFLICT_RESOLVED);
857
}
858
/*
859
* If the value doesn't match but the Defaults name did we don't
860
* consider that a conflict.
861
*/
862
if (!mergeable) {
863
log_warnx(U_("%s:%d:%d: conflicting Defaults entry \"%s\" host-specific in %s:%d:%d"),
864
def->file, def->line, def->column, def->var,
865
d->file, d->line, d->column);
866
debug_return_int(CONFLICT_UNRESOLVED);
867
}
868
}
869
}
870
871
debug_return_int(CONFLICT_NONE);
872
}
873
874
/*
875
* Merge Defaults entries in parse_trees and store the result in
876
* merged_tree. If a hostname was specified with the sudoers source,
877
* create a host-specific Defaults entry where possible.
878
* Returns true on success, else false.
879
*/
880
static bool
881
merge_defaults(struct sudoers_parse_tree_list *parse_trees,
882
struct sudoers_parse_tree *merged_tree, struct member_list *bound_hosts)
883
{
884
struct sudoers_parse_tree *parse_tree;
885
struct defaults *def;
886
struct member *m;
887
debug_decl(merge_defaults, SUDOERS_DEBUG_DEFAULTS);
888
889
TAILQ_FOREACH(parse_tree, parse_trees, entries) {
890
/*
891
* If parse_tree has a host name associated with it,
892
* try to make the Defaults setting host-specific.
893
*/
894
TAILQ_FOREACH(def, &parse_tree->defaults, entries) {
895
if (parse_tree->lhost != NULL && def->type == DEFAULTS) {
896
m = new_member(parse_tree->lhost, WORD);
897
if (m == NULL)
898
debug_return_bool(false);
899
log_warnx(U_("%s:%d:%d: made Defaults \"%s\" specific to host %s"),
900
def->file, def->line, def->column, def->var,
901
parse_tree->lhost);
902
TAILQ_INSERT_TAIL(&def->binding->members, m, entries);
903
def->type = DEFAULTS_HOST;
904
}
905
}
906
}
907
908
TAILQ_FOREACH(parse_tree, parse_trees, entries) {
909
while ((def = TAILQ_FIRST(&parse_tree->defaults)) != NULL) {
910
/*
911
* Only add Defaults entry if not overridden by subsequent sudoers.
912
*/
913
TAILQ_REMOVE(&parse_tree->defaults, def, entries);
914
switch (defaults_check_conflict(def, parse_tree)) {
915
case CONFLICT_NONE:
916
if (def->type != DEFAULTS_HOST) {
917
log_warnx(U_("%s:%d:%d: unable to make Defaults \"%s\" host-specific"),
918
def->file, def->line, def->column, def->var);
919
}
920
TAILQ_INSERT_TAIL(&merged_tree->defaults, def, entries);
921
break;
922
case CONFLICT_RESOLVED:
923
/* Duplicate or merged into a subsequent Defaults setting. */
924
free_default(def);
925
break;
926
case CONFLICT_UNRESOLVED:
927
log_warnx(U_("%s:%d:%d: removing Defaults \"%s\" overridden by subsequent entries"),
928
def->file, def->line, def->column, def->var);
929
free_default(def);
930
break;
931
default:
932
/* warning printed by defaults_check_conflict() */
933
free_default(def);
934
debug_return_bool(false);
935
}
936
}
937
}
938
939
/*
940
* Simplify host lists in the merged Defaults.
941
*/
942
TAILQ_FOREACH(def, &merged_tree->defaults, entries) {
943
/* TODO: handle refcnt != 1 */
944
if (def->type == DEFAULTS_HOST && def->binding->refcnt == 1) {
945
if (!simplify_host_list(&def->binding->members, def->file,
946
def->line, def->column, bound_hosts)) {
947
debug_return_bool(false);
948
}
949
m = TAILQ_FIRST(&def->binding->members);
950
if (m->type == ALL && !m->negated) {
951
if (TAILQ_NEXT(m, entries) == NULL) {
952
/* Convert Defaults@ALL -> Defaults */
953
def->type = DEFAULTS;
954
free_members(&def->binding->members);
955
TAILQ_INIT(&def->binding->members);
956
}
957
}
958
}
959
}
960
961
debug_return_bool(true);
962
}
963
964
/*
965
* Returns true if cs1 is equivalent to cs2, else false.
966
*/
967
static bool
968
cmndspec_equivalent(struct cmndspec *cs1, struct cmndspec *cs2, bool check_negated)
969
{
970
debug_decl(cmndspec_equivalent, SUDOERS_DEBUG_PARSER);
971
972
if (cs1->runasuserlist != NULL && cs2->runasuserlist != NULL) {
973
if (!member_list_override(cs1->runasuserlist, cs2->runasuserlist, check_negated))
974
debug_return_bool(false);
975
} else if (cs1->runasuserlist != cs2->runasuserlist) {
976
debug_return_bool(false);
977
}
978
if (cs1->runasgrouplist != NULL && cs2->runasgrouplist != NULL) {
979
if (!member_list_override(cs1->runasgrouplist, cs2->runasgrouplist, check_negated))
980
debug_return_bool(false);
981
} else if (cs1->runasgrouplist != cs2->runasgrouplist) {
982
debug_return_bool(false);
983
}
984
if (!member_equivalent(cs1->cmnd, cs2->cmnd))
985
debug_return_bool(false);
986
if (TAGS_CHANGED(cs1->tags, cs2->tags))
987
debug_return_bool(false);
988
if (cs1->timeout != cs2->timeout)
989
debug_return_bool(false);
990
if (cs1->notbefore != cs2->notbefore)
991
debug_return_bool(false);
992
if (cs1->notafter != cs2->notafter)
993
debug_return_bool(false);
994
if (cs1->runcwd != NULL && cs2->runcwd != NULL) {
995
if (strcmp(cs1->runcwd, cs2->runcwd) != 0)
996
debug_return_bool(false);
997
} else if (cs1->runcwd != cs2->runcwd) {
998
debug_return_bool(false);
999
}
1000
if (cs1->runchroot != NULL && cs2->runchroot != NULL) {
1001
if (strcmp(cs1->runchroot, cs2->runchroot) != 0)
1002
debug_return_bool(false);
1003
} else if (cs1->runchroot != cs2->runchroot) {
1004
debug_return_bool(false);
1005
}
1006
if (cs1->role != NULL && cs2->role != NULL) {
1007
if (strcmp(cs1->role, cs2->role) != 0)
1008
debug_return_bool(false);
1009
} else if (cs1->role != cs2->role) {
1010
debug_return_bool(false);
1011
}
1012
if (cs1->type != NULL && cs2->type != NULL) {
1013
if (strcmp(cs1->type, cs2->type) != 0)
1014
debug_return_bool(false);
1015
} else if (cs1->type != cs2->type) {
1016
debug_return_bool(false);
1017
}
1018
if (cs1->apparmor_profile != NULL && cs2->apparmor_profile != NULL) {
1019
if (strcmp(cs1->apparmor_profile, cs2->apparmor_profile) != 0)
1020
debug_return_bool(false);
1021
} else if (cs1->apparmor_profile != cs2->apparmor_profile) {
1022
debug_return_bool(false);
1023
}
1024
if (cs1->privs != NULL && cs2->privs != NULL) {
1025
if (strcmp(cs1->privs, cs2->privs) != 0)
1026
debug_return_bool(false);
1027
} else if (cs1->privs != cs2->privs) {
1028
debug_return_bool(false);
1029
}
1030
if (cs1->limitprivs != NULL && cs2->limitprivs != NULL) {
1031
if (strcmp(cs1->limitprivs, cs2->limitprivs) != 0)
1032
debug_return_bool(false);
1033
} else if (cs1->limitprivs != cs2->limitprivs) {
1034
debug_return_bool(false);
1035
}
1036
1037
debug_return_bool(true);
1038
}
1039
1040
/*
1041
* Returns true if csl1 is equivalent to csl2, else false.
1042
*/
1043
static bool
1044
cmndspec_list_equivalent(struct cmndspec_list *csl1, struct cmndspec_list *csl2,
1045
bool check_negated)
1046
{
1047
struct cmndspec *cs1 = TAILQ_FIRST(csl1);
1048
struct cmndspec *cs2 = TAILQ_FIRST(csl2);
1049
debug_decl(cmndspec_list_equivalent, SUDOERS_DEBUG_PARSER);
1050
1051
while (cs1 != NULL && cs2 != NULL) {
1052
if (!cmndspec_equivalent(cs1, cs2, check_negated))
1053
debug_return_bool(false);
1054
cs1 = TAILQ_NEXT(cs1, entries);
1055
cs2 = TAILQ_NEXT(cs2, entries);
1056
}
1057
1058
if (cs1 != NULL || cs2 != NULL)
1059
debug_return_bool(false);
1060
debug_return_bool(true);
1061
}
1062
1063
/*
1064
* Check whether userspec us1 is overridden by another sudoers file entry.
1065
* If us1 and another userspec differ only in their host lists, merges
1066
* the hosts from us1 into that userspec.
1067
* Returns true if overridden, else false.
1068
* TODO: merge privs
1069
*/
1070
static enum cvtsudoers_conflict
1071
userspec_overridden(struct userspec *us1,
1072
struct sudoers_parse_tree *parse_tree, bool check_negated)
1073
{
1074
struct userspec *us2;
1075
bool hosts_differ = false;
1076
debug_decl(userspec_overridden, SUDOERS_DEBUG_PARSER);
1077
1078
if (TAILQ_EMPTY(&parse_tree->userspecs))
1079
debug_return_int(CONFLICT_NONE);
1080
1081
/* Sudoers rules are applied in reverse order (last match wins). */
1082
TAILQ_FOREACH_REVERSE(us2, &parse_tree->userspecs, userspec_list, entries) {
1083
struct privilege *priv1, *priv2;
1084
1085
if (!member_list_override(&us1->users, &us2->users, check_negated))
1086
continue;
1087
1088
/* XXX - order should not matter */
1089
priv1 = TAILQ_LAST(&us1->privileges, privilege_list);
1090
priv2 = TAILQ_LAST(&us2->privileges, privilege_list);
1091
while (priv1 != NULL && priv2 != NULL) {
1092
if (!defaults_list_equivalent(&priv1->defaults, &priv2->defaults))
1093
break;
1094
if (!cmndspec_list_equivalent(&priv1->cmndlist, &priv2->cmndlist, check_negated))
1095
break;
1096
1097
if (!member_list_override(&priv1->hostlist, &priv2->hostlist, check_negated))
1098
hosts_differ = true;
1099
1100
priv1 = TAILQ_PREV(priv1, privilege_list, entries);
1101
priv2 = TAILQ_PREV(priv2, privilege_list, entries);
1102
}
1103
if (priv1 != NULL || priv2 != NULL) {
1104
/* mismatch */
1105
continue;
1106
}
1107
1108
/*
1109
* If we have a match of everything except the host list,
1110
* merge the differing host lists.
1111
*/
1112
if (hosts_differ) {
1113
priv1 = TAILQ_LAST(&us1->privileges, privilege_list);
1114
priv2 = TAILQ_LAST(&us2->privileges, privilege_list);
1115
while (priv1 != NULL && priv2 != NULL) {
1116
if (!member_list_override(&priv1->hostlist, &priv2->hostlist, check_negated)) {
1117
/*
1118
* Priv matches but hosts differ, prepend priv1 hostlist
1119
* to into priv2 hostlist (hence the double concat).
1120
*/
1121
TAILQ_CONCAT(&priv1->hostlist, &priv2->hostlist, entries);
1122
TAILQ_CONCAT(&priv2->hostlist, &priv1->hostlist, entries);
1123
log_warnx(U_("%s:%d:%d: merging userspec into %s:%d:%d"),
1124
us1->file, us1->line, us1->column,
1125
us2->file, us2->line, us2->column);
1126
}
1127
priv1 = TAILQ_PREV(priv1, privilege_list, entries);
1128
priv2 = TAILQ_PREV(priv2, privilege_list, entries);
1129
}
1130
debug_return_int(CONFLICT_RESOLVED);
1131
}
1132
debug_return_int(CONFLICT_UNRESOLVED);
1133
}
1134
1135
debug_return_int(CONFLICT_NONE);
1136
}
1137
1138
/*
1139
* Check whether userspec us1 is overridden by another sudoers file entry.
1140
* If us1 and another userspec differ only in their host lists, merges
1141
* the hosts from us1 into that userspec.
1142
* Returns true if overridden, else false.
1143
*/
1144
static enum cvtsudoers_conflict
1145
userspec_check_conflict(struct userspec *us1,
1146
struct sudoers_parse_tree *parse_tree0)
1147
{
1148
struct sudoers_parse_tree *parse_tree = parse_tree0;
1149
debug_decl(userspec_check_conflict, SUDOERS_DEBUG_PARSER);
1150
1151
while ((parse_tree = TAILQ_NEXT(parse_tree, entries)) != NULL) {
1152
enum cvtsudoers_conflict ret =
1153
userspec_overridden(us1, parse_tree, false);
1154
if (ret != CONFLICT_NONE)
1155
debug_return_int(ret);
1156
}
1157
1158
debug_return_int(CONFLICT_NONE);
1159
}
1160
1161
/*
1162
* Merge userspecs in parse_trees and store the result in merged_tree.
1163
* If a hostname was specified with the sudoers source, make the
1164
* privilege host-specific where possible.
1165
* Returns true on success, else false.
1166
*/
1167
static bool
1168
merge_userspecs(struct sudoers_parse_tree_list *parse_trees,
1169
struct sudoers_parse_tree *merged_tree, struct member_list *bound_hosts)
1170
{
1171
struct sudoers_parse_tree *parse_tree;
1172
struct userspec *us;
1173
struct privilege *priv;
1174
struct member *m;
1175
debug_decl(merge_userspecs, SUDOERS_DEBUG_DEFAULTS);
1176
1177
/*
1178
* If parse_tree has a host name associated with it,
1179
* try to make the privilege host-specific.
1180
*/
1181
TAILQ_FOREACH(parse_tree, parse_trees, entries) {
1182
if (parse_tree->lhost == NULL)
1183
continue;
1184
TAILQ_FOREACH(us, &parse_tree->userspecs, entries) {
1185
TAILQ_FOREACH(priv, &us->privileges, entries) {
1186
TAILQ_FOREACH(m, &priv->hostlist, entries) {
1187
/* We don't alter !ALL in a hostlist (XXX - should we?). */
1188
if (m->type == ALL && !m->negated) {
1189
char *copy = strdup(parse_tree->lhost);
1190
if (copy == NULL) {
1191
sudo_warnx(U_("%s: %s"), __func__,
1192
U_("unable to allocate memory"));
1193
debug_return_bool(false);
1194
}
1195
m->type = WORD;
1196
m->name = copy;
1197
}
1198
}
1199
}
1200
}
1201
}
1202
1203
/*
1204
* Prune out duplicate userspecs after substituting hostname(s).
1205
* Traverse the list in reverse order--in sudoers last match wins.
1206
* XXX - do this at the privilege/cmndspec level instead.
1207
*/
1208
TAILQ_FOREACH(parse_tree, parse_trees, entries) {
1209
while ((us = TAILQ_LAST(&parse_tree->userspecs, userspec_list)) != NULL) {
1210
TAILQ_REMOVE(&parse_tree->userspecs, us, entries);
1211
switch (userspec_check_conflict(us, parse_tree)) {
1212
case CONFLICT_NONE:
1213
TAILQ_INSERT_HEAD(&merged_tree->userspecs, us, entries);
1214
break;
1215
case CONFLICT_RESOLVED:
1216
free_userspec(us);
1217
break;
1218
case CONFLICT_UNRESOLVED:
1219
log_warnx(U_("%s:%d:%d: removing userspec overridden by subsequent entries"),
1220
us->file, us->line, us->column);
1221
free_userspec(us);
1222
break;
1223
default:
1224
/* warning printed by defaults_check_conflict() */
1225
free_userspec(us);
1226
debug_return_bool(false);
1227
}
1228
}
1229
}
1230
1231
/*
1232
* Simplify member lists in the merged tree.
1233
* Convert host lists with all hosts listed to "ALL" and
1234
* collapse other entries around "ALL".
1235
*/
1236
TAILQ_FOREACH_REVERSE(us, &merged_tree->userspecs, userspec_list, entries) {
1237
TAILQ_FOREACH_REVERSE(priv, &us->privileges, privilege_list, entries) {
1238
/* TODO: simplify other lists? */
1239
if (!simplify_host_list(&priv->hostlist, us->file, us->line,
1240
us->column, bound_hosts)) {
1241
debug_return_bool(false);
1242
}
1243
}
1244
}
1245
1246
debug_return_bool(true);
1247
}
1248
1249
struct sudoers_parse_tree *
1250
merge_sudoers(struct sudoers_parse_tree_list *parse_trees,
1251
struct sudoers_parse_tree *merged_tree)
1252
{
1253
struct member_list bound_hosts = TAILQ_HEAD_INITIALIZER(bound_hosts);
1254
struct sudoers_parse_tree *parse_tree;
1255
debug_decl(merge_sudoers, SUDOERS_DEBUG_UTIL);
1256
1257
/*
1258
* If all sudoers sources have a host associated with them, we
1259
* can replace a list of those hosts with "ALL" in Defaults
1260
* and userspecs.
1261
*/
1262
TAILQ_FOREACH(parse_tree, parse_trees, entries) {
1263
if (parse_tree->lhost == NULL)
1264
break;
1265
}
1266
if (parse_tree == NULL) {
1267
TAILQ_FOREACH(parse_tree, parse_trees, entries) {
1268
struct member *m = new_member(parse_tree->lhost, WORD);
1269
if (m == NULL)
1270
goto bad;
1271
TAILQ_INSERT_TAIL(&bound_hosts, m, entries);
1272
}
1273
}
1274
1275
if ((merged_tree->aliases = alloc_aliases()) == NULL) {
1276
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
1277
goto bad;
1278
}
1279
1280
if (!merge_aliases(parse_trees, merged_tree))
1281
goto bad;
1282
1283
if (!merge_defaults(parse_trees, merged_tree, &bound_hosts))
1284
goto bad;
1285
1286
if (!merge_userspecs(parse_trees, merged_tree, &bound_hosts))
1287
goto bad;
1288
1289
free_members(&bound_hosts);
1290
debug_return_ptr(merged_tree);
1291
bad:
1292
free_members(&bound_hosts);
1293
debug_return_ptr(NULL);
1294
}
1295
1296