From 4df9687832f77daadbc9c46bb0b767466ca7e877 Mon Sep 17 00:00:00 2001 From: Virtually Nick Date: Sat, 18 Feb 2023 08:45:58 -0500 Subject: [PATCH] [WIP] Add SSH tunneling for RDP. --- src/protocols/rdp/client.c | 4 + src/protocols/rdp/rdp.c | 98 ++++++++++++++++ src/protocols/rdp/rdp.h | 8 ++ src/protocols/rdp/settings.c | 211 ++++++++++++++++++++++++++--------- src/protocols/rdp/settings.h | 63 +++++++++++ 5 files changed, 334 insertions(+), 50 deletions(-) diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index aed5ea68..11c953bf 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -31,6 +31,7 @@ #ifdef ENABLE_COMMON_SSH #include "common-ssh/sftp.h" #include "common-ssh/ssh.h" +#include "common-ssh/tunnel.h" #include "common-ssh/user.h" #endif @@ -220,6 +221,9 @@ int guac_rdp_client_free_handler(guac_client* client) { if (rdp_client->sftp_user) guac_common_ssh_destroy_user(rdp_client->sftp_user); + if (rdp_client->ssh_tunnel) + guac_common_ssh_tunnel_cleanup(rdp_client->ssh_tunnel); + guac_common_ssh_uninit(); #endif diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index e7ec71c5..4e4fa4f5 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -49,6 +49,7 @@ #ifdef ENABLE_COMMON_SSH #include "common-ssh/sftp.h" #include "common-ssh/ssh.h" +#include "common-ssh/tunnel.h" #include "common-ssh/user.h" #endif @@ -92,6 +93,13 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_settings* settings = rdp_client->settings; + + + /* Check for SSH tunneling and start it up, if requested */ + if (settings->ssh_tunnel) { + + } + /* Push desired settings to FreeRDP */ guac_rdp_push_settings(client, settings, instance); @@ -814,6 +822,96 @@ void* guac_rdp_client_thread(void* data) { "SFTP connection succeeded."); } + + /* If SSH tunneling is enabled, we set up the tunnel and redirect the connection. */ + if (settings->ssh_tunnel) { + + /* Allocate memory for the SSH tunnel data. */ + rdp_client->ssh_tunnel = malloc(sizeof(guac_ssh_tunnel)); + + guac_client_log(client, GUAC_LOG_DEBUG, + "SSH tunneling is enabled, connecting via SSH."); + + /* Associate the guac_client object with the tunnel. */ + rdp_client->ssh_tunnel->client = client; + + /* Abort if tunnel username is missing */ + if (settings->ssh_tunnel_username == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "An SSH tunnel-specific username is required if " + "SSH tunneling is enabled."); + return NULL; + } + + rdp_client->ssh_tunnel->user = + guac_common_ssh_create_user(settings->ssh_tunnel_username); + + /* Import SSH tunnel private key, if given */ + if (settings->ssh_tunnel_private_key != NULL) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Authenticating SSH tunnel with private key."); + + /* Abort if SSH tunnel private key cannot be read */ + if (guac_common_ssh_user_import_key(rdp_client->ssh_tunnel->user, + settings->ssh_tunnel_private_key, + settings->ssh_tunnel_passphrase)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "SSH tunnel private key unreadable."); + return NULL; + } + + } + + /* Otherwise, use specified SSH tunnel password */ + else { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Authenticating SSH tunnel with password."); + + guac_common_ssh_user_set_password(rdp_client->ssh_tunnel->user, + settings->ssh_tunnel_password); + + } + + /* Attempt SSH tunnel connection */ + rdp_client->ssh_tunnel->session = + guac_common_ssh_create_session(client, settings->ssh_tunnel_host, + settings->ssh_tunnel_port, rdp_client->ssh_tunnel->user, + settings->ssh_tunnel_alive_interval, + settings->ssh_tunnel_host_key, NULL); + + /* Fail if SSH tunnel connection does not succeed */ + if (rdp_client->ssh_tunnel->session == NULL) { + /* Already aborted within guac_common_ssh_create_session() */ + return NULL; + } + + guac_client_log(client, GUAC_LOG_DEBUG, + "SSH session created for tunneling, initializing the tunnel."); + + /* Initialize the tunnel or fail. */ + if (guac_common_ssh_tunnel_init(rdp_client->ssh_tunnel, + settings->hostname, settings->port)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to initialize SSH tunnel, aborting connection."); + return NULL; + } + + /* If tunnel socket is not returned, bail out. */ + if (rdp_client->ssh_tunnel->socket_path == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to obtain socket for SSH tunnel, aborting."); + return NULL; + } + + /* Overwrite the hostname with the path to the socket and zero out port. */ + settings->hostname = guac_strdup(rdp_client->ssh_tunnel->socket_path); + settings->port = 0; + + guac_client_log(client, GUAC_LOG_DEBUG, + "SSH tunnel connection succeeded."); + } #endif /* Set up screen recording, if requested */ diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 9e3d3fe0..a763efe3 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -37,6 +37,7 @@ #ifdef ENABLE_COMMON_SSH #include "common-ssh/sftp.h" #include "common-ssh/ssh.h" +#include "common-ssh/tunnel.h" #include "common-ssh/user.h" #endif @@ -50,6 +51,8 @@ #include #include +#define GUAC_RDP_DEFAULT_CONNECTION_TIMEOUT 30 + /** * RDP-specific client data. */ @@ -154,6 +157,11 @@ typedef struct guac_rdp_client { * An SFTP-based filesystem. */ guac_common_ssh_sftp_filesystem* sftp_filesystem; + + /** + * The SSH session used for the tunnel. + */ + guac_ssh_tunnel* ssh_tunnel; #endif /** diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index f2dbeabd..625a3368 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -20,6 +20,7 @@ #include "argv.h" #include "common/defaults.h" #include "common/string.h" +#include "common-ssh/ssh-constants.h" #include "config.h" #include "resolution.h" #include "settings.h" @@ -110,6 +111,15 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "sftp-server-alive-interval", "sftp-disable-download", "sftp-disable-upload", + "ssh-tunnel", + "ssh-tunnel-host", + "ssh-tunnel-port", + "ssh-tunnel-host-key", + "ssh-tunnel-username", + "ssh-tunnel-password", + "ssh-tunnel-private-key", + "ssh-tunnel-passphrase", + "ssh-tunnel-alive-interval", #endif "recording-path", @@ -493,6 +503,55 @@ enum RDP_ARGS_IDX { * blank otherwise. */ IDX_SFTP_DISABLE_UPLOAD, + + /** + * True if SSH tunneling should be enabled. If false or not set, SSH + * tunneling will not be used. + */ + IDX_SSH_TUNNEL, + + /** + * The hostname or IP address of the SSH server to use for tunneling. + */ + IDX_SSH_TUNNEL_HOST, + + /** + * The TCP port of the SSH server to use for tunneling. + */ + IDX_SSH_TUNNEL_PORT, + + /** + * If host key checking should be done, the public key of the SSH host + * to be used for tunneling. + */ + IDX_SSH_TUNNEL_HOST_KEY, + + /** + * The username for authenticating to the SSH hsot for tunneling. + */ + IDX_SSH_TUNNEL_USERNAME, + + /** + * The password to use to authenticate to the SSH host for tunneling. + */ + IDX_SSH_TUNNEL_PASSWORD, + + /** + * The private key to use to authenticate to the SSH host for tunneling, + * as an alternative to password-based authentication. + */ + IDX_SSH_TUNNEL_PRIVATE_KEY, + + /** + * The passphrase to use to decrypt the private key. + */ + IDX_SSH_TUNNEL_PASSPHRASE, + + /** + * The interval at which keepalive packets should be sent to the SSH + * tunneling server, or zero if keepalive should be disabled. + */ + IDX_SSH_TUNNEL_ALIVE_INTERVAL, #endif /** @@ -1036,66 +1095,112 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_ENABLE_SFTP, 0); - /* Hostname for SFTP connection */ - settings->sftp_hostname = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_HOSTNAME, settings->hostname); + /* Only parse remaining SFTP settings if it's enabled. */ + if (settings->enable_sftp) { + /* Hostname for SFTP connection */ + settings->sftp_hostname = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_HOSTNAME, settings->hostname); - /* The public SSH host key. */ - settings->sftp_host_key = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_HOST_KEY, NULL); + /* The public SSH host key. */ + settings->sftp_host_key = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_HOST_KEY, NULL); - /* Port for SFTP connection */ - settings->sftp_port = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_PORT, "22"); + /* Port for SFTP connection */ + settings->sftp_port = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_PORT, GUAC_COMMON_SSH_DEFAULT_PORT); - /* Username for SSH/SFTP authentication */ - settings->sftp_username = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_USERNAME, - settings->username != NULL ? settings->username : ""); + /* Username for SSH/SFTP authentication */ + settings->sftp_username = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_USERNAME, + settings->username != NULL ? settings->username : ""); - /* Password for SFTP (if not using private key) */ - settings->sftp_password = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_PASSWORD, ""); + /* Password for SFTP (if not using private key) */ + settings->sftp_password = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_PASSWORD, ""); - /* Private key for SFTP (if not using password) */ - settings->sftp_private_key = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_PRIVATE_KEY, NULL); + /* Private key for SFTP (if not using password) */ + settings->sftp_private_key = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_PRIVATE_KEY, NULL); - /* Passphrase for decrypting the SFTP private key (if applicable */ - settings->sftp_passphrase = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_PASSPHRASE, ""); + /* Passphrase for decrypting the SFTP private key (if applicable */ + settings->sftp_passphrase = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_PASSPHRASE, ""); - /* Default upload directory */ - settings->sftp_directory = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_DIRECTORY, NULL); + /* Default upload directory */ + settings->sftp_directory = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_DIRECTORY, NULL); - /* SFTP root directory */ - settings->sftp_root_directory = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_ROOT_DIRECTORY, "/"); + /* SFTP root directory */ + settings->sftp_root_directory = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_ROOT_DIRECTORY, GUAC_COMMON_SSH_SFTP_DEFAULT_ROOT); - /* Default keepalive value */ - settings->sftp_server_alive_interval = - guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_SERVER_ALIVE_INTERVAL, 0); - - /* Whether or not to disable file download over SFTP. */ - settings->sftp_disable_download = + /* Default keepalive value */ + settings->sftp_server_alive_interval = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_SERVER_ALIVE_INTERVAL, 0); + + /* Whether or not to disable file download over SFTP. */ + settings->sftp_disable_download = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_DISABLE_DOWNLOAD, 0); + + /* Whether or not to disable file upload over SFTP. */ + settings->sftp_disable_upload = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_DISABLE_UPLOAD, 0); + } + + /* Parse SSH tunneling. */ + settings->ssh_tunnel = guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_DISABLE_DOWNLOAD, 0); + IDX_SSH_TUNNEL, false); - /* Whether or not to disable file upload over SFTP. */ - settings->sftp_disable_upload = - guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_DISABLE_UPLOAD, 0); + /* Only parse remaining tunneling settings if it has been enabled. */ + if (settings->ssh_tunnel) { + + settings->ssh_tunnel_host = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_HOST, NULL); + + settings->ssh_tunnel_port = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PORT, GUAC_COMMON_SSH_DEFAULT_PORT); + + settings->ssh_tunnel_host_key = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_HOST_KEY, NULL); + + settings->ssh_tunnel_username = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_USERNAME, NULL); + + settings->ssh_tunnel_password = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PASSWORD, NULL); + + settings->ssh_tunnel_private_key = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PRIVATE_KEY, NULL); + + settings->ssh_tunnel_passphrase = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PASSPHRASE, NULL); + + settings->ssh_tunnel_alive_interval = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_ALIVE_INTERVAL, + GUAC_COMMON_SSH_DEFAULT_ALIVE_INTERVAL); + + } #endif /* Read recording path */ @@ -1324,7 +1429,7 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { } #ifdef ENABLE_COMMON_SSH - /* Free SFTP settings */ + /* Free SFTP and SSH tunnel settings */ free(settings->sftp_directory); free(settings->sftp_root_directory); free(settings->sftp_host_key); @@ -1334,6 +1439,12 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { free(settings->sftp_port); free(settings->sftp_private_key); free(settings->sftp_username); + free(settings->ssh_tunnel_host); + free(settings->ssh_tunnel_host_key); + free(settings->ssh_tunnel_port); + free(settings->ssh_tunnel_username); + free(settings->ssh_tunnel_password); + free(settings->ssh_tunnel_private_key); #endif /* Free RD gateway information */ diff --git a/src/protocols/rdp/settings.h b/src/protocols/rdp/settings.h index 3fe9d002..602a61b6 100644 --- a/src/protocols/rdp/settings.h +++ b/src/protocols/rdp/settings.h @@ -494,6 +494,69 @@ typedef struct guac_rdp_settings { * Whether or not to disable file upload over SFTP. */ int sftp_disable_upload; + + /** + * Whether to enable tunneling of this connection through the specified + * SSH server. If set to "true", guacd will attempt to connect to the SSH + * server and tunnel all of the traffic through the SSH connection. If + * set to "false" or not set, SSH tunneling will not be used. + */ + bool ssh_tunnel; + + /** + * The hostname or address of the host through which traffic should be + * tunneled over SSH. If tunneling is enabled, this is required, or the + * connection will be aborted. + */ + char* ssh_tunnel_host; + + /** + * The port on which to connect to the SSH server to tunnel traffic, if + * SSH tunneling is enabled. If not specified, this will default to 22, the + * normal SSH port. + */ + char* ssh_tunnel_port; + + /** + * The public key of the SSH host through which this connection will be + * tunneled. If unset, no host key checking will be done and the connection + * will be attempted regardless of the identity of the remote host. + */ + char* ssh_tunnel_host_key; + + /** + * The username to use when connecting to the SSH host to tunnel traffic. + * This is required if SSH tunneling is enabled. + */ + char* ssh_tunnel_username; + + /** + * The password to use when connecting to the SSH host to tunnel traffic, + * if password authentication is used. + */ + char* ssh_tunnel_password; + + /** + * The private key to use to authenticate to the SSH server to tunnel traffic, + * if key-based authentication is used. + */ + char* ssh_tunnel_private_key; + + /** + * The passphrase of the private key to use to decrypt the private key when + * using key-based authentication, if the key is encrypted. + */ + char* ssh_tunnel_passphrase; + + /** + * The interval at which keepalive messages will be sent to the SSH server + * over which the connection is being tunneled. The default is 0, meaning + * that keepalive messages will be disabled. The minimum value is 2 to avoid + * busy loop scenarios, and a value of 1 is automatically increased to 2 by + * the underlying libssh2 implementation. + */ + int ssh_tunnel_alive_interval; + #endif /**