Merge pull request #53 from glyptodon/common-ssh

GUAC-1171: Migrate to common SSH client base
This commit is contained in:
James Muehlner 2015-07-10 21:37:49 -07:00
commit 7c0858b3cb
28 changed files with 2399 additions and 1244 deletions

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2013 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -26,6 +26,7 @@ ACLOCAL_AMFLAGS = -I m4
DIST_SUBDIRS = \ DIST_SUBDIRS = \
src/libguac \ src/libguac \
src/common \ src/common \
src/common-ssh \
src/terminal \ src/terminal \
src/guacd \ src/guacd \
src/protocols/rdp \ src/protocols/rdp \
@ -40,6 +41,10 @@ SUBDIRS = \
src/guacd \ src/guacd \
tests tests
if ENABLE_COMMON_SSH
SUBDIRS += src/common-ssh
endif
if ENABLE_TERMINAL if ENABLE_TERMINAL
SUBDIRS += src/terminal SUBDIRS += src/terminal
endif endif
@ -60,5 +65,8 @@ if ENABLE_VNC
SUBDIRS += src/protocols/vnc SUBDIRS += src/protocols/vnc
endif endif
EXTRA_DIST = LICENSE doc/Doxyfile bin/guacctl EXTRA_DIST = \
LICENSE \
bin/guacctl \
doc/Doxyfile

View File

@ -117,6 +117,10 @@ AC_SUBST([LIBGUAC_INCLUDE], '-I$(top_srcdir)/src/libguac')
AC_SUBST([COMMON_LTLIB], '$(top_builddir)/src/common/libguac_common.la') AC_SUBST([COMMON_LTLIB], '$(top_builddir)/src/common/libguac_common.la')
AC_SUBST([COMMON_INCLUDE], '-I$(top_srcdir)/src/common') AC_SUBST([COMMON_INCLUDE], '-I$(top_srcdir)/src/common')
# Common base SSH client
AC_SUBST([COMMON_SSH_LTLIB], '$(top_builddir)/src/common-ssh/libguac_common_ssh.la')
AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh')
# Terminal emulator # Terminal emulator
AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la') AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la')
AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)') AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)')
@ -834,6 +838,9 @@ then
[have_libssh2=no]) [have_libssh2=no])
fi fi
AM_CONDITIONAL([ENABLE_COMMON_SSH], [test "x${have_libssh2}" = "xyes" \
-a "x${have_ssl}" = "xyes"])
AM_CONDITIONAL([ENABLE_SSH], [test "x${have_libssh2}" = "xyes" \ AM_CONDITIONAL([ENABLE_SSH], [test "x${have_libssh2}" = "xyes" \
-a "x${have_terminal}" = "xyes" \ -a "x${have_terminal}" = "xyes" \
-a "x${have_ssl}" = "xyes"]) -a "x${have_ssl}" = "xyes"])
@ -920,6 +927,7 @@ AC_SUBST(TELNET_LIBS)
AC_CONFIG_FILES([Makefile AC_CONFIG_FILES([Makefile
tests/Makefile tests/Makefile
src/common/Makefile src/common/Makefile
src/common-ssh/Makefile
src/terminal/Makefile src/terminal/Makefile
src/libguac/Makefile src/libguac/Makefile
src/guacd/Makefile src/guacd/Makefile

View File

@ -0,0 +1,54 @@
#
# Copyright (C) 2015 Glyptodon LLC
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
noinst_LTLIBRARIES = libguac_common_ssh.la
libguac_common_ssh_la_SOURCES = \
guac_sftp.c \
guac_ssh.c \
guac_ssh_buffer.c \
guac_ssh_key.c \
guac_ssh_user.c
noinst_HEADERS = \
guac_sftp.h \
guac_ssh.h \
guac_ssh_buffer.h \
guac_ssh_key.h \
guac_ssh_user.h
libguac_common_ssh_la_CFLAGS = \
-Werror -Wall -pedantic \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@
libguac_common_ssh_la_LIBADD = \
@LIBGUAC_LTLIB@
libguac_common_ssh_la_LDFLAGS = \
@PTHREAD_LIBS@ \
@SSH_LIBS@ \
@SSL_LIBS@

716
src/common-ssh/guac_sftp.c Normal file
View File

@ -0,0 +1,716 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "guac_sftp.h"
#include "guac_ssh.h"
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <libssh2.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>
/**
* Concatenates the given filename with the given path, separating the two
* with a single forward slash. The full result must be no more than
* GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long, counting null terminator.
*
* @param fullpath
* The buffer to store the result within. This buffer must be at least
* GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long.
*
* @param path
* The path to append the filename to.
*
* @param filename
* The filename to append to the path.
*
* @return
* Non-zero if the filename is valid and was successfully appended to the
* path, zero otherwise.
*/
static int guac_ssh_append_filename(char* fullpath, const char* path,
const char* filename) {
int i;
/* Disallow "." as a filename */
if (strcmp(filename, ".") == 0)
return 0;
/* Disallow ".." as a filename */
if (strcmp(filename, "..") == 0)
return 0;
/* Copy path, append trailing slash */
for (i=0; i<GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
/*
* Append trailing slash only if:
* 1) Trailing slash is not already present
* 2) Path is non-empty
*/
char c = path[i];
if (c == '\0') {
if (i > 0 && path[i-1] != '/')
fullpath[i++] = '/';
break;
}
/* Copy character if not end of string */
fullpath[i] = c;
}
/* Append filename */
for (; i<GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
char c = *(filename++);
if (c == '\0')
break;
/* Filenames may not contain slashes */
if (c == '\\' || c == '/')
return 0;
/* Append each character within filename */
fullpath[i] = c;
}
/* Verify path length is within maximum */
if (i == GUAC_COMMON_SSH_SFTP_MAX_PATH)
return 0;
/* Terminate path string */
fullpath[i] = '\0';
/* Append was successful */
return 1;
}
/**
* Handler for blob messages which continue an inbound SFTP data transfer
* (upload). The data associated with the given stream is expected to be a
* pointer to an open LIBSSH2_SFTP_HANDLE for the file to which the data
* should be written.
*
* @param client
* The client receiving the blob message.
*
* @param stream
* The Guacamole protocol stream associated with the received blob message.
*
* @param data
* The data received within the blob.
*
* @param length
* The length of the received data, in bytes.
*
* @return
* Zero if the blob is handled successfully, or non-zero on error.
*/
static int guac_common_ssh_sftp_blob_handler(guac_client* client,
guac_stream* stream, void* data, int length) {
/* Pull file from stream */
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
/* Attempt write */
if (libssh2_sftp_write(file, data, length) == length) {
guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes written", length);
guac_protocol_send_ack(client->socket, stream, "SFTP: OK",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
}
/* Inform of any errors */
else {
guac_client_log(client, GUAC_LOG_INFO, "Unable to write to file");
guac_protocol_send_ack(client->socket, stream, "SFTP: Write failed",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
}
return 0;
}
/**
* Handler for end messages which terminate an inbound SFTP data transfer
* (upload). The data associated with the given stream is expected to be a
* pointer to an open LIBSSH2_SFTP_HANDLE for the file to which the data
* has been written and which should now be closed.
*
* @param client
* The client receiving the end message.
*
* @param stream
* The Guacamole protocol stream associated with the received end message.
*
* @return
* Zero if the file is closed successfully, or non-zero on error.
*/
static int guac_common_ssh_sftp_end_handler(guac_client* client,
guac_stream* stream) {
/* Pull file from stream */
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
/* Attempt to close file */
if (libssh2_sftp_close(file) == 0) {
guac_client_log(client, GUAC_LOG_DEBUG, "File closed");
guac_protocol_send_ack(client->socket, stream, "SFTP: OK",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
}
else {
guac_client_log(client, GUAC_LOG_INFO, "Unable to close file");
guac_protocol_send_ack(client->socket, stream, "SFTP: Close failed",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
}
return 0;
}
int guac_common_ssh_sftp_handle_file_stream(guac_object* filesystem,
guac_stream* stream, char* mimetype, char* filename) {
guac_common_ssh_sftp_data* sftp_data =
(guac_common_ssh_sftp_data*) filesystem->data;
guac_client* client = sftp_data->ssh_session->client;
char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH];
LIBSSH2_SFTP_HANDLE* file;
/* Concatenate filename with path */
if (!guac_ssh_append_filename(fullpath, sftp_data->upload_path,
filename)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Filename \"%s\" is invalid or resulting path is too long",
filename);
/* Abort transfer - invalid filename */
guac_protocol_send_ack(client->socket, stream,
"SFTP: Illegal filename",
GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
guac_socket_flush(client->socket);
return 0;
}
/* Open file via SFTP */
file = libssh2_sftp_open(sftp_data->sftp_session, fullpath,
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
S_IRUSR | S_IWUSR);
/* Inform of status */
if (file != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"File \"%s\" opened",
fullpath);
guac_protocol_send_ack(client->socket, stream, "SFTP: File opened",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
}
else {
guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s",
fullpath, libssh2_sftp_last_error(sftp_data->sftp_session));
guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed",
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
guac_socket_flush(client->socket);
}
/* Set handlers for file stream */
stream->blob_handler = guac_common_ssh_sftp_blob_handler;
stream->end_handler = guac_common_ssh_sftp_end_handler;
/* Store file within stream */
stream->data = file;
return 0;
}
/**
* Handler for ack messages which continue an outbound SFTP data transfer
* (download), signalling the current status and requesting additional data.
* The data associated with the given stream is expected to be a pointer to an
* open LIBSSH2_SFTP_HANDLE for the file from which the data is to be read.
*
* @param client
* The client receiving the ack message.
*
* @param stream
* The Guacamole protocol stream associated with the received ack message.
*
* @param message
* An arbitrary human-readable message describing the nature of the
* success or failure denoted by the ack message.
*
* @param status
* The status code associated with the ack message, which may indicate
* success or an error.
*
* @return
* Zero if the file is read from successfully, or non-zero on error.
*/
static int guac_common_ssh_sftp_ack_handler(guac_client* client,
guac_stream* stream, char* message, guac_protocol_status status) {
/* Pull file from stream */
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
/* If successful, read data */
if (status == GUAC_PROTOCOL_STATUS_SUCCESS) {
/* Attempt read into buffer */
char buffer[4096];
int bytes_read = libssh2_sftp_read(file, buffer, sizeof(buffer));
/* If bytes read, send as blob */
if (bytes_read > 0) {
guac_protocol_send_blob(client->socket, stream,
buffer, bytes_read);
guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes sent to client",
bytes_read);
}
/* If EOF, send end */
else if (bytes_read == 0) {
guac_client_log(client, GUAC_LOG_DEBUG, "File sent");
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
}
/* Otherwise, fail stream */
else {
guac_client_log(client, GUAC_LOG_INFO, "Error reading file");
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
}
guac_socket_flush(client->socket);
}
/* Otherwise, return stream to client */
else
guac_client_free_stream(client, stream);
return 0;
}
guac_stream* guac_common_ssh_sftp_download_file(guac_object* filesystem,
char* filename) {
guac_common_ssh_sftp_data* sftp_data =
(guac_common_ssh_sftp_data*) filesystem->data;
guac_client* client = sftp_data->ssh_session->client;
guac_stream* stream;
LIBSSH2_SFTP_HANDLE* file;
/* Attempt to open file for reading */
file = libssh2_sftp_open(sftp_data->sftp_session, filename,
LIBSSH2_FXF_READ, 0);
if (file == NULL) {
guac_client_log(client, GUAC_LOG_INFO,
"Unable to read file \"%s\": %s",
filename, libssh2_sftp_last_error(sftp_data->sftp_session));
return NULL;
}
/* Allocate stream */
stream = guac_client_alloc_stream(client);
stream->ack_handler = guac_common_ssh_sftp_ack_handler;
stream->data = file;
/* Send stream start, strip name */
filename = basename(filename);
guac_protocol_send_file(client->socket, stream,
"application/octet-stream", filename);
guac_socket_flush(client->socket);
guac_client_log(client, GUAC_LOG_DEBUG, "Sending file \"%s\"", filename);
return stream;
}
void guac_common_ssh_sftp_set_upload_path(guac_object* filesystem,
const char* path) {
guac_common_ssh_sftp_data* sftp_data =
(guac_common_ssh_sftp_data*) filesystem->data;
guac_client* client = sftp_data->ssh_session->client;
/* Ignore requests which exceed maximum-allowed path */
int length = strnlen(path, GUAC_COMMON_SSH_SFTP_MAX_PATH)+1;
if (length > GUAC_COMMON_SSH_SFTP_MAX_PATH) {
guac_client_log(client, GUAC_LOG_ERROR,
"Submitted path exceeds limit of %i bytes",
GUAC_COMMON_SSH_SFTP_MAX_PATH);
return;
}
/* Copy path */
memcpy(sftp_data->upload_path, path, length);
guac_client_log(client, GUAC_LOG_DEBUG, "Upload path set to \"%s\"", path);
}
/**
* Handler for ack messages received due to receipt of a "body" or "blob"
* instruction associated with a SFTP directory list operation.
*
* @param client
* The client receiving the ack message.
*
* @param stream
* The Guacamole protocol stream associated with the received ack message.
*
* @param message
* An arbitrary human-readable message describing the nature of the
* success or failure denoted by this ack message.
*
* @param status
* The status code associated with this ack message, which may indicate
* success or an error.
*
* @return
* Zero on success, non-zero on error.
*/
static int guac_common_ssh_sftp_ls_ack_handler(guac_client* client,
guac_stream* stream, char* message, guac_protocol_status status) {
int bytes_read;
int blob_written = 0;
char filename[GUAC_COMMON_SSH_SFTP_MAX_PATH];
LIBSSH2_SFTP_ATTRIBUTES attributes;
guac_common_ssh_sftp_ls_state* list_state =
(guac_common_ssh_sftp_ls_state*) stream->data;
guac_common_ssh_sftp_data* sftp_data = list_state->sftp_data;
LIBSSH2_SFTP* sftp = sftp_data->sftp_session;
/* If unsuccessful, free stream and abort */
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
libssh2_sftp_closedir(list_state->directory);
guac_client_free_stream(client, stream);
free(list_state);
return 0;
}
/* While directory entries remain */
while ((bytes_read = libssh2_sftp_readdir(list_state->directory,
filename, sizeof(filename), &attributes)) > 0
&& !blob_written) {
char absolute_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
/* Skip current and parent directory entries */
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
continue;
/* Concatenate into absolute path - skip if invalid */
if (!guac_ssh_append_filename(absolute_path,
list_state->directory_name, filename)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Skipping filename \"%s\" - filename is invalid or "
"resulting path is too long", filename);
continue;
}
/* Stat explicitly if symbolic link (might point to directory) */
if (LIBSSH2_SFTP_S_ISLNK(attributes.permissions))
libssh2_sftp_stat(sftp, absolute_path, &attributes);
/* Determine mimetype */
const char* mimetype;
if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions))
mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE;
else
mimetype = "application/octet-stream";
/* Write entry */
blob_written |= guac_common_json_write_property(client, stream,
&list_state->json_state, absolute_path, mimetype);
}
/* Complete JSON and cleanup at end of directory */
if (bytes_read <= 0) {
/* Complete JSON object */
guac_common_json_end_object(client, stream, &list_state->json_state);
guac_common_json_flush(client, stream, &list_state->json_state);
/* Clean up resources */
libssh2_sftp_closedir(list_state->directory);
free(list_state);
/* Signal of stream */
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
}
guac_socket_flush(client->socket);
return 0;
}
/**
* Handler for get messages. In context of SFTP and the filesystem exposed via
* the Guacamole protocol, get messages request the body of a file within the
* filesystem.
*
* @param client
* The client receiving the get message.
*
* @param object
* The Guacamole protocol object associated with the get request itself.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
static int guac_common_ssh_sftp_get_handler(guac_client* client,
guac_object* object, char* name) {
guac_common_ssh_sftp_data* sftp_data =
(guac_common_ssh_sftp_data*) object->data;
LIBSSH2_SFTP* sftp = sftp_data->sftp_session;
LIBSSH2_SFTP_ATTRIBUTES attributes;
/* Attempt to read file information */
if (libssh2_sftp_stat(sftp, name, &attributes)) {
guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"",
name);
return 0;
}
/* If directory, send contents of directory */
if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) {
/* Open as directory */
LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, name);
if (dir == NULL) {
guac_client_log(client, GUAC_LOG_INFO,
"Unable to read directory \"%s\": %s",
name, libssh2_sftp_last_error(sftp));
return 0;
}
/* Init directory listing state */
guac_common_ssh_sftp_ls_state* list_state =
malloc(sizeof(guac_common_ssh_sftp_ls_state));
list_state->directory = dir;
list_state->sftp_data = sftp_data;
strncpy(list_state->directory_name, name,
sizeof(list_state->directory_name));
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
stream->ack_handler = guac_common_ssh_sftp_ls_ack_handler;
stream->data = list_state;
/* Init JSON object state */
guac_common_json_begin_object(client, stream, &list_state->json_state);
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name);
}
/* Otherwise, send file contents */
else {
/* Open as normal file */
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
LIBSSH2_FXF_READ, 0);
if (file == NULL) {
guac_client_log(client, GUAC_LOG_INFO,
"Unable to read file \"%s\": %s",
name, libssh2_sftp_last_error(sftp));
return 0;
}
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
stream->ack_handler = guac_common_ssh_sftp_ack_handler;
stream->data = file;
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
"application/octet-stream", name);
}
guac_socket_flush(client->socket);
return 0;
}
/**
* Handler for put messages. In context of SFTP and the filesystem exposed via
* the Guacamole protocol, put messages request write access to a file within
* the filesystem.
*
* @param client
* The client receiving the put message.
*
* @param object
* The Guacamole protocol object associated with the put request itself.
*
* @param stream
* The Guacamole protocol stream along which the client will be sending
* file data.
*
* @param mimetype
* The mimetype of the data being send along the stream.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
static int guac_common_ssh_sftp_put_handler(guac_client* client,
guac_object* object, guac_stream* stream, char* mimetype, char* name) {
guac_common_ssh_sftp_data* sftp_data =
(guac_common_ssh_sftp_data*) object->data;
LIBSSH2_SFTP* sftp = sftp_data->sftp_session;
/* Open file via SFTP */
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
S_IRUSR | S_IWUSR);
/* Acknowledge stream if successful */
if (file != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG, "File \"%s\" opened", name);
guac_protocol_send_ack(client->socket, stream, "SFTP: File opened",
GUAC_PROTOCOL_STATUS_SUCCESS);
}
/* Abort on failure */
else {
guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s",
name, libssh2_sftp_last_error(sftp));
guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed",
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
}
/* Set handlers for file stream */
stream->blob_handler = guac_common_ssh_sftp_blob_handler;
stream->end_handler = guac_common_ssh_sftp_end_handler;
/* Store file within stream */
stream->data = file;
guac_socket_flush(client->socket);
return 0;
}
guac_object* guac_common_ssh_create_sftp_filesystem(
guac_common_ssh_session* session,
const char* name) {
guac_client* client = session->client;
/* Allocate data for SFTP session */
guac_common_ssh_sftp_data* sftp_data =
malloc(sizeof(guac_common_ssh_sftp_data));
/* Associate SSH session with SFTP data */
sftp_data->ssh_session = session;
/* Initially upload files to current directory */
strcpy(sftp_data->upload_path, ".");
/* Request SFTP */
sftp_data->sftp_session = libssh2_sftp_init(session->session);
if (sftp_data->sftp_session == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Unable to start SFTP session.");
return NULL;
}
/* Init filesystem */
guac_object* filesystem = guac_client_alloc_object(client);
filesystem->get_handler = guac_common_ssh_sftp_get_handler;
filesystem->put_handler = guac_common_ssh_sftp_put_handler;
filesystem->data = sftp_data;
/* Send filesystem to client */
guac_protocol_send_filesystem(client->socket, filesystem, "/");
guac_socket_flush(client->socket);
/* Return allocated filesystem */
return filesystem;
}
void guac_common_ssh_destroy_sftp_filesystem(guac_object* filesystem) {
guac_common_ssh_sftp_data* sftp_data =
(guac_common_ssh_sftp_data*) filesystem->data;
/* Shutdown SFTP session */
libssh2_sftp_shutdown(sftp_data->sftp_session);
/* Clean up the SFTP filesystem object */
guac_client_free_object(sftp_data->ssh_session->client, filesystem);
/* Disconnect SSH session corresponding to the SFTP session */
guac_common_ssh_destroy_session(sftp_data->ssh_session);
}

