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

1.1       misho       1: /*
1.1.1.4   misho       2:  * Copyright (c) 1996, 1998-2005, 2007-2013
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/stat.h>
                     28: #include <stdio.h>
                     29: #ifdef STDC_HEADERS
                     30: # include <stdlib.h>
                     31: # include <stddef.h>
                     32: #else
                     33: # ifdef HAVE_STDLIB_H
                     34: #  include <stdlib.h>
                     35: # endif
                     36: #endif /* STDC_HEADERS */
                     37: #ifdef HAVE_STRING_H
                     38: # include <string.h>
                     39: #endif /* HAVE_STRING_H */
                     40: #ifdef HAVE_STRINGS_H
                     41: # include <strings.h>
                     42: #endif /* HAVE_STRINGS_H */
1.1.1.4   misho      43: #if defined(HAVE_STDINT_H)
                     44: # include <stdint.h>
                     45: #elif defined(HAVE_INTTYPES_H)
                     46: # include <inttypes.h>
                     47: #endif
1.1       misho      48: #ifdef HAVE_UNISTD_H
                     49: # include <unistd.h>
                     50: #endif /* HAVE_UNISTD_H */
                     51: #ifdef HAVE_FNMATCH
                     52: # include <fnmatch.h>
1.1.1.4   misho      53: #else
                     54: # include "compat/fnmatch.h"
1.1       misho      55: #endif /* HAVE_FNMATCH */
1.1.1.4   misho      56: #ifndef SUDOERS_NAME_MATCH
                     57: # ifdef HAVE_GLOB
                     58: #  include <glob.h>
                     59: # else
                     60: #  include "compat/glob.h"
                     61: # endif /* HAVE_GLOB */
                     62: #endif /* SUDOERS_NAME_MATCH */
1.1       misho      63: #ifdef HAVE_NETGROUP_H
                     64: # include <netgroup.h>
1.1.1.4   misho      65: #else
                     66: # include <netdb.h>
1.1       misho      67: #endif /* HAVE_NETGROUP_H */
                     68: #ifdef 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: # ifdef HAVE_SYS_NDIR_H
                     75: #  include <sys/ndir.h>
                     76: # endif
                     77: # ifdef HAVE_SYS_DIR_H
                     78: #  include <sys/dir.h>
                     79: # endif
                     80: # ifdef HAVE_NDIR_H
                     81: #  include <ndir.h>
                     82: # endif
                     83: #endif
1.1.1.4   misho      84: #include <ctype.h>
                     85: #include <pwd.h>
                     86: #include <grp.h>
                     87: #include <errno.h>
1.1       misho      88: 
                     89: #include "sudoers.h"
                     90: #include "parse.h"
1.1.1.4   misho      91: #include "sha2.h"
1.1       misho      92: #include <gram.h>
                     93: 
                     94: static struct member_list empty;
                     95: 
1.1.1.2   misho      96: static bool command_matches_dir(char *, size_t);
1.1.1.4   misho      97: #ifndef SUDOERS_NAME_MATCH
1.1.1.2   misho      98: static bool command_matches_glob(char *, char *);
1.1.1.4   misho      99: #endif
1.1.1.2   misho     100: static bool command_matches_fnmatch(char *, char *);
1.1.1.4   misho     101: static bool command_matches_normal(char *, char *, struct sudo_digest *);
1.1       misho     102: 
                    103: /*
1.1.1.2   misho     104:  * Returns true if string 's' contains meta characters.
1.1       misho     105:  */
                    106: #define has_meta(s)    (strpbrk(s, "\\?*[]") != NULL)
                    107: 
                    108: /*
                    109:  * Check for user described by pw in a list of members.
                    110:  * Returns ALLOW, DENY or UNSPEC.
                    111:  */
1.1.1.4   misho     112: int
                    113: userlist_matches(struct passwd *pw, struct member_list *list)
1.1       misho     114: {
                    115:     struct member *m;
                    116:     struct alias *a;
                    117:     int rval, matched = UNSPEC;
1.1.1.4   misho     118:     debug_decl(userlist_matches, SUDO_DEBUG_MATCH)
1.1       misho     119: 
                    120:     tq_foreach_rev(list, m) {
                    121:        switch (m->type) {
                    122:            case ALL:
                    123:                matched = !m->negated;
                    124:                break;
                    125:            case NETGROUP:
                    126:                if (netgr_matches(m->name, NULL, NULL, pw->pw_name))
                    127:                    matched = !m->negated;
                    128:                break;
                    129:            case USERGROUP:
                    130:                if (usergr_matches(m->name, pw->pw_name, pw))
                    131:                    matched = !m->negated;
                    132:                break;
                    133:            case ALIAS:
1.1.1.4   misho     134:                if ((a = alias_get(m->name, USERALIAS)) != NULL) {
                    135:                    rval = userlist_matches(pw, &a->members);
1.1       misho     136:                    if (rval != UNSPEC)
                    137:                        matched = m->negated ? !rval : rval;
1.1.1.4   misho     138:                    alias_put(a);
1.1       misho     139:                    break;
                    140:                }
                    141:                /* FALLTHROUGH */
                    142:            case WORD:
                    143:                if (userpw_matches(m->name, pw->pw_name, pw))
                    144:                    matched = !m->negated;
                    145:                break;
                    146:        }
                    147:        if (matched != UNSPEC)
                    148:            break;
                    149:     }
1.1.1.2   misho     150:     debug_return_bool(matched);
1.1       misho     151: }
                    152: 
                    153: /*
                    154:  * Check for user described by pw in a list of members.
                    155:  * If both lists are empty compare against def_runas_default.
                    156:  * Returns ALLOW, DENY or UNSPEC.
                    157:  */
