switch from rcS.d to rc[0-6].d
[debian/sudo] / check.c
1 /*
2  * Copyright (c) 1993-1996,1998-2005, 2007-2009
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.247 2009/05/25 12:02:41 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, mode)
88     int validated;
89     int mode;
90 {
91     char *timestampdir = NULL;
92     char *timestampfile = NULL;
93     char *prompt;
94     int status;
95
96     if (mode & MODE_INVALIDATE) {
97         /* do not check or update timestamp */
98         status = TS_ERROR;
99     } else {
100         if (user_uid == 0 || user_uid == runas_pw->pw_uid || user_is_exempt())
101             return;
102
103         build_timestamp(&timestampdir, &timestampfile);
104         status = timestamp_status(timestampdir, timestampfile, user_name,
105         TS_MAKE_DIRS);
106     }
107     if (status != TS_CURRENT || ISSET(validated, FLAG_CHECK_USER)) {
108         /* Bail out if we are non-interactive and a password is required */
109         if (ISSET(mode, MODE_NONINTERACTIVE))
110             errorx(1, "sorry, a password is required to run %s", getprogname());
111
112         /* If user specified -A, make sure we have an askpass helper. */
113         if (ISSET(tgetpass_flags, TGP_ASKPASS)) {
114             if (user_askpass == NULL)
115                 log_error(NO_MAIL,
116                     "no askpass program specified, try setting SUDO_ASKPASS");
117         } else if (!ISSET(tgetpass_flags, TGP_STDIN)) {
118             /* If no tty but DISPLAY is set, use askpass if we have it. */
119             if (!user_ttypath && !tty_present()) {
120                 if (user_askpass && user_display && *user_display != '\0') {
121                     SET(tgetpass_flags, TGP_ASKPASS);
122                 } else if (!def_visiblepw) {
123                     log_error(NO_MAIL,
124                         "no tty present and no askpass program specified");
125                 }
126             }
127         }
128
129         if (!ISSET(tgetpass_flags, TGP_ASKPASS))
130             lecture(status);
131
132         /* Expand any escapes in the prompt. */
133         prompt = expand_prompt(user_prompt ? user_prompt : def_passprompt,
134             user_name, user_shost);
135
136         verify_user(auth_pw, prompt);
137     }
138     /* Only update timestamp if user was validated. */
139     if (status != TS_ERROR && ISSET(validated, VALIDATE_OK))
140         update_timestamp(timestampdir, timestampfile);
141     efree(timestampdir);
142     efree(timestampfile);
143 }
144
145 /*
146  * Standard sudo lecture.
147  */
148 static void
149 lecture(status)
150     int status;
151 {
152     FILE *fp;
153     char buf[BUFSIZ];
154     ssize_t nread;
155
156     if (def_lecture == never ||
157         (def_lecture == once && status != TS_MISSING && status != TS_ERROR))
158         return;
159
160     if (def_lecture_file && (fp = fopen(def_lecture_file, "r")) != NULL) {
161         while ((nread = fread(buf, sizeof(char), sizeof(buf), fp)) != 0)
162             fwrite(buf, nread, 1, stderr);
163         fclose(fp);
164     } else {
165         (void) fputs("\n\
166 We trust you have received the usual lecture from the local System\n\
167 Administrator. It usually boils down to these three things:\n\
168 \n\
169     #1) Respect the privacy of others.\n\
170     #2) Think before you type.\n\
171     #3) With great power comes great responsibility.\n\n",
172     stderr);
173     }
174 }
175
176 /*
177  * Update the time on the timestamp file/dir or create it if necessary.
178  */
179 static void
180 update_timestamp(timestampdir, timestampfile)
181     char *timestampdir;
182     char *timestampfile;
183 {
184     if (timestamp_uid != 0)
185         set_perms(PERM_TIMESTAMP);
186     if (touch(-1, timestampfile ? timestampfile : timestampdir, NULL) == -1) {
187         if (timestampfile) {
188             int fd = open(timestampfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
189
190             if (fd == -1)
191                 log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
192             else
193                 close(fd);
194         } else {
195             if (mkdir(timestampdir, 0700) == -1)
196                 log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
197         }
198     }
199     if (timestamp_uid != 0)
200         set_perms(PERM_ROOT);
201 }
202
203 /*
204  * Expand %h and %u escapes in the prompt and pass back the dynamically
205  * allocated result.  Returns the same string if there are no escapes.
206  */
207 static char *
208 expand_prompt(old_prompt, user, host)
209     char *old_prompt;
210     char *user;
211     char *host;
212 {
213     size_t len, n;
214     int subst;
215     char *p, *np, *new_prompt, *endp;
216
217     /* How much space do we need to malloc for the prompt? */
218     subst = 0;
219     for (p = old_prompt, len = strlen(old_prompt); *p; p++) {
220         if (p[0] =='%') {
221             switch (p[1]) {
222                 case 'h':
223                     p++;
224                     len += strlen(user_shost) - 2;
225                     subst = 1;
226                     break;
227                 case 'H':
228                     p++;
229                     len += strlen(user_host) - 2;
230                     subst = 1;
231                     break;
232                 case 'p':
233                     p++;
234                     if (def_rootpw)
235                             len += 2;
236                     else if (def_targetpw || def_runaspw)
237                             len += strlen(runas_pw->pw_name) - 2;
238                     else
239                             len += strlen(user_name) - 2;
240                     subst = 1;
241                     break;
242                 case 'u':
243                     p++;
244                     len += strlen(user_name) - 2;
245                     subst = 1;
246                     break;
247                 case 'U':
248                     p++;
249                     len += strlen(runas_pw->pw_name) - 2;
250                     subst = 1;
251                     break;
252                 case '%':
253                     p++;
254                     len--;
255                     subst = 1;
256                     break;
257                 default:
258                     break;
259             }
260         }
261     }
262
263     if (subst) {
264         new_prompt = (char *) emalloc(++len);
265         endp = new_prompt + len;
266         for (p = old_prompt, np = new_prompt; *p; p++) {
267             if (p[0] =='%') {
268                 switch (p[1]) {
269                     case 'h':
270                         p++;
271                         n = strlcpy(np, user_shost, np - endp);
272                         if (n >= np - endp)
273                             goto oflow;
274                         np += n;
275                         continue;
276                     case 'H':
277                         p++;
278                         n = strlcpy(np, user_host, np - endp);
279                         if (n >= np - endp)
280                             goto oflow;
281                         np += n;
282                         continue;
283                     case 'p':
284                         p++;
285                         if (def_rootpw)
286                                 n = strlcpy(np, "root", np - endp);
287                         else if (def_targetpw || def_runaspw)
288                                 n = strlcpy(np, runas_pw->pw_name, np - endp);
289                         else
290                                 n = strlcpy(np, user_name, np - endp);
291                         if (n >= np - endp)
292                                 goto oflow;
293                         np += n;
294                         continue;
295                     case 'u':
296                         p++;
297                         n = strlcpy(np, user_name, np - endp);
298                         if (n >= np - endp)
299                             goto oflow;
300                         np += n;
301                         continue;
302                     case 'U':
303                         p++;
304                         n = strlcpy(np,  runas_pw->pw_name, np - endp);
305                         if (n >= np - endp)
306                             goto oflow;
307                         np += n;
308                         continue;
309                     case '%':
310                         /* convert %% -> % */
311                         p++;
312                         break;
313                     default:
314                         /* no conversion */
315                         break;
316                 }
317             }
318             *np++ = *p;
319             if (np >= endp)
320                 goto oflow;
321         }
322         *np = '\0';
323     } else
324         new_prompt = old_prompt;
325
326     return(new_prompt);
327
328 oflow:
329     /* We pre-allocate enough space, so this should never happen. */
330     errorx(1, "internal error, expand_prompt() overflow");
331 }
332
333 /*
334  * Checks if the user is exempt from supplying a password.
335  */
336 int
337 user_is_exempt()
338 {
339     struct group *grp;
340     char **gr_mem;
341
342     if (!def_exempt_group)
343         return(FALSE);
344
345     if (!(grp = sudo_getgrnam(def_exempt_group)))
346         return(FALSE);
347
348     if (user_gid == grp->gr_gid)
349         return(TRUE);
350
351     for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) {
352         if (strcmp(user_name, *gr_mem) == 0)
353             return(TRUE);
354     }
355
356     return(FALSE);
357 }
358
359 /*
360  * Fills in timestampdir as well as timestampfile if using tty tickets.
361  */
362 static void
363 build_timestamp(timestampdir, timestampfile)
364     char **timestampdir;
365     char **timestampfile;
366 {
367     char *dirparent;
368     int len;
369
370     dirparent = def_timestampdir;
371     len = easprintf(timestampdir, "%s/%s", dirparent, user_name);
372     if (len >= PATH_MAX)
373         log_error(0, "timestamp path too long: %s", *timestampdir);
374
375     /*
376      * Timestamp file may be a file in the directory or NUL to use
377      * the directory as the timestamp.
378      */
379     if (def_tty_tickets) {
380         char *p;
381
382         if ((p = strrchr(user_tty, '/')))
383             p++;
384         else
385             p = user_tty;
386         if (def_targetpw)
387             len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name,
388                 p, runas_pw->pw_name);
389         else
390             len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p);
391         if (len >= PATH_MAX)
392             log_error(0, "timestamp path too long: %s", *timestampfile);
393     } else if (def_targetpw) {
394         len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name,
395             runas_pw->pw_name);
396         if (len >= PATH_MAX)
397             log_error(0, "timestamp path too long: %s", *timestampfile);
398     } else
399         *timestampfile = NULL;
400 }
401
402 /*
403  * Check the timestamp file and directory and return their status.
404  */
405 static int
406 timestamp_status(timestampdir, timestampfile, user, flags)
407     char *timestampdir;
408     char *timestampfile;
409     char *user;
410     int flags;
411 {
412     struct stat sb;
413     time_t now;
414     char *dirparent = def_timestampdir;
415     int status = TS_ERROR;              /* assume the worst */
416
417     if (timestamp_uid != 0)
418         set_perms(PERM_TIMESTAMP);
419
420     /*
421      * Sanity check dirparent and make it if it doesn't already exist.
422      * We start out assuming the worst (that the dir is not sane) and
423      * if it is ok upgrade the status to ``no timestamp file''.
424      * Note that we don't check the parent(s) of dirparent for
425      * sanity since the sudo dir is often just located in /tmp.
426      */
427     if (lstat(dirparent, &sb) == 0) {
428         if (!S_ISDIR(sb.st_mode))
429             log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
430                 dirparent, (unsigned int) sb.st_mode);
431         else if (sb.st_uid != timestamp_uid)
432             log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
433                 dirparent, (unsigned long) sb.st_uid,
434                 (unsigned long) timestamp_uid);
435         else if ((sb.st_mode & 0000022))
436             log_error(NO_EXIT,
437                 "%s writable by non-owner (0%o), should be mode 0700",
438                 dirparent, (unsigned int) sb.st_mode);
439         else {
440             if ((sb.st_mode & 0000777) != 0700)
441                 (void) chmod(dirparent, 0700);
442             status = TS_MISSING;
443         }
444     } else if (errno != ENOENT) {
445         log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
446     } else {
447         /* No dirparent, try to make one. */
448         if (ISSET(flags, TS_MAKE_DIRS)) {
449             if (mkdir(dirparent, S_IRWXU))
450                 log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
451                     dirparent);
452             else
453                 status = TS_MISSING;
454         }
455     }
456     if (status == TS_ERROR) {
457         if (timestamp_uid != 0)
458             set_perms(PERM_ROOT);
459         return(status);
460     }
461
462     /*
463      * Sanity check the user's ticket dir.  We start by downgrading
464      * the status to TS_ERROR.  If the ticket dir exists and is sane
465      * this will be upgraded to TS_OLD.  If the dir does not exist,
466      * it will be upgraded to TS_MISSING.
467      */
468     status = TS_ERROR;                  /* downgrade status again */
469     if (lstat(timestampdir, &sb) == 0) {
470         if (!S_ISDIR(sb.st_mode)) {
471             if (S_ISREG(sb.st_mode)) {
472                 /* convert from old style */
473                 if (unlink(timestampdir) == 0)
474                     status = TS_MISSING;
475             } else
476                 log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
477                     timestampdir, (unsigned int) sb.st_mode);
478         } else if (sb.st_uid != timestamp_uid)
479             log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
480                 timestampdir, (unsigned long) sb.st_uid,
481                 (unsigned long) timestamp_uid);
482         else if ((sb.st_mode & 0000022))
483             log_error(NO_EXIT,
484                 "%s writable by non-owner (0%o), should be mode 0700",
485                 timestampdir, (unsigned int) sb.st_mode);
486         else {
487             if ((sb.st_mode & 0000777) != 0700)
488                 (void) chmod(timestampdir, 0700);
489             status = TS_OLD;            /* do date check later */
490         }
491     } else if (errno != ENOENT) {
492         log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
493     } else
494         status = TS_MISSING;
495
496     /*
497      * If there is no user ticket dir, AND we are in tty ticket mode,
498      * AND the TS_MAKE_DIRS flag is set, create the user ticket dir.
499      */
500     if (status == TS_MISSING && timestampfile && ISSET(flags, TS_MAKE_DIRS)) {
501         if (mkdir(timestampdir, S_IRWXU) == -1) {
502             status = TS_ERROR;
503             log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
504         }
505     }
506
507     /*
508      * Sanity check the tty ticket file if it exists.
509      */
510     if (timestampfile && status != TS_ERROR) {
511         if (status != TS_MISSING)
512             status = TS_NOFILE;                 /* dir there, file missing */
513         if (lstat(timestampfile, &sb) == 0) {
514             if (!S_ISREG(sb.st_mode)) {
515                 status = TS_ERROR;
516                 log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
517                     timestampfile, (unsigned int) sb.st_mode);
518             } else {
519                 /* If bad uid or file mode, complain and kill the bogus file. */
520                 if (sb.st_uid != timestamp_uid) {
521                     log_error(NO_EXIT,
522                         "%s owned by uid %lu, should be uid %lu",
523                         timestampfile, (unsigned long) sb.st_uid,
524                         (unsigned long) timestamp_uid);
525                     (void) unlink(timestampfile);
526                 } else if ((sb.st_mode & 0000022)) {
527                     log_error(NO_EXIT,
528                         "%s writable by non-owner (0%o), should be mode 0600",
529                         timestampfile, (unsigned int) sb.st_mode);
530                     (void) unlink(timestampfile);
531                 } else {
532                     /* If not mode 0600, fix it. */
533                     if ((sb.st_mode & 0000777) != 0600)
534                         (void) chmod(timestampfile, 0600);
535
536                     status = TS_OLD;    /* actually check mtime below */
537                 }
538             }
539         } else if (errno != ENOENT) {
540             log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
541             status = TS_ERROR;
542         }
543     }
544
545     /*
546      * If the file/dir exists and we are not removing it, check its mtime.
547      */
548     if (status == TS_OLD && !ISSET(flags, TS_REMOVE)) {
549         /* Negative timeouts only expire manually (sudo -k). */
550         if (def_timestamp_timeout < 0 && sb.st_mtime != 0)
551             status = TS_CURRENT;
552         else {
553             /* XXX - should use timespec here */
554             now = time(NULL);
555             if (def_timestamp_timeout &&
556                 now - sb.st_mtime < 60 * def_timestamp_timeout) {
557                 /*
558                  * Check for bogus time on the stampfile.  The clock may
559                  * have been set back or someone could be trying to spoof us.
560                  */
561                 if (sb.st_mtime > now + 60 * def_timestamp_timeout * 2) {
562                     log_error(NO_EXIT,
563                         "timestamp too far in the future: %20.20s",
564                         4 + ctime(&sb.st_mtime));
565                     if (timestampfile)
566                         (void) unlink(timestampfile);
567                     else
568                         (void) rmdir(timestampdir);
569                     status = TS_MISSING;
570                 } else
571                     status = TS_CURRENT;
572             }
573         }
574     }
575
576     if (timestamp_uid != 0)
577         set_perms(PERM_ROOT);
578     return(status);
579 }
580
581 /*
582  * Remove the timestamp ticket file/dir.
583  */
584 void
585 remove_timestamp(remove)
586     int remove;
587 {
588     struct timespec ts;
589     char *timestampdir, *timestampfile, *path;
590     int status;
591
592     build_timestamp(&timestampdir, &timestampfile);
593     status = timestamp_status(timestampdir, timestampfile, user_name,
594         TS_REMOVE);
595     if (status == TS_OLD || status == TS_CURRENT) {
596         path = timestampfile ? timestampfile : timestampdir;
597         if (remove) {
598             if (timestampfile)
599                 status = unlink(timestampfile);
600             else
601                 status = rmdir(timestampdir);
602             if (status == -1 && errno != ENOENT) {
603                 log_error(NO_EXIT, "can't remove %s (%s), will reset to Epoch",
604                     path, strerror(errno));
605                 remove = FALSE;
606             }
607         } else {
608             timespecclear(&ts);
609             if (touch(-1, path, &ts) == -1)
610                 error(1, "can't reset %s to Epoch", path);
611         }
612     }
613
614     efree(timestampdir);
615     efree(timestampfile);
616 }