[BACK]Return to parse.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / sudo

Annotation of src/usr.bin/sudo/parse.c, Revision 1.11

1.1       millert     1: /*
1.9       millert     2:  * Copyright (c) 1996, 1998-2003 Todd C. Miller <Todd.Miller@courtesan.com>
1.1       millert     3:  * All rights reserved.
                      4:  *
1.8       millert     5:  * This code is derived from software contributed by Chris Jepeway.
1.1       millert     6:  *
                      7:  * Redistribution and use in source and binary forms, with or without
                      8:  * modification, are permitted provided that the following conditions
                      9:  * are met:
                     10:  *
                     11:  * 1. Redistributions of source code must retain the above copyright
                     12:  *    notice, this list of conditions and the following disclaimer.
                     13:  *
                     14:  * 2. Redistributions in binary form must reproduce the above copyright
                     15:  *    notice, this list of conditions and the following disclaimer in the
                     16:  *    documentation and/or other materials provided with the distribution.
                     17:  *
                     18:  * 3. The name of the author may not be used to endorse or promote products
                     19:  *    derived from this software without specific prior written permission.
                     20:  *
                     21:  * 4. Products derived from this software may not be called "Sudo" nor
                     22:  *    may "Sudo" appear in their names without specific prior written
                     23:  *    permission from the author.
                     24:  *
                     25:  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
                     26:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
                     27:  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
                     28:  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
                     29:  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
                     30:  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
                     31:  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
                     32:  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
                     33:  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
                     34:  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.11    ! millert    35:  *
        !            36:  * Sponsored in part by the Defense Advanced Research Projects
        !            37:  * Agency (DARPA) and Air Force Research Laboratory, Air Force
        !            38:  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
1.1       millert    39:  */
                     40:
                     41: #include "config.h"
                     42:
1.6       millert    43: #include <sys/types.h>
                     44: #include <sys/param.h>
                     45: #include <sys/stat.h>
1.1       millert    46: #include <stdio.h>
                     47: #ifdef STDC_HEADERS
                     48: # include <stdlib.h>
1.6       millert    49: # include <stddef.h>
                     50: #else
                     51: # ifdef HAVE_STDLIB_H
                     52: #  include <stdlib.h>
                     53: # endif
1.1       millert    54: #endif /* STDC_HEADERS */
1.6       millert    55: #ifdef HAVE_STRING_H
                     56: # include <string.h>
                     57: #else
                     58: # ifdef HAVE_STRINGS_H
                     59: #  include <strings.h>
                     60: # endif
                     61: #endif /* HAVE_STRING_H */
1.1       millert    62: #ifdef HAVE_UNISTD_H
                     63: # include <unistd.h>
                     64: #endif /* HAVE_UNISTD_H */
1.2       millert    65: #ifdef HAVE_FNMATCH
1.1       millert    66: # include <fnmatch.h>
1.10      millert    67: #endif /* HAVE_FNMATCH */
1.1       millert    68: #ifdef HAVE_NETGROUP_H
                     69: # include <netgroup.h>
                     70: #endif /* HAVE_NETGROUP_H */
                     71: #include <ctype.h>
                     72: #include <pwd.h>
                     73: #include <grp.h>
                     74: #include <netinet/in.h>
                     75: #include <arpa/inet.h>
                     76: #include <netdb.h>
1.6       millert    77: #ifdef HAVE_DIRENT_H
1.1       millert    78: # include <dirent.h>
                     79: # define NAMLEN(dirent) strlen((dirent)->d_name)
                     80: #else
                     81: # define dirent direct
                     82: # define NAMLEN(dirent) (dirent)->d_namlen
1.6       millert    83: # ifdef HAVE_SYS_NDIR_H
1.1       millert    84: #  include <sys/ndir.h>
                     85: # endif
1.6       millert    86: # ifdef HAVE_SYS_DIR_H
1.1       millert    87: #  include <sys/dir.h>
                     88: # endif
1.6       millert    89: # ifdef HAVE_NDIR_H
1.1       millert    90: #  include <ndir.h>
                     91: # endif
                     92: #endif
                     93:
                     94: #include "sudo.h"
                     95: #include "parse.h"
                     96: #include "interfaces.h"
                     97:
                     98: #ifndef HAVE_FNMATCH
                     99: # include "emul/fnmatch.h"
                    100: #endif /* HAVE_FNMATCH */
                    101:
                    102: #ifndef lint
