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