Coverage Report

Created: 2026-05-30 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sudo/plugins/sudoers/match.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 1996, 1998-2005, 2007-2023, 2025
5
 *  Todd C. Miller <Todd.Miller@sudo.ws>
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
 *
19
 * Sponsored in part by the Defense Advanced Research Projects
20
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
21
 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
22
 */
23
24
#include <config.h>
25
26
#include <sys/stat.h>
27
#ifdef HAVE_SYS_SYSTEMINFO_H
28
# include <sys/systeminfo.h>
29
#endif
30
#include <stdio.h>
31
#include <stdlib.h>
32
#include <string.h>
33
#ifdef HAVE_STRINGS_H
34
# include <strings.h>
35
#endif /* HAVE_STRINGS_H */
36
#include <unistd.h>
37
#ifdef HAVE_NETGROUP_H
38
# include <netgroup.h>
39
#else
40
# include <netdb.h>
41
#endif /* HAVE_NETGROUP_H */
42
#include <dirent.h>
43
#include <fcntl.h>
44
#include <pwd.h>
45
#include <grp.h>
46
#include <errno.h>
47
#ifdef HAVE_FNMATCH
48
# include <fnmatch.h>
49
#else
50
# include <compat/fnmatch.h>
51
#endif /* HAVE_FNMATCH */
52
53
#include <sudoers.h>
54
#include <gram.h>
55
56
/*
57
 * Check whether user described by pw matches member.
58
 * Returns ALLOW, DENY or UNSPEC.
59
 */
60
int
61
user_matches(const struct sudoers_parse_tree *parse_tree,
62
    const struct passwd *pw, const struct member *m)
63
0
{
64
0
    const struct sudoers_context *ctx = parse_tree->ctx;
65
0
    const char *lhost = parse_tree->lhost ? parse_tree->lhost : ctx->runas.host;
66
0
    const char *shost = parse_tree->shost ? parse_tree->shost : ctx->runas.shost;
67
0
    int matched = UNSPEC;
68
0
    struct alias *a;
69
0
    debug_decl(user_matches, SUDOERS_DEBUG_MATCH);
70
71
0
    switch (m->type) {
72
0
  case ALL:
73
0
      matched = m->negated ? DENY : ALLOW;
74
0
      break;
75
0
  case NETGROUP:
76
0
      if (netgr_matches(parse_tree->nss, m->name,
77
0
    def_netgroup_tuple ? lhost : NULL,
78
0
    def_netgroup_tuple ? shost : NULL, pw->pw_name) == ALLOW)
79
0
    matched = m->negated ? DENY : ALLOW;
80
0
      break;
81
0
  case USERGROUP:
82
0
      if (usergr_matches(m->name, pw->pw_name, pw) == ALLOW)
83
0
    matched = m->negated ? DENY : ALLOW;
84
0
      break;
85
0
  case ALIAS:
86
0
      if ((a = alias_get(parse_tree, m->name, USERALIAS)) != NULL) {
87
    /* XXX */
88
0
    const int rc = userlist_matches(parse_tree, pw, &a->members);
89
0
    if (SPECIFIED(rc)) {
90
0
        if (m->negated) {
91
0
      matched = rc == ALLOW ? DENY : ALLOW;
92
0
        } else {
93
0
      matched = rc;
94
0
        }
95
0
    }
96
0
    alias_put(a);
97
0
    break;
98
0
      }
99
0
      FALLTHROUGH;
100
0
  case WORD:
101
0
      if (userpw_matches(m->name, pw->pw_name, pw) == ALLOW)
102
0
    matched = m->negated ? DENY : ALLOW;
103
0
      break;
104
0
    }
105
0
    debug_return_int(matched);
106
0
}
107
108
/*
109
 * Check for user described by pw in a list of members.
110
 * Returns ALLOW, DENY or UNSPEC.
111
 */
112
int
113
userlist_matches(const struct sudoers_parse_tree *parse_tree,
114
    const struct passwd *pw, const struct member_list *list)
