Coverage Report

Created: 2025-10-28 06:14

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
    }
211
0
    debug_return_int(user_matched);
212
0
}
213
214
/*
215
 * Check whether the requested runas group matches group_list, the
216
 * group portion of a sudoers runaslist, or the runas user's groups.
217
 * Returns ALLOW, DENY or UNSPEC.
218
 */
219
static int
220
runas_grouplist_matches(const struct sudoers_parse_tree *parse_tree,
221
    const struct member_list *group_list, int user_matched)
222
0
{
223
0
    const struct sudoers_context *ctx = parse_tree->ctx;
224
0
    int group_matched = UNSPEC;
225
0
    struct member *m;
226
0
    struct alias *a;
227
0
    debug_decl(runas_grouplist_matches, SUDOERS_DEBUG_MATCH);
228
229
0
    if (group_list != NULL) {
230
0
  TAILQ_FOREACH_REVERSE(m, group_list, member_list, entries) {
231
0
      switch (m->type) {
232
0
    case ALL:
233
0
        group_matched = m->negated ? DENY : ALLOW;
234
0
        break;
235
0
    case ALIAS:
236
0
        a = alias_get(parse_tree, m->name, RUNASALIAS);
237
0
        if (a != NULL) {
238
0
      const int rc = runas_grouplist_matches(parse_tree,
239
0
          &a->members, user_matched);
240
0
      if (SPECIFIED(rc)) {
241
0
          if (m->negated) {
242
0
        group_matched = rc == ALLOW ? DENY : ALLOW;
243
0
          } else {
244
0
        group_matched = rc;
245
0
          }
246
0
      }
247
0
      alias_put(a);
248
0
      break;
249
0
        }
250
0
        FALLTHROUGH;
251
0
    case WORD:
252
0
        if (group_matches(m->name, ctx->runas.gr) == ALLOW)
253
0
      group_matched = m->negated ? DENY : ALLOW;
254
0
        break;
255
0
      }
256
0
  }
257
0
    }
258
0
    if (!SPECIFIED(group_matched) && user_matched == ALLOW) {
259
0
  struct gid_list *runas_groups;
260
  /*
261
   * The runas group was not explicitly allowed by sudoers.
262
   * If the runas user matched, check its group list too.
263
   */
264
0
  if (ctx->runas.pw->pw_gid == ctx->runas.gr->gr_gid) {
265
0
      group_matched = ALLOW; /* runas group matches passwd db */
266
0
  } else if ((runas_groups = runas_getgroups(ctx)) != NULL) {
267
0
      int i;
268
269
0
      for (i = 0; i < runas_groups->ngids; i++) {
270
0
    if (runas_groups->gids[i] == ctx->runas.gr->gr_gid) {
271
0
        group_matched = ALLOW; /* matched aux group vector */
272
0
        break;
273
0
    }
274
0
      }
275
0
      sudo_gidlist_delref(runas_groups);
276
0
  }
277
0
    }
278
279
0
    debug_return_int(group_matched);
280
0
}
281
282
/*
283
 * Check whether the sudoers runaslist, composed of user_list and
284
 * group_list, matches the runas user/group requested by the user.
285
 * Either (or both) user_list and group_list may be NULL.
286
 * If user_list is NULL, a list containing runas_default is used.
287
 * Returns ALLOW, DENY or UNSPEC.
288
 */
289
int
290
runaslist_matches(const struct sudoers_parse_tree *parse_tree,
291
    const struct member_list *user_list, const struct member_list *group_list)
