Annotation of embedaddon/sudo/plugins/sudoers/sudoreplay.c, revision 1.1.1.5
1.1 misho 1: /*
1.1.1.4 misho 2: * Copyright (c) 2009-2013 Todd C. Miller <Todd.Miller@courtesan.com>
1.1 misho 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>
1.1.1.3 misho 20: #include <sys/uio.h>
1.1 misho 21: #ifdef HAVE_SYS_SYSMACROS_H
22: # include <sys/sysmacros.h>
23: #endif
24: #include <sys/stat.h>
25: #include <sys/time.h>
26: #include <sys/wait.h>
27: #include <sys/ioctl.h>
28: #ifdef HAVE_SYS_SELECT_H
29: #include <sys/select.h>
30: #endif /* HAVE_SYS_SELECT_H */
31: #include <stdio.h>
32: #ifdef STDC_HEADERS
33: # include <stdlib.h>
34: # include <stddef.h>
35: #else
36: # ifdef HAVE_STDLIB_H
37: # include <stdlib.h>
38: # endif
39: #endif /* STDC_HEADERS */
40: #ifdef HAVE_STRING_H
41: # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
42: # include <memory.h>
43: # endif
44: # include <string.h>
45: #endif /* HAVE_STRING_H */
46: #ifdef HAVE_STRINGS_H
47: # include <strings.h>
48: #endif /* HAVE_STRINGS_H */
49: #ifdef HAVE_UNISTD_H
50: # include <unistd.h>
51: #endif /* HAVE_UNISTD_H */
52: #if TIME_WITH_SYS_TIME
53: # include <time.h>
54: #endif
1.1.1.2 misho 55: #ifndef HAVE_STRUCT_TIMESPEC
1.1 misho 56: # include "compat/timespec.h"
57: #endif
58: #include <ctype.h>
59: #include <errno.h>
60: #include <limits.h>
61: #include <fcntl.h>
62: #ifdef HAVE_DIRENT_H
63: # include <dirent.h>
64: # define NAMLEN(dirent) strlen((dirent)->d_name)
65: #else
66: # define dirent direct
67: # define NAMLEN(dirent) (dirent)->d_namlen
68: # ifdef HAVE_SYS_NDIR_H
69: # include <sys/ndir.h>
70: # endif
71: # ifdef HAVE_SYS_DIR_H
72: # include <sys/dir.h>
73: # endif
74: # ifdef HAVE_NDIR_H
75: # include <ndir.h>
76: # endif
77: #endif
78: #ifdef HAVE_REGCOMP
79: # include <regex.h>
80: #endif
81: #ifdef HAVE_ZLIB_H
82: # include <zlib.h>
83: #endif
84: #include <signal.h>
1.1.1.2 misho 85: #ifdef HAVE_STDBOOL_H
86: # include <stdbool.h>
87: #else
88: # include "compat/stdbool.h"
89: #endif /* HAVE_STDBOOL_H */
1.1.1.5 ! misho 90: #ifdef HAVE_GETOPT_LONG
! 91: # include <getopt.h>
! 92: # else
! 93: # include "compat/getopt.h"
! 94: #endif /* HAVE_GETOPT_LONG */
1.1 misho 95:
96: #include <pathnames.h>
97:
98: #include "missing.h"
99: #include "alloc.h"
1.1.1.5 ! misho 100: #include "fatal.h"
1.1 misho 101: #include "gettext.h"
1.1.1.4 misho 102: #include "logging.h"
1.1.1.5 ! misho 103: #include "iolog.h"
1.1.1.2 misho 104: #include "sudo_plugin.h"
105: #include "sudo_conf.h"
106: #include "sudo_debug.h"
1.1 misho 107:
108: #ifndef LINE_MAX
109: # define LINE_MAX 2048
110: #endif
111:
112: /*
113: * Info present in the I/O log file
114: */
115: struct log_info {
116: char *cwd;
117: char *user;
118: char *runas_user;
119: char *runas_group;
120: char *tty;
121: char *cmd;
122: time_t tstamp;
1.1.1.2 misho 123: int rows;
124: int cols;
1.1 misho 125: };
126:
127: /*
128: * Handle expressions like:
129: * ( user millert or user root ) and tty console and command /bin/sh
130: */
1.1.1.5 ! misho 131: static struct search_node {
1.1 misho 132: struct search_node *next;
133: #define ST_EXPR 1
134: #define ST_TTY 2
135: #define ST_USER 3
136: #define ST_PATTERN 4
137: #define ST_RUNASUSER 5
138: #define ST_RUNASGROUP 6
139: #define ST_FROMDATE 7
140: #define ST_TODATE 8
141: #define ST_CWD 9
142: char type;
143: char negated;
144: char or;
145: char pad;
146: union {
147: #ifdef HAVE_REGCOMP
148: regex_t cmdre;
149: #endif
150: time_t tstamp;
151: char *cwd;
152: char *tty;
153: char *user;
154: char *pattern;
155: char *runas_group;
156: char *runas_user;
157: struct search_node *expr;
158: void *ptr;
159: } u;
160: } *search_expr;
161:
162: #define STACK_NODE_SIZE 32
163: static struct search_node *node_stack[32];
164: static int stack_top;
165:
1.1.1.5 ! misho 166: static int timing_idx_adj = 0;
! 167:
1.1 misho 168: static const char *session_dir = _PATH_SUDO_IO_LOGDIR;
169:
1.1.1.5 ! misho 170: static const char short_opts[] = "d:f:hlm:s:V";
! 171: static struct option long_opts[] = {
! 172: { "directory", required_argument, NULL, 'd' },
! 173: { "filter", required_argument, NULL, 'f' },
! 174: { "help", no_argument, NULL, 'h' },
! 175: { "list", no_argument, NULL, 'l' },
! 176: { "max-wait", required_argument, NULL, 'm' },
! 177: { "speed", required_argument, NULL, 's' },
! 178: { "version", no_argument, NULL, 'V' },
! 179: { NULL, no_argument, NULL, '\0' },
1.1 misho 180: };
181:
182: extern time_t get_date(char *);
183: extern char *get_timestr(time_t, int);
184: extern int term_raw(int, int);
185: extern int term_restore(int, int);
1.1.1.2 misho 186: extern void get_ttysize(int *rowp, int *colp);
1.1 misho 187:
188: static int list_sessions(int, char **, const char *, const char *, const char *);
189: static int parse_expr(struct search_node **, char **);
190: static void check_input(int, double *);
191: static void delay(double);
192: static void help(void) __attribute__((__noreturn__));
193: static void usage(int);
1.1.1.5 ! misho 194: static int open_io_fd(char *path, int len, struct io_log_file *iol);
1.1 misho 195: static int parse_timing(const char *buf, const char *decimal, int *idx, double *seconds, size_t *nbytes);
1.1.1.2 misho 196: static struct log_info *parse_logfile(char *logfile);
197: static void free_log_info(struct log_info *li);
1.1.1.3 misho 198: static size_t atomic_writev(int fd, struct iovec *iov, int iovcnt);
1.1.1.4 misho 199: static void sudoreplay_handler(int);
200: static void sudoreplay_cleanup(void);
1.1 misho 201:
202: #ifdef HAVE_REGCOMP
203: # define REGEX_T regex_t
204: #else
205: # define REGEX_T char
206: #endif
207:
208: #define VALID_ID(s) (isalnum((unsigned char)(s)[0]) && \
209: isalnum((unsigned char)(s)[1]) && isalnum((unsigned char)(s)[2]) && \
210: isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
211: isalnum((unsigned char)(s)[5]) && (s)[6] == '\0')
212:
213: #define IS_IDLOG(s) ( \
214: isalnum((unsigned char)(s)[0]) && isalnum((unsigned char)(s)[1]) && \
215: (s)[2] == '/' && \
216: isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
217: (s)[5] == '/' && \
218: isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
219: (s)[8] == '/' && (s)[9] == 'l' && (s)[10] == 'o' && (s)[11] == 'g' && \
1.1.1.2 misho 220: (s)[12] == '\0')
1.1 misho 221:
1.1.1.4 misho 222: __dso_public int main(int argc, char *argv[]);
223:
1.1 misho 224: int
225: main(int argc, char *argv[])
226: {
1.1.1.3 misho 227: int ch, idx, plen, exitcode = 0, rows = 0, cols = 0;
228: bool interactive = false, listonly = false, need_nlcr = false;
1.1.1.5 ! misho 229: bool def_filter = true;
1.1.1.4 misho 230: const char *decimal, *id, *user = NULL, *pattern = NULL, *tty = NULL;
1.1 misho 231: char path[PATH_MAX], buf[LINE_MAX], *cp, *ep;
232: double seconds, to_wait, speed = 1.0, max_wait = 0;
233: sigaction_t sa;
1.1.1.3 misho 234: size_t len, nbytes, nread;
1.1.1.2 misho 235: struct log_info *li;
1.1.1.3 misho 236: struct iovec *iov = NULL;
237: int iovcnt = 0, iovmax = 0;
1.1.1.2 misho 238: debug_decl(main, SUDO_DEBUG_MAIN)
239:
240: #if defined(SUDO_DEVEL) && defined(__OpenBSD__)
241: {
242: extern char *malloc_options;
243: malloc_options = "AFGJPR";
244: }
245: #endif
1.1 misho 246:
247: #if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME)
248: setprogname(argc > 0 ? argv[0] : "sudoreplay");
249: #endif
250:
1.1.1.4 misho 251: sudoers_setlocale(SUDOERS_LOCALE_USER, NULL);
1.1 misho 252: decimal = localeconv()->decimal_point;
253: bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have sudoreplay domain */
254: textdomain("sudoers");
255:
1.1.1.4 misho 256: /* Register fatal/fatalx callback. */
257: fatal_callback_register(sudoreplay_cleanup);
258:
1.1.1.2 misho 259: /* Read sudo.conf. */
1.1.1.4 misho 260: sudo_conf_read(NULL);
1.1.1.2 misho 261:
1.1.1.5 ! misho 262: while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
! 263: switch (ch) {
1.1 misho 264: case 'd':
265: session_dir = optarg;
266: break;
267: case 'f':
268: /* Set the replay filter. */
1.1.1.5 ! misho 269: def_filter = false;
1.1 misho 270: for (cp = strtok(optarg, ","); cp; cp = strtok(NULL, ",")) {
271: if (strcmp(cp, "stdout") == 0)
1.1.1.5 ! misho 272: io_log_files[IOFD_STDOUT].enabled = true;
1.1 misho 273: else if (strcmp(cp, "stderr") == 0)
1.1.1.5 ! misho 274: io_log_files[IOFD_STDERR].enabled = true;
1.1 misho 275: else if (strcmp(cp, "ttyout") == 0)
1.1.1.5 ! misho 276: io_log_files[IOFD_TTYOUT].enabled = true;
1.1 misho 277: else
1.1.1.4 misho 278: fatalx(_("invalid filter option: %s"), optarg);
1.1 misho 279: }
280: break;
281: case 'h':
282: help();
283: /* NOTREACHED */
284: case 'l':
1.1.1.2 misho 285: listonly = true;
1.1 misho 286: break;
287: case 'm':
288: errno = 0;
289: max_wait = strtod(optarg, &ep);
290: if (*ep != '\0' || errno != 0)
1.1.1.4 misho 291: fatalx(_("invalid max wait: %s"), optarg);
1.1 misho 292: break;
293: case 's':
294: errno = 0;
295: speed = strtod(optarg, &ep);
296: if (*ep != '\0' || errno != 0)
1.1.1.4 misho 297: fatalx(_("invalid speed factor: %s"), optarg);
1.1 misho 298: break;
299: case 'V':
300: (void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION);
1.1.1.2 misho 301: goto done;
1.1 misho 302: default:
303: usage(1);
304: /* NOTREACHED */
305: }
306:
307: }
308: argc -= optind;
309: argv += optind;
310:
1.1.1.2 misho 311: if (listonly) {
312: exitcode = list_sessions(argc, argv, pattern, user, tty);
313: goto done;
314: }
1.1 misho 315:
316: if (argc != 1)
317: usage(1);
318:
1.1.1.5 ! misho 319: /* By default we replay stdout, stderr and ttyout. */
! 320: if (def_filter) {
! 321: io_log_files[IOFD_STDOUT].enabled = true;
! 322: io_log_files[IOFD_STDERR].enabled = true;
! 323: io_log_files[IOFD_TTYOUT].enabled = true;
! 324: }
! 325:
1.1 misho 326: /* 6 digit ID in base 36, e.g. 01G712AB or free-form name */
327: id = argv[0];
328: if (VALID_ID(id)) {
329: plen = snprintf(path, sizeof(path), "%s/%.2s/%.2s/%.2s/timing",
330: session_dir, id, &id[2], &id[4]);
331: if (plen <= 0 || plen >= sizeof(path))
1.1.1.4 misho 332: fatalx(_("%s/%.2s/%.2s/%.2s/timing: %s"), session_dir,
1.1 misho 333: id, &id[2], &id[4], strerror(ENAMETOOLONG));
334: } else {
335: plen = snprintf(path, sizeof(path), "%s/%s/timing",
336: session_dir, id);
337: if (plen <= 0 || plen >= sizeof(path))
1.1.1.4 misho 338: fatalx(_("%s/%s/timing: %s"), session_dir,
1.1 misho 339: id, strerror(ENAMETOOLONG));
340: }
341: plen -= 7;
342:
343: /* Open files for replay, applying replay filter for the -f flag. */
344: for (idx = 0; idx < IOFD_MAX; idx++) {
1.1.1.5 ! misho 345: if (open_io_fd(path, plen, &io_log_files[idx]) == -1)
! 346: fatal(_("unable to open %s"), path);
1.1 misho 347: }
348:
1.1.1.2 misho 349: /* Parse log file. */
1.1 misho 350: path[plen] = '\0';
351: strlcat(path, "/log", sizeof(path));
1.1.1.2 misho 352: if ((li = parse_logfile(path)) == NULL)
353: exit(1);
354: printf(_("Replaying sudo session: %s\n"), li->cmd);
355:
356: /* Make sure the terminal is large enough. */
357: get_ttysize(&rows, &cols);
358: if (li->rows != 0 && li->cols != 0) {
359: if (li->rows > rows) {
360: printf(_("Warning: your terminal is too small to properly replay the log.\n"));
361: printf(_("Log geometry is %d x %d, your terminal's geometry is %d x %d."), li->rows, li->cols, rows, cols);
362: }
363: }
364:
365: /* Done with parsed log file. */
366: free_log_info(li);
367: li = NULL;
1.1 misho 368:
369: fflush(stdout);
1.1.1.2 misho 370: memset(&sa, 0, sizeof(sa));
1.1 misho 371: sigemptyset(&sa.sa_mask);
372: sa.sa_flags = SA_RESETHAND;
1.1.1.4 misho 373: sa.sa_handler = sudoreplay_handler;
1.1 misho 374: (void) sigaction(SIGINT, &sa, NULL);
375: (void) sigaction(SIGKILL, &sa, NULL);
376: (void) sigaction(SIGTERM, &sa, NULL);
377: (void) sigaction(SIGHUP, &sa, NULL);
378: sa.sa_flags = SA_RESTART;
379: sa.sa_handler = SIG_IGN;
380: (void) sigaction(SIGTSTP, &sa, NULL);
381: (void) sigaction(SIGQUIT, &sa, NULL);
382:
383: /* XXX - read user input from /dev/tty and set STDOUT to raw if not a pipe */
384: /* Set stdin to raw mode if it is a tty */
385: interactive = isatty(STDIN_FILENO);
386: if (interactive) {
387: ch = fcntl(STDIN_FILENO, F_GETFL, 0);
388: if (ch != -1)
389: (void) fcntl(STDIN_FILENO, F_SETFL, ch | O_NONBLOCK);
390: if (!term_raw(STDIN_FILENO, 1))
1.1.1.4 misho 391: fatal(_("unable to set tty to raw mode"));
1.1.1.3 misho 392: iovmax = 32;
393: iov = ecalloc(iovmax, sizeof(*iov));
1.1 misho 394: }
395:
396: /*
397: * Timing file consists of line of the format: "%f %d\n"
398: */
399: #ifdef HAVE_ZLIB_H
1.1.1.5 ! misho 400: while (gzgets(io_log_files[IOFD_TIMING].fd.g, buf, sizeof(buf)) != NULL) {
1.1 misho 401: #else
1.1.1.5 ! misho 402: while (fgets(buf, sizeof(buf), io_log_files[IOFD_TIMING].fd.f) != NULL) {
1.1 misho 403: #endif
1.1.1.3 misho 404: char last_char = '\0';
405:
1.1 misho 406: if (!parse_timing(buf, decimal, &idx, &seconds, &nbytes))
1.1.1.4 misho 407: fatalx(_("invalid timing file line: %s"), buf);
1.1 misho 408:
409: if (interactive)
410: check_input(STDIN_FILENO, &speed);
411:
412: /* Adjust delay using speed factor and clamp to max_wait */
413: to_wait = seconds / speed;
414: if (max_wait && to_wait > max_wait)
415: to_wait = max_wait;
416: delay(to_wait);
417:
1.1.1.5 ! misho 418: /* Even if we are not replaying, we still have to delay. */
! 419: if (io_log_files[idx].fd.v == NULL)
1.1 misho 420: continue;
421:
1.1.1.3 misho 422: /* Check whether we need to convert newline to CR LF pairs. */
423: if (interactive)
424: need_nlcr = (idx == IOFD_STDOUT || idx == IOFD_STDERR);
425:
1.1 misho 426: /* All output is sent to stdout. */
427: while (nbytes != 0) {
428: if (nbytes > sizeof(buf))
429: len = sizeof(buf);
430: else
431: len = nbytes;
432: #ifdef HAVE_ZLIB_H
1.1.1.5 ! misho 433: nread = gzread(io_log_files[idx].fd.g, buf, len);
1.1 misho 434: #else
1.1.1.5 ! misho 435: nread = fread(buf, 1, len, io_log_files[idx].fd.f);
1.1 misho 436: #endif
437: nbytes -= nread;
1.1.1.3 misho 438:
439: /* Convert newline to carriage return + linefeed if needed. */
440: if (need_nlcr) {
441: size_t remainder = nread;
442: size_t linelen;
443: iovcnt = 0;
444: cp = buf;
445: ep = cp - 1;
446: /* Handle a "\r\n" pair that spans a buffer. */
447: if (last_char == '\r' && buf[0] == '\n') {
448: ep++;
449: remainder--;
450: }
451: while ((ep = memchr(ep + 1, '\n', remainder)) != NULL) {
452: /* Is there already a carriage return? */
453: if (cp != ep && ep[-1] == '\r') {
454: remainder = (size_t)(&buf[nread - 1] - ep);
455: continue;
1.1 misho 456: }
1.1.1.3 misho 457:
458: /* Store the line in iov followed by \r\n pair. */
459: if (iovcnt + 3 > iovmax) {
460: iovmax <<= 1;
461: iov = erealloc3(iov, iovmax, sizeof(*iov));
462: }
463: linelen = (size_t)(ep - cp) + 1;
464: iov[iovcnt].iov_base = cp;
465: iov[iovcnt].iov_len = linelen - 1; /* not including \n */
466: iovcnt++;
467: iov[iovcnt].iov_base = "\r\n";
468: iov[iovcnt].iov_len = 2;
469: iovcnt++;
470: cp = ep + 1;
471: remainder -= linelen;
472: }
473: if (cp - buf != nread) {
474: /*
475: * Partial line without a linefeed or multiple lines
476: * with \r\n pairs.
477: */
478: iov[iovcnt].iov_base = cp;
479: iov[iovcnt].iov_len = nread - (cp - buf);
480: iovcnt++;
1.1 misho 481: }
1.1.1.3 misho 482: last_char = buf[nread - 1]; /* stash last char of old buffer */
483: } else {
484: /* No conversion needed. */
485: iov[0].iov_base = buf;
486: iov[0].iov_len = nread;
487: iovcnt = 1;
488: }
489: if (atomic_writev(STDOUT_FILENO, iov, iovcnt) == -1)
1.1.1.4 misho 490: fatal(_("writing to standard output"));
1.1 misho 491: }
492: }
493: term_restore(STDIN_FILENO, 1);
1.1.1.2 misho 494: done:
495: sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode);
496: exit(exitcode);
1.1 misho 497: }
498:
499: static void
500: delay(double secs)
501: {
502: struct timespec ts, rts;
503: int rval;
504:
505: /*
506: * Typical max resolution is 1/HZ but we can't portably check that.
507: * If the interval is small enough, just ignore it.
508: */
509: if (secs < 0.0001)
510: return;
511:
512: rts.tv_sec = secs;
513: rts.tv_nsec = (secs - (double) rts.tv_sec) * 1000000000.0;
514: do {
515: memcpy(&ts, &rts, sizeof(ts));
516: rval = nanosleep(&ts, &rts);
517: } while (rval == -1 && errno == EINTR);
518: if (rval == -1) {
1.1.1.4 misho 519: fatal_nodebug("nanosleep: tv_sec %lld, tv_nsec %ld",
520: (long long)ts.tv_sec, (long)ts.tv_nsec);
1.1 misho 521: }
522: }
523:
524: static int
1.1.1.5 ! misho 525: open_io_fd(char *path, int len, struct io_log_file *iol)
1.1 misho 526: {
1.1.1.3 misho 527: debug_decl(open_io_fd, SUDO_DEBUG_UTIL)
528:
1.1.1.5 ! misho 529: if (!iol->enabled)
! 530: debug_return_int(0);
1.1 misho 531:
1.1.1.5 ! misho 532: path[len] = '\0';
! 533: strlcat(path, iol->suffix, PATH_MAX);
1.1 misho 534: #ifdef HAVE_ZLIB_H
1.1.1.5 ! misho 535: iol->fd.g = gzopen(path, "r");
1.1 misho 536: #else
1.1.1.5 ! misho 537: iol->fd.f = fopen(path, "r");
1.1 misho 538: #endif
1.1.1.5 ! misho 539: debug_return_int(iol->fd.v ? 0 : -1);
1.1 misho 540: }
541:
542: /*
1.1.1.3 misho 543: * Call writev(), restarting as needed and handling EAGAIN since
544: * fd may be in non-blocking mode.
545: */
546: static size_t
547: atomic_writev(int fd, struct iovec *iov, int iovcnt)
548: {
549: ssize_t n, nwritten = 0;
550: size_t count, remainder, nbytes = 0;
551: int i;
552: debug_decl(atomic_writev, SUDO_DEBUG_UTIL)
553:
554: for (i = 0; i < iovcnt; i++)
555: nbytes += iov[i].iov_len;
556:
557: for (;;) {
558: n = writev(STDOUT_FILENO, iov, iovcnt);
559: if (n > 0) {
560: nwritten += n;
561: remainder = nbytes - nwritten;
562: if (remainder == 0)
563: break;
564: /* short writev, adjust iov and do the rest. */
565: count = 0;
566: i = iovcnt;
567: while (i--) {
568: count += iov[i].iov_len;
569: if (count == remainder) {
570: iov += i;
571: iovcnt -= i;
572: break;
573: }
574: if (count > remainder) {
575: size_t off = (count - remainder);
576: /* XXX - side effect prevents iov from being const */
577: iov[i].iov_base = (char *)iov[i].iov_base + off;
578: iov[i].iov_len -= off;
579: iov += i;
580: iovcnt -= i;
581: break;
582: }
583: }
584: continue;
585: }
586: if (n == 0 || errno == EAGAIN) {
587: int nready;
588: fd_set fdsw;
589: FD_ZERO(&fdsw);
590: FD_SET(STDOUT_FILENO, &fdsw);
591: do {
592: nready = select(STDOUT_FILENO + 1, NULL, &fdsw, NULL, NULL);
593: } while (nready == -1 && errno == EINTR);
594: if (nready == 1)
595: continue;
596: }
597: if (errno == EINTR)
598: continue;
599: nwritten = -1;
600: break;
601: }
602: debug_return_size_t(nwritten);
603: }
604:
605: /*
1.1 misho 606: * Build expression list from search args
607: */
608: static int
609: parse_expr(struct search_node **headp, char *argv[])
610: {
611: struct search_node *sn, *newsn;
612: char or = 0, not = 0, type, **av;
1.1.1.2 misho 613: debug_decl(parse_expr, SUDO_DEBUG_UTIL)
1.1 misho 614:
615: sn = *headp;
616: for (av = argv; *av; av++) {
617: switch (av[0][0]) {
618: case 'a': /* and (ignore) */
619: if (strncmp(*av, "and", strlen(*av)) != 0)
620: goto bad;
621: continue;
622: case 'o': /* or */
623: if (strncmp(*av, "or", strlen(*av)) != 0)
624: goto bad;
625: or = 1;
626: continue;
627: case '!': /* negate */
628: if (av[0][1] != '\0')
629: goto bad;
630: not = 1;
631: continue;
632: case 'c': /* command */
633: if (av[0][1] == '\0')
1.1.1.4 misho 634: fatalx(_("ambiguous expression \"%s\""), *av);
1.1 misho 635: if (strncmp(*av, "cwd", strlen(*av)) == 0)
636: type = ST_CWD;
637: else if (strncmp(*av, "command", strlen(*av)) == 0)
638: type = ST_PATTERN;
639: else
640: goto bad;
641: break;
642: case 'f': /* from date */
643: if (strncmp(*av, "fromdate", strlen(*av)) != 0)
644: goto bad;
645: type = ST_FROMDATE;
646: break;
647: case 'g': /* runas group */
648: if (strncmp(*av, "group", strlen(*av)) != 0)
649: goto bad;
650: type = ST_RUNASGROUP;
651: break;
652: case 'r': /* runas user */
653: if (strncmp(*av, "runas", strlen(*av)) != 0)
654: goto bad;
655: type = ST_RUNASUSER;
656: break;
657: case 't': /* tty or to date */
658: if (av[0][1] == '\0')
1.1.1.4 misho 659: fatalx(_("ambiguous expression \"%s\""), *av);
1.1 misho 660: if (strncmp(*av, "todate", strlen(*av)) == 0)
661: type = ST_TODATE;
662: else if (strncmp(*av, "tty", strlen(*av)) == 0)
663: type = ST_TTY;
664: else
665: goto bad;
666: break;
667: case 'u': /* user */
668: if (strncmp(*av, "user", strlen(*av)) != 0)
669: goto bad;
670: type = ST_USER;
671: break;
672: case '(': /* start sub-expression */
673: if (av[0][1] != '\0')
674: goto bad;
675: if (stack_top + 1 == STACK_NODE_SIZE) {
1.1.1.4 misho 676: fatalx(_("too many parenthesized expressions, max %d"),
1.1 misho 677: STACK_NODE_SIZE);
678: }
679: node_stack[stack_top++] = sn;
680: type = ST_EXPR;
681: break;
682: case ')': /* end sub-expression */
683: if (av[0][1] != '\0')
684: goto bad;
685: /* pop */
686: if (--stack_top < 0)
1.1.1.4 misho 687: fatalx(_("unmatched ')' in expression"));
1.1 misho 688: if (node_stack[stack_top])
689: sn->next = node_stack[stack_top]->next;
1.1.1.2 misho 690: debug_return_int(av - argv + 1);
1.1 misho 691: bad:
692: default:
1.1.1.4 misho 693: fatalx(_("unknown search term \"%s\""), *av);
1.1 misho 694: /* NOTREACHED */
695: }
696:
697: /* Allocate new search node */
1.1.1.2 misho 698: newsn = ecalloc(1, sizeof(*newsn));
1.1 misho 699: newsn->type = type;
700: newsn->or = or;
701: newsn->negated = not;
1.1.1.2 misho 702: /* newsn->next = NULL; */
1.1 misho 703: if (type == ST_EXPR) {
704: av += parse_expr(&newsn->u.expr, av + 1);
705: } else {
706: if (*(++av) == NULL)
1.1.1.4 misho 707: fatalx(_("%s requires an argument"), av[-1]);
1.1 misho 708: #ifdef HAVE_REGCOMP
709: if (type == ST_PATTERN) {
710: if (regcomp(&newsn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0)
1.1.1.4 misho 711: fatalx(_("invalid regular expression: %s"), *av);
1.1 misho 712: } else
713: #endif
714: if (type == ST_TODATE || type == ST_FROMDATE) {
715: newsn->u.tstamp = get_date(*av);
716: if (newsn->u.tstamp == -1)
1.1.1.4 misho 717: fatalx(_("could not parse date \"%s\""), *av);
1.1 misho 718: } else {
719: newsn->u.ptr = *av;
720: }
721: }
722: not = or = 0; /* reset state */
723: if (sn)
724: sn->next = newsn;
725: else
726: *headp = newsn;
727: sn = newsn;
728: }
729: if (stack_top)
1.1.1.4 misho 730: fatalx(_("unmatched '(' in expression"));
1.1 misho 731: if (or)
1.1.1.4 misho 732: fatalx(_("illegal trailing \"or\""));
1.1 misho 733: if (not)
1.1.1.4 misho 734: fatalx(_("illegal trailing \"!\""));
1.1 misho 735:
1.1.1.2 misho 736: debug_return_int(av - argv);
1.1 misho 737: }
738:
1.1.1.2 misho 739: static bool
1.1 misho 740: match_expr(struct search_node *head, struct log_info *log)
741: {
742: struct search_node *sn;
1.1.1.2 misho 743: bool matched = true;
744: int rc;
745: debug_decl(match_expr, SUDO_DEBUG_UTIL)
1.1 misho 746:
747: for (sn = head; sn; sn = sn->next) {
748: /* If we have no match, skip ahead to the next OR entry. */
749: if (!matched && !sn->or)
750: continue;
751:
752: switch (sn->type) {
753: case ST_EXPR:
754: matched = match_expr(sn->u.expr, log);
755: break;
756: case ST_CWD:
757: matched = strcmp(sn->u.cwd, log->cwd) == 0;
758: break;
759: case ST_TTY:
760: matched = strcmp(sn->u.tty, log->tty) == 0;
761: break;
762: case ST_RUNASGROUP:
763: matched = strcmp(sn->u.runas_group, log->runas_group) == 0;
764: break;
765: case ST_RUNASUSER:
766: matched = strcmp(sn->u.runas_user, log->runas_user) == 0;
767: break;
768: case ST_USER:
769: matched = strcmp(sn->u.user, log->user) == 0;
770: break;
771: case ST_PATTERN:
772: #ifdef HAVE_REGCOMP
773: rc = regexec(&sn->u.cmdre, log->cmd, 0, NULL, 0);
774: if (rc && rc != REG_NOMATCH) {
775: char buf[BUFSIZ];
776: regerror(rc, &sn->u.cmdre, buf, sizeof(buf));
1.1.1.4 misho 777: fatalx("%s", buf);
1.1 misho 778: }
779: matched = rc == REG_NOMATCH ? 0 : 1;
780: #else
781: matched = strstr(log.cmd, sn->u.pattern) != NULL;
782: #endif
783: break;
784: case ST_FROMDATE:
785: matched = log->tstamp >= sn->u.tstamp;
786: break;
787: case ST_TODATE:
788: matched = log->tstamp <= sn->u.tstamp;
789: break;
790: }
791: if (sn->negated)
792: matched = !matched;
793: }
1.1.1.2 misho 794: debug_return_bool(matched);
1.1 misho 795: }
796:
1.1.1.2 misho 797: static struct log_info *
798: parse_logfile(char *logfile)
1.1 misho 799: {
800: FILE *fp;
1.1.1.2 misho 801: char *buf = NULL, *cp, *ep;
1.1 misho 802: size_t bufsize = 0, cwdsize = 0, cmdsize = 0;
1.1.1.2 misho 803: struct log_info *li = NULL;
804: debug_decl(list_session, SUDO_DEBUG_UTIL)
1.1 misho 805:
806: fp = fopen(logfile, "r");
807: if (fp == NULL) {
808: warning(_("unable to open %s"), logfile);
1.1.1.2 misho 809: goto bad;
1.1 misho 810: }
811:
812: /*
813: * ID file has three lines:
814: * 1) a log info line
815: * 2) cwd
816: * 3) command with args
817: */
1.1.1.2 misho 818: li = ecalloc(1, sizeof(*li));
1.1 misho 819: if (getline(&buf, &bufsize, fp) == -1 ||
1.1.1.2 misho 820: getline(&li->cwd, &cwdsize, fp) == -1 ||
821: getline(&li->cmd, &cmdsize, fp) == -1) {
822: goto bad;
1.1 misho 823: }
824:
1.1.1.2 misho 825: /* Strip the newline from the cwd and command. */
826: li->cwd[strcspn(li->cwd, "\n")] = '\0';
827: li->cmd[strcspn(li->cmd, "\n")] = '\0';
828:
829: /*
830: * Crack the log line (rows and cols not present in old versions).
831: * timestamp:user:runas_user:runas_group:tty:rows:cols
832: */
1.1 misho 833: buf[strcspn(buf, "\n")] = '\0';
834:
1.1.1.2 misho 835: /* timestamp */
836: if ((ep = strchr(buf, ':')) == NULL)
837: goto bad;
838: if ((li->tstamp = atoi(buf)) == 0)
839: goto bad;
1.1 misho 840:
1.1.1.2 misho 841: /* user */
842: cp = ep + 1;
843: if ((ep = strchr(cp, ':')) == NULL)
844: goto bad;
845: li->user = estrndup(cp, (size_t)(ep - cp));
1.1 misho 846:
1.1.1.2 misho 847: /* runas user */
848: cp = ep + 1;
849: if ((ep = strchr(cp, ':')) == NULL)
850: goto bad;
851: li->runas_user = estrndup(cp, (size_t)(ep - cp));
1.1 misho 852:
1.1.1.2 misho 853: /* runas group */
854: cp = ep + 1;
855: if ((ep = strchr(cp, ':')) == NULL)
856: goto bad;
857: if (cp != ep)
858: li->runas_group = estrndup(cp, (size_t)(ep - cp));
1.1 misho 859:
1.1.1.2 misho 860: /* tty, followed by optional rows + columns */
861: cp = ep + 1;
862: if ((ep = strchr(cp, ':')) == NULL) {
863: li->tty = estrdup(cp);
864: } else {
865: li->tty = estrndup(cp, (size_t)(ep - cp));
866: cp = ep + 1;
867: li->rows = atoi(cp);
868: if ((ep = strchr(cp, ':')) != NULL) {
869: cp = ep + 1;
870: li->cols = atoi(cp);
871: }
872: }
873: fclose(fp);
874: efree(buf);
875: debug_return_ptr(li);
876:
877: bad:
1.1.1.3 misho 878: if (fp != NULL)
879: fclose(fp);
1.1.1.2 misho 880: efree(buf);
881: free_log_info(li);
882: debug_return_ptr(NULL);
883: }
884:
885: static void
886: free_log_info(struct log_info *li)
887: {
888: if (li != NULL) {
889: efree(li->cwd);
890: efree(li->user);
891: efree(li->runas_user);
892: efree(li->runas_group);
893: efree(li->tty);
894: efree(li->cmd);
895: efree(li);
896: }
897: }
898:
899: static int
900: list_session(char *logfile, REGEX_T *re, const char *user, const char *tty)
901: {
902: char idbuf[7], *idstr, *cp;
903: struct log_info *li;
904: int rval = -1;
905: debug_decl(list_session, SUDO_DEBUG_UTIL)
1.1 misho 906:
1.1.1.2 misho 907: if ((li = parse_logfile(logfile)) == NULL)
908: goto done;
1.1 misho 909:
910: /* Match on search expression if there is one. */
1.1.1.2 misho 911: if (search_expr && !match_expr(search_expr, li))
1.1 misho 912: goto done;
913:
914: /* Convert from /var/log/sudo-sessions/00/00/01/log to 000001 */
915: cp = logfile + strlen(session_dir) + 1;
916: if (IS_IDLOG(cp)) {
1.1.1.2 misho 917: idbuf[0] = cp[0];
918: idbuf[1] = cp[1];
919: idbuf[2] = cp[3];
920: idbuf[3] = cp[4];
921: idbuf[4] = cp[6];
922: idbuf[5] = cp[7];
1.1 misho 923: idbuf[6] = '\0';
924: idstr = idbuf;
925: } else {
926: /* Not an id, just use the iolog_file portion. */
927: cp[strlen(cp) - 4] = '\0';
928: idstr = cp;
929: }
1.1.1.2 misho 930: /* XXX - print rows + cols? */
1.1 misho 931: printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ",
1.1.1.2 misho 932: get_timestr(li->tstamp, 1), li->user, li->tty, li->cwd, li->runas_user);
933: if (li->runas_group)
934: printf("GROUP=%s ; ", li->runas_group);
935: printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd);
1.1 misho 936:
937: rval = 0;
938:
939: done:
1.1.1.2 misho 940: free_log_info(li);
941: debug_return_int(rval);
1.1 misho 942: }
943:
944: static int
1.1.1.2 misho 945: session_compare(const void *v1, const void *v2)
946: {
947: const char *s1 = *(const char **)v1;
948: const char *s2 = *(const char **)v2;
949: return strcmp(s1, s2);
950: }
951:
1.1.1.4 misho 952: /* XXX - always returns 0, calls fatal() on failure */
1.1.1.2 misho 953: static int
1.1 misho 954: find_sessions(const char *dir, REGEX_T *re, const char *user, const char *tty)
955: {
956: DIR *d;
957: struct dirent *dp;
958: struct stat sb;
1.1.1.2 misho 959: size_t sdlen, sessions_len = 0, sessions_size = 36*36;
960: int i, len;
961: char pathbuf[PATH_MAX], **sessions = NULL;
1.1.1.3 misho 962: #ifdef HAVE_STRUCT_DIRENT_D_TYPE
963: bool checked_type = true;
964: #else
965: const bool checked_type = false;
966: #endif
1.1.1.2 misho 967: debug_decl(find_sessions, SUDO_DEBUG_UTIL)
1.1 misho 968:
969: d = opendir(dir);
970: if (d == NULL)
1.1.1.4 misho 971: fatal(_("unable to open %s"), dir);
1.1 misho 972:
973: /* XXX - would be faster to chdir and use relative names */
974: sdlen = strlcpy(pathbuf, dir, sizeof(pathbuf));
975: if (sdlen + 1 >= sizeof(pathbuf)) {
976: errno = ENAMETOOLONG;
1.1.1.4 misho 977: fatal("%s/", dir);
1.1 misho 978: }
979: pathbuf[sdlen++] = '/';
980: pathbuf[sdlen] = '\0';
1.1.1.2 misho 981:
982: /* Store potential session dirs for sorting. */
983: sessions = emalloc2(sessions_size, sizeof(char *));
1.1 misho 984: while ((dp = readdir(d)) != NULL) {
985: /* Skip "." and ".." */
986: if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
987: (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
988: continue;
1.1.1.2 misho 989: #ifdef HAVE_STRUCT_DIRENT_D_TYPE
1.1.1.3 misho 990: if (checked_type) {
991: if (dp->d_type != DT_DIR) {
992: /* Not all file systems support d_type. */
993: if (dp->d_type != DT_UNKNOWN)
994: continue;
995: checked_type = false;
996: }
997: }
1.1.1.2 misho 998: #endif
999:
1000: /* Add name to session list. */
1001: if (sessions_len + 1 > sessions_size) {
1002: sessions_size <<= 1;
1003: sessions = erealloc3(sessions, sessions_size, sizeof(char *));
1004: }
1005: sessions[sessions_len++] = estrdup(dp->d_name);
1006: }
1007: closedir(d);
1.1 misho 1008:
1.1.1.2 misho 1009: /* Sort and list the sessions. */
1010: qsort(sessions, sessions_len, sizeof(char *), session_compare);
1011: for (i = 0; i < sessions_len; i++) {
1.1 misho 1012: len = snprintf(&pathbuf[sdlen], sizeof(pathbuf) - sdlen,
1.1.1.2 misho 1013: "%s/log", sessions[i]);
1.1 misho 1014: if (len <= 0 || len >= sizeof(pathbuf) - sdlen) {
1015: errno = ENAMETOOLONG;
1.1.1.4 misho 1016: fatal("%s/%s/log", dir, sessions[i]);
1.1 misho 1017: }
1.1.1.2 misho 1018: efree(sessions[i]);
1.1 misho 1019:
1020: /* Check for dir with a log file. */
1021: if (lstat(pathbuf, &sb) == 0 && S_ISREG(sb.st_mode)) {
1022: list_session(pathbuf, re, user, tty);
1023: } else {
1024: /* Strip off "/log" and recurse if a dir. */
1025: pathbuf[sdlen + len - 4] = '\0';
1.1.1.3 misho 1026: if (checked_type || (lstat(pathbuf, &sb) == 0 && S_ISDIR(sb.st_mode)))
1.1 misho 1027: find_sessions(pathbuf, re, user, tty);
1028: }
1029: }
1.1.1.2 misho 1030: efree(sessions);
1.1 misho 1031:
1.1.1.2 misho 1032: debug_return_int(0);
1.1 misho 1033: }
1034:
1.1.1.4 misho 1035: /* XXX - always returns 0, calls fatal() on failure */
1.1 misho 1036: static int
1037: list_sessions(int argc, char **argv, const char *pattern, const char *user,
1038: const char *tty)
1039: {
1040: REGEX_T rebuf, *re = NULL;
1.1.1.2 misho 1041: debug_decl(list_sessions, SUDO_DEBUG_UTIL)
1.1 misho 1042:
1043: /* Parse search expression if present */
1044: parse_expr(&search_expr, argv);
1045:
1046: #ifdef HAVE_REGCOMP
1047: /* optional regex */
1048: if (pattern) {
1049: re = &rebuf;
1050: if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0)
1.1.1.4 misho 1051: fatalx(_("invalid regular expression: %s"), pattern);
1.1 misho 1052: }
1053: #else
1054: re = (char *) pattern;
1055: #endif /* HAVE_REGCOMP */
1056:
1.1.1.2 misho 1057: debug_return_int(find_sessions(session_dir, re, user, tty));
1.1 misho 1058: }
1059:
1060: /*
1061: * Check input for ' ', '<', '>'
1062: * pause, slow, fast
1063: */
1064: static void
1065: check_input(int ttyfd, double *speed)
1066: {
1067: fd_set *fdsr;
1068: int nready, paused = 0;
1069: struct timeval tv;
1070: char ch;
1071: ssize_t n;
1.1.1.2 misho 1072: debug_decl(check_input, SUDO_DEBUG_UTIL)
1.1 misho 1073:
1.1.1.2 misho 1074: fdsr = ecalloc(howmany(ttyfd + 1, NFDBITS), sizeof(fd_mask));
1.1 misho 1075: for (;;) {
1076: FD_SET(ttyfd, fdsr);
1077: tv.tv_sec = 0;
1078: tv.tv_usec = 0;
1079:
1080: nready = select(ttyfd + 1, fdsr, NULL, NULL, paused ? NULL : &tv);
1081: if (nready != 1)
1082: break;
1083: n = read(ttyfd, &ch, 1);
1084: if (n == 1) {
1085: if (paused) {
1086: paused = 0;
1087: continue;
1088: }
1089: switch (ch) {
1090: case ' ':
1091: paused = 1;
1092: break;
1093: case '<':
1094: *speed /= 2;
1095: break;
1096: case '>':
1097: *speed *= 2;
1098: break;
1099: }
1100: }
1101: }
1102: free(fdsr);
1.1.1.2 misho 1103: debug_return;
1.1 misho 1104: }
1105:
1106: /*
1107: * Parse a timing line, which is formatted as:
1108: * index sleep_time num_bytes
1109: * Where index is IOFD_*, sleep_time is the number of seconds to sleep
1110: * before writing the data and num_bytes is the number of bytes to output.
1111: * Returns 1 on success and 0 on failure.
1112: */
1113: static int
1114: parse_timing(buf, decimal, idx, seconds, nbytes)
1115: const char *buf;
1116: const char *decimal;
1117: int *idx;
1118: double *seconds;
1119: size_t *nbytes;
1120: {
1121: unsigned long ul;
1122: long l;
1123: double d, fract = 0;
1124: char *cp, *ep;
1.1.1.2 misho 1125: debug_decl(parse_timing, SUDO_DEBUG_UTIL)
1.1 misho 1126:
1127: /* Parse index */
1128: ul = strtoul(buf, &ep, 10);
1.1.1.5 ! misho 1129: if (ul >= IOFD_TIMING) {
! 1130: if (ul != 6)
! 1131: goto bad;
! 1132: /* work around a bug in timing files generated by sudo 1.8.7 */
! 1133: timing_idx_adj = 2;
! 1134: }
! 1135: *idx = (int)ul - timing_idx_adj;
1.1 misho 1136: for (cp = ep + 1; isspace((unsigned char) *cp); cp++)
1137: continue;
1138:
1139: /*
1140: * Parse number of seconds. Sudo logs timing data in the C locale
1141: * but this may not match the current locale so we cannot use strtod().
1142: * Furthermore, sudo < 1.7.4 logged with the user's locale so we need
1143: * to be able to parse those logs too.
1144: */
1145: errno = 0;
1146: l = strtol(cp, &ep, 10);
1147: if ((errno == ERANGE && (l == LONG_MAX || l == LONG_MIN)) ||
1148: l < 0 || l > INT_MAX ||
1149: (*ep != '.' && strncmp(ep, decimal, strlen(decimal)) != 0)) {
1150: goto bad;
1151: }
1152: *seconds = (double)l;
1153: cp = ep + (*ep == '.' ? 1 : strlen(decimal));
1154: d = 10.0;
1155: while (isdigit((unsigned char) *cp)) {
1156: fract += (*cp - '0') / d;
1157: d *= 10;
1158: cp++;
1159: }
1160: *seconds += fract;
1161: while (isspace((unsigned char) *cp))
1162: cp++;
1163:
1164: errno = 0;
1165: ul = strtoul(cp, &ep, 10);
1166: if (errno == ERANGE && ul == ULONG_MAX)
1167: goto bad;
1168: *nbytes = (size_t)ul;
1169:
1.1.1.2 misho 1170: debug_return_int(1);
1.1 misho 1171: bad:
1.1.1.2 misho 1172: debug_return_int(0);
1.1 misho 1173: }
1174:
1175: static void
1176: usage(int fatal)
1177: {
1178: fprintf(fatal ? stderr : stdout,
1.1.1.5 ! misho 1179: _("usage: %s [-h] [-d dir] [-m num] [-s num] ID\n"),
1.1 misho 1180: getprogname());
1181: fprintf(fatal ? stderr : stdout,
1.1.1.5 ! misho 1182: _("usage: %s [-h] [-d dir] -l [search expression]\n"),
1.1 misho 1183: getprogname());
1184: if (fatal)
1185: exit(1);
1186: }
1187:
1188: static void
1189: help(void)
1190: {
1191: (void) printf(_("%s - replay sudo session logs\n\n"), getprogname());
1192: usage(0);
1193: (void) puts(_("\nOptions:\n"
1.1.1.5 ! misho 1194: " -d, --directory=dir specify directory for session logs\n"
! 1195: " -f, --filter=filter specify which I/O type(s) to display\n"
! 1196: " -h, --help display help message and exit\n"
! 1197: " -l, --list list available session IDs, with optional expression\n"
! 1198: " -m, --max-wait=num max number of seconds to wait between events\n"
! 1199: " -s, --speed=num speed up or slow down output\n"
! 1200: " -V, --version display version information and exit"));
1.1 misho 1201: exit(0);
1202: }
1203:
1204: /*
1.1.1.4 misho 1205: * Cleanup hook for fatal()/fatalx()
1.1 misho 1206: */
1.1.1.4 misho 1207: static void
1208: sudoreplay_cleanup(void)
1209: {
1210: term_restore(STDIN_FILENO, 0);
1211: }
1212:
1213: /*
1214: * Signal handler for SIGINT, SIGKILL, SIGTERM, SIGHUP
1215: * Must be installed with SA_RESETHAND enabled.
1216: */
1217: static void
1218: sudoreplay_handler(int signo)
1.1 misho 1219: {
1220: term_restore(STDIN_FILENO, 0);
1.1.1.4 misho 1221: kill(getpid(), signo);
1.1 misho 1222: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>