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

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