1.1.1.4   misho     158: int
                    159: runaslist_matches(struct member_list *user_list,
1.1.1.3   misho     160:     struct member_list *group_list, struct member **matching_user,
                    161:     struct member **matching_group)
1.1       misho     162: {
                    163:     struct member *m;
                    164:     struct alias *a;
                    165:     int rval;
                    166:     int user_matched = UNSPEC;
                    167:     int group_matched = UNSPEC;
1.1.1.4   misho     168:     debug_decl(runaslist_matches, SUDO_DEBUG_MATCH)
1.1       misho     169: 
                    170:     if (runas_pw != NULL) {
                    171:        /* If no runas user or runas group listed in sudoers, use default. */
                    172:        if (tq_empty(user_list) && tq_empty(group_list))
1.1.1.2   misho     173:            debug_return_int(userpw_matches(def_runas_default, runas_pw->pw_name, runas_pw));
1.1       misho     174: 
                    175:        tq_foreach_rev(user_list, m) {
                    176:            switch (m->type) {
                    177:                case ALL:
                    178:                    user_matched = !m->negated;
                    179:                    break;
                    180:                case NETGROUP:
                    181:                    if (netgr_matches(m->name, NULL, NULL, runas_pw->pw_name))
                    182:                        user_matched = !m->negated;
                    183:                    break;
                    184:                case USERGROUP:
                    185:                    if (usergr_matches(m->name, runas_pw->pw_name, runas_pw))
                    186:                        user_matched = !m->negated;
                    187:                    break;
                    188:                case ALIAS:
1.1.1.4   misho     189:                    if ((a = alias_get(m->name, RUNASALIAS)) != NULL) {
                    190:                        rval = runaslist_matches(&a->members, &empty,
1.1.1.3   misho     191:                            matching_user, NULL);
1.1       misho     192:                        if (rval != UNSPEC)
                    193:                            user_matched = m->negated ? !rval : rval;
1.1.1.4   misho     194:                        alias_put(a);
1.1       misho     195:                        break;
                    196:                    }
                    197:                    /* FALLTHROUGH */
                    198:                case WORD:
                    199:                    if (userpw_matches(m->name, runas_pw->pw_name, runas_pw))
                    200:                        user_matched = !m->negated;
                    201:                    break;
1.1.1.3   misho     202:                case MYSELF:
                    203:                    if (!ISSET(sudo_user.flags, RUNAS_USER_SPECIFIED) ||
                    204:                        strcmp(user_name, runas_pw->pw_name) == 0)
                    205:                        user_matched = !m->negated;
                    206:                    break;
1.1       misho     207:            }
1.1.1.3   misho     208:            if (user_matched != UNSPEC) {
                    209:                if (matching_user != NULL && m->type != ALIAS)
                    210:                    *matching_user = m;
1.1       misho     211:                break;
1.1.1.3   misho     212:            }
1.1       misho     213:        }
                    214:     }
                    215: 
                    216:     if (runas_gr != NULL) {
                    217:        if (user_matched == UNSPEC) {
                    218:            if (runas_pw == NULL || strcmp(runas_pw->pw_name, user_name) == 0)
                    219:                user_matched = ALLOW;   /* only changing group */
                    220:        }
                    221:        tq_foreach_rev(group_list, m) {
                    222:            switch (m->type) {
                    223:                case ALL:
                    224:                    group_matched = !m->negated;
                    225:                    break;
                    226:                case ALIAS:
1.1.1.4   misho     227:                    if ((a = alias_get(m->name, RUNASALIAS)) != NULL) {
                    228:                        rval = runaslist_matches(&empty, &a->members,
1.1.1.3   misho     229:                            NULL, matching_group);
1.1       misho     230:                        if (rval != UNSPEC)
                    231:                            group_matched = m->negated ? !rval : rval;
1.1.1.4   misho     232:                        alias_put(a);
1.1       misho     233:                        break;
                    234:                    }
                    235:                    /* FALLTHROUGH */
                    236:                case WORD:
                    237:                    if (group_matches(m->name, runas_gr))
                    238:                        group_matched = !m->negated;
                    239:                    break;
                    240:            }
1.1.1.3   misho     241:            if (group_matched != UNSPEC) {
                    242:                if (matching_group != NULL && m->type != ALIAS)
                    243:                    *matching_group = m;
1.1       misho     244:                break;
1.1.1.3   misho     245:            }
1.1       misho     246:        }
                    247:        if (group_matched == UNSPEC) {
                    248:            if (runas_pw != NULL && runas_pw->pw_gid == runas_gr->gr_gid)
                    249:                group_matched = ALLOW;  /* runas group matches passwd db */
                    250:        }
                    251:     }
                    252: 
                    253:     if (user_matched == DENY || group_matched == DENY)
1.1.1.2   misho     254:        debug_return_int(DENY);
1.1       misho     255:     if (user_matched == group_matched || runas_gr == NULL)
1.1.1.2   misho     256:        debug_return_int(user_matched);
                    257:     debug_return_int(UNSPEC);
1.1       misho     258: }
                    259: 
                    260: /*
                    261:  * Check for host and shost in a list of members.
                    262:  * Returns ALLOW, DENY or UNSPEC.
                    263:  */
