Files
sudo/plugins/sudoers/logging.c
Todd C. Miller 5ad68edd65 It is possible for WIFSTOPPED to be true even if waitpid() is not
given WUNTRACED if the child is ptraced.  Don't exit the waitpid()
loop if WIFSTOPPED is true, just in case.
2015-10-02 11:24:01 -06:00

932 lines
25 KiB
C

/*
* Copyright (c) 1994-1996, 1998-2015 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
* 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.
*
* Sponsored in part by the Defense Advanced Research Projects
* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
*/
#ifdef __TANDEM
# include <floss.h>
#endif
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <unistd.h>
#ifdef HAVE_NL_LANGINFO
# include <langinfo.h>
#endif /* HAVE_NL_LANGINFO */
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include "sudoers.h"
/* Special message for log_warning() so we know to use ngettext() */
#define INCORRECT_PASSWORD_ATTEMPT ((char *)0x01)
static void do_syslog(int, char *);
static bool do_logfile(const char *);
static bool send_mail(const char *fmt, ...);
static bool should_mail(int);
static void mysyslog(int, const char *, ...);
static char *new_logline(const char *, int);
#define MAXSYSLOGTRIES 16 /* num of retries for broken syslogs */
/*
* We do an openlog(3)/closelog(3) for each message because some
* authentication methods (notably PAM) use syslog(3) for their
* own nefarious purposes and may call openlog(3) and closelog(3).
* Note that because we don't want to assume that all systems have
* vsyslog(3) (HP-UX doesn't) "%m" will not be expanded.
*/
static void
mysyslog(int pri, const char *fmt, ...)
{
char buf[MAXSYSLOGLEN+1];
va_list ap;
debug_decl(mysyslog, SUDOERS_DEBUG_LOGGING)
va_start(ap, fmt);
openlog("sudo", 0, def_syslog);
vsnprintf(buf, sizeof(buf), fmt, ap);
syslog(pri, "%s", buf);
va_end(ap);
closelog();
debug_return;
}
/*
* 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(int pri, char *msg)
{
size_t len, maxlen;
char *p, *tmp, save;
const char *fmt;
int oldlocale;
debug_decl(do_syslog, SUDOERS_DEBUG_LOGGING)
sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
/*
* Log the full line, breaking into multiple syslog(3) calls if necessary
*/
fmt = _("%8s : %s");
maxlen = MAXSYSLOGLEN - (strlen(fmt) - 5 + strlen(user_name));
for (p = msg; *p != '\0'; ) {
len = strlen(p);
if (len > maxlen) {
/*
* Break up the line into what will fit on one syslog(3) line
* Try to avoid breaking words into several lines if possible.
*/
tmp = memrchr(p, ' ', maxlen);
if (tmp == NULL)
tmp = p + maxlen;
/* NULL terminate line, but save the char to restore later */
save = *tmp;
*tmp = '\0';
mysyslog(pri, fmt, user_name, p);
*tmp = save; /* restore saved character */
/* Advance p and eliminate leading whitespace */
for (p = tmp; *p == ' '; p++)
;
} else {
mysyslog(pri, fmt, user_name, p);
p += len;
}
fmt = _("%8s : (command continued) %s");
maxlen = MAXSYSLOGLEN - (strlen(fmt) - 5 + strlen(user_name));
}
sudoers_setlocale(oldlocale, NULL);
debug_return;
}
static bool
do_logfile(const char *msg)
{
char *full_line;
mode_t oldmask;
bool rval = false;
int len, oldlocale;
FILE *fp;
debug_decl(do_logfile, SUDOERS_DEBUG_LOGGING)
sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
oldmask = umask(077);
fp = fopen(def_logfile, "a");
(void) umask(oldmask);
if (fp == NULL) {
send_mail(_("unable to open log file: %s: %s"),
def_logfile, strerror(errno));
} else if (!sudo_lock_file(fileno(fp), SUDO_LOCK)) {
send_mail(_("unable to lock log file: %s: %s"),
def_logfile, strerror(errno));
} else {
const char *timestr = get_timestr(time(NULL), def_log_year);
if (timestr == NULL)
timestr = "invalid date";
if (def_log_host) {
len = asprintf(&full_line, "%s : %s : HOST=%s : %s",
timestr, user_name, user_srunhost, msg);
} else {
len = asprintf(&full_line, "%s : %s : %s",
timestr, user_name, msg);
}
if (len != -1) {
if ((size_t)def_loglinelen < sizeof(LOG_INDENT)) {
/* Don't pretty-print long log file lines (hard to grep). */
(void) fputs(full_line, fp);
} else {
/* Write line with word wrap around def_loglinelen chars. */
writeln_wrap(fp, full_line, len, def_loglinelen);
}
free(full_line);
(void) fflush(fp);
if (!ferror(fp))
rval = true;
} else {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
}
(void) fclose(fp);
}
sudoers_setlocale(oldlocale, NULL);
debug_return_bool(rval);
}
/*
* Log, audit and mail the denial message, optionally informing the user.
*/
bool
log_denial(int status, bool inform_user)
{
const char *message;
char *logline;
int oldlocale;
bool uid_changed, rval = true;
debug_decl(log_denial, SUDOERS_DEBUG_LOGGING)
/* Handle auditing first (audit_failure() handles the locale itself). */
if (ISSET(status, FLAG_NO_USER | FLAG_NO_HOST))
audit_failure(NewArgc, NewArgv, N_("No user or host"));
else
audit_failure(NewArgc, 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");
else if (ISSET(status, FLAG_NO_HOST))
message = _("user NOT authorized on host");
else
message = _("command not allowed");
logline = new_logline(message, 0);
if (logline == NULL)
debug_return_bool(false);
/* Become root if we are not already. */
uid_changed = set_perms(PERM_ROOT);
if (should_mail(status))
send_mail("%s", logline); /* send mail based on status */
/*
* Log via syslog and/or a file.
*/
if (def_syslog)
do_syslog(def_syslog_badpri, logline);
if (def_logfile && !do_logfile(logline))
rval = false;
if (uid_changed) {
if (!restore_perms())
rval = false; /* XXX - return -1 instead? */
}
free(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);
} else if (ISSET(status, FLAG_NO_HOST)) {
sudo_printf(SUDO_CONV_ERROR_MSG, _("%s is not allowed to run sudo "
"on %s. This incident will be reported.\n"),
user_name, user_srunhost);
} else if (ISSET(status, FLAG_NO_CHECK)) {
sudo_printf(SUDO_CONV_ERROR_MSG, _("Sorry, user %s may not run "
"sudo on %s.\n"), user_name, user_srunhost);
} else {
sudo_printf(SUDO_CONV_ERROR_MSG, _("Sorry, user %s is not allowed "
"to execute '%s%s%s' as %s%s%s on %s.\n"),
user_name, user_cmnd, user_args ? " " : "",
user_args ? user_args : "",
list_pw ? list_pw->pw_name : runas_pw ?
runas_pw->pw_name : user_name, runas_gr ? ":" : "",
runas_gr ? runas_gr->gr_name : "", user_host);
}
sudoers_setlocale(oldlocale, NULL);
}
debug_return_bool(rval);
}
/*
* Log and audit that user was not allowed to run the command.
*/
bool
log_failure(int status, int flags)
{
bool rval, inform_user = true;
debug_decl(log_failure, SUDOERS_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;
rval = 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)
sudo_warnx(U_("%s: command not found"), user_cmnd);
else if (flags == NOT_FOUND_DOT)
sudo_warnx(U_("ignoring `%s' found in '.'\nUse `sudo ./%s' if this is the `%s' you wish to run."), user_cmnd, user_cmnd, user_cmnd);
}
debug_return_bool(rval);
}
/*
* Log and audit that user was not able to authenticate themselves.
*/
bool
log_auth_failure(int status, unsigned int tries)
{
int flags = 0;
bool rval = true;
debug_decl(log_auth_failure, SUDOERS_DEBUG_LOGGING)
/* Handle auditing first. */
audit_failure(NewArgc, NewArgv, N_("authentication failure"));
/*
* 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 (ISSET(status, VALIDATE_SUCCESS)) {
/* Command allowed, auth failed; do we need to send mail? */
if (def_mail_badpass || def_mail_always)
SET(flags, SLOG_SEND_MAIL);
} else {
/* Command denied, auth failed; make sure we don't send mail twice. */
if (def_mail_badpass && !should_mail(status))
SET(flags, SLOG_SEND_MAIL);
/* Don't log the bad password message, we'll log a denial instead. */
SET(flags, SLOG_NO_LOG);
}
/*
* If sudoers denied the command we'll log that separately.
*/
if (ISSET(status, FLAG_BAD_PASSWORD))
rval = log_warningx(flags, INCORRECT_PASSWORD_ATTEMPT, tries);
else if (ISSET(status, FLAG_NON_INTERACTIVE))
rval = log_warningx(flags, N_("a password is required"));
debug_return_bool(rval);
}
/*
* Log and potentially mail the allowed command.
*/
bool
log_allowed(int status)
{
char *logline;
int oldlocale;
bool uid_changed, rval = true;
debug_decl(log_allowed, SUDOERS_DEBUG_LOGGING)
/* Log and mail messages should be in the sudoers locale. */
sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
if ((logline = new_logline(NULL, 0)) == NULL)
debug_return_bool(false);
/* Become root if we are not already. */
uid_changed = set_perms(PERM_ROOT);
/* XXX - return value */
if (should_mail(status))
send_mail("%s", logline); /* send mail based on status */
/*
* Log via syslog and/or a file.
*/
if (def_syslog)
do_syslog(def_syslog_goodpri, logline);
if (def_logfile && !do_logfile(logline))
rval = false;
if (uid_changed) {
if (!restore_perms())
rval = false; /* XXX - return -1 instead? */
}
free(logline);
sudoers_setlocale(oldlocale, NULL);
debug_return_bool(rval);
}
/*
* Perform logging for log_warning()/log_warningx().
*/
static bool
vlog_warning(int flags, const char *fmt, va_list ap)
{
int oldlocale, serrno = errno;
char *logline, *message;
bool uid_changed, rval = true;
va_list ap2;
int len;
debug_decl(vlog_error, SUDOERS_DEBUG_LOGGING)
/* Need extra copy of ap for sudo_vwarn()/sudo_vwarnx() below. */
va_copy(ap2, ap);
/* 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) {
unsigned int tries = va_arg(ap, unsigned int);
len = asprintf(&message, ngettext("%u incorrect password attempt",
"%u incorrect password attempts", tries), tries);
} else {
len = vasprintf(&message, _(fmt), ap);
}
if (len == -1) {
rval = false;
goto done;
}
/* Log to debug file. */
if (SLOG_USE_ERRNO) {
sudo_debug_printf2(NULL, NULL, 0,
SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|sudo_debug_subsys, "%s", message);
} else {
sudo_debug_printf2(NULL, NULL, 0,
SUDO_DEBUG_WARN|sudo_debug_subsys, "%s", message);
}
if (ISSET(flags, SLOG_RAW_MSG)) {
logline = message;
} else {
logline = new_logline(message, ISSET(flags, SLOG_USE_ERRNO) ? serrno : 0);
free(message);
if (logline == NULL) {
rval = false;
goto done;
}
}
/* Become root if we are not already. */
uid_changed = set_perms(PERM_ROOT);
/*
* Send a copy of the error via mail.
* XXX - return value
*/
if (ISSET(flags, SLOG_SEND_MAIL))
send_mail("%s", logline);
/*
* Log to syslog and/or a file.
*/
if (!ISSET(flags, SLOG_NO_LOG)) {
if (def_syslog)
do_syslog(def_syslog_badpri, logline);
if (def_logfile && !do_logfile(logline))
rval = false;
}
if (uid_changed)
restore_perms();
free(logline);
/*
* Tell the user (in their locale).
*/
if (!ISSET(flags, SLOG_NO_STDERR)) {
sudoers_setlocale(SUDOERS_LOCALE_USER, NULL);
if (fmt == INCORRECT_PASSWORD_ATTEMPT) {
unsigned int tries = va_arg(ap2, unsigned int);
sudo_warnx_nodebug(ngettext("%u incorrect password attempt",
"%u incorrect password attempts", tries), tries);
} else {
errno = serrno;
if (ISSET(flags, SLOG_USE_ERRNO))
sudo_vwarn_nodebug(_(fmt), ap2);
else
sudo_vwarnx_nodebug(_(fmt), ap2);
}
}
done:
va_end(ap2);
sudoers_setlocale(oldlocale, NULL);
debug_return_bool(rval);
}
bool
log_warning(int flags, const char *fmt, ...)
{
va_list ap;
bool rval;
debug_decl(log_error, SUDOERS_DEBUG_LOGGING)
/* Log the error. */
va_start(ap, fmt);
rval = vlog_warning(flags|SLOG_USE_ERRNO, fmt, ap);
va_end(ap);
debug_return_bool(rval);
}
bool
log_warningx(int flags, const char *fmt, ...)
{
va_list ap;
bool rval;
debug_decl(log_error, SUDOERS_DEBUG_LOGGING)
/* Log the error. */
va_start(ap, fmt);
rval = vlog_warning(flags, fmt, ap);
va_end(ap);
debug_return_bool(rval);
}
#define MAX_MAILFLAGS 63
/*
* Send a message to MAILTO user
*/
static bool
send_mail(const char *fmt, ...)
{
FILE *mail;
char *p;
const char *timestr;
int fd, pfd[2], status;
pid_t pid, rv;
sigaction_t sa;
struct stat sb;
va_list ap;
#ifndef NO_ROOT_MAILER
static char *root_envp[] = {
"HOME=/",
"PATH=/usr/bin:/bin:/usr/sbin:/sbin",
"LOGNAME=root",
"USERNAME=root",
"USER=root",
NULL
};
#endif /* NO_ROOT_MAILER */
debug_decl(send_mail, SUDOERS_DEBUG_LOGGING)
/* If mailer is disabled just return. */
if (!def_mailerpath || !def_mailto)
debug_return_bool(true);
/* Make sure the mailer exists and is a regular file. */
if (stat(def_mailerpath, &sb) != 0 || !S_ISREG(sb.st_mode))
debug_return_bool(false);
/* Fork and return, child will daemonize. */
switch (pid = sudo_debug_fork()) {
case -1:
/* Error. */
sudo_warn(U_("unable to fork"));
debug_return_bool(false);
break;
case 0:
/* Child. */
switch (pid = fork()) {
case -1:
/* Error. */
mysyslog(LOG_ERR, _("unable to fork: %m"));
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to fork: %s",
strerror(errno));
_exit(1);
case 0:
/* Grandchild continues below. */
break;
default:
/* Parent will wait for us. */
_exit(0);
}
break;
default:
/* Parent. */
for (;;) {
rv = waitpid(pid, &status, 0);
if (rv == -1 && errno != EINTR)
break;
if (rv != -1 && !WIFSTOPPED(status))
break;
}
return true; /* not debug */
}
/* Daemonize - disassociate from session/tty. */
if (setsid() == -1)
sudo_warn("setsid");
if (chdir("/") == -1)
sudo_warn("chdir(/)");
if ((fd = open(_PATH_DEVNULL, O_RDWR, 0644)) != -1) {
(void) dup2(fd, STDIN_FILENO);
(void) dup2(fd, STDOUT_FILENO);
(void) dup2(fd, STDERR_FILENO);
}
sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, NULL);
/* Close password, group and other fds so we don't leak. */
sudo_endpwent();
sudo_endgrent();
closefrom(STDERR_FILENO + 1);
/* Ignore SIGPIPE in case mailer exits prematurely (or is missing). */
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_INTERRUPT;
sa.sa_handler = SIG_IGN;
(void) sigaction(SIGPIPE, &sa, NULL);
if (pipe(pfd) == -1) {
mysyslog(LOG_ERR, _("unable to open pipe: %m"));
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to open pipe: %s",
strerror(errno));
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
_exit(1);
}
switch (pid = sudo_debug_fork()) {
case -1:
/* Error. */
mysyslog(LOG_ERR, _("unable to fork: %m"));
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to fork: %s",
strerror(errno));
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
_exit(1);
break;
case 0:
{
char *last, *argv[MAX_MAILFLAGS + 1];
char *mflags, *mpath = def_mailerpath;
int i;
/* Child, set stdin to output side of the pipe */
if (pfd[0] != STDIN_FILENO) {
if (dup2(pfd[0], STDIN_FILENO) == -1) {
mysyslog(LOG_ERR, _("unable to dup stdin: %m"));
sudo_debug_printf(SUDO_DEBUG_ERROR,
"unable to dup stdin: %s", strerror(errno));
_exit(127);
}
(void) close(pfd[0]);
}
(void) close(pfd[1]);
/* Build up an argv based on the mailer path and flags */
if ((mflags = strdup(def_mailerflags)) == NULL) {
mysyslog(LOG_ERR, _("unable to allocate memory"));
_exit(127);
}
if ((argv[0] = strrchr(mpath, '/')))
argv[0]++;
else
argv[0] = mpath;
i = 1;
if ((p = strtok_r(mflags, " \t", &last))) {
do {
argv[i] = p;
} while (++i < MAX_MAILFLAGS && (p = strtok_r(NULL, " \t", &last)));
}
argv[i] = NULL;
/*
* Depending on the config, either run the mailer as root
* (so user cannot kill it) or as the user (for the paranoid).
*/
#ifndef NO_ROOT_MAILER
(void) set_perms(PERM_ROOT);
execve(mpath, argv, root_envp);
#else
(void) set_perms(PERM_FULL_USER);
execv(mpath, argv);
#endif /* NO_ROOT_MAILER */
mysyslog(LOG_ERR, _("unable to execute %s: %m"), mpath);
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to execute %s: %s",
mpath, strerror(errno));
_exit(127);
}
break;
}
(void) close(pfd[0]);
mail = fdopen(pfd[1], "w");
/* 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++) {
/* 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);
}
#ifdef HAVE_NL_LANGINFO
if (strcmp(def_sudoers_locale, "C") != 0)
(void) fprintf(mail, "\nContent-Type: text/plain; charset=\"%s\"\nContent-Transfer-Encoding: 8bit", nl_langinfo(CODESET));
#endif /* HAVE_NL_LANGINFO */
if ((timestr = get_timestr(time(NULL), def_log_year)) == NULL)
timestr = "invalid date";
(void) fprintf(mail, "\n\n%s : %s : %s : ", user_host, timestr, user_name);
va_start(ap, fmt);
(void) vfprintf(mail, fmt, ap);
va_end(ap);
fputs("\n\n", mail);
fclose(mail);
for (;;) {
rv = waitpid(pid, &status, 0);
if (rv == -1 && errno != EINTR)
break;
if (rv != -1 && !WIFSTOPPED(status))
break;
}
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
_exit(0);
}
/*
* Determine whether we should send mail based on "status" and defaults options.
*/
static bool
should_mail(int status)
{
debug_decl(should_mail, SUDOERS_DEBUG_LOGGING)
debug_return_bool(def_mail_always || ISSET(status, VALIDATE_ERROR) ||
(def_mail_all_cmnds && ISSET(sudo_mode, (MODE_RUN|MODE_EDIT))) ||
(def_mail_no_user && ISSET(status, FLAG_NO_USER)) ||
(def_mail_no_host && ISSET(status, FLAG_NO_HOST)) ||
(def_mail_no_perms && !ISSET(status, VALIDATE_SUCCESS)));
}
#define LL_TTY_STR "TTY="
#define LL_CWD_STR "PWD=" /* XXX - should be CWD= */
#define LL_USER_STR "USER="
#define LL_GROUP_STR "GROUP="
#define LL_ENV_STR "ENV="
#define LL_CMND_STR "COMMAND="
#define LL_TSID_STR "TSID="
#define IS_SESSID(s) ( \
isalnum((unsigned char)(s)[0]) && isalnum((unsigned char)(s)[1]) && \
(s)[2] == '/' && \
isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
(s)[5] == '/' && \
isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
(s)[8] == '\0')
/*
* Allocate and fill in a new logline.
*/
static char *
new_logline(const char *message, int serrno)
{
char *line = NULL, *errstr = NULL, *evstr = NULL;
#ifndef SUDOERS_NO_SEQ
char sessid[7];
#endif
const char *tsid = NULL;
size_t len = 0;
debug_decl(new_logline, SUDOERS_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)) {
sessid[0] = sudo_user.iolog_file[0];
sessid[1] = sudo_user.iolog_file[1];
sessid[2] = sudo_user.iolog_file[3];
sessid[3] = sudo_user.iolog_file[4];
sessid[4] = sudo_user.iolog_file[6];
sessid[5] = sudo_user.iolog_file[7];
sessid[6] = '\0';
tsid = sessid;
} else {
tsid = sudo_user.iolog_file;
}
}
#endif
/*
* Compute line length
*/
if (message != NULL)
len += strlen(message) + 3;
if (serrno) {
errstr = strerror(serrno);
len += strlen(errstr) + 3;
}
len += sizeof(LL_TTY_STR) + 2 + strlen(user_tty);
len += sizeof(LL_CWD_STR) + 2 + strlen(user_cwd);
if (runas_pw != NULL)
len += sizeof(LL_USER_STR) + 2 + strlen(runas_pw->pw_name);
if (runas_gr != NULL)
len += sizeof(LL_GROUP_STR) + 2 + strlen(runas_gr->gr_name);
if (tsid != NULL)
len += sizeof(LL_TSID_STR) + 2 + strlen(tsid);
if (sudo_user.env_vars != NULL) {
size_t evlen = 0;
char * const *ep;
for (ep = sudo_user.env_vars; *ep != NULL; ep++)
evlen += strlen(*ep) + 1;
if (evlen != 0) {
if ((evstr = malloc(evlen)) == NULL)
goto oom;
evstr[0] = '\0';
for (ep = sudo_user.env_vars; *ep != NULL; ep++) {
strlcat(evstr, *ep, evlen);
strlcat(evstr, " ", evlen); /* NOTE: last one will fail */
}
len += sizeof(LL_ENV_STR) + 2 + evlen;
}
}
if (user_cmnd != NULL) {
/* Note: we log "sudo -l command arg ..." as "list command arg ..." */
len += sizeof(LL_CMND_STR) - 1 + strlen(user_cmnd);
if (ISSET(sudo_mode, MODE_CHECK))
len += sizeof("list ") - 1;
if (user_args != NULL)
len += strlen(user_args) + 1;
}
/*
* Allocate and build up the line.
*/
if ((line = malloc(++len)) == NULL)
goto oom;
line[0] = '\0';
if (message != NULL) {
if (strlcat(line, message, len) >= len ||
strlcat(line, errstr ? " : " : " ; ", len) >= len)
goto toobig;
}
if (serrno) {
if (strlcat(line, errstr, len) >= len ||
strlcat(line, " ; ", len) >= len)
goto toobig;
}
if (strlcat(line, LL_TTY_STR, len) >= len ||
strlcat(line, user_tty, len) >= len ||
strlcat(line, " ; ", len) >= len)
goto toobig;
if (strlcat(line, LL_CWD_STR, len) >= len ||
strlcat(line, user_cwd, len) >= len ||
strlcat(line, " ; ", len) >= len)
goto toobig;
if (runas_pw != NULL) {
if (strlcat(line, LL_USER_STR, len) >= len ||
strlcat(line, runas_pw->pw_name, len) >= len ||
strlcat(line, " ; ", len) >= len)
goto toobig;
}
if (runas_gr != NULL) {
if (strlcat(line, LL_GROUP_STR, len) >= len ||
strlcat(line, runas_gr->gr_name, len) >= len ||
strlcat(line, " ; ", len) >= len)
goto toobig;
}
if (tsid != NULL) {
if (strlcat(line, LL_TSID_STR, len) >= len ||
strlcat(line, tsid, len) >= len ||
strlcat(line, " ; ", len) >= len)
goto toobig;
}
if (evstr != NULL) {
if (strlcat(line, LL_ENV_STR, len) >= len ||
strlcat(line, evstr, len) >= len ||
strlcat(line, " ; ", len) >= len)
goto toobig;
free(evstr);
evstr = NULL;
}
if (user_cmnd != NULL) {
if (strlcat(line, LL_CMND_STR, len) >= len)
goto toobig;
if (ISSET(sudo_mode, MODE_CHECK) && strlcat(line, "list ", len) >= len)
goto toobig;
if (strlcat(line, user_cmnd, len) >= len)
goto toobig;
if (user_args != NULL) {
if (strlcat(line, " ", len) >= len ||
strlcat(line, user_args, len) >= len)
goto toobig;
}
}
debug_return_str(line);
oom:
free(evstr);
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_str(NULL);
toobig:
free(evstr);
free(line);
sudo_warnx(U_("internal error, %s overflow"), __func__);
debug_return_str(NULL);
}