2 * Copyright (c) 1993-1996,1998-2001 Todd C. Miller <Todd.Miller@courtesan.com>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
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.
37 #include <sys/types.h>
38 #include <sys/param.h>
49 #endif /* STDC_HEADERS */
53 # ifdef HAVE_STRINGS_H
56 #endif /* HAVE_STRING_H */
59 #endif /* HAVE_UNISTD_H */
70 static const char rcsid[] = "$Sudo: check.c,v 1.203 2002/04/25 15:30:12 millert Exp $";
73 /* Status codes for timestamp_status() */
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 *));
87 * This function only returns if the user can successfully
88 * verify who he/she is.
93 char *timestampdir = NULL;
94 char *timestampfile = NULL;
98 if (user_uid == 0 || user_is_exempt())
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 */
107 /* Expand any escapes in the prompt. */
108 prompt = expand_prompt(user_prompt ? user_prompt : def_str(I_PASSPROMPT),
109 user_name, user_shost);
111 verify_user(auth_pw, prompt);
113 if (status != TS_ERROR)
114 update_timestamp(timestampdir, timestampfile);
121 * Standard sudo lecture.
122 * TODO: allow the user to specify a file name instead.
128 if (def_flag(I_LECTURE)) {
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\
133 #1) Respect the privacy of others.\n\
134 #2) Think before you type.\n\n",
140 * Update the time on the timestamp file/dir or create it if necessary.
143 update_timestamp(timestampdir, timestampfile)
148 if (touch(timestampfile ? timestampfile : timestampdir, time(NULL)) == -1) {
150 int fd = open(timestampfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
153 log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
157 if (mkdir(timestampdir, 0700) == -1)
158 log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
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.
168 expand_prompt(old_prompt, user, host)
175 char *p, *np, *new_prompt, lastchar;
177 /* How much space do we need to malloc for the prompt? */
179 for (p = old_prompt, len = strlen(old_prompt), lastchar = '\0'; *p; p++) {
180 if (lastchar == '%') {
182 len += strlen(user_shost) - 2;
184 } else if (*p == 'u') {
185 len += strlen(user_name) - 2;
190 if (lastchar == '%' && *p == '%') {
198 new_prompt = (char *) emalloc(len + 1);
199 for (p = old_prompt, np = new_prompt, lastchar = '\0'; *p; p++) {
200 if (lastchar == '%' && (*p == 'h' || *p == 'u' || *p == '%')) {
201 /* substitute user/host name */
204 strcpy(np, user_shost);
205 np += strlen(user_shost);
206 } else if (*p == 'u') {
208 strcpy(np, user_name);
209 np += strlen(user_name);
214 if (lastchar == '%' && *p == '%')
221 new_prompt = old_prompt;
227 * Checks if the user is exempt from supplying a password.
235 if (!def_str(I_EXEMPT_GROUP))
238 if (!(grp = getgrnam(def_str(I_EXEMPT_GROUP))))
241 if (user_gid == grp->gr_gid)
244 for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) {
245 if (strcmp(user_name, *gr_mem) == 0)
253 * Fills in timestampdir as well as timestampfile if using tty tickets.
256 build_timestamp(timestampdir, timestampfile)
258 char **timestampfile;
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);
269 * Timestamp file may be a file in the directory or NUL to use
270 * the directory as the timestamp.
272 if (def_flag(I_TTY_TICKETS)) {
275 if ((p = strrchr(user_tty, '/')))
279 if (def_flag(I_TARGETPW))
280 len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name,
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,
289 if (len >= MAXPATHLEN)
290 log_error(0, "timestamp path too long: %s", timestampfile);
292 *timestampfile = NULL;
296 * Check the timestamp file and directory and return their status.
299 timestamp_status(timestampdir, timestampfile, user, make_dirs)
307 char *dirparent = def_str(I_TIMESTAMPDIR);
308 int status = TS_ERROR; /* assume the worst */
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.
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))
326 "%s writable by non-owner (0%o), should be mode 0700",
327 dirparent, sb.st_mode);
329 if ((sb.st_mode & 0000777) != 0700)
330 (void) chmod(dirparent, 0700);
333 } else if (errno != ENOENT) {
334 log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
336 /* No dirparent, try to make one. */
338 if (mkdir(dirparent, S_IRWXU))
339 log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
345 if (status == TS_ERROR)
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.
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)
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))
369 "%s writable by non-owner (0%o), should be mode 0700",
370 timestampdir, sb.st_mode);
372 if ((sb.st_mode & 0000777) != 0700)
373 (void) chmod(timestampdir, 0700);
374 status = TS_OLD; /* do date check later */
376 } else if (errno != ENOENT) {
377 log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
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.
385 if (status == TS_MISSING && timestampfile && make_dirs) {
386 if (mkdir(timestampdir, S_IRWXU) == -1) {
388 log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
393 * Sanity check the tty ticket file if it exists.
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)) {
401 log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
402 timestampfile, sb.st_mode);
404 /* If bad uid or file mode, complain and kill the bogus file. */
405 if (sb.st_uid != 0) {
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)) {
412 "%s writable by non-owner (0%o), should be mode 0600",
413 timestampfile, sb.st_mode);
414 (void) unlink(timestampfile);
416 /* If not mode 0600, fix it. */
417 if ((sb.st_mode & 0000777) != 0600)
418 (void) chmod(timestampfile, 0600);
420 status = TS_OLD; /* actually check mtime below */
423 } else if (errno != ENOENT) {
424 log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
430 * If the file/dir exists, check its mtime.
432 if (status == TS_OLD) {
433 /* Negative timeouts only expire manually (sudo -k). */
434 if (def_ival(I_TIMESTAMP_TIMEOUT) < 0 && sb.st_mtime != 0)
438 if (def_ival(I_TIMESTAMP_TIMEOUT) &&
439 now - sb.st_mtime < 60 * def_ival(I_TIMESTAMP_TIMEOUT)) {
441 * Check for bogus time on the stampfile. The clock may
442 * have been set back or someone could be trying to spoof us.
444 if (sb.st_mtime > now + 60 * def_ival(I_TIMESTAMP_TIMEOUT) * 2) {
446 "timestamp too far in the future: %20.20s",
447 4 + ctime(&sb.st_mtime));
449 (void) unlink(timestampfile);
451 (void) rmdir(timestampdir);
463 * Remove the timestamp ticket file/dir.
466 remove_timestamp(remove)
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;
480 status = unlink(timestampfile);
482 status = rmdir(timestampdir);
483 if (status == -1 && errno != ENOENT) {
484 log_error(NO_EXIT, "can't remove %s (%s), will reset to epoch",
485 ts, strerror(errno));
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));