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

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 */
1.2     ! millert    53: #ifdef HAVE_FNMATCH
1.1       millert    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
1.2     ! millert    94: static const char rcsid[] = "$Sudo: parse.c,v 1.123 1999/12/09 03:54:57 millert Exp $";
1.1       millert    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:      */
1.2     ! millert   145:     if (def_flag(I_AUTHENTICATE))
        !           146:        error = VALIDATE_NOT_OK;
        !           147:     else
        !           148:        error = VALIDATE_NOT_OK | FLAG_NOPASS;
1.1       millert   149:     if (check_cmnd == TRUE) {
                    150:        error |= FLAG_NO_HOST;
                    151:        if (!top)
                    152:            error |= FLAG_NO_USER;
                    153:     } else
                    154:        error |= FLAG_NO_CHECK;
                    155:
                    156:     /*
                    157:      * Only check the actual command if the check_cmnd flag is set.
                    158:      * It is not set for the "validate" and "list" pseudo-commands.
                    159:      * Always check the host and user.
                    160:      */
                    161:     if (check_cmnd == FALSE)
                    162:        while (top) {
                    163:            if (host_matches == TRUE) {
                    164:                /* User may always validate or list on allowed hosts */
                    165:                if (no_passwd == TRUE)
                    166:                    return(VALIDATE_OK | FLAG_NOPASS);
                    167:                else
                    168:                    return(VALIDATE_OK);
                    169:            }
                    170:            top--;
                    171:        }
                    172:     else
                    173:        while (top) {
                    174:            if (host_matches == TRUE) {
                    175:                error &= ~FLAG_NO_HOST;
                    176:                if (runas_matches == TRUE) {
                    177:                    if (cmnd_matches == TRUE) {
                    178:                        /*
                    179:                         * User was granted access to cmnd on host.
                    180:                         * If no passwd required return as such.
                    181:                         */
                    182:                        if (no_passwd == TRUE)
                    183:                            return(VALIDATE_OK | FLAG_NOPASS);
                    184:                        else
                    185:                            return(VALIDATE_OK);
                    186:                    } else if (cmnd_matches == FALSE) {
                    187:                        /*
                    188:                         * User was explicitly denied access to cmnd on host.
                    189:                         */
                    190:                        if (no_passwd == TRUE)
                    191:                            return(VALIDATE_NOT_OK | FLAG_NOPASS);
                    192:                        else
                    193:                            return(VALIDATE_NOT_OK);
                    194:                    }
                    195:                }
                    196:            }
                    197:            top--;
                    198:        }
                    199:
                    200:     /*
                    201:      * The user was not explicitly granted nor denied access.
                    202:      */
                    203:     return(error);
                    204: }
                    205:
                    206: /*
                    207:  * If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
                    208:  * otherwise, return TRUE if cmnd names one of the inodes in path.
                    209:  */
                    210: int
                    211: command_matches(cmnd, cmnd_args, path, sudoers_args)
                    212:     char *cmnd;
                    213:     char *cmnd_args;
                    214:     char *path;
                    215:     char *sudoers_args;
                    216: {
                    217:     int plen;
                    218:     static struct stat cst;
                    219:     struct stat pst;
                    220:     DIR *dirp;
                    221:     struct dirent *dent;
                    222:     char buf[MAXPATHLEN];
                    223:     static char *cmnd_base;
                    224:
                    225:     /* Don't bother with pseudo commands like "validate" */
                    226:     if (strchr(cmnd, '/') == NULL)
                    227:        return(FALSE);
                    228:
                    229:     plen = strlen(path);
                    230:
                    231:     /* Only need to stat cmnd once since it never changes */
                    232:     if (cst.st_dev == 0) {
                    233:        if (stat(cmnd, &cst) == -1)
                    234:            return(FALSE);
                    235:        if ((cmnd_base = strrchr(cmnd, '/')) == NULL)
                    236:            cmnd_base = cmnd;
                    237:        else
                    238:            cmnd_base++;
                    239:     }
                    240:
                    241:     /*
                    242:      * If the pathname has meta characters in it use fnmatch(3)
                    243:      * to do the matching
                    244:      */
                    245:     if (has_meta(path)) {
                    246:        /*
                    247:         * Return true if fnmatch(3) succeeds AND
                    248:         *  a) there are no args in sudoers OR
                    249:         *  b) there are no args on command line and none required by sudoers OR
                    250:         *  c) there are args in sudoers and on command line and they match
                    251:         * else return false.
                    252:         */
                    253:        if (fnmatch(path, cmnd, FNM_PATHNAME) != 0)
                    254:            return(FALSE);
                    255:        if (!sudoers_args ||
                    256:            (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
                    257:            (sudoers_args && fnmatch(sudoers_args, cmnd_args ? cmnd_args : "",
                    258:            0) == 0)) {
                    259:            if (safe_cmnd)
                    260:                free(safe_cmnd);
                    261:            safe_cmnd = estrdup(user_cmnd);
                    262:            return(TRUE);
                    263:        } else
                    264:            return(FALSE);
                    265:     } else {
                    266:        /*
                    267:         * No meta characters
                    268:         * Check to make sure this is not a directory spec (doesn't end in '/')
                    269:         */
                    270:        if (path[plen - 1] != '/') {
                    271:            char *p;
                    272:
                    273:            /* Only proceed if the basenames of cmnd and path are the same */
                    274:            if ((p = strrchr(path, '/')) == NULL)
                    275:                p = path;
                    276:            else
                    277:                p++;
                    278:            if (strcmp(cmnd_base, p) != 0 || stat(path, &pst) == -1)
                    279:                return(FALSE);
                    280:
                    281:            /*
                    282:             * Return true if inode/device matches AND
                    283:             *  a) there are no args in sudoers OR
                    284:             *  b) there are no args on command line and none req by sudoers OR
                    285:             *  c) there are args in sudoers and on command line and they match
                    286:             */
                    287:            if (cst.st_dev != pst.st_dev || cst.st_ino != pst.st_ino)
                    288:                return(FALSE);
                    289:            if (!sudoers_args ||
                    290:                (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
                    291:                (sudoers_args &&
                    292:                 fnmatch(sudoers_args, cmnd_args ? cmnd_args : "", 0) == 0)) {
                    293:                if (safe_cmnd)
                    294:                    free(safe_cmnd);
                    295:                safe_cmnd = estrdup(path);
                    296:                return(TRUE);
                    297:            } else
                    298:                return(FALSE);
                    299:        }
                    300:
                    301:        /*
                    302:         * Grot through path's directory entries, looking for cmnd.
                    303:         */
                    304:        dirp = opendir(path);
                    305:        if (dirp == NULL)
                    306:            return(FALSE);
                    307:
                    308:        while ((dent = readdir(dirp)) != NULL) {
                    309:            /* ignore paths > MAXPATHLEN (XXX - log) */
                    310:            if (plen + NAMLEN(dent) >= sizeof(buf))
                    311:                continue;
                    312:            strcpy(buf, path);
                    313:            strcat(buf, dent->d_name);
                    314:
                    315:            /* only stat if basenames are the same */
                    316:            if (strcmp(cmnd_base, dent->d_name) != 0 || stat(buf, &pst) == -1)
                    317:                continue;
                    318:            if (cst.st_dev == pst.st_dev && cst.st_ino == pst.st_ino) {
                    319:                if (safe_cmnd)
                    320:                    free(safe_cmnd);
                    321:                safe_cmnd = estrdup(buf);
                    322:                break;
                    323:            }
                    324:        }
                    325:
                    326:        closedir(dirp);
                    327:        return(dent != NULL);
                    328:     }
                    329: }
                    330:
                    331: /*
                    332:  * Returns TRUE if "n" is one of our ip addresses or if
                    333:  * "n" is a network that we are on, else returns FALSE.
                    334:  */
                    335: int
                    336: addr_matches(n)
                    337:     char *n;
                    338: {
                    339:     int i;
                    340:     char *m;
                    341:     struct in_addr addr, mask;
                    342:
                    343:     /* If there's an explicit netmask, use it. */
                    344:     if ((m = strchr(n, '/'))) {
                    345:        *m++ = '\0';
                    346:        addr.s_addr = inet_addr(n);
                    347:        if (strchr(m, '.'))
                    348:            mask.s_addr = inet_addr(m);
                    349:        else
                    350:            mask.s_addr = (1 << atoi(m)) - 1;   /* XXX - better way? */
                    351:        *(m - 1) = '/';
                    352:
                    353:        for (i = 0; i < num_interfaces; i++)
                    354:            if ((interfaces[i].addr.s_addr & mask.s_addr) == addr.s_addr)
                    355:                return(TRUE);
                    356:     } else {
                    357:        addr.s_addr = inet_addr(n);
                    358:
                    359:        for (i = 0; i < num_interfaces; i++)
                    360:            if (interfaces[i].addr.s_addr == addr.s_addr ||
                    361:                (interfaces[i].addr.s_addr & interfaces[i].netmask.s_addr)
                    362:                == addr.s_addr)
                    363:                return(TRUE);
                    364:     }
                    365:
                    366:     return(FALSE);
                    367: }
                    368:
                    369: /*
                    370:  *  Returns TRUE if the given user belongs to the named group,
                    371:  *  else returns FALSE.
                    372:  */
                    373: int
                    374: usergr_matches(group, user)
                    375:     char *group;
                    376:     char *user;
                    377: {
                    378:     struct group *grp;
                    379:     struct passwd *pw;
                    380:     char **cur;
                    381:
                    382:     /* make sure we have a valid usergroup, sudo style */
                    383:     if (*group++ != '%')
                    384:        return(FALSE);
                    385:
                    386:     if ((grp = getgrnam(group)) == NULL)
                    387:        return(FALSE);
                    388:
                    389:     /*
                    390:      * Check against user's real gid as well as group's user list
                    391:      */
                    392:     if ((pw = getpwnam(user)) == NULL)
                    393:        return(FALSE);
                    394:
                    395:     if (grp->gr_gid == pw->pw_gid)
                    396:        return(TRUE);
                    397:
                    398:     for (cur=grp->gr_mem; *cur; cur++) {
                    399:        if (strcmp(*cur, user) == 0)
                    400:            return(TRUE);
                    401:     }
                    402:
                    403:     return(FALSE);
                    404: }
                    405:
                    406: /*
                    407:  * Returns TRUE if "host" and "user" belong to the netgroup "netgr",
                    408:  * else return FALSE.  Either of "host" or "user" may be NULL
                    409:  * in which case that argument is not checked...
                    410:  */
                    411: int
                    412: netgr_matches(netgr, host, user)
                    413:     char *netgr;
                    414:     char *host;
                    415:     char *user;
                    416: {
                    417: #ifdef HAVE_GETDOMAINNAME
                    418:     static char *domain = (char *) -1;
                    419: #else
                    420:     static char *domain = NULL;
                    421: #endif /* HAVE_GETDOMAINNAME */
                    422:
                    423:     /* make sure we have a valid netgroup, sudo style */
                    424:     if (*netgr++ != '+')
                    425:        return(FALSE);
                    426:
                    427: #ifdef HAVE_GETDOMAINNAME
                    428:     /* get the domain name (if any) */
                    429:     if (domain == (char *) -1) {
                    430:        domain = (char *) emalloc(MAXHOSTNAMELEN);
                    431:        if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
                    432:            free(domain);
                    433:            domain = NULL;
                    434:        }
                    435:     }
                    436: #endif /* HAVE_GETDOMAINNAME */
                    437:
                    438: #ifdef HAVE_INNETGR
                    439:     return(innetgr(netgr, host, user, domain));
                    440: #else
                    441:     return(FALSE);
                    442: #endif /* HAVE_INNETGR */
                    443: }
                    444:
                    445: /*
                    446:  * Returns TRUE if "s" has shell meta characters in it,
                    447:  * else returns FALSE.
                    448:  */
                    449: static int
                    450: has_meta(s)
                    451:     char *s;
                    452: {
                    453:     register char *t;
                    454:
                    455:     for (t = s; *t; t++) {
                    456:        if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
                    457:            return(TRUE);
                    458:     }
                    459:     return(FALSE);
                    460: }