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

/*
 * Copyright (c) 2009-2014 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <config.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#ifdef TIME_WITH_SYS_TIME
# include <time.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#ifdef HAVE_ZLIB_H
# include <zlib.h>
#endif

#include "sudoers.h"
#include "iolog.h"

struct script_buf {
    int len; /* buffer length (how much read in) */
    int off; /* write position (how much already consumed) */
    char buf[16 * 1024];
};

/* XXX - separate sudoers.h and iolog.h? */
#undef runas_pw
#undef runas_gr

struct iolog_details {
    const char *cwd;
    const char *tty;
    const char *user;
    const char *command;
    const char *iolog_path;
    struct passwd *runas_pw;
    struct group *runas_gr;
    int lines;
    int cols;
};

static int iolog_compress;
static struct timeval last_time;
static unsigned int sessid_max = SESSID_MAX;

/* sudoers_io is declared at the end of this file. */
extern __dso_public struct io_plugin sudoers_io;

/*
 * Create path and any parent directories as needed.
 * If is_temp is set, use mkdtemp() for the final directory.
 */
static void
io_mkdirs(char *path, mode_t mode, bool is_temp)
{
    struct stat sb;
    gid_t parent_gid = 0;
    char *slash = path;
    debug_decl(io_mkdirs, SUDO_DEBUG_UTIL)

    /* Fast path: not a temporary and already exists. */
    if (!is_temp && stat(path, &sb) == 0) {
	if (!S_ISDIR(sb.st_mode)) {
	    log_fatal(0, N_("%s exists but is not a directory (0%o)"),
		path, (unsigned int) sb.st_mode);
	}
	debug_return;
    }

    while ((slash = strchr(slash + 1, '/')) != NULL) {
	*slash = '\0';
	if (stat(path, &sb) != 0) {
	    if (mkdir(path, mode) != 0)
		log_fatal(USE_ERRNO, N_("unable to mkdir %s"), path);
	    ignore_result(chown(path, (uid_t)-1, parent_gid));
	} else if (!S_ISDIR(sb.st_mode)) {
	    log_fatal(0, N_("%s exists but is not a directory (0%o)"),
		path, (unsigned int) sb.st_mode);
	} else {
	    /* Inherit gid of parent dir for ownership. */
	    parent_gid = sb.st_gid;
	}
	*slash = '/';
    }
    /* Create final path component. */
    if (is_temp) {
	if (mkdtemp(path) == NULL)
	    log_fatal(USE_ERRNO, N_("unable to mkdir %s"), path);
	ignore_result(chown(path, (uid_t)-1, parent_gid));
    } else {
	if (mkdir(path, mode) != 0 && errno != EEXIST)
	    log_fatal(USE_ERRNO, N_("unable to mkdir %s"), path);
	ignore_result(chown(path, (uid_t)-1, parent_gid));
    }
    debug_return;
}

/*
 * Set max session ID (aka sequence number)
 */
int
io_set_max_sessid(const char *maxval)
{
    const char *errstr;
    unsigned int value;
    debug_decl(io_set_max_sessid, SUDO_DEBUG_UTIL)

    value = strtonum(maxval, 0, SESSID_MAX, &errstr);
    if (errstr != NULL) {
	if (errno != ERANGE) {
	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
		"bad maxseq: %s: %s", maxval, errstr);
	    debug_return_bool(false);
	}
	/* Out of range, clamp to SESSID_MAX as documented. */
	value = SESSID_MAX;
    }
    sessid_max = value;
    debug_return_bool(true);
}

/*
 * Read the on-disk sequence number, set sessid to the next
 * number, and update the on-disk copy.
 * Uses file locking to avoid sequence number collisions.
 */
