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

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