180
src/common-ssh/guac_sftp.h Normal file
View File

@ -0,0 +1,180 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUAC_COMMON_SSH_SFTP_H
#define GUAC_COMMON_SSH_SFTP_H
#include "guac_json.h"
#include "guac_ssh.h"
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <libssh2.h>
#include <libssh2_sftp.h>
/**
* Maximum number of bytes per path.
*/
#define GUAC_COMMON_SSH_SFTP_MAX_PATH 2048
/**
* Data associated with an SFTP-driven filesystem object.
*/
typedef struct guac_common_ssh_sftp_data {
/**
* The distinct SSH session used for SFTP.
*/
guac_common_ssh_session* ssh_session;
/**
* SFTP session, used for file transfers.
*/
LIBSSH2_SFTP* sftp_session;
/**
* The path files will be sent to, if uploaded directly via a "file"
* instruction.
*/
char upload_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
} guac_common_ssh_sftp_data;
/**
* The current state of a directory listing operation.
*/
typedef struct guac_common_ssh_sftp_ls_state {
/**
* Data associated with the current SFTP session.
*/
guac_common_ssh_sftp_data* sftp_data;
/**
* Reference to the directory currently being listed over SFTP. This
* directory must already be open from a call to libssh2_sftp_opendir().
*/
LIBSSH2_SFTP_HANDLE* directory;
/**
* The absolute path of the directory being listed.
*/
char directory_name[GUAC_COMMON_SSH_SFTP_MAX_PATH];
/**
* The current state of the JSON directory object being written.
*/
guac_common_json_state json_state;
} guac_common_ssh_sftp_ls_state;
/**
* Creates a new Guacamole filesystem object which provides access to files
* and directories via SFTP using the given SSH session. When the filesystem
* will no longer be used, it must be explicitly destroyed with
* guac_common_ssh_destroy_sftp_filesystem().
*
* @param session
* The session to use to provide SFTP.
*
* @param name
* The name to send as the name of the filesystem.
*
* @return
* A new Guacamole filesystem object, already configured to use SFTP for
* uploading and downloading files.
*/
guac_object* guac_common_ssh_create_sftp_filesystem(
guac_common_ssh_session* session,
const char* name);
/**
* Destroys the given filesystem object, disconnecting from SFTP and freeing
* and associated resources.
*
* @param object
* The filesystem object to destroy.
*/
void guac_common_ssh_destroy_sftp_filesystem(guac_object* filesystem);
/**
* Initiates an SFTP file download to the user via the Guacamole "file"
* instruction. The download will be automatically monitored and continued
* after this function terminates in response to "ack" instructions received by
* the client.
*
* @param filesystem
* The filesystem containing the file to be downloaded.
*
* @param filename
* The filename of the file to download, relative to the given filesystem.
*
* @return
* The file stream created for the file download, already configured to
* properly handle "ack" responses, etc. from the client.
*/
guac_stream* guac_common_ssh_sftp_download_file(guac_object* filesystem,
char* filename);
/**
* Handles an incoming stream from a Guacamole "file" instruction, saving the
* contents of that stream to the file having the given name within the
* upload directory set by guac_common_ssh_sftp_set_upload_path().
*
* @param filesystem
* The filesystem that should receive the uploaded file.
*
* @param stream
* The stream through which the uploaded file data will be received.
*
* @param mimetype
* The mimetype of the data being received.
*
* @param filename
* The filename of the file to write to. This filename will always be taken
* relative to the upload path set by
* guac_common_ssh_sftp_set_upload_path().
*
* @return
* Zero if the incoming stream has been handled successfully, non-zero on
* failure.
*/
int guac_common_ssh_sftp_handle_file_stream(guac_object* filesystem,
guac_stream* stream, char* mimetype, char* filename);
/**
* Set the destination directory for future uploads submitted via
* guac_common_ssh_sftp_handle_file_stream(). This function has no bearing
* on the destination directories of files uploaded with "put" instructions.
*
* @param filesystem
* The filesystem to set the upload path of.
*
* @param path
* The path to use for future uploads submitted via the
* guac_common_ssh_sftp_handle_file_stream() function.
*/
void guac_common_ssh_sftp_set_upload_path(guac_object* filesystem,
const char* path);
#endif

475
src/common-ssh/guac_ssh.c Normal file
View File

@ -0,0 +1,475 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "guac_ssh.h"
#include "guac_ssh_key.h"
#include <guacamole/client.h>
#include <libssh2.h>
#ifdef LIBSSH2_USES_GCRYPT
#include <gcrypt.h>
#endif
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#ifdef LIBSSH2_USES_GCRYPT
GCRY_THREAD_OPTION_PTHREAD_IMPL;
#endif
/**
* Array of mutexes, used by OpenSSL.
*/
static pthread_mutex_t* guac_common_ssh_openssl_locks;
/**
* Called by OpenSSL when locking or unlocking the Nth mutex.
*
* @param mode
* A bitmask denoting the action to be taken on the Nth lock, such as
* CRYPTO_LOCK or CRYPTO_UNLOCK.
*
* @param n
* The index of the lock to lock or unlock.
*
* @param file
* The filename of the function setting the lock, for debugging purposes.
*
* @param line
* The line number of the function setting the lock, for debugging
* purposes.
*/
static void guac_common_ssh_openssl_locking_callback(int mode, int n,
const char* file, int line){
/* Lock given mutex upon request */
if (mode & CRYPTO_LOCK)
pthread_mutex_lock(&(guac_common_ssh_openssl_locks[n]));
/* Unlock given mutex upon request */
else if (mode & CRYPTO_UNLOCK)
pthread_mutex_unlock(&(guac_common_ssh_openssl_locks[n]));
}
/**
* Called by OpenSSL when determining the current thread ID.
*
* @return
* An ID which uniquely identifies the current thread.
*/
static unsigned long guac_common_ssh_openssl_id_callback() {
return (unsigned long) pthread_self();
}
/**
* Creates the given number of mutexes, such that OpenSSL will have at least
* this number of mutexes at its disposal.
*
* @param count
* The number of mutexes (locks) to create.
*/
static void guac_common_ssh_openssl_init_locks(int count) {
int i;
/* Allocate required number of locks */
guac_common_ssh_openssl_locks =
malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks());
/* Initialize each lock */
for (i=0; i < count; i++)
pthread_mutex_init(&(guac_common_ssh_openssl_locks[i]), NULL);
}
/**
* Frees the given number of mutexes.
*
* @param count
* The number of mutexes (locks) to free.
*/
static void guac_common_ssh_openssl_free_locks(int count) {
int i;
/* Free all locks */
for (i=0; i < count; i++)
pthread_mutex_destroy(&(guac_common_ssh_openssl_locks[i]));
}
int guac_common_ssh_init(guac_client* client) {
#ifdef LIBSSH2_USES_GCRYPT
/* Init threadsafety in libgcrypt */
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
if (!gcry_check_version(GCRYPT_VERSION)) {
guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
return 1;
}
#endif
/* Init threadsafety in OpenSSL */
guac_common_ssh_openssl_init_locks(CRYPTO_num_locks());
CRYPTO_set_id_callback(guac_common_ssh_openssl_id_callback);
CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback);
/* Init OpenSSL */
SSL_library_init();
ERR_load_crypto_strings();
/* Init libssh2 */
libssh2_init(0);
/* Success */
return 0;
}
void guac_common_ssh_uninit() {
guac_common_ssh_openssl_free_locks(CRYPTO_num_locks());
}
/**
* Callback invoked by libssh2 when libssh2_userauth_publickkey() is invoked.
* This callback must sign the given data, returning the signature as newly-
* allocated buffer space.
*
* @param session
* The SSH session for which the signature is being generated.
*
* @param sig
* A pointer to the buffer space containing the signature. This callback
* MUST allocate and assign this space.
*
* @param sig_len
* The length of the signature within the allocated buffer space, in bytes.
* This value must be set to the size of the signature after the signing
* operation completes.
*
* @param data
* The arbitrary data that must be signed.
*
* @param data_len
* The length of the arbitrary data to be signed, in bytes.
*
* @param abstract
* The value of the abstract parameter provided with the corresponding call
* to libssh2_userauth_publickey().
*
* @return
* Zero on success, non-zero if the signing operation failed.
*/
static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session,
unsigned char** sig, size_t* sig_len,
const unsigned char* data, size_t data_len, void **abstract) {
guac_common_ssh_key* key = (guac_common_ssh_key*) abstract;
int length;
/* Allocate space for signature */
*sig = malloc(4096);
/* Sign with key */
length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig);
if (length < 0)
return 1;
*sig_len = length;
return 0;
}
/**
* Callback for the keyboard-interactive authentication method. Currently
* supports just one prompt for the password. This callback is invoked as
* needed to fullfill a call to libssh2_userauth_keyboard_interactive().
*
* @param name
* An arbitrary name which should be printed to the terminal for the
* benefit of the user. This is currently ignored.
*
* @param name_len
* The length of the name string, in bytes.
*
* @param instruction
* Arbitrary instructions which should be printed to the terminal for the
* benefit of the user. This is currently ignored.
*
* @param instruction_len
* The length of the instruction string, in bytes.
*
* @param num_prompts
* The number of keyboard-interactive prompts for which responses are
* requested. This callback currently only supports one prompt, and assumes
* that this prompt is requesting the password.
*
* @param prompts
* An array of all keyboard-interactive prompts for which responses are
* requested.
*
* @param responses
* A parallel array into which all prompt responses should be stored. Each
* entry within this array corresponds to the entry in the prompts array
* with the same index.
*
* @param abstract
* The value of the abstract parameter provided when the SSH session was
* created with libssh2_session_init_ex().
*/
static void guac_common_ssh_kbd_callback(const char *name, int name_len,
const char *instruction, int instruction_len, int num_prompts,
const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
void **abstract) {
guac_common_ssh_session* common_session =
(guac_common_ssh_session*) *abstract;
guac_client* client = common_session->client;
/* Send password if only one prompt */
if (num_prompts == 1) {
char* password = common_session->user->password;
responses[0].text = strdup(password);
responses[0].length = strlen(password);
}
/* If more than one prompt, a single password is not enough */
else
guac_client_log(client, GUAC_LOG_WARNING,
"Unsupported number of keyboard-interactive prompts: %i",
num_prompts);
}
/**
* Authenticates the user associated with the given session over SSH. All
* required credentials must already be present within the user object
* associated with the given session.
*
* @param session
* The session associated with the user to be authenticated.
*
* @return
* Zero if authentication succeeds, or non-zero if authentication has
* failed.
*/
static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) {
guac_client* client = common_session->client;
guac_common_ssh_user* user = common_session->user;
LIBSSH2_SESSION* session = common_session->session;
/* Get user credentials */
char* username = user->username;
char* password = user->password;
guac_common_ssh_key* key = user->private_key;
/* Get list of supported authentication methods */
char* user_authlist = libssh2_userauth_list(session, username,
strlen(username));
guac_client_log(client, GUAC_LOG_DEBUG,
"Supported authentication methods: %s", user_authlist);
/* Authenticate with private key, if provided */
if (key != NULL) {
/* Check if public key auth is supported on the server */
if (strstr(user_authlist, "publickey") == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"Public key authentication not supported");
return 1;
}
/* Attempt public key auth */
if (libssh2_userauth_publickey(session, username,
(unsigned char*) key->public_key, key->public_key_length,
guac_common_ssh_sign_callback, (void**) key)) {
/* Abort on failure */
char* error_message;
libssh2_session_last_error(session, &error_message, NULL, 0);
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"Public key authentication failed: %s", error_message);
return 1;
}
/* Private key authentication succeeded */
return 0;
}
/* Authenticate with password */
if (strstr(user_authlist, "password") != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Using password authentication method");
return libssh2_userauth_password(session, username, password);
}
/* Authenticate with password via keyboard-interactive auth */
if (strstr(user_authlist, "keyboard-interactive") != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Using keyboard-interactive authentication method");
return libssh2_userauth_keyboard_interactive(session, username,
&guac_common_ssh_kbd_callback);
}
/* No known authentication types available */
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE,
"No known authentication methods");
return 1;
}
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
const char* hostname, const char* port, guac_common_ssh_user* user) {
int retval;
int fd;
struct addrinfo* addresses;
struct addrinfo* current_address;
char connected_address[1024];
char connected_port[64];
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
.ai_protocol = IPPROTO_TCP
};
/* Get socket */
fd = socket(AF_INET, SOCK_STREAM, 0);
/* 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));
/* 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_ERROR,
"Unable to connect to any addresses.");
return NULL;
}
/* Free addrinfo */
freeaddrinfo(addresses);
/* Allocate new session */
guac_common_ssh_session* common_session =
malloc(sizeof(guac_common_ssh_session));
/* Open SSH session */
LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL,
NULL, common_session);
if (session == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Session allocation failed.");
free(common_session);
return NULL;
}
/* Perform handshake */
if (libssh2_session_handshake(session, fd)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"SSH handshake failed.");
free(common_session);
return NULL;
}
/* Store basic session data */
common_session->client = client;
common_session->user = user;
common_session->session = session;
common_session->fd = fd;
/* Attempt authentication */
if (guac_common_ssh_authenticate(common_session)) {
free(common_session);
return NULL;
}
/* Return created session */
return common_session;
}
void guac_common_ssh_destroy_session(guac_common_ssh_session* session) {
libssh2_session_disconnect(session->session, "Bye");
libssh2_session_free(session->session);
free(session);
}

113
src/common-ssh/guac_ssh.h Normal file
View File