292
0
{
293
0
    const struct sudoers_context *ctx = parse_tree->ctx;
294
0
    struct member_list _user_list = TAILQ_HEAD_INITIALIZER(_user_list);
295
0
    int user_matched, group_matched = UNSPEC;
296
0
    struct member m_user;
297
0
    debug_decl(runaslist_matches, SUDOERS_DEBUG_MATCH);
298
299
    /* If no runas user listed in sudoers, use the default value.  */
300
0
    if (user_list == NULL) {
301
0
  m_user.name = def_runas_default;
302
0
  m_user.type = WORD;
303
0
  m_user.negated = false;
304
0
  TAILQ_INSERT_HEAD(&_user_list, &m_user, entries);
305
0
  user_list = &_user_list;
306
0
    }
307
308
0
    user_matched = runas_userlist_matches(parse_tree, user_list);
309
0
    if (ISSET(ctx->settings.flags, RUNAS_GROUP_SPECIFIED)) {
310
0
  group_matched = runas_grouplist_matches(parse_tree, group_list,
311
0
      user_matched);
312
  /*
313
   * Allow "sudo -g group" or "sudo -u myname -g group"
314
   * if the runas group matches.
315
   */
316
0
  if (group_matched == ALLOW && user_matched == UNSPEC) {
317
0
      if (strcmp(ctx->user.name, ctx->runas.pw->pw_name) == 0)
318
0
    user_matched = ALLOW;
319
0
  }
320
0
    }
321
322
0
    if (user_matched == DENY || group_matched == DENY)
323
0
  debug_return_int(DENY);
324
0
    if (user_matched == group_matched || ctx->runas.gr == NULL)
325
0
  debug_return_int(user_matched);
326
0
    debug_return_int(UNSPEC);
327
0
}
328
329
/*
330
 * Check for lhost and shost in a list of members.
331
 * Returns ALLOW, DENY or UNSPEC.
332
 */
333
static int
334
hostlist_matches_int(const struct sudoers_parse_tree *parse_tree,
335
    const struct passwd *pw, const char *lhost, const char *shost,
336
    const struct member_list *list)
337
0
{
338
0
    struct member *m;
339
0
    int matched = UNSPEC;
340
0
    debug_decl(hostlist_matches, SUDOERS_DEBUG_MATCH);
341
342
0
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
343
0
  matched = host_matches(parse_tree, pw, lhost, shost, m);
344
0
  if (SPECIFIED(matched))
345
0
      break;
346
0
    }
347
0
    debug_return_int(matched);
348
0
}
349
350
/*
351
 * Check for ctx->runas.host and ctx->runas.shost in a list of members.
352
 * Returns ALLOW, DENY or UNSPEC.
353
 */
354
int
355
hostlist_matches(const struct sudoers_parse_tree *parse_tree,
356
    const struct passwd *pw, const struct member_list *list)
357
0
{
358
0
    const struct sudoers_context *ctx = parse_tree->ctx;
359
0
    const char *lhost = parse_tree->lhost ? parse_tree->lhost : ctx->runas.host;
360
0
    const char *shost = parse_tree->shost ? parse_tree->shost : ctx->runas.shost;
361
362
0
    return hostlist_matches_int(parse_tree, pw, lhost, shost, list);
363
0
}
364
365
/*
366
 * Check whether host or shost matches member.
367
 * Returns ALLOW, DENY or UNSPEC.
368
 */
369
int
370
host_matches(const struct sudoers_parse_tree *parse_tree,
371
    const struct passwd *pw, const char *lhost, const char *shost,
372
    const struct member *m)
