Imported Upstream version 1.8.7
[debian/sudo] / plugins / sudoers / logging.c
index 395f83b41f8b517ed84eb5e885c0270e473686ec..d7256a5f3ee3d746a5104fb3dd3d78083667d28c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994-1996, 1998-2011 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 1994-1996, 1998-2013 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
@@ -25,7 +25,6 @@
 #include <config.h>
 
 #include <sys/types.h>
-#include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/wait.h>
@@ -47,9 +46,6 @@
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif /* HAVE_UNISTD_H */
-#ifdef HAVE_SETLOCALE
-# include <locale.h>
-#endif /* HAVE_SETLOCALE */
 #ifdef HAVE_NL_LANGINFO
 # include <langinfo.h>
 #endif /* HAVE_NL_LANGINFO */
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <setjmp.h>
 
 #include "sudoers.h"
 
+#ifndef va_copy
+# define va_copy(d, s) memcpy(&(d), &(s), sizeof(d));
+#endif
+
+/* Special message for log_warning() so we know to use ngettext() */
+#define INCORRECT_PASSWORD_ATTEMPT     ((char *)0x01)
+
 static void do_syslog(int, char *);
 static void do_logfile(char *);
 static void send_mail(const char *fmt, ...);
@@ -71,7 +73,7 @@ static int should_mail(int);
 static void mysyslog(int, const char *, ...);
 static char *new_logline(const char *, int);
 
-extern sigjmp_buf error_jmp;
+extern char **NewArgv; /* XXX - for auditing */
 
 #define MAXSYSLOGTRIES 16      /* num of retries for broken syslogs */
 
@@ -117,9 +119,6 @@ mysyslog(int pri, const char *fmt, ...)
     debug_return;
 }
 
-#define FMT_FIRST "%8s : %s"
-#define FMT_CONTD "%8s : (command continued) %s"
-
 /*
  * Log a message to syslog, pre-pending the username and splitting the
  * message into parts if it is longer than MAXSYSLOGLEN.
@@ -130,18 +129,15 @@ do_syslog(int pri, char *msg)
     size_t len, maxlen;
     char *p, *tmp, save;
     const char *fmt;
+    int oldlocale;
     debug_decl(do_syslog, SUDO_DEBUG_LOGGING)
 
-#ifdef HAVE_SETLOCALE
-    const char *old_locale = estrdup(setlocale(LC_ALL, NULL));
-    if (!setlocale(LC_ALL, def_sudoers_locale))
-       setlocale(LC_ALL, "C");
-#endif /* HAVE_SETLOCALE */
+    sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
 
     /*
      * Log the full line, breaking into multiple syslog(3) calls if necessary
      */
-    fmt = _(FMT_FIRST);
+    fmt = _("%8s : %s");
     maxlen = MAXSYSLOGLEN - (strlen(fmt) - 5 + strlen(user_name));
     for (p = msg; *p != '\0'; ) {
        len = strlen(p);
@@ -169,14 +165,11 @@ do_syslog(int pri, char *msg)
            mysyslog(pri, fmt, user_name, p);
            p += len;
        }
-       fmt = _(FMT_CONTD);
+       fmt = _("%8s : (command continued) %s");
        maxlen = MAXSYSLOGLEN - (strlen(fmt) - 5 + strlen(user_name));
     }
 
-#ifdef HAVE_SETLOCALE
-    setlocale(LC_ALL, old_locale);
-    efree((void *)old_locale);
-#endif /* HAVE_SETLOCALE */
+    sudoers_setlocale(oldlocale, NULL);
 
     debug_return;
 }
@@ -188,9 +181,12 @@ do_logfile(char *msg)
     size_t len;
     mode_t oldmask;
     time_t now;
+    int oldlocale;
     FILE *fp;
     debug_decl(do_logfile, SUDO_DEBUG_LOGGING)
 
+    sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
+
     oldmask = umask(077);
     fp = fopen(def_logfile, "a");
     (void) umask(oldmask);
@@ -201,13 +197,7 @@ do_logfile(char *msg)
        send_mail(_("unable to lock log file: %s: %s"),
            def_logfile, strerror(errno));
     } else {
-#ifdef HAVE_SETLOCALE
-       const char *old_locale = estrdup(setlocale(LC_ALL, NULL));
-       if (!setlocale(LC_ALL, def_sudoers_locale))
-           setlocale(LC_ALL, "C");
-#endif /* HAVE_SETLOCALE */
-
-       now = time(NULL);
+       time(&now);
        if (def_loglinelen < sizeof(LOG_INDENT)) {
            /* Don't pretty-print long log file lines (hard to grep) */
            if (def_log_host)
@@ -233,24 +223,32 @@ do_logfile(char *msg)
        (void) fflush(fp);
        (void) lock_file(fileno(fp), SUDO_UNLOCK);
        (void) fclose(fp);
-
-#ifdef HAVE_SETLOCALE
-       setlocale(LC_ALL, old_locale);
-       efree((void *)old_locale);
-#endif /* HAVE_SETLOCALE */
     }
+    sudoers_setlocale(oldlocale, NULL);
+
     debug_return;
 }
 
 /*
- * Log and mail the denial message, optionally informing the user.
+ * Log, audit and mail the denial message, optionally informing the user.
  */
 void
