Annotation of embedaddon/sudo/plugins/sudoers/sudoreplay.c, revision 1.1.1.6

1.1       misho       1: /*
1.1.1.4   misho       2:  * Copyright (c) 2009-2013 Todd C. Miller <Todd.Miller@courtesan.com>
1.1       misho       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>
1.1.1.3   misho      20: #include <sys/uio.h>
1.1       misho      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 */
1.1.1.6 ! misho      46: #ifdef TIME_WITH_SYS_TIME
1.1       misho      47: # include <time.h>
                     48: #endif
1.1.1.2   misho      49: #ifndef HAVE_STRUCT_TIMESPEC
1.1       misho      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>
1.1.1.2   misho      79: #ifdef HAVE_STDBOOL_H
                     80: # include <stdbool.h>
                     81: #else
                     82: # include "compat/stdbool.h"
                     83: #endif /* HAVE_STDBOOL_H */
1.1.1.5   misho      84: #ifdef HAVE_GETOPT_LONG
                     85: # include <getopt.h>
                     86: # else
                     87: # include "compat/getopt.h"
                     88: #endif /* HAVE_GETOPT_LONG */
1.1       misho      89: 
                     90: #include <pathnames.h>
                     91: 
1.1.1.6 ! misho      92: #include "gettext.h"           /* must be included before missing.h */
        !            93: 
1.1       misho      94: #include "missing.h"
                     95: #include "alloc.h"
1.1.1.5   misho      96: #include "fatal.h"
1.1.1.4   misho      97: #include "logging.h"
1.1.1.5   misho      98: #include "iolog.h"
1.1.1.6 ! misho      99: #include "queue.h"
1.1.1.2   misho     100: #include "sudo_plugin.h"
                    101: #include "sudo_conf.h"
                    102: #include "sudo_debug.h"
1.1.1.6 ! misho     103: #include "sudo_event.h"
        !           104: #include "sudo_util.h"
1.1       misho     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;
1.1.1.2   misho     121:     int rows;
                    122:     int cols;
1.1       misho     123: };
                    124: 
1.1.1.6 ! misho     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: 
1.1       misho     133: /*
                    134:  * Handle expressions like:
                    135:  * ( user millert or user root ) and tty console and command /bin/sh
                    136:  */
1.1.1.6 ! misho     137: STAILQ_HEAD(search_node_list, search_node);
        !           138: struct search_node {
        !           139:     STAILQ_ENTRY(search_node) entries;
1.1       misho     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;
1.1.1.6 ! misho     150:     bool negated;
        !           151:     bool or;
1.1       misho     152:     union {
                    153: #ifdef HAVE_REGCOMP
                    154:        regex_t cmdre;
1.1.1.6 ! misho     155: #else
        !           156:        char *pattern;
1.1       misho     157: #endif
                    158:        time_t tstamp;
                    159:        char *cwd;
                    160:        char *tty;
                    161:        char *user;
                    162:        char *runas_group;
                    163:        char *runas_user;
1.1.1.6 ! misho     164:        struct search_node_list expr;
1.1       misho     165:        void *ptr;
                    166:     } u;
1.1.1.6 ! misho     167: };
        !           168: 
        !           169: static struct search_node_list search_expr = STAILQ_HEAD_INITIALIZER(search_expr);
1.1       misho     170: 
1.1.1.6 ! misho     171: static int timing_idx_adj;
1.1       misho     172: 
1.1.1.6 ! misho     173: static double speed_factor = 1.0;
1.1.1.5   misho     174: 
1.1       misho     175: static const char *session_dir = _PATH_SUDO_IO_LOGDIR;
                    176: 
1.1.1.5   misho     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' },
1.1       misho     187: };
                    188: 
1.1.1.6 ! misho     189: /* XXX move to separate header? */
1.1       misho     190: extern char *get_timestr(time_t, int);
1.1.1.6 ! misho     191: extern time_t get_date(char *);
1.1       misho     192: 
                    193: static int list_sessions(int, char **, const char *, const char *, const char *);
1.1.1.5   misho     194: static int open_io_fd(char *path, int len, struct io_log_file *iol);
1.1.1.6 ! misho     195: static int parse_expr(struct search_node_list *, char **, bool);
1.1       misho     196: static int parse_timing(const char *buf, const char *decimal, int *idx, double *seconds, size_t *nbytes);
1.1.1.2   misho     197: static struct log_info *parse_logfile(char *logfile);
1.1.1.6 ! misho     198: static void check_input(int fd, int what, void *v);
1.1.1.2   misho     199: static void free_log_info(struct log_info *li);
1.1.1.6 ! misho     200: static void help(void) __attribute__((__noreturn__));
        !           201: static void replay_session(const double max_wait, const char *decimal);
1.1.1.4   misho     202: static void sudoreplay_cleanup(void);
1.1.1.6 ! misho     203: static void sudoreplay_handler(int);
        !           204: static void usage(int);
        !           205: static void write_output(int fd, int what, void *v);