1.1.1.4   misho     264: int
                    265: hostlist_matches(struct member_list *list)
1.1       misho     266: {
                    267:     struct member *m;
                    268:     struct alias *a;
                    269:     int rval, matched = UNSPEC;
1.1.1.4   misho     270:     debug_decl(hostlist_matches, SUDO_DEBUG_MATCH)
1.1       misho     271: 
                    272:     tq_foreach_rev(list, m) {
                    273:        switch (m->type) {
                    274:            case ALL:
                    275:                matched = !m->negated;
                    276:                break;
                    277:            case NETGROUP:
1.1.1.5 ! misho     278:                if (netgr_matches(m->name, user_runhost, user_srunhost, NULL))
1.1       misho     279:                    matched = !m->negated;
                    280:                break;
                    281:            case NTWKADDR:
                    282:                if (addr_matches(m->name))
                    283:                    matched = !m->negated;
                    284:                break;
                    285:            case ALIAS:
1.1.1.4   misho     286:                if ((a = alias_get(m->name, HOSTALIAS)) != NULL) {
                    287:                    rval = hostlist_matches(&a->members);
1.1       misho     288:                    if (rval != UNSPEC)
                    289:                        matched = m->negated ? !rval : rval;
1.1.1.4   misho     290:                    alias_put(a);
1.1       misho     291:                    break;
                    292:                }
                    293:                /* FALLTHROUGH */
                    294:            case WORD:
1.1.1.5 ! misho     295:                if (hostname_matches(user_srunhost, user_runhost, m->name))
1.1       misho     296:                    matched = !m->negated;
                    297:                break;
                    298:        }
                    299:        if (matched != UNSPEC)
                    300:            break;
                    301:     }
1.1.1.2   misho     302:     debug_return_bool(matched);
1.1       misho     303: }
                    304: 
                    305: /*
                    306:  * Check for cmnd and args in a list of members.
                    307:  * Returns ALLOW, DENY or UNSPEC.
                    308:  */
1.1.1.4   misho     309: int
                    310: cmndlist_matches(struct member_list *list)
1.1       misho     311: {
                    312:     struct member *m;
                    313:     int matched = UNSPEC;
1.1.1.4   misho     314:     debug_decl(cmndlist_matches, SUDO_DEBUG_MATCH)
1.1       misho     315: 
                    316:     tq_foreach_rev(list, m) {
                    317:        matched = cmnd_matches(m);
                    318:        if (matched != UNSPEC)
                    319:            break;
                    320:     }
1.1.1.2   misho     321:     debug_return_bool(matched);
1.1       misho     322: }
                    323: 
                    324: /*
                    325:  * Check cmnd and args.
                    326:  * Returns ALLOW, DENY or UNSPEC.
                    327:  */
                    328: int
                    329: cmnd_matches(struct member *m)
                    330: {
                    331:     struct alias *a;
                    332:     struct sudo_command *c;
                    333:     int rval, matched = UNSPEC;
1.1.1.2   misho     334:     debug_decl(cmnd_matches, SUDO_DEBUG_MATCH)
1.1       misho     335: 
                    336:     switch (m->type) {
                    337:        case ALL:
                    338:            matched = !m->negated;
                    339:            break;
                    340:        case ALIAS:
1.1.1.4   misho     341:            if ((a = alias_get(m->name, CMNDALIAS)) != NULL) {
                    342:                rval = cmndlist_matches(&a->members);
1.1       misho     343:                if (rval != UNSPEC)
                    344:                    matched = m->negated ? !rval : rval;
1.1.1.4   misho     345:                alias_put(a);
1.1       misho     346:            }
                    347:            break;
                    348:        case COMMAND:
                    349:            c = (struct sudo_command *)m->name;
1.1.1.4   misho     350:            if (command_matches(c->cmnd, c->args, c->digest))
1.1       misho     351:                matched = !m->negated;
                    352:            break;
                    353:     }
1.1.1.2   misho     354:     debug_return_bool(matched);
1.1       misho     355: }
                    356: 
1.1.1.2   misho     357: static bool
1.1.1.4   misho     358: command_args_match(char *sudoers_cmnd, char *sudoers_args)
1.1       misho     359: {
                    360:     int flags = 0;
1.1.1.2   misho     361:     debug_decl(command_args_match, SUDO_DEBUG_MATCH)
1.1       misho     362: 
                    363:     /*
                    364:      * If no args specified in sudoers, any user args are allowed.
                    365:      * If the empty string is specified in sudoers, no user args are allowed.
                    366:      */
                    367:     if (!sudoers_args ||
                    368:        (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)))
