Imported Upstream version 1.6.6
[debian/sudo] / check.c
1 /*
2  * Copyright (c) 1993-1996,1998-2001 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 <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/stat.h>
40 #include <sys/file.h>
41 #include <stdio.h>
42 #ifdef STDC_HEADERS
43 # include <stdlib.h>
44 # include <stddef.h>
45 #else
46 # ifdef HAVE_STDLIB_H
47 #  include <stdlib.h>
48 # endif
49 #endif /* STDC_HEADERS */
50 #ifdef HAVE_STRING_H
51 # include <string.h>
52 #else
53 # ifdef HAVE_STRINGS_H
54 #  include <strings.h>
55 # endif
56 #endif /* HAVE_STRING_H */
57 #ifdef HAVE_UNISTD_H
58 # include <unistd.h>
59 #endif /* HAVE_UNISTD_H */
60 #include <errno.h>
61 #include <fcntl.h>
62 #include <signal.h>
63 #include <time.h>
64 #include <pwd.h>
65 #include <grp.h>
66
67 #include "sudo.h"
68
69 #ifndef lint
70 static const char rcsid[] = "$Sudo: check.c,v 1.203 2002/04/25 15:30:12 millert Exp $";
71 #endif /* lint */
72
73 /* Status codes for timestamp_status() */
74 #define TS_CURRENT              0
75 #define TS_OLD                  1
76 #define TS_MISSING              2
77 #define TS_NOFILE               3
78 #define TS_ERROR                4
79
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 *));
85
86 /*
87  * This function only returns if the user can successfully
88  * verify who he/she is.  
89  */
90 void
91 check_user()
92 {
93     char *timestampdir = NULL;
94     char *timestampfile = NULL;
95     char *prompt;
96     int status;
97
98     if (user_uid == 0 || user_is_exempt())
99         return;
100
101     build_timestamp(&timestampdir, &timestampfile);
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 */
106
107         /* Expand any escapes in the prompt. */
108         prompt = expand_prompt(user_prompt ? user_prompt : def_str(I_PASSPROMPT),
109             user_name, user_shost);
110
111         verify_user(auth_pw, prompt);
112     }
113     if (status != TS_ERROR)
114         update_timestamp(timestampdir, timestampfile);
115     free(timestampdir);
116     if (timestampfile)
117         free(timestampfile);
118 }
119
120 /*
121  * Standard sudo lecture.
122  * TODO: allow the user to specify a file name instead.
123  */
124 static void
125 lecture()
126 {
127
128     if (def_flag(I_LECTURE)) {
129         (void) fputs("\n\
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\
132 \n\
133         #1) Respect the privacy of others.\n\
134         #2) Think before you type.\n\n",
135         stderr);
136     }
137 }
138
139 /*
140  * Update the time on the timestamp file/dir or create it if necessary.
141  */
142 static void
143 update_timestamp(timestampdir, timestampfile)
144     char *timestampdir;
145     char *timestampfile;
146 {
147
148     if (touch(timestampfile ? timestampfile : timestampdir, time(NULL)) == -1) {
149         if (timestampfile) {
150             int fd = open(timestampfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
151
152             if (fd == -1)
153                 log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
154             else
155                 close(fd);
156         } else {
157             if (mkdir(timestampdir, 0700) == -1)
158                 log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
159         }
160     }
161 }
162
163 /*
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.
166  */
167 static char *
168 expand_prompt(old_prompt, user, host)
169     char *old_prompt;
170     char *user;
171     char *host;
172 {
173     size_t len;
174     int subst;
175     char *p, *np, *new_prompt, lastchar;
176
177     /* How much space do we need to malloc for the prompt? */
178     subst = 0;
179     for (p = old_prompt, len = strlen(old_prompt), lastchar = '\0'; *p; p++) {
180         if (lastchar == '%') {
181             if (*p == 'h') {
182                 len += strlen(user_shost) - 2;
183                 subst = 1;
184             } else if (*p == 'u') {
185                 len += strlen(user_name) - 2;
186                 subst = 1;
187             }
188         }
189
190         if (lastchar == '%' && *p == '%') {
191             lastchar = '\0';
192             len--;
193         } else
194             lastchar = *p;
195     }
196
197     if (subst) {
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 */
202                 if (*p == 'h') {
203                     np--;
204                     strcpy(np, user_shost);
205                     np += strlen(user_shost);
206                 } else if (*p == 'u') {
207                     np--;
208                     strcpy(np, user_name);
209                     np += strlen(user_name);
210                 }
211             } else
212                 *np++ = *p;
213
214             if (lastchar == '%' && *p == '%')
215                 lastchar = '\0';
216             else
217                 lastchar = *p;
218         }
219         *np = '\0';
220     } else
221         new_prompt = old_prompt;
222
223     return(new_prompt);
224 }
225
226 /*
227  * Checks if the user is exempt from supplying a password.
228  */
229 int
230 user_is_exempt()
231 {
232     struct group *grp;
233     char **gr_mem;
234
235     if (!def_str(I_EXEMPT_GROUP))
236         return(FALSE);
237
238     if (!(grp = getgrnam(def_str(I_EXEMPT_GROUP))))
239         return(FALSE);
240
241     if (user_gid == grp->gr_gid)
242         return(TRUE);
243
244     for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) {
245         if (strcmp(user_name, *gr_mem) == 0)
246             return(TRUE);
247     }
248
249     return(FALSE);
250 }
251
252 /*
253  * Fills in timestampdir as well as timestampfile if using tty tickets.
254  */
255 static void
256 build_timestamp(timestampdir, timestampfile)
257     char **timestampdir;
258     char **timestampfile;
259 {
260     char *dirparent;
261     int len;
262
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);
267
268     /*
269      * Timestamp file may be a file in the directory or NUL to use
270      * the directory as the timestamp.
271      */
272     if (def_flag(I_TTY_TICKETS)) {
273         char *p;
274
275         if ((p = strrchr(user_tty, '/')))
276             p++;
277         else
278             p = user_tty;
279         if (def_flag(I_TARGETPW))
280             len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name,
281                 p, *user_runas);
282         else
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,
288             *user_runas);
289         if (len >= MAXPATHLEN)
290             log_error(0, "timestamp path too long: %s", timestampfile);
291     } else
292         *timestampfile = NULL;
293 }
294
295 /*
296  * Check the timestamp file and directory and return their status.
297  */
298 static int
299 timestamp_status(timestampdir, timestampfile, user, make_dirs)
300     char *timestampdir;
301     char *timestampfile;
302     char *user;
303     int make_dirs;
304 {
305     struct stat sb;
306     time_t now;
307     char *dirparent = def_str(I_TIMESTAMPDIR);
308     int status = TS_ERROR;              /* assume the worst */
309
310     /*
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.
316      */
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))
325             log_error(NO_EXIT,
326                 "%s writable by non-owner (0%o), should be mode 0700",
327                 dirparent, sb.st_mode);
328         else {
329             if ((sb.st_mode & 0000777) != 0700)
330                 (void) chmod(dirparent, 0700);
331             status = TS_MISSING;
332         }
333     } else if (errno != ENOENT) {
334         log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
335     } else {
336         /* No dirparent, try to make one. */
337         if (make_dirs) {
338             if (mkdir(dirparent, S_IRWXU))
339                 log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
340                     dirparent);
341             else
342                 status = TS_MISSING;
343         }
344     }
345     if (status == TS_ERROR)
346         return(status);
347
348     /*
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.
353      */
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)
360                     status = TS_MISSING;
361             } else
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))
368             log_error(NO_EXIT,
369                 "%s writable by non-owner (0%o), should be mode 0700",
370                 timestampdir, sb.st_mode);
371         else {
372             if ((sb.st_mode & 0000777) != 0700)
373                 (void) chmod(timestampdir, 0700);
374             status = TS_OLD;            /* do date check later */
375         }
376     } else if (errno != ENOENT) {
377         log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
378     } else
379         status = TS_MISSING;
380
381     /*
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.
384      */
385     if (status == TS_MISSING && timestampfile && make_dirs) {
386         if (mkdir(timestampdir, S_IRWXU) == -1) {
387             status = TS_ERROR;
388             log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
389         }
390     }
391
392     /*
393      * Sanity check the tty ticket file if it exists.
394      */
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)) {
400                 status = TS_ERROR;
401                 log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
402                     timestampfile, sb.st_mode);
403             } else {
404                 /* If bad uid or file mode, complain and kill the bogus file. */
405                 if (sb.st_uid != 0) {
406                     log_error(NO_EXIT,
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)) {
411                     log_error(NO_EXIT,
412                         "%s writable by non-owner (0%o), should be mode 0600",
413                         timestampfile, sb.st_mode);
414                     (void) unlink(timestampfile);
415                 } else {
416                     /* If not mode 0600, fix it. */
417                     if ((sb.st_mode & 0000777) != 0600)
418                         (void) chmod(timestampfile, 0600);
419
420                     status = TS_OLD;    /* actually check mtime below */
421                 }
422             }
423         } else if (errno != ENOENT) {
424             log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
425             status = TS_ERROR;
426         }
427     }
428
429     /*
430      * If the file/dir exists, check its mtime.
431      */
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)
435             status = TS_CURRENT;
436         else {
437             now = time(NULL);
438             if (def_ival(I_TIMESTAMP_TIMEOUT) && 
439                 now - sb.st_mtime < 60 * def_ival(I_TIMESTAMP_TIMEOUT)) {
440                 /*
441                  * Check for bogus time on the stampfile.  The clock may
442                  * have been set back or someone could be trying to spoof us.
443                  */
444                 if (sb.st_mtime > now + 60 * def_ival(I_TIMESTAMP_TIMEOUT) * 2) {
445                     log_error(NO_EXIT,
446                         "timestamp too far in the future: %20.20s",
447                         4 + ctime(&sb.st_mtime));
448                     if (timestampfile)
449                         (void) unlink(timestampfile);
450                     else
451                         (void) rmdir(timestampdir);
452                     status = TS_MISSING;
453                 } else
454                     status = TS_CURRENT;
455             }
456         }
457     }
458
459     return(status);
460 }
461
462 /*
463  * Remove the timestamp ticket file/dir.
464  */
465 void
466 remove_timestamp(remove)
467     int remove;
468 {
469     char *timestampdir;
470     char *timestampfile;
471     char *ts;
472     int status;
473
474     build_timestamp(&timestampdir, &timestampfile);
475     status = timestamp_status(timestampdir, timestampfile, user_name, FALSE);
476     if (status == TS_OLD || status == TS_CURRENT) {
477         ts = timestampfile ? timestampfile : timestampdir;
478         if (remove) {
479             if (timestampfile)
480                 status = unlink(timestampfile);
481             else
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));
486                 remove = FALSE;
487             }
488         }
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));
492         }
493     }
494
495     free(timestampdir);
496     if (timestampfile)
497         free(timestampfile);
498 }