Coverage Report

Created: 2026-10-17 06:18

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
    const char *user = NULL;
381
0
    debug_decl(host_matches, SUDOERS_DEBUG_MATCH);
382
383
0
    switch (m->type) {
384
0
  case ALL:
385
0
      ret = m->negated ? DENY : ALLOW;
386
0
      break;
387
0
  case NETGROUP:
388
0
      if (def_netgroup_tuple && pw != NULL)
389
0
    user = pw->pw_name;
390
0
      if (netgr_matches(parse_tree->nss, m->name, lhost, shost,
391
0
        user) == ALLOW) {
392
0
    ret = m->negated ? DENY : ALLOW;
393
0
      }
394
0
      break;
395
0
  case NTWKADDR:
396
0
      if (addr_matches(m->name) == ALLOW)
397
0
    ret = m->negated ? DENY : ALLOW;
398
0
      break;
399
0
  case ALIAS:
400
0
      a = alias_get(parse_tree, m->name, HOSTALIAS);
401
0
      if (a != NULL) {
402
    /* XXX */
403
0
    const int rc = hostlist_matches_int(parse_tree, pw, lhost,
404
0
        shost, &a->members);
405
0
    if (SPECIFIED(rc)) {
406
0
        if (m->negated) {
407
0
      ret = rc == ALLOW ? DENY : ALLOW;
408
0
        } else {
409
0
      ret = rc;
410
0
        }
411
0
    }
412
0
    alias_put(a);
413
0
    break;
414
0
      }
415
0
      FALLTHROUGH;
416
0
  case WORD:
417
0
      if (hostname_matches(shost, lhost, m->name) == ALLOW)
418
0
    ret = m->negated ? DENY : ALLOW;
419
0
      break;
420
0
    }
421
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG,
422
0
  "host %s (%s) matches sudoers host %s%s: %s", lhost, shost,
423
0
  m->negated ? "!" : "", m->name ? m->name : "ALL",
424
0
  ret == ALLOW ? "ALLOW" : "DENY");
425
0
    debug_return_int(ret);
426
0
}
427
428
/*
429
 * Check for cmnd and args in a list of members.
430
 * Returns ALLOW, DENY or UNSPEC.
431
 */
432
int
433
cmndlist_matches(const struct sudoers_parse_tree *parse_tree,
434
    const struct member_list *list, const char *runchroot,
435
    struct cmnd_info *info)
436
0
{
437
0
    struct member *m;
438
0
    int matched;
439
0
    debug_decl(cmndlist_matches, SUDOERS_DEBUG_MATCH);
440
441
0
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
442
0
  matched = cmnd_matches(parse_tree, m, runchroot, info);
443
0
  if (SPECIFIED(matched))
444
0
      debug_return_int(matched);
445
0
    }
446
0
    debug_return_int(UNSPEC);
447
0
}
448
449
/*
450
 * Check cmnd and args.
451
 * Returns ALLOW, DENY or UNSPEC.
452
 */
453
int
454
cmnd_matches(const struct sudoers_parse_tree *parse_tree,
455
    const struct member *m, const char *runchroot, struct cmnd_info *info)
456
0
{
457
0
    struct alias *a;
458
0
    struct sudo_command *c;
459
0
    int rc, matched = UNSPEC;
460
0
    debug_decl(cmnd_matches, SUDOERS_DEBUG_MATCH);
461
462
0
    switch (m->type) {
463
0
  case ALL:
464
0
  case COMMAND:
465
0
      c = (struct sudo_command *)m->name;
466
0
      if (command_matches(parse_tree->ctx, c->cmnd, c->args, runchroot,
467
0
        info, &c->digests) == ALLOW)
468
0
    matched = m->negated ? DENY : ALLOW;
469
0
      break;
470
0
  case ALIAS:
471
0
      a = alias_get(parse_tree, m->name, CMNDALIAS);
472
0
      if (a != NULL) {
473
0
    rc = cmndlist_matches(parse_tree, &a->members, runchroot, info);
474
0
    if (SPECIFIED(rc)) {
475
0
        if (m->negated) {
476
0
      matched = rc == ALLOW ? DENY : ALLOW;
477
0
        } else {
478
0
      matched = rc;
479
0
        }
480
0
    }
481
0
    alias_put(a);
482
0
      }
483
0
      break;
484
0
    }
485
0
    debug_return_int(matched);
486
0
}
487
488
/*
489
 * Like cmnd_matches() but only matches against the ALL command.
490
 * Returns ALLOW, DENY or UNSPEC.
491
 */
