[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.5.4.1

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