[BACK]Return to check.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / sudo

Annotation of src/usr.bin/sudo/check.c, Revision 1.8

1.1       millert     1: /*
1.8     ! millert     2:  * Copyright (c) 1993-1996,1998-2003 Todd C. Miller <Todd.Miller@courtesan.com>
1.1       millert     3:  * All rights reserved.
                      4:  *
                      5:  * Redistribution and use in source and binary forms, with or without
                      6:  * modification, are permitted provided that the following conditions
                      7:  * are met:
                      8:  *
                      9:  * 1. Redistributions of source code must retain the above copyright
                     10:  *    notice, this list of conditions and the following disclaimer.
                     11:  *
                     12:  * 2. Redistributions in binary form must reproduce the above copyright
                     13:  *    notice, this list of conditions and the following disclaimer in the
                     14:  *    documentation and/or other materials provided with the distribution.
                     15:  *
                     16:  * 3. The name of the author may not be used to endorse or promote products
                     17:  *    derived from this software without specific prior written permission.
                     18:  *
                     19:  * 4. Products derived from this software may not be called "Sudo" nor
                     20:  *    may "Sudo" appear in their names without specific prior written
                     21:  *    permission from the author.
                     22:  *
                     23:  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
                     24:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
                     25:  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
                     26:  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
                     27:  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
                     28:  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
                     29:  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
                     30:  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
                     31:  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
                     32:  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                     33:  */
                     34:
                     35: #include "config.h"
                     36:
1.6       millert    37: #include <sys/types.h>
                     38: #include <sys/param.h>
                     39: #include <sys/stat.h>
                     40: #include <sys/file.h>
1.1       millert    41: #include <stdio.h>
                     42: #ifdef STDC_HEADERS
1.6       millert    43: # include <stdlib.h>
                     44: # include <stddef.h>
                     45: #else
                     46: # ifdef HAVE_STDLIB_H
                     47: #  include <stdlib.h>
                     48: # endif
1.1       millert    49: #endif /* STDC_HEADERS */
1.6       millert    50: #ifdef HAVE_STRING_H
                     51: # include <string.h>
                     52: #else
                     53: # ifdef HAVE_STRINGS_H
                     54: #  include <strings.h>
                     55: # endif
                     56: #endif /* HAVE_STRING_H */
1.1       millert    57: #ifdef HAVE_UNISTD_H
1.6       millert    58: # include <unistd.h>
1.1       millert    59: #endif /* HAVE_UNISTD_H */
                     60: #include <errno.h>
                     61: #include <fcntl.h>
                     62: #include <signal.h>
                     63: #include <time.h>
                     64: #include <pwd.h>
                     65: #include <grp.h>
                     66:
                     67: #include "sudo.h"
                     68:
                     69: #ifndef lint
