Add support for logging server warning/error messages.

We can use sudo_warn_set_conversation() to set a conversation
function that either writes to a log file or calls syslog().
This commit is contained in:
Todd C. Miller
2021-06-13 18:27:36 -06:00
parent df1895f66f
commit 2c1988410e
6 changed files with 381 additions and 22 deletions

View File

@@ -16,7 +16,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.TH "SUDO_LOGSRVD.CONF" "@mansectform@" "May 1, 2021" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.TH "SUDO_LOGSRVD.CONF" "@mansectform@" "June 13, 2021" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.nh
.if n .ad l
.SH "NAME"
@@ -128,6 +128,23 @@ Multiple
lines may be specified to listen on more than one port or interface.
.RE
.TP 10n
server_log = string
Where to log server warning and error messages.
Supported values are
\fInone\fR,
\fIstderr\fR,
\fIsyslog\fR,
or a path name beginning with the
\(oq/\(cq
character.
Note that a value of
\fIstderr\fR
is only effective when used in conjunction with the
\fB\-n\fR
option.
The default value is
\fIsyslog\fR.
.TP 10n
pid_file = path
The path to the file containing the process ID of the running
\fBsudo_logsrvd\fR.
@@ -704,6 +721,12 @@ When a message is split, additional parts will include the string
after the user name and before the continued command line arguments.
JSON-format log entries are never split and are not affected by
\fImaxlen\fR.
.TP 6n
server_facility = string
Syslog facility if syslog is being used for server warning messages.
See above for a list of supported facilities.
Defaults to
\fRdaemon\fR
.SS "logfile"
The
\fIlogfile\fR
@@ -761,6 +784,9 @@ Sudo log server configuration file
# The file containing the ID of the running sudo_logsrvd process.
#pid_file = @rundir@/sudo_logsrvd.pid
# Where to log server warnings: none, stderr, syslog, or a path name.
#server_log = syslog
# If true, enable the SO_KEEPALIVE socket option on client connections.
#tcp_keepalive = true
@@ -955,6 +981,10 @@ Sudo log server configuration file
# client.
#alert_priority = alert
# The syslog facility to use for server warning messages.
# Defaults to daemon.
#server_facility = daemon
[logfile]
# The path to the file-based event log.
# This path must be fully-qualified and start with a '/' character.

View File

@@ -15,7 +15,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd May 1, 2021
.Dd June 13, 2021
.Dt SUDO_LOGSRVD.CONF @mansectform@
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@@ -114,6 +114,22 @@ plaintext and TLS connections.
Multiple
.Em listen_address
lines may be specified to listen on more than one port or interface.
.It server_log = string
Where to log server warning and error messages.
Supported values are
.Em none ,
.Em stderr ,
.Em syslog ,
or a path name beginning with the
.Ql /
character.
Note that a value of
.Em stderr
is only effective when used in conjunction with the
.Fl n
option.
The default value is
.Em syslog .
.It pid_file = path
The path to the file containing the process ID of the running
.Nm sudo_logsrvd .
@@ -634,6 +650,11 @@ When a message is split, additional parts will include the string
after the user name and before the continued command line arguments.
JSON-format log entries are never split and are not affected by
.Em maxlen .
.It server_facility = string
Syslog facility if syslog is being used for server warning messages.
See above for a list of supported facilities.
Defaults to
.Li daemon
.El
.Ss logfile
The
@@ -692,6 +713,9 @@ Sudo log server configuration file
# The file containing the ID of the running sudo_logsrvd process.
#pid_file = @rundir@/sudo_logsrvd.pid
# Where to log server warnings: none, stderr, syslog, or a path name.
#server_log = syslog
# If true, enable the SO_KEEPALIVE socket option on client connections.
#tcp_keepalive = true
@@ -886,6 +910,10 @@ Sudo log server configuration file
# client.
#alert_priority = alert
# The syslog facility to use for server warning messages.
# Defaults to daemon.
#server_facility = daemon
[logfile]
# The path to the file-based event log.
# This path must be fully-qualified and start with a '/' character.

View File

@@ -24,6 +24,9 @@
# The file containing the ID of the running sudo_logsrvd process.
#pid_file = /var/run/sudo/sudo_logsrvd.pid
# Where to log server warnings: none, stderr, syslog, or a path name.
#server_log = syslog
# If true, enable the SO_KEEPALIVE socket option on client connections.
#tcp_keepalive = true
@@ -219,6 +222,10 @@
# client.
#alert_priority = alert
# The syslog facility to use for server warning messages.
# Defaults to daemon.
#server_facility = daemon
[logfile]
# The path to the file-based event log.
# This path must be fully-qualified and start with a '/' character.

View File

@@ -87,6 +87,7 @@ static struct connection_list connections = TAILQ_HEAD_INITIALIZER(connections);
static struct listener_list listeners = TAILQ_HEAD_INITIALIZER(listeners);
static const char server_id[] = "Sudo Audit Server " PACKAGE_VERSION;
static const char *conf_file = _PATH_SUDO_LOGSRVD_CONF;
static bool is_early = true;
/* Event loop callbacks. */
static void client_msg_cb(int fd, int what, void *v);
@@ -1819,6 +1820,9 @@ daemonize(bool nofork)
int fd;
debug_decl(daemonize, SUDO_DEBUG_UTIL);
if (chdir("/") == -1)
sudo_warn("chdir(\"/\")");
if (!nofork) {
switch (sudo_debug_fork()) {
case -1:
@@ -1835,21 +1839,38 @@ daemonize(bool nofork)
if (setsid() == -1)
sudo_fatal("setsid");
write_pidfile();
}
if (chdir("/") == -1)
sudo_warn("chdir(\"/\")");
if ((fd = open(_PATH_DEVNULL, O_RDWR)) != -1) {
(void) dup2(fd, STDIN_FILENO);
(void) dup2(fd, STDOUT_FILENO);
(void) dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
(void) close(fd);
if ((fd = open(_PATH_DEVNULL, O_RDWR)) != -1) {
(void) dup2(fd, STDIN_FILENO);
(void) dup2(fd, STDOUT_FILENO);
(void) dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
(void) close(fd);
}
} else {
if ((fd = open(_PATH_DEVNULL, O_RDWR)) != -1) {
/* Preserve stdout/stderr in nofork mode (if open). */
(void) dup2(fd, STDIN_FILENO);
if (fcntl(STDOUT_FILENO, F_GETFL) == -1)
(void) dup2(fd, STDOUT_FILENO);
if (fcntl(STDERR_FILENO, F_GETFL) == -1)
(void) dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
(void) close(fd);
}
}
is_early = false;
debug_return;
}
/* The early flag is used to decide whether sudo_warn() goes to stderr too. */
bool
logsrvd_is_early(void)
{
return is_early;
}
static void
usage(bool fatal)
{

View File

@@ -197,6 +197,7 @@ bool fmt_log_id_message(const char *id, struct connection_closure *closure);
bool schedule_error_message(const char *errstr, struct connection_closure *closure);
struct connection_buffer *get_free_buf(size_t, struct connection_closure *closure);
struct connection_closure *connection_closure_alloc(int fd, bool tls, bool relay_only, struct sudo_event_base *base);
bool logsrvd_is_early(void);
/* logsrvd_conf.c */
bool logsrvd_conf_read(const char *path);

View File

@@ -78,6 +78,13 @@
((_c)->relay._f != -1 ? (_c)->relay._f : (_c)->server._f)
#endif
enum server_log_type {
SERVER_LOG_NONE,
SERVER_LOG_STDERR,
SERVER_LOG_SYSLOG,
SERVER_LOG_FILE
};
struct logsrvd_config;
typedef bool (*logsrvd_conf_cb_t)(struct logsrvd_config *, const char *, size_t);
@@ -102,6 +109,9 @@ static struct logsrvd_config {
struct address_list_container addresses;
struct timespec timeout;
bool tcp_keepalive;
enum server_log_type log_type;
FILE *log_stream;
char *log_file;
char *pid_file;
#if defined(HAVE_OPENSSL)
char *tls_key_path;
@@ -152,6 +162,7 @@ static struct logsrvd_config {
} eventlog;
struct logsrvd_config_syslog {
unsigned int maxlen;
int server_facility;
int facility;
int acceptpri;
int rejectpri;
@@ -560,6 +571,37 @@ cb_server_pid_file(struct logsrvd_config *config, const char *str, size_t offset
debug_return_bool(true);
}
static bool
cb_server_log(struct logsrvd_config *config, const char *str, size_t offset)
{
char *copy = NULL;
enum server_log_type log_type = SERVER_LOG_NONE;
debug_decl(cb_server_log, SUDO_DEBUG_UTIL);
/* An empty value means to disable the server log. */
if (*str != '\0') {
if (*str != '/') {
log_type = SERVER_LOG_FILE;
if ((copy = strdup(str)) == NULL) {
sudo_warn(NULL);
debug_return_bool(false);
}
} else if (strcmp(str, "stderr") == 0) {
log_type = SERVER_LOG_STDERR;
} else if (strcmp(str, "syslog") == 0) {
log_type = SERVER_LOG_SYSLOG;
} else {
debug_return_bool(false);
}
}
free(config->server.log_file);
config->server.log_file = copy;
config->server.log_type = log_type;
debug_return_bool(true);
}
#if defined(HAVE_OPENSSL)
static bool
cb_tls_key(struct logsrvd_config *config, const char *path, size_t offset)
@@ -806,6 +848,23 @@ cb_syslog_maxlen(struct logsrvd_config *config, const char *str, size_t offset)
debug_return_bool(true);
}
static bool
cb_syslog_server_facility(struct logsrvd_config *config, const char *str, size_t offset)
{
int logfac;
debug_decl(cb_syslog_server_facility, SUDO_DEBUG_UTIL);
if (!sudo_str2logfac(str, &logfac)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"invalid syslog priority %s", str);
debug_return_bool(false);
}
config->syslog.server_facility = logfac;
debug_return_bool(true);
}
static bool
cb_syslog_facility(struct logsrvd_config *config, const char *str, size_t offset)
{
@@ -940,6 +999,7 @@ static struct logsrvd_config_entry server_conf_entries[] = {
{ "timeout", cb_server_timeout },
{ "tcp_keepalive", cb_server_keepalive },
{ "pid_file", cb_server_pid_file },
{ "server_log", cb_server_log },
#if defined(HAVE_OPENSSL)
{ "tls_key", cb_tls_key, offsetof(struct logsrvd_config, server.tls_key_path) },
{ "tls_cacert", cb_tls_cacert, offsetof(struct logsrvd_config, server.tls_cacert_path) },
@@ -993,6 +1053,7 @@ static struct logsrvd_config_entry eventlog_conf_entries[] = {
static struct logsrvd_config_entry syslog_conf_entries[] = {
{ "maxlen", cb_syslog_maxlen },
{ "server_facility", cb_syslog_server_facility },
{ "facility", cb_syslog_facility },
{ "reject_priority", cb_syslog_rejectpri },
{ "accept_priority", cb_syslog_acceptpri },
@@ -1098,28 +1159,25 @@ done:
}
static FILE *
logsrvd_open_eventlog(struct logsrvd_config *config)
logsrvd_open_log_file(const char *path, int flags)
{
mode_t oldmask;
FILE *fp = NULL;
const char *omode;
int fd, flags;
debug_decl(logsrvd_open_eventlog, SUDO_DEBUG_UTIL);
int fd;
debug_decl(logsrvd_open_log_file, SUDO_DEBUG_UTIL);
/* Cannot append to a JSON file. */
if (config->eventlog.log_format == EVLOG_JSON) {
flags = O_RDWR|O_CREAT;
omode = "w";
} else {
flags = O_WRONLY|O_APPEND|O_CREAT;
if (ISSET(flags, O_APPEND)) {
omode = "a";
} else {
omode = "w";
}
oldmask = umask(S_IRWXG|S_IRWXO);
fd = open(config->logfile.path, flags, S_IRUSR|S_IWUSR);
fd = open(path, flags, S_IRUSR|S_IWUSR);
(void)umask(oldmask);
if (fd == -1 || (fp = fdopen(fd, omode)) == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to open log file %s", config->logfile.path);
"unable to open log file %s", path);
if (fd != -1)
close(fd);
}
@@ -1127,6 +1185,21 @@ logsrvd_open_eventlog(struct logsrvd_config *config)
debug_return_ptr(fp);
}
static FILE *
logsrvd_open_eventlog(struct logsrvd_config *config)
{
int flags;
debug_decl(logsrvd_open_eventlog, SUDO_DEBUG_UTIL);
/* Cannot append to a JSON file. */
if (config->eventlog.log_format == EVLOG_JSON) {
flags = O_RDWR|O_CREAT;
} else {
flags = O_WRONLY|O_APPEND|O_CREAT;
}
debug_return_ptr(logsrvd_open_log_file(config->logfile.path, flags));
}
static FILE *
logsrvd_stub_open_log(int type, const char *logfile)
{
@@ -1160,6 +1233,175 @@ logsrvd_conf_eventlog_setconf(struct logsrvd_config *config)
debug_return;
}
/*
* Conversation function for use by sudo_warn/sudo_fatal.
* Logs to stdout/stderr.
*/
static int
logsrvd_conv_stderr(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
{
int i;
debug_decl(logsrvd_conv_stderr, SUDO_DEBUG_UTIL);
for (i = 0; i < num_msgs; i++) {
if (fputs(msgs[i].msg, stderr) == EOF)
debug_return_int(-1);
}
debug_return_int(0);
}
/*
* Conversation function for use by sudo_warn/sudo_fatal.
* Acts as a no-op log sink.
*/
static int
logsrvd_conv_none(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
{
/* Also write to stderr if still in the foreground. */
if (logsrvd_is_early()) {
(void)logsrvd_conv_stderr(num_msgs, msgs, replies, callback);
}
return 0;
}
/*
* Conversation function for use by sudo_warn/sudo_fatal.
* Logs to syslog.
*/
static int
logsrvd_conv_syslog(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
{
char *buf = NULL, *cp = NULL;
const char *progname;
size_t proglen, bufsize = 0;
int i;
debug_decl(logsrvd_conv_syslog, SUDO_DEBUG_UTIL);
/* Also write to stderr if still in the foreground. */
if (logsrvd_is_early()) {
(void)logsrvd_conv_stderr(num_msgs, msgs, replies, callback);
}
/*
* Concat messages into a flag string that we can syslog.
*/
progname = getprogname();
proglen = strlen(progname);
for (i = 0; i < num_msgs; i++) {
const char *msg = msgs[i].msg;
size_t len = strlen(msg);
size_t used = (size_t)(cp - buf);
/* Strip leading "sudo_logsrvd: " prefix. */
if (strncmp(msg, progname, proglen) == 0) {
msg += proglen;
len -= proglen;
if (len == 0) {
/* Skip over ": " string that follows program name. */
if (i + 1 < num_msgs && strcmp(msgs[i + 1].msg, ": ") == 0) {
i++;
continue;
}
} else if (msg[0] == ':' && msg[1] == ' ') {
/* Handle "progname: " */
msg += 2;
len -= 2;
}
}
/* Strip off trailing newlines. */
while (len > 1 && msgs[i].msg[len - 1] == '\n')
len--;
if (len == 0)
continue;
if (len >= bufsize - used) {
bufsize += 1024;
char *tmp = realloc(buf, bufsize);
if (tmp == NULL) {
free(buf);
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to allocate memory");
debug_return_int(-1);
}
buf = tmp;
cp = tmp + used;
}
memcpy(cp, msgs[i].msg, len);
cp[len] = '\0';
cp += len;
}
if (buf != NULL) {
openlog(progname, 0, logsrvd_config->syslog.server_facility);
syslog(LOG_ERR, "%s", buf);
free(buf);
/* Restore old syslog settings. */
if (logsrvd_config->eventlog.log_type == EVLOG_SYSLOG)
openlog("sudo", 0, logsrvd_config->syslog.facility);
}
debug_return_int(0);
}
/*
* Conversation function for use by sudo_warn/sudo_fatal.
* Logs to an already-open log file.
*/
static int
logsrvd_conv_logfile(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
{
const char *progname;
size_t proglen;
int i;
debug_decl(logsrvd_conv_logfile, SUDO_DEBUG_UTIL);
/* Also write to stderr if still in the foreground. */
if (logsrvd_is_early()) {
(void)logsrvd_conv_stderr(num_msgs, msgs, replies, callback);
}
if (logsrvd_config->server.log_stream == NULL) {
errno = EBADF;
debug_return_int(-1);
}
progname = getprogname();
proglen = strlen(progname);
for (i = 0; i < num_msgs; i++) {
const char *msg = msgs[i].msg;
size_t len = strlen(msg);
/* Strip leading "sudo_logsrvd: " prefix. */
if (strncmp(msg, progname, proglen) == 0) {
msg += proglen;
len -= proglen;
if (len == 0) {
/* Skip over ": " string that follows program name. */
if (i + 1 < num_msgs && strcmp(msgs[i + 1].msg, ": ") == 0) {
i++;
continue;
}
} else if (msg[0] == ':' && msg[1] == ' ') {
/* Handle "progname: " */
msg += 2;
len -= 2;
}
}
if (fwrite(msgs[i].msg, len, 1, logsrvd_config->server.log_stream) != 1)
debug_return_int(-1);
}
debug_return_int(0);
}
/* Free the specified struct logsrvd_config and its contents. */
static void
logsrvd_conf_free(struct logsrvd_config *config)
@@ -1172,6 +1414,9 @@ logsrvd_conf_free(struct logsrvd_config *config)
/* struct logsrvd_config_server */
address_list_delref(&config->server.addresses.addrs);
free(config->server.pid_file);
free(config->server.log_file);
if (config->server.log_stream != NULL)
fclose(config->server.log_stream);
#if defined(HAVE_OPENSSL)
free(config->server.tls_key_path);
free(config->server.tls_cert_path);
@@ -1245,6 +1490,7 @@ logsrvd_conf_alloc(void)
config->server.addresses.refcnt = 1;
config->server.timeout.tv_sec = DEFAULT_SOCKET_TIMEOUT_SEC;
config->server.tcp_keepalive = true;
config->server.log_type = SERVER_LOG_SYSLOG;
config->server.pid_file = strdup(_PATH_SUDO_LOGSRVD_PID);
if (config->server.pid_file == NULL) {
sudo_warn(NULL);
@@ -1298,6 +1544,7 @@ logsrvd_conf_alloc(void)
/* Syslog defaults */
config->syslog.maxlen = 960;
config->syslog.server_facility = LOG_DAEMON;
if (!cb_syslog_facility(config, LOGFAC, 0)) {
sudo_warnx(U_("unknown syslog facility %s"), LOGFAC);
goto bad;
@@ -1411,6 +1658,31 @@ logsrvd_conf_apply(struct logsrvd_config *config)
if (TAILQ_EMPTY(&config->relay.relays.addrs))
config->relay.store_first = false;
/* Open server log if specified. */
switch (config->server.log_type) {
case SERVER_LOG_SYSLOG:
sudo_warn_set_conversation(logsrvd_conv_syslog);
break;
case SERVER_LOG_FILE:
config->server.log_stream =
logsrvd_open_log_file(config->server.log_file, O_WRONLY|O_APPEND|O_CREAT);
if (config->server.log_stream == NULL)
debug_return_bool(false);
sudo_warn_set_conversation(logsrvd_conv_logfile);
break;
case SERVER_LOG_NONE:
sudo_warn_set_conversation(logsrvd_conv_none);
break;
case SERVER_LOG_STDERR:
/* Default is stderr. */
sudo_warn_set_conversation(NULL);
break;
default:
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"cannot open unknown log type %d", config->eventlog.log_type);
break;
}
/* Open event log if specified. */
switch (config->eventlog.log_type) {
case EVLOG_SYSLOG: