Compare commits

...

5 Commits

Author SHA1 Message Date
Virtually Nick
3b33f63fc1 [WIP] Add SSH tunneling settings for VNC. 2023-02-24 17:38:40 -05:00
Virtually Nick
ad751f109a [WIP] Add SSH tunneling settings for Telnet. 2023-02-24 17:38:40 -05:00
Virtually Nick
8394ef0ed6 [WIP] Add SSH tunneling settings for SSH. 2023-02-24 17:38:40 -05:00
Virtually Nick
4df9687832 [WIP] Add SSH tunneling for RDP. 2023-02-24 17:22:36 -05:00
Virtually Nick
3693601240 [WIP] Start of common settings for SSH tunneling. 2023-02-24 17:22:36 -05:00
28 changed files with 2171 additions and 169 deletions

View File

@ -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 = \

View File

@ -25,6 +25,8 @@
#include <guacamole/client.h>
#include <libssh2.h>
#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"

View File

@ -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

View File

@ -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 <libssh2.h>
#include <pthread.h>
/**
* 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

View File

@ -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);

View File

@ -41,6 +41,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#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 */

367
src/common-ssh/tunnel.c Normal file
View File

@ -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 <guacamole/string.h>
#include <errno.h>
#include <libgen.h>
#include <libssh2.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
/**
* 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;
}

View File

@ -31,6 +31,7 @@
#ifdef ENABLE_COMMON_SSH
#include "common-ssh/sftp.h"
#include "common-ssh/ssh.h"
#include "common-ssh/tunnel.h"
#include "common-ssh/user.h"
#endif
@ -220,6 +221,9 @@ int guac_rdp_client_free_handler(guac_client* client) {
if (rdp_client->sftp_user)
guac_common_ssh_destroy_user(rdp_client->sftp_user);
if (rdp_client->ssh_tunnel)
guac_common_ssh_tunnel_cleanup(rdp_client->ssh_tunnel);
guac_common_ssh_uninit();
#endif

View File

@ -49,6 +49,7 @@
#ifdef ENABLE_COMMON_SSH
#include "common-ssh/sftp.h"
#include "common-ssh/ssh.h"
#include "common-ssh/tunnel.h"
#include "common-ssh/user.h"
#endif
@ -92,6 +93,13 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_settings* settings = rdp_client->settings;
/* Check for SSH tunneling and start it up, if requested */
if (settings->ssh_tunnel) {
}
/* Push desired settings to FreeRDP */
guac_rdp_push_settings(client, settings, instance);
@ -814,6 +822,96 @@ void* guac_rdp_client_thread(void* data) {
"SFTP connection succeeded.");
}
/* If SSH tunneling is enabled, we set up the tunnel and redirect the connection. */
if (settings->ssh_tunnel) {
/* Allocate memory for the SSH tunnel data. */
rdp_client->ssh_tunnel = malloc(sizeof(guac_ssh_tunnel));
guac_client_log(client, GUAC_LOG_DEBUG,
"SSH tunneling is enabled, connecting via SSH.");
/* Associate the guac_client object with the tunnel. */
rdp_client->ssh_tunnel->client = client;
/* Abort if tunnel username is missing */
if (settings->ssh_tunnel_username == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"An SSH tunnel-specific username is required if "
"SSH tunneling is enabled.");
return NULL;
}
rdp_client->ssh_tunnel->user =
guac_common_ssh_create_user(settings->ssh_tunnel_username);
/* Import SSH tunnel private key, if given */
if (settings->ssh_tunnel_private_key != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Authenticating SSH tunnel with private key.");
/* Abort if SSH tunnel private key cannot be read */
if (guac_common_ssh_user_import_key(rdp_client->ssh_tunnel->user,
settings->ssh_tunnel_private_key,
settings->ssh_tunnel_passphrase)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"SSH tunnel private key unreadable.");
return NULL;
}
}
/* Otherwise, use specified SSH tunnel password */
else {
guac_client_log(client, GUAC_LOG_DEBUG,
"Authenticating SSH tunnel with password.");
guac_common_ssh_user_set_password(rdp_client->ssh_tunnel->user,
settings->ssh_tunnel_password);
}
/* Attempt SSH tunnel connection */
rdp_client->ssh_tunnel->session =
guac_common_ssh_create_session(client, settings->ssh_tunnel_host,
settings->ssh_tunnel_port, rdp_client->ssh_tunnel->user,
settings->ssh_tunnel_alive_interval,
settings->ssh_tunnel_host_key, NULL);
/* Fail if SSH tunnel connection does not succeed */
if (rdp_client->ssh_tunnel->session == NULL) {
/* Already aborted within guac_common_ssh_create_session() */
return NULL;
}
guac_client_log(client, GUAC_LOG_DEBUG,
"SSH session created for tunneling, initializing the tunnel.");
/* Initialize the tunnel or fail. */
if (guac_common_ssh_tunnel_init(rdp_client->ssh_tunnel,
settings->hostname, settings->port)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Unable to initialize SSH tunnel, aborting connection.");
return NULL;
}
/* If tunnel socket is not returned, bail out. */
if (rdp_client->ssh_tunnel->socket_path == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Unable to obtain socket for SSH tunnel, aborting.");
return NULL;
}
/* Overwrite the hostname with the path to the socket and zero out port. */
settings->hostname = guac_strdup(rdp_client->ssh_tunnel->socket_path);
settings->port = 0;
guac_client_log(client, GUAC_LOG_DEBUG,
"SSH tunnel connection succeeded.");
}
#endif
/* Set up screen recording, if requested */