115
0
{
116
0
    struct member *m;
117
0
    int matched = UNSPEC;
118
0
    debug_decl(userlist_matches, SUDOERS_DEBUG_MATCH);
119
120
0
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
121
0
  matched = user_matches(parse_tree, pw, m);
122
0
  if (SPECIFIED(matched))
123
0
      break;
124
0
    }
125
0
    debug_return_int(matched);
126
0
}
127
128
struct gid_list *
129
runas_getgroups(const struct sudoers_context *ctx)
130
0
{
131
0
    const struct passwd *pw;
132
0
    debug_decl(runas_getgroups, SUDOERS_DEBUG_MATCH);
133
134
0
    if (def_preserve_groups) {
135
0
  sudo_gidlist_addref(ctx->user.gid_list);
136
0
  debug_return_ptr(ctx->user.gid_list);
137
0
    }
138
139
    /* Only use results from a group db query, not the front end. */
140
0
    pw = ctx->runas.pw ? ctx->runas.pw : ctx->user.pw;
141
0
    debug_return_ptr(sudo_get_gidlist(pw, ENTRY_TYPE_QUERIED));
142
0
}
143
144
/*
145
 * Check whether the requested runas user matches user_list, the
146
 * user portion of a sudoers runaslist.  If user_list is NULL, a
147
 * list containing runas_default is used.
148
 * Returns ALLOW, DENY or UNSPEC.
149
 */
150
static int
151
runas_userlist_matches(const struct sudoers_parse_tree *parse_tree,
152
    const struct member_list *user_list)
153
0
{
154
0
    const struct sudoers_context *ctx = parse_tree->ctx;
155
0
    const char *lhost = parse_tree->lhost ? parse_tree->lhost : ctx->runas.host;
156
0
    const char *shost = parse_tree->shost ? parse_tree->shost : ctx->runas.shost;
157
0
    int user_matched = UNSPEC;
158
0
    struct member *m;
159
0
    struct alias *a;
160
0
    debug_decl(runas_userlist_matches, SUDOERS_DEBUG_MATCH);
161
162
0
    TAILQ_FOREACH_REVERSE(m, user_list, member_list, entries) {
163
0
  switch (m->type) {
164
0
      case ALL:
165
0
    user_matched = m->negated ? DENY : ALLOW;
166
0
    break;
167
0
      case NETGROUP:
168
0
    if (netgr_matches(parse_tree->nss, m->name,
169
0
        def_netgroup_tuple ? lhost : NULL,
170
0
        def_netgroup_tuple ? shost : NULL,
171
0
        ctx->runas.pw->pw_name) == ALLOW)
172
0
        user_matched = m->negated ? DENY : ALLOW;
173
0
    break;
174
0
      case USERGROUP:
175
0
    if (usergr_matches(m->name, ctx->runas.pw->pw_name, ctx->runas.pw) == ALLOW)
176
0
        user_matched = m->negated ? DENY : ALLOW;
177
0
    break;
178
0
      case ALIAS:
179
0
    a = alias_get(parse_tree, m->name, RUNASALIAS);
180
0
    if (a != NULL) {
181
0
        const int rc = runas_userlist_matches(parse_tree,
182
0
      &a->members);
183
0
        if (SPECIFIED(rc)) {
184
0
      if (m->negated) {
185
0
          user_matched = rc == ALLOW ? DENY : ALLOW;
186
0
      } else {
187
0
          user_matched = rc;
188
0
      }
189
0
        }
190
0
        alias_put(a);
191
0
        break;
192
0
    }
193
0
    FALLTHROUGH;
194
0
      case WORD:
195
0
    if (userpw_matches(m->name, ctx->runas.pw->pw_name, ctx->runas.pw) == ALLOW)
196
0
        user_matched = m->negated ? DENY : ALLOW;
197
0
    break;
198
0
      case MYSELF:
199
    /*
200
     * Only match a rule with an empty runas user if a group
201
     * was specified on the command line without a user _or_
202
     * the user specified their own name on the command line.
203
     */
204
0
    if ((!ISSET(ctx->settings.flags, RUNAS_USER_SPECIFIED) &&
205
0
      ISSET(ctx->settings.flags, RUNAS_GROUP_SPECIFIED)) ||
206
0
      strcmp(ctx->user.name, ctx->runas.pw->pw_name) == 0)
207
0
        user_matched = m->negated ? DENY : ALLOW;
208
0
    break;
209
0
  }
210
0
  if (SPECIFIED(user_matched))
211
0
      break;
212
0
    }
213
0
    debug_return_int(user_matched);
214
0
}
215
216
/*
217
 * Check whether the requested runas group matches group_list, the
218
 * group portion of a sudoers runaslist, or the runas user's groups.
219
 * Returns ALLOW, DENY or UNSPEC.
220
 */