373
0
{
374
0
    struct alias *a;
375
0
    int ret = UNSPEC;
376
0
    debug_decl(host_matches, SUDOERS_DEBUG_MATCH);
377
378
0
    switch (m->type) {
379
0
  case ALL:
380
0
      ret = m->negated ? DENY : ALLOW;
381
0
      break;
382
0
  case NETGROUP:
383
0
      if (netgr_matches(parse_tree->nss, m->name, lhost, shost,
384
0
    def_netgroup_tuple ? pw->pw_name : NULL) == ALLOW)
385
0
    ret = m->negated ? DENY : ALLOW;
386
0
      break;
387
0
  case NTWKADDR:
388
0
      if (addr_matches(m->name) == ALLOW)
389
0
    ret = m->negated ? DENY : ALLOW;
390
0
      break;
391
0
  case ALIAS:
392
0
      a = alias_get(parse_tree, m->name, HOSTALIAS);
393
0
      if (a != NULL) {
394
    /* XXX */
395
0
    const int rc = hostlist_matches_int(parse_tree, pw, lhost,
396
0
        shost, &a->members);
397
0
    if (SPECIFIED(rc)) {
398
0
        if (m->negated) {
399
0
      ret = rc == ALLOW ? DENY : ALLOW;
400
0
        } else {
401
0
      ret = rc;
402
0
        }
403
0
    }
404
0
    alias_put(a);
405
0
    break;
406
0
      }
407
0
      FALLTHROUGH;
408
0
  case WORD:
409
0
      if (hostname_matches(shost, lhost, m->name) == ALLOW)
410
0
    ret = m->negated ? DENY : ALLOW;
411
0
      break;
412
0
    }
413
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG,
414
0
  "host %s (%s) matches sudoers host %s%s: %s", lhost, shost,
415
0
  m->negated ? "!" : "", m->name ? m->name : "ALL",
416
0
  ret == ALLOW ? "ALLOW" : "DENY");
417
0
    debug_return_int(ret);
418
0
}
419
420
/*
421
 * Check for cmnd and args in a list of members.
422
 * Returns ALLOW, DENY or UNSPEC.
423
 */
424
int
425
cmndlist_matches(const struct sudoers_parse_tree *parse_tree,
426
    const struct member_list *list, const char *runchroot,
427
    struct cmnd_info *info)
428
0
{
429
0
    struct member *m;
430
0
    int matched;
431
0
    debug_decl(cmndlist_matches, SUDOERS_DEBUG_MATCH);
432
433
0
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
434
0
  matched = cmnd_matches(parse_tree, m, runchroot, info);
435
0
  if (SPECIFIED(matched))
436
0
      debug_return_int(matched);
437
0
    }
438
0
    debug_return_int(UNSPEC);
439
0
}
440
441
/*
442
 * Check cmnd and args.
443
 * Returns ALLOW, DENY or UNSPEC.
444
 */
445
int
446
cmnd_matches(const struct sudoers_parse_tree *parse_tree,
447
    const struct member *m, const char *runchroot, struct cmnd_info *info)
448
0
{
449
0
    struct alias *a;
450
0
    struct sudo_command *c;
451
0
    int rc, matched = UNSPEC;
452
0
    debug_decl(cmnd_matches, SUDOERS_DEBUG_MATCH);
453
454
0
    switch (m->type) {
455
0
  case ALL:
456
0
  case COMMAND:
457
0
      c = (struct sudo_command *)m->name;
458
0
      if (command_matches(parse_tree->ctx, c->cmnd, c->args, runchroot,
459
0
        info, &c->digests) == ALLOW)
460
0
    matched = m->negated ? DENY : ALLOW;
461
0
      break;
462
0
  case ALIAS:
463
0
      a = alias_get(parse_tree, m->name, CMNDALIAS);
464
0
      if (a != NULL) {
465
0
    rc = cmndlist_matches(parse_tree, &a->members, runchroot, info);
466
0
    if (SPECIFIED(rc)) {
467
0
        if (m->negated) {
468
0
      matched = rc == ALLOW ? DENY : ALLOW;
469
0
        } else {
470
0
      matched = rc;
471
0
        }
472
0
    }
473
0
    alias_put(a);
474
0
      }
475
0
      break;
476
0
    }
477
0
    debug_return_int(matched);
478
0
}
479
480
/*
481
 * Like cmnd_matches() but only matches against the ALL command.
482
 * Returns ALLOW, DENY or UNSPEC.
483
 */
484
int
485
cmnd_matches_all(const struct sudoers_parse_tree *parse_tree,
486
    const struct member *m, const char *runchroot, struct cmnd_info *info)
