Annotation of embedaddon/sudo/plugins/sudoers/match.c, revision 1.1.1.3

1.1       misho       1: /*
1.1.1.2   misho       2:  * Copyright (c) 1996, 1998-2005, 2007-2012
1.1       misho       3:  *     Todd C. Miller <Todd.Miller@courtesan.com>
                      4:  *
                      5:  * Permission to use, copy, modify, and distribute this software for any
                      6:  * purpose with or without fee is hereby granted, provided that the above
                      7:  * copyright notice and this permission notice appear in all copies.
                      8:  *
                      9:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
                     10:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
                     11:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
                     12:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     13:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     14:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     15:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
                     16:  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
                     17:  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                     18:  *
                     19:  * Sponsored in part by the Defense Advanced Research Projects
                     20:  * Agency (DARPA) and Air Force Research Laboratory, Air Force
                     21:  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
                     22:  */
                     23: 
                     24: #include <config.h>
                     25: 
                     26: #include <sys/types.h>
                     27: #include <sys/param.h>
                     28: #include <sys/stat.h>
                     29: #include <stdio.h>
                     30: #ifdef STDC_HEADERS
                     31: # include <stdlib.h>
                     32: # include <stddef.h>
                     33: #else
                     34: # ifdef HAVE_STDLIB_H
                     35: #  include <stdlib.h>
                     36: # endif
                     37: #endif /* STDC_HEADERS */
                     38: #ifdef HAVE_STRING_H
                     39: # include <string.h>
                     40: #endif /* HAVE_STRING_H */
                     41: #ifdef HAVE_STRINGS_H
                     42: # include <strings.h>
                     43: #endif /* HAVE_STRINGS_H */
                     44: #ifdef HAVE_UNISTD_H
                     45: # include <unistd.h>
                     46: #endif /* HAVE_UNISTD_H */
                     47: #ifdef HAVE_FNMATCH
                     48: # include <fnmatch.h>
                     49: #endif /* HAVE_FNMATCH */
1.1.1.2   misho      50: #ifdef HAVE_GLOB
1.1       misho      51: # include <glob.h>
1.1.1.2   misho      52: #endif /* HAVE_GLOB */
1.1       misho      53: #ifdef HAVE_NETGROUP_H
                     54: # include <netgroup.h>
                     55: #endif /* HAVE_NETGROUP_H */
                     56: #include <ctype.h>
                     57: #include <pwd.h>
                     58: #include <grp.h>
                     59: #include <netdb.h>
                     60: #ifdef HAVE_DIRENT_H
                     61: # include <dirent.h>
                     62: # define NAMLEN(dirent) strlen((dirent)->d_name)
                     63: #else
                     64: # define dirent direct
                     65: # define NAMLEN(dirent) (dirent)->d_namlen
                     66: # ifdef HAVE_SYS_NDIR_H
                     67: #  include <sys/ndir.h>
                     68: # endif
                     69: # ifdef HAVE_SYS_DIR_H
                     70: #  include <sys/dir.h>
                     71: # endif
                     72: # ifdef HAVE_NDIR_H
                     73: #  include <ndir.h>
                     74: # endif
                     75: #endif
                     76: 
                     77: #include "sudoers.h"
                     78: #include "parse.h"
                     79: #include <gram.h>
                     80: 
                     81: #ifndef HAVE_FNMATCH
                     82: # include "compat/fnmatch.h"
                     83: #endif /* HAVE_FNMATCH */
1.1.1.2   misho      84: #ifndef HAVE_GLOB
1.1       misho      85: # include "compat/glob.h"
1.1.1.2   misho      86: #endif /* HAVE_GLOB */
1.1       misho      87: 
                     88: static struct member_list empty;
                     89: 
1.1.1.2   misho      90: static bool command_matches_dir(char *, size_t);
                     91: static bool command_matches_glob(char *, char *);
                     92: static bool command_matches_fnmatch(char *, char *);
                     93: static bool command_matches_normal(char *, char *);
1.1       misho      94: 
                     95: /*
1.1.1.2   misho      96:  * Returns true if string 's' contains meta characters.
1.1       misho      97:  */
                     98: #define has_meta(s)    (strpbrk(s, "\\?*[]") != NULL)
                     99: 
                    100: /*
                    101:  * Check for user described by pw in a list of members.
                    102:  * Returns ALLOW, DENY or UNSPEC.
                    103:  */
                    104: static int
                    105: _userlist_matches(struct passwd *pw, struct member_list *list)
                    106: {
                    107:     struct member *m;
                    108:     struct alias *a;
                    109:     int rval, matched = UNSPEC;
1.1.1.2   misho     110:     debug_decl(_userlist_matches, SUDO_DEBUG_MATCH)
1.1       misho     111: 
                    112:     tq_foreach_rev(list, m) {
                    113:        switch (m->type) {
                    114:            case ALL:
                    115:                matched = !m->negated;
                    116:                break;
                    117:            case NETGROUP:
                    118:                if (netgr_matches(m->name, NULL, NULL, pw->pw_name))
                    119:                    matched = !m->negated;
                    120:                break;
                    121:            case USERGROUP:
                    122:                if (usergr_matches(m->name, pw->pw_name, pw))
                    123:                    matched = !m->negated;
                    124:                break;
                    125:            case ALIAS:
                    126:                if ((a = alias_find(m->name, USERALIAS)) != NULL) {
                    127:                    rval = _userlist_matches(pw, &a->members);
                    128:                    if (rval != UNSPEC)
                    129:                        matched = m->negated ? !rval : rval;
                    130:                    break;
                    131:                }
                    132:                /* FALLTHROUGH */
                    133:            case WORD:
                    134:                if (userpw_matches(m->name, pw->pw_name, pw))
                    135:                    matched = !m->negated;
                    136:                break;
                    137:        }
                    138:        if (matched != UNSPEC)
                    139:            break;
                    140:     }
1.1.1.2   misho     141:     debug_return_bool(matched);
1.1       misho     142: }
                    143: 
                    144: int
                    145: userlist_matches(struct passwd *pw, struct member_list *list)
                    146: {
                    147:     alias_seqno++;
                    148:     return _userlist_matches(pw, list);
                    149: }
                    150: 
                    151: /*
                    152:  * Check for user described by pw in a list of members.
                    153:  * If both lists are empty compare against def_runas_default.
                    154:  * Returns ALLOW, DENY or UNSPEC.
                    155:  */
                    156: static int
1.1.1.3 ! misho     157: _runaslist_matches(struct member_list *user_list,
        !           158:     struct member_list *group_list, struct member **matching_user,
        !           159:     struct member **matching_group)
