diff options
author | Todd C. Miller <Todd.Miller@sudo.ws> | 2021-06-13 18:27:36 -0600 |
---|---|---|
committer | Todd C. Miller <Todd.Miller@sudo.ws> | 2021-06-13 18:27:36 -0600 |
commit | 344e9d691e7b29f2b104bce868d86439f74d6bbf (patch) | |
tree | b866a5f3e54b381dcb17df63aeb6d712770a9c6e | |
parent | 2f3f3774a5269644b90effe3a36ec13c2ae7c150 (diff) | |
download | sudo-344e9d691e7b29f2b104bce868d86439f74d6bbf.tar.gz |
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().
-rw-r--r-- | doc/sudo_logsrvd.conf.man.in | 32 | ||||
-rw-r--r-- | doc/sudo_logsrvd.conf.mdoc.in | 30 | ||||
-rw-r--r-- | examples/sudo_logsrvd.conf | 7 | ||||
-rw-r--r-- | logsrvd/logsrvd.c | 39 | ||||
-rw-r--r-- | logsrvd/logsrvd.h | 1 | ||||
-rw-r--r-- | logsrvd/logsrvd_conf.c | 294 |
6 files changed, 381 insertions, 22 deletions
diff --git a/doc/sudo_logsrvd.conf.man.in b/doc/sudo_logsrvd.conf.man.in index ea21f10af..fcd4aa7f3 100644 --- a/doc/sudo_logsrvd.conf.man.in +++ b/doc/sudo_logsrvd.conf.man.in @@ -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. diff --git a/doc/sudo_logsrvd.conf.mdoc.in b/doc/sudo_logsrvd.conf.mdoc.in index b8f1f6911..c8254ac1d 100644 --- a/doc/sudo_logsrvd.conf.mdoc.in +++ b/doc/sudo_logsrvd.conf.mdoc.in @@ -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. diff --git a/examples/sudo_logsrvd.conf b/examples/sudo_logsrvd.conf index e73cd9998..2d42a6ab9 100644 --- a/examples/sudo_logsrvd.conf +++ b/examples/sudo_logsrvd.conf @@ -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. diff --git a/logsrvd/logsrvd.c b/logsrvd/logsrvd.c index a5d16730a..d019836e4 100644 --- a/logsrvd/logsrvd.c +++ b/logsrvd/logsrvd.c @@ -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) { diff --git a/logsrvd/logsrvd.h b/logsrvd/logsrvd.h index 69bae730e..3d4575f4e 100644 --- a/logsrvd/logsrvd.h +++ b/logsrvd/logsrvd.h @@ -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); diff --git a/logsrvd/logsrvd_conf.c b/logsrvd/logsrvd_conf.c index e380cd7ac..18d46b11f 100644 --- a/logsrvd/logsrvd_conf.c +++ b/logsrvd/logsrvd_conf.c @@ -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) @@ -807,6 +849,23 @@ cb_syslog_maxlen(struct logsrvd_config *config, const char *str, size_t offset) } 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) { int logfac; @@ -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); } @@ -1128,6 +1186,21 @@ logsrvd_open_eventlog(struct logsrvd_config *config) } 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) { /* Actual open already done by logsrvd_open_eventlog() */ @@ -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: |