221
static int
222
runas_grouplist_matches(const struct sudoers_parse_tree *parse_tree,
223
    const struct member_list *group_list, int user_matched)
224
0
{
225
0
    const struct sudoers_context *ctx = parse_tree->ctx;
226
0
    int group_matched = UNSPEC;
227
0
    struct member *m;
228
0
    struct alias *a;
229
0
    debug_decl(runas_grouplist_matches, SUDOERS_DEBUG_MATCH);
230
231
0
    if (group_list != NULL) {
232
0
  TAILQ_FOREACH_REVERSE(m, group_list, member_list, entries) {
233
0
      switch (m->type) {
234
0
    case ALL:
235
0
        group_matched = m->negated ? DENY : ALLOW;
236
0
        break;
237
0
    case ALIAS:
238
0
        a = alias_get(parse_tree, m->name, RUNASALIAS);
239
0
        if (a != NULL) {
240
0
      const int rc = runas_grouplist_matches(parse_tree,
241
0
          &a->members, user_matched);
242
0
      if (SPECIFIED(rc)) {
243
0
          if (m->negated) {
244
0
        group_matched = rc == ALLOW ? DENY : ALLOW;
245
0
          } else {
246
0
        group_matched = rc;
247
0
          }
248
0
      }
249
0
      alias_put(a);
250
0
      break;
251
0
        }
252
0
        FALLTHROUGH;
253
0
    case WORD:
254
0
        if (group_matches(m->name, ctx->runas.gr) == ALLOW)
255
0
      group_matched = m->negated ? DENY : ALLOW;
256
0
        break;
257
0
      }
258
0
      if (SPECIFIED(group_matched))
259
0
    break;
260
0
  }
261
0
    }
262
0
    if (!SPECIFIED(group_matched) && user_matched == ALLOW) {
263
0
  struct gid_list *runas_groups;
264
  /*
265
   * The runas group was not explicitly allowed by sudoers.
266
   * If the runas user matched, check its group list too.
267
   */
268
0
  if (ctx->runas.pw->pw_gid == ctx->runas.gr->gr_gid) {
269
0
      group_matched = ALLOW; /* runas group matches passwd db */
270
0
  } else if ((runas_groups = runas_getgroups(ctx)) != NULL) {
271
0
      int i;
272
273
0
      for (i = 0; i < runas_groups->ngids; i++) {
274
0
    if (runas_groups->gids[i] == ctx->runas.gr->gr_gid) {
275
0
        group_matched = ALLOW; /* matched aux group vector */
276
0
        break;
277
0
    }
278
0
      }
279
0
      sudo_gidlist_delref(runas_groups);
280
0
  }
281
0
    }
282
283
0
    debug_return_int(group_matched);
284
0
}
285
286
/*
287
 * Check whether the sudoers runaslist, composed of user_list and
288
 * group_list, matches the runas user/group requested by the user.
289
 * Either (or both) user_list and group_list may be NULL.
290
 * If user_list is NULL, a list containing runas_default is used.
291
 * Returns ALLOW, DENY or UNSPEC.
292
 */
293
int
294
runaslist_matches(const struct sudoers_parse_tree *parse_tree,
295
    const struct member_list *user_list, const struct member_list *group_list)
