Coverage Report

Created: 2025-10-13 06:08

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
8.72k
{
64
8.72k
    const struct sudoers_context *ctx = parse_tree->ctx;
65
8.72k
    const char *lhost = parse_tree->lhost ? parse_tree->lhost : ctx->runas.host;
66
8.72k
    const char *shost = parse_tree->shost ? parse_tree->shost : ctx->runas.shost;
67
8.72k
    int matched = UNSPEC;
68
8.72k
    struct alias *a;
69
8.72k
    debug_decl(user_matches, SUDOERS_DEBUG_MATCH);
70
71
8.72k
    switch (m->type) {
72
108
  case ALL:
73
108
      matched = m->negated ? DENY : ALLOW;
74
108
      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
7.64k
  case USERGROUP:
82
7.64k
      if (usergr_matches(m->name, pw->pw_name, pw) == ALLOW)
83
1.32k
    matched = m->negated ? DENY : ALLOW;
84
7.64k
      break;
85
24
  case ALIAS:
86
24
      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
24
      FALLTHROUGH;
100
978
  case WORD:
101
978
      if (userpw_matches(m->name, pw->pw_name, pw) == ALLOW)
102
89
    matched = m->negated ? DENY : ALLOW;
103
978
      break;
104
8.72k
    }
105
8.72k
    debug_return_int(matched);
106
8.72k
}
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
4.39k
{
116
4.39k
    struct member *m;
117
4.39k
    int matched = UNSPEC;
118
4.39k
    debug_decl(userlist_matches, SUDOERS_DEBUG_MATCH);
119
120
8.72k
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
121
8.72k
  matched = user_matches(parse_tree, pw, m);
122
8.72k
  if (SPECIFIED(matched))
123
1.51k
      break;
124
8.72k
    }
125
4.39k
    debug_return_int(matched);