1.8     ! millert    70: static const char rcsid[] = "$Sudo: check.c,v 1.210 2003/03/15 20:31:01 millert Exp $";
1.1       millert    71: #endif /* lint */
                     72:
                     73: /* Status codes for timestamp_status() */
                     74: #define TS_CURRENT             0
                     75: #define TS_OLD                 1
                     76: #define TS_MISSING             2
                     77: #define TS_NOFILE              3
                     78: #define TS_ERROR               4
                     79:
                     80: static void  build_timestamp   __P((char **, char **));
                     81: static int   timestamp_status  __P((char *, char *, char *, int));
                     82: static char *expand_prompt     __P((char *, char *, char *));
                     83: static void  lecture           __P((void));
                     84: static void  update_timestamp  __P((char *, char *));
                     85:
                     86: /*
                     87:  * This function only returns if the user can successfully
                     88:  * verify who he/she is.
                     89:  */
                     90: void
                     91: check_user()
                     92: {
                     93:     char *timestampdir = NULL;
                     94:     char *timestampfile = NULL;
                     95:     char *prompt;
                     96:     int status;
                     97:
                     98:     if (user_uid == 0 || user_is_exempt())
                     99:        return;
                    100:
                    101:     build_timestamp(&timestampdir, &timestampfile);
                    102:     status = timestamp_status(timestampdir, timestampfile, user_name, TRUE);
                    103:     if (status != TS_CURRENT) {
                    104:        if (status == TS_MISSING || status == TS_ERROR)
                    105:            lecture();          /* first time through they get a lecture */
                    106:
                    107:        /* Expand any escapes in the prompt. */
                    108:        prompt = expand_prompt(user_prompt ? user_prompt : def_str(I_PASSPROMPT),
                    109:            user_name, user_shost);
                    110:
1.4       millert   111:        verify_user(auth_pw, prompt);
1.1       millert   112:     }
                    113:     if (status != TS_ERROR)
                    114:        update_timestamp(timestampdir, timestampfile);
                    115:     free(timestampdir);
                    116:     if (timestampfile)
                    117:        free(timestampfile);
                    118: }
                    119:
                    120: /*
                    121:  * Standard sudo lecture.
                    122:  * TODO: allow the user to specify a file name instead.
                    123:  */
                    124: static void
                    125: lecture()
                    126: {
                    127:
                    128:     if (def_flag(I_LECTURE)) {
                    129:        (void) fputs("\n\
                    130: We trust you have received the usual lecture from the local System\n\
                    131: Administrator. It usually boils down to these two things:\n\
                    132: \n\
                    133:        #1) Respect the privacy of others.\n\
                    134:        #2) Think before you type.\n\n",
                    135:        stderr);
                    136:     }
                    137: }
                    138:
                    139: /*
                    140:  * Update the time on the timestamp file/dir or create it if necessary.
                    141:  */
                    142: static void
                    143: update_timestamp(timestampdir, timestampfile)
                    144:     char *timestampdir;
                    145:     char *timestampfile;
                    146: {
                    147:
1.8     ! millert   148:     if (timestamp_uid != 0)
        !           149:        set_perms(PERM_TIMESTAMP);
1.1       millert   150:     if (touch(timestampfile ? timestampfile : timestampdir, time(NULL)) == -1) {
                    151:        if (timestampfile) {
                    152:            int fd = open(timestampfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
                    153:
                    154:            if (fd == -1)
                    155:                log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
                    156:            else
                    157:                close(fd);
                    158:        } else {
                    159:            if (mkdir(timestampdir, 0700) == -1)
                    160:                log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
                    161:        }
                    162:     }
1.8     ! millert   163:     if (timestamp_uid != 0)
        !           164:        set_perms(PERM_ROOT);
1.1       millert   165: }
                    166:
                    167: /*
                    168:  * Expand %h and %u escapes in the prompt and pass back the dynamically
                    169:  * allocated result.  Returns the same string if there are no escapes.
                    170:  */
                    171: static char *
                    172: expand_prompt(old_prompt, user, host)
                    173:     char *old_prompt;
                    174:     char *user;
                    175:     char *host;
                    176: {
1.8     ! millert   177:     size_t len, n;
1.1       millert   178:     int subst;
1.8     ! millert   179:     char *p, *np, *new_prompt, *endp;
1.1       millert   180:
                    181:     /* How much space do we need to malloc for the prompt? */
                    182:     subst = 0;
1.8     ! millert   183:     for (p = old_prompt, len = strlen(old_prompt); *p; p++) {
        !           184:        if (p[0] =='%') {
        !           185:            switch (p[1]) {
        !           186:                case 'h':
        !           187:                    p++;
        !           188:                    len += strlen(user_shost) - 2;
        !           189:                    subst = 1;
        !           190:                    break;
        !           191:                case 'H':
        !           192:                    p++;
        !           193:                    len += strlen(user_host) - 2;
        !           194:                    subst = 1;
        !           195:                    break;
        !           196:                case 'u':
        !           197:                    p++;
        !           198:                    len += strlen(user_name) - 2;
        !           199:                    subst = 1;
        !           200:                    break;
        !           201:                case 'U':
        !           202:                    p++;
        !           203:                    len += strlen(*user_runas) - 2;
        !           204:                    subst = 1;
        !           205:                    break;
        !           206:                case '%':
        !           207:                    p++;
        !           208:                    len--;
        !           209:                    subst = 1;
        !           210:                    break;
        !           211:                default:
        !           212:                    break;
1.1       millert   213:            }
                    214:        }
                    215:     }
                    216:
                    217:     if (subst) {
1.8     ! millert   218:        new_prompt = (char *) emalloc(++len);
        !           219:        *new_prompt = '\0';
        !           220:        endp = new_prompt + len - 1;
        !           221:        for (p = old_prompt, np = new_prompt; *p; p++) {
        !           222:            if (p[0] =='%') {
        !           223:                switch (p[1]) {
        !           224:                    case 'h':
        !           225:                        p++;
        !           226:                        if ((n = strlcat(new_prompt, user_shost, len)) >= len)
        !           227:                            goto oflow;
        !           228:                        np += n;
        !           229:                        continue;
        !           230:                    case 'H':
        !           231:                        p++;
        !           232:                        if ((n = strlcat(new_prompt, user_host, len)) >= len)
        !           233:                            goto oflow;
        !           234:                        np += n;
        !           235:                        continue;
        !           236:                    case 'u':
        !           237:                        p++;
        !           238:                        if ((n = strlcat(new_prompt, user_name, len)) >= len)
        !           239:                            goto oflow;
        !           240:                        np += n;
        !           241:                        continue;
        !           242:                    case 'U':
        !           243:                        p++;
        !           244:                        if ((n = strlcat(new_prompt, *user_runas, len)) >= len)
        !           245:                            goto oflow;
        !           246:                        np += n;
        !           247:                        continue;
        !           248:                    case '%':
        !           249:                        /* convert %% -> % */
        !           250:                        p++;
        !           251:                        break;
        !           252:                    default:
        !           253:                        /* no conversion */
        !           254:                        break;
1.1       millert   255:                }
1.8     ! millert   256:            }
        !           257:            if (np >= endp)
        !           258:                goto oflow;
        !           259:            *np++ = *p;
1.1       millert   260:        }
                    261:        *np = '\0';
                    262:     } else
                    263:        new_prompt = old_prompt;
                    264:
                    265:     return(new_prompt);
1.8     ! millert   266:
        !           267: oflow:
        !           268:     /* We pre-allocate enough space, so this should never happen. */
        !           269:     (void) fprintf(stderr, "%s: internal error, expand_prompt() overflow\n",
        !           270:        Argv[0]);
        !           271:     exit(1);
1.1       millert   272: }
                    273:
                    274: /*
                    275:  * Checks if the user is exempt from supplying a password.
                    276:  */
                    277: int
                    278: user_is_exempt()
                    279: {
                    280:     struct group *grp;
                    281:     char **gr_mem;
                    282:
1.6       millert   283:     if (!def_str(I_EXEMPT_GROUP))
1.1       millert   284:        return(FALSE);
                    285:
1.6       millert   286:     if (!(grp = getgrnam(def_str(I_EXEMPT_GROUP))))
1.1       millert   287:        return(FALSE);
                    288:
1.5       millert   289:     if (user_gid == grp->gr_gid)
1.1       millert   290:        return(TRUE);
                    291:
                    292:     for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) {
                    293:        if (strcmp(user_name, *gr_mem) == 0)
                    294:            return(TRUE);
                    295:     }
                    296:
                    297:     return(FALSE);
                    298: }
                    299:
                    300: /*
                    301:  * Fills in timestampdir as well as timestampfile if using tty tickets.
                    302:  */
                    303: static void
                    304: build_timestamp(timestampdir, timestampfile)
                    305:     char **timestampdir;
                    306:     char **timestampfile;
                    307: {
1.4       millert   308:     char *dirparent;
                    309:     int len;
1.1       millert   310:
1.4       millert   311:     dirparent = def_str(I_TIMESTAMPDIR);
                    312:     len = easprintf(timestampdir, "%s/%s", dirparent, user_name);
                    313:     if (len >= MAXPATHLEN)
                    314:        log_error(0, "timestamp path too long: %s", timestampdir);
                    315:
                    316:     /*
                    317:      * Timestamp file may be a file in the directory or NUL to use
                    318:      * the directory as the timestamp.
                    319:      */
1.1       millert   320:     if (def_flag(I_TTY_TICKETS)) {
                    321:        char *p;
                    322:
                    323:        if ((p = strrchr(user_tty, '/')))
                    324:            p++;
                    325:        else
                    326:            p = user_tty;
1.4       millert   327:        if (def_flag(I_TARGETPW))
                    328:            len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name,
                    329:                p, *user_runas);
                    330:        else
                    331:            len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p);
                    332:        if (len >= MAXPATHLEN)
                    333:            log_error(0, "timestamp path too long: %s", timestampfile);
                    334:     } else if (def_flag(I_TARGETPW)) {
                    335:        len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name,
                    336:            *user_runas);
                    337:        if (len >= MAXPATHLEN)
                    338:            log_error(0, "timestamp path too long: %s", timestampfile);
                    339:     } else