-log_denial(int status, int inform_user)
+log_denial(int status, bool inform_user)
 {
-    char *logline, *message;
+    const char *message;
+    char *logline;
+    int oldlocale;
     debug_decl(log_denial, SUDO_DEBUG_LOGGING)
 
+    /* Handle auditing first (audit_failure() handles the locale itself). */
+    if (ISSET(status, FLAG_NO_USER | FLAG_NO_HOST))
+       audit_failure(NewArgv, N_("No user or host"));
+    else
+       audit_failure(NewArgv, N_("validation failure"));
+
+    /* Log and mail messages should be in the sudoers locale. */
+    sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
+
     /* Set error message. */
     if (ISSET(status, FLAG_NO_USER))
        message = _("user NOT in sudoers");
@@ -261,11 +259,31 @@ log_denial(int status, int inform_user)
 
     logline = new_logline(message, 0);
 
+    /* Become root if we are not already. */
+    set_perms(PERM_ROOT|PERM_NOEXIT);
+
     if (should_mail(status))
        send_mail("%s", logline);       /* send mail based on status */
 
-    /* Inform the user if they failed to authenticate.  */
+    /*
+     * Log via syslog and/or a file.
+     */
+    if (def_syslog)
+       do_syslog(def_syslog_badpri, logline);
+    if (def_logfile)
+       do_logfile(logline);
+
+    restore_perms();
+
+    efree(logline);
+
+    /* Restore locale. */
+    sudoers_setlocale(oldlocale, NULL);
+
+    /* Inform the user if they failed to authenticate (in their locale).  */
     if (inform_user) {
+       sudoers_setlocale(SUDOERS_LOCALE_USER, &oldlocale);
+
        if (ISSET(status, FLAG_NO_USER)) {
            sudo_printf(SUDO_CONV_ERROR_MSG, _("%s is not in the sudoers "
                "file.  This incident will be reported.\n"), user_name);
@@ -285,17 +303,81 @@ log_denial(int status, int inform_user)
                runas_pw->pw_name : user_name, runas_gr ? ":" : "",
                runas_gr ? runas_gr->gr_name : "", user_host);
        }
+       sudoers_setlocale(oldlocale, NULL);
     }
+    debug_return;
+}
+
+/*
+ * Log and audit that user was not allowed to run the command.
+ */
+void
+log_failure(int status, int flags)
+{
+    bool inform_user = true;
+    debug_decl(log_failure, SUDO_DEBUG_LOGGING)
+
+    /* The user doesn't always get to see the log message (path info). */
+    if (!ISSET(status, FLAG_NO_USER | FLAG_NO_HOST) && def_path_info &&
+       (flags == NOT_FOUND_DOT || flags == NOT_FOUND))
+       inform_user = false;
+    log_denial(status, inform_user);
+
+    if (!inform_user) {
+       /*
+        * We'd like to not leak path info at all here, but that can
+        * *really* confuse the users.  To really close the leak we'd
+        * have to say "not allowed to run foo" even when the problem
+        * is just "no foo in path" since the user can trivially set
+        * their path to just contain a single dir.
+        */
+       if (flags == NOT_FOUND)
+           warningx(_("%s: command not found"), user_cmnd);
+       else if (flags == NOT_FOUND_DOT)
+           warningx(_("ignoring `%s' found in '.'\nUse `sudo ./%s' if this is the `%s' you wish to run."), user_cmnd, user_cmnd, user_cmnd);
+    }
+
+    debug_return;
+}
+
+/*
+ * Log and audit that user was not able to authenticate themselves.
+ */
+void
+log_auth_failure(int status, int tries)
+{
+    int flags = NO_MAIL;
+    debug_decl(log_auth_failure, SUDO_DEBUG_LOGGING)
+
+    /* Handle auditing first. */
+    audit_failure(NewArgv, N_("authentication failure"));
 
     /*
-     * Log via syslog and/or a file.
+     * Do we need to send mail?
+     * We want to avoid sending multiple messages for the same command
+     * so if we are going to send an email about the denial, that takes
+     * precedence.
      */
-    if (def_syslog)
-       do_syslog(def_syslog_badpri, logline);
-    if (def_logfile)
-       do_logfile(logline);
+    if (ISSET(status, VALIDATE_OK)) {
+       /* Command allowed, auth failed; do we need to send mail? */
+       if (def_mail_badpass || def_mail_always)
+           flags = 0;
+    } else {
+       /* Command denied, auth failed; make sure we don't send mail twice. */
+       if (def_mail_badpass && !should_mail(status))
+           flags = 0;
+       /* Don't log the bad password message, we'll log a denial instead. */
+       flags |= NO_LOG;
+    }
+
+    /*
+     * If sudoers denied the command we'll log that separately.
+     */
+    if (ISSET(status, FLAG_BAD_PASSWORD))
+       log_warning(flags, INCORRECT_PASSWORD_ATTEMPT, tries);
+    else if (ISSET(status, FLAG_NON_INTERACTIVE))
+       log_warning(flags, N_("a password is required"));
 
-    efree(logline);
     debug_return;
 }
 