1.1       misho     160: {
                    161:     struct member *m;
                    162:     struct alias *a;
                    163:     int rval;
                    164:     int user_matched = UNSPEC;
                    165:     int group_matched = UNSPEC;
1.1.1.2   misho     166:     debug_decl(_runaslist_matches, SUDO_DEBUG_MATCH)
1.1       misho     167: 
                    168:     if (runas_pw != NULL) {
                    169:        /* If no runas user or runas group listed in sudoers, use default. */
                    170:        if (tq_empty(user_list) && tq_empty(group_list))
1.1.1.2   misho     171:            debug_return_int(userpw_matches(def_runas_default, runas_pw->pw_name, runas_pw));
1.1       misho     172: 
                    173:        tq_foreach_rev(user_list, m) {
                    174:            switch (m->type) {
                    175:                case ALL:
                    176:                    user_matched = !m->negated;
                    177:                    break;
                    178:                case NETGROUP:
                    179:                    if (netgr_matches(m->name, NULL, NULL, runas_pw->pw_name))
                    180:                        user_matched = !m->negated;
                    181:                    break;
                    182:                case USERGROUP:
                    183:                    if (usergr_matches(m->name, runas_pw->pw_name, runas_pw))
                    184:                        user_matched = !m->negated;
                    185:                    break;
                    186:                case ALIAS:
                    187:                    if ((a = alias_find(m->name, RUNASALIAS)) != NULL) {
1.1.1.3 ! misho     188:                        rval = _runaslist_matches(&a->members, &empty,
        !           189:                            matching_user, NULL);
1.1       misho     190:                        if (rval != UNSPEC)
                    191:                            user_matched = m->negated ? !rval : rval;
                    192:                        break;
                    193:                    }
                    194:                    /* FALLTHROUGH */
                    195:                case WORD:
                    196:                    if (userpw_matches(m->name, runas_pw->pw_name, runas_pw))
                    197:                        user_matched = !m->negated;
                    198:                    break;
1.1.1.3 ! misho     199:                case MYSELF:
        !           200:                    if (!ISSET(sudo_user.flags, RUNAS_USER_SPECIFIED) ||
        !           201:                        strcmp(user_name, runas_pw->pw_name) == 0)
        !           202:                        user_matched = !m->negated;
        !           203:                    break;
1.1       misho     204:            }
1.1.1.3 ! misho     205:            if (user_matched != UNSPEC) {
        !           206:                if (matching_user != NULL && m->type != ALIAS)
        !           207:                    *matching_user = m;
1.1       misho     208:                break;
1.1.1.3 ! misho     209:            }
1.1       misho     210:        }
                    211:     }
                    212: 
                    213:     if (runas_gr != NULL) {
                    214:        if (user_matched == UNSPEC) {
                    215:            if (runas_pw == NULL || strcmp(runas_pw->pw_name, user_name) == 0)
                    216:                user_matched = ALLOW;   /* only changing group */
                    217:        }
                    218:        tq_foreach_rev(group_list, m) {
                    219:            switch (m->type) {
                    220:                case ALL:
                    221:                    group_matched = !m->negated;
                    222:                    break;
                    223:                case ALIAS:
                    224:                    if ((a = alias_find(m->name, RUNASALIAS)) != NULL) {
1.1.1.3 ! misho     225:                        rval = _runaslist_matches(&empty, &a->members,
        !           226:                            NULL, matching_group);
1.1       misho     227:                        if (rval != UNSPEC)
                    228:                            group_matched = m->negated ? !rval : rval;
                    229:                        break;
                    230:                    }
                    231:                    /* FALLTHROUGH */
                    232:                case WORD:
                    233:                    if (group_matches(m->name, runas_gr))
                    234:                        group_matched = !m->negated;
                    235:                    break;
                    236:            }
1.1.1.3 ! misho     237:            if (group_matched != UNSPEC) {
        !           238:                if (matching_group != NULL && m->type != ALIAS)
        !           239:                    *matching_group = m;
1.1       misho     240:                break;
1.1.1.3 ! misho     241:            }
1.1       misho     242:        }
                    243:        if (group_matched == UNSPEC) {
                    244:            if (runas_pw != NULL && runas_pw->pw_gid == runas_gr->gr_gid)
                    245:                group_matched = ALLOW;  /* runas group matches passwd db */
                    246:        }
                    247:     }
                    248: 
                    249:     if (user_matched == DENY || group_matched == DENY)
1.1.1.2   misho     250:        debug_return_int(DENY);
1.1       misho     251:     if (user_matched == group_matched || runas_gr == NULL)
1.1.1.2   misho     252:        debug_return_int(user_matched);
                    253:     debug_return_int(UNSPEC);
1.1       misho     254: }
                    255: 
                    256: int
1.1.1.3 ! misho     257: runaslist_matches(struct member_list *user_list,
        !           258:     struct member_list *group_list, struct member **matching_user,
        !           259:     struct member **matching_group)