View File

@ -37,6 +37,7 @@
#ifdef ENABLE_COMMON_SSH
#include "common-ssh/sftp.h"
#include "common-ssh/ssh.h"
#include "common-ssh/tunnel.h"
#include "common-ssh/user.h"
#endif
@ -50,6 +51,8 @@
#include <pthread.h>
#include <stdint.h>
#define GUAC_RDP_DEFAULT_CONNECTION_TIMEOUT 30
/**
* RDP-specific client data.
*/
@ -154,6 +157,11 @@ typedef struct guac_rdp_client {
* An SFTP-based filesystem.
*/
guac_common_ssh_sftp_filesystem* sftp_filesystem;
/**
* The SSH session used for the tunnel.
*/
guac_ssh_tunnel* ssh_tunnel;
#endif
/**

View File

@ -20,6 +20,7 @@
#include "argv.h"
#include "common/defaults.h"
#include "common/string.h"
#include "common-ssh/ssh-constants.h"
#include "config.h"
#include "resolution.h"
#include "settings.h"
@ -110,6 +111,15 @@ const char* GUAC_RDP_CLIENT_ARGS[] = {
"sftp-server-alive-interval",
"sftp-disable-download",
"sftp-disable-upload",
"ssh-tunnel",
"ssh-tunnel-host",
"ssh-tunnel-port",
"ssh-tunnel-host-key",
"ssh-tunnel-username",
"ssh-tunnel-password",
"ssh-tunnel-private-key",
"ssh-tunnel-passphrase",
"ssh-tunnel-alive-interval",
#endif
"recording-path",
@ -493,6 +503,55 @@ enum RDP_ARGS_IDX {
* blank otherwise.
*/
IDX_SFTP_DISABLE_UPLOAD,
/**
* True if SSH tunneling should be enabled. If false or not set, SSH
* tunneling will not be used.
*/
IDX_SSH_TUNNEL,
/**
* The hostname or IP address of the SSH server to use for tunneling.
*/
IDX_SSH_TUNNEL_HOST,
/**
* The TCP port of the SSH server to use for tunneling.
*/
IDX_SSH_TUNNEL_PORT,
/**
* If host key checking should be done, the public key of the SSH host
* to be used for tunneling.
*/
IDX_SSH_TUNNEL_HOST_KEY,
/**
* The username for authenticating to the SSH hsot for tunneling.
*/
IDX_SSH_TUNNEL_USERNAME,
/**
* The password to use to authenticate to the SSH host for tunneling.
*/
IDX_SSH_TUNNEL_PASSWORD,
/**
* The private key to use to authenticate to the SSH host for tunneling,
* as an alternative to password-based authentication.
*/
IDX_SSH_TUNNEL_PRIVATE_KEY,
/**
* The passphrase to use to decrypt the private key.
*/
IDX_SSH_TUNNEL_PASSPHRASE,
/**
* The interval at which keepalive packets should be sent to the SSH
* tunneling server, or zero if keepalive should be disabled.
*/
IDX_SSH_TUNNEL_ALIVE_INTERVAL,
#endif
/**
@ -1036,66 +1095,112 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_SFTP, 0);
/* Hostname for SFTP connection */
settings->sftp_hostname =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_HOSTNAME, settings->hostname);
/* Only parse remaining SFTP settings if it's enabled. */
if (settings->enable_sftp) {
/* Hostname for SFTP connection */
settings->sftp_hostname =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_HOSTNAME, settings->hostname);
/* The public SSH host key. */
settings->sftp_host_key =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_HOST_KEY, NULL);
/* The public SSH host key. */
settings->sftp_host_key =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_HOST_KEY, NULL);
/* Port for SFTP connection */
settings->sftp_port =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PORT, "22");
/* Port for SFTP connection */
settings->sftp_port =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PORT, GUAC_COMMON_SSH_DEFAULT_PORT);
/* Username for SSH/SFTP authentication */
settings->sftp_username =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_USERNAME,
settings->username != NULL ? settings->username : "");
/* Username for SSH/SFTP authentication */
settings->sftp_username =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_USERNAME,
settings->username != NULL ? settings->username : "");
/* Password for SFTP (if not using private key) */
settings->sftp_password =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PASSWORD, "");
/* Password for SFTP (if not using private key) */
settings->sftp_password =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PASSWORD, "");
/* Private key for SFTP (if not using password) */
settings->sftp_private_key =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PRIVATE_KEY, NULL);
/* Private key for SFTP (if not using password) */
settings->sftp_private_key =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PRIVATE_KEY, NULL);
/* Passphrase for decrypting the SFTP private key (if applicable */
settings->sftp_passphrase =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PASSPHRASE, "");
/* Passphrase for decrypting the SFTP private key (if applicable */
settings->sftp_passphrase =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PASSPHRASE, "");
/* Default upload directory */
settings->sftp_directory =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_DIRECTORY, NULL);
/* Default upload directory */
settings->sftp_directory =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_DIRECTORY, NULL);
/* SFTP root directory */
settings->sftp_root_directory =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_ROOT_DIRECTORY, "/");
/* SFTP root directory */
settings->sftp_root_directory =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_ROOT_DIRECTORY, GUAC_COMMON_SSH_SFTP_DEFAULT_ROOT);
/* Default keepalive value */
settings->sftp_server_alive_interval =
guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_SERVER_ALIVE_INTERVAL, 0);
/* Whether or not to disable file download over SFTP. */
settings->sftp_disable_download =
/* Default keepalive value */
settings->sftp_server_alive_interval =
guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_SERVER_ALIVE_INTERVAL, 0);
/* Whether or not to disable file download over SFTP. */
settings->sftp_disable_download =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_DISABLE_DOWNLOAD, 0);
/* Whether or not to disable file upload over SFTP. */
settings->sftp_disable_upload =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_DISABLE_UPLOAD, 0);
}
/* Parse SSH tunneling. */
settings->ssh_tunnel =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_DISABLE_DOWNLOAD, 0);
IDX_SSH_TUNNEL, false);
/* Whether or not to disable file upload over SFTP. */
settings->sftp_disable_upload =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_DISABLE_UPLOAD, 0);
/* Only parse remaining tunneling settings if it has been enabled. */
if (settings->ssh_tunnel) {
settings->ssh_tunnel_host =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SSH_TUNNEL_HOST, NULL);
settings->ssh_tunnel_port =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SSH_TUNNEL_PORT, GUAC_COMMON_SSH_DEFAULT_PORT);
settings->ssh_tunnel_host_key =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SSH_TUNNEL_HOST_KEY, NULL);
settings->ssh_tunnel_username =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SSH_TUNNEL_USERNAME, NULL);
settings->ssh_tunnel_password =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SSH_TUNNEL_PASSWORD, NULL);
settings->ssh_tunnel_private_key =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SSH_TUNNEL_PRIVATE_KEY, NULL);
settings->ssh_tunnel_passphrase =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SSH_TUNNEL_PASSPHRASE, NULL);
settings->ssh_tunnel_alive_interval =
guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SSH_TUNNEL_ALIVE_INTERVAL,
GUAC_COMMON_SSH_DEFAULT_ALIVE_INTERVAL);
}
#endif
/* Read recording path */
@ -1324,7 +1429,7 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) {
}
#ifdef ENABLE_COMMON_SSH
/* Free SFTP settings */
/* Free SFTP and SSH tunnel settings */
free(settings->sftp_directory);
free(settings->sftp_root_directory);
free(settings->sftp_host_key);
@ -1334,6 +1439,12 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) {
free(settings->sftp_port);
free(settings->sftp_private_key);
free(settings->sftp_username);
free(settings->ssh_tunnel_host);
free(settings->ssh_tunnel_host_key);
free(settings->ssh_tunnel_port);
free(settings->ssh_tunnel_username);
free(settings->ssh_tunnel_password);
free(settings->ssh_tunnel_private_key);
#endif
/* Free RD gateway information */

View File

@ -494,6 +494,69 @@ typedef struct guac_rdp_settings {
* Whether or not to disable file upload over SFTP.
*/
int sftp_disable_upload;
/**
* Whether to enable tunneling of this connection through the specified
* SSH server. If set to "true", guacd will attempt to connect to the SSH
* server and tunnel all of the traffic through the SSH connection. If
* set to "false" or not set, SSH tunneling will not be used.
*/
bool ssh_tunnel;
/**
* The hostname or address of the host through which traffic should be
* tunneled over SSH. If tunneling is enabled, this is required, or the
* connection will be aborted.
*/
char* ssh_tunnel_host;
/**
* The port on which to connect to the SSH server to tunnel traffic, if
* SSH tunneling is enabled. If not specified, this will default to 22, the
* normal SSH port.
*/
char* ssh_tunnel_port;
/**
* The public key of the SSH host through which this connection will be
* tunneled. If unset, no host key checking will be done and the connection
* will be attempted regardless of the identity of the remote host.
*/
char* ssh_tunnel_host_key;
/**
* The username to use when connecting to the SSH host to tunnel traffic.
* This is required if SSH tunneling is enabled.
*/
char* ssh_tunnel_username;
/**
* The password to use when connecting to the SSH host to tunnel traffic,
* if password authentication is used.
*/
char* ssh_tunnel_password;
/**
* The private key to use to authenticate to the SSH server to tunnel traffic,
* if key-based authentication is used.
*/
char* ssh_tunnel_private_key;
/**
* The passphrase of the private key to use to decrypt the private key when
* using key-based authentication, if the key is encrypted.
*/
char* ssh_tunnel_passphrase;
/**
* The interval at which keepalive messages will be sent to the SSH server
* over which the connection is being tunneled. The default is 0, meaning
* that keepalive messages will be disabled. The minimum value is 2 to avoid
* busy loop scenarios, and a value of 1 is automatically increased to 2 by
* the underlying libssh2 implementation.
*/
int ssh_tunnel_alive_interval;
#endif
/**

View File

@ -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"
@ -101,6 +102,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);

View File

@ -22,6 +22,7 @@
#include "argv.h"
#include "client.h"
#include "common/defaults.h"
#include "common-ssh/ssh-constants.h"
#include "settings.h"
#include "terminal/terminal.h"
@ -47,6 +48,15 @@ const char* GUAC_SSH_CLIENT_ARGS[] = {
"sftp-disable-upload",
"private-key",
"passphrase",
"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",
#ifdef ENABLE_SSH_AGENT
"enable-agent",
#endif
@ -148,6 +158,55 @@ enum SSH_ARGS_IDX {
*/
IDX_PASSPHRASE,
/**
* 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,
#ifdef ENABLE_SSH_AGENT
/**
* Whether SSH agent forwarding support should be enabled.
@ -418,6 +477,49 @@ 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);
}
#ifdef ENABLE_SSH_AGENT
settings->enable_agent =
guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv,
@ -427,7 +529,7 @@ 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);
/* Read-only mode */
settings->read_only =
@ -578,6 +680,15 @@ void guac_ssh_settings_free(guac_ssh_settings* settings) {
/* Free SFTP settings */
free(settings->sftp_root_directory);
/* Free tunnel settings */
free(settings->ssh_tunnel_host);
free(settings->ssh_tunnel_host_key);
free(settings->ssh_tunnel_port);
free(settings->ssh_tunnel_username);
free(settings->ssh_tunnel_password);
free(settings->ssh_tunnel_private_key);
free(settings->ssh_tunnel_passphrase);
/* Free typescript settings */
free(settings->typescript_name);
free(settings->typescript_path);

View File

@ -26,12 +26,6 @@
#include <stdbool.h>
/**
* The port to connect to when initiating any SSH connection, if no other port
* is specified.
*/
#define GUAC_SSH_DEFAULT_PORT "22"
/**
* The filename to use for the typescript, if not specified.
*/
@ -178,6 +172,68 @@ 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;
#ifdef ENABLE_SSH_AGENT
/**
* Whether the SSH agent is enabled.

View File

@ -37,6 +37,7 @@
#include <guacamole/client.h>
#include <guacamole/recording.h>
#include <guacamole/socket.h>
#include <guacamole/string.h>
#include <guacamole/timestamp.h>
#include <guacamole/wol.h>
#include <openssl/err.h>
@ -226,6 +227,96 @@ 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_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 */

View File

@ -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.
*/

View File

@ -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@

View File

@ -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 <langinfo.h>
#include <locale.h>
#include <pthread.h>
@ -54,6 +61,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);
@ -93,6 +104,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);

View File

@ -21,6 +21,7 @@
#include "argv.h"
#include "common/defaults.h"
#include "common-ssh/ssh-constants.h"
#include "settings.h"
#include "terminal/terminal.h"
@ -66,6 +67,32 @@ 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",
#endif
NULL
};
@ -272,6 +299,137 @@ 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,
#endif
TELNET_ARGS_COUNT
};
@ -530,6 +688,118 @@ 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);
}
#endif
/* Parsing was successful */
return settings;
@ -570,6 +840,23 @@ void guac_telnet_settings_free(guac_telnet_settings* settings) {
free(settings->wol_mac_addr);
free(settings->wol_broadcast_addr);
/* Free SSH and SFTP settings. */
free(settings->sftp_hostname);
free(settings->sftp_host_key);
free(settings->sftp_username);
free(settings->sftp_password);
free(settings->sftp_private_key);
free(settings->sftp_passphrase);
free(settings->sftp_directory);
free(settings->sftp_root_directory);
free(settings->ssh_tunnel_host);
free(settings->ssh_tunnel_host_key);
free(settings->ssh_tunnel_port);
free(settings->ssh_tunnel_username);
free(settings->ssh_tunnel_password);
free(settings->ssh_tunnel_private_key);
free(settings->ssh_tunnel_passphrase);
/* Free overall structure */
free(settings);

View File

@ -269,6 +269,150 @@ 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;
#endif
} guac_telnet_settings;
/**

View File

@ -26,6 +26,7 @@
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/recording.h>
#include <guacamole/string.h>
#include <guacamole/timestamp.h>
#include <guacamole/wol.h>
#include <libtelnet.h>
@ -40,6 +41,7 @@
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <unistd.h>
/**
@ -392,69 +394,184 @@ 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;
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
.ai_protocol = IPPROTO_TCP
};
if (settings->ssh_tunnel) {
/* Get socket */
fd = socket(AF_INET, SOCK_STREAM, 0);
/* Allocate memory for the SSH tunnel data. */
telnet_client->ssh_tunnel = malloc(sizeof(guac_ssh_tunnel));
/* Get addresses connection */
if ((retval = getaddrinfo(settings->hostname, settings->port,
&hints, &addresses))) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error parsing given address or port: %s",
gai_strerror(retval));
return NULL;
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;
/* Attempt connection to each address until success */
current_address = addresses;
while (current_address != NULL) {
/* 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;
}
int retval;
telnet_client->ssh_tunnel->user =
guac_common_ssh_create_user(settings->ssh_tunnel_username);
/* 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));
/* Import SSH tunnel private key, if given */
if (settings->ssh_tunnel_private_key != NULL) {
/* Connect */
if (connect(fd, current_address->ai_addr,
current_address->ai_addrlen) == 0) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Authenticating SSH tunnel with private key.");
guac_client_log(client, GUAC_LOG_DEBUG, "Successfully connected to "
"host %s, port %s", connected_address, connected_port);
/* Done if successful connect */
break;
/* 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 log information regarding bind failure */
else
guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to "
"host %s, port %s: %s",
connected_address, connected_port, strerror(errno));
/* Otherwise, use specified SSH tunnel password */
else {
current_address = current_address->ai_next;
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_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;
}
}
/* 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;
}
/* SSH tunneling is not in use, so open the standard TCP socket. */
else {
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
.ai_protocol = IPPROTO_TCP
};
/* Free addrinfo */
freeaddrinfo(addresses);
/* Get socket */
fd = socket(AF_INET, SOCK_STREAM, 0);
/* Get addresses connection */
if ((retval = getaddrinfo(settings->hostname, settings->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) {
int retval;
/* 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));
/* 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 */
else
guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to "
"host %s, port %s: %s",
connected_address, connected_port, strerror(errno));
current_address = current_address->ai_next;
}
/* 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;
}
/* Free addrinfo */
freeaddrinfo(addresses);
}
/* Open telnet session */
telnet_t* telnet = telnet_init(__telnet_options, __guac_telnet_event_handler, 0, client);

