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