126
4.39k
}
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
133
{
154
133
    const struct sudoers_context *ctx = parse_tree->ctx;
155
133
    const char *lhost = parse_tree->lhost ? parse_tree->lhost : ctx->runas.host;
156
133
    const char *shost = parse_tree->shost ? parse_tree->shost : ctx->runas.shost;
157
133
    int user_matched = UNSPEC;
158
133
    struct member *m;
159
133
    struct alias *a;
160
133
    debug_decl(runas_userlist_matches, SUDOERS_DEBUG_MATCH);
161
162
133
    TAILQ_FOREACH_REVERSE(m, user_list, member_list, entries) {
163
133
  switch (m->type) {
164
11
      case ALL:
165
11
    user_matched = m->negated ? DENY : ALLOW;
166
11
    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
3
      case ALIAS:
179
3
    a = alias_get(parse_tree, m->name, RUNASALIAS);
180
3
    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
3
    FALLTHROUGH;
194
122
      case WORD:
195
122
    if (userpw_matches(m->name, ctx->runas.pw->pw_name, ctx->runas.pw) == ALLOW)
196
14
        user_matched = m->negated ? DENY : ALLOW;
197
122
    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
133
  }
210
133
    }
211
133
    debug_return_int(user_matched);
212
133
}
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
35
{
223
35
    const struct sudoers_context *ctx = parse_tree->ctx;
224
35
    int group_matched = UNSPEC;
225
35
    struct member *m;
226
35
    struct alias *a;
227
35
    debug_decl(runas_grouplist_matches, SUDOERS_DEBUG_MATCH);
228
229
35
    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
35
    if (!SPECIFIED(group_matched) && user_matched == ALLOW) {
259
9
  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
9
  if (ctx->runas.pw->pw_gid == ctx->runas.gr->gr_gid) {
265
9
      group_matched = ALLOW; /* runas group matches passwd db */
266
9
  } 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
9
    }
278
279
35
    debug_return_int(group_matched);
280
35
}
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
133
{
293
133
    const struct sudoers_context *ctx = parse_tree->ctx;
294
133
    struct member_list _user_list = TAILQ_HEAD_INITIALIZER(_user_list);
295
133
    int user_matched, group_matched = UNSPEC;
296
133
    struct member m_user;
297
133
    debug_decl(runaslist_matches, SUDOERS_DEBUG_MATCH);
298
299
    /* If no runas user listed in sudoers, use the default value.  */
300
133
    if (user_list == NULL) {
301
20
  m_user.name = def_runas_default;
302
20
  m_user.type = WORD;
303
20
  m_user.negated = false;
304
20
  TAILQ_INSERT_HEAD(&_user_list, &m_user, entries);
305
20
  user_list = &_user_list;
306
20
    }
307
308
133
    user_matched = runas_userlist_matches(parse_tree, user_list);
309
133
    if (ISSET(ctx->settings.flags, RUNAS_GROUP_SPECIFIED)) {
310
35
  group_matched = runas_grouplist_matches(parse_tree, group_list,
311
35
      user_matched);
312
  /*
313
   * Allow "sudo -g group" or "sudo -u myname -g group"
314
   * if the runas group matches.
315
   */
316
35
  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
35
    }
321
322
133
    if (user_matched == DENY || group_matched == DENY)
323
0
  debug_return_int(DENY);
324
133
    if (user_matched == group_matched || ctx->runas.gr == NULL)
325
133
  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
1.47k
{
338
1.47k
    struct member *m;
339
1.47k
    int matched = UNSPEC;
340
1.47k
    debug_decl(hostlist_matches, SUDOERS_DEBUG_MATCH);
341
342
1.61k
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
343
1.61k
  matched = host_matches(parse_tree, pw, lhost, shost, m);
344
1.61k
  if (SPECIFIED(matched))
345
130
      break;
346
1.61k
    }
347
1.47k
    debug_return_int(matched);
348
1.47k
}
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
1.47k
{
358
1.47k
    const struct sudoers_context *ctx = parse_tree->ctx;
359
1.47k
    const char *lhost = parse_tree->lhost ? parse_tree->lhost : ctx->runas.host;
360
1.47k
    const char *shost = parse_tree->shost ? parse_tree->shost : ctx->runas.shost;
361
362
1.47k
    return hostlist_matches_int(parse_tree, pw, lhost, shost, list);
363
1.47k
}
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
1.61k
{
374
1.61k
    struct alias *a;
375
1.61k
    int ret = UNSPEC;
376
1.61k
    debug_decl(host_matches, SUDOERS_DEBUG_MATCH);
377
378
1.61k
    switch (m->type) {
379
48
  case ALL:
380
48
      ret = m->negated ? DENY : ALLOW;
381
48
      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
165
  case NTWKADDR:
388
165
      if (addr_matches(m->name) == ALLOW)
389
0
    ret = m->negated ? DENY : ALLOW;
390
165
      break;
391
120
  case ALIAS:
392
120
      a = alias_get(parse_tree, m->name, HOSTALIAS);
393
120
      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
120
      FALLTHROUGH;
408
1.40k
  case WORD:
409
1.40k
      if (hostname_matches(shost, lhost, m->name) == ALLOW)
410
82
    ret = m->negated ? DENY : ALLOW;
411
1.40k
      break;
412
1.61k
    }
413
1.61k
    sudo_debug_printf(SUDO_DEBUG_DEBUG,
414
1.61k
  "host %s (%s) matches sudoers host %s%s: %s", lhost, shost,
415
1.61k
  m->negated ? "!" : "", m->name ? m->name : "ALL",
416
1.61k
  ret == ALLOW ? "ALLOW" : "DENY");
417
1.61k
    debug_return_int(ret);
418
1.61k
}
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
40
{
429
40
    struct member *m;
430
40
    int matched;
431
40
    debug_decl(cmndlist_matches, SUDOERS_DEBUG_MATCH);
432
433
56
    TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
434
56
  matched = cmnd_matches(parse_tree, m, runchroot, info);
435
56
  if (SPECIFIED(matched))
436
32
      debug_return_int(matched);
437
56
    }