1.1       misho     260: {
                    261:     alias_seqno++;
                    262:     return _runaslist_matches(user_list ? user_list : &empty,
1.1.1.3 ! misho     263:        group_list ? group_list : &empty, matching_user, matching_group);
1.1       misho     264: }
                    265: 
                    266: /*
                    267:  * Check for host and shost in a list of members.
                    268:  * Returns ALLOW, DENY or UNSPEC.
                    269:  */
                    270: static int
                    271: _hostlist_matches(struct member_list *list)
                    272: {
                    273:     struct member *m;
                    274:     struct alias *a;
                    275:     int rval, matched = UNSPEC;
1.1.1.2   misho     276:     debug_decl(_hostlist_matches, SUDO_DEBUG_MATCH)
1.1       misho     277: 
                    278:     tq_foreach_rev(list, m) {
                    279:        switch (m->type) {
                    280:            case ALL:
                    281:                matched = !m->negated;
                    282:                break;
                    283:            case NETGROUP:
                    284:                if (netgr_matches(m->name, user_host, user_shost, NULL))
                    285:                    matched = !m->negated;
                    286:                break;
                    287:            case NTWKADDR:
                    288:                if (addr_matches(m->name))
                    289:                    matched = !m->negated;
                    290:                break;
                    291:            case ALIAS:
                    292:                if ((a = alias_find(m->name, HOSTALIAS)) != NULL) {
                    293:                    rval = _hostlist_matches(&a->members);
                    294:                    if (rval != UNSPEC)
                    295:                        matched = m->negated ? !rval : rval;
                    296:                    break;
                    297:                }
                    298:                /* FALLTHROUGH */
                    299:            case WORD:
                    300:                if (hostname_matches(user_shost, user_host, m->name))
                    301:                    matched = !m->negated;
                    302:                break;
                    303:        }
                    304:        if (matched != UNSPEC)
                    305:            break;
                    306:     }
1.1.1.2   misho     307:     debug_return_bool(matched);
1.1       misho     308: }
                    309: 
                    310: int
                    311: hostlist_matches(struct member_list *list)
                    312: {
                    313:     alias_seqno++;
                    314:     return _hostlist_matches(list);
                    315: }
                    316: 
                    317: /*
                    318:  * Check for cmnd and args in a list of members.
                    319:  * Returns ALLOW, DENY or UNSPEC.
                    320:  */
                    321: static int
                    322: _cmndlist_matches(struct member_list *list)
                    323: {
                    324:     struct member *m;
                    325:     int matched = UNSPEC;
1.1.1.2   misho     326:     debug_decl(_cmndlist_matches, SUDO_DEBUG_MATCH)
1.1       misho     327: 
                    328:     tq_foreach_rev(list, m) {
                    329:        matched = cmnd_matches(m);
                    330:        if (matched != UNSPEC)
                    331:            break;
                    332:     }
1.1.1.2   misho     333:     debug_return_bool(matched);
1.1       misho     334: }
                    335: 
                    336: int
                    337: cmndlist_matches(struct member_list *list)
                    338: {
                    339:     alias_seqno++;
                    340:     return _cmndlist_matches(list);
                    341: }
                    342: 
                    343: /*
                    344:  * Check cmnd and args.
                    345:  * Returns ALLOW, DENY or UNSPEC.
                    346:  */
                    347: int
                    348: cmnd_matches(struct member *m)
                    349: {
                    350:     struct alias *a;
                    351:     struct sudo_command *c;
                    352:     int rval, matched = UNSPEC;
1.1.1.2   misho     353:     debug_decl(cmnd_matches, SUDO_DEBUG_MATCH)
1.1       misho     354: 
                    355:     switch (m->type) {
                    356:        case ALL:
                    357:            matched = !m->negated;
                    358:            break;
                    359:        case ALIAS:
                    360:            alias_seqno++;
                    361:            if ((a = alias_find(m->name, CMNDALIAS)) != NULL) {
                    362:                rval = _cmndlist_matches(&a->members);
                    363:                if (rval != UNSPEC)
                    364:                    matched = m->negated ? !rval : rval;
                    365:            }
                    366:            break;
                    367:        case COMMAND:
                    368:            c = (struct sudo_command *)m->name;
                    369:            if (command_matches(c->cmnd, c->args))
                    370:                matched = !m->negated;
                    371:            break;
                    372:     }
1.1.1.2   misho     373:     debug_return_bool(matched);
1.1       misho     374: }
                    375: 
1.1.1.2   misho     376: static bool
1.1       misho     377: command_args_match(sudoers_cmnd, sudoers_args)
                    378:     char *sudoers_cmnd;
                    379:     char *sudoers_args;
                    380: {
                    381:     int flags = 0;
1.1.1.2   misho     382:     debug_decl(command_args_match, SUDO_DEBUG_MATCH)
1.1       misho     383: 
                    384:     /*
                    385:      * If no args specified in sudoers, any user args are allowed.
                    386:      * If the empty string is specified in sudoers, no user args are allowed.
                    387:      */
                    388:     if (!sudoers_args ||
                    389:        (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)))