492
int
493
cmnd_matches_all(const struct sudoers_parse_tree *parse_tree,
494
    const struct member *m, const char *runchroot, struct cmnd_info *info)
495
0
{
496
0
    const bool negated = m->negated;
497
0
    struct sudo_command *c;
498
0
    int matched = UNSPEC;
499
0
    struct alias *a;
500
0
    debug_decl(cmnd_matches_all, SUDOERS_DEBUG_MATCH);
501
502
0
    switch (m->type) {
503
0
  case ALL:
504
0
      c = (struct sudo_command *)m->name;
505
0
      if (command_matches(parse_tree->ctx, c->cmnd, c->args, runchroot,
506
0
        info, &c->digests) == ALLOW)
507
0
    matched = negated ? DENY : ALLOW;
508
0
      break;
509
0
  case ALIAS:
510
0
      a = alias_get(parse_tree, m->name, CMNDALIAS);
511
0
      if (a != NULL) {
512
0
    TAILQ_FOREACH_REVERSE(m, &a->members, member_list, entries) {
513
0
        matched = cmnd_matches_all(parse_tree, m, runchroot, info);
514
0
        if (SPECIFIED(matched)) {
515
0
      if (negated)
516
0
          matched = matched == ALLOW ? DENY : ALLOW;
517
0
      break;
518
0
        }
519
0
    }
520
0
    alias_put(a);
521
0
      }
522
0
      break;
523
0
    }
524
0
    debug_return_int(matched);
525
0
}
526
527
/*
528
 * Returns ALLOW if the hostname matches the pattern, else DENY
529
 */
530
int
531
hostname_matches(const char *shost, const char *lhost, const char *pattern)
532
0
{
533
0
    const char *host;
534
0
    int ret;
535
0
    debug_decl(hostname_matches, SUDOERS_DEBUG_MATCH);
536
537
0
    host = strchr(pattern, '.') != NULL ? lhost : shost;
538
0
    ret = DENY;
539
0
    if (has_meta(pattern)) {
540
0
  if (fnmatch(pattern, host, FNM_CASEFOLD) == 0)
541
0
      ret = ALLOW;
542
0
    } else {
543
0
  if (strcasecmp(host, pattern) == 0)
544
0
      ret = ALLOW;
545
0
    }
546
0
    debug_return_int(ret);
547
0
}
548
549
/*
550
 * Returns ALLOW if the user/uid from sudoers matches the specified user/uid,
551
 * else returns DENY.
552
 */
553
int
554
userpw_matches(const char *sudoers_user, const char *user, const struct passwd *pw)
555
0
{
556
0
    const char *errstr;
557
0
    int ret = DENY;
558
0
    uid_t uid;
559
0
    debug_decl(userpw_matches, SUDOERS_DEBUG_MATCH);
560
561
0
    if (pw != NULL && *sudoers_user == '#') {
562
0
  uid = (uid_t) sudo_strtoid(sudoers_user + 1, &errstr);
563
0
  if (errstr == NULL && uid == pw->pw_uid) {
564
0
      ret = ALLOW;
565
0
      goto done;
566
0
  }
567
0
    }
568
0
    if (def_case_insensitive_user) {
569
0
  if (strcasecmp(sudoers_user, user) == 0)
570
0
      ret = ALLOW;
571
0
    } else {
572
0
  if (strcmp(sudoers_user, user) == 0)
573
0
      ret = ALLOW;
574
0
    }
575
0
done:
576
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
577
0
  "user %s matches sudoers user %s: %s",
578
0
  user, sudoers_user, ret == ALLOW ? "ALLOW" : "DENY");
579
0
    debug_return_int(ret);
580
0
}
581
582
/*
583
 * Returns ALLOW if the group/gid from sudoers matches the specified group/gid,
584
 * else returns DENY.
585
 */
