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