fix up changelog
[debian/sudo] / sudoreplay.c
1 /*
2  * Copyright (c) 2009-2010 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 #include <config.h>
18
19 #include <sys/types.h>
20 #include <sys/param.h>
21 #include <sys/stat.h>
22 #include <sys/time.h>
23 #include <sys/wait.h>
24 #include <sys/ioctl.h>
25 #ifdef HAVE_SYS_SELECT_H
26 #include <sys/select.h>
27 #endif /* HAVE_SYS_SELECT_H */
28 #include <stdio.h>
29 #ifdef STDC_HEADERS
30 # include <stdlib.h>
31 # include <stddef.h>
32 #else
33 # ifdef HAVE_STDLIB_H
34 #  include <stdlib.h>
35 # endif
36 #endif /* STDC_HEADERS */
37 #ifdef HAVE_STRING_H
38 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
39 #  include <memory.h>
40 # endif
41 # include <string.h>
42 #endif /* HAVE_STRING_H */
43 #ifdef HAVE_STRINGS_H
44 # include <strings.h>
45 #endif /* HAVE_STRINGS_H */
46 #ifdef HAVE_UNISTD_H
47 # include <unistd.h>
48 #endif /* HAVE_UNISTD_H */
49 #if TIME_WITH_SYS_TIME
50 # include <time.h>
51 #endif
52 #ifndef HAVE_TIMESPEC
53 # include <emul/timespec.h>
54 #endif
55 #include <ctype.h>
56 #include <errno.h>
57 #include <limits.h>
58 #include <fcntl.h>
59 #ifdef HAVE_DIRENT_H
60 # include <dirent.h>
61 # define NAMLEN(dirent) strlen((dirent)->d_name)
62 #else
63 # define dirent direct
64 # define NAMLEN(dirent) (dirent)->d_namlen
65 # ifdef HAVE_SYS_NDIR_H
66 #  include <sys/ndir.h>
67 # endif
68 # ifdef HAVE_SYS_DIR_H
69 #  include <sys/dir.h>
70 # endif
71 # ifdef HAVE_NDIR_H
72 #  include <ndir.h>
73 # endif
74 #endif
75 #ifdef HAVE_REGCOMP
76 # include <regex.h>
77 #endif
78 #ifdef HAVE_ZLIB_H
79 # include <zlib.h>
80 #endif
81 #ifdef HAVE_SETLOCALE
82 # include <locale.h>
83 #endif
84 #include <signal.h>
85
86 #include <pathnames.h>
87
88 #include "compat.h"
89 #include "alloc.h"
90 #include "error.h"
91 #include "missing.h"
92
93 #ifndef LINE_MAX
94 # define LINE_MAX 2048
95 #endif
96
97 /* Must match the defines in iolog.c */
98 #define IOFD_STDIN      0
99 #define IOFD_STDOUT     1
100 #define IOFD_STDERR     2
101 #define IOFD_TTYIN      3
102 #define IOFD_TTYOUT     4
103 #define IOFD_TIMING     5
104 #define IOFD_MAX        6
105
106 /* Bitmap of iofds to be replayed */
107 unsigned int replay_filter = (1 << IOFD_STDOUT) | (1 << IOFD_STDERR) |
108                              (1 << IOFD_TTYOUT);
109
110 /* For getopt(3) */
111 extern char *optarg;
112 extern int optind;
113
114 int Argc;
115 char **Argv;
116
117 union io_fd {
118     FILE *f;
119 #ifdef HAVE_ZLIB_H
120     gzFile g;
121 #endif
122     void *v;
123 };
124
125 /*
126  * Info present in the I/O log file
127  */
128 struct log_info {
129     char *cwd;
130     char *user;
131     char *runas_user;
132     char *runas_group;
133     char *tty;
134     char *cmd;
135     time_t tstamp;
136 };
137
138 /*
139  * Handle expressions like:
140  * ( user millert or user root ) and tty console and command /bin/sh
141  */
142 struct search_node {
143     struct search_node *next;
144 #define ST_EXPR         1
145 #define ST_TTY          2
146 #define ST_USER         3
147 #define ST_PATTERN      4
148 #define ST_RUNASUSER    5
149 #define ST_RUNASGROUP   6
150 #define ST_FROMDATE     7
151 #define ST_TODATE       8
152 #define ST_CWD          9
153     char type;
154     char negated;
155     char or;
156     char pad;
157     union {
158 #ifdef HAVE_REGCOMP
159         regex_t cmdre;
160 #endif
161         time_t tstamp;
162         char *cwd;
163         char *tty;
164         char *user;
165         char *pattern;
166         char *runas_group;
167         char *runas_user;
168         struct search_node *expr;
169         void *ptr;
170     } u;
171 } *search_expr;
172
173 #define STACK_NODE_SIZE 32
174 static struct search_node *node_stack[32];
175 static int stack_top;
176
177 static const char *session_dir = _PATH_SUDO_IO_LOGDIR;
178
179 static union io_fd io_fds[IOFD_MAX];
180 static const char *io_fnames[IOFD_MAX] = {
181     "/stdin",
182     "/stdout",
183     "/stderr",
184     "/ttyin",
185     "/ttyout",
186     "/timing"
187 };
188
189 extern time_t get_date __P((char *));
190 extern char *get_timestr __P((time_t, int));
191 extern int term_raw __P((int, int));
192 extern int term_restore __P((int, int));
193 extern void zero_bytes __P((volatile void *, size_t));
194 void cleanup __P((int));
195
196 static int list_sessions __P((int, char **, const char *, const char *, const char *));
197 static int parse_expr __P((struct search_node **, char **));
198 static void check_input __P((int, double *));
199 static void delay __P((double));
200 static void usage __P((void));
201 static void *open_io_fd __P((char *pathbuf, int len, const char *suffix));
202 static int parse_timing __P((const char *buf, const char *decimal, int *idx, double *seconds, size_t *nbytes));
203
204 #ifdef HAVE_REGCOMP
205 # define REGEX_T        regex_t
206 #else
207 # define REGEX_T        char
208 #endif
209
210 #define VALID_ID(s) (isalnum((unsigned char)(s)[0]) && \
211     isalnum((unsigned char)(s)[1]) && isalnum((unsigned char)(s)[2]) && \
212     isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
213     isalnum((unsigned char)(s)[5]) && (s)[6] == '\0')
214
215 int
216 main(argc, argv)
217     int argc;
218     char *argv[];
219 {
220     int ch, idx, plen, nready, interactive = 0, listonly = 0;
221     const char *id, *user = NULL, *pattern = NULL, *tty = NULL, *decimal = ".";
222     char path[PATH_MAX], buf[LINE_MAX], *cp, *ep;
223     double seconds, to_wait, speed = 1.0, max_wait = 0;
224     FILE *lfile;
225     fd_set *fdsw;
226     sigaction_t sa;
227     size_t len, nbytes, nread, off;
228     ssize_t nwritten;
229
230     Argc = argc;
231     Argv = argv;
232
233 #ifdef HAVE_SETLOCALE
234     setlocale(LC_ALL, "");
235     decimal = localeconv()->decimal_point;
236 #endif
237
238     while ((ch = getopt(argc, argv, "d:f:lm:s:V")) != -1) {
239         switch(ch) {
240         case 'd':
241             session_dir = optarg;
242             break;
243         case 'f':
244             /* Set the replay filter. */
245             replay_filter = 0;
246             for (cp = strtok(optarg, ","); cp; cp = strtok(NULL, ",")) {
247                 if (strcmp(cp, "stdout") == 0)
248                     SET(replay_filter, 1 << IOFD_STDOUT);
249                 else if (strcmp(cp, "stderr") == 0)
250                     SET(replay_filter, 1 << IOFD_STDERR);
251                 else if (strcmp(cp, "ttyout") == 0)
252                     SET(replay_filter, 1 << IOFD_TTYOUT);
253                 else
254                     errorx(1, "invalid filter option: %s", optarg);
255             }
256             break;
257         case 'l':
258             listonly = 1;
259             break;
260         case 'm':
261             errno = 0;
262             max_wait = strtod(optarg, &ep);
263             if (*ep != '\0' || errno != 0)
264                 errorx(1, "invalid max wait: %s", optarg);
265             break;
266         case 's':
267             errno = 0;
268             speed = strtod(optarg, &ep);
269             if (*ep != '\0' || errno != 0)
270                 errorx(1, "invalid speed factor: %s", optarg);
271             break;
272         case 'V':
273             (void) printf("%s version %s\n", getprogname(), PACKAGE_VERSION);
274             exit(0);
275         default:
276             usage();
277             /* NOTREACHED */
278         }
279
280     }
281     argc -= optind;
282     argv += optind;
283
284     if (listonly)
285         exit(list_sessions(argc, argv, pattern, user, tty));
286
287     if (argc != 1)
288         usage();
289
290     /* 6 digit ID in base 36, e.g. 01G712AB */
291     id = argv[0];
292     if (!VALID_ID(id))
293         errorx(1, "invalid ID %s", id);
294
295     plen = snprintf(path, sizeof(path), "%s/%.2s/%.2s/%.2s/timing",
296         session_dir, id, &id[2], &id[4]);
297     if (plen <= 0 || plen >= sizeof(path))
298         errorx(1, "%s/%.2s/%.2s/%.2s/%.2s/timing: %s", session_dir,
299             id, &id[2], &id[4], strerror(ENAMETOOLONG));
300     plen -= 7;
301
302     /* Open files for replay, applying replay filter for the -f flag. */
303     for (idx = 0; idx < IOFD_MAX; idx++) {
304         if (ISSET(replay_filter, 1 << idx) || idx == IOFD_TIMING) {
305             io_fds[idx].v = open_io_fd(path, plen, io_fnames[idx]);
306             if (io_fds[idx].v == NULL)
307                 error(1, "unable to open %s", path);
308         }
309     }
310
311     /* Read log file. */
312     path[plen] = '\0';
313     strlcat(path, "/log", sizeof(path));
314     lfile = fopen(path, "r");
315     if (lfile == NULL)
316         error(1, "unable to open %s", path);
317     cp = NULL;
318     len = 0;
319     getline(&cp, &len, lfile); /* log */
320     getline(&cp, &len, lfile); /* cwd */
321     getline(&cp, &len, lfile); /* command */
322     printf("Replaying sudo session: %s", cp);
323     free(cp);
324     fclose(lfile);
325
326     fflush(stdout);
327     zero_bytes(&sa, sizeof(sa));
328     sigemptyset(&sa.sa_mask);
329     sa.sa_flags = SA_RESETHAND;
330     sa.sa_handler = cleanup;
331     (void) sigaction(SIGINT, &sa, NULL);
332     (void) sigaction(SIGKILL, &sa, NULL);
333     (void) sigaction(SIGTERM, &sa, NULL);
334     (void) sigaction(SIGHUP, &sa, NULL);
335     sa.sa_flags = SA_RESTART;
336     sa.sa_handler = SIG_IGN;
337     (void) sigaction(SIGTSTP, &sa, NULL);
338     (void) sigaction(SIGQUIT, &sa, NULL);
339
340     /* XXX - read user input from /dev/tty and set STDOUT to raw if not a pipe */
341     /* Set stdin to raw mode if it is a tty */
342     interactive = isatty(STDIN_FILENO);
343     if (interactive) {
344         ch = fcntl(STDIN_FILENO, F_GETFL, 0);
345         if (ch != -1)
346             (void) fcntl(STDIN_FILENO, F_SETFL, ch | O_NONBLOCK);
347         if (!term_raw(STDIN_FILENO, 1))
348             error(1, "cannot set tty to raw mode");
349     }
350     fdsw = (fd_set *)emalloc2(howmany(STDOUT_FILENO + 1, NFDBITS),
351         sizeof(fd_mask));
352
353     /*
354      * Timing file consists of line of the format: "%f %d\n"
355      */
356 #ifdef HAVE_ZLIB_H
357     while (gzgets(io_fds[IOFD_TIMING].g, buf, sizeof(buf)) != NULL) {
358 #else
359     while (fgets(buf, sizeof(buf), io_fds[IOFD_TIMING].f) != NULL) {
360 #endif
361         if (!parse_timing(buf, decimal, &idx, &seconds, &nbytes))
362             errorx(1, "invalid timing file line: %s", buf);
363
364         if (interactive)
365             check_input(STDIN_FILENO, &speed);
366
367         /* Adjust delay using speed factor and clamp to max_wait */
368         to_wait = seconds / speed;
369         if (max_wait && to_wait > max_wait)
370             to_wait = max_wait;
371         delay(to_wait);
372
373         /* Even if we are not relaying, we still have to delay. */
374         if (io_fds[idx].v == NULL)
375             continue;
376
377         /* All output is sent to stdout. */
378         while (nbytes != 0) {
379             if (nbytes > sizeof(buf))
380                 len = sizeof(buf);
381             else
382                 len = nbytes;
383 #ifdef HAVE_ZLIB_H
384             nread = gzread(io_fds[idx].g, buf, len);
385 #else
386             nread = fread(buf, 1, len, io_fds[idx].f);
387 #endif
388             nbytes -= nread;
389             off = 0;
390             do {
391                 /* no stdio, must be unbuffered */
392                 nwritten = write(STDOUT_FILENO, buf + off, nread - off);
393                 if (nwritten == -1) {
394                     if (errno == EINTR)
395                         continue;
396                     if (errno == EAGAIN) {
397                         FD_SET(STDOUT_FILENO, fdsw);
398                         do {
399                             nready = select(STDOUT_FILENO + 1, NULL, fdsw, NULL, NULL);
400                         } while (nready == -1 && errno == EINTR);
401                         if (nready == 1)
402                             continue;
403                     }
404                     error(1, "writing to standard output");
405                 }
406                 off += nwritten;
407             } while (nread > off);
408         }
409     }
410     term_restore(STDIN_FILENO, 1);
411     exit(0);
412 }
413
414 static void
415 delay(secs)
416     double secs;
417 {
418     struct timespec ts, rts;
419     int rval;
420
421     /*
422      * Typical max resolution is 1/HZ but we can't portably check that.
423      * If the interval is small enough, just ignore it.
424      */
425     if (secs < 0.0001)
426         return;
427
428     rts.tv_sec = secs;
429     rts.tv_nsec = (secs - (double) rts.tv_sec) * 1000000000.0;
430     do {
431       memcpy(&ts, &rts, sizeof(ts));
432       rval = nanosleep(&ts, &rts);
433     } while (rval == -1 && errno == EINTR);
434     if (rval == -1)
435         error(1, "nanosleep: tv_sec %ld, tv_nsec %ld", ts.tv_sec, ts.tv_nsec);
436 }
437
438 static void *
439 open_io_fd(path, len, suffix)
440     char *path;
441     int len;
442     const char *suffix;
443 {
444     path[len] = '\0';
445     strlcat(path, suffix, PATH_MAX);
446
447 #ifdef HAVE_ZLIB_H
448     return gzopen(path, "r");
449 #else
450     return fopen(path, "r");
451 #endif
452 }
453
454 /*
455  * Build expression list from search args
456  */
457 static int
458 parse_expr(headp, argv)
459     struct search_node **headp;
460     char *argv[];
461 {
462     struct search_node *sn, *newsn;
463     char or = 0, not = 0, type, **av;
464
465     sn = *headp;
466     for (av = argv; *av; av++) {
467         switch (av[0][0]) {
468         case 'a': /* and (ignore) */
469             if (strncmp(*av, "and", strlen(*av)) != 0)
470                 goto bad;
471             continue;
472         case 'o': /* or */
473             if (strncmp(*av, "or", strlen(*av)) != 0)
474                 goto bad;
475             or = 1;
476             continue;
477         case '!': /* negate */
478             if (av[0][1] != '\0')
479                 goto bad;
480             not = 1;
481             continue;
482         case 'c': /* command */
483             if (av[0][1] == '\0')
484                 errorx(1, "ambiguous expression \"%s\"", *av);
485             if (strncmp(*av, "cwd", strlen(*av)) == 0)
486                 type = ST_CWD;
487             else if (strncmp(*av, "command", strlen(*av)) == 0)
488                 type = ST_PATTERN;
489             else
490                 goto bad;
491             break;
492         case 'f': /* from date */
493             if (strncmp(*av, "fromdate", strlen(*av)) != 0)
494                 goto bad;
495             type = ST_FROMDATE;
496             break;
497         case 'g': /* runas group */
498             if (strncmp(*av, "group", strlen(*av)) != 0)
499                 goto bad;
500             type = ST_RUNASGROUP;
501             break;
502         case 'r': /* runas user */
503             if (strncmp(*av, "runas", strlen(*av)) != 0)
504                 goto bad;
505             type = ST_RUNASUSER;
506             break;
507         case 't': /* tty or to date */
508             if (av[0][1] == '\0')
509                 errorx(1, "ambiguous expression \"%s\"", *av);
510             if (strncmp(*av, "todate", strlen(*av)) == 0)
511                 type = ST_TODATE;
512             else if (strncmp(*av, "tty", strlen(*av)) == 0)
513                 type = ST_TTY;
514             else
515                 goto bad;
516             break;
517         case 'u': /* user */
518             if (strncmp(*av, "user", strlen(*av)) != 0)
519                 goto bad;
520             type = ST_USER;
521             break;
522         case '(': /* start sub-expression */
523             if (av[0][1] != '\0')
524                 goto bad;
525             if (stack_top + 1 == STACK_NODE_SIZE) {
526                 errorx(1, "too many parenthesized expressions, max %d",
527                     STACK_NODE_SIZE);
528             }
529             node_stack[stack_top++] = sn;
530             type = ST_EXPR;
531             break;
532         case ')': /* end sub-expression */
533             if (av[0][1] != '\0')
534                 goto bad;
535             /* pop */
536             if (--stack_top < 0)
537                 errorx(1, "unmatched ')' in expression");
538             if (node_stack[stack_top])
539                 sn->next = node_stack[stack_top]->next;
540             return(av - argv + 1);
541         bad:
542         default:
543             errorx(1, "unknown search term \"%s\"", *av);
544             /* NOTREACHED */
545         }
546
547         /* Allocate new search node */
548         newsn = emalloc(sizeof(*newsn));
549         newsn->next = NULL;
550         newsn->type = type;
551         newsn->or = or;
552         newsn->negated = not;
553         if (type == ST_EXPR) {
554             av += parse_expr(&newsn->u.expr, av + 1);
555         } else {
556             if (*(++av) == NULL)
557                 errorx(1, "%s requires an argument", av[-1]);
558 #ifdef HAVE_REGCOMP
559             if (type == ST_PATTERN) {
560                 if (regcomp(&newsn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0)
561                     errorx(1, "invalid regex: %s", *av);
562             } else
563 #endif
564             if (type == ST_TODATE || type == ST_FROMDATE) {
565                 newsn->u.tstamp = get_date(*av);
566                 if (newsn->u.tstamp == -1)
567                     errorx(1, "could not parse date \"%s\"", *av);
568             } else {
569                 newsn->u.ptr = *av;
570             }
571         }
572         not = or = 0; /* reset state */
573         if (sn)
574             sn->next = newsn;
575         else
576             *headp = newsn;
577         sn = newsn;
578     }
579     if (stack_top)
580         errorx(1, "unmatched '(' in expression");
581     if (or)
582         errorx(1, "illegal trailing \"or\"");
583     if (not)
584         errorx(1, "illegal trailing \"!\"");
585
586     return(av - argv);
587 }
588
589 static int
590 match_expr(head, log)
591     struct search_node *head;
592     struct log_info *log;
593 {
594     struct search_node *sn;
595     int matched = 1, rc;
596
597     for (sn = head; sn; sn = sn->next) {
598         /* If we have no match, skip ahead to the next OR entry. */
599         if (!matched && !sn->or)
600             continue;
601
602         switch (sn->type) {
603         case ST_EXPR:
604             matched = match_expr(sn->u.expr, log);
605             break;
606         case ST_CWD:
607             matched = strcmp(sn->u.cwd, log->cwd) == 0;
608             break;
609         case ST_TTY:
610             matched = strcmp(sn->u.tty, log->tty) == 0;
611             break;
612         case ST_RUNASGROUP:
613             matched = strcmp(sn->u.runas_group, log->runas_group) == 0;
614             break;
615         case ST_RUNASUSER:
616             matched = strcmp(sn->u.runas_user, log->runas_user) == 0;
617             break;
618         case ST_USER:
619             matched = strcmp(sn->u.user, log->user) == 0;
620             break;
621         case ST_PATTERN:
622 #ifdef HAVE_REGCOMP
623             rc = regexec(&sn->u.cmdre, log->cmd, 0, NULL, 0);
624             if (rc && rc != REG_NOMATCH) {
625                 char buf[BUFSIZ];
626                 regerror(rc, &sn->u.cmdre, buf, sizeof(buf));
627                 errorx(1, "%s", buf);
628             }
629             matched = rc == REG_NOMATCH ? 0 : 1;
630 #else
631             matched = strstr(log.cmd, sn->u.pattern) != NULL;
632 #endif
633             break;
634         case ST_FROMDATE:
635             matched = log->tstamp >= sn->u.tstamp;
636             break;
637         case ST_TODATE:
638             matched = log->tstamp <= sn->u.tstamp;
639             break;
640         }
641         if (sn->negated)
642             matched = !matched;
643     }
644     return(matched);
645 }
646
647 static int
648 list_session_dir(pathbuf, re, user, tty)
649     char *pathbuf;
650     REGEX_T *re;
651     const char *user;
652     const char *tty;
653 {
654     FILE *fp;
655     DIR *d;
656     struct dirent *dp;
657     char *buf = NULL, *cmd = NULL, *cwd = NULL, idstr[7], *cp;
658     struct log_info li;
659     size_t bufsize = 0, cwdsize = 0, cmdsize = 0, plen;
660
661     plen = strlen(pathbuf);
662     d = opendir(pathbuf);
663     if (d == NULL && errno != ENOTDIR) {
664         warning("cannot opendir %s", pathbuf);
665         return(-1);
666     }
667     while ((dp = readdir(d)) != NULL) {
668         if (NAMLEN(dp) != 2 || !isalnum((unsigned char)dp->d_name[0]) ||
669             !isalnum((unsigned char)dp->d_name[1]))
670             continue;
671
672         /* open log file, print id and command */
673         pathbuf[plen + 0] = '/';
674         pathbuf[plen + 1] = dp->d_name[0];
675         pathbuf[plen + 2] = dp->d_name[1];
676         pathbuf[plen + 3] = '/';
677         pathbuf[plen + 4] = 'l';
678         pathbuf[plen + 5] = 'o';
679         pathbuf[plen + 6] = 'g';
680         pathbuf[plen + 7] = '\0';
681         fp = fopen(pathbuf, "r");
682         if (fp == NULL) {
683             warning("unable to open %s", pathbuf);
684             continue;
685         }
686
687         /*
688          * ID file has three lines:
689          *  1) a log info line
690          *  2) cwd
691          *  3) command with args
692          */
693         if (getline(&buf, &bufsize, fp) == -1 ||
694             getline(&cwd, &cwdsize, fp) == -1 ||
695             getline(&cmd, &cmdsize, fp) == -1) {
696             fclose(fp);
697             continue;
698         }
699         fclose(fp);
700
701         /* crack the log line: timestamp:user:runas_user:runas_group:tty */
702         buf[strcspn(buf, "\n")] = '\0';
703         if ((li.tstamp = atoi(buf)) == 0)
704             continue;
705
706         if ((cp = strchr(buf, ':')) == NULL)
707             continue;
708         *cp++ = '\0';
709         li.user = cp;
710
711         if ((cp = strchr(cp, ':')) == NULL)
712             continue;
713         *cp++ = '\0';
714         li.runas_user = cp;
715
716         if ((cp = strchr(cp, ':')) == NULL)
717             continue;
718         *cp++ = '\0';
719         li.runas_group = cp;
720
721         if ((cp = strchr(cp, ':')) == NULL)
722             continue;
723         *cp++ = '\0';
724         li.tty = cp;
725
726         cwd[strcspn(cwd, "\n")] = '\0';
727         li.cwd = cwd;
728
729         cmd[strcspn(cmd, "\n")] = '\0';
730         li.cmd = cmd;
731
732         /* Match on search expression if there is one. */
733         if (search_expr && !match_expr(search_expr, &li))
734             continue;
735
736         /* Convert from /var/log/sudo-sessions/00/00/01 to 000001 */
737         idstr[0] = pathbuf[plen - 5];
738         idstr[1] = pathbuf[plen - 4];
739         idstr[2] = pathbuf[plen - 2];
740         idstr[3] = pathbuf[plen - 1];
741         idstr[4] = pathbuf[plen + 1];
742         idstr[5] = pathbuf[plen + 2];
743         idstr[6] = '\0';
744         printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ",
745             get_timestr(li.tstamp, 1), li.user, li.tty, li.cwd, li.runas_user);
746         if (*li.runas_group)
747             printf("GROUP=%s ; ", li.runas_group);
748         printf("TSID=%s ; COMMAND=%s\n", idstr, li.cmd);
749     }
750     return(0);
751 }
752
753 static int
754 list_sessions(argc, argv, pattern, user, tty)
755     int argc;
756     char **argv;
757     const char *pattern;
758     const char *user;
759     const char *tty;
760 {
761     DIR *d1, *d2;
762     struct dirent *dp1, *dp2;
763     REGEX_T rebuf, *re = NULL;
764     size_t sdlen;
765     char pathbuf[PATH_MAX];
766
767     /* Parse search expression if present */
768     parse_expr(&search_expr, argv);
769
770     d1 = opendir(session_dir);
771     if (d1 == NULL)
772         error(1, "unable to open %s", session_dir);
773
774 #ifdef HAVE_REGCOMP
775     /* optional regex */
776     if (pattern) {
777         re = &rebuf;
778         if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0)
779             errorx(1, "invalid regex: %s", pattern);
780     }
781 #else
782     re = (char *) pattern;
783 #endif /* HAVE_REGCOMP */
784
785     sdlen = strlcpy(pathbuf, session_dir, sizeof(pathbuf));
786     if (sdlen + sizeof("/00/00/00/log") >= sizeof(pathbuf)) {
787         errno = ENAMETOOLONG;
788         error(1, "%s/00/00/00/log", session_dir);
789     }
790
791     /*
792      * Three levels of directory, e.g. 00/00/00 .. ZZ/ZZ/ZZ
793      * We do a depth-first traversal.
794      */
795     while ((dp1 = readdir(d1)) != NULL) {
796         if (NAMLEN(dp1) != 2 || !isalnum((unsigned char)dp1->d_name[0]) ||
797             !isalnum((unsigned char)dp1->d_name[1]))
798             continue;
799
800         pathbuf[sdlen + 0] = '/';
801         pathbuf[sdlen + 1] = dp1->d_name[0];
802         pathbuf[sdlen + 2] = dp1->d_name[1];
803         pathbuf[sdlen + 3] = '\0';
804         d2 = opendir(pathbuf);
805         if (d2 == NULL)
806             continue;
807
808         while ((dp2 = readdir(d2)) != NULL) {
809             if (NAMLEN(dp2) != 2 || !isalnum((unsigned char)dp2->d_name[0]) ||
810                 !isalnum((unsigned char)dp2->d_name[1]))
811                 continue;
812
813             pathbuf[sdlen + 3] = '/';
814             pathbuf[sdlen + 4] = dp2->d_name[0];
815             pathbuf[sdlen + 5] = dp2->d_name[1];
816             pathbuf[sdlen + 6] = '\0';
817             list_session_dir(pathbuf, re, user, tty);
818         }
819         closedir(d2);
820     }
821     closedir(d1);
822     return(0);
823 }
824
825 /*
826  * Check input for ' ', '<', '>'
827  * pause, slow, fast
828  */
829 static void
830 check_input(ttyfd, speed)
831     int ttyfd;
832     double *speed;
833 {
834     fd_set *fdsr;
835     int nready, paused = 0;
836     struct timeval tv;
837     char ch;
838     ssize_t n;
839
840     fdsr = (fd_set *)emalloc2(howmany(ttyfd + 1, NFDBITS), sizeof(fd_mask));
841
842     for (;;) {
843         FD_SET(ttyfd, fdsr);
844         tv.tv_sec = 0;
845         tv.tv_usec = 0;
846
847         nready = select(ttyfd + 1, fdsr, NULL, NULL, paused ? NULL : &tv);
848         if (nready != 1)
849             break;
850         n = read(ttyfd, &ch, 1);
851         if (n == 1) {
852             if (paused) {
853                 paused = 0;
854                 continue;
855             }
856             switch (ch) {
857             case ' ':
858                 paused = 1;
859                 break;
860             case '<':
861                 *speed /= 2;
862                 break;
863             case '>':
864                 *speed *= 2;
865                 break;
866             }
867         }
868     }
869     free(fdsr);
870 }
871
872 /*
873  * Parse a timing line, which is formatted as:
874  *      index sleep_time num_bytes
875  * Where index is IOFD_*, sleep_time is the number of seconds to sleep
876  * before writing the data and num_bytes is the number of bytes to output.
877  * Returns 1 on success and 0 on failure.
878  */
879 static int
880 parse_timing(buf, decimal, idx, seconds, nbytes)
881     const char *buf;
882     const char *decimal;
883     int *idx;
884     double *seconds;
885     size_t *nbytes;
886 {
887     unsigned long ul;
888     long l;
889     double d, fract = 0;
890     char *cp, *ep;
891
892     /* Parse index */
893     ul = strtoul(buf, &ep, 10);
894     if (ul > IOFD_MAX)
895         goto bad;
896     *idx = (int)ul;
897     for (cp = ep + 1; isspace((unsigned char) *cp); cp++)
898         continue;
899
900     /*
901      * Parse number of seconds.  Sudo logs timing data in the C locale
902      * but this may not match the current locale so we cannot use strtod().
903      * Furthermore, sudo < 1.7.4 logged with the user's locale so we need
904      * to be able to parse those logs too.
905      */
906     errno = 0;
907     l = strtol(cp, &ep, 10);
908     if ((errno == ERANGE && (l == LONG_MAX || l == LONG_MIN)) ||
909         l < 0 || l > INT_MAX ||
910         (*ep != '.' && strncmp(ep, decimal, strlen(decimal)) != 0)) {
911         goto bad;
912     }
913     *seconds = (double)l;
914     cp = ep + (*ep == '.' ? 1 : strlen(decimal));
915     d = 10.0;
916     while (isdigit((unsigned char) *cp)) {
917         fract += (*cp - '0') / d;
918         d *= 10;
919         cp++;
920     }
921     *seconds += fract;
922     while (isspace((unsigned char) *cp))
923         cp++;
924
925     errno = 0;
926     ul = strtoul(cp, &ep, 10);
927     if (errno == ERANGE && ul == ULONG_MAX)
928         goto bad;
929     *nbytes = (size_t)ul;
930
931     return 1;
932 bad:
933     return 0;
934 }
935
936 static void
937 usage()
938 {
939     fprintf(stderr,
940         "usage: %s [-d directory] [-m max_wait] [-s speed_factor] ID\n",
941         getprogname());
942     fprintf(stderr,
943         "usage: %s [-d directory] -l [search expression]\n",
944         getprogname());
945     exit(1);
946 }
947
948 /*
949  * Cleanup hook for error()/errorx()
950   */
951 void
952 cleanup(signo)
953     int signo;
954 {
955     term_restore(STDIN_FILENO, 0);
956     if (signo)
957         kill(getpid(), signo);
958 }