version 1.1, 2012/02/21 16:23:02
|
version 1.1.1.4, 2013/07/22 10:46:12
|
Line 1
|
Line 1
|
/* |
/* |
* Copyright (c) 2009-2011 Todd C. Miller <Todd.Miller@courtesan.com> | * Copyright (c) 2009-2013 Todd C. Miller <Todd.Miller@courtesan.com> |
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
Line 17
|
Line 17
|
#include <config.h> |
#include <config.h> |
|
|
#include <sys/types.h> |
#include <sys/types.h> |
#include <sys/param.h> | #include <sys/uio.h> |
#ifdef HAVE_SYS_SYSMACROS_H |
#ifdef HAVE_SYS_SYSMACROS_H |
# include <sys/sysmacros.h> |
# include <sys/sysmacros.h> |
#endif |
#endif |
Line 52
|
Line 52
|
#if TIME_WITH_SYS_TIME |
#if TIME_WITH_SYS_TIME |
# include <time.h> |
# include <time.h> |
#endif |
#endif |
#ifndef HAVE_TIMESPEC | #ifndef HAVE_STRUCT_TIMESPEC |
# include "compat/timespec.h" |
# include "compat/timespec.h" |
#endif |
#endif |
#include <ctype.h> |
#include <ctype.h> |
Line 81
|
Line 81
|
#ifdef HAVE_ZLIB_H |
#ifdef HAVE_ZLIB_H |
# include <zlib.h> |
# include <zlib.h> |
#endif |
#endif |
#ifdef HAVE_SETLOCALE |
|
# include <locale.h> |
|
#endif |
|
#include <signal.h> |
#include <signal.h> |
|
#ifdef HAVE_STDBOOL_H |
|
# include <stdbool.h> |
|
#else |
|
# include "compat/stdbool.h" |
|
#endif /* HAVE_STDBOOL_H */ |
|
|
#include <pathnames.h> |
#include <pathnames.h> |
|
|
Line 92
|
Line 94
|
#include "alloc.h" |
#include "alloc.h" |
#include "error.h" |
#include "error.h" |
#include "gettext.h" |
#include "gettext.h" |
|
#include "logging.h" |
|
#include "sudo_plugin.h" |
|
#include "sudo_conf.h" |
|
#include "sudo_debug.h" |
|
|
#ifndef LINE_MAX |
#ifndef LINE_MAX |
# define LINE_MAX 2048 |
# define LINE_MAX 2048 |
Line 133 struct log_info {
|
Line 139 struct log_info {
|
char *tty; |
char *tty; |
char *cmd; |
char *cmd; |
time_t tstamp; |
time_t tstamp; |
|
int rows; |
|
int cols; |
}; |
}; |
|
|
/* |
/* |
Line 190 extern time_t get_date(char *);
|
Line 198 extern time_t get_date(char *);
|
extern char *get_timestr(time_t, int); |
extern char *get_timestr(time_t, int); |
extern int term_raw(int, int); |
extern int term_raw(int, int); |
extern int term_restore(int, int); |
extern int term_restore(int, int); |
extern void zero_bytes(volatile void *, size_t); | extern void get_ttysize(int *rowp, int *colp); |
void cleanup(int); | |
|
|
static int list_sessions(int, char **, const char *, const char *, const char *); |
static int list_sessions(int, char **, const char *, const char *, const char *); |
static int parse_expr(struct search_node **, char **); |
static int parse_expr(struct search_node **, char **); |
Line 201 static void help(void) __attribute__((__noreturn__));
|
Line 208 static void help(void) __attribute__((__noreturn__));
|
static void usage(int); |
static void usage(int); |
static int open_io_fd(char *pathbuf, int len, const char *suffix, union io_fd *fdp); |
static int open_io_fd(char *pathbuf, int len, const char *suffix, union io_fd *fdp); |
static int parse_timing(const char *buf, const char *decimal, int *idx, double *seconds, size_t *nbytes); |
static int parse_timing(const char *buf, const char *decimal, int *idx, double *seconds, size_t *nbytes); |
|
static struct log_info *parse_logfile(char *logfile); |
|
static void free_log_info(struct log_info *li); |
|
static size_t atomic_writev(int fd, struct iovec *iov, int iovcnt); |
|
static void sudoreplay_handler(int); |
|
static void sudoreplay_cleanup(void); |
|
|
#ifdef HAVE_REGCOMP |
#ifdef HAVE_REGCOMP |
# define REGEX_T regex_t |
# define REGEX_T regex_t |
Line 220 static int parse_timing(const char *buf, const char *d
|
Line 232 static int parse_timing(const char *buf, const char *d
|
(s)[5] == '/' && \ |
(s)[5] == '/' && \ |
isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \ |
isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \ |
(s)[8] == '/' && (s)[9] == 'l' && (s)[10] == 'o' && (s)[11] == 'g' && \ |
(s)[8] == '/' && (s)[9] == 'l' && (s)[10] == 'o' && (s)[11] == 'g' && \ |
(s)[9] == '\0') | (s)[12] == '\0') |
|
|
|
__dso_public int main(int argc, char *argv[]); |
|
|
int |
int |
main(int argc, char *argv[]) |
main(int argc, char *argv[]) |
{ |
{ |
int ch, idx, plen, nready, interactive = 0, listonly = 0; | int ch, idx, plen, exitcode = 0, rows = 0, cols = 0; |
const char *id, *user = NULL, *pattern = NULL, *tty = NULL, *decimal = "."; | bool interactive = false, listonly = false, need_nlcr = false; |
| const char *decimal, *id, *user = NULL, *pattern = NULL, *tty = NULL; |
char path[PATH_MAX], buf[LINE_MAX], *cp, *ep; |
char path[PATH_MAX], buf[LINE_MAX], *cp, *ep; |
double seconds, to_wait, speed = 1.0, max_wait = 0; |
double seconds, to_wait, speed = 1.0, max_wait = 0; |
FILE *lfile; |
|
fd_set *fdsw; |
|
sigaction_t sa; |
sigaction_t sa; |
size_t len, nbytes, nread, off; | size_t len, nbytes, nread; |
ssize_t nwritten; | struct log_info *li; |
| struct iovec *iov = NULL; |
| int iovcnt = 0, iovmax = 0; |
| debug_decl(main, SUDO_DEBUG_MAIN) |
|
|
|
#if defined(SUDO_DEVEL) && defined(__OpenBSD__) |
|
{ |
|
extern char *malloc_options; |
|
malloc_options = "AFGJPR"; |
|
} |
|
#endif |
|
|
#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) |
#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) |
setprogname(argc > 0 ? argv[0] : "sudoreplay"); |
setprogname(argc > 0 ? argv[0] : "sudoreplay"); |
#endif |
#endif |
|
|
#ifdef HAVE_SETLOCALE | sudoers_setlocale(SUDOERS_LOCALE_USER, NULL); |
setlocale(LC_ALL, ""); | |
decimal = localeconv()->decimal_point; |
decimal = localeconv()->decimal_point; |
#endif |
|
bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have sudoreplay domain */ |
bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have sudoreplay domain */ |
textdomain("sudoers"); |
textdomain("sudoers"); |
|
|
|
/* Register fatal/fatalx callback. */ |
|
fatal_callback_register(sudoreplay_cleanup); |
|
|
|
/* Read sudo.conf. */ |
|
sudo_conf_read(NULL); |
|
|
while ((ch = getopt(argc, argv, "d:f:hlm:s:V")) != -1) { |
while ((ch = getopt(argc, argv, "d:f:hlm:s:V")) != -1) { |
switch(ch) { |
switch(ch) { |
case 'd': |
case 'd': |
Line 262 main(int argc, char *argv[])
|
Line 289 main(int argc, char *argv[])
|
else if (strcmp(cp, "ttyout") == 0) |
else if (strcmp(cp, "ttyout") == 0) |
SET(replay_filter, 1 << IOFD_TTYOUT); |
SET(replay_filter, 1 << IOFD_TTYOUT); |
else |
else |
errorx(1, _("invalid filter option: %s"), optarg); | fatalx(_("invalid filter option: %s"), optarg); |
} |
} |
break; |
break; |
case 'h': |
case 'h': |
help(); |
help(); |
/* NOTREACHED */ |
/* NOTREACHED */ |
case 'l': |
case 'l': |
listonly = 1; | listonly = true; |
break; |
break; |
case 'm': |
case 'm': |
errno = 0; |
errno = 0; |
max_wait = strtod(optarg, &ep); |
max_wait = strtod(optarg, &ep); |
if (*ep != '\0' || errno != 0) |
if (*ep != '\0' || errno != 0) |
errorx(1, _("invalid max wait: %s"), optarg); | fatalx(_("invalid max wait: %s"), optarg); |
break; |
break; |
case 's': |
case 's': |
errno = 0; |
errno = 0; |
speed = strtod(optarg, &ep); |
speed = strtod(optarg, &ep); |
if (*ep != '\0' || errno != 0) |
if (*ep != '\0' || errno != 0) |
errorx(1, _("invalid speed factor: %s"), optarg); | fatalx(_("invalid speed factor: %s"), optarg); |
break; |
break; |
case 'V': |
case 'V': |
(void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION); |
(void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION); |
exit(0); | goto done; |
default: |
default: |
usage(1); |
usage(1); |
/* NOTREACHED */ |
/* NOTREACHED */ |
Line 295 main(int argc, char *argv[])
|
Line 322 main(int argc, char *argv[])
|
argc -= optind; |
argc -= optind; |
argv += optind; |
argv += optind; |
|
|
if (listonly) | if (listonly) { |
exit(list_sessions(argc, argv, pattern, user, tty)); | exitcode = list_sessions(argc, argv, pattern, user, tty); |
| goto done; |
| } |
|
|
if (argc != 1) |
if (argc != 1) |
usage(1); |
usage(1); |
Line 307 main(int argc, char *argv[])
|
Line 336 main(int argc, char *argv[])
|
plen = snprintf(path, sizeof(path), "%s/%.2s/%.2s/%.2s/timing", |
plen = snprintf(path, sizeof(path), "%s/%.2s/%.2s/%.2s/timing", |
session_dir, id, &id[2], &id[4]); |
session_dir, id, &id[2], &id[4]); |
if (plen <= 0 || plen >= sizeof(path)) |
if (plen <= 0 || plen >= sizeof(path)) |
errorx(1, _("%s/%.2s/%.2s/%.2s/timing: %s"), session_dir, | fatalx(_("%s/%.2s/%.2s/%.2s/timing: %s"), session_dir, |
id, &id[2], &id[4], strerror(ENAMETOOLONG)); |
id, &id[2], &id[4], strerror(ENAMETOOLONG)); |
} else { |
} else { |
plen = snprintf(path, sizeof(path), "%s/%s/timing", |
plen = snprintf(path, sizeof(path), "%s/%s/timing", |
session_dir, id); |
session_dir, id); |
if (plen <= 0 || plen >= sizeof(path)) |
if (plen <= 0 || plen >= sizeof(path)) |
errorx(1, _("%s/%s/timing: %s"), session_dir, | fatalx(_("%s/%s/timing: %s"), session_dir, |
id, strerror(ENAMETOOLONG)); |
id, strerror(ENAMETOOLONG)); |
} |
} |
plen -= 7; |
plen -= 7; |
Line 322 main(int argc, char *argv[])
|
Line 351 main(int argc, char *argv[])
|
for (idx = 0; idx < IOFD_MAX; idx++) { |
for (idx = 0; idx < IOFD_MAX; idx++) { |
if (ISSET(replay_filter, 1 << idx) || idx == IOFD_TIMING) { |
if (ISSET(replay_filter, 1 << idx) || idx == IOFD_TIMING) { |
if (open_io_fd(path, plen, io_fnames[idx], &io_fds[idx]) == -1) |
if (open_io_fd(path, plen, io_fnames[idx], &io_fds[idx]) == -1) |
error(1, _("unable to open %s"), path); | fatal(_("unable to open %s"), path); |
} |
} |
} |
} |
|
|
/* Read log file. */ | /* Parse log file. */ |
path[plen] = '\0'; |
path[plen] = '\0'; |
strlcat(path, "/log", sizeof(path)); |
strlcat(path, "/log", sizeof(path)); |
lfile = fopen(path, "r"); | if ((li = parse_logfile(path)) == NULL) |
if (lfile == NULL) | exit(1); |
error(1, _("unable to open %s"), path); | printf(_("Replaying sudo session: %s\n"), li->cmd); |
cp = NULL; | |
len = 0; | /* Make sure the terminal is large enough. */ |
/* Pull out command (third line). */ | get_ttysize(&rows, &cols); |
if (getline(&cp, &len, lfile) == -1 || | if (li->rows != 0 && li->cols != 0) { |
getline(&cp, &len, lfile) == -1 || | if (li->rows > rows) { |
getline(&cp, &len, lfile) == -1) { | printf(_("Warning: your terminal is too small to properly replay the log.\n")); |
errorx(1, _("invalid log file %s"), path); | printf(_("Log geometry is %d x %d, your terminal's geometry is %d x %d."), li->rows, li->cols, rows, cols); |
| } |
} |
} |
printf(_("Replaying sudo session: %s"), cp); |
|
free(cp); |
|
fclose(lfile); |
|
|
|
|
/* Done with parsed log file. */ |
|
free_log_info(li); |
|
li = NULL; |
|
|
fflush(stdout); |
fflush(stdout); |
zero_bytes(&sa, sizeof(sa)); | memset(&sa, 0, sizeof(sa)); |
sigemptyset(&sa.sa_mask); |
sigemptyset(&sa.sa_mask); |
sa.sa_flags = SA_RESETHAND; |
sa.sa_flags = SA_RESETHAND; |
sa.sa_handler = cleanup; | sa.sa_handler = sudoreplay_handler; |
(void) sigaction(SIGINT, &sa, NULL); |
(void) sigaction(SIGINT, &sa, NULL); |
(void) sigaction(SIGKILL, &sa, NULL); |
(void) sigaction(SIGKILL, &sa, NULL); |
(void) sigaction(SIGTERM, &sa, NULL); |
(void) sigaction(SIGTERM, &sa, NULL); |
Line 366 main(int argc, char *argv[])
|
Line 397 main(int argc, char *argv[])
|
if (ch != -1) |
if (ch != -1) |
(void) fcntl(STDIN_FILENO, F_SETFL, ch | O_NONBLOCK); |
(void) fcntl(STDIN_FILENO, F_SETFL, ch | O_NONBLOCK); |
if (!term_raw(STDIN_FILENO, 1)) |
if (!term_raw(STDIN_FILENO, 1)) |
error(1, _("unable to set tty to raw mode")); | fatal(_("unable to set tty to raw mode")); |
| iovmax = 32; |
| iov = ecalloc(iovmax, sizeof(*iov)); |
} |
} |
fdsw = (fd_set *)emalloc2(howmany(STDOUT_FILENO + 1, NFDBITS), |
|
sizeof(fd_mask)); |
|
|
|
/* |
/* |
* Timing file consists of line of the format: "%f %d\n" |
* Timing file consists of line of the format: "%f %d\n" |
Line 379 main(int argc, char *argv[])
|
Line 410 main(int argc, char *argv[])
|
#else |
#else |
while (fgets(buf, sizeof(buf), io_fds[IOFD_TIMING].f) != NULL) { |
while (fgets(buf, sizeof(buf), io_fds[IOFD_TIMING].f) != NULL) { |
#endif |
#endif |
|
char last_char = '\0'; |
|
|
if (!parse_timing(buf, decimal, &idx, &seconds, &nbytes)) |
if (!parse_timing(buf, decimal, &idx, &seconds, &nbytes)) |
errorx(1, _("invalid timing file line: %s"), buf); | fatalx(_("invalid timing file line: %s"), buf); |
|
|
if (interactive) |
if (interactive) |
check_input(STDIN_FILENO, &speed); |
check_input(STDIN_FILENO, &speed); |
Line 395 main(int argc, char *argv[])
|
Line 428 main(int argc, char *argv[])
|
if (io_fds[idx].v == NULL) |
if (io_fds[idx].v == NULL) |
continue; |
continue; |
|
|
|
/* Check whether we need to convert newline to CR LF pairs. */ |
|
if (interactive) |
|
need_nlcr = (idx == IOFD_STDOUT || idx == IOFD_STDERR); |
|
|
/* All output is sent to stdout. */ |
/* All output is sent to stdout. */ |
while (nbytes != 0) { |
while (nbytes != 0) { |
if (nbytes > sizeof(buf)) |
if (nbytes > sizeof(buf)) |
Line 407 main(int argc, char *argv[])
|
Line 444 main(int argc, char *argv[])
|
nread = fread(buf, 1, len, io_fds[idx].f); |
nread = fread(buf, 1, len, io_fds[idx].f); |
#endif |
#endif |
nbytes -= nread; |
nbytes -= nread; |
off = 0; | |
do { | /* Convert newline to carriage return + linefeed if needed. */ |
/* no stdio, must be unbuffered */ | if (need_nlcr) { |
nwritten = write(STDOUT_FILENO, buf + off, nread - off); | size_t remainder = nread; |
if (nwritten == -1) { | size_t linelen; |
if (errno == EINTR) | iovcnt = 0; |
continue; | cp = buf; |
if (errno == EAGAIN) { | ep = cp - 1; |
FD_SET(STDOUT_FILENO, fdsw); | /* Handle a "\r\n" pair that spans a buffer. */ |
do { | if (last_char == '\r' && buf[0] == '\n') { |
nready = select(STDOUT_FILENO + 1, NULL, fdsw, NULL, NULL); | ep++; |
} while (nready == -1 && errno == EINTR); | remainder--; |
if (nready == 1) | } |
continue; | while ((ep = memchr(ep + 1, '\n', remainder)) != NULL) { |
| /* Is there already a carriage return? */ |
| if (cp != ep && ep[-1] == '\r') { |
| remainder = (size_t)(&buf[nread - 1] - ep); |
| continue; |
} |
} |
error(1, _("writing to standard output")); | |
| /* Store the line in iov followed by \r\n pair. */ |
| if (iovcnt + 3 > iovmax) { |
| iovmax <<= 1; |
| iov = erealloc3(iov, iovmax, sizeof(*iov)); |
| } |
| linelen = (size_t)(ep - cp) + 1; |
| iov[iovcnt].iov_base = cp; |
| iov[iovcnt].iov_len = linelen - 1; /* not including \n */ |
| iovcnt++; |
| iov[iovcnt].iov_base = "\r\n"; |
| iov[iovcnt].iov_len = 2; |
| iovcnt++; |
| cp = ep + 1; |
| remainder -= linelen; |
} |
} |
off += nwritten; | if (cp - buf != nread) { |
} while (nread > off); | /* |
| * Partial line without a linefeed or multiple lines |
| * with \r\n pairs. |
| */ |
| iov[iovcnt].iov_base = cp; |
| iov[iovcnt].iov_len = nread - (cp - buf); |
| iovcnt++; |
| } |
| last_char = buf[nread - 1]; /* stash last char of old buffer */ |
| } else { |
| /* No conversion needed. */ |
| iov[0].iov_base = buf; |
| iov[0].iov_len = nread; |
| iovcnt = 1; |
| } |
| if (atomic_writev(STDOUT_FILENO, iov, iovcnt) == -1) |
| fatal(_("writing to standard output")); |
} |
} |
} |
} |
term_restore(STDIN_FILENO, 1); |
term_restore(STDIN_FILENO, 1); |
exit(0); | done: |
| sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode); |
| exit(exitcode); |
} |
} |
|
|
static void |
static void |
Line 452 delay(double secs)
|
Line 525 delay(double secs)
|
rval = nanosleep(&ts, &rts); |
rval = nanosleep(&ts, &rts); |
} while (rval == -1 && errno == EINTR); |
} while (rval == -1 && errno == EINTR); |
if (rval == -1) { |
if (rval == -1) { |
error(1, _("nanosleep: tv_sec %ld, tv_nsec %ld"), | fatal_nodebug("nanosleep: tv_sec %lld, tv_nsec %ld", |
(long)ts.tv_sec, (long)ts.tv_nsec); | (long long)ts.tv_sec, (long)ts.tv_nsec); |
} |
} |
} |
} |
|
|
static int |
static int |
open_io_fd(char *path, int len, const char *suffix, union io_fd *fdp) |
open_io_fd(char *path, int len, const char *suffix, union io_fd *fdp) |
{ |
{ |
|
debug_decl(open_io_fd, SUDO_DEBUG_UTIL) |
|
|
path[len] = '\0'; |
path[len] = '\0'; |
strlcat(path, suffix, PATH_MAX); |
strlcat(path, suffix, PATH_MAX); |
|
|
#ifdef HAVE_ZLIB_H |
#ifdef HAVE_ZLIB_H |
fdp->g = gzopen(path, "r"); |
fdp->g = gzopen(path, "r"); |
return fdp->g ? 0 : -1; |
|
#else |
#else |
fdp->f = fopen(path, "r"); |
fdp->f = fopen(path, "r"); |
return fdp->f ? 0 : -1; |
|
#endif |
#endif |
|
debug_return_int(fdp->v ? 0 : -1); |
} |
} |
|
|
/* |
/* |
|
* Call writev(), restarting as needed and handling EAGAIN since |
|
* fd may be in non-blocking mode. |
|
*/ |
|
static size_t |
|
atomic_writev(int fd, struct iovec *iov, int iovcnt) |
|
{ |
|
ssize_t n, nwritten = 0; |
|
size_t count, remainder, nbytes = 0; |
|
int i; |
|
debug_decl(atomic_writev, SUDO_DEBUG_UTIL) |
|
|
|
for (i = 0; i < iovcnt; i++) |
|
nbytes += iov[i].iov_len; |
|
|
|
for (;;) { |
|
n = writev(STDOUT_FILENO, iov, iovcnt); |
|
if (n > 0) { |
|
nwritten += n; |
|
remainder = nbytes - nwritten; |
|
if (remainder == 0) |
|
break; |
|
/* short writev, adjust iov and do the rest. */ |
|
count = 0; |
|
i = iovcnt; |
|
while (i--) { |
|
count += iov[i].iov_len; |
|
if (count == remainder) { |
|
iov += i; |
|
iovcnt -= i; |
|
break; |
|
} |
|
if (count > remainder) { |
|
size_t off = (count - remainder); |
|
/* XXX - side effect prevents iov from being const */ |
|
iov[i].iov_base = (char *)iov[i].iov_base + off; |
|
iov[i].iov_len -= off; |
|
iov += i; |
|
iovcnt -= i; |
|
break; |
|
} |
|
} |
|
continue; |
|
} |
|
if (n == 0 || errno == EAGAIN) { |
|
int nready; |
|
fd_set fdsw; |
|
FD_ZERO(&fdsw); |
|
FD_SET(STDOUT_FILENO, &fdsw); |
|
do { |
|
nready = select(STDOUT_FILENO + 1, NULL, &fdsw, NULL, NULL); |
|
} while (nready == -1 && errno == EINTR); |
|
if (nready == 1) |
|
continue; |
|
} |
|
if (errno == EINTR) |
|
continue; |
|
nwritten = -1; |
|
break; |
|
} |
|
debug_return_size_t(nwritten); |
|
} |
|
|
|
/* |
* Build expression list from search args |
* Build expression list from search args |
*/ |
*/ |
static int |
static int |
Line 480 parse_expr(struct search_node **headp, char *argv[])
|
Line 617 parse_expr(struct search_node **headp, char *argv[])
|
{ |
{ |
struct search_node *sn, *newsn; |
struct search_node *sn, *newsn; |
char or = 0, not = 0, type, **av; |
char or = 0, not = 0, type, **av; |
|
debug_decl(parse_expr, SUDO_DEBUG_UTIL) |
|
|
sn = *headp; |
sn = *headp; |
for (av = argv; *av; av++) { |
for (av = argv; *av; av++) { |
Line 500 parse_expr(struct search_node **headp, char *argv[])
|
Line 638 parse_expr(struct search_node **headp, char *argv[])
|
continue; |
continue; |
case 'c': /* command */ |
case 'c': /* command */ |
if (av[0][1] == '\0') |
if (av[0][1] == '\0') |
errorx(1, _("ambiguous expression \"%s\""), *av); | fatalx(_("ambiguous expression \"%s\""), *av); |
if (strncmp(*av, "cwd", strlen(*av)) == 0) |
if (strncmp(*av, "cwd", strlen(*av)) == 0) |
type = ST_CWD; |
type = ST_CWD; |
else if (strncmp(*av, "command", strlen(*av)) == 0) |
else if (strncmp(*av, "command", strlen(*av)) == 0) |
Line 525 parse_expr(struct search_node **headp, char *argv[])
|
Line 663 parse_expr(struct search_node **headp, char *argv[])
|
break; |
break; |
case 't': /* tty or to date */ |
case 't': /* tty or to date */ |
if (av[0][1] == '\0') |
if (av[0][1] == '\0') |
errorx(1, _("ambiguous expression \"%s\""), *av); | fatalx(_("ambiguous expression \"%s\""), *av); |
if (strncmp(*av, "todate", strlen(*av)) == 0) |
if (strncmp(*av, "todate", strlen(*av)) == 0) |
type = ST_TODATE; |
type = ST_TODATE; |
else if (strncmp(*av, "tty", strlen(*av)) == 0) |
else if (strncmp(*av, "tty", strlen(*av)) == 0) |
Line 542 parse_expr(struct search_node **headp, char *argv[])
|
Line 680 parse_expr(struct search_node **headp, char *argv[])
|
if (av[0][1] != '\0') |
if (av[0][1] != '\0') |
goto bad; |
goto bad; |
if (stack_top + 1 == STACK_NODE_SIZE) { |
if (stack_top + 1 == STACK_NODE_SIZE) { |
errorx(1, _("too many parenthesized expressions, max %d"), | fatalx(_("too many parenthesized expressions, max %d"), |
STACK_NODE_SIZE); |
STACK_NODE_SIZE); |
} |
} |
node_stack[stack_top++] = sn; |
node_stack[stack_top++] = sn; |
Line 553 parse_expr(struct search_node **headp, char *argv[])
|
Line 691 parse_expr(struct search_node **headp, char *argv[])
|
goto bad; |
goto bad; |
/* pop */ |
/* pop */ |
if (--stack_top < 0) |
if (--stack_top < 0) |
errorx(1, _("unmatched ')' in expression")); | fatalx(_("unmatched ')' in expression")); |
if (node_stack[stack_top]) |
if (node_stack[stack_top]) |
sn->next = node_stack[stack_top]->next; |
sn->next = node_stack[stack_top]->next; |
return av - argv + 1; | debug_return_int(av - argv + 1); |
bad: |
bad: |
default: |
default: |
errorx(1, _("unknown search term \"%s\""), *av); | fatalx(_("unknown search term \"%s\""), *av); |
/* NOTREACHED */ |
/* NOTREACHED */ |
} |
} |
|
|
/* Allocate new search node */ |
/* Allocate new search node */ |
newsn = emalloc(sizeof(*newsn)); | newsn = ecalloc(1, sizeof(*newsn)); |
newsn->next = NULL; | |
newsn->type = type; |
newsn->type = type; |
newsn->or = or; |
newsn->or = or; |
newsn->negated = not; |
newsn->negated = not; |
|
/* newsn->next = NULL; */ |
if (type == ST_EXPR) { |
if (type == ST_EXPR) { |
av += parse_expr(&newsn->u.expr, av + 1); |
av += parse_expr(&newsn->u.expr, av + 1); |
} else { |
} else { |
if (*(++av) == NULL) |
if (*(++av) == NULL) |
errorx(1, _("%s requires an argument"), av[-1]); | fatalx(_("%s requires an argument"), av[-1]); |
#ifdef HAVE_REGCOMP |
#ifdef HAVE_REGCOMP |
if (type == ST_PATTERN) { |
if (type == ST_PATTERN) { |
if (regcomp(&newsn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0) |
if (regcomp(&newsn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0) |
errorx(1, _("invalid regular expression: %s"), *av); | fatalx(_("invalid regular expression: %s"), *av); |
} else |
} else |
#endif |
#endif |
if (type == ST_TODATE || type == ST_FROMDATE) { |
if (type == ST_TODATE || type == ST_FROMDATE) { |
newsn->u.tstamp = get_date(*av); |
newsn->u.tstamp = get_date(*av); |
if (newsn->u.tstamp == -1) |
if (newsn->u.tstamp == -1) |
errorx(1, _("could not parse date \"%s\""), *av); | fatalx(_("could not parse date \"%s\""), *av); |
} else { |
} else { |
newsn->u.ptr = *av; |
newsn->u.ptr = *av; |
} |
} |
Line 596 parse_expr(struct search_node **headp, char *argv[])
|
Line 734 parse_expr(struct search_node **headp, char *argv[])
|
sn = newsn; |
sn = newsn; |
} |
} |
if (stack_top) |
if (stack_top) |
errorx(1, _("unmatched '(' in expression")); | fatalx(_("unmatched '(' in expression")); |
if (or) |
if (or) |
errorx(1, _("illegal trailing \"or\"")); | fatalx(_("illegal trailing \"or\"")); |
if (not) |
if (not) |
errorx(1, _("illegal trailing \"!\"")); | fatalx(_("illegal trailing \"!\"")); |
|
|
return av - argv; | debug_return_int(av - argv); |
} |
} |
|
|
static int | static bool |
match_expr(struct search_node *head, struct log_info *log) |
match_expr(struct search_node *head, struct log_info *log) |
{ |
{ |
struct search_node *sn; |
struct search_node *sn; |
int matched = 1, rc; | bool matched = true; |
| int rc; |
| debug_decl(match_expr, SUDO_DEBUG_UTIL) |
|
|
for (sn = head; sn; sn = sn->next) { |
for (sn = head; sn; sn = sn->next) { |
/* If we have no match, skip ahead to the next OR entry. */ |
/* If we have no match, skip ahead to the next OR entry. */ |
Line 641 match_expr(struct search_node *head, struct log_info *
|
Line 781 match_expr(struct search_node *head, struct log_info *
|
if (rc && rc != REG_NOMATCH) { |
if (rc && rc != REG_NOMATCH) { |
char buf[BUFSIZ]; |
char buf[BUFSIZ]; |
regerror(rc, &sn->u.cmdre, buf, sizeof(buf)); |
regerror(rc, &sn->u.cmdre, buf, sizeof(buf)); |
errorx(1, "%s", buf); | fatalx("%s", buf); |
} |
} |
matched = rc == REG_NOMATCH ? 0 : 1; |
matched = rc == REG_NOMATCH ? 0 : 1; |
#else |
#else |
Line 658 match_expr(struct search_node *head, struct log_info *
|
Line 798 match_expr(struct search_node *head, struct log_info *
|
if (sn->negated) |
if (sn->negated) |
matched = !matched; |
matched = !matched; |
} |
} |
return matched; | debug_return_bool(matched); |
} |
} |
|
|
static int | static struct log_info * |
list_session(char *logfile, REGEX_T *re, const char *user, const char *tty) | parse_logfile(char *logfile) |
{ |
{ |
FILE *fp; |
FILE *fp; |
char *buf = NULL, *cmd = NULL, *cwd = NULL, idbuf[7], *idstr, *cp; | char *buf = NULL, *cp, *ep; |
struct log_info li; | |
size_t bufsize = 0, cwdsize = 0, cmdsize = 0; |
size_t bufsize = 0, cwdsize = 0, cmdsize = 0; |
int rval = -1; | struct log_info *li = NULL; |
| debug_decl(list_session, SUDO_DEBUG_UTIL) |
|
|
fp = fopen(logfile, "r"); |
fp = fopen(logfile, "r"); |
if (fp == NULL) { |
if (fp == NULL) { |
warning(_("unable to open %s"), logfile); |
warning(_("unable to open %s"), logfile); |
goto done; | goto bad; |
} |
} |
|
|
/* |
/* |
Line 682 list_session(char *logfile, REGEX_T *re, const char *u
|
Line 822 list_session(char *logfile, REGEX_T *re, const char *u
|
* 2) cwd |
* 2) cwd |
* 3) command with args |
* 3) command with args |
*/ |
*/ |
|
li = ecalloc(1, sizeof(*li)); |
if (getline(&buf, &bufsize, fp) == -1 || |
if (getline(&buf, &bufsize, fp) == -1 || |
getline(&cwd, &cwdsize, fp) == -1 || | getline(&li->cwd, &cwdsize, fp) == -1 || |
getline(&cmd, &cmdsize, fp) == -1) { | getline(&li->cmd, &cmdsize, fp) == -1) { |
goto done; | goto bad; |
} |
} |
|
|
/* crack the log line: timestamp:user:runas_user:runas_group:tty */ | /* Strip the newline from the cwd and command. */ |
| li->cwd[strcspn(li->cwd, "\n")] = '\0'; |
| li->cmd[strcspn(li->cmd, "\n")] = '\0'; |
| |
| /* |
| * Crack the log line (rows and cols not present in old versions). |
| * timestamp:user:runas_user:runas_group:tty:rows:cols |
| */ |
buf[strcspn(buf, "\n")] = '\0'; |
buf[strcspn(buf, "\n")] = '\0'; |
if ((li.tstamp = atoi(buf)) == 0) |
|
goto done; |
|
|
|
if ((cp = strchr(buf, ':')) == NULL) | /* timestamp */ |
goto done; | if ((ep = strchr(buf, ':')) == NULL) |
*cp++ = '\0'; | goto bad; |
li.user = cp; | if ((li->tstamp = atoi(buf)) == 0) |
| goto bad; |
|
|
if ((cp = strchr(cp, ':')) == NULL) | /* user */ |
goto done; | cp = ep + 1; |
*cp++ = '\0'; | if ((ep = strchr(cp, ':')) == NULL) |
li.runas_user = cp; | goto bad; |
| li->user = estrndup(cp, (size_t)(ep - cp)); |
|
|
if ((cp = strchr(cp, ':')) == NULL) | /* runas user */ |
goto done; | cp = ep + 1; |
*cp++ = '\0'; | if ((ep = strchr(cp, ':')) == NULL) |
li.runas_group = cp; | goto bad; |
| li->runas_user = estrndup(cp, (size_t)(ep - cp)); |
|
|
if ((cp = strchr(cp, ':')) == NULL) | /* runas group */ |
goto done; | cp = ep + 1; |
*cp++ = '\0'; | if ((ep = strchr(cp, ':')) == NULL) |
li.tty = cp; | goto bad; |
| if (cp != ep) |
| li->runas_group = estrndup(cp, (size_t)(ep - cp)); |
|
|
cwd[strcspn(cwd, "\n")] = '\0'; | /* tty, followed by optional rows + columns */ |
li.cwd = cwd; | cp = ep + 1; |
| if ((ep = strchr(cp, ':')) == NULL) { |
| li->tty = estrdup(cp); |
| } else { |
| li->tty = estrndup(cp, (size_t)(ep - cp)); |
| cp = ep + 1; |
| li->rows = atoi(cp); |
| if ((ep = strchr(cp, ':')) != NULL) { |
| cp = ep + 1; |
| li->cols = atoi(cp); |
| } |
| } |
| fclose(fp); |
| efree(buf); |
| debug_return_ptr(li); |
|
|
cmd[strcspn(cmd, "\n")] = '\0'; | bad: |
li.cmd = cmd; | if (fp != NULL) |
| fclose(fp); |
| efree(buf); |
| free_log_info(li); |
| debug_return_ptr(NULL); |
| } |
|
|
|
static void |
|
free_log_info(struct log_info *li) |
|
{ |
|
if (li != NULL) { |
|
efree(li->cwd); |
|
efree(li->user); |
|
efree(li->runas_user); |
|
efree(li->runas_group); |
|
efree(li->tty); |
|
efree(li->cmd); |
|
efree(li); |
|
} |
|
} |
|
|
|
static int |
|
list_session(char *logfile, REGEX_T *re, const char *user, const char *tty) |
|
{ |
|
char idbuf[7], *idstr, *cp; |
|
struct log_info *li; |
|
int rval = -1; |
|
debug_decl(list_session, SUDO_DEBUG_UTIL) |
|
|
|
if ((li = parse_logfile(logfile)) == NULL) |
|
goto done; |
|
|
/* Match on search expression if there is one. */ |
/* Match on search expression if there is one. */ |
if (search_expr && !match_expr(search_expr, &li)) | if (search_expr && !match_expr(search_expr, li)) |
goto done; |
goto done; |
|
|
/* Convert from /var/log/sudo-sessions/00/00/01/log to 000001 */ |
/* Convert from /var/log/sudo-sessions/00/00/01/log to 000001 */ |
cp = logfile + strlen(session_dir) + 1; |
cp = logfile + strlen(session_dir) + 1; |
if (IS_IDLOG(cp)) { |
if (IS_IDLOG(cp)) { |
idbuf[0] = cp[7]; | idbuf[0] = cp[0]; |
idbuf[1] = cp[6]; | idbuf[1] = cp[1]; |
idbuf[2] = cp[4]; | idbuf[2] = cp[3]; |
idbuf[3] = cp[3]; | idbuf[3] = cp[4]; |
idbuf[4] = cp[1]; | idbuf[4] = cp[6]; |
idbuf[5] = cp[0]; | idbuf[5] = cp[7]; |
idbuf[6] = '\0'; |
idbuf[6] = '\0'; |
idstr = idbuf; |
idstr = idbuf; |
} else { |
} else { |
Line 739 list_session(char *logfile, REGEX_T *re, const char *u
|
Line 934 list_session(char *logfile, REGEX_T *re, const char *u
|
cp[strlen(cp) - 4] = '\0'; |
cp[strlen(cp) - 4] = '\0'; |
idstr = cp; |
idstr = cp; |
} |
} |
|
/* XXX - print rows + cols? */ |
printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ", |
printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ", |
get_timestr(li.tstamp, 1), li.user, li.tty, li.cwd, li.runas_user); | get_timestr(li->tstamp, 1), li->user, li->tty, li->cwd, li->runas_user); |
if (*li.runas_group) | if (li->runas_group) |
printf("GROUP=%s ; ", li.runas_group); | printf("GROUP=%s ; ", li->runas_group); |
printf("TSID=%s ; COMMAND=%s\n", idstr, li.cmd); | printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd); |
|
|
rval = 0; |
rval = 0; |
|
|
done: |
done: |
fclose(fp); | free_log_info(li); |
return rval; | debug_return_int(rval); |
} |
} |
|
|
static int |
static int |
|
session_compare(const void *v1, const void *v2) |
|
{ |
|
const char *s1 = *(const char **)v1; |
|
const char *s2 = *(const char **)v2; |
|
return strcmp(s1, s2); |
|
} |
|
|
|
/* XXX - always returns 0, calls fatal() on failure */ |
|
static int |
find_sessions(const char *dir, REGEX_T *re, const char *user, const char *tty) |
find_sessions(const char *dir, REGEX_T *re, const char *user, const char *tty) |
{ |
{ |
DIR *d; |
DIR *d; |
struct dirent *dp; |
struct dirent *dp; |
struct stat sb; |
struct stat sb; |
size_t sdlen; | size_t sdlen, sessions_len = 0, sessions_size = 36*36; |
int len; | int i, len; |
char pathbuf[PATH_MAX]; | char pathbuf[PATH_MAX], **sessions = NULL; |
| #ifdef HAVE_STRUCT_DIRENT_D_TYPE |
| bool checked_type = true; |
| #else |
| const bool checked_type = false; |
| #endif |
| debug_decl(find_sessions, SUDO_DEBUG_UTIL) |
|
|
d = opendir(dir); |
d = opendir(dir); |
if (d == NULL) |
if (d == NULL) |
error(1, _("unable to open %s"), dir); | fatal(_("unable to open %s"), dir); |
|
|
/* XXX - would be faster to chdir and use relative names */ |
/* XXX - would be faster to chdir and use relative names */ |
sdlen = strlcpy(pathbuf, dir, sizeof(pathbuf)); |
sdlen = strlcpy(pathbuf, dir, sizeof(pathbuf)); |
if (sdlen + 1 >= sizeof(pathbuf)) { |
if (sdlen + 1 >= sizeof(pathbuf)) { |
errno = ENAMETOOLONG; |
errno = ENAMETOOLONG; |
error(1, "%s/", dir); | fatal("%s/", dir); |
} |
} |
pathbuf[sdlen++] = '/'; |
pathbuf[sdlen++] = '/'; |
pathbuf[sdlen] = '\0'; |
pathbuf[sdlen] = '\0'; |
|
|
|
/* Store potential session dirs for sorting. */ |
|
sessions = emalloc2(sessions_size, sizeof(char *)); |
while ((dp = readdir(d)) != NULL) { |
while ((dp = readdir(d)) != NULL) { |
/* Skip "." and ".." */ |
/* Skip "." and ".." */ |
if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || |
if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || |
(dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) |
(dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) |
continue; |
continue; |
|
#ifdef HAVE_STRUCT_DIRENT_D_TYPE |
|
if (checked_type) { |
|
if (dp->d_type != DT_DIR) { |
|
/* Not all file systems support d_type. */ |
|
if (dp->d_type != DT_UNKNOWN) |
|
continue; |
|
checked_type = false; |
|
} |
|
} |
|
#endif |
|
|
|
/* Add name to session list. */ |
|
if (sessions_len + 1 > sessions_size) { |
|
sessions_size <<= 1; |
|
sessions = erealloc3(sessions, sessions_size, sizeof(char *)); |
|
} |
|
sessions[sessions_len++] = estrdup(dp->d_name); |
|
} |
|
closedir(d); |
|
|
|
/* Sort and list the sessions. */ |
|
qsort(sessions, sessions_len, sizeof(char *), session_compare); |
|
for (i = 0; i < sessions_len; i++) { |
len = snprintf(&pathbuf[sdlen], sizeof(pathbuf) - sdlen, |
len = snprintf(&pathbuf[sdlen], sizeof(pathbuf) - sdlen, |
"%s/log", dp->d_name); | "%s/log", sessions[i]); |
if (len <= 0 || len >= sizeof(pathbuf) - sdlen) { |
if (len <= 0 || len >= sizeof(pathbuf) - sdlen) { |
errno = ENAMETOOLONG; |
errno = ENAMETOOLONG; |
error(1, "%s/%s/log", dir, dp->d_name); | fatal("%s/%s/log", dir, sessions[i]); |
} |
} |
|
efree(sessions[i]); |
|
|
/* Check for dir with a log file. */ |
/* Check for dir with a log file. */ |
if (lstat(pathbuf, &sb) == 0 && S_ISREG(sb.st_mode)) { |
if (lstat(pathbuf, &sb) == 0 && S_ISREG(sb.st_mode)) { |
Line 793 find_sessions(const char *dir, REGEX_T *re, const char
|
Line 1030 find_sessions(const char *dir, REGEX_T *re, const char
|
} else { |
} else { |
/* Strip off "/log" and recurse if a dir. */ |
/* Strip off "/log" and recurse if a dir. */ |
pathbuf[sdlen + len - 4] = '\0'; |
pathbuf[sdlen + len - 4] = '\0'; |
if (lstat(pathbuf, &sb) == 0 && S_ISDIR(sb.st_mode)) | if (checked_type || (lstat(pathbuf, &sb) == 0 && S_ISDIR(sb.st_mode))) |
find_sessions(pathbuf, re, user, tty); |
find_sessions(pathbuf, re, user, tty); |
} |
} |
} |
} |
closedir(d); | efree(sessions); |
|
|
return 0; | debug_return_int(0); |
} |
} |
|
|
|
/* XXX - always returns 0, calls fatal() on failure */ |
static int |
static int |
list_sessions(int argc, char **argv, const char *pattern, const char *user, |
list_sessions(int argc, char **argv, const char *pattern, const char *user, |
const char *tty) |
const char *tty) |
{ |
{ |
REGEX_T rebuf, *re = NULL; |
REGEX_T rebuf, *re = NULL; |
|
debug_decl(list_sessions, SUDO_DEBUG_UTIL) |
|
|
/* Parse search expression if present */ |
/* Parse search expression if present */ |
parse_expr(&search_expr, argv); |
parse_expr(&search_expr, argv); |
Line 816 list_sessions(int argc, char **argv, const char *patte
|
Line 1055 list_sessions(int argc, char **argv, const char *patte
|
if (pattern) { |
if (pattern) { |
re = &rebuf; |
re = &rebuf; |
if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0) |
if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0) |
errorx(1, _("invalid regex: %s"), pattern); | fatalx(_("invalid regular expression: %s"), pattern); |
} |
} |
#else |
#else |
re = (char *) pattern; |
re = (char *) pattern; |
#endif /* HAVE_REGCOMP */ |
#endif /* HAVE_REGCOMP */ |
|
|
return find_sessions(session_dir, re, user, tty); | debug_return_int(find_sessions(session_dir, re, user, tty)); |
} |
} |
|
|
/* |
/* |
Line 837 check_input(int ttyfd, double *speed)
|
Line 1076 check_input(int ttyfd, double *speed)
|
struct timeval tv; |
struct timeval tv; |
char ch; |
char ch; |
ssize_t n; |
ssize_t n; |
|
debug_decl(check_input, SUDO_DEBUG_UTIL) |
|
|
fdsr = (fd_set *)emalloc2(howmany(ttyfd + 1, NFDBITS), sizeof(fd_mask)); | fdsr = ecalloc(howmany(ttyfd + 1, NFDBITS), sizeof(fd_mask)); |
| |
for (;;) { |
for (;;) { |
FD_SET(ttyfd, fdsr); |
FD_SET(ttyfd, fdsr); |
tv.tv_sec = 0; |
tv.tv_sec = 0; |
Line 868 check_input(int ttyfd, double *speed)
|
Line 1107 check_input(int ttyfd, double *speed)
|
} |
} |
} |
} |
free(fdsr); |
free(fdsr); |
|
debug_return; |
} |
} |
|
|
/* |
/* |
Line 889 parse_timing(buf, decimal, idx, seconds, nbytes)
|
Line 1129 parse_timing(buf, decimal, idx, seconds, nbytes)
|
long l; |
long l; |
double d, fract = 0; |
double d, fract = 0; |
char *cp, *ep; |
char *cp, *ep; |
|
debug_decl(parse_timing, SUDO_DEBUG_UTIL) |
|
|
/* Parse index */ |
/* Parse index */ |
ul = strtoul(buf, &ep, 10); |
ul = strtoul(buf, &ep, 10); |
Line 929 parse_timing(buf, decimal, idx, seconds, nbytes)
|
Line 1170 parse_timing(buf, decimal, idx, seconds, nbytes)
|
goto bad; |
goto bad; |
*nbytes = (size_t)ul; |
*nbytes = (size_t)ul; |
|
|
return 1; | debug_return_int(1); |
bad: |
bad: |
return 0; | debug_return_int(0); |
} |
} |
|
|
static void |
static void |
Line 964 help(void)
|
Line 1205 help(void)
|
} |
} |
|
|
/* |
/* |
* Cleanup hook for error()/errorx() | * Cleanup hook for fatal()/fatalx() |
*/ |
*/ |
void | static void |
cleanup(int signo) | sudoreplay_cleanup(void) |
{ |
{ |
term_restore(STDIN_FILENO, 0); |
term_restore(STDIN_FILENO, 0); |
if (signo) | } |
kill(getpid(), signo); | |
| /* |
| * Signal handler for SIGINT, SIGKILL, SIGTERM, SIGHUP |
| * Must be installed with SA_RESETHAND enabled. |
| */ |
| static void |
| sudoreplay_handler(int signo) |
| { |
| term_restore(STDIN_FILENO, 0); |
| kill(getpid(), signo); |
} |
} |