Files
sudo/logging.c
Todd C. Miller 275c2fc980 Run most of the code as root, not the invoking user. It doesn't really
gain us anything to run as the user since an attacker can just have
an setuid(0) in their egg.  Running as root solves potential problems
wrt signalling.
1999-08-20 20:37:16 +00:00

549 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 maxlen = MAXLOGFILELEN;
now = time((time_t) 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);
}
}
#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 && (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
(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 (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
/* 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 (LOGGING & SLOG_SYSLOG)
do_syslog(PRI_FAILURE, logline);
#endif
#if (LOGGING & SLOG_FILE)
do_logfile(logline);
#endif
free(logline);
if (message != logline);
free(message);
}
/*
* Send a message to ALERTMAIL
*/
#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;
/* If any of these bits are set in status, we send mail. */
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;
}