487
0
{
488
0
    const bool negated = m->negated;
489
0
    struct sudo_command *c;
490
0
    int matched = UNSPEC;
491
0
    struct alias *a;
492
0
    debug_decl(cmnd_matches_all, SUDOERS_DEBUG_MATCH);
493
494
0
    switch (m->type) {
495
0
  case ALL:
496
0
      c = (struct sudo_command *)m->name;
497
0
      if (command_matches(parse_tree->ctx, c->cmnd, c->args, runchroot,
498
0
        info, &c->digests) == ALLOW)
499
0
    matched = negated ? DENY : ALLOW;
500
0
      break;
501
0
  case ALIAS:
502
0
      a = alias_get(parse_tree, m->name, CMNDALIAS);
503
0
      if (a != NULL) {
504
0
    TAILQ_FOREACH_REVERSE(m, &a->members, member_list, entries) {
505
0
        matched = cmnd_matches_all(parse_tree, m, runchroot, info);
506
0
        if (SPECIFIED(matched)) {
507
0
      if (negated)
508
0
          matched = matched == ALLOW ? DENY : ALLOW;
509
0
      break;
510
0
        }
511
0
    }
512
0
    alias_put(a);
513
0
      }
514
0
      break;
515
0
    }
516
0
    debug_return_int(matched);
517
0
}
518
519
/*
520
 * Returns ALLOW if the hostname matches the pattern, else DENY
521
 */
522
int
523
hostname_matches(const char *shost, const char *lhost, const char *pattern)
524
0
{
525
0
    const char *host;
526
0
    int ret;
527
0
    debug_decl(hostname_matches, SUDOERS_DEBUG_MATCH);
528
529
0
    host = strchr(pattern, '.') != NULL ? lhost : shost;
530
0
    ret = DENY;
531
0
    if (has_meta(pattern)) {
532
0
  if (fnmatch(pattern, host, FNM_CASEFOLD) == 0)
533
0
      ret = ALLOW;
534
0
    } else {
535
0
  if (strcasecmp(host, pattern) == 0)
536
0
      ret = ALLOW;
537
0
    }
538
0
    debug_return_int(ret);
539
0
}
540
541
/*
542
 * Returns ALLOW if the user/uid from sudoers matches the specified user/uid,
543
 * else returns DENY.
544
 */
545
int
546
userpw_matches(const char *sudoers_user, const char *user, const struct passwd *pw)
547
0
{
548
0
    const char *errstr;
549
0
    int ret = DENY;
550
0
    uid_t uid;
551
0
    debug_decl(userpw_matches, SUDOERS_DEBUG_MATCH);
552
553
0
    if (pw != NULL && *sudoers_user == '#') {
554
0
  uid = (uid_t) sudo_strtoid(sudoers_user + 1, &errstr);
555
0
  if (errstr == NULL && uid == pw->pw_uid) {
556
0
      ret = ALLOW;
557
0
      goto done;
558
0
  }
559
0
    }
560
0
    if (def_case_insensitive_user) {
561
0
  if (strcasecmp(sudoers_user, user) == 0)
562
0
      ret = ALLOW;
563
0
    } else {
564
0
  if (strcmp(sudoers_user, user) == 0)
565
0
      ret = ALLOW;
566
0
    }
567
0
done:
568
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
569
0
  "user %s matches sudoers user %s: %s",
570
0
  user, sudoers_user, ret == ALLOW ? "ALLOW" : "DENY");
571
0
    debug_return_int(ret);
572
0
}
573
574
/*
575
 * Returns ALLOW if the group/gid from sudoers matches the specified group/gid,
576
 * else returns DENY.
577
 */
578
int
579
group_matches(const char *sudoers_group, const struct group *gr)
580
0
{
581
0
    const char *errstr;
582
0
    int ret = DENY;
583
0
    gid_t gid;
584
0
    debug_decl(group_matches, SUDOERS_DEBUG_MATCH);
585
586
0
    if (*sudoers_group == '#') {
587
0
  gid = (gid_t) sudo_strtoid(sudoers_group + 1, &errstr);
588
0
  if (errstr == NULL && gid == gr->gr_gid) {
589
0
      ret = ALLOW;
590
0
      goto done;
591
0
  }
592
0
    }
593
0
    if (def_case_insensitive_group) {
594
0
  if (strcasecmp(sudoers_group, gr->gr_name) == 0)
595
0
      ret = ALLOW;
596
0
    } else {
597
0
  if (strcmp(sudoers_group, gr->gr_name) == 0)
598
0
      ret = ALLOW;
599
0
    }
600
0
done:
601
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
602
0
  "group %s matches sudoers group %s: %s",
603
0
  gr->gr_name, sudoers_group, ret == ALLOW ? "ALLOW" : "DENY");
