1: /*
2: * Copyright (c) 2014 Todd C. Miller <Todd.Miller@courtesan.com>
3: *
4: * Permission to use, copy, modify, and distribute this software for any
5: * purpose with or without fee is hereby granted, provided that the above
6: * copyright notice and this permission notice appear in all copies.
7: *
8: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15: */
16:
17: #include <config.h>
18:
19: #include <sys/types.h>
20: #include <sys/time.h>
21: #include <sys/stat.h>
22: #include <stdio.h>
23: #ifdef STDC_HEADERS
24: # include <stdlib.h>
25: # include <stddef.h>
26: #else
27: # ifdef HAVE_STDLIB_H
28: # include <stdlib.h>
29: # endif
30: #endif /* STDC_HEADERS */
31: #ifdef HAVE_STRING_H
32: # include <string.h>
33: #endif /* HAVE_STRING_H */
34: #ifdef HAVE_STRINGS_H
35: # include <strings.h>
36: #endif /* HAVE_STRINGS_H */
37: #ifdef HAVE_UNISTD_H
38: # include <unistd.h>
39: #endif /* HAVE_UNISTD_H */
40: #ifdef TIME_WITH_SYS_TIME
41: # include <time.h>
42: #endif
43: #ifndef HAVE_STRUCT_TIMESPEC
44: # include "compat/timespec.h"
45: #endif
46: #include <errno.h>
47: #include <fcntl.h>
48: #include <pwd.h>
49: #include <grp.h>
50:
51: #include "sudoers.h"
52: #include "secure_path.h"
53: #include "check.h"
54:
55: /* On Linux, CLOCK_MONOTONIC does not run while suspended. */
56: #if defined(CLOCK_BOOTTIME)
57: # define SUDO_CLOCK_MONOTONIC CLOCK_BOOTTIME
58: #elif defined(CLOCK_MONOTONIC)
59: # define SUDO_CLOCK_MONOTONIC CLOCK_MONOTONIC
60: #else
61: # define SUDO_CLOCK_MONOTONIC CLOCK_REALTIME
62: #endif
63:
64: static char timestamp_file[PATH_MAX];
65: static off_t timestamp_hint = (off_t)-1;
66: static struct timestamp_entry timestamp_key;
67:
68: /*
69: * Returns true if entry matches key, else false.
70: */
71: static bool
72: ts_match_record(struct timestamp_entry *key, struct timestamp_entry *entry)
73: {
74: debug_decl(ts_match_record, SUDO_DEBUG_AUTH)
75:
76: if (entry->version != key->version)
77: debug_return_bool(false);
78: if (!ISSET(key->flags, TS_ANYUID) && entry->auth_uid != key->auth_uid)
79: debug_return_bool(false);
80: if (entry->type != key->type)
81: debug_return_bool(false);
82: switch (entry->type) {
83: case TS_GLOBAL:
84: /* no ppid or tty to match */
85: break;
86: case TS_PPID:
87: /* verify parent pid */
88: if (entry->u.ppid != key->u.ppid)
89: debug_return_bool(false);
90: break;
91: case TS_TTY:
92: if (entry->u.ttydev != key->u.ttydev)
93: debug_return_bool(false);
94: break;
95: default:
96: /* unknown record type, ignore it */
97: debug_return_bool(false);
98: }
99: debug_return_bool(true);
100: }
101:
102: /*
103: * Searches the time stamp file descriptor for a record that matches key.
104: * On success, fills in entry with the matching record and returns true.
105: * On failure, returns false.
106: *
107: * Note that records are searched starting at the current file offset,
108: * which may not be the beginning of the file.
109: */
110: static bool
111: ts_find_record(int fd, struct timestamp_entry *key, struct timestamp_entry *entry)
112: {
113: struct timestamp_entry cur;
114: debug_decl(ts_find_record, SUDO_DEBUG_AUTH)
115:
116: /*
117: * Look for a matching record.
118: * We don't match on the sid or actual time stamp.
119: */
120: while (read(fd, &cur, sizeof(cur)) == sizeof(cur)) {
121: if (cur.size != sizeof(cur)) {
122: /* wrong size, seek to start of next record */
123: sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
124: "wrong sized record, got %hu, expected %zu",
125: cur.size, sizeof(cur));
126: lseek(fd, (off_t)cur.size - (off_t)sizeof(cur), SEEK_CUR);
127: if (cur.size == 0)
128: break; /* size must be non-zero */
129: continue;
130: }
131: if (ts_match_record(key, &cur)) {
132: memcpy(entry, &cur, sizeof(struct timestamp_entry));
133: debug_return_bool(true);
134: }
135: }
136: debug_return_bool(false);
137: }
138:
139: /*
140: * Find matching record to update or append a new one.
141: * Returns true if the entry was written successfully, else false.
142: */
143: static bool
144: ts_update_record(int fd, struct timestamp_entry *entry, off_t timestamp_hint)
145: {
146: struct timestamp_entry cur;
147: ssize_t nwritten;
148: off_t old_eof = (off_t)-1;
149: debug_decl(ts_update_record, SUDO_DEBUG_AUTH)
150:
151: /* First try the hint if one is given. */
152: if (timestamp_hint != (off_t)-1) {
153: if (lseek(fd, timestamp_hint, SEEK_SET) != -1) {
154: if (read(fd, &cur, sizeof(cur)) == sizeof(cur)) {
155: if (ts_match_record(entry, &cur)) {
156: sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
157: "found existing time stamp record using hint");
158: goto found_it;
159: }
160: }
161: }
162: }
163:
164: /* Search for matching record. */
165: sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
166: "searching for time stamp record");
167: lseek(fd, (off_t)0, SEEK_SET);
168: if (ts_find_record(fd, entry, &cur)) {
169: sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
170: "found existing time stamp record");
171: found_it:
172: /* back up over old record */
173: lseek(fd, (off_t)0 - (off_t)cur.size, SEEK_CUR);
174: } else {
175: sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
176: "appending new time stamp record");
177: old_eof = lseek(fd, (off_t)0, SEEK_CUR);
178: }
179:
180: /* Overwrite existing record or append to end. */
181: nwritten = write(fd, entry, sizeof(struct timestamp_entry));
182: if ((size_t)nwritten == sizeof(struct timestamp_entry))
183: debug_return_bool(true);
184:
185: log_warning(nwritten == -1 ? USE_ERRNO : 0,
186: N_("unable to write to %s"), timestamp_file);
187:
188: /* Truncate on partial write to be safe. */
189: if (nwritten > 0 && old_eof != (off_t)-1) {
190: sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
191: "short write, truncating partial time stamp record");
192: if (ftruncate(fd, old_eof) != 0) {
193: warning(U_("unable to truncate time stamp file to %lld bytes"),
194: (long long)old_eof);
195: }
196: }
197:
198: debug_return_bool(false);
199: }
200:
201: /*
202: * Create a directory and any missing parent directories with the
203: * specified mode.
204: * Returns true on success.
205: * Returns false on failure and displays a warning to stderr.
206: */
207: static bool
208: ts_mkdirs(char *path, uid_t owner, mode_t mode, mode_t parent_mode, bool quiet)
209: {
210: struct stat sb;
211: gid_t parent_gid = 0;
212: char *slash = path;
213: bool rval = false;
214: debug_decl(ts_mkdirs, SUDO_DEBUG_AUTH)
215:
216: while ((slash = strchr(slash + 1, '/')) != NULL) {
217: *slash = '\0';
218: if (stat(path, &sb) != 0) {
219: sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
220: "mkdir %s, mode 0%o", path, parent_mode);
221: if (mkdir(path, parent_mode) != 0) {
222: if (!quiet)
223: warning(U_("unable to mkdir %s"), path);
224: goto done;
225: }
226: ignore_result(chown(path, (uid_t)-1, parent_gid));
227: } else if (!S_ISDIR(sb.st_mode)) {
228: if (!quiet) {
229: warningx(U_("%s exists but is not a directory (0%o)"),
230: path, (unsigned int) sb.st_mode);
231: }
232: goto done;
233: } else {
234: /* Inherit gid of parent dir for ownership. */
235: parent_gid = sb.st_gid;
236: }
237: *slash = '/';
238: }
239: /* Create final path component. */
240: sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
241: "mkdir %s, mode 0%o", path, mode);
242: if (mkdir(path, mode) != 0 && errno != EEXIST) {
243: if (!quiet)
244: warning(U_("unable to mkdir %s"), path);
245: goto done;
246: }
247: ignore_result(chown(path, owner, parent_gid));
248: rval = true;
249: done:
250: debug_return_bool(rval);
251: }
252:
253: /*
254: * Check that path is owned by timestamp_uid and not writable by
255: * group or other. If path is missing and make_it is true, create
256: * the directory and its parent dirs.
257: * Returns true on success or false on failure, setting errno.
258: */
259: static bool
260: ts_secure_dir(char *path, bool make_it, bool quiet)
261: {
262: struct stat sb;
263: bool rval = false;
264: debug_decl(ts_secure_dir, SUDO_DEBUG_AUTH)
265:
266: sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "checking %s", path);
267: switch (sudo_secure_dir(path, timestamp_uid, -1, &sb)) {
268: case SUDO_PATH_SECURE:
269: rval = true;
270: break;
271: case SUDO_PATH_MISSING:
272: if (make_it && ts_mkdirs(path, timestamp_uid, 0700, 0711, quiet)) {
273: rval = true;
274: break;
275: }
276: errno = ENOENT;
277: break;
278: case SUDO_PATH_BAD_TYPE:
279: errno = ENOTDIR;
280: if (!quiet)
281: warning("%s", path);
282: break;
283: case SUDO_PATH_WRONG_OWNER:
284: if (!quiet) {
285: warningx(U_("%s is owned by uid %u, should be %u"),
286: path, (unsigned int) sb.st_uid,
287: (unsigned int) timestamp_uid);
288: }
289: errno = EACCES;
290: break;
291: case SUDO_PATH_GROUP_WRITABLE:
292: if (!quiet)
293: warningx(U_("%s is group writable"), path);
294: errno = EACCES;
295: break;
296: }
297: debug_return_bool(rval);
298: }
299:
300: /*
301: * Fills in the timestamp_file[] global variable.
302: * Returns the length of timestamp_file.
303: */
304: int
305: build_timestamp(struct passwd *pw)
306: {
307: int len;
308: debug_decl(build_timestamp, SUDO_DEBUG_AUTH)
309:
310: len = snprintf(timestamp_file, sizeof(timestamp_file), "%s/%s",
311: def_timestampdir, user_name);
312: if (len <= 0 || (size_t)len >= sizeof(timestamp_file)) {
313: log_fatal(0, N_("timestamp path too long: %s/%s"),
314: def_timestampdir, user_name);
315: }
316:
317: debug_return_int(len);
318: }
319:
320: /*
321: * Update the time on the timestamp file/dir or create it if necessary.
322: * Returns true on success or false on failure.
323: */
324: bool
325: update_timestamp(struct passwd *pw)
326: {
327: struct timestamp_entry entry;
328: bool rval = false;
329: int fd;
330: debug_decl(update_timestamp, SUDO_DEBUG_AUTH)
331:
332: /* Zero timeout means don't update the time stamp file. */
333: if (def_timestamp_timeout == 0)
334: goto done;
335:
336: /* Check/create parent directories as needed. */
337: if (!ts_secure_dir(def_timestampdir, true, false))
338: goto done;
339:
340: /* Fill in time stamp. */
341: memcpy(&entry, ×tamp_key, sizeof(struct timestamp_entry));
342: clock_gettime(SUDO_CLOCK_MONOTONIC, &entry.ts);
343:
344: /* Open time stamp file and lock it for exclusive access. */
345: if (timestamp_uid != 0)
346: set_perms(PERM_TIMESTAMP);
347: fd = open(timestamp_file, O_RDWR|O_CREAT, 0600);
348: if (timestamp_uid != 0)
349: restore_perms();
350: if (fd == -1) {
351: log_warning(USE_ERRNO, N_("unable to open %s"), timestamp_file);
352: goto done;
353: }
354:
355: /* Update record or append a new one. */
356: lock_file(fd, SUDO_LOCK);
357: ts_update_record(fd, &entry, timestamp_hint);
358: close(fd);
359:
360: rval = true;
361:
362: done:
363: debug_return_bool(rval);
364: }
365:
366: /*
367: * Check the timestamp file and directory and return their status.
368: * Returns one of TS_CURRENT, TS_OLD, TS_MISSING, TS_NOFILE, TS_ERROR.
369: */
370: int
371: timestamp_status(struct passwd *pw)
372: {
373: struct timestamp_entry entry;
374: struct timespec diff, timeout;
375: int status = TS_ERROR; /* assume the worst */
376: struct stat sb;
377: int fd = -1;
378: debug_decl(timestamp_status, SUDO_DEBUG_AUTH)
379:
380: /* Reset time stamp offset hint. */
381: timestamp_hint = (off_t)-1;
382:
383: /* Zero timeout means ignore time stamp files. */
384: if (def_timestamp_timeout == 0) {
385: status = TS_OLD; /* XXX - could also be TS_MISSING */
386: goto done;
387: }
388:
389: /* Ignore time stamp files in an insecure directory. */
390: if (!ts_secure_dir(def_timestampdir, false, false)) {
391: if (errno != ENOENT) {
392: status = TS_ERROR;
393: goto done;
394: }
395: status = TS_MISSING; /* not insecure, just missing */
396: }
397:
398: /*
399: * Create a key used for matching entries in the time stamp file.
400: * The actual time stamp in the key is used below as the time "now".
401: */
402: memset(×tamp_key, 0, sizeof(timestamp_key));
403: timestamp_key.version = TS_VERSION;
404: timestamp_key.size = sizeof(timestamp_key);
405: timestamp_key.type = TS_GLOBAL; /* may be overriden below */
406: if (pw != NULL) {
407: timestamp_key.auth_uid = pw->pw_uid;
408: } else {
409: timestamp_key.flags = TS_ANYUID;
410: }
411: timestamp_key.sid = user_sid;
412: if (def_tty_tickets) {
413: if (user_ttypath != NULL && stat(user_ttypath, &sb) == 0) {
414: /* tty-based time stamp */
415: timestamp_key.type = TS_TTY;
416: timestamp_key.u.ttydev = sb.st_rdev;
417: } else {
418: /* ppid-based time stamp */
419: timestamp_key.type = TS_PPID;
420: timestamp_key.u.ppid = getppid();
421: }
422: }
423: clock_gettime(SUDO_CLOCK_MONOTONIC, ×tamp_key.ts);
424:
425: /* If the time stamp dir is missing there is nothing to do. */
426: if (status == TS_MISSING)
427: goto done;
428:
429: /* Open time stamp file and lock it for exclusive access. */
430: if (timestamp_uid != 0)
431: set_perms(PERM_TIMESTAMP);
432: fd = open(timestamp_file, O_RDWR);
433: if (timestamp_uid != 0)
434: restore_perms();
435: if (fd == -1) {
436: status = TS_MISSING;
437: goto done;
438: }
439: lock_file(fd, SUDO_LOCK);
440:
441: /* Ignore and clear time stamp file if mtime predates boot time. */
442: if (fstat(fd, &sb) == 0) {
443: struct timeval boottime, mtime;
444:
445: mtim_get(&sb, &mtime);
446: if (get_boottime(&boottime) && sudo_timevalcmp(&mtime, &boottime, <)) {
447: ignore_result(ftruncate(fd, (off_t)0));
448: status = TS_MISSING;
449: goto done;
450: }
451: }
452:
453: /* Read existing record, if any. */
454: if (!ts_find_record(fd, ×tamp_key, &entry)) {
455: status = TS_MISSING;
456: goto done;
457: }
458:
459: /* Set record position hint for use by update_timestamp() */
460: timestamp_hint = lseek(fd, (off_t)0, SEEK_CUR);
461: if (timestamp_hint != (off_t)-1)
462: timestamp_hint -= entry.size;
463:
464: if (ISSET(entry.flags, TS_DISABLED)) {
465: status = TS_OLD; /* disabled via sudo -k */
466: goto done;
467: }
468:
469: if (entry.type != TS_GLOBAL && entry.sid != timestamp_key.sid) {
470: status = TS_OLD; /* belongs to different session */
471: goto done;
472: }
473:
474: /* Negative timeouts only expire manually (sudo -k). */
475: if (def_timestamp_timeout < 0) {
476: status = TS_CURRENT;
477: goto done;
478: }
479:
480: /* Compare stored time stamp with current time. */
481: sudo_timespecsub(×tamp_key.ts, &entry.ts, &diff);
482: timeout.tv_sec = 60 * def_timestamp_timeout;
483: timeout.tv_nsec = ((60.0 * def_timestamp_timeout) - (double)timeout.tv_sec)
484: * 1000000000.0;
485: if (sudo_timespeccmp(&diff, &timeout, <)) {
486: status = TS_CURRENT;
487: #ifdef CLOCK_MONOTONIC
488: /* A monotonic clock should never run backwards. */
489: if (diff.tv_sec < 0) {
490: log_warning(0, N_("ignoring time stamp from the future"));
491: status = TS_OLD;
492: SET(entry.flags, TS_DISABLED);
493: ts_update_record(fd, &entry, timestamp_hint);
494: }
495: #else
496: /* Check for bogus (future) time in the stampfile. */
497: sudo_timespecsub(&entry.ts, ×tamp_key.ts, &diff);
498: timeout.tv_sec *= 2;
499: if (sudo_timespeccmp(&diff, &timeout, >)) {
500: time_t tv_sec = (time_t)entry.ts.tv_sec;
501: log_warning(0,
502: N_("time stamp too far in the future: %20.20s"),
503: 4 + ctime(&tv_sec));
504: status = TS_OLD;
505: SET(entry.flags, TS_DISABLED);
506: ts_update_record(fd, &entry, timestamp_hint);
507: }
508: #endif /* CLOCK_MONOTONIC */
509: } else {
510: status = TS_OLD;
511: }
512:
513: done:
514: if (fd != -1)
515: close(fd);
516: debug_return_int(status);
517: }
518:
519: /*
520: * Remove the timestamp entry or file if unlink_it is set.
521: */
522: void
523: remove_timestamp(bool unlink_it)
524: {
525: struct timestamp_entry entry;
526: int fd = -1;
527: debug_decl(remove_timestamp, SUDO_DEBUG_AUTH)
528:
529: if (build_timestamp(NULL) == -1)
530: debug_return;
531:
532: /* For "sudo -K" simply unlink the time stamp file. */
533: if (unlink_it) {
534: (void) unlink(timestamp_file);
535: debug_return;
536: }
537:
538: /*
539: * Create a key used for matching entries in the time stamp file.
540: */
541: memset(×tamp_key, 0, sizeof(timestamp_key));
542: timestamp_key.version = TS_VERSION;
543: timestamp_key.size = sizeof(timestamp_key);
544: timestamp_key.type = TS_GLOBAL; /* may be overriden below */
545: timestamp_key.flags = TS_ANYUID;
546: if (def_tty_tickets) {
547: struct stat sb;
548: if (user_ttypath != NULL && stat(user_ttypath, &sb) == 0) {
549: /* tty-based time stamp */
550: timestamp_key.type = TS_TTY;
551: timestamp_key.u.ttydev = sb.st_rdev;
552: } else {
553: /* ppid-based time stamp */
554: timestamp_key.type = TS_PPID;
555: timestamp_key.u.ppid = getppid();
556: }
557: }
558:
559: /* Open time stamp file and lock it for exclusive access. */
560: if (timestamp_uid != 0)
561: set_perms(PERM_TIMESTAMP);
562: fd = open(timestamp_file, O_RDWR);
563: if (timestamp_uid != 0)
564: restore_perms();
565: if (fd == -1)
566: goto done;
567: lock_file(fd, SUDO_LOCK);
568:
569: /*
570: * Find matching entries and invalidate them.
571: */
572: while (ts_find_record(fd, ×tamp_key, &entry)) {
573: /* Set record position hint for use by update_timestamp() */
574: timestamp_hint = lseek(fd, (off_t)0, SEEK_CUR);
575: if (timestamp_hint != (off_t)-1)
576: timestamp_hint -= (off_t)entry.size;
577: /* Disable the entry. */
578: SET(entry.flags, TS_DISABLED);
579: ts_update_record(fd, &entry, timestamp_hint);
580: }
581: close(fd);
582:
583: done:
584: debug_return;
585: }
586:
587: /*
588: * Returns true if the user has already been lectured.
589: */
590: bool
591: already_lectured(int unused)
592: {
593: char status_file[PATH_MAX];
594: struct stat sb;
595: int len;
596: debug_decl(already_lectured, SUDO_DEBUG_AUTH)
597:
598: if (ts_secure_dir(def_lecture_status_dir, false, true)) {
599: len = snprintf(status_file, sizeof(status_file), "%s/%s",
600: def_lecture_status_dir, user_name);
601: if (len <= 0 || (size_t)len >= sizeof(status_file)) {
602: log_fatal(0, N_("lecture status path too long: %s/%s"),
603: def_lecture_status_dir, user_name);
604: }
605: debug_return_bool(stat(status_file, &sb) == 0);
606: }
607: debug_return_bool(false);
608: }
609:
610: /*
611: * Create the lecture status file.
612: * Returns true on success or false on failure.
613: */
614: bool
615: set_lectured(void)
616: {
617: char lecture_status[PATH_MAX];
618: int len, fd = -1;
619: debug_decl(set_lectured, SUDO_DEBUG_AUTH)
620:
621: len = snprintf(lecture_status, sizeof(lecture_status), "%s/%s",
622: def_lecture_status_dir, user_name);
623: if (len <= 0 || (size_t)len >= sizeof(lecture_status)) {
624: log_fatal(0, N_("lecture status path too long: %s/%s"),
625: def_lecture_status_dir, user_name);
626: }
627:
628: /* Sanity check lecture dir and create if missing. */
629: if (!ts_secure_dir(def_lecture_status_dir, true, false))
630: goto done;
631:
632: /* Create lecture file. */
633: if (timestamp_uid != 0)
634: set_perms(PERM_TIMESTAMP);
635: fd = open(lecture_status, O_WRONLY|O_CREAT|O_TRUNC, 0600);
636: if (timestamp_uid != 0)
637: restore_perms();
638: if (fd != -1)
639: close(fd);
640:
641: done:
642: debug_return_bool(fd != -1 ? true : false);
643: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>