diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 5d1a88d1..3cc43d8e 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 5d829b9a..c294d2be 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 00000000..d3e8d98c --- /dev/null +++ b/src/common-ssh/common-ssh/ssh-constants.h @@ -0,0 +1,40 @@ +/* + * 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 + +/** + * For SFTP connections, the default root directory at which to start + * the session. + */ +#define GUAC_COMMON_SSH_SFTP_DEFAULT_ROOT "/" + +#endif \ No newline at end of file diff --git a/src/common-ssh/common-ssh/tunnel.h b/src/common-ssh/common-ssh/tunnel.h new file mode 100644 index 00000000..647f0cb4 --- /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 cecfb413..1bac85a8 100644 --- a/src/common-ssh/key.c +++ b/src/common-ssh/key.c @@ -185,7 +185,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 1cbd4834..49d76ee3 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #ifdef LIBSSH2_USES_GCRYPT @@ -409,81 +410,111 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, guac_ssh_credential_handler* credential_handler) { int retval; - int fd; - struct addrinfo* addresses; - struct addrinfo* current_address; + struct stat sb; - char connected_address[1024]; - char connected_port[64]; - - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_protocol = IPPROTO_TCP - }; - - /* Get addresses connection */ - if ((retval = getaddrinfo(hostname, port, &hints, &addresses))) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Error parsing given address or port: %s", - gai_strerror(retval)); - return NULL; - } - - /* Attempt connection to each address until success */ - current_address = addresses; - while (current_address != NULL) { - - /* Resolve hostname */ - if ((retval = getnameinfo(current_address->ai_addr, - current_address->ai_addrlen, - connected_address, sizeof(connected_address), - connected_port, sizeof(connected_port), - NI_NUMERICHOST | NI_NUMERICSERV))) - guac_client_log(client, GUAC_LOG_DEBUG, - "Unable to resolve host: %s", gai_strerror(retval)); - - /* Get socket */ - fd = socket(current_address->ai_family, SOCK_STREAM, 0); + /* Hostname is a UNIX socket */ + if (stat(hostname, &sb) == 0 && (sb.st_mode & S_IFMT) == S_IFSOCK) { + fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Unable to create socket: %s", strerror(errno)); - freeaddrinfo(addresses); + "Error opening UNIX socket: %s", strerror(errno)); return NULL; } - /* Connect */ - if (connect(fd, current_address->ai_addr, - current_address->ai_addrlen) == 0) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Successfully connected to host %s, port %s", - connected_address, connected_port); - - /* Done if successful connect */ - break; + struct sockaddr_un socket_addr = { + .sun_family = AF_UNIX + }; + strncpy(socket_addr.sun_path, hostname, 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", + hostname); + return NULL; } - /* Otherwise log information regarding bind failure */ - guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to " - "host %s, port %s: %s", - connected_address, connected_port, strerror(errno)); - - close(fd); - current_address = current_address->ai_next; + guac_client_log(client, GUAC_LOG_DEBUG, + "Connected SSH to UNIX socket at \"%s\"", hostname); } - /* Free addrinfo */ - freeaddrinfo(addresses); + /* Normal hostname or IP. */ + else { + struct addrinfo* addresses; + struct addrinfo* current_address; - /* If unable to connect to anything, fail */ - if (current_address == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, - "Unable to connect to any addresses."); - return NULL; + char connected_address[1024]; + char connected_port[64]; + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP + }; + + /* Get addresses connection */ + if ((retval = getaddrinfo(hostname, port, &hints, &addresses))) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Error parsing given address or port: %s", + gai_strerror(retval)); + return NULL; + } + + /* Attempt connection to each address until success */ + current_address = addresses; + while (current_address != NULL) { + + /* Resolve hostname */ + if ((retval = getnameinfo(current_address->ai_addr, + current_address->ai_addrlen, + connected_address, sizeof(connected_address), + connected_port, sizeof(connected_port), + NI_NUMERICHOST | NI_NUMERICSERV))) + guac_client_log(client, GUAC_LOG_DEBUG, + "Unable to resolve host: %s", gai_strerror(retval)); + + /* Get socket */ + fd = socket(current_address->ai_family, SOCK_STREAM, 0); + if (fd < 0) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to create socket: %s", strerror(errno)); + freeaddrinfo(addresses); + return NULL; + } + + /* Connect */ + if (connect(fd, current_address->ai_addr, + current_address->ai_addrlen) == 0) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Successfully connected to host %s, port %s", + connected_address, connected_port); + + /* Done if successful connect */ + break; + + } + + /* Otherwise log information regarding bind failure */ + guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to " + "host %s, port %s: %s", + connected_address, connected_port, strerror(errno)); + + close(fd); + current_address = current_address->ai_next; + + } + + /* Free addrinfo */ + freeaddrinfo(addresses); + + /* If unable to connect to anything, fail */ + if (current_address == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, + "Unable to connect to any addresses."); + return NULL; + } } /* Allocate new session */ diff --git a/src/common-ssh/tunnel.c b/src/common-ssh/tunnel.c new file mode 100644 index 00000000..b32179b4 --- /dev/null +++ b/src/common-ssh/tunnel.c @@ -0,0 +1,367 @@ +/* + * 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 + +/** + * 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 POSIX thread used to run the main tunnel worker. + */ + pthread_t tunnel_thread; + + /** + * The POSIX thread used to run the tunnel input worker. + */ + pthread_t tunnel_input_thread; + + /** + * A POSIX mutex lock used to manage concurrent access to the SSH2 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 connections on the socket and accept one if it comes along. + 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."); + + // Start the libssh2 direct tcpip 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."); + + 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."); + + // Process 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."); + + // On error or when socket is closed, wait for input thread and 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); + 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; + + // Create expected path to 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 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 directory and 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 socket and listen + 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)); + free(ssh_tunnel->socket_path); + + return 0; + +} \ No newline at end of file