604
0
    debug_return_int(ret);
605
0
}
606
607
/*
608
 * Returns true if the given user belongs to the named group,
609
 * else returns false.
610
 */
611
int
612
usergr_matches(const char *group, const char *user, const struct passwd *pw)
613
0
{
614
0
    struct passwd *pw0 = NULL;
615
0
    int ret = DENY;
616
0
    debug_decl(usergr_matches, SUDOERS_DEBUG_MATCH);
617
618
    /* Make sure we have a valid usergroup, sudo style */
619
0
    if (*group++ != '%') {
620
0
  sudo_debug_printf(SUDO_DEBUG_DIAG, "user group %s has no leading '%%'",
621
0
      group);
622
0
  goto done;
623
0
    }
624
625
    /* Query group plugin for %:name groups. */
626
0
    if (*group == ':' && def_group_plugin) {
627
0
  if (group_plugin_query(user, group + 1, pw) == true)
628
0
      ret = ALLOW;
629
0
  goto done;
630
0
    }
631
632
    /* Look up user's primary gid in the passwd file. */
633
0
    if (pw == NULL) {
634
0
  if ((pw0 = sudo_getpwnam(user)) == NULL) {
635
0
      sudo_debug_printf(SUDO_DEBUG_DIAG, "unable to find %s in passwd db",
636
0
    user);
637
0
      goto done;
638
0
  }
639
0
  pw = pw0;
640
0
    }
641
642
0
    if (user_in_group(pw, group)) {
643
0
  ret = ALLOW;
644
0
  goto done;
645
0
    }
646
647
    /* Query the group plugin for Unix groups too? */
648
0
    if (def_group_plugin && def_always_query_group_plugin) {
649
0
  if (group_plugin_query(user, group, pw) == true) {
650
0
      ret = ALLOW;
651
0
      goto done;
652
0
  }
653
0
    }
654
655
0
done:
656
0
    if (pw0 != NULL)
657
0
  sudo_pw_delref(pw0);
658
659
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
660
0
  "user %s matches group %s: %s", user, group,
661
0
  ret == ALLOW ? "ALLOW" : "DENY");
662
0
    debug_return_int(ret);
663
0
}
664
665
#if defined(HAVE_GETDOMAINNAME) || defined(SI_SRPC_DOMAIN)
666
/*
667
 * Check the domain for invalid characters.
668
 * Linux getdomainname(2) returns (none) if no domain is set.
669
 */
670
static bool
671
valid_domain(const char *domain)
672
0
{
673
0
    const char *cp;
674
0
    debug_decl(valid_domain, SUDOERS_DEBUG_MATCH);
675
676
0
    for (cp = domain; *cp != '\0'; cp++) {
677
  /* Check for illegal characters, Linux may use "(none)". */
678
0
  if (*cp == '(' || *cp == ')' || *cp == ',' || *cp == ' ')
679
0
      break;
680
0
    }
681
0
    if (cp == domain || *cp != '\0')
682
0
  debug_return_bool(false);
683
0
    debug_return_bool(true);
684
0
}
685
686
/*
687
 * Get NIS-style domain name and copy from static storage or NULL if none.
688
 */