1.1.1.2   misho     369:        debug_return_bool(true);
1.1       misho     370:     /*
                    371:      * If args are specified in sudoers, they must match the user args.
                    372:      * If running as sudoedit, all args are assumed to be paths.
                    373:      */
                    374:     if (sudoers_args) {
                    375:        /* For sudoedit, all args are assumed to be pathnames. */
                    376:        if (strcmp(sudoers_cmnd, "sudoedit") == 0)
                    377:            flags = FNM_PATHNAME;
                    378:        if (fnmatch(sudoers_args, user_args ? user_args : "", flags) == 0)
1.1.1.2   misho     379:            debug_return_bool(true);
1.1       misho     380:     }
1.1.1.2   misho     381:     debug_return_bool(false);
1.1       misho     382: }
                    383: 
                    384: /*
1.1.1.2   misho     385:  * If path doesn't end in /, return true iff cmnd & path name the same inode;
                    386:  * otherwise, return true if user_cmnd names one of the inodes in path.
1.1       misho     387:  */
1.1.1.2   misho     388: bool
1.1.1.4   misho     389: command_matches(char *sudoers_cmnd, char *sudoers_args, struct sudo_digest *digest)
1.1       misho     390: {
1.1.1.2   misho     391:     debug_decl(command_matches, SUDO_DEBUG_MATCH)
                    392: 
1.1       misho     393:     /* Check for pseudo-commands */
                    394:     if (sudoers_cmnd[0] != '/') {
                    395:        /*
                    396:         * Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
                    397:         *  a) there are no args in sudoers OR
                    398:         *  b) there are no args on command line and none req by sudoers OR
                    399:         *  c) there are args in sudoers and on command line and they match
                    400:         */
                    401:        if (strcmp(sudoers_cmnd, "sudoedit") != 0 ||
                    402:            strcmp(user_cmnd, "sudoedit") != 0)
1.1.1.2   misho     403:            debug_return_bool(false);
1.1       misho     404:        if (command_args_match(sudoers_cmnd, sudoers_args)) {
                    405:            efree(safe_cmnd);
                    406:            safe_cmnd = estrdup(sudoers_cmnd);
1.1.1.2   misho     407:            debug_return_bool(true);
1.1       misho     408:        } else
1.1.1.2   misho     409:            debug_return_bool(false);
1.1       misho     410:     }
                    411: 
                    412:     if (has_meta(sudoers_cmnd)) {
                    413:        /*
                    414:         * If sudoers_cmnd has meta characters in it, we need to
                    415:         * use glob(3) and/or fnmatch(3) to do the matching.
                    416:         */
1.1.1.4   misho     417: #ifdef SUDOERS_NAME_MATCH
                    418:        debug_return_bool(command_matches_fnmatch(sudoers_cmnd, sudoers_args));
                    419: #else
1.1       misho     420:        if (def_fast_glob)
1.1.1.2   misho     421:            debug_return_bool(command_matches_fnmatch(sudoers_cmnd, sudoers_args));
                    422:        debug_return_bool(command_matches_glob(sudoers_cmnd, sudoers_args));
1.1.1.4   misho     423: #endif
1.1       misho     424:     }
1.1.1.4   misho     425:     debug_return_bool(command_matches_normal(sudoers_cmnd, sudoers_args, digest));
1.1       misho     426: }
                    427: 
1.1.1.2   misho     428: static bool
1.1       misho     429: command_matches_fnmatch(char *sudoers_cmnd, char *sudoers_args)
                    430: {
1.1.1.2   misho     431:     debug_decl(command_matches_fnmatch, SUDO_DEBUG_MATCH)
                    432: 
1.1       misho     433:     /*
                    434:      * Return true if fnmatch(3) succeeds AND
                    435:      *  a) there are no args in sudoers OR
                    436:      *  b) there are no args on command line and none required by sudoers OR
                    437:      *  c) there are args in sudoers and on command line and they match
                    438:      * else return false.
                    439:      */
                    440:     if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
1.1.1.2   misho     441:        debug_return_bool(false);
1.1       misho     442:     if (command_args_match(sudoers_cmnd, sudoers_args)) {
                    443:        if (safe_cmnd)
                    444:            free(safe_cmnd);
                    445:        safe_cmnd = estrdup(user_cmnd);
1.1.1.2   misho     446:        debug_return_bool(true);
                    447:     }
                    448:     debug_return_bool(false);
1.1       misho     449: }
                    450: 
