Files
sudo/logging.c
Todd C. Miller b9746389ce Don't wait for child to finish in log_error(), let the signal handler
get it if we are still running, else let init reap it for us.  The extra
time it takes to wait lets the user know that mail is being sent.

Install SIGCHLD handler in main() and for POSIX signals, block everything
*except* SIGCHLD.
1999-08-19 17:45:36 +00:00

551 lines
13 KiB
C

/*
* 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$";
#endif /* lint */
#if (LOGGING & SLOG_SYSLOG)
static void do_syslog __P((int, char *));
#endif
#if (LOGGING & SLOG_FILE)
static void do_logfile __P((char *));
#endif
static void send_mail __P((char *));
static void mail_auth __P((int, char *));
#if (LOGGING & SLOG_SYSLOG)
# 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);
}
}
}
#endif /* LOGGING & SLOG_SYSLOG */
#if (LOGGING & SLOG_FILE)
static void
do_logfile(msg)
char *msg;
{
char *full_line;
char *beg, *oldend, *end;
FILE *fp;
mode_t oldmask;
time_t now;
int oldeuid = geteuid();
int maxlen = MAXLOGFILELEN;
now = time((time_t) 0);
/* Become root if we are not already. */
if (oldeuid)
set_perms(PERM_ROOT, 0);
oldmask = umask(077);
fp = fopen(_PATH_SUDO_LOGFILE, "a");
(void) umask(oldmask);
if (fp == NULL) {
easprintf(&full_line, "Can't open log file: %s: %s",
_PATH_SUDO_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",
_PATH_SUDO_LOGFILE, strerror(errno));
send_mail(full_line);
free(full_line);
} else {
# ifndef WRAP_LOG
# ifdef HOST_IN_LOG
(void) fprintf(fp, "%15.15s : %s : HOST=%s : %s\n", ctime(&now) + 4,
user_name, user_shost, msg);
# else
(void) fprintf(fp, "%15.15s : %s : %s\n", ctime(&now) + 4, user_name,
msg);
# endif
# else
# ifdef HOST_IN_LOG
easprintf(&full_line, "%15.15s : %s : HOST=%s : %s",
ctime(&now) + 4, user_name, user_shost, msg);
# else
easprintf(&full_line, "%15.15s : %s : %s", ctime(&now) + 4,
user_name, msg);
# endif
/*
* 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 substring */
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);
# endif
(void) fflush(fp);
(void) lock_file(fileno(fp), SUDO_UNLOCK);
(void) fclose(fp);
}
if (oldeuid)
set_perms(PERM_USER, 0); /* relinquish root */
}
#endif /* LOGGING & SLOG_FILE */
/*
* 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;
#if (LOGGING & SLOG_SYSLOG)
int pri;
if (status & VALIDATE_OK)
pri = PRI_SUCCESS;
else
pri = PRI_FAILURE;
#endif /* LOGGING & SLOG_SYSLOG */
/* 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) {
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 & VALIDATE_NOT_OK)
(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);
else
(void) fprintf(stderr, "An unknown error has occurred.\n");
}
/*
* Log via syslog and/or a file.
*/
#if (LOGGING & SLOG_SYSLOG)
do_syslog(pri, logline);
#endif
#if (LOGGING & SLOG_FILE)
do_logfile(logline);
#endif
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
/* 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 (LOGGING & SLOG_SYSLOG)
do_syslog(PRI_FAILURE, logline);
#endif
#if (LOGGING & SLOG_FILE)
do_logfile(logline);
#endif
free(logline);
if (message != logline);
free(message);
}
#ifdef _PATH_SENDMAIL
static void
send_mail(line)
char *line;
{
FILE *mail;
char *p;
int pfd[2], pid;
time_t now;
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:
/* Grandchild. */
(void) close(pfd[1]);
(void) dup2(pfd[0], STDIN_FILENO);
(void) close(pfd[0]);
/* Run sendmail as root so user cannot kill it. */
set_perms(PERM_ROOT, 0);
execl(_PATH_SENDMAIL, "sendmail", "-t", NULL);
_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: ", ALERTMAIL,
user_name);
for (p = MAILSUBJECT; *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);
}
now = time((time_t) 0);
p = ctime(&now) + 4;
(void) fprintf(mail, "\n\n%s : %15.15s : %s : %s\n\n", user_host, p,
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);
}
}
}
#else
static void
send_mail(line)
char *line;
{
return;
}
#endif
/*
* 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;
mail_mask = VALIDATE_ERROR;
#ifdef SEND_MAIL_WHEN_OK
mail_mask |= VALIDATE_OK;
#endif
#ifdef SEND_MAIL_WHEN_NO_USER
mail_mask |= FLAG_NO_USER;
#endif
#ifdef SEND_MAIL_WHEN_NO_HOST
mail_mask |= FLAG_NO_HOST;
#endif
#ifdef SEND_MAIL_WHEN_NOT_OK
mail_mask |= VALIDATE_NOT_OK;
#endif
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;
}