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(×tampdir, ×tampfile);
! 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(×tampdir, ×tampfile);
! 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: }