1.1       millert   340:        *timestampfile = NULL;
                    341: }
                    342:
                    343: /*
                    344:  * Check the timestamp file and directory and return their status.
                    345:  */
                    346: static int
                    347: timestamp_status(timestampdir, timestampfile, user, make_dirs)
                    348:     char *timestampdir;
                    349:     char *timestampfile;
                    350:     char *user;
                    351:     int make_dirs;
                    352: {
                    353:     struct stat sb;
                    354:     time_t now;
                    355:     char *dirparent = def_str(I_TIMESTAMPDIR);
                    356:     int status = TS_ERROR;             /* assume the worst */
                    357:
1.8     ! millert   358:     if (timestamp_uid != 0)
        !           359:        set_perms(PERM_TIMESTAMP);
        !           360:
1.1       millert   361:     /*
                    362:      * Sanity check dirparent and make it if it doesn't already exist.
                    363:      * We start out assuming the worst (that the dir is not sane) and
                    364:      * if it is ok upgrade the status to ``no timestamp file''.
                    365:      * Note that we don't check the parent(s) of dirparent for
                    366:      * sanity since the sudo dir is often just located in /tmp.
                    367:      */
                    368:     if (lstat(dirparent, &sb) == 0) {
                    369:        if (!S_ISDIR(sb.st_mode))
                    370:            log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
                    371:                dirparent, sb.st_mode);
1.8     ! millert   372:        else if (sb.st_uid != timestamp_uid)
        !           373:            log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
        !           374:                dirparent, (unsigned long) sb.st_uid,
        !           375:                (unsigned long) timestamp_uid);
