Diff for /embedaddon/sudo/plugins/sudoers/sudoreplay.c between versions 1.1 and 1.1.1.6

version 1.1, 2012/02/21 16:23:02 version 1.1.1.6, 2014/06/15 16:12:54
Line 1 Line 1
 /*  /*
 * Copyright (c) 2009-2011 Todd C. Miller <Todd.Miller@courtesan.com> * Copyright (c) 2009-2013 Todd C. Miller <Todd.Miller@courtesan.com>
  *   *
  * Permission to use, copy, modify, and distribute this software for any   * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above   * purpose with or without fee is hereby granted, provided that the above
Line 17 Line 17
 #include <config.h>  #include <config.h>
   
 #include <sys/types.h>  #include <sys/types.h>
#include <sys/param.h>#include <sys/uio.h>
#ifdef HAVE_SYS_SYSMACROS_H 
# include <sys/sysmacros.h> 
#endif 
 #include <sys/stat.h>  #include <sys/stat.h>
 #include <sys/time.h>  #include <sys/time.h>
 #include <sys/wait.h>  #include <sys/wait.h>
 #include <sys/ioctl.h>  #include <sys/ioctl.h>
 #ifdef HAVE_SYS_SELECT_H  
 #include <sys/select.h>  
 #endif /* HAVE_SYS_SELECT_H */  
 #include <stdio.h>  #include <stdio.h>
 #ifdef STDC_HEADERS  #ifdef STDC_HEADERS
 # include <stdlib.h>  # include <stdlib.h>
Line 49 Line 43
 #ifdef HAVE_UNISTD_H  #ifdef HAVE_UNISTD_H
 # include <unistd.h>  # include <unistd.h>
 #endif /* HAVE_UNISTD_H */  #endif /* HAVE_UNISTD_H */
#if TIME_WITH_SYS_TIME#ifdef TIME_WITH_SYS_TIME
 # include <time.h>  # include <time.h>
 #endif  #endif
#ifndef HAVE_TIMESPEC#ifndef HAVE_STRUCT_TIMESPEC
 # include "compat/timespec.h"  # include "compat/timespec.h"
 #endif  #endif
 #include <ctype.h>  #include <ctype.h>
Line 81 Line 75
 #ifdef HAVE_ZLIB_H  #ifdef HAVE_ZLIB_H
 # include <zlib.h>  # include <zlib.h>
 #endif  #endif
 #ifdef HAVE_SETLOCALE  
 # include <locale.h>  
 #endif  
 #include <signal.h>  #include <signal.h>
   #ifdef HAVE_STDBOOL_H
   # include <stdbool.h>
   #else
   # include "compat/stdbool.h"
   #endif /* HAVE_STDBOOL_H */
   #ifdef HAVE_GETOPT_LONG
   # include <getopt.h>
   # else
   # include "compat/getopt.h"
   #endif /* HAVE_GETOPT_LONG */
   
 #include <pathnames.h>  #include <pathnames.h>
   
   #include "gettext.h"            /* must be included before missing.h */
   
 #include "missing.h"  #include "missing.h"
 #include "alloc.h"  #include "alloc.h"
#include "error.h"#include "fatal.h"
#include "gettext.h"#include "logging.h"
 #include "iolog.h"
 #include "queue.h"
 #include "sudo_plugin.h"
 #include "sudo_conf.h"
 #include "sudo_debug.h"
 #include "sudo_event.h"
 #include "sudo_util.h"
   
 #ifndef LINE_MAX  #ifndef LINE_MAX
 # define LINE_MAX 2048  # define LINE_MAX 2048
 #endif  #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;  
   
 union io_fd {  
     FILE *f;  
 #ifdef HAVE_ZLIB_H  
     gzFile g;  
 #endif  
     void *v;  
 };  
   
 /*  /*
  * Info present in the I/O log file   * Info present in the I/O log file
  */   */
