Annotation of src/usr.bin/sudo/check.c, Revision 1.5.4.2
1.1 millert 1: /*
1.5.4.1 millert 2: * Copyright (c) 1993-1996,1998-2001 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.5.4.1 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.5.4.1 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 */
50: #ifdef HAVE_STRING_H
1.5.4.1 millert 51: # include <string.h>
52: #else
53: # ifdef HAVE_STRINGS_H
54: # include <strings.h>
55: # endif
1.1 millert 56: #endif /* HAVE_STRING_H */
1.5.4.1 millert 57: #ifdef HAVE_UNISTD_H
58: # include <unistd.h>
59: #endif /* HAVE_UNISTD_H */
1.1 millert 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.5.4.1 millert 70: static const char rcsid[] = "$Sudo: check.c,v 1.202 2001/12/14 19:52:47 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:
148: if (touch(timestampfile ? timestampfile : timestampdir, time(NULL)) == -1) {
149: if (timestampfile) {
150: int fd = open(timestampfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
151:
152: if (fd == -1)
153: log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
154: else
155: close(fd);
156: } else {
157: if (mkdir(timestampdir, 0700) == -1)
158: log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
159: }
160: }
161: }
162:
163: /*
164: * Expand %h and %u escapes in the prompt and pass back the dynamically
165: * allocated result. Returns the same string if there are no escapes.
166: */
167: static char *
168: expand_prompt(old_prompt, user, host)
169: char *old_prompt;
170: char *user;
171: char *host;
172: {
173: size_t len;
174: int subst;
175: char *p, *np, *new_prompt, lastchar;
176:
177: /* How much space do we need to malloc for the prompt? */
178: subst = 0;
179: for (p = old_prompt, len = strlen(old_prompt), lastchar = '\0'; *p; p++) {
180: if (lastchar == '%') {
181: if (*p == 'h') {
182: len += strlen(user_shost) - 2;
183: subst = 1;
184: } else if (*p == 'u') {
185: len += strlen(user_name) - 2;
186: subst = 1;
187: }
188: }
189:
190: if (lastchar == '%' && *p == '%') {
191: lastchar = '\0';
192: len--;
193: } else
194: lastchar = *p;
195: }
196:
197: if (subst) {
198: new_prompt = (char *) emalloc(len + 1);
1.5.4.2 ! millert 199: for (p = old_prompt, np = new_prompt, lastchar = '\0'; *p; p++) {
1.1 millert 200: if (lastchar == '%' && (*p == 'h' || *p == 'u' || *p == '%')) {
1.5.4.2 ! millert 201: /* substitute user/host name */
1.1 millert 202: if (*p == 'h') {
203: np--;
204: strcpy(np, user_shost);
205: np += strlen(user_shost);
206: } else if (*p == 'u') {
207: np--;
208: strcpy(np, user_name);
209: np += strlen(user_name);
210: }
211: } else
212: *np++ = *p;
213:
214: if (lastchar == '%' && *p == '%')
215: lastchar = '\0';
216: else
217: lastchar = *p;
218: }
219: *np = '\0';
220: } else
221: new_prompt = old_prompt;
222:
223: return(new_prompt);
224: }
225:
226: /*
227: * Checks if the user is exempt from supplying a password.
228: */
229: int
230: user_is_exempt()
231: {
232: struct group *grp;
233: char **gr_mem;
234:
1.5.4.1 millert 235: if (!def_str(I_EXEMPT_GROUP))
1.1 millert 236: return(FALSE);
237:
1.5.4.1 millert 238: if (!(grp = getgrnam(def_str(I_EXEMPT_GROUP))))
1.1 millert 239: return(FALSE);
240:
1.5 millert 241: if (user_gid == grp->gr_gid)
1.1 millert 242: return(TRUE);
243:
244: for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) {
245: if (strcmp(user_name, *gr_mem) == 0)
246: return(TRUE);
247: }
248:
249: return(FALSE);
250: }
251:
252: /*
253: * Fills in timestampdir as well as timestampfile if using tty tickets.
254: */
255: static void
256: build_timestamp(timestampdir, timestampfile)
257: char **timestampdir;
258: char **timestampfile;
259: {
1.4 millert 260: char *dirparent;
261: int len;
1.1 millert 262:
1.4 millert 263: dirparent = def_str(I_TIMESTAMPDIR);
264: len = easprintf(timestampdir, "%s/%s", dirparent, user_name);
265: if (len >= MAXPATHLEN)
266: log_error(0, "timestamp path too long: %s", timestampdir);
267:
268: /*
269: * Timestamp file may be a file in the directory or NUL to use
270: * the directory as the timestamp.
271: */
1.1 millert 272: if (def_flag(I_TTY_TICKETS)) {
273: char *p;
274:
275: if ((p = strrchr(user_tty, '/')))
276: p++;
277: else
278: p = user_tty;
1.4 millert 279: if (def_flag(I_TARGETPW))
280: len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name,
281: p, *user_runas);
282: else
283: len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p);
284: if (len >= MAXPATHLEN)
285: log_error(0, "timestamp path too long: %s", timestampfile);
286: } else if (def_flag(I_TARGETPW)) {
287: len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name,
288: *user_runas);
289: if (len >= MAXPATHLEN)
290: log_error(0, "timestamp path too long: %s", timestampfile);
291: } else
1.1 millert 292: *timestampfile = NULL;
293: }
294:
295: /*
296: * Check the timestamp file and directory and return their status.
297: */
298: static int
299: timestamp_status(timestampdir, timestampfile, user, make_dirs)
300: char *timestampdir;
301: char *timestampfile;
302: char *user;
303: int make_dirs;
304: {
305: struct stat sb;
306: time_t now;
307: char *dirparent = def_str(I_TIMESTAMPDIR);
308: int status = TS_ERROR; /* assume the worst */
309:
310: /*
311: * Sanity check dirparent and make it if it doesn't already exist.
312: * We start out assuming the worst (that the dir is not sane) and
313: * if it is ok upgrade the status to ``no timestamp file''.
314: * Note that we don't check the parent(s) of dirparent for
315: * sanity since the sudo dir is often just located in /tmp.
316: */
317: if (lstat(dirparent, &sb) == 0) {
318: if (!S_ISDIR(sb.st_mode))
319: log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
320: dirparent, sb.st_mode);
321: else if (sb.st_uid != 0)
322: log_error(NO_EXIT, "%s owned by uid %ld, should be owned by root",
323: dirparent, (long) sb.st_uid);
324: else if ((sb.st_mode & 0000022))
325: log_error(NO_EXIT,
326: "%s writable by non-owner (0%o), should be mode 0700",
327: dirparent, sb.st_mode);
328: else {
329: if ((sb.st_mode & 0000777) != 0700)
330: (void) chmod(dirparent, 0700);
331: status = TS_MISSING;
332: }
333: } else if (errno != ENOENT) {
334: log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
335: } else {
336: /* No dirparent, try to make one. */
337: if (make_dirs) {
338: if (mkdir(dirparent, S_IRWXU))
339: log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
340: dirparent);
341: else
342: status = TS_MISSING;
343: }
344: }
345: if (status == TS_ERROR)
346: return(status);
347:
348: /*
349: * Sanity check the user's ticket dir. We start by downgrading
350: * the status to TS_ERROR. If the ticket dir exists and is sane
351: * this will be upgraded to TS_OLD. If the dir does not exist,
352: * it will be upgraded to TS_MISSING.
353: */
354: status = TS_ERROR; /* downgrade status again */
355: if (lstat(timestampdir, &sb) == 0) {
356: if (!S_ISDIR(sb.st_mode)) {
357: if (S_ISREG(sb.st_mode)) {
358: /* convert from old style */
359: if (unlink(timestampdir) == 0)
360: status = TS_MISSING;
361: } else
362: log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
363: timestampdir, sb.st_mode);
364: } else if (sb.st_uid != 0)
365: log_error(NO_EXIT, "%s owned by uid %ld, should be owned by root",
366: timestampdir, (long) sb.st_uid);
367: else if ((sb.st_mode & 0000022))
368: log_error(NO_EXIT,
369: "%s writable by non-owner (0%o), should be mode 0700",
370: timestampdir, sb.st_mode);
371: else {
372: if ((sb.st_mode & 0000777) != 0700)
373: (void) chmod(timestampdir, 0700);
374: status = TS_OLD; /* do date check later */
375: }
376: } else if (errno != ENOENT) {
377: log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
378: } else
379: status = TS_MISSING;
380:
381: /*
382: * If there is no user ticket dir, AND we are in tty ticket mode,
383: * AND the make_dirs flag is set, create the user ticket dir.
384: */
385: if (status == TS_MISSING && timestampfile && make_dirs) {
386: if (mkdir(timestampdir, S_IRWXU) == -1) {
387: status = TS_ERROR;
388: log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
389: }
390: }
391:
392: /*
393: * Sanity check the tty ticket file if it exists.
394: */
395: if (timestampfile && status != TS_ERROR) {
396: if (status != TS_MISSING)
397: status = TS_NOFILE; /* dir there, file missing */
398: if (lstat(timestampfile, &sb) == 0) {
399: if (!S_ISREG(sb.st_mode)) {
400: status = TS_ERROR;
401: log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
402: timestampfile, sb.st_mode);
403: } else {
404: /* If bad uid or file mode, complain and kill the bogus file. */
405: if (sb.st_uid != 0) {
406: log_error(NO_EXIT,
407: "%s owned by uid %ld, should be owned by root",
408: timestampfile, (long) sb.st_uid);
409: (void) unlink(timestampfile);
410: } else if ((sb.st_mode & 0000022)) {
411: log_error(NO_EXIT,
412: "%s writable by non-owner (0%o), should be mode 0600",
413: timestampfile, sb.st_mode);
414: (void) unlink(timestampfile);
415: } else {
416: /* If not mode 0600, fix it. */
417: if ((sb.st_mode & 0000777) != 0600)
418: (void) chmod(timestampfile, 0600);
419:
420: status = TS_OLD; /* actually check mtime below */
421: }
422: }
423: } else if (errno != ENOENT) {
424: log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
425: status = TS_ERROR;
426: }
427: }
428:
429: /*
430: * If the file/dir exists, check its mtime.
431: */
432: if (status == TS_OLD) {
1.5.4.1 millert 433: /* Negative timeouts only expire manually (sudo -k). */
434: if (def_ival(I_TIMESTAMP_TIMEOUT) < 0 && sb.st_mtime != 0)
435: status = TS_CURRENT;
436: else {
437: now = time(NULL);
438: if (def_ival(I_TIMESTAMP_TIMEOUT) &&
439: now - sb.st_mtime < 60 * def_ival(I_TIMESTAMP_TIMEOUT)) {
440: /*
441: * Check for bogus time on the stampfile. The clock may
442: * have been set back or someone could be trying to spoof us.
443: */
444: if (sb.st_mtime > now + 60 * def_ival(I_TIMESTAMP_TIMEOUT) * 2) {
445: log_error(NO_EXIT,
446: "timestamp too far in the future: %20.20s",
447: 4 + ctime(&sb.st_mtime));
448: if (timestampfile)
449: (void) unlink(timestampfile);
450: else
451: (void) rmdir(timestampdir);
452: status = TS_MISSING;
453: } else
454: status = TS_CURRENT;
455: }
1.1 millert 456: }
457: }
458:
459: return(status);
460: }
461:
462: /*
463: * Remove the timestamp ticket file/dir.
464: */
465: void
466: remove_timestamp(remove)
467: int remove;
468: {
469: char *timestampdir;
470: char *timestampfile;
471: char *ts;
472: int status;
473:
474: build_timestamp(×tampdir, ×tampfile);
475: status = timestamp_status(timestampdir, timestampfile, user_name, FALSE);
476: if (status == TS_OLD || status == TS_CURRENT) {
477: ts = timestampfile ? timestampfile : timestampdir;
478: if (remove) {
479: if (timestampfile)
480: status = unlink(timestampfile);
481: else
482: status = rmdir(timestampdir);
1.2 millert 483: if (status == -1 && errno != ENOENT) {
1.1 millert 484: log_error(NO_EXIT, "can't remove %s (%s), will reset to epoch",
1.2 millert 485: ts, strerror(errno));
1.1 millert 486: remove = FALSE;
487: }
488: }
489: if (!remove && touch(ts, 0) == -1) {
490: (void) fprintf(stderr, "%s: can't reset %s to epoch: %s\n",
491: Argv[0], ts, strerror(errno));
492: }
493: }
494:
495: free(timestampdir);
496: if (timestampfile)
497: free(timestampfile);
498: }