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