Line 133  struct log_info { Line 118  struct log_info {
     char *tty;      char *tty;
     char *cmd;      char *cmd;
     time_t tstamp;      time_t tstamp;
       int rows;
       int cols;
 };  };
   
   /* Closure for write_output */
   struct write_closure {
       struct sudo_event *wevent;
       struct iovec *iov;
       unsigned int iovcnt;
       size_t nbytes;
   };
   
 /*  /*
  * Handle expressions like:   * Handle expressions like:
  * ( user millert or user root ) and tty console and command /bin/sh   * ( user millert or user root ) and tty console and command /bin/sh
  */   */
   STAILQ_HEAD(search_node_list, search_node);
 struct search_node {  struct search_node {
    struct search_node *next;    STAILQ_ENTRY(search_node) entries;
 #define ST_EXPR         1  #define ST_EXPR         1
 #define ST_TTY          2  #define ST_TTY          2
 #define ST_USER         3  #define ST_USER         3
Line 151  struct search_node { Line 147  struct search_node {
 #define ST_TODATE       8  #define ST_TODATE       8
 #define ST_CWD          9  #define ST_CWD          9
     char type;      char type;
    char negated;    bool negated;
    char or;    bool or;
    char pad; 
     union {      union {
 #ifdef HAVE_REGCOMP  #ifdef HAVE_REGCOMP
         regex_t cmdre;          regex_t cmdre;
   #else
           char *pattern;
 #endif  #endif
         time_t tstamp;          time_t tstamp;
         char *cwd;          char *cwd;
         char *tty;          char *tty;
         char *user;          char *user;
         char *pattern;  
         char *runas_group;          char *runas_group;
         char *runas_user;          char *runas_user;
        struct search_node *expr;        struct search_node_list expr;
         void *ptr;          void *ptr;
     } u;      } u;
} *search_expr;};
   
#define STACK_NODE_SIZE 32static struct search_node_list search_expr = STAILQ_HEAD_INITIALIZER(search_expr);
static struct search_node *node_stack[32]; 
static int stack_top; 
   
   static int timing_idx_adj;
   
   static double speed_factor = 1.0;
   
 static const char *session_dir = _PATH_SUDO_IO_LOGDIR;  static const char *session_dir = _PATH_SUDO_IO_LOGDIR;
   
static union io_fd io_fds[IOFD_MAX];static const char short_opts[] =  "d:f:hlm:s:V";
static const char *io_fnames[IOFD_MAX] = {static struct option long_opts[] = {
    "/stdin",    { "directory",  required_argument,      NULL,   'd' },
    "/stdout",    { "filter",         required_argument,      NULL,   'f' },
    "/stderr",    { "help",           no_argument,            NULL,   'h' },
    "/ttyin",    { "list",           no_argument,            NULL,   'l' },
    "/ttyout",    { "max-wait",       required_argument,      NULL,   'm' },
    "/timing"    { "speed",          required_argument,      NULL,   's' },
     { "version",        no_argument,            NULL,   'V' },
     { NULL,             no_argument,            NULL,   '\0' },
 };  };
   
extern time_t get_date(char *);/* XXX move to separate header? */
 extern char *get_timestr(time_t, int);  extern char *get_timestr(time_t, int);
extern int term_raw(int, int);extern time_t get_date(char *);
extern int term_restore(int, int); 
extern void zero_bytes(volatile void *, size_t); 
void cleanup(int); 
   
 static int list_sessions(int, char **, const char *, const char *, const char *);  static int list_sessions(int, char **, const char *, const char *, const char *);
static int parse_expr(struct search_node **, char **);static int open_io_fd(char *path, int len, struct io_log_file *iol);
static void check_input(int, double *);static int parse_expr(struct search_node_list *, char **, bool);
static void delay(double);static int parse_timing(const char *buf, const char *decimal, int *idx, double *seconds, size_t *nbytes);
 static struct log_info *parse_logfile(char *logfile);
 static void check_input(int fd, int what, void *v);
 static void free_log_info(struct log_info *li);
 static void help(void) __attribute__((__noreturn__));  static void help(void) __attribute__((__noreturn__));
   static void replay_session(const double max_wait, const char *decimal);
   static void sudoreplay_cleanup(void);
   static void sudoreplay_handler(int);
 static void usage(int);  static void usage(int);
static int open_io_fd(char *pathbuf, int len, const char *suffix, union io_fd *fdp);static void write_output(int fd, int what, void *v);
static int parse_timing(const char *buf, const char *decimal, int *idx, double *seconds, size_t *nbytes); 
   
 #ifdef HAVE_REGCOMP  #ifdef HAVE_REGCOMP
 # define REGEX_T        regex_t  # define REGEX_T        regex_t
Line 220  static int parse_timing(const char *buf, const char *d Line 222  static int parse_timing(const char *buf, const char *d
     (s)[5] == '/' && \      (s)[5] == '/' && \
     isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \      isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
     (s)[8] == '/' && (s)[9] == 'l' && (s)[10] == 'o' && (s)[11] == 'g' && \      (s)[8] == '/' && (s)[9] == 'l' && (s)[10] == 'o' && (s)[11] == 'g' && \
    (s)[9] == '\0')    (s)[12] == '\0')
   
   __dso_public int main(int argc, char *argv[]);
   
 int  int
 main(int argc, char *argv[])  main(int argc, char *argv[])
 {  {
    int ch, idx, plen, nready, interactive = 0, listonly = 0;    int ch, idx, plen, exitcode = 0, rows = 0, cols = 0;
    const char *id, *user = NULL, *pattern = NULL, *tty = NULL, *decimal = ".";    bool def_filter = true, listonly = false;
    char path[PATH_MAX], buf[LINE_MAX], *cp, *ep;    const char *decimal, *id, *user = NULL, *pattern = NULL, *tty = NULL;
    double seconds, to_wait, speed = 1.0, max_wait = 0;    char *cp, *ep, path[PATH_MAX];
    FILE *lfile;    struct log_info *li;
    fd_set *fdsw;    double max_wait = 0;
    sigaction_t sa;    debug_decl(main, SUDO_DEBUG_MAIN)
    size_t len, nbytes, nread, off; 
    ssize_t nwritten; 
   
#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME)#if defined(SUDO_DEVEL) && defined(__OpenBSD__)
    setprogname(argc > 0 ? argv[0] : "sudoreplay");    {
         extern char *malloc_options;
         malloc_options = "AFGJPR";
     }  
 #endif  #endif
   
#ifdef HAVE_SETLOCALE    initprogname(argc > 0 ? argv[0] : "sudoreplay");
     setlocale(LC_ALL, "");      setlocale(LC_ALL, "");
     decimal = localeconv()->decimal_point;      decimal = localeconv()->decimal_point;
 #endif  
     bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have sudoreplay domain */      bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have sudoreplay domain */
     textdomain("sudoers");      textdomain("sudoers");
   
    while ((ch = getopt(argc, argv, "d:f:hlm:s:V")) != -1) {    /* Register fatal/fatalx callback. */
        switch(ch) {    fatal_callback_register(sudoreplay_cleanup);
 
     /* Read sudo.conf. */
     sudo_conf_read(NULL);
 
     while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
         switch (ch) {
         case 'd':          case 'd':
             session_dir = optarg;              session_dir = optarg;
             break;              break;
         case 'f':          case 'f':
             /* Set the replay filter. */              /* Set the replay filter. */
            replay_filter = 0;            def_filter = false;
             for (cp = strtok(optarg, ","); cp; cp = strtok(NULL, ",")) {              for (cp = strtok(optarg, ","); cp; cp = strtok(NULL, ",")) {
                 if (strcmp(cp, "stdout") == 0)                  if (strcmp(cp, "stdout") == 0)
                    SET(replay_filter, 1 << IOFD_STDOUT);                    io_log_files[IOFD_STDOUT].enabled = true;
                 else if (strcmp(cp, "stderr") == 0)                  else if (strcmp(cp, "stderr") == 0)
                    SET(replay_filter, 1 << IOFD_STDERR);                    io_log_files[IOFD_STDERR].enabled = true;
                 else if (strcmp(cp, "ttyout") == 0)                  else if (strcmp(cp, "ttyout") == 0)
                    SET(replay_filter, 1 << IOFD_TTYOUT);                    io_log_files[IOFD_TTYOUT].enabled = true;
                 else                  else
                    errorx(1, _("invalid filter option: %s"), optarg);                    fatalx(U_("invalid filter option: %s"), optarg);
             }              }
             break;              break;
         case 'h':          case 'h':
             help();              help();
             /* NOTREACHED */              /* NOTREACHED */
         case 'l':          case 'l':
            listonly = 1;            listonly = true;
             break;              break;
         case 'm':          case 'm':
             errno = 0;              errno = 0;
             max_wait = strtod(optarg, &ep);              max_wait = strtod(optarg, &ep);
             if (*ep != '\0' || errno != 0)              if (*ep != '\0' || errno != 0)
                errorx(1, _("invalid max wait: %s"), optarg);                fatalx(U_("invalid max wait: %s"), optarg);
             break;              break;
         case 's':          case 's':
             errno = 0;              errno = 0;
            speed = strtod(optarg, &ep);            speed_factor = strtod(optarg, &ep);
             if (*ep != '\0' || errno != 0)              if (*ep != '\0' || errno != 0)
                errorx(1, _("invalid speed factor: %s"), optarg);                fatalx(U_("invalid speed factor: %s"), optarg);
             break;              break;
         case 'V':          case 'V':
             (void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION);              (void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION);
            exit(0);            goto done;
         default:          default:
             usage(1);              usage(1);
             /* NOTREACHED */              /* NOTREACHED */
Line 295  main(int argc, char *argv[]) Line 305  main(int argc, char *argv[])
     argc -= optind;      argc -= optind;
     argv += optind;      argv += optind;
   
    if (listonly)    if (listonly) {
        exit(list_sessions(argc, argv, pattern, user, tty));        exitcode = list_sessions(argc, argv, pattern, user, tty);
         goto done;
     }
   
     if (argc != 1)      if (argc != 1)
         usage(1);          usage(1);
   
       /* By default we replay stdout, stderr and ttyout. */
       if (def_filter) {
           io_log_files[IOFD_STDOUT].enabled = true;
           io_log_files[IOFD_STDERR].enabled = true;
           io_log_files[IOFD_TTYOUT].enabled = true;
       }
   
     /* 6 digit ID in base 36, e.g. 01G712AB or free-form name */      /* 6 digit ID in base 36, e.g. 01G712AB or free-form name */
     id = argv[0];      id = argv[0];
     if (VALID_ID(id)) {      if (VALID_ID(id)) {
         plen = snprintf(path, sizeof(path), "%s/%.2s/%.2s/%.2s/timing",          plen = snprintf(path, sizeof(path), "%s/%.2s/%.2s/%.2s/timing",
             session_dir, id, &id[2], &id[4]);              session_dir, id, &id[2], &id[4]);
        if (plen <= 0 || plen >= sizeof(path))        if (plen <= 0 || (size_t)plen >= sizeof(path))
            errorx(1, _("%s/%.2s/%.2s/%.2s/timing: %s"), session_dir,            fatalx(U_("%s/%.2s/%.2s/%.2s/timing: %s"), session_dir,
                 id, &id[2], &id[4], strerror(ENAMETOOLONG));                  id, &id[2], &id[4], strerror(ENAMETOOLONG));
     } else {      } else {
         plen = snprintf(path, sizeof(path), "%s/%s/timing",          plen = snprintf(path, sizeof(path), "%s/%s/timing",
             session_dir, id);              session_dir, id);
        if (plen <= 0 || plen >= sizeof(path))        if (plen <= 0 || (size_t)plen >= sizeof(path))
            errorx(1, _("%s/%s/timing: %s"), session_dir,            fatalx(U_("%s/%s/timing: %s"), session_dir,
                 id, strerror(ENAMETOOLONG));                  id, strerror(ENAMETOOLONG));
     }      }
     plen -= 7;      plen -= 7;
   
     /* Open files for replay, applying replay filter for the -f flag. */      /* Open files for replay, applying replay filter for the -f flag. */
     for (idx = 0; idx < IOFD_MAX; idx++) {      for (idx = 0; idx < IOFD_MAX; idx++) {
        if (ISSET(replay_filter, 1 << idx) || idx == IOFD_TIMING) {        if (open_io_fd(path, plen, &io_log_files[idx]) == -1) 
            if (open_io_fd(path, plen, io_fnames[idx], &io_fds[idx]) == -1)            fatal(U_("unable to open %s"), path);
                error(1, _("unable to open %s"), path); 
        } 
     }      }
   
    /* Read log file. */    /* Parse log file. */
     path[plen] = '\0';      path[plen] = '\0';
     strlcat(path, "/log", sizeof(path));      strlcat(path, "/log", sizeof(path));
    lfile = fopen(path, "r");    if ((li = parse_logfile(path)) == NULL)
    if (lfile == NULL)        exit(1);
        error(1, _("unable to open %s"), path);    printf(_("Replaying sudo session: %s\n"), li->cmd);
    cp = NULL;
    len = 0;    /* Make sure the terminal is large enough. */
    /* Pull out command (third line). */    get_ttysize(&rows, &cols);
    if (getline(&cp, &len, lfile) == -1 ||    if (li->rows != 0 && li->cols != 0) {
        getline(&cp, &len, lfile) == -1 ||        if (li->rows > rows) {
        getline(&cp, &len, lfile) == -1) {            printf(_("Warning: your terminal is too small to properly replay the log.\n"));
        errorx(1, _("invalid log file %s"), path);            printf(_("Log geometry is %d x %d, your terminal's geometry is %d x %d."), li->rows, li->cols, rows, cols);
         }
     }      }
     printf(_("Replaying sudo session: %s"), cp);  
     free(cp);  
     fclose(lfile);  
   
       /* Done with parsed log file. */
       free_log_info(li);
       li = NULL;
   
       /* Replay session corresponding to io_log_files[]. */
       replay_session(max_wait, decimal);
   
       term_restore(STDIN_FILENO, 1);
   done:
       sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode);
       exit(exitcode);
   }
   
   static void
   replay_session(const double max_wait, const char *decimal)
   {
       struct sudo_event *input_ev, *output_ev;
       unsigned int i, iovcnt = 0, iovmax = 0;
       struct sudo_event_base *evbase;
       struct iovec iovb, *iov = &iovb;
       bool interactive;
       struct write_closure wc;
       char buf[LINE_MAX];
       sigaction_t sa;
       int idx;
       debug_decl(replay_session, SUDO_DEBUG_UTIL)
   
       /* Restore tty settings if interupted. */
     fflush(stdout);      fflush(stdout);
    zero_bytes(&sa, sizeof(sa));    memset(&sa, 0, sizeof(sa));
     sigemptyset(&sa.sa_mask);      sigemptyset(&sa.sa_mask);
     sa.sa_flags = SA_RESETHAND;      sa.sa_flags = SA_RESETHAND;
    sa.sa_handler = cleanup;    sa.sa_handler = sudoreplay_handler;
     (void) sigaction(SIGINT, &sa, NULL);      (void) sigaction(SIGINT, &sa, NULL);
     (void) sigaction(SIGKILL, &sa, NULL);  
     (void) sigaction(SIGTERM, &sa, NULL);      (void) sigaction(SIGTERM, &sa, NULL);
     (void) sigaction(SIGHUP, &sa, NULL);      (void) sigaction(SIGHUP, &sa, NULL);
       (void) sigaction(SIGQUIT, &sa, NULL);
   
       /* Don't suspend as we cannot restore the screen on resume. */
     sa.sa_flags = SA_RESTART;      sa.sa_flags = SA_RESTART;
     sa.sa_handler = SIG_IGN;      sa.sa_handler = SIG_IGN;
     (void) sigaction(SIGTSTP, &sa, NULL);      (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 */      /* 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 */      /* Set stdin to raw mode if it is a tty */
     interactive = isatty(STDIN_FILENO);      interactive = isatty(STDIN_FILENO);
     if (interactive) {      if (interactive) {
        ch = fcntl(STDIN_FILENO, F_GETFL, 0);        idx = fcntl(STDIN_FILENO, F_GETFL, 0);
        if (ch != -1)        if (idx != -1)
            (void) fcntl(STDIN_FILENO, F_SETFL, ch | O_NONBLOCK);            (void) fcntl(STDIN_FILENO, F_SETFL, idx | O_NONBLOCK);
         if (!term_raw(STDIN_FILENO, 1))          if (!term_raw(STDIN_FILENO, 1))
            error(1, _("unable to set tty to raw mode"));            fatal(U_("unable to set tty to raw mode"));
     }      }
     fdsw = (fd_set *)emalloc2(howmany(STDOUT_FILENO + 1, NFDBITS),  
         sizeof(fd_mask));  
   
       /* Setup event base and input/output events. */
       evbase = sudo_ev_base_alloc();
       if (evbase == NULL)
           fatal(NULL);
       input_ev = sudo_ev_alloc(STDIN_FILENO, interactive ? SUDO_EV_READ :
           SUDO_EV_TIMEOUT, check_input, sudo_ev_self_cbarg());
       if (input_ev == NULL)
           fatal(NULL);
       output_ev = sudo_ev_alloc(STDIN_FILENO, SUDO_EV_WRITE, write_output, &wc);
       if (output_ev == NULL)
           fatal(NULL);
   
     /*      /*
     * Timing file consists of line of the format: "%f %d\n"     * Read each line of the timing file, displaying the output streams.
      */       */
 #ifdef HAVE_ZLIB_H  #ifdef HAVE_ZLIB_H
    while (gzgets(io_fds[IOFD_TIMING].g, buf, sizeof(buf)) != NULL) {    while (gzgets(io_log_files[IOFD_TIMING].fd.g, buf, sizeof(buf)) != NULL) {
 #else  #else
    while (fgets(buf, sizeof(buf), io_fds[IOFD_TIMING].f) != NULL) {    while (fgets(buf, sizeof(buf), io_log_files[IOFD_TIMING].fd.f) != NULL) {
 #endif  #endif
           size_t len, nbytes, nread;
           double seconds, to_wait;
           struct timeval timeout;
           bool need_nlcr = false;
           char last_char = '\0';
   
           buf[strcspn(buf, "\n")] = '\0';
         if (!parse_timing(buf, decimal, &idx, &seconds, &nbytes))          if (!parse_timing(buf, decimal, &idx, &seconds, &nbytes))
            errorx(1, _("invalid timing file line: %s"), buf);            fatalx(U_("invalid timing file line: %s"), buf);
   
         if (interactive)  
             check_input(STDIN_FILENO, &speed);  
   
         /* Adjust delay using speed factor and clamp to max_wait */          /* Adjust delay using speed factor and clamp to max_wait */
        to_wait = seconds / speed;        to_wait = seconds / speed_factor;
         if (max_wait && to_wait > max_wait)          if (max_wait && to_wait > max_wait)
             to_wait = max_wait;              to_wait = max_wait;
         delay(to_wait);  
   
        /* Even if we are not relaying, we still have to delay. */        /* Convert delay to a timeval. */
        if (io_fds[idx].v == NULL)        timeout.tv_sec = to_wait;
         timeout.tv_usec = (to_wait - timeout.tv_sec) * 1000000.0;
 
         /* Run event event loop to delay and get keyboard input. */
         sudo_ev_add(evbase, input_ev, &timeout, false);
         sudo_ev_loop(evbase, 0);
 
         /* Even if we are not replaying, we still have to delay. */
         if (io_log_files[idx].fd.v == NULL)
             continue;              continue;
   
           /* Check whether we need to convert newline to CR LF pairs. */
           if (interactive) 
               need_nlcr = (idx == IOFD_STDOUT || idx == IOFD_STDERR);
   
         /* All output is sent to stdout. */          /* All output is sent to stdout. */
           /* XXX - assumes no wall clock time spent writing output. */
         while (nbytes != 0) {          while (nbytes != 0) {
             if (nbytes > sizeof(buf))              if (nbytes > sizeof(buf))
                 len = sizeof(buf);                  len = sizeof(buf);
             else              else
                 len = nbytes;                  len = nbytes;
 #ifdef HAVE_ZLIB_H  #ifdef HAVE_ZLIB_H
            nread = gzread(io_fds[idx].g, buf, len);            nread = gzread(io_log_files[idx].fd.g, buf, len);
 #else  #else
            nread = fread(buf, 1, len, io_fds[idx].f);            nread = fread(buf, 1, len, io_log_files[idx].fd.f);
 #endif  #endif
             nbytes -= nread;              nbytes -= nread;
            off = 0;
            do {            /* Convert newline to carriage return + linefeed if needed. */
                /* no stdio, must be unbuffered */            if (need_nlcr) {
                nwritten = write(STDOUT_FILENO, buf + off, nread - off);                size_t remainder = nread;
                if (nwritten == -1) {                size_t linelen;
                    if (errno == EINTR)                char *cp = buf;
                        continue;                char *ep = buf - 1;
                    if (errno == EAGAIN) {
                        FD_SET(STDOUT_FILENO, fdsw);                /* Handle a "\r\n" pair that spans a buffer. */
                        do {                if (last_char == '\r' && buf[0] == '\n') {
                            nready = select(STDOUT_FILENO + 1, NULL, fdsw, NULL, NULL);                    ep++;
                        } while (nready == -1 && errno == EINTR);                    remainder--;
                        if (nready == 1) 
                            continue; 
                    } 
                    error(1, _("writing to standard output")); 
                 }                  }
                 off += nwritten;  
             } while (nread > off);  
         }  
     }  
     term_restore(STDIN_FILENO, 1);  
     exit(0);  
 }  
   
static void                iovcnt = 0;
delay(double secs)                while ((ep = memchr(ep + 1, '\n', remainder)) != NULL) {
{                    /* Is there already a carriage return? */
    struct timespec ts, rts;                    if (cp != ep && ep[-1] == '\r') {
    int rval;                        remainder = (size_t)(&buf[nread - 1] - ep);
                         continue;
                     }
   
    /*                    /* Store the line in iov followed by \r\n pair. */
     * Typical max resolution is 1/HZ but we can't portably check that.                    if (iovcnt + 3 > iovmax) {
     * If the interval is small enough, just ignore it.                        iov = iovmax ?
     */                            erealloc3(iov, iovmax <<= 1, sizeof(*iov)) :
    if (secs < 0.0001)                            emalloc2(iovmax = 32, sizeof(*iov));
        return;                    }
                     linelen = (size_t)(ep - cp) + 1;
                     iov[iovcnt].iov_base = cp;
                     iov[iovcnt].iov_len = linelen - 1; /* not including \n */
                     iovcnt++;
                     iov[iovcnt].iov_base = "\r\n";
                     iov[iovcnt].iov_len = 2;
                     iovcnt++;
                     cp = ep + 1;
                     remainder -= linelen;
                 }
                 if ((size_t)(cp - buf) != nread) {
                     /*
                      * Partial line without a linefeed or multiple lines
                      * with \r\n pairs.
                      */
                     iov[iovcnt].iov_base = cp;
                     iov[iovcnt].iov_len = nread - (cp - buf);
                     iovcnt++;
                 }
                 last_char = buf[nread - 1]; /* stash last char of old buffer */
             } else {
                 /* No conversion needed. */
                 iov[0].iov_base = buf;
                 iov[0].iov_len = nread;
                 iovcnt = 1;
             }
   
    rts.tv_sec = secs;            /* Setup closure for write_output. */
    rts.tv_nsec = (secs - (double) rts.tv_sec) * 1000000000.0;            wc.wevent = output_ev;
    do {            wc.iov = iov;
      memcpy(&ts, &rts, sizeof(ts));            wc.iovcnt = iovcnt;
      rval = nanosleep(&ts, &rts);            wc.nbytes = 0;
    } while (rval == -1 && errno == EINTR);            for (i = 0; i < iovcnt; i++)
    if (rval == -1) {                wc.nbytes += iov[i].iov_len;
        error(1, _("nanosleep: tv_sec %ld, tv_nsec %ld"),
            (long)ts.tv_sec, (long)ts.tv_nsec);            /* Run event event loop to write output. */
             /* XXX - should use a single event loop with a circular buffer. */
             sudo_ev_add(evbase, output_ev, NULL, false);
             sudo_ev_loop(evbase, 0);
         }
     }      }
       debug_return;
 }  }
   
 static int  static int
open_io_fd(char *path, int len, const char *suffix, union io_fd *fdp)open_io_fd(char *path, int len, struct io_log_file *iol)
 {  {
    path[len] = '\0';    debug_decl(open_io_fd, SUDO_DEBUG_UTIL)
    strlcat(path, suffix, PATH_MAX); 
   
       if (!iol->enabled)
           debug_return_int(0);
   
       path[len] = '\0';
       strlcat(path, iol->suffix, PATH_MAX);
 #ifdef HAVE_ZLIB_H  #ifdef HAVE_ZLIB_H
    fdp->g = gzopen(path, "r");    iol->fd.g = gzopen(path, "r");
    return fdp->g ? 0 : -1; 
 #else  #else
    fdp->f = fopen(path, "r");    iol->fd.f = fopen(path, "r");
    return fdp->f ? 0 : -1; 
 #endif  #endif
       debug_return_int(iol->fd.v ? 0 : -1);
 }  }
   
   static void
   write_output(int fd, int what, void *v)
   {
       struct write_closure *wc = v;
       ssize_t nwritten;
       size_t count, remainder;
       unsigned int i;
       debug_decl(write_output, SUDO_DEBUG_UTIL)
   
       nwritten = writev(STDOUT_FILENO, wc->iov, wc->iovcnt);
       switch (nwritten) {
       case -1:
           if (errno != EINTR && errno != EAGAIN)
               fatal(U_("unable to write to %s"), "stdout");
           break;
       case 0:
           break;
       default:
           remainder = wc->nbytes - nwritten;
           if (remainder == 0) {
               /* writev completed */
               debug_return;
           }
   
           /* short writev, adjust iov so we can write the remainder. */
           count = 0;
           i = wc->iovcnt;
           while (i--) {
               count += wc->iov[i].iov_len;
               if (count == remainder) {
                   wc->iov += i;
                   wc->iovcnt -= i;
                   break;
               }
               if (count > remainder) {
                   size_t off = (count - remainder);
                   wc->iov[i].iov_base = (char *)wc->iov[i].iov_base + off;
                   wc->iov[i].iov_len -= off;
                   wc->iov += i;
                   wc->iovcnt -= i;
                   break;
               }
           }
           break;
       }
   
       /* Reschedule event to write remainder. */
       sudo_ev_add(sudo_ev_get_base(wc->wevent), wc->wevent, NULL, false);
       debug_return;
   }
   
 /*  /*
  * Build expression list from search args   * Build expression list from search args
  */   */
 static int  static int
parse_expr(struct search_node **headp, char *argv[])parse_expr(struct search_node_list *head, char *argv[], bool sub_expr)
 {  {
    struct search_node *sn, *newsn;    bool or = false, not = false;
    char or = 0, not = 0, type, **av;    struct search_node *sn;
     char type, **av;
     debug_decl(parse_expr, SUDO_DEBUG_UTIL)
   
    sn = *headp;    for (av = argv; *av != NULL; av++) {
    for (av = argv; *av; av++) { 
         switch (av[0][0]) {          switch (av[0][0]) {
         case 'a': /* and (ignore) */          case 'a': /* and (ignore) */
             if (strncmp(*av, "and", strlen(*av)) != 0)              if (strncmp(*av, "and", strlen(*av)) != 0)
Line 491  parse_expr(struct search_node **headp, char *argv[]) Line 638  parse_expr(struct search_node **headp, char *argv[])
         case 'o': /* or */          case 'o': /* or */
             if (strncmp(*av, "or", strlen(*av)) != 0)              if (strncmp(*av, "or", strlen(*av)) != 0)
                 goto bad;                  goto bad;
            or = 1;            or = true;
             continue;              continue;
         case '!': /* negate */          case '!': /* negate */
             if (av[0][1] != '\0')              if (av[0][1] != '\0')
                 goto bad;                  goto bad;
            not = 1;            not = true;
             continue;              continue;
         case 'c': /* command */          case 'c': /* command */
             if (av[0][1] == '\0')              if (av[0][1] == '\0')
                errorx(1, _("ambiguous expression \"%s\""), *av);                fatalx(U_("ambiguous expression \"%s\""), *av);
             if (strncmp(*av, "cwd", strlen(*av)) == 0)              if (strncmp(*av, "cwd", strlen(*av)) == 0)
                 type = ST_CWD;                  type = ST_CWD;
             else if (strncmp(*av, "command", strlen(*av)) == 0)              else if (strncmp(*av, "command", strlen(*av)) == 0)
Line 525  parse_expr(struct search_node **headp, char *argv[]) Line 672  parse_expr(struct search_node **headp, char *argv[])
             break;              break;
         case 't': /* tty or to date */          case 't': /* tty or to date */
             if (av[0][1] == '\0')              if (av[0][1] == '\0')
                errorx(1, _("ambiguous expression \"%s\""), *av);                fatalx(U_("ambiguous expression \"%s\""), *av);
             if (strncmp(*av, "todate", strlen(*av)) == 0)              if (strncmp(*av, "todate", strlen(*av)) == 0)
                 type = ST_TODATE;                  type = ST_TODATE;
             else if (strncmp(*av, "tty", strlen(*av)) == 0)              else if (strncmp(*av, "tty", strlen(*av)) == 0)
Line 541  parse_expr(struct search_node **headp, char *argv[]) Line 688  parse_expr(struct search_node **headp, char *argv[])
         case '(': /* start sub-expression */          case '(': /* start sub-expression */
             if (av[0][1] != '\0')              if (av[0][1] != '\0')
                 goto bad;                  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;              type = ST_EXPR;
             break;              break;
         case ')': /* end sub-expression */          case ')': /* end sub-expression */
             if (av[0][1] != '\0')              if (av[0][1] != '\0')
                 goto bad;                  goto bad;
            /* pop */            if (!sub_expr)
            if (--stack_top < 0)                fatalx(U_("unmatched ')' in expression"));
                errorx(1, _("unmatched ')' in expression"));            debug_return_int(av - argv + 1);
            if (node_stack[stack_top]) 
                sn->next = node_stack[stack_top]->next; 
            return av - argv + 1; 
         bad:          bad:
         default:          default:
            errorx(1, _("unknown search term \"%s\""), *av);            fatalx(U_("unknown search term \"%s\""), *av);
             /* NOTREACHED */              /* NOTREACHED */
         }          }
   
         /* Allocate new search node */          /* Allocate new search node */
        newsn = emalloc(sizeof(*newsn));        sn = ecalloc(1, sizeof(*sn));
        newsn->next = NULL;        sn->type = type;
        newsn->type = type;        sn->or = or;
        newsn->or = or;        sn->negated = not;
        newsn->negated = not; 
         if (type == ST_EXPR) {          if (type == ST_EXPR) {
            av += parse_expr(&newsn->u.expr, av + 1);            STAILQ_INIT(&sn->u.expr);
             av += parse_expr(&sn->u.expr, av + 1, true);
         } else {          } else {
             if (*(++av) == NULL)              if (*(++av) == NULL)
                errorx(1, _("%s requires an argument"), av[-1]);                fatalx(U_("%s requires an argument"), av[-1]);
 #ifdef HAVE_REGCOMP  #ifdef HAVE_REGCOMP
             if (type == ST_PATTERN) {              if (type == ST_PATTERN) {
                if (regcomp(&newsn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0)                if (regcomp(&sn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0)
                    errorx(1, _("invalid regular expression: %s"), *av);                    fatalx(U_("invalid regular expression: %s"), *av);
             } else              } else
 #endif  #endif
             if (type == ST_TODATE || type == ST_FROMDATE) {              if (type == ST_TODATE || type == ST_FROMDATE) {
                newsn->u.tstamp = get_date(*av);                sn->u.tstamp = get_date(*av);
                if (newsn->u.tstamp == -1)                if (sn->u.tstamp == -1)
                    errorx(1, _("could not parse date \"%s\""), *av);                    fatalx(U_("could not parse date \"%s\""), *av);
             } else {              } else {
                newsn->u.ptr = *av;                sn->u.ptr = *av;
             }              }
         }          }
        not = or = 0; /* reset state */        not = or = false; /* reset state */
        if (sn)        STAILQ_INSERT_TAIL(head, sn, entries);
            sn->next = newsn; 
        else 
            *headp = newsn; 
        sn = newsn; 
     }      }
    if (stack_top)    if (sub_expr)
        errorx(1, _("unmatched '(' in expression"));        fatalx(U_("unmatched '(' in expression"));
     if (or)      if (or)
        errorx(1, _("illegal trailing \"or\""));        fatalx(U_("illegal trailing \"or\""));
     if (not)      if (not)
        errorx(1, _("illegal trailing \"!\""));        fatalx(U_("illegal trailing \"!\""));
   
    return av - argv;    debug_return_int(av - argv);
 }  }
   
static intstatic bool
match_expr(struct search_node *head, struct log_info *log)match_expr(struct search_node_list *head, struct log_info *log, bool last_match)
 {  {
     struct search_node *sn;      struct search_node *sn;
    int matched = 1, rc;    bool res, matched = last_match;
     int rc;
     debug_decl(match_expr, SUDO_DEBUG_UTIL)
   
    for (sn = head; sn; sn = sn->next) {    STAILQ_FOREACH(sn, head, entries) {
        /* If we have no match, skip ahead to the next OR entry. */ 
        if (!matched && !sn->or) 
            continue; 
 
         switch (sn->type) {          switch (sn->type) {
         case ST_EXPR:          case ST_EXPR:
            matched = match_expr(sn->u.expr, log);            res = match_expr(&sn->u.expr, log, matched);
             break;              break;
         case ST_CWD:          case ST_CWD:
            matched = strcmp(sn->u.cwd, log->cwd) == 0;            res = strcmp(sn->u.cwd, log->cwd) == 0;
             break;              break;
         case ST_TTY:          case ST_TTY:
            matched = strcmp(sn->u.tty, log->tty) == 0;            res = strcmp(sn->u.tty, log->tty) == 0;
             break;              break;
         case ST_RUNASGROUP:          case ST_RUNASGROUP:
            matched = strcmp(sn->u.runas_group, log->runas_group) == 0;            res = strcmp(sn->u.runas_group, log->runas_group) == 0;
             break;              break;
         case ST_RUNASUSER:          case ST_RUNASUSER:
            matched = strcmp(sn->u.runas_user, log->runas_user) == 0;            res = strcmp(sn->u.runas_user, log->runas_user) == 0;
             break;              break;
         case ST_USER:          case ST_USER:
            matched = strcmp(sn->u.user, log->user) == 0;            res = strcmp(sn->u.user, log->user) == 0;
             break;              break;
         case ST_PATTERN:          case ST_PATTERN:
 #ifdef HAVE_REGCOMP  #ifdef HAVE_REGCOMP
Line 641  match_expr(struct search_node *head, struct log_info * Line 774  match_expr(struct search_node *head, struct log_info *
             if (rc && rc != REG_NOMATCH) {              if (rc && rc != REG_NOMATCH) {
                 char buf[BUFSIZ];                  char buf[BUFSIZ];
                 regerror(rc, &sn->u.cmdre, buf, sizeof(buf));                  regerror(rc, &sn->u.cmdre, buf, sizeof(buf));
                errorx(1, "%s", buf);                fatalx("%s", buf);
             }              }
            matched = rc == REG_NOMATCH ? 0 : 1;            res = rc == REG_NOMATCH ? 0 : 1;
 #else  #else
            matched = strstr(log.cmd, sn->u.pattern) != NULL;            res = strstr(log.cmd, sn->u.pattern) != NULL;
 #endif  #endif
             break;              break;
         case ST_FROMDATE:          case ST_FROMDATE:
            matched = log->tstamp >= sn->u.tstamp;            res = log->tstamp >= sn->u.tstamp;
             break;              break;
         case ST_TODATE:          case ST_TODATE:
            matched = log->tstamp <= sn->u.tstamp;            res = log->tstamp <= sn->u.tstamp;
             break;              break;
           default:
               fatalx(U_("unknown search type %d"), sn->type);
               /* NOTREACHED */
         }          }
         if (sn->negated)          if (sn->negated)
            matched = !matched;            res = !res;
         matched = sn->or ? (res || last_match) : (res && last_match);
         last_match = matched;
     }      }
    return matched;    debug_return_bool(matched);
 }  }
   
static intstatic struct log_info *
list_session(char *logfile, REGEX_T *re, const char *user, const char *tty)parse_logfile(char *logfile)
 {  {
     FILE *fp;      FILE *fp;
    char *buf = NULL, *cmd = NULL, *cwd = NULL, idbuf[7], *idstr, *cp;    char *buf = NULL, *cp, *ep;
    struct log_info li;    const char *errstr;
     size_t bufsize = 0, cwdsize = 0, cmdsize = 0;      size_t bufsize = 0, cwdsize = 0, cmdsize = 0;
    int rval = -1;    struct log_info *li = NULL;
     debug_decl(parse_logfile, SUDO_DEBUG_UTIL)
   
     fp = fopen(logfile, "r");      fp = fopen(logfile, "r");
     if (fp == NULL) {      if (fp == NULL) {
        warning(_("unable to open %s"), logfile);        warning(U_("unable to open %s"), logfile);
        goto done;        goto bad;
     }      }
   
     /*      /*
Line 682  list_session(char *logfile, REGEX_T *re, const char *u Line 821  list_session(char *logfile, REGEX_T *re, const char *u
      *  2) cwd       *  2) cwd
      *  3) command with args       *  3) command with args
      */       */
       li = ecalloc(1, sizeof(*li));
     if (getline(&buf, &bufsize, fp) == -1 ||      if (getline(&buf, &bufsize, fp) == -1 ||
        getline(&cwd, &cwdsize, fp) == -1 ||        getline(&li->cwd, &cwdsize, fp) == -1 ||
        getline(&cmd, &cmdsize, fp) == -1) {        getline(&li->cmd, &cmdsize, fp) == -1) {
        goto done;        warning(U_("%s: invalid log file"), logfile);
         goto bad;
     }      }
   
    /* crack the log line: timestamp:user:runas_user:runas_group:tty */    /* Strip the newline from the cwd and command. */
     li->cwd[strcspn(li->cwd, "\n")] = '\0';
     li->cmd[strcspn(li->cmd, "\n")] = '\0';
 
     /*
      * Crack the log line (rows and cols not present in old versions).
      *        timestamp:user:runas_user:runas_group:tty:rows:cols
      * XXX - probably better to use strtok and switch on the state.
      */
     buf[strcspn(buf, "\n")] = '\0';      buf[strcspn(buf, "\n")] = '\0';
    if ((li.tstamp = atoi(buf)) == 0)    cp = buf;
        goto done; 
   
    if ((cp = strchr(buf, ':')) == NULL)    /* timestamp */
        goto done;    if ((ep = strchr(cp, ':')) == NULL) {
    *cp++ = '\0';        warning(U_("%s: time stamp field is missing"), logfile);
    li.user = cp;        goto bad;
     }
     *ep = '\0';
     li->tstamp = sizeof(time_t) == 4 ? strtonum(cp, INT_MIN, INT_MAX, &errstr) :
         strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
     if (errstr != NULL) {
         warning(U_("%s: time stamp %s: %s"), logfile, cp, errstr);
         goto bad;
     }
   
    if ((cp = strchr(cp, ':')) == NULL)    /* user */
        goto done;    cp = ep + 1;
    *cp++ = '\0';    if ((ep = strchr(cp, ':')) == NULL) {
    li.runas_user = cp;        warning(U_("%s: user field is missing"), logfile);
         goto bad;
     }
     li->user = estrndup(cp, (size_t)(ep - cp));
   
    if ((cp = strchr(cp, ':')) == NULL)    /* runas user */
        goto done;    cp = ep + 1;
    *cp++ = '\0';    if ((ep = strchr(cp, ':')) == NULL) {
    li.runas_group = cp;        warning(U_("%s: runas user field is missing"), logfile);
         goto bad;
     }
     li->runas_user = estrndup(cp, (size_t)(ep - cp));
   
    if ((cp = strchr(cp, ':')) == NULL)    /* runas group */
        goto done;    cp = ep + 1;
    *cp++ = '\0';    if ((ep = strchr(cp, ':')) == NULL) {
    li.tty = cp;        warning(U_("%s: runas group field is missing"), logfile);
         goto bad;
     }
     if (cp != ep)
         li->runas_group = estrndup(cp, (size_t)(ep - cp));
   
    cwd[strcspn(cwd, "\n")] = '\0';    /* tty, followed by optional rows + columns */
    li.cwd = cwd;    cp = ep + 1;
     if ((ep = strchr(cp, ':')) == NULL) {
         /* just the tty */
         li->tty = estrdup(cp);
     } else {
         /* tty followed by rows + columns */
         li->tty = estrndup(cp, (size_t)(ep - cp));
         cp = ep + 1;
         /* need to NULL out separator to use strtonum() */
         if ((ep = strchr(cp, ':')) != NULL) {
             *ep = '\0';
         }
         li->rows = strtonum(cp, 1, INT_MAX, &errstr);
         if (errstr != NULL) {
             sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
                 "%s: tty rows %s: %s", logfile, cp, errstr);
         }
         if (ep != NULL) {
             cp = ep + 1;
             li->cols = strtonum(cp, 1, INT_MAX, &errstr);
             if (errstr != NULL) {
                 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
                     "%s: tty cols %s: %s", logfile, cp, errstr);
             }
         }
     }
     fclose(fp);
     efree(buf);
     debug_return_ptr(li);
   
    cmd[strcspn(cmd, "\n")] = '\0';bad:
    li.cmd = cmd;    if (fp != NULL)
         fclose(fp);
     efree(buf);
     free_log_info(li);
     debug_return_ptr(NULL);
 }
   
   static void
   free_log_info(struct log_info *li)
   {
       if (li != NULL) {
           efree(li->cwd);
           efree(li->user);
           efree(li->runas_user);
           efree(li->runas_group);
           efree(li->tty);
           efree(li->cmd);
           efree(li);
       }
   }
   
   static int
   list_session(char *logfile, REGEX_T *re, const char *user, const char *tty)
   {
       char idbuf[7], *idstr, *cp;
       const char *timestr;
       struct log_info *li;
       int rval = -1;
       debug_decl(list_session, SUDO_DEBUG_UTIL)
   
       if ((li = parse_logfile(logfile)) == NULL)
           goto done;
   
     /* Match on search expression if there is one. */      /* Match on search expression if there is one. */
    if (search_expr && !match_expr(search_expr, &li))    if (!STAILQ_EMPTY(&search_expr) && !match_expr(&search_expr, li, true))
         goto done;          goto done;
   
     /* Convert from /var/log/sudo-sessions/00/00/01/log to 000001 */      /* Convert from /var/log/sudo-sessions/00/00/01/log to 000001 */
     cp = logfile + strlen(session_dir) + 1;      cp = logfile + strlen(session_dir) + 1;
     if (IS_IDLOG(cp)) {      if (IS_IDLOG(cp)) {
        idbuf[0] = cp[7];        idbuf[0] = cp[0];
        idbuf[1] = cp[6];        idbuf[1] = cp[1];
        idbuf[2] = cp[4];        idbuf[2] = cp[3];
        idbuf[3] = cp[3];        idbuf[3] = cp[4];
        idbuf[4] = cp[1];        idbuf[4] = cp[6];
        idbuf[5] = cp[0];        idbuf[5] = cp[7];
         idbuf[6] = '\0';          idbuf[6] = '\0';
         idstr = idbuf;          idstr = idbuf;
     } else {      } else {
Line 739  list_session(char *logfile, REGEX_T *re, const char *u Line 964  list_session(char *logfile, REGEX_T *re, const char *u
         cp[strlen(cp) - 4] = '\0';          cp[strlen(cp) - 4] = '\0';
         idstr = cp;          idstr = cp;
     }      }
       /* XXX - print rows + cols? */
       timestr = get_timestr(li->tstamp, 1);
     printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ",      printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ",
        get_timestr(li.tstamp, 1), li.user, li.tty, li.cwd, li.runas_user);        timestr ? timestr : "invalid date",
    if (*li.runas_group)        li->user, li->tty, li->cwd, li->runas_user);
        printf("GROUP=%s ; ", li.runas_group);    if (li->runas_group)
    printf("TSID=%s ; COMMAND=%s\n", idstr, li.cmd);        printf("GROUP=%s ; ", li->runas_group);
     printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd);
   
     rval = 0;      rval = 0;
   
 done:  done:
    fclose(fp);    free_log_info(li);
    return rval;    debug_return_int(rval);
 }  }
   
 static int  static int
   session_compare(const void *v1, const void *v2)
   {
       const char *s1 = *(const char **)v1;
       const char *s2 = *(const char **)v2;
       return strcmp(s1, s2);
   }
   
   /* XXX - always returns 0, calls fatal() on failure */
   static int
 find_sessions(const char *dir, REGEX_T *re, const char *user, const char *tty)  find_sessions(const char *dir, REGEX_T *re, const char *user, const char *tty)
 {  {
     DIR *d;      DIR *d;
     struct dirent *dp;      struct dirent *dp;
     struct stat sb;      struct stat sb;
    size_t sdlen;    size_t sdlen, sessions_len = 0, sessions_size = 36*36;
     unsigned int i;
     int len;      int len;
    char pathbuf[PATH_MAX];    char pathbuf[PATH_MAX], **sessions = NULL;
 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
     bool checked_type = true;
 #else
     const bool checked_type = false;
 #endif
     debug_decl(find_sessions, SUDO_DEBUG_UTIL)
   
     d = opendir(dir);      d = opendir(dir);
     if (d == NULL)      if (d == NULL)
        error(1, _("unable to open %s"), dir);        fatal(U_("unable to open %s"), dir);
   
     /* XXX - would be faster to chdir and use relative names */      /* XXX - would be faster to chdir and use relative names */
     sdlen = strlcpy(pathbuf, dir, sizeof(pathbuf));      sdlen = strlcpy(pathbuf, dir, sizeof(pathbuf));
     if (sdlen + 1 >= sizeof(pathbuf)) {      if (sdlen + 1 >= sizeof(pathbuf)) {
         errno = ENAMETOOLONG;          errno = ENAMETOOLONG;
        error(1, "%s/", dir);        fatal("%s/", dir);
     }      }
     pathbuf[sdlen++] = '/';      pathbuf[sdlen++] = '/';
     pathbuf[sdlen] = '\0';      pathbuf[sdlen] = '\0';
   
       /* Store potential session dirs for sorting. */
       sessions = emalloc2(sessions_size, sizeof(char *));
     while ((dp = readdir(d)) != NULL) {      while ((dp = readdir(d)) != NULL) {
         /* Skip "." and ".." */          /* Skip "." and ".." */
         if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||          if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
             (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))              (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
             continue;              continue;
   #ifdef HAVE_STRUCT_DIRENT_D_TYPE
           if (checked_type) {
               if (dp->d_type != DT_DIR) {
                   /* Not all file systems support d_type. */
                   if (dp->d_type != DT_UNKNOWN)
                       continue;
                   checked_type = false;
               }
           }
   #endif
   
           /* Add name to session list. */
           if (sessions_len + 1 > sessions_size) {
               sessions_size <<= 1;
               sessions = erealloc3(sessions, sessions_size, sizeof(char *));
           }
           sessions[sessions_len++] = estrdup(dp->d_name);
       }
       closedir(d);
   
       /* Sort and list the sessions. */
       qsort(sessions, sessions_len, sizeof(char *), session_compare);
       for (i = 0; i < sessions_len; i++) {
         len = snprintf(&pathbuf[sdlen], sizeof(pathbuf) - sdlen,          len = snprintf(&pathbuf[sdlen], sizeof(pathbuf) - sdlen,
            "%s/log", dp->d_name);            "%s/log", sessions[i]);
        if (len <= 0 || len >= sizeof(pathbuf) - sdlen) {        if (len <= 0 || (size_t)len >= sizeof(pathbuf) - sdlen) {
             errno = ENAMETOOLONG;              errno = ENAMETOOLONG;
            error(1, "%s/%s/log", dir, dp->d_name);            fatal("%s/%s/log", dir, sessions[i]);
         }          }
           efree(sessions[i]);
   
         /* Check for dir with a log file. */          /* Check for dir with a log file. */
         if (lstat(pathbuf, &sb) == 0 && S_ISREG(sb.st_mode)) {          if (lstat(pathbuf, &sb) == 0 && S_ISREG(sb.st_mode)) {
Line 793  find_sessions(const char *dir, REGEX_T *re, const char Line 1063  find_sessions(const char *dir, REGEX_T *re, const char
         } else {          } else {
             /* Strip off "/log" and recurse if a dir. */              /* Strip off "/log" and recurse if a dir. */
             pathbuf[sdlen + len - 4] = '\0';              pathbuf[sdlen + len - 4] = '\0';
            if (lstat(pathbuf, &sb) == 0 && S_ISDIR(sb.st_mode))            if (checked_type || (lstat(pathbuf, &sb) == 0 && S_ISDIR(sb.st_mode)))
                 find_sessions(pathbuf, re, user, tty);                  find_sessions(pathbuf, re, user, tty);
         }          }
     }      }
    closedir(d);    efree(sessions);
   
    return 0;    debug_return_int(0);
 }  }
   
   /* XXX - always returns 0, calls fatal() on failure */
 static int  static int
 list_sessions(int argc, char **argv, const char *pattern, const char *user,  list_sessions(int argc, char **argv, const char *pattern, const char *user,
     const char *tty)      const char *tty)
 {  {
     REGEX_T rebuf, *re = NULL;      REGEX_T rebuf, *re = NULL;
       debug_decl(list_sessions, SUDO_DEBUG_UTIL)
   
     /* Parse search expression if present */      /* Parse search expression if present */
    parse_expr(&search_expr, argv);    parse_expr(&search_expr, argv, false);
   
 #ifdef HAVE_REGCOMP  #ifdef HAVE_REGCOMP
     /* optional regex */      /* optional regex */
     if (pattern) {      if (pattern) {
         re = &rebuf;          re = &rebuf;
         if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0)          if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0)
            errorx(1, _("invalid regex: %s"), pattern);            fatalx(U_("invalid regular expression: %s"), pattern);
     }      }
 #else  #else
     re = (char *) pattern;      re = (char *) pattern;
 #endif /* HAVE_REGCOMP */  #endif /* HAVE_REGCOMP */
   
    return find_sessions(session_dir, re, user, tty);    debug_return_int(find_sessions(session_dir, re, user, tty));
 }  }
   
 /*  /*
 * Check input for ' ', '<', '>' * Check input for ' ', '<', '>', return
 * pause, slow, fast * pause, slow, fast, next
  */   */
 static void  static void
check_input(int ttyfd, double *speed)check_input(int fd, int what, void *v)
 {  {
    fd_set *fdsr;    struct sudo_event *ev = v;
    int nready, paused = 0;    struct sudo_event_base *evbase = sudo_ev_get_base(ev);
    struct timeval tv;    struct timeval tv, *timeout = NULL;
     static bool paused = 0;
     char ch;      char ch;
    ssize_t n;    debug_decl(check_input, SUDO_DEBUG_UTIL)
   
    fdsr = (fd_set *)emalloc2(howmany(ttyfd + 1, NFDBITS), sizeof(fd_mask));    if (ISSET(what, SUDO_EV_READ)) {
        switch (read(fd, &ch, 1)) {
    for (;;) {        case -1:
        FD_SET(ttyfd, fdsr);            if (errno != EINTR && errno != EAGAIN)
        tv.tv_sec = 0;                fatal(U_("unable to read %s"), "stdin");
        tv.tv_usec = 0; 
 
        nready = select(ttyfd + 1, fdsr, NULL, NULL, paused ? NULL : &tv); 
        if (nready != 1) 
             break;              break;
        n = read(ttyfd, &ch, 1);        case 0:
        if (n == 1) {            /* Ignore EOF. */
             break;
         case 1:
             if (paused) {              if (paused) {
                paused = 0;                /* Any key will unpause, event is finished. */
                continue;                /* XXX - pause time could be less than timeout */
                 paused = false;
                 debug_return; /* XXX */
             }              }
             switch (ch) {              switch (ch) {
             case ' ':              case ' ':
                paused = 1;                paused = true;
                 break;                  break;
             case '<':              case '<':
                *speed /= 2;                speed_factor /= 2;
                 break;                  break;
             case '>':              case '>':
                *speed *= 2;                speed_factor *= 2;
                 break;                  break;
               case '\r':
               case '\n':
                   debug_return; /* XXX */
             }              }
               break;
         }          }
           if (!paused) {
               /* Determine remaining timeout, if any. */
               sudo_ev_get_timeleft(ev, &tv);
               if (!sudo_timevalisset(&tv)) {
                   /* No time left, event is done. */
                   debug_return;
               }
               timeout = &tv;
           }
           /* Re-enable event. */
           sudo_ev_add(evbase, ev, timeout, false);
     }      }
    free(fdsr);    debug_return;
 }  }
   
 /*  /*
Line 878  check_input(int ttyfd, double *speed) Line 1166  check_input(int ttyfd, double *speed)
  * Returns 1 on success and 0 on failure.   * Returns 1 on success and 0 on failure.
  */   */
 static int  static int
parse_timing(buf, decimal, idx, seconds, nbytes)parse_timing(const char *buf, const char *decimal, int *idx, double *seconds,
    const char *buf;    size_t *nbytes)
    const char *decimal; 
    int *idx; 
    double *seconds; 
    size_t *nbytes; 
 {  {
     unsigned long ul;      unsigned long ul;
     long l;      long l;
     double d, fract = 0;      double d, fract = 0;
     char *cp, *ep;      char *cp, *ep;
       debug_decl(parse_timing, SUDO_DEBUG_UTIL)
   
     /* Parse index */      /* Parse index */
     ul = strtoul(buf, &ep, 10);      ul = strtoul(buf, &ep, 10);
    if (ul > IOFD_MAX)    if (ep == buf || !isspace((unsigned char) *ep))
         goto bad;          goto bad;
    *idx = (int)ul;    if (ul >= IOFD_TIMING) {
         if (ul != 6)
             goto bad;
         /* work around a bug in timing files generated by sudo 1.8.7 */
         timing_idx_adj = 2;
     }
     *idx = (int)ul - timing_idx_adj;
     for (cp = ep + 1; isspace((unsigned char) *cp); cp++)      for (cp = ep + 1; isspace((unsigned char) *cp); cp++)
         continue;          continue;
   
Line 906  parse_timing(buf, decimal, idx, seconds, nbytes) Line 1197  parse_timing(buf, decimal, idx, seconds, nbytes)
      */       */
     errno = 0;      errno = 0;
     l = strtol(cp, &ep, 10);      l = strtol(cp, &ep, 10);
    if ((errno == ERANGE && (l == LONG_MAX || l == LONG_MIN)) ||    if (ep == cp || (*ep != '.' && strncmp(ep, decimal, strlen(decimal)) != 0))
        l < 0 || l > INT_MAX || 
        (*ep != '.' && strncmp(ep, decimal, strlen(decimal)) != 0)) { 
         goto bad;          goto bad;
    }    if (l < 0 || l > INT_MAX || (errno == ERANGE && l == LONG_MAX))
         goto bad;
     *seconds = (double)l;      *seconds = (double)l;
     cp = ep + (*ep == '.' ? 1 : strlen(decimal));      cp = ep + (*ep == '.' ? 1 : strlen(decimal));
     d = 10.0;      d = 10.0;
Line 925  parse_timing(buf, decimal, idx, seconds, nbytes) Line 1215  parse_timing(buf, decimal, idx, seconds, nbytes)
   
     errno = 0;      errno = 0;
     ul = strtoul(cp, &ep, 10);      ul = strtoul(cp, &ep, 10);
    if (errno == ERANGE && ul == ULONG_MAX)    if (ep == cp || *ep != '\0' || (errno == ERANGE && ul == ULONG_MAX))
         goto bad;          goto bad;
     *nbytes = (size_t)ul;      *nbytes = (size_t)ul;
   
    return 1;    debug_return_int(1);
 bad:  bad:
    return 0;    debug_return_int(0);
 }  }
   
 static void  static void
 usage(int fatal)  usage(int fatal)
 {  {
     fprintf(fatal ? stderr : stdout,      fprintf(fatal ? stderr : stdout,
        _("usage: %s [-h] [-d directory] [-m max_wait] [-s speed_factor] ID\n"),        _("usage: %s [-h] [-d dir] [-m num] [-s num] ID\n"),
         getprogname());          getprogname());
     fprintf(fatal ? stderr : stdout,      fprintf(fatal ? stderr : stdout,
        _("usage: %s [-h] [-d directory] -l [search expression]\n"),        _("usage: %s [-h] [-d dir] -l [search expression]\n"),
         getprogname());          getprogname());
     if (fatal)      if (fatal)
         exit(1);          exit(1);
Line 953  help(void) Line 1243  help(void)
     (void) printf(_("%s - replay sudo session logs\n\n"), getprogname());      (void) printf(_("%s - replay sudo session logs\n\n"), getprogname());
     usage(0);      usage(0);
     (void) puts(_("\nOptions:\n"      (void) puts(_("\nOptions:\n"
        "  -d directory     specify directory for session logs\n"        "  -d, --directory=dir  specify directory for session logs\n"
        "  -f filter        specify which I/O type to display\n"        "  -f, --filter=filter  specify which I/O type(s) to display\n"
        "  -h               display help message and exit\n"        "  -h, --help           display help message and exit\n"
        "  -l [expression]  list available session IDs that match expression\n"        "  -l, --list           list available session IDs, with optional expression\n"
        "  -m max_wait      max number of seconds to wait between events\n"        "  -m, --max-wait=num   max number of seconds to wait between events\n"
        "  -s speed_factor  speed up or slow down output\n"        "  -s, --speed=num      speed up or slow down output\n"
        "  -V               display version information and exit"));        "  -V, --version        display version information and exit"));
     exit(0);      exit(0);
 }  }
   
 /*  /*
 * Cleanup hook for error()/errorx() * Cleanup hook for fatal()/fatalx()
   */    */
voidstatic void
cleanup(int signo)sudoreplay_cleanup(void)
 {  {
     term_restore(STDIN_FILENO, 0);      term_restore(STDIN_FILENO, 0);
    if (signo)}
        kill(getpid(), signo);
 /*
  * Signal handler for SIGINT, SIGTERM, SIGHUP, SIGQUIT
  * Must be installed with SA_RESETHAND enabled.
  */
 static void
 sudoreplay_handler(int signo)
 {
     term_restore(STDIN_FILENO, 0);
     kill(getpid(), signo);
 }  }

Removed from v.1.1  
changed lines
  Added in v.1.1.1.6


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