Files
sudo/common/sudo_debug.c
Todd C. Miller c8ce3a0a85 Log the process id in the debug file output. Since we don't want
to keep calling getpid(), stash the value at init time and when we
fork().
2012-04-06 15:20:16 -04:00

553 lines
14 KiB
C

/*
* Copyright (c) 2011 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/param.h>
#include <sys/stat.h>
#include <sys/uio.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_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 */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include "missing.h"
#include "alloc.h"
#include "error.h"
#include "gettext.h"
#include "sudo_plugin.h"
#include "sudo_debug.h"
/*
* The debug priorities and subsystems are currently hard-coded.
* In the future we might consider allowing plugins to register their
* own subsystems and provide direct access to the debugging API.
*/
/* Note: this must match the order in sudo_debug.h */
const char *const sudo_debug_priorities[] = {
"crit",
"err",
"warn",
"notice",
"diag",
"info",
"trace",
"debug",
NULL
};
/* Note: this must match the order in sudo_debug.h */
const char *const sudo_debug_subsystems[] = {
"main",
"args",
"exec",
"pty",
"utmp",
"conv",
"pcomm",
"util",
"netif",
"audit",
"edit",
"selinux",
"ldap",
"match",
"parser",
"alias",
"defaults",
"auth",
"env",
"logging",
"nss",
"rbtree",
"perms",
"plugin",
"hooks",
NULL
};
#define NUM_SUBSYSTEMS (sizeof(sudo_debug_subsystems) / sizeof(sudo_debug_subsystems[0]) - 1)
/* Values for sudo_debug_mode */
#define SUDO_DEBUG_MODE_DISABLED 0
#define SUDO_DEBUG_MODE_FILE 1
#define SUDO_DEBUG_MODE_CONV 2
static int sudo_debug_settings[NUM_SUBSYSTEMS];
static int sudo_debug_fd = -1;
static int sudo_debug_mode;
static char sudo_debug_pidstr[(((sizeof(int) * 8) + 2) / 3) + 3];
static size_t sudo_debug_pidlen;
extern sudo_conv_t sudo_conv;
/*
* Parse settings string from sudo.conf and open debugfile.
* Returns 1 on success, 0 if cannot open debugfile.
* Unsupported subsystems and priorities are silently ignored.
*/
int sudo_debug_init(const char *debugfile, const char *settings)
{
char *buf, *cp, *subsys, *pri;
int i, j;
/* Init per-subsystems settings to -1 since 0 is a valid priority. */
for (i = 0; i < NUM_SUBSYSTEMS; i++)
sudo_debug_settings[i] = -1;
/* Open debug file if specified. */
if (debugfile != NULL) {
if (sudo_debug_fd != -1)
close(sudo_debug_fd);
sudo_debug_fd = open(debugfile, O_WRONLY|O_APPEND|O_CREAT,
S_IRUSR|S_IWUSR);
if (sudo_debug_fd == -1)
return 0;
(void)fcntl(sudo_debug_fd, F_SETFD, FD_CLOEXEC);
sudo_debug_mode = SUDO_DEBUG_MODE_FILE;
} else {
/* Called from the plugin, no debug file. */
sudo_debug_mode = SUDO_DEBUG_MODE_CONV;
}
/* Parse settings string. */
buf = estrdup(settings);
for ((cp = strtok(buf, ",")); cp != NULL; (cp = strtok(NULL, ","))) {
/* Should be in the form subsys@pri. */
subsys = cp;
if ((pri = strchr(cp, '@')) == NULL)
continue;
*pri++ = '\0';
/* Look up priority and subsystem, fill in sudo_debug_settings[]. */
for (i = 0; sudo_debug_priorities[i] != NULL; i++) {
if (strcasecmp(pri, sudo_debug_priorities[i]) == 0) {
for (j = 0; sudo_debug_subsystems[j] != NULL; j++) {
if (strcasecmp(subsys, "all") == 0) {
sudo_debug_settings[j] = i;
continue;
}
if (strcasecmp(subsys, sudo_debug_subsystems[j]) == 0) {
sudo_debug_settings[j] = i;
break;
}
}
break;
}
}
}
efree(buf);
(void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
(int)getpid());
sudo_debug_pidlen = strlen(sudo_debug_pidstr);
return 1;
}
pid_t
sudo_debug_fork(void)
{
pid_t pid;
if ((pid = fork()) == 0) {
(void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
(int)getpid());
sudo_debug_pidlen = strlen(sudo_debug_pidstr);
}
return pid;
}
void
sudo_debug_enter(const char *func, const char *file, int line,
int subsys)
{
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"-> %s @ %s:%d", func, file, line);
}
void sudo_debug_exit(const char *func, const char *file, int line,
int subsys)
{
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d", func, file, line);
}
void sudo_debug_exit_int(const char *func, const char *file, int line,
int subsys, int rval)
{
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d := %d", func, file, line, rval);
}
void sudo_debug_exit_long(const char *func, const char *file, int line,
int subsys, long rval)
{
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d := %ld", func, file, line, rval);
}
void sudo_debug_exit_size_t(const char *func, const char *file, int line,
int subsys, size_t rval)
{
/* XXX - should use %zu but our snprintf.c doesn't support it */
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d := %lu", func, file, line, (unsigned long)rval);
}
/* We use int, not bool, here for functions that return -1 on error. */
void sudo_debug_exit_bool(const char *func, const char *file, int line,
int subsys, int rval)
{
if (rval == true || rval == false) {
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d := %s", func, file, line, rval ? "true" : "false");
} else {
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d := %d", func, file, line, rval);
}
}
void sudo_debug_exit_str(const char *func, const char *file, int line,
int subsys, const char *rval)
{
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d := %s", func, file, line, rval ? rval : "(null)");
}
void sudo_debug_exit_str_masked(const char *func, const char *file, int line,
int subsys, const char *rval)
{
static const char stars[] = "********************************************************************************";
int len = rval ? strlen(rval) : sizeof("(null)") - 1;
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d := %.*s", func, file, line, len, rval ? stars : "(null)");
}
void sudo_debug_exit_ptr(const char *func, const char *file, int line,
int subsys, const void *rval)
{
sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
"<- %s @ %s:%d := %p", func, file, line, rval);
}
static void
sudo_debug_write_conv(const char *func, const char *file, int lineno,
const char *str, int len, int errno_val)
{
struct sudo_conv_message msg;
struct sudo_conv_reply repl;
char *buf = NULL;
/* Call conversation function */
if (sudo_conv != NULL) {
/* Remove the newline at the end if appending extra info. */
if (str[len - 1] == '\n')
len--;
if (func != NULL && file != NULL && lineno != 0) {
if (errno_val) {
easprintf(&buf, "%.*s: %s @ %s() %s:%d", len, str,
strerror(errno_val), func, file, lineno);
} else {
easprintf(&buf, "%.*s @ %s() %s:%d", len, str,
func, file, lineno);
}
str = buf;
} else if (errno_val) {
easprintf(&buf, "%.*s: %s", len, str, strerror(errno_val));
str = buf;
}
memset(&msg, 0, sizeof(msg));
memset(&repl, 0, sizeof(repl));
msg.msg_type = SUDO_CONV_DEBUG_MSG;
msg.msg = str;
sudo_conv(1, &msg, &repl);
if (buf != NULL)
efree(buf);
}
}
static void
sudo_debug_write_file(const char *func, const char *file, int lineno,
const char *str, int len, int errno_val)
{
char *timestr, numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
time_t now;
struct iovec iov[12];
int iovcnt = 4;
bool need_newline = false;
/* Prepend program name and pid with a trailing space. */
iov[1].iov_base = (char *)getprogname();
iov[1].iov_len = strlen(iov[1].iov_base);
iov[2].iov_base = sudo_debug_pidstr;
iov[2].iov_len = sudo_debug_pidlen;
/* Add string along with newline if it doesn't have one. */
iov[3].iov_base = (char *)str;
iov[3].iov_len = len;
if (str[len - 1] != '\n')
need_newline = true;
/* Append error string if errno is specified. */
if (errno_val) {
iov[iovcnt].iov_base = ": ";
iov[iovcnt].iov_len = 2;
iovcnt++;
iov[iovcnt].iov_base = strerror(errno_val);
iov[iovcnt].iov_len = strlen(iov[iovcnt].iov_base);
iovcnt++;
/* Move newline to the end. */
if (!need_newline) {
need_newline = true;
iov[3].iov_len--;
}
}
/* If function, file and lineno are specified, append them. */
if (func != NULL && file != NULL && lineno != 0) {
iov[iovcnt].iov_base = " @ ";
iov[iovcnt].iov_len = 3;
iovcnt++;
iov[iovcnt].iov_base = (char *)func;
iov[iovcnt].iov_len = strlen(func);
iovcnt++;
iov[iovcnt].iov_base = "() ";
iov[iovcnt].iov_len = 3;
iovcnt++;
iov[iovcnt].iov_base = (char *)file;
iov[iovcnt].iov_len = strlen(file);
iovcnt++;
(void)snprintf(numbuf, sizeof(numbuf), ":%d", lineno);
iov[iovcnt].iov_base = numbuf;
iov[iovcnt].iov_len = strlen(numbuf);
iovcnt++;
/* Move newline to the end. */
if (!need_newline) {
need_newline = true;
iov[3].iov_len--;
}
}
/* Append newline as needed. */
if (need_newline) {
/* force newline */
iov[iovcnt].iov_base = "\n";
iov[iovcnt].iov_len = 1;
iovcnt++;
}
/* Do timestamp last due to ctime's static buffer. */
now = time(NULL);
timestr = ctime(&now) + 4;
timestr[15] = ' '; /* replace year with a space */
timestr[16] = '\0';
iov[0].iov_base = timestr;
iov[0].iov_len = 16;
/* Write message in a single syscall */
ignore_result(writev(sudo_debug_fd, iov, iovcnt));
}
void
sudo_debug_write2(const char *func, const char *file, int lineno,
const char *str, int len, int errno_val)
{
if (len <= 0)
return;
switch (sudo_debug_mode) {
case SUDO_DEBUG_MODE_CONV:
sudo_debug_write_conv(func, file, lineno, str, len, errno_val);
break;
case SUDO_DEBUG_MODE_FILE:
sudo_debug_write_file(func, file, lineno, str, len, errno_val);
break;
}
}
/* XXX - turn into a macro */
void
sudo_debug_write(const char *str, int len, int errno_val)
{
sudo_debug_write2(NULL, NULL, 0, str, len, errno_val);
}
void
sudo_debug_printf2(const char *func, const char *file, int lineno, int level,
const char *fmt, ...)
{
int buflen, pri, subsys, saved_errno = errno;
va_list ap;
char *buf;
if (!sudo_debug_mode)
return;
/* Extract pri and subsystem from level. */
pri = SUDO_DEBUG_PRI(level);
subsys = SUDO_DEBUG_SUBSYS(level);
/* Make sure we want debug info at this level. */
if (subsys < NUM_SUBSYSTEMS && sudo_debug_settings[subsys] >= pri) {
va_start(ap, fmt);
buflen = vasprintf(&buf, fmt, ap);
va_end(ap);
if (buflen != -1) {
int errcode = ISSET(level, SUDO_DEBUG_ERRNO) ? saved_errno : 0;
if (ISSET(level, SUDO_DEBUG_LINENO))
sudo_debug_write2(func, file, lineno, buf, buflen, errcode);
else
sudo_debug_write2(NULL, NULL, 0, buf, buflen, errcode);
free(buf);
}
}
errno = saved_errno;
}
void
sudo_debug_execve2(int level, const char *path, char *const argv[], char *const envp[])
{
char * const *av;
char *buf, *cp;
int buflen, pri, subsys, log_envp = 0;
size_t plen;
if (!sudo_debug_mode)
return;
/* Extract pri and subsystem from level. */
pri = SUDO_DEBUG_PRI(level);
subsys = SUDO_DEBUG_SUBSYS(level);
/* Make sure we want debug info at this level. */
if (subsys >= NUM_SUBSYSTEMS || sudo_debug_settings[subsys] < pri)
return;
/* Log envp for debug level "debug". */
if (sudo_debug_settings[subsys] >= SUDO_DEBUG_DEBUG - 1 && envp[0] != NULL)
log_envp = 1;
#define EXEC_PREFIX "exec "
/* Alloc and build up buffer. */
plen = strlen(path);
buflen = sizeof(EXEC_PREFIX) -1 + plen;
if (argv[0] != NULL) {
buflen += sizeof(" []") - 1;
for (av = argv; *av; av++)
buflen += strlen(*av) + 1;
buflen--;
}
if (log_envp) {
buflen += sizeof(" []") - 1;
for (av = envp; *av; av++)
buflen += strlen(*av) + 1;
buflen--;
}
buf = malloc(buflen + 1);
if (buf == NULL)
return;
/* Copy prefix and command. */
memcpy(buf, EXEC_PREFIX, sizeof(EXEC_PREFIX) - 1);
cp = buf + sizeof(EXEC_PREFIX) - 1;
memcpy(cp, path, plen);
cp += plen;
/* Copy argv. */
if (argv[0] != NULL) {
*cp++ = ' ';
*cp++ = '[';
for (av = argv; *av; av++) {
size_t avlen = strlen(*av);
memcpy(cp, *av, avlen);
cp += avlen;
*cp++ = ' ';
}
cp[-1] = ']';
}
if (log_envp) {
*cp++ = ' ';
*cp++ = '[';
for (av = envp; *av; av++) {
size_t avlen = strlen(*av);
memcpy(cp, *av, avlen);
cp += avlen;
*cp++ = ' ';
}
cp[-1] = ']';
}
*cp = '\0';
sudo_debug_write(buf, buflen, 0);
free(buf);
}
/*
* Dup sudo_debug_fd to the specified value so we don't
* close it when calling closefrom().
*/
int
sudo_debug_fd_set(int fd)
{
if (sudo_debug_fd != -1 && fd != sudo_debug_fd) {
if (dup2(sudo_debug_fd, fd) == -1)
return -1;
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
close(sudo_debug_fd);
sudo_debug_fd = fd;
}
return sudo_debug_fd;
}