/* * SPDX-License-Identifier: ISC * * Copyright (c) 2019-2022 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. */ /* * This is an open source non-commercial project. Dear PVS-Studio, please check it. * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com */ #include #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 #define NEED_INET_NTOP /* to expose sudo_inet_ntop in sudo_compat.h */ #include "sudo_compat.h" #include "sudo_debug.h" #include "sudo_event.h" #include "sudo_eventlog.h" #include "sudo_gettext.h" #include "sudo_iolog.h" #include "sudo_fatal.h" #include "sudo_queue.h" #include "sudo_util.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 defined(HAVE_OPENSSL) if (relay_closure->tls_client.ssl != NULL) { sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "closing down TLS connection to %s", relay_closure->relay_name.name); if (SSL_shutdown(relay_closure->tls_client.ssl) == 0) SSL_shutdown(relay_closure->tls_client.ssl); SSL_free(relay_closure->tls_client.ssl); } #endif if (relay_closure->relays != NULL) address_list_delref(relay_closure->relays); sudo_rcstr_delref(relay_closure->relay_name.name); 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) { shutdown(relay_closure->sock, SHUT_RDWR); 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(); 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); } /* * Allocate a new buffer, copy buf to it and insert on the write queue. * On success the relay write event is enabled. * The length parameter does not include space for the message's wire size. */ static bool relay_enqueue_write(uint8_t *msgbuf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; struct connection_buffer *buf; uint32_t msg_len; bool ret = false; debug_decl(relay_enqueue_write, SUDO_DEBUG_UTIL); /* Wire message size is used for length encoding, precedes message. */ msg_len = htonl((uint32_t)len); sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "size + client message %zu bytes", len); if ((buf = get_free_buf(sizeof(msg_len) + len, closure)) == NULL) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "unable to allocate connection_buffer"); goto done; } memcpy(buf->data, &msg_len, sizeof(msg_len)); memcpy(buf->data + sizeof(msg_len), msgbuf, len); buf->len = sizeof(msg_len) + len; if (sudo_ev_add(closure->evbase, relay_closure->write_ev, NULL, false) == -1) { sudo_warnx("%s", U_("unable to add event to queue")); goto done; } 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); } /* * 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 = NULL; uint32_t msg_len; bool ret = false; size_t len; debug_decl(fmt_client_message, SUDO_DEBUG_UTIL); len = client_message__get_packed_size(msg); if (len > MESSAGE_SIZE_MAX) { sudo_warnx(U_("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); sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "size + client message %zu bytes", len); if ((buf = get_free_buf(len, closure)) == NULL) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "unable to allocate connection_buffer"); goto done; } 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); ret = true; done: 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 = (char *)"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_warnx("%s", U_("unable to add event to queue")); ret = false; } if (sudo_ev_add(closure->evbase, relay_closure->write_ev, NULL, false) == -1) { sudo_warnx("%s", U_("unable to add event to queue")); ret = false; } } debug_return_bool(ret); } #if defined(HAVE_OPENSSL) /* Wrapper for start_relay() called via tls_connect_cb() */ static bool tls_client_start_fn(struct tls_client_closure *tls_client) { sudo_ev_free(tls_client->tls_connect_ev); tls_client->tls_connect_ev = NULL; return start_relay(SSL_get_fd(tls_client->ssl), tls_client->parent_closure); } /* Perform TLS connection to the relay host. */ static bool connect_relay_tls(struct connection_closure *closure) { struct tls_client_closure *tls_client = &closure->relay_closure->tls_client; SSL_CTX *ssl_ctx = logsrvd_relay_tls_ctx(); debug_decl(connect_relay_tls, SUDO_DEBUG_UTIL); /* Populate struct tls_client_closure. */ tls_client->parent_closure = closure; tls_client->evbase = closure->evbase; tls_client->tls_connect_ev = sudo_ev_alloc(closure->relay_closure->sock, SUDO_EV_WRITE, tls_connect_cb, tls_client); if (tls_client->tls_connect_ev == NULL) goto bad; tls_client->peer_name = &closure->relay_closure->relay_name; tls_client->connect_timeout = *logsrvd_conf_relay_connect_timeout(); tls_client->start_fn = tls_client_start_fn; if (!tls_ctx_client_setup(ssl_ctx, closure->relay_closure->sock, tls_client)) goto bad; debug_return_bool(true); bad: debug_return_bool(false); } #endif /* HAVE_OPENSSL */ /* * 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. */ static 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_warn("socket"); goto bad; } if (logsrvd_conf_relay_tcp_keepalive()) { int keepalive = 1; if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)) == -1) { sudo_warn("SO_KEEPALIVE"); } } ret = fcntl(sock, F_GETFL, 0); if (ret == -1 || fcntl(sock, F_SETFL, ret | O_NONBLOCK) == -1) { sudo_warn("fcntl(O_NONBLOCK)"); 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; #ifdef HAVE_STRUCT_IN6_ADDR case AF_INET6: addr = (char *)&relay->sa_un.sin6.sin6_addr; break; #endif default: errno = EAFNOSUPPORT; sudo_warn("connect"); goto bad; } inet_ntop(relay->sa_un.sa.sa_family, addr, relay_closure->relay_name.ipaddr, sizeof(relay_closure->relay_name.ipaddr)); relay_closure->relay_name.name = sudo_rcstr_addref(relay->sa_host); if (ret == 0) { if (relay_closure->sock != -1) { shutdown(relay_closure->sock, SHUT_RDWR); close(relay_closure->sock); } relay_closure->sock = sock; #if defined(HAVE_OPENSSL) /* Relay connection succeeded, start TLS handshake. */ if (relay_closure->relay_addr->tls) { if (!connect_relay_tls(closure)) goto bad; } else #endif { /* Connection succeeded without blocking. */ 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_relay_connect_timeout(), false) == -1) { sudo_warnx("%s", U_("unable to add event to queue")); goto bad; } if (relay_closure->sock != -1) { shutdown(relay_closure->sock, SHUT_RDWR); close(relay_closure->sock); } relay_closure->sock = sock; closure->state = CONNECTING; } debug_return_int(ret); bad: /* Connection or system error. */ if (sock != -1) { shutdown(sock, SHUT_RDWR); close(sock); } sudo_rcstr_delref(relay_closure->relay_name.name); relay_closure->relay_name.name = NULL; 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) { closure->state = INITIAL; #if defined(HAVE_OPENSSL) /* Relay connection succeeded, start TLS handshake. */ if (relay_closure->relay_addr->tls) { if (!connect_relay_tls(closure)) { closure->errstr = _("TLS handshake with relay host failed"); if (!schedule_error_message(closure->errstr, closure)) connection_close(closure); } } else #endif { /* Relay connection succeeded, start talking to the client. */ if (!start_relay(sock, closure)) { closure->errstr = _("unable to allocate memory"); if (!schedule_error_message(closure->errstr, closure)) connection_close(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): %s", relay_closure->relay_name.name, relay_closure->relay_name.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"); if (!schedule_error_message(closure->errstr, closure)) connection_close(closure); } } 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); /* Switch to relay client message handlers. */ closure->cms = &cms_relay; 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_warnx(U_("unexpected state %d for %s"), closure->state, relay_closure->relay_name.ipaddr); closure->errstr = _("state machine error"); debug_return_bool(false); } /* Check that ServerHello is valid. */ if (msg->server_id == NULL || msg->server_id[0] == '\0') { sudo_warnx(U_("%s: invalid ServerHello, missing server_id"), relay_closure->relay_name.ipaddr); closure->errstr = _("invalid ServerHello"); debug_return_bool(false); } sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "relay server %s (%s) ID %s", relay_closure->relay_name.name, relay_closure->relay_name.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_warnx(U_("unexpected state %d for %s"), closure->state, closure->relay_closure->relay_name.ipaddr); 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; int len; debug_decl(handle_log_id, SUDO_DEBUG_UTIL); sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "log ID %s from relay %s (%s)", id, closure->relay_closure->relay_name.name, closure->relay_closure->relay_name.ipaddr); /* No client connection when replaying a journaled entry. */ if (closure->write_ev == NULL) debug_return_bool(true); /* Generate a new log ID that includes the relay host. */ len = asprintf(&new_id, "%s/%s", id, closure->relay_closure->relay_name.name); if (len != -1) { if (fmt_log_id_message(id, closure)) { if (sudo_ev_add(closure->evbase, closure->write_ev, logsrvd_conf_relay_timeout(), false) == -1) { sudo_warnx("%s", U_("unable to add event to queue")); } 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): %s", relay_closure->relay_name.name, relay_closure->relay_name.ipaddr, errmsg); /* Server will drop connection after the error message. */ sudo_ev_del(closure->evbase, closure->relay_closure->read_ev); sudo_ev_del(closure->evbase, closure->relay_closure->write_ev); if (!schedule_error_message(errmsg, closure)) debug_return_bool(false); 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): %s", relay_closure->relay_name.name, relay_closure->relay_name.ipaddr, errmsg); if (!schedule_error_message(errmsg, closure)) debug_return_bool(false); 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_warnx(U_("unable to unpack %s size %zu"), "ServerMessage", 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_warnx(U_("unexpected type_case value %d in %s from %s"), msg->type_case, "ServerMessage", closure->relay_closure->relay_name.ipaddr); 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); /* For TLS we may need to read as part of SSL_write(). */ if (relay_closure->write_instead_of_read) { relay_closure->write_instead_of_read = false; relay_client_msg_cb(fd, what, v); debug_return; } if (what == SUDO_EV_TIMEOUT) { sudo_warnx(U_("timed out reading from relay %s (%s)"), relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); closure->errstr = _("timeout reading from relay"); goto send_error; } #if defined(HAVE_OPENSSL) if (relay_closure->tls_client.ssl != NULL) { SSL *ssl = relay_closure->tls_client.ssl; sudo_debug_printf(SUDO_DEBUG_INFO, "%s: ServerMessage from relay %s (%s) [TLS]", __func__, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); nread = SSL_read(ssl, buf->data + buf->len, buf->size - buf->len); if (nread <= 0) { const char *errstr; int err; switch (SSL_get_error(ssl, nread)) { case SSL_ERROR_ZERO_RETURN: /* ssl connection shutdown cleanly */ nread = 0; break; case SSL_ERROR_WANT_READ: /* ssl wants to read more, read event is always active */ sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO, "SSL_read returns SSL_ERROR_WANT_READ"); debug_return; case SSL_ERROR_WANT_WRITE: /* ssl wants to write, schedule a write if not pending */ sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO, "SSL_read returns SSL_ERROR_WANT_WRITE"); if (!sudo_ev_pending(relay_closure->write_ev, SUDO_EV_WRITE, NULL)) { /* Enable a temporary write event. */ if (sudo_ev_add(closure->evbase, relay_closure->write_ev, NULL, false) == -1) { sudo_warnx("%s", U_("unable to add event to queue")); closure->errstr = _("unable to allocate memory"); goto send_error; } relay_closure->temporary_write_event = true; } /* Redirect write event to finish SSL_read() */ relay_closure->read_instead_of_write = true; debug_return; case SSL_ERROR_SSL: /* * For TLS 1.3, if the cert verify function on the server * returns an error, OpenSSL will send an internal error * alert when we read ServerHello. Convert to a more useful * message and hope that no actual internal error occurs. */ err = ERR_get_error(); #if !defined(HAVE_WOLFSSL) if (closure->state == INITIAL && ERR_GET_REASON(err) == SSL_R_TLSV1_ALERT_INTERNAL_ERROR) { errstr = _("relay host name does not match certificate"); closure->errstr = errstr; } else #endif { errstr = ERR_reason_error_string(err); closure->errstr = _("error reading from relay"); } sudo_warnx("%s: SSL_read: %s", relay_closure->relay_name.ipaddr, errstr ? errstr : strerror(errno)); goto send_error; case SSL_ERROR_SYSCALL: if (nread == 0) { /* EOF, handled below */ sudo_warnx(U_("EOF from %s without proper TLS shutdown"), relay_closure->relay_name.ipaddr); break; } sudo_warn("%s: SSL_read", relay_closure->relay_name.ipaddr); closure->errstr = _("error reading from relay"); goto send_error; default: errstr = ERR_reason_error_string(ERR_get_error()); sudo_warnx("%s: SSL_read: %s", relay_closure->relay_name.ipaddr, errstr ? errstr : strerror(errno)); closure->errstr = _("error reading from relay"); goto send_error; } } } else #endif { sudo_debug_printf(SUDO_DEBUG_INFO, "%s: ServerMessage from relay %s (%s)", __func__, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); nread = read(fd, buf->data + buf->len, buf->size - buf->len); } sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received %zd bytes from relay %s (%s)", __func__, nread, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); switch (nread) { case -1: if (errno == EAGAIN || errno == EINTR) debug_return; sudo_warn("%s: read", relay_closure->relay_name.ipaddr); closure->errstr = _("unable to read from relay"); goto send_error; case 0: /* EOF from relay server, close the socket. */ shutdown(relay_closure->sock, SHUT_RDWR); 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, "premature EOF from %s (%s) [state %d]", relay_closure->relay_name.name, relay_closure->relay_name.ipaddr, closure->state); closure->errstr = _("relay server closed connection"); goto send_error; } if (closure->sock == -1) connection_close(closure); 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_warnx(U_("server message too large: %zu"), (size_t)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 (!schedule_error_message(closure->errstr, closure)) goto close_connection; debug_return; close_connection: connection_close(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); /* For TLS we may need to write as part of SSL_read(). */ if (relay_closure->read_instead_of_write) { relay_closure->read_instead_of_write = false; /* Delete write event if it was only due to SSL_read(). */ if (relay_closure->temporary_write_event) { relay_closure->temporary_write_event = false; sudo_ev_del(closure->evbase, relay_closure->write_ev); } relay_server_msg_cb(fd, what, v); debug_return; } if (what == SUDO_EV_TIMEOUT) { sudo_warnx(U_("timed out writing to relay %s (%s)"), relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); closure->errstr = _("timeout writing to relay"); goto send_error; } if ((buf = TAILQ_FIRST(&relay_closure->write_bufs)) == NULL) { sudo_warnx(U_("missing write buffer for client %s"), relay_closure->relay_name.ipaddr); goto close_connection; } sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending %u bytes to server %s (%s)", __func__, buf->len - buf->off, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); #if defined(HAVE_OPENSSL) if (relay_closure->tls_client.ssl != NULL) { SSL *ssl = relay_closure->tls_client.ssl; nwritten = SSL_write(ssl, buf->data + buf->off, buf->len - buf->off); if (nwritten <= 0) { const char *errstr; switch (SSL_get_error(ssl, nwritten)) { case SSL_ERROR_ZERO_RETURN: /* ssl connection shutdown cleanly */ shutdown(relay_closure->sock, SHUT_RDWR); 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, "premature EOF from %s (state %d)", relay_closure->relay_name.ipaddr, closure->state); closure->errstr = _("relay server closed connection"); goto send_error; } debug_return; case SSL_ERROR_WANT_READ: /* ssl wants to read, read event always active */ sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO, "SSL_write returns SSL_ERROR_WANT_READ"); /* Redirect read event to finish SSL_write() */ relay_closure->write_instead_of_read = true; debug_return; case SSL_ERROR_WANT_WRITE: /* ssl wants to write more, write event remains active */ sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO, "SSL_write returns SSL_ERROR_WANT_WRITE"); debug_return; case SSL_ERROR_SYSCALL: sudo_warn("%s: SSL_write", relay_closure->relay_name.ipaddr); closure->errstr = _("error writing to relay"); goto send_error; default: errstr = ERR_reason_error_string(ERR_get_error()); sudo_warnx("%s: SSL_write: %s", relay_closure->relay_name.ipaddr, errstr ? errstr : strerror(errno)); closure->errstr = _("error writing to relay"); goto send_error; } } } else #endif { nwritten = write(fd, buf->data + buf->off, buf->len - buf->off); if (nwritten == -1) { if (errno == EAGAIN || errno == EINTR) debug_return; sudo_warn("%s: write", relay_closure->relay_name.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 (!schedule_error_message(closure->errstr, closure)) goto close_connection; debug_return; close_connection: connection_close(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. */ static bool relay_accept(AcceptMessage *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; const char *source = closure->journal_path ? closure->journal_path : closure->ipaddr; debug_decl(relay_accept, SUDO_DEBUG_UTIL); sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relaying AcceptMessage from %s to %s (%s)", __func__, source, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); debug_return_bool(relay_enqueue_write(buf, len, closure)); } /* * Relay a RejectMessage from the client to the relay server. */ static bool relay_reject(RejectMessage *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; const char *source = closure->journal_path ? closure->journal_path : closure->ipaddr; debug_decl(relay_reject, SUDO_DEBUG_UTIL); sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relaying RejectMessage from %s to %s (%s)", __func__, source, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); debug_return_bool(relay_enqueue_write(buf, len, closure)); } /* * Relay an ExitMessage from the client to the relay server. */ static bool relay_exit(ExitMessage *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; const char *source = closure->journal_path ? closure->journal_path : closure->ipaddr; debug_decl(relay_exit, SUDO_DEBUG_UTIL); sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relaying ExitMessage from %s to %s (%s)", __func__, source, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); debug_return_bool(relay_enqueue_write(buf, len, closure)); } /* * Relay a RestartMessage from the client to the relay server. * We must rebuild the packed message because the log_id is modified. */ static bool relay_restart(RestartMessage *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; const char *source = closure->journal_path ? closure->journal_path : closure->ipaddr; 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 (%s)", __func__, source, relay_closure->relay_name.name, relay_closure->relay_name.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) { if (cp != restart_msg.log_id) 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_warnx("%s", U_("unable to add event to queue")); ret = false; } } debug_return_bool(ret); } /* * Relay an AlertMessage from the client to the relay server. */ static bool relay_alert(AlertMessage *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; const char *source = closure->journal_path ? closure->journal_path : closure->ipaddr; bool ret; debug_decl(relay_alert, SUDO_DEBUG_UTIL); sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relaying AlertMessage from %s to %s (%s)", __func__, source, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); ret = relay_enqueue_write(buf, len, closure); debug_return_bool(ret); } /* * Relay a CommandSuspend from the client to the relay server. */ static bool relay_suspend(CommandSuspend *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; const char *source = closure->journal_path ? closure->journal_path : closure->ipaddr; bool ret; debug_decl(relay_suspend, SUDO_DEBUG_UTIL); sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relaying CommandSuspend from %s to %s (%s)", __func__, source, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); ret = relay_enqueue_write(buf, len, closure); debug_return_bool(ret); } /* * Relay a ChangeWindowSize from the client to the relay server. */ static bool relay_winsize(ChangeWindowSize *msg, uint8_t *buf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; const char *source = closure->journal_path ? closure->journal_path : closure->ipaddr; bool ret; debug_decl(relay_winsize, SUDO_DEBUG_UTIL); sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relaying ChangeWindowSize from %s to %s (%s)", __func__, source, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); ret = relay_enqueue_write(buf, len, closure); debug_return_bool(ret); } /* * Relay an IoBuffer from the client to the relay server. */ static bool relay_iobuf(int iofd, IoBuffer *iobuf, uint8_t *buf, size_t len, struct connection_closure *closure) { struct relay_closure *relay_closure = closure->relay_closure; const char *source = closure->journal_path ? closure->journal_path : closure->ipaddr; bool ret; debug_decl(relay_iobuf, SUDO_DEBUG_UTIL); sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relaying IoBuffer from %s to %s (%s)", __func__, source, relay_closure->relay_name.name, relay_closure->relay_name.ipaddr); ret = relay_enqueue_write(buf, len, closure); 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_close(closure); } debug_return_bool(true); } struct client_message_switch cms_relay = { relay_accept, relay_reject, relay_exit, relay_restart, relay_alert, relay_iobuf, relay_suspend, relay_winsize };