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