1.1       misho     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' && \
1.1.1.2   misho     225:     (s)[12] == '\0')
1.1       misho     226: 
1.1.1.4   misho     227: __dso_public int main(int argc, char *argv[]);
                    228: 
1.1       misho     229: int
                    230: main(int argc, char *argv[])
                    231: {
1.1.1.3   misho     232:     int ch, idx, plen, exitcode = 0, rows = 0, cols = 0;
1.1.1.6 ! misho     233:     bool def_filter = true, listonly = false;
1.1.1.4   misho     234:     const char *decimal, *id, *user = NULL, *pattern = NULL, *tty = NULL;
1.1.1.6 ! misho     235:     char *cp, *ep, path[PATH_MAX];
1.1.1.2   misho     236:     struct log_info *li;
1.1.1.6 ! misho     237:     double max_wait = 0;
1.1.1.2   misho     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
1.1       misho     246: 
1.1.1.6 ! misho     247:     initprogname(argc > 0 ? argv[0] : "sudoreplay");
        !           248:     setlocale(LC_ALL, "");
1.1       misho     249:     decimal = localeconv()->decimal_point;
                    250:     bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have sudoreplay domain */
                    251:     textdomain("sudoers");
                    252: 
1.1.1.4   misho     253:     /* Register fatal/fatalx callback. */
                    254:     fatal_callback_register(sudoreplay_cleanup);
                    255: 
1.1.1.2   misho     256:     /* Read sudo.conf. */
1.1.1.4   misho     257:     sudo_conf_read(NULL);
1.1.1.2   misho     258: 
1.1.1.5   misho     259:     while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
                    260:        switch (ch) {
1.1       misho     261:        case 'd':
                    262:            session_dir = optarg;
                    263:            break;
                    264:        case 'f':
                    265:            /* Set the replay filter. */
1.1.1.5   misho     266:            def_filter = false;
1.1       misho     267:            for (cp = strtok(optarg, ","); cp; cp = strtok(NULL, ",")) {
                    268:                if (strcmp(cp, "stdout") == 0)
1.1.1.5   misho     269:                    io_log_files[IOFD_STDOUT].enabled = true;
1.1       misho     270:                else if (strcmp(cp, "stderr") == 0)
1.1.1.5   misho     271:                    io_log_files[IOFD_STDERR].enabled = true;
1.1       misho     272:                else if (strcmp(cp, "ttyout") == 0)
1.1.1.5   misho     273:                    io_log_files[IOFD_TTYOUT].enabled = true;
1.1       misho     274:                else
1.1.1.6 ! misho     275:                    fatalx(U_("invalid filter option: %s"), optarg);
1.1       misho     276:            }
                    277:            break;
                    278:        case 'h':
                    279:            help();
                    280:            /* NOTREACHED */
                    281:        case 'l':
1.1.1.2   misho     282:            listonly = true;
1.1       misho     283:            break;
                    284:        case 'm':
                    285:            errno = 0;
                    286:            max_wait = strtod(optarg, &ep);
                    287:            if (*ep != '\0' || errno != 0)
1.1.1.6 ! misho     288:                fatalx(U_("invalid max wait: %s"), optarg);
1.1       misho     289:            break;
                    290:        case 's':
                    291:            errno = 0;
1.1.1.6 ! misho     292:            speed_factor = strtod(optarg, &ep);
1.1       misho     293:            if (*ep != '\0' || errno != 0)
1.1.1.6 ! misho     294:                fatalx(U_("invalid speed factor: %s"), optarg);
1.1       misho     295:            break;
                    296:        case 'V':
                    297:            (void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION);
1.1.1.2   misho     298:            goto done;
1.1       misho     299:        default:
                    300:            usage(1);
                    301:            /* NOTREACHED */
                    302:        }
                    303: 
                    304:     }
                    305:     argc -= optind;
                    306:     argv += optind;
                    307: 
1.1.1.2   misho     308:     if (listonly) {
                    309:        exitcode = list_sessions(argc, argv, pattern, user, tty);
                    310:        goto done;
                    311:     }
1.1       misho     312: 
                    313:     if (argc != 1)
                    314:        usage(1);
                    315: 
