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