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