1.1.1.5   misho     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: 
1.1       misho     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]);
1.1.1.6 ! misho     328:        if (plen <= 0 || (size_t)plen >= sizeof(path))
        !           329:            fatalx(U_("%s/%.2s/%.2s/%.2s/timing: %s"), session_dir,
1.1       misho     330:                id, &id[2], &id[4], strerror(ENAMETOOLONG));
                    331:     } else {
                    332:        plen = snprintf(path, sizeof(path), "%s/%s/timing",
                    333:            session_dir, id);
1.1.1.6 ! misho     334:        if (plen <= 0 || (size_t)plen >= sizeof(path))
        !           335:            fatalx(U_("%s/%s/timing: %s"), session_dir,
1.1       misho     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++) {
1.1.1.5   misho     342:        if (open_io_fd(path, plen, &io_log_files[idx]) == -1) 
1.1.1.6 ! misho     343:            fatal(U_("unable to open %s"), path);
1.1       misho     344:     }
                    345: 
1.1.1.2   misho     346:     /* Parse log file. */
1.1       misho     347:     path[plen] = '\0';
                    348:     strlcat(path, "/log", sizeof(path));
1.1.1.2   misho     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;
1.1       misho     365: 
1.1.1.6 ! misho     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. */
1.1       misho     390:     fflush(stdout);
1.1.1.2   misho     391:     memset(&sa, 0, sizeof(sa));
1.1       misho     392:     sigemptyset(&sa.sa_mask);
                    393:     sa.sa_flags = SA_RESETHAND;
1.1.1.4   misho     394:     sa.sa_handler = sudoreplay_handler;
1.1       misho     395:     (void) sigaction(SIGINT, &sa, NULL);
                    396:     (void) sigaction(SIGTERM, &sa, NULL);
                    397:     (void) sigaction(SIGHUP, &sa, NULL);
1.1.1.6 ! misho     398:     (void) sigaction(SIGQUIT, &sa, NULL);
        !           399: 
        !           400:     /* Don't suspend as we cannot restore the screen on resume. */
1.1       misho     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) {
1.1.1.6 ! misho     409:        idx = fcntl(STDIN_FILENO, F_GETFL, 0);
        !           410:        if (idx != -1)
        !           411:            (void) fcntl(STDIN_FILENO, F_SETFL, idx | O_NONBLOCK);
1.1       misho     412:        if (!term_raw(STDIN_FILENO, 1))
1.1.1.6 ! misho     413:            fatal(U_("unable to set tty to raw mode"));
1.1       misho     414:     }
                    415: 
1.1.1.6 ! misho     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: 
1.1       misho     428:     /*
1.1.1.6 ! misho     429:      * Read each line of the timing file, displaying the output streams.
1.1       misho     430:      */
                    431: #ifdef HAVE_ZLIB_H
1.1.1.5   misho     432:     while (gzgets(io_log_files[IOFD_TIMING].fd.g, buf, sizeof(buf)) != NULL) {
1.1       misho     433: #else
1.1.1.5   misho     434:     while (fgets(buf, sizeof(buf), io_log_files[IOFD_TIMING].fd.f) != NULL) {
1.1       misho     435: #endif
1.1.1.6 ! misho     436:        size_t len, nbytes, nread;
        !           437:        double seconds, to_wait;
        !           438:        struct timeval timeout;
        !           439:        bool need_nlcr = false;
1.1.1.3   misho     440:        char last_char = '\0';
                    441: 
1.1.1.6 ! misho     442:        buf[strcspn(buf, "\n")] = '\0';
1.1       misho     443:        if (!parse_timing(buf, decimal, &idx, &seconds, &nbytes))
1.1.1.6 ! misho     444:            fatalx(U_("invalid timing file line: %s"), buf);
1.1       misho     445: 
                    446:        /* Adjust delay using speed factor and clamp to max_wait */
1.1.1.6 ! misho     447:        to_wait = seconds / speed_factor;
1.1       misho     448:        if (max_wait && to_wait > max_wait)
                    449:            to_wait = max_wait;
1.1.1.6 ! misho     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);
1.1       misho     458: 
1.1.1.5   misho     459:        /* Even if we are not replaying, we still have to delay. */
                    460:        if (io_log_files[idx].fd.v == NULL)
1.1       misho     461:            continue;
                    462: 
1.1.1.3   misho     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: 
1.1       misho     467:        /* All output is sent to stdout. */
1.1.1.6 ! misho     468:        /* XXX - assumes no wall clock time spent writing output. */
1.1       misho     469:        while (nbytes != 0) {
                    470:            if (nbytes > sizeof(buf))
                    471:                len = sizeof(buf);
                    472:            else
                    473:                len = nbytes;
                    474: #ifdef HAVE_ZLIB_H
1.1.1.5   misho     475:            nread = gzread(io_log_files[idx].fd.g, buf, len);
1.1       misho     476: #else
1.1.1.5   misho     477:            nread = fread(buf, 1, len, io_log_files[idx].fd.f);
1.1       misho     478: #endif
                    479:            nbytes -= nread;
1.1.1.3   misho     480: 
                    481:            /* Convert newline to carriage return + linefeed if needed. */
                    482:            if (need_nlcr) {
                    483:                size_t remainder = nread;
                    484:                size_t linelen;
1.1.1.6 ! misho     485:                char *cp = buf;
        !           486:                char *ep = buf - 1;
        !           487: 
1.1.1.3   misho     488:                /* Handle a "\r\n" pair that spans a buffer. */
                    489:                if (last_char == '\r' && buf[0] == '\n') {
                    490:                    ep++;
                    491:                    remainder--;
                    492:                }
1.1.1.6 ! misho     493: 
        !           494:                iovcnt = 0;
1.1.1.3   misho     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;
1.1       misho     500:                    }
1.1.1.3   misho     501: 
                    502:                    /* Store the line in iov followed by \r\n pair. */
                    503:                    if (iovcnt + 3 > iovmax) {
1.1.1.6 ! misho     504:                        iov = iovmax ?
        !           505:                            erealloc3(iov, iovmax <<= 1, sizeof(*iov)) :
        !           506:                            emalloc2(iovmax = 32, sizeof(*iov));
1.1.1.3   misho     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:                }
1.1.1.6 ! misho     518:                if ((size_t)(cp - buf) != nread) {
1.1.1.3   misho     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++;
1.1       misho     526:                }
1.1.1.3   misho     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:            }
1.1       misho     534: 
1.1.1.6 ! misho     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:        }
1.1       misho     548:     }
1.1.1.6 ! misho     549:     debug_return;
1.1       misho     550: }
                    551: 
                    552: static int
