Annotation of embedaddon/sudo/plugins/sudoers/timestamp.c, revision 1.1.1.1
1.1 misho 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: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>