@ -0,0 +1,113 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUAC_COMMON_SSH_H
#define GUAC_COMMON_SSH_H
#include "guac_ssh_user.h"
#include <guacamole/client.h>
#include <libssh2.h>
/**
* An SSH session, backed by libssh2 and associated with a particular
* Guacamole client.
*/
typedef struct guac_common_ssh_session {
/**
* The Guacamole client using this SSH session.
*/
guac_client* client;
/**
* The user that will be authenticating via SSH.
*/
guac_common_ssh_user* user;
/**
* The underlying SSH session from libssh2.
*/
LIBSSH2_SESSION* session;
/**
* The file descriptor of the socket being used for the SSH connection.
*/
int fd;
} guac_common_ssh_session;
/**
* Initializes the underlying SSH and encryption libraries used by Guacamole.
* This function must be called before any other guac_common_ssh_*() functions
* are called.
*
* @param client
* The Guacamole client that will be using SSH.
*
* @return
* Zero if initialization, or non-zero if an error occurs.
*/
int guac_common_ssh_init(guac_client* client);
/**
* Cleans up the underlying SSH and encryption libraries used by Guacamole.
* This function must be called once no other guac_common_ssh_*() functions
* will be used.
*/
void guac_common_ssh_uninit();
/**
* Connects to the SSH server running at the given hostname and port, and
* authenticates as the given user. If an error occurs while connecting or
* authenticating, the Guacamole client will automatically and fatally abort.
*
* @param client
* The Guacamole client that will be using SSH.
*
* @param hostname
* The hostname of the SSH server to connect to.
*
* @param port
* The port to connect to on the given hostname.
*
* @param user
* The user to authenticate as, once connected.
*
* @return
* A new SSH session if the connection and authentication succeed, or NULL
* if the connection or authentication were not successful.
*/
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
const char* hostname, const char* port, guac_common_ssh_user* user);
/**
* Disconnects and destroys the given SSH session, freeing all associated
* resources.
*
* @param session
* The SSH session to destroy.
*/
void guac_common_ssh_destroy_session(guac_common_ssh_session* session);
#endif

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2015 Glyptodon LLC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -29,7 +29,7 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
void buffer_write_byte(char** buffer, uint8_t value) { void guac_common_ssh_buffer_write_byte(char** buffer, uint8_t value) {
uint8_t* data = (uint8_t*) *buffer; uint8_t* data = (uint8_t*) *buffer;
*data = value; *data = value;
@ -38,7 +38,7 @@ void buffer_write_byte(char** buffer, uint8_t value) {
} }
void buffer_write_uint32(char** buffer, uint32_t value) { void guac_common_ssh_buffer_write_uint32(char** buffer, uint32_t value) {
uint8_t* data = (uint8_t*) *buffer; uint8_t* data = (uint8_t*) *buffer;
@ -51,19 +51,20 @@ void buffer_write_uint32(char** buffer, uint32_t value) {
} }
void buffer_write_data(char** buffer, const char* data, int length) { void guac_common_ssh_buffer_write_data(char** buffer, const char* data,
int length) {
memcpy(*buffer, data, length); memcpy(*buffer, data, length);
*buffer += length; *buffer += length;
} }
void buffer_write_bignum(char** buffer, BIGNUM* value) { void guac_common_ssh_buffer_write_bignum(char** buffer, BIGNUM* value) {
unsigned char* bn_buffer; unsigned char* bn_buffer;
int length; int length;
/* If zero, just write zero length */ /* If zero, just write zero length */
if (BN_is_zero(value)) { if (BN_is_zero(value)) {
buffer_write_uint32(buffer, 0); guac_common_ssh_buffer_write_uint32(buffer, 0);
return; return;
} }
@ -76,11 +77,11 @@ void buffer_write_bignum(char** buffer, BIGNUM* value) {
/* If first byte has high bit set, write padding byte */ /* If first byte has high bit set, write padding byte */
if (bn_buffer[0] & 0x80) { if (bn_buffer[0] & 0x80) {
buffer_write_uint32(buffer, length+1); guac_common_ssh_buffer_write_uint32(buffer, length+1);
buffer_write_byte(buffer, 0); guac_common_ssh_buffer_write_byte(buffer, 0);
} }
else else
buffer_write_uint32(buffer, length); guac_common_ssh_buffer_write_uint32(buffer, length);
/* Write data */ /* Write data */
memcpy(*buffer, bn_buffer, length); memcpy(*buffer, bn_buffer, length);
@ -90,12 +91,13 @@ void buffer_write_bignum(char** buffer, BIGNUM* value) {
} }
void buffer_write_string(char** buffer, const char* string, int length) { void guac_common_ssh_buffer_write_string(char** buffer, const char* string,
buffer_write_uint32(buffer, length); int length) {
buffer_write_data(buffer, string, length); guac_common_ssh_buffer_write_uint32(buffer, length);
guac_common_ssh_buffer_write_data(buffer, string, length);
} }
uint8_t buffer_read_byte(char** buffer) { uint8_t guac_common_ssh_buffer_read_byte(char** buffer) {
uint8_t* data = (uint8_t*) *buffer; uint8_t* data = (uint8_t*) *buffer;
uint8_t value = *data; uint8_t value = *data;
@ -106,7 +108,7 @@ uint8_t buffer_read_byte(char** buffer) {
} }
uint32_t buffer_read_uint32(char** buffer) { uint32_t guac_common_ssh_buffer_read_uint32(char** buffer) {
uint8_t* data = (uint8_t*) *buffer; uint8_t* data = (uint8_t*) *buffer;
uint32_t value = uint32_t value =
@ -121,11 +123,11 @@ uint32_t buffer_read_uint32(char** buffer) {
} }
char* buffer_read_string(char** buffer, int* length) { char* guac_common_ssh_buffer_read_string(char** buffer, int* length) {
char* value; char* value;
*length = buffer_read_uint32(buffer); *length = guac_common_ssh_buffer_read_uint32(buffer);
value = *buffer; value = *buffer;
*buffer += *length; *buffer += *length;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2015 Glyptodon LLC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -20,9 +20,8 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_COMMON_SSH_BUFFER_H
#ifndef _GUAC_SSH_BUFFER_H #define GUAC_COMMON_SSH_BUFFER_H
#define _GUAC_SSH_BUFFER_H
#include "config.h" #include "config.h"
@ -32,51 +31,107 @@
/** /**
* Writes the given byte to the given buffer, advancing the buffer pointer by * Writes the given byte to the given buffer, advancing the buffer pointer by
* one byte. * one byte.
*
* @param buffer
* The buffer to write to.
*
* @param value
* The value to write.
*/ */
void buffer_write_byte(char** buffer, uint8_t value); void guac_common_ssh_buffer_write_byte(char** buffer, uint8_t value);
/** /**
* Writes the given integer to the given buffer, advancing the buffer pointer * Writes the given integer to the given buffer, advancing the buffer pointer
* four bytes. * four bytes.
*
* @param buffer
* The buffer to write to.
*
* @param value
* The value to write.
*/ */
void buffer_write_uint32(char** buffer, uint32_t value); void guac_common_ssh_buffer_write_uint32(char** buffer, uint32_t value);
/** /**
* Writes the given string and its length to the given buffer, advancing the * Writes the given string and its length to the given buffer, advancing the
* buffer pointer by the size of the length (four bytes) and the size of the * buffer pointer by the size of the length (four bytes) and the size of the
* string. * string.
*
* @param buffer
* The buffer to write to.
*
* @param string
* The string value to write.
*
* @param length
* The length of the string to write, in bytes.
*/ */
void buffer_write_string(char** buffer, const char* string, int length); void guac_common_ssh_buffer_write_string(char** buffer, const char* string,
int length);
/** /**
* Writes the given BIGNUM the given buffer, advancing the buffer pointer by * Writes the given BIGNUM the given buffer, advancing the buffer pointer by
* the size of the length (four bytes) and the size of the BIGNUM. * the size of the length (four bytes) and the size of the BIGNUM.
*
* @param buffer
* The buffer to write to.
*
* @param value
* The value to write.
*/ */
void buffer_write_bignum(char** buffer, BIGNUM* value); void guac_common_ssh_buffer_write_bignum(char** buffer, BIGNUM* value);
/** /**
* Writes the given data the given buffer, advancing the buffer pointer by the * Writes the given data the given buffer, advancing the buffer pointer by the
* given length. * given length.
*
* @param data
* The arbitrary data to write.
*
* @param length
* The length of data to write, in bytes.
*/ */
void buffer_write_data(char** buffer, const char* data, int length); void guac_common_ssh_buffer_write_data(char** buffer, const char* data, int length);
/** /**
* Reads a single byte from the given buffer, advancing the buffer by one byte. * Reads a single byte from the given buffer, advancing the buffer by one byte.
*
* @param buffer
* The buffer to read from.
*
* @return
* The value read from the buffer.
*/ */
uint8_t buffer_read_byte(char** buffer); uint8_t guac_common_ssh_buffer_read_byte(char** buffer);
/** /**
* Reads an integer from the given buffer, advancing the buffer by four bytes. * Reads an integer from the given buffer, advancing the buffer by four bytes.
*
* @param buffer
* The buffer to read from.
*
* @return
* The value read from the buffer.
*/ */
uint32_t buffer_read_uint32(char** buffer); uint32_t guac_common_ssh_buffer_read_uint32(char** buffer);
/** /**
* Reads a string and its length from the given buffer, advancing the buffer * Reads a string and its length from the given buffer, advancing the buffer
* by the size of the length (four bytes) and the size of the string, and * by the size of the length (four bytes) and the size of the string, and
* returning a pointer to the buffer. The length of the string is stored in * returning a pointer to the buffer. The length of the string is stored in
* the given int. * the given int.
*
* @param buffer
* The buffer to read from.
*
* @param length
* A pointer to an integer into which the length of the read string will
* be stored.
*
* @return
* A pointer to the value within the buffer.
*/ */
char* buffer_read_string(char** buffer, int* length); char* guac_common_ssh_buffer_read_string(char** buffer, int* length);
#endif #endif

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2015 Glyptodon LLC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -22,8 +22,8 @@
#include "config.h" #include "config.h"
#include "ssh_buffer.h" #include "guac_ssh_buffer.h"
#include "ssh_key.h" #include "guac_ssh_key.h"
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/bn.h> #include <openssl/bn.h>
@ -37,9 +37,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) { guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
char* passphrase) {
ssh_key* key; guac_common_ssh_key* key;
BIO* key_bio; BIO* key_bio;
char* public_key; char* public_key;
@ -61,7 +62,7 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
return NULL; return NULL;
/* Allocate key */ /* Allocate key */
key = malloc(sizeof(ssh_key)); key = malloc(sizeof(guac_common_ssh_key));
key->rsa = rsa_key; key->rsa = rsa_key;
/* Set type */ /* Set type */
@ -72,9 +73,9 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
pos = public_key; pos = public_key;
/* Derive public key */ /* Derive public key */
buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1); guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
buffer_write_bignum(&pos, rsa_key->e); guac_common_ssh_buffer_write_bignum(&pos, rsa_key->e);
buffer_write_bignum(&pos, rsa_key->n); guac_common_ssh_buffer_write_bignum(&pos, rsa_key->n);
/* Save public key to structure */ /* Save public key to structure */
key->public_key = public_key; key->public_key = public_key;
@ -95,7 +96,7 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
return NULL; return NULL;
/* Allocate key */ /* Allocate key */
key = malloc(sizeof(ssh_key)); key = malloc(sizeof(guac_common_ssh_key));
key->dsa = dsa_key; key->dsa = dsa_key;
/* Set type */ /* Set type */
@ -106,11 +107,11 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
pos = public_key; pos = public_key;
/* Derive public key */ /* Derive public key */
buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1); guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
buffer_write_bignum(&pos, dsa_key->p); guac_common_ssh_buffer_write_bignum(&pos, dsa_key->p);
buffer_write_bignum(&pos, dsa_key->q); guac_common_ssh_buffer_write_bignum(&pos, dsa_key->q);
buffer_write_bignum(&pos, dsa_key->g); guac_common_ssh_buffer_write_bignum(&pos, dsa_key->g);
buffer_write_bignum(&pos, dsa_key->pub_key); guac_common_ssh_buffer_write_bignum(&pos, dsa_key->pub_key);
/* Save public key to structure */ /* Save public key to structure */
key->public_key = public_key; key->public_key = public_key;
@ -134,14 +135,14 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
} }
const char* ssh_key_error() { const char* guac_common_ssh_key_error() {
/* Return static error string */ /* Return static error string */
return ERR_reason_error_string(ERR_get_error()); return ERR_reason_error_string(ERR_get_error());
} }
void ssh_key_free(ssh_key* key) { void guac_common_ssh_key_free(guac_common_ssh_key* key) {
/* Free key-specific data */ /* Free key-specific data */
if (key->type == SSH_KEY_RSA) if (key->type == SSH_KEY_RSA)
@ -153,7 +154,8 @@ void ssh_key_free(ssh_key* key) {
free(key); free(key);
} }
int ssh_key_sign(ssh_key* key, const char* data, int length, unsigned char* sig) { int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
int length, unsigned char* sig) {
const EVP_MD* md; const EVP_MD* md;
EVP_MD_CTX md_ctx; EVP_MD_CTX md_ctx;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2015 Glyptodon LLC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -20,9 +20,8 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_COMMON_SSH_KEY_H
#ifndef _GUAC_SSH_KEY_H #define GUAC_COMMON_SSH_KEY_H
#define _GUAC_SSH_KEY_H
#include "config.h" #include "config.h"
@ -51,7 +50,7 @@
/** /**
* The type of an SSH key. * The type of an SSH key.
*/ */
typedef enum ssh_key_type { typedef enum guac_common_ssh_key_type {
/** /**
* RSA key. * RSA key.
@ -63,17 +62,17 @@ typedef enum ssh_key_type {
*/ */
SSH_KEY_DSA SSH_KEY_DSA
} ssh_key_type; } guac_common_ssh_key_type;
/** /**
* Abstraction of a key used for SSH authentication. * Abstraction of a key used for SSH authentication.
*/ */
typedef struct ssh_key { typedef struct guac_common_ssh_key {
/** /**
* The type of this key. * The type of this key.
*/ */
ssh_key_type type; guac_common_ssh_key_type type;
/** /**
* Underlying RSA private key, if any. * Underlying RSA private key, if any.
@ -105,13 +104,28 @@ typedef struct ssh_key {
*/ */
int private_key_length; int private_key_length;
} ssh_key; } guac_common_ssh_key;
/** /**
* Allocates a new key containing the given private key data and specified * Allocates a new key containing the given private key data and specified
* passphrase. If unable to read the key, NULL is returned. * passphrase. If unable to read the key, NULL is returned.
*
* @param data
* The base64-encoded data to decode when reading the key.
*
* @param length
* The length of the provided data, in bytes.
*
* @param passphrase
* The passphrase to use when decrypting the key, if any, or an empty
* string or NULL if no passphrase is needed.
*
* @return
* The decoded, decrypted private key, or NULL if the key could not be
* decoded.
*/ */
ssh_key* ssh_key_alloc(char* data, int length, char* passphrase); guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
char* passphrase);
/** /**
* Returns a statically-allocated string describing the most recent SSH key * Returns a statically-allocated string describing the most recent SSH key
@ -120,18 +134,40 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase);
* @return * @return
* A statically-allocated string describing the most recent SSH key error. * A statically-allocated string describing the most recent SSH key error.
*/ */
const char* ssh_key_error(); const char* guac_common_ssh_key_error();
/** /**
* Frees all memory associated with the given key. * Frees all memory associated with the given key.
*
* @param key
* The key to free.
*/ */
void ssh_key_free(ssh_key* key); void guac_common_ssh_key_free(guac_common_ssh_key* key);
/** /**
* Signs the given data using the given key, returning the length of the * Signs the given data using the given key, returning the length of the
* signature in bytes, or a value less than zero on error. * signature in bytes, or a value less than zero on error.
*
* @param key
* The key to use when signing the given data.
*
* @param data
* The arbitrary data to sign.
*
* @param length
* The length of the arbitrary data being signed, in bytes.
*
* @param sig
* The buffer into which the signature should be written. The buffer must
* be at least DSA_SIG_SIZE for DSA keys. For RSA keys, the signature size
* is dependent only on key size, and is equal to the length of the
* modulus, in bytes.
*
* @return
* The number of bytes in the resulting signature.
*/ */
int ssh_key_sign(ssh_key* key, const char* data, int length, unsigned char* sig); int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
int length, unsigned char* sig);
#endif #endif

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "guac_ssh_key.h"
#include "guac_ssh_user.h"
#include <stdlib.h>
#include <string.h>
guac_common_ssh_user* guac_common_ssh_create_user(const char* username) {
guac_common_ssh_user* user = malloc(sizeof(guac_common_ssh_user));
/* Init user */
user->username = strdup(username);
user->password = NULL;
user->private_key = NULL;
return user;
}
void guac_common_ssh_destroy_user(guac_common_ssh_user* user) {
/* Free private key, if present */
if (user->private_key != NULL)
guac_common_ssh_key_free(user->private_key);
/* Free all other data */
free(user->password);
free(user->username);
}
void guac_common_ssh_user_set_password(guac_common_ssh_user* user,
const char* password) {
/* Replace current password with given value */
free(user->password);
user->password = strdup(password);
}
int guac_common_ssh_user_import_key(guac_common_ssh_user* user,
char* private_key, char* passphrase) {
/* Free existing private key, if present */
if (user->private_key != NULL)
guac_common_ssh_key_free(user->private_key);
/* Attempt to read key without passphrase if none given */
if (passphrase == NULL)
user->private_key = guac_common_ssh_key_alloc(private_key,
strlen(private_key), "");
/* Otherwise, use provided passphrase */
else
user->private_key = guac_common_ssh_key_alloc(private_key,
strlen(private_key), passphrase);
/* Fail if key could not be read */
return user->private_key == NULL;
}

View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUAC_COMMON_SSH_USER_H
#define GUAC_COMMON_SSH_USER_H
#include "guac_ssh_key.h"
/**
* Data describing an SSH user, including their credentials.
*/
typedef struct guac_common_ssh_user {
/**
* The username of this user.
*/
char* username;
/**
* The password which should be used to authenticate this user, if any, or
* NULL if a private key will be used instead.
*/
char* password;
/**
* The private key which should be used to authenticate this user, if any,
* or NULL if a password will be used instead.
*/
guac_common_ssh_key* private_key;
} guac_common_ssh_user;
/**
* Creates a new SSH user with the given username. When additionally populated
* with a password or private key, this user can then be used for
* authentication.
*
* @param username
* The username of the user being created.
*
* @return
* A new SSH user having the given username, but no associated password
* or private key.
*/
guac_common_ssh_user* guac_common_ssh_create_user(const char* username);
/**
* Destroys the given user object, releasing all associated resources.
*
* @param user
* The user to destroy.
*/
void guac_common_ssh_destroy_user(guac_common_ssh_user* user);
/**
* Associates the given user with the given password, such that that password
* is used for future authentication attempts.
*
* @param user
* The user to associate with the given password.
*
* @param password
* The password to associate with the given user.
*/
void guac_common_ssh_user_set_password(guac_common_ssh_user* user,
const char* password);
/**
* Imports the given private key, associating that key with the given user. If
* necessary to decrypt the key, a passphrase may be specified. The private key
* must be provided in base64 form. If the private key is imported
* successfully, it will be used for future authentication attempts.
*
* @param user
* The user to associate with the given private key.
*
* @param private_key
* The base64-encoded private key to import.
*
* @param passphrase
* The passphrase to use to decrypt the given private key, or NULL if no
* passphrase should be used.
*
* @return
* Zero if the private key is successfully imported, or non-zero if the
* private key could not be imported due to an error.
*/
int guac_common_ssh_user_import_key(guac_common_ssh_user* user,
char* private_key, char* passphrase);
#endif

View File

@ -22,7 +22,6 @@
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@
noinst_LTLIBRARIES = libguac_common.la noinst_LTLIBRARIES = libguac_common.la
@ -50,5 +49,10 @@ libguac_common_la_SOURCES = \
guac_string.c \ guac_string.c \
guac_surface.c guac_surface.c
libguac_common_la_LIBADD = @LIBGUAC_LTLIB@ libguac_common_la_CFLAGS = \
-Werror -Wall -pedantic \
@LIBGUAC_INCLUDE@
libguac_common_la_LIBADD = \
@LIBGUAC_LTLIB@

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2013 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -22,8 +22,6 @@
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
sbin_PROGRAMS = guacd sbin_PROGRAMS = guacd
man_MANS = \ man_MANS = \
@ -47,10 +45,24 @@ guacd_SOURCES = \
conf-parse.c \ conf-parse.c \
log.c log.c
guacd_LDADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ guacd_CFLAGS = \
guacd_LDFLAGS = @PTHREAD_LIBS@ @SSL_LIBS@ -Werror -Wall -pedantic \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@
guacd_LDADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
guacd_LDFLAGS = \
@PTHREAD_LIBS@ \
@SSL_LIBS@
EXTRA_DIST = \
init.d/guacd.in \
man/guacd.8 \
man/guacd.conf.5
EXTRA_DIST = init.d/guacd.in man/guacd.8 man/guacd.conf.5
CLEANFILES = $(init_SCRIPTS) CLEANFILES = $(init_SCRIPTS)
# SSL support # SSL support

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2013 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -22,9 +22,11 @@
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS = -Werror -Wall -pedantic -Iguacamole
lib_LTLIBRARIES = libguac.la
libguacincdir = $(includedir)/guacamole libguacincdir = $(includedir)/guacamole
libguacinc_HEADERS = \ libguacinc_HEADERS = \
guacamole/audio.h \ guacamole/audio.h \
guacamole/audio-fntypes.h \ guacamole/audio-fntypes.h \
@ -89,7 +91,17 @@ libguac_la_SOURCES += ogg_encoder.c
noinst_HEADERS += ogg_encoder.h noinst_HEADERS += ogg_encoder.h
endif endif
lib_LTLIBRARIES = libguac.la libguac_la_CFLAGS = \
libguac_la_LDFLAGS = -version-info 9:0:0 @PTHREAD_LIBS@ @CAIRO_LIBS@ @PNG_LIBS@ @VORBIS_LIBS@ @UUID_LIBS@ -Werror -Wall -pedantic -Iguacamole
libguac_la_LIBADD = @LIBADD_DLOPEN@
libguac_la_LDFLAGS = \
-version-info 9:0:0 \
@CAIRO_LIBS@ \
@PNG_LIBS@ \
@PTHREAD_LIBS@ \
@UUID_LIBS@ \
@VORBIS_LIBS@
libguac_la_LIBADD = \
@LIBADD_DLOPEN@

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2013 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -21,9 +21,7 @@
# #
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
lib_LTLIBRARIES = libguac-client-rdp.la lib_LTLIBRARIES = libguac-client-rdp.la
@ -114,17 +112,83 @@ noinst_HEADERS += rdp_disp.h
libguac_client_rdp_la_SOURCES += rdp_disp.c libguac_client_rdp_la_SOURCES += rdp_disp.c
endif endif
libguac_client_rdp_la_LDFLAGS = -version-info 0:0:0 @RDP_LIBS@ @PTHREAD_LIBS@ @CAIRO_LIBS@ #
guacsvc_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@ # Main RDP client library
guacsnd_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@ #
guacdr_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@
libguac_client_rdp_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ libguac_client_rdp_la_CFLAGS = \
guacsvc_libadd = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ -Werror -Wall -Iinclude \
guacsnd_libadd = @LIBGUAC_LTLIB@ @COMMON_INCLUDE@ \
guacdr_libadd = @LIBGUAC_LTLIB@ @LIBGUAC_INCLUDE@
libguac_client_rdp_la_LDFLAGS = \
-version-info 0:0:0 \
@CAIRO_LIBS@ \
@PTHREAD_LIBS@ \
@RDP_LIBS@
libguac_client_rdp_la_LIBADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
#
# RDPDR
#
guacdr_cflags = \
-Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@
guacdr_ldflags = \
-module -avoid-version -shared \
@PTHREAD_LIBS@ \
@RDP_LIBS@
guacdr_libadd = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
#
# RDPSND
#
guacsnd_cflags = \
-Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@
guacsnd_ldflags = \
-module -avoid-version -shared \
@PTHREAD_LIBS@ \
@RDP_LIBS@
guacsnd_libadd = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
#
# Static Virtual Channels
#
guacsvc_cflags = \
-Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@
guacsvc_ldflags = \
-module -avoid-version -shared \
@PTHREAD_LIBS@ \
@RDP_LIBS@
guacsvc_libadd = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
#
# Autogenerate keymaps # Autogenerate keymaps
#
CLEANFILES = _generated_keymaps.c CLEANFILES = _generated_keymaps.c
BUILT_SOURCES = _generated_keymaps.c BUILT_SOURCES = _generated_keymaps.c
@ -140,41 +204,55 @@ rdp_keymaps = \
_generated_keymaps.c: $(rdp_keymaps) _generated_keymaps.c: $(rdp_keymaps)
keymaps/generate.pl $(rdp_keymaps) keymaps/generate.pl $(rdp_keymaps)
EXTRA_DIST = $(rdp_keymaps) keymaps/generate.pl EXTRA_DIST = \
$(rdp_keymaps) \
keymaps/generate.pl
if LEGACY_FREERDP_EXTENSIONS if LEGACY_FREERDP_EXTENSIONS
# FreeRDP 1.0-style extensions # FreeRDP 1.0-style extensions
freerdp_LTLIBRARIES = guacsvc.la guacsnd.la guacdr.la freerdp_LTLIBRARIES = \
guacdr.la \
guacsnd.la \
guacsvc.la
guacsvc_la_SOURCES = ${guacsvc_sources} guacdr_la_SOURCES = ${guacdr_sources}
guacsvc_la_LDFLAGS = ${guacsvc_ldflags} guacdr_la_CFLAGS = ${guacdr_cflags}
guacsvc_la_LIBADD = ${guacsvc_libadd} guacdr_la_LDFLAGS = ${guacdr_ldflags}
guacdr_la_LIBADD = ${guacdr_libadd}
guacsnd_la_SOURCES = ${guacsnd_sources} guacsnd_la_SOURCES = ${guacsnd_sources}
guacsnd_la_CFLAGS = ${guacsnd_cflags}
guacsnd_la_LDFLAGS = ${guacsnd_ldflags} guacsnd_la_LDFLAGS = ${guacsnd_ldflags}
guacsnd_la_LIBADD = ${guacsnd_libadd} guacsnd_la_LIBADD = ${guacsnd_libadd}
guacdr_la_SOURCES = ${guacdr_sources} guacsvc_la_SOURCES = ${guacsvc_sources}
guacdr_la_LDFLAGS = ${guacdr_ldflags} guacsvc_la_CFLAGS = ${guacsvc_cflags}
guacdr_la_LIBADD = ${guacdr_libadd} guacsvc_la_LDFLAGS = ${guacsvc_ldflags}
guacsvc_la_LIBADD = ${guacsvc_libadd}
else else
# FreeRDP 1.1 (and hopefully onward) extensions # FreeRDP 1.1 (and hopefully onward) extensions
freerdp_LTLIBRARIES = guacsvc-client.la guacsnd-client.la guacdr-client.la freerdp_LTLIBRARIES = \
guacdr-client.la \
guacsnd-client.la \
guacsvc-client.la
guacsvc_client_la_SOURCES = ${guacsvc_sources} guacdr_client_la_SOURCES = ${guacdr_sources}
guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags} guacdr_client_la_CFLAGS = ${guacdr_cflags}
guacsvc_client_la_LIBADD = ${guacsvc_libadd} guacdr_client_la_LDFLAGS = ${guacdr_ldflags}
guacdr_client_la_LIBADD = ${guacdr_libadd}
guacsnd_client_la_SOURCES = ${guacsnd_sources} guacsnd_client_la_SOURCES = ${guacsnd_sources}
guacsnd_client_la_CFLAGS = ${guacsnd_cflags}
guacsnd_client_la_LDFLAGS = ${guacsnd_ldflags} guacsnd_client_la_LDFLAGS = ${guacsnd_ldflags}
guacsnd_client_la_LIBADD = ${guacsnd_libadd} guacsnd_client_la_LIBADD = ${guacsnd_libadd}
guacdr_client_la_SOURCES = ${guacdr_sources} guacsvc_client_la_SOURCES = ${guacsvc_sources}
guacdr_client_la_LDFLAGS = ${guacdr_ldflags} guacsvc_client_la_CFLAGS = ${guacsvc_cflags}
guacdr_client_la_LIBADD = ${guacdr_libadd} guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags}
guacsvc_client_la_LIBADD = ${guacsvc_libadd}
endif endif

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2013 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -21,7 +21,6 @@
# #
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libguac-client-ssh.la lib_LTLIBRARIES = libguac-client-ssh.la
@ -31,18 +30,14 @@ libguac_client_ssh_la_SOURCES = \
clipboard.c \ clipboard.c \
guac_handlers.c \ guac_handlers.c \
sftp.c \ sftp.c \
ssh_buffer.c \ ssh_client.c
ssh_client.c \
ssh_key.c
noinst_HEADERS = \ noinst_HEADERS = \
client.h \ client.h \
clipboard.h \ clipboard.h \
guac_handlers.h \ guac_handlers.h \
sftp.h \ sftp.h \
ssh_buffer.h \ ssh_client.h
ssh_client.h \
ssh_key.h
# Add agent sources if enabled # Add agent sources if enabled
if ENABLE_SSH_AGENT if ENABLE_SSH_AGENT
@ -50,7 +45,21 @@ libguac_client_ssh_la_SOURCES += ssh_agent.c
noinst_HEADERS += ssh_agent.h noinst_HEADERS += ssh_agent.h
endif endif
libguac_client_ssh_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@ libguac_client_ssh_la_CFLAGS = \
libguac_client_ssh_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@ -Werror -Wall -Iinclude \
libguac_client_ssh_la_LDFLAGS = -version-info 0:0:0 @SSH_LIBS@ @SSL_LIBS@ @PTHREAD_LIBS@ @COMMON_SSH_INCLUDE@ \
@LIBGUAC_INCLUDE@ \
@TERMINAL_INCLUDE@
libguac_client_ssh_la_LIBADD = \
@COMMON_LTLIB@ \
@COMMON_SSH_LTLIB@ \
@LIBGUAC_LTLIB@ \
@TERMINAL_LTLIB@
libguac_client_ssh_la_LDFLAGS = \
-version-info 0:0:0 \
@PTHREAD_LIBS@ \
@SSH_LIBS@ \
@SSL_LIBS@

View File

@ -142,7 +142,6 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
strcpy(client_data->password, argv[IDX_PASSWORD]); strcpy(client_data->password, argv[IDX_PASSWORD]);
/* Init public key auth information */ /* Init public key auth information */
client_data->key = NULL;
strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]); strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]);
strcpy(client_data->key_passphrase, argv[IDX_PASSPHRASE]); strcpy(client_data->key_passphrase, argv[IDX_PASSPHRASE]);
@ -160,10 +159,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
/* Parse SFTP enable */ /* Parse SFTP enable */
client_data->enable_sftp = strcmp(argv[IDX_ENABLE_SFTP], "true") == 0; client_data->enable_sftp = strcmp(argv[IDX_ENABLE_SFTP], "true") == 0;
client_data->sftp_session = NULL;
client_data->sftp_ssh_session = NULL;
client_data->sftp_filesystem = NULL; client_data->sftp_filesystem = NULL;
strcpy(client_data->sftp_upload_path, ".");
#ifdef ENABLE_SSH_AGENT #ifdef ENABLE_SSH_AGENT
client_data->enable_agent = strcmp(argv[IDX_ENABLE_AGENT], "true") == 0; client_data->enable_agent = strcmp(argv[IDX_ENABLE_AGENT], "true") == 0;