586
int
587
group_matches(const char *sudoers_group, const struct group *gr)
588
0
{
589
0
    const char *errstr;
590
0
    int ret = DENY;
591
0
    gid_t gid;
592
0
    debug_decl(group_matches, SUDOERS_DEBUG_MATCH);
593
594
0
    if (*sudoers_group == '#') {
595
0
  gid = (gid_t) sudo_strtoid(sudoers_group + 1, &errstr);
596
0
  if (errstr == NULL && gid == gr->gr_gid) {
597
0
      ret = ALLOW;
598
0
      goto done;
599
0
  }
600
0
    }
601
0
    if (def_case_insensitive_group) {
602
0
  if (strcasecmp(sudoers_group, gr->gr_name) == 0)
603
0
      ret = ALLOW;
604
0
    } else {
605
0
  if (strcmp(sudoers_group, gr->gr_name) == 0)
606
0
      ret = ALLOW;
607
0
    }
608
0
done:
609
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
610
0
  "group %s matches sudoers group %s: %s",
611
0
  gr->gr_name, sudoers_group, ret == ALLOW ? "ALLOW" : "DENY");
612
0
    debug_return_int(ret);
613
0
}
614
615
/*
616
 * Returns true if the given user belongs to the named group,
617
 * else returns false.
618
 */
619
int
620
usergr_matches(const char *group, const char *user, const struct passwd *pw)
621
0
{
622
0
    struct passwd *pw0 = NULL;
623
0
    int ret = DENY;
624
0
    debug_decl(usergr_matches, SUDOERS_DEBUG_MATCH);
625
626
    /* Make sure we have a valid usergroup, sudo style */
627
0
    if (*group++ != '%') {
628
0
  sudo_debug_printf(SUDO_DEBUG_DIAG, "user group %s has no leading '%%'",
629
0
      group);
630
0
  goto done;
631
0
    }
632
633
    /* Query group plugin for %:name groups. */
634
0
    if (*group == ':' && def_group_plugin) {
635
0
  if (group_plugin_query(user, group + 1, pw) == true)
636
0
      ret = ALLOW;
637
0
  goto done;
638
0
    }
639
640
    /* Look up user's primary gid in the passwd file. */
641
0
    if (pw == NULL) {
642
0
  if ((pw0 = sudo_getpwnam(user)) == NULL) {
643
0
      sudo_debug_printf(SUDO_DEBUG_DIAG, "unable to find %s in passwd db",
644
0
    user);
645
0
      goto done;
646
0
  }
647
0
  pw = pw0;
648
0
    }
649
650
0
    if (user_in_group(pw, group)) {
651
0
  ret = ALLOW;
652
0
  goto done;
653
0
    }
654
655
    /* Query the group plugin for Unix groups too? */
656
0
    if (def_group_plugin && def_always_query_group_plugin) {
657
0
  if (group_plugin_query(user, group, pw) == true) {
658
0
      ret = ALLOW;
659
0
      goto done;
660
0
  }
661
0
    }
662
663
0
done:
664
0
    if (pw0 != NULL)
665
0
  sudo_pw_delref(pw0);
666
667
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
668
0
  "user %s matches group %s: %s", user, group,
669
0
  ret == ALLOW ? "ALLOW" : "DENY");
670
0
    debug_return_int(ret);
671
0
}
672
673
#if defined(HAVE_GETDOMAINNAME) || defined(SI_SRPC_DOMAIN)
674
/*
675
 * Check the domain for invalid characters.
676
 * Linux getdomainname(2) returns (none) if no domain is set.
677
 */
678
static bool
679
valid_domain(const char *domain)
680
0
{
681
0
    const char *cp;
682
0
    debug_decl(valid_domain, SUDOERS_DEBUG_MATCH);
683
684
0
    for (cp = domain; *cp != '\0'; cp++) {
685
  /* Check for illegal characters, Linux may use "(none)". */
686
0
  if (*cp == '(' || *cp == ')' || *cp == ',' || *cp == ' ')
687
0
      break;
688
0
    }
689
0
    if (cp == domain || *cp != '\0')
690
0
  debug_return_bool(false);
691
0
    debug_return_bool(true);
692
0
}
693
694
/*
695
 * Get NIS-style domain name and copy from static storage or NULL if none.
696
 */