1.1.1.5   misho     553: open_io_fd(char *path, int len, struct io_log_file *iol)
1.1       misho     554: {
1.1.1.3   misho     555:     debug_decl(open_io_fd, SUDO_DEBUG_UTIL)
                    556: 
1.1.1.5   misho     557:     if (!iol->enabled)
                    558:        debug_return_int(0);
1.1       misho     559: 
1.1.1.5   misho     560:     path[len] = '\0';
                    561:     strlcat(path, iol->suffix, PATH_MAX);
1.1       misho     562: #ifdef HAVE_ZLIB_H
1.1.1.5   misho     563:     iol->fd.g = gzopen(path, "r");
1.1       misho     564: #else
1.1.1.5   misho     565:     iol->fd.f = fopen(path, "r");
1.1       misho     566: #endif
1.1.1.5   misho     567:     debug_return_int(iol->fd.v ? 0 : -1);
1.1       misho     568: }
                    569: 
1.1.1.6 ! misho     570: static void
        !           571: write_output(int fd, int what, void *v)
1.1.1.3   misho     572: {
1.1.1.6 ! misho     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;
1.1.1.3   misho     610:                break;
                    611:            }
                    612:        }
                    613:        break;
                    614:     }
1.1.1.6 ! misho     615: 
        !           616:     /* Reschedule event to write remainder. */
        !           617:     sudo_ev_add(sudo_ev_get_base(wc->wevent), wc->wevent, NULL, false);
        !           618:     debug_return;
1.1.1.3   misho     619: }
                    620: 
                    621: /*
1.1       misho     622:  * Build expression list from search args
                    623:  */
                    624: static int
1.1.1.6 ! misho     625: parse_expr(struct search_node_list *head, char *argv[], bool sub_expr)
1.1       misho     626: {
1.1.1.6 ! misho     627:     bool or = false, not = false;
        !           628:     struct search_node *sn;
        !           629:     char type, **av;
1.1.1.2   misho     630:     debug_decl(parse_expr, SUDO_DEBUG_UTIL)
1.1       misho     631: 
1.1.1.6 ! misho     632:     for (av = argv; *av != NULL; av++) {
1.1       misho     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;
1.1.1.6 ! misho     641:            or = true;
1.1       misho     642:            continue;
                    643:        case '!': /* negate */
                    644:            if (av[0][1] != '\0')
                    645:                goto bad;
1.1.1.6 ! misho     646:            not = true;
1.1       misho     647:            continue;
                    648:        case 'c': /* command */
                    649:            if (av[0][1] == '\0')
1.1.1.6 ! misho     650:                fatalx(U_("ambiguous expression \"%s\""), *av);
1.1       misho     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')
1.1.1.6 ! misho     675:                fatalx(U_("ambiguous expression \"%s\""), *av);
1.1       misho     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;
1.1.1.6 ! misho     696:            if (!sub_expr)
        !           697:                fatalx(U_("unmatched ')' in expression"));
1.1.1.2   misho     698:            debug_return_int(av - argv + 1);
1.1       misho     699:        bad:
                    700:        default:
1.1.1.6 ! misho     701:            fatalx(U_("unknown search term \"%s\""), *av);
1.1       misho     702:            /* NOTREACHED */
                    703:        }
                    704: 
                    705:        /* Allocate new search node */
1.1.1.6 ! misho     706:        sn = ecalloc(1, sizeof(*sn));
        !           707:        sn->type = type;
        !           708:        sn->or = or;
        !           709:        sn->negated = not;
1.1       misho     710:        if (type == ST_EXPR) {
1.1.1.6 ! misho     711:            STAILQ_INIT(&sn->u.expr);
        !           712:            av += parse_expr(&sn->u.expr, av + 1, true);
1.1       misho     713:        } else {
                    714:            if (*(++av) == NULL)
1.1.1.6 ! misho     715:                fatalx(U_("%s requires an argument"), av[-1]);
1.1       misho     716: #ifdef HAVE_REGCOMP
                    717:            if (type == ST_PATTERN) {
1.1.1.6 ! misho     718:                if (regcomp(&sn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0)
        !           719:                    fatalx(U_("invalid regular expression: %s"), *av);
1.1       misho     720:            } else
                    721: #endif
                    722:            if (type == ST_TODATE || type == ST_FROMDATE) {
1.1.1.6 ! misho     723:                sn->u.tstamp = get_date(*av);
        !           724:                if (sn->u.tstamp == -1)
        !           725:                    fatalx(U_("could not parse date \"%s\""), *av);
1.1       misho     726:            } else {
1.1.1.6 ! misho     727:                sn->u.ptr = *av;
1.1       misho     728:            }
                    729:        }
1.1.1.6 ! misho     730:        not = or = false; /* reset state */
        !           731:        STAILQ_INSERT_TAIL(head, sn, entries);
1.1       misho     732:     }
1.1.1.6 ! misho     733:     if (sub_expr)
        !           734:        fatalx(U_("unmatched '(' in expression"));
1.1       misho     735:     if (or)
1.1.1.6 ! misho     736:        fatalx(U_("illegal trailing \"or\""));
1.1       misho     737:     if (not)
1.1.1.6 ! misho     738:        fatalx(U_("illegal trailing \"!\""));
1.1       misho     739: 
1.1.1.2   misho     740:     debug_return_int(av - argv);
1.1       misho     741: }
                    742: 
