diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:54:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:54:46 +0000 |
commit | cd7b005519ade8ab6c97fcb21590b71b7d1be6e3 (patch) | |
tree | c611a8d0cd5e8f68f41b8c2d16ba580e0f40a38d /rtrlib/transport/ssh | |
parent | Initial commit. (diff) | |
download | librtr-upstream.tar.xz librtr-upstream.zip |
Adding upstream version 0.8.0.upstream/0.8.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rtrlib/transport/ssh')
-rw-r--r-- | rtrlib/transport/ssh/ssh_transport.c | 392 | ||||
-rw-r--r-- | rtrlib/transport/ssh/ssh_transport.h | 74 | ||||
-rw-r--r-- | rtrlib/transport/ssh/ssh_transport_private.h | 13 |
3 files changed, 479 insertions, 0 deletions
diff --git a/rtrlib/transport/ssh/ssh_transport.c b/rtrlib/transport/ssh/ssh_transport.c new file mode 100644 index 0000000..222ea25 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport.c @@ -0,0 +1,392 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "ssh_transport_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/lib/utils_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/transport/transport_private.h" + +#include <assert.h> +#include <errno.h> +#include <libssh/libssh.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#define SSH_DBG(fmt, sock, ...) \ + do { \ + const struct tr_ssh_socket *tmp = sock; \ + lrtr_dbg("SSH Transport(%s@%s:%u): " fmt, tmp->config.username, tmp->config.host, tmp->config.port, \ + ##__VA_ARGS__); \ + } while (0) +#define SSH_DBG1(a, sock) SSH_DBG(a, sock) + +struct tr_ssh_socket { + ssh_session session; + ssh_channel channel; + struct tr_ssh_config config; + char *ident; +}; + +static int tr_ssh_open(void *tr_ssh_sock); +static void tr_ssh_close(void *tr_ssh_sock); +static void tr_ssh_free(struct tr_socket *tr_sock); +static int tr_ssh_recv(const void *tr_ssh_sock, void *buf, const size_t buf_len, const time_t timeout); +static int tr_ssh_send(const void *tr_ssh_sock, const void *pdu, const size_t len, const time_t timeout); +static int tr_ssh_recv_async(const struct tr_ssh_socket *tr_ssh_sock, void *buf, const size_t buf_len); +static const char *tr_ssh_ident(void *tr_ssh_sock); + +/* WARNING: This function has cancelable sections! */ +int tr_ssh_open(void *socket) +{ + struct tr_ssh_socket *ssh_socket = socket; + const struct tr_ssh_config *config = &ssh_socket->config; + + assert(!ssh_socket->channel); + assert(!ssh_socket->session); + + ssh_socket->session = ssh_new(); + if (!ssh_socket->session) { + SSH_DBG("%s: can't create ssh_session", ssh_socket, __func__); + goto error; + } + + const int verbosity = SSH_LOG_NOLOG; + + ssh_options_set(ssh_socket->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + ssh_options_set(ssh_socket->session, SSH_OPTIONS_HOST, config->host); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_PORT, &(config->port)); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_BINDADDR, config->bindaddr); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_USER, config->username); + + if (config->server_hostkey_path) + ssh_options_set(ssh_socket->session, SSH_OPTIONS_KNOWNHOSTS, config->server_hostkey_path); + + if (config->client_privkey_path) + ssh_options_set(ssh_socket->session, SSH_OPTIONS_IDENTITY, config->client_privkey_path); + if (config->new_socket) { + int fd; + + fd = config->new_socket(config->data); + if (fd >= 0) { + ssh_options_set(ssh_socket->session, SSH_OPTIONS_FD, &fd); + } else { + SSH_DBG1("tr_ssh_init: opening SSH connection failed", ssh_socket); + goto error; + } + } + + ssh_set_blocking(ssh_socket->session, 0); + int ret; + + do { + ret = ssh_connect(ssh_socket->session); + + if (ret == SSH_ERROR) { + SSH_DBG("%s: opening SSH connection failed", ssh_socket, __func__); + goto error; + } else if (ret == SSH_AGAIN) { + socket_t fd = ssh_get_fd(ssh_socket->session); + + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + struct timeval timeout = {.tv_sec = ssh_socket->config.connect_timeout, .tv_usec = 0}; + int oldcancelstate; + + /* Enable cancellability for the select call + * to prevent rtr_stop from blocking for a long time. + * It must be the only blocking call in this function. + * Since local resources have all been freed this should be safe. + */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + int sret = select(fd + 1, &rfds, NULL, NULL, &timeout); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + + if (sret < 0) { + SSH_DBG("could not select ssh socket, %s", ssh_socket, strerror(errno)); + + } else if (sret == 0) { + SSH_DBG1("connection attempt timed out", ssh_socket); + goto error; + } + } + + } while (ret != SSH_OK); + + ssh_set_blocking(ssh_socket->session, 1); + + // check server identity +#if LIBSSH_VERSION_MAJOR > 0 || LIBSSH_VERSION_MINOR > 8 + if ((config->server_hostkey_path) && (ssh_session_is_known_server(ssh_socket->session) != SSH_KNOWN_HOSTS_OK)) { +#else + if ((config->server_hostkey_path) && (ssh_is_server_known(ssh_socket->session) != SSH_SERVER_KNOWN_OK)) { +#endif + SSH_DBG("%s: Wrong hostkey", ssh_socket, __func__); + goto error; + } + + if (config->client_privkey_path) { + SSH_DBG("%s: Trying publickey authentication", ssh_socket, __func__); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_IDENTITY, config->client_privkey_path); + + int rtval; + +#if LIBSSH_VERSION_MAJOR > 0 || LIBSSH_VERSION_MINOR > 5 + rtval = ssh_userauth_publickey_auto(ssh_socket->session, NULL, NULL); +#else /* else use libSSH version 0.5.0 */ + rtval = ssh_userauth_autopubkey(ssh_socket->session, NULL); +#endif + if (rtval != SSH_AUTH_SUCCESS) { + SSH_DBG("%s: Publickey authentication failed", ssh_socket, __func__); + goto error; + } + + } else { + SSH_DBG("%s: Trying password authentication", ssh_socket, __func__); + + if (ssh_userauth_password(ssh_socket->session, NULL, config->password) != SSH_AUTH_SUCCESS) { + SSH_DBG("%s: Password authentication failed", ssh_socket, __func__); + goto error; + } + } + + ssh_socket->channel = ssh_channel_new(ssh_socket->session); + if (!ssh_socket->channel) + goto error; + + if (ssh_channel_open_session(ssh_socket->channel) == SSH_ERROR) + goto error; + + if (ssh_channel_request_subsystem(ssh_socket->channel, "rpki-rtr") == SSH_ERROR) { + SSH_DBG("%s: Error requesting subsystem rpki-rtr", ssh_socket, __func__); + goto error; + } + SSH_DBG1("Connection established", ssh_socket); + + return TR_SUCCESS; + +error: + tr_ssh_close(ssh_socket); + return TR_ERROR; +} + +void tr_ssh_close(void *tr_ssh_sock) +{ + struct tr_ssh_socket *socket = tr_ssh_sock; + + if (socket->channel) { + if (ssh_channel_is_open(socket->channel)) + ssh_channel_close(socket->channel); + ssh_channel_free(socket->channel); + socket->channel = NULL; + } + if (socket->session) { + ssh_disconnect(socket->session); + ssh_free(socket->session); + socket->session = NULL; + } + SSH_DBG1("Socket closed", socket); +} + +void tr_ssh_free(struct tr_socket *tr_sock) +{ + struct tr_ssh_socket *tr_ssh_sock = tr_sock->socket; + + assert(!tr_ssh_sock->channel); + assert(!tr_ssh_sock->session); + + SSH_DBG1("Freeing socket", tr_ssh_sock); + + lrtr_free(tr_ssh_sock->config.host); + lrtr_free(tr_ssh_sock->config.bindaddr); + lrtr_free(tr_ssh_sock->config.username); + lrtr_free(tr_ssh_sock->config.client_privkey_path); + lrtr_free(tr_ssh_sock->config.server_hostkey_path); + + if (tr_ssh_sock->ident) + lrtr_free(tr_ssh_sock->ident); + lrtr_free(tr_ssh_sock); + tr_sock->socket = NULL; +} + +int tr_ssh_recv_async(const struct tr_ssh_socket *tr_ssh_sock, void *buf, const size_t buf_len) +{ + const int rtval = ssh_channel_read_nonblocking(tr_ssh_sock->channel, buf, buf_len, false); + + if (rtval == 0) { + if (ssh_channel_is_eof(tr_ssh_sock->channel) != 0) { + SSH_DBG1("remote has sent EOF", tr_ssh_sock); + return TR_CLOSED; + } else { + return TR_WOULDBLOCK; + } + } else if (rtval == SSH_ERROR) { + SSH_DBG1("recv(..) error", tr_ssh_sock); + return TR_ERROR; + } + return rtval; +} + +int tr_ssh_recv(const void *tr_ssh_sock, void *buf, const size_t buf_len, const time_t timeout) +{ + ssh_channel rchans[2] = {((struct tr_ssh_socket *)tr_ssh_sock)->channel, NULL}; + struct timeval timev = {timeout, 0}; + int ret; + + ret = ssh_channel_select(rchans, NULL, NULL, &timev); + if (ret == SSH_EINTR) + return TR_INTR; + else if (ret == SSH_ERROR) + return TR_ERROR; + + if (ssh_channel_is_eof(((struct tr_ssh_socket *)tr_ssh_sock)->channel) != 0) + return TR_ERROR; + + if (!rchans[0]) + return TR_WOULDBLOCK; + + return tr_ssh_recv_async(tr_ssh_sock, buf, buf_len); +} + +int tr_ssh_send(const void *tr_ssh_sock, const void *pdu, const size_t len, + const time_t timeout __attribute__((unused))) +{ + int ret = ssh_channel_write(((struct tr_ssh_socket *)tr_ssh_sock)->channel, pdu, len); + + if (ret == SSH_ERROR) + return TR_ERROR; + + return ret; +} + +const char *tr_ssh_ident(void *tr_ssh_sock) +{ + size_t len; + struct tr_ssh_socket *sock = tr_ssh_sock; + + assert(sock); + + if (sock->ident) + return sock->ident; + + len = strlen(sock->config.username) + 1 + strlen(sock->config.host) + 1 + 5 + 1; + sock->ident = lrtr_malloc(len); + if (!sock->ident) + return NULL; + snprintf(sock->ident, len, "%s@%s:%u", sock->config.username, sock->config.host, sock->config.port); + return sock->ident; +} + +RTRLIB_EXPORT int tr_ssh_init(const struct tr_ssh_config *config, struct tr_socket *socket) +{ + socket->close_fp = &tr_ssh_close; + socket->free_fp = &tr_ssh_free; + socket->open_fp = &tr_ssh_open; + socket->recv_fp = &tr_ssh_recv; + socket->send_fp = &tr_ssh_send; + socket->ident_fp = &tr_ssh_ident; + + socket->socket = lrtr_calloc(1, sizeof(struct tr_ssh_socket)); + struct tr_ssh_socket *ssh_socket = socket->socket; + + ssh_socket->channel = NULL; + ssh_socket->session = NULL; + ssh_socket->config.host = lrtr_strdup(config->host); + if (!ssh_socket->config.host) + goto error; + ssh_socket->config.port = config->port; + + ssh_socket->config.username = lrtr_strdup(config->username); + if (!ssh_socket->config.username) + goto error; + + if ((config->password && config->client_privkey_path) || (!config->password && !config->client_privkey_path)) + return TR_ERROR; + + if (config->bindaddr) { + ssh_socket->config.bindaddr = lrtr_strdup(config->bindaddr); + + if (!ssh_socket->config.bindaddr) + goto error; + + } else { + ssh_socket->config.bindaddr = NULL; + } + + if (config->client_privkey_path) { + ssh_socket->config.client_privkey_path = lrtr_strdup(config->client_privkey_path); + if (!ssh_socket->config.client_privkey_path) + goto error; + + } else { + ssh_socket->config.client_privkey_path = NULL; + } + + if (config->server_hostkey_path) { + ssh_socket->config.server_hostkey_path = lrtr_strdup(config->server_hostkey_path); + + if (!ssh_socket->config.client_privkey_path) + goto error; + + } else { + ssh_socket->config.server_hostkey_path = NULL; + } + + if (config->connect_timeout == 0) + ssh_socket->config.connect_timeout = RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT; + else + ssh_socket->config.connect_timeout = config->connect_timeout; + + if (config->password) { + ssh_socket->config.password = lrtr_strdup(config->password); + + if (!ssh_socket->config.password) + goto error; + + } else { + ssh_socket->config.password = NULL; + } + + ssh_socket->ident = NULL; + ssh_socket->config.data = config->data; + ssh_socket->config.new_socket = config->new_socket; + + return TR_SUCCESS; + +error: + if (ssh_socket->config.host) + free(ssh_socket->config.host); + + if (ssh_socket->config.username) + free(ssh_socket->config.username); + + if (ssh_socket->config.bindaddr) + free(ssh_socket->config.bindaddr); + + if (ssh_socket->config.client_privkey_path) + free(ssh_socket->config.client_privkey_path); + + if (ssh_socket->config.server_hostkey_path) + free(ssh_socket->config.server_hostkey_path); + + if (ssh_socket->config.password) + free(ssh_socket->config.password); + + return TR_ERROR; +} diff --git a/rtrlib/transport/ssh/ssh_transport.h b/rtrlib/transport/ssh/ssh_transport.h new file mode 100644 index 0000000..188e256 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport.h @@ -0,0 +1,74 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_ssh_transport_h SSH transport socket + * @ingroup mod_transport_h + * @brief An implementation of the SSH protocol for the RTR transport. + * @details This transport implementation uses libssh + * (http://www.libssh.org/) for all ssh specific operations.\n + * See @ref mod_transport_h "transport interface" for a list of supported + * operations. + * + * @{ + * + * @example ssh_tr.c + * Example of how to open a SSH transport connection. + */ + +#ifndef SSH_TRANSPORT_H +#define SSH_TRANSPORT_H + +#include "rtrlib/transport/transport.h" + +/** + * @brief A tr_ssh_config struct holds configuration data for an tr_ssh socket. + * @param host Hostname or IP address to connect to. + * @param port Port to connect to. + * @param bindaddr Hostname or IP address to connect from. NULL for + * determination by OS. + * @param username Username for authentication. + * @param server_hostkey_path Path to public SSH key of the server or NULL to + * don't verify host authenticity. + * @param client_privkey_path Path to private key of the authentication keypair + * or NULL to use ~/.ssh/id_rsa. + * @param data Information to pass to callback function + * in charge of retrieving socket + * @param new_socket(void *opaque_info) callback routine, that + * Pointer to the function that is called every time a new connection + * is made. The returned socket is expected to be ready for use (e.g. + * in state established), and must use a reliably stream-oriented transport. + * When new_socket() is used, host, port, and bindaddr are not used. + * @param connect_timeout Time in seconds to wait for a successful connection. + * Defaults to #RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT + */ +struct tr_ssh_config { + char *host; + unsigned int port; + char *bindaddr; + char *username; + char *server_hostkey_path; + char *client_privkey_path; + void *data; + int (*new_socket)(void *data); + unsigned int connect_timeout; + char *password; +}; + +/** + * @brief Initializes the tr_socket struct for a SSH connection. + * @param[in] config SSH configuration for the connection. + * @param[out] socket Initialized transport socket. + * @returns TR_SUCCESS On success. + * @returns TR_ERROR On error. + */ +int tr_ssh_init(const struct tr_ssh_config *config, struct tr_socket *socket); + +#endif +/** @} */ diff --git a/rtrlib/transport/ssh/ssh_transport_private.h b/rtrlib/transport/ssh/ssh_transport_private.h new file mode 100644 index 0000000..b574863 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport_private.h @@ -0,0 +1,13 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef SSH_TRANSPORT_PRIVATE_H +#define SSH_TRANSPORT_PRIVATE_H +#include "ssh_transport.h" +#endif |