1.1.1.4   misho     451: #ifndef SUDOERS_NAME_MATCH
1.1.1.2   misho     452: static bool
1.1       misho     453: command_matches_glob(char *sudoers_cmnd, char *sudoers_args)
                    454: {
                    455:     struct stat sudoers_stat;
                    456:     size_t dlen;
                    457:     char **ap, *base, *cp;
                    458:     glob_t gl;
1.1.1.2   misho     459:     debug_decl(command_matches_glob, SUDO_DEBUG_MATCH)
1.1       misho     460: 
                    461:     /*
                    462:      * First check to see if we can avoid the call to glob(3).
                    463:      * Short circuit if there are no meta chars in the command itself
                    464:      * and user_base and basename(sudoers_cmnd) don't match.
                    465:      */
                    466:     dlen = strlen(sudoers_cmnd);
                    467:     if (sudoers_cmnd[dlen - 1] != '/') {
                    468:        if ((base = strrchr(sudoers_cmnd, '/')) != NULL) {
                    469:            base++;
                    470:            if (!has_meta(base) && strcmp(user_base, base) != 0)
1.1.1.2   misho     471:                debug_return_bool(false);
1.1       misho     472:        }
                    473:     }
                    474:     /*
                    475:      * Return true if we find a match in the glob(3) results AND
                    476:      *  a) there are no args in sudoers OR
                    477:      *  b) there are no args on command line and none required by sudoers OR
                    478:      *  c) there are args in sudoers and on command line and they match
                    479:      * else return false.
                    480:      */
1.1.1.2   misho     481:     if (glob(sudoers_cmnd, GLOB_NOSORT, NULL, &gl) != 0 || gl.gl_pathc == 0) {
1.1       misho     482:        globfree(&gl);
1.1.1.2   misho     483:        debug_return_bool(false);
1.1       misho     484:     }
                    485:     /* For each glob match, compare basename, st_dev and st_ino. */
                    486:     for (ap = gl.gl_pathv; (cp = *ap) != NULL; ap++) {
                    487:        /* If it ends in '/' it is a directory spec. */
                    488:        dlen = strlen(cp);
                    489:        if (cp[dlen - 1] == '/') {
                    490:            if (command_matches_dir(cp, dlen))
1.1.1.2   misho     491:                debug_return_bool(true);
1.1       misho     492:            continue;
                    493:        }
                    494: 
                    495:        /* Only proceed if user_base and basename(cp) match */
                    496:        if ((base = strrchr(cp, '/')) != NULL)
                    497:            base++;
                    498:        else
                    499:            base = cp;
                    500:        if (strcmp(user_base, base) != 0 ||
                    501:            stat(cp, &sudoers_stat) == -1)
                    502:            continue;
                    503:        if (user_stat == NULL ||
                    504:            (user_stat->st_dev == sudoers_stat.st_dev &&
                    505:            user_stat->st_ino == sudoers_stat.st_ino)) {
                    506:            efree(safe_cmnd);
                    507:            safe_cmnd = estrdup(cp);
                    508:            break;
                    509:        }
                    510:     }
                    511:     globfree(&gl);
                    512:     if (cp == NULL)
1.1.1.2   misho     513:        debug_return_bool(false);
1.1       misho     514: 
                    515:     if (command_args_match(sudoers_cmnd, sudoers_args)) {
                    516:        efree(safe_cmnd);
                    517:        safe_cmnd = estrdup(user_cmnd);
1.1.1.2   misho     518:        debug_return_bool(true);
1.1       misho     519:     }
1.1.1.2   misho     520:     debug_return_bool(false);
1.1       misho     521: }
1.1.1.4   misho     522: #endif /* SUDOERS_NAME_MATCH */
1.1       misho     523: 
1.1.1.4   misho     524: #ifdef SUDOERS_NAME_MATCH
1.1.1.2   misho     525: static bool
1.1.1.4   misho     526: command_matches_normal(char *sudoers_cmnd, char *sudoers_args, struct sudo_digest *digest)
                    527: {
                    528:     size_t dlen;
                    529:     debug_decl(command_matches_normal, SUDO_DEBUG_MATCH)
                    530: 
                    531:     dlen = strlen(sudoers_cmnd);
                    532: 
                    533:     /* If it ends in '/' it is a directory spec. */
                    534:     if (sudoers_cmnd[dlen - 1] == '/')
                    535:        debug_return_bool(command_matches_dir(sudoers_cmnd, dlen));
                    536: 
                    537:     if (strcmp(user_cmnd, sudoers_cmnd) == 0) {
                    538:        if (command_args_match(sudoers_cmnd, sudoers_args)) {
                    539:            efree(safe_cmnd);
                    540:            safe_cmnd = estrdup(sudoers_cmnd);
                    541:            debug_return_bool(true);
                    542:        }
                    543:     }
                    544:     debug_return_bool(false);
                    545: }
                    546: #else /* !SUDOERS_NAME_MATCH */
                    547: 
                    548: static struct digest_function {
                    549:     const char *digest_name;
                    550:     const unsigned int digest_len;
                    551:     void (*init)(SHA2_CTX *);
                    552:     void (*update)(SHA2_CTX *, const unsigned char *, size_t);
                    553:     void (*final)(unsigned char *, SHA2_CTX *);
                    554: } digest_functions[] = {
                    555:     {
                    556:        "SHA224",
                    557:        SHA224_DIGEST_LENGTH,
                    558:        SHA224Init,
                    559:        SHA224Update,
                    560:        SHA224Final
                    561:     }, {
                    562:        "SHA256",
                    563:        SHA256_DIGEST_LENGTH,
                    564:        SHA256Init,
                    565:        SHA256Update,
                    566:        SHA256Final
                    567:     }, {
                    568:        "SHA384",
                    569:        SHA384_DIGEST_LENGTH,
                    570:        SHA384Init,
                    571:        SHA384Update,
                    572:        SHA384Final
                    573:     }, {
                    574:        "SHA512",
                    575:        SHA512_DIGEST_LENGTH,
                    576:        SHA512Init,
                    577:        SHA512Update,
                    578:        SHA512Final
                    579:     }, {
                    580:        NULL
                    581:     }
                    582: };
                    583: 
                    584: static bool
                    585: digest_matches(char *file, struct sudo_digest *sd)
                    586: {
                    587:     unsigned char file_digest[SHA512_DIGEST_LENGTH];
                    588:     unsigned char sudoers_digest[SHA512_DIGEST_LENGTH];
                    589:     unsigned char buf[32 * 1024];
                    590:     struct digest_function *func = NULL;
                    591:     size_t nread;
                    592:     SHA2_CTX ctx;
                    593:     FILE *fp;
                    594:     unsigned int i;
                    595:     debug_decl(digest_matches, SUDO_DEBUG_MATCH)
                    596: 
                    597:     for (i = 0; digest_functions[i].digest_name != NULL; i++) {
                    598:        if (sd->digest_type == i) {
                    599:            func = &digest_functions[i];
                    600:            break;
                    601:        }
                    602:     }
                    603:     if (func == NULL) {
                    604:        warningx(_("unsupported digest type %d for %s"), sd->digest_type, file);
                    605:        debug_return_bool(false);
                    606:     }
                    607:     if (strlen(sd->digest_str) == func->digest_len * 2) {
                    608:        /* Convert the command digest from ascii hex to binary. */
                    609:        for (i = 0; i < func->digest_len; i++) {
                    610:            if (!isxdigit((unsigned char)sd->digest_str[i + i]) ||
                    611:                !isxdigit((unsigned char)sd->digest_str[i + i + 1])) {
                    612:                goto bad_format;
                    613:            }
                    614:            sudoers_digest[i] = hexchar(&sd->digest_str[i + i]);
                    615:        }
                    616:     } else {
                    617:        size_t len = base64_decode(sd->digest_str, sudoers_digest,
                    618:            sizeof(sudoers_digest));
                    619:        if (len != func->digest_len)
                    620:            goto bad_format;
                    621:     }
                    622: 
                    623:     if ((fp = fopen(file, "r")) == NULL) {
                    624:        sudo_debug_printf(SUDO_DEBUG_INFO, "unable to open %s: %s",
                    625:            file, strerror(errno));
                    626:        debug_return_bool(false);
                    627:     }
                    628: 
                    629:     func->init(&ctx);
                    630:     while ((nread = fread(buf, 1, sizeof(buf), fp)) != 0) {
                    631:        func->update(&ctx, buf, nread);
                    632:     }
                    633:     if (ferror(fp)) {
                    634:        warningx(_("%s: read error"), file);
                    635:        fclose(fp);
                    636:        debug_return_bool(false);
                    637:     }
                    638:     fclose(fp);
                    639:     func->final(file_digest, &ctx);
                    640: 
                    641:     debug_return_bool(memcmp(file_digest, sudoers_digest, func->digest_len) == 0);
                    642: bad_format:
                    643:     warningx(_("digest for %s (%s) is not in %s form"), file,
                    644:        sd->digest_str, func->digest_name);
                    645:     debug_return_bool(false);
                    646: }
                    647: 
                    648: static bool
                    649: command_matches_normal(char *sudoers_cmnd, char *sudoers_args, struct sudo_digest *digest)
1.1       misho     650: {
                    651:     struct stat sudoers_stat;
                    652:     char *base;
                    653:     size_t dlen;
1.1.1.2   misho     654:     debug_decl(command_matches_normal, SUDO_DEBUG_MATCH)
1.1       misho     655: 
                    656:     /* If it ends in '/' it is a directory spec. */
                    657:     dlen = strlen(sudoers_cmnd);
                    658:     if (sudoers_cmnd[dlen - 1] == '/')
1.1.1.2   misho     659:        debug_return_bool(command_matches_dir(sudoers_cmnd, dlen));
1.1       misho     660: 
                    661:     /* Only proceed if user_base and basename(sudoers_cmnd) match */
                    662:     if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
                    663:        base = sudoers_cmnd;
                    664:     else
                    665:        base++;
                    666:     if (strcmp(user_base, base) != 0 ||
                    667:        stat(sudoers_cmnd, &sudoers_stat) == -1)
1.1.1.2   misho     668:        debug_return_bool(false);
1.1       misho     669: 
                    670:     /*
                    671:      * Return true if inode/device matches AND
                    672:      *  a) there are no args in sudoers OR
                    673:      *  b) there are no args on command line and none req by sudoers OR
                    674:      *  c) there are args in sudoers and on command line and they match
1.1.1.4   misho     675:      *  d) there is a digest and it matches
1.1       misho     676:      */
                    677:     if (user_stat != NULL &&
                    678:        (user_stat->st_dev != sudoers_stat.st_dev ||
                    679:        user_stat->st_ino != sudoers_stat.st_ino))
1.1.1.2   misho     680:        debug_return_bool(false);
1.1.1.4   misho     681:     if (!command_args_match(sudoers_cmnd, sudoers_args))
                    682:        debug_return_bool(false);
                    683:     if (digest != NULL && !digest_matches(sudoers_cmnd, digest)) {
                    684:        /* XXX - log functions not available but we should log very loudly */
                    685:        debug_return_bool(false);
1.1       misho     686:     }
1.1.1.4   misho     687:     efree(safe_cmnd);
                    688:     safe_cmnd = estrdup(sudoers_cmnd);
                    689:     debug_return_bool(true);
1.1       misho     690: }
1.1.1.4   misho     691: #endif /* SUDOERS_NAME_MATCH */
1.1       misho     692: 
1.1.1.4   misho     693: #ifdef SUDOERS_NAME_MATCH
                    694: /*
                    695:  * Return true if user_cmnd begins with sudoers_dir, else false.
                    696:  * Note that sudoers_dir include the trailing '/'
                    697:  */
                    698: static bool
                    699: command_matches_dir(char *sudoers_dir, size_t dlen)
                    700: {
                    701:     debug_decl(command_matches_dir, SUDO_DEBUG_MATCH)
                    702:     debug_return_bool(strncmp(user_cmnd, sudoers_dir, dlen) == 0);
                    703: }
                    704: #else /* !SUDOERS_NAME_MATCH */