1.1.1.2   misho     390:        debug_return_bool(true);
1.1       misho     391:     /*
                    392:      * If args are specified in sudoers, they must match the user args.
                    393:      * If running as sudoedit, all args are assumed to be paths.
                    394:      */
                    395:     if (sudoers_args) {
                    396:        /* For sudoedit, all args are assumed to be pathnames. */
                    397:        if (strcmp(sudoers_cmnd, "sudoedit") == 0)
                    398:            flags = FNM_PATHNAME;
                    399:        if (fnmatch(sudoers_args, user_args ? user_args : "", flags) == 0)
1.1.1.2   misho     400:            debug_return_bool(true);
1.1       misho     401:     }
1.1.1.2   misho     402:     debug_return_bool(false);
1.1       misho     403: }
                    404: 
                    405: /*
1.1.1.2   misho     406:  * If path doesn't end in /, return true iff cmnd & path name the same inode;
                    407:  * otherwise, return true if user_cmnd names one of the inodes in path.
1.1       misho     408:  */
1.1.1.2   misho     409: bool
1.1       misho     410: command_matches(char *sudoers_cmnd, char *sudoers_args)
                    411: {
1.1.1.2   misho     412:     debug_decl(command_matches, SUDO_DEBUG_MATCH)
                    413: 
1.1       misho     414:     /* Check for pseudo-commands */
                    415:     if (sudoers_cmnd[0] != '/') {
                    416:        /*
                    417:         * Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
                    418:         *  a) there are no args in sudoers OR
                    419:         *  b) there are no args on command line and none req by sudoers OR
                    420:         *  c) there are args in sudoers and on command line and they match
                    421:         */
                    422:        if (strcmp(sudoers_cmnd, "sudoedit") != 0 ||
                    423:            strcmp(user_cmnd, "sudoedit") != 0)
1.1.1.2   misho     424:            debug_return_bool(false);
1.1       misho     425:        if (command_args_match(sudoers_cmnd, sudoers_args)) {
                    426:            efree(safe_cmnd);
                    427:            safe_cmnd = estrdup(sudoers_cmnd);
1.1.1.2   misho     428:            debug_return_bool(true);
1.1       misho     429:        } else
1.1.1.2   misho     430:            debug_return_bool(false);
1.1       misho     431:     }
                    432: 
                    433:     if (has_meta(sudoers_cmnd)) {
                    434:        /*
                    435:         * If sudoers_cmnd has meta characters in it, we need to
                    436:         * use glob(3) and/or fnmatch(3) to do the matching.
                    437:         */
                    438:        if (def_fast_glob)
1.1.1.2   misho     439:            debug_return_bool(command_matches_fnmatch(sudoers_cmnd, sudoers_args));
                    440:        debug_return_bool(command_matches_glob(sudoers_cmnd, sudoers_args));
1.1       misho     441:     }
1.1.1.2   misho     442:     debug_return_bool(command_matches_normal(sudoers_cmnd, sudoers_args));
1.1       misho     443: }
                    444: 
1.1.1.2   misho     445: static bool
1.1       misho     446: command_matches_fnmatch(char *sudoers_cmnd, char *sudoers_args)
                    447: {
1.1.1.2   misho     448:     debug_decl(command_matches_fnmatch, SUDO_DEBUG_MATCH)
                    449: 
1.1       misho     450:     /*
                    451:      * Return true if fnmatch(3) succeeds AND
                    452:      *  a) there are no args in sudoers OR
                    453:      *  b) there are no args on command line and none required by sudoers OR
                    454:      *  c) there are args in sudoers and on command line and they match
                    455:      * else return false.
                    456:      */
                    457:     if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
1.1.1.2   misho     458:        debug_return_bool(false);
1.1       misho     459:     if (command_args_match(sudoers_cmnd, sudoers_args)) {
                    460:        if (safe_cmnd)
                    461:            free(safe_cmnd);
                    462:        safe_cmnd = estrdup(user_cmnd);
1.1.1.2   misho     463:        debug_return_bool(true);
                    464:     }
                    465:     debug_return_bool(false);
1.1       misho     466: }
                    467: 