1.1       millert   376:        else if ((sb.st_mode & 0000022))
                    377:            log_error(NO_EXIT,
                    378:                "%s writable by non-owner (0%o), should be mode 0700",
                    379:                dirparent, sb.st_mode);
                    380:        else {
                    381:            if ((sb.st_mode & 0000777) != 0700)
                    382:                (void) chmod(dirparent, 0700);
                    383:            status = TS_MISSING;
                    384:        }
                    385:     } else if (errno != ENOENT) {
                    386:        log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
                    387:     } else {
                    388:        /* No dirparent, try to make one. */
                    389:        if (make_dirs) {
                    390:            if (mkdir(dirparent, S_IRWXU))
                    391:                log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
                    392:                    dirparent);
                    393:            else
                    394:                status = TS_MISSING;
                    395:        }
                    396:     }
1.8     ! millert   397:     if (status == TS_ERROR) {
        !           398:        if (timestamp_uid != 0)
        !           399:            set_perms(PERM_ROOT);
1.1       millert   400:        return(status);
1.8     ! millert   401:     }
1.1       millert   402:
                    403:     /*
                    404:      * Sanity check the user's ticket dir.  We start by downgrading
                    405:      * the status to TS_ERROR.  If the ticket dir exists and is sane
                    406:      * this will be upgraded to TS_OLD.  If the dir does not exist,
                    407:      * it will be upgraded to TS_MISSING.
                    408:      */
                    409:     status = TS_ERROR;                 /* downgrade status again */
                    410:     if (lstat(timestampdir, &sb) == 0) {
                    411:        if (!S_ISDIR(sb.st_mode)) {
                    412:            if (S_ISREG(sb.st_mode)) {
                    413:                /* convert from old style */
                    414:                if (unlink(timestampdir) == 0)
                    415:                    status = TS_MISSING;
                    416:            } else
                    417:                log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
                    418:                    timestampdir, sb.st_mode);
1.8     ! millert   419:        } else if (sb.st_uid != timestamp_uid)
        !           420:            log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
        !           421:                timestampdir, (unsigned long) sb.st_uid,
        !           422:                (unsigned long) timestamp_uid);
