File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / plugins / sudoers / sudoreplay.c
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 16:12:54 2014 UTC (10 years, 3 months ago) by misho
Branches: sudo, MAIN
CVS tags: v1_8_10p3_0, v1_8_10p3, HEAD
sudo v 1.8.10p3

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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>