]> granicus.if.org Git - sudo/blob - plugins/sudoers/sudoreplay.c
If the sudoreplay ID option is a fully-qualified path, use it directly.
[sudo] / plugins / sudoers / sudoreplay.c
1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2009-2019 Todd C. Miller <Todd.Miller@sudo.ws>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 /*
20  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22  */
23
24 #include <config.h>
25
26 #include <sys/types.h>
27 #include <sys/uio.h>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30 #include <sys/ioctl.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #if defined(HAVE_STDINT_H)
34 # include <stdint.h>
35 #elif defined(HAVE_INTTYPES_H)
36 # include <inttypes.h>
37 #endif
38 #ifdef HAVE_STRING_H
39 # include <string.h>
40 #endif /* HAVE_STRING_H */
41 #ifdef HAVE_STRINGS_H
42 # include <strings.h>
43 #endif /* HAVE_STRINGS_H */
44 #include <unistd.h>
45 #include <time.h>
46 #include <ctype.h>
47 #include <errno.h>
48 #include <limits.h>
49 #include <fcntl.h>
50 #include <dirent.h>
51 #ifdef HAVE_STDBOOL_H
52 # include <stdbool.h>
53 #else
54 # include "compat/stdbool.h"
55 #endif /* HAVE_STDBOOL_H */
56 #include <regex.h>
57 #include <signal.h>
58 #include <time.h>
59
60 #include <pathnames.h>
61
62 #include "sudo_gettext.h"       /* must be included before sudo_compat.h */
63
64 #include "sudo_compat.h"
65 #include "sudo_fatal.h"
66 #include "logging.h"
67 #include "iolog.h"
68 #include "iolog_files.h"
69 #include "sudo_queue.h"
70 #include "sudo_plugin.h"
71 #include "sudo_conf.h"
72 #include "sudo_debug.h"
73 #include "sudo_event.h"
74 #include "sudo_util.h"
75
76 #ifdef HAVE_GETOPT_LONG
77 # include <getopt.h>
78 # else
79 # include "compat/getopt.h"
80 #endif /* HAVE_GETOPT_LONG */
81
82 struct replay_closure {
83     struct sudo_event_base *evbase;
84     struct sudo_event *delay_ev;
85     struct sudo_event *keyboard_ev;
86     struct sudo_event *output_ev;
87     struct sudo_event *sighup_ev;
88     struct sudo_event *sigint_ev;
89     struct sudo_event *sigquit_ev;
90     struct sudo_event *sigterm_ev;
91     struct sudo_event *sigtstp_ev;
92     struct timing_closure timing;
93     bool interactive;
94     bool suspend_wait;
95     struct io_buffer {
96         unsigned int len; /* buffer length (how much produced) */
97         unsigned int off; /* write position (how much already consumed) */
98         unsigned int toread; /* how much remains to be read */
99         int lastc;        /* last char written */
100         char buf[64 * 1024];
101     } iobuf;
102 };
103
104 /*
105  * Handle expressions like:
106  * ( user millert or user root ) and tty console and command /bin/sh
107  */
108 STAILQ_HEAD(search_node_list, search_node);
109 struct search_node {
110     STAILQ_ENTRY(search_node) entries;
111 #define ST_EXPR         1
112 #define ST_TTY          2
113 #define ST_USER         3
114 #define ST_PATTERN      4
115 #define ST_RUNASUSER    5
116 #define ST_RUNASGROUP   6
117 #define ST_FROMDATE     7
118 #define ST_TODATE       8
119 #define ST_CWD          9
120     char type;
121     bool negated;
122     bool or;
123     union {
124         regex_t cmdre;
125         time_t tstamp;
126         char *cwd;
127         char *tty;
128         char *user;
129         char *runas_group;
130         char *runas_user;
131         struct search_node_list expr;
132         void *ptr;
133     } u;
134 };
135
136 static struct search_node_list search_expr = STAILQ_HEAD_INITIALIZER(search_expr);
137
138 static double speed_factor = 1.0;
139
140 static const char *session_dir = _PATH_SUDO_IO_LOGDIR;
141
142 static bool terminal_can_resize, terminal_was_resized;
143
144 static int terminal_rows, terminal_cols;
145
146 static int ttyfd = -1;
147
148 static const char short_opts[] =  "d:f:hlm:nRSs:V";
149 static struct option long_opts[] = {
150     { "directory",      required_argument,      NULL,   'd' },
151     { "filter",         required_argument,      NULL,   'f' },
152     { "help",           no_argument,            NULL,   'h' },
153     { "list",           no_argument,            NULL,   'l' },
154     { "max-wait",       required_argument,      NULL,   'm' },
155     { "non-interactive", no_argument,           NULL,   'n' },
156     { "no-resize",      no_argument,            NULL,   'R' },
157     { "suspend-wait",   no_argument,            NULL,   'S' },
158     { "speed",          required_argument,      NULL,   's' },
159     { "version",        no_argument,            NULL,   'V' },
160     { NULL,             no_argument,            NULL,   '\0' },
161 };
162
163 /* XXX move to separate header? (currently in sudoers.h) */
164 extern char *get_timestr(time_t, int);
165 extern time_t get_date(char *);
166
167 static int list_sessions(int, char **, const char *, const char *, const char *);
168 static int open_io_fd(char *path, int len, struct io_log_file *iol);
169 static int parse_expr(struct search_node_list *, char **, bool);
170 static void read_keyboard(int fd, int what, void *v);
171 static void help(void) __attribute__((__noreturn__));
172 static int replay_session(struct timespec *max_wait, const char *decimal, bool interactive, bool suspend_wait);
173 static void sudoreplay_cleanup(void);
174 static void usage(int);
175 static void write_output(int fd, int what, void *v);
176 static void restore_terminal_size(void);
177 static void setup_terminal(struct log_info *li, bool interactive, bool resize);
178
179 #define VALID_ID(s) (isalnum((unsigned char)(s)[0]) && \
180     isalnum((unsigned char)(s)[1]) && isalnum((unsigned char)(s)[2]) && \
181     isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
182     isalnum((unsigned char)(s)[5]) && (s)[6] == '\0')
183
184 #define IS_IDLOG(s) ( \
185     isalnum((unsigned char)(s)[0]) && isalnum((unsigned char)(s)[1]) && \
186     (s)[2] == '/' && \
187     isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
188     (s)[5] == '/' && \
189     isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
190     (s)[8] == '/' && (s)[9] == 'l' && (s)[10] == 'o' && (s)[11] == 'g' && \
191     (s)[12] == '\0')
192
193 __dso_public int main(int argc, char *argv[]);
194
195 int
196 main(int argc, char *argv[])
197 {
198     int ch, i, plen, exitcode = 0;
199     bool def_filter = true, listonly = false;
200     bool interactive = true, suspend_wait = false, resize = true;
201     const char *decimal, *id, *user = NULL, *pattern = NULL, *tty = NULL;
202     char *cp, *ep, path[PATH_MAX];
203     struct log_info *li;
204     struct timespec max_delay_storage, *max_delay = NULL;
205     double dval;
206     debug_decl(main, SUDO_DEBUG_MAIN)
207
208 #if defined(SUDO_DEVEL) && defined(__OpenBSD__)
209     {
210         extern char *malloc_options;
211         malloc_options = "S";
212     }
213 #endif
214
215     initprogname(argc > 0 ? argv[0] : "sudoreplay");
216     setlocale(LC_ALL, "");
217     decimal = localeconv()->decimal_point;
218     bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have sudoreplay domain */
219     textdomain("sudoers");
220
221     /* Register fatal/fatalx callback. */
222     sudo_fatal_callback_register(sudoreplay_cleanup);
223
224     /* Read sudo.conf and initialize the debug subsystem. */
225     if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1)
226         exit(EXIT_FAILURE);
227     sudo_debug_register(getprogname(), NULL, NULL,
228         sudo_conf_debug_files(getprogname()));
229
230     while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
231         switch (ch) {
232         case 'd':
233             session_dir = optarg;
234             break;
235         case 'f':
236             /* Set the replay filter. */
237             def_filter = false;
238             for (cp = strtok_r(optarg, ",", &ep); cp; cp = strtok_r(NULL, ",", &ep)) {
239                 if (strcmp(cp, "stdin") == 0)
240                     io_log_files[IOFD_STDIN].enabled = true;
241                 else if (strcmp(cp, "stdout") == 0)
242                     io_log_files[IOFD_STDOUT].enabled = true;
243                 else if (strcmp(cp, "stderr") == 0)
244                     io_log_files[IOFD_STDERR].enabled = true;
245                 else if (strcmp(cp, "ttyin") == 0)
246                     io_log_files[IOFD_TTYIN].enabled = true;
247                 else if (strcmp(cp, "ttyout") == 0)
248                     io_log_files[IOFD_TTYOUT].enabled = true;
249                 else
250                     sudo_fatalx(U_("invalid filter option: %s"), optarg);
251             }
252             break;
253         case 'h':
254             help();
255             /* NOTREACHED */
256         case 'l':
257             listonly = true;
258             break;
259         case 'm':
260             errno = 0;
261             dval = strtod(optarg, &ep);
262             if (*ep != '\0' || errno != 0)
263                 sudo_fatalx(U_("invalid max wait: %s"), optarg);
264             if (dval <= 0.0) {
265                 sudo_timespecclear(&max_delay_storage);
266             } else {
267                 max_delay_storage.tv_sec = dval;
268                 max_delay_storage.tv_nsec =
269                     (dval - max_delay_storage.tv_sec) * 1000000000.0;
270             }
271             max_delay = &max_delay_storage;
272             break;
273         case 'n':
274             interactive = false;
275             break;
276         case 'R':
277             resize = false;
278             break;
279         case 'S':
280             suspend_wait = true;
281             break;
282         case 's':
283             errno = 0;
284             speed_factor = strtod(optarg, &ep);
285             if (*ep != '\0' || errno != 0)
286                 sudo_fatalx(U_("invalid speed factor: %s"), optarg);
287             break;
288         case 'V':
289             (void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION);
290             goto done;
291         default:
292             usage(1);
293             /* NOTREACHED */
294         }
295
296     }
297     argc -= optind;
298     argv += optind;
299
300     if (listonly) {
301         exitcode = list_sessions(argc, argv, pattern, user, tty);
302         goto done;
303     }
304
305     if (argc != 1)
306         usage(1);
307
308     /* By default we replay stdout, stderr and ttyout. */
309     if (def_filter) {
310         io_log_files[IOFD_STDOUT].enabled = true;
311         io_log_files[IOFD_STDERR].enabled = true;
312         io_log_files[IOFD_TTYOUT].enabled = true;
313     }
314
315     /* 6 digit ID in base 36, e.g. 01G712AB or free-form name */
316     id = argv[0];
317     if (VALID_ID(id)) {
318         plen = snprintf(path, sizeof(path), "%s/%.2s/%.2s/%.2s/timing",
319             session_dir, id, &id[2], &id[4]);
320         if (plen < 0 || plen >= ssizeof(path))
321             sudo_fatalx(U_("%s/%.2s/%.2s/%.2s/timing: %s"), session_dir,
322                 id, &id[2], &id[4], strerror(ENAMETOOLONG));
323     } else if (id[0] == '/') {
324         plen = snprintf(path, sizeof(path), "%s/timing", id);
325         if (plen < 0 || plen >= ssizeof(path))
326             sudo_fatalx(U_("%s/timing: %s"), id, strerror(ENAMETOOLONG));
327     } else {
328         plen = snprintf(path, sizeof(path), "%s/%s/timing", session_dir, id);
329         if (plen < 0 || plen >= ssizeof(path))
330             sudo_fatalx(U_("%s/%s/timing: %s"), session_dir, id,
331                 strerror(ENAMETOOLONG));
332     }
333     plen -= 7;
334
335     /* Open files for replay, applying replay filter for the -f flag. */
336     for (i = 0; i < IOFD_MAX; i++) {
337         if (open_io_fd(path, plen, &io_log_files[i]) == -1)
338             sudo_fatal(U_("unable to open %s"), path);
339     }
340
341     /* Parse log file. */
342     path[plen] = '\0';
343     strlcat(path, "/log", sizeof(path));
344     if ((li = parse_logfile(path)) == NULL)
345         exit(1);
346     printf(_("Replaying sudo session: %s"), li->cmd);
347
348     /* Setup terminal if appropriate. */
349     if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO))
350         interactive = false;
351     setup_terminal(li, interactive, resize);
352     putchar('\r');
353     putchar('\n');
354
355     /* Done with parsed log file. */
356     free_log_info(li);
357     li = NULL;
358
359     /* Replay session corresponding to io_log_files[]. */
360     exitcode = replay_session(max_delay, decimal, interactive, suspend_wait);
361
362     restore_terminal_size();
363     sudo_term_restore(ttyfd, true);
364 done:
365     sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode);
366     exit(exitcode);
367 }
368
369 /*
370  * Call gzread() or fread() for the I/O log file in question.
371  * Return 0 for EOF or -1 on error.
372  */
373 static ssize_t
374 io_log_read(union io_fd ifd, char *buf, size_t nbytes)
375 {
376     ssize_t nread;
377     debug_decl(io_log_read, SUDO_DEBUG_UTIL)
378
379     if (nbytes > INT_MAX) {
380         errno = EINVAL;
381         debug_return_ssize_t(-1);
382     }
383 #ifdef HAVE_ZLIB_H
384     nread = gzread(ifd.g, buf, nbytes);
385 #else
386     nread = (ssize_t)fread(buf, 1, nbytes, ifd.f);
387     if (nread == 0 && ferror(ifd.f))
388         nread = -1;
389 #endif
390     debug_return_ssize_t(nread);
391 }
392
393 static int
394 io_log_eof(union io_fd ifd)
395 {
396     int ret;
397     debug_decl(io_log_eof, SUDO_DEBUG_UTIL)
398
399 #ifdef HAVE_ZLIB_H
400     ret = gzeof(ifd.g);
401 #else
402     ret = feof(ifd.f);
403 #endif
404     debug_return_int(ret);
405 }
406
407 static char *
408 io_log_gets(union io_fd ifd, char *buf, size_t nbytes)
409 {
410     char *str;
411     debug_decl(io_log_gets, SUDO_DEBUG_UTIL)
412
413 #ifdef HAVE_ZLIB_H
414     str = gzgets(ifd.g, buf, nbytes);
415 #else
416     str = fgets(buf, nbytes, ifd.f);
417 #endif
418     debug_return_str(str);
419 }
420
421 /*
422  * List of terminals that support xterm-like resizing.
423  * This is not an exhaustive list.
424  * For a list of VT100 style escape codes, see:
425  *  http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#VT100%20Mode
426  */
427 struct term_names {
428     const char *name;
429     unsigned int len;
430 } compatible_terms[] = {
431     { "Eterm", 5 },
432     { "aterm", 5 },
433     { "dtterm", 6 },
434     { "gnome", 5 },
435     { "konsole", 7 },
436     { "kvt\0", 4 },
437     { "mlterm", 6 },
438     { "rxvt", 4 },
439     { "xterm", 5 },
440     { NULL, 0 }
441 };
442
443 struct getsize_closure {
444     int nums[2];
445     int nums_depth;
446     int nums_maxdepth;
447     int state;
448     const char *cp;
449     struct sudo_event *ev;
450     struct timespec timeout;
451 };
452
453 /* getsize states */
454 #define INITIAL         0x00
455 #define NEW_NUMBER      0x01
456 #define NUMBER          0x02
457 #define GOTSIZE         0x04
458 #define READCHAR        0x10
459
460 /*
461  * Callback for reading the terminal size response.
462  * We use an event for this to support timeouts.
463  */
464 static void
465 getsize_cb(int fd, int what, void *v)
466 {
467     struct getsize_closure *gc = v;
468     unsigned char ch = '\0';
469     debug_decl(getsize_cb, SUDO_DEBUG_UTIL)
470
471     for (;;) {
472         if (gc->cp[0] == '\0') {
473             gc->state = GOTSIZE;
474             goto done;
475         }
476         if (ISSET(gc->state, READCHAR)) {
477             ssize_t nread = read(ttyfd, &ch, 1);
478             switch (nread) {
479             case -1:
480                 if (errno == EAGAIN)
481                     goto another;
482                 /* FALLTHROUGH */
483             case 0:
484                 goto done;
485             default:
486                 CLR(gc->state, READCHAR);
487                 break;
488             }
489         }
490         switch (gc->state) {
491         case INITIAL:
492             if (ch == 0233 && gc->cp[0] == '\033') {
493                 /* meta escape, equivalent to ESC[ */
494                 ch = '[';
495                 gc->cp++;
496             }
497             if (gc->cp[0] == '%' && gc->cp[1] == 'd') {
498                 gc->state = NEW_NUMBER;
499                 continue;
500             }
501             if (gc->cp[0] != ch) {
502                 sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
503                     "got %d, expected %d", ch, gc->cp[0]);
504                 goto done;
505             }
506             sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
507                 "got %d", ch);
508             SET(gc->state, READCHAR);
509             gc->cp++;
510             break;
511         case NEW_NUMBER:
512             sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
513                 "parsing number");
514             if (!isdigit(ch))
515                 goto done;
516             gc->cp += 2;
517             if (gc->nums_depth > gc->nums_maxdepth)
518                 goto done;
519             gc->nums[gc->nums_depth] = 0;
520             gc->state = NUMBER;
521             /* FALLTHROUGH */
522         case NUMBER:
523             if (!isdigit(ch)) {
524                 /* done with number, reparse ch */
525                 sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
526                     "number %d (ch %d)", gc->nums[gc->nums_depth], ch);
527                 gc->nums_depth++;
528                 gc->state = INITIAL;
529                 continue;
530             }
531             sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
532                 "got %d", ch);
533             if (gc->nums[gc->nums_depth] > INT_MAX / 10)
534                 goto done;
535             gc->nums[gc->nums_depth] *= 10;
536             gc->nums[gc->nums_depth] += (ch - '0');
537             SET(gc->state, READCHAR);
538             break;
539         }
540     }
541
542 another:
543     if (sudo_ev_add(NULL, gc->ev, &gc->timeout, false) == -1)
544         sudo_fatal(U_("unable to add event to queue"));
545 done:
546     debug_return;
547 }
548
549
550 /*
551  * Get the terminal size using vt100 terminal escapes.
552  */
553 static bool
554 xterm_get_size(int *new_rows, int *new_cols)
555 {
556     struct sudo_event_base *evbase;
557     struct getsize_closure gc;
558     const char getsize_request[] = "\0337\033[r\033[999;999H\033[6n";
559     const char getsize_response[] = "\033[%d;%dR";
560     bool ret = false;
561     debug_decl(xterm_get_size, SUDO_DEBUG_UTIL)
562
563     /* request the terminal's size */
564     if (write(ttyfd, getsize_request, strlen(getsize_request)) == -1) {
565         sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
566             "%s: error writing xterm size request", __func__);
567         goto done;
568     }
569
570     /*
571      * Callback info for reading back the size with a 10 second timeout.
572      * We expect two numbers (rows and cols).
573      */
574     gc.state = INITIAL|READCHAR;
575     gc.nums_depth = 0;
576     gc.nums_maxdepth = 1;
577     gc.cp = getsize_response;
578     gc.timeout.tv_sec = 10;
579     gc.timeout.tv_nsec = 0;
580
581     /* Setup an event for reading the terminal size */
582     evbase = sudo_ev_base_alloc();
583     if (evbase == NULL)
584         sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
585     gc.ev = sudo_ev_alloc(ttyfd, SUDO_EV_READ, getsize_cb, &gc);
586     if (gc.ev == NULL)
587         sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
588
589     /* Read back terminal size response */
590     if (sudo_ev_add(evbase, gc.ev, &gc.timeout, false) == -1)
591         sudo_fatal(U_("unable to add event to queue"));
592     sudo_ev_dispatch(evbase);
593
594     if (gc.state == GOTSIZE) {
595         sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
596             "terminal size %d x %x", gc.nums[0], gc.nums[1]);
597         *new_rows = gc.nums[0];
598         *new_cols = gc.nums[1];
599         ret = true;
600     }
601
602     sudo_ev_base_free(evbase);
603     sudo_ev_free(gc.ev);
604
605 done:
606     debug_return_bool(ret);
607 }
608
609 /*
610  * Set the size of the text area to rows and cols.
611  * Depending on the terminal implementation, the window itself may
612  * or may not shrink to a smaller size.
613  */
614 static bool
615 xterm_set_size(int rows, int cols)
616 {
617     const char setsize_fmt[] = "\033[8;%d;%dt";
618     int len, new_rows, new_cols;
619     bool ret = false;
620     char buf[1024];
621     debug_decl(xterm_set_size, SUDO_DEBUG_UTIL)
622
623     /* XXX - save cursor and position restore after resizing */
624     len = snprintf(buf, sizeof(buf), setsize_fmt, rows, cols);
625     if (len < 0 || len >= ssizeof(buf)) {
626         /* not possible due to size of buf */
627         sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
628             "%s: internal error, buffer too small?", __func__);
629         goto done;
630     }
631     if (write(ttyfd, buf, strlen(buf)) == -1) {
632         sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
633             "%s: error writing xterm resize request", __func__);
634         goto done;
635     }
636     /* XXX - keyboard input will interfere with this */
637     if (!xterm_get_size(&new_rows, &new_cols))
638         goto done;
639     if (rows == new_rows && cols == new_cols)
640         ret = true;
641
642 done:
643     debug_return_bool(ret);
644 }
645
646 static void
647 setup_terminal(struct log_info *li, bool interactive, bool resize)
648 {
649     const char *term;
650     debug_decl(check_terminal, SUDO_DEBUG_UTIL)
651
652     fflush(stdout);
653
654     /* Open fd for /dev/tty and set to raw mode. */
655     if (interactive) {
656         ttyfd = open(_PATH_TTY, O_RDWR);
657         while (!sudo_term_raw(ttyfd, 1)) {
658             if (errno != EINTR)
659                 sudo_fatal(U_("unable to set tty to raw mode"));
660             kill(getpid(), SIGTTOU);
661         }
662     }
663
664     /* Find terminal size if the session has size info. */
665     if (li->rows == 0 && li->cols == 0) {
666         /* no tty size info, hope for the best... */
667         debug_return;
668     }
669
670     if (resize && ttyfd != -1) {
671         term = getenv("TERM");
672         if (term != NULL && *term != '\0') {
673             struct term_names *tn;
674
675             for (tn = compatible_terms; tn->name != NULL; tn++) {
676                 if (strncmp(term, tn->name, tn->len) == 0) {
677                     /* xterm-like terminals can resize themselves. */
678                     if (xterm_get_size(&terminal_rows, &terminal_cols))
679                         terminal_can_resize = true;
680                     break;
681                 }
682             }
683         }
684     }
685
686     if (!terminal_can_resize) {
687         /* either not xterm or not interactive */
688         sudo_get_ttysize(&terminal_rows, &terminal_cols);
689     }
690
691     if (li->rows == terminal_rows && li->cols == terminal_cols) {
692         /* nothing to change */
693         debug_return;
694     }
695
696     if (terminal_can_resize) {
697         /* session terminal size is different, try to resize ours */
698         if (xterm_set_size(li->rows, li->cols)) {
699             /* success */
700             sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
701                 "resized terminal to %d x %x", li->rows, li->cols);
702             terminal_was_resized = true;
703             debug_return;
704         }
705         /* resize failed, don't try again */
706         terminal_can_resize = false;
707     }
708
709     if (li->rows > terminal_rows || li->cols > terminal_cols) {
710         fputs(_("Warning: your terminal is too small to properly replay the log.\n"), stdout);
711         printf(_("Log geometry is %d x %d, your terminal's geometry is %d x %d."), li->rows, li->cols, terminal_rows, terminal_cols);
712     }
713     debug_return;
714 }
715
716 static void
717 resize_terminal(int rows, int cols)
718 {
719     debug_decl(resize_terminal, SUDO_DEBUG_UTIL)
720
721     if (terminal_can_resize) {
722         if (xterm_set_size(rows, cols))
723             terminal_was_resized = true;
724         else
725             terminal_can_resize = false;
726     }
727
728     debug_return;
729 }
730
731 static void
732 restore_terminal_size(void)
733 {
734     debug_decl(restore_terminal, SUDO_DEBUG_UTIL)
735
736     if (terminal_was_resized) {
737         /* We are still in raw mode, hence the carriage return. */
738         putchar('\r');
739         fputs(U_("Replay finished, press any key to restore the terminal."),
740             stdout);
741         fflush(stdout);
742         (void)getchar();
743         xterm_set_size(terminal_rows, terminal_cols);
744         putchar('\r');
745         putchar('\n');
746     }
747
748     debug_return;
749 }
750
751 /*
752  * Read the next record from the timing file and schedule a delay
753  * event with the specified timeout.
754  * Return 0 on success, 1 on EOF and -1 on error.
755  */
756 static int
757 read_timing_record(struct replay_closure *closure)
758 {
759     struct timespec timeout;
760     char buf[LINE_MAX];
761     debug_decl(read_timing_record, SUDO_DEBUG_UTIL)
762
763     /* Read next record from timing file. */
764     if (io_log_gets(io_log_files[IOFD_TIMING].fd, buf, sizeof(buf)) == NULL) {
765         /* EOF or error reading timing file, we are done. */
766         debug_return_int(io_log_eof(io_log_files[IOFD_TIMING].fd) ? 1 : -1);
767     }
768
769     /* Parse timing file record. */
770     buf[strcspn(buf, "\n")] = '\0';
771     if (!parse_timing(buf, &timeout, &closure->timing))
772         sudo_fatalx(U_("invalid timing file line: %s"), buf);
773
774     /* Record number bytes to read. */
775     /* XXX - remove timing->nbytes? */
776     if (closure->timing.event != IO_EVENT_WINSIZE &&
777         closure->timing.event != IO_EVENT_SUSPEND) {
778         closure->iobuf.len = 0;
779         closure->iobuf.off = 0;
780         closure->iobuf.lastc = '\0';
781         closure->iobuf.toread = closure->timing.u.nbytes;
782     }
783
784     /* Adjust delay using speed factor and max_delay. */
785     adjust_delay(&timeout, closure->timing.max_delay, speed_factor);
786
787     /* Schedule the delay event. */
788     if (sudo_ev_add(closure->evbase, closure->delay_ev, &timeout, false) == -1)
789         sudo_fatal(U_("unable to add event to queue"));
790
791     debug_return_int(0);
792 }
793
794 /*
795  * Read next timing record.
796  * Exits the event loop on EOF, breaks out on error.
797  */
798 static void
799 next_timing_record(struct replay_closure *closure)
800 {
801     debug_decl(next_timing_record, SUDO_DEBUG_UTIL)
802
803 again:
804     switch (read_timing_record(closure)) {
805     case 0:
806         /* success */
807         if (closure->timing.event == IO_EVENT_SUSPEND &&
808             closure->timing.u.signo == SIGCONT && !closure->suspend_wait) {
809             /* Ignore time spent suspended. */
810             goto again;
811         }
812         break;
813     case 1:
814         /* EOF */
815         sudo_ev_loopexit(closure->evbase);
816         break;
817     default:
818         /* error */
819         sudo_ev_loopbreak(closure->evbase);
820         break;
821     }
822     debug_return;
823 }
824
825 static bool
826 fill_iobuf(struct replay_closure *closure)
827 {
828     const size_t space = sizeof(closure->iobuf.buf) - closure->iobuf.len;
829     const struct timing_closure *timing = &closure->timing;
830     debug_decl(fill_iobuf, SUDO_DEBUG_UTIL)
831
832     if (closure->iobuf.toread != 0 && space != 0) {
833         const size_t len =
834             closure->iobuf.toread < space ? closure->iobuf.toread : space;
835         ssize_t nread = io_log_read(timing->fd,
836             closure->iobuf.buf + closure->iobuf.off, len);
837         if (nread <= 0) {
838             if (nread == 0) {
839                 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
840                     "%s: premature EOF, expected %u bytes",
841                     io_log_files[timing->event].suffix, closure->iobuf.toread);
842             } else {
843                 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
844                     "%s: read error", io_log_files[timing->event].suffix);
845             }
846             sudo_warnx(U_("unable to read %s"),
847                 io_log_files[timing->event].suffix);
848             debug_return_bool(false);
849         }
850         closure->iobuf.toread -= nread;
851         closure->iobuf.len += nread;
852     }
853
854     debug_return_bool(true);
855 }
856
857 /*
858  * Called when the inter-record delay has expired.
859  * Depending on the record type, either reads the next
860  * record or changes window size.
861  */
862 static void
863 delay_cb(int fd, int what, void *v)
864 {
865     struct replay_closure *closure = v;
866     struct timing_closure *timing = &closure->timing;
867     debug_decl(delay_cb, SUDO_DEBUG_UTIL)
868
869     switch (timing->event) {
870     case IO_EVENT_WINSIZE:
871         resize_terminal(timing->u.winsize.rows, timing->u.winsize.cols);
872         break;
873     case IO_EVENT_STDIN:
874         if (io_log_files[IOFD_STDIN].enabled)
875             timing->fd = io_log_files[IOFD_STDIN].fd;
876         break;
877     case IO_EVENT_STDOUT:
878         if (io_log_files[IOFD_STDOUT].enabled)
879             timing->fd = io_log_files[IOFD_STDOUT].fd;
880         break;
881     case IO_EVENT_STDERR:
882         if (io_log_files[IOFD_STDERR].enabled)
883             timing->fd = io_log_files[IOFD_STDERR].fd;
884         break;
885     case IO_EVENT_TTYIN:
886         if (io_log_files[IOFD_TTYIN].enabled)
887             timing->fd = io_log_files[IOFD_TTYIN].fd;
888         break;
889     case IO_EVENT_TTYOUT:
890         if (io_log_files[IOFD_TTYOUT].enabled)
891             timing->fd = io_log_files[IOFD_TTYOUT].fd;
892         break;
893     }
894
895     if (timing->fd.v != NULL) {
896         /* If the stream is open, enable the write event. */
897         if (sudo_ev_add(closure->evbase, closure->output_ev, NULL, false) == -1)
898             sudo_fatal(U_("unable to add event to queue"));
899     } else {
900         /* Not replaying, get the next timing record and continue. */
901         next_timing_record(closure);
902     }
903
904     debug_return;
905 }
906
907 static void
908 replay_closure_free(struct replay_closure *closure)
909 {
910     /*
911      * Free events and event base, then the closure itself.
912      */
913     sudo_ev_free(closure->delay_ev);
914     sudo_ev_free(closure->keyboard_ev);
915     sudo_ev_free(closure->output_ev);
916     sudo_ev_free(closure->sighup_ev);
917     sudo_ev_free(closure->sigint_ev);
918     sudo_ev_free(closure->sigquit_ev);
919     sudo_ev_free(closure->sigterm_ev);
920     sudo_ev_free(closure->sigtstp_ev);
921     sudo_ev_base_free(closure->evbase);
922     free(closure);
923 }
924
925 static void
926 signal_cb(int signo, int what, void *v)
927 {
928     struct replay_closure *closure = v;
929     debug_decl(signal_cb, SUDO_DEBUG_UTIL)
930
931     switch (signo) {
932     case SIGHUP:
933     case SIGINT:
934     case SIGQUIT:
935     case SIGTERM:
936         /* Free the event base and restore signal handlers. */
937         replay_closure_free(closure);
938
939         /* Restore the terminal and die. */
940         sudoreplay_cleanup();
941         kill(getpid(), signo);
942         break;
943     case SIGTSTP:
944         /* Ignore ^Z since we have no way to restore the screen. */
945         break;
946     }
947
948     debug_return;
949 }
950
951 static struct replay_closure *
952 replay_closure_alloc(struct timespec *max_delay, const char *decimal,
953     bool interactive, bool suspend_wait)
954 {
955     struct replay_closure *closure;
956     debug_decl(replay_closure_alloc, SUDO_DEBUG_UTIL)
957
958     if ((closure = calloc(1, sizeof(*closure))) == NULL)
959         debug_return_ptr(NULL);
960
961     closure->interactive = interactive;
962     closure->suspend_wait = suspend_wait;
963     closure->timing.max_delay = max_delay;
964     closure->timing.decimal = decimal;
965
966     /*
967      * Setup event base and delay, input and output events.
968      * If interactive, take input from and write to /dev/tty.
969      * If not interactive there is no input event.
970      */
971     closure->evbase = sudo_ev_base_alloc();
972     if (closure->evbase == NULL)
973         goto bad;
974     closure->delay_ev = sudo_ev_alloc(-1, SUDO_EV_TIMEOUT, delay_cb, closure);
975     if (closure->delay_ev == NULL)
976         goto bad;
977     if (interactive) {
978         closure->keyboard_ev = sudo_ev_alloc(ttyfd, SUDO_EV_READ|SUDO_EV_PERSIST,
979             read_keyboard, closure);
980         if (closure->keyboard_ev == NULL)
981             goto bad;
982         if (sudo_ev_add(closure->evbase, closure->keyboard_ev, NULL, false) == -1)
983             sudo_fatal(U_("unable to add event to queue"));
984     }
985     closure->output_ev = sudo_ev_alloc(interactive ? ttyfd : STDOUT_FILENO,
986         SUDO_EV_WRITE, write_output, closure);
987     if (closure->output_ev == NULL)
988         goto bad;
989
990     /*
991      * Setup signal events, we need to restore the terminal if killed.
992      */
993     closure->sighup_ev = sudo_ev_alloc(SIGHUP, SUDO_EV_SIGNAL, signal_cb,
994         closure);
995     if (closure->sighup_ev == NULL)
996         goto bad;
997     if (sudo_ev_add(closure->evbase, closure->sighup_ev, NULL, false) == -1)
998         sudo_fatal(U_("unable to add event to queue"));
999
1000     closure->sigint_ev = sudo_ev_alloc(SIGINT, SUDO_EV_SIGNAL, signal_cb,
1001         closure);
1002     if (closure->sigint_ev == NULL)
1003         goto bad;
1004     if (sudo_ev_add(closure->evbase, closure->sigint_ev, NULL, false) == -1)
1005         sudo_fatal(U_("unable to add event to queue"));
1006
1007     closure->sigquit_ev = sudo_ev_alloc(SIGQUIT, SUDO_EV_SIGNAL, signal_cb,
1008         closure);
1009     if (closure->sigquit_ev == NULL)
1010         goto bad;
1011     if (sudo_ev_add(closure->evbase, closure->sigquit_ev, NULL, false) == -1)
1012         sudo_fatal(U_("unable to add event to queue"));
1013
1014     closure->sigterm_ev = sudo_ev_alloc(SIGTERM, SUDO_EV_SIGNAL, signal_cb,
1015         closure);
1016     if (closure->sigterm_ev == NULL)
1017         goto bad;
1018     if (sudo_ev_add(closure->evbase, closure->sigterm_ev, NULL, false) == -1)
1019         sudo_fatal(U_("unable to add event to queue"));
1020
1021     closure->sigtstp_ev = sudo_ev_alloc(SIGTSTP, SUDO_EV_SIGNAL, signal_cb,
1022         closure);
1023     if (closure->sigtstp_ev == NULL)
1024         goto bad;
1025     if (sudo_ev_add(closure->evbase, closure->sigtstp_ev, NULL, false) == -1)
1026         sudo_fatal(U_("unable to add event to queue"));
1027
1028     debug_return_ptr(closure);
1029 bad:
1030     replay_closure_free(closure);
1031     debug_return_ptr(NULL);
1032 }
1033
1034 static int
1035 replay_session(struct timespec *max_delay, const char *decimal,
1036     bool interactive, bool suspend_wait)
1037 {
1038     struct replay_closure *closure;
1039     int ret = 0;
1040     debug_decl(replay_session, SUDO_DEBUG_UTIL)
1041
1042     /* Allocate the delay closure and read the first timing record. */
1043     closure = replay_closure_alloc(max_delay, decimal, interactive,
1044         suspend_wait);
1045     if (read_timing_record(closure) != 0) {
1046         ret = 1;
1047         goto done;
1048     }
1049
1050     /* Run event loop. */
1051     sudo_ev_dispatch(closure->evbase);
1052     if (sudo_ev_got_break(closure->evbase))
1053         ret = 1;
1054
1055 done:
1056     /* Clean up and return. */
1057     replay_closure_free(closure);
1058     debug_return_int(ret);
1059 }
1060
1061 static int
1062 open_io_fd(char *path, int len, struct io_log_file *iol)
1063 {
1064     debug_decl(open_io_fd, SUDO_DEBUG_UTIL)
1065
1066     if (!iol->enabled)
1067         debug_return_int(0);
1068
1069     path[len] = '\0';
1070     strlcat(path, iol->suffix, PATH_MAX);
1071 #ifdef HAVE_ZLIB_H
1072     iol->fd.g = gzopen(path, "r");
1073 #else
1074     iol->fd.f = fopen(path, "r");
1075 #endif
1076     if (iol->fd.v == NULL) {
1077         iol->enabled = false;
1078         debug_return_int(-1);
1079     }
1080     debug_return_int(0);
1081 }
1082
1083 /*
1084  * Write the I/O buffer.
1085  */
1086 static void
1087 write_output(int fd, int what, void *v)
1088 {
1089     struct replay_closure *closure = v;
1090     const struct timing_closure *timing = &closure->timing;
1091     struct io_buffer *iobuf = &closure->iobuf;
1092     unsigned iovcnt = 1;
1093     struct iovec iov[2];
1094     bool added_cr = false;
1095     size_t nbytes, nwritten;
1096     debug_decl(write_output, SUDO_DEBUG_UTIL)
1097
1098     /* Refill iobuf if there is more to read and buf is empty. */
1099     if (!fill_iobuf(closure)) {
1100         sudo_ev_loopbreak(closure->evbase);
1101         debug_return;
1102     }
1103
1104     nbytes = iobuf->len - iobuf->off;
1105     iov[0].iov_base = iobuf->buf + iobuf->off;
1106     iov[0].iov_len = nbytes;
1107
1108     if (closure->interactive &&
1109         (timing->event == IO_EVENT_STDOUT || timing->event == IO_EVENT_STDERR)) {
1110         char *nl;
1111
1112         /*
1113          * We may need to insert a carriage return before the newline.
1114          * Note that the carriage return may have already been written.
1115          */
1116         nl = memchr(iov[0].iov_base, '\n', iov[0].iov_len);
1117         if (nl != NULL) {
1118             size_t len = (size_t)(nl - (char *)iov[0].iov_base);
1119             if ((nl == iov[0].iov_base && iobuf->lastc != '\r') ||
1120                 (nl != iov[0].iov_base && nl[-1] != '\r')) {
1121                 iov[0].iov_len = len;
1122                 iov[1].iov_base = "\r\n";
1123                 iov[1].iov_len = 2;
1124                 iovcnt = 2;
1125                 nbytes = iov[0].iov_len + iov[1].iov_len;
1126                 added_cr = true;
1127             }
1128         }
1129     }
1130
1131     nwritten = writev(fd, iov, iovcnt);
1132     switch ((ssize_t)nwritten) {
1133     case -1:
1134         if (errno != EINTR && errno != EAGAIN)
1135             sudo_fatal(U_("unable to write to %s"), "stdout");
1136         break;
1137     case 0:
1138         /* Should not happen. */
1139         break;
1140     default:
1141         if (added_cr && nwritten >= nbytes - 1) {
1142             /* The last char written was either '\r' or '\n'. */
1143             iobuf->lastc = nwritten == nbytes ? '\n' : '\r';
1144         } else {
1145             /* Stash the last char written. */
1146             iobuf->lastc = *((char *)iov[0].iov_base + nwritten);
1147         }
1148         if (added_cr) {
1149             /* Subtract one for the carriage return we added above. */
1150             nwritten--;
1151         }
1152         iobuf->off += nwritten;
1153         break;
1154     }
1155
1156     if (iobuf->off == iobuf->len) {
1157         /* Write complete, go to next timing entry if possible. */
1158         switch (read_timing_record(closure)) {
1159         case 0:
1160             /* success */
1161             break;
1162         case 1:
1163             /* EOF */
1164             sudo_ev_loopexit(closure->evbase);
1165             break;
1166         default:
1167             /* error */
1168             sudo_ev_loopbreak(closure->evbase);
1169             break;
1170         }
1171     } else {
1172         /* Reschedule event to write remainder. */
1173         if (sudo_ev_add(NULL, closure->output_ev, NULL, false) == -1)
1174             sudo_fatal(U_("unable to add event to queue"));
1175     }
1176     debug_return;
1177 }
1178
1179 /*
1180  * Build expression list from search args
1181  */
1182 static int
1183 parse_expr(struct search_node_list *head, char *argv[], bool sub_expr)
1184 {
1185     bool or = false, not = false;
1186     struct search_node *sn;
1187     char type, **av;
1188     debug_decl(parse_expr, SUDO_DEBUG_UTIL)
1189
1190     for (av = argv; *av != NULL; av++) {
1191         switch (av[0][0]) {
1192         case 'a': /* and (ignore) */
1193             if (strncmp(*av, "and", strlen(*av)) != 0)
1194                 goto bad;
1195             continue;
1196         case 'o': /* or */
1197             if (strncmp(*av, "or", strlen(*av)) != 0)
1198                 goto bad;
1199             or = true;
1200             continue;
1201         case '!': /* negate */
1202             if (av[0][1] != '\0')
1203                 goto bad;
1204             not = true;
1205             continue;
1206         case 'c': /* cwd or command */
1207             if (av[0][1] == '\0')
1208                 sudo_fatalx(U_("ambiguous expression \"%s\""), *av);
1209             if (strncmp(*av, "cwd", strlen(*av)) == 0)
1210                 type = ST_CWD;
1211             else if (strncmp(*av, "command", strlen(*av)) == 0)
1212                 type = ST_PATTERN;
1213             else
1214                 goto bad;
1215             break;
1216         case 'f': /* from date */
1217             if (strncmp(*av, "fromdate", strlen(*av)) != 0)
1218                 goto bad;
1219             type = ST_FROMDATE;
1220             break;
1221         case 'g': /* runas group */
1222             if (strncmp(*av, "group", strlen(*av)) != 0)
1223                 goto bad;
1224             type = ST_RUNASGROUP;
1225             break;
1226         case 'r': /* runas user */
1227             if (strncmp(*av, "runas", strlen(*av)) != 0)
1228                 goto bad;
1229             type = ST_RUNASUSER;
1230             break;
1231         case 't': /* tty or to date */
1232             if (av[0][1] == '\0')
1233                 sudo_fatalx(U_("ambiguous expression \"%s\""), *av);
1234             if (strncmp(*av, "todate", strlen(*av)) == 0)
1235                 type = ST_TODATE;
1236             else if (strncmp(*av, "tty", strlen(*av)) == 0)
1237                 type = ST_TTY;
1238             else
1239                 goto bad;
1240             break;
1241         case 'u': /* user */
1242             if (strncmp(*av, "user", strlen(*av)) != 0)
1243                 goto bad;
1244             type = ST_USER;
1245             break;
1246         case '(': /* start sub-expression */
1247             if (av[0][1] != '\0')
1248                 goto bad;
1249             type = ST_EXPR;
1250             break;
1251         case ')': /* end sub-expression */
1252             if (av[0][1] != '\0')
1253                 goto bad;
1254             if (!sub_expr)
1255                 sudo_fatalx(U_("unmatched ')' in expression"));
1256             debug_return_int(av - argv + 1);
1257         default:
1258         bad:
1259             sudo_fatalx(U_("unknown search term \"%s\""), *av);
1260             /* NOTREACHED */
1261         }
1262
1263         /* Allocate new search node */
1264         if ((sn = calloc(1, sizeof(*sn))) == NULL)
1265             sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
1266         sn->type = type;
1267         sn->or = or;
1268         sn->negated = not;
1269         if (type == ST_EXPR) {
1270             STAILQ_INIT(&sn->u.expr);
1271             av += parse_expr(&sn->u.expr, av + 1, true);
1272         } else {
1273             if (*(++av) == NULL)
1274                 sudo_fatalx(U_("%s requires an argument"), av[-1]);
1275             if (type == ST_PATTERN) {
1276                 if (regcomp(&sn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0)
1277                     sudo_fatalx(U_("invalid regular expression: %s"), *av);
1278             } else if (type == ST_TODATE || type == ST_FROMDATE) {
1279                 sn->u.tstamp = get_date(*av);
1280                 if (sn->u.tstamp == -1)
1281                     sudo_fatalx(U_("could not parse date \"%s\""), *av);
1282             } else {
1283                 sn->u.ptr = *av;
1284             }
1285         }
1286         not = or = false; /* reset state */
1287         STAILQ_INSERT_TAIL(head, sn, entries);
1288     }
1289     if (sub_expr)
1290         sudo_fatalx(U_("unmatched '(' in expression"));
1291     if (or)
1292         sudo_fatalx(U_("illegal trailing \"or\""));
1293     if (not)
1294         sudo_fatalx(U_("illegal trailing \"!\""));
1295
1296     debug_return_int(av - argv);
1297 }
1298
1299 static bool
1300 match_expr(struct search_node_list *head, struct log_info *log, bool last_match)
1301 {
1302     struct search_node *sn;
1303     bool res = false, matched = last_match;
1304     int rc;
1305     debug_decl(match_expr, SUDO_DEBUG_UTIL)
1306
1307     STAILQ_FOREACH(sn, head, entries) {
1308         switch (sn->type) {
1309         case ST_EXPR:
1310             res = match_expr(&sn->u.expr, log, matched);
1311             break;
1312         case ST_CWD:
1313             res = strcmp(sn->u.cwd, log->cwd) == 0;
1314             break;
1315         case ST_TTY:
1316             res = strcmp(sn->u.tty, log->tty) == 0;
1317             break;
1318         case ST_RUNASGROUP:
1319             if (log->runas_group != NULL)
1320                 res = strcmp(sn->u.runas_group, log->runas_group) == 0;
1321             break;
1322         case ST_RUNASUSER:
1323             res = strcmp(sn->u.runas_user, log->runas_user) == 0;
1324             break;
1325         case ST_USER:
1326             res = strcmp(sn->u.user, log->user) == 0;
1327             break;
1328         case ST_PATTERN:
1329             rc = regexec(&sn->u.cmdre, log->cmd, 0, NULL, 0);
1330             if (rc && rc != REG_NOMATCH) {
1331                 char buf[BUFSIZ];
1332                 regerror(rc, &sn->u.cmdre, buf, sizeof(buf));
1333                 sudo_fatalx("%s", buf);
1334             }
1335             res = rc == REG_NOMATCH ? 0 : 1;
1336             break;
1337         case ST_FROMDATE:
1338             res = log->tstamp >= sn->u.tstamp;
1339             break;
1340         case ST_TODATE:
1341             res = log->tstamp <= sn->u.tstamp;
1342             break;
1343         default:
1344             sudo_fatalx(U_("unknown search type %d"), sn->type);
1345             /* NOTREACHED */
1346         }
1347         if (sn->negated)
1348             res = !res;
1349         matched = sn->or ? (res || last_match) : (res && last_match);
1350         last_match = matched;
1351     }
1352     debug_return_bool(matched);
1353 }
1354
1355 static int
1356 list_session(char *logfile, regex_t *re, const char *user, const char *tty)
1357 {
1358     char idbuf[7], *idstr, *cp;
1359     const char *timestr;
1360     struct log_info *li;
1361     int ret = -1;
1362     debug_decl(list_session, SUDO_DEBUG_UTIL)
1363
1364     if ((li = parse_logfile(logfile)) == NULL)
1365         goto done;
1366
1367     /* Match on search expression if there is one. */
1368     if (!STAILQ_EMPTY(&search_expr) && !match_expr(&search_expr, li, true))
1369         goto done;
1370
1371     /* Convert from /var/log/sudo-sessions/00/00/01/log to 000001 */
1372     cp = logfile + strlen(session_dir) + 1;
1373     if (IS_IDLOG(cp)) {
1374         idbuf[0] = cp[0];
1375         idbuf[1] = cp[1];
1376         idbuf[2] = cp[3];
1377         idbuf[3] = cp[4];
1378         idbuf[4] = cp[6];
1379         idbuf[5] = cp[7];
1380         idbuf[6] = '\0';
1381         idstr = idbuf;
1382     } else {
1383         /* Not an id, just use the iolog_file portion. */
1384         cp[strlen(cp) - 4] = '\0';
1385         idstr = cp;
1386     }
1387     /* XXX - print rows + cols? */
1388     timestr = get_timestr(li->tstamp, 1);
1389     printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ",
1390         timestr ? timestr : "invalid date",
1391         li->user, li->tty, li->cwd, li->runas_user);
1392     if (li->runas_group)
1393         printf("GROUP=%s ; ", li->runas_group);
1394     printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd);
1395
1396     ret = 0;
1397
1398 done:
1399     free_log_info(li);
1400     debug_return_int(ret);
1401 }
1402
1403 static int
1404 session_compare(const void *v1, const void *v2)
1405 {
1406     const char *s1 = *(const char **)v1;
1407     const char *s2 = *(const char **)v2;
1408     return strcmp(s1, s2);
1409 }
1410
1411 /* XXX - always returns 0, calls sudo_fatal() on failure */
1412 static int
1413 find_sessions(const char *dir, regex_t *re, const char *user, const char *tty)
1414 {
1415     DIR *d;
1416     struct dirent *dp;
1417     struct stat sb;
1418     size_t sdlen, sessions_len = 0, sessions_size = 0;
1419     unsigned int i;
1420     int len;
1421     char pathbuf[PATH_MAX], **sessions = NULL;
1422 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
1423     bool checked_type = true;
1424 #else
1425     const bool checked_type = false;
1426 #endif
1427     debug_decl(find_sessions, SUDO_DEBUG_UTIL)
1428
1429     d = opendir(dir);
1430     if (d == NULL)
1431         sudo_fatal(U_("unable to open %s"), dir);
1432
1433     /* XXX - would be faster to use openat() and relative names */
1434     sdlen = strlcpy(pathbuf, dir, sizeof(pathbuf));
1435     if (sdlen + 1 >= sizeof(pathbuf)) {
1436         errno = ENAMETOOLONG;
1437         sudo_fatal("%s/", dir);
1438     }
1439     pathbuf[sdlen++] = '/';
1440     pathbuf[sdlen] = '\0';
1441
1442     /* Store potential session dirs for sorting. */
1443     while ((dp = readdir(d)) != NULL) {
1444         /* Skip "." and ".." */
1445         if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
1446             (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
1447             continue;
1448 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
1449         if (checked_type) {
1450             if (dp->d_type != DT_DIR) {
1451                 /* Not all file systems support d_type. */
1452                 if (dp->d_type != DT_UNKNOWN)
1453                     continue;
1454                 checked_type = false;
1455             }
1456         }
1457 #endif
1458
1459         /* Add name to session list. */
1460         if (sessions_len + 1 > sessions_size) {
1461             if (sessions_size == 0)
1462                 sessions_size = 36 * 36 / 2;
1463             sessions = reallocarray(sessions, sessions_size, 2 * sizeof(char *));
1464             if (sessions == NULL)
1465                 sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
1466             sessions_size *= 2;
1467         }
1468         if ((sessions[sessions_len] = strdup(dp->d_name)) == NULL)
1469             sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
1470         sessions_len++;
1471     }
1472     closedir(d);
1473
1474     /* Sort and list the sessions. */
1475     if (sessions != NULL) {
1476         qsort(sessions, sessions_len, sizeof(char *), session_compare);
1477         for (i = 0; i < sessions_len; i++) {
1478             len = snprintf(&pathbuf[sdlen], sizeof(pathbuf) - sdlen,
1479                 "%s/log", sessions[i]);
1480             if (len < 0 || (size_t)len >= sizeof(pathbuf) - sdlen) {
1481                 errno = ENAMETOOLONG;
1482                 sudo_fatal("%s/%s/log", dir, sessions[i]);
1483             }
1484             free(sessions[i]);
1485
1486             /* Check for dir with a log file. */
1487             if (lstat(pathbuf, &sb) == 0 && S_ISREG(sb.st_mode)) {
1488                 list_session(pathbuf, re, user, tty);
1489             } else {
1490                 /* Strip off "/log" and recurse if a dir. */
1491                 pathbuf[sdlen + len - 4] = '\0';
1492                 if (checked_type ||
1493                     (lstat(pathbuf, &sb) == 0 && S_ISDIR(sb.st_mode)))
1494                     find_sessions(pathbuf, re, user, tty);
1495             }
1496         }
1497         free(sessions);
1498     }
1499
1500     debug_return_int(0);
1501 }
1502
1503 /* XXX - always returns 0, calls sudo_fatal() on failure */
1504 static int
1505 list_sessions(int argc, char **argv, const char *pattern, const char *user,
1506     const char *tty)
1507 {
1508     regex_t rebuf, *re = NULL;
1509     debug_decl(list_sessions, SUDO_DEBUG_UTIL)
1510
1511     /* Parse search expression if present */
1512     parse_expr(&search_expr, argv, false);
1513
1514     /* optional regex */
1515     if (pattern) {
1516         re = &rebuf;
1517         if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0)
1518             sudo_fatalx(U_("invalid regular expression: %s"), pattern);
1519     }
1520
1521     debug_return_int(find_sessions(session_dir, re, user, tty));
1522 }
1523
1524 /*
1525  * Check keyboard for ' ', '<', '>', return
1526  * pause, slow, fast, next
1527  */
1528 static void
1529 read_keyboard(int fd, int what, void *v)
1530 {
1531     struct replay_closure *closure = v;
1532     static bool paused = false;
1533     struct timespec ts;
1534     ssize_t nread;
1535     char ch;
1536     debug_decl(read_keyboard, SUDO_DEBUG_UTIL)
1537
1538     nread = read(fd, &ch, 1);
1539     switch (nread) {
1540     case -1:
1541         if (errno != EINTR && errno != EAGAIN)
1542             sudo_fatal(U_("unable to read %s"), "stdin");
1543         break;
1544     case 0:
1545         /* Ignore EOF. */
1546         break;
1547     default:
1548         if (paused) {
1549             /* Any key will unpause, run the delay callback directly. */
1550             paused = false;
1551             delay_cb(-1, SUDO_EV_TIMEOUT, closure);
1552             debug_return;
1553         }
1554         switch (ch) {
1555         case ' ':
1556             paused = true;
1557             /* Disable the delay event until we unpause. */
1558             sudo_ev_del(closure->evbase, closure->delay_ev);
1559             break;
1560         case '<':
1561             speed_factor /= 2;
1562             sudo_ev_get_timeleft(closure->delay_ev, &ts);
1563             if (sudo_timespecisset(&ts)) {
1564                 /* Double remaining timeout. */
1565                 ts.tv_sec *= 2;
1566                 ts.tv_nsec *= 2;
1567                 if (ts.tv_nsec >= 1000000000) {
1568                     ts.tv_sec++;
1569                     ts.tv_nsec -= 1000000000;
1570                 }
1571                 if (sudo_ev_add(NULL, closure->delay_ev, &ts, false) == -1) {
1572                     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1573                         "failed to double remaining delay timeout");
1574                 }
1575             }
1576             break;
1577         case '>':
1578             speed_factor *= 2;
1579             sudo_ev_get_timeleft(closure->delay_ev, &ts);
1580             if (sudo_timespecisset(&ts)) {
1581                 /* Halve remaining timeout. */
1582                 if (ts.tv_sec & 1)
1583                     ts.tv_nsec += 500000000;
1584                 ts.tv_sec /= 2;
1585                 ts.tv_nsec /= 2;
1586                 if (sudo_ev_add(NULL, closure->delay_ev, &ts, false) == -1) {
1587                     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1588                         "failed to halve remaining delay timeout");
1589                 }
1590             }
1591             break;
1592         case '\r':
1593         case '\n':
1594             /* Cancel existing delay, run callback directly. */
1595             sudo_ev_del(closure->evbase, closure->delay_ev);
1596             delay_cb(-1, SUDO_EV_TIMEOUT, closure);
1597             break;
1598         default:
1599             /* Unknown key, nothing to do. */
1600             break;
1601         }
1602         break;
1603     }
1604     debug_return;
1605 }
1606
1607 static void
1608 usage(int fatal)
1609 {
1610     fprintf(fatal ? stderr : stdout,
1611         _("usage: %s [-hnRS] [-d dir] [-m num] [-s num] ID\n"),
1612         getprogname());
1613     fprintf(fatal ? stderr : stdout,
1614         _("usage: %s [-h] [-d dir] -l [search expression]\n"),
1615         getprogname());
1616     if (fatal)
1617         exit(1);
1618 }
1619
1620 static void
1621 help(void)
1622 {
1623     (void) printf(_("%s - replay sudo session logs\n\n"), getprogname());
1624     usage(0);
1625     (void) puts(_("\nOptions:\n"
1626         "  -d, --directory=dir    specify directory for session logs\n"
1627         "  -f, --filter=filter    specify which I/O type(s) to display\n"
1628         "  -h, --help             display help message and exit\n"
1629         "  -l, --list             list available session IDs, with optional expression\n"
1630         "  -m, --max-wait=num     max number of seconds to wait between events\n"
1631         "  -n, --non-interactive  no prompts, session is sent to the standard output\n"
1632         "  -R, --no-resize        do not attempt to re-size the terminal\n"
1633         "  -S, --suspend-wait     wait while the command was suspended\n"
1634         "  -s, --speed=num        speed up or slow down output\n"
1635         "  -V, --version          display version information and exit"));
1636     exit(0);
1637 }
1638
1639 /*
1640  * Cleanup hook for sudo_fatal()/sudo_fatalx()
1641   */
1642 static void
1643 sudoreplay_cleanup(void)
1644 {
1645     restore_terminal_size();
1646     sudo_term_restore(ttyfd, false);
1647 }