438
8
    debug_return_int(UNSPEC);
439
8
}
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
81
{
449
81
    struct alias *a;
450
81
    struct sudo_command *c;
451
81
    int rc, matched = UNSPEC;
452
81
    debug_decl(cmnd_matches, SUDOERS_DEBUG_MATCH);
453
454
81
    switch (m->type) {
455
36
  case ALL:
456
57
  case COMMAND:
457
57
      c = (struct sudo_command *)m->name;
458
57
      if (command_matches(parse_tree->ctx, c->cmnd, c->args, runchroot,
459
57
        info, &c->digests) == ALLOW)
460
34
    matched = m->negated ? DENY : ALLOW;
461
57
      break;
462
24
  case ALIAS:
463
24
      a = alias_get(parse_tree, m->name, CMNDALIAS);
464
24
      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
24
      break;
476
81
    }
477
81
    debug_return_int(matched);
478
81
}
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
1.40k
{
525
1.40k
    const char *host;
526
1.40k
    int ret;
527
1.40k
    debug_decl(hostname_matches, SUDOERS_DEBUG_MATCH);
528
529
1.40k
    host = strchr(pattern, '.') != NULL ? lhost : shost;
530
1.40k
    ret = DENY;
531
1.40k
    if (has_meta(pattern)) {
532
82
  if (fnmatch(pattern, host, FNM_CASEFOLD) == 0)
533
82
      ret = ALLOW;
534
1.32k
    } else {
535
1.32k
  if (strcasecmp(host, pattern) == 0)
536
0
      ret = ALLOW;
537
1.32k
    }
538
1.40k
    debug_return_int(ret);
539
1.40k
}
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
1.10k
{
548
1.10k
    const char *errstr;
549
1.10k
    int ret = DENY;
550
1.10k
    uid_t uid;
551
1.10k
    debug_decl(userpw_matches, SUDOERS_DEBUG_MATCH);
552
553
1.10k
    if (pw != NULL && *sudoers_user == '#') {
554
357
  uid = (uid_t) sudo_strtoid(sudoers_user + 1, &errstr);
555
357
  if (errstr == NULL && uid == pw->pw_uid) {
556
89
      ret = ALLOW;
557
89
      goto done;
558
89
  }
559
357
    }
560
1.01k
    if (def_case_insensitive_user) {
561
1.01k
  if (strcasecmp(sudoers_user, user) == 0)
562
14
      ret = ALLOW;
563
1.01k
    } else {
564
0
  if (strcmp(sudoers_user, user) == 0)
565
0
      ret = ALLOW;
566
0
    }
567
1.10k
done:
568
1.10k
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
569
1.10k
  "user %s matches sudoers user %s: %s",
570
1.10k
  user, sudoers_user, ret == ALLOW ? "ALLOW" : "DENY");
571
1.10k
    debug_return_int(ret);
572
1.10k
}
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
7.64k
{
614
7.64k
    struct passwd *pw0 = NULL;
615
7.64k
    int ret = DENY;
616
7.64k
    debug_decl(usergr_matches, SUDOERS_DEBUG_MATCH);
617
618
    /* Make sure we have a valid usergroup, sudo style */
619
7.64k
    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
7.64k
    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
7.64k
    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
7.64k
    if (user_in_group(pw, group)) {
643
1.32k
  ret = ALLOW;
644
1.32k
  goto done;
645
1.32k
    }
646
647
    /* Query the group plugin for Unix groups too? */
648
6.32k
    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
7.64k
done:
656
7.64k
    if (pw0 != NULL)
657
0
  sudo_pw_delref(pw0);
658
659
7.64k
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
660
7.64k
  "user %s matches group %s: %s", user, group,
661
7.64k
  ret == ALLOW ? "ALLOW" : "DENY");
662
7.64k
    debug_return_int(ret);
663
7.64k
}
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
}