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