void
io_nextid(char *iolog_dir, char *iolog_dir_fallback, char sessid[7])
{
    struct stat sb;
    char buf[32], *ep;
    int fd, i;
    unsigned long id = 0;
    int len;
    ssize_t nread;
    char pathbuf[PATH_MAX];
    static const char b36char[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    debug_decl(io_nextid, SUDO_DEBUG_UTIL)

    /*
     * Create I/O log directory if it doesn't already exist.
     */
    io_mkdirs(iolog_dir, S_IRWXU, false);

    /*
     * Open sequence file
     */
    len = snprintf(pathbuf, sizeof(pathbuf), "%s/seq", iolog_dir);
    if (len <= 0 || (size_t)len >= sizeof(pathbuf)) {
	errno = ENAMETOOLONG;
	log_fatal(USE_ERRNO, "%s/seq", pathbuf);
    }
    fd = open(pathbuf, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
    if (fd == -1)
	log_fatal(USE_ERRNO, N_("unable to open %s"), pathbuf);
    lock_file(fd, SUDO_LOCK);

    /*
     * If there is no seq file in iolog_dir and a fallback dir was
     * specified, look for seq in the fallback dir.  This is to work
     * around a bug in sudo 1.8.5 and older where iolog_dir was not
     * expanded before the sequence number was updated.
     */
    if (iolog_dir_fallback != NULL && fstat(fd, &sb) == 0 && sb.st_size == 0) {
	char fallback[PATH_MAX];

	len = snprintf(fallback, sizeof(fallback), "%s/seq",
	    iolog_dir_fallback);
	if (len > 0 && (size_t)len < sizeof(fallback)) {
	    int fd2 = open(fallback, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
	    if (fd2 != -1) {
		nread = read(fd2, buf, sizeof(buf) - 1);
		if (nread > 0) {
		    if (buf[nread - 1] == '\n')
			nread--;
		    buf[nread] = '\0';
		    id = strtoul(buf, &ep, 36);
		    if (ep == buf || *ep != '\0' || id >= sessid_max) {
			sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
			    "%s: bad sequence number: %s", fallback, buf);
			id = 0;
		    }
		}
		close(fd2);
	    }
	}
    }

    /* Read current seq number (base 36). */
    if (id == 0) {
	nread = read(fd, buf, sizeof(buf) - 1);
	if (nread != 0) {
	    if (nread == -1)
		log_fatal(USE_ERRNO, N_("unable to read %s"), pathbuf);
	    if (buf[nread - 1] == '\n')
		nread--;
	    buf[nread] = '\0';
	    id = strtoul(buf, &ep, 36);
	    if (ep == buf || *ep != '\0' || id >= sessid_max) {
		sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
		    "%s: bad sequence number: %s", pathbuf, buf);
		id = 0;
	    }
	}
    }
    id++;

    /*
     * Convert id to a string and stash in sessid.
     * Note that that least significant digits go at the end of the string.
     */
    for (i = 5; i >= 0; i--) {
	buf[i] = b36char[id % 36];
	id /= 36;
    }
    buf[6] = '\n';

    /* Stash id for logging purposes. */
    memcpy(sessid, buf, 6);
    sessid[6] = '\0';

    /* Rewind and overwrite old seq file, including the NUL byte. */
    if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1 || write(fd, buf, 7) != 7)
	log_fatal(USE_ERRNO, N_("unable to write to %s"), pathbuf);
    close(fd);

    debug_return;
}

/*
 * Copy iolog_path to pathbuf and create the directory and any intermediate
 * directories.  If iolog_path ends in 'XXXXXX', use mkdtemp().
 */
static size_t
mkdir_iopath(const char *iolog_path, char *pathbuf, size_t pathsize)
{
    size_t len;
    bool is_temp = false;
    debug_decl(mkdir_iopath, SUDO_DEBUG_UTIL)

    len = strlcpy(pathbuf, iolog_path, pathsize);
    if (len >= pathsize) {
	errno = ENAMETOOLONG;
	log_fatal(USE_ERRNO, "%s", iolog_path);
    }

    /*
     * Create path and intermediate subdirs as needed.
     * If path ends in at least 6 Xs (ala POSIX mktemp), use mkdtemp().
     */
    if (len >= 6 && strcmp(&pathbuf[len - 6], "XXXXXX") == 0)
	is_temp = true;
    io_mkdirs(pathbuf, S_IRWXU, is_temp);

    debug_return_size_t(len);
}

/*
 * Append suffix to pathbuf after len chars and open the resulting file.
 * Note that the size of pathbuf is assumed to be PATH_MAX.
 * Uses zlib if docompress is true.
 * Stores the open file handle which has the close-on-exec flag set.
 */