296
0
{
297
0
    const struct sudoers_context *ctx = parse_tree->ctx;
298
0
    struct member_list _user_list = TAILQ_HEAD_INITIALIZER(_user_list);
299
0
    int user_matched, group_matched = UNSPEC;
300
0
    struct member m_user;
301
0
    debug_decl(runaslist_matches, SUDOERS_DEBUG_MATCH);
302
303
    /* If no runas user listed in sudoers, use the default value.  */
304
0
    if (user_list == NULL) {
305
0
  m_user.name = def_runas_default;
306
0
  m_user.type = WORD;
307
0
  m_user.negated = false;
308
0
  TAILQ_INSERT_HEAD(&_user_list, &m_user, entries);
309
0
  user_list = &_user_list;
310
0
    }
311
312
0
    user_matched = runas_userlist_matches(parse_tree, user_list);
313
0
    if (ISSET(ctx->settings.flags, RUNAS_GROUP_SPECIFIED)) {
314
0
  group_matched = runas_grouplist_matches(parse_tree, group_list,
315
0
      user_matched);
316
  /*
317
   * Allow "sudo -g group" or "sudo -u myname -g group"
318
   * if the runas group matches.
319
   */
320
0
  if (group_matched == ALLOW && user_matched == UNSPEC) {
321
0
      if (strcmp(ctx->user.name, ctx->runas.pw->pw_name) == 0)
322
0
    user_matched = ALLOW;
323
0
  }
324
0
    }
325
326
0
    if (user_matched == DENY || group_matched == DENY)
327
0
  debug_return_int(DENY);
328
0
    if (user_matched == group_matched || ctx->runas.gr == NULL)
329
0
  debug_return_int(user_matched);
330
0
    debug_return_int(UNSPEC);
331
0
}
332
333
/*
334
 * Check for lhost and shost in a list of members.
335
 * Returns ALLOW, DENY or UNSPEC.
336
 */
337
static int
338
hostlist_matches_int(const struct sudoers_parse_tree *parse_tree,
339
    const struct passwd *pw, const char *lhost, const char *shost,
340
    const struct member_list *list)
341
0
{
342
0
    struct member *m;
343
0
    int matched = UNSPEC;
344
0
    debug_decl(hostlist_matches, SUDOERS_DEBUG_MATCH);
345
346
0
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
347
0
  matched = host_matches(parse_tree, pw, lhost, shost, m);
348
0
  if (SPECIFIED(matched))
349
0
      break;
350
0
    }
351
0
    debug_return_int(matched);
352
0
}
353
354
/*
355
 * Check for ctx->runas.host and ctx->runas.shost in a list of members.
356
 * Returns ALLOW, DENY or UNSPEC.
357
 */
358
int
359
hostlist_matches(const struct sudoers_parse_tree *parse_tree,
360
    const struct passwd *pw, const struct member_list *list)
361
0
{
362
0
    const struct sudoers_context *ctx = parse_tree->ctx;
363
0
    const char *lhost = parse_tree->lhost ? parse_tree->lhost : ctx->runas.host;
364
0
    const char *shost = parse_tree->shost ? parse_tree->shost : ctx->runas.shost;
365
366
0
    return hostlist_matches_int(parse_tree, pw, lhost, shost, list);
367
0
}
368
369
/*
370
 * Check whether host or shost matches member.
371
 * Returns ALLOW, DENY or UNSPEC.
372
 */
373
int
374
host_matches(const struct sudoers_parse_tree *parse_tree,
375
    const struct passwd *pw, const char *lhost, const char *shost,
376
    const struct member *m)
