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

1.1     ! millert     1: /*
        !             2:  * Copyright (c) 1996, 1998, 1999 Todd C. Miller <Todd.Miller@courtesan.com>
        !             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:
        !            40: #include <stdio.h>
        !            41: #ifdef STDC_HEADERS
        !            42: # include <stdlib.h>
        !            43: #endif /* STDC_HEADERS */
        !            44: #ifdef HAVE_UNISTD_H
        !            45: # include <unistd.h>
        !            46: #endif /* HAVE_UNISTD_H */
        !            47: #ifdef HAVE_STRING_H
        !            48: # include <string.h>
        !            49: #endif /* HAVE_STRING_H */
        !            50: #ifdef HAVE_STRINGS_H
        !            51: # include <strings.h>
        !            52: #endif /* HAVE_STRINGS_H */
        !            53: #if defined(HAVE_FNMATCH) && defined(HAVE_FNMATCH_H)
        !            54: # include <fnmatch.h>
        !            55: #endif /* HAVE_FNMATCH_H */
        !            56: #ifdef HAVE_NETGROUP_H
        !            57: # include <netgroup.h>
        !            58: #endif /* HAVE_NETGROUP_H */
        !            59: #include <ctype.h>
        !            60: #include <pwd.h>
        !            61: #include <grp.h>
        !            62: #include <sys/param.h>
        !            63: #include <sys/types.h>
        !            64: #include <netinet/in.h>
        !            65: #include <arpa/inet.h>
        !            66: #include <netdb.h>
        !            67: #include <sys/stat.h>
        !            68: #if HAVE_DIRENT_H
        !            69: # include <dirent.h>
        !            70: # define NAMLEN(dirent) strlen((dirent)->d_name)
        !            71: #else
        !            72: # define dirent direct
        !            73: # define NAMLEN(dirent) (dirent)->d_namlen
        !            74: # if HAVE_SYS_NDIR_H
        !            75: #  include <sys/ndir.h>
        !            76: # endif
        !            77: # if HAVE_SYS_DIR_H
        !            78: #  include <sys/dir.h>
        !            79: # endif
        !            80: # if HAVE_NDIR_H
        !            81: #  include <ndir.h>
        !            82: # endif
        !            83: #endif
        !            84:
        !            85: #include "sudo.h"
        !            86: #include "parse.h"
        !            87: #include "interfaces.h"
        !            88:
        !            89: #ifndef HAVE_FNMATCH
        !            90: # include "emul/fnmatch.h"
        !            91: #endif /* HAVE_FNMATCH */
        !            92:
        !            93: #ifndef lint
        !            94: static const char rcsid[] = "$Sudo: parse.c,v 1.121 1999/08/28 10:00:22 millert Exp $";
        !            95: #endif /* lint */
        !            96:
        !            97: /*
        !            98:  * Globals
        !            99:  */
        !           100: int parse_error = FALSE;
        !           101: extern FILE *yyin, *yyout;
        !           102:
        !           103: /*
        !           104:  * Prototypes
        !           105:  */
        !           106: static int has_meta    __P((char *));
        !           107:        void init_parser        __P((void));
        !           108:
        !           109: /*
        !           110:  * Look up the user in the sudoers file and check to see if they are
        !           111:  * allowed to run the specified command on this host as the target user.
        !           112:  */
        !           113: int
        !           114: sudoers_lookup(check_cmnd)
        !           115:     int check_cmnd;
        !           116: {
        !           117:     int error;
        !           118:
        !           119:     /* Become sudoers file owner */
        !           120:     set_perms(PERM_SUDOERS, 0);
        !           121:
        !           122:     /* We opened _PATH_SUDOERS in check_sudoers() so just rewind it. */
        !           123:     rewind(sudoers_fp);
        !           124:     yyin = sudoers_fp;
        !           125:     yyout = stdout;
        !           126:
        !           127:     /* Allocate space for data structures in the parser. */
        !           128:     init_parser();
        !           129:
        !           130:     /* Need to be root while stat'ing things in the parser. */
        !           131:     set_perms(PERM_ROOT, 0);
        !           132:     error = yyparse();
        !           133:
        !           134:     /* Close the sudoers file now that we are done with it. */
        !           135:     (void) fclose(sudoers_fp);
        !           136:     sudoers_fp = NULL;
        !           137:
        !           138:     if (error || parse_error)
        !           139:        return(VALIDATE_ERROR);
        !           140:
        !           141:     /*
        !           142:      * Assume the worst.  If the stack is empty the user was
        !           143:      * not mentioned at all.
        !           144:      */
        !           145:     error = VALIDATE_NOT_OK;
        !           146:     if (check_cmnd == TRUE) {
        !           147:        error |= FLAG_NO_HOST;
        !           148:        if (!top)
        !           149:            error |= FLAG_NO_USER;
        !           150:     } else
        !           151:        error |= FLAG_NO_CHECK;
        !           152:
        !           153:     /*
        !           154:      * Only check the actual command if the check_cmnd flag is set.
        !           155:      * It is not set for the "validate" and "list" pseudo-commands.
        !           156:      * Always check the host and user.
        !           157:      */
        !           158:     if (check_cmnd == FALSE)
        !           159:        while (top) {
        !           160:            if (host_matches == TRUE) {
        !           161:                /* User may always validate or list on allowed hosts */
        !           162:                if (no_passwd == TRUE)
        !           163:                    return(VALIDATE_OK | FLAG_NOPASS);
        !           164:                else
        !           165:                    return(VALIDATE_OK);
        !           166:            }
        !           167:            top--;
        !           168:        }
        !           169:     else
        !           170:        while (top) {
        !           171:            if (host_matches == TRUE) {
        !           172:                error &= ~FLAG_NO_HOST;
        !           173:                if (runas_matches == TRUE) {
        !           174:                    if (cmnd_matches == TRUE) {
        !           175:                        /*
        !           176:                         * User was granted access to cmnd on host.
        !           177:                         * If no passwd required return as such.
        !           178:                         */
        !           179:                        if (no_passwd == TRUE)
        !           180:                            return(VALIDATE_OK | FLAG_NOPASS);
        !           181:                        else
        !           182:                            return(VALIDATE_OK);
        !           183:                    } else if (cmnd_matches == FALSE) {
        !           184:                        /*
        !           185:                         * User was explicitly denied access to cmnd on host.
        !           186:                         */
        !           187:                        if (no_passwd == TRUE)
        !           188:                            return(VALIDATE_NOT_OK | FLAG_NOPASS);
        !           189:                        else
        !           190:                            return(VALIDATE_NOT_OK);
        !           191:                    }
        !           192:                }
        !           193:            }
        !           194:            top--;
        !           195:        }
        !           196:
        !           197:     /*
        !           198:      * The user was not explicitly granted nor denied access.
        !           199:      */
        !           200:     return(error);
        !           201: }
        !           202:
        !           203: /*
        !           204:  * If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
        !           205:  * otherwise, return TRUE if cmnd names one of the inodes in path.
        !           206:  */
        !           207: int
        !           208: command_matches(cmnd, cmnd_args, path, sudoers_args)
        !           209:     char *cmnd;
        !           210:     char *cmnd_args;
        !           211:     char *path;
        !           212:     char *sudoers_args;
        !           213: {
        !           214:     int plen;
        !           215:     static struct stat cst;
        !           216:     struct stat pst;
        !           217:     DIR *dirp;
        !           218:     struct dirent *dent;
        !           219:     char buf[MAXPATHLEN];
        !           220:     static char *cmnd_base;
        !           221:
        !           222:     /* Don't bother with pseudo commands like "validate" */
        !           223:     if (strchr(cmnd, '/') == NULL)
        !           224:        return(FALSE);
        !           225:
        !           226:     plen = strlen(path);
        !           227:
        !           228:     /* Only need to stat cmnd once since it never changes */
        !           229:     if (cst.st_dev == 0) {
        !           230:        if (stat(cmnd, &cst) == -1)
        !           231:            return(FALSE);
        !           232:        if ((cmnd_base = strrchr(cmnd, '/')) == NULL)
        !           233:            cmnd_base = cmnd;
        !           234:        else
        !           235:            cmnd_base++;
        !           236:     }
        !           237:
        !           238:     /*
        !           239:      * If the pathname has meta characters in it use fnmatch(3)
        !           240:      * to do the matching
        !           241:      */
        !           242:     if (has_meta(path)) {
        !           243:        /*
        !           244:         * Return true if fnmatch(3) succeeds AND
        !           245:         *  a) there are no args in sudoers OR
        !           246:         *  b) there are no args on command line and none required by sudoers OR
        !           247:         *  c) there are args in sudoers and on command line and they match
        !           248:         * else return false.
        !           249:         */
        !           250:        if (fnmatch(path, cmnd, FNM_PATHNAME) != 0)
        !           251:            return(FALSE);
        !           252:        if (!sudoers_args ||
        !           253:            (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
        !           254:            (sudoers_args && fnmatch(sudoers_args, cmnd_args ? cmnd_args : "",
        !           255:            0) == 0)) {
        !           256:            if (safe_cmnd)
        !           257:                free(safe_cmnd);
        !           258:            safe_cmnd = estrdup(user_cmnd);
        !           259:            return(TRUE);
        !           260:        } else
        !           261:            return(FALSE);
        !           262:     } else {
        !           263:        /*
        !           264:         * No meta characters
        !           265:         * Check to make sure this is not a directory spec (doesn't end in '/')
        !           266:         */
        !           267:        if (path[plen - 1] != '/') {
        !           268:            char *p;
        !           269:
        !           270:            /* Only proceed if the basenames of cmnd and path are the same */
        !           271:            if ((p = strrchr(path, '/')) == NULL)
        !           272:                p = path;
        !           273:            else
        !           274:                p++;
        !           275:            if (strcmp(cmnd_base, p) != 0 || stat(path, &pst) == -1)
        !           276:                return(FALSE);
        !           277:
        !           278:            /*
        !           279:             * Return true if inode/device matches AND
        !           280:             *  a) there are no args in sudoers OR
        !           281:             *  b) there are no args on command line and none req by sudoers OR
        !           282:             *  c) there are args in sudoers and on command line and they match
        !           283:             */
        !           284:            if (cst.st_dev != pst.st_dev || cst.st_ino != pst.st_ino)
        !           285:                return(FALSE);
        !           286:            if (!sudoers_args ||
        !           287:                (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
        !           288:                (sudoers_args &&
        !           289:                 fnmatch(sudoers_args, cmnd_args ? cmnd_args : "", 0) == 0)) {
        !           290:                if (safe_cmnd)
        !           291:                    free(safe_cmnd);
        !           292:                safe_cmnd = estrdup(path);
        !           293:                return(TRUE);
        !           294:            } else
        !           295:                return(FALSE);
        !           296:        }
        !           297:
        !           298:        /*
        !           299:         * Grot through path's directory entries, looking for cmnd.
        !           300:         */
        !           301:        dirp = opendir(path);
        !           302:        if (dirp == NULL)
        !           303:            return(FALSE);
        !           304:
        !           305:        while ((dent = readdir(dirp)) != NULL) {
        !           306:            /* ignore paths > MAXPATHLEN (XXX - log) */
        !           307:            if (plen + NAMLEN(dent) >= sizeof(buf))
        !           308:                continue;
        !           309:            strcpy(buf, path);
        !           310:            strcat(buf, dent->d_name);
        !           311:
        !           312:            /* only stat if basenames are the same */
        !           313:            if (strcmp(cmnd_base, dent->d_name) != 0 || stat(buf, &pst) == -1)
        !           314:                continue;
        !           315:            if (cst.st_dev == pst.st_dev && cst.st_ino == pst.st_ino) {
        !           316:                if (safe_cmnd)
        !           317:                    free(safe_cmnd);
        !           318:                safe_cmnd = estrdup(buf);
        !           319:                break;
        !           320:            }
        !           321:        }
        !           322:
        !           323:        closedir(dirp);
        !           324:        return(dent != NULL);
        !           325:     }
        !           326: }
        !           327:
        !           328: /*
        !           329:  * Returns TRUE if "n" is one of our ip addresses or if
        !           330:  * "n" is a network that we are on, else returns FALSE.
        !           331:  */
        !           332: int
        !           333: addr_matches(n)
        !           334:     char *n;
        !           335: {
        !           336:     int i;
        !           337:     char *m;
        !           338:     struct in_addr addr, mask;
        !           339:
        !           340:     /* If there's an explicit netmask, use it. */
        !           341:     if ((m = strchr(n, '/'))) {
        !           342:        *m++ = '\0';
        !           343:        addr.s_addr = inet_addr(n);
        !           344:        if (strchr(m, '.'))
        !           345:            mask.s_addr = inet_addr(m);
        !           346:        else
        !           347:            mask.s_addr = (1 << atoi(m)) - 1;   /* XXX - better way? */
        !           348:        *(m - 1) = '/';
        !           349:
        !           350:        for (i = 0; i < num_interfaces; i++)
        !           351:            if ((interfaces[i].addr.s_addr & mask.s_addr) == addr.s_addr)
        !           352:                return(TRUE);
        !           353:     } else {
        !           354:        addr.s_addr = inet_addr(n);
        !           355:
        !           356:        for (i = 0; i < num_interfaces; i++)
        !           357:            if (interfaces[i].addr.s_addr == addr.s_addr ||
        !           358:                (interfaces[i].addr.s_addr & interfaces[i].netmask.s_addr)
        !           359:                == addr.s_addr)
        !           360:                return(TRUE);
        !           361:     }
        !           362:
        !           363:     return(FALSE);
        !           364: }
        !           365:
        !           366: /*
        !           367:  *  Returns TRUE if the given user belongs to the named group,
        !           368:  *  else returns FALSE.
        !           369:  */
        !           370: int
        !           371: usergr_matches(group, user)
        !           372:     char *group;
        !           373:     char *user;
        !           374: {
        !           375:     struct group *grp;
        !           376:     struct passwd *pw;
        !           377:     char **cur;
        !           378:
        !           379:     /* make sure we have a valid usergroup, sudo style */
        !           380:     if (*group++ != '%')
        !           381:        return(FALSE);
        !           382:
        !           383:     if ((grp = getgrnam(group)) == NULL)
        !           384:        return(FALSE);
        !           385:
        !           386:     /*
        !           387:      * Check against user's real gid as well as group's user list
        !           388:      */
        !           389:     if ((pw = getpwnam(user)) == NULL)
        !           390:        return(FALSE);
        !           391:
        !           392:     if (grp->gr_gid == pw->pw_gid)
        !           393:        return(TRUE);
        !           394:
        !           395:     for (cur=grp->gr_mem; *cur; cur++) {
        !           396:        if (strcmp(*cur, user) == 0)
        !           397:            return(TRUE);
        !           398:     }
        !           399:
        !           400:     return(FALSE);
        !           401: }
        !           402:
        !           403: /*
        !           404:  * Returns TRUE if "host" and "user" belong to the netgroup "netgr",
        !           405:  * else return FALSE.  Either of "host" or "user" may be NULL
        !           406:  * in which case that argument is not checked...
        !           407:  */
        !           408: int
        !           409: netgr_matches(netgr, host, user)
        !           410:     char *netgr;
        !           411:     char *host;
        !           412:     char *user;
        !           413: {
        !           414: #ifdef HAVE_GETDOMAINNAME
        !           415:     static char *domain = (char *) -1;
        !           416: #else
        !           417:     static char *domain = NULL;
        !           418: #endif /* HAVE_GETDOMAINNAME */
        !           419:
        !           420:     /* make sure we have a valid netgroup, sudo style */
        !           421:     if (*netgr++ != '+')
        !           422:        return(FALSE);
        !           423:
        !           424: #ifdef HAVE_GETDOMAINNAME
        !           425:     /* get the domain name (if any) */
        !           426:     if (domain == (char *) -1) {
        !           427:        domain = (char *) emalloc(MAXHOSTNAMELEN);
        !           428:        if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
        !           429:            free(domain);
        !           430:            domain = NULL;
        !           431:        }
        !           432:     }
        !           433: #endif /* HAVE_GETDOMAINNAME */
        !           434:
        !           435: #ifdef HAVE_INNETGR
        !           436:     return(innetgr(netgr, host, user, domain));
        !           437: #else
        !           438:     return(FALSE);
        !           439: #endif /* HAVE_INNETGR */
        !           440: }
        !           441:
        !           442: /*
        !           443:  * Returns TRUE if "s" has shell meta characters in it,
        !           444:  * else returns FALSE.
        !           445:  */
        !           446: static int
        !           447: has_meta(s)
        !           448:     char *s;
        !           449: {
        !           450:     register char *t;
        !           451:
        !           452:     for (t = s; *t; t++) {
        !           453:        if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
        !           454:            return(TRUE);
        !           455:     }
        !           456:     return(FALSE);
        !           457: }