1.1.1.2   misho     468: static bool
1.1       misho     469: command_matches_glob(char *sudoers_cmnd, char *sudoers_args)
                    470: {
                    471:     struct stat sudoers_stat;
                    472:     size_t dlen;
                    473:     char **ap, *base, *cp;
                    474:     glob_t gl;
1.1.1.2   misho     475:     debug_decl(command_matches_glob, SUDO_DEBUG_MATCH)
1.1       misho     476: 
                    477:     /*
                    478:      * First check to see if we can avoid the call to glob(3).
                    479:      * Short circuit if there are no meta chars in the command itself
                    480:      * and user_base and basename(sudoers_cmnd) don't match.
                    481:      */
                    482:     dlen = strlen(sudoers_cmnd);
                    483:     if (sudoers_cmnd[dlen - 1] != '/') {
                    484:        if ((base = strrchr(sudoers_cmnd, '/')) != NULL) {
                    485:            base++;
                    486:            if (!has_meta(base) && strcmp(user_base, base) != 0)
1.1.1.2   misho     487:                debug_return_bool(false);
1.1       misho     488:        }
                    489:     }
                    490:     /*
                    491:      * Return true if we find a match in the glob(3) results AND
                    492:      *  a) there are no args in sudoers OR
                    493:      *  b) there are no args on command line and none required by sudoers OR
                    494:      *  c) there are args in sudoers and on command line and they match
                    495:      * else return false.
                    496:      */
1.1.1.2   misho     497:     if (glob(sudoers_cmnd, GLOB_NOSORT, NULL, &gl) != 0 || gl.gl_pathc == 0) {
1.1       misho     498:        globfree(&gl);
1.1.1.2   misho     499:        debug_return_bool(false);
1.1       misho     500:     }
                    501:     /* For each glob match, compare basename, st_dev and st_ino. */
                    502:     for (ap = gl.gl_pathv; (cp = *ap) != NULL; ap++) {
                    503:        /* If it ends in '/' it is a directory spec. */
                    504:        dlen = strlen(cp);
                    505:        if (cp[dlen - 1] == '/') {
                    506:            if (command_matches_dir(cp, dlen))
1.1.1.2   misho     507:                debug_return_bool(true);
1.1       misho     508:            continue;
                    509:        }
                    510: 
                    511:        /* Only proceed if user_base and basename(cp) match */
                    512:        if ((base = strrchr(cp, '/')) != NULL)
                    513:            base++;
                    514:        else
                    515:            base = cp;
                    516:        if (strcmp(user_base, base) != 0 ||
                    517:            stat(cp, &sudoers_stat) == -1)
                    518:            continue;
                    519:        if (user_stat == NULL ||
                    520:            (user_stat->st_dev == sudoers_stat.st_dev &&
                    521:            user_stat->st_ino == sudoers_stat.st_ino)) {
                    522:            efree(safe_cmnd);
                    523:            safe_cmnd = estrdup(cp);
                    524:            break;
                    525:        }
                    526:     }
                    527:     globfree(&gl);
                    528:     if (cp == NULL)
1.1.1.2   misho     529:        debug_return_bool(false);
1.1       misho     530: 
                    531:     if (command_args_match(sudoers_cmnd, sudoers_args)) {
                    532:        efree(safe_cmnd);
                    533:        safe_cmnd = estrdup(user_cmnd);
1.1.1.2   misho     534:        debug_return_bool(true);
1.1       misho     535:     }
1.1.1.2   misho     536:     debug_return_bool(false);
1.1       misho     537: }
                    538: 