377
0
{
378
0
    struct alias *a;
379
0
    int ret = UNSPEC;
380
0
    debug_decl(host_matches, SUDOERS_DEBUG_MATCH);
381
382
0
    switch (m->type) {
383
0
  case ALL:
384
0
      ret = m->negated ? DENY : ALLOW;
385
0
      break;
386
0
  case NETGROUP:
387
0
      if (netgr_matches(parse_tree->nss, m->name, lhost, shost,
388
0
    def_netgroup_tuple ? pw->pw_name : NULL) == ALLOW)
389
0
    ret = m->negated ? DENY : ALLOW;
390
0
      break;
391
0
  case NTWKADDR:
392
0
      if (addr_matches(m->name) == ALLOW)
393
0
    ret = m->negated ? DENY : ALLOW;
394
0
      break;
395
0
  case ALIAS:
396
0
      a = alias_get(parse_tree, m->name, HOSTALIAS);
397
0
      if (a != NULL) {
398
    /* XXX */
399
0
    const int rc = hostlist_matches_int(parse_tree, pw, lhost,
400
0
        shost, &a->members);
401
0
    if (SPECIFIED(rc)) {
402
0
        if (m->negated) {
403
0
      ret = rc == ALLOW ? DENY : ALLOW;
404
0
        } else {
405
0
      ret = rc;
406
0
        }
407
0
    }
408
0
    alias_put(a);
409
0
    break;
410
0
      }
411
0
      FALLTHROUGH;
412
0
  case WORD:
413
0
      if (hostname_matches(shost, lhost, m->name) == ALLOW)
414
0
    ret = m->negated ? DENY : ALLOW;
415
0
      break;
416
0
    }
417
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG,
418
0
  "host %s (%s) matches sudoers host %s%s: %s", lhost, shost,
419
0
  m->negated ? "!" : "", m->name ? m->name : "ALL",
420
0
  ret == ALLOW ? "ALLOW" : "DENY");
421
0
    debug_return_int(ret);
422
0
}
423
424
/*
425
 * Check for cmnd and args in a list of members.
426
 * Returns ALLOW, DENY or UNSPEC.
427
 */
428
int
429
cmndlist_matches(const struct sudoers_parse_tree *parse_tree,
430
    const struct member_list *list, const char *runchroot,
431
    struct cmnd_info *info)
432
0
{
433
0
    struct member *m;
434
0
    int matched;
435
0
    debug_decl(cmndlist_matches, SUDOERS_DEBUG_MATCH);
436
437
0
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
438
0
  matched = cmnd_matches(parse_tree, m, runchroot, info);
439
0
  if (SPECIFIED(matched))
440
0
      debug_return_int(matched);
441
0
    }
442
0
    debug_return_int(UNSPEC);
443
0
}
444
445
/*
446
 * Check cmnd and args.
447
 * Returns ALLOW, DENY or UNSPEC.
448
 */
449
int
450
cmnd_matches(const struct sudoers_parse_tree *parse_tree,
451
    const struct member *m, const char *runchroot, struct cmnd_info *info)
452
0
{
453
0
    struct alias *a;
454
0
    struct sudo_command *c;
455
0
    int rc, matched = UNSPEC;
456
0
    debug_decl(cmnd_matches, SUDOERS_DEBUG_MATCH);
457
458
0
    switch (m->type) {
459
0
  case ALL:
460
0
  case COMMAND:
461
0
      c = (struct sudo_command *)m->name;
462
0
      if (command_matches(parse_tree->ctx, c->cmnd, c->args, runchroot,
463
0
        info, &c->digests) == ALLOW)
464
0
    matched = m->negated ? DENY : ALLOW;
465
0
      break;
466
0
  case ALIAS:
467
0
      a = alias_get(parse_tree, m->name, CMNDALIAS);
468
0
      if (a != NULL) {
469
0
    rc = cmndlist_matches(parse_tree, &a->members, runchroot, info);
470
0
    if (SPECIFIED(rc)) {
471
0
        if (m->negated) {
472
0
      matched = rc == ALLOW ? DENY : ALLOW;
473
0
        } else {
474
0
      matched = rc;
475
0
        }
476
0
    }
477
0
    alias_put(a);
478
0
      }
479
0
      break;
480
0
    }
481
0
    debug_return_int(matched);
482
0
}
483
484
/*
485
 * Like cmnd_matches() but only matches against the ALL command.
486
 * Returns ALLOW, DENY or UNSPEC.
487
 */
488
int
489
cmnd_matches_all(const struct sudoers_parse_tree *parse_tree,
490
    const struct member *m, const char *runchroot, struct cmnd_info *info)
