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

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