1.1.1.2   misho     539: static bool
1.1       misho     540: command_matches_normal(char *sudoers_cmnd, char *sudoers_args)
                    541: {
                    542:     struct stat sudoers_stat;
                    543:     char *base;
                    544:     size_t dlen;
1.1.1.2   misho     545:     debug_decl(command_matches_normal, SUDO_DEBUG_MATCH)
1.1       misho     546: 
                    547:     /* If it ends in '/' it is a directory spec. */
                    548:     dlen = strlen(sudoers_cmnd);
                    549:     if (sudoers_cmnd[dlen - 1] == '/')
1.1.1.2   misho     550:        debug_return_bool(command_matches_dir(sudoers_cmnd, dlen));
1.1       misho     551: 
                    552:     /* Only proceed if user_base and basename(sudoers_cmnd) match */
                    553:     if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
                    554:        base = sudoers_cmnd;
                    555:     else
                    556:        base++;
                    557:     if (strcmp(user_base, base) != 0 ||
                    558:        stat(sudoers_cmnd, &sudoers_stat) == -1)
1.1.1.2   misho     559:        debug_return_bool(false);
1.1       misho     560: 
                    561:     /*
                    562:      * Return true if inode/device matches AND
                    563:      *  a) there are no args in sudoers OR
                    564:      *  b) there are no args on command line and none req by sudoers OR
                    565:      *  c) there are args in sudoers and on command line and they match
                    566:      */
                    567:     if (user_stat != NULL &&
                    568:        (user_stat->st_dev != sudoers_stat.st_dev ||
                    569:        user_stat->st_ino != sudoers_stat.st_ino))
1.1.1.2   misho     570:        debug_return_bool(false);
1.1       misho     571:     if (command_args_match(sudoers_cmnd, sudoers_args)) {
                    572:        efree(safe_cmnd);
                    573:        safe_cmnd = estrdup(sudoers_cmnd);
1.1.1.2   misho     574:        debug_return_bool(true);
1.1       misho     575:     }
1.1.1.2   misho     576:     debug_return_bool(false);
1.1       misho     577: }
                    578: 
                    579: /*
1.1.1.2   misho     580:  * Return true if user_cmnd names one of the inodes in dir, else false.
1.1       misho     581:  */
1.1.1.2   misho     582: static bool
1.1       misho     583: command_matches_dir(char *sudoers_dir, size_t dlen)
                    584: {
                    585:     struct stat sudoers_stat;
                    586:     struct dirent *dent;
                    587:     char buf[PATH_MAX];
                    588:     DIR *dirp;
1.1.1.2   misho     589:     debug_decl(command_matches_dir, SUDO_DEBUG_MATCH)
1.1       misho     590: 
                    591:     /*
                    592:      * Grot through directory entries, looking for user_base.
                    593:      */
                    594:     dirp = opendir(sudoers_dir);
                    595:     if (dirp == NULL)
1.1.1.2   misho     596:        debug_return_bool(false);
1.1       misho     597: 
                    598:     if (strlcpy(buf, sudoers_dir, sizeof(buf)) >= sizeof(buf)) {
                    599:        closedir(dirp);
1.1.1.2   misho     600:        debug_return_bool(false);
1.1       misho     601:     }
                    602:     while ((dent = readdir(dirp)) != NULL) {
                    603:        /* ignore paths > PATH_MAX (XXX - log) */
                    604:        buf[dlen] = '\0';
                    605:        if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
                    606:            continue;
                    607: 
                    608:        /* only stat if basenames are the same */
                    609:        if (strcmp(user_base, dent->d_name) != 0 ||
                    610:            stat(buf, &sudoers_stat) == -1)
                    611:            continue;
                    612:        if (user_stat == NULL ||
                    613:            (user_stat->st_dev == sudoers_stat.st_dev &&
                    614:            user_stat->st_ino == sudoers_stat.st_ino)) {
                    615:            efree(safe_cmnd);
                    616:            safe_cmnd = estrdup(buf);
                    617:            break;
                    618:        }
                    619:     }
                    620: 
                    621:     closedir(dirp);
1.1.1.2   misho     622:     debug_return_bool(dent != NULL);
1.1       misho     623: }
                    624: 
                    625: /*
1.1.1.2   misho     626:  * Returns true if the hostname matches the pattern, else false
1.1       misho     627:  */
1.1.1.2   misho     628: bool
1.1       misho     629: hostname_matches(char *shost, char *lhost, char *pattern)
                    630: {
1.1.1.2   misho     631:     debug_decl(hostname_matches, SUDO_DEBUG_MATCH)
                    632: 
1.1       misho     633:     if (has_meta(pattern)) {
                    634:        if (strchr(pattern, '.'))
1.1.1.2   misho     635:            debug_return_bool(!fnmatch(pattern, lhost, FNM_CASEFOLD));
1.1       misho     636:        else
1.1.1.2   misho     637:            debug_return_bool(!fnmatch(pattern, shost, FNM_CASEFOLD));
1.1       misho     638:     } else {
                    639:        if (strchr(pattern, '.'))
1.1.1.2   misho     640:            debug_return_bool(!strcasecmp(lhost, pattern));
1.1       misho     641:        else
1.1.1.2   misho     642:            debug_return_bool(!strcasecmp(shost, pattern));
1.1       misho     643:     }
                    644: }
                    645: 
                    646: /*
1.1.1.2   misho     647:  *  Returns true if the user/uid from sudoers matches the specified user/uid,
                    648:  *  else returns false.
1.1       misho     649:  */
1.1.1.2   misho     650: bool
1.1       misho     651: userpw_matches(char *sudoers_user, char *user, struct passwd *pw)
                    652: {
1.1.1.2   misho     653:     debug_decl(userpw_matches, SUDO_DEBUG_MATCH)
                    654: 
1.1       misho     655:     if (pw != NULL && *sudoers_user == '#') {
                    656:        uid_t uid = (uid_t) atoi(sudoers_user + 1);
                    657:        if (uid == pw->pw_uid)
1.1.1.2   misho     658:            debug_return_bool(true);
1.1       misho     659:     }
1.1.1.2   misho     660:     debug_return_bool(strcmp(sudoers_user, user) == 0);
1.1       misho     661: }
                    662: 
                    663: /*
1.1.1.2   misho     664:  *  Returns true if the group/gid from sudoers matches the specified group/gid,
                    665:  *  else returns false.
1.1       misho     666:  */
1.1.1.2   misho     667: bool
1.1       misho     668: group_matches(char *sudoers_group, struct group *gr)
                    669: {
1.1.1.2   misho     670:     debug_decl(group_matches, SUDO_DEBUG_MATCH)
                    671: 
1.1       misho     672:     if (*sudoers_group == '#') {
                    673:        gid_t gid = (gid_t) atoi(sudoers_group + 1);
                    674:        if (gid == gr->gr_gid)
1.1.1.2   misho     675:            debug_return_bool(true);
1.1       misho     676:     }
1.1.1.2   misho     677:     debug_return_bool(strcmp(gr->gr_name, sudoers_group) == 0);
1.1       misho     678: }
                    679: 
                    680: /*
1.1.1.2   misho     681:  *  Returns true if the given user belongs to the named group,
                    682:  *  else returns false.
1.1       misho     683:  */
1.1.1.2   misho     684: bool
1.1       misho     685: usergr_matches(char *group, char *user, struct passwd *pw)
                    686: {
1.1.1.2   misho     687:     int matched = false;
1.1       misho     688:     struct passwd *pw0 = NULL;
1.1.1.2   misho     689:     debug_decl(usergr_matches, SUDO_DEBUG_MATCH)
1.1       misho     690: 
                    691:     /* make sure we have a valid usergroup, sudo style */
                    692:     if (*group++ != '%')
                    693:        goto done;
                    694: 
                    695:     if (*group == ':' && def_group_plugin) {
                    696:        matched = group_plugin_query(user, group + 1, pw);
                    697:        goto done;
                    698:     }
                    699: 
                    700:     /* look up user's primary gid in the passwd file */
                    701:     if (pw == NULL) {
                    702:        if ((pw0 = sudo_getpwnam(user)) == NULL)
                    703:            goto done;
                    704:        pw = pw0;
                    705:     }
                    706: 
                    707:     if (user_in_group(pw, group)) {
1.1.1.2   misho     708:        matched = true;
1.1       misho     709:        goto done;
                    710:     }
                    711: 
                    712:     /* not a Unix group, could be an external group */
                    713:     if (def_group_plugin && group_plugin_query(user, group, pw)) {
1.1.1.2   misho     714:        matched = true;
1.1       misho     715:        goto done;
                    716:     }
                    717: 
                    718: done:
                    719:     if (pw0 != NULL)
1.1.1.3 ! misho     720:        sudo_pw_delref(pw0);
1.1       misho     721: 
1.1.1.2   misho     722:     debug_return_bool(matched);
1.1       misho     723: }
                    724: 
                    725: /*
1.1.1.2   misho     726:  * Returns true if "host" and "user" belong to the netgroup "netgr",
                    727:  * else return false.  Either of "host", "shost" or "user" may be NULL
1.1       misho     728:  * in which case that argument is not checked...
                    729:  *
                    730:  * XXX - swap order of host & shost
                    731:  */