1.11    ! millert   103: static const char rcsid[] = "$Sudo: parse.c,v 1.142 2003/04/16 00:42:10 millert Exp $";
1.1       millert   104: #endif /* lint */
                    105:
                    106: /*
                    107:  * Globals
                    108:  */
                    109: int parse_error = FALSE;
1.3       millert   110: extern int keepall;
1.1       millert   111: extern FILE *yyin, *yyout;
                    112:
                    113: /*
                    114:  * Prototypes
                    115:  */
                    116: static int has_meta    __P((char *));
                    117:        void init_parser        __P((void));
                    118:
                    119: /*
                    120:  * Look up the user in the sudoers file and check to see if they are
                    121:  * allowed to run the specified command on this host as the target user.
                    122:  */
                    123: int
1.6       millert   124: sudoers_lookup(pwflag)
                    125:     int pwflag;
1.1       millert   126: {
                    127:     int error;
1.5       millert   128:     int pwcheck;
1.6       millert   129:     int nopass;
1.1       millert   130:
                    131:     /* Become sudoers file owner */
1.9       millert   132:     set_perms(PERM_SUDOERS);
1.1       millert   133:
                    134:     /* We opened _PATH_SUDOERS in check_sudoers() so just rewind it. */
                    135:     rewind(sudoers_fp);
                    136:     yyin = sudoers_fp;
                    137:     yyout = stdout;
                    138:
                    139:     /* Allocate space for data structures in the parser. */
                    140:     init_parser();
                    141:
1.5       millert   142:     /* If pwcheck *could* be PWCHECK_ALL or PWCHECK_ANY, keep more state. */
1.6       millert   143:     if (pwflag > 0)
1.3       millert   144:        keepall = TRUE;
                    145:
1.1       millert   146:     /* Need to be root while stat'ing things in the parser. */
1.9       millert   147:     set_perms(PERM_ROOT);
1.1       millert   148:     error = yyparse();
                    149:
                    150:     /* Close the sudoers file now that we are done with it. */
                    151:     (void) fclose(sudoers_fp);
                    152:     sudoers_fp = NULL;
                    153:
                    154:     if (error || parse_error)
                    155:        return(VALIDATE_ERROR);
                    156:
                    157:     /*
1.5       millert   158:      * The pw options may have changed during sudoers parse so we
                    159:      * wait until now to set this.
                    160:      */
1.6       millert   161:     if (pwflag)
                    162:        pwcheck = (pwflag == -1) ? PWCHECK_NEVER : def_ival(pwflag);
                    163:     else
                    164:        pwcheck = 0;
1.5       millert   165:
                    166:     /*
1.1       millert   167:      * Assume the worst.  If the stack is empty the user was
                    168:      * not mentioned at all.
                    169:      */
1.2       millert   170:     if (def_flag(I_AUTHENTICATE))
                    171:        error = VALIDATE_NOT_OK;
                    172:     else
                    173:        error = VALIDATE_NOT_OK | FLAG_NOPASS;
1.5       millert   174:     if (pwcheck) {
1.3       millert   175:        error |= FLAG_NO_CHECK;
                    176:     } else {
1.1       millert   177:        error |= FLAG_NO_HOST;
                    178:        if (!top)
                    179:            error |= FLAG_NO_USER;
1.3       millert   180:     }
1.1       millert   181:
                    182:     /*
1.5       millert   183:      * Only check the actual command if pwcheck flag is not set.
1.3       millert   184:      * It is set for the "validate", "list" and "kill" pseudo-commands.
1.1       millert   185:      * Always check the host and user.
                    186:      */
1.6       millert   187:     nopass = -1;
1.5       millert   188:     if (pwcheck) {
1.6       millert   189:        int found;
1.3       millert   190:
1.5       millert   191:        if (pwcheck == PWCHECK_NEVER || !def_flag(I_AUTHENTICATE))
1.3       millert   192:            nopass = FLAG_NOPASS;
                    193:        found = 0;
1.1       millert   194:        while (top) {
                    195:            if (host_matches == TRUE) {
1.3       millert   196:                found = 1;
1.5       millert   197:                if (pwcheck == PWCHECK_ANY && no_passwd == TRUE)
1.3       millert   198:                    nopass = FLAG_NOPASS;
1.5       millert   199:                else if (pwcheck == PWCHECK_ALL && nopass != 0)
1.3       millert   200:                    nopass = (no_passwd == TRUE) ? FLAG_NOPASS : 0;
1.1       millert   201:            }
                    202:            top--;
                    203:        }
1.3       millert   204:        if (found) {
                    205:            if (nopass == -1)
                    206:                nopass = 0;
                    207:            return(VALIDATE_OK | nopass);
                    208:        }
                    209:     } else {
1.1       millert   210:        while (top) {
                    211:            if (host_matches == TRUE) {
                    212:                error &= ~FLAG_NO_HOST;
                    213:                if (runas_matches == TRUE) {
                    214:                    if (cmnd_matches == TRUE) {
                    215:                        /*
                    216:                         * User was granted access to cmnd on host.
                    217:                         * If no passwd required return as such.
                    218:                         */
                    219:                        if (no_passwd == TRUE)
                    220:                            return(VALIDATE_OK | FLAG_NOPASS);
                    221:                        else
                    222:                            return(VALIDATE_OK);
                    223:                    } else if (cmnd_matches == FALSE) {
                    224:                        /*
                    225:                         * User was explicitly denied access to cmnd on host.
                    226:                         */
                    227:                        if (no_passwd == TRUE)
                    228:                            return(VALIDATE_NOT_OK | FLAG_NOPASS);
                    229:                        else
                    230:                            return(VALIDATE_NOT_OK);
                    231:                    }
                    232:                }
                    233:            }
                    234:            top--;
                    235:        }
1.3       millert   236:     }
1.1       millert   237:
                    238:     /*
                    239:      * The user was not explicitly granted nor denied access.
                    240:      */
1.6       millert   241:     if (nopass == -1)
                    242:        nopass = 0;
                    243:     return(error | nopass);
1.1       millert   244: }
                    245:
                    246: /*
                    247:  * If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
                    248:  * otherwise, return TRUE if cmnd names one of the inodes in path.
                    249:  */
                    250: int
                    251: command_matches(cmnd, cmnd_args, path, sudoers_args)
                    252:     char *cmnd;
                    253:     char *cmnd_args;
                    254:     char *path;
                    255:     char *sudoers_args;
                    256: {
                    257:     int plen;
                    258:     static struct stat cst;
                    259:     struct stat pst;
                    260:     DIR *dirp;
                    261:     struct dirent *dent;
                    262:     char buf[MAXPATHLEN];
                    263:     static char *cmnd_base;
                    264:
                    265:     /* Don't bother with pseudo commands like "validate" */
                    266:     if (strchr(cmnd, '/') == NULL)
                    267:        return(FALSE);
                    268:
                    269:     plen = strlen(path);
                    270:
                    271:     /* Only need to stat cmnd once since it never changes */
                    272:     if (cst.st_dev == 0) {
                    273:        if (stat(cmnd, &cst) == -1)
                    274:            return(FALSE);
                    275:        if ((cmnd_base = strrchr(cmnd, '/')) == NULL)
                    276:            cmnd_base = cmnd;
                    277:        else
                    278:            cmnd_base++;
                    279:     }
                    280:
                    281:     /*
                    282:      * If the pathname has meta characters in it use fnmatch(3)
                    283:      * to do the matching
                    284:      */
                    285:     if (has_meta(path)) {
                    286:        /*
                    287:         * Return true if fnmatch(3) succeeds AND
                    288:         *  a) there are no args in sudoers OR
                    289:         *  b) there are no args on command line and none required by sudoers OR
                    290:         *  c) there are args in sudoers and on command line and they match
                    291:         * else return false.
                    292:         */
                    293:        if (fnmatch(path, cmnd, FNM_PATHNAME) != 0)
                    294:            return(FALSE);
                    295:        if (!sudoers_args ||
                    296:            (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
                    297:            (sudoers_args && fnmatch(sudoers_args, cmnd_args ? cmnd_args : "",
                    298:            0) == 0)) {
                    299:            if (safe_cmnd)
                    300:                free(safe_cmnd);
                    301:            safe_cmnd = estrdup(user_cmnd);
                    302:            return(TRUE);
                    303:        } else
                    304:            return(FALSE);
                    305:     } else {
                    306:        /*
                    307:         * No meta characters
                    308:         * Check to make sure this is not a directory spec (doesn't end in '/')
                    309:         */
                    310:        if (path[plen - 1] != '/') {
                    311:            char *p;
                    312:
                    313:            /* Only proceed if the basenames of cmnd and path are the same */
                    314:            if ((p = strrchr(path, '/')) == NULL)
                    315:                p = path;
                    316:            else
                    317:                p++;
                    318:            if (strcmp(cmnd_base, p) != 0 || stat(path, &pst) == -1)
                    319:                return(FALSE);
                    320:
                    321:            /*
                    322:             * Return true if inode/device matches AND
                    323:             *  a) there are no args in sudoers OR
                    324:             *  b) there are no args on command line and none req by sudoers OR
                    325:             *  c) there are args in sudoers and on command line and they match
                    326:             */
                    327:            if (cst.st_dev != pst.st_dev || cst.st_ino != pst.st_ino)
                    328:                return(FALSE);
                    329:            if (!sudoers_args ||
                    330:                (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
                    331:                (sudoers_args &&
                    332:                 fnmatch(sudoers_args, cmnd_args ? cmnd_args : "", 0) == 0)) {
                    333:                if (safe_cmnd)
                    334:                    free(safe_cmnd);
                    335:                safe_cmnd = estrdup(path);
                    336:                return(TRUE);
                    337:            } else
                    338:                return(FALSE);
                    339:        }
                    340:
                    341:        /*
                    342:         * Grot through path's directory entries, looking for cmnd.
                    343:         */
                    344:        dirp = opendir(path);
                    345:        if (dirp == NULL)
                    346:            return(FALSE);
                    347:
                    348:        while ((dent = readdir(dirp)) != NULL) {
                    349:            /* ignore paths > MAXPATHLEN (XXX - log) */
1.9       millert   350:            if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf) ||
                    351:                strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
1.1       millert   352:                continue;
                    353:
                    354:            /* only stat if basenames are the same */
                    355:            if (strcmp(cmnd_base, dent->d_name) != 0 || stat(buf, &pst) == -1)
                    356:                continue;
                    357:            if (cst.st_dev == pst.st_dev && cst.st_ino == pst.st_ino) {
                    358:                if (safe_cmnd)
                    359:                    free(safe_cmnd);
                    360:                safe_cmnd = estrdup(buf);
                    361:                break;
                    362:            }
                    363:        }
                    364:
                    365:        closedir(dirp);
                    366:        return(dent != NULL);
                    367:     }
                    368: }
                    369:
                    370: /*
                    371:  * Returns TRUE if "n" is one of our ip addresses or if
                    372:  * "n" is a network that we are on, else returns FALSE.
                    373:  */
                    374: int
                    375: addr_matches(n)
                    376:     char *n;
                    377: {
                    378:     int i;
                    379:     char *m;
                    380:     struct in_addr addr, mask;
                    381:
                    382:     /* If there's an explicit netmask, use it. */
                    383:     if ((m = strchr(n, '/'))) {
                    384:        *m++ = '\0';
                    385:        addr.s_addr = inet_addr(n);
                    386:        if (strchr(m, '.'))
                    387:            mask.s_addr = inet_addr(m);
1.6       millert   388:        else {
                    389:            i = 32 - atoi(m);
                    390:            mask.s_addr = 0xffffffff;
                    391:            mask.s_addr >>= i;
                    392:            mask.s_addr <<= i;
                    393:            mask.s_addr = htonl(mask.s_addr);
                    394:        }
1.1       millert   395:        *(m - 1) = '/';
                    396:
                    397:        for (i = 0; i < num_interfaces; i++)
                    398:            if ((interfaces[i].addr.s_addr & mask.s_addr) == addr.s_addr)
                    399:                return(TRUE);
                    400:     } else {
                    401:        addr.s_addr = inet_addr(n);
                    402:
                    403:        for (i = 0; i < num_interfaces; i++)
                    404:            if (interfaces[i].addr.s_addr == addr.s_addr ||
                    405:                (interfaces[i].addr.s_addr & interfaces[i].netmask.s_addr)
                    406:                == addr.s_addr)
                    407:                return(TRUE);
                    408:     }
                    409:
                    410:     return(FALSE);
                    411: }
                    412:
                    413: /*
1.4       millert   414:  * Returns 0 if the hostname matches the pattern and non-zero otherwise.
                    415:  */
                    416: int
                    417: hostname_matches(shost, lhost, pattern)
                    418:     char *shost;
                    419:     char *lhost;
                    420:     char *pattern;
                    421: {
                    422:     if (has_meta(pattern)) {
                    423:        if (strchr(pattern, '.'))
                    424:            return(fnmatch(pattern, lhost, FNM_CASEFOLD));
                    425:        else
                    426:            return(fnmatch(pattern, shost, FNM_CASEFOLD));
                    427:     } else {
                    428:        if (strchr(pattern, '.'))
                    429:            return(strcasecmp(lhost, pattern));
                    430:        else
                    431:            return(strcasecmp(shost, pattern));
                    432:     }
                    433: }
                    434:
                    435: /*
1.1       millert   436:  *  Returns TRUE if the given user belongs to the named group,
                    437:  *  else returns FALSE.
                    438:  */
                    439: int
                    440: usergr_matches(group, user)
                    441:     char *group;
                    442:     char *user;
                    443: {
                    444:     struct group *grp;
                    445:     struct passwd *pw;
                    446:     char **cur;
                    447:
                    448:     /* make sure we have a valid usergroup, sudo style */
                    449:     if (*group++ != '%')
                    450:        return(FALSE);
                    451:
                    452:     if ((grp = getgrnam(group)) == NULL)
                    453:        return(FALSE);
                    454:
                    455:     /*
                    456:      * Check against user's real gid as well as group's user list
                    457:      */
                    458:     if ((pw = getpwnam(user)) == NULL)
                    459:        return(FALSE);
                    460:
                    461:     if (grp->gr_gid == pw->pw_gid)
                    462:        return(TRUE);
                    463:
                    464:     for (cur=grp->gr_mem; *cur; cur++) {
                    465:        if (strcmp(*cur, user) == 0)
                    466:            return(TRUE);
                    467:     }
                    468:
                    469:     return(FALSE);
                    470: }
                    471:
                    472: /*
                    473:  * Returns TRUE if "host" and "user" belong to the netgroup "netgr",
1.3       millert   474:  * else return FALSE.  Either of "host", "shost" or "user" may be NULL
1.1       millert   475:  * in which case that argument is not checked...
                    476:  */
                    477: int
1.3       millert   478: netgr_matches(netgr, host, shost, user)
1.1       millert   479:     char *netgr;
                    480:     char *host;
1.3       millert   481:     char *shost;
1.1       millert   482:     char *user;
                    483: {
                    484: #ifdef HAVE_GETDOMAINNAME
                    485:     static char *domain = (char *) -1;
                    486: #else
                    487:     static char *domain = NULL;
                    488: #endif /* HAVE_GETDOMAINNAME */
                    489:
                    490:     /* make sure we have a valid netgroup, sudo style */
                    491:     if (*netgr++ != '+')
                    492:        return(FALSE);
                    493:
                    494: #ifdef HAVE_GETDOMAINNAME
                    495:     /* get the domain name (if any) */
                    496:     if (domain == (char *) -1) {
                    497:        domain = (char *) emalloc(MAXHOSTNAMELEN);
                    498:        if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
                    499:            free(domain);
                    500:            domain = NULL;
                    501:        }
                    502:     }
                    503: #endif /* HAVE_GETDOMAINNAME */
                    504:
                    505: #ifdef HAVE_INNETGR
1.3       millert   506:     if (innetgr(netgr, host, user, domain))
                    507:        return(TRUE);
                    508:     else if (host != shost && innetgr(netgr, shost, user, domain))
                    509:        return(TRUE);
                    510: #endif /* HAVE_INNETGR */
                    511:
1.1       millert   512:     return(FALSE);
                    513: }
                    514:
                    515: /*
                    516:  * Returns TRUE if "s" has shell meta characters in it,
                    517:  * else returns FALSE.
                    518:  */
                    519: static int
                    520: has_meta(s)
                    521:     char *s;
                    522: {
1.4       millert   523:     char *t;
1.1       millert   524:
                    525:     for (t = s; *t; t++) {
                    526:        if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
                    527:            return(TRUE);
                    528:     }
                    529:     return(FALSE);
                    530: }