From c2d3070fa17661cba9d1e4f6405d962cfd1b5c01 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Fri, 23 Apr 2021 16:55:30 -0600 Subject: [PATCH] Journal messages to disk when store_first is set in the relay section. Instead of forwarding messages immediately, they are journaled locally in wire format. This will be used to implement relay store-and-forward mode. --- MANIFEST | 1 + logsrvd/Makefile.in | 52 ++++-- logsrvd/iolog_writer.c | 2 +- logsrvd/logsrvd.c | 225 +++++++++++++++--------- logsrvd/logsrvd.h | 9 + logsrvd/logsrvd_conf.c | 4 + logsrvd/logsrvd_journal.c | 352 ++++++++++++++++++++++++++++++++++++++ logsrvd/logsrvd_relay.c | 8 - 8 files changed, 549 insertions(+), 104 deletions(-) create mode 100644 logsrvd/logsrvd_journal.c diff --git a/MANIFEST b/MANIFEST index 114feb91a..c8296e523 100644 --- a/MANIFEST +++ b/MANIFEST @@ -349,6 +349,7 @@ logsrvd/logsrv_util.h logsrvd/logsrvd.c logsrvd/logsrvd.h logsrvd/logsrvd_conf.c +logsrvd/logsrvd_journal.c logsrvd/logsrvd_relay.c logsrvd/regress/corpus/seed/logsrvd_conf/logsrvd.conf.1 logsrvd/regress/corpus/seed/logsrvd_conf/logsrvd.conf.2 diff --git a/logsrvd/Makefile.in b/logsrvd/Makefile.in index 7a1d4c2af..a30031056 100644 --- a/logsrvd/Makefile.in +++ b/logsrvd/Makefile.in @@ -120,7 +120,7 @@ SHELL = @SHELL@ PROGS = sudo_logsrvd sudo_sendlog LOGSRVD_OBJS = logsrv_util.o iolog_writer.o logsrvd.o logsrvd_conf.o \ - logsrvd_relay.o tls_client.o tls_init.o + logsrvd_journal.o logsrvd_relay.o tls_client.o tls_init.o SENDLOG_OBJS = logsrv_util.o sendlog.o tls_client.o tls_init.o @@ -383,27 +383,45 @@ logsrvd_conf.i: $(srcdir)/logsrvd_conf.c $(incdir)/compat/getaddrinfo.h \ $(CC) -E -o $@ $(CPPFLAGS) $< logsrvd_conf.plog: logsrvd_conf.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logsrvd_conf.c --i-file $< --output-file $@ -logsrvd_relay.o: $(srcdir)/logsrvd_relay.c $(incdir)/compat/getopt.h \ - $(incdir)/compat/stdbool.h $(incdir)/log_server.pb-c.h \ - $(incdir)/protobuf-c/protobuf-c.h $(incdir)/sudo_compat.h \ - $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ +logsrvd_journal.o: $(srcdir)/logsrvd_journal.c $(incdir)/compat/stdbool.h \ + $(incdir)/log_server.pb-c.h \ + $(incdir)/protobuf-c/protobuf-c.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_eventlog.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_iolog.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/logsrv_util.h \ + $(srcdir)/logsrvd.h $(srcdir)/tls_common.h \ + $(top_builddir)/config.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/logsrvd_journal.c +logsrvd_journal.i: $(srcdir)/logsrvd_journal.c $(incdir)/compat/stdbool.h \ + $(incdir)/log_server.pb-c.h \ + $(incdir)/protobuf-c/protobuf-c.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_eventlog.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_iolog.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/logsrv_util.h \ + $(srcdir)/logsrvd.h $(srcdir)/tls_common.h \ + $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +logsrvd_journal.plog: logsrvd_journal.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logsrvd_journal.c --i-file $< --output-file $@ +logsrvd_relay.o: $(srcdir)/logsrvd_relay.c $(incdir)/compat/stdbool.h \ + $(incdir)/log_server.pb-c.h $(incdir)/protobuf-c/protobuf-c.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_event.h $(incdir)/sudo_eventlog.h \ $(incdir)/sudo_gettext.h $(incdir)/sudo_iolog.h \ - $(incdir)/sudo_json.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(srcdir)/logsrv_util.h \ - $(srcdir)/logsrvd.h $(srcdir)/tls_common.h \ - $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(srcdir)/logsrv_util.h $(srcdir)/logsrvd.h \ + $(srcdir)/tls_common.h $(top_builddir)/config.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/logsrvd_relay.c -logsrvd_relay.i: $(srcdir)/logsrvd_relay.c $(incdir)/compat/getopt.h \ - $(incdir)/compat/stdbool.h $(incdir)/log_server.pb-c.h \ - $(incdir)/protobuf-c/protobuf-c.h $(incdir)/sudo_compat.h \ - $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ +logsrvd_relay.i: $(srcdir)/logsrvd_relay.c $(incdir)/compat/stdbool.h \ + $(incdir)/log_server.pb-c.h $(incdir)/protobuf-c/protobuf-c.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_event.h $(incdir)/sudo_eventlog.h \ $(incdir)/sudo_gettext.h $(incdir)/sudo_iolog.h \ - $(incdir)/sudo_json.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(srcdir)/logsrv_util.h \ - $(srcdir)/logsrvd.h $(srcdir)/tls_common.h \ - $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(srcdir)/logsrv_util.h $(srcdir)/logsrvd.h \ + $(srcdir)/tls_common.h $(top_builddir)/config.h $(CC) -E -o $@ $(CPPFLAGS) $< logsrvd_relay.plog: logsrvd_relay.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logsrvd_relay.c --i-file $< --output-file $@ diff --git a/logsrvd/iolog_writer.c b/logsrvd/iolog_writer.c index beaf7e976..d5ddebe68 100644 --- a/logsrvd/iolog_writer.c +++ b/logsrvd/iolog_writer.c @@ -982,7 +982,7 @@ bad: * Add given delta to elapsed time. * We cannot use timespecadd here since delta is not struct timespec. */ -static void +void update_elapsed_time(TimeSpec *delta, struct timespec *elapsed) { debug_decl(update_elapsed_time, SUDO_DEBUG_UTIL); diff --git a/logsrvd/logsrvd.c b/logsrvd/logsrvd.c index 7a34c1f76..6f2861a1e 100644 --- a/logsrvd/logsrvd.c +++ b/logsrvd/logsrvd.c @@ -132,6 +132,9 @@ connection_closure_free(struct connection_closure *closure) free(buf->data); free(buf); } + free(closure->journal_path); + if (closure->journal != NULL) + fclose(closure->journal); free(closure); if (shutting_down && TAILQ_EMPTY(&connections)) @@ -313,6 +316,7 @@ static bool handle_accept(AcceptMessage *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { + char *log_id = NULL; struct logsrvd_info_closure info = { msg->info_msgs, msg->n_info_msgs }; debug_decl(handle_accept, SUDO_DEBUG_UTIL); @@ -338,30 +342,44 @@ handle_accept(AcceptMessage *msg, uint8_t *buf, size_t len, debug_return_bool(relay_accept(msg, buf, len, closure)); } - closure->evlog = evlog_new(msg->submit_time, msg->info_msgs, - msg->n_info_msgs, closure); - if (closure->evlog == NULL) { - closure->errstr = _("error parsing AcceptMessage"); - debug_return_bool(false); - } - - /* Create I/O log info file and parent directories. */ - if (msg->expect_iobufs) { - if (!iolog_init(msg, closure)) { - closure->errstr = _("error creating I/O log"); + if (logsrvd_conf_relay_store_first()) { + /* Store message in a journal for later relaying. */ + if (!journal_open(closure)) + debug_return_bool(false); + if (!journal_write(buf, len, closure)) + debug_return_bool(false); + if (msg->expect_iobufs) { + closure->log_io = true; + log_id = closure->journal_path; + } + } else { + /* Store sudo-style event and I/O logs. */ + closure->evlog = evlog_new(msg->submit_time, msg->info_msgs, + msg->n_info_msgs, closure); + if (closure->evlog == NULL) { + closure->errstr = _("error parsing AcceptMessage"); + debug_return_bool(false); + } + + /* Create I/O log info file and parent directories. */ + if (msg->expect_iobufs) { + if (!iolog_init(msg, closure)) { + closure->errstr = _("error creating I/O log"); + debug_return_bool(false); + } + closure->log_io = true; + log_id = closure->evlog->iolog_path; + } + + if (!eventlog_accept(closure->evlog, 0, logsrvd_json_log_cb, &info)) { + closure->errstr = _("error logging accept event"); debug_return_bool(false); } - closure->log_io = true; } - if (!eventlog_accept(closure->evlog, 0, logsrvd_json_log_cb, &info)) { - closure->errstr = _("error logging accept event"); - debug_return_bool(false); - } - - if (msg->expect_iobufs) { + if (log_id != NULL) { /* Send log ID to client for restarting connections. */ - if (!fmt_log_id_message(closure->evlog->iolog_path, closure)) + if (!fmt_log_id_message(log_id, closure)) debug_return_bool(false); if (sudo_ev_add(closure->evbase, closure->write_ev, logsrvd_conf_server_timeout(), false) == -1) { @@ -407,17 +425,25 @@ handle_reject(RejectMessage *msg, uint8_t *buf, size_t len, debug_return_bool(relay_reject(msg, buf, len, closure)); } - closure->evlog = evlog_new(msg->submit_time, msg->info_msgs, - msg->n_info_msgs, closure); - if (closure->evlog == NULL) { - closure->errstr = _("error parsing RejectMessage"); - debug_return_bool(false); - } + if (logsrvd_conf_relay_store_first()) { + /* Store message in a journal for later relaying. */ + if (!journal_open(closure)) + debug_return_bool(false); + if (!journal_write(buf, len, closure)) + debug_return_bool(false); + } else { + closure->evlog = evlog_new(msg->submit_time, msg->info_msgs, + msg->n_info_msgs, closure); + if (closure->evlog == NULL) { + closure->errstr = _("error parsing RejectMessage"); + debug_return_bool(false); + } - if (!eventlog_reject(closure->evlog, 0, msg->reason, - logsrvd_json_log_cb, &info)) { - closure->errstr = _("error logging reject event"); - debug_return_bool(false); + if (!eventlog_reject(closure->evlog, 0, msg->reason, + logsrvd_json_log_cb, &info)) { + closure->errstr = _("error logging reject event"); + debug_return_bool(false); + } } closure->state = FINISHED; @@ -456,6 +482,15 @@ handle_exit(ExitMessage *msg, uint8_t *buf, size_t len, "command exited with %d", msg->exit_value); } + if (logsrvd_conf_relay_store_first()) { + /* Store message in a journal for later relaying. */ + if (!journal_write(buf, len, closure)) + debug_return_bool(false); + if (!journal_finish(closure)) + debug_return_bool(false); + /* XXX - schedule relay of journal file */ + } + if (closure->log_io) { /* No more data, command exited. */ closure->state = EXITED; @@ -465,12 +500,14 @@ handle_exit(ExitMessage *msg, uint8_t *buf, size_t len, __func__, (long long)closure->elapsed_time.tv_sec, closure->elapsed_time.tv_nsec); - /* Clear write bits from I/O timing file to indicate completion. */ - mode = logsrvd_conf_iolog_mode(); - CLR(mode, S_IWUSR|S_IWGRP|S_IWOTH); - if (fchmodat(closure->iolog_dir_fd, "timing", mode, 0) == -1) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, - "unable to fchmodat timing file"); + if (closure->iolog_dir_fd != -1) { + /* Clear write bits from I/O timing file to indicate completion. */ + mode = logsrvd_conf_iolog_mode(); + CLR(mode, S_IWUSR|S_IWGRP|S_IWOTH); + if (fchmodat(closure->iolog_dir_fd, "timing", mode, 0) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to fchmodat timing file"); + } } /* Schedule the final commit point event immediately. */ @@ -491,6 +528,7 @@ static bool handle_restart(RestartMessage *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { + bool restarted; debug_decl(handle_restart, SUDO_DEBUG_UTIL); if (closure->state != INITIAL) { @@ -507,7 +545,12 @@ handle_restart(RestartMessage *msg, uint8_t *buf, size_t len, debug_return_bool(relay_restart(msg, buf, len, closure)); } - if (!iolog_restart(msg, closure)) { + if (logsrvd_conf_relay_store_first()) { + restarted = journal_restart(msg, closure); + } else { + restarted = iolog_restart(msg, closure); + } + if (!restarted) { sudo_debug_printf(SUDO_DEBUG_WARN, "%s: unable to restart I/O log", __func__); /* XXX - structured error message so client can send from beginning */ if (!fmt_error_message(closure->errstr, closure)) @@ -549,22 +592,28 @@ handle_alert(AlertMessage *msg, uint8_t *buf, size_t len, debug_return_bool(relay_alert(msg, buf, len, closure)); } - if (msg->info_msgs != NULL && msg->n_info_msgs != 0) { - closure->evlog = evlog_new(NULL, msg->info_msgs, - msg->n_info_msgs, closure); - if (closure->evlog == NULL) { - closure->errstr = _("error parsing AlertMessage"); + if (logsrvd_conf_relay_store_first()) { + /* Store message in a journal for later relaying. */ + if (!journal_write(buf, len, closure)) + debug_return_bool(false); + } else { + if (msg->info_msgs != NULL && msg->n_info_msgs != 0) { + closure->evlog = evlog_new(NULL, msg->info_msgs, + msg->n_info_msgs, closure); + if (closure->evlog == NULL) { + closure->errstr = _("error parsing AlertMessage"); + debug_return_bool(false); + } + } + + alert_time.tv_sec = msg->alert_time->tv_sec; + alert_time.tv_nsec = msg->alert_time->tv_nsec; + if (!eventlog_alert(closure->evlog, 0, &alert_time, msg->reason, NULL)) { + closure->errstr = _("error logging alert event"); debug_return_bool(false); } } - alert_time.tv_sec = msg->alert_time->tv_sec; - alert_time.tv_nsec = msg->alert_time->tv_nsec; - if (!eventlog_alert(closure->evlog, 0, &alert_time, msg->reason, NULL)) { - closure->errstr = _("error logging alert event"); - debug_return_bool(false); - } - debug_return_bool(true); } @@ -594,22 +643,29 @@ handle_iobuf(int iofd, IoBuffer *iobuf, uint8_t *buf, size_t len, debug_return_bool(relay_iobuf(iobuf, buf, len, closure)); } - /* Store IoBuffer in log. */ - if (store_iobuf(iofd, iobuf, closure) == -1) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "failed to store IoBuffer"); - closure->errstr = _("error writing IoBuffer"); - debug_return_bool(false); - } - - /* Random drop is a debugging tool to test client restart. */ - if (random_drop > 0.0) { - double randval = arc4random() / (double)UINT32_MAX; - if (randval < random_drop) { - sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, - "randomly dropping connection (%f < %f)", randval, random_drop); + if (logsrvd_conf_relay_store_first()) { + /* Store message in a journal for later relaying. */ + if (!journal_write(buf, len, closure)) + debug_return_bool(false); + update_elapsed_time(iobuf->delay, &closure->elapsed_time); + } else { + /* Store IoBuffer in log. */ + if (store_iobuf(iofd, iobuf, closure) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "failed to store IoBuffer"); + closure->errstr = _("error writing IoBuffer"); debug_return_bool(false); } + + /* Random drop is a debugging tool to test client restart. */ + if (random_drop > 0.0) { + double randval = arc4random() / (double)UINT32_MAX; + if (randval < random_drop) { + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "randomly dropping connection (%f < %f)", randval, random_drop); + debug_return_bool(false); + } + } } /* Schedule a commit point in 10 sec if one is not already pending. */ @@ -652,12 +708,18 @@ handle_winsize(ChangeWindowSize *msg, uint8_t *buf, size_t len, sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received ChangeWindowSize", __func__); - /* Store new window size in log. */ - if (store_winsize(msg, closure) == -1) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "failed to store ChangeWindowSize"); - closure->errstr = _("error writing ChangeWindowSize"); - debug_return_bool(false); + if (logsrvd_conf_relay_store_first()) { + /* Store message in a journal for later relaying. */ + if (!journal_write(buf, len, closure)) + debug_return_bool(false); + } else { + /* Store new window size in log. */ + if (store_winsize(msg, closure) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "failed to store ChangeWindowSize"); + closure->errstr = _("error writing ChangeWindowSize"); + debug_return_bool(false); + } } debug_return_bool(true); @@ -690,19 +752,26 @@ handle_suspend(CommandSuspend *msg, uint8_t *buf, size_t len, sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received CommandSuspend", __func__); - /* Store suspend signal in log. */ - if (store_suspend(msg, closure) == -1) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "failed to store CommandSuspend"); - closure->errstr = _("error writing CommandSuspend"); - debug_return_bool(false); + if (logsrvd_conf_relay_store_first()) { + /* Store message in a journal for later relaying. */ + if (!journal_write(buf, len, closure)) + debug_return_bool(false); + } else { + /* Store suspend signal in log. */ + if (store_suspend(msg, closure) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "failed to store CommandSuspend"); + closure->errstr = _("error writing CommandSuspend"); + debug_return_bool(false); + } } debug_return_bool(true); } static bool -handle_client_hello(ClientHello *msg, struct connection_closure *closure) +handle_client_hello(ClientHello *msg, uint8_t *buf, size_t len, + struct connection_closure *closure) { debug_decl(handle_client_hello, SUDO_DEBUG_UTIL); @@ -775,7 +844,7 @@ handle_client_message(uint8_t *buf, size_t len, ret = handle_suspend(msg->u.suspend_event, buf, len, closure); break; case CLIENT_MESSAGE__TYPE_HELLO_MSG: - ret = handle_client_hello(msg->u.hello_msg, closure); + ret = handle_client_hello(msg->u.hello_msg, buf, len, closure); break; default: sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, @@ -1344,7 +1413,7 @@ tls_handshake_cb(int fd, int what, void *v) SSL_get_cipher(closure->ssl)); /* Start the actual protocol now that the TLS handshake is complete. */ - if (logsrvd_conf_relay_address() != NULL) { + if (!TAILQ_EMPTY(logsrvd_conf_relay_address()) && !logsrvd_conf_relay_store_first()) { if (!connect_relay(closure)) goto bad; } else { @@ -1484,7 +1553,7 @@ new_connection(int sock, bool tls, const struct sockaddr *sa, #endif /* If no TLS handshake, start the protocol immediately. */ if (!tls) { - if (logsrvd_conf_relay_address() != NULL) { + if (!TAILQ_EMPTY(logsrvd_conf_relay_address()) && !logsrvd_conf_relay_store_first()) { if (!connect_relay(closure)) goto bad; } else { diff --git a/logsrvd/logsrvd.h b/logsrvd/logsrvd.h index 35f820788..1d4d2b724 100644 --- a/logsrvd/logsrvd.h +++ b/logsrvd/logsrvd.h @@ -96,6 +96,8 @@ struct connection_closure { SSL *ssl; #endif const char *errstr; + FILE *journal; + char *journal_path; struct iolog_file iolog_files[IOFD_MAX]; int iolog_dir_fd; int sock; @@ -152,6 +154,7 @@ int store_iobuf(int iofd, IoBuffer *msg, struct connection_closure *closure); int store_suspend(CommandSuspend *msg, struct connection_closure *closure); int store_winsize(ChangeWindowSize *msg, struct connection_closure *closure); void iolog_close_all(struct connection_closure *closure); +void update_elapsed_time(TimeSpec *delta, struct timespec *elapsed); /* logsrvd.c */ bool start_protocol(struct connection_closure *closure); @@ -186,6 +189,12 @@ void address_list_addref(struct server_address_list *); void address_list_delref(struct server_address_list *); void logsrvd_conf_cleanup(void); +/* logsrvd_journal.c */ +bool journal_open(struct connection_closure *closure); +bool journal_finish(struct connection_closure *closure); +bool journal_write(uint8_t *buf, size_t len, struct connection_closure *closure); +bool journal_restart(RestartMessage *msg, struct connection_closure *closure); + /* logsrvd_relay.c */ void relay_closure_free(struct relay_closure *relay_closure); bool connect_relay(struct connection_closure *closure); diff --git a/logsrvd/logsrvd_conf.c b/logsrvd/logsrvd_conf.c index dd692d73e..ca93a890a 100644 --- a/logsrvd/logsrvd_conf.c +++ b/logsrvd/logsrvd_conf.c @@ -1399,6 +1399,10 @@ logsrvd_conf_apply(struct logsrvd_config *config) } #endif /* HAVE_OPENSSL */ + /* Clear store_first if not relaying. */ + if (TAILQ_EMPTY(&config->relay.relays.addrs)) + config->relay.store_first = false; + /* Open event log if specified. */ switch (config->eventlog.log_type) { case EVLOG_SYSLOG: diff --git a/logsrvd/logsrvd_journal.c b/logsrvd/logsrvd_journal.c new file mode 100644 index 000000000..a4bdbeb62 --- /dev/null +++ b/logsrvd/logsrvd_journal.c @@ -0,0 +1,352 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2021 Todd C. Miller + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef HAVE_STDBOOL_H +# include +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#if defined(HAVE_STDINT_H) +# include +#elif defined(HAVE_INTTYPES_H) +# include +#endif +#include +#include +#include +#include +#include + +#include "sudo_compat.h" +#include "sudo_conf.h" +#include "sudo_debug.h" +#include "sudo_gettext.h" +#include "sudo_eventlog.h" +#include "sudo_iolog.h" +#include "sudo_util.h" + +#include "log_server.pb-c.h" +#include "logsrvd.h" + +/* + * Helper function to set closure->journal and closure->journal_path. + */ +static bool +journal_fdopen(int fd, const char *journal_path, + struct connection_closure *closure) +{ + debug_decl(journal_fdopen, SUDO_DEBUG_UTIL); + + closure->journal_path = strdup(journal_path); + if (closure->journal_path == NULL) { + closure->errstr = _("unable to allocate memory"); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to allocate memory"); + debug_return_bool(false); + } + + /* Defer fdopen() until last--it cannot be undone. */ + if ((closure->journal = fdopen(fd, "r+")) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to fdopen journal file %s", journal_path); + closure->errstr = _("unable to allocate memory"); + debug_return_bool(false); + } + + debug_return_bool(true); +} + +/* + * Create a temporary file in the relay dir and store it in the closure. + */ +bool +journal_open(struct connection_closure *closure) +{ + char journal_path[PATH_MAX]; + int fd, len; + debug_decl(journal_open, SUDO_DEBUG_UTIL); + + len = snprintf(journal_path, sizeof(journal_path), "%s/relay.XXXXXXXX", + logsrvd_conf_relay_dir()); + if (len >= ssizeof(journal_path)) { + errno = ENAMETOOLONG; + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "%s/relay.XXXXXXXX", logsrvd_conf_relay_dir()); + debug_return_bool(false); + } + /* TODO: use same escapes as iolog_path? */ + if (!sudo_mkdir_parents(journal_path, ROOT_UID, ROOT_GID, + S_IRWXU|S_IXGRP|S_IXOTH, false)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to create parent dir for %s", journal_path); + closure->errstr = _("unable to create journal file"); + debug_return_bool(false); + } + if ((fd = mkstemp(journal_path)) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to create journal file %s", journal_path); + closure->errstr = _("unable to create journal file"); + debug_return_bool(false); + } + if (!journal_fdopen(fd, journal_path, closure)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to fdopen journal file %s", journal_path); + close(fd); + debug_return_bool(false); + } + + debug_return_bool(true); +} + +/* + * Flush any buffered data and rewind journal to the beginning. + * The actual open file is closed in connection_closure_free(). + */ +bool +journal_finish(struct connection_closure *closure) +{ + bool ret; + debug_decl(journal_finish, SUDO_DEBUG_UTIL); + + ret = fflush(closure->journal) == 0; + if (!ret) + closure->errstr = _("unable to write journal file"); + rewind(closure->journal); + + debug_return_bool(ret); +} + +/* + * Seek ahead in the journal to the specified target time. + * Returns true if we reached the target time exactly, else false. + */ +static bool +journal_seek(struct timespec *target, struct connection_closure *closure) +{ + ClientMessage *msg = NULL; + struct timespec elapsed_time = { 0, 0 }; + size_t nread, bufsize = 0; + uint8_t *buf = NULL; + uint32_t msg_len; + bool ret = false; + debug_decl(journal_seek, SUDO_DEBUG_UTIL); + + for (;;) { + TimeSpec *delay = NULL; + + /* Read message size (uint32_t in network byte order). */ + nread = fread(&msg_len, sizeof(msg_len), 1, closure->journal); + if (nread != 1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to read message length from %s", closure->journal_path); + if (feof(closure->journal)) + closure->errstr = _("unexpected EOF reading journal file"); + else + closure->errstr = _("error reading journal file"); + break; + } + msg_len = ntohl(msg_len); + if (msg_len > MESSAGE_SIZE_MAX) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: client message too large %u > %u", + closure->journal_path, msg_len, MESSAGE_SIZE_MAX); + closure->errstr = _("client message too large"); + break; + } + if (msg_len > bufsize) { + bufsize = sudo_pow2_roundup(msg_len); + free(buf); + if ((buf = malloc(bufsize)) == NULL) { + closure->errstr = _("unable to allocate memory"); + break; + } + } + + /* Read actual message now that we know the size. */ + nread = fread(buf, msg_len, 1, closure->journal); + if (nread != 1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to read message from %s", closure->journal_path); + if (feof(closure->journal)) + closure->errstr = _("unexpected EOF reading journal file"); + else + closure->errstr = _("error reading journal file"); + break; + } + + client_message__free_unpacked(msg, NULL); + msg = client_message__unpack(NULL, msg_len, buf); + if (msg == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to unpack ClientMessage size %u", msg_len); + closure->errstr = _("invalid journal file, unable to restart"); + break; + } + + switch (msg->type_case) { + case CLIENT_MESSAGE__TYPE_HELLO_MSG: + case CLIENT_MESSAGE__TYPE_ACCEPT_MSG: + case CLIENT_MESSAGE__TYPE_REJECT_MSG: + case CLIENT_MESSAGE__TYPE_EXIT_MSG: + case CLIENT_MESSAGE__TYPE_RESTART_MSG: + case CLIENT_MESSAGE__TYPE_ALERT_MSG: + /* No associated delay. */ + break; + case CLIENT_MESSAGE__TYPE_TTYIN_BUF: + delay = msg->u.ttyin_buf->delay; + break; + case CLIENT_MESSAGE__TYPE_TTYOUT_BUF: + delay = msg->u.ttyout_buf->delay; + break; + case CLIENT_MESSAGE__TYPE_STDIN_BUF: + delay = msg->u.stdin_buf->delay; + break; + case CLIENT_MESSAGE__TYPE_STDOUT_BUF: + delay = msg->u.stdout_buf->delay; + break; + case CLIENT_MESSAGE__TYPE_STDERR_BUF: + delay = msg->u.stderr_buf->delay; + break; + case CLIENT_MESSAGE__TYPE_WINSIZE_EVENT: + delay = msg->u.winsize_event->delay; + break; + case CLIENT_MESSAGE__TYPE_SUSPEND_EVENT: + delay = msg->u.suspend_event->delay; + break; + default: + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unexpected type_case value %d", msg->type_case); + break; + } + if (delay != NULL) { + elapsed_time.tv_sec += delay->tv_sec; + elapsed_time.tv_nsec += delay->tv_nsec; + if (elapsed_time.tv_nsec >= 1000000000) { + elapsed_time.tv_sec++; + elapsed_time.tv_nsec -= 1000000000; + } + } + + if (timespeccmp(&elapsed_time, target, >=)) { + if (sudo_timespeccmp(&elapsed_time, target, ==)) { + ret = true; + break; + } + + /* Mismatch between resume point and stored log. */ + closure->errstr = _("invalid journal file, unable to restart"); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to find resume point [%lld, %ld] in %s", + (long long)target->tv_sec, target->tv_nsec, + closure->journal_path); + break; + } + } + + client_message__free_unpacked(msg, NULL); + free(buf); + + debug_return_bool(ret); +} + +/* + * Restart an existing journal. + * Seeks to the resume_point in RestartMessage before continuing. + * Returns true if we reached the target time exactly, else false. + */ +bool +journal_restart(RestartMessage *msg, struct connection_closure *closure) +{ + struct timespec target; + int fd, len; + char *cp, journal_path[PATH_MAX]; + debug_decl(journal_restart, SUDO_DEBUG_UTIL); + + /* Strip off leading hostname from log_id. */ + if ((cp = strchr(msg->log_id, '/')) != NULL) { + if (cp != msg->log_id) + cp++; + } else { + cp = msg->log_id; + } + len = snprintf(journal_path, sizeof(journal_path), "%s/%s", + logsrvd_conf_relay_dir(), cp); + if (len >= ssizeof(journal_path)) { + errno = ENAMETOOLONG; + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "%s/%s", logsrvd_conf_relay_dir(), cp); + closure->errstr = _("unable to create journal file"); + debug_return_bool(false); + } + if ((fd = open(journal_path, O_RDWR)) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to open journal file %s", journal_path); + closure->errstr = _("unable to create journal file"); + debug_return_bool(false); + } + if (!journal_fdopen(fd, journal_path, closure)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to fdopen journal file %s", journal_path); + close(fd); + debug_return_bool(false); + } + + /* Seek forward to resume point. */ + target.tv_sec = msg->resume_point->tv_sec; + target.tv_nsec = msg->resume_point->tv_nsec; + if (!journal_seek(&target, closure)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to seek to [%lld, %ld] in journal file %s", + (long long)target.tv_sec, target.tv_nsec, journal_path); + debug_return_bool(false); + } + + debug_return_bool(true); +} + +bool +journal_write(uint8_t *buf, size_t len, struct connection_closure *closure) +{ + uint32_t msg_len; + debug_decl(journal_write, SUDO_DEBUG_UTIL); + + /* 32-bit message length in network byte order. */ + msg_len = htonl((uint32_t)len); + if (fwrite(&msg_len, 1, sizeof(msg_len), closure->journal) != sizeof(msg_len)) { + closure->errstr = _("unable to write journal file"); + debug_return_bool(false); + } + /* message payload */ + if (fwrite(buf, 1, len, closure->journal) != len) { + closure->errstr = _("unable to write journal file"); + debug_return_bool(false); + } + debug_return_bool(true); +} diff --git a/logsrvd/logsrvd_relay.c b/logsrvd/logsrvd_relay.c index e7024bfa5..abb78bacb 100644 --- a/logsrvd/logsrvd_relay.c +++ b/logsrvd/logsrvd_relay.c @@ -43,11 +43,6 @@ #include #include #include -#ifdef HAVE_GETOPT_LONG -# include -# else -# include "compat/getopt.h" -#endif /* HAVE_GETOPT_LONG */ #if defined(HAVE_OPENSSL) # include @@ -56,14 +51,11 @@ #define NEED_INET_NTOP /* to expose sudo_inet_ntop in sudo_compat.h */ -#include "pathnames.h" #include "sudo_compat.h" -#include "sudo_conf.h" #include "sudo_debug.h" #include "sudo_event.h" #include "sudo_eventlog.h" #include "sudo_gettext.h" -#include "sudo_json.h" #include "sudo_iolog.h" #include "sudo_queue.h" #include "sudo_util.h"