/* * Copyright (c) 2011 Todd C. Miller * * 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 #include #include #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif /* STDC_HEADERS */ #ifdef HAVE_STDBOOL_H # include #else # include "compat/stdbool.h" #endif #ifdef HAVE_STRING_H # include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ #include #include #include #include #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; }