Merge tag 'debian/1.8.5p2-1' into squeeze
[debian/sudo] / common / sudo_debug.c
diff --git a/common/sudo_debug.c b/common/sudo_debug.c
new file mode 100644 (file)
index 0000000..f41fa8b
--- /dev/null
@@ -0,0 +1,552 @@
+/*
+ * 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;
+}