static void
open_io_fd(char *pathbuf, size_t len, struct io_log_file *iol, bool docompress)
{
    int fd;
    debug_decl(open_io_fd, SUDO_DEBUG_UTIL)

    pathbuf[len] = '\0';
    strlcat(pathbuf, iol->suffix, PATH_MAX);
    if (iol->enabled) {
	fd = open(pathbuf, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR);
	if (fd != -1) {
	    fcntl(fd, F_SETFD, FD_CLOEXEC);
#ifdef HAVE_ZLIB_H
	    if (docompress)
		iol->fd.g = gzdopen(fd, "w");
	    else
#endif
		iol->fd.f = fdopen(fd, "w");
	}
	if (fd == -1 || iol->fd.v == NULL)
	    log_fatal(USE_ERRNO, N_("unable to create %s"), pathbuf);
    } else {
	/* Remove old log file if we recycled sequence numbers. */
	unlink(pathbuf);
    }
    debug_return;
}

/*
 * Pull out I/O log related data from user_info and command_info arrays.
 * Returns true if I/O logging is enabled, else false.
 */
static bool
iolog_deserialize_info(struct iolog_details *details, char * const user_info[],
    char * const command_info[])
{
    const char *runas_uid_str = "0", *runas_euid_str = NULL;
    const char *runas_gid_str = "0", *runas_egid_str = NULL;
    const char *errstr;
    char idbuf[MAX_UID_T_LEN + 2];
    char * const *cur;
    id_t id;
    uid_t runas_uid = 0;
    gid_t runas_gid = 0;
    debug_decl(iolog_deserialize_info, SUDO_DEBUG_UTIL)

    details->lines = 24;
    details->cols = 80;

    for (cur = user_info; *cur != NULL; cur++) {
	switch (**cur) {
	case 'c':
	    if (strncmp(*cur, "cols=", sizeof("cols=") - 1) == 0) {
		int n = strtonum(*cur + sizeof("cols=") - 1, 1, INT_MAX, NULL);
		if (n > 0)
		    details->cols = n;
		continue;
	    }
	    if (strncmp(*cur, "cwd=", sizeof("cwd=") - 1) == 0) {
		details->cwd = *cur + sizeof("cwd=") - 1;
		continue;
	    }
	    break;
	case 'l':
	    if (strncmp(*cur, "lines=", sizeof("lines=") - 1) == 0) {
		int n = strtonum(*cur + sizeof("lines=") - 1, 1, INT_MAX, NULL);
		if (n > 0)
		    details->lines = n;
		continue;
	    }
	    break;
	case 't':
	    if (strncmp(*cur, "tty=", sizeof("tty=") - 1) == 0) {
		details->tty = *cur + sizeof("tty=") - 1;
		continue;
	    }
	    break;
	case 'u':
	    if (strncmp(*cur, "user=", sizeof("user=") - 1) == 0) {
		details->user = *cur + sizeof("user=") - 1;
		continue;
	    }
	    break;
	}
    }

    for (cur = command_info; *cur != NULL; cur++) {
	switch (**cur) {
	case 'c':
	    if (strncmp(*cur, "command=", sizeof("command=") - 1) == 0) {
		details->command = *cur + sizeof("command=") - 1;
		continue;
	    }
	    break;
	case 'i':
	    if (strncmp(*cur, "iolog_path=", sizeof("iolog_path=") - 1) == 0) {
		details->iolog_path = *cur + sizeof("iolog_path=") - 1;
		continue;
	    }
	    if (strncmp(*cur, "iolog_stdin=", sizeof("iolog_stdin=") - 1) == 0) {
		if (atobool(*cur + sizeof("iolog_stdin=") - 1) == true)
		    io_log_files[IOFD_STDIN].enabled = true;
		continue;
	    }
	    if (strncmp(*cur, "iolog_stdout=", sizeof("iolog_stdout=") - 1) == 0) {
		if (atobool(*cur + sizeof("iolog_stdout=") - 1) == true)
		    io_log_files[IOFD_STDOUT].enabled = true;
		continue;
	    }
	    if (strncmp(*cur, "iolog_stderr=", sizeof("iolog_stderr=") - 1) == 0) {
		if (atobool(*cur + sizeof("iolog_stderr=") - 1) == true)
		    io_log_files[IOFD_STDERR].enabled = true;
		continue;
	    }
	    if (strncmp(*cur, "iolog_ttyin=", sizeof("iolog_ttyin=") - 1) == 0) {
		if (atobool(*cur + sizeof("iolog_ttyin=") - 1) == true)
		    io_log_files[IOFD_TTYIN].enabled = true;
		continue;
	    }
	    if (strncmp(*cur, "iolog_ttyout=", sizeof("iolog_ttyout=") - 1) == 0) {
		if (atobool(*cur + sizeof("iolog_ttyout=") - 1) == true)
		    io_log_files[IOFD_TTYOUT].enabled = true;
		continue;
	    }
	    if (strncmp(*cur, "iolog_compress=", sizeof("iolog_compress=") - 1) == 0) {
		if (atobool(*cur + sizeof("iolog_compress=") - 1) == true)
		    iolog_compress = true; /* must be global */
		continue;
	    }
	    break;
	case 'm':
	    if (strncmp(*cur, "maxseq=", sizeof("maxseq=") - 1) == 0)
		io_set_max_sessid(*cur + sizeof("maxseq=") - 1);
	    break;
	case 'r':
	    if (strncmp(*cur, "runas_gid=", sizeof("runas_gid=") - 1) == 0) {
		runas_gid_str = *cur + sizeof("runas_gid=") - 1;
		continue;
	    }
	    if (strncmp(*cur, "runas_egid=", sizeof("runas_egid=") - 1) == 0) {
		runas_egid_str = *cur + sizeof("runas_egid=") - 1;
		continue;
	    }
	    if (strncmp(*cur, "runas_uid=", sizeof("runas_uid=") - 1) == 0) {
		runas_uid_str = *cur + sizeof("runas_uid=") - 1;
		continue;
	    }
	    if (strncmp(*cur, "runas_euid=", sizeof("runas_euid=") - 1) == 0) {
		runas_euid_str = *cur + sizeof("runas_euid=") - 1;
		continue;
	    }
	    break;
	}
    }

    /*
     * Lookup runas user and group, preferring effective over real uid/gid.
     */
    if (runas_euid_str != NULL)
	runas_uid_str = runas_euid_str;
    if (runas_uid_str != NULL) {
	id = atoid(runas_uid_str, NULL, NULL, &errstr);
	if (errstr != NULL)
	    warningx("runas uid %s: %s", runas_uid_str, U_(errstr));
	else
	    runas_uid = (uid_t)id;
    }
    if (runas_egid_str != NULL)
	runas_gid_str = runas_egid_str;
    if (runas_gid_str != NULL) {
	id = atoid(runas_gid_str, NULL, NULL, &errstr);
	if (errstr != NULL)
	    warningx("runas gid %s: %s", runas_gid_str, U_(errstr));
	else
	    runas_gid = (gid_t)id;
    }

    details->runas_pw = sudo_getpwuid(runas_uid);
    if (details->runas_pw == NULL) {
	idbuf[0] = '#';
	strlcpy(&idbuf[1], runas_uid_str, sizeof(idbuf) - 1);
	details->runas_pw = sudo_fakepwnam(idbuf, runas_gid);
    }

    if (runas_gid != details->runas_pw->pw_gid) {
	details->runas_gr = sudo_getgrgid(runas_gid);
	if (details->runas_gr == NULL) {
	    idbuf[0] = '#';
	    strlcpy(&idbuf[1], runas_gid_str, sizeof(idbuf) - 1);
	    details->runas_gr = sudo_fakegrnam(idbuf);
	}
    }
    debug_return_bool(
	io_log_files[IOFD_STDIN].enabled || io_log_files[IOFD_STDOUT].enabled ||
	io_log_files[IOFD_STDERR].enabled || io_log_files[IOFD_TTYIN].enabled ||
	io_log_files[IOFD_TTYOUT].enabled);
}

/*
 * Write the "/log" file that contains the user and command info.
 */
void
write_info_log(char *pathbuf, size_t len, struct iolog_details *details,
    char * const argv[], struct timeval *now)
{
    char * const *av;
    FILE *fp;
    int fd;

    pathbuf[len] = '\0';
    strlcat(pathbuf, "/log", PATH_MAX);
    fd = open(pathbuf, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR);
    if (fd == -1 || (fp = fdopen(fd, "w")) == NULL)
	log_fatal(USE_ERRNO, N_("unable to create %s"), pathbuf);

    fprintf(fp, "%lld:%s:%s:%s:%s:%d:%d\n%s\n%s", (long long)now->tv_sec,
	details->user ? details->user : "unknown", details->runas_pw->pw_name,
	details->runas_gr ? details->runas_gr->gr_name : "",
	details->tty ? details->tty : "unknown", details->lines, details->cols,
	details->cwd ? details->cwd : "unknown",
	details->command ? details->command : "unknown");
    for (av = argv + 1; *av != NULL; av++) {
	fputc(' ', fp);
	fputs(*av, fp);
    }
    fputc('\n', fp);
    fclose(fp);
}

static int
sudoers_io_open(unsigned int version, sudo_conv_t conversation,
    sudo_printf_t plugin_printf, char * const settings[],
    char * const user_info[], char * const command_info[],
    int argc, char * const argv[], char * const user_env[], char * const args[])
{
    struct iolog_details details;
    char pathbuf[PATH_MAX], sessid[7];
    char *tofree = NULL;
    char * const *cur;
    const char *debug_flags = NULL;
    size_t len;
    int i, rval = -1;
    debug_decl(sudoers_io_open, SUDO_DEBUG_PLUGIN)

    sudo_conv = conversation;
    sudo_printf = plugin_printf;

    /* If we have no command (because -V was specified) just return. */
    if (argc == 0)
	debug_return_bool(true);

    memset(&details, 0, sizeof(details));

    if (fatal_setjmp() != 0) {
	/* called via fatal(), fatalx() or log_fatal() */
	rval = -1;
	goto done;
    }

    bindtextdomain("sudoers", LOCALEDIR);

    sudo_setpwent();
    sudo_setgrent();

    /*
     * Check for debug flags in settings list.
     */
    for (cur = settings; *cur != NULL; cur++) {
	if (strncmp(*cur, "debug_flags=", sizeof("debug_flags=") - 1) == 0)
	    debug_flags = *cur + sizeof("debug_flags=") - 1;
    }
    if (debug_flags != NULL)
	sudo_debug_init(NULL, debug_flags);

    /*
     * Pull iolog settings out of command_info.
     */
    if (!iolog_deserialize_info(&details, user_info, command_info)) {
	rval = false;
	goto done;
    }

    /* If no I/O log path defined we need to figure it out ourselves. */
    if (details.iolog_path == NULL) {
	/* Get next session ID and convert it into a path. */
	tofree = emalloc(sizeof(_PATH_SUDO_IO_LOGDIR) + sizeof(sessid) + 2);
	memcpy(tofree, _PATH_SUDO_IO_LOGDIR, sizeof(_PATH_SUDO_IO_LOGDIR));
	io_nextid(tofree, NULL, sessid);
	snprintf(tofree + sizeof(_PATH_SUDO_IO_LOGDIR), sizeof(sessid) + 2,
	    "%c%c/%c%c/%c%c", sessid[0], sessid[1], sessid[2], sessid[3],
	    sessid[4], sessid[5]);
	details.iolog_path = tofree;
    }

    /*
     * Make local copy of I/O log path and create it, along with any
     * intermediate subdirs.  Calls mkdtemp() if iolog_path ends in XXXXXX.
     */
    len = mkdir_iopath(details.iolog_path, pathbuf, sizeof(pathbuf));
    if (len >= sizeof(pathbuf))
	goto done;

    /* Write log file with user and command details. */
    gettimeofday(&last_time, NULL);
    write_info_log(pathbuf, len, &details, argv, &last_time);

    /* Create the timing and I/O log files. */
    for (i = 0; i < IOFD_MAX; i++)
	open_io_fd(pathbuf, len, &io_log_files[i], iolog_compress);

    /*
     * Clear I/O log function pointers for disabled log functions.
     */
    if (!io_log_files[IOFD_STDIN].enabled)
	sudoers_io.log_stdin = NULL;
    if (!io_log_files[IOFD_STDOUT].enabled)
	sudoers_io.log_stdout = NULL;
    if (!io_log_files[IOFD_STDERR].enabled)
	sudoers_io.log_stderr = NULL;
    if (!io_log_files[IOFD_TTYIN].enabled)
	sudoers_io.log_ttyin = NULL;
    if (!io_log_files[IOFD_TTYOUT].enabled)
	sudoers_io.log_ttyout = NULL;

    rval = true;

done:
    fatal_disable_setjmp();
    efree(tofree);
    if (details.runas_pw)
	sudo_pw_delref(details.runas_pw);
    sudo_endpwent();
    if (details.runas_gr)
	sudo_gr_delref(details.runas_gr);
    sudo_endgrent();

    debug_return_bool(rval);
}

static void
sudoers_io_close(int exit_status, int error)
{
    int i;
    debug_decl(sudoers_io_close, SUDO_DEBUG_PLUGIN)

    if (fatal_setjmp() != 0) {
	/* called via fatal(), fatalx() or log_fatal() */
	fatal_disable_setjmp();
	debug_return;
    }

    for (i = 0; i < IOFD_MAX; i++) {
	if (io_log_files[i].fd.v == NULL)
	    continue;
#ifdef HAVE_ZLIB_H
	if (iolog_compress)
	    gzclose(io_log_files[i].fd.g);
	else
#endif
	    fclose(io_log_files[i].fd.f);
    }
    debug_return;
}

static int
sudoers_io_version(int verbose)
{
    debug_decl(sudoers_io_version, SUDO_DEBUG_PLUGIN)

    if (fatal_setjmp() != 0) {
	/* called via fatal(), fatalx() or log_fatal() */
	fatal_disable_setjmp();
	debug_return_bool(-1);
    }

    sudo_printf(SUDO_CONV_INFO_MSG, "Sudoers I/O plugin version %s\n",
	PACKAGE_VERSION);

    debug_return_bool(true);
}

/*
 * Generic I/O logging function.  Called by the I/O logging entry points.
 */
static int
sudoers_io_log(const char *buf, unsigned int len, int idx)
{
    struct timeval now, delay;
    debug_decl(sudoers_io_version, SUDO_DEBUG_PLUGIN)

    gettimeofday(&now, NULL);

    if (fatal_setjmp() != 0) {
	/* called via fatal(), fatalx() or log_fatal() */
	fatal_disable_setjmp();
	debug_return_bool(-1);
    }

#ifdef HAVE_ZLIB_H
    if (iolog_compress)
	ignore_result(gzwrite(io_log_files[idx].fd.g, (const voidp)buf, len));
    else
#endif
	ignore_result(fwrite(buf, 1, len, io_log_files[idx].fd.f));
    sudo_timevalsub(&now, &last_time, &delay);
#ifdef HAVE_ZLIB_H
    if (iolog_compress)
	gzprintf(io_log_files[IOFD_TIMING].fd.g, "%d %f %u\n", idx,
	    delay.tv_sec + ((double)delay.tv_usec / 1000000), len);
    else
#endif
	fprintf(io_log_files[IOFD_TIMING].fd.f, "%d %f %u\n", idx,
	    delay.tv_sec + ((double)delay.tv_usec / 1000000), len);
    last_time.tv_sec = now.tv_sec;
    last_time.tv_usec = now.tv_usec;

    debug_return_bool(true);
}

static int
sudoers_io_log_ttyin(const char *buf, unsigned int len)
{
    return sudoers_io_log(buf, len, IOFD_TTYIN);
}

static int
sudoers_io_log_ttyout(const char *buf, unsigned int len)
{
    return sudoers_io_log(buf, len, IOFD_TTYOUT);
}

static int
sudoers_io_log_stdin(const char *buf, unsigned int len)
{
    return sudoers_io_log(buf, len, IOFD_STDIN);
}

static int
sudoers_io_log_stdout(const char *buf, unsigned int len)
{
    return sudoers_io_log(buf, len, IOFD_STDOUT);
}

static int
sudoers_io_log_stderr(const char *buf, unsigned int len)
{
    return sudoers_io_log(buf, len, IOFD_STDERR);
}

__dso_public struct io_plugin sudoers_io = {
    SUDO_IO_PLUGIN,
    SUDO_API_VERSION,
    sudoers_io_open,
    sudoers_io_close,
    sudoers_io_version,
    sudoers_io_log_ttyin,
    sudoers_io_log_ttyout,
    sudoers_io_log_stdin,
    sudoers_io_log_stdout,
    sudoers_io_log_stderr
};

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