1 /*
2  * Copyright (c) 1993-1996,1998-2004 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  *
16  * Sponsored in part by the Defense Advanced Research Projects
17  * Agency (DARPA) and Air Force Research Laboratory, Air Force
18  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
19  */
20 
21 #include "config.h"
22 
23 #include <sys/types.h>
24 #include <sys/param.h>
25 #include <sys/stat.h>
26 #ifndef __TANDEM
27 # include <sys/file.h>
28 #endif
29 #include <stdio.h>
30 #ifdef STDC_HEADERS
31 # include <stdlib.h>
32 # include <stddef.h>
33 #else
34 # ifdef HAVE_STDLIB_H
35 #  include <stdlib.h>
36 # endif
37 #endif /* STDC_HEADERS */
38 #ifdef HAVE_STRING_H
39 # include <string.h>
40 #else
41 # ifdef HAVE_STRINGS_H
42 #  include <strings.h>
43 # endif
44 #endif /* HAVE_STRING_H */
45 #ifdef HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif /* HAVE_UNISTD_H */
48 #ifdef HAVE_ERR_H
49 # include <err.h>
50 #else
51 # include "emul/err.h"
52 #endif /* HAVE_ERR_H */
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <signal.h>
56 #include <time.h>
57 #include <pwd.h>
58 #include <grp.h>
59 
60 #include "sudo.h"
61 
62 #ifndef lint
63 static const char rcsid[] = "$Sudo: check.c,v 1.226 2004/09/08 15:48:23 millert Exp $";
64 #endif /* lint */
65 
66 /* Status codes for timestamp_status() */
67 #define TS_CURRENT		0
68 #define TS_OLD			1
69 #define TS_MISSING		2
70 #define TS_NOFILE		3
71 #define TS_ERROR		4
72 
73 static void  build_timestamp	__P((char **, char **));
74 static int   timestamp_status	__P((char *, char *, char *, int));
75 static char *expand_prompt	__P((char *, char *, char *));
76 static void  lecture		__P((int));
77 static void  update_timestamp	__P((char *, char *));
78 
79 /*
80  * This function only returns if the user can successfully
81  * verify who he/she is.
82  */
83 void
check_user(override)84 check_user(override)
85     int override;
86 {
87     char *timestampdir = NULL;
88     char *timestampfile = NULL;
89     char *prompt;
90     int status;
91 
92     if (user_uid == 0 || user_uid == runas_pw->pw_uid || user_is_exempt())
93 	return;
94 
95     build_timestamp(&timestampdir, &timestampfile);
96     status = timestamp_status(timestampdir, timestampfile, user_name, TRUE);
97     if (override || status != TS_CURRENT) {
98 	lecture(status);
99 
100 	/* Expand any escapes in the prompt. */
101 	prompt = expand_prompt(user_prompt ? user_prompt : def_passprompt,
102 	    user_name, user_shost);
103 
104 	verify_user(auth_pw, prompt);
105     }
106     if (status != TS_ERROR)
107 	update_timestamp(timestampdir, timestampfile);
108     free(timestampdir);
109     if (timestampfile)
110 	free(timestampfile);
111 }
112 
113 /*
114  * Standard sudo lecture.
115  * TODO: allow the user to specify a file name instead.
116  */
117 static void
lecture(status)118 lecture(status)
119     int status;
120 {
121     FILE *fp;
122     char buf[BUFSIZ];
123     ssize_t nread;
124 
125     if (def_lecture == never ||
126 	(def_lecture == once && status != TS_MISSING && status != TS_ERROR))
127 	return;
128 
129     if (def_lecture_file && (fp = fopen(def_lecture_file, "r")) != NULL) {
130 	while ((nread = fread(buf, sizeof(char), sizeof(buf), fp)) != 0)
131 	    fwrite(buf, nread, 1, stderr);
132     } else {
133 	(void) fputs("\n\
134 We trust you have received the usual lecture from the local System\n\
135 Administrator. It usually boils down to these three things:\n\
136 \n\
137     #1) Respect the privacy of others.\n\
138     #2) Think before you type.\n\
139     #3) With great power comes great responsibility.\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
update_timestamp(timestampdir,timestampfile)148 update_timestamp(timestampdir, timestampfile)
149     char *timestampdir;
150     char *timestampfile;
151 {
152     if (timestamp_uid != 0)
153 	set_perms(PERM_TIMESTAMP);
154     if (touch(-1, timestampfile ? timestampfile : timestampdir, NULL) == -1) {
155 	if (timestampfile) {
156 	    int fd = open(timestampfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
157 
158 	    if (fd == -1)
159 		log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
160 	    else
161 		close(fd);
162 	} else {
163 	    if (mkdir(timestampdir, 0700) == -1)
164 		log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
165 	}
166     }
167     if (timestamp_uid != 0)
168 	set_perms(PERM_ROOT);
169 }
170 
171 /*
172  * Expand %h and %u escapes in the prompt and pass back the dynamically
173  * allocated result.  Returns the same string if there are no escapes.
174  */
175 static char *
expand_prompt(old_prompt,user,host)176 expand_prompt(old_prompt, user, host)
177     char *old_prompt;
178     char *user;
179     char *host;
180 {
181     size_t len, n;
182     int subst;
183     char *p, *np, *new_prompt, *endp;
184 
185     /* How much space do we need to malloc for the prompt? */
186     subst = 0;
187     for (p = old_prompt, len = strlen(old_prompt); *p; p++) {
188 	if (p[0] =='%') {
189 	    switch (p[1]) {
190 		case 'h':
191 		    p++;
192 		    len += strlen(user_shost) - 2;
193 		    subst = 1;
194 		    break;
195 		case 'H':
196 		    p++;
197 		    len += strlen(user_host) - 2;
198 		    subst = 1;
199 		    break;
200 		case 'u':
201 		    p++;
202 		    len += strlen(user_name) - 2;
203 		    subst = 1;
204 		    break;
205 		case 'U':
206 		    p++;
207 		    len += strlen(*user_runas) - 2;
208 		    subst = 1;
209 		    break;
210 		case '%':
211 		    p++;
212 		    len--;
213 		    subst = 1;
214 		    break;
215 		default:
216 		    break;
217 	    }
218 	}
219     }
220 
221     if (subst) {
222 	new_prompt = (char *) emalloc(++len);
223 	endp = new_prompt + len;
224 	for (p = old_prompt, np = new_prompt; *p; p++) {
225 	    if (p[0] =='%') {
226 		switch (p[1]) {
227 		    case 'h':
228 			p++;
229 			n = strlcpy(np, user_shost, np - endp);
230 			if (n >= np - endp)
231 			    goto oflow;
232 			np += n;
233 			continue;
234 		    case 'H':
235 			p++;
236 			n = strlcpy(np, user_host, np - endp);
237 			if (n >= np - endp)
238 			    goto oflow;
239 			np += n;
240 			continue;
241 		    case 'u':
242 			p++;
243 			n = strlcpy(np, user_name, np - endp);
244 			if (n >= np - endp)
245 			    goto oflow;
246 			np += n;
247 			continue;
248 		    case 'U':
249 			p++;
250 			n = strlcpy(np,  *user_runas, np - endp);
251 			if (n >= np - endp)
252 			    goto oflow;
253 			np += n;
254 			continue;
255 		    case '%':
256 			/* convert %% -> % */
257 			p++;
258 			break;
259 		    default:
260 			/* no conversion */
261 			break;
262 		}
263 	    }
264 	    *np++ = *p;
265 	    if (np >= endp)
266 		goto oflow;
267 	}
268 	*np = '\0';
269     } else
270 	new_prompt = old_prompt;
271 
272     return(new_prompt);
273 
274 oflow:
275     /* We pre-allocate enough space, so this should never happen. */
276     errx(1, "internal error, expand_prompt() overflow");
277 }
278 
279 /*
280  * Checks if the user is exempt from supplying a password.
281  */
282 int
user_is_exempt()283 user_is_exempt()
284 {
285     struct group *grp;
286     char **gr_mem;
287 
288     if (!def_exempt_group)
289 	return(FALSE);
290 
291     if (!(grp = getgrnam(def_exempt_group)))
292 	return(FALSE);
293 
294     if (user_gid == grp->gr_gid)
295 	return(TRUE);
296 
297     for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) {
298 	if (strcmp(user_name, *gr_mem) == 0)
299 	    return(TRUE);
300     }
301 
302     return(FALSE);
303 }
304 
305 /*
306  * Fills in timestampdir as well as timestampfile if using tty tickets.
307  */
308 static void
build_timestamp(timestampdir,timestampfile)309 build_timestamp(timestampdir, timestampfile)
310     char **timestampdir;
311     char **timestampfile;
312 {
313     char *dirparent;
314     int len;
315 
316     dirparent = def_timestampdir;
317     len = easprintf(timestampdir, "%s/%s", dirparent, user_name);
318     if (len >= PATH_MAX)
319 	log_error(0, "timestamp path too long: %s", *timestampdir);
320 
321     /*
322      * Timestamp file may be a file in the directory or NUL to use
323      * the directory as the timestamp.
324      */
325     if (def_tty_tickets) {
326 	char *p;
327 
328 	if ((p = strrchr(user_tty, '/')))
329 	    p++;
330 	else
331 	    p = user_tty;
332 	if (def_targetpw)
333 	    len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name,
334 		p, *user_runas);
335 	else
336 	    len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p);
337 	if (len >= PATH_MAX)
338 	    log_error(0, "timestamp path too long: %s", *timestampfile);
339     } else if (def_targetpw) {
340 	len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name,
341 	    *user_runas);
342 	if (len >= PATH_MAX)
343 	    log_error(0, "timestamp path too long: %s", *timestampfile);
344     } else
345 	*timestampfile = NULL;
346 }
347 
348 /*
349  * Check the timestamp file and directory and return their status.
350  */
351 static int
timestamp_status(timestampdir,timestampfile,user,make_dirs)352 timestamp_status(timestampdir, timestampfile, user, make_dirs)
353     char *timestampdir;
354     char *timestampfile;
355     char *user;
356     int make_dirs;
357 {
358     struct stat sb;
359     time_t now;
360     char *dirparent = def_timestampdir;
361     int status = TS_ERROR;		/* assume the worst */
362 
363     if (timestamp_uid != 0)
364 	set_perms(PERM_TIMESTAMP);
365 
366     /*
367      * Sanity check dirparent and make it if it doesn't already exist.
368      * We start out assuming the worst (that the dir is not sane) and
369      * if it is ok upgrade the status to ``no timestamp file''.
370      * Note that we don't check the parent(s) of dirparent for
371      * sanity since the sudo dir is often just located in /tmp.
372      */
373     if (lstat(dirparent, &sb) == 0) {
374 	if (!S_ISDIR(sb.st_mode))
375 	    log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
376 		dirparent, sb.st_mode);
377 	else if (sb.st_uid != timestamp_uid)
378 	    log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
379 		dirparent, (unsigned long) sb.st_uid,
380 		(unsigned long) timestamp_uid);
381 	else if ((sb.st_mode & 0000022))
382 	    log_error(NO_EXIT,
383 		"%s writable by non-owner (0%o), should be mode 0700",
384 		dirparent, sb.st_mode);
385 	else {
386 	    if ((sb.st_mode & 0000777) != 0700)
387 		(void) chmod(dirparent, 0700);
388 	    status = TS_MISSING;
389 	}
390     } else if (errno != ENOENT) {
391 	log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
392     } else {
393 	/* No dirparent, try to make one. */
394 	if (make_dirs) {
395 	    if (mkdir(dirparent, S_IRWXU))
396 		log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
397 		    dirparent);
398 	    else
399 		status = TS_MISSING;
400 	}
401     }
402     if (status == TS_ERROR) {
403 	if (timestamp_uid != 0)
404 	    set_perms(PERM_ROOT);
405 	return(status);
406     }
407 
408     /*
409      * Sanity check the user's ticket dir.  We start by downgrading
410      * the status to TS_ERROR.  If the ticket dir exists and is sane
411      * this will be upgraded to TS_OLD.  If the dir does not exist,
412      * it will be upgraded to TS_MISSING.
413      */
414     status = TS_ERROR;			/* downgrade status again */
415     if (lstat(timestampdir, &sb) == 0) {
416 	if (!S_ISDIR(sb.st_mode)) {
417 	    if (S_ISREG(sb.st_mode)) {
418 		/* convert from old style */
419 		if (unlink(timestampdir) == 0)
420 		    status = TS_MISSING;
421 	    } else
422 		log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
423 		    timestampdir, sb.st_mode);
424 	} else if (sb.st_uid != timestamp_uid)
425 	    log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
426 		timestampdir, (unsigned long) sb.st_uid,
427 		(unsigned long) timestamp_uid);
428 	else if ((sb.st_mode & 0000022))
429 	    log_error(NO_EXIT,
430 		"%s writable by non-owner (0%o), should be mode 0700",
431 		timestampdir, sb.st_mode);
432 	else {
433 	    if ((sb.st_mode & 0000777) != 0700)
434 		(void) chmod(timestampdir, 0700);
435 	    status = TS_OLD;		/* do date check later */
436 	}
437     } else if (errno != ENOENT) {
438 	log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
439     } else
440 	status = TS_MISSING;
441 
442     /*
443      * If there is no user ticket dir, AND we are in tty ticket mode,
444      * AND the make_dirs flag is set, create the user ticket dir.
445      */
446     if (status == TS_MISSING && timestampfile && make_dirs) {
447 	if (mkdir(timestampdir, S_IRWXU) == -1) {
448 	    status = TS_ERROR;
449 	    log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
450 	}
451     }
452 
453     /*
454      * Sanity check the tty ticket file if it exists.
455      */
456     if (timestampfile && status != TS_ERROR) {
457 	if (status != TS_MISSING)
458 	    status = TS_NOFILE;			/* dir there, file missing */
459 	if (lstat(timestampfile, &sb) == 0) {
460 	    if (!S_ISREG(sb.st_mode)) {
461 		status = TS_ERROR;
462 		log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
463 		    timestampfile, sb.st_mode);
464 	    } else {
465 		/* If bad uid or file mode, complain and kill the bogus file. */
466 		if (sb.st_uid != timestamp_uid) {
467 		    log_error(NO_EXIT,
468 			"%s owned by uid %lu, should be uid %lu",
469 			timestampfile, (unsigned long) sb.st_uid,
470 			(unsigned long) timestamp_uid);
471 		    (void) unlink(timestampfile);
472 		} else if ((sb.st_mode & 0000022)) {
473 		    log_error(NO_EXIT,
474 			"%s writable by non-owner (0%o), should be mode 0600",
475 			timestampfile, sb.st_mode);
476 		    (void) unlink(timestampfile);
477 		} else {
478 		    /* If not mode 0600, fix it. */
479 		    if ((sb.st_mode & 0000777) != 0600)
480 			(void) chmod(timestampfile, 0600);
481 
482 		    status = TS_OLD;	/* actually check mtime below */
483 		}
484 	    }
485 	} else if (errno != ENOENT) {
486 	    log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
487 	    status = TS_ERROR;
488 	}
489     }
490 
491     /*
492      * If the file/dir exists, check its mtime.
493      */
494     if (status == TS_OLD) {
495 	/* Negative timeouts only expire manually (sudo -k). */
496 	if (def_timestamp_timeout < 0 && sb.st_mtime != 0)
497 	    status = TS_CURRENT;
498 	else {
499 	    /* XXX - should use timespec here */
500 	    now = time(NULL);
501 	    if (def_timestamp_timeout &&
502 		now - sb.st_mtime < 60 * def_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_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 	    }
519 	}
520     }
521 
522     if (timestamp_uid != 0)
523 	set_perms(PERM_ROOT);
524     return(status);
525 }
526 
527 /*
528  * Remove the timestamp ticket file/dir.
529  */
530 void
remove_timestamp(remove)531 remove_timestamp(remove)
532     int remove;
533 {
534     struct timespec ts;
535     char *timestampdir, *timestampfile, *path;
536     int status;
537 
538     build_timestamp(&timestampdir, &timestampfile);
539     status = timestamp_status(timestampdir, timestampfile, user_name, FALSE);
540     if (status == TS_OLD || status == TS_CURRENT) {
541 	path = timestampfile ? timestampfile : timestampdir;
542 	if (remove) {
543 	    if (timestampfile)
544 		status = unlink(timestampfile);
545 	    else
546 		status = rmdir(timestampdir);
547 	    if (status == -1 && errno != ENOENT) {
548 		log_error(NO_EXIT, "can't remove %s (%s), will reset to Epoch",
549 		    path, strerror(errno));
550 		remove = FALSE;
551 	    }
552 	} else {
553 	    timespecclear(&ts);
554 	    if (touch(-1, path, &ts) == -1)
555 		err(1, "can't reset %s to Epoch", path);
556 	}
557     }
558 
559     free(timestampdir);
560     if (timestampfile)
561 	free(timestampfile);
562 }
563