Merge pull request #53 from glyptodon/common-ssh
GUAC-1171: Migrate to common SSH client base
This commit is contained in:
commit
7c0858b3cb
12
Makefile.am
12
Makefile.am
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -26,6 +26,7 @@ ACLOCAL_AMFLAGS = -I m4
|
||||
DIST_SUBDIRS = \
|
||||
src/libguac \
|
||||
src/common \
|
||||
src/common-ssh \
|
||||
src/terminal \
|
||||
src/guacd \
|
||||
src/protocols/rdp \
|
||||
@ -40,6 +41,10 @@ SUBDIRS = \
|
||||
src/guacd \
|
||||
tests
|
||||
|
||||
if ENABLE_COMMON_SSH
|
||||
SUBDIRS += src/common-ssh
|
||||
endif
|
||||
|
||||
if ENABLE_TERMINAL
|
||||
SUBDIRS += src/terminal
|
||||
endif
|
||||
@ -60,5 +65,8 @@ if ENABLE_VNC
|
||||
SUBDIRS += src/protocols/vnc
|
||||
endif
|
||||
|
||||
EXTRA_DIST = LICENSE doc/Doxyfile bin/guacctl
|
||||
EXTRA_DIST = \
|
||||
LICENSE \
|
||||
bin/guacctl \
|
||||
doc/Doxyfile
|
||||
|
||||
|
@ -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_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
|
||||
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)')
|
||||
@ -834,6 +838,9 @@ then
|
||||
[have_libssh2=no])
|
||||
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" \
|
||||
-a "x${have_terminal}" = "xyes" \
|
||||
-a "x${have_ssl}" = "xyes"])
|
||||
@ -920,6 +927,7 @@ AC_SUBST(TELNET_LIBS)
|
||||
AC_CONFIG_FILES([Makefile
|
||||
tests/Makefile
|
||||
src/common/Makefile
|
||||
src/common-ssh/Makefile
|
||||
src/terminal/Makefile
|
||||
src/libguac/Makefile
|
||||
src/guacd/Makefile
|
||||
|
54
src/common-ssh/Makefile.am
Normal file
54
src/common-ssh/Makefile.am
Normal 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
716
src/common-ssh/guac_sftp.c
Normal 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
180
src/common-ssh/guac_sftp.h
Normal 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
475
src/common-ssh/guac_ssh.c
Normal 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
113
src/common-ssh/guac_ssh.h
Normal 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
|
||||
|
@ -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
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -29,7 +29,7 @@
|
||||
#include <string.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;
|
||||
*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;
|
||||
|
||||
@ -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);
|
||||
*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;
|
||||
int length;
|
||||
|
||||
/* If zero, just write zero length */
|
||||
if (BN_is_zero(value)) {
|
||||
buffer_write_uint32(buffer, 0);
|
||||
guac_common_ssh_buffer_write_uint32(buffer, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -76,11 +77,11 @@ void buffer_write_bignum(char** buffer, BIGNUM* value) {
|
||||
|
||||
/* If first byte has high bit set, write padding byte */
|
||||
if (bn_buffer[0] & 0x80) {
|
||||
buffer_write_uint32(buffer, length+1);
|
||||
buffer_write_byte(buffer, 0);
|
||||
guac_common_ssh_buffer_write_uint32(buffer, length+1);
|
||||
guac_common_ssh_buffer_write_byte(buffer, 0);
|
||||
}
|
||||
else
|
||||
buffer_write_uint32(buffer, length);
|
||||
guac_common_ssh_buffer_write_uint32(buffer, length);
|
||||
|
||||
/* Write data */
|
||||
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) {
|
||||
buffer_write_uint32(buffer, length);
|
||||
buffer_write_data(buffer, string, length);
|
||||
void guac_common_ssh_buffer_write_string(char** buffer, const char* string,
|
||||
int 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 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;
|
||||
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;
|
||||
|
||||
*length = buffer_read_uint32(buffer);
|
||||
*length = guac_common_ssh_buffer_read_uint32(buffer);
|
||||
value = *buffer;
|
||||
|
||||
*buffer += *length;
|
@ -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
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -20,9 +20,8 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _GUAC_SSH_BUFFER_H
|
||||
#define _GUAC_SSH_BUFFER_H
|
||||
#ifndef GUAC_COMMON_SSH_BUFFER_H
|
||||
#define GUAC_COMMON_SSH_BUFFER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@ -32,51 +31,107 @@
|
||||
/**
|
||||
* Writes the given byte to the given buffer, advancing the buffer pointer by
|
||||
* 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
|
||||
* 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
|
||||
* buffer pointer by the size of the length (four bytes) and the size of the
|
||||
* 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
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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
|
||||
* 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
|
||||
* 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
|
||||
|
@ -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
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -22,8 +22,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ssh_buffer.h"
|
||||
#include "ssh_key.h"
|
||||
#include "guac_ssh_buffer.h"
|
||||
#include "guac_ssh_key.h"
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/bn.h>
|
||||
@ -37,9 +37,10 @@
|
||||
#include <stdlib.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;
|
||||
|
||||
char* public_key;
|
||||
@ -61,7 +62,7 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
||||
return NULL;
|
||||
|
||||
/* Allocate key */
|
||||
key = malloc(sizeof(ssh_key));
|
||||
key = malloc(sizeof(guac_common_ssh_key));
|
||||
key->rsa = rsa_key;
|
||||
|
||||
/* Set type */
|
||||
@ -72,9 +73,9 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
||||
pos = public_key;
|
||||
|
||||
/* Derive public key */
|
||||
buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
|
||||
buffer_write_bignum(&pos, rsa_key->e);
|
||||
buffer_write_bignum(&pos, rsa_key->n);
|
||||
guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, rsa_key->e);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, rsa_key->n);
|
||||
|
||||
/* Save public key to structure */
|
||||
key->public_key = public_key;
|
||||
@ -95,7 +96,7 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
||||
return NULL;
|
||||
|
||||
/* Allocate key */
|
||||
key = malloc(sizeof(ssh_key));
|
||||
key = malloc(sizeof(guac_common_ssh_key));
|
||||
key->dsa = dsa_key;
|
||||
|
||||
/* Set type */
|
||||
@ -106,11 +107,11 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
||||
pos = public_key;
|
||||
|
||||
/* Derive public key */
|
||||
buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
|
||||
buffer_write_bignum(&pos, dsa_key->p);
|
||||
buffer_write_bignum(&pos, dsa_key->q);
|
||||
buffer_write_bignum(&pos, dsa_key->g);
|
||||
buffer_write_bignum(&pos, dsa_key->pub_key);
|
||||
guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->p);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->q);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->g);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->pub_key);
|
||||
|
||||
/* Save public key to structure */
|
||||
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 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 */
|
||||
if (key->type == SSH_KEY_RSA)
|
||||
@ -153,7 +154,8 @@ void ssh_key_free(ssh_key* 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;
|
||||
EVP_MD_CTX md_ctx;
|
@ -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
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -20,9 +20,8 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _GUAC_SSH_KEY_H
|
||||
#define _GUAC_SSH_KEY_H
|
||||
#ifndef GUAC_COMMON_SSH_KEY_H
|
||||
#define GUAC_COMMON_SSH_KEY_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@ -51,7 +50,7 @@
|
||||
/**
|
||||
* The type of an SSH key.
|
||||
*/
|
||||
typedef enum ssh_key_type {
|
||||
typedef enum guac_common_ssh_key_type {
|
||||
|
||||
/**
|
||||
* RSA key.
|
||||
@ -63,17 +62,17 @@ typedef enum ssh_key_type {
|
||||
*/
|
||||
SSH_KEY_DSA
|
||||
|
||||
} ssh_key_type;
|
||||
} guac_common_ssh_key_type;
|
||||
|
||||
/**
|
||||
* Abstraction of a key used for SSH authentication.
|
||||
*/
|
||||
typedef struct ssh_key {
|
||||
typedef struct guac_common_ssh_key {
|
||||
|
||||
/**
|
||||
* The type of this key.
|
||||
*/
|
||||
ssh_key_type type;
|
||||
guac_common_ssh_key_type type;
|
||||
|
||||
/**
|
||||
* Underlying RSA private key, if any.
|
||||
@ -105,13 +104,28 @@ typedef struct ssh_key {
|
||||
*/
|
||||
int private_key_length;
|
||||
|
||||
} ssh_key;
|
||||
} guac_common_ssh_key;
|
||||
|
||||
/**
|
||||
* Allocates a new key containing the given private key data and specified
|
||||
* 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
|
||||
@ -120,18 +134,40 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase);
|
||||
* @return
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
* 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
|
||||
|
84
src/common-ssh/guac_ssh_user.c
Normal file
84
src/common-ssh/guac_ssh_user.c
Normal 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;
|
||||
|
||||
}
|
||||
|
111
src/common-ssh/guac_ssh_user.h
Normal file
111
src/common-ssh/guac_ssh_user.h
Normal 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
|
||||
|
@ -22,7 +22,6 @@
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@
|
||||
|
||||
noinst_LTLIBRARIES = libguac_common.la
|
||||
|
||||
@ -50,5 +49,10 @@ libguac_common_la_SOURCES = \
|
||||
guac_string.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@
|
||||
|
||||
|
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -22,8 +22,6 @@
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
|
||||
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
|
||||
|
||||
sbin_PROGRAMS = guacd
|
||||
|
||||
man_MANS = \
|
||||
@ -47,10 +45,24 @@ guacd_SOURCES = \
|
||||
conf-parse.c \
|
||||
log.c
|
||||
|
||||
guacd_LDADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
||||
guacd_LDFLAGS = @PTHREAD_LIBS@ @SSL_LIBS@
|
||||
guacd_CFLAGS = \
|
||||
-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)
|
||||
|
||||
# SSL support
|
||||
|
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -22,9 +22,11 @@
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AM_CFLAGS = -Werror -Wall -pedantic -Iguacamole
|
||||
|
||||
lib_LTLIBRARIES = libguac.la
|
||||
|
||||
libguacincdir = $(includedir)/guacamole
|
||||
|
||||
libguacinc_HEADERS = \
|
||||
guacamole/audio.h \
|
||||
guacamole/audio-fntypes.h \
|
||||
@ -89,7 +91,17 @@ libguac_la_SOURCES += ogg_encoder.c
|
||||
noinst_HEADERS += ogg_encoder.h
|
||||
endif
|
||||
|
||||
lib_LTLIBRARIES = libguac.la
|
||||
libguac_la_LDFLAGS = -version-info 9:0:0 @PTHREAD_LIBS@ @CAIRO_LIBS@ @PNG_LIBS@ @VORBIS_LIBS@ @UUID_LIBS@
|
||||
libguac_la_LIBADD = @LIBADD_DLOPEN@
|
||||
libguac_la_CFLAGS = \
|
||||
-Werror -Wall -pedantic -Iguacamole
|
||||
|
||||
libguac_la_LDFLAGS = \
|
||||
-version-info 9:0:0 \
|
||||
@CAIRO_LIBS@ \
|
||||
@PNG_LIBS@ \
|
||||
@PTHREAD_LIBS@ \
|
||||
@UUID_LIBS@ \
|
||||
@VORBIS_LIBS@
|
||||
|
||||
libguac_la_LIBADD = \
|
||||
@LIBADD_DLOPEN@
|
||||
|
||||
|
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -21,9 +21,7 @@
|
||||
#
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AM_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
|
||||
|
||||
lib_LTLIBRARIES = libguac-client-rdp.la
|
||||
|
||||
@ -114,17 +112,83 @@ noinst_HEADERS += rdp_disp.h
|
||||
libguac_client_rdp_la_SOURCES += rdp_disp.c
|
||||
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@
|
||||
guacsnd_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@
|
||||
guacdr_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@
|
||||
#
|
||||
# Main RDP client library
|
||||
#
|
||||
|
||||
libguac_client_rdp_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
||||
guacsvc_libadd = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
||||
guacsnd_libadd = @LIBGUAC_LTLIB@
|
||||
guacdr_libadd = @LIBGUAC_LTLIB@
|
||||
libguac_client_rdp_la_CFLAGS = \
|
||||
-Werror -Wall -Iinclude \
|
||||
@COMMON_INCLUDE@ \
|
||||
@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
|
||||
#
|
||||
|
||||
CLEANFILES = _generated_keymaps.c
|
||||
BUILT_SOURCES = _generated_keymaps.c
|
||||
|
||||
@ -140,41 +204,55 @@ rdp_keymaps = \
|
||||
_generated_keymaps.c: $(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
|
||||
|
||||
# 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}
|
||||
guacsvc_la_LDFLAGS = ${guacsvc_ldflags}
|
||||
guacsvc_la_LIBADD = ${guacsvc_libadd}
|
||||
guacdr_la_SOURCES = ${guacdr_sources}
|
||||
guacdr_la_CFLAGS = ${guacdr_cflags}
|
||||
guacdr_la_LDFLAGS = ${guacdr_ldflags}
|
||||
guacdr_la_LIBADD = ${guacdr_libadd}
|
||||
|
||||
guacsnd_la_SOURCES = ${guacsnd_sources}
|
||||
guacsnd_la_CFLAGS = ${guacsnd_cflags}
|
||||
guacsnd_la_LDFLAGS = ${guacsnd_ldflags}
|
||||
guacsnd_la_LIBADD = ${guacsnd_libadd}
|
||||
|
||||
guacdr_la_SOURCES = ${guacdr_sources}
|
||||
guacdr_la_LDFLAGS = ${guacdr_ldflags}
|
||||
guacdr_la_LIBADD = ${guacdr_libadd}
|
||||
guacsvc_la_SOURCES = ${guacsvc_sources}
|
||||
guacsvc_la_CFLAGS = ${guacsvc_cflags}
|
||||
guacsvc_la_LDFLAGS = ${guacsvc_ldflags}
|
||||
guacsvc_la_LIBADD = ${guacsvc_libadd}
|
||||
|
||||
else
|
||||
|
||||
# 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}
|
||||
guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags}
|
||||
guacsvc_client_la_LIBADD = ${guacsvc_libadd}
|
||||
guacdr_client_la_SOURCES = ${guacdr_sources}
|
||||
guacdr_client_la_CFLAGS = ${guacdr_cflags}
|
||||
guacdr_client_la_LDFLAGS = ${guacdr_ldflags}
|
||||
guacdr_client_la_LIBADD = ${guacdr_libadd}
|
||||
|
||||
guacsnd_client_la_SOURCES = ${guacsnd_sources}
|
||||
guacsnd_client_la_CFLAGS = ${guacsnd_cflags}
|
||||
guacsnd_client_la_LDFLAGS = ${guacsnd_ldflags}
|
||||
guacsnd_client_la_LIBADD = ${guacsnd_libadd}
|
||||
|
||||
guacdr_client_la_SOURCES = ${guacdr_sources}
|
||||
guacdr_client_la_LDFLAGS = ${guacdr_ldflags}
|
||||
guacdr_client_la_LIBADD = ${guacdr_libadd}
|
||||
guacsvc_client_la_SOURCES = ${guacsvc_sources}
|
||||
guacsvc_client_la_CFLAGS = ${guacsvc_cflags}
|
||||
guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags}
|
||||
guacsvc_client_la_LIBADD = ${guacsvc_libadd}
|
||||
|
||||
endif
|
||||
|
||||
|
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -21,7 +21,6 @@
|
||||
#
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
lib_LTLIBRARIES = libguac-client-ssh.la
|
||||
@ -31,18 +30,14 @@ libguac_client_ssh_la_SOURCES = \
|
||||
clipboard.c \
|
||||
guac_handlers.c \
|
||||
sftp.c \
|
||||
ssh_buffer.c \
|
||||
ssh_client.c \
|
||||
ssh_key.c
|
||||
ssh_client.c
|
||||
|
||||
noinst_HEADERS = \
|
||||
client.h \
|
||||
clipboard.h \
|
||||
guac_handlers.h \
|
||||
sftp.h \
|
||||
ssh_buffer.h \
|
||||
ssh_client.h \
|
||||
ssh_key.h
|
||||
ssh_client.h
|
||||
|
||||
# Add agent sources if enabled
|
||||
if ENABLE_SSH_AGENT
|
||||
@ -50,7 +45,21 @@ libguac_client_ssh_la_SOURCES += ssh_agent.c
|
||||
noinst_HEADERS += ssh_agent.h
|
||||
endif
|
||||
|
||||
libguac_client_ssh_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@
|
||||
libguac_client_ssh_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@
|
||||
libguac_client_ssh_la_LDFLAGS = -version-info 0:0:0 @SSH_LIBS@ @SSL_LIBS@ @PTHREAD_LIBS@
|
||||
libguac_client_ssh_la_CFLAGS = \
|
||||
-Werror -Wall -Iinclude \
|
||||
@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@
|
||||
|
||||
|
@ -142,7 +142,6 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
|
||||
strcpy(client_data->password, argv[IDX_PASSWORD]);
|
||||
|
||||
/* Init public key auth information */
|
||||
client_data->key = NULL;
|
||||
strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]);
|
||||
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 */
|
||||
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;
|
||||
strcpy(client_data->sftp_upload_path, ".");
|
||||
|
||||
#ifdef ENABLE_SSH_AGENT
|
||||
client_data->enable_agent = strcmp(argv[IDX_ENABLE_AGENT], "true") == 0;
|
||||
|
@ -26,8 +26,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "guac_ssh.h"
|
||||
#include "sftp.h"
|
||||
#include "ssh_key.h"
|
||||
#include "terminal.h"
|
||||
|
||||
#include <libssh2.h>
|
||||
@ -77,11 +77,6 @@ typedef struct ssh_guac_client_data {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -117,29 +112,13 @@ typedef struct ssh_guac_client_data {
|
||||
/**
|
||||
* SSH session, used by the SSH client thread.
|
||||
*/
|
||||
LIBSSH2_SESSION* session;
|
||||
|
||||
/**
|
||||
* The distinct SSH session used for SFTP.
|
||||
*/
|
||||
LIBSSH2_SESSION* sftp_ssh_session;
|
||||
|
||||
/**
|
||||
* SFTP session, used for file transfers.
|
||||
*/
|
||||
LIBSSH2_SFTP* sftp_session;
|
||||
guac_common_ssh_session* session;
|
||||
|
||||
/**
|
||||
* The filesystem object exposed for the SFTP session.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -24,7 +24,8 @@
|
||||
|
||||
#include "client.h"
|
||||
#include "guac_handlers.h"
|
||||
#include "ssh_key.h"
|
||||
#include "guac_sftp.h"
|
||||
#include "guac_ssh.h"
|
||||
#include "terminal.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
@ -101,31 +102,18 @@ int ssh_guac_client_free_handler(guac_client* client) {
|
||||
/* Free channels */
|
||||
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 */
|
||||
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 */
|
||||
if (guac_client_data->session != NULL)
|
||||
libssh2_session_free(guac_client_data->session);
|
||||
|
||||
/* Free auth key */
|
||||
if (guac_client_data->key != NULL)
|
||||
ssh_key_free(guac_client_data->key);
|
||||
guac_common_ssh_destroy_session(guac_client_data->session);
|
||||
|
||||
/* Free generic data struct */
|
||||
free(client->data);
|
||||
|
||||
guac_common_ssh_uninit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -22,517 +22,43 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "guac_json.h"
|
||||
|
||||
#include "client.h"
|
||||
#include "guac_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/object.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.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,
|
||||
char* mimetype, char* filename) {
|
||||
|
||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
||||
char fullpath[GUAC_SFTP_MAX_PATH];
|
||||
LIBSSH2_SFTP_HANDLE* file;
|
||||
guac_object* filesystem = client_data->sftp_filesystem;
|
||||
|
||||
/* Concatenate filename with path */
|
||||
if (!guac_ssh_append_filename(fullpath,
|
||||
client_data->sftp_upload_path, filename)) {
|
||||
/* Handle file upload */
|
||||
return guac_common_ssh_sftp_handle_file_stream(filesystem, stream,
|
||||
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,
|
||||
char* filename) {
|
||||
|
||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
||||
guac_stream* stream;
|
||||
LIBSSH2_SFTP_HANDLE* file;
|
||||
guac_object* filesystem = client_data->sftp_filesystem;
|
||||
|
||||
/* Attempt to open file for reading */
|
||||
file = libssh2_sftp_open(client_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(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;
|
||||
/* Initiate download of requested file */
|
||||
return guac_common_ssh_sftp_download_file(filesystem, filename);
|
||||
|
||||
}
|
||||
|
||||
void guac_sftp_set_upload_path(guac_client* client, char* path) {
|
||||
|
||||
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 */
|
||||
if (length > GUAC_SFTP_MAX_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);
|
||||
/* Set upload path as specified */
|
||||
guac_common_ssh_sftp_set_upload_path(filesystem, 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;
|
||||
}
|
||||
|
||||
|
@ -26,161 +26,65 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "guac_json.h"
|
||||
|
||||
#include <libssh2.h>
|
||||
#include <libssh2_sftp.h>
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/object.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/stream.h>
|
||||
|
||||
/**
|
||||
* Maximum number of bytes per path.
|
||||
*/
|
||||
#define GUAC_SFTP_MAX_PATH 2048
|
||||
|
||||
/**
|
||||
* The current state of a directory listing operation.
|
||||
*/
|
||||
typedef struct guac_sftp_ls_state {
|
||||
|
||||
/**
|
||||
* 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_SFTP_MAX_PATH];
|
||||
|
||||
/**
|
||||
* 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).
|
||||
* 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_sftp_set_upload_path().
|
||||
*
|
||||
* @param client
|
||||
* The client receiving 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_sftp_file_handler(guac_client* client, guac_stream* stream,
|
||||
char* mimetype, char* filename);
|
||||
|
||||
/**
|
||||
* Handler for blob messages which continues an SFTP data transfer (upload).
|
||||
*/
|
||||
int guac_sftp_blob_handler(guac_client* client, guac_stream* stream,
|
||||
void* data, int length);
|
||||
|
||||
/**
|
||||
* Handler for end messages which ends an SFTP data transfer (upload).
|
||||
*/
|
||||
int guac_sftp_end_handler(guac_client* client, guac_stream* stream);
|
||||
|
||||
/**
|
||||
* Handler for ack messages which continues an SFTP download.
|
||||
*/
|
||||
int guac_sftp_ack_handler(guac_client* client, guac_stream* stream,
|
||||
char* message, guac_protocol_status status);
|
||||
|
||||
/**
|
||||
* Begins (and automatically continues) an SFTP file download to the user.
|
||||
* 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 client
|
||||
* The client receiving the file.
|
||||
*
|
||||
* @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_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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
|
@ -23,8 +23,9 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "client.h"
|
||||
#include "guac_sftp.h"
|
||||
#include "guac_ssh.h"
|
||||
#include "sftp.h"
|
||||
#include "ssh_key.h"
|
||||
#include "terminal.h"
|
||||
|
||||
#ifdef ENABLE_SSH_AGENT
|
||||
@ -56,6 +57,96 @@
|
||||
#include <sys/socket.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) {
|
||||
|
||||
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) {
|
||||
|
||||
guac_client* client = (guac_client*) data;
|
||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
||||
|
||||
char name[1024];
|
||||
|
||||
guac_socket* socket = client->socket;
|
||||
char buffer[8192];
|
||||
int bytes_read = -1234;
|
||||
|
||||
int socket_fd;
|
||||
|
||||
pthread_t input_thread;
|
||||
|
||||
#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.");
|
||||
/* Init SSH base libraries */
|
||||
if (guac_common_ssh_init(client))
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Init threadsafety in OpenSSL */
|
||||
__openssl_init_locks(CRYPTO_num_locks());
|
||||
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);
|
||||
/* Get user and credentials */
|
||||
guac_common_ssh_user* user = guac_ssh_get_user(client);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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 */
|
||||
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) {
|
||||
/* Already aborted within __guac_ssh_create_session() */
|
||||
/* Already aborted within guac_common_ssh_create_session() */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&client_data->term_channel_lock, NULL);
|
||||
|
||||
/* 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -454,30 +227,26 @@ void* ssh_client_thread(void* data) {
|
||||
/* Start SFTP session as well, if enabled */
|
||||
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 */
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP...");
|
||||
client_data->sftp_ssh_session = __guac_ssh_create_session(client, NULL);
|
||||
if (client_data->sftp_ssh_session == NULL) {
|
||||
/* Already aborted within __guac_ssh_create_session() */
|
||||
guac_common_ssh_session* sftp_ssh_session =
|
||||
guac_common_ssh_create_session(client, client_data->hostname,
|
||||
client_data->port, user);
|
||||
if (sftp_ssh_session == NULL) {
|
||||
/* Already aborted within guac_common_ssh_create_session() */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Request SFTP */
|
||||
client_data->sftp_session = libssh2_sftp_init(client_data->sftp_ssh_session);
|
||||
if (client_data->sftp_session == NULL) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to start SFTP session.");
|
||||
return NULL;
|
||||
}
|
||||
client_data->sftp_filesystem =
|
||||
guac_common_ssh_create_sftp_filesystem(sftp_ssh_session, "/");
|
||||
|
||||
/* Set file handler */
|
||||
/* Set generic (non-filesystem) file upload handler */
|
||||
client->file_handler = guac_sftp_file_handler;
|
||||
|
||||
/* Expose filesystem */
|
||||
client_data->sftp_filesystem = guac_sftp_expose_filesystem(client);
|
||||
/* 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;
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized");
|
||||
|
||||
@ -506,10 +275,10 @@ void* ssh_client_thread(void* data) {
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
bytes_read = 0;
|
||||
int bytes_read = 0;
|
||||
for (;;) {
|
||||
|
||||
/* Track total amount of data read */
|
||||
@ -558,13 +327,14 @@ void* ssh_client_thread(void* data) {
|
||||
struct timeval timeout;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(socket_fd, &fds);
|
||||
FD_SET(client_data->session->fd, &fds);
|
||||
|
||||
/* Wait for one second */
|
||||
timeout.tv_sec = 1;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -574,7 +344,6 @@ void* ssh_client_thread(void* data) {
|
||||
guac_client_stop(client);
|
||||
pthread_join(input_thread, NULL);
|
||||
|
||||
__openssl_free_locks(CRYPTO_num_locks());
|
||||
pthread_mutex_destroy(&client_data->term_channel_lock);
|
||||
|
||||
guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended.");
|
||||
|
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -21,7 +21,6 @@
|
||||
#
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
lib_LTLIBRARIES = libguac-client-telnet.la
|
||||
@ -38,7 +37,18 @@ noinst_HEADERS = \
|
||||
guac_handlers.h \
|
||||
telnet_client.h
|
||||
|
||||
libguac_client_telnet_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@
|
||||
libguac_client_telnet_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@
|
||||
libguac_client_telnet_la_LDFLAGS = -version-info 0:0:0 @TELNET_LIBS@ @PTHREAD_LIBS@
|
||||
libguac_client_telnet_la_CFLAGS = \
|
||||
-Werror -Wall -Iinclude \
|
||||
@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@
|
||||
|
||||
|
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -21,11 +21,7 @@
|
||||
#
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AM_CFLAGS = -Werror -Wall -pedantic -Iinclude \
|
||||
@COMMON_INCLUDE@ \
|
||||
@LIBGUAC_INCLUDE@
|
||||
|
||||
lib_LTLIBRARIES = libguac-client-vnc.la
|
||||
|
||||
@ -47,10 +43,18 @@ libguac_client_vnc_la_SOURCES += pulse.c
|
||||
noinst_HEADERS += pulse.h
|
||||
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@ \
|
||||
@PULSE_LIBS@ \
|
||||
@VNC_LIBS@
|
||||
|
||||
libguac_client_vnc_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
||||
libguac_client_vnc_la_LIBADD = \
|
||||
@COMMON_LTLIB@ \
|
||||
@LIBGUAC_LTLIB@
|
||||
|
||||
|
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -22,7 +22,6 @@
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AM_CFLAGS = -Werror -Wall -pedantic @PANGO_CFLAGS@ @PANGOCAIRO_CFLAGS@ @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
|
||||
|
||||
noinst_LTLIBRARIES = libguac_terminal.la
|
||||
|
||||
@ -55,6 +54,20 @@ libguac_terminal_la_SOURCES = \
|
||||
terminal.c \
|
||||
terminal_handlers.c
|
||||
|
||||
libguac_terminal_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
||||
libguac_terminal_la_LDFLAGS = @PTHREAD_LIBS@ @PANGO_LIBS@ @PANGOCAIRO_LIBS@ @CAIRO_LIBS@ @MATH_LIBS@
|
||||
libguac_terminal_la_CFLAGS = \
|
||||
-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@
|
||||
|
||||
|
@ -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
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -22,7 +22,6 @@
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
|
||||
|
||||
TESTS = test_libguac
|
||||
check_PROGRAMS = test_libguac
|
||||
@ -51,5 +50,13 @@ test_libguac_SOURCES = \
|
||||
util/guac_pool.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@
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user