View File

@ -26,8 +26,8 @@
#include "config.h" #include "config.h"
#include "guac_ssh.h"
#include "sftp.h" #include "sftp.h"
#include "ssh_key.h"
#include "terminal.h" #include "terminal.h"
#include <libssh2.h> #include <libssh2.h>
@ -77,11 +77,6 @@ typedef struct ssh_guac_client_data {
*/ */
char key_passphrase[1024]; char key_passphrase[1024];
/**
* The private key to use for authentication, if any.
*/
ssh_key* key;
/** /**
* The name of the font to use for display rendering. * The name of the font to use for display rendering.
*/ */
@ -117,29 +112,13 @@ typedef struct ssh_guac_client_data {
/** /**
* SSH session, used by the SSH client thread. * SSH session, used by the SSH client thread.
*/ */
LIBSSH2_SESSION* session; guac_common_ssh_session* session;
/**
* The distinct SSH session used for SFTP.
*/
LIBSSH2_SESSION* sftp_ssh_session;
/**
* SFTP session, used for file transfers.
*/
LIBSSH2_SFTP* sftp_session;
/** /**
* The filesystem object exposed for the SFTP session. * The filesystem object exposed for the SFTP session.
*/ */
guac_object* sftp_filesystem; guac_object* sftp_filesystem;
/**
* The path files will be sent to, if uploaded directly via a "file"
* instruction.
*/
char sftp_upload_path[GUAC_SFTP_MAX_PATH];
/** /**
* SSH terminal channel, used by the SSH client thread. * SSH terminal channel, used by the SSH client thread.
*/ */

View File

@ -24,7 +24,8 @@
#include "client.h" #include "client.h"
#include "guac_handlers.h" #include "guac_handlers.h"
#include "ssh_key.h" #include "guac_sftp.h"
#include "guac_ssh.h"
#include "terminal.h" #include "terminal.h"
#include <cairo/cairo.h> #include <cairo/cairo.h>
@ -101,31 +102,18 @@ int ssh_guac_client_free_handler(guac_client* client) {
/* Free channels */ /* Free channels */
libssh2_channel_free(guac_client_data->term_channel); libssh2_channel_free(guac_client_data->term_channel);
/* Shutdown SFTP session, if any */
if (guac_client_data->sftp_session)
libssh2_sftp_shutdown(guac_client_data->sftp_session);
/* Disconnect SSH session corresponding to the SFTP session */
if (guac_client_data->sftp_ssh_session) {
libssh2_session_disconnect(guac_client_data->sftp_ssh_session, "Bye");
libssh2_session_free(guac_client_data->sftp_ssh_session);
}
/* Clean up the SFTP filesystem object */ /* Clean up the SFTP filesystem object */
if (guac_client_data->sftp_filesystem) if (guac_client_data->sftp_filesystem)
guac_client_free_object(client, guac_client_data->sftp_filesystem); guac_common_ssh_destroy_sftp_filesystem(guac_client_data->sftp_filesystem);
/* Free session */ /* Free session */
if (guac_client_data->session != NULL) if (guac_client_data->session != NULL)
libssh2_session_free(guac_client_data->session); guac_common_ssh_destroy_session(guac_client_data->session);
/* Free auth key */
if (guac_client_data->key != NULL)
ssh_key_free(guac_client_data->key);
/* Free generic data struct */ /* Free generic data struct */
free(client->data); free(client->data);
guac_common_ssh_uninit();
return 0; return 0;
} }

View File

@ -22,517 +22,43 @@
#include "config.h" #include "config.h"
#include "guac_json.h"
#include "client.h" #include "client.h"
#include "guac_sftp.h"
#include "sftp.h" #include "sftp.h"
#include <fcntl.h>
#include <libgen.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libssh2_sftp.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
/**
* Concatenates the given filename with the given path, separating the two
* with a single forward slash. The full result must be no more than
* GUAC_SFTP_MAX_PATH bytes long, counting null terminator.
*
* @param fullpath
* The buffer to store the result within. This buffer must be at least
* GUAC_SFTP_MAX_PATH bytes long.
*
* @param path
* The path to append the filename to.
*
* @param filename
* The filename to append to the path.
*
* @return
* true if the filename is valid and was successfully appended to the path,
* false otherwise.
*/
static bool guac_ssh_append_filename(char* fullpath, const char* path,
const char* filename) {
int i;
/* Disallow "." as a filename */
if (strcmp(filename, ".") == 0)
return false;
/* Disallow ".." as a filename */
if (strcmp(filename, "..") == 0)
return false;
/* Copy path, append trailing slash */
for (i=0; i<GUAC_SFTP_MAX_PATH; i++) {
/*
* Append trailing slash only if:
* 1) Trailing slash is not already present
* 2) Path is non-empty
*/
char c = path[i];
if (c == '\0') {
if (i > 0 && path[i-1] != '/')
fullpath[i++] = '/';
break;
}
/* Copy character if not end of string */
fullpath[i] = c;
}
/* Append filename */
for (; i<GUAC_SFTP_MAX_PATH; i++) {
char c = *(filename++);
if (c == '\0')
break;
/* Filenames may not contain slashes */
if (c == '\\' || c == '/')
return false;
/* Append each character within filename */
fullpath[i] = c;
}
/* Verify path length is within maximum */
if (i == GUAC_SFTP_MAX_PATH)
return false;
/* Terminate path string */
fullpath[i] = '\0';
/* Append was successful */
return true;
}
int guac_sftp_file_handler(guac_client* client, guac_stream* stream, int guac_sftp_file_handler(guac_client* client, guac_stream* stream,
char* mimetype, char* filename) { char* mimetype, char* filename) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
char fullpath[GUAC_SFTP_MAX_PATH]; guac_object* filesystem = client_data->sftp_filesystem;
LIBSSH2_SFTP_HANDLE* file;
/* Concatenate filename with path */ /* Handle file upload */
if (!guac_ssh_append_filename(fullpath, return guac_common_ssh_sftp_handle_file_stream(filesystem, stream,
client_data->sftp_upload_path, filename)) { mimetype, filename);
guac_client_log(client, GUAC_LOG_DEBUG,
"Filename \"%s\" is invalid or resulting path is too long",
filename);
/* Abort transfer - invalid filename */
guac_protocol_send_ack(client->socket, stream,
"SFTP: Illegal filename",
GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
guac_socket_flush(client->socket);
return 0;
}
/* Open file via SFTP */
file = libssh2_sftp_open(client_data->sftp_session, fullpath,
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
S_IRUSR | S_IWUSR);
/* Inform of status */
if (file != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"File \"%s\" opened",
fullpath);
guac_protocol_send_ack(client->socket, stream, "SFTP: File opened", GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
}
else {
guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s",
fullpath, libssh2_sftp_last_error(client_data->sftp_session));
guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
guac_socket_flush(client->socket);
}
/* Set handlers for file stream */
stream->blob_handler = guac_sftp_blob_handler;
stream->end_handler = guac_sftp_end_handler;
/* Store file within stream */
stream->data = file;
return 0;
}
int guac_sftp_blob_handler(guac_client* client, guac_stream* stream,
void* data, int length) {
/* Pull file from stream */
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
/* Attempt write */
if (libssh2_sftp_write(file, data, length) == length) {
guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes written", length);
guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
}
/* Inform of any errors */
else {
guac_client_log(client, GUAC_LOG_INFO, "Unable to write to file: %s",
libssh2_sftp_last_error(client_data->sftp_session));
guac_protocol_send_ack(client->socket, stream, "SFTP: Write failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
}
return 0;
}
int guac_sftp_end_handler(guac_client* client, guac_stream* stream) {
/* Pull file from stream */
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
/* Attempt to close file */
if (libssh2_sftp_close(file) == 0) {
guac_client_log(client, GUAC_LOG_DEBUG, "File closed");
guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
}
else {
guac_client_log(client, GUAC_LOG_INFO, "Unable to close file");
guac_protocol_send_ack(client->socket, stream, "SFTP: Close failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
}
return 0;
}
int guac_sftp_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
/* If successful, read data */
if (status == GUAC_PROTOCOL_STATUS_SUCCESS) {
/* Attempt read into buffer */
char buffer[4096];
int bytes_read = libssh2_sftp_read(file, buffer, sizeof(buffer));
/* If bytes read, send as blob */
if (bytes_read > 0) {
guac_protocol_send_blob(client->socket, stream,
buffer, bytes_read);
guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes sent to client",
bytes_read);
}
/* If EOF, send end */
else if (bytes_read == 0) {
guac_client_log(client, GUAC_LOG_DEBUG, "File sent");
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
}
/* Otherwise, fail stream */
else {
guac_client_log(client, GUAC_LOG_INFO, "Error reading file: %s",
libssh2_sftp_last_error(client_data->sftp_session));
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
}
guac_socket_flush(client->socket);
}
/* Otherwise, return stream to client */
else
guac_client_free_stream(client, stream);
return 0;
} }
guac_stream* guac_sftp_download_file(guac_client* client, guac_stream* guac_sftp_download_file(guac_client* client,
char* filename) { char* filename) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
guac_stream* stream; guac_object* filesystem = client_data->sftp_filesystem;
LIBSSH2_SFTP_HANDLE* file;
/* Attempt to open file for reading */ /* Initiate download of requested file */
file = libssh2_sftp_open(client_data->sftp_session, filename, return guac_common_ssh_sftp_download_file(filesystem, filename);
LIBSSH2_FXF_READ, 0);
if (file == NULL) {
guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\": %s",
filename,
libssh2_sftp_last_error(client_data->sftp_session));
return NULL;
}
/* Allocate stream */
stream = guac_client_alloc_stream(client);
stream->ack_handler = guac_sftp_ack_handler;
stream->data = file;
/* Send stream start, strip name */
filename = basename(filename);
guac_protocol_send_file(client->socket, stream,
"application/octet-stream", filename);
guac_socket_flush(client->socket);
guac_client_log(client, GUAC_LOG_DEBUG, "Sending file \"%s\"", filename);
return stream;
} }
void guac_sftp_set_upload_path(guac_client* client, char* path) { void guac_sftp_set_upload_path(guac_client* client, char* path) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
int length = strnlen(path, GUAC_SFTP_MAX_PATH); guac_object* filesystem = client_data->sftp_filesystem;
/* Ignore requests which exceed maximum-allowed path */ /* Set upload path as specified */
if (length > GUAC_SFTP_MAX_PATH) { guac_common_ssh_sftp_set_upload_path(filesystem, path);
guac_client_log(client, GUAC_LOG_ERROR,
"Submitted path exceeds limit of %i bytes",
GUAC_SFTP_MAX_PATH);
return;
}
/* Copy path */
memcpy(client_data->sftp_upload_path, path, length);
guac_client_log(client, GUAC_LOG_DEBUG, "Upload path set to \"%s\"", path);
} }
guac_object* guac_sftp_expose_filesystem(guac_client* client) {
/* Init filesystem */
guac_object* filesystem = guac_client_alloc_object(client);
filesystem->get_handler = guac_sftp_get_handler;
filesystem->put_handler = guac_sftp_put_handler;
/* Send filesystem to client */
guac_protocol_send_filesystem(client->socket, filesystem, "/");
guac_socket_flush(client->socket);
/* Return allocated filesystem */
return filesystem;
}
int guac_sftp_ls_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status) {
int bytes_read;
bool blob_written = false;
char filename[GUAC_SFTP_MAX_PATH];
LIBSSH2_SFTP_ATTRIBUTES attributes;
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
LIBSSH2_SFTP* sftp = client_data->sftp_session;
guac_sftp_ls_state* list_state = (guac_sftp_ls_state*) stream->data;
/* If unsuccessful, free stream and abort */
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
libssh2_sftp_closedir(list_state->directory);
guac_client_free_stream(client, stream);
free(list_state);
return 0;
}
/* While directory entries remain */
while ((bytes_read = libssh2_sftp_readdir(list_state->directory,
filename, sizeof(filename), &attributes)) > 0
&& !blob_written) {
char absolute_path[GUAC_SFTP_MAX_PATH];
/* Skip current and parent directory entries */
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
continue;
/* Concatenate into absolute path - skip if invalid */
if (!guac_ssh_append_filename(absolute_path,
list_state->directory_name, filename)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Skipping filename \"%s\" - filename is invalid or "
"resulting path is too long", filename);
continue;
}
/* Stat explicitly if symbolic link (might point to directory) */
if (LIBSSH2_SFTP_S_ISLNK(attributes.permissions))
libssh2_sftp_stat(sftp, absolute_path, &attributes);
/* Determine mimetype */
const char* mimetype;
if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions))
mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE;
else
mimetype = "application/octet-stream";
/* Write entry */
blob_written |= guac_common_json_write_property(client, stream,
&list_state->json_state, absolute_path, mimetype);
}
/* Complete JSON and cleanup at end of directory */
if (bytes_read <= 0) {
/* Complete JSON object */
guac_common_json_end_object(client, stream, &list_state->json_state);
guac_common_json_flush(client, stream, &list_state->json_state);
/* Clean up resources */
libssh2_sftp_closedir(list_state->directory);
free(list_state);
/* Signal of stream */
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
}
guac_socket_flush(client->socket);
return 0;
}
int guac_sftp_get_handler(guac_client* client, guac_object* object,
char* name) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
LIBSSH2_SFTP* sftp = client_data->sftp_session;
LIBSSH2_SFTP_ATTRIBUTES attributes;
/* Attempt to read file information */
if (libssh2_sftp_stat(sftp, name, &attributes)) {
guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"",
name);
return 0;
}
/* If directory, send contents of directory */
if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) {
/* Open as directory */
LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, name);
if (dir == NULL) {
guac_client_log(client, GUAC_LOG_INFO,
"Unable to read directory \"%s\": %s",
name, libssh2_sftp_last_error(sftp));
return 0;
}
/* Init directory listing state */
guac_sftp_ls_state* list_state = malloc(sizeof(guac_sftp_ls_state));
list_state->directory = dir;
strncpy(list_state->directory_name, name,
sizeof(list_state->directory_name));
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
stream->ack_handler = guac_sftp_ls_ack_handler;
stream->data = list_state;
/* Init JSON object state */
guac_common_json_begin_object(client, stream, &list_state->json_state);
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name);
}
/* Otherwise, send file contents */
else {
/* Open as normal file */
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
LIBSSH2_FXF_READ, 0);
if (file == NULL) {
guac_client_log(client, GUAC_LOG_INFO,
"Unable to read file \"%s\": %s",
name, libssh2_sftp_last_error(sftp));
return 0;
}
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
stream->ack_handler = guac_sftp_ack_handler;
stream->data = file;
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
"application/octet-stream", name);
}
guac_socket_flush(client->socket);
return 0;
}
int guac_sftp_put_handler(guac_client* client, guac_object* object,
guac_stream* stream, char* mimetype, char* name) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
LIBSSH2_SFTP* sftp = client_data->sftp_session;
/* Open file via SFTP */
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
S_IRUSR | S_IWUSR);
/* Acknowledge stream if successful */
if (file != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG, "File \"%s\" opened", name);
guac_protocol_send_ack(client->socket, stream, "SFTP: File opened",
GUAC_PROTOCOL_STATUS_SUCCESS);
}
/* Abort on failure */
else {
guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s",
name, libssh2_sftp_last_error(sftp));
guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed",
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
}
/* Set handlers for file stream */
stream->blob_handler = guac_sftp_blob_handler;
stream->end_handler = guac_sftp_end_handler;
/* Store file within stream */
stream->data = file;
guac_socket_flush(client->socket);
return 0;
}