1.1       misho     705: /*
1.1.1.2   misho     706:  * Return true if user_cmnd names one of the inodes in dir, else false.
1.1       misho     707:  */
1.1.1.2   misho     708: static bool
1.1       misho     709: command_matches_dir(char *sudoers_dir, size_t dlen)
                    710: {
                    711:     struct stat sudoers_stat;
                    712:     struct dirent *dent;
                    713:     char buf[PATH_MAX];
                    714:     DIR *dirp;
1.1.1.2   misho     715:     debug_decl(command_matches_dir, SUDO_DEBUG_MATCH)
1.1       misho     716: 
                    717:     /*
                    718:      * Grot through directory entries, looking for user_base.
                    719:      */
                    720:     dirp = opendir(sudoers_dir);
                    721:     if (dirp == NULL)
1.1.1.2   misho     722:        debug_return_bool(false);
1.1       misho     723: 
                    724:     if (strlcpy(buf, sudoers_dir, sizeof(buf)) >= sizeof(buf)) {
                    725:        closedir(dirp);
1.1.1.2   misho     726:        debug_return_bool(false);
1.1       misho     727:     }
                    728:     while ((dent = readdir(dirp)) != NULL) {
                    729:        /* ignore paths > PATH_MAX (XXX - log) */
                    730:        buf[dlen] = '\0';
                    731:        if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
                    732:            continue;
                    733: 
                    734:        /* only stat if basenames are the same */
                    735:        if (strcmp(user_base, dent->d_name) != 0 ||
                    736:            stat(buf, &sudoers_stat) == -1)
                    737:            continue;
                    738:        if (user_stat == NULL ||
                    739:            (user_stat->st_dev == sudoers_stat.st_dev &&
                    740:            user_stat->st_ino == sudoers_stat.st_ino)) {
                    741:            efree(safe_cmnd);
                    742:            safe_cmnd = estrdup(buf);
                    743:            break;
                    744:        }
                    745:     }
                    746: 
                    747:     closedir(dirp);
1.1.1.2   misho     748:     debug_return_bool(dent != NULL);
1.1       misho     749: }
1.1.1.4   misho     750: #endif /* SUDOERS_NAME_MATCH */
1.1       misho     751: 
                    752: /*
1.1.1.2   misho     753:  * Returns true if the hostname matches the pattern, else false
1.1       misho     754:  */
1.1.1.2   misho     755: bool
1.1       misho     756: hostname_matches(char *shost, char *lhost, char *pattern)
                    757: {
1.1.1.2   misho     758:     debug_decl(hostname_matches, SUDO_DEBUG_MATCH)
                    759: 
1.1       misho     760:     if (has_meta(pattern)) {
                    761:        if (strchr(pattern, '.'))
1.1.1.2   misho     762:            debug_return_bool(!fnmatch(pattern, lhost, FNM_CASEFOLD));
1.1       misho     763:        else
1.1.1.2   misho     764:            debug_return_bool(!fnmatch(pattern, shost, FNM_CASEFOLD));
1.1       misho     765:     } else {
                    766:        if (strchr(pattern, '.'))
1.1.1.2   misho     767:            debug_return_bool(!strcasecmp(lhost, pattern));
1.1       misho     768:        else
1.1.1.2   misho     769:            debug_return_bool(!strcasecmp(shost, pattern));
1.1       misho     770:     }
                    771: }
                    772: 
                    773: /*
1.1.1.2   misho     774:  *  Returns true if the user/uid from sudoers matches the specified user/uid,
                    775:  *  else returns false.
1.1       misho     776:  */
1.1.1.2   misho     777: bool
1.1       misho     778: userpw_matches(char *sudoers_user, char *user, struct passwd *pw)
                    779: {
1.1.1.2   misho     780:     debug_decl(userpw_matches, SUDO_DEBUG_MATCH)
                    781: 
1.1       misho     782:     if (pw != NULL && *sudoers_user == '#') {
                    783:        uid_t uid = (uid_t) atoi(sudoers_user + 1);
                    784:        if (uid == pw->pw_uid)
1.1.1.2   misho     785:            debug_return_bool(true);
1.1       misho     786:     }
1.1.1.2   misho     787:     debug_return_bool(strcmp(sudoers_user, user) == 0);
1.1       misho     788: }
                    789: 
                    790: /*
1.1.1.2   misho     791:  *  Returns true if the group/gid from sudoers matches the specified group/gid,
                    792:  *  else returns false.
1.1       misho     793:  */
1.1.1.2   misho     794: bool
1.1       misho     795: group_matches(char *sudoers_group, struct group *gr)
                    796: {
1.1.1.2   misho     797:     debug_decl(group_matches, SUDO_DEBUG_MATCH)
                    798: 
1.1       misho     799:     if (*sudoers_group == '#') {
                    800:        gid_t gid = (gid_t) atoi(sudoers_group + 1);
                    801:        if (gid == gr->gr_gid)
1.1.1.2   misho     802:            debug_return_bool(true);
1.1       misho     803:     }
1.1.1.2   misho     804:     debug_return_bool(strcmp(gr->gr_name, sudoers_group) == 0);
1.1       misho     805: }
                    806: 
                    807: /*
1.1.1.2   misho     808:  *  Returns true if the given user belongs to the named group,
                    809:  *  else returns false.
1.1       misho     810:  */
1.1.1.2   misho     811: bool
1.1       misho     812: usergr_matches(char *group, char *user, struct passwd *pw)
                    813: {
1.1.1.2   misho     814:     int matched = false;
1.1       misho     815:     struct passwd *pw0 = NULL;
1.1.1.2   misho     816:     debug_decl(usergr_matches, SUDO_DEBUG_MATCH)
1.1       misho     817: 
                    818:     /* make sure we have a valid usergroup, sudo style */
                    819:     if (*group++ != '%')
                    820:        goto done;
                    821: 
                    822:     if (*group == ':' && def_group_plugin) {
                    823:        matched = group_plugin_query(user, group + 1, pw);
                    824:        goto done;
                    825:     }
                    826: 
                    827:     /* look up user's primary gid in the passwd file */
                    828:     if (pw == NULL) {
                    829:        if ((pw0 = sudo_getpwnam(user)) == NULL)
                    830:            goto done;
                    831:        pw = pw0;
                    832:     }
                    833: 
                    834:     if (user_in_group(pw, group)) {
1.1.1.2   misho     835:        matched = true;
1.1       misho     836:        goto done;
                    837:     }
                    838: 
                    839:     /* not a Unix group, could be an external group */
                    840:     if (def_group_plugin && group_plugin_query(user, group, pw)) {
1.1.1.2   misho     841:        matched = true;
1.1       misho     842:        goto done;
                    843:     }
                    844: 
                    845: done:
                    846:     if (pw0 != NULL)
1.1.1.3   misho     847:        sudo_pw_delref(pw0);
1.1       misho     848: 
1.1.1.2   misho     849:     debug_return_bool(matched);
1.1       misho     850: }
                    851: 
1.1.1.4   misho     852: #ifdef HAVE_INNETGR
                    853: /*
                    854:  * Get NIS-style domain name and return a malloc()ed copy or NULL if none.
                    855:  */
                    856: static char *
                    857: sudo_getdomainname(void)
                    858: {
                    859:     char *domain = NULL;
                    860: #ifdef HAVE_GETDOMAINNAME
                    861:     char *buf, *cp;
                    862: 
                    863:     buf = emalloc(HOST_NAME_MAX + 1);
                    864:     if (getdomainname(buf, HOST_NAME_MAX + 1) == 0 && *buf != '\0') {
                    865:        domain = buf;
                    866:        for (cp = buf; *cp != '\0'; cp++) {
                    867:            /* Check for illegal characters, Linux may use "(none)". */
                    868:            if (*cp == '(' || *cp == ')' || *cp == ',' || *cp == ' ') {
                    869:                domain = NULL;
                    870:                break;
                    871:            }
                    872:        }
                    873:     }
                    874:     if (domain == NULL)
                    875:        efree(buf);
                    876: #endif /* HAVE_GETDOMAINNAME */
                    877:     return domain;
                    878: }
                    879: #endif /* HAVE_INNETGR */
                    880: 
1.1       misho     881: /*
1.1.1.2   misho     882:  * Returns true if "host" and "user" belong to the netgroup "netgr",
                    883:  * else return false.  Either of "host", "shost" or "user" may be NULL
1.1       misho     884:  * in which case that argument is not checked...
                    885:  *
                    886:  * XXX - swap order of host & shost
                    887:  */
1.1.1.2   misho     888: bool
1.1       misho     889: netgr_matches(char *netgr, char *lhost, char *shost, char *user)
                    890: {
1.1.1.4   misho     891: #ifdef HAVE_INNETGR
1.1       misho     892:     static char *domain;
                    893:     static int initialized;
                    894: #endif
1.1.1.2   misho     895:     debug_decl(netgr_matches, SUDO_DEBUG_MATCH)
1.1       misho     896: 
1.1.1.4   misho     897: #ifdef HAVE_INNETGR
1.1       misho     898:     /* make sure we have a valid netgroup, sudo style */
                    899:     if (*netgr++ != '+')
1.1.1.2   misho     900:        debug_return_bool(false);
1.1       misho     901: 
                    902:     /* get the domain name (if any) */
                    903:     if (!initialized) {
1.1.1.4   misho     904:        domain = sudo_getdomainname();
1.1       misho     905:        initialized = 1;
                    906:     }
                    907: 
                    908:     if (innetgr(netgr, lhost, user, domain))
1.1.1.2   misho     909:        debug_return_bool(true);
1.1       misho     910:     else if (lhost != shost && innetgr(netgr, shost, user, domain))
1.1.1.2   misho     911:        debug_return_bool(true);
1.1       misho     912: #endif /* HAVE_INNETGR */
                    913: 
1.1.1.2   misho     914:     debug_return_bool(false);
1.1       misho     915: }

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