X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=sudoreplay.c;fp=sudoreplay.c;h=0000000000000000000000000000000000000000;hb=6ad45aa23af5f5f3b54468937d6a13089201b891;hp=58b8639d7a1b641e02fd285670621805832cf880;hpb=97bd3ae46779c69fcdab82d0c64bdf05be009ec3;p=debian%2Fsudo diff --git a/sudoreplay.c b/sudoreplay.c deleted file mode 100644 index 58b8639..0000000 --- a/sudoreplay.c +++ /dev/null @@ -1,958 +0,0 @@ -/* - * Copyright (c) 2009-2010 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include -#include -#include -#include -#include -#include -#ifdef HAVE_SYS_SELECT_H -#include -#endif /* HAVE_SYS_SELECT_H */ -#include -#ifdef STDC_HEADERS -# include -# include -#else -# ifdef HAVE_STDLIB_H -# include -# endif -#endif /* STDC_HEADERS */ -#ifdef HAVE_STRING_H -# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) -# include -# endif -# include -#endif /* HAVE_STRING_H */ -#ifdef HAVE_STRINGS_H -# include -#endif /* HAVE_STRINGS_H */ -#ifdef HAVE_UNISTD_H -# include -#endif /* HAVE_UNISTD_H */ -#if TIME_WITH_SYS_TIME -# include -#endif -#ifndef HAVE_TIMESPEC -# include -#endif -#include -#include -#include -#include -#ifdef HAVE_DIRENT_H -# include -# define NAMLEN(dirent) strlen((dirent)->d_name) -#else -# define dirent direct -# define NAMLEN(dirent) (dirent)->d_namlen -# ifdef HAVE_SYS_NDIR_H -# include -# endif -# ifdef HAVE_SYS_DIR_H -# include -# endif -# ifdef HAVE_NDIR_H -# include -# endif -#endif -#ifdef HAVE_REGCOMP -# include -#endif -#ifdef HAVE_ZLIB_H -# include -#endif -#ifdef HAVE_SETLOCALE -# include -#endif -#include - -#include - -#include "compat.h" -#include "alloc.h" -#include "error.h" -#include "missing.h" - -#ifndef LINE_MAX -# define LINE_MAX 2048 -#endif - -/* Must match the defines in iolog.c */ -#define IOFD_STDIN 0 -#define IOFD_STDOUT 1 -#define IOFD_STDERR 2 -#define IOFD_TTYIN 3 -#define IOFD_TTYOUT 4 -#define IOFD_TIMING 5 -#define IOFD_MAX 6 - -/* Bitmap of iofds to be replayed */ -unsigned int replay_filter = (1 << IOFD_STDOUT) | (1 << IOFD_STDERR) | - (1 << IOFD_TTYOUT); - -/* For getopt(3) */ -extern char *optarg; -extern int optind; - -int Argc; -char **Argv; - -union io_fd { - FILE *f; -#ifdef HAVE_ZLIB_H - gzFile g; -#endif - void *v; -}; - -/* - * Info present in the I/O log file - */ -struct log_info { - char *cwd; - char *user; - char *runas_user; - char *runas_group; - char *tty; - char *cmd; - time_t tstamp; -}; - -/* - * Handle expressions like: - * ( user millert or user root ) and tty console and command /bin/sh - */ -struct search_node { - struct search_node *next; -#define ST_EXPR 1 -#define ST_TTY 2 -#define ST_USER 3 -#define ST_PATTERN 4 -#define ST_RUNASUSER 5 -#define ST_RUNASGROUP 6 -#define ST_FROMDATE 7 -#define ST_TODATE 8 -#define ST_CWD 9 - char type; - char negated; - char or; - char pad; - union { -#ifdef HAVE_REGCOMP - regex_t cmdre; -#endif - time_t tstamp; - char *cwd; - char *tty; - char *user; - char *pattern; - char *runas_group; - char *runas_user; - struct search_node *expr; - void *ptr; - } u; -} *search_expr; - -#define STACK_NODE_SIZE 32 -static struct search_node *node_stack[32]; -static int stack_top; - -static const char *session_dir = _PATH_SUDO_IO_LOGDIR; - -static union io_fd io_fds[IOFD_MAX]; -static const char *io_fnames[IOFD_MAX] = { - "/stdin", - "/stdout", - "/stderr", - "/ttyin", - "/ttyout", - "/timing" -}; - -extern time_t get_date __P((char *)); -extern char *get_timestr __P((time_t, int)); -extern int term_raw __P((int, int)); -extern int term_restore __P((int, int)); -extern void zero_bytes __P((volatile void *, size_t)); -void cleanup __P((int)); - -static int list_sessions __P((int, char **, const char *, const char *, const char *)); -static int parse_expr __P((struct search_node **, char **)); -static void check_input __P((int, double *)); -static void delay __P((double)); -static void usage __P((void)); -static void *open_io_fd __P((char *pathbuf, int len, const char *suffix)); -static int parse_timing __P((const char *buf, const char *decimal, int *idx, double *seconds, size_t *nbytes)); - -#ifdef HAVE_REGCOMP -# define REGEX_T regex_t -#else -# define REGEX_T char -#endif - -#define VALID_ID(s) (isalnum((unsigned char)(s)[0]) && \ - isalnum((unsigned char)(s)[1]) && isalnum((unsigned char)(s)[2]) && \ - isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \ - isalnum((unsigned char)(s)[5]) && (s)[6] == '\0') - -int -main(argc, argv) - int argc; - char *argv[]; -{ - int ch, idx, plen, nready, interactive = 0, listonly = 0; - const char *id, *user = NULL, *pattern = NULL, *tty = NULL, *decimal = "."; - char path[PATH_MAX], buf[LINE_MAX], *cp, *ep; - double seconds, to_wait, speed = 1.0, max_wait = 0; - FILE *lfile; - fd_set *fdsw; - sigaction_t sa; - size_t len, nbytes, nread, off; - ssize_t nwritten; - - Argc = argc; - Argv = argv; - -#ifdef HAVE_SETLOCALE - setlocale(LC_ALL, ""); - decimal = localeconv()->decimal_point; -#endif - - while ((ch = getopt(argc, argv, "d:f:lm:s:V")) != -1) { - switch(ch) { - case 'd': - session_dir = optarg; - break; - case 'f': - /* Set the replay filter. */ - replay_filter = 0; - for (cp = strtok(optarg, ","); cp; cp = strtok(NULL, ",")) { - if (strcmp(cp, "stdout") == 0) - SET(replay_filter, 1 << IOFD_STDOUT); - else if (strcmp(cp, "stderr") == 0) - SET(replay_filter, 1 << IOFD_STDERR); - else if (strcmp(cp, "ttyout") == 0) - SET(replay_filter, 1 << IOFD_TTYOUT); - else - errorx(1, "invalid filter option: %s", optarg); - } - break; - case 'l': - listonly = 1; - break; - case 'm': - errno = 0; - max_wait = strtod(optarg, &ep); - if (*ep != '\0' || errno != 0) - errorx(1, "invalid max wait: %s", optarg); - break; - case 's': - errno = 0; - speed = strtod(optarg, &ep); - if (*ep != '\0' || errno != 0) - errorx(1, "invalid speed factor: %s", optarg); - break; - case 'V': - (void) printf("%s version %s\n", getprogname(), PACKAGE_VERSION); - exit(0); - default: - usage(); - /* NOTREACHED */ - } - - } - argc -= optind; - argv += optind; - - if (listonly) - exit(list_sessions(argc, argv, pattern, user, tty)); - - if (argc != 1) - usage(); - - /* 6 digit ID in base 36, e.g. 01G712AB */ - id = argv[0]; - if (!VALID_ID(id)) - errorx(1, "invalid ID %s", id); - - plen = snprintf(path, sizeof(path), "%s/%.2s/%.2s/%.2s/timing", - session_dir, id, &id[2], &id[4]); - if (plen <= 0 || plen >= sizeof(path)) - errorx(1, "%s/%.2s/%.2s/%.2s/%.2s/timing: %s", session_dir, - id, &id[2], &id[4], strerror(ENAMETOOLONG)); - plen -= 7; - - /* Open files for replay, applying replay filter for the -f flag. */ - for (idx = 0; idx < IOFD_MAX; idx++) { - if (ISSET(replay_filter, 1 << idx) || idx == IOFD_TIMING) { - io_fds[idx].v = open_io_fd(path, plen, io_fnames[idx]); - if (io_fds[idx].v == NULL) - error(1, "unable to open %s", path); - } - } - - /* Read log file. */ - path[plen] = '\0'; - strlcat(path, "/log", sizeof(path)); - lfile = fopen(path, "r"); - if (lfile == NULL) - error(1, "unable to open %s", path); - cp = NULL; - len = 0; - getline(&cp, &len, lfile); /* log */ - getline(&cp, &len, lfile); /* cwd */ - getline(&cp, &len, lfile); /* command */ - printf("Replaying sudo session: %s", cp); - free(cp); - fclose(lfile); - - fflush(stdout); - zero_bytes(&sa, sizeof(sa)); - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESETHAND; - sa.sa_handler = cleanup; - (void) sigaction(SIGINT, &sa, NULL); - (void) sigaction(SIGKILL, &sa, NULL); - (void) sigaction(SIGTERM, &sa, NULL); - (void) sigaction(SIGHUP, &sa, NULL); - sa.sa_flags = SA_RESTART; - sa.sa_handler = SIG_IGN; - (void) sigaction(SIGTSTP, &sa, NULL); - (void) sigaction(SIGQUIT, &sa, NULL); - - /* XXX - read user input from /dev/tty and set STDOUT to raw if not a pipe */ - /* Set stdin to raw mode if it is a tty */ - interactive = isatty(STDIN_FILENO); - if (interactive) { - ch = fcntl(STDIN_FILENO, F_GETFL, 0); - if (ch != -1) - (void) fcntl(STDIN_FILENO, F_SETFL, ch | O_NONBLOCK); - if (!term_raw(STDIN_FILENO, 1)) - error(1, "cannot set tty to raw mode"); - } - fdsw = (fd_set *)emalloc2(howmany(STDOUT_FILENO + 1, NFDBITS), - sizeof(fd_mask)); - - /* - * Timing file consists of line of the format: "%f %d\n" - */ -#ifdef HAVE_ZLIB_H - while (gzgets(io_fds[IOFD_TIMING].g, buf, sizeof(buf)) != NULL) { -#else - while (fgets(buf, sizeof(buf), io_fds[IOFD_TIMING].f) != NULL) { -#endif - if (!parse_timing(buf, decimal, &idx, &seconds, &nbytes)) - errorx(1, "invalid timing file line: %s", buf); - - if (interactive) - check_input(STDIN_FILENO, &speed); - - /* Adjust delay using speed factor and clamp to max_wait */ - to_wait = seconds / speed; - if (max_wait && to_wait > max_wait) - to_wait = max_wait; - delay(to_wait); - - /* Even if we are not relaying, we still have to delay. */ - if (io_fds[idx].v == NULL) - continue; - - /* All output is sent to stdout. */ - while (nbytes != 0) { - if (nbytes > sizeof(buf)) - len = sizeof(buf); - else - len = nbytes; -#ifdef HAVE_ZLIB_H - nread = gzread(io_fds[idx].g, buf, len); -#else - nread = fread(buf, 1, len, io_fds[idx].f); -#endif - nbytes -= nread; - off = 0; - do { - /* no stdio, must be unbuffered */ - nwritten = write(STDOUT_FILENO, buf + off, nread - off); - if (nwritten == -1) { - if (errno == EINTR) - continue; - if (errno == EAGAIN) { - FD_SET(STDOUT_FILENO, fdsw); - do { - nready = select(STDOUT_FILENO + 1, NULL, fdsw, NULL, NULL); - } while (nready == -1 && errno == EINTR); - if (nready == 1) - continue; - } - error(1, "writing to standard output"); - } - off += nwritten; - } while (nread > off); - } - } - term_restore(STDIN_FILENO, 1); - exit(0); -} - -static void -delay(secs) - double secs; -{ - struct timespec ts, rts; - int rval; - - /* - * Typical max resolution is 1/HZ but we can't portably check that. - * If the interval is small enough, just ignore it. - */ - if (secs < 0.0001) - return; - - rts.tv_sec = secs; - rts.tv_nsec = (secs - (double) rts.tv_sec) * 1000000000.0; - do { - memcpy(&ts, &rts, sizeof(ts)); - rval = nanosleep(&ts, &rts); - } while (rval == -1 && errno == EINTR); - if (rval == -1) - error(1, "nanosleep: tv_sec %ld, tv_nsec %ld", ts.tv_sec, ts.tv_nsec); -} - -static void * -open_io_fd(path, len, suffix) - char *path; - int len; - const char *suffix; -{ - path[len] = '\0'; - strlcat(path, suffix, PATH_MAX); - -#ifdef HAVE_ZLIB_H - return gzopen(path, "r"); -#else - return fopen(path, "r"); -#endif -} - -/* - * Build expression list from search args - */ -static int -parse_expr(headp, argv) - struct search_node **headp; - char *argv[]; -{ - struct search_node *sn, *newsn; - char or = 0, not = 0, type, **av; - - sn = *headp; - for (av = argv; *av; av++) { - switch (av[0][0]) { - case 'a': /* and (ignore) */ - if (strncmp(*av, "and", strlen(*av)) != 0) - goto bad; - continue; - case 'o': /* or */ - if (strncmp(*av, "or", strlen(*av)) != 0) - goto bad; - or = 1; - continue; - case '!': /* negate */ - if (av[0][1] != '\0') - goto bad; - not = 1; - continue; - case 'c': /* command */ - if (av[0][1] == '\0') - errorx(1, "ambiguous expression \"%s\"", *av); - if (strncmp(*av, "cwd", strlen(*av)) == 0) - type = ST_CWD; - else if (strncmp(*av, "command", strlen(*av)) == 0) - type = ST_PATTERN; - else - goto bad; - break; - case 'f': /* from date */ - if (strncmp(*av, "fromdate", strlen(*av)) != 0) - goto bad; - type = ST_FROMDATE; - break; - case 'g': /* runas group */ - if (strncmp(*av, "group", strlen(*av)) != 0) - goto bad; - type = ST_RUNASGROUP; - break; - case 'r': /* runas user */ - if (strncmp(*av, "runas", strlen(*av)) != 0) - goto bad; - type = ST_RUNASUSER; - break; - case 't': /* tty or to date */ - if (av[0][1] == '\0') - errorx(1, "ambiguous expression \"%s\"", *av); - if (strncmp(*av, "todate", strlen(*av)) == 0) - type = ST_TODATE; - else if (strncmp(*av, "tty", strlen(*av)) == 0) - type = ST_TTY; - else - goto bad; - break; - case 'u': /* user */ - if (strncmp(*av, "user", strlen(*av)) != 0) - goto bad; - type = ST_USER; - break; - case '(': /* start sub-expression */ - if (av[0][1] != '\0') - goto bad; - if (stack_top + 1 == STACK_NODE_SIZE) { - errorx(1, "too many parenthesized expressions, max %d", - STACK_NODE_SIZE); - } - node_stack[stack_top++] = sn; - type = ST_EXPR; - break; - case ')': /* end sub-expression */ - if (av[0][1] != '\0') - goto bad; - /* pop */ - if (--stack_top < 0) - errorx(1, "unmatched ')' in expression"); - if (node_stack[stack_top]) - sn->next = node_stack[stack_top]->next; - return(av - argv + 1); - bad: - default: - errorx(1, "unknown search term \"%s\"", *av); - /* NOTREACHED */ - } - - /* Allocate new search node */ - newsn = emalloc(sizeof(*newsn)); - newsn->next = NULL; - newsn->type = type; - newsn->or = or; - newsn->negated = not; - if (type == ST_EXPR) { - av += parse_expr(&newsn->u.expr, av + 1); - } else { - if (*(++av) == NULL) - errorx(1, "%s requires an argument", av[-1]); -#ifdef HAVE_REGCOMP - if (type == ST_PATTERN) { - if (regcomp(&newsn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0) - errorx(1, "invalid regex: %s", *av); - } else -#endif - if (type == ST_TODATE || type == ST_FROMDATE) { - newsn->u.tstamp = get_date(*av); - if (newsn->u.tstamp == -1) - errorx(1, "could not parse date \"%s\"", *av); - } else { - newsn->u.ptr = *av; - } - } - not = or = 0; /* reset state */ - if (sn) - sn->next = newsn; - else - *headp = newsn; - sn = newsn; - } - if (stack_top) - errorx(1, "unmatched '(' in expression"); - if (or) - errorx(1, "illegal trailing \"or\""); - if (not) - errorx(1, "illegal trailing \"!\""); - - return(av - argv); -} - -static int -match_expr(head, log) - struct search_node *head; - struct log_info *log; -{ - struct search_node *sn; - int matched = 1, rc; - - for (sn = head; sn; sn = sn->next) { - /* If we have no match, skip ahead to the next OR entry. */ - if (!matched && !sn->or) - continue; - - switch (sn->type) { - case ST_EXPR: - matched = match_expr(sn->u.expr, log); - break; - case ST_CWD: - matched = strcmp(sn->u.cwd, log->cwd) == 0; - break; - case ST_TTY: - matched = strcmp(sn->u.tty, log->tty) == 0; - break; - case ST_RUNASGROUP: - matched = strcmp(sn->u.runas_group, log->runas_group) == 0; - break; - case ST_RUNASUSER: - matched = strcmp(sn->u.runas_user, log->runas_user) == 0; - break; - case ST_USER: - matched = strcmp(sn->u.user, log->user) == 0; - break; - case ST_PATTERN: -#ifdef HAVE_REGCOMP - rc = regexec(&sn->u.cmdre, log->cmd, 0, NULL, 0); - if (rc && rc != REG_NOMATCH) { - char buf[BUFSIZ]; - regerror(rc, &sn->u.cmdre, buf, sizeof(buf)); - errorx(1, "%s", buf); - } - matched = rc == REG_NOMATCH ? 0 : 1; -#else - matched = strstr(log.cmd, sn->u.pattern) != NULL; -#endif - break; - case ST_FROMDATE: - matched = log->tstamp >= sn->u.tstamp; - break; - case ST_TODATE: - matched = log->tstamp <= sn->u.tstamp; - break; - } - if (sn->negated) - matched = !matched; - } - return(matched); -} - -static int -list_session_dir(pathbuf, re, user, tty) - char *pathbuf; - REGEX_T *re; - const char *user; - const char *tty; -{ - FILE *fp; - DIR *d; - struct dirent *dp; - char *buf = NULL, *cmd = NULL, *cwd = NULL, idstr[7], *cp; - struct log_info li; - size_t bufsize = 0, cwdsize = 0, cmdsize = 0, plen; - - plen = strlen(pathbuf); - d = opendir(pathbuf); - if (d == NULL && errno != ENOTDIR) { - warning("cannot opendir %s", pathbuf); - return(-1); - } - while ((dp = readdir(d)) != NULL) { - if (NAMLEN(dp) != 2 || !isalnum((unsigned char)dp->d_name[0]) || - !isalnum((unsigned char)dp->d_name[1])) - continue; - - /* open log file, print id and command */ - pathbuf[plen + 0] = '/'; - pathbuf[plen + 1] = dp->d_name[0]; - pathbuf[plen + 2] = dp->d_name[1]; - pathbuf[plen + 3] = '/'; - pathbuf[plen + 4] = 'l'; - pathbuf[plen + 5] = 'o'; - pathbuf[plen + 6] = 'g'; - pathbuf[plen + 7] = '\0'; - fp = fopen(pathbuf, "r"); - if (fp == NULL) { - warning("unable to open %s", pathbuf); - continue; - } - - /* - * ID file has three lines: - * 1) a log info line - * 2) cwd - * 3) command with args - */ - if (getline(&buf, &bufsize, fp) == -1 || - getline(&cwd, &cwdsize, fp) == -1 || - getline(&cmd, &cmdsize, fp) == -1) { - fclose(fp); - continue; - } - fclose(fp); - - /* crack the log line: timestamp:user:runas_user:runas_group:tty */ - buf[strcspn(buf, "\n")] = '\0'; - if ((li.tstamp = atoi(buf)) == 0) - continue; - - if ((cp = strchr(buf, ':')) == NULL) - continue; - *cp++ = '\0'; - li.user = cp; - - if ((cp = strchr(cp, ':')) == NULL) - continue; - *cp++ = '\0'; - li.runas_user = cp; - - if ((cp = strchr(cp, ':')) == NULL) - continue; - *cp++ = '\0'; - li.runas_group = cp; - - if ((cp = strchr(cp, ':')) == NULL) - continue; - *cp++ = '\0'; - li.tty = cp; - - cwd[strcspn(cwd, "\n")] = '\0'; - li.cwd = cwd; - - cmd[strcspn(cmd, "\n")] = '\0'; - li.cmd = cmd; - - /* Match on search expression if there is one. */ - if (search_expr && !match_expr(search_expr, &li)) - continue; - - /* Convert from /var/log/sudo-sessions/00/00/01 to 000001 */ - idstr[0] = pathbuf[plen - 5]; - idstr[1] = pathbuf[plen - 4]; - idstr[2] = pathbuf[plen - 2]; - idstr[3] = pathbuf[plen - 1]; - idstr[4] = pathbuf[plen + 1]; - idstr[5] = pathbuf[plen + 2]; - idstr[6] = '\0'; - printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ", - get_timestr(li.tstamp, 1), li.user, li.tty, li.cwd, li.runas_user); - if (*li.runas_group) - printf("GROUP=%s ; ", li.runas_group); - printf("TSID=%s ; COMMAND=%s\n", idstr, li.cmd); - } - return(0); -} - -static int -list_sessions(argc, argv, pattern, user, tty) - int argc; - char **argv; - const char *pattern; - const char *user; - const char *tty; -{ - DIR *d1, *d2; - struct dirent *dp1, *dp2; - REGEX_T rebuf, *re = NULL; - size_t sdlen; - char pathbuf[PATH_MAX]; - - /* Parse search expression if present */ - parse_expr(&search_expr, argv); - - d1 = opendir(session_dir); - if (d1 == NULL) - error(1, "unable to open %s", session_dir); - -#ifdef HAVE_REGCOMP - /* optional regex */ - if (pattern) { - re = &rebuf; - if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0) - errorx(1, "invalid regex: %s", pattern); - } -#else - re = (char *) pattern; -#endif /* HAVE_REGCOMP */ - - sdlen = strlcpy(pathbuf, session_dir, sizeof(pathbuf)); - if (sdlen + sizeof("/00/00/00/log") >= sizeof(pathbuf)) { - errno = ENAMETOOLONG; - error(1, "%s/00/00/00/log", session_dir); - } - - /* - * Three levels of directory, e.g. 00/00/00 .. ZZ/ZZ/ZZ - * We do a depth-first traversal. - */ - while ((dp1 = readdir(d1)) != NULL) { - if (NAMLEN(dp1) != 2 || !isalnum((unsigned char)dp1->d_name[0]) || - !isalnum((unsigned char)dp1->d_name[1])) - continue; - - pathbuf[sdlen + 0] = '/'; - pathbuf[sdlen + 1] = dp1->d_name[0]; - pathbuf[sdlen + 2] = dp1->d_name[1]; - pathbuf[sdlen + 3] = '\0'; - d2 = opendir(pathbuf); - if (d2 == NULL) - continue; - - while ((dp2 = readdir(d2)) != NULL) { - if (NAMLEN(dp2) != 2 || !isalnum((unsigned char)dp2->d_name[0]) || - !isalnum((unsigned char)dp2->d_name[1])) - continue; - - pathbuf[sdlen + 3] = '/'; - pathbuf[sdlen + 4] = dp2->d_name[0]; - pathbuf[sdlen + 5] = dp2->d_name[1]; - pathbuf[sdlen + 6] = '\0'; - list_session_dir(pathbuf, re, user, tty); - } - closedir(d2); - } - closedir(d1); - return(0); -} - -/* - * Check input for ' ', '<', '>' - * pause, slow, fast - */ -static void -check_input(ttyfd, speed) - int ttyfd; - double *speed; -{ - fd_set *fdsr; - int nready, paused = 0; - struct timeval tv; - char ch; - ssize_t n; - - fdsr = (fd_set *)emalloc2(howmany(ttyfd + 1, NFDBITS), sizeof(fd_mask)); - - for (;;) { - FD_SET(ttyfd, fdsr); - tv.tv_sec = 0; - tv.tv_usec = 0; - - nready = select(ttyfd + 1, fdsr, NULL, NULL, paused ? NULL : &tv); - if (nready != 1) - break; - n = read(ttyfd, &ch, 1); - if (n == 1) { - if (paused) { - paused = 0; - continue; - } - switch (ch) { - case ' ': - paused = 1; - break; - case '<': - *speed /= 2; - break; - case '>': - *speed *= 2; - break; - } - } - } - free(fdsr); -} - -/* - * Parse a timing line, which is formatted as: - * index sleep_time num_bytes - * Where index is IOFD_*, sleep_time is the number of seconds to sleep - * before writing the data and num_bytes is the number of bytes to output. - * Returns 1 on success and 0 on failure. - */ -static int -parse_timing(buf, decimal, idx, seconds, nbytes) - const char *buf; - const char *decimal; - int *idx; - double *seconds; - size_t *nbytes; -{ - unsigned long ul; - long l; - double d, fract = 0; - char *cp, *ep; - - /* Parse index */ - ul = strtoul(buf, &ep, 10); - if (ul > IOFD_MAX) - goto bad; - *idx = (int)ul; - for (cp = ep + 1; isspace((unsigned char) *cp); cp++) - continue; - - /* - * Parse number of seconds. Sudo logs timing data in the C locale - * but this may not match the current locale so we cannot use strtod(). - * Furthermore, sudo < 1.7.4 logged with the user's locale so we need - * to be able to parse those logs too. - */ - errno = 0; - l = strtol(cp, &ep, 10); - if ((errno == ERANGE && (l == LONG_MAX || l == LONG_MIN)) || - l < 0 || l > INT_MAX || - (*ep != '.' && strncmp(ep, decimal, strlen(decimal)) != 0)) { - goto bad; - } - *seconds = (double)l; - cp = ep + (*ep == '.' ? 1 : strlen(decimal)); - d = 10.0; - while (isdigit((unsigned char) *cp)) { - fract += (*cp - '0') / d; - d *= 10; - cp++; - } - *seconds += fract; - while (isspace((unsigned char) *cp)) - cp++; - - errno = 0; - ul = strtoul(cp, &ep, 10); - if (errno == ERANGE && ul == ULONG_MAX) - goto bad; - *nbytes = (size_t)ul; - - return 1; -bad: - return 0; -} - -static void -usage() -{ - fprintf(stderr, - "usage: %s [-d directory] [-m max_wait] [-s speed_factor] ID\n", - getprogname()); - fprintf(stderr, - "usage: %s [-d directory] -l [search expression]\n", - getprogname()); - exit(1); -} - -/* - * Cleanup hook for error()/errorx() - */ -void -cleanup(signo) - int signo; -{ - term_restore(STDIN_FILENO, 0); - if (signo) - kill(getpid(), signo); -}