--- /dev/null
+/*
+ * 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;
+}