1.1.1.2   misho     743: static bool
1.1.1.6 ! misho     744: match_expr(struct search_node_list *head, struct log_info *log, bool last_match)
1.1       misho     745: {
                    746:     struct search_node *sn;
1.1.1.6 ! misho     747:     bool res, matched = last_match;
1.1.1.2   misho     748:     int rc;
                    749:     debug_decl(match_expr, SUDO_DEBUG_UTIL)
1.1       misho     750: 
1.1.1.6 ! misho     751:     STAILQ_FOREACH(sn, head, entries) {
1.1       misho     752:        switch (sn->type) {
                    753:        case ST_EXPR:
1.1.1.6 ! misho     754:            res = match_expr(&sn->u.expr, log, matched);
1.1       misho     755:            break;
                    756:        case ST_CWD:
1.1.1.6 ! misho     757:            res = strcmp(sn->u.cwd, log->cwd) == 0;
1.1       misho     758:            break;
                    759:        case ST_TTY:
1.1.1.6 ! misho     760:            res = strcmp(sn->u.tty, log->tty) == 0;
1.1       misho     761:            break;
                    762:        case ST_RUNASGROUP:
1.1.1.6 ! misho     763:            res = strcmp(sn->u.runas_group, log->runas_group) == 0;
1.1       misho     764:            break;
                    765:        case ST_RUNASUSER:
1.1.1.6 ! misho     766:            res = strcmp(sn->u.runas_user, log->runas_user) == 0;
1.1       misho     767:            break;
                    768:        case ST_USER:
1.1.1.6 ! misho     769:            res = strcmp(sn->u.user, log->user) == 0;
1.1       misho     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));
1.1.1.4   misho     777:                fatalx("%s", buf);
1.1       misho     778:            }
1.1.1.6 ! misho     779:            res = rc == REG_NOMATCH ? 0 : 1;
1.1       misho     780: #else
1.1.1.6 ! misho     781:            res = strstr(log.cmd, sn->u.pattern) != NULL;
1.1       misho     782: #endif
                    783:            break;
                    784:        case ST_FROMDATE:
1.1.1.6 ! misho     785:            res = log->tstamp >= sn->u.tstamp;
1.1       misho     786:            break;
                    787:        case ST_TODATE:
1.1.1.6 ! misho     788:            res = log->tstamp <= sn->u.tstamp;
1.1       misho     789:            break;
1.1.1.6 ! misho     790:        default:
        !           791:            fatalx(U_("unknown search type %d"), sn->type);
        !           792:            /* NOTREACHED */
1.1       misho     793:        }
                    794:        if (sn->negated)
1.1.1.6 ! misho     795:            res = !res;
        !           796:        matched = sn->or ? (res || last_match) : (res && last_match);
        !           797:        last_match = matched;
1.1       misho     798:     }
1.1.1.2   misho     799:     debug_return_bool(matched);
1.1       misho     800: }
                    801: 
1.1.1.2   misho     802: static struct log_info *
                    803: parse_logfile(char *logfile)
1.1       misho     804: {
                    805:     FILE *fp;
1.1.1.2   misho     806:     char *buf = NULL, *cp, *ep;
1.1.1.6 ! misho     807:     const char *errstr;
1.1       misho     808:     size_t bufsize = 0, cwdsize = 0, cmdsize = 0;
1.1.1.2   misho     809:     struct log_info *li = NULL;
1.1.1.6 ! misho     810:     debug_decl(parse_logfile, SUDO_DEBUG_UTIL)
1.1       misho     811: 
                    812:     fp = fopen(logfile, "r");
                    813:     if (fp == NULL) {
1.1.1.6 ! misho     814:        warning(U_("unable to open %s"), logfile);
1.1.1.2   misho     815:        goto bad;
1.1       misho     816:     }
                    817: 
                    818:     /*
                    819:      * ID file has three lines:
                    820:      *  1) a log info line
                    821:      *  2) cwd
                    822:      *  3) command with args
                    823:      */
1.1.1.2   misho     824:     li = ecalloc(1, sizeof(*li));
1.1       misho     825:     if (getline(&buf, &bufsize, fp) == -1 ||
1.1.1.2   misho     826:        getline(&li->cwd, &cwdsize, fp) == -1 ||
                    827:        getline(&li->cmd, &cmdsize, fp) == -1) {
1.1.1.6 ! misho     828:        warning(U_("%s: invalid log file"), logfile);
1.1.1.2   misho     829:        goto bad;
1.1       misho     830:     }
                    831: 
1.1.1.2   misho     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
1.1.1.6 ! misho     839:      * XXX - probably better to use strtok and switch on the state.
1.1.1.2   misho     840:      */
1.1       misho     841:     buf[strcspn(buf, "\n")] = '\0';
1.1.1.6 ! misho     842:     cp = buf;
1.1       misho     843: 
1.1.1.2   misho     844:     /* timestamp */
1.1.1.6 ! misho     845:     if ((ep = strchr(cp, ':')) == NULL) {
        !           846:        warning(U_("%s: time stamp field is missing"), logfile);
1.1.1.2   misho     847:        goto bad;
1.1.1.6 ! misho     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);
1.1.1.2   misho     854:        goto bad;
1.1.1.6 ! misho     855:     }
1.1       misho     856: 
1.1.1.2   misho     857:     /* user */
                    858:     cp = ep + 1;
1.1.1.6 ! misho     859:     if ((ep = strchr(cp, ':')) == NULL) {
        !           860:        warning(U_("%s: user field is missing"), logfile);
1.1.1.2   misho     861:        goto bad;
1.1.1.6 ! misho     862:     }
1.1.1.2   misho     863:     li->user = estrndup(cp, (size_t)(ep - cp));
1.1       misho     864: 
1.1.1.2   misho     865:     /* runas user */
                    866:     cp = ep + 1;
1.1.1.6 ! misho     867:     if ((ep = strchr(cp, ':')) == NULL) {
        !           868:        warning(U_("%s: runas user field is missing"), logfile);
1.1.1.2   misho     869:        goto bad;
1.1.1.6 ! misho     870:     }
1.1.1.2   misho     871:     li->runas_user = estrndup(cp, (size_t)(ep - cp));
1.1       misho     872: 
1.1.1.2   misho     873:     /* runas group */
                    874:     cp = ep + 1;
1.1.1.6 ! misho     875:     if ((ep = strchr(cp, ':')) == NULL) {
        !           876:        warning(U_("%s: runas group field is missing"), logfile);
1.1.1.2   misho     877:        goto bad;
1.1.1.6 ! misho     878:     }
1.1.1.2   misho     879:     if (cp != ep)
                    880:        li->runas_group = estrndup(cp, (size_t)(ep - cp));
1.1       misho     881: 
1.1.1.2   misho     882:     /* tty, followed by optional rows + columns */
                    883:     cp = ep + 1;
                    884:     if ((ep = strchr(cp, ':')) == NULL) {
1.1.1.6 ! misho     885:        /* just the tty */
1.1.1.2   misho     886:        li->tty = estrdup(cp);
                    887:     } else {
1.1.1.6 ! misho     888:        /* tty followed by rows + columns */
1.1.1.2   misho     889:        li->tty = estrndup(cp, (size_t)(ep - cp));
                    890:        cp = ep + 1;
1.1.1.6 ! misho     891:        /* need to NULL out separator to use strtonum() */
1.1.1.2   misho     892:        if ((ep = strchr(cp, ':')) != NULL) {
1.1.1.6 ! misho     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) {
1.1.1.2   misho     901:            cp = ep + 1;
1.1.1.6 ! misho     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:            }
1.1.1.2   misho     907:        }
                    908:     }
                    909:     fclose(fp);
                    910:     efree(buf);
                    911:     debug_return_ptr(li);
                    912: 
                    913: bad:
1.1.1.3   misho     914:     if (fp != NULL)
                    915:        fclose(fp);
1.1.1.2   misho     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;
1.1.1.6 ! misho     939:     const char *timestr;
1.1.1.2   misho     940:     struct log_info *li;
                    941:     int rval = -1;
                    942:     debug_decl(list_session, SUDO_DEBUG_UTIL)
1.1       misho     943: 
1.1.1.2   misho     944:     if ((li = parse_logfile(logfile)) == NULL)
                    945:        goto done;
