Annotation of embedaddon/sudo/plugins/sudoers/match.c, revision 1.1.1.1
1.1 misho 1: /*
2: * Copyright (c) 1996, 1998-2005, 2007-2011
3: * Todd C. Miller <Todd.Miller@courtesan.com>
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: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
17: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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/types.h>
27: #include <sys/param.h>
28: #include <sys/stat.h>
29: #include <stdio.h>
30: #ifdef STDC_HEADERS
31: # include <stdlib.h>
32: # include <stddef.h>
33: #else
34: # ifdef HAVE_STDLIB_H
35: # include <stdlib.h>
36: # endif
37: #endif /* STDC_HEADERS */
38: #ifdef HAVE_STRING_H
39: # include <string.h>
40: #endif /* HAVE_STRING_H */
41: #ifdef HAVE_STRINGS_H
42: # include <strings.h>
43: #endif /* HAVE_STRINGS_H */
44: #ifdef HAVE_UNISTD_H
45: # include <unistd.h>
46: #endif /* HAVE_UNISTD_H */
47: #ifdef HAVE_FNMATCH
48: # include <fnmatch.h>
49: #endif /* HAVE_FNMATCH */
50: #ifdef HAVE_EXTENDED_GLOB
51: # include <glob.h>
52: #endif /* HAVE_EXTENDED_GLOB */
53: #ifdef HAVE_NETGROUP_H
54: # include <netgroup.h>
55: #endif /* HAVE_NETGROUP_H */
56: #include <ctype.h>
57: #include <pwd.h>
58: #include <grp.h>
59: #include <netdb.h>
60: #ifdef HAVE_DIRENT_H
61: # include <dirent.h>
62: # define NAMLEN(dirent) strlen((dirent)->d_name)
63: #else
64: # define dirent direct
65: # define NAMLEN(dirent) (dirent)->d_namlen
66: # ifdef HAVE_SYS_NDIR_H
67: # include <sys/ndir.h>
68: # endif
69: # ifdef HAVE_SYS_DIR_H
70: # include <sys/dir.h>
71: # endif
72: # ifdef HAVE_NDIR_H
73: # include <ndir.h>
74: # endif
75: #endif
76:
77: #include "sudoers.h"
78: #include "parse.h"
79: #include <gram.h>
80:
81: #ifndef HAVE_FNMATCH
82: # include "compat/fnmatch.h"
83: #endif /* HAVE_FNMATCH */
84: #ifndef HAVE_EXTENDED_GLOB
85: # include "compat/glob.h"
86: #endif /* HAVE_EXTENDED_GLOB */
87:
88: static struct member_list empty;
89:
90: static int command_matches_dir(char *, size_t);
91: static int command_matches_glob(char *, char *);
92: static int command_matches_fnmatch(char *, char *);
93: static int command_matches_normal(char *, char *);
94:
95: /*
96: * Returns TRUE if string 's' contains meta characters.
97: */
98: #define has_meta(s) (strpbrk(s, "\\?*[]") != NULL)
99:
100: /*
101: * Check for user described by pw in a list of members.
102: * Returns ALLOW, DENY or UNSPEC.
103: */
104: static int
105: _userlist_matches(struct passwd *pw, struct member_list *list)
106: {
107: struct member *m;
108: struct alias *a;
109: int rval, matched = UNSPEC;
110:
111: tq_foreach_rev(list, m) {
112: switch (m->type) {
113: case ALL:
114: matched = !m->negated;
115: break;
116: case NETGROUP:
117: if (netgr_matches(m->name, NULL, NULL, pw->pw_name))
118: matched = !m->negated;
119: break;
120: case USERGROUP:
121: if (usergr_matches(m->name, pw->pw_name, pw))
122: matched = !m->negated;
123: break;
124: case ALIAS:
125: if ((a = alias_find(m->name, USERALIAS)) != NULL) {
126: rval = _userlist_matches(pw, &a->members);
127: if (rval != UNSPEC)
128: matched = m->negated ? !rval : rval;
129: break;
130: }
131: /* FALLTHROUGH */
132: case WORD:
133: if (userpw_matches(m->name, pw->pw_name, pw))
134: matched = !m->negated;
135: break;
136: }
137: if (matched != UNSPEC)
138: break;
139: }
140: return matched;
141: }
142:
143: int
144: userlist_matches(struct passwd *pw, struct member_list *list)
145: {
146: alias_seqno++;
147: return _userlist_matches(pw, list);
148: }
149:
150: /*
151: * Check for user described by pw in a list of members.
152: * If both lists are empty compare against def_runas_default.
153: * Returns ALLOW, DENY or UNSPEC.
154: */
155: static int
156: _runaslist_matches(struct member_list *user_list, struct member_list *group_list)
157: {
158: struct member *m;
159: struct alias *a;
160: int rval;
161: int user_matched = UNSPEC;
162: int group_matched = UNSPEC;
163:
164: if (runas_pw != NULL) {
165: /* If no runas user or runas group listed in sudoers, use default. */
166: if (tq_empty(user_list) && tq_empty(group_list))
167: return userpw_matches(def_runas_default, runas_pw->pw_name, runas_pw);
168:
169: tq_foreach_rev(user_list, m) {
170: switch (m->type) {
171: case ALL:
172: user_matched = !m->negated;
173: break;
174: case NETGROUP:
175: if (netgr_matches(m->name, NULL, NULL, runas_pw->pw_name))
176: user_matched = !m->negated;
177: break;
178: case USERGROUP:
179: if (usergr_matches(m->name, runas_pw->pw_name, runas_pw))
180: user_matched = !m->negated;
181: break;
182: case ALIAS:
183: if ((a = alias_find(m->name, RUNASALIAS)) != NULL) {
184: rval = _runaslist_matches(&a->members, &empty);
185: if (rval != UNSPEC)
186: user_matched = m->negated ? !rval : rval;
187: break;
188: }
189: /* FALLTHROUGH */
190: case WORD:
191: if (userpw_matches(m->name, runas_pw->pw_name, runas_pw))
192: user_matched = !m->negated;
193: break;
194: }
195: if (user_matched != UNSPEC)
196: break;
197: }
198: }
199:
200: if (runas_gr != NULL) {
201: if (user_matched == UNSPEC) {
202: if (runas_pw == NULL || strcmp(runas_pw->pw_name, user_name) == 0)
203: user_matched = ALLOW; /* only changing group */
204: }
205: tq_foreach_rev(group_list, m) {
206: switch (m->type) {
207: case ALL:
208: group_matched = !m->negated;
209: break;
210: case ALIAS:
211: if ((a = alias_find(m->name, RUNASALIAS)) != NULL) {
212: rval = _runaslist_matches(&empty, &a->members);
213: if (rval != UNSPEC)
214: group_matched = m->negated ? !rval : rval;
215: break;
216: }
217: /* FALLTHROUGH */
218: case WORD:
219: if (group_matches(m->name, runas_gr))
220: group_matched = !m->negated;
221: break;
222: }
223: if (group_matched != UNSPEC)
224: break;
225: }
226: if (group_matched == UNSPEC) {
227: if (runas_pw != NULL && runas_pw->pw_gid == runas_gr->gr_gid)
228: group_matched = ALLOW; /* runas group matches passwd db */
229: }
230: }
231:
232: if (user_matched == DENY || group_matched == DENY)
233: return DENY;
234: if (user_matched == group_matched || runas_gr == NULL)
235: return user_matched;
236: return UNSPEC;
237: }
238:
239: int
240: runaslist_matches(struct member_list *user_list, struct member_list *group_list)
241: {
242: alias_seqno++;
243: return _runaslist_matches(user_list ? user_list : &empty,
244: group_list ? group_list : &empty);
245: }
246:
247: /*
248: * Check for host and shost in a list of members.
249: * Returns ALLOW, DENY or UNSPEC.
250: */
251: static int
252: _hostlist_matches(struct member_list *list)
253: {
254: struct member *m;
255: struct alias *a;
256: int rval, matched = UNSPEC;
257:
258: tq_foreach_rev(list, m) {
259: switch (m->type) {
260: case ALL:
261: matched = !m->negated;
262: break;
263: case NETGROUP:
264: if (netgr_matches(m->name, user_host, user_shost, NULL))
265: matched = !m->negated;
266: break;
267: case NTWKADDR:
268: if (addr_matches(m->name))
269: matched = !m->negated;
270: break;
271: case ALIAS:
272: if ((a = alias_find(m->name, HOSTALIAS)) != NULL) {
273: rval = _hostlist_matches(&a->members);
274: if (rval != UNSPEC)
275: matched = m->negated ? !rval : rval;
276: break;
277: }
278: /* FALLTHROUGH */
279: case WORD:
280: if (hostname_matches(user_shost, user_host, m->name))
281: matched = !m->negated;
282: break;
283: }
284: if (matched != UNSPEC)
285: break;
286: }
287: return matched;
288: }
289:
290: int
291: hostlist_matches(struct member_list *list)
292: {
293: alias_seqno++;
294: return _hostlist_matches(list);
295: }
296:
297: /*
298: * Check for cmnd and args in a list of members.
299: * Returns ALLOW, DENY or UNSPEC.
300: */
301: static int
302: _cmndlist_matches(struct member_list *list)
303: {
304: struct member *m;
305: int matched = UNSPEC;
306:
307: tq_foreach_rev(list, m) {
308: matched = cmnd_matches(m);
309: if (matched != UNSPEC)
310: break;
311: }
312: return matched;
313: }
314:
315: int
316: cmndlist_matches(struct member_list *list)
317: {
318: alias_seqno++;
319: return _cmndlist_matches(list);
320: }
321:
322: /*
323: * Check cmnd and args.
324: * Returns ALLOW, DENY or UNSPEC.
325: */
326: int
327: cmnd_matches(struct member *m)
328: {
329: struct alias *a;
330: struct sudo_command *c;
331: int rval, matched = UNSPEC;
332:
333: switch (m->type) {
334: case ALL:
335: matched = !m->negated;
336: break;
337: case ALIAS:
338: alias_seqno++;
339: if ((a = alias_find(m->name, CMNDALIAS)) != NULL) {
340: rval = _cmndlist_matches(&a->members);
341: if (rval != UNSPEC)
342: matched = m->negated ? !rval : rval;
343: }
344: break;
345: case COMMAND:
346: c = (struct sudo_command *)m->name;
347: if (command_matches(c->cmnd, c->args))
348: matched = !m->negated;
349: break;
350: }
351: return matched;
352: }
353:
354: static int
355: command_args_match(sudoers_cmnd, sudoers_args)
356: char *sudoers_cmnd;
357: char *sudoers_args;
358: {
359: int flags = 0;
360:
361: /*
362: * If no args specified in sudoers, any user args are allowed.
363: * If the empty string is specified in sudoers, no user args are allowed.
364: */
365: if (!sudoers_args ||
366: (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)))
367: return TRUE;
368: /*
369: * If args are specified in sudoers, they must match the user args.
370: * If running as sudoedit, all args are assumed to be paths.
371: */
372: if (sudoers_args) {
373: /* For sudoedit, all args are assumed to be pathnames. */
374: if (strcmp(sudoers_cmnd, "sudoedit") == 0)
375: flags = FNM_PATHNAME;
376: if (fnmatch(sudoers_args, user_args ? user_args : "", flags) == 0)
377: return TRUE;
378: }
379: return FALSE;
380: }
381:
382: /*
383: * If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
384: * otherwise, return TRUE if user_cmnd names one of the inodes in path.
385: */
386: int
387: command_matches(char *sudoers_cmnd, char *sudoers_args)
388: {
389: /* Check for pseudo-commands */
390: if (sudoers_cmnd[0] != '/') {
391: /*
392: * Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
393: * a) there are no args in sudoers OR
394: * b) there are no args on command line and none req by sudoers OR
395: * c) there are args in sudoers and on command line and they match
396: */
397: if (strcmp(sudoers_cmnd, "sudoedit") != 0 ||
398: strcmp(user_cmnd, "sudoedit") != 0)
399: return FALSE;
400: if (command_args_match(sudoers_cmnd, sudoers_args)) {
401: efree(safe_cmnd);
402: safe_cmnd = estrdup(sudoers_cmnd);
403: return TRUE;
404: } else
405: return FALSE;
406: }
407:
408: if (has_meta(sudoers_cmnd)) {
409: /*
410: * If sudoers_cmnd has meta characters in it, we need to
411: * use glob(3) and/or fnmatch(3) to do the matching.
412: */
413: if (def_fast_glob)
414: return command_matches_fnmatch(sudoers_cmnd, sudoers_args);
415: return command_matches_glob(sudoers_cmnd, sudoers_args);
416: }
417: return command_matches_normal(sudoers_cmnd, sudoers_args);
418: }
419:
420: static int
421: command_matches_fnmatch(char *sudoers_cmnd, char *sudoers_args)
422: {
423: /*
424: * Return true if fnmatch(3) succeeds AND
425: * a) there are no args in sudoers OR
426: * b) there are no args on command line and none required by sudoers OR
427: * c) there are args in sudoers and on command line and they match
428: * else return false.
429: */
430: if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
431: return FALSE;
432: if (command_args_match(sudoers_cmnd, sudoers_args)) {
433: if (safe_cmnd)
434: free(safe_cmnd);
435: safe_cmnd = estrdup(user_cmnd);
436: return TRUE;
437: } else
438: return FALSE;
439: }
440:
441: static int
442: command_matches_glob(char *sudoers_cmnd, char *sudoers_args)
443: {
444: struct stat sudoers_stat;
445: size_t dlen;
446: char **ap, *base, *cp;
447: glob_t gl;
448:
449: /*
450: * First check to see if we can avoid the call to glob(3).
451: * Short circuit if there are no meta chars in the command itself
452: * and user_base and basename(sudoers_cmnd) don't match.
453: */
454: dlen = strlen(sudoers_cmnd);
455: if (sudoers_cmnd[dlen - 1] != '/') {
456: if ((base = strrchr(sudoers_cmnd, '/')) != NULL) {
457: base++;
458: if (!has_meta(base) && strcmp(user_base, base) != 0)
459: return FALSE;
460: }
461: }
462: /*
463: * Return true if we find a match in the glob(3) results AND
464: * a) there are no args in sudoers OR
465: * b) there are no args on command line and none required by sudoers OR
466: * c) there are args in sudoers and on command line and they match
467: * else return false.
468: */
469: #define GLOB_FLAGS (GLOB_NOSORT | GLOB_MARK | GLOB_BRACE | GLOB_TILDE)
470: if (glob(sudoers_cmnd, GLOB_FLAGS, NULL, &gl) != 0 || gl.gl_pathc == 0) {
471: globfree(&gl);
472: return FALSE;
473: }
474: /* For each glob match, compare basename, st_dev and st_ino. */
475: for (ap = gl.gl_pathv; (cp = *ap) != NULL; ap++) {
476: /* If it ends in '/' it is a directory spec. */
477: dlen = strlen(cp);
478: if (cp[dlen - 1] == '/') {
479: if (command_matches_dir(cp, dlen))
480: return TRUE;
481: continue;
482: }
483:
484: /* Only proceed if user_base and basename(cp) match */
485: if ((base = strrchr(cp, '/')) != NULL)
486: base++;
487: else
488: base = cp;
489: if (strcmp(user_base, base) != 0 ||
490: stat(cp, &sudoers_stat) == -1)
491: continue;
492: if (user_stat == NULL ||
493: (user_stat->st_dev == sudoers_stat.st_dev &&
494: user_stat->st_ino == sudoers_stat.st_ino)) {
495: efree(safe_cmnd);
496: safe_cmnd = estrdup(cp);
497: break;
498: }
499: }
500: globfree(&gl);
501: if (cp == NULL)
502: return FALSE;
503:
504: if (command_args_match(sudoers_cmnd, sudoers_args)) {
505: efree(safe_cmnd);
506: safe_cmnd = estrdup(user_cmnd);
507: return TRUE;
508: }
509: return FALSE;
510: }
511:
512: static int
513: command_matches_normal(char *sudoers_cmnd, char *sudoers_args)
514: {
515: struct stat sudoers_stat;
516: char *base;
517: size_t dlen;
518:
519: /* If it ends in '/' it is a directory spec. */
520: dlen = strlen(sudoers_cmnd);
521: if (sudoers_cmnd[dlen - 1] == '/')
522: return command_matches_dir(sudoers_cmnd, dlen);
523:
524: /* Only proceed if user_base and basename(sudoers_cmnd) match */
525: if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
526: base = sudoers_cmnd;
527: else
528: base++;
529: if (strcmp(user_base, base) != 0 ||
530: stat(sudoers_cmnd, &sudoers_stat) == -1)
531: return FALSE;
532:
533: /*
534: * Return true if inode/device matches AND
535: * a) there are no args in sudoers OR
536: * b) there are no args on command line and none req by sudoers OR
537: * c) there are args in sudoers and on command line and they match
538: */
539: if (user_stat != NULL &&
540: (user_stat->st_dev != sudoers_stat.st_dev ||
541: user_stat->st_ino != sudoers_stat.st_ino))
542: return FALSE;
543: if (command_args_match(sudoers_cmnd, sudoers_args)) {
544: efree(safe_cmnd);
545: safe_cmnd = estrdup(sudoers_cmnd);
546: return TRUE;
547: }
548: return FALSE;
549: }
550:
551: /*
552: * Return TRUE if user_cmnd names one of the inodes in dir, else FALSE.
553: */
554: static int
555: command_matches_dir(char *sudoers_dir, size_t dlen)
556: {
557: struct stat sudoers_stat;
558: struct dirent *dent;
559: char buf[PATH_MAX];
560: DIR *dirp;
561:
562: /*
563: * Grot through directory entries, looking for user_base.
564: */
565: dirp = opendir(sudoers_dir);
566: if (dirp == NULL)
567: return FALSE;
568:
569: if (strlcpy(buf, sudoers_dir, sizeof(buf)) >= sizeof(buf)) {
570: closedir(dirp);
571: return FALSE;
572: }
573: while ((dent = readdir(dirp)) != NULL) {
574: /* ignore paths > PATH_MAX (XXX - log) */
575: buf[dlen] = '\0';
576: if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
577: continue;
578:
579: /* only stat if basenames are the same */
580: if (strcmp(user_base, dent->d_name) != 0 ||
581: stat(buf, &sudoers_stat) == -1)
582: continue;
583: if (user_stat == NULL ||
584: (user_stat->st_dev == sudoers_stat.st_dev &&
585: user_stat->st_ino == sudoers_stat.st_ino)) {
586: efree(safe_cmnd);
587: safe_cmnd = estrdup(buf);
588: break;
589: }
590: }
591:
592: closedir(dirp);
593: return dent != NULL;
594: }
595:
596: /*
597: * Returns TRUE if the hostname matches the pattern, else FALSE
598: */
599: int
600: hostname_matches(char *shost, char *lhost, char *pattern)
601: {
602: if (has_meta(pattern)) {
603: if (strchr(pattern, '.'))
604: return !fnmatch(pattern, lhost, FNM_CASEFOLD);
605: else
606: return !fnmatch(pattern, shost, FNM_CASEFOLD);
607: } else {
608: if (strchr(pattern, '.'))
609: return !strcasecmp(lhost, pattern);
610: else
611: return !strcasecmp(shost, pattern);
612: }
613: }
614:
615: /*
616: * Returns TRUE if the user/uid from sudoers matches the specified user/uid,
617: * else returns FALSE.
618: */
619: int
620: userpw_matches(char *sudoers_user, char *user, struct passwd *pw)
621: {
622: if (pw != NULL && *sudoers_user == '#') {
623: uid_t uid = (uid_t) atoi(sudoers_user + 1);
624: if (uid == pw->pw_uid)
625: return TRUE;
626: }
627: return strcmp(sudoers_user, user) == 0;
628: }
629:
630: /*
631: * Returns TRUE if the group/gid from sudoers matches the specified group/gid,
632: * else returns FALSE.
633: */
634: int
635: group_matches(char *sudoers_group, struct group *gr)
636: {
637: if (*sudoers_group == '#') {
638: gid_t gid = (gid_t) atoi(sudoers_group + 1);
639: if (gid == gr->gr_gid)
640: return TRUE;
641: }
642: return strcmp(gr->gr_name, sudoers_group) == 0;
643: }
644:
645: /*
646: * Returns TRUE if the given user belongs to the named group,
647: * else returns FALSE.
648: */
649: int
650: usergr_matches(char *group, char *user, struct passwd *pw)
651: {
652: int matched = FALSE;
653: struct passwd *pw0 = NULL;
654:
655: /* make sure we have a valid usergroup, sudo style */
656: if (*group++ != '%')
657: goto done;
658:
659: if (*group == ':' && def_group_plugin) {
660: matched = group_plugin_query(user, group + 1, pw);
661: goto done;
662: }
663:
664: /* look up user's primary gid in the passwd file */
665: if (pw == NULL) {
666: if ((pw0 = sudo_getpwnam(user)) == NULL)
667: goto done;
668: pw = pw0;
669: }
670:
671: if (user_in_group(pw, group)) {
672: matched = TRUE;
673: goto done;
674: }
675:
676: /* not a Unix group, could be an external group */
677: if (def_group_plugin && group_plugin_query(user, group, pw)) {
678: matched = TRUE;
679: goto done;
680: }
681:
682: done:
683: if (pw0 != NULL)
684: pw_delref(pw0);
685:
686: return matched;
687: }
688:
689: /*
690: * Returns TRUE if "host" and "user" belong to the netgroup "netgr",
691: * else return FALSE. Either of "host", "shost" or "user" may be NULL
692: * in which case that argument is not checked...
693: *
694: * XXX - swap order of host & shost
695: */
696: int
697: netgr_matches(char *netgr, char *lhost, char *shost, char *user)
698: {
699: static char *domain;
700: #ifdef HAVE_GETDOMAINNAME
701: static int initialized;
702: #endif
703:
704: /* make sure we have a valid netgroup, sudo style */
705: if (*netgr++ != '+')
706: return FALSE;
707:
708: #ifdef HAVE_GETDOMAINNAME
709: /* get the domain name (if any) */
710: if (!initialized) {
711: domain = (char *) emalloc(MAXHOSTNAMELEN + 1);
712: if (getdomainname(domain, MAXHOSTNAMELEN + 1) == -1 || *domain == '\0') {
713: efree(domain);
714: domain = NULL;
715: }
716: initialized = 1;
717: }
718: #endif /* HAVE_GETDOMAINNAME */
719:
720: #ifdef HAVE_INNETGR
721: if (innetgr(netgr, lhost, user, domain))
722: return TRUE;
723: else if (lhost != shost && innetgr(netgr, shost, user, domain))
724: return TRUE;
725: #endif /* HAVE_INNETGR */
726:
727: return FALSE;
728: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>