1.1.1.2   misho     732: bool
1.1       misho     733: netgr_matches(char *netgr, char *lhost, char *shost, char *user)
                    734: {
                    735:     static char *domain;
                    736: #ifdef HAVE_GETDOMAINNAME
                    737:     static int initialized;
                    738: #endif
1.1.1.2   misho     739:     debug_decl(netgr_matches, SUDO_DEBUG_MATCH)
1.1       misho     740: 
                    741:     /* make sure we have a valid netgroup, sudo style */
                    742:     if (*netgr++ != '+')
1.1.1.2   misho     743:        debug_return_bool(false);
1.1       misho     744: 
                    745: #ifdef HAVE_GETDOMAINNAME
                    746:     /* get the domain name (if any) */
                    747:     if (!initialized) {
                    748:        domain = (char *) emalloc(MAXHOSTNAMELEN + 1);
                    749:        if (getdomainname(domain, MAXHOSTNAMELEN + 1) == -1 || *domain == '\0') {
                    750:            efree(domain);
                    751:            domain = NULL;
                    752:        }
                    753:        initialized = 1;
                    754:     }
                    755: #endif /* HAVE_GETDOMAINNAME */
                    756: 
                    757: #ifdef HAVE_INNETGR
                    758:     if (innetgr(netgr, lhost, user, domain))
1.1.1.2   misho     759:        debug_return_bool(true);
1.1       misho     760:     else if (lhost != shost && innetgr(netgr, shost, user, domain))
1.1.1.2   misho     761:        debug_return_bool(true);
1.1       misho     762: #endif /* HAVE_INNETGR */
                    763: 
1.1.1.2   misho     764:     debug_return_bool(false);
1.1       misho     765: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>