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