1.1       misho     946: 
                    947:     /* Match on search expression if there is one. */
1.1.1.6 ! misho     948:     if (!STAILQ_EMPTY(&search_expr) && !match_expr(&search_expr, li, true))
1.1       misho     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)) {
1.1.1.2   misho     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];
1.1       misho     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:     }
1.1.1.2   misho     967:     /* XXX - print rows + cols? */
1.1.1.6 ! misho     968:     timestr = get_timestr(li->tstamp, 1);
1.1       misho     969:     printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ",
1.1.1.6 ! misho     970:        timestr ? timestr : "invalid date",
        !           971:        li->user, li->tty, li->cwd, li->runas_user);
1.1.1.2   misho     972:     if (li->runas_group)
                    973:        printf("GROUP=%s ; ", li->runas_group);
                    974:     printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd);
1.1       misho     975: 
                    976:     rval = 0;
                    977: 
                    978: done:
1.1.1.2   misho     979:     free_log_info(li);
                    980:     debug_return_int(rval);
1.1       misho     981: }
                    982: 
                    983: static int
1.1.1.2   misho     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: 
1.1.1.4   misho     991: /* XXX - always returns 0, calls fatal() on failure */
1.1.1.2   misho     992: static int
1.1       misho     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;
1.1.1.2   misho     998:     size_t sdlen, sessions_len = 0, sessions_size = 36*36;
1.1.1.6 ! misho     999:     unsigned int i;
        !          1000:     int len;
1.1.1.2   misho    1001:     char pathbuf[PATH_MAX], **sessions = NULL;
1.1.1.3   misho    1002: #ifdef HAVE_STRUCT_DIRENT_D_TYPE
                   1003:     bool checked_type = true;
                   1004: #else
                   1005:     const bool checked_type = false;
                   1006: #endif
1.1.1.2   misho    1007:     debug_decl(find_sessions, SUDO_DEBUG_UTIL)
1.1       misho    1008: 
                   1009:     d = opendir(dir);
                   1010:     if (d == NULL)
1.1.1.6 ! misho    1011:        fatal(U_("unable to open %s"), dir);
1.1       misho    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;
1.1.1.4   misho    1017:        fatal("%s/", dir);
1.1       misho    1018:     }
                   1019:     pathbuf[sdlen++] = '/';
                   1020:     pathbuf[sdlen] = '\0';
1.1.1.2   misho    1021: 
                   1022:     /* Store potential session dirs for sorting. */
                   1023:     sessions = emalloc2(sessions_size, sizeof(char *));
1.1       misho    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;
1.1.1.2   misho    1029: #ifdef HAVE_STRUCT_DIRENT_D_TYPE
1.1.1.3   misho    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:        }
1.1.1.2   misho    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);
1.1       misho    1048: 
1.1.1.2   misho    1049:     /* Sort and list the sessions. */
                   1050:     qsort(sessions, sessions_len, sizeof(char *), session_compare);
                   1051:     for (i = 0; i < sessions_len; i++) {
1.1       misho    1052:        len = snprintf(&pathbuf[sdlen], sizeof(pathbuf) - sdlen,
1.1.1.2   misho    1053:            "%s/log", sessions[i]);
1.1.1.6 ! misho    1054:        if (len <= 0 || (size_t)len >= sizeof(pathbuf) - sdlen) {
1.1       misho    1055:            errno = ENAMETOOLONG;
1.1.1.4   misho    1056:            fatal("%s/%s/log", dir, sessions[i]);
1.1       misho    1057:        }
1.1.1.2   misho    1058:        efree(sessions[i]);
1.1       misho    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';
1.1.1.3   misho    1066:            if (checked_type || (lstat(pathbuf, &sb) == 0 && S_ISDIR(sb.st_mode)))
1.1       misho    1067:                find_sessions(pathbuf, re, user, tty);
                   1068:        }
                   1069:     }
1.1.1.2   misho    1070:     efree(sessions);
1.1       misho    1071: 
1.1.1.2   misho    1072:     debug_return_int(0);
1.1       misho    1073: }
                   1074: 
1.1.1.4   misho    1075: /* XXX - always returns 0, calls fatal() on failure */
1.1       misho    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;
1.1.1.2   misho    1081:     debug_decl(list_sessions, SUDO_DEBUG_UTIL)
1.1       misho    1082: 
                   1083:     /* Parse search expression if present */
1.1.1.6 ! misho    1084:     parse_expr(&search_expr, argv, false);
1.1       misho    1085: 
                   1086: #ifdef HAVE_REGCOMP
                   1087:     /* optional regex */
                   1088:     if (pattern) {
                   1089:        re = &rebuf;
                   1090:        if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0)
1.1.1.6 ! misho    1091:            fatalx(U_("invalid regular expression: %s"), pattern);
1.1       misho    1092:     }
                   1093: #else
                   1094:     re = (char *) pattern;
                   1095: #endif /* HAVE_REGCOMP */
                   1096: 