@@ -306,10 +388,17 @@ void
 log_allowed(int status)
 {
     char *logline;
+    int oldlocale;
     debug_decl(log_allowed, SUDO_DEBUG_LOGGING)
 
+    /* Log and mail messages should be in the sudoers locale. */
+    sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
+
     logline = new_logline(NULL, 0);
 
+    /* Become root if we are not already. */
+    set_perms(PERM_ROOT|PERM_NOEXIT);
+
     if (should_mail(status))
        send_mail("%s", logline);       /* send mail based on status */
 
@@ -321,42 +410,51 @@ log_allowed(int status)
     if (def_logfile)
        do_logfile(logline);
 
+    restore_perms();
+
     efree(logline);
+
+    sudoers_setlocale(oldlocale, NULL);
+
     debug_return;
 }
 
-void
-log_error(int flags, const char *fmt, ...)
+/*
+ * Perform logging for log_warning()/log_fatal()
+ */
+static void
+vlog_warning(int flags, const char *fmt, va_list ap)
 {
-    int serrno = errno;
+    int oldlocale, serrno = errno;
     char *logline, *message;
-    va_list ap;
-    debug_decl(log_error, SUDO_DEBUG_LOGGING)
+    va_list ap2;
+    debug_decl(vlog_error, SUDO_DEBUG_LOGGING)
 
-    /* Expand printf-style format + args. */
-    va_start(ap, fmt);
-    evasprintf(&message, fmt, ap);
-    va_end(ap);
+    /* Need extra copy of ap for warning() below. */
+    if (!ISSET(flags, NO_STDERR))
+       va_copy(ap2, ap);
 
-    /* Become root if we are not already to avoid user interference */
-    set_perms(PERM_ROOT|PERM_NOEXIT);
+    /* Log messages should be in the sudoers locale. */
+    sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
+
+    /* Expand printf-style format + args (with a special case). */
+    if (fmt == INCORRECT_PASSWORD_ATTEMPT) {
+       int tries = va_arg(ap, int);
+       easprintf(&message, ngettext("%d incorrect password attempt",
+           "%d incorrect password attempts", tries), tries);
+    } else {
+       evasprintf(&message, _(fmt), ap);
+    }
 
-    if (ISSET(flags, MSG_ONLY))
+    if (ISSET(flags, MSG_ONLY)) {
        logline = message;
-    else
+    } else {
        logline = new_logline(message, ISSET(flags, USE_ERRNO) ? serrno : 0);
-
-    /*
-     * Tell the user.
-     */
-    if (!ISSET(flags, NO_STDERR)) {
-       if (ISSET(flags, USE_ERRNO))
-           warning("%s", message);
-       else
-           warningx("%s", message);
-    }
-    if (logline != message)
         efree(message);
+    }
+
+    /* Become root if we are not already. */
+    set_perms(PERM_ROOT|PERM_NOEXIT);
 
     /*
      * Send a copy of the error via mail.
@@ -367,22 +465,70 @@ log_error(int flags, const char *fmt, ...)
     /*
      * Log to syslog and/or a file.
      */
-    if (def_syslog)
-       do_syslog(def_syslog_badpri, logline);
-    if (def_logfile)
-       do_logfile(logline);
+    if (!ISSET(flags, NO_LOG)) {
+       if (def_syslog)
+           do_syslog(def_syslog_badpri, logline);
+       if (def_logfile)
+           do_logfile(logline);
+    }
+
+    restore_perms();
 
     efree(logline);
 
-    restore_perms();
+    sudoers_setlocale(oldlocale, NULL);
 
-    if (!ISSET(flags, NO_EXIT)) {
-       plugin_cleanup(0);
-       siglongjmp(error_jmp, 1);
+    /*
+     * Tell the user (in their locale).
+     */
+    if (!ISSET(flags, NO_STDERR)) {
+       if (fmt == INCORRECT_PASSWORD_ATTEMPT) {
+           int tries = va_arg(ap2, int);
+           warningx(ngettext("%d incorrect password attempt",
+               "%d incorrect password attempts", tries), tries);
+       } else {
+           if (ISSET(flags, USE_ERRNO))
+               vwarning(fmt, ap2);
+           else
+               vwarningx(fmt, ap2);
+       }
+       va_end(ap2);
     }
+
     debug_return;
 }
 
+void
+log_warning(int flags, const char *fmt, ...)
+{
+    va_list ap;
+    debug_decl(log_error, SUDO_DEBUG_LOGGING)
+
+    /* Log the error. */
+    va_start(ap, fmt);
+    vlog_warning(flags, fmt, ap);
+    va_end(ap);
+
+    debug_return;
+}
+
+void
+log_fatal(int flags, const char *fmt, ...)
+{
+    va_list ap;
+    debug_decl(log_error, SUDO_DEBUG_LOGGING)
+
+    /* Log the error. */
+    va_start(ap, fmt);
+    vlog_warning(flags, fmt, ap);
+    va_end(ap);
+
+    /* Exit the plugin. */
+    sudoers_cleanup();
+    sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+    fatal_longjmp(1);
+}
+
 #define MAX_MAILFLAGS  63
 
 /*
@@ -414,10 +560,10 @@ send_mail(const char *fmt, ...)
        debug_return;
 
     /* Fork and return, child will daemonize. */
-    switch (pid = fork()) {
+    switch (pid = sudo_debug_fork()) {
        case -1:
            /* Error. */
-           error(1, _("unable to fork"));
+           fatal(_("unable to fork"));
            break;
        case 0:
            /* Child. */
@@ -455,13 +601,7 @@ send_mail(const char *fmt, ...)
        (void) dup2(fd, STDERR_FILENO);
     }
 
-#ifdef HAVE_SETLOCALE
-    if (!setlocale(LC_ALL, def_sudoers_locale)) {
-       setlocale(LC_ALL, "C");
-       efree(def_sudoers_locale);
-       def_sudoers_locale = estrdup("C");
-    }
-#endif /* HAVE_SETLOCALE */
+    sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, NULL);
 
     /* Close password, group and other fds so we don't leak. */
     sudo_endpwent();
@@ -483,7 +623,7 @@ send_mail(const char *fmt, ...)
        _exit(1);
     }
 
-    switch (pid = fork()) {
+    switch (pid = sudo_debug_fork()) {
        case -1:
            /* Error. */
            mysyslog(LOG_ERR, _("unable to fork: %m"));
@@ -551,7 +691,7 @@ send_mail(const char *fmt, ...)
     /* Pipes are all setup, send message. */
     (void) fprintf(mail, "To: %s\nFrom: %s\nAuto-Submitted: %s\nSubject: ",
        def_mailto, def_mailfrom ? def_mailfrom : user_name, "auto-generated");
-    for (p = def_mailsub; *p; p++) {
+    for (p = _(def_mailsub); *p; p++) {
        /* Expand escapes in the subject */
        if (*p == '%' && *(p+1) != '%') {
            switch (*(++p)) {
@@ -625,12 +765,15 @@ should_mail(int status)
 static char *
 new_logline(const char *message, int serrno)
 {
+    char *line, *errstr = NULL, *evstr = NULL;
+#ifndef SUDOERS_NO_SEQ
+    char sessid[7];
+#endif
+    const char *tsid = NULL;
     size_t len = 0;
-    char *errstr = NULL;
-    char *evstr = NULL;
-    char *line, sessid[7], *tsid = NULL;
     debug_decl(new_logline, SUDO_DEBUG_LOGGING)
 
+#ifndef SUDOERS_NO_SEQ
     /* A TSID may be a sudoers-style session ID or a free-form string. */
     if (sudo_user.iolog_file != NULL) {
        if (IS_SESSID(sudo_user.iolog_file)) {
@@ -646,6 +789,7 @@ new_logline(const char *message, int serrno)
            tsid = sudo_user.iolog_file;
        }
     }
+#endif
 
     /*
      * Compute line length
@@ -752,5 +896,5 @@ new_logline(const char *message, int serrno)
 
     debug_return_str(line);
 toobig:
-    errorx(1, _("internal error: insufficient space for log line"));
+    fatalx(_("internal error: insufficient space for log line"));
 }