689
const char *
690
sudo_getdomainname(void)
691
0
{
692
0
    static char *domain;
693
0
    static bool initialized;
694
0
    debug_decl(sudo_getdomainname, SUDOERS_DEBUG_MATCH);
695
696
0
    if (!initialized) {
697
0
  const size_t host_name_max = sudo_host_name_max();
698
0
  int rc;
699
700
0
  domain = malloc(host_name_max + 1);
701
0
  if (domain != NULL) {
702
0
      domain[0] = '\0';
703
# ifdef SI_SRPC_DOMAIN
704
      rc = sysinfo(SI_SRPC_DOMAIN, domain, host_name_max + 1);
705
# else
706
0
      rc = getdomainname(domain, host_name_max + 1);
707
0
# endif
708
0
      if (rc == -1 || !valid_domain(domain)) {
709
    /* Error or invalid domain name. */
710
0
    free(domain);
711
0
    domain = NULL;
712
0
      }
713
0
  } else {
714
      /* XXX - want to pass error back to caller */
715
0
      sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
716
0
    "unable to allocate memory");
717
0
  }
718
0
  initialized = true;
719
0
    }
720
0
    debug_return_str(domain);
721
0
}
722
#else
723
const char *
724
sudo_getdomainname(void)
725
{
726
    debug_decl(sudo_getdomainname, SUDOERS_DEBUG_MATCH);
727
    debug_return_ptr(NULL);
728
}
729
#endif /* HAVE_GETDOMAINNAME || SI_SRPC_DOMAIN */
730
731
/*
732
 * Returns ALLOW if "host" and "user" belong to the netgroup "netgr",
733
 * else return DENY.  Either of "lhost", "shost" or "user" may be NULL
734
 * in which case that argument is not checked...
735
 */
736
int
737
netgr_matches(const struct sudo_nss *nss, const char *netgr,
738
    const char *lhost, const char *shost, const char *user)
739
0
{
740
0
    const char *domain;
741
0
    int ret = DENY;
742
0
    debug_decl(netgr_matches, SUDOERS_DEBUG_MATCH);
743
744
0
    if (!def_use_netgroups) {
745
0
  sudo_debug_printf(SUDO_DEBUG_INFO, "netgroups are disabled");
746
0
  debug_return_int(DENY);
747
0
    }
748
749
    /* make sure we have a valid netgroup, sudo style */
750
0
    if (*netgr++ != '+') {
751
0
  sudo_debug_printf(SUDO_DEBUG_DIAG, "netgroup %s has no leading '+'",
752
0
      netgr);
753
0
  debug_return_int(DENY);
754
0
    }
755
756
    /* get the domain name (if any) */
757
0
    domain = sudo_getdomainname();
758
759
    /* Use nss-specific innetgr() function if available. */
760
0
    if (nss != NULL && nss->innetgr != NULL) {
761
0
  switch (nss->innetgr(nss, netgr, lhost, user, domain)) {
762
0
  case 0:
763
0
      if (lhost != shost) {
764
0
    if (nss->innetgr(nss, netgr, shost, user, domain) == 1)
765
0
        ret = ALLOW;
766
0
      }
767
0
      goto done;
768
0
  case 1:
769
0
      ret = ALLOW;
770
0
      goto done;
771
0
  default:
772
      /* Not supported, use system innetgr(3). */
773
0
      break;
774
0
  }
775
0
    }
776
777
0
#ifdef HAVE_INNETGR
778
    /* Use system innetgr() function. */
779
0
    if (innetgr(netgr, lhost, user, domain) == 1) {
780
0
  ret = ALLOW;
781
0
    } else if (lhost != shost) {
782
0
  if (innetgr(netgr, shost, user, domain) == 1)
783
0
      ret = ALLOW;
784
0
    }
785
#else
786
    sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
787
  "%s: no system netgroup support", __func__);
788
#endif /* HAVE_INNETGR */
789
790
0
done:
791
0
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
792
0
  "netgroup %s matches (%s|%s, %s, %s): %s", netgr, lhost ? lhost : "",
793
0
  shost ? shost : "", user ? user : "", domain ? domain : "",
794
0
  ret == ALLOW ? "ALLOW" : "DENY");
795
796
0
    debug_return_int(ret);
797
0
}