summaryrefslogtreecommitdiffstats
path: root/usr.bin/sudo/logging.c
diff options
context:
space:
mode:
authormillert <millert@openbsd.org>1999-11-18 16:29:01 +0000
committermillert <millert@openbsd.org>1999-11-18 16:29:01 +0000
commitee4a81dac7260aeb8b39c7b51d451b215a8821f1 (patch)
treee7ce36a1f0161cd43c76d6b6cc71a3721ed0b8cc /usr.bin/sudo/logging.c
parentmore %d vs. %s in fmt-strings (diff)
downloadwireguard-openbsd-ee4a81dac7260aeb8b39c7b51d451b215a8821f1.tar.xz
wireguard-openbsd-ee4a81dac7260aeb8b39c7b51d451b215a8821f1.zip
sudo 1.6, now with a BSD license
Diffstat (limited to 'usr.bin/sudo/logging.c')
-rw-r--r--usr.bin/sudo/logging.c587
1 files changed, 587 insertions, 0 deletions
diff --git a/usr.bin/sudo/logging.c b/usr.bin/sudo/logging.c
new file mode 100644
index 00000000000..2addc043562
--- /dev/null
+++ b/usr.bin/sudo/logging.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (c) 1994-1996,1998-1999 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * 4. Products derived from this software may not be called "Sudo" nor
+ * may "Sudo" appear in their names without specific prior written
+ * permission from the author.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#endif /* STDC_HEADERS */
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <pwd.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "sudo.h"
+
+#ifndef lint
+static const char rcsid[] = "$Sudo: logging.c,v 1.139 1999/10/09 05:01:48 millert Exp $";
+#endif /* lint */
+
+static void do_syslog __P((int, char *));
+static void do_logfile __P((char *));
+static void send_mail __P((char *));
+static void mail_auth __P((int, char *));
+static char *get_timestr __P((void));
+
+#ifdef BROKEN_SYSLOG
+# define MAXSYSLOGTRIES 16 /* num of retries for broken syslogs */
+# define SYSLOG syslog_wrapper
+
+static void syslog_wrapper __P((int, char *, char *, char *));
+
+/*
+ * Some versions of syslog(3) don't guarantee success and return
+ * an int (notably HP-UX < 10.0). So, if at first we don't succeed,
+ * try, try again...
+ */
+static void
+syslog_wrapper(pri, fmt, ap)
+ int pri;
+ const char *fmt;
+ va_list ap;
+{
+ int i;
+
+ for (i = 0; i < MAXSYSLOGTRIES; i++)
+ if (vsyslog(pri, fmt, ap) == 0)
+ break;
+}
+#else
+# define SYSLOG syslog
+#endif /* BROKEN_SYSLOG */
+
+/*
+ * Log a message to syslog, pre-pending the username and splitting the
+ * message into parts if it is longer than MAXSYSLOGLEN.
+ */
+static void
+do_syslog(pri, msg)
+ int pri;
+ char *msg;
+{
+ int count;
+ char *p;
+ char *tmp;
+ char save;
+
+ /*
+ * Log the full line, breaking into multiple syslog(3) calls if necessary
+ */
+ for (p = msg, count = 0; count < strlen(msg) / MAXSYSLOGLEN + 1; count++) {
+ if (strlen(p) > MAXSYSLOGLEN) {
+ /*
+ * Break up the line into what will fit on one syslog(3) line
+ * Try to break on a word boundary if possible.
+ */
+ for (tmp = p + MAXSYSLOGLEN; tmp > p && *tmp != ' '; tmp--)
+ ;
+ if (tmp <= p)
+ tmp = p + MAXSYSLOGLEN;
+
+ /* NULL terminate line, but save the char to restore later */
+ save = *tmp;
+ *tmp = '\0';
+
+ if (count == 0)
+ SYSLOG(pri, "%8.8s : %s", user_name, p);
+ else
+ SYSLOG(pri, "%8.8s : (command continued) %s", user_name, p);
+
+ *tmp = save; /* restore saved character */
+
+ /* Eliminate leading whitespace */
+ for (p = tmp; *p != ' '; p++)
+ ;
+ } else {
+ if (count == 0)
+ SYSLOG(pri, "%8.8s : %s", user_name, p);
+ else
+ SYSLOG(pri, "%8.8s : (command continued) %s", user_name, p);
+ }
+ }
+}
+
+static void
+do_logfile(msg)
+ char *msg;
+{
+ char *full_line;
+ char *beg, *oldend, *end;
+ FILE *fp;
+ mode_t oldmask;
+ int maxlen = def_ival(I_LOGLEN);
+
+ oldmask = umask(077);
+ fp = fopen(def_str(I_LOGFILE), "a");
+ (void) umask(oldmask);
+ if (fp == NULL) {
+ easprintf(&full_line, "Can't open log file: %s: %s",
+ def_str(I_LOGFILE), strerror(errno));
+ send_mail(full_line);
+ free(full_line);
+ } else if (!lock_file(fileno(fp), SUDO_LOCK)) {
+ easprintf(&full_line, "Can't lock log file: %s: %s",
+ def_str(I_LOGFILE), strerror(errno));
+ send_mail(full_line);
+ free(full_line);
+ } else {
+ if (def_ival(I_LOGLEN) == 0) {
+ /* Don't pretty-print long log file lines (hard to grep) */
+ if (def_flag(I_LOG_HOST))
+ (void) fprintf(fp, "%s : %s : HOST=%s : %s\n", get_timestr(),
+ user_name, user_shost, msg);
+ else
+ (void) fprintf(fp, "%s : %s : %s\n", get_timestr(),
+ user_name, msg);
+ } else {
+ if (def_flag(I_LOG_HOST))
+ easprintf(&full_line, "%s : %s : HOST=%s : %s", get_timestr(),
+ user_name, user_shost, msg);
+ else
+ easprintf(&full_line, "%s : %s : %s", get_timestr(),
+ user_name, msg);
+
+ /*
+ * Print out full_line with word wrap
+ */
+ beg = end = full_line;
+ while (beg) {
+ oldend = end;
+ end = strchr(oldend, ' ');
+
+ if (maxlen > 0 && end) {
+ *end = '\0';
+ if (strlen(beg) > maxlen) {
+ /* too far, need to back up & print the line */
+
+ if (beg == (char *)full_line)
+ maxlen -= 4; /* don't indent first line */
+
+ *end = ' ';
+ if (oldend != beg) {
+ /* rewind & print */
+ end = oldend-1;
+ while (*end == ' ')
+ --end;
+ *(++end) = '\0';
+ (void) fprintf(fp, "%s\n ", beg);
+ *end = ' ';
+ } else {
+ (void) fprintf(fp, "%s\n ", beg);
+ }
+
+ /* reset beg to point to the start of the new substr */
+ beg = end;
+ while (*beg == ' ')
+ ++beg;
+ } else {
+ /* we still have room */
+ *end = ' ';
+ }
+
+ /* remove leading whitespace */
+ while (*end == ' ')
+ ++end;
+ } else {
+ /* final line */
+ (void) fprintf(fp, "%s\n", beg);
+ beg = NULL; /* exit condition */
+ }
+ }
+ free(full_line);
+ }
+ (void) fflush(fp);
+ (void) lock_file(fileno(fp), SUDO_UNLOCK);
+ (void) fclose(fp);
+ }
+}
+
+/*
+ * Two main functions, log_error() to log errors and log_auth() to
+ * log allow/deny messages.
+ */
+void
+log_auth(status, inform_user)
+ int status;
+ int inform_user;
+{
+ char *message;
+ char *logline;
+ int pri;
+
+ if (status & VALIDATE_OK)
+ pri = def_ival(I_GOODPRI);
+ else
+ pri = def_ival(I_BADPRI);
+
+ /* Set error message, if any. */
+ if (status & VALIDATE_OK)
+ message = "";
+ else if (status & FLAG_NO_USER)
+ message = "user NOT in sudoers ; ";
+ else if (status & FLAG_NO_HOST)
+ message = "user NOT authorized on host ; ";
+ else if (status & VALIDATE_NOT_OK)
+ message = "command not allowed ; ";
+ else
+ message = "unknown error ; ";
+
+ easprintf(&logline, "%sTTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s%s%s",
+ message, user_tty, user_cwd, *user_runas, user_cmnd,
+ user_args ? " " : "", user_args ? user_args : "");
+
+ mail_auth(status, logline); /* send mail based on status */
+
+ /* Inform the user if they failed to authenticate. */
+ if (inform_user && (status & VALIDATE_NOT_OK)) {
+ if (status & FLAG_NO_USER)
+ (void) fprintf(stderr, "%s is not in the sudoers file. %s",
+ user_name, "This incident will be reported.\n");
+ else if (status & FLAG_NO_HOST)
+ (void) fprintf(stderr, "%s is not allowed to run sudo on %s. %s",
+ user_name, user_shost, "This incident will be reported.\n");
+ else if (status & FLAG_NO_CHECK)
+ (void) fprintf(stderr, "Sorry, user %s may not run sudo on %s.\n",
+ user_name, user_shost);
+ else
+ (void) fprintf(stderr,
+ "Sorry, user %s is not allowed to execute '%s%s%s' as %s on %s.\n",
+ user_name, user_cmnd, user_args ? " " : "",
+ user_args ? user_args : "", *user_runas, user_host);
+ }
+
+ /*
+ * Log via syslog and/or a file.
+ */
+ if (def_str(I_LOGFACSTR))
+ do_syslog(pri, logline);
+ if (def_str(I_LOGFILE))
+ do_logfile(logline);
+
+ free(logline);
+}
+
+void
+#ifdef __STDC__
+log_error(int flags, const char *fmt, ...)
+#else
+log_error(va_alist)
+ va_dcl
+#endif
+{
+ int serrno = errno;
+ char *message;
+ char *logline;
+ va_list ap;
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ int flags;
+ const char *fmt;
+
+ va_start(ap);
+ flags = va_arg(ap, int);
+ fmt = va_arg(ap, const char *);
+#endif
+
+ /* Become root if we are not already to avoid user control */
+ if (geteuid() != 0)
+ set_perms(PERM_ROOT, 0);
+
+ /* Expand printf-style format + args. */
+ evasprintf(&message, fmt, ap);
+ va_end(ap);
+
+ if (flags & MSG_ONLY)
+ logline = message;
+ else if (flags & USE_ERRNO) {
+ if (user_args) {
+ easprintf(&logline,
+ "%s: %s ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s %s",
+ message, strerror(serrno), user_tty, user_cwd, *user_runas,
+ user_cmnd, user_args);
+ } else {
+ easprintf(&logline,
+ "%s: %s ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s", message,
+ strerror(serrno), user_tty, user_cwd, *user_runas, user_cmnd);
+ }
+ } else {
+ if (user_args) {
+ easprintf(&logline,
+ "%s ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s %s", message,
+ user_tty, user_cwd, *user_runas, user_cmnd, user_args);
+ } else {
+ easprintf(&logline,
+ "%s ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s", message,
+ user_tty, user_cwd, *user_runas, user_cmnd);
+ }
+ }
+
+ /*
+ * Tell the user.
+ */
+ (void) fprintf(stderr, "%s: %s", Argv[0], message);
+ if (flags & USE_ERRNO)
+ (void) fprintf(stderr, ": %s", strerror(serrno));
+ (void) fputc('\n', stderr);
+
+ /*
+ * Send a copy of the error via mail.
+ */
+ if (!(flags & NO_MAIL))
+ send_mail(logline);
+
+ /*
+ * Log to syslog and/or a file.
+ */
+ if (def_str(I_LOGFACSTR))
+ do_syslog(def_ival(I_BADPRI), logline);
+ if (def_str(I_LOGFILE))
+ do_logfile(logline);
+
+ free(logline);
+ if (message != logline);
+ free(message);
+
+ if (!(flags & NO_EXIT))
+ exit(1);
+}
+
+#define MAX_MAILFLAGS 63
+
+/*
+ * Send a message to MAILTO user
+ */
+static void
+send_mail(line)
+ char *line;
+{
+ FILE *mail;
+ char *p;
+ int pfd[2], pid;
+
+ /* Just return if mailer is disabled. */
+ if (!def_str(I_MAILERPATH) || !def_str(I_MAILTO))
+ return;
+
+ if ((pid = fork()) > 0) { /* Child. */
+
+ /* We do an explicit wait() later on... */
+ (void) signal(SIGCHLD, SIG_IGN);
+
+ if (pipe(pfd) == -1) {
+ (void) fprintf(stderr, "%s: cannot open pipe: %s\n",
+ Argv[0], strerror(errno));
+ exit(1);
+ }
+
+ switch (pid = fork()) {
+ case -1:
+ /* Error. */
+ /* XXX - parent will continue, return an exit val to
+ let parent know and abort? */
+ (void) fprintf(stderr, "%s: cannot fork: %s\n",
+ Argv[0], strerror(errno));
+ exit(1);
+ break;
+ case 0:
+ {
+ char *argv[MAX_MAILFLAGS + 1];
+ char *mpath, *mflags;
+ int i;
+
+ /* Grandchild. */
+ (void) close(pfd[1]);
+ (void) dup2(pfd[0], STDIN_FILENO);
+ (void) close(pfd[0]);
+
+ /* Build up an argv based the mailer path and flags */
+ mflags = estrdup(def_str(I_MAILERFLAGS));
+ mpath = estrdup(def_str(I_MAILERPATH));
+ if ((argv[0] = strrchr(mpath, ' ')))
+ argv[0]++;
+ else
+ argv[0] = mpath;
+
+ i = 1;
+ if ((p = strtok(mflags, " \t"))) {
+ do {
+ argv[i] = p;
+ } while (++i < MAX_MAILFLAGS && (p = strtok(NULL, " \t")));
+ }
+ argv[i] = NULL;
+
+ /* Run mailer as root so user cannot kill it. */
+ set_perms(PERM_ROOT, 0);
+ execv(mpath, argv);
+ _exit(127);
+ }
+ break;
+ }
+
+ mail = fdopen(pfd[1], "w");
+ (void) close(pfd[0]);
+
+ /* Pipes are all setup, send message via sendmail. */
+ (void) fprintf(mail, "To: %s\nFrom: %s\nSubject: ",
+ def_str(I_MAILTO), user_name);
+ for (p = def_str(I_MAILSUB); *p; p++) {
+ /* Expand escapes in the subject */
+ if (*p == '%' && *(p+1) != '%') {
+ switch (*(++p)) {
+ case 'h':
+ (void) fputs(user_host, mail);
+ break;
+ case 'u':
+ (void) fputs(user_name, mail);
+ break;
+ default:
+ p--;
+ break;
+ }
+ } else
+ (void) fputc(*p, mail);
+ }
+ (void) fprintf(mail, "\n\n%s : %s : %s : %s\n\n", user_host,
+ get_timestr(), user_name, line);
+ fclose(mail);
+ reapchild(0);
+ _exit(0);
+ } else {
+ /* Parent, just return unless there is an error. */
+ if (pid == -1) {
+ (void) fprintf(stderr, "%s: cannot fork: %s\n",
+ Argv[0], strerror(errno));
+ exit(1);
+ }
+ }
+}
+
+/*
+ * Send mail based on the value of "status" and compile-time options.
+ */
+static void
+mail_auth(status, line)
+ int status;
+ char *line;
+{
+ int mail_mask;
+
+ /* If any of these bits are set in status, we send mail. */
+ if (def_flag(I_MAIL_ALWAYS))
+ mail_mask =
+ VALIDATE_ERROR|VALIDATE_OK|FLAG_NO_USER|FLAG_NO_HOST|VALIDATE_NOT_OK;
+ else {
+ mail_mask = VALIDATE_ERROR;
+ if (def_flag(I_MAIL_NOUSER))
+ mail_mask |= FLAG_NO_USER;
+ if (def_flag(I_MAIL_NOHOST))
+ mail_mask |= FLAG_NO_HOST;
+ if (def_flag(I_MAIL_NOPERMS))
+ mail_mask |= VALIDATE_NOT_OK;
+ }
+
+ if ((status & mail_mask) != 0)
+ send_mail(line);
+}
+
+/*
+ * SIGCHLD sig handler--wait for children as they die.
+ */
+RETSIGTYPE
+reapchild(sig)
+ int sig;
+{
+ int status, serrno = errno;
+
+#ifdef sudo_waitpid
+ while (sudo_waitpid(-1, &status, WNOHANG) != -1)
+ ;
+#else
+ (void) wait(&status);
+#endif
+#ifndef POSIX_SIGNALS
+ (void) signal(SIGCHLD, reapchild);
+#endif /* POSIX_SIGNALS */
+ errno = serrno;
+}
+
+/*
+ * Return an ascii string with the current date + time
+ * Uses strftime() if available, else falls back to ctime().
+ */
+static char *
+get_timestr()
+{
+ char *s;
+ time_t now = time((time_t) 0);
+#ifdef HAVE_STRFTIME
+ static char buf[128];
+ struct tm *timeptr;
+
+ timeptr = localtime(&now);
+ if (def_flag(I_LOG_YEAR))
+ s = "%h %e %T %Y";
+ else
+ s = "%h %e %T";
+
+ /* strftime() does not guarantee to NUL-terminate so we must check. */
+ buf[sizeof(buf) - 1] = '\0';
+ if (strftime(buf, sizeof(buf), s, timeptr) && buf[sizeof(buf) - 1] == '\0')
+ return(buf);
+
+#endif /* HAVE_STRFTIME */
+
+ s = ctime(&now) + 4; /* skip day of the week */
+ if (def_flag(I_LOG_YEAR))
+ s[20] = '\0'; /* avoid the newline */
+ else
+ s[15] = '\0'; /* don't care about year */
+
+ return(s);
+}