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