diff --git a/MANIFEST b/MANIFEST index 70c9cbca2..ab91ce5c8 100644 --- a/MANIFEST +++ b/MANIFEST @@ -348,6 +348,7 @@ logsrvd/logsrv_util.h logsrvd/logsrvd.c logsrvd/logsrvd.h logsrvd/logsrvd_conf.c +logsrvd/logsrvd_relay.c logsrvd/regress/corpus/seed/logsrvd_conf/logsrvd.conf.1 logsrvd/regress/corpus/seed/logsrvd_conf/logsrvd.conf.2 logsrvd/regress/corpus/seed/logsrvd_conf/logsrvd.conf.3 diff --git a/logsrvd/Makefile.in b/logsrvd/Makefile.in index 80ece85b1..85de55ae0 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 \ - tls_init.o + logsrvd_relay.o tls_init.o SENDLOG_OBJS = logsrv_util.o sendlog.o tls_client.o tls_init.o @@ -383,6 +383,30 @@ 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 \ + $(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 + $(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 \ + $(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 + $(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 $@ sendlog.o: $(srcdir)/sendlog.c $(incdir)/compat/getaddrinfo.h \ $(incdir)/compat/getopt.h $(incdir)/compat/stdbool.h \ $(incdir)/hostcheck.h $(incdir)/log_server.pb-c.h \ diff --git a/logsrvd/logsrvd.c b/logsrvd/logsrvd.c index 12351d33b..2c347868e 100644 --- a/logsrvd/logsrvd.c +++ b/logsrvd/logsrvd.c @@ -95,7 +95,7 @@ static void client_msg_cb(int fd, int what, void *v); /* * Free a struct connection_closure container and its contents. */ -static void +void connection_closure_free(struct connection_closure *closure) { debug_decl(connection_closure_free, SUDO_DEBUG_UTIL); @@ -106,12 +106,8 @@ connection_closure_free(struct connection_closure *closure) struct connection_buffer *buf; TAILQ_REMOVE(&connections, closure, entries); -#if defined(HAVE_OPENSSL) - if (closure->ssl != NULL) { - SSL_shutdown(closure->ssl); - SSL_free(closure->ssl); - } -#endif + if (closure->relay_closure != NULL) + relay_closure_free(closure->relay_closure); close(closure->sock); iolog_close_all(closure); sudo_ev_free(closure->commit_ev); @@ -119,6 +115,10 @@ connection_closure_free(struct connection_closure *closure) sudo_ev_free(closure->write_ev); #if defined(HAVE_OPENSSL) sudo_ev_free(closure->ssl_accept_ev); + if (closure->ssl != NULL) { + SSL_shutdown(closure->ssl); + SSL_free(closure->ssl); + } #endif eventlog_free(closure->evlog); free(closure->read_buf.data); @@ -141,7 +141,7 @@ connection_closure_free(struct connection_closure *closure) debug_return; } -static struct connection_buffer * +struct connection_buffer * get_free_buf(struct connection_closure *closure) { struct connection_buffer *buf; @@ -156,7 +156,7 @@ get_free_buf(struct connection_closure *closure) debug_return_ptr(buf); } -static bool +bool fmt_server_message(struct connection_closure *closure, ServerMessage *msg) { struct connection_buffer *buf; @@ -227,7 +227,7 @@ fmt_hello_message(struct connection_closure *closure) debug_return_bool(fmt_server_message(closure, &msg)); } -static bool +bool fmt_log_id_message(const char *id, struct connection_closure *closure) { ServerMessage msg = SERVER_MESSAGE__INIT; @@ -239,7 +239,7 @@ fmt_log_id_message(const char *id, struct connection_closure *closure) debug_return_bool(fmt_server_message(closure, &msg)); } -static bool +bool fmt_error_message(const char *errstr, struct connection_closure *closure) { ServerMessage msg = SERVER_MESSAGE__INIT; @@ -333,6 +333,11 @@ handle_accept(AcceptMessage *msg, struct connection_closure *closure) } sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received AcceptMessage", __func__); + if (closure->relay_closure != NULL) { + /* Forward AcceptMessage to connected relay. */ + debug_return_bool(relay_accept(msg, closure)); + } + closure->evlog = evlog_new(msg->submit_time, msg->info_msgs, msg->n_info_msgs, closure); if (closure->evlog == NULL) { @@ -396,6 +401,11 @@ handle_reject(RejectMessage *msg, struct connection_closure *closure) } sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received RejectMessage", __func__); + if (closure->relay_closure != NULL) { + /* Forward RejectMessage to connected relay. */ + debug_return_bool(relay_reject(msg, closure)); + } + closure->evlog = evlog_new(msg->submit_time, msg->info_msgs, msg->n_info_msgs, closure); if (closure->evlog == NULL) { @@ -429,6 +439,11 @@ handle_exit(ExitMessage *msg, struct connection_closure *closure) sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received ExitMessage", __func__); + if (closure->relay_closure != NULL) { + /* Forward ExitMessage to connected relay. */ + debug_return_bool(relay_exit(msg, closure)); + } + /* Sudo I/O logs don't store this info. */ if (msg->signal != NULL && msg->signal[0] != '\0') { sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, @@ -484,6 +499,11 @@ handle_restart(RestartMessage *msg, struct connection_closure *closure) sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received RestartMessage for %s", __func__, msg->log_id); + if (closure->relay_closure != NULL) { + /* Forward RestartMessage to connected relay. */ + debug_return_bool(relay_restart(msg, closure)); + } + if (!iolog_restart(msg, closure)) { sudo_debug_printf(SUDO_DEBUG_WARN, "%s: unable to restart I/O log", __func__); /* XXX - structured error message so client can send from beginning */ @@ -520,6 +540,11 @@ handle_alert(AlertMessage *msg, struct connection_closure *closure) } sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received AlertMessage", __func__); + if (closure->relay_closure != NULL) { + /* Forward AlertMessage to connected relay. */ + debug_return_bool(relay_alert(msg, closure)); + } + if (msg->info_msgs != NULL && msg->n_info_msgs != 0) { closure->evlog = evlog_new(NULL, msg->info_msgs, msg->n_info_msgs, closure); @@ -540,7 +565,7 @@ handle_alert(AlertMessage *msg, struct connection_closure *closure) } static bool -handle_iobuf(int iofd, IoBuffer *msg, struct connection_closure *closure) +handle_iobuf(int iofd, IoBuffer *iobuf, struct connection_closure *closure) { debug_decl(handle_iobuf, SUDO_DEBUG_UTIL); @@ -559,8 +584,13 @@ handle_iobuf(int iofd, IoBuffer *msg, struct connection_closure *closure) sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received IoBuffer", __func__); + if (closure->relay_closure != NULL) { + /* Forward IoBuffer to connected relay. */ + debug_return_bool(relay_iobuf(iofd, iobuf, closure)); + } + /* Store IoBuffer in log. */ - if (store_iobuf(iofd, msg, closure) == -1) { + 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"); @@ -682,6 +712,7 @@ handle_client_message(uint8_t *buf, size_t len, bool ret = false; debug_decl(handle_client_message, SUDO_DEBUG_UTIL); + /* TODO: can we extract type_case without unpacking for relay case? */ msg = client_message__unpack(NULL, len, buf); if (msg == NULL) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, @@ -777,7 +808,10 @@ server_shutdown(struct sudo_event_base *base) TAILQ_FOREACH_SAFE(closure, &connections, entries, next) { closure->state = SHUTDOWN; sudo_ev_del(base, closure->read_ev); - if (closure->log_io) { + if (closure->relay_closure != NULL) { + /* Connection being relayed, check for pending I/O. */ + relay_shutdown(closure); + } else if (closure->log_io) { /* Schedule final commit point for the connection. */ if (sudo_ev_add(base, closure->commit_ev, &tv, false) == -1) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, @@ -1072,7 +1106,7 @@ close_connection: /* * Format and schedule a commit_point message. */ -static bool +bool schedule_commit_point(TimeSpec *commit_point, struct connection_closure *closure) { @@ -1130,12 +1164,19 @@ server_commit_cb(int unused, int what, void *v) * When we enter the event loop the ServerHello message will be written * and any pending ClientMessage will be read. */ -static bool +bool start_protocol(struct connection_closure *closure) { const struct timespec *timeout = logsrvd_conf_get_sock_timeout(); debug_decl(start_protocol, SUDO_DEBUG_UTIL); + if (closure->relay_closure != NULL && closure->relay_closure->relays != NULL) { + /* No longer need the stashed relays list. */ + address_list_delref(closure->relay_closure->relays); + closure->relay_closure->relays = NULL; + closure->relay_closure->relay_addr = NULL; + } + if (!fmt_hello_message(closure)) debug_return_bool(false); @@ -1296,8 +1337,13 @@ 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 (!start_protocol(closure)) - goto bad; + if (logsrvd_conf_relay() != NULL) { + if (!connect_relay(closure)) + goto bad; + } else { + if (!start_protocol(closure)) + goto bad; + } debug_return; bad: @@ -1387,7 +1433,8 @@ new_connection(int sock, bool tls, const struct sockaddr *sa, sizeof(closure->ipaddr)); #endif /* HAVE_STRUCT_IN6_ADDR */ } else { - sudo_fatal("%s", U_("unable to get remote IP addr")); + errno = EAFNOSUPPORT; + sudo_warn("%s", U_("unable to get remote IP addr")); goto bad; } sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, @@ -1430,8 +1477,13 @@ new_connection(int sock, bool tls, const struct sockaddr *sa, #endif /* If no TLS handshake, start the protocol immediately. */ if (!tls) { - if (!start_protocol(closure)) - goto bad; + if (logsrvd_conf_relay() != NULL) { + if (!connect_relay(closure)) + goto bad; + } else { + if (!start_protocol(closure)) + goto bad; + } } debug_return_bool(true); diff --git a/logsrvd/logsrvd.h b/logsrvd/logsrvd.h index 2377858c4..d3da0f993 100644 --- a/logsrvd/logsrvd.h +++ b/logsrvd/logsrvd.h @@ -47,6 +47,7 @@ */ enum connection_status { INITIAL, + CONNECTING, RUNNING, EXITED, SHUTDOWN, @@ -54,11 +55,31 @@ enum connection_status { ERROR }; +/* + * Per-connection relay state. + */ +struct relay_closure { + struct server_address_list *relays; + struct server_address *relay_addr; + struct sudo_event *read_ev; + struct sudo_event *write_ev; + struct sudo_event *connect_ev; + struct connection_buffer read_buf; + struct connection_buffer_list write_bufs; + int sock; +#ifdef HAVE_STRUCT_IN6_ADDR + char ipaddr[INET6_ADDRSTRLEN]; +#else + char ipaddr[INET_ADDRSTRLEN]; +#endif +}; + /* * Per-connection state. */ struct connection_closure { TAILQ_ENTRY(connection_closure) entries; + struct relay_closure *relay_closure; struct eventlog *evlog; struct timespec elapsed_time; struct connection_buffer read_buf; @@ -74,19 +95,19 @@ struct connection_closure { #endif const char *errstr; struct iolog_file iolog_files[IOFD_MAX]; + int iolog_dir_fd; + int sock; + enum connection_status state; bool tls; bool log_io; bool read_instead_of_write; bool write_instead_of_read; bool temporary_write_event; - int iolog_dir_fd; - int sock; #ifdef HAVE_STRUCT_IN6_ADDR char ipaddr[INET6_ADDRSTRLEN]; #else char ipaddr[INET_ADDRSTRLEN]; #endif - enum connection_status state; }; union sockaddr_union { @@ -147,14 +168,24 @@ 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); +/* logsrvd.c */ +bool start_protocol(struct connection_closure *closure); +void connection_closure_free(struct connection_closure *closure); +bool schedule_commit_point(TimeSpec *commit_point, struct connection_closure *closure); +bool fmt_log_id_message(const char *id, struct connection_closure *closure); +bool fmt_error_message(const char *errstr, struct connection_closure *closure); +struct connection_buffer *get_free_buf(struct connection_closure *closure); + /* logsrvd_conf.c */ bool logsrvd_conf_read(const char *path); const char *logsrvd_conf_iolog_dir(void); const char *logsrvd_conf_iolog_file(void); struct server_address_list *logsrvd_conf_listen_address(void); +struct server_address_list *logsrvd_conf_relay(void); bool logsrvd_conf_tcp_keepalive(void); const char *logsrvd_conf_pid_file(void); struct timespec *logsrvd_conf_get_sock_timeout(void); +struct timespec *logsrvd_conf_get_connect_timeout(void); #if defined(HAVE_OPENSSL) const struct logsrvd_tls_config *logsrvd_get_tls_config(void); struct logsrvd_tls_runtime *logsrvd_get_tls_runtime(void); @@ -163,4 +194,15 @@ mode_t logsrvd_conf_iolog_mode(void); void address_list_addref(struct server_address_list *); void address_list_delref(struct server_address_list *); +/* logsrvd_relay.c */ +void relay_closure_free(struct relay_closure *relay_closure); +bool connect_relay(struct connection_closure *closure); +bool relay_accept(AcceptMessage *msg, struct connection_closure *closure); +bool relay_reject(RejectMessage *msg, struct connection_closure *closure); +bool relay_exit(ExitMessage *msg, struct connection_closure *closure); +bool relay_restart(RestartMessage *msg, struct connection_closure *closure); +bool relay_alert(AlertMessage *msg, struct connection_closure *closure); +bool relay_iobuf(int iofd, IoBuffer *iobuf, struct connection_closure *closure); +bool relay_shutdown(struct connection_closure *closure); + #endif /* SUDO_LOGSRVD_H */ diff --git a/logsrvd/logsrvd_conf.c b/logsrvd/logsrvd_conf.c index 55a07c28a..d71b16746 100644 --- a/logsrvd/logsrvd_conf.c +++ b/logsrvd/logsrvd_conf.c @@ -84,7 +84,9 @@ struct address_list_container { static struct logsrvd_config { struct logsrvd_config_server { struct address_list_container addresses; + struct address_list_container relays; struct timespec timeout; + struct timespec connect_timeout; bool tcp_keepalive; char *pid_file; #if defined(HAVE_OPENSSL) @@ -147,6 +149,12 @@ logsrvd_conf_listen_address(void) return &logsrvd_config->server.addresses.addrs; } +struct server_address_list * +logsrvd_conf_relay(void) +{ + return &logsrvd_config->server.relays.addrs; +} + bool logsrvd_conf_tcp_keepalive(void) { @@ -169,6 +177,16 @@ logsrvd_conf_get_sock_timeout(void) return NULL; } +struct timespec * +logsrvd_conf_get_connect_timeout(void) +{ + if (sudo_timespecisset(&logsrvd_config->server.connect_timeout)) { + return &(logsrvd_config->server.connect_timeout); + } + + return NULL; +} + #if defined(HAVE_OPENSSL) const struct logsrvd_tls_config * logsrvd_get_tls_config(void) @@ -380,6 +398,12 @@ cb_listen_address(struct logsrvd_config *config, const char *str) return append_address(&config->server.addresses.addrs, str); } +static bool +cb_relay(struct logsrvd_config *config, const char *str) +{ + return append_address(&config->server.relays.addrs, str); +} + static bool cb_timeout(struct logsrvd_config *config, const char *str) { @@ -396,6 +420,22 @@ cb_timeout(struct logsrvd_config *config, const char *str) debug_return_bool(true); } +static bool +cb_connect_timeout(struct logsrvd_config *config, const char *str) +{ + int timeout; + const char* errstr; + debug_decl(cb_connect_timeout, SUDO_DEBUG_UTIL); + + timeout = sudo_strtonum(str, 0, UINT_MAX, &errstr); + if (errstr != NULL) + debug_return_bool(false); + + config->server.connect_timeout.tv_sec = timeout; + + debug_return_bool(true); +} + static bool cb_keepalive(struct logsrvd_config *config, const char *str) { @@ -719,7 +759,9 @@ address_list_delref(struct server_address_list *al) static struct logsrvd_config_entry server_conf_entries[] = { { "listen_address", cb_listen_address }, + { "relay", cb_relay }, { "timeout", cb_timeout }, + { "connect_timeout", cb_connect_timeout }, { "tcp_keepalive", cb_keepalive }, { "pid_file", cb_pid_file }, #if defined(HAVE_OPENSSL) @@ -932,6 +974,7 @@ logsrvd_conf_free(struct logsrvd_config *config) /* struct logsrvd_config_server */ address_list_delref(&config->server.addresses.addrs); + address_list_delref(&config->server.relays.addrs); free(config->server.pid_file); /* struct logsrvd_config_iolog */ @@ -976,6 +1019,8 @@ logsrvd_conf_alloc(void) /* Server defaults */ TAILQ_INIT(&config->server.addresses.addrs); config->server.addresses.refcnt = 1; + TAILQ_INIT(&config->server.relays.addrs); + config->server.relays.refcnt = 1; config->server.timeout.tv_sec = DEFAULT_SOCKET_TIMEOUT_SEC; config->server.tcp_keepalive = true; config->server.pid_file = strdup(_PATH_SUDO_LOGSRVD_PID); diff --git a/logsrvd/logsrvd_relay.c b/logsrvd/logsrvd_relay.c new file mode 100644 index 000000000..fc7d8f467 --- /dev/null +++ b/logsrvd/logsrvd_relay.c @@ -0,0 +1,1023 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2019-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 +#ifdef HAVE_GETOPT_LONG +# include +# else +# include "compat/getopt.h" +#endif /* HAVE_GETOPT_LONG */ + +#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" + +#include "log_server.pb-c.h" +#include "logsrvd.h" + +static void relay_client_msg_cb(int fd, int what, void *v); +static void relay_server_msg_cb(int fd, int what, void *v); +static void connect_cb(int sock, int what, void *v); +static bool start_relay(int sock, struct connection_closure *closure); + +/* + * Free a struct relay_closure container and its contents. + */ +void +relay_closure_free(struct relay_closure *relay_closure) +{ + struct connection_buffer *buf; + debug_decl(relay_closure_free, SUDO_DEBUG_UTIL); + + if (relay_closure->relays != NULL) + address_list_delref(relay_closure->relays); + sudo_ev_free(relay_closure->read_ev); + sudo_ev_free(relay_closure->write_ev); + sudo_ev_free(relay_closure->connect_ev); + free(relay_closure->read_buf.data); + while ((buf = TAILQ_FIRST(&relay_closure->write_bufs)) != NULL) { + TAILQ_REMOVE(&relay_closure->write_bufs, buf, entries); + free(buf->data); + free(buf); + } + if (relay_closure->sock != -1) + close(relay_closure->sock); + free(relay_closure); + + debug_return; +} + +/* + * Allocate a relay closure. + * Note that allocation of the events is deferred until we know the socket. + */ +static struct relay_closure * +relay_closure_alloc(void) +{ + struct relay_closure *relay_closure; + debug_decl(relay_closure_alloc, SUDO_DEBUG_UTIL); + + if ((relay_closure = calloc(1, sizeof(*relay_closure))) == NULL) + debug_return_ptr(NULL); + + /* We take a reference to relays so it doesn't change while connecting. */ + relay_closure->sock = -1; + relay_closure->relays = logsrvd_conf_relay(); + address_list_addref(relay_closure->relays); + TAILQ_INIT(&relay_closure->write_bufs); + + relay_closure->read_buf.size = 8 * 1024; + relay_closure->read_buf.data = malloc(relay_closure->read_buf.size); + if (relay_closure->read_buf.data == NULL) + goto bad; + + debug_return_ptr(relay_closure); +bad: + relay_closure_free(relay_closure); + debug_return_ptr(NULL); +} + +/* + * Format a ClientMessage and store the wire format message in buf. + * Returns true on success, false on failure. + */ +static bool +fmt_client_message(struct connection_closure *closure, ClientMessage *msg) +{ + struct relay_closure *relay_closure = closure->relay_closure; + struct connection_buffer *buf; + uint32_t msg_len; + bool ret = false; + size_t len; + debug_decl(fmt_client_message, SUDO_DEBUG_UTIL); + + if ((buf = get_free_buf(closure)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate connection_buffer"); + goto done; + } + + len = client_message__get_packed_size(msg); + if (len > MESSAGE_SIZE_MAX) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "client message too large: %zu", len); + goto done; + } + + /* Wire message size is used for length encoding, precedes message. */ + msg_len = htonl((uint32_t)len); + len += sizeof(msg_len); + + /* Resize buffer as needed. */ + if (len > buf->size) { + free(buf->data); + buf->size = sudo_pow2_roundup(len); + if ((buf->data = malloc(buf->size)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to malloc %u", buf->size); + buf->size = 0; + goto done; + } + } + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "size + client message %zu bytes", len); + + memcpy(buf->data, &msg_len, sizeof(msg_len)); + client_message__pack(msg, buf->data + sizeof(msg_len)); + buf->len = len; + TAILQ_INSERT_TAIL(&relay_closure->write_bufs, buf, entries); + buf = NULL; + + ret = true; + +done: + if (buf != NULL) { + free(buf->data); + free(buf); + } + debug_return_bool(ret); +} + +static bool +fmt_client_hello(struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + ClientHello hello_msg = CLIENT_HELLO__INIT; + bool ret; + debug_decl(fmt_client_hello, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending ClientHello", __func__); + hello_msg.client_id = "Sudo Logsrvd " PACKAGE_VERSION; + + client_msg.u.hello_msg = &hello_msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_HELLO_MSG; + ret = fmt_client_message(closure, &client_msg); + if (ret) { + if (sudo_ev_add(closure->evbase, relay_closure->read_ev, NULL, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server read event"); + ret = false; + } + if (sudo_ev_add(closure->evbase, relay_closure->write_ev, NULL, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + ret = false; + } + } + + debug_return_bool(ret); +} + +/* + * Try to connect to the next relay host. + * Returns 0 on success, -1 on error, setting errno. + * If there is no next relay, errno is set to ENOENT. + */ +int +connect_relay_next(struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + struct server_address *relay; + int ret, sock = -1; + char *addr; + debug_decl(connect_relay_next, SUDO_DEBUG_UTIL); + + /* Get next relay or return ENOENT none are left. */ + if (relay_closure->relay_addr != NULL) { + relay = TAILQ_NEXT(relay_closure->relay_addr, entries); + } else { + relay = TAILQ_FIRST(relay_closure->relays); + } + if (relay == NULL) { + errno = ENOENT; + goto bad; + } + relay_closure->relay_addr = relay; + + sock = socket(relay->sa_un.sa.sa_family, SOCK_STREAM, 0); + if (sock == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to allocate relay socket"); + goto bad; + } + ret = fcntl(sock, F_GETFL, 0); + if (ret == -1 || fcntl(sock, F_SETFL, ret | O_NONBLOCK) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "fcntl(O_NONBLOCK) failed"); + goto bad; + } + + ret = connect(sock, &relay->sa_un.sa, relay->sa_size); + if (ret == -1 && errno != EINPROGRESS) + goto bad; + + switch (relay->sa_un.sa.sa_family) { + case AF_INET: + addr = (char *)&relay->sa_un.sin.sin_addr; + break; + case AF_INET6: + addr = (char *)&relay->sa_un.sin6.sin6_addr; + break; + default: + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unsupported address family from connect(): %d", + relay->sa_un.sa.sa_family); + goto bad; + } + inet_ntop(relay->sa_un.sa.sa_family, addr, relay_closure->ipaddr, + sizeof(relay_closure->ipaddr)); + + if (ret == 0) { + /* Connection succeeded without blocking. */ + relay_closure->sock = sock; + if (!start_relay(sock, closure)) + goto bad; + } else { + /* Connection will be completed in connect_cb(). */ + relay_closure->connect_ev = sudo_ev_alloc(sock, SUDO_EV_WRITE, + connect_cb, closure); + if (relay_closure->connect_ev == NULL) + goto bad; + if (sudo_ev_add(closure->evbase, relay_closure->connect_ev, + logsrvd_conf_get_connect_timeout(), false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server connect event"); + goto bad; + } + relay_closure->sock = sock; + closure->state = CONNECTING; + } + debug_return_int(ret); + +bad: + /* Connection or system error. */ + if (sock != -1) + close(sock); + sudo_ev_free(relay_closure->connect_ev); + relay_closure->connect_ev = NULL; + debug_return_int(-1); +} + +static void +connect_cb(int sock, int what, void *v) +{ + struct connection_closure *closure = v; + struct relay_closure *relay_closure = closure->relay_closure; + int errnum, optval, ret; + socklen_t optlen = sizeof(optval); + debug_decl(connect_cb, SUDO_DEBUG_UTIL); + + if (what == SUDO_EV_TIMEOUT) { + errnum = ETIMEDOUT; + } else { + ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, &optlen); + errnum = ret == 0 ? optval : errno; + } + if (errnum == 0) { + /* Relay connection succeeded, start talking to the client. */ + closure->state = INITIAL; + if (!start_relay(sock, closure)) + connection_closure_free(closure); + } else { + /* Connection failed, try next relay (if any). */ + int res; + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "unable to connect to relay %s: %s", relay_closure->ipaddr, + strerror(errnum)); + while ((res = connect_relay_next(closure)) == -1) { + if (errno == ENOENT || errno == EINPROGRESS) { + /* Out of relays or connecting asynchronously. */ + break; + } + } + if (res == -1 && errno != EINPROGRESS) { + closure->errstr = _("unable to connect to relay host"); + closure->state = ERROR; + } + } + + debug_return; +} + +/* Connect to the first available relay host. */ +bool +connect_relay(struct connection_closure *closure) +{ + struct relay_closure *relay_closure; + int res; + debug_decl(connect_relay, SUDO_DEBUG_UTIL); + + relay_closure = closure->relay_closure = relay_closure_alloc(); + if (relay_closure == NULL) + debug_return_bool(false); + + while ((res = connect_relay_next(closure)) == -1) { + if (errno == ENOENT || errno == EINPROGRESS) { + /* Out of relays or connecting asynchronously. */ + break; + } + } + + if (res == -1 && errno != EINPROGRESS) + debug_return_bool(false); + debug_return_bool(true); +} + +/* + * Respond to a ServerHello message from the relay. + * Returns true on success, false on error. + */ +static bool +handle_server_hello(ServerHello *msg, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + debug_decl(handle_server_hello, SUDO_DEBUG_UTIL); + + if (closure->state != INITIAL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unexpected state %d", closure->state); + closure->errstr = _("state machine error"); + } + + /* Check that ServerHello is valid. */ + if (msg->server_id == NULL || msg->server_id[0] == '\0') { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "invalid ServerHello, missing server_id"); + closure->errstr = _("invalid ServerHello"); + debug_return_bool(false); + } + + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "relay server (%s) ID %s", relay_closure->ipaddr, msg->server_id); + + /* TODO: handle redirect */ + + debug_return_bool(true); +} + +/* + * Respond to a CommitPoint message from the relay. + * Returns true on success, false on error. + */ +static bool +handle_commit_point(TimeSpec *commit_point, struct connection_closure *closure) +{ + debug_decl(handle_commit_point, SUDO_DEBUG_UTIL); + + if (closure->state < RUNNING) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unexpected state %d", closure->state); + closure->errstr = _("state machine error"); + debug_return_bool(false); + } + + /* Pass commit point from relay to client. */ + debug_return_bool(schedule_commit_point(commit_point, closure)); +} + +/* + * Respond to a LogId message from the relay. + * Always returns true. + */ +static bool +handle_log_id(char *id, struct connection_closure *closure) +{ + char *new_id; + bool ret = false; + debug_decl(handle_log_id, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "log ID %s from relay (%s)", id, closure->relay_closure->ipaddr); + + /* Generate a new log ID that includes the relay host. */ + if (asprintf(&new_id, "%s/%s", id, closure->relay_closure->ipaddr) != -1) { + if (fmt_log_id_message(id, closure)) { + if (sudo_ev_add(closure->evbase, closure->write_ev, + logsrvd_conf_get_sock_timeout(), false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + } else { + ret = true; + } + } + free(new_id); + } + + debug_return_bool(ret); +} + +/* + * Respond to a ServerError message from the relay. + * Always returns false. + */ +static bool +handle_server_error(char *errmsg, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + debug_decl(handle_server_error, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "error message received from relay (%s): %s", + relay_closure->ipaddr, errmsg); + + if (!fmt_error_message(errmsg, closure)) + debug_return_bool(false); + + sudo_ev_del(closure->evbase, closure->read_ev); + if (sudo_ev_add(closure->evbase, closure->write_ev, + logsrvd_conf_get_sock_timeout(), false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + debug_return_bool(false); + } + closure->state = ERROR; + + debug_return_bool(true); +} + +/* + * Respond to a ServerAbort message from the server. + * Always returns false. + */ +static bool +handle_server_abort(char *errmsg, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + debug_decl(handle_server_abort, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "abort message received from relay (%s): %s", + relay_closure->ipaddr, errmsg); + + if (!fmt_error_message(errmsg, closure)) + debug_return_bool(false); + + sudo_ev_del(closure->evbase, closure->read_ev); + if (sudo_ev_add(closure->evbase, closure->write_ev, + logsrvd_conf_get_sock_timeout(), false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + debug_return_bool(false); + } + closure->state = ERROR; + + debug_return_bool(true); +} + +/* + * Respond to a ServerMessage from the relay. + * Returns true on success, false on error. + */ +static bool +handle_server_message(uint8_t *buf, size_t len, struct connection_closure *closure) +{ + ServerMessage *msg; + bool ret = false; + debug_decl(handle_server_message, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: unpacking ServerMessage", __func__); + msg = server_message__unpack(NULL, len, buf); + if (msg == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to unpack ServerMessage size %zu", len); + debug_return_bool(false); + } + + switch (msg->type_case) { + case SERVER_MESSAGE__TYPE_HELLO: + if ((ret = handle_server_hello(msg->u.hello, closure))) { + /* Relay server said hello, start talking to client. */ + ret = start_protocol(closure); + } + break; + case SERVER_MESSAGE__TYPE_COMMIT_POINT: + ret = handle_commit_point(msg->u.commit_point, closure); + break; + case SERVER_MESSAGE__TYPE_LOG_ID: + ret = handle_log_id(msg->u.log_id, closure); + break; + case SERVER_MESSAGE__TYPE_ERROR: + ret = handle_server_error(msg->u.error, closure); + break; + case SERVER_MESSAGE__TYPE_ABORT: + ret = handle_server_abort(msg->u.abort, closure); + break; + default: + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unexpected type_case value %d", msg->type_case); + closure->errstr = _("unrecognized ServerMessage type"); + break; + } + + server_message__free_unpacked(msg, NULL); + debug_return_bool(ret); +} + +/* + * Read and unpack a ServerMessage from the relay (read callback). + */ +static void +relay_server_msg_cb(int fd, int what, void *v) +{ + struct connection_closure *closure = v; + struct relay_closure *relay_closure = closure->relay_closure; + struct connection_buffer *buf = &relay_closure->read_buf; + ssize_t nread; + uint32_t msg_len; + debug_decl(relay_server_msg_cb, SUDO_DEBUG_UTIL); + + if (what == SUDO_EV_TIMEOUT) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "timed out reading from relay (%s)", relay_closure->ipaddr); + closure->errstr = _("timeout reading from relay"); + goto send_error; + } + + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: ServerMessage from relay %s", + __func__, relay_closure->ipaddr); + + nread = recv(fd, buf->data + buf->len, buf->size - buf->len, 0); + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received %zd bytes from relay %s", + __func__, nread, relay_closure->ipaddr); + switch (nread) { + case -1: + if (errno == EAGAIN) + debug_return; + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "recv from %s", relay_closure->ipaddr); + closure->errstr = _("unable to read from relay"); + goto send_error; + case 0: + /* EOF from relay server, close the socket. */ + close(relay_closure->sock); + relay_closure->sock = -1; + sudo_ev_del(closure->evbase, relay_closure->read_ev); + sudo_ev_del(closure->evbase, relay_closure->write_ev); + + if (closure->state != FINISHED) { + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "unexpected EOF from %s (state %d)", relay_closure->ipaddr, + closure->state); + closure->errstr = _("unexpected EOF from relay"); + goto send_error; + } + debug_return; + default: + break; + } + buf->len += nread; + + while (buf->len - buf->off >= sizeof(msg_len)) { + /* Read wire message size (uint32_t in network byte order). */ + memcpy(&msg_len, buf->data + buf->off, sizeof(msg_len)); + msg_len = ntohl(msg_len); + + if (msg_len > MESSAGE_SIZE_MAX) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "server message too large: %u", msg_len); + closure->errstr = _("server message too large"); + goto send_error; + } + + if (msg_len + sizeof(msg_len) > buf->len - buf->off) { + /* Incomplete message, we'll read the rest next time. */ + if (!expand_buf(buf, msg_len + sizeof(msg_len))) { + closure->errstr = _("unable to allocate memory"); + goto send_error; + } + debug_return; + } + + /* Parse ServerMessage (could be zero bytes). */ + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: parsing ServerMessage, size %u", __func__, msg_len); + buf->off += sizeof(msg_len); + if (!handle_server_message(buf->data + buf->off, msg_len, closure)) + goto send_error; + buf->off += msg_len; + } + buf->len -= buf->off; + buf->off = 0; + debug_return; + +send_error: + /* + * Try to send client an error message before closing connection. + * If we are already in an error state, just give up. + */ + if (closure->state == ERROR) + goto close_connection; + if (closure->errstr != NULL || !fmt_error_message(closure->errstr, closure)) + goto close_connection; + sudo_ev_del(closure->evbase, relay_closure->read_ev); + if (sudo_ev_add(closure->evbase, closure->write_ev, + logsrvd_conf_get_sock_timeout(), false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + goto close_connection; + } + closure->state = ERROR; + debug_return; + +close_connection: + connection_closure_free(closure); + debug_return; +} + +/* + * Forward a ClientMessage to the relay (write callback). + */ +static void +relay_client_msg_cb(int fd, int what, void *v) +{ + struct connection_closure *closure = v; + struct relay_closure *relay_closure = closure->relay_closure; + struct connection_buffer *buf; + ssize_t nwritten; + debug_decl(relay_client_msg_cb, SUDO_DEBUG_UTIL); + + if (what == SUDO_EV_TIMEOUT) { + closure->errstr = _("timeout writing to relay"); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "timed out writing to relay (%s)", relay_closure->ipaddr); + goto send_error; + } + + if ((buf = TAILQ_FIRST(&relay_closure->write_bufs)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "missing write buffer"); + goto close_connection; + } + + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending %u bytes to server (%s)", + __func__, buf->len - buf->off, relay_closure->ipaddr); + + nwritten = send(fd, buf->data + buf->off, buf->len - buf->off, 0); + if (nwritten == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "send to %s", relay_closure->ipaddr); + closure->errstr = _("error writing to relay"); + goto send_error; + } + buf->off += nwritten; + + if (buf->off == buf->len) { + /* sent entire message, move buf to free list */ + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: finished sending %u bytes to server", __func__, buf->len); + buf->off = 0; + buf->len = 0; + TAILQ_REMOVE(&relay_closure->write_bufs, buf, entries); + TAILQ_INSERT_TAIL(&closure->free_bufs, buf, entries); + if (TAILQ_EMPTY(&relay_closure->write_bufs)) + sudo_ev_del(closure->evbase, relay_closure->write_ev); + } + debug_return; + +send_error: + /* + * Try to send client an error message before closing connection. + * If we are already in an error state, just give up. + */ + if (closure->state == ERROR) + goto close_connection; + if (closure->errstr != NULL || !fmt_error_message(closure->errstr, closure)) + goto close_connection; + sudo_ev_del(closure->evbase, relay_closure->read_ev); + if (sudo_ev_add(closure->evbase, closure->write_ev, + logsrvd_conf_get_sock_timeout(), false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + goto close_connection; + } + closure->state = ERROR; + debug_return; + +close_connection: + connection_closure_free(closure); + debug_return; +} + +/* Begin the conversation with the relay host. */ +static bool +start_relay(int sock, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + debug_decl(start_relay, SUDO_DEBUG_UTIL); + + /* No longer need the connect event. */ + sudo_ev_free(relay_closure->connect_ev); + relay_closure->connect_ev = NULL; + + /* Allocate relay read/write events now that we know the socket. */ + relay_closure->read_ev = sudo_ev_alloc(sock, SUDO_EV_READ|SUDO_EV_PERSIST, + relay_server_msg_cb, closure); + relay_closure->write_ev = sudo_ev_alloc(sock, SUDO_EV_WRITE|SUDO_EV_PERSIST, + relay_client_msg_cb, closure); + if (relay_closure->read_ev == NULL || relay_closure->write_ev == NULL) + debug_return_bool(false); + + /* Start communication with the relay server by saying hello. */ + debug_return_bool(fmt_client_hello(closure)); +} + +/* + * Relay an AcceptMessage from the client to the relay server. + */ +bool +relay_accept(AcceptMessage *msg, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + struct sudo_event_base *evbase = closure->evbase; + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + bool ret; + debug_decl(relay_accept, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: relaying AcceptMessage from %s to %s", __func__, + closure->ipaddr, relay_closure->ipaddr); + + client_msg.u.accept_msg = msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_ACCEPT_MSG; + ret = fmt_client_message(closure, &client_msg); + if (ret) { + if (sudo_ev_add(evbase, relay_closure->write_ev, NULL, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + ret = false; + } + } + + if (ret) { + /* success */ + if (msg->expect_iobufs) + closure->log_io = true; + closure->state = RUNNING; + } + + debug_return_bool(ret); +} + +/* + * Relay a RejectMessage from the client to the relay server. + */ +bool +relay_reject(RejectMessage *msg, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + struct sudo_event_base *evbase = closure->evbase; + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + bool ret; + debug_decl(relay_reject, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: relaying RejectMessage from %s to %s", __func__, + closure->ipaddr, relay_closure->ipaddr); + + client_msg.u.reject_msg = msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_REJECT_MSG; + ret = fmt_client_message(closure, &client_msg); + if (ret) { + if (sudo_ev_add(evbase, relay_closure->write_ev, NULL, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + ret = false; + } + } + + closure->state = FINISHED; + + debug_return_bool(ret); +} + +/* + * Relay an ExitMessage from the client to the relay server. + */ +bool +relay_exit(ExitMessage *msg, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + struct sudo_event_base *evbase = closure->evbase; + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + bool ret; + debug_decl(relay_exit, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: relaying ExitMessage from %s to %s", __func__, + closure->ipaddr, relay_closure->ipaddr); + + client_msg.u.exit_msg = msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_EXIT_MSG; + ret = fmt_client_message(closure, &client_msg); + if (ret) { + if (sudo_ev_add(evbase, relay_closure->write_ev, NULL, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + ret = false; + } + } + + if (ret) { + /* Command exited, if I/O logging wait for commit point. */ + closure->state = closure->log_io ? EXITED : FINISHED; + } + + debug_return_bool(ret); +} + +/* + * Relay a RestartMessage from the client to the relay server. + */ +bool +relay_restart(RestartMessage *msg, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + struct sudo_event_base *evbase = closure->evbase; + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + RestartMessage restart_msg = *msg; + char *cp; + bool ret; + debug_decl(relay_restart, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: relaying RestartMessage from %s to %s", __func__, + closure->ipaddr, relay_closure->ipaddr); + + /* + * We prepend "relayhost/" to the log ID before relaying it to + * the client. Perform the reverse operation before passing the + * log ID to the relay host. + */ + if ((cp = strchr(restart_msg.log_id, '/')) != NULL) + restart_msg.log_id = cp + 1; + + client_msg.u.restart_msg = &restart_msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_RESTART_MSG; + ret = fmt_client_message(closure, &client_msg); + if (ret) { + if (sudo_ev_add(evbase, relay_closure->write_ev, NULL, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + ret = false; + } + } + + closure->state = ret ? RUNNING : ERROR; + + debug_return_bool(ret); +} + +/* + * Relay an AlertMessage from the client to the relay server. + */ +bool +relay_alert(AlertMessage *msg, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + struct sudo_event_base *evbase = closure->evbase; + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + bool ret; + debug_decl(relay_alert, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: relaying AlertMessage from %s to %s", __func__, + closure->ipaddr, relay_closure->ipaddr); + + client_msg.u.alert_msg = msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_ALERT_MSG; + ret = fmt_client_message(closure, &client_msg); + if (ret) { + if (sudo_ev_add(evbase, relay_closure->write_ev, NULL, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + ret = false; + } + } + + debug_return_bool(ret); +} + +/* + * Relay an IoBuffer from the client to the relay server. + */ +bool +relay_iobuf(int iofd, IoBuffer *iobuf, struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + struct sudo_event_base *evbase = closure->evbase; + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + bool ret; + debug_decl(relay_iobuf, SUDO_DEBUG_UTIL); + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: relaying IoBuffer from %s to %s", __func__, + closure->ipaddr, relay_closure->ipaddr); + + switch (iofd) { + case IOFD_TTYIN: + client_msg.type_case = CLIENT_MESSAGE__TYPE_TTYIN_BUF; + client_msg.u.ttyin_buf = iobuf; + break; + case IOFD_TTYOUT: + client_msg.type_case = CLIENT_MESSAGE__TYPE_TTYOUT_BUF; + client_msg.u.ttyout_buf = iobuf; + break; + case IOFD_STDIN: + client_msg.type_case = CLIENT_MESSAGE__TYPE_STDIN_BUF; + client_msg.u.stdin_buf = iobuf; + break; + case IOFD_STDOUT: + client_msg.type_case = CLIENT_MESSAGE__TYPE_STDOUT_BUF; + client_msg.u.stdout_buf = iobuf; + break; + case IOFD_STDERR: + client_msg.type_case = CLIENT_MESSAGE__TYPE_STDERR_BUF; + client_msg.u.stderr_buf = iobuf; + break; + default: + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unexpected iofd value %d", iofd); + debug_return_bool(false); + } + ret = fmt_client_message(closure, &client_msg); + if (ret) { + if (sudo_ev_add(evbase, relay_closure->write_ev, NULL, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to add server write event"); + ret = false; + } + } + + debug_return_bool(ret); +} + +/* + * Shutdown relay connection when server is exiting. + */ +bool +relay_shutdown(struct connection_closure *closure) +{ + struct relay_closure *relay_closure = closure->relay_closure; + debug_decl(relay_shutdown, SUDO_DEBUG_UTIL); + + /* Close connection unless relay events are pending. */ + if (!sudo_ev_pending(relay_closure->read_ev, SUDO_EV_READ, NULL) && + !sudo_ev_pending(relay_closure->write_ev, SUDO_EV_WRITE, NULL) && + TAILQ_EMPTY(&relay_closure->write_bufs)) { + connection_closure_free(closure); + } + + debug_return_bool(true); +}