491
0
{
492
0
    const bool negated = m->negated;
493
0
    struct sudo_command *c;
494
0
    int matched = UNSPEC;
495
0
    struct alias *a;
496
0
    debug_decl(cmnd_matches_all, SUDOERS_DEBUG_MATCH);
497
498
0
    switch (m->type) {
499
0
  case ALL:
500
0
      c = (struct sudo_command *)m->name;
501
0
      if (command_matches(parse_tree->ctx, c->cmnd, c->args, runchroot,
502
0
        info, &c->digests) == ALLOW)
503
0
    matched = negated ? DENY : ALLOW;
504
0
      break;
505
0
  case ALIAS:
506
0
      a = alias_get(parse_tree, m->name, CMNDALIAS);
507
0
      if (a != NULL) {
508
0
    TAILQ_FOREACH_REVERSE(m, &a->members, member_list, entries) {
509
0
        matched = cmnd_matches_all(parse_tree, m, runchroot, info);
510
0
        if (SPECIFIED(matched)) {
511
0
      if (negated)
512
0
          matched = matched == ALLOW ? DENY : ALLOW;
513
0
      break;
514
0
        }
515
0
    }
516
0
    alias_put(a);
517
0
      }
518
0
      break;
519
0
    }
520
0
    debug_return_int(matched);
521
0
}
522
523
/*
524
 * Returns ALLOW if the hostname matches the pattern, else DENY
525
 */
526
int
527
hostname_matches(const char *shost, const char *lhost, const char *pattern)
528
0
{
529
0
    const char *host;
530
0
    int ret;
531
0
    debug_decl(hostname_matches, SUDOERS_DEBUG_MATCH);
532
533
0
    host = strchr(pattern, '.') != NULL ? lhost : shost;
534
0
    ret = DENY;
535
0
    if (has_meta(pattern)) {
536
0
  if (fnmatch(pattern, host, FNM_CASEFOLD) == 0)
537
0
      ret = ALLOW;
538
0
    } else {
539
0
  if (strcasecmp(host, pattern) == 0)
540
0
      ret = ALLOW;
541
0
    }
542
0
    debug_return_int(ret);
543
0
}
544
545
/*
546
 * Returns ALLOW if the user/uid from sudoers matches the specified user/uid,
547
 * else returns DENY.
548
 */
549
int
550
userpw_matches(const char *sudoers_user, const char *user, const struct passwd *pw)
551
0
{
552
0
    const char *errstr;
553
0
    int ret = DENY;
554
0
    uid_t uid;
555
0
    debug_decl(userpw_matches, SUDOERS_DEBUG_MATCH);
556
557
0
    if (pw != NULL && *sudoers_user == '#') {
558
0
  uid = (uid_t) sudo_strtoid(sudoers_user + 1, &errstr);
559
0
  if (errstr == NULL && uid == pw->pw_uid) {
560
0
      ret = ALLOW;
561
0
      goto done;
562
0
  }
563
0
    }
564
0
    if (def_case_insensitive_user) {
565
0
  if (strcasecmp(sudoers_user, user) == 0)
566
0
      ret = ALLOW;
567
0
    } else {
568
0
  if (strcmp(sudoers_user, user) == 0)
569
0
      ret = ALLOW;
570
0
    }
571
0
done:
572
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
573
0
  "user %s matches sudoers user %s: %s",
574
0
  user, sudoers_user, ret == ALLOW ? "ALLOW" : "DENY");
575
0
    debug_return_int(ret);
576
0
}
577
578
/*
579
 * Returns ALLOW if the group/gid from sudoers matches the specified group/gid,
580
 * else returns DENY.
581
 */
582
int
583
group_matches(const char *sudoers_group, const struct group *gr)
584
0
{
585
0
    const char *errstr;
586
0
    int ret = DENY;
587
0
    gid_t gid;
588
0
    debug_decl(group_matches, SUDOERS_DEBUG_MATCH);
589
590
0
    if (*sudoers_group == '#') {
591
0
  gid = (gid_t) sudo_strtoid(sudoers_group + 1, &errstr);
592
0
  if (errstr == NULL && gid == gr->gr_gid) {
593
0
      ret = ALLOW;
594
0
      goto done;
595
0
  }
596
0
    }
597
0
    if (def_case_insensitive_group) {
598
0
  if (strcasecmp(sudoers_group, gr->gr_name) == 0)
599
0
      ret = ALLOW;
600
0
    } else {
601
0
  if (strcmp(sudoers_group, gr->gr_name) == 0)
602
0
      ret = ALLOW;
603
0
    }
604
0
done:
605
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
606
0
  "group %s matches sudoers group %s: %s",
607
0
  gr->gr_name, sudoers_group, ret == ALLOW ? "ALLOW" : "DENY");
608
0
    debug_return_int(ret);
609
0
}
610
611
/*
612
 * Returns true if the given user belongs to the named group,
613
 * else returns false.
614
 */
615
int
616
usergr_matches(const char *group, const char *user, const struct passwd *pw)
617
0
{
618
0
    struct passwd *pw0 = NULL;
619
0
    int ret = DENY;
620
0
    debug_decl(usergr_matches, SUDOERS_DEBUG_MATCH);
621
622
    /* Make sure we have a valid usergroup, sudo style */
623
0
    if (*group++ != '%') {
624
0
  sudo_debug_printf(SUDO_DEBUG_DIAG, "user group %s has no leading '%%'",
625
0
      group);
626
0
  goto done;
627
0
    }
628
629
    /* Query group plugin for %:name groups. */
630
0
    if (*group == ':' && def_group_plugin) {
631
0
  if (group_plugin_query(user, group + 1, pw) == true)
632
0
      ret = ALLOW;
633
0
  goto done;
634
0
    }
635
636
    /* Look up user's primary gid in the passwd file. */
637
0
    if (pw == NULL) {
638
0
  if ((pw0 = sudo_getpwnam(user)) == NULL) {
639
0
      sudo_debug_printf(SUDO_DEBUG_DIAG, "unable to find %s in passwd db",
640
0
    user);
641
0
      goto done;
642
0
  }
643
0
  pw = pw0;
644
0
    }
645
646
0
    if (user_in_group(pw, group)) {
647
0
  ret = ALLOW;
648
0
  goto done;
649
0
    }
650
651
    /* Query the group plugin for Unix groups too? */
652
0
    if (def_group_plugin && def_always_query_group_plugin) {
653
0
  if (group_plugin_query(user, group, pw) == true) {
654
0
      ret = ALLOW;
655
0
      goto done;
656
0
  }
657
0
    }
658
659
0
done:
660
0
    if (pw0 != NULL)
661
0
  sudo_pw_delref(pw0);
662
663
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
664
0
  "user %s matches group %s: %s", user, group,
665
0
  ret == ALLOW ? "ALLOW" : "DENY");
666
0
    debug_return_int(ret);
667
0
}
668
669
#if defined(HAVE_GETDOMAINNAME) || defined(SI_SRPC_DOMAIN)
670
/*
671
 * Check the domain for invalid characters.
672
 * Linux getdomainname(2) returns (none) if no domain is set.
673
 */
674
static bool
675
valid_domain(const char *domain)
676
0
{
677
0
    const char *cp;
678
0
    debug_decl(valid_domain, SUDOERS_DEBUG_MATCH);
679
680
0
    for (cp = domain; *cp != '\0'; cp++) {
681
  /* Check for illegal characters, Linux may use "(none)". */
682
0
  if (*cp == '(' || *cp == ')' || *cp == ',' || *cp == ' ')
683
0
      break;
684
0
    }
685
0
    if (cp == domain || *cp != '\0')
686
0
  debug_return_bool(false);
687
0
    debug_return_bool(true);
688
0
}
689
690
/*
691
 * Get NIS-style domain name and copy from static storage or NULL if none.
692
 */