697
const char *
698
sudo_getdomainname(void)
699
0
{
700
0
    static char *domain;
701
0
    static bool initialized;
702
0
    debug_decl(sudo_getdomainname, SUDOERS_DEBUG_MATCH);
703
704
0
    if (!initialized) {
705
0
  const size_t host_name_max = sudo_host_name_max();
706
0
  int rc;
707
708
0
  domain = malloc(host_name_max + 1);
709
0
  if (domain != NULL) {
710
0
      domain[0] = '\0';
711
# ifdef SI_SRPC_DOMAIN
712
      rc = sysinfo(SI_SRPC_DOMAIN, domain, host_name_max + 1);
713
# else
714
0
      rc = getdomainname(domain, host_name_max + 1);
715
0
# endif
716
0
      if (rc == -1 || !valid_domain(domain)) {
717
    /* Error or invalid domain name. */
718
0
    free(domain);
719
0
    domain = NULL;
720
0
      }
721
0
  } else {
722
      /* XXX - want to pass error back to caller */
723
0
      sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
724
0
    "unable to allocate memory");
725
0
  }
726
0
  initialized = true;
727
0
    }
728
0
    debug_return_str(domain);
729
0
}
730
#else
731
const char *
732
sudo_getdomainname(void)
733
{
734
    debug_decl(sudo_getdomainname, SUDOERS_DEBUG_MATCH);
735
    debug_return_ptr(NULL);
736
}
737
#endif /* HAVE_GETDOMAINNAME || SI_SRPC_DOMAIN */
738
739
/*
740
 * Returns ALLOW if "host" and "user" belong to the netgroup "netgr",
741
 * else return DENY.  Either of "lhost", "shost" or "user" may be NULL
742
 * in which case that argument is not checked...
743
 */
744
int
745
netgr_matches(const struct sudo_nss *nss, const char *netgr,
746
    const char *lhost, const char *shost, const char *user)
747
0
{
748
0
    const char *domain;
749
0
    int ret = DENY;
750
0
    debug_decl(netgr_matches, SUDOERS_DEBUG_MATCH);
751
752
0
    if (!def_use_netgroups) {
753
0
  sudo_debug_printf(SUDO_DEBUG_INFO, "netgroups are disabled");
754
0
  debug_return_int(DENY);
755
0
    }
756
757
    /* make sure we have a valid netgroup, sudo style */
758
0
    if (*netgr++ != '+') {
759
0
  sudo_debug_printf(SUDO_DEBUG_DIAG, "netgroup %s has no leading '+'",
760
0
      netgr);
761
0
  debug_return_int(DENY);
762
0
    }
763
764
    /* get the domain name (if any) */
765
0
    domain = sudo_getdomainname();
766
767
    /* Use nss-specific innetgr() function if available. */
768
0
    if (nss != NULL && nss->innetgr != NULL) {
769
0
  switch (nss->innetgr(nss, netgr, lhost, user, domain)) {
770
0
  case 0:
771
0
      if (lhost != shost) {
772
0
    if (nss->innetgr(nss, netgr, shost, user, domain) == 1)
773
0
        ret = ALLOW;
774
0
      }
775
0
      goto done;
776
0
  case 1:
777
0
      ret = ALLOW;
778
0
      goto done;
779
0
  default:
780
      /* Not supported, use system innetgr(3). */
781
0
      break;
782
0
  }
783
0
    }
784
785
0
#ifdef HAVE_INNETGR
786
    /* Use system innetgr() function. */
787
0
    if (innetgr(netgr, lhost, user, domain) == 1) {
788
0
  ret = ALLOW;
789
0
    } else if (lhost != shost) {
790
0
  if (innetgr(netgr, shost, user, domain) == 1)
791
0
      ret = ALLOW;
792
0
    }
793
#else
794
    sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
795
  "%s: no system netgroup support", __func__);
796
#endif /* HAVE_INNETGR */
797
798
0
done:
799
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
800
0
  "netgroup %s matches (%s|%s, %s, %s): %s", netgr, lhost ? lhost : "",
801
0
  shost ? shost : "", user ? user : "", domain ? domain : "",
802
0
  ret == ALLOW ? "ALLOW" : "DENY");
803
804
0
    debug_return_int(ret);
805
0
}