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

1.1     ! millert     1: /*
        !             2:  * Copyright (c) 1994-1996,1998-1999 Todd C. Miller <Todd.Miller@courtesan.com>
        !             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:
        !            37: #include <stdio.h>
        !            38: #ifdef STDC_HEADERS
        !            39: #include <stdlib.h>
        !            40: #endif /* STDC_HEADERS */
        !            41: #ifdef HAVE_UNISTD_H
        !            42: #include <unistd.h>
        !            43: #endif /* HAVE_UNISTD_H */
        !            44: #ifdef HAVE_STRING_H
        !            45: #include <string.h>
        !            46: #endif /* HAVE_STRING_H */
        !            47: #ifdef HAVE_STRINGS_H
        !            48: #include <strings.h>
        !            49: #endif /* HAVE_STRINGS_H */
        !            50: #include <errno.h>
        !            51: #include <fcntl.h>
        !            52: #include <signal.h>
        !            53: #include <time.h>
        !            54: #include <sys/param.h>
        !            55: #include <sys/types.h>
        !            56: #include <sys/stat.h>
        !            57: #include <sys/file.h>
        !            58: #include <pwd.h>
        !            59: #include <grp.h>
        !            60:
        !            61: #include "sudo.h"
        !            62:
        !            63: #ifndef lint
        !            64: static const char rcsid[] = "$Sudo: check.c,v 1.192 1999/10/07 21:20:55 millert Exp $";
        !            65: #endif /* lint */
        !            66:
        !            67: /* Status codes for timestamp_status() */
        !            68: #define TS_CURRENT             0
        !            69: #define TS_OLD                 1
        !            70: #define TS_MISSING             2
        !            71: #define TS_NOFILE              3
        !            72: #define TS_ERROR               4
        !            73:
        !            74:        int   user_is_exempt    __P((void));
        !            75: static void  build_timestamp   __P((char **, char **));
        !            76: static int   timestamp_status  __P((char *, char *, char *, int));
        !            77: static char *expand_prompt     __P((char *, char *, char *));
        !            78: static void  lecture           __P((void));
        !            79: static void  update_timestamp  __P((char *, char *));
        !            80:
        !            81: /*
        !            82:  * This function only returns if the user can successfully
        !            83:  * verify who he/she is.
        !            84:  */
        !            85: void
        !            86: check_user()
        !            87: {
        !            88:     char *timestampdir = NULL;
        !            89:     char *timestampfile = NULL;
        !            90:     char *prompt;
        !            91:     int status;
        !            92:
        !            93:     if (user_uid == 0 || user_is_exempt())
        !            94:        return;
        !            95:
        !            96:     build_timestamp(&timestampdir, &timestampfile);
        !            97:     status = timestamp_status(timestampdir, timestampfile, user_name, TRUE);
        !            98:     if (status != TS_CURRENT) {
        !            99:        if (status == TS_MISSING || status == TS_ERROR)
        !           100:            lecture();          /* first time through they get a lecture */
        !           101:
        !           102:        /* Expand any escapes in the prompt. */
        !           103:        prompt = expand_prompt(user_prompt ? user_prompt : def_str(I_PASSPROMPT),
        !           104:            user_name, user_shost);
        !           105:
        !           106:        verify_user(prompt);
        !           107:     }
        !           108:     if (status != TS_ERROR)
        !           109:        update_timestamp(timestampdir, timestampfile);
        !           110:     free(timestampdir);
        !           111:     if (timestampfile)
        !           112:        free(timestampfile);
        !           113: }
        !           114:
        !           115: /*
        !           116:  * Standard sudo lecture.
        !           117:  * TODO: allow the user to specify a file name instead.
        !           118:  */
        !           119: static void
        !           120: lecture()
        !           121: {
        !           122:
        !           123:     if (def_flag(I_LECTURE)) {
        !           124:        (void) fputs("\n\
        !           125: We trust you have received the usual lecture from the local System\n\
        !           126: Administrator. It usually boils down to these two things:\n\
        !           127: \n\
        !           128:        #1) Respect the privacy of others.\n\
        !           129:        #2) Think before you type.\n\n",
        !           130:        stderr);
        !           131:     }
        !           132: }
        !           133:
        !           134: /*
        !           135:  * Update the time on the timestamp file/dir or create it if necessary.
        !           136:  */
        !           137: static void
        !           138: update_timestamp(timestampdir, timestampfile)
        !           139:     char *timestampdir;
        !           140:     char *timestampfile;
        !           141: {
        !           142:
        !           143:     if (touch(timestampfile ? timestampfile : timestampdir, time(NULL)) == -1) {
        !           144:        if (timestampfile) {
        !           145:            int fd = open(timestampfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
        !           146:
        !           147:            if (fd == -1)
        !           148:                log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
        !           149:            else
        !           150:                close(fd);
        !           151:        } else {
        !           152:            if (mkdir(timestampdir, 0700) == -1)
        !           153:                log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
        !           154:        }
        !           155:     }
        !           156: }
        !           157:
        !           158: /*
        !           159:  * Expand %h and %u escapes in the prompt and pass back the dynamically
        !           160:  * allocated result.  Returns the same string if there are no escapes.
        !           161:  */
        !           162: static char *
        !           163: expand_prompt(old_prompt, user, host)
        !           164:     char *old_prompt;
        !           165:     char *user;
        !           166:     char *host;
        !           167: {
        !           168:     size_t len;
        !           169:     int subst;
        !           170:     char *p, *np, *new_prompt, lastchar;
        !           171:
        !           172:     /* How much space do we need to malloc for the prompt? */
        !           173:     subst = 0;
        !           174:     for (p = old_prompt, len = strlen(old_prompt), lastchar = '\0'; *p; p++) {
        !           175:        if (lastchar == '%') {
        !           176:            if (*p == 'h') {
        !           177:                len += strlen(user_shost) - 2;
        !           178:                subst = 1;
        !           179:            } else if (*p == 'u') {
        !           180:                len += strlen(user_name) - 2;
        !           181:                subst = 1;
        !           182:            }
        !           183:        }
        !           184:
        !           185:        if (lastchar == '%' && *p == '%') {
        !           186:            lastchar = '\0';
        !           187:            len--;
        !           188:        } else
        !           189:            lastchar = *p;
        !           190:     }
        !           191:
        !           192:     if (subst) {
        !           193:        new_prompt = (char *) emalloc(len + 1);
        !           194:        for (p = old_prompt, np = new_prompt; *p; p++) {
        !           195:            if (lastchar == '%' && (*p == 'h' || *p == 'u' || *p == '%')) {
        !           196:                /* substiture user/host name */
        !           197:                if (*p == 'h') {
        !           198:                    np--;
        !           199:                    strcpy(np, user_shost);
        !           200:                    np += strlen(user_shost);
        !           201:                } else if (*p == 'u') {
        !           202:                    np--;
        !           203:                    strcpy(np, user_name);
        !           204:                    np += strlen(user_name);
        !           205:                }
        !           206:            } else
        !           207:                *np++ = *p;
        !           208:
        !           209:            if (lastchar == '%' && *p == '%')
        !           210:                lastchar = '\0';
        !           211:            else
        !           212:                lastchar = *p;
        !           213:        }
        !           214:        *np = '\0';
        !           215:     } else
        !           216:        new_prompt = old_prompt;
        !           217:
        !           218:     return(new_prompt);
        !           219: }
        !           220:
        !           221: /*
        !           222:  * Checks if the user is exempt from supplying a password.
        !           223:  */
        !           224: int
        !           225: user_is_exempt()
        !           226: {
        !           227:     struct group *grp;
        !           228:     char **gr_mem;
        !           229:
        !           230:     if (!def_str(I_EXEMPT_GRP))
        !           231:        return(FALSE);
        !           232:
        !           233:     if (!(grp = getgrnam(def_str(I_EXEMPT_GRP))))
        !           234:        return(FALSE);
        !           235:
        !           236:     if (getgid() == grp->gr_gid)
        !           237:        return(TRUE);
        !           238:
        !           239:     for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) {
        !           240:        if (strcmp(user_name, *gr_mem) == 0)
        !           241:            return(TRUE);
        !           242:     }
        !           243:
        !           244:     return(FALSE);
        !           245: }
        !           246:
        !           247: /*
        !           248:  * Fills in timestampdir as well as timestampfile if using tty tickets.
        !           249:  */
        !           250: static void
        !           251: build_timestamp(timestampdir, timestampfile)
        !           252:     char **timestampdir;
        !           253:     char **timestampfile;
        !           254: {
        !           255:     char *dirparent = def_str(I_TIMESTAMPDIR);
        !           256:
        !           257:     if (def_flag(I_TTY_TICKETS)) {
        !           258:        char *p;
        !           259:
        !           260:        if ((p = strrchr(user_tty, '/')))
        !           261:            p++;
        !           262:        else
        !           263:            p = user_tty;
        !           264:        if (strlen(dirparent) + strlen(user_name) + strlen(p) + 3 > MAXPATHLEN)
        !           265:            log_error(0, "timestamp path too long: %s/%s/%s", dirparent,
        !           266:                user_name, p);
        !           267:        easprintf(timestampdir, "%s/%s", dirparent, user_name);
        !           268:        easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p);
        !           269:     } else {
        !           270:        if (strlen(dirparent) + strlen(user_name) + 2 > MAXPATHLEN)
        !           271:            log_error(0, "timestamp path too long: %s/%s", dirparent, user_name);
        !           272:        easprintf(timestampdir, "%s/%s", dirparent, user_name);
        !           273:        *timestampfile = NULL;
        !           274:     }
        !           275: }
        !           276:
        !           277: /*
        !           278:  * Check the timestamp file and directory and return their status.
        !           279:  */
        !           280: static int
        !           281: timestamp_status(timestampdir, timestampfile, user, make_dirs)
        !           282:     char *timestampdir;
        !           283:     char *timestampfile;
        !           284:     char *user;
        !           285:     int make_dirs;
        !           286: {
        !           287:     struct stat sb;
        !           288:     time_t now;
        !           289:     char *dirparent = def_str(I_TIMESTAMPDIR);
        !           290:     int status = TS_ERROR;             /* assume the worst */
        !           291:
        !           292:     /*
        !           293:      * Sanity check dirparent and make it if it doesn't already exist.
        !           294:      * We start out assuming the worst (that the dir is not sane) and
        !           295:      * if it is ok upgrade the status to ``no timestamp file''.
        !           296:      * Note that we don't check the parent(s) of dirparent for
        !           297:      * sanity since the sudo dir is often just located in /tmp.
        !           298:      */
        !           299:     if (lstat(dirparent, &sb) == 0) {
        !           300:        if (!S_ISDIR(sb.st_mode))
        !           301:            log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
        !           302:                dirparent, sb.st_mode);
        !           303:        else if (sb.st_uid != 0)
        !           304:            log_error(NO_EXIT, "%s owned by uid %ld, should be owned by root",
        !           305:                dirparent, (long) sb.st_uid);
        !           306:        else if ((sb.st_mode & 0000022))
        !           307:            log_error(NO_EXIT,
        !           308:                "%s writable by non-owner (0%o), should be mode 0700",
        !           309:                dirparent, sb.st_mode);
        !           310:        else {
        !           311:            if ((sb.st_mode & 0000777) != 0700)
        !           312:                (void) chmod(dirparent, 0700);
        !           313:            status = TS_MISSING;
        !           314:        }
        !           315:     } else if (errno != ENOENT) {
        !           316:        log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
        !           317:     } else {
        !           318:        /* No dirparent, try to make one. */
        !           319:        if (make_dirs) {
        !           320:            if (mkdir(dirparent, S_IRWXU))
        !           321:                log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
        !           322:                    dirparent);
        !           323:            else
        !           324:                status = TS_MISSING;
        !           325:        }
        !           326:     }
        !           327:     if (status == TS_ERROR)
        !           328:        return(status);
        !           329:
        !           330:     /*
        !           331:      * Sanity check the user's ticket dir.  We start by downgrading
        !           332:      * the status to TS_ERROR.  If the ticket dir exists and is sane
        !           333:      * this will be upgraded to TS_OLD.  If the dir does not exist,
        !           334:      * it will be upgraded to TS_MISSING.
        !           335:      */
        !           336:     status = TS_ERROR;                 /* downgrade status again */
        !           337:     if (lstat(timestampdir, &sb) == 0) {
        !           338:        if (!S_ISDIR(sb.st_mode)) {
        !           339:            if (S_ISREG(sb.st_mode)) {
        !           340:                /* convert from old style */
        !           341:                if (unlink(timestampdir) == 0)
        !           342:                    status = TS_MISSING;
        !           343:            } else
        !           344:                log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
        !           345:                    timestampdir, sb.st_mode);
        !           346:        } else if (sb.st_uid != 0)
        !           347:            log_error(NO_EXIT, "%s owned by uid %ld, should be owned by root",
        !           348:                timestampdir, (long) sb.st_uid);
        !           349:        else if ((sb.st_mode & 0000022))
        !           350:            log_error(NO_EXIT,
        !           351:                "%s writable by non-owner (0%o), should be mode 0700",
        !           352:                timestampdir, sb.st_mode);
        !           353:        else {
        !           354:            if ((sb.st_mode & 0000777) != 0700)
        !           355:                (void) chmod(timestampdir, 0700);
        !           356:            status = TS_OLD;            /* do date check later */
        !           357:        }
        !           358:     } else if (errno != ENOENT) {
        !           359:        log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
        !           360:     } else
        !           361:        status = TS_MISSING;
        !           362:
        !           363:     /*
        !           364:      * If there is no user ticket dir, AND we are in tty ticket mode,
        !           365:      * AND the make_dirs flag is set, create the user ticket dir.
        !           366:      */
        !           367:     if (status == TS_MISSING && timestampfile && make_dirs) {
        !           368:        if (mkdir(timestampdir, S_IRWXU) == -1) {
        !           369:            status = TS_ERROR;
        !           370:            log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
        !           371:        }
        !           372:     }
        !           373:
        !           374:     /*
        !           375:      * Sanity check the tty ticket file if it exists.
        !           376:      */
        !           377:     if (timestampfile && status != TS_ERROR) {
        !           378:        if (status != TS_MISSING)
        !           379:            status = TS_NOFILE;                 /* dir there, file missing */
        !           380:        if (lstat(timestampfile, &sb) == 0) {
        !           381:            if (!S_ISREG(sb.st_mode)) {
        !           382:                status = TS_ERROR;
        !           383:                log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
        !           384:                    timestampfile, sb.st_mode);
        !           385:            } else {
        !           386:                /* If bad uid or file mode, complain and kill the bogus file. */
        !           387:                if (sb.st_uid != 0) {
        !           388:                    log_error(NO_EXIT,
        !           389:                        "%s owned by uid %ld, should be owned by root",
        !           390:                        timestampfile, (long) sb.st_uid);
        !           391:                    (void) unlink(timestampfile);
        !           392:                } else if ((sb.st_mode & 0000022)) {
        !           393:                    log_error(NO_EXIT,
        !           394:                        "%s writable by non-owner (0%o), should be mode 0600",
        !           395:                        timestampfile, sb.st_mode);
        !           396:                    (void) unlink(timestampfile);
        !           397:                } else {
        !           398:                    /* If not mode 0600, fix it. */
        !           399:                    if ((sb.st_mode & 0000777) != 0600)
        !           400:                        (void) chmod(timestampfile, 0600);
        !           401:
        !           402:                    status = TS_OLD;    /* actually check mtime below */
        !           403:                }
        !           404:            }
        !           405:        } else if (errno != ENOENT) {
        !           406:            log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
        !           407:            status = TS_ERROR;
        !           408:        }
        !           409:     }
        !           410:
        !           411:     /*
        !           412:      * If the file/dir exists, check its mtime.
        !           413:      */
        !           414:     if (status == TS_OLD) {
        !           415:        now = time(NULL);
        !           416:        if (def_ival(I_TS_TIMEOUT) &&
        !           417:            now - sb.st_mtime < 60 * def_ival(I_TS_TIMEOUT)) {
        !           418:            /*
        !           419:             * Check for bogus time on the stampfile.  The clock may
        !           420:             * have been set back or someone could be trying to spoof us.
        !           421:             */
        !           422:            if (sb.st_mtime > now + 60 * def_ival(I_TS_TIMEOUT) * 2) {
        !           423:                log_error(NO_EXIT,
        !           424:                    "timestamp too far in the future: %20.20s",
        !           425:                    4 + ctime(&sb.st_mtime));
        !           426:                if (timestampfile)
        !           427:                    (void) unlink(timestampfile);
        !           428:                else
        !           429:                    (void) rmdir(timestampdir);
        !           430:                status = TS_MISSING;
        !           431:            } else
        !           432:                status = TS_CURRENT;
        !           433:        }
        !           434:     }
        !           435:
        !           436:     return(status);
        !           437: }
        !           438:
        !           439: /*
        !           440:  * Remove the timestamp ticket file/dir.
        !           441:  */
        !           442: void
        !           443: remove_timestamp(remove)
        !           444:     int remove;
        !           445: {
        !           446:     char *timestampdir;
        !           447:     char *timestampfile;
        !           448:     char *ts;
        !           449:     int status;
        !           450:
        !           451:     build_timestamp(&timestampdir, &timestampfile);
        !           452:     status = timestamp_status(timestampdir, timestampfile, user_name, FALSE);
        !           453:     if (status == TS_OLD || status == TS_CURRENT) {
        !           454:        ts = timestampfile ? timestampfile : timestampdir;
        !           455:        if (remove) {
        !           456:            if (timestampfile)
        !           457:                status = unlink(timestampfile);
        !           458:            else
        !           459:                status = rmdir(timestampdir);
        !           460:            if (status == -1) {
        !           461:                log_error(NO_EXIT, "can't remove %s (%s), will reset to epoch",
        !           462:                    strerror(errno), ts);
        !           463:                remove = FALSE;
        !           464:            }
        !           465:        }
        !           466:        if (!remove && touch(ts, 0) == -1) {
        !           467:            (void) fprintf(stderr, "%s: can't reset %s to epoch: %s\n",
        !           468:                Argv[0], ts, strerror(errno));
        !           469:        }
        !           470:     }
        !           471:
        !           472:     free(timestampdir);
        !           473:     if (timestampfile)
        !           474:        free(timestampfile);
        !           475: }