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