1.1       millert   423:        else if ((sb.st_mode & 0000022))
                    424:            log_error(NO_EXIT,
                    425:                "%s writable by non-owner (0%o), should be mode 0700",
                    426:                timestampdir, sb.st_mode);
                    427:        else {
                    428:            if ((sb.st_mode & 0000777) != 0700)
                    429:                (void) chmod(timestampdir, 0700);
                    430:            status = TS_OLD;            /* do date check later */
                    431:        }
                    432:     } else if (errno != ENOENT) {
                    433:        log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
                    434:     } else
                    435:        status = TS_MISSING;
                    436:
                    437:     /*
                    438:      * If there is no user ticket dir, AND we are in tty ticket mode,
                    439:      * AND the make_dirs flag is set, create the user ticket dir.
                    440:      */
                    441:     if (status == TS_MISSING && timestampfile && make_dirs) {
                    442:        if (mkdir(timestampdir, S_IRWXU) == -1) {
                    443:            status = TS_ERROR;
                    444:            log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
                    445:        }
                    446:     }
                    447:
                    448:     /*
                    449:      * Sanity check the tty ticket file if it exists.
                    450:      */
                    451:     if (timestampfile && status != TS_ERROR) {
                    452:        if (status != TS_MISSING)
                    453:            status = TS_NOFILE;                 /* dir there, file missing */
                    454:        if (lstat(timestampfile, &sb) == 0) {
                    455:            if (!S_ISREG(sb.st_mode)) {
                    456:                status = TS_ERROR;
                    457:                log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
                    458:                    timestampfile, sb.st_mode);
                    459:            } else {
                    460:                /* If bad uid or file mode, complain and kill the bogus file. */
1.8     ! millert   461:                if (sb.st_uid != timestamp_uid) {
1.1       millert   462:                    log_error(NO_EXIT,
1.8     ! millert   463:                        "%s owned by uid %ud, should be uid %lu",
        !           464:                        timestampfile, (unsigned long) sb.st_uid,
        !           465:                        (unsigned long) timestamp_uid);
1.1       millert   466:                    (void) unlink(timestampfile);
                    467:                } else if ((sb.st_mode & 0000022)) {
                    468:                    log_error(NO_EXIT,
                    469:                        "%s writable by non-owner (0%o), should be mode 0600",
                    470:                        timestampfile, sb.st_mode);
                    471:                    (void) unlink(timestampfile);
                    472:                } else {
                    473:                    /* If not mode 0600, fix it. */
                    474:                    if ((sb.st_mode & 0000777) != 0600)
                    475:                        (void) chmod(timestampfile, 0600);
                    476:
                    477:                    status = TS_OLD;    /* actually check mtime below */
                    478:                }
                    479:            }
                    480:        } else if (errno != ENOENT) {
                    481:            log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
                    482:            status = TS_ERROR;
                    483:        }
                    484:     }
                    485:
                    486:     /*
                    487:      * If the file/dir exists, check its mtime.
                    488:      */
                    489:     if (status == TS_OLD) {
1.6       millert   490:        /* Negative timeouts only expire manually (sudo -k). */
                    491:        if (def_ival(I_TIMESTAMP_TIMEOUT) < 0 && sb.st_mtime != 0)
                    492:            status = TS_CURRENT;
                    493:        else {
                    494:            now = time(NULL);
                    495:            if (def_ival(I_TIMESTAMP_TIMEOUT) &&
                    496:                now - sb.st_mtime < 60 * def_ival(I_TIMESTAMP_TIMEOUT)) {
                    497:                /*
                    498:                 * Check for bogus time on the stampfile.  The clock may
                    499:                 * have been set back or someone could be trying to spoof us.
                    500:                 */
                    501:                if (sb.st_mtime > now + 60 * def_ival(I_TIMESTAMP_TIMEOUT) * 2) {
                    502:                    log_error(NO_EXIT,
                    503:                        "timestamp too far in the future: %20.20s",
                    504:                        4 + ctime(&sb.st_mtime));
                    505:                    if (timestampfile)
                    506:                        (void) unlink(timestampfile);
                    507:                    else
                    508:                        (void) rmdir(timestampdir);
                    509:                    status = TS_MISSING;
                    510:                } else
                    511:                    status = TS_CURRENT;
                    512:            }
1.1       millert   513:        }
                    514:     }
                    515:
1.8     ! millert   516:     if (timestamp_uid != 0)
        !           517:        set_perms(PERM_ROOT);
1.1       millert   518:     return(status);
                    519: }
                    520:
                    521: /*
                    522:  * Remove the timestamp ticket file/dir.
                    523:  */
                    524: void
                    525: remove_timestamp(remove)
                    526:     int remove;
                    527: {
                    528:     char *timestampdir;
                    529:     char *timestampfile;
                    530:     char *ts;
                    531:     int status;
                    532:
                    533:     build_timestamp(&timestampdir, &timestampfile);
                    534:     status = timestamp_status(timestampdir, timestampfile, user_name, FALSE);
                    535:     if (status == TS_OLD || status == TS_CURRENT) {
                    536:        ts = timestampfile ? timestampfile : timestampdir;
                    537:        if (remove) {
                    538:            if (timestampfile)
                    539:                status = unlink(timestampfile);
                    540:            else
                    541:                status = rmdir(timestampdir);
1.2       millert   542:            if (status == -1 && errno != ENOENT) {
1.1       millert   543:                log_error(NO_EXIT, "can't remove %s (%s), will reset to epoch",
1.2       millert   544:                    ts, strerror(errno));
1.1       millert   545:                remove = FALSE;
                    546:            }
                    547:        }
                    548:        if (!remove && touch(ts, 0) == -1) {
                    549:            (void) fprintf(stderr, "%s: can't reset %s to epoch: %s\n",
                    550:                Argv[0], ts, strerror(errno));
                    551:        }
                    552:     }
                    553:
                    554:     free(timestampdir);
                    555:     if (timestampfile)
                    556:        free(timestampfile);
                    557: }