1.1.1.2   misho    1097:     debug_return_int(find_sessions(session_dir, re, user, tty));
1.1       misho    1098: }
                   1099: 
                   1100: /*
1.1.1.6 ! misho    1101:  * Check input for ' ', '<', '>', return
        !          1102:  * pause, slow, fast, next
1.1       misho    1103:  */
                   1104: static void
1.1.1.6 ! misho    1105: check_input(int fd, int what, void *v)
1.1       misho    1106: {
1.1.1.6 ! misho    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;
1.1       misho    1111:     char ch;
1.1.1.2   misho    1112:     debug_decl(check_input, SUDO_DEBUG_UTIL)
1.1       misho    1113: 
1.1.1.6 ! misho    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. */
1.1       misho    1122:            break;
1.1.1.6 ! misho    1123:        case 1:
1.1       misho    1124:            if (paused) {
1.1.1.6 ! misho    1125:                /* Any key will unpause, event is finished. */
        !          1126:                /* XXX - pause time could be less than timeout */
        !          1127:                paused = false;
        !          1128:                debug_return; /* XXX */
1.1       misho    1129:            }
                   1130:            switch (ch) {
                   1131:            case ' ':
1.1.1.6 ! misho    1132:                paused = true;
1.1       misho    1133:                break;
                   1134:            case '<':
1.1.1.6 ! misho    1135:                speed_factor /= 2;
1.1       misho    1136:                break;
                   1137:            case '>':
1.1.1.6 ! misho    1138:                speed_factor *= 2;
1.1       misho    1139:                break;
1.1.1.6 ! misho    1140:            case '\r':
        !          1141:            case '\n':
        !          1142:                debug_return; /* XXX */
1.1       misho    1143:            }
1.1.1.6 ! misho    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;
1.1       misho    1154:        }
1.1.1.6 ! misho    1155:        /* Re-enable event. */
        !          1156:        sudo_ev_add(evbase, ev, timeout, false);
1.1       misho    1157:     }
1.1.1.2   misho    1158:     debug_return;
1.1       misho    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
1.1.1.6 ! misho    1169: parse_timing(const char *buf, const char *decimal, int *idx, double *seconds,
        !          1170:     size_t *nbytes)
1.1       misho    1171: {
                   1172:     unsigned long ul;
                   1173:     long l;
                   1174:     double d, fract = 0;
                   1175:     char *cp, *ep;
1.1.1.2   misho    1176:     debug_decl(parse_timing, SUDO_DEBUG_UTIL)
1.1       misho    1177: 
                   1178:     /* Parse index */
                   1179:     ul = strtoul(buf, &ep, 10);
1.1.1.6 ! misho    1180:     if (ep == buf || !isspace((unsigned char) *ep))
        !          1181:        goto bad;
1.1.1.5   misho    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;
1.1       misho    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);
1.1.1.6 ! misho    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))
1.1       misho    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);
1.1.1.6 ! misho    1218:     if (ep == cp || *ep != '\0' || (errno == ERANGE && ul == ULONG_MAX))
1.1       misho    1219:        goto bad;
                   1220:     *nbytes = (size_t)ul;
                   1221: 
1.1.1.2   misho    1222:     debug_return_int(1);
1.1       misho    1223: bad:
1.1.1.2   misho    1224:     debug_return_int(0);
1.1       misho    1225: }
                   1226: 
                   1227: static void
                   1228: usage(int fatal)
                   1229: {
                   1230:     fprintf(fatal ? stderr : stdout,
1.1.1.5   misho    1231:        _("usage: %s [-h] [-d dir] [-m num] [-s num] ID\n"),
1.1       misho    1232:        getprogname());
                   1233:     fprintf(fatal ? stderr : stdout,
1.1.1.5   misho    1234:        _("usage: %s [-h] [-d dir] -l [search expression]\n"),
1.1       misho    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"
1.1.1.5   misho    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"));
1.1       misho    1253:     exit(0);
                   1254: }
                   1255: 
                   1256: /*
1.1.1.4   misho    1257:  * Cleanup hook for fatal()/fatalx()
1.1       misho    1258:   */
1.1.1.4   misho    1259: static void
                   1260: sudoreplay_cleanup(void)
                   1261: {
                   1262:     term_restore(STDIN_FILENO, 0);
                   1263: }
                   1264: 
                   1265: /*
1.1.1.6 ! misho    1266:  * Signal handler for SIGINT, SIGTERM, SIGHUP, SIGQUIT
1.1.1.4   misho    1267:  * Must be installed with SA_RESETHAND enabled.
                   1268:  */
                   1269: static void
                   1270: sudoreplay_handler(int signo)
1.1       misho    1271: {
                   1272:     term_restore(STDIN_FILENO, 0);
1.1.1.4   misho    1273:     kill(getpid(), signo);
1.1       misho    1274: }

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