View File

@ -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 <guacamole/recording.h>
#include <libtelnet.h>
@ -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;
/**

View File

@ -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
@ -62,6 +63,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;
}
@ -121,6 +126,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

View File

@ -22,6 +22,7 @@
#include "argv.h"
#include "client.h"
#include "common/defaults.h"
#include "common-ssh/ssh-constants.h"
#include "settings.h"
#include <guacamole/user.h>
@ -75,6 +76,15 @@ 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",
#endif
"recording-path",
@ -284,6 +294,55 @@ 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,
#endif
/**
@ -547,12 +606,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,
@ -561,6 +621,49 @@ 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);
}
#endif
/* Read recording path */
@ -670,6 +773,13 @@ void guac_vnc_settings_free(guac_vnc_settings* settings) {
free(settings->sftp_port);
free(settings->sftp_private_key);
free(settings->sftp_username);
free(settings->ssh_tunnel_host);
free(settings->ssh_tunnel_host_key);
free(settings->ssh_tunnel_port);
free(settings->ssh_tunnel_username);
free(settings->ssh_tunnel_password);
free(settings->ssh_tunnel_private_key);
free(settings->ssh_tunnel_passphrase);
#endif
#ifdef ENABLE_PULSE

View File

@ -231,6 +231,69 @@ 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;
#endif
/**

View File

@ -38,6 +38,7 @@
#ifdef ENABLE_COMMON_SSH
#include "common-ssh/sftp.h"
#include "common-ssh/ssh.h"
#include "common-ssh/tunnel.h"
#include "sftp.h"
#endif
@ -45,6 +46,7 @@
#include <guacamole/protocol.h>
#include <guacamole/recording.h>
#include <guacamole/socket.h>
#include <guacamole/string.h>
#include <guacamole/timestamp.h>
#include <guacamole/wol.h>
#include <rfb/rfbclient.h>
@ -297,6 +299,98 @@ 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_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;
@ -330,7 +424,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) {

View File

@ -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
@ -117,6 +118,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
/**