693
const char *
694
sudo_getdomainname(void)
695
0
{
696
0
    static char *domain;
697
0
    static bool initialized;
698
0
    debug_decl(sudo_getdomainname, SUDOERS_DEBUG_MATCH);
699
700
0
    if (!initialized) {
701
0
  const size_t host_name_max = sudo_host_name_max();
702
0
  int rc;
703
704
0
  domain = malloc(host_name_max + 1);
705
0
  if (domain != NULL) {
706
0
      domain[0] = '\0';
707
# ifdef SI_SRPC_DOMAIN
708
      rc = sysinfo(SI_SRPC_DOMAIN, domain, host_name_max + 1);
709
# else
710
0
      rc = getdomainname(domain, host_name_max + 1);
711
0
# endif
712
0
      if (rc == -1 || !valid_domain(domain)) {
713
    /* Error or invalid domain name. */
714
0
    free(domain);
715
0
    domain = NULL;
716
0
      }
717
0
  } else {
718
      /* XXX - want to pass error back to caller */
719
0
      sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
720
0
    "unable to allocate memory");
721
0
  }
722
0
  initialized = true;
723
0
    }
724
0
    debug_return_str(domain);
725
0
}
726
#else
727
const char *
728
sudo_getdomainname(void)
729
{
730
    debug_decl(sudo_getdomainname, SUDOERS_DEBUG_MATCH);
731
    debug_return_ptr(NULL);
732
}
733
#endif /* HAVE_GETDOMAINNAME || SI_SRPC_DOMAIN */
734
735
/*
736
 * Returns ALLOW if "host" and "user" belong to the netgroup "netgr",
737
 * else return DENY.  Either of "lhost", "shost" or "user" may be NULL
738
 * in which case that argument is not checked...
739
 */
740
int
741
netgr_matches(const struct sudo_nss *nss, const char *netgr,
742
    const char *lhost, const char *shost, const char *user)
743
0
{
744
0
    const char *domain;
745
0
    int ret = DENY;
746
0
    debug_decl(netgr_matches, SUDOERS_DEBUG_MATCH);
747
748
0
    if (!def_use_netgroups) {
749
0
  sudo_debug_printf(SUDO_DEBUG_INFO, "netgroups are disabled");
750
0
  debug_return_int(DENY);
751
0
    }
752
753
    /* make sure we have a valid netgroup, sudo style */
754
0
    if (*netgr++ != '+') {
755
0
  sudo_debug_printf(SUDO_DEBUG_DIAG, "netgroup %s has no leading '+'",
756
0
      netgr);
757
0
  debug_return_int(DENY);
758
0
    }
759
760
    /* get the domain name (if any) */
761
0
    domain = sudo_getdomainname();
762
763
    /* Use nss-specific innetgr() function if available. */
764
0
    if (nss != NULL && nss->innetgr != NULL) {
765
0
  switch (nss->innetgr(nss, netgr, lhost, user, domain)) {
766
0
  case 0:
767
0
      if (lhost != shost) {
768
0
    if (nss->innetgr(nss, netgr, shost, user, domain) == 1)
769
0
        ret = ALLOW;
770
0
      }
771
0
      goto done;
772
0
  case 1:
773
0
      ret = ALLOW;
774
0
      goto done;
775
0
  default:
776
      /* Not supported, use system innetgr(3). */
777
0
      break;
778
0
  }
779
0
    }
780
781
0
#ifdef HAVE_INNETGR
782
    /* Use system innetgr() function. */
783
0
    if (innetgr(netgr, lhost, user, domain) == 1) {
784
0
  ret = ALLOW;
785
0
    } else if (lhost != shost) {
786
0
  if (innetgr(netgr, shost, user, domain) == 1)
787
0
      ret = ALLOW;
788
0
    }
789
#else
790
    sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
791
  "%s: no system netgroup support", __func__);
792
#endif /* HAVE_INNETGR */
793
794
0
done:
795
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
796
0
  "netgroup %s matches (%s|%s, %s, %s): %s", netgr, lhost ? lhost : "",
797
0
  shost ? shost : "", user ? user : "", domain ? domain : "",
798
0
  ret == ALLOW ? "ALLOW" : "DENY");
799
800
0
    debug_return_int(ret);
801
0
}