diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 5d1a88d153..3cc43d8e7e 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -25,6 +25,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 +AM_CPPFLAGS = -DGUACD_STATE_DIR='"$(localstatedir)/run/guacd"' noinst_LTLIBRARIES = libguac_common_ssh.la SUBDIRS = . tests @@ -34,6 +35,7 @@ libguac_common_ssh_la_SOURCES = \ sftp.c \ ssh.c \ key.c \ + tunnel.c \ user.c noinst_HEADERS = \ @@ -41,6 +43,7 @@ noinst_HEADERS = \ common-ssh/key.h \ common-ssh/sftp.h \ common-ssh/ssh.h \ + common-ssh/tunnel.h \ common-ssh/user.h libguac_common_ssh_la_CFLAGS = \ diff --git a/src/common-ssh/common-ssh/key.h b/src/common-ssh/common-ssh/key.h index 4ed9bacd90..704a9bfbbf 100644 --- a/src/common-ssh/common-ssh/key.h +++ b/src/common-ssh/common-ssh/key.h @@ -25,6 +25,8 @@ #include #include +#define GUAC_COMMON_SSH_KEY_DEFAULT_KNOWN_HOSTS "/etc/guacamole/ssh_known_hosts" + /** * OpenSSH v1 private keys are PEM-wrapped base64-encoded blobs. The encoded data begins with: * "openssh-key-v1\0" diff --git a/src/common-ssh/common-ssh/ssh-constants.h b/src/common-ssh/common-ssh/ssh-constants.h new file mode 100644 index 0000000000..e2e8dd8575 --- /dev/null +++ b/src/common-ssh/common-ssh/ssh-constants.h @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_COMMON_SSH_CONSTANTS_H +#define GUAC_COMMON_SSH_CONSTANTS_H + +/** + * The default port to use for SSH and SFTP connections. + */ +#define GUAC_COMMON_SSH_DEFAULT_PORT "22" + +/** + * The default interval at which to send keepalives, which is zero, where + * keepalives will not be sent. + */ +#define GUAC_COMMON_SSH_DEFAULT_ALIVE_INTERVAL 0 + +/** + * The default timeout for establishing SSH connections, in seconds. + */ +#define GUAC_COMMON_SSH_DEFAULT_TIMEOUT 30 + +/** + * For SFTP connections, the default root directory at which to start + * the session. + */ +#define GUAC_COMMON_SSH_SFTP_DEFAULT_ROOT "/" + +#endif diff --git a/src/common-ssh/common-ssh/tunnel.h b/src/common-ssh/common-ssh/tunnel.h new file mode 100644 index 0000000000..647f0cb4b0 --- /dev/null +++ b/src/common-ssh/common-ssh/tunnel.h @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_COMMON_SSH_TUNNEL_H +#define GUAC_COMMON_SSH_TUNNEL_H + +#include "common-ssh/ssh.h" + +#include +#include + +/** + * Default backlog size for the socket used for the SSH tunnel. + */ +#define GUAC_COMMON_SSH_TUNNEL_BACKLOG_SIZE 8 + +/** + * The default directory mode that will be used to create the directory that + * will store the sockets. + */ +#define GUAC_COMMON_SSH_TUNNEL_DIRECTORY_MODE 0700 + +/** + * The default mode of the file that will be used to access the UNIX domain + * socket. + */ +#define GUAC_COMMON_SSH_TUNNEL_SOCKET_MODE 0600 + +/** + * A data structure that contains the elements needed to be passed between + * the various Guacamole Client protocol implementations and the common SSH + * tunnel code. + */ +typedef struct guac_ssh_tunnel { + + /** + * The Guacamole Client that is using this SSH tunnel. + */ + guac_client* client; + + /** + * The user and credentials for authenticating the SSH tunnel. + */ + guac_common_ssh_user* user; + + /** + * The SSH session to use to tunnel the data. + */ + guac_common_ssh_session* session; + + /** + * The libssh2 channel that will carry the tunnel data over the SSH connection. + */ + LIBSSH2_CHANNEL *channel; + + /** + * The path to the local socket that will be used by guacd to communicate + * with the SSH tunnel. + */ + char* socket_path; + +} guac_ssh_tunnel; + +/** + * Initialize the SSH tunnel to the given host and port combination through + * the provided SSH session, and open a socket at the specified path for the + * communication. This function will place the absolute path of the domain + * socket in the socket_path variable and return zero on success or non-zero + * on failure. + * + * @param ssh_tunnel + * The data structure containing relevant SSH tunnel information, including + * the guac_client that initialized the tunnel and the various libssh2 + * session and channel objects. + * + * @param host + * The hostname or IP address to connect to over the tunnel. + * + * @param port + * The TCP port to connect to over the tunnel. + * + * @return + * Zero on success, non-zero on failure. + */ +int guac_common_ssh_tunnel_init(guac_ssh_tunnel* ssh_tunnel, + char* remote_host, + int remote_port); + +/** + * Clean up the SSH tunnel, shutting down the channel and freeing the + * various data items created for the tunnel. + * + * @param ssh_tunnel + * The guac_common_ssh_session used to establish the tunnel. + * + * @return + * Zero on success, non-zero on failure. + */ +int guac_common_ssh_tunnel_cleanup(guac_ssh_tunnel* ssh_tunnel); + +#endif \ No newline at end of file diff --git a/src/common-ssh/key.c b/src/common-ssh/key.c index a7f861d890..58a8863e0b 100644 --- a/src/common-ssh/key.c +++ b/src/common-ssh/key.c @@ -192,7 +192,7 @@ int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* clien /* Otherwise, we look for a ssh_known_hosts file within GUACAMOLE_HOME and read that in. */ else { - const char *guac_known_hosts = "/etc/guacamole/ssh_known_hosts"; + const char *guac_known_hosts = GUAC_COMMON_SSH_KEY_DEFAULT_KNOWN_HOSTS; if (access(guac_known_hosts, F_OK) != -1) known_hosts = libssh2_knownhost_readfile(ssh_known_hosts, guac_known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH); diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index 582c3ebc16..9e98618773 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -473,10 +474,20 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, int timeout, int keepalive, const char* host_key, guac_ssh_credential_handler* credential_handler) { - int fd = guac_tcp_connect(hostname, port, timeout); + int fd; + struct stat sb; + + /* Hostname is a UNIX socket */ + if (stat(hostname, &sb) == 0 && (sb.st_mode & S_IFMT) == S_IFSOCK) + fd = guac_socket_unix_connect(hostname); + + /* Hostname is IP-based */ + else + fd = guac_tcp_connect(hostname, port, timeout); + if (fd < 0) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Failed to open TCP connection to %s on %s.", hostname, port); + "Failed to open SSH connection to %s on %s", hostname, port); return NULL; } diff --git a/src/common-ssh/tunnel.c b/src/common-ssh/tunnel.c new file mode 100644 index 0000000000..96788ef2aa --- /dev/null +++ b/src/common-ssh/tunnel.c @@ -0,0 +1,374 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common-ssh/ssh.h" +#include "common-ssh/tunnel.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * A collection of the data required to establish the tunnel connection to the + * remote host over SSH and pass data between threads. + */ +typedef struct ssh_tunnel_data { + + /** + * The SSH tunnel. + */ + guac_ssh_tunnel* ssh_tunnel; + + /** + * The thread used to run the main tunnel worker. + */ + pthread_t tunnel_thread; + + /** + * The thread used to run the tunnel input worker. + */ + pthread_t tunnel_input_thread; + + /** + * A lock used to manage concurrent access to the libsh2 channel. + */ + pthread_mutex_t tunnel_channel_lock; + + /** + * The file descriptor of the socket that guacd will listen on to start + * the tunnel to the remote system. + */ + int listen_socket; + + /** + * The file descriptor of the socket that will be used to read and write + * data to the remote tunnel. + */ + int tunnel_socket; + + /** + * The UNIX address family data structure. + */ + struct sockaddr_un tunnel_addr; + + /** + * The hostname or IP address of the remote host. + */ + char* remote_host; + + /** + * The TCP port to connect to on the remote host. + */ + int remote_port; + +} ssh_tunnel_data; + +/** + * A function called by pthread_create that will be the worker function for + * incoming data on the SSH tunnel. + * + * @param data + * A pointer to the ssh_tunnel_parameters structure that contains the data + * required to pass data from the local system over the tunnel to the remote + * SSH server. + */ +static void* guac_common_ssh_tunnel_input_worker(void* data) { + + ssh_tunnel_data* tunnel_data = (ssh_tunnel_data*) data; + + char buffer[8192]; + int bytes_read; + int retval = 0; + + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_DEBUG, + "Waiting for data on socket."); + + /* Read data from the socket and write it to the channel. */ + while (true) { + bytes_read = read(tunnel_data->tunnel_socket, buffer, sizeof(buffer)); + if (bytes_read <= 0) + break; + + pthread_mutex_lock(&(tunnel_data->tunnel_channel_lock)); + libssh2_channel_write(tunnel_data->ssh_tunnel->channel, buffer, bytes_read); + pthread_mutex_unlock(&(tunnel_data->tunnel_channel_lock)); + } + + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_DEBUG, + "Finished reading from socket, exiting."); + + pthread_exit(&retval); + return NULL; + +} + +/** + * A function passed to phtread_create that will be the worker function for + * the SSH tunnel. The data passed should be a ssh_tunnel_parameters structure + * that contains all of the information this function will need to start + * the remote connection and process the data. Note that the socket passed + * via this data structure should already be in the LISTENING state by the + * time this function is called, and this function will wait for and accept + * a connection on the socket in order to start the process of connecting to + * the remote host over the tunnel and pass data. + * + * @param data + * A pointer to a ssh_tunnel_parameters structure that contains the data + * required to establish the connection over the SSH tunnel. + */ +static void* guac_common_ssh_tunnel_worker(void* data) { + + ssh_tunnel_data* tunnel_data = (ssh_tunnel_data*) data; + int bytes_read, bytes_written, bytes_current; + int retval = 0; + char buffer[8192]; + fd_set fds; + struct timeval tv; + + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_DEBUG, + "Starting tunnel worker - waiting for connection."); + + /* Wait for a connection on the listening socket and process if we get it. */ + socklen_t addr_len = sizeof(tunnel_data->tunnel_addr); + tunnel_data->tunnel_socket = accept(tunnel_data->listen_socket, + (struct sockaddr*)(&(tunnel_data->tunnel_addr)), + &addr_len); + if (tunnel_data->tunnel_socket < 0) { + pthread_exit(&retval); + return NULL; + } + + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_DEBUG, + "Connection received, starting libssh2 channel."); + + /* Get the libssh2 Direct TCP/IP channel. */ + tunnel_data->ssh_tunnel->channel = libssh2_channel_direct_tcpip( + tunnel_data->ssh_tunnel->session->session, + tunnel_data->remote_host, + tunnel_data->remote_port); + + if (!tunnel_data->ssh_tunnel->channel) { + pthread_exit(&retval); + return NULL; + } + + guac_client_log(tunnel_data->ssh_tunnel->client, + GUAC_LOG_DEBUG, + "Channel started, starting output thread."); + + /* Turn off blocking on the socket, and start the input thread. */ + libssh2_session_set_blocking(tunnel_data->ssh_tunnel->session->session, 0); + pthread_create(&(tunnel_data->tunnel_input_thread), NULL, + guac_common_ssh_tunnel_input_worker, (void *) tunnel_data); + + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_DEBUG, + "Processing tunnel data."); + + /* Loop to process the data. */ + while (true) { + FD_ZERO(&fds); + FD_SET(tunnel_data->tunnel_socket, &fds); + tv.tv_sec = 0; + tv.tv_usec = 100000; + retval = select(tunnel_data->tunnel_socket, &fds, NULL, NULL, &tv); + + if (retval < 0) { + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_ERROR, + "Error receiving data from socket."); + pthread_exit(&retval); + return NULL; + } + + pthread_mutex_lock(&(tunnel_data->tunnel_channel_lock)); + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_TRACE, + "Lock acquired, reading data from channel."); + bytes_read = libssh2_channel_read(tunnel_data->ssh_tunnel->channel, + buffer, sizeof(buffer)); + pthread_mutex_unlock(&(tunnel_data->tunnel_channel_lock)); + + /* No data read from the channel, skip the rest of the loop. */ + if (bytes_read == LIBSSH2_ERROR_EAGAIN) + continue; + + if (bytes_read < 0) { + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_ERROR, + "Error reading from libssh2 channel, giving up."); + pthread_exit(&retval); + return NULL; + } + + bytes_written = 0; + while (bytes_written < bytes_read) { + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_TRACE, + "Writing channel data to socket."); + bytes_current = send(tunnel_data->tunnel_socket, + buffer + bytes_written, bytes_read - bytes_written, 0); + + if (bytes_current <= 0) { + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_ERROR, + "Error writing to socket, ending thread."); + pthread_exit(&retval); + return NULL; + } + bytes_written += bytes_current; + } + + if (libssh2_channel_eof(tunnel_data->ssh_tunnel->channel)) { + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_ERROR, + "Received eof on libssh2 channel, giving up."); + pthread_exit(&retval); + return NULL; + } + + } + + guac_client_log(tunnel_data->ssh_tunnel->client, GUAC_LOG_DEBUG, + "Waiting for input thread to exit."); + + /* Error or closed socket - wait for the input thread to exit. */ + pthread_join(tunnel_data->tunnel_input_thread, NULL); + + /* Close file descriptors and free data. */ + close(tunnel_data->tunnel_socket); + close(tunnel_data->listen_socket); + guac_mem_free(tunnel_data->remote_host); + return NULL; + +} + +int guac_common_ssh_tunnel_init(guac_ssh_tunnel* ssh_tunnel, char* remote_host, + int remote_port) { + + struct stat socket_stat; + + ssh_tunnel_data* tunnel_data = calloc(1, sizeof(ssh_tunnel_data)); + tunnel_data->ssh_tunnel = ssh_tunnel; + tunnel_data->remote_host = guac_strdup(remote_host); + tunnel_data->remote_port = remote_port; + + /* Assemble the expected path to the socket. */ + ssh_tunnel->socket_path = malloc(4096); + snprintf(ssh_tunnel->socket_path, 4096, "%s/%s/tunnel", + GUACD_STATE_DIR, ssh_tunnel->client->connection_id); + + guac_client_log(ssh_tunnel->client, GUAC_LOG_DEBUG, + "Socket: %s", ssh_tunnel->socket_path); + const char* socket_dir = dirname(guac_strdup(ssh_tunnel->socket_path)); + + /* Check if the socket already exists, and abort if it does. */ + if (stat((const char *)ssh_tunnel->socket_path, &socket_stat) == 0) { + guac_client_abort(ssh_tunnel->client, GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT, + "Socket already exists: %s", ssh_tunnel->socket_path); + return -1; + } + + /* Create the directory and the socket. */ + if (mkdir(socket_dir, GUAC_COMMON_SSH_TUNNEL_DIRECTORY_MODE)) { + guac_client_abort(ssh_tunnel->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Failed to make socket directory \"%s\": %s", socket_dir, + strerror(errno)); + return -1; + + } + + /* Set up the socket and listen on it. */ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + guac_client_abort(ssh_tunnel->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Failed to create UNIX domain socket."); + return -1; + } + + guac_client_log(ssh_tunnel->client, GUAC_LOG_DEBUG, "Socket created, binding."); + + /* Bind to the UNIX domain socket. */ + tunnel_data->tunnel_addr.sun_family = AF_UNIX; + strncpy(tunnel_data->tunnel_addr.sun_path, ssh_tunnel->socket_path, + sizeof(tunnel_data->tunnel_addr.sun_path) - 1); + + if (bind(fd, (const struct sockaddr *) &tunnel_data->tunnel_addr, sizeof(struct sockaddr_un)) < 0) { + guac_client_abort(ssh_tunnel->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Failed to bind to UNIX domain socket at \"%s\": %s", + ssh_tunnel->socket_path, strerror(errno)); + return -1; + } + + /* Listen on the UNIX domain socket for an incoming connection */ + if (listen(fd, GUAC_COMMON_SSH_TUNNEL_BACKLOG_SIZE) < 0) { + guac_client_abort(ssh_tunnel->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Failed to listen on UNIX domain socket at \"%s\": %s", + ssh_tunnel->socket_path, strerror(errno)); + return -1; + } + tunnel_data->listen_socket = fd; + + guac_client_log(ssh_tunnel->client, GUAC_LOG_DEBUG, + "Listening on socket, creating worker thread."); + + /* Create a thread to wait for the incoming connection and do the work. */ + int retval = pthread_create(&(tunnel_data->tunnel_thread), + NULL, guac_common_ssh_tunnel_worker, + (void *) tunnel_data); + if (retval) { + guac_client_abort(ssh_tunnel->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Failed to start worker thread: %d", retval); + return -1; + } + + guac_client_log(ssh_tunnel->client, GUAC_LOG_DEBUG, + "Worker created, return socket path to client."); + + /* Return success */ + return 0; + +} + + +int guac_common_ssh_tunnel_cleanup(guac_ssh_tunnel* ssh_tunnel) { + + /* Stop libssh2 channel and free it */ + if (ssh_tunnel->channel) { + libssh2_channel_close(ssh_tunnel->channel); + libssh2_channel_free(ssh_tunnel->channel); + } + + /* Clean up the SSH session */ + if (ssh_tunnel->session) + guac_common_ssh_destroy_session(ssh_tunnel->session); + + /* Remove socket and directory, and free string */ + unlink(ssh_tunnel->socket_path); + rmdir(dirname(ssh_tunnel->socket_path)); + guac_mem_free(ssh_tunnel->socket_path); + + return 0; + +} \ No newline at end of file diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index fd4f5cc3c5..baa3a3f02e 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -82,6 +82,7 @@ libguacinc_HEADERS = \ guacamole/socket-constants.h \ guacamole/socket-fntypes.h \ guacamole/socket-types.h \ + guacamole/socket-unix.h \ guacamole/stream.h \ guacamole/stream-types.h \ guacamole/string.h \ @@ -159,6 +160,7 @@ libguac_la_SOURCES = \ socket-fd.c \ socket-nest.c \ socket-tee.c \ + socket-unix.c \ string.c \ tcp.c \ timestamp.c \ diff --git a/src/libguac/guacamole/socket-unix.h b/src/libguac/guacamole/socket-unix.h new file mode 100644 index 0000000000..15ee3b9c98 --- /dev/null +++ b/src/libguac/guacamole/socket-unix.h @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef __GUAC_SOCKET_UNIX_H +#define __GUAC_SOCKET_UNIX_H + +#include "config.h" + +#include + +/** + * Given a path to a UNIX socket, attempt to connect to that socket, returning + * the open socket if the connection succeeds, or a negative value if it fails. + * If it fails the errno variable will be set. + * + * @param path + * The path to the UNIX socket. If the path begins with a slash, it will + * be interpreted as an absolute path to the socket on the system running + * guacd. If the path begins with anything but a slash, it will be a path + * relative to the working directory from which guacd was started. + * + * @return + * A valid socket if the connection succeeds, or a negative integer if it + * fails. + */ +int guac_socket_unix_connect(const char* path); + +#endif // __GUAC_SOCKET_UNIX_H \ No newline at end of file diff --git a/src/libguac/socket-unix.c b/src/libguac/socket-unix.c new file mode 100644 index 0000000000..72afa4ec25 --- /dev/null +++ b/src/libguac/socket-unix.c @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "guacamole/error.h" +#include "guacamole/socket.h" + +#include +#include +#include +#include +#include +#include + +int guac_socket_unix_connect(const char* path) { + + /* Attempt to open a socket. */ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + guac_error = GUAC_STATUS_INVALID_ARGUMENT; + guac_error_message = "Unable to acquire a UNIX socket."; + close(fd); + return -1; + } + + /* Set up the socket with the path. */ + struct sockaddr_un socket_addr = { + .sun_family = AF_UNIX + }; + strncpy(socket_addr.sun_path, path, sizeof(socket_addr.sun_path) - 1); + + /* Attempt to connect via the open socket. */ + if (connect(fd, (const struct sockaddr *) &socket_addr, sizeof(struct sockaddr_un))) { + guac_error = GUAC_STATUS_INVALID_ARGUMENT; + guac_error_message = "Unable to connect to the UNIX socket."; + close(fd); + return -1; + } + + /* Return the open socket. */ + return fd; + +} \ No newline at end of file diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index c75d02c656..8705fdbfc3 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -33,6 +33,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 @@ -301,6 +302,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 aad1763b85..521cc1706f 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -46,6 +46,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 @@ -918,6 +919,97 @@ 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_timeout, + 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 dc7c264d5e..cc7b2c921e 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -36,6 +36,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 @@ -67,6 +68,8 @@ #define GUAC_RDP_CONTEXT(rdp_instance) ((rdp_instance)) #endif +#define GUAC_RDP_DEFAULT_CONNECTION_TIMEOUT 30 + /** * The maximum number of input events to allow in the event queue. */ @@ -200,6 +203,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 c4292417a1..11aa2b3f4f 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 "rdp.h" #include "resolution.h" @@ -117,6 +118,16 @@ 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", + "ssh-tunnel-timeout", #endif "recording-path", @@ -534,6 +545,61 @@ 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, + + /** + * The amount of time, in seconds, after which the attempt to establish + * a SSH tunnel connection will fail. + */ + IDX_SSH_TUNNEL_TIMEOUT, #endif /** @@ -1103,76 +1169,127 @@ 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); - /* SFTP timeout */ - settings->sftp_timeout = - guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_TIMEOUT, RDP_DEFAULT_SFTP_TIMEOUT); + /* SFTP timeout */ + settings->sftp_timeout = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_TIMEOUT, RDP_DEFAULT_SFTP_TIMEOUT); - /* 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, ""); + /* Public key for authenticating to SFTP server, if applicable. */ + settings->sftp_public_key = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_PUBLIC_KEY, NULL); - /* Public key for authenticating to SFTP server, if applicable. */ - settings->sftp_public_key = - guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_SFTP_PUBLIC_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, ""); - /* 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); + + settings->ssh_tunnel_timeout = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_TIMEOUT, + GUAC_COMMON_SSH_DEFAULT_TIMEOUT); + + } #endif /* Read recording path */ @@ -1427,7 +1544,7 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { } #ifdef ENABLE_COMMON_SSH - /* Free SFTP settings */ + /* Free SFTP and SSH tunnel settings */ guac_mem_free(settings->sftp_directory); guac_mem_free(settings->sftp_root_directory); guac_mem_free(settings->sftp_host_key); @@ -1438,6 +1555,12 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { guac_mem_free(settings->sftp_private_key); guac_mem_free(settings->sftp_public_key); guac_mem_free(settings->sftp_username); + guac_mem_free(settings->ssh_tunnel_host); + guac_mem_free(settings->ssh_tunnel_host_key); + guac_mem_free(settings->ssh_tunnel_port); + guac_mem_free(settings->ssh_tunnel_username); + guac_mem_free(settings->ssh_tunnel_password); + guac_mem_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 5f01a1adf3..be41631621 100644 --- a/src/protocols/rdp/settings.h +++ b/src/protocols/rdp/settings.h @@ -537,6 +537,75 @@ 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; + + /** + * The amount of time, in seconds, after which the attempt to connect + * to the SSH server to establish the tunnel will time out. + */ + int ssh_tunnel_timeout; + #endif /** diff --git a/src/protocols/ssh/client.c b/src/protocols/ssh/client.c index 991d8746ed..25b5ab480b 100644 --- a/src/protocols/ssh/client.c +++ b/src/protocols/ssh/client.c @@ -22,6 +22,7 @@ #include "argv.h" #include "client.h" #include "common-ssh/sftp.h" +#include "common-ssh/tunnel.h" #include "ssh.h" #include "terminal/terminal.h" #include "user.h" @@ -130,6 +131,9 @@ int guac_ssh_client_free_handler(guac_client* client) { if (ssh_client->recording != NULL) guac_recording_free(ssh_client->recording); + if (ssh_client->ssh_tunnel != NULL) + guac_common_ssh_tunnel_cleanup(ssh_client->ssh_tunnel); + /* Free interactive SSH session */ if (ssh_client->session != NULL) guac_common_ssh_destroy_session(ssh_client->session); diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 39bb127772..930fc1f047 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -23,6 +23,7 @@ #include "client.h" #include "common/defaults.h" #include "common/clipboard.h" +#include "common-ssh/ssh-constants.h" #include "settings.h" #include "terminal/terminal.h" @@ -51,6 +52,16 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "private-key", "passphrase", "public-key", + "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", + "ssh-tunnel-timeout", #ifdef ENABLE_SSH_AGENT "enable-agent", #endif @@ -165,6 +176,61 @@ enum SSH_ARGS_IDX { */ IDX_PUBLIC_KEY, + /** + * 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, + + /** + * The maximum amount of time, in seconds, allowed for the connection + * to the SSH tunneling host to succeed. + */ + IDX_SSH_TUNNEL_TIMEOUT, + #ifdef ENABLE_SSH_AGENT /** * Whether SSH agent forwarding support should be enabled. @@ -456,6 +522,54 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_SFTP_DISABLE_UPLOAD, false); + /* Parse SSH tunneling. */ + settings->ssh_tunnel = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL, false); + + /* 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_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_HOST, NULL); + + settings->ssh_tunnel_port = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PORT, GUAC_COMMON_SSH_DEFAULT_PORT); + + settings->ssh_tunnel_host_key = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_HOST_KEY, NULL); + + settings->ssh_tunnel_username = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_USERNAME, NULL); + + settings->ssh_tunnel_password = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PASSWORD, NULL); + + settings->ssh_tunnel_private_key = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PRIVATE_KEY, NULL); + + settings->ssh_tunnel_passphrase = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PASSPHRASE, NULL); + + settings->ssh_tunnel_alive_interval = + guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_ALIVE_INTERVAL, + GUAC_COMMON_SSH_DEFAULT_ALIVE_INTERVAL); + + settings->ssh_tunnel_timeout = + guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_TIMEOUT, + GUAC_COMMON_SSH_DEFAULT_TIMEOUT); + + } + #ifdef ENABLE_SSH_AGENT settings->enable_agent = guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, @@ -465,12 +579,12 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, /* Read port */ settings->port = guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, - IDX_PORT, GUAC_SSH_DEFAULT_PORT); + IDX_PORT, GUAC_COMMON_SSH_DEFAULT_PORT); /* Parse the timeout value. */ settings->timeout = guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, - IDX_TIMEOUT, GUAC_SSH_DEFAULT_TIMEOUT); + IDX_TIMEOUT, GUAC_COMMON_SSH_DEFAULT_TIMEOUT); /* Read-only mode */ settings->read_only = @@ -653,6 +767,15 @@ void guac_ssh_settings_free(guac_ssh_settings* settings) { /* Free SFTP settings */ guac_mem_free(settings->sftp_root_directory); + /* Free tunnel settings */ + guac_mem_free(settings->ssh_tunnel_host); + guac_mem_free(settings->ssh_tunnel_host_key); + guac_mem_free(settings->ssh_tunnel_port); + guac_mem_free(settings->ssh_tunnel_username); + guac_mem_free(settings->ssh_tunnel_password); + guac_mem_free(settings->ssh_tunnel_private_key); + guac_mem_free(settings->ssh_tunnel_passphrase); + /* Free typescript settings */ guac_mem_free(settings->typescript_name); guac_mem_free(settings->typescript_path); diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index c67c292f56..edb50ec68b 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -26,18 +26,6 @@ #include -/** - * The port to connect to when initiating any SSH connection, if no other port - * is specified. - */ -#define GUAC_SSH_DEFAULT_PORT "22" - -/** - * The default number of seconds to attempt a connection to the SSH/SFTP - * server before giving up. - */ -#define GUAC_SSH_DEFAULT_TIMEOUT 10 - /** * The filename to use for the typescript, if not specified. */ @@ -201,6 +189,74 @@ typedef struct guac_ssh_settings { */ bool 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; + + /** + * The maximum amount of time, in seconds, that is allowed for the SSH + * connection to the tunneling host to succeed. + */ + int ssh_tunnel_timeout; + #ifdef ENABLE_SSH_AGENT /** * Whether the SSH agent is enabled. diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 1c5ba76f5e..b795eebbf2 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -274,6 +275,97 @@ void* ssh_client_thread(void* data) { return NULL; } + if (settings->ssh_tunnel) { + + /* Allocate memory for the SSH tunnel data. */ + ssh_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. */ + ssh_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; + } + + ssh_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(ssh_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(ssh_client->ssh_tunnel->user, + settings->ssh_tunnel_password); + + } + + /* Attempt SSH tunnel connection */ + ssh_client->ssh_tunnel->session = + guac_common_ssh_create_session(client, settings->ssh_tunnel_host, + settings->ssh_tunnel_port, ssh_client->ssh_tunnel->user, + settings->ssh_tunnel_timeout, + settings->ssh_tunnel_alive_interval, + settings->ssh_tunnel_host_key, NULL); + + /* Fail if SSH tunnel connection does not succeed */ + if (ssh_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(ssh_client->ssh_tunnel, + settings->hostname, strtol(settings->port, NULL, 10))) { + 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 (ssh_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(ssh_client->ssh_tunnel->socket_path); + settings->port = 0; + + guac_client_log(client, GUAC_LOG_DEBUG, + "SSH tunnel connection succeeded."); + + } + char ssh_ttymodes[GUAC_SSH_TTYMODES_SIZE(1)]; /* Set up screen recording, if requested */ diff --git a/src/protocols/ssh/ssh.h b/src/protocols/ssh/ssh.h index 1d1aae73db..472a31c418 100644 --- a/src/protocols/ssh/ssh.h +++ b/src/protocols/ssh/ssh.h @@ -25,6 +25,7 @@ #include "common/clipboard.h" #include "common-ssh/sftp.h" #include "common-ssh/ssh.h" +#include "common-ssh/tunnel.h" #include "common-ssh/user.h" #include "settings.h" #include "terminal/terminal.h" @@ -60,6 +61,11 @@ typedef struct guac_ssh_client { */ pthread_t client_thread; + /** + * If SSH tunneling is enabled, this is the object that tracks the tunnel. + */ + guac_ssh_tunnel* ssh_tunnel; + /** * The user and credentials to use for all SSH sessions. */ diff --git a/src/protocols/telnet/Makefile.am b/src/protocols/telnet/Makefile.am index d0264f674c..1f3bbe3fd5 100644 --- a/src/protocols/telnet/Makefile.am +++ b/src/protocols/telnet/Makefile.am @@ -50,11 +50,13 @@ noinst_HEADERS = \ libguac_client_telnet_la_CFLAGS = \ -Werror -Wall -Iinclude \ + @COMMON_SSH_INCLUDE@ \ @LIBGUAC_INCLUDE@ \ @TERMINAL_INCLUDE@ libguac_client_telnet_la_LIBADD = \ @COMMON_LTLIB@ \ + @COMMON_SSH_LTLIB@ \ @LIBGUAC_LTLIB@ \ @TERMINAL_LTLIB@ diff --git a/src/protocols/telnet/client.c b/src/protocols/telnet/client.c index ba1f20f48a..fb3b3d60a9 100644 --- a/src/protocols/telnet/client.c +++ b/src/protocols/telnet/client.c @@ -24,6 +24,13 @@ #include "telnet.h" #include "user.h" +#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 + #include #include #include @@ -83,6 +90,10 @@ int guac_client_init(guac_client* client) { client->free_handler = guac_telnet_client_free_handler; client->leave_handler = guac_telnet_user_leave_handler; +#ifdef ENABLE_COMMON_SSH + guac_common_ssh_init(client); +#endif + /* Register handlers for argument values that may be sent after the handshake */ guac_argv_register(GUAC_TELNET_ARGV_COLOR_SCHEME, guac_telnet_argv_callback, NULL, GUAC_ARGV_OPTION_ECHO); guac_argv_register(GUAC_TELNET_ARGV_FONT_NAME, guac_telnet_argv_callback, NULL, GUAC_ARGV_OPTION_ECHO); @@ -122,6 +133,25 @@ int guac_telnet_client_free_handler(guac_client* client) { telnet_free(telnet_client->telnet); } +#ifdef ENABLE_COMMON_SSH + /* Free SFTP filesystem, if loaded */ + if (telnet_client->sftp_filesystem) + guac_common_ssh_destroy_sftp_filesystem(telnet_client->sftp_filesystem); + + /* Free SFTP session */ + if (telnet_client->sftp_session) + guac_common_ssh_destroy_session(telnet_client->sftp_session); + + /* Free SFTP user */ + if (telnet_client->sftp_user) + guac_common_ssh_destroy_user(telnet_client->sftp_user); + + if (telnet_client->ssh_tunnel) + guac_common_ssh_tunnel_cleanup(telnet_client->ssh_tunnel); + + guac_common_ssh_uninit(); +#endif + /* Free settings */ if (telnet_client->settings != NULL) guac_telnet_settings_free(telnet_client->settings); diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 7756ad521e..570d39e350 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -22,6 +22,7 @@ #include "argv.h" #include "common/defaults.h" #include "common/clipboard.h" +#include "common-ssh/ssh-constants.h" #include "settings.h" #include "terminal/terminal.h" @@ -72,6 +73,33 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "wol-broadcast-addr", "wol-udp-port", "wol-wait-time", + +#ifdef ENABLE_COMMON_SSH + "enable-sftp", + "sftp-hostname", + "sftp-host-key", + "sftp-port", + "sftp-username", + "sftp-password", + "sftp-private-key", + "sftp-passphrase", + "sftp-directory", + "sftp-root-directory", + "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", + "ssh-tunnel-timeout", +#endif + NULL }; @@ -300,6 +328,143 @@ enum TELNET_ARGS_IDX { */ IDX_WOL_WAIT_TIME, +#ifdef ENABLE_COMMON_SSH + /** + * "true" if SFTP should be enabled for the connection, "false" or + * blank otherwise. + */ + IDX_ENABLE_SFTP, + + /** + * The hostname of the SSH server to connect to for SFTP. If blank, the + * hostname of the telnet server will be used. + */ + IDX_SFTP_HOSTNAME, + + /** + * The public SSH host key to identify the SFTP server. + */ + IDX_SFTP_HOST_KEY, + + /** + * The port of the SSH server to connect to for SFTP. If blank, the default + * SSH port of "22" will be used. + */ + IDX_SFTP_PORT, + + /** + * The username to provide when authenticating with the SSH server for + * SFTP. + */ + IDX_SFTP_USERNAME, + + /** + * The password to provide when authenticating with the SSH server for + * SFTP (if not using a private key). + */ + IDX_SFTP_PASSWORD, + + /** + * The base64-encoded private key to use when authenticating with the SSH + * server for SFTP (if not using a password). + */ + IDX_SFTP_PRIVATE_KEY, + + /** + * The passphrase to use to decrypt the provided base64-encoded private + * key. + */ + IDX_SFTP_PASSPHRASE, + + /** + * The default location for file uploads within the SSH server. This will + * apply only to uploads which do not use the filesystem guac_object (where + * the destination directory is otherwise ambiguous). + */ + IDX_SFTP_DIRECTORY, + + /** + * The path of the directory within the SSH server to expose as a + * filesystem guac_object. If omitted, "/" will be used by default. + */ + IDX_SFTP_ROOT_DIRECTORY, + + /** + * The interval at which SSH keepalive messages are sent to the server for + * SFTP connections. The default is 0 (disabling keepalives), and a value + * of 1 is automatically incremented to 2 by libssh2 to avoid busy loop corner + * cases. + */ + IDX_SFTP_SERVER_ALIVE_INTERVAL, + + /** + * If set to "true", file downloads over SFTP will be blocked. If set to + * "false" or not set, file downloads will be allowed. + */ + IDX_SFTP_DISABLE_DOWNLOAD, + + /** + * If set to "true", file uploads over SFTP will be blocked. If set to + * "false" or not set, file uploads will be allowed. + */ + 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, + + /** + * The maximum amount of time, in seconds, that the SSH connection to + * the tunnel host may take to succeed. + */ + IDX_SSH_TUNNEL_TIMEOUT, +#endif + TELNET_ARGS_COUNT }; @@ -594,6 +759,123 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, } +#ifdef ENABLE_COMMON_SSH + /* SFTP enable/disable */ + settings->enable_sftp = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_ENABLE_SFTP, false); + + /* If SFTP is not enabled, no reason to parse the rest. */ + if (settings->enable_sftp) { + /* Hostname for SFTP connection */ + settings->sftp_hostname = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_HOSTNAME, settings->hostname); + + /* The public SSH host key. */ + settings->sftp_host_key = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_HOST_KEY, NULL); + + /* Port for SFTP connection */ + settings->sftp_port = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_PORT, "22"); + + /* Username for SSH/SFTP authentication */ + settings->sftp_username = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_USERNAME, ""); + + /* Password for SFTP (if not using private key) */ + settings->sftp_password = + guac_user_parse_args_string(user, GUAC_TELNET_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_TELNET_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_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_PASSPHRASE, ""); + + /* Default upload directory */ + settings->sftp_directory = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_DIRECTORY, NULL); + + /* SFTP root directory */ + settings->sftp_root_directory = + guac_user_parse_args_string(user, GUAC_TELNET_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_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_SERVER_ALIVE_INTERVAL, + GUAC_COMMON_SSH_DEFAULT_ALIVE_INTERVAL); + + settings->sftp_disable_download = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_DISABLE_DOWNLOAD, false); + + settings->sftp_disable_upload = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SFTP_DISABLE_UPLOAD, false); + } + + /* Parse SSH tunneling. */ + settings->ssh_tunnel = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL, false); + + /* 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_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_HOST, NULL); + + settings->ssh_tunnel_port = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PORT, GUAC_COMMON_SSH_DEFAULT_PORT); + + settings->ssh_tunnel_host_key = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_HOST_KEY, NULL); + + settings->ssh_tunnel_username = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_USERNAME, NULL); + + settings->ssh_tunnel_password = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PASSWORD, NULL); + + settings->ssh_tunnel_private_key = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PRIVATE_KEY, NULL); + + settings->ssh_tunnel_passphrase = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PASSPHRASE, NULL); + + settings->ssh_tunnel_alive_interval = + guac_user_parse_args_int(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_ALIVE_INTERVAL, + GUAC_COMMON_SSH_DEFAULT_ALIVE_INTERVAL); + + settings->ssh_tunnel_timeout = + guac_user_parse_args_int(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_TIMEOUT, + GUAC_COMMON_SSH_DEFAULT_TIMEOUT); + + } +#endif + /* Parsing was successful */ return settings; @@ -634,6 +916,23 @@ void guac_telnet_settings_free(guac_telnet_settings* settings) { guac_mem_free(settings->wol_mac_addr); guac_mem_free(settings->wol_broadcast_addr); + /* Free SSH and SFTP settings. */ + guac_mem_free(settings->sftp_hostname); + guac_mem_free(settings->sftp_host_key); + guac_mem_free(settings->sftp_username); + guac_mem_free(settings->sftp_password); + guac_mem_free(settings->sftp_private_key); + guac_mem_free(settings->sftp_passphrase); + guac_mem_free(settings->sftp_directory); + guac_mem_free(settings->sftp_root_directory); + guac_mem_free(settings->ssh_tunnel_host); + guac_mem_free(settings->ssh_tunnel_host_key); + guac_mem_free(settings->ssh_tunnel_port); + guac_mem_free(settings->ssh_tunnel_username); + guac_mem_free(settings->ssh_tunnel_password); + guac_mem_free(settings->ssh_tunnel_private_key); + guac_mem_free(settings->ssh_tunnel_passphrase); + /* Free overall structure */ guac_mem_free(settings); diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 5eb8f2fb80..db3a32a1c7 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -297,6 +297,156 @@ typedef struct guac_telnet_settings { */ int wol_wait_time; +#ifdef ENABLE_COMMON_SSH + /** + * Whether SFTP should be enabled for the VNC connection. + */ + bool enable_sftp; + + /** + * The hostname of the SSH server to connect to for SFTP. + */ + char* sftp_hostname; + + /** + * The public SSH host key. + */ + char* sftp_host_key; + + /** + * The port of the SSH server to connect to for SFTP. + */ + char* sftp_port; + + /** + * The username to provide when authenticating with the SSH server for + * SFTP. + */ + char* sftp_username; + + /** + * The password to provide when authenticating with the SSH server for + * SFTP (if not using a private key). + */ + char* sftp_password; + + /** + * The base64-encoded private key to use when authenticating with the SSH + * server for SFTP (if not using a password). + */ + char* sftp_private_key; + + /** + * The passphrase to use to decrypt the provided base64-encoded private + * key. + */ + char* sftp_passphrase; + + /** + * The default location for file uploads within the SSH server. This will + * apply only to uploads which do not use the filesystem guac_object (where + * the destination directory is otherwise ambiguous). + */ + char* sftp_directory; + + /** + * The path of the directory within the SSH server to expose as a + * filesystem guac_object. + */ + char* sftp_root_directory; + + /** + * The interval at which SSH keepalive messages are sent to the server for + * SFTP connections. The default is 0 (disabling keepalives), and a value + * of 1 is automatically increased to 2 by libssh2 to avoid busy loop corner + * cases. + */ + int sftp_server_alive_interval; + + /** + * Whether file downloads over SFTP should be blocked. If set to "true", + * the local client will not be able to download files from the SFTP server. + * If set to "false" or not set, file downloads will be allowed. + */ + bool sftp_disable_download; + + /** + * Whether file uploads over SFTP should be blocked. If set to "true", the + * local client will not be able to upload files to the SFTP server. If set + * to "false" or not set, file uploads will be allowed. + */ + bool 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; + + /** + * The maximum amount of time, in seconds, that a connection to the SSH + * tunnel host may take to succeed. + */ + int ssh_tunnel_timeout; + +#endif + } guac_telnet_settings; /** diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 37e68e0777..1a153805e9 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include /** @@ -386,7 +388,124 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) { guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; guac_telnet_settings* settings = telnet_client->settings; - int fd = guac_tcp_connect(settings->hostname, settings->port, settings->timeout); + int fd = 0; + + if (settings->ssh_tunnel) { + + /* Allocate memory for the SSH tunnel data. */ + telnet_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. */ + telnet_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; + } + + telnet_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(telnet_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(telnet_client->ssh_tunnel->user, + settings->ssh_tunnel_password); + + } + + /* Attempt SSH tunnel connection */ + telnet_client->ssh_tunnel->session = + guac_common_ssh_create_session(client, settings->ssh_tunnel_host, + settings->ssh_tunnel_port, telnet_client->ssh_tunnel->user, + settings->ssh_tunnel_timeout, + settings->ssh_tunnel_alive_interval, + settings->ssh_tunnel_host_key, NULL); + + /* Fail if SSH tunnel connection does not succeed */ + if (telnet_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(telnet_client->ssh_tunnel, + settings->hostname, strtol(settings->port, NULL, 10))) { + 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 (telnet_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(telnet_client->ssh_tunnel->socket_path); + settings->port = 0; + + guac_client_log(client, GUAC_LOG_DEBUG, + "SSH tunnel connection succeeded."); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (fd < 0) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Error opening UNIX socket for telnet connection: %s", + strerror(errno)); + return NULL; + } + + struct sockaddr_un socket_addr = { + .sun_family = AF_UNIX + }; + strncpy(socket_addr.sun_path, telnet_client->ssh_tunnel->socket_path, + sizeof(socket_addr.sun_path) - 1); + + if (connect(fd, (const struct sockaddr *) &socket_addr, sizeof(struct sockaddr_un))) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Error connecting to UNIX socket for SSH tunnel: %s", + telnet_client->ssh_tunnel->socket_path); + return NULL; + } + + } + + /* SSH tunneling is not in use, so open the standard TCP socket. */ + else + fd = guac_tcp_connect(settings->hostname, settings->port, settings->timeout); /* Open telnet session */ telnet_t* telnet = telnet_init(__telnet_options, __guac_telnet_event_handler, 0, client); diff --git a/src/protocols/telnet/telnet.h b/src/protocols/telnet/telnet.h index 338f94c85f..801458f15a 100644 --- a/src/protocols/telnet/telnet.h +++ b/src/protocols/telnet/telnet.h @@ -24,6 +24,13 @@ #include "settings.h" #include "terminal/terminal.h" +#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 + #include #include @@ -77,6 +84,29 @@ typedef struct guac_telnet_client { */ guac_recording* recording; +#ifdef ENABLE_COMMON_SSH + /** + * The user and credentials used to authenticate for SFTP. + */ + guac_common_ssh_user* sftp_user; + + /** + * The SSH session used for SFTP. + */ + guac_common_ssh_session* sftp_session; + + /** + * An SFTP-based filesystem. + */ + guac_common_ssh_sftp_filesystem* sftp_filesystem; + + /** + * The SSH tunnel data. + */ + guac_ssh_tunnel* ssh_tunnel; +#endif + + } guac_telnet_client; /** diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 005bb3a2a5..dba782e0c9 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -26,6 +26,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 @@ -121,6 +122,10 @@ int guac_client_init(guac_client* client) { client->leave_handler = guac_vnc_user_leave_handler; client->free_handler = guac_vnc_client_free_handler; +#ifdef ENABLE_COMMON_SSH + guac_common_ssh_init(client); +#endif + return 0; } @@ -185,6 +190,9 @@ int guac_vnc_client_free_handler(guac_client* client) { if (vnc_client->sftp_user) guac_common_ssh_destroy_user(vnc_client->sftp_user); + if (vnc_client->ssh_tunnel) + guac_common_ssh_tunnel_cleanup(vnc_client->ssh_tunnel); + guac_common_ssh_uninit(); #endif diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index b2322f33ca..75cc671f2d 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -23,6 +23,7 @@ #include "client.h" #include "common/defaults.h" #include "common/clipboard.h" +#include "common-ssh/ssh-constants.h" #include "settings.h" #include @@ -80,6 +81,16 @@ const char* GUAC_VNC_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", + "ssh-tunnel-timeout", #endif "recording-path", @@ -313,6 +324,61 @@ enum VNC_ARGS_IDX { * "false" or not set, file uploads will be allowed. */ 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, + + /** + * The maximum amount of time, in seconds, that the SSH connection to the + * tunnel host may take to be established. + */ + IDX_SSH_TUNNEL_TIMEOUT, #endif /** @@ -635,12 +701,13 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, /* SFTP root directory */ settings->sftp_root_directory = guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, - IDX_SFTP_ROOT_DIRECTORY, "/"); + 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_VNC_CLIENT_ARGS, argv, - IDX_SFTP_SERVER_ALIVE_INTERVAL, 0); + IDX_SFTP_SERVER_ALIVE_INTERVAL, + GUAC_COMMON_SSH_DEFAULT_ALIVE_INTERVAL); settings->sftp_disable_download = guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, @@ -649,6 +716,54 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, settings->sftp_disable_upload = guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_SFTP_DISABLE_UPLOAD, false); + + /* Parse SSH tunneling settings. */ + settings->ssh_tunnel = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL, false); + + /* 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_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_HOST, NULL); + + settings->ssh_tunnel_port = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PORT, GUAC_COMMON_SSH_DEFAULT_PORT); + + settings->ssh_tunnel_host_key = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_HOST_KEY, NULL); + + settings->ssh_tunnel_username = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_USERNAME, NULL); + + settings->ssh_tunnel_password = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PASSWORD, NULL); + + settings->ssh_tunnel_private_key = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PRIVATE_KEY, NULL); + + settings->ssh_tunnel_passphrase = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_PASSPHRASE, NULL); + + settings->ssh_tunnel_alive_interval = + guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_ALIVE_INTERVAL, + GUAC_COMMON_SSH_DEFAULT_ALIVE_INTERVAL); + + settings->ssh_tunnel_timeout = + guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SSH_TUNNEL_TIMEOUT, + GUAC_COMMON_SSH_DEFAULT_TIMEOUT); + + } #endif /* Read recording path */ @@ -785,6 +900,13 @@ void guac_vnc_settings_free(guac_vnc_settings* settings) { guac_mem_free(settings->sftp_private_key); guac_mem_free(settings->sftp_public_key); guac_mem_free(settings->sftp_username); + guac_mem_free(settings->ssh_tunnel_host); + guac_mem_free(settings->ssh_tunnel_host_key); + guac_mem_free(settings->ssh_tunnel_port); + guac_mem_free(settings->ssh_tunnel_username); + guac_mem_free(settings->ssh_tunnel_password); + guac_mem_free(settings->ssh_tunnel_private_key); + guac_mem_free(settings->ssh_tunnel_passphrase); #endif #ifdef ENABLE_PULSE diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index a23585f0db..fb799c17e1 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -267,6 +267,75 @@ typedef struct guac_vnc_settings { * to "false" or not set, file uploads will be allowed. */ bool 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; + + /** + * The maximum amount of time, in seconds, that the SSH connection to the + * tunnel host may take to be established. + */ + int ssh_tunnel_timeout; + #endif /** diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 8f5fa9129f..1ec8d3e636 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -36,6 +36,7 @@ #ifdef ENABLE_COMMON_SSH #include "common-ssh/sftp.h" #include "common-ssh/ssh.h" +#include "common-ssh/tunnel.h" #include "sftp.h" #endif @@ -408,6 +409,99 @@ void* guac_vnc_client_thread(void* data) { rfbClientLog = guac_vnc_client_log_info; rfbClientErr = guac_vnc_client_log_error; +#ifdef ENABLE_COMMON_SSH + /* 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. */ + vnc_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. */ + vnc_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; + } + + vnc_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(vnc_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(vnc_client->ssh_tunnel->user, + settings->ssh_tunnel_password); + + } + + /* Attempt SSH tunnel connection */ + vnc_client->ssh_tunnel->session = + guac_common_ssh_create_session(client, settings->ssh_tunnel_host, + settings->ssh_tunnel_port, vnc_client->ssh_tunnel->user, + settings->ssh_tunnel_timeout, + settings->ssh_tunnel_alive_interval, + settings->ssh_tunnel_host_key, NULL); + + /* Fail if SSH tunnel connection does not succeed */ + if (vnc_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(vnc_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 (vnc_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(vnc_client->ssh_tunnel->socket_path); + settings->port = 0; + + guac_client_log(client, GUAC_LOG_DEBUG, + "SSH tunnel connection succeeded."); + } +#endif + /* Attempt connection */ rfbClient* rfb_client = guac_vnc_get_client(client); int retries_remaining = settings->retries; @@ -441,7 +535,6 @@ void* guac_vnc_client_thread(void* data) { #endif #ifdef ENABLE_COMMON_SSH - guac_common_ssh_init(client); /* Connect via SSH if SFTP is enabled */ if (settings->enable_sftp) { diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index dbe857c420..7da541f034 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -39,6 +39,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 @@ -146,6 +147,11 @@ typedef struct guac_vnc_client { * An SFTP-based filesystem. */ guac_common_ssh_sftp_filesystem* sftp_filesystem; + + /** + * The data structure containing SSH tunnel-related information. + */ + guac_ssh_tunnel* ssh_tunnel; #endif /**