Coverage Report

Created: 2025-07-11 06:58

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