View File

@ -26,161 +26,65 @@
#include "config.h" #include "config.h"
#include "guac_json.h"
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/protocol.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
/** /**
* Maximum number of bytes per path. * Handles an incoming stream from a Guacamole "file" instruction, saving the
*/ * contents of that stream to the file having the given name within the
#define GUAC_SFTP_MAX_PATH 2048 * upload directory set by guac_sftp_set_upload_path().
*
/** * @param client
* The current state of a directory listing operation. * The client receiving the uploaded file.
*/ *
typedef struct guac_sftp_ls_state { * @param stream
* The stream through which the uploaded file data will be received.
/** *
* Reference to the directory currently being listed over SFTP. This * @param mimetype
* directory must already be open from a call to libssh2_sftp_opendir(). * The mimetype of the data being received.
*/ *
LIBSSH2_SFTP_HANDLE* directory; * @param filename
* The filename of the file to write to. This filename will always be taken
/** * relative to the upload path set by
* The absolute path of the directory being listed. * guac_common_ssh_sftp_set_upload_path().
*/ *
char directory_name[GUAC_SFTP_MAX_PATH]; * @return
* Zero if the incoming stream has been handled successfully, non-zero on
/** * failure.
* The current state of the JSON directory object being written.
*/
guac_common_json_state json_state;
} guac_sftp_ls_state;
/**
* Handler for file messages which begins an SFTP data transfer (upload).
*/ */
int guac_sftp_file_handler(guac_client* client, guac_stream* stream, int guac_sftp_file_handler(guac_client* client, guac_stream* stream,
char* mimetype, char* filename); char* mimetype, char* filename);
/** /**
* Handler for blob messages which continues an SFTP data transfer (upload). * Initiates an SFTP file download to the user via the Guacamole "file"
*/ * instruction. The download will be automatically monitored and continued
int guac_sftp_blob_handler(guac_client* client, guac_stream* stream, * after this function terminates in response to "ack" instructions received by
void* data, int length); * the client.
*
/** * @param client
* Handler for end messages which ends an SFTP data transfer (upload). * The client receiving the file.
*/ *
int guac_sftp_end_handler(guac_client* client, guac_stream* stream); * @param filename
* The filename of the file to download, relative to the given filesystem.
/** *
* Handler for ack messages which continues an SFTP download. * @return
*/ * The file stream created for the file download, already configured to
int guac_sftp_ack_handler(guac_client* client, guac_stream* stream, * properly handle "ack" responses, etc. from the client.
char* message, guac_protocol_status status);
/**
* Begins (and automatically continues) an SFTP file download to the user.
*/ */
guac_stream* guac_sftp_download_file(guac_client* client, char* filename); guac_stream* guac_sftp_download_file(guac_client* client, char* filename);
/** /**
* Set the destination directory for future uploads. * Sets the destination directory for future uploads submitted via Guacamole
* "file" instruction. This function has no bearing on the destination
* directories of files uploaded with "put" instructions.
*
* @param client
* The client setting the upload path.
*
* @param path
* The path to use for future uploads submitted via "file" instruction.
*/ */
void guac_sftp_set_upload_path(guac_client* client, char* path); void guac_sftp_set_upload_path(guac_client* client, char* path);
/**
* Exposes access to SFTP via a filesystem object, returning that object. The
* object returned must eventually be explicitly freed through a call to
* guac_client_free_object().
*
* @param client
* The Guacamole client to expose the filesystem to.
*
* @return
* The resulting Guacamole filesystem object, initialized and exposed to
* the client.
*/
guac_object* guac_sftp_expose_filesystem(guac_client* client);
/**
* Handler for get messages. In context of SFTP and the filesystem exposed via
* the Guacamole protocol, get messages request the body of a file within the
* filesystem.
*
* @param client
* The client receiving the get message.
*
* @param object
* The Guacamole protocol object associated with the get request itself.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_sftp_get_handler(guac_client* client, guac_object* object,
char* name);
/**
* Handler for put messages. In context of SFTP and the filesystem exposed via
* the Guacamole protocol, put messages request write access to a file within
* the filesystem.
*
* @param client
* The client receiving the put message.
*
* @param object
* The Guacamole protocol object associated with the put request itself.
*
* @param stream
* The Guacamole protocol stream along which the client will be sending
* file data.
*
* @param mimetype
* The mimetype of the data being send along the stream.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_sftp_put_handler(guac_client* client, guac_object* object,
guac_stream* stream, char* mimetype, char* name);
/**
* Handler for ack messages received due to receipt of a "body" or "blob"
* instruction associated with a SFTP directory list operation.
*
* @param client
* The client receiving the ack message.
*
* @param stream
* The Guacamole protocol stream associated with the received ack message.
*
* @param message
* An arbitrary human-readable message describing the nature of the
* success or failure denoted by this ack message.
*
* @param status
* The status code associated with this ack message, which may indicate
* success or an error.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_sftp_ls_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status);
#endif #endif

View File

@ -23,8 +23,9 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "guac_sftp.h"
#include "guac_ssh.h"
#include "sftp.h" #include "sftp.h"
#include "ssh_key.h"
#include "terminal.h" #include "terminal.h"
#ifdef ENABLE_SSH_AGENT #ifdef ENABLE_SSH_AGENT
@ -56,6 +57,96 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/time.h> #include <sys/time.h>
/**
* Produces a new user object containing a username and password or private
* key, prompting the user as necessary to obtain that information.
*
* @param client
* The Guacamole client containing any existing user data, as well as the
* terminal to use when prompting the user.
*
* @return
* A new user object containing the user's username and other credentials.
*/
static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
guac_common_ssh_user* user;
/* Get username */
if (client_data->username[0] == 0)
guac_terminal_prompt(client_data->term, "Login as: ",
client_data->username, sizeof(client_data->username), true);
/* Create user object from username */
user = guac_common_ssh_create_user(client_data->username);
/* If key specified, import */
if (client_data->key_base64[0] != 0) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Attempting private key import (WITHOUT passphrase)");
/* Attempt to read key without passphrase */
if (guac_common_ssh_user_import_key(user,
client_data->key_base64, NULL)) {
/* Log failure of initial attempt */
guac_client_log(client, GUAC_LOG_DEBUG,
"Initial import failed: %s",
guac_common_ssh_key_error());
guac_client_log(client, GUAC_LOG_DEBUG,
"Re-attempting private key import (WITH passphrase)");
/* Prompt for passphrase if missing */
if (client_data->key_passphrase[0] == 0)
guac_terminal_prompt(client_data->term, "Key passphrase: ",
client_data->key_passphrase,
sizeof(client_data->key_passphrase), false);
/* Reattempt import with passphrase */
if (guac_common_ssh_user_import_key(user,
client_data->key_base64,
client_data->key_passphrase)) {
/* If still failing, give up */
guac_client_abort(client,
GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"Auth key import failed: %s",
guac_common_ssh_key_error());
guac_common_ssh_destroy_user(user);
return NULL;
}
} /* end decrypt key with passphrase */
/* Success */
guac_client_log(client, GUAC_LOG_INFO,
"Auth key successfully imported.");
} /* end if key given */
/* Otherwise, get password if not provided */
else if (client_data->password[0] == 0) {
guac_terminal_prompt(client_data->term, "Password: ",
client_data->password, sizeof(client_data->password), false);
guac_common_ssh_user_set_password(user, client_data->password);
}
/* Clear screen of any prompts */
guac_terminal_printf(client_data->term, "\x1B[H\x1B[J");
return user;
}
void* ssh_input_thread(void* data) { void* ssh_input_thread(void* data) {
guac_client* client = (guac_client*) data; guac_client* client = (guac_client*) data;
@ -75,363 +166,45 @@ void* ssh_input_thread(void* data) {
} }
static int __sign_callback(LIBSSH2_SESSION* session,
unsigned char** sig, size_t* sig_len,
const unsigned char* data, size_t data_len, void **abstract) {
ssh_key* key = (ssh_key*) abstract;
int length;
/* Allocate space for signature */
*sig = malloc(4096);
/* Sign with key */
length = ssh_key_sign(key, (const char*) data, data_len, *sig);
if (length < 0)
return 1;
*sig_len = length;
return 0;
}
/**
* Callback for the keyboard-interactive authentication method. Currently
* suports just one prompt for the password.
*/
static void __kbd_callback(const char *name, int name_len,
const char *instruction, int instruction_len,
int num_prompts,
const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
void **abstract) {
guac_client* client = (guac_client*) *abstract;
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
if (num_prompts == 1) {
responses[0].text = strdup(client_data->password);
responses[0].length = strlen(client_data->password);
}
else
guac_client_log(client, GUAC_LOG_WARNING,
"Unsupported number of keyboard-interactive prompts: %i",
num_prompts);
}
static LIBSSH2_SESSION* __guac_ssh_create_session(guac_client* client,
int* socket_fd) {
int retval;
int fd;
struct addrinfo* addresses;
struct addrinfo* current_address;
char connected_address[1024];
char connected_port[64];
char *user_authlist;
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
.ai_protocol = IPPROTO_TCP
};
/* Get socket */
fd = socket(AF_INET, SOCK_STREAM, 0);
/* Get addresses connection */
if ((retval = getaddrinfo(client_data->hostname, client_data->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_ERROR, "Unable to connect to any addresses.");
return NULL;
}
/* Free addrinfo */
freeaddrinfo(addresses);
/* Open SSH session */
LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL,
NULL, client);
if (session == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Session allocation failed.");
return NULL;
}
/* Perform handshake */
if (libssh2_session_handshake(session, fd)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "SSH handshake failed.");
return NULL;
}
/* Save file descriptor */
if (socket_fd != NULL)
*socket_fd = fd;
/* Get list of suported authentication methods */
user_authlist = libssh2_userauth_list(session, client_data->username, strlen(client_data->username));
guac_client_log(client, GUAC_LOG_DEBUG, "Supported authentication methods: %s", user_authlist);
/* Authenticate with key if available */
if (client_data->key != NULL) {
/* Check if public key auth is suported on the server */
if (strstr(user_authlist, "publickey") == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"Public key authentication not suported");
return NULL;
}
if (!libssh2_userauth_publickey(session, client_data->username,
(unsigned char*) client_data->key->public_key,
client_data->key->public_key_length,
__sign_callback, (void**) client_data->key))
return session;
else {
char* error_message;
libssh2_session_last_error(session, &error_message, NULL, 0);
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"Public key authentication failed: %s", error_message);
return NULL;
}
}
/* Authenticate with password */
if (strstr(user_authlist, "password") != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG, "Using password authentication method");
retval = libssh2_userauth_password(session, client_data->username, client_data->password);
}
else if (strstr(user_authlist, "keyboard-interactive") != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG, "Using keyboard-interactive authentication method");
retval = libssh2_userauth_keyboard_interactive(session, client_data->username, &__kbd_callback);
}
else {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE, "No known authentication methods");
return NULL;
}
if (retval == 0)
return session;
else {
char* error_message;
libssh2_session_last_error(session, &error_message, NULL, 0);
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"Password authentication failed: %s", error_message);
return NULL;
}
}
#ifdef LIBSSH2_USES_GCRYPT
GCRY_THREAD_OPTION_PTHREAD_IMPL;
#endif
/**
* Array of mutexes, used by OpenSSL.
*/
static pthread_mutex_t* __openssl_locks;
/**
* Called by OpenSSL when locking or unlocking the Nth mutex.
*/
static void __openssl_locking_callback(int mode, int n, const char* file, int line){
if (mode & CRYPTO_LOCK)
pthread_mutex_lock(&(__openssl_locks[n]));
else if (mode & CRYPTO_UNLOCK)
pthread_mutex_unlock(&(__openssl_locks[n]));
}
/**
* Called by OpenSSL when determining the current thread ID.
*/
static unsigned long __openssl_id_callback() {
return (unsigned long) pthread_self();
}
/**
* Creates the given number of mutexes, such that OpenSSL will have at least
* this number of mutexes at its disposal.
*/
static void __openssl_init_locks(int count) {
int i;
__openssl_locks = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks());
for (i=0; i<count; i++)
pthread_mutex_init(&(__openssl_locks[i]), NULL);
}
/**
* Frees the given number of mutexes.
*/
static void __openssl_free_locks(int count) {
int i;
for (i=0; i<count; i++)
pthread_mutex_destroy(&(__openssl_locks[i]));
}
void* ssh_client_thread(void* data) { void* ssh_client_thread(void* data) {
guac_client* client = (guac_client*) data; guac_client* client = (guac_client*) data;
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
char name[1024];
guac_socket* socket = client->socket; guac_socket* socket = client->socket;
char buffer[8192]; char buffer[8192];
int bytes_read = -1234;
int socket_fd;
pthread_t input_thread; pthread_t input_thread;
#ifdef LIBSSH2_USES_GCRYPT /* Init SSH base libraries */
/* Init threadsafety in libgcrypt */ if (guac_common_ssh_init(client))
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
if (!gcry_check_version(GCRYPT_VERSION)) {
guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
return NULL; return NULL;
}
#endif
/* Init threadsafety in OpenSSL */ /* Get user and credentials */
__openssl_init_locks(CRYPTO_num_locks()); guac_common_ssh_user* user = guac_ssh_get_user(client);
CRYPTO_set_id_callback(__openssl_id_callback);
CRYPTO_set_locking_callback(__openssl_locking_callback);
/* Init OpenSSL */
SSL_library_init();
ERR_load_crypto_strings();
libssh2_init(0);
/* Get username */
if (client_data->username[0] == 0)
guac_terminal_prompt(client_data->term, "Login as: ",
client_data->username, sizeof(client_data->username), true);
/* Send new name */ /* Send new name */
snprintf(name, sizeof(name)-1, "%s@%s", client_data->username, client_data->hostname); char name[1024];
snprintf(name, sizeof(name)-1, "%s@%s",
client_data->username, client_data->hostname);
guac_protocol_send_name(socket, name); guac_protocol_send_name(socket, name);
/* If key specified, import */
if (client_data->key_base64[0] != 0) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Attempting private key import (WITHOUT passphrase)");
/* Attempt to read key without passphrase */
client_data->key = ssh_key_alloc(client_data->key_base64,
strlen(client_data->key_base64), "");
/* On failure, attempt with passphrase */
if (client_data->key == NULL) {
/* Log failure of initial attempt */
guac_client_log(client, GUAC_LOG_DEBUG,
"Initial import failed: %s", ssh_key_error());
guac_client_log(client, GUAC_LOG_DEBUG,
"Re-attempting private key import (WITH passphrase)");
/* Prompt for passphrase if missing */
if (client_data->key_passphrase[0] == 0)
guac_terminal_prompt(client_data->term, "Key passphrase: ",
client_data->key_passphrase, sizeof(client_data->key_passphrase), false);
/* Import key with passphrase */
client_data->key = ssh_key_alloc(client_data->key_base64,
strlen(client_data->key_base64),
client_data->key_passphrase);
/* If still failing, give up */
if (client_data->key == NULL) {
guac_client_abort(client,
GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"Auth key import failed: %s", ssh_key_error());
return NULL;
}
} /* end decrypt key with passphrase */
/* Success */
guac_client_log(client, GUAC_LOG_INFO, "Auth key successfully imported.");
} /* end if key given */
/* Otherwise, get password if not provided */
else if (client_data->password[0] == 0)
guac_terminal_prompt(client_data->term, "Password: ",
client_data->password, sizeof(client_data->password), false);
/* Clear screen */
guac_terminal_printf(client_data->term, "\x1B[H\x1B[J");
/* Open SSH session */ /* Open SSH session */
client_data->session = __guac_ssh_create_session(client, &socket_fd); client_data->session = guac_common_ssh_create_session(client,
client_data->hostname, client_data->port, user);
if (client_data->session == NULL) { if (client_data->session == NULL) {
/* Already aborted within __guac_ssh_create_session() */ /* Already aborted within guac_common_ssh_create_session() */
return NULL; return NULL;
} }
pthread_mutex_init(&client_data->term_channel_lock, NULL); pthread_mutex_init(&client_data->term_channel_lock, NULL);
/* Open channel for terminal */ /* Open channel for terminal */
client_data->term_channel = libssh2_channel_open_session(client_data->session); client_data->term_channel =
libssh2_channel_open_session(client_data->session->session);
if (client_data->term_channel == NULL) { if (client_data->term_channel == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to open terminal channel."); guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Unable to open terminal channel.");
return NULL; return NULL;
} }
@ -454,30 +227,26 @@ void* ssh_client_thread(void* data) {
/* Start SFTP session as well, if enabled */ /* Start SFTP session as well, if enabled */
if (client_data->enable_sftp) { if (client_data->enable_sftp) {
/* Init handlers for Guacamole-specific console codes */
client_data->term->upload_path_handler = guac_sftp_set_upload_path;
client_data->term->file_download_handler = guac_sftp_download_file;
/* Create SSH session specific for SFTP */ /* Create SSH session specific for SFTP */
guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP...");
client_data->sftp_ssh_session = __guac_ssh_create_session(client, NULL); guac_common_ssh_session* sftp_ssh_session =
if (client_data->sftp_ssh_session == NULL) { guac_common_ssh_create_session(client, client_data->hostname,
/* Already aborted within __guac_ssh_create_session() */ client_data->port, user);
if (sftp_ssh_session == NULL) {
/* Already aborted within guac_common_ssh_create_session() */
return NULL; return NULL;
} }
/* Request SFTP */ /* Request SFTP */
client_data->sftp_session = libssh2_sftp_init(client_data->sftp_ssh_session); client_data->sftp_filesystem =
if (client_data->sftp_session == NULL) { guac_common_ssh_create_sftp_filesystem(sftp_ssh_session, "/");
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to start SFTP session.");
return NULL;
}
/* Set file handler */ /* Set generic (non-filesystem) file upload handler */
client->file_handler = guac_sftp_file_handler; client->file_handler = guac_sftp_file_handler;
/* Expose filesystem */ /* Init handlers for Guacamole-specific console codes */
client_data->sftp_filesystem = guac_sftp_expose_filesystem(client); client_data->term->upload_path_handler = guac_sftp_set_upload_path;
client_data->term->file_download_handler = guac_sftp_download_file;
guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized"); guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized");
@ -506,10 +275,10 @@ void* ssh_client_thread(void* data) {
} }
/* Set non-blocking */ /* Set non-blocking */
libssh2_session_set_blocking(client_data->session, 0); libssh2_session_set_blocking(client_data->session->session, 0);
/* While data available, write to terminal */ /* While data available, write to terminal */
bytes_read = 0; int bytes_read = 0;
for (;;) { for (;;) {
/* Track total amount of data read */ /* Track total amount of data read */
@ -558,13 +327,14 @@ void* ssh_client_thread(void* data) {
struct timeval timeout; struct timeval timeout;
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(socket_fd, &fds); FD_SET(client_data->session->fd, &fds);
/* Wait for one second */ /* Wait for one second */
timeout.tv_sec = 1; timeout.tv_sec = 1;
timeout.tv_usec = 0; timeout.tv_usec = 0;
if (select(socket_fd+1, &fds, NULL, NULL, &timeout) < 0) if (select(client_data->session->fd + 1, &fds,
NULL, NULL, &timeout) < 0)
break; break;
} }
@ -574,7 +344,6 @@ void* ssh_client_thread(void* data) {
guac_client_stop(client); guac_client_stop(client);
pthread_join(input_thread, NULL); pthread_join(input_thread, NULL);
__openssl_free_locks(CRYPTO_num_locks());
pthread_mutex_destroy(&client_data->term_channel_lock); pthread_mutex_destroy(&client_data->term_channel_lock);
guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended."); guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended.");

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2014 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -21,7 +21,6 @@
# #
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libguac-client-telnet.la lib_LTLIBRARIES = libguac-client-telnet.la
@ -38,7 +37,18 @@ noinst_HEADERS = \
guac_handlers.h \ guac_handlers.h \
telnet_client.h telnet_client.h
libguac_client_telnet_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@ libguac_client_telnet_la_CFLAGS = \
libguac_client_telnet_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@ -Werror -Wall -Iinclude \
libguac_client_telnet_la_LDFLAGS = -version-info 0:0:0 @TELNET_LIBS@ @PTHREAD_LIBS@ @LIBGUAC_INCLUDE@ \
@TERMINAL_INCLUDE@
libguac_client_telnet_la_LIBADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@ \
@TERMINAL_LTLIB@
libguac_client_telnet_la_LDFLAGS = \
-version-info 0:0:0 \
@PTHREAD_LIBS@ \
@TELNET_LIBS@

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2013 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -21,11 +21,7 @@
# #
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS = -Werror -Wall -pedantic -Iinclude \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@
lib_LTLIBRARIES = libguac-client-vnc.la lib_LTLIBRARIES = libguac-client-vnc.la
@ -47,10 +43,18 @@ libguac_client_vnc_la_SOURCES += pulse.c
noinst_HEADERS += pulse.h noinst_HEADERS += pulse.h
endif endif
libguac_client_vnc_la_LDFLAGS = -version-info 0:0:0 \ libguac_client_vnc_la_CFLAGS = \
-Werror -Wall -pedantic -Iinclude \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@
libguac_client_vnc_la_LDFLAGS = \
-version-info 0:0:0 \
@CAIRO_LIBS@ \ @CAIRO_LIBS@ \
@PULSE_LIBS@ \ @PULSE_LIBS@ \
@VNC_LIBS@ @VNC_LIBS@
libguac_client_vnc_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ libguac_client_vnc_la_LIBADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2013 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,6 @@
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS = -Werror -Wall -pedantic @PANGO_CFLAGS@ @PANGOCAIRO_CFLAGS@ @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
noinst_LTLIBRARIES = libguac_terminal.la noinst_LTLIBRARIES = libguac_terminal.la
@ -55,6 +54,20 @@ libguac_terminal_la_SOURCES = \
terminal.c \ terminal.c \
terminal_handlers.c terminal_handlers.c
libguac_terminal_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ libguac_terminal_la_CFLAGS = \
libguac_terminal_la_LDFLAGS = @PTHREAD_LIBS@ @PANGO_LIBS@ @PANGOCAIRO_LIBS@ @CAIRO_LIBS@ @MATH_LIBS@ -Werror -Wall -pedantic \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@ \
@PANGO_CFLAGS@ \
@PANGOCAIRO_CFLAGS@
libguac_terminal_la_LIBADD = \
@LIBGUAC_LTLIB@
libguac_terminal_la_LDFLAGS = \
@CAIRO_LIBS@ \
@MATH_LIBS@ \
@PANGO_LIBS@ \
@PANGOCAIRO_LIBS@ \
@PTHREAD_LIBS@

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2013 Glyptodon LLC # Copyright (C) 2015 Glyptodon LLC
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,6 @@
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
TESTS = test_libguac TESTS = test_libguac
check_PROGRAMS = test_libguac check_PROGRAMS = test_libguac
@ -51,5 +50,13 @@ test_libguac_SOURCES = \
util/guac_pool.c \ util/guac_pool.c \
util/guac_unicode.c util/guac_unicode.c
test_libguac_LDADD = @LIBGUAC_LTLIB@ @CUNIT_LIBS@ @COMMON_LTLIB@ test_libguac_CFLAGS = \
-Werror -Wall -pedantic \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@
test_libguac_LDADD = \
@COMMON_LTLIB@ \
@CUNIT_LIBS@ \
@LIBGUAC_LTLIB@