diff options
Diffstat (limited to '')
-rw-r--r-- | logsrvd/logsrvd_conf.c | 1143 |
1 files changed, 1143 insertions, 0 deletions
diff --git a/logsrvd/logsrvd_conf.c b/logsrvd/logsrvd_conf.c new file mode 100644 index 0000000..d686ea6 --- /dev/null +++ b/logsrvd/logsrvd_conf.c @@ -0,0 +1,1143 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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. + */ + +#include "config.h" + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <grp.h> +#include <pwd.h> +#ifndef HAVE_GETADDRINFO +# include "compat/getaddrinfo.h" +#endif + +#include "pathnames.h" +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_eventlog.h" +#include "sudo_fatal.h" +#include "sudo_gettext.h" +#include "sudo_iolog.h" +#include "sudo_util.h" + +#include "log_server.pb-c.h" +#include "logsrvd.h" + +#if defined(HAVE_OPENSSL) +# define DEFAULT_CA_CERT_PATH "/etc/ssl/sudo/cacert.pem" +# define DEFAULT_SERVER_CERT_PATH "/etc/ssl/sudo/certs/logsrvd_cert.pem" +# define DEFAULT_SERVER_KEY_PATH "/etc/ssl/sudo/private/logsrvd_key.pem" +#endif + +struct logsrvd_config; +typedef bool (*logsrvd_conf_cb_t)(struct logsrvd_config *config, const char *); + +struct logsrvd_config_entry { + char *conf_str; + logsrvd_conf_cb_t setter; +}; + +struct logsrvd_config_section { + char *name; + struct logsrvd_config_entry *entries; +}; + +static struct logsrvd_config { + struct logsrvd_config_server { + struct listen_address_list addresses; + struct timespec timeout; + bool tcp_keepalive; + char *pid_file; +#if defined(HAVE_OPENSSL) + bool tls; + struct logsrvd_tls_config tls_config; + struct logsrvd_tls_runtime tls_runtime; +#endif + } server; + struct logsrvd_config_iolog { + bool compress; + bool flush; + bool gid_set; + uid_t uid; + gid_t gid; + mode_t mode; + unsigned int maxseq; + char *iolog_dir; + char *iolog_file; + } iolog; + struct logsrvd_config_eventlog { + int log_type; + enum eventlog_format log_format; + } eventlog; + struct logsrvd_config_syslog { + unsigned int maxlen; + int facility; + int acceptpri; + int rejectpri; + int alertpri; + } syslog; + struct logsrvd_config_logfile { + char *path; + char *time_format; + FILE *stream; + } logfile; +} *logsrvd_config; + +/* iolog getters */ +mode_t +logsrvd_conf_iolog_mode(void) +{ + return logsrvd_config->iolog.mode; +} + +const char * +logsrvd_conf_iolog_dir(void) +{ + return logsrvd_config->iolog.iolog_dir; +} + +const char * +logsrvd_conf_iolog_file(void) +{ + return logsrvd_config->iolog.iolog_file; +} + +/* server getters */ +struct listen_address_list * +logsrvd_conf_listen_address(void) +{ + return &logsrvd_config->server.addresses; +} + +bool +logsrvd_conf_tcp_keepalive(void) +{ + return logsrvd_config->server.tcp_keepalive; +} + +const char * +logsrvd_conf_pid_file(void) +{ + return logsrvd_config->server.pid_file; +} + +struct timespec * +logsrvd_conf_get_sock_timeout(void) +{ + if (sudo_timespecisset(&logsrvd_config->server.timeout)) { + return &(logsrvd_config->server.timeout); + } + + return NULL; +} + +#if defined(HAVE_OPENSSL) +const struct logsrvd_tls_config * +logsrvd_get_tls_config(void) +{ + return &logsrvd_config->server.tls_config; +} + +struct logsrvd_tls_runtime * +logsrvd_get_tls_runtime(void) +{ + return &logsrvd_config->server.tls_runtime; +} +#endif + +/* I/O log callbacks */ +static bool +cb_iolog_dir(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_iolog_dir, SUDO_DEBUG_UTIL); + + free(config->iolog.iolog_dir); + if ((config->iolog.iolog_dir = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_iolog_file(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_iolog_file, SUDO_DEBUG_UTIL); + + free(config->iolog.iolog_file); + if ((config->iolog.iolog_file = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_iolog_compress(struct logsrvd_config *config, const char *str) +{ + int val; + debug_decl(cb_iolog_compress, SUDO_DEBUG_UTIL); + + if ((val = sudo_strtobool(str)) == -1) + debug_return_bool(false); + + config->iolog.compress = val; + debug_return_bool(true); +} + +static bool +cb_iolog_flush(struct logsrvd_config *config, const char *str) +{ + int val; + debug_decl(cb_iolog_flush, SUDO_DEBUG_UTIL); + + if ((val = sudo_strtobool(str)) == -1) + debug_return_bool(false); + + config->iolog.flush = val; + debug_return_bool(true); +} + +static bool +cb_iolog_user(struct logsrvd_config *config, const char *user) +{ + struct passwd *pw; + debug_decl(cb_iolog_user, SUDO_DEBUG_UTIL); + + if ((pw = getpwnam(user)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unknown user %s", user); + debug_return_bool(false); + } + config->iolog.uid = pw->pw_uid; + if (!config->iolog.gid_set) + config->iolog.gid = pw->pw_gid; + + debug_return_bool(true); +} + +static bool +cb_iolog_group(struct logsrvd_config *config, const char *group) +{ + struct group *gr; + debug_decl(cb_iolog_group, SUDO_DEBUG_UTIL); + + if ((gr = getgrnam(group)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unknown group %s", group); + debug_return_bool(false); + } + config->iolog.gid = gr->gr_gid; + config->iolog.gid_set = true; + + debug_return_bool(true); +} + +static bool +cb_iolog_mode(struct logsrvd_config *config, const char *str) +{ + const char *errstr; + mode_t mode; + debug_decl(cb_iolog_mode, SUDO_DEBUG_UTIL); + + mode = sudo_strtomode(str, &errstr); + if (errstr != NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to parse iolog mode %s", str); + debug_return_bool(false); + } + config->iolog.mode = mode; + debug_return_bool(true); +} + +static bool +cb_iolog_maxseq(struct logsrvd_config *config, const char *str) +{ + const char *errstr; + unsigned int value; + debug_decl(cb_iolog_maxseq, SUDO_DEBUG_UTIL); + + value = sudo_strtonum(str, 0, SESSID_MAX, &errstr); + if (errstr != NULL) { + if (errno != ERANGE) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "bad maxseq: %s: %s", str, errstr); + debug_return_bool(false); + } + /* Out of range, clamp to SESSID_MAX as documented. */ + value = SESSID_MAX; + } + config->iolog.maxseq = value; + debug_return_bool(true); +} + +/* Server callbacks */ +static bool +cb_listen_address(struct logsrvd_config *config, const char *str) +{ + struct addrinfo hints, *res, *res0 = NULL; + char *copy, *host, *port; + bool tls, ret = false; + int error; + debug_decl(cb_iolog_mode, SUDO_DEBUG_UTIL); + + if ((copy = strdup(str)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + + /* Parse host[:port] */ + if (!iolog_parse_host_port(copy, &host, &port, &tls, DEFAULT_PORT, + DEFAULT_PORT_TLS)) + goto done; + if (host[0] == '*' && host[1] == '\0') + host = NULL; + +#if !defined(HAVE_OPENSSL) + if (tls) { + sudo_warnx("%s", U_("TLS not supported")); + goto done; + } +#endif + + /* Resolve host (and port if it is a service). */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + error = getaddrinfo(host, port, &hints, &res0); + if (error != 0) { + sudo_gai_warn(error, U_("%s:%s"), host ? host : "*", port); + goto done; + } + for (res = res0; res != NULL; res = res->ai_next) { + struct listen_address *addr; + + if ((addr = malloc(sizeof(*addr))) == NULL) { + sudo_warn(NULL); + goto done; + } + if ((addr->sa_str = strdup(str)) == NULL) { + sudo_warn(NULL); + free(addr); + goto done; + } + memcpy(&addr->sa_un, res->ai_addr, res->ai_addrlen); + addr->sa_size = res->ai_addrlen; + addr->tls = tls; + TAILQ_INSERT_TAIL(&config->server.addresses, addr, entries); + } + + ret = true; +done: + if (res0 != NULL) + freeaddrinfo(res0); + free(copy); + debug_return_bool(ret); +} + +static bool +cb_timeout(struct logsrvd_config *config, const char *str) +{ + int timeout; + const char* errstr; + debug_decl(cb_timeout, SUDO_DEBUG_UTIL); + + timeout = sudo_strtonum(str, 0, UINT_MAX, &errstr); + if (errstr != NULL) + debug_return_bool(false); + + config->server.timeout.tv_sec = timeout; + + debug_return_bool(true); +} + +static bool +cb_keepalive(struct logsrvd_config *config, const char *str) +{ + int val; + debug_decl(cb_keepalive, SUDO_DEBUG_UTIL); + + if ((val = sudo_strtobool(str)) == -1) + debug_return_bool(false); + + config->server.tcp_keepalive = val; + debug_return_bool(true); +} + +static bool +cb_pid_file(struct logsrvd_config *config, const char *str) +{ + char *copy = NULL; + debug_decl(cb_pid_file, SUDO_DEBUG_UTIL); + + /* An empty value means to disable the pid file. */ + if (*str != '\0') { + if (*str != '/') { + sudo_warnx(U_("%s: not a fully qualified path"), str); + debug_return_bool(false); + } + if ((copy = strdup(str)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + } + + free(config->server.pid_file); + config->server.pid_file = copy; + + debug_return_bool(true); +} + +#if defined(HAVE_OPENSSL) +static bool +cb_tls_key(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_tls_key, SUDO_DEBUG_UTIL); + + free(config->server.tls_config.pkey_path); + if ((config->server.tls_config.pkey_path = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_cacert(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_tls_cacert, SUDO_DEBUG_UTIL); + + free(config->server.tls_config.cacert_path); + if ((config->server.tls_config.cacert_path = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_cert(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_tls_cert, SUDO_DEBUG_UTIL); + + free(config->server.tls_config.cert_path); + if ((config->server.tls_config.cert_path = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_dhparam(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_tls_dhparam, SUDO_DEBUG_UTIL); + + free(config->server.tls_config.dhparams_path); + if ((config->server.tls_config.dhparams_path = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_ciphers12(struct logsrvd_config *config, const char *str) +{ + debug_decl(cb_tls_ciphers12, SUDO_DEBUG_UTIL); + + free(config->server.tls_config.ciphers_v12); + if ((config->server.tls_config.ciphers_v12 = strdup(str)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_ciphers13(struct logsrvd_config *config, const char *str) +{ + debug_decl(cb_tls_ciphers13, SUDO_DEBUG_UTIL); + + free(config->server.tls_config.ciphers_v13); + if ((config->server.tls_config.ciphers_v13 = strdup(str)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_verify(struct logsrvd_config *config, const char *str) +{ + int val; + debug_decl(cb_tls_verify, SUDO_DEBUG_UTIL); + + if ((val = sudo_strtobool(str)) == -1) + debug_return_bool(false); + + config->server.tls_config.verify = val; + debug_return_bool(true); +} + +static bool +cb_tls_checkpeer(struct logsrvd_config *config, const char *str) +{ + int val; + debug_decl(cb_tls_checkpeer, SUDO_DEBUG_UTIL); + + if ((val = sudo_strtobool(str)) == -1) + debug_return_bool(false); + + config->server.tls_config.check_peer = val; + debug_return_bool(true); +} +#endif + +/* eventlog callbacks */ +static bool +cb_eventlog_type(struct logsrvd_config *config, const char *str) +{ + debug_decl(cb_eventlog_type, SUDO_DEBUG_UTIL); + + if (strcmp(str, "none") == 0) + config->eventlog.log_type = EVLOG_NONE; + else if (strcmp(str, "syslog") == 0) + config->eventlog.log_type = EVLOG_SYSLOG; + else if (strcmp(str, "logfile") == 0) + config->eventlog.log_type = EVLOG_FILE; + else + debug_return_bool(false); + + debug_return_bool(true); +} + +static bool +cb_eventlog_format(struct logsrvd_config *config, const char *str) +{ + debug_decl(cb_eventlog_format, SUDO_DEBUG_UTIL); + + if (strcmp(str, "json") == 0) + config->eventlog.log_format = EVLOG_JSON; + else if (strcmp(str, "sudo") == 0) + config->eventlog.log_format = EVLOG_SUDO; + else + debug_return_bool(false); + + debug_return_bool(true); +} + +/* syslog callbacks */ +static bool +cb_syslog_maxlen(struct logsrvd_config *config, const char *str) +{ + unsigned int maxlen; + const char *errstr; + debug_decl(cb_syslog_maxlen, SUDO_DEBUG_UTIL); + + maxlen = sudo_strtonum(str, 1, UINT_MAX, &errstr); + if (errstr != NULL) + debug_return_bool(false); + + config->syslog.maxlen = maxlen; + + debug_return_bool(true); +} + +static bool +cb_syslog_facility(struct logsrvd_config *config, const char *str) +{ + int logfac; + debug_decl(cb_syslog_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.facility = logfac; + + debug_return_bool(true); +} + +static bool +cb_syslog_acceptpri(struct logsrvd_config *config, const char *str) +{ + int logpri; + debug_decl(cb_syslog_acceptpri, SUDO_DEBUG_UTIL); + + if (!sudo_str2logpri(str, &logpri)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "invalid syslog priority %s", str); + debug_return_bool(false); + } + + config->syslog.acceptpri = logpri; + + debug_return_bool(true); +} + +static bool +cb_syslog_rejectpri(struct logsrvd_config *config, const char *str) +{ + int logpri; + debug_decl(cb_syslog_rejectpri, SUDO_DEBUG_UTIL); + + if (!sudo_str2logpri(str, &logpri)) + debug_return_bool(false); + + config->syslog.rejectpri = logpri; + + debug_return_bool(true); +} + +static bool +cb_syslog_alertpri(struct logsrvd_config *config, const char *str) +{ + int logpri; + debug_decl(cb_syslog_alertpri, SUDO_DEBUG_UTIL); + + if (!sudo_str2logpri(str, &logpri)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "invalid syslog priority %s", str); + debug_return_bool(false); + } + + config->syslog.alertpri = logpri; + + debug_return_bool(true); +} + +/* logfile callbacks */ +static bool +cb_logfile_path(struct logsrvd_config *config, const char *str) +{ + char *copy = NULL; + debug_decl(cb_logfile_path, SUDO_DEBUG_UTIL); + + if (*str != '/') { + debug_return_bool(false); + sudo_warnx(U_("%s: not a fully qualified path"), str); + debug_return_bool(false); + } + if ((copy = strdup(str)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + + free(config->logfile.path); + config->logfile.path = copy; + + debug_return_bool(true); +} + +static bool +cb_logfile_time_format(struct logsrvd_config *config, const char *str) +{ + char *copy = NULL; + debug_decl(cb_logfile_time_format, SUDO_DEBUG_UTIL); + + if ((copy = strdup(str)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + + free(config->logfile.time_format); + config->logfile.time_format = copy; + + debug_return_bool(true); +} + +static struct logsrvd_config_entry server_conf_entries[] = { + { "listen_address", cb_listen_address }, + { "timeout", cb_timeout }, + { "tcp_keepalive", cb_keepalive }, + { "pid_file", cb_pid_file }, +#if defined(HAVE_OPENSSL) + { "tls_key", cb_tls_key }, + { "tls_cacert", cb_tls_cacert }, + { "tls_cert", cb_tls_cert }, + { "tls_dhparams", cb_tls_dhparam }, + { "tls_ciphers_v12", cb_tls_ciphers12 }, + { "tls_ciphers_v13", cb_tls_ciphers13 }, + { "tls_checkpeer", cb_tls_checkpeer }, + { "tls_verify", cb_tls_verify }, +#endif + { NULL } +}; + +static struct logsrvd_config_entry iolog_conf_entries[] = { + { "iolog_dir", cb_iolog_dir }, + { "iolog_file", cb_iolog_file }, + { "iolog_flush", cb_iolog_flush }, + { "iolog_compress", cb_iolog_compress }, + { "iolog_user", cb_iolog_user }, + { "iolog_group", cb_iolog_group }, + { "iolog_mode", cb_iolog_mode }, + { "maxseq", cb_iolog_maxseq }, + { NULL } +}; + +static struct logsrvd_config_entry eventlog_conf_entries[] = { + { "log_type", cb_eventlog_type }, + { "log_format", cb_eventlog_format }, + { NULL } +}; + +static struct logsrvd_config_entry syslog_conf_entries[] = { + { "maxlen", cb_syslog_maxlen }, + { "facility", cb_syslog_facility }, + { "reject_priority", cb_syslog_rejectpri }, + { "accept_priority", cb_syslog_acceptpri }, + { "alert_priority", cb_syslog_alertpri }, + { NULL } +}; + +static struct logsrvd_config_entry logfile_conf_entries[] = { + { "path", cb_logfile_path }, + { "time_format", cb_logfile_time_format }, + { NULL } +}; + +static struct logsrvd_config_section logsrvd_config_sections[] = { + { "server", server_conf_entries }, + { "iolog", iolog_conf_entries }, + { "eventlog", eventlog_conf_entries }, + { "syslog", syslog_conf_entries }, + { "logfile", logfile_conf_entries }, + { NULL } +}; + +static bool +logsrvd_conf_parse(struct logsrvd_config *config, FILE *fp, const char *path) +{ + struct logsrvd_config_section *conf_section = NULL; + unsigned int lineno = 0; + size_t linesize = 0; + char *line = NULL; + bool ret = false; + debug_decl(logsrvd_conf_parse, SUDO_DEBUG_UTIL); + + while (sudo_parseln(&line, &linesize, &lineno, fp, 0) != -1) { + struct logsrvd_config_entry *entry; + char *ep, *val; + + /* Skip blank, comment or invalid lines. */ + if (*line == '\0' || *line == ';') + continue; + + /* New section */ + if (line[0] == '[') { + char *section_name = line + 1; + char *cp = strchr(section_name, ']'); + if (cp == NULL) { + sudo_warnx(U_("%s:%d unmatched '[': %s"), + path, lineno, line); + goto done; + } + *cp = '\0'; + for (conf_section = logsrvd_config_sections; conf_section->name != NULL; + conf_section++) { + if (strcasecmp(section_name, conf_section->name) == 0) + break; + } + if (conf_section->name == NULL) { + sudo_warnx(U_("%s:%d invalid config section: %s"), + path, lineno, section_name); + goto done; + } + continue; + } + + if ((ep = strchr(line, '=')) == NULL) { + sudo_warnx(U_("%s:%d invalid configuration line: %s"), + path, lineno, line); + goto done; + } + + if (conf_section == NULL) { + sudo_warnx(U_("%s:%d expected section name: %s"), + path, lineno, line); + goto done; + } + + val = ep + 1; + while (isspace((unsigned char)*val)) + val++; + while (ep > line && isspace((unsigned char)ep[-1])) + ep--; + *ep = '\0'; + for (entry = conf_section->entries; entry->conf_str != NULL; entry++) { + if (strcasecmp(line, entry->conf_str) == 0) { + if (!entry->setter(config, val)) { + sudo_warnx(U_("invalid value for %s: %s"), + entry->conf_str, val); + goto done; + } + break; + } + } + if (entry->conf_str == NULL) { + sudo_warnx(U_("%s:%d unknown key: %s"), path, lineno, line); + goto done; + } + } + ret = true; + +done: + free(line); + debug_return_bool(ret); +} + +static FILE * +logsrvd_open_eventlog(struct logsrvd_config *config) +{ + mode_t oldmask; + FILE *fp = NULL; + const char *omode; + int fd, 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; + omode = "w"; + } else { + flags = O_WRONLY|O_APPEND|O_CREAT; + omode = "a"; + } + oldmask = umask(S_IRWXG|S_IRWXO); + fd = open(config->logfile.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); + if (fd != -1) + close(fd); + } + + debug_return_ptr(fp); +} + +static FILE * +logsrvd_stub_open_log(int type, const char *logfile) +{ + /* Actual open already done by logsrvd_open_eventlog() */ + return logsrvd_config->logfile.stream; +} + +static void +logsrvd_stub_close_log(int type, FILE *fp) +{ + return; +} + +/* Set eventlog configuration settings from on logsrvd config. */ +static void +logsrvd_conf_eventlog_setconf(struct logsrvd_config *config) +{ + debug_decl(logsrvd_conf_eventlog_setconf, SUDO_DEBUG_UTIL); + + eventlog_set_type(config->eventlog.log_type); + eventlog_set_format(config->eventlog.log_format); + eventlog_set_syslog_acceptpri(config->syslog.acceptpri); + eventlog_set_syslog_rejectpri(config->syslog.rejectpri); + eventlog_set_syslog_alertpri(config->syslog.alertpri); + eventlog_set_syslog_maxlen(config->syslog.maxlen); + eventlog_set_logpath(config->logfile.path); + eventlog_set_time_fmt(config->logfile.time_format); + eventlog_set_open_log(logsrvd_stub_open_log); + eventlog_set_close_log(logsrvd_stub_close_log); + + debug_return; +} + +/* Free the specified struct logsrvd_config and its contents. */ +void +logsrvd_conf_free(struct logsrvd_config *config) +{ + struct listen_address *addr; + debug_decl(logsrvd_conf_free, SUDO_DEBUG_UTIL); + + if (config == NULL) + debug_return; + + /* struct logsrvd_config_server */ + while ((addr = TAILQ_FIRST(&config->server.addresses))) { + TAILQ_REMOVE(&config->server.addresses, addr, entries); + free(addr->sa_str); + free(addr); + } + free(config->server.pid_file); + + /* struct logsrvd_config_iolog */ + free(config->iolog.iolog_dir); + free(config->iolog.iolog_file); + + /* struct logsrvd_config_logfile */ + free(config->logfile.path); + free(config->logfile.time_format); + if (config->logfile.stream != NULL) + fclose(config->logfile.stream); + +#if defined(HAVE_OPENSSL) + free(config->server.tls_config.pkey_path); + free(config->server.tls_config.cert_path); + free(config->server.tls_config.cacert_path); + free(config->server.tls_config.dhparams_path); + free(config->server.tls_config.ciphers_v12); + free(config->server.tls_config.ciphers_v13); + + if (config->server.tls_runtime.ssl_ctx != NULL) + SSL_CTX_free(config->server.tls_runtime.ssl_ctx); +#endif + + free(config); + + debug_return; +} + +/* Allocate a new struct logsrvd_config and set default values. */ +struct logsrvd_config * +logsrvd_conf_alloc(void) +{ + struct logsrvd_config *config; + debug_decl(logsrvd_conf_alloc, SUDO_DEBUG_UTIL); + + if ((config = calloc(1, sizeof(*config))) == NULL) { + sudo_warn(NULL); + debug_return_ptr(NULL); + } + + /* Server defaults */ + TAILQ_INIT(&config->server.addresses); + config->server.timeout.tv_sec = DEFAULT_SOCKET_TIMEOUT_SEC; + config->server.tcp_keepalive = true; + config->server.pid_file = strdup(_PATH_SUDO_LOGSRVD_PID); + if (config->server.pid_file == NULL) { + sudo_warn(NULL); + goto bad; + } + +#if defined(HAVE_OPENSSL) + /* + * Only set default CA and cert paths if the files actually exist. + * This ensures we don't enable TLS by default when it is not configured. + */ + if (access(DEFAULT_CA_CERT_PATH, R_OK) == 0) { + config->server.tls_config.cacert_path = strdup(DEFAULT_CA_CERT_PATH); + if (config->server.tls_config.cacert_path == NULL) { + sudo_warn(NULL); + goto bad; + } + } + if (access(DEFAULT_SERVER_CERT_PATH, R_OK) == 0) { + config->server.tls_config.cert_path = strdup(DEFAULT_SERVER_CERT_PATH); + if (config->server.tls_config.cert_path == NULL) { + sudo_warn(NULL); + goto bad; + } + } + config->server.tls_config.pkey_path = strdup(DEFAULT_SERVER_KEY_PATH); + if (config->server.tls_config.pkey_path == NULL) { + sudo_warn(NULL); + goto bad; + } + config->server.tls_config.verify = true; + config->server.tls_config.check_peer = false; +#endif + + /* I/O log defaults */ + config->iolog.compress = false; + config->iolog.flush = true; + config->iolog.mode = S_IRUSR|S_IWUSR; + config->iolog.maxseq = SESSID_MAX; + if (!cb_iolog_dir(config, _PATH_SUDO_IO_LOGDIR)) + goto bad; + if (!cb_iolog_file(config, "%{seq}")) + goto bad; + config->iolog.uid = ROOT_UID; + config->iolog.gid = ROOT_GID; + config->iolog.gid_set = false; + + /* Event log defaults */ + config->eventlog.log_type = EVLOG_SYSLOG; + config->eventlog.log_format = EVLOG_SUDO; + + /* Syslog defaults */ + config->syslog.maxlen = 960; + if (!cb_syslog_facility(config, LOGFAC)) { + sudo_warnx(U_("unknown syslog facility %s"), LOGFAC); + goto bad; + } + if (!cb_syslog_acceptpri(config, PRI_SUCCESS)) { + sudo_warnx(U_("unknown syslog priority %s"), PRI_SUCCESS); + goto bad; + } + if (!cb_syslog_rejectpri(config, PRI_FAILURE)) { + sudo_warnx(U_("unknown syslog priority %s"), PRI_FAILURE); + goto bad; + } + if (!cb_syslog_alertpri(config, PRI_FAILURE)) { + sudo_warnx(U_("unknown syslog priority %s"), PRI_FAILURE); + goto bad; + } + + /* Log file defaults */ + if (!cb_logfile_time_format(config, "%h %e %T")) + goto bad; + if (!cb_logfile_path(config, _PATH_SUDO_LOGFILE)) + goto bad; + + debug_return_ptr(config); +bad: + logsrvd_conf_free(config); + debug_return_ptr(NULL); +} + +bool +logsrvd_conf_apply(struct logsrvd_config *config) +{ + debug_decl(logsrvd_conf_apply, SUDO_DEBUG_UTIL); + + /* There can be multiple addresses so we can't set a default earlier. */ + if (TAILQ_EMPTY(&config->server.addresses)) { + /* Enable plaintext listender. */ + if (!cb_listen_address(config, "*:" DEFAULT_PORT)) + debug_return_bool(false); +#if defined(HAVE_OPENSSL) + /* If a certificate was specified, enable the TLS listener too. */ + if (config->server.tls_config.cert_path != NULL) { + if (!cb_listen_address(config, "*:" DEFAULT_PORT_TLS "(tls)")) + debug_return_bool(false); + } + } else { + struct listen_address *addr; + + /* Check that TLS configuration is valid. */ + TAILQ_FOREACH(addr, &config->server.addresses, entries) { + if (!addr->tls) + continue; + /* + * If a TLS listener was explicitly enabled but the cert path + * was not, use the default. + */ + if (config->server.tls_config.cert_path == NULL) { + config->server.tls_config.cert_path = + strdup(DEFAULT_SERVER_CERT_PATH); + if (config->server.tls_config.cert_path == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + } + break; + } +#endif + } + + /* Open event log if specified. */ + switch (config->eventlog.log_type) { + case EVLOG_SYSLOG: + openlog("sudo", 0, config->syslog.facility); + break; + case EVLOG_FILE: + config->logfile.stream = logsrvd_open_eventlog(config); + if (config->logfile.stream == NULL) + debug_return_bool(false); + break; + case EVLOG_NONE: + break; + default: + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "cannot open unknown log type %d", config->eventlog.log_type); + break; + } + + /* Set I/O log library settings */ + iolog_set_defaults(); + iolog_set_compress(config->iolog.compress); + iolog_set_flush(config->iolog.flush); + iolog_set_owner(config->iolog.uid, config->iolog.gid); + iolog_set_mode(config->iolog.mode); + iolog_set_maxseq(config->iolog.maxseq); + + /* Set event log config */ + logsrvd_conf_eventlog_setconf(config); + + logsrvd_conf_free(logsrvd_config); + logsrvd_config = config; + + debug_return_bool(true); +} + +/* + * Read .ini style logsrvd.conf file. + * Note that we use '#' not ';' for the comment character. + */ +bool +logsrvd_conf_read(const char *path) +{ + struct logsrvd_config *config; + bool ret = false; + FILE *fp = NULL; + debug_decl(logsrvd_conf_read, SUDO_DEBUG_UTIL); + + config = logsrvd_conf_alloc(); + + if ((fp = fopen(path, "r")) == NULL) { + if (errno != ENOENT) { + sudo_warn("%s", path); + goto done; + } + } else { + if (!logsrvd_conf_parse(config, fp, path)) + goto done; + } + + /* Install new config */ + if (logsrvd_conf_apply(config)) { + config = NULL; + ret = true; + } + +done: + logsrvd_conf_free(config); + if (fp != NULL) + fclose(fp); + debug_return_bool(ret); +} |