2
0
mirror of https://github.com/sudo-project/sudo.git synced 2025-08-22 01:49:11 +00:00
sudo/lib/iolog/iolog_fileio.c

898 lines
21 KiB
C

/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2009-2019 Todd C. Miller <Todd.Miller@sudo.ws>
*
* 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.
*/
/*
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
*/
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#include "sudo_gettext.h" /* must be included before sudo_compat.h */
#include "sudo_compat.h"
#include "sudo_conf.h"
#include "sudo_debug.h"
#include "sudo_event.h"
#include "sudo_queue.h"
#include "sudo_util.h"
#include "sudo_fatal.h"
#include "sudo_iolog.h"
#include "pathnames.h"
static unsigned char const gzip_magic[2] = {0x1f, 0x8b};
static unsigned int sessid_max = SESSID_MAX;
static mode_t iolog_filemode = S_IRUSR|S_IWUSR;
static mode_t iolog_dirmode = S_IRWXU;
static uid_t iolog_uid = ROOT_UID;
static gid_t iolog_gid = ROOT_GID;
static bool iolog_gid_set;
static bool iolog_compress;
static bool iolog_flush;
/*
* Set effective user and group-IDs to iolog_uid and iolog_gid.
* If restore flag is set, swap them back.
*/
static bool
io_swapids(bool restore)
{
#ifdef HAVE_SETEUID
static uid_t user_euid = (uid_t)-1;
static gid_t user_egid = (gid_t)-1;
debug_decl(io_swapids, SUDO_DEBUG_UTIL)
if (user_euid == (uid_t)-1)
user_euid = geteuid();
if (user_egid == (gid_t)-1)
user_euid = getegid();
if (restore) {
if (seteuid(user_euid) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to restore effective uid to %d", __func__,
(int)user_euid);
sudo_warn("seteuid() %d -> %d", (int)iolog_uid, (int)user_euid);
debug_return_bool(false);
}
if (setegid(user_egid) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to restore effective gid to %d", __func__,
(int)user_egid);
sudo_warn("setegid() %d -> %d", (int)iolog_gid, (int)user_egid);
debug_return_bool(false);
}
} else {
/* Fail silently if the user has insufficient privileges. */
if (setegid(iolog_gid) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to set effective gid to %d", __func__,
(int)iolog_gid);
debug_return_bool(false);
}
if (seteuid(iolog_uid) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to set effective uid to %d", __func__,
(int)iolog_uid);
debug_return_bool(false);
}
}
debug_return_bool(true);
#else
return false;
#endif
}
/*
* Create directory and any parent directories as needed.
*/
static bool
io_mkdirs(char *path)
{
mode_t omask;
struct stat sb;
bool ok, uid_changed = false;
debug_decl(io_mkdirs, SUDO_DEBUG_UTIL)
/* umask must not be more restrictive than the file modes. */
omask = umask(ACCESSPERMS & ~(iolog_filemode|iolog_dirmode));
ok = stat(path, &sb) == 0;
if (!ok && errno == EACCES) {
/* Try again as the I/O log owner (for NFS). */
if (io_swapids(false)) {
ok = stat(path, &sb) == 0;
if (!io_swapids(true))
ok = false;
}
}
if (ok) {
if (S_ISDIR(sb.st_mode)) {
if (sb.st_uid != iolog_uid || sb.st_gid != iolog_gid) {
if (chown(path, iolog_uid, iolog_gid) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to chown %d:%d %s", __func__,
(int)iolog_uid, (int)iolog_gid, path);
}
}
if ((sb.st_mode & ALLPERMS) != iolog_dirmode) {
if (chmod(path, iolog_dirmode) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to chmod 0%o %s", __func__,
(int)iolog_dirmode, path);
}
}
} else {
sudo_warnx(U_("%s exists but is not a directory (0%o)"),
path, (unsigned int) sb.st_mode);
ok = false;
}
goto done;
}
ok = sudo_mkdir_parents(path, iolog_uid, iolog_gid, iolog_dirmode, true);
if (!ok && errno == EACCES) {
/* Try again as the I/O log owner (for NFS). */
uid_changed = io_swapids(false);
if (uid_changed)
ok = sudo_mkdir_parents(path, -1, -1, iolog_dirmode, false);
}
if (ok) {
/* Create final path component. */
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"mkdir %s, mode 0%o", path, (unsigned int) iolog_dirmode);
ok = mkdir(path, iolog_dirmode) == 0 || errno == EEXIST;
if (!ok) {
if (errno == EACCES && !uid_changed) {
/* Try again as the I/O log owner (for NFS). */
uid_changed = io_swapids(false);
if (uid_changed)
ok = mkdir(path, iolog_dirmode) == 0 || errno == EEXIST;
}
if (!ok)
sudo_warn(U_("unable to mkdir %s"), path);
} else {
if (chown(path, iolog_uid, iolog_gid) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to chown %d:%d %s", __func__,
(int)iolog_uid, (int)iolog_gid, path);
}
}
}
if (uid_changed) {
if (!io_swapids(true))
ok = false;
}
done:
umask(omask);
debug_return_bool(ok);
}
/*
* Create temporary directory and any parent directories as needed.
*/
static bool
io_mkdtemp(char *path)
{
bool ok, uid_changed = false;
debug_decl(io_mkdtemp, SUDO_DEBUG_UTIL)
ok = sudo_mkdir_parents(path, iolog_uid, iolog_gid, iolog_dirmode, true);
if (!ok && errno == EACCES) {
/* Try again as the I/O log owner (for NFS). */
uid_changed = io_swapids(false);
if (uid_changed)
ok = sudo_mkdir_parents(path, -1, -1, iolog_dirmode, false);
}
if (ok) {
/* Create final path component. */
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"mkdtemp %s", path);
/* We cannot retry mkdtemp() so always open as iolog user */
if (!uid_changed)
uid_changed = io_swapids(false);
if (mkdtemp(path) == NULL) {
sudo_warn(U_("unable to mkdir %s"), path);
ok = false;
} else {
if (chmod(path, iolog_dirmode) != 0) {
sudo_warn(U_("unable to change mode of %s to 0%o"),
path, (unsigned int)iolog_dirmode);
}
}
}
if (uid_changed) {
if (!io_swapids(true))
ok = false;
}
debug_return_bool(ok);
}
/*
* Set max sequence number (aka session ID)
*/
bool
iolog_set_maxseq(const char *maxval)
{
const char *errstr;
unsigned int value;
debug_decl(iolog_set_maxseq, SUDO_DEBUG_UTIL)
value = sudo_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);
}
/*
* Set iolog_uid (and iolog_gid if iolog_group not specified).
*/
bool
iolog_set_user(const struct passwd *pw)
{
debug_decl(iolog_set_user, SUDO_DEBUG_UTIL)
if (pw != NULL) {
iolog_uid = pw->pw_uid;
if (!iolog_gid_set)
iolog_gid = pw->pw_gid;
} else {
/* Reset to default. */
iolog_uid = ROOT_UID;
if (!iolog_gid_set)
iolog_gid = ROOT_GID;
}
debug_return_bool(true);
}
/*
* Set iolog_gid.
*/
bool
iolog_set_group(const struct group *gr)
{
debug_decl(iolog_set_group, SUDO_DEBUG_UTIL)
if (gr != NULL) {
iolog_gid = gr->gr_gid;
iolog_gid_set = true;
} else {
/* Reset to default. */
iolog_gid = ROOT_GID;
iolog_gid_set = false;
}
debug_return_bool(true);
}
/*
* Set iolog_filemode and iolog_dirmode.
*/
bool
iolog_set_mode(mode_t mode)
{
debug_decl(iolog_set_mode, SUDO_DEBUG_UTIL)
/* I/O log files must be readable and writable by owner. */
iolog_filemode = S_IRUSR|S_IWUSR;
/* Add in group and other read/write if specified. */
iolog_filemode |= mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
/* For directory mode, add execute bits as needed. */
iolog_dirmode = iolog_filemode | S_IXUSR;
if (iolog_dirmode & (S_IRGRP|S_IWGRP))
iolog_dirmode |= S_IXGRP;
if (iolog_dirmode & (S_IROTH|S_IWOTH))
iolog_dirmode |= S_IXOTH;
debug_return_bool(true);
}
/*
* Set iolog_compress
*/
bool
iolog_set_compress(const char *str)
{
int result;
debug_decl(iolog_set_compress, SUDO_DEBUG_UTIL)
if ((result = sudo_strtobool(str)) == -1)
debug_return_bool(false);
iolog_compress = result;
debug_return_bool(true);
}
/*
* Set iolog_flush
*/
bool
iolog_set_flush(const char *str)
{
int result;
debug_decl(iolog_set_flush, SUDO_DEBUG_UTIL)
if ((result = sudo_strtobool(str)) == -1)
debug_return_bool(false);
iolog_flush = result;
debug_return_bool(true);
}
/*
* Wrapper for open(2) that sets umask and retries as iolog_uid/iolog_gid
* if open(2) returns EACCES.
*/
static int
io_open(const char *path, int flags)
{
int fd;
mode_t omask = S_IRWXG|S_IRWXO;
debug_decl(io_open, SUDO_DEBUG_UTIL)
if (ISSET(flags, O_CREAT)) {
/* umask must not be more restrictive than the file modes. */
omask = umask(ACCESSPERMS & ~(iolog_filemode|iolog_dirmode));
}
fd = open(path, flags, iolog_filemode);
if (fd == -1 && errno == EACCES) {
/* Try again as the I/O log owner (for NFS). */
if (io_swapids(false)) {
fd = open(path, flags, iolog_filemode);
if (!io_swapids(true)) {
/* io_swapids() warns on error. */
if (fd != -1) {
close(fd);
fd = -1;
}
}
}
}
if (ISSET(flags, O_CREAT))
umask(omask);
debug_return_int(fd);
}
/*
* 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.
*/
bool
iolog_nextid(char *iolog_dir, char sessid[7])
{
char buf[32], *ep;
int i, len, fd = -1;
unsigned long id = 0;
ssize_t nread;
bool ret = false;
char pathbuf[PATH_MAX];
static const char b36char[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
debug_decl(iolog_nextid, SUDO_DEBUG_UTIL)
/*
* Create I/O log directory if it doesn't already exist.
*/
if (!io_mkdirs(iolog_dir))
goto done;
/*
* Open sequence file
*/
len = snprintf(pathbuf, sizeof(pathbuf), "%s/seq", iolog_dir);
if (len < 0 || len >= ssizeof(pathbuf)) {
errno = ENAMETOOLONG;
goto done;
}
fd = io_open(pathbuf, O_RDWR|O_CREAT);
if (fd == -1) {
goto done;
}
sudo_lock_file(fd, SUDO_LOCK);
if (fchown(fd, iolog_uid, iolog_gid) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to fchown %d:%d %s", __func__,
(int)iolog_uid, (int)iolog_gid, pathbuf);
}
/* Read current seq number (base 36). */
if (id == 0) {
nread = read(fd, buf, sizeof(buf) - 1);
if (nread != 0) {
if (nread == -1) {
goto done;
}
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. */
#ifdef HAVE_PWRITE
if (pwrite(fd, buf, 7, 0) != 7) {
#else
if (lseek(fd, 0, SEEK_SET) == -1 || write(fd, buf, 7) != 7) {
#endif
goto done;
}
ret = true;
done:
if (fd != -1)
close(fd);
debug_return_bool(ret);
}
/*
* Copy iolog_path to pathbuf and create the directory and any intermediate
* directories. If iolog_path ends in 'XXXXXX', use mkdtemp().
* Returns SIZE_MAX on error.
*/
size_t
mkdir_iopath(const char *iolog_path, char *pathbuf, size_t pathsize)
{
size_t len;
bool ok;
debug_decl(mkdir_iopath, SUDO_DEBUG_UTIL)
len = strlcpy(pathbuf, iolog_path, pathsize);
if (len >= pathsize) {
errno = ENAMETOOLONG;
debug_return_size_t((size_t)-1);
}
/*
* Create path and intermediate subdirs as needed.
* If path ends in at least 6 Xs (ala POSIX mktemp), use mkdtemp().
* Sets iolog_gid (if it is not already set) as a side effect.
*/
if (len >= 6 && strcmp(&pathbuf[len - 6], "XXXXXX") == 0)
ok = io_mkdtemp(pathbuf);
else
ok = io_mkdirs(pathbuf);
debug_return_size_t(ok ? len : (size_t)-1);
}
/*
* Append suffix to pathbuf after len chars and open the resulting file.
* Note that the size of pathbuf is assumed to be PATH_MAX.
* Stores the open file handle which has the close-on-exec flag set.
* XXX - move enabled logic into caller?
*/
bool
iolog_open(struct iolog_file *iol, char *path, const char *mode)
{
int flags;
unsigned char magic[2];
debug_decl(iolog_open, SUDO_DEBUG_UTIL)
if (mode[0] == 'r') {
flags = mode[1] == '+' ? O_RDWR : O_RDONLY;
} else if (mode[0] == 'w') {
flags = O_CREAT|O_TRUNC;
flags |= mode[1] == '+' ? O_RDWR : O_WRONLY;
} else {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: invalid I/O mode %s", __func__, mode);
debug_return_bool(false);
}
iol->compressed = false;
if (iol->enabled) {
int fd = io_open(path, flags);
if (fd != -1) {
if (*mode == 'w') {
if (fchown(fd, iolog_uid, iolog_gid) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to fchown %d:%d %s", __func__,
(int)iolog_uid, (int)iolog_gid, path);
}
iol->compressed = iolog_compress;
} else {
/* check for gzip magic number */
if (read(fd, magic, sizeof(magic)) == ssizeof(magic)) {
if (magic[0] == gzip_magic[0] && magic[1] == gzip_magic[1])
iol->compressed = true;
}
(void)lseek(fd, 0, SEEK_SET);
}
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
#ifdef HAVE_ZLIB_H
if (iol->compressed)
iol->fd.g = gzdopen(fd, mode);
else
#endif
iol->fd.f = fdopen(fd, mode);
if (iol->fd.v == NULL) {
int save_errno = errno;
close(fd);
errno = save_errno;
fd = -1;
}
}
if (fd == -1) {
iol->enabled = false;
debug_return_bool(false);
}
} else {
if (*mode == 'w') {
/* Remove old log file in case we recycled sequence numbers. */
(void)unlink(path);
}
}
debug_return_bool(true);
}
#ifdef HAVE_ZLIB_H
static const char *
gzstrerror(gzFile file)
{
int errnum;
return gzerror(file, &errnum);
}
#endif /* HAVE_ZLIB_H */
/*
* Close an I/O log.
*/
bool
iolog_close(struct iolog_file *iol, const char **errstr)
{
bool ret = true;
debug_decl(iolog_close, SUDO_DEBUG_UTIL)
#ifdef HAVE_ZLIB_H
if (iol->compressed) {
if (gzclose(iol->fd.g) != Z_OK) {
ret = false;
if (errstr != NULL)
*errstr = gzstrerror(iol->fd.g);
}
} else
#endif
if (fclose(iol->fd.f) != 0) {
ret = false;
if (errstr != NULL)
*errstr = strerror(errno);
}
debug_return_bool(ret);
}
/*
* I/O log wrapper for fseek/gzseek.
*/
off_t
iolog_seek(struct iolog_file *iol, off_t offset, int whence)
{
off_t ret;
//debug_decl(iolog_seek, SUDO_DEBUG_UTIL)
#ifdef HAVE_ZLIB_H
if (iol->compressed)
ret = gzseek(iol->fd.g, offset, whence);
else
#endif
#ifdef HAVE_FSEEKO
ret = fseeko(iol->fd.f, offset, whence);
#else
ret = fseek(iol->fd.f, offset, whence);
#endif
//debug_return_off_t(ret);
return ret;
}
/*
* Read from a (possibly compressed) I/O log file.
*/
ssize_t
iolog_read(struct iolog_file *iol, void *buf, size_t nbytes,
const char **errstr)
{
ssize_t nread;
debug_decl(iolog_read, SUDO_DEBUG_UTIL)
if (nbytes > UINT_MAX) {
errno = EINVAL;
if (errstr != NULL)
*errstr = strerror(errno);
debug_return_ssize_t(-1);
}
#ifdef HAVE_ZLIB_H
if (iol->compressed) {
if ((nread = gzread(iol->fd.g, buf, nbytes)) == -1) {
if (errstr != NULL)
*errstr = gzstrerror(iol->fd.g);
}
} else
#endif
{
nread = (ssize_t)fread(buf, 1, nbytes, iol->fd.f);
if (nread == 0 && ferror(iol->fd.f)) {
nread = -1;
if (errstr != NULL)
*errstr = strerror(errno);
}
}
debug_return_ssize_t(nread);
}
/*
* Write to an I/O log, optionally compressing.
*/
ssize_t
iolog_write(struct iolog_file *iol, const void *buf, size_t len,
const char **errstr)
{
ssize_t ret;
debug_decl(iolog_write, SUDO_DEBUG_UTIL)
if (len > UINT_MAX) {
errno = EINVAL;
if (errstr != NULL)
*errstr = strerror(errno);
debug_return_ssize_t(-1);
}
#ifdef HAVE_ZLIB_H
if (iol->compressed) {
ret = gzwrite(iol->fd.g, (const voidp)buf, len);
if (ret == 0) {
ret = -1;
if (errstr != NULL)
*errstr = gzstrerror(iol->fd.g);
goto done;
}
if (iolog_flush) {
if (gzflush(iol->fd.g, Z_SYNC_FLUSH) != Z_OK) {
ret = -1;
if (errstr != NULL)
*errstr = gzstrerror(iol->fd.g);
goto done;
}
}
} else
#endif
{
ret = fwrite(buf, 1, len, iol->fd.f);
if (ret == 0) {
ret = -1;
if (errstr != NULL)
*errstr = strerror(errno);
goto done;
}
if (iolog_flush) {
if (fflush(iol->fd.f) != 0) {
ret = -1;
if (errstr != NULL)
*errstr = strerror(errno);
goto done;
}
}
}
done:
debug_return_ssize_t(ret);
}
/*
* Returns true if at end of I/O log file, else false.
*/
bool
iolog_eof(struct iolog_file *iol)
{
bool ret;
debug_decl(iolog_eof, SUDO_DEBUG_UTIL)
#ifdef HAVE_ZLIB_H
if (iol->compressed)
ret = gzeof(iol->fd.g) == 1;
else
#endif
ret = feof(iol->fd.f) == 1;
debug_return_int(ret);
}
/*
* Like gets() but for struct iolog_file.
*/
char *
iolog_gets(struct iolog_file *iol, char *buf, size_t nbytes,
const char **errstr)
{
char *str;
debug_decl(iolog_gets, SUDO_DEBUG_UTIL)
if (nbytes > UINT_MAX) {
errno = EINVAL;
if (errstr != NULL)
*errstr = strerror(errno);
debug_return_str(NULL);
}
#ifdef HAVE_ZLIB_H
if (iol->compressed) {
if ((str = gzgets(iol->fd.g, buf, nbytes)) == NULL) {
if (errstr != NULL)
*errstr = gzstrerror(iol->fd.g);
}
} else
#endif
{
if ((str = fgets(buf, nbytes, iol->fd.f)) == NULL) {
if (errstr != NULL)
*errstr = strerror(errno);
}
}
debug_return_str(str);
}
/*
* Write the I/O log file that contains the user and command info.
* This file is not compressed.
*/
bool
iolog_write_info_file(const char *parent, struct iolog_info *log_info,
char * const argv[])
{
char path[PATH_MAX];
char * const *av;
FILE *fp;
int error, len, fd;
debug_decl(iolog_info_write_log, SUDO_DEBUG_UTIL)
len = snprintf(path, sizeof(path), "%s/log", parent);
if (len < 0 || len >= ssizeof(path)) {
errno = ENAMETOOLONG;
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"%s/log", parent);
debug_return_bool(false);
}
fd = io_open(path, O_CREAT|O_TRUNC|O_WRONLY);
if (fd == -1 || (fp = fdopen(fd, "w")) == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to open %s", path);
if (fd != -1)
close(fd);
debug_return_bool(false);
}
if (fchown(fd, iolog_uid, iolog_gid) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to fchown %d:%d %s", __func__,
(int)iolog_uid, (int)iolog_gid, path);
}
fprintf(fp, "%lld:%s:%s:%s:%s:%d:%d\n%s\n",
(long long)log_info->tstamp,
log_info->user ? log_info->user : "unknown",
log_info->runas_user ? log_info->runas_user : RUNAS_DEFAULT,
log_info->runas_group ? log_info->runas_group : "",
log_info->tty ? log_info->tty : "unknown",
log_info->lines, log_info->cols,
log_info->cwd ? log_info->cwd : "unknown");
fputs(log_info->cmd ? log_info->cmd : "unknown", fp);
for (av = argv + 1; *av != NULL; av++) {
fputc(' ', fp);
fputs(*av, fp);
}
fputc('\n', fp);
fflush(fp);
if ((error = ferror(fp))) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to write to I/O log file %s", path);
}
fclose(fp);
debug_return_bool(!error);
}
/*
* Map IOFD_* -> name.
*/
const char *
iolog_fd_to_name(int iofd)
{
const char *ret = NULL;
debug_decl(iolog_fd_to_name, SUDO_DEBUG_UTIL)
switch (iofd) {
case IOFD_STDIN:
ret = "stdin";
break;
case IOFD_STDOUT:
ret = "stdout";
break;
case IOFD_STDERR:
ret = "stderr";
break;
case IOFD_TTYIN:
ret = "ttyin";
break;
case IOFD_TTYOUT:
ret = "ttyout";
break;
case IOFD_TIMING:
ret = "timing";
break;
default:
sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: unexpected iofd %d",
__func__, iofd);
break;
}
debug_return_const_str(ret);
}