Imported Upstream version 1.8.7
[debian/sudo] / plugins / sudoers / timestamp.c
1 /*
2  * Copyright (c) 1993-1996,1998-2005, 2007-2013
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/time.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 #endif /* HAVE_STRING_H */
42 #ifdef HAVE_STRINGS_H
43 # include <strings.h>
44 #endif /* HAVE_STRINGS_H */
45 #ifdef HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif /* HAVE_UNISTD_H */
48 #if TIME_WITH_SYS_TIME
49 # include <time.h>
50 #endif
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <signal.h>
54 #include <pwd.h>
55 #include <grp.h>
56
57 #include "sudoers.h"
58 #include "check.h"
59
60 static struct sudo_tty_info tty_info;
61 static char timestampdir[PATH_MAX];
62 static char timestampfile[PATH_MAX];
63
64 /*
65  * Fills in timestampdir as well as timestampfile if using tty tickets.
66  */
67 int
68 build_timestamp(struct passwd *pw)
69 {
70     char *dirparent;
71     struct stat sb;
72     int len;
73     debug_decl(build_timestamp, SUDO_DEBUG_AUTH)
74
75     /* Stash the tty's device, session ID and ctime for ticket comparison. */
76     if (def_tty_tickets && user_ttypath && stat(user_ttypath, &sb) == 0) {
77         tty_info.dev = sb.st_dev;
78         tty_info.ino = sb.st_ino;
79         tty_info.rdev = sb.st_rdev;
80         tty_info.uid = sb.st_uid;
81         tty_info.gid = sb.st_gid;
82         tty_info.sid = user_sid;
83     }
84
85     dirparent = def_timestampdir;
86     timestampfile[0] = '\0';
87     len = snprintf(timestampdir, sizeof(timestampdir), "%s/%s", dirparent,
88         user_name);
89     if (len <= 0 || len >= sizeof(timestampdir))
90         goto bad;
91
92     /*
93      * Timestamp file may be a file in the directory or NUL to use
94      * the directory as the timestamp.
95      */
96     if (def_tty_tickets) {
97         char *p;
98
99         if ((p = strrchr(user_tty, '/')))
100             p++;
101         else
102             p = user_tty;
103         if (def_targetpw)
104             len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s:%s",
105                 dirparent, user_name, p, runas_pw->pw_name);
106         else
107             len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s",
108                 dirparent, user_name, p);
109         if (len <= 0 || len >= sizeof(timestampfile))
110             goto bad;
111     } else if (def_targetpw) {
112         len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s",
113             dirparent, user_name, runas_pw->pw_name);
114         if (len <= 0 || len >= sizeof(timestampfile))
115             goto bad;
116     }
117     sudo_debug_printf(SUDO_DEBUG_INFO, "using timestamp file %s", timestampfile);
118
119     debug_return_int(len);
120 bad:
121     log_fatal(0, N_("timestamp path too long: %s"),
122         *timestampfile ? timestampfile : timestampdir);
123     /* NOTREACHED */
124     debug_return_int(-1);
125 }
126
127 /*
128  * Update the time on the timestamp file/dir or create it if necessary.
129  */
130 bool
131 update_timestamp(struct passwd *pw)
132 {
133     debug_decl(update_timestamp, SUDO_DEBUG_AUTH)
134
135     /* If using tty timestamps but we have no tty there is nothing to do. */
136     if (def_tty_tickets && !user_ttypath)
137         debug_return_bool(false);
138
139     if (timestamp_uid != 0)
140         set_perms(PERM_TIMESTAMP);
141     if (*timestampfile) {
142         /*
143          * Store tty info in timestamp file
144          */
145         int fd = open(timestampfile, O_WRONLY|O_CREAT, 0600);
146         if (fd == -1)
147             log_warning(USE_ERRNO, N_("unable to open %s"), timestampfile);
148         else {
149             lock_file(fd, SUDO_LOCK);
150             if (write(fd, &tty_info, sizeof(tty_info)) != sizeof(tty_info))
151                 log_warning(USE_ERRNO, N_("unable to write to %s"), timestampfile);
152             close(fd);
153         }
154     } else {
155         if (touch(-1, timestampdir, NULL) == -1) {
156             if (mkdir(timestampdir, 0700) == -1) {
157                 log_warning(USE_ERRNO, N_("unable to mkdir %s"),
158                     timestampdir);
159             }
160         }
161     }
162     if (timestamp_uid != 0)
163         restore_perms();
164     debug_return_bool(true);
165 }
166
167 /*
168  * Check the timestamp file and directory and return their status.
169  */
170 static int
171 timestamp_status_internal(bool removing)
172 {
173     struct stat sb;
174     struct timeval boottime, mtime;
175     time_t now;
176     char *dirparent = def_timestampdir;
177     int status = TS_ERROR;              /* assume the worst */
178     debug_decl(timestamp_status_internal, SUDO_DEBUG_AUTH)
179
180     if (timestamp_uid != 0)
181         set_perms(PERM_TIMESTAMP);
182
183     /*
184      * Sanity check dirparent and make it if it doesn't already exist.
185      * We start out assuming the worst (that the dir is not sane) and
186      * if it is ok upgrade the status to ``no timestamp file''.
187      * Note that we don't check the parent(s) of dirparent for
188      * sanity since the sudo dir is often just located in /tmp.
189      */
190     if (lstat(dirparent, &sb) == 0) {
191         if (!S_ISDIR(sb.st_mode))
192             log_warning(0, N_("%s exists but is not a directory (0%o)"),
193                 dirparent, (unsigned int) sb.st_mode);
194         else if (sb.st_uid != timestamp_uid)
195             log_warning(0, N_("%s owned by uid %u, should be uid %u"),
196                 dirparent, (unsigned int) sb.st_uid,
197                 (unsigned int) timestamp_uid);
198         else if ((sb.st_mode & 0000022))
199             log_warning(0,
200                 N_("%s writable by non-owner (0%o), should be mode 0700"),
201                 dirparent, (unsigned int) sb.st_mode);
202         else {
203             if ((sb.st_mode & 0000777) != 0700)
204                 (void) chmod(dirparent, 0700);
205             status = TS_MISSING;
206         }
207     } else if (errno != ENOENT) {
208         log_warning(USE_ERRNO, N_("unable to stat %s"), dirparent);
209     } else {
210         /* No dirparent, try to make one. */
211         if (!removing) {
212             if (mkdir(dirparent, S_IRWXU))
213                 log_warning(USE_ERRNO, N_("unable to mkdir %s"),
214                     dirparent);
215             else
216                 status = TS_MISSING;
217         }
218     }
219     if (status == TS_ERROR)
220         goto done;
221
222     /*
223      * Sanity check the user's ticket dir.  We start by downgrading
224      * the status to TS_ERROR.  If the ticket dir exists and is sane
225      * this will be upgraded to TS_OLD.  If the dir does not exist,
226      * it will be upgraded to TS_MISSING.
227      */
228     status = TS_ERROR;                  /* downgrade status again */
229     if (lstat(timestampdir, &sb) == 0) {
230         if (!S_ISDIR(sb.st_mode)) {
231             if (S_ISREG(sb.st_mode)) {
232                 /* convert from old style */
233                 if (unlink(timestampdir) == 0)
234                     status = TS_MISSING;
235             } else
236                 log_warning(0, N_("%s exists but is not a directory (0%o)"),
237                     timestampdir, (unsigned int) sb.st_mode);
238         } else if (sb.st_uid != timestamp_uid)
239             log_warning(0, N_("%s owned by uid %u, should be uid %u"),
240                 timestampdir, (unsigned int) sb.st_uid,
241                 (unsigned int) timestamp_uid);
242         else if ((sb.st_mode & 0000022))
243             log_warning(0,
244                 N_("%s writable by non-owner (0%o), should be mode 0700"),
245                 timestampdir, (unsigned int) sb.st_mode);
246         else {
247             if ((sb.st_mode & 0000777) != 0700)
248                 (void) chmod(timestampdir, 0700);
249             status = TS_OLD;            /* do date check later */
250         }
251     } else if (errno != ENOENT) {
252         log_warning(USE_ERRNO, N_("unable to stat %s"), timestampdir);
253     } else
254         status = TS_MISSING;
255
256     /*
257      * If there is no user ticket dir, AND we are in tty ticket mode,
258      * AND we are not just going to remove it, create the user ticket dir.
259      */
260     if (status == TS_MISSING && *timestampfile && !removing) {
261         if (mkdir(timestampdir, S_IRWXU) == -1) {
262             status = TS_ERROR;
263             log_warning(USE_ERRNO, N_("unable to mkdir %s"), timestampdir);
264         }
265     }
266
267     /*
268      * Sanity check the tty ticket file if it exists.
269      */
270     if (*timestampfile && status != TS_ERROR) {
271         if (status != TS_MISSING)
272             status = TS_NOFILE;                 /* dir there, file missing */
273         if (def_tty_tickets && !user_ttypath)
274             goto done;                          /* no tty, always prompt */
275         if (lstat(timestampfile, &sb) == 0) {
276             if (!S_ISREG(sb.st_mode)) {
277                 status = TS_ERROR;
278                 log_warning(0, N_("%s exists but is not a regular file (0%o)"),
279                     timestampfile, (unsigned int) sb.st_mode);
280             } else {
281                 /* If bad uid or file mode, complain and kill the bogus file. */
282                 if (sb.st_uid != timestamp_uid) {
283                     log_warning(0,
284                         N_("%s owned by uid %u, should be uid %u"),
285                         timestampfile, (unsigned int) sb.st_uid,
286                         (unsigned int) timestamp_uid);
287                     (void) unlink(timestampfile);
288                 } else if ((sb.st_mode & 0000022)) {
289                     log_warning(0,
290                         N_("%s writable by non-owner (0%o), should be mode 0600"),
291                         timestampfile, (unsigned int) sb.st_mode);
292                     (void) unlink(timestampfile);
293                 } else {
294                     /* If not mode 0600, fix it. */
295                     if ((sb.st_mode & 0000777) != 0600)
296                         (void) chmod(timestampfile, 0600);
297
298                     /*
299                      * Check for stored tty info.  If the file is zero-sized
300                      * it is an old-style timestamp with no tty info in it.
301                      * If removing, we don't care about the contents.
302                      * The actual mtime check is done later.
303                      */
304                     if (removing) {
305                         status = TS_OLD;
306                     } else if (sb.st_size != 0) {
307                         struct sudo_tty_info info;
308                         int fd = open(timestampfile, O_RDONLY, 0644);
309                         if (fd != -1) {
310                             if (read(fd, &info, sizeof(info)) == sizeof(info) &&
311                                 memcmp(&info, &tty_info, sizeof(info)) == 0) {
312                                 status = TS_OLD;
313                             }
314                             close(fd);
315                         }
316                     }
317                 }
318             }
319         } else if (errno != ENOENT) {
320             log_warning(USE_ERRNO, N_("unable to stat %s"), timestampfile);
321             status = TS_ERROR;
322         }
323     }
324
325     /*
326      * If the file/dir exists and we are not removing it, check its mtime.
327      */
328     if (status == TS_OLD && !removing) {
329         mtim_get(&sb, &mtime);
330         if (timevalisset(&mtime)) {
331             /* Negative timeouts only expire manually (sudo -k). */
332             if (def_timestamp_timeout < 0) {
333                 status = TS_CURRENT;
334             } else {
335                 time(&now);
336                 if (def_timestamp_timeout &&
337                     now - mtime.tv_sec < 60 * def_timestamp_timeout) {
338                     /*
339                      * Check for bogus time on the stampfile.  The clock may
340                      * have been set back or user could be trying to spoof us.
341                      */
342                     if (mtime.tv_sec > now + 60 * def_timestamp_timeout * 2) {
343                         time_t tv_sec = (time_t)mtime.tv_sec;
344                         log_warning(0,
345                             N_("timestamp too far in the future: %20.20s"),
346                             4 + ctime(&tv_sec));
347                         if (*timestampfile)
348                             (void) unlink(timestampfile);
349                         else
350                             (void) rmdir(timestampdir);
351                         status = TS_MISSING;
352                     } else if (get_boottime(&boottime) &&
353                         timevalcmp(&mtime, &boottime, <)) {
354                         status = TS_OLD;
355                     } else {
356                         status = TS_CURRENT;
357                     }
358                 }
359             }
360         }
361     }
362
363 done:
364     if (timestamp_uid != 0)
365         restore_perms();
366     debug_return_int(status);
367 }
368
369 int
370 timestamp_status(struct passwd *pw)
371 {
372     return timestamp_status_internal(false);
373 }
374
375 /*
376  * Remove the timestamp ticket file/dir.
377  */
378 void
379 remove_timestamp(bool remove)
380 {
381     struct timeval tv;
382     char *path;
383     int status;
384     debug_decl(remove_timestamp, SUDO_DEBUG_AUTH)
385
386     if (build_timestamp(NULL) == -1)
387         debug_return;
388
389     status = timestamp_status_internal(true);
390     if (status != TS_MISSING && status != TS_ERROR) {
391         path = *timestampfile ? timestampfile : timestampdir;
392         if (remove) {
393             if (*timestampfile)
394                 status = unlink(timestampfile);
395             else
396                 status = rmdir(timestampdir);
397             if (status == -1 && errno != ENOENT) {
398                 log_warning(0,
399                     N_("unable to remove %s, will reset to the epoch"), path);
400                 remove = false;
401             }
402         }
403         if (!remove) {
404             timevalclear(&tv);
405             if (touch(-1, path, &tv) == -1 && errno != ENOENT)
406                 fatal(_("unable to reset %s to the epoch"), path);
407         }
408     }
409
410     debug_return;
411 }
412
413 /*
414  * Lecture status is currently implied by the timestamp status but
415  * may be stored separately in a future release.
416  */
417 bool
418 set_lectured(void)
419 {
420     return true;
421 }