[WIP] Start of common settings for SSH tunneling.
This commit is contained in:
parent
47b9360d46
commit
3693601240
@ -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 = \
|
||||
|
@ -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"
|
||||
|
40
src/common-ssh/common-ssh/ssh-constants.h
Normal file
40
src/common-ssh/common-ssh/ssh-constants.h
Normal 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
|
117
src/common-ssh/common-ssh/tunnel.h
Normal file
117
src/common-ssh/common-ssh/tunnel.h
Normal 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
|
@ -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);
|
||||
|
||||
|
@ -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
367
src/common-ssh/tunnel.c
Normal 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;
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user