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
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -26,6 +26,7 @@ ACLOCAL_AMFLAGS = -I m4
|
|||||||
DIST_SUBDIRS = \
|
DIST_SUBDIRS = \
|
||||||
src/libguac \
|
src/libguac \
|
||||||
src/common \
|
src/common \
|
||||||
|
src/common-ssh \
|
||||||
src/terminal \
|
src/terminal \
|
||||||
src/guacd \
|
src/guacd \
|
||||||
src/protocols/rdp \
|
src/protocols/rdp \
|
||||||
@ -40,6 +41,10 @@ SUBDIRS = \
|
|||||||
src/guacd \
|
src/guacd \
|
||||||
tests
|
tests
|
||||||
|
|
||||||
|
if ENABLE_COMMON_SSH
|
||||||
|
SUBDIRS += src/common-ssh
|
||||||
|
endif
|
||||||
|
|
||||||
if ENABLE_TERMINAL
|
if ENABLE_TERMINAL
|
||||||
SUBDIRS += src/terminal
|
SUBDIRS += src/terminal
|
||||||
endif
|
endif
|
||||||
@ -60,5 +65,8 @@ if ENABLE_VNC
|
|||||||
SUBDIRS += src/protocols/vnc
|
SUBDIRS += src/protocols/vnc
|
||||||
endif
|
endif
|
||||||
|
|
||||||
EXTRA_DIST = LICENSE doc/Doxyfile bin/guacctl
|
EXTRA_DIST = \
|
||||||
|
LICENSE \
|
||||||
|
bin/guacctl \
|
||||||
|
doc/Doxyfile
|
||||||
|
|
||||||
|
@ -117,6 +117,10 @@ AC_SUBST([LIBGUAC_INCLUDE], '-I$(top_srcdir)/src/libguac')
|
|||||||
AC_SUBST([COMMON_LTLIB], '$(top_builddir)/src/common/libguac_common.la')
|
AC_SUBST([COMMON_LTLIB], '$(top_builddir)/src/common/libguac_common.la')
|
||||||
AC_SUBST([COMMON_INCLUDE], '-I$(top_srcdir)/src/common')
|
AC_SUBST([COMMON_INCLUDE], '-I$(top_srcdir)/src/common')
|
||||||
|
|
||||||
|
# Common base SSH client
|
||||||
|
AC_SUBST([COMMON_SSH_LTLIB], '$(top_builddir)/src/common-ssh/libguac_common_ssh.la')
|
||||||
|
AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh')
|
||||||
|
|
||||||
# Terminal emulator
|
# Terminal emulator
|
||||||
AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la')
|
AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la')
|
||||||
AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)')
|
AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)')
|
||||||
@ -834,6 +838,9 @@ then
|
|||||||
[have_libssh2=no])
|
[have_libssh2=no])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
AM_CONDITIONAL([ENABLE_COMMON_SSH], [test "x${have_libssh2}" = "xyes" \
|
||||||
|
-a "x${have_ssl}" = "xyes"])
|
||||||
|
|
||||||
AM_CONDITIONAL([ENABLE_SSH], [test "x${have_libssh2}" = "xyes" \
|
AM_CONDITIONAL([ENABLE_SSH], [test "x${have_libssh2}" = "xyes" \
|
||||||
-a "x${have_terminal}" = "xyes" \
|
-a "x${have_terminal}" = "xyes" \
|
||||||
-a "x${have_ssl}" = "xyes"])
|
-a "x${have_ssl}" = "xyes"])
|
||||||
@ -920,6 +927,7 @@ AC_SUBST(TELNET_LIBS)
|
|||||||
AC_CONFIG_FILES([Makefile
|
AC_CONFIG_FILES([Makefile
|
||||||
tests/Makefile
|
tests/Makefile
|
||||||
src/common/Makefile
|
src/common/Makefile
|
||||||
|
src/common-ssh/Makefile
|
||||||
src/terminal/Makefile
|
src/terminal/Makefile
|
||||||
src/libguac/Makefile
|
src/libguac/Makefile
|
||||||
src/guacd/Makefile
|
src/guacd/Makefile
|
||||||
|
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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -29,7 +29,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
void buffer_write_byte(char** buffer, uint8_t value) {
|
void guac_common_ssh_buffer_write_byte(char** buffer, uint8_t value) {
|
||||||
|
|
||||||
uint8_t* data = (uint8_t*) *buffer;
|
uint8_t* data = (uint8_t*) *buffer;
|
||||||
*data = value;
|
*data = value;
|
||||||
@ -38,7 +38,7 @@ void buffer_write_byte(char** buffer, uint8_t value) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void buffer_write_uint32(char** buffer, uint32_t value) {
|
void guac_common_ssh_buffer_write_uint32(char** buffer, uint32_t value) {
|
||||||
|
|
||||||
uint8_t* data = (uint8_t*) *buffer;
|
uint8_t* data = (uint8_t*) *buffer;
|
||||||
|
|
||||||
@ -51,19 +51,20 @@ void buffer_write_uint32(char** buffer, uint32_t value) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void buffer_write_data(char** buffer, const char* data, int length) {
|
void guac_common_ssh_buffer_write_data(char** buffer, const char* data,
|
||||||
|
int length) {
|
||||||
memcpy(*buffer, data, length);
|
memcpy(*buffer, data, length);
|
||||||
*buffer += length;
|
*buffer += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
void buffer_write_bignum(char** buffer, BIGNUM* value) {
|
void guac_common_ssh_buffer_write_bignum(char** buffer, BIGNUM* value) {
|
||||||
|
|
||||||
unsigned char* bn_buffer;
|
unsigned char* bn_buffer;
|
||||||
int length;
|
int length;
|
||||||
|
|
||||||
/* If zero, just write zero length */
|
/* If zero, just write zero length */
|
||||||
if (BN_is_zero(value)) {
|
if (BN_is_zero(value)) {
|
||||||
buffer_write_uint32(buffer, 0);
|
guac_common_ssh_buffer_write_uint32(buffer, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,11 +77,11 @@ void buffer_write_bignum(char** buffer, BIGNUM* value) {
|
|||||||
|
|
||||||
/* If first byte has high bit set, write padding byte */
|
/* If first byte has high bit set, write padding byte */
|
||||||
if (bn_buffer[0] & 0x80) {
|
if (bn_buffer[0] & 0x80) {
|
||||||
buffer_write_uint32(buffer, length+1);
|
guac_common_ssh_buffer_write_uint32(buffer, length+1);
|
||||||
buffer_write_byte(buffer, 0);
|
guac_common_ssh_buffer_write_byte(buffer, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
buffer_write_uint32(buffer, length);
|
guac_common_ssh_buffer_write_uint32(buffer, length);
|
||||||
|
|
||||||
/* Write data */
|
/* Write data */
|
||||||
memcpy(*buffer, bn_buffer, length);
|
memcpy(*buffer, bn_buffer, length);
|
||||||
@ -90,12 +91,13 @@ void buffer_write_bignum(char** buffer, BIGNUM* value) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void buffer_write_string(char** buffer, const char* string, int length) {
|
void guac_common_ssh_buffer_write_string(char** buffer, const char* string,
|
||||||
buffer_write_uint32(buffer, length);
|
int length) {
|
||||||
buffer_write_data(buffer, string, length);
|
guac_common_ssh_buffer_write_uint32(buffer, length);
|
||||||
|
guac_common_ssh_buffer_write_data(buffer, string, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t buffer_read_byte(char** buffer) {
|
uint8_t guac_common_ssh_buffer_read_byte(char** buffer) {
|
||||||
|
|
||||||
uint8_t* data = (uint8_t*) *buffer;
|
uint8_t* data = (uint8_t*) *buffer;
|
||||||
uint8_t value = *data;
|
uint8_t value = *data;
|
||||||
@ -106,7 +108,7 @@ uint8_t buffer_read_byte(char** buffer) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t buffer_read_uint32(char** buffer) {
|
uint32_t guac_common_ssh_buffer_read_uint32(char** buffer) {
|
||||||
|
|
||||||
uint8_t* data = (uint8_t*) *buffer;
|
uint8_t* data = (uint8_t*) *buffer;
|
||||||
uint32_t value =
|
uint32_t value =
|
||||||
@ -121,11 +123,11 @@ uint32_t buffer_read_uint32(char** buffer) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char* buffer_read_string(char** buffer, int* length) {
|
char* guac_common_ssh_buffer_read_string(char** buffer, int* length) {
|
||||||
|
|
||||||
char* value;
|
char* value;
|
||||||
|
|
||||||
*length = buffer_read_uint32(buffer);
|
*length = guac_common_ssh_buffer_read_uint32(buffer);
|
||||||
value = *buffer;
|
value = *buffer;
|
||||||
|
|
||||||
*buffer += *length;
|
*buffer += *length;
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2013 Glyptodon LLC
|
* Copyright (C) 2015 Glyptodon LLC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -20,9 +20,8 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef GUAC_COMMON_SSH_BUFFER_H
|
||||||
#ifndef _GUAC_SSH_BUFFER_H
|
#define GUAC_COMMON_SSH_BUFFER_H
|
||||||
#define _GUAC_SSH_BUFFER_H
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@ -32,51 +31,107 @@
|
|||||||
/**
|
/**
|
||||||
* Writes the given byte to the given buffer, advancing the buffer pointer by
|
* Writes the given byte to the given buffer, advancing the buffer pointer by
|
||||||
* one byte.
|
* one byte.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to write to.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The value to write.
|
||||||
*/
|
*/
|
||||||
void buffer_write_byte(char** buffer, uint8_t value);
|
void guac_common_ssh_buffer_write_byte(char** buffer, uint8_t value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given integer to the given buffer, advancing the buffer pointer
|
* Writes the given integer to the given buffer, advancing the buffer pointer
|
||||||
* four bytes.
|
* four bytes.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to write to.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The value to write.
|
||||||
*/
|
*/
|
||||||
void buffer_write_uint32(char** buffer, uint32_t value);
|
void guac_common_ssh_buffer_write_uint32(char** buffer, uint32_t value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given string and its length to the given buffer, advancing the
|
* Writes the given string and its length to the given buffer, advancing the
|
||||||
* buffer pointer by the size of the length (four bytes) and the size of the
|
* buffer pointer by the size of the length (four bytes) and the size of the
|
||||||
* string.
|
* string.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to write to.
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* The string value to write.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The length of the string to write, in bytes.
|
||||||
*/
|
*/
|
||||||
void buffer_write_string(char** buffer, const char* string, int length);
|
void guac_common_ssh_buffer_write_string(char** buffer, const char* string,
|
||||||
|
int length);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given BIGNUM the given buffer, advancing the buffer pointer by
|
* Writes the given BIGNUM the given buffer, advancing the buffer pointer by
|
||||||
* the size of the length (four bytes) and the size of the BIGNUM.
|
* the size of the length (four bytes) and the size of the BIGNUM.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to write to.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The value to write.
|
||||||
*/
|
*/
|
||||||
void buffer_write_bignum(char** buffer, BIGNUM* value);
|
void guac_common_ssh_buffer_write_bignum(char** buffer, BIGNUM* value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given data the given buffer, advancing the buffer pointer by the
|
* Writes the given data the given buffer, advancing the buffer pointer by the
|
||||||
* given length.
|
* given length.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The arbitrary data to write.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The length of data to write, in bytes.
|
||||||
*/
|
*/
|
||||||
void buffer_write_data(char** buffer, const char* data, int length);
|
void guac_common_ssh_buffer_write_data(char** buffer, const char* data, int length);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a single byte from the given buffer, advancing the buffer by one byte.
|
* Reads a single byte from the given buffer, advancing the buffer by one byte.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to read from.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The value read from the buffer.
|
||||||
*/
|
*/
|
||||||
uint8_t buffer_read_byte(char** buffer);
|
uint8_t guac_common_ssh_buffer_read_byte(char** buffer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads an integer from the given buffer, advancing the buffer by four bytes.
|
* Reads an integer from the given buffer, advancing the buffer by four bytes.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to read from.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The value read from the buffer.
|
||||||
*/
|
*/
|
||||||
uint32_t buffer_read_uint32(char** buffer);
|
uint32_t guac_common_ssh_buffer_read_uint32(char** buffer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a string and its length from the given buffer, advancing the buffer
|
* Reads a string and its length from the given buffer, advancing the buffer
|
||||||
* by the size of the length (four bytes) and the size of the string, and
|
* by the size of the length (four bytes) and the size of the string, and
|
||||||
* returning a pointer to the buffer. The length of the string is stored in
|
* returning a pointer to the buffer. The length of the string is stored in
|
||||||
* the given int.
|
* the given int.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to read from.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* A pointer to an integer into which the length of the read string will
|
||||||
|
* be stored.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A pointer to the value within the buffer.
|
||||||
*/
|
*/
|
||||||
char* buffer_read_string(char** buffer, int* length);
|
char* guac_common_ssh_buffer_read_string(char** buffer, int* length);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2013 Glyptodon LLC
|
* Copyright (C) 2015 Glyptodon LLC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -22,8 +22,8 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "ssh_buffer.h"
|
#include "guac_ssh_buffer.h"
|
||||||
#include "ssh_key.h"
|
#include "guac_ssh_key.h"
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
#include <openssl/bn.h>
|
#include <openssl/bn.h>
|
||||||
@ -37,9 +37,10 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
||||||
|
char* passphrase) {
|
||||||
|
|
||||||
ssh_key* key;
|
guac_common_ssh_key* key;
|
||||||
BIO* key_bio;
|
BIO* key_bio;
|
||||||
|
|
||||||
char* public_key;
|
char* public_key;
|
||||||
@ -61,7 +62,7 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Allocate key */
|
/* Allocate key */
|
||||||
key = malloc(sizeof(ssh_key));
|
key = malloc(sizeof(guac_common_ssh_key));
|
||||||
key->rsa = rsa_key;
|
key->rsa = rsa_key;
|
||||||
|
|
||||||
/* Set type */
|
/* Set type */
|
||||||
@ -72,9 +73,9 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
|||||||
pos = public_key;
|
pos = public_key;
|
||||||
|
|
||||||
/* Derive public key */
|
/* Derive public key */
|
||||||
buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
|
guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
|
||||||
buffer_write_bignum(&pos, rsa_key->e);
|
guac_common_ssh_buffer_write_bignum(&pos, rsa_key->e);
|
||||||
buffer_write_bignum(&pos, rsa_key->n);
|
guac_common_ssh_buffer_write_bignum(&pos, rsa_key->n);
|
||||||
|
|
||||||
/* Save public key to structure */
|
/* Save public key to structure */
|
||||||
key->public_key = public_key;
|
key->public_key = public_key;
|
||||||
@ -95,7 +96,7 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Allocate key */
|
/* Allocate key */
|
||||||
key = malloc(sizeof(ssh_key));
|
key = malloc(sizeof(guac_common_ssh_key));
|
||||||
key->dsa = dsa_key;
|
key->dsa = dsa_key;
|
||||||
|
|
||||||
/* Set type */
|
/* Set type */
|
||||||
@ -106,11 +107,11 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
|||||||
pos = public_key;
|
pos = public_key;
|
||||||
|
|
||||||
/* Derive public key */
|
/* Derive public key */
|
||||||
buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
|
guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
|
||||||
buffer_write_bignum(&pos, dsa_key->p);
|
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->p);
|
||||||
buffer_write_bignum(&pos, dsa_key->q);
|
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->q);
|
||||||
buffer_write_bignum(&pos, dsa_key->g);
|
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->g);
|
||||||
buffer_write_bignum(&pos, dsa_key->pub_key);
|
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->pub_key);
|
||||||
|
|
||||||
/* Save public key to structure */
|
/* Save public key to structure */
|
||||||
key->public_key = public_key;
|
key->public_key = public_key;
|
||||||
@ -134,14 +135,14 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* ssh_key_error() {
|
const char* guac_common_ssh_key_error() {
|
||||||
|
|
||||||
/* Return static error string */
|
/* Return static error string */
|
||||||
return ERR_reason_error_string(ERR_get_error());
|
return ERR_reason_error_string(ERR_get_error());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ssh_key_free(ssh_key* key) {
|
void guac_common_ssh_key_free(guac_common_ssh_key* key) {
|
||||||
|
|
||||||
/* Free key-specific data */
|
/* Free key-specific data */
|
||||||
if (key->type == SSH_KEY_RSA)
|
if (key->type == SSH_KEY_RSA)
|
||||||
@ -153,7 +154,8 @@ void ssh_key_free(ssh_key* key) {
|
|||||||
free(key);
|
free(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ssh_key_sign(ssh_key* key, const char* data, int length, unsigned char* sig) {
|
int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
|
||||||
|
int length, unsigned char* sig) {
|
||||||
|
|
||||||
const EVP_MD* md;
|
const EVP_MD* md;
|
||||||
EVP_MD_CTX md_ctx;
|
EVP_MD_CTX md_ctx;
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2013 Glyptodon LLC
|
* Copyright (C) 2015 Glyptodon LLC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -20,9 +20,8 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef GUAC_COMMON_SSH_KEY_H
|
||||||
#ifndef _GUAC_SSH_KEY_H
|
#define GUAC_COMMON_SSH_KEY_H
|
||||||
#define _GUAC_SSH_KEY_H
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@ -51,7 +50,7 @@
|
|||||||
/**
|
/**
|
||||||
* The type of an SSH key.
|
* The type of an SSH key.
|
||||||
*/
|
*/
|
||||||
typedef enum ssh_key_type {
|
typedef enum guac_common_ssh_key_type {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RSA key.
|
* RSA key.
|
||||||
@ -63,17 +62,17 @@ typedef enum ssh_key_type {
|
|||||||
*/
|
*/
|
||||||
SSH_KEY_DSA
|
SSH_KEY_DSA
|
||||||
|
|
||||||
} ssh_key_type;
|
} guac_common_ssh_key_type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstraction of a key used for SSH authentication.
|
* Abstraction of a key used for SSH authentication.
|
||||||
*/
|
*/
|
||||||
typedef struct ssh_key {
|
typedef struct guac_common_ssh_key {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of this key.
|
* The type of this key.
|
||||||
*/
|
*/
|
||||||
ssh_key_type type;
|
guac_common_ssh_key_type type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Underlying RSA private key, if any.
|
* Underlying RSA private key, if any.
|
||||||
@ -105,13 +104,28 @@ typedef struct ssh_key {
|
|||||||
*/
|
*/
|
||||||
int private_key_length;
|
int private_key_length;
|
||||||
|
|
||||||
} ssh_key;
|
} guac_common_ssh_key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates a new key containing the given private key data and specified
|
* Allocates a new key containing the given private key data and specified
|
||||||
* passphrase. If unable to read the key, NULL is returned.
|
* passphrase. If unable to read the key, NULL is returned.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The base64-encoded data to decode when reading the key.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The length of the provided data, in bytes.
|
||||||
|
*
|
||||||
|
* @param passphrase
|
||||||
|
* The passphrase to use when decrypting the key, if any, or an empty
|
||||||
|
* string or NULL if no passphrase is needed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The decoded, decrypted private key, or NULL if the key could not be
|
||||||
|
* decoded.
|
||||||
*/
|
*/
|
||||||
ssh_key* ssh_key_alloc(char* data, int length, char* passphrase);
|
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
||||||
|
char* passphrase);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a statically-allocated string describing the most recent SSH key
|
* Returns a statically-allocated string describing the most recent SSH key
|
||||||
@ -120,18 +134,40 @@ ssh_key* ssh_key_alloc(char* data, int length, char* passphrase);
|
|||||||
* @return
|
* @return
|
||||||
* A statically-allocated string describing the most recent SSH key error.
|
* A statically-allocated string describing the most recent SSH key error.
|
||||||
*/
|
*/
|
||||||
const char* ssh_key_error();
|
const char* guac_common_ssh_key_error();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees all memory associated with the given key.
|
* Frees all memory associated with the given key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* The key to free.
|
||||||
*/
|
*/
|
||||||
void ssh_key_free(ssh_key* key);
|
void guac_common_ssh_key_free(guac_common_ssh_key* key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs the given data using the given key, returning the length of the
|
* Signs the given data using the given key, returning the length of the
|
||||||
* signature in bytes, or a value less than zero on error.
|
* signature in bytes, or a value less than zero on error.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* The key to use when signing the given data.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The arbitrary data to sign.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The length of the arbitrary data being signed, in bytes.
|
||||||
|
*
|
||||||
|
* @param sig
|
||||||
|
* The buffer into which the signature should be written. The buffer must
|
||||||
|
* be at least DSA_SIG_SIZE for DSA keys. For RSA keys, the signature size
|
||||||
|
* is dependent only on key size, and is equal to the length of the
|
||||||
|
* modulus, in bytes.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The number of bytes in the resulting signature.
|
||||||
*/
|
*/
|
||||||
int ssh_key_sign(ssh_key* key, const char* data, int length, unsigned char* sig);
|
int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
|
||||||
|
int length, unsigned char* sig);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
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
|
AUTOMAKE_OPTIONS = foreign
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@
|
|
||||||
|
|
||||||
noinst_LTLIBRARIES = libguac_common.la
|
noinst_LTLIBRARIES = libguac_common.la
|
||||||
|
|
||||||
@ -50,5 +49,10 @@ libguac_common_la_SOURCES = \
|
|||||||
guac_string.c \
|
guac_string.c \
|
||||||
guac_surface.c
|
guac_surface.c
|
||||||
|
|
||||||
libguac_common_la_LIBADD = @LIBGUAC_LTLIB@
|
libguac_common_la_CFLAGS = \
|
||||||
|
-Werror -Wall -pedantic \
|
||||||
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
libguac_common_la_LIBADD = \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2013 Glyptodon LLC
|
# Copyright (C) 2015 Glyptodon LLC
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -22,8 +22,6 @@
|
|||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
|
||||||
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
|
|
||||||
|
|
||||||
sbin_PROGRAMS = guacd
|
sbin_PROGRAMS = guacd
|
||||||
|
|
||||||
man_MANS = \
|
man_MANS = \
|
||||||
@ -47,10 +45,24 @@ guacd_SOURCES = \
|
|||||||
conf-parse.c \
|
conf-parse.c \
|
||||||
log.c
|
log.c
|
||||||
|
|
||||||
guacd_LDADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
guacd_CFLAGS = \
|
||||||
guacd_LDFLAGS = @PTHREAD_LIBS@ @SSL_LIBS@
|
-Werror -Wall -pedantic \
|
||||||
|
@COMMON_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
guacd_LDADD = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
guacd_LDFLAGS = \
|
||||||
|
@PTHREAD_LIBS@ \
|
||||||
|
@SSL_LIBS@
|
||||||
|
|
||||||
|
EXTRA_DIST = \
|
||||||
|
init.d/guacd.in \
|
||||||
|
man/guacd.8 \
|
||||||
|
man/guacd.conf.5
|
||||||
|
|
||||||
EXTRA_DIST = init.d/guacd.in man/guacd.8 man/guacd.conf.5
|
|
||||||
CLEANFILES = $(init_SCRIPTS)
|
CLEANFILES = $(init_SCRIPTS)
|
||||||
|
|
||||||
# SSL support
|
# SSL support
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2013 Glyptodon LLC
|
# Copyright (C) 2015 Glyptodon LLC
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -22,9 +22,11 @@
|
|||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
AM_CFLAGS = -Werror -Wall -pedantic -Iguacamole
|
|
||||||
|
lib_LTLIBRARIES = libguac.la
|
||||||
|
|
||||||
libguacincdir = $(includedir)/guacamole
|
libguacincdir = $(includedir)/guacamole
|
||||||
|
|
||||||
libguacinc_HEADERS = \
|
libguacinc_HEADERS = \
|
||||||
guacamole/audio.h \
|
guacamole/audio.h \
|
||||||
guacamole/audio-fntypes.h \
|
guacamole/audio-fntypes.h \
|
||||||
@ -89,7 +91,17 @@ libguac_la_SOURCES += ogg_encoder.c
|
|||||||
noinst_HEADERS += ogg_encoder.h
|
noinst_HEADERS += ogg_encoder.h
|
||||||
endif
|
endif
|
||||||
|
|
||||||
lib_LTLIBRARIES = libguac.la
|
libguac_la_CFLAGS = \
|
||||||
libguac_la_LDFLAGS = -version-info 9:0:0 @PTHREAD_LIBS@ @CAIRO_LIBS@ @PNG_LIBS@ @VORBIS_LIBS@ @UUID_LIBS@
|
-Werror -Wall -pedantic -Iguacamole
|
||||||
libguac_la_LIBADD = @LIBADD_DLOPEN@
|
|
||||||
|
libguac_la_LDFLAGS = \
|
||||||
|
-version-info 9:0:0 \
|
||||||
|
@CAIRO_LIBS@ \
|
||||||
|
@PNG_LIBS@ \
|
||||||
|
@PTHREAD_LIBS@ \
|
||||||
|
@UUID_LIBS@ \
|
||||||
|
@VORBIS_LIBS@
|
||||||
|
|
||||||
|
libguac_la_LIBADD = \
|
||||||
|
@LIBADD_DLOPEN@
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2013 Glyptodon LLC
|
# Copyright (C) 2015 Glyptodon LLC
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -21,9 +21,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
AM_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
|
|
||||||
|
|
||||||
lib_LTLIBRARIES = libguac-client-rdp.la
|
lib_LTLIBRARIES = libguac-client-rdp.la
|
||||||
|
|
||||||
@ -114,17 +112,83 @@ noinst_HEADERS += rdp_disp.h
|
|||||||
libguac_client_rdp_la_SOURCES += rdp_disp.c
|
libguac_client_rdp_la_SOURCES += rdp_disp.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
libguac_client_rdp_la_LDFLAGS = -version-info 0:0:0 @RDP_LIBS@ @PTHREAD_LIBS@ @CAIRO_LIBS@
|
#
|
||||||
guacsvc_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@
|
# Main RDP client library
|
||||||
guacsnd_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@
|
#
|
||||||
guacdr_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@
|
|
||||||
|
|
||||||
libguac_client_rdp_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
libguac_client_rdp_la_CFLAGS = \
|
||||||
guacsvc_libadd = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
-Werror -Wall -Iinclude \
|
||||||
guacsnd_libadd = @LIBGUAC_LTLIB@
|
@COMMON_INCLUDE@ \
|
||||||
guacdr_libadd = @LIBGUAC_LTLIB@
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
libguac_client_rdp_la_LDFLAGS = \
|
||||||
|
-version-info 0:0:0 \
|
||||||
|
@CAIRO_LIBS@ \
|
||||||
|
@PTHREAD_LIBS@ \
|
||||||
|
@RDP_LIBS@
|
||||||
|
|
||||||
|
libguac_client_rdp_la_LIBADD = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
#
|
||||||
|
# RDPDR
|
||||||
|
#
|
||||||
|
|
||||||
|
guacdr_cflags = \
|
||||||
|
-Werror -Wall -Iinclude \
|
||||||
|
@COMMON_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
guacdr_ldflags = \
|
||||||
|
-module -avoid-version -shared \
|
||||||
|
@PTHREAD_LIBS@ \
|
||||||
|
@RDP_LIBS@
|
||||||
|
|
||||||
|
guacdr_libadd = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
#
|
||||||
|
# RDPSND
|
||||||
|
#
|
||||||
|
|
||||||
|
guacsnd_cflags = \
|
||||||
|
-Werror -Wall -Iinclude \
|
||||||
|
@COMMON_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
guacsnd_ldflags = \
|
||||||
|
-module -avoid-version -shared \
|
||||||
|
@PTHREAD_LIBS@ \
|
||||||
|
@RDP_LIBS@
|
||||||
|
|
||||||
|
guacsnd_libadd = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
#
|
||||||
|
# Static Virtual Channels
|
||||||
|
#
|
||||||
|
|
||||||
|
guacsvc_cflags = \
|
||||||
|
-Werror -Wall -Iinclude \
|
||||||
|
@COMMON_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
guacsvc_ldflags = \
|
||||||
|
-module -avoid-version -shared \
|
||||||
|
@PTHREAD_LIBS@ \
|
||||||
|
@RDP_LIBS@
|
||||||
|
|
||||||
|
guacsvc_libadd = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
#
|
||||||
# Autogenerate keymaps
|
# Autogenerate keymaps
|
||||||
|
#
|
||||||
|
|
||||||
CLEANFILES = _generated_keymaps.c
|
CLEANFILES = _generated_keymaps.c
|
||||||
BUILT_SOURCES = _generated_keymaps.c
|
BUILT_SOURCES = _generated_keymaps.c
|
||||||
|
|
||||||
@ -140,41 +204,55 @@ rdp_keymaps = \
|
|||||||
_generated_keymaps.c: $(rdp_keymaps)
|
_generated_keymaps.c: $(rdp_keymaps)
|
||||||
keymaps/generate.pl $(rdp_keymaps)
|
keymaps/generate.pl $(rdp_keymaps)
|
||||||
|
|
||||||
EXTRA_DIST = $(rdp_keymaps) keymaps/generate.pl
|
EXTRA_DIST = \
|
||||||
|
$(rdp_keymaps) \
|
||||||
|
keymaps/generate.pl
|
||||||
|
|
||||||
if LEGACY_FREERDP_EXTENSIONS
|
if LEGACY_FREERDP_EXTENSIONS
|
||||||
|
|
||||||
# FreeRDP 1.0-style extensions
|
# FreeRDP 1.0-style extensions
|
||||||
freerdp_LTLIBRARIES = guacsvc.la guacsnd.la guacdr.la
|
freerdp_LTLIBRARIES = \
|
||||||
|
guacdr.la \
|
||||||
|
guacsnd.la \
|
||||||
|
guacsvc.la
|
||||||
|
|
||||||
guacsvc_la_SOURCES = ${guacsvc_sources}
|
guacdr_la_SOURCES = ${guacdr_sources}
|
||||||
guacsvc_la_LDFLAGS = ${guacsvc_ldflags}
|
guacdr_la_CFLAGS = ${guacdr_cflags}
|
||||||
guacsvc_la_LIBADD = ${guacsvc_libadd}
|
guacdr_la_LDFLAGS = ${guacdr_ldflags}
|
||||||
|
guacdr_la_LIBADD = ${guacdr_libadd}
|
||||||
|
|
||||||
guacsnd_la_SOURCES = ${guacsnd_sources}
|
guacsnd_la_SOURCES = ${guacsnd_sources}
|
||||||
|
guacsnd_la_CFLAGS = ${guacsnd_cflags}
|
||||||
guacsnd_la_LDFLAGS = ${guacsnd_ldflags}
|
guacsnd_la_LDFLAGS = ${guacsnd_ldflags}
|
||||||
guacsnd_la_LIBADD = ${guacsnd_libadd}
|
guacsnd_la_LIBADD = ${guacsnd_libadd}
|
||||||
|
|
||||||
guacdr_la_SOURCES = ${guacdr_sources}
|
guacsvc_la_SOURCES = ${guacsvc_sources}
|
||||||
guacdr_la_LDFLAGS = ${guacdr_ldflags}
|
guacsvc_la_CFLAGS = ${guacsvc_cflags}
|
||||||
guacdr_la_LIBADD = ${guacdr_libadd}
|
guacsvc_la_LDFLAGS = ${guacsvc_ldflags}
|
||||||
|
guacsvc_la_LIBADD = ${guacsvc_libadd}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
# FreeRDP 1.1 (and hopefully onward) extensions
|
# FreeRDP 1.1 (and hopefully onward) extensions
|
||||||
freerdp_LTLIBRARIES = guacsvc-client.la guacsnd-client.la guacdr-client.la
|
freerdp_LTLIBRARIES = \
|
||||||
|
guacdr-client.la \
|
||||||
|
guacsnd-client.la \
|
||||||
|
guacsvc-client.la
|
||||||
|
|
||||||
guacsvc_client_la_SOURCES = ${guacsvc_sources}
|
guacdr_client_la_SOURCES = ${guacdr_sources}
|
||||||
guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags}
|
guacdr_client_la_CFLAGS = ${guacdr_cflags}
|
||||||
guacsvc_client_la_LIBADD = ${guacsvc_libadd}
|
guacdr_client_la_LDFLAGS = ${guacdr_ldflags}
|
||||||
|
guacdr_client_la_LIBADD = ${guacdr_libadd}
|
||||||
|
|
||||||
guacsnd_client_la_SOURCES = ${guacsnd_sources}
|
guacsnd_client_la_SOURCES = ${guacsnd_sources}
|
||||||
|
guacsnd_client_la_CFLAGS = ${guacsnd_cflags}
|
||||||
guacsnd_client_la_LDFLAGS = ${guacsnd_ldflags}
|
guacsnd_client_la_LDFLAGS = ${guacsnd_ldflags}
|
||||||
guacsnd_client_la_LIBADD = ${guacsnd_libadd}
|
guacsnd_client_la_LIBADD = ${guacsnd_libadd}
|
||||||
|
|
||||||
guacdr_client_la_SOURCES = ${guacdr_sources}
|
guacsvc_client_la_SOURCES = ${guacsvc_sources}
|
||||||
guacdr_client_la_LDFLAGS = ${guacdr_ldflags}
|
guacsvc_client_la_CFLAGS = ${guacsvc_cflags}
|
||||||
guacdr_client_la_LIBADD = ${guacdr_libadd}
|
guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags}
|
||||||
|
guacsvc_client_la_LIBADD = ${guacsvc_libadd}
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2013 Glyptodon LLC
|
# Copyright (C) 2015 Glyptodon LLC
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -21,7 +21,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
lib_LTLIBRARIES = libguac-client-ssh.la
|
lib_LTLIBRARIES = libguac-client-ssh.la
|
||||||
@ -31,18 +30,14 @@ libguac_client_ssh_la_SOURCES = \
|
|||||||
clipboard.c \
|
clipboard.c \
|
||||||
guac_handlers.c \
|
guac_handlers.c \
|
||||||
sftp.c \
|
sftp.c \
|
||||||
ssh_buffer.c \
|
ssh_client.c
|
||||||
ssh_client.c \
|
|
||||||
ssh_key.c
|
|
||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
client.h \
|
client.h \
|
||||||
clipboard.h \
|
clipboard.h \
|
||||||
guac_handlers.h \
|
guac_handlers.h \
|
||||||
sftp.h \
|
sftp.h \
|
||||||
ssh_buffer.h \
|
ssh_client.h
|
||||||
ssh_client.h \
|
|
||||||
ssh_key.h
|
|
||||||
|
|
||||||
# Add agent sources if enabled
|
# Add agent sources if enabled
|
||||||
if ENABLE_SSH_AGENT
|
if ENABLE_SSH_AGENT
|
||||||
@ -50,7 +45,21 @@ libguac_client_ssh_la_SOURCES += ssh_agent.c
|
|||||||
noinst_HEADERS += ssh_agent.h
|
noinst_HEADERS += ssh_agent.h
|
||||||
endif
|
endif
|
||||||
|
|
||||||
libguac_client_ssh_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@
|
libguac_client_ssh_la_CFLAGS = \
|
||||||
libguac_client_ssh_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@
|
-Werror -Wall -Iinclude \
|
||||||
libguac_client_ssh_la_LDFLAGS = -version-info 0:0:0 @SSH_LIBS@ @SSL_LIBS@ @PTHREAD_LIBS@
|
@COMMON_SSH_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@ \
|
||||||
|
@TERMINAL_INCLUDE@
|
||||||
|
|
||||||
|
libguac_client_ssh_la_LIBADD = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@COMMON_SSH_LTLIB@ \
|
||||||
|
@LIBGUAC_LTLIB@ \
|
||||||
|
@TERMINAL_LTLIB@
|
||||||
|
|
||||||
|
libguac_client_ssh_la_LDFLAGS = \
|
||||||
|
-version-info 0:0:0 \
|
||||||
|
@PTHREAD_LIBS@ \
|
||||||
|
@SSH_LIBS@ \
|
||||||
|
@SSL_LIBS@
|
||||||
|
|
||||||
|
@ -142,7 +142,6 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
|
|||||||
strcpy(client_data->password, argv[IDX_PASSWORD]);
|
strcpy(client_data->password, argv[IDX_PASSWORD]);
|
||||||
|
|
||||||
/* Init public key auth information */
|
/* Init public key auth information */
|
||||||
client_data->key = NULL;
|
|
||||||
strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]);
|
strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]);
|
||||||
strcpy(client_data->key_passphrase, argv[IDX_PASSPHRASE]);
|
strcpy(client_data->key_passphrase, argv[IDX_PASSPHRASE]);
|
||||||
|
|
||||||
@ -160,10 +159,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
|
|||||||
|
|
||||||
/* Parse SFTP enable */
|
/* Parse SFTP enable */
|
||||||
client_data->enable_sftp = strcmp(argv[IDX_ENABLE_SFTP], "true") == 0;
|
client_data->enable_sftp = strcmp(argv[IDX_ENABLE_SFTP], "true") == 0;
|
||||||
client_data->sftp_session = NULL;
|
|
||||||
client_data->sftp_ssh_session = NULL;
|
|
||||||
client_data->sftp_filesystem = NULL;
|
client_data->sftp_filesystem = NULL;
|
||||||
strcpy(client_data->sftp_upload_path, ".");
|
|
||||||
|
|
||||||
#ifdef ENABLE_SSH_AGENT
|
#ifdef ENABLE_SSH_AGENT
|
||||||
client_data->enable_agent = strcmp(argv[IDX_ENABLE_AGENT], "true") == 0;
|
client_data->enable_agent = strcmp(argv[IDX_ENABLE_AGENT], "true") == 0;
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "guac_ssh.h"
|
||||||
#include "sftp.h"
|
#include "sftp.h"
|
||||||
#include "ssh_key.h"
|
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
|
|
||||||
#include <libssh2.h>
|
#include <libssh2.h>
|
||||||
@ -77,11 +77,6 @@ typedef struct ssh_guac_client_data {
|
|||||||
*/
|
*/
|
||||||
char key_passphrase[1024];
|
char key_passphrase[1024];
|
||||||
|
|
||||||
/**
|
|
||||||
* The private key to use for authentication, if any.
|
|
||||||
*/
|
|
||||||
ssh_key* key;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the font to use for display rendering.
|
* The name of the font to use for display rendering.
|
||||||
*/
|
*/
|
||||||
@ -117,29 +112,13 @@ typedef struct ssh_guac_client_data {
|
|||||||
/**
|
/**
|
||||||
* SSH session, used by the SSH client thread.
|
* SSH session, used by the SSH client thread.
|
||||||
*/
|
*/
|
||||||
LIBSSH2_SESSION* session;
|
guac_common_ssh_session* session;
|
||||||
|
|
||||||
/**
|
|
||||||
* The distinct SSH session used for SFTP.
|
|
||||||
*/
|
|
||||||
LIBSSH2_SESSION* sftp_ssh_session;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SFTP session, used for file transfers.
|
|
||||||
*/
|
|
||||||
LIBSSH2_SFTP* sftp_session;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The filesystem object exposed for the SFTP session.
|
* The filesystem object exposed for the SFTP session.
|
||||||
*/
|
*/
|
||||||
guac_object* sftp_filesystem;
|
guac_object* sftp_filesystem;
|
||||||
|
|
||||||
/**
|
|
||||||
* The path files will be sent to, if uploaded directly via a "file"
|
|
||||||
* instruction.
|
|
||||||
*/
|
|
||||||
char sftp_upload_path[GUAC_SFTP_MAX_PATH];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SSH terminal channel, used by the SSH client thread.
|
* SSH terminal channel, used by the SSH client thread.
|
||||||
*/
|
*/
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "guac_handlers.h"
|
#include "guac_handlers.h"
|
||||||
#include "ssh_key.h"
|
#include "guac_sftp.h"
|
||||||
|
#include "guac_ssh.h"
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
|
|
||||||
#include <cairo/cairo.h>
|
#include <cairo/cairo.h>
|
||||||
@ -101,31 +102,18 @@ int ssh_guac_client_free_handler(guac_client* client) {
|
|||||||
/* Free channels */
|
/* Free channels */
|
||||||
libssh2_channel_free(guac_client_data->term_channel);
|
libssh2_channel_free(guac_client_data->term_channel);
|
||||||
|
|
||||||
/* Shutdown SFTP session, if any */
|
|
||||||
if (guac_client_data->sftp_session)
|
|
||||||
libssh2_sftp_shutdown(guac_client_data->sftp_session);
|
|
||||||
|
|
||||||
/* Disconnect SSH session corresponding to the SFTP session */
|
|
||||||
if (guac_client_data->sftp_ssh_session) {
|
|
||||||
libssh2_session_disconnect(guac_client_data->sftp_ssh_session, "Bye");
|
|
||||||
libssh2_session_free(guac_client_data->sftp_ssh_session);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Clean up the SFTP filesystem object */
|
/* Clean up the SFTP filesystem object */
|
||||||
if (guac_client_data->sftp_filesystem)
|
if (guac_client_data->sftp_filesystem)
|
||||||
guac_client_free_object(client, guac_client_data->sftp_filesystem);
|
guac_common_ssh_destroy_sftp_filesystem(guac_client_data->sftp_filesystem);
|
||||||
|
|
||||||
/* Free session */
|
/* Free session */
|
||||||
if (guac_client_data->session != NULL)
|
if (guac_client_data->session != NULL)
|
||||||
libssh2_session_free(guac_client_data->session);
|
guac_common_ssh_destroy_session(guac_client_data->session);
|
||||||
|
|
||||||
/* Free auth key */
|
|
||||||
if (guac_client_data->key != NULL)
|
|
||||||
ssh_key_free(guac_client_data->key);
|
|
||||||
|
|
||||||
/* Free generic data struct */
|
/* Free generic data struct */
|
||||||
free(client->data);
|
free(client->data);
|
||||||
|
|
||||||
|
guac_common_ssh_uninit();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,517 +22,43 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "guac_json.h"
|
|
||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
#include "guac_sftp.h"
|
||||||
#include "sftp.h"
|
#include "sftp.h"
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <libgen.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <libssh2_sftp.h>
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <guacamole/object.h>
|
|
||||||
#include <guacamole/protocol.h>
|
|
||||||
#include <guacamole/socket.h>
|
|
||||||
#include <guacamole/stream.h>
|
#include <guacamole/stream.h>
|
||||||
|
|
||||||
/**
|
|
||||||
* Concatenates the given filename with the given path, separating the two
|
|
||||||
* with a single forward slash. The full result must be no more than
|
|
||||||
* GUAC_SFTP_MAX_PATH bytes long, counting null terminator.
|
|
||||||
*
|
|
||||||
* @param fullpath
|
|
||||||
* The buffer to store the result within. This buffer must be at least
|
|
||||||
* GUAC_SFTP_MAX_PATH bytes long.
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
* The path to append the filename to.
|
|
||||||
*
|
|
||||||
* @param filename
|
|
||||||
* The filename to append to the path.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* true if the filename is valid and was successfully appended to the path,
|
|
||||||
* false otherwise.
|
|
||||||
*/
|
|
||||||
static bool guac_ssh_append_filename(char* fullpath, const char* path,
|
|
||||||
const char* filename) {
|
|
||||||
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* Disallow "." as a filename */
|
|
||||||
if (strcmp(filename, ".") == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Disallow ".." as a filename */
|
|
||||||
if (strcmp(filename, "..") == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Copy path, append trailing slash */
|
|
||||||
for (i=0; i<GUAC_SFTP_MAX_PATH; i++) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Append trailing slash only if:
|
|
||||||
* 1) Trailing slash is not already present
|
|
||||||
* 2) Path is non-empty
|
|
||||||
*/
|
|
||||||
|
|
||||||
char c = path[i];
|
|
||||||
if (c == '\0') {
|
|
||||||
if (i > 0 && path[i-1] != '/')
|
|
||||||
fullpath[i++] = '/';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy character if not end of string */
|
|
||||||
fullpath[i] = c;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Append filename */
|
|
||||||
for (; i<GUAC_SFTP_MAX_PATH; i++) {
|
|
||||||
|
|
||||||
char c = *(filename++);
|
|
||||||
if (c == '\0')
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* Filenames may not contain slashes */
|
|
||||||
if (c == '\\' || c == '/')
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Append each character within filename */
|
|
||||||
fullpath[i] = c;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Verify path length is within maximum */
|
|
||||||
if (i == GUAC_SFTP_MAX_PATH)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Terminate path string */
|
|
||||||
fullpath[i] = '\0';
|
|
||||||
|
|
||||||
/* Append was successful */
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_sftp_file_handler(guac_client* client, guac_stream* stream,
|
int guac_sftp_file_handler(guac_client* client, guac_stream* stream,
|
||||||
char* mimetype, char* filename) {
|
char* mimetype, char* filename) {
|
||||||
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
||||||
char fullpath[GUAC_SFTP_MAX_PATH];
|
guac_object* filesystem = client_data->sftp_filesystem;
|
||||||
LIBSSH2_SFTP_HANDLE* file;
|
|
||||||
|
|
||||||
/* Concatenate filename with path */
|
/* Handle file upload */
|
||||||
if (!guac_ssh_append_filename(fullpath,
|
return guac_common_ssh_sftp_handle_file_stream(filesystem, stream,
|
||||||
client_data->sftp_upload_path, filename)) {
|
mimetype, filename);
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
||||||
"Filename \"%s\" is invalid or resulting path is too long",
|
|
||||||
filename);
|
|
||||||
|
|
||||||
/* Abort transfer - invalid filename */
|
|
||||||
guac_protocol_send_ack(client->socket, stream,
|
|
||||||
"SFTP: Illegal filename",
|
|
||||||
GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
|
|
||||||
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open file via SFTP */
|
|
||||||
file = libssh2_sftp_open(client_data->sftp_session, fullpath,
|
|
||||||
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
|
|
||||||
S_IRUSR | S_IWUSR);
|
|
||||||
|
|
||||||
/* Inform of status */
|
|
||||||
if (file != NULL) {
|
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
||||||
"File \"%s\" opened",
|
|
||||||
fullpath);
|
|
||||||
|
|
||||||
guac_protocol_send_ack(client->socket, stream, "SFTP: File opened", GUAC_PROTOCOL_STATUS_SUCCESS);
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s",
|
|
||||||
fullpath, libssh2_sftp_last_error(client_data->sftp_session));
|
|
||||||
guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set handlers for file stream */
|
|
||||||
stream->blob_handler = guac_sftp_blob_handler;
|
|
||||||
stream->end_handler = guac_sftp_end_handler;
|
|
||||||
|
|
||||||
/* Store file within stream */
|
|
||||||
stream->data = file;
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_sftp_blob_handler(guac_client* client, guac_stream* stream,
|
|
||||||
void* data, int length) {
|
|
||||||
|
|
||||||
/* Pull file from stream */
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
|
||||||
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
|
|
||||||
|
|
||||||
/* Attempt write */
|
|
||||||
if (libssh2_sftp_write(file, data, length) == length) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes written", length);
|
|
||||||
guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS);
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Inform of any errors */
|
|
||||||
else {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Unable to write to file: %s",
|
|
||||||
libssh2_sftp_last_error(client_data->sftp_session));
|
|
||||||
guac_protocol_send_ack(client->socket, stream, "SFTP: Write failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR);
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_sftp_end_handler(guac_client* client, guac_stream* stream) {
|
|
||||||
|
|
||||||
/* Pull file from stream */
|
|
||||||
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
|
|
||||||
|
|
||||||
/* Attempt to close file */
|
|
||||||
if (libssh2_sftp_close(file) == 0) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "File closed");
|
|
||||||
guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS);
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Unable to close file");
|
|
||||||
guac_protocol_send_ack(client->socket, stream, "SFTP: Close failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR);
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_sftp_ack_handler(guac_client* client, guac_stream* stream,
|
|
||||||
char* message, guac_protocol_status status) {
|
|
||||||
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
|
||||||
LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data;
|
|
||||||
|
|
||||||
/* If successful, read data */
|
|
||||||
if (status == GUAC_PROTOCOL_STATUS_SUCCESS) {
|
|
||||||
|
|
||||||
/* Attempt read into buffer */
|
|
||||||
char buffer[4096];
|
|
||||||
int bytes_read = libssh2_sftp_read(file, buffer, sizeof(buffer));
|
|
||||||
|
|
||||||
/* If bytes read, send as blob */
|
|
||||||
if (bytes_read > 0) {
|
|
||||||
guac_protocol_send_blob(client->socket, stream,
|
|
||||||
buffer, bytes_read);
|
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes sent to client",
|
|
||||||
bytes_read);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If EOF, send end */
|
|
||||||
else if (bytes_read == 0) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "File sent");
|
|
||||||
guac_protocol_send_end(client->socket, stream);
|
|
||||||
guac_client_free_stream(client, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Otherwise, fail stream */
|
|
||||||
else {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Error reading file: %s",
|
|
||||||
libssh2_sftp_last_error(client_data->sftp_session));
|
|
||||||
guac_protocol_send_end(client->socket, stream);
|
|
||||||
guac_client_free_stream(client, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Otherwise, return stream to client */
|
|
||||||
else
|
|
||||||
guac_client_free_stream(client, stream);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guac_stream* guac_sftp_download_file(guac_client* client,
|
guac_stream* guac_sftp_download_file(guac_client* client,
|
||||||
char* filename) {
|
char* filename) {
|
||||||
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
||||||
guac_stream* stream;
|
guac_object* filesystem = client_data->sftp_filesystem;
|
||||||
LIBSSH2_SFTP_HANDLE* file;
|
|
||||||
|
|
||||||
/* Attempt to open file for reading */
|
/* Initiate download of requested file */
|
||||||
file = libssh2_sftp_open(client_data->sftp_session, filename,
|
return guac_common_ssh_sftp_download_file(filesystem, filename);
|
||||||
LIBSSH2_FXF_READ, 0);
|
|
||||||
if (file == NULL) {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\": %s",
|
|
||||||
filename,
|
|
||||||
libssh2_sftp_last_error(client_data->sftp_session));
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate stream */
|
|
||||||
stream = guac_client_alloc_stream(client);
|
|
||||||
stream->ack_handler = guac_sftp_ack_handler;
|
|
||||||
stream->data = file;
|
|
||||||
|
|
||||||
/* Send stream start, strip name */
|
|
||||||
filename = basename(filename);
|
|
||||||
guac_protocol_send_file(client->socket, stream,
|
|
||||||
"application/octet-stream", filename);
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Sending file \"%s\"", filename);
|
|
||||||
return stream;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_sftp_set_upload_path(guac_client* client, char* path) {
|
void guac_sftp_set_upload_path(guac_client* client, char* path) {
|
||||||
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
||||||
int length = strnlen(path, GUAC_SFTP_MAX_PATH);
|
guac_object* filesystem = client_data->sftp_filesystem;
|
||||||
|
|
||||||
/* Ignore requests which exceed maximum-allowed path */
|
/* Set upload path as specified */
|
||||||
if (length > GUAC_SFTP_MAX_PATH) {
|
guac_common_ssh_sftp_set_upload_path(filesystem, path);
|
||||||
guac_client_log(client, GUAC_LOG_ERROR,
|
|
||||||
"Submitted path exceeds limit of %i bytes",
|
|
||||||
GUAC_SFTP_MAX_PATH);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy path */
|
|
||||||
memcpy(client_data->sftp_upload_path, path, length);
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Upload path set to \"%s\"", path);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guac_object* guac_sftp_expose_filesystem(guac_client* client) {
|
|
||||||
|
|
||||||
/* Init filesystem */
|
|
||||||
guac_object* filesystem = guac_client_alloc_object(client);
|
|
||||||
filesystem->get_handler = guac_sftp_get_handler;
|
|
||||||
filesystem->put_handler = guac_sftp_put_handler;
|
|
||||||
|
|
||||||
/* Send filesystem to client */
|
|
||||||
guac_protocol_send_filesystem(client->socket, filesystem, "/");
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
|
|
||||||
/* Return allocated filesystem */
|
|
||||||
return filesystem;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_sftp_ls_ack_handler(guac_client* client, guac_stream* stream,
|
|
||||||
char* message, guac_protocol_status status) {
|
|
||||||
|
|
||||||
int bytes_read;
|
|
||||||
bool blob_written = false;
|
|
||||||
|
|
||||||
char filename[GUAC_SFTP_MAX_PATH];
|
|
||||||
LIBSSH2_SFTP_ATTRIBUTES attributes;
|
|
||||||
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
|
||||||
LIBSSH2_SFTP* sftp = client_data->sftp_session;
|
|
||||||
guac_sftp_ls_state* list_state = (guac_sftp_ls_state*) stream->data;
|
|
||||||
|
|
||||||
/* If unsuccessful, free stream and abort */
|
|
||||||
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
|
|
||||||
libssh2_sftp_closedir(list_state->directory);
|
|
||||||
guac_client_free_stream(client, stream);
|
|
||||||
free(list_state);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* While directory entries remain */
|
|
||||||
while ((bytes_read = libssh2_sftp_readdir(list_state->directory,
|
|
||||||
filename, sizeof(filename), &attributes)) > 0
|
|
||||||
&& !blob_written) {
|
|
||||||
|
|
||||||
char absolute_path[GUAC_SFTP_MAX_PATH];
|
|
||||||
|
|
||||||
/* Skip current and parent directory entries */
|
|
||||||
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Concatenate into absolute path - skip if invalid */
|
|
||||||
if (!guac_ssh_append_filename(absolute_path,
|
|
||||||
list_state->directory_name, filename)) {
|
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
||||||
"Skipping filename \"%s\" - filename is invalid or "
|
|
||||||
"resulting path is too long", filename);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stat explicitly if symbolic link (might point to directory) */
|
|
||||||
if (LIBSSH2_SFTP_S_ISLNK(attributes.permissions))
|
|
||||||
libssh2_sftp_stat(sftp, absolute_path, &attributes);
|
|
||||||
|
|
||||||
/* Determine mimetype */
|
|
||||||
const char* mimetype;
|
|
||||||
if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions))
|
|
||||||
mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE;
|
|
||||||
else
|
|
||||||
mimetype = "application/octet-stream";
|
|
||||||
|
|
||||||
/* Write entry */
|
|
||||||
blob_written |= guac_common_json_write_property(client, stream,
|
|
||||||
&list_state->json_state, absolute_path, mimetype);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Complete JSON and cleanup at end of directory */
|
|
||||||
if (bytes_read <= 0) {
|
|
||||||
|
|
||||||
/* Complete JSON object */
|
|
||||||
guac_common_json_end_object(client, stream, &list_state->json_state);
|
|
||||||
guac_common_json_flush(client, stream, &list_state->json_state);
|
|
||||||
|
|
||||||
/* Clean up resources */
|
|
||||||
libssh2_sftp_closedir(list_state->directory);
|
|
||||||
free(list_state);
|
|
||||||
|
|
||||||
/* Signal of stream */
|
|
||||||
guac_protocol_send_end(client->socket, stream);
|
|
||||||
guac_client_free_stream(client, stream);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_sftp_get_handler(guac_client* client, guac_object* object,
|
|
||||||
char* name) {
|
|
||||||
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
|
||||||
LIBSSH2_SFTP* sftp = client_data->sftp_session;
|
|
||||||
LIBSSH2_SFTP_ATTRIBUTES attributes;
|
|
||||||
|
|
||||||
/* Attempt to read file information */
|
|
||||||
if (libssh2_sftp_stat(sftp, name, &attributes)) {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"",
|
|
||||||
name);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If directory, send contents of directory */
|
|
||||||
if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) {
|
|
||||||
|
|
||||||
/* Open as directory */
|
|
||||||
LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, name);
|
|
||||||
if (dir == NULL) {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO,
|
|
||||||
"Unable to read directory \"%s\": %s",
|
|
||||||
name, libssh2_sftp_last_error(sftp));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Init directory listing state */
|
|
||||||
guac_sftp_ls_state* list_state = malloc(sizeof(guac_sftp_ls_state));
|
|
||||||
|
|
||||||
list_state->directory = dir;
|
|
||||||
strncpy(list_state->directory_name, name,
|
|
||||||
sizeof(list_state->directory_name));
|
|
||||||
|
|
||||||
/* Allocate stream for body */
|
|
||||||
guac_stream* stream = guac_client_alloc_stream(client);
|
|
||||||
stream->ack_handler = guac_sftp_ls_ack_handler;
|
|
||||||
stream->data = list_state;
|
|
||||||
|
|
||||||
/* Init JSON object state */
|
|
||||||
guac_common_json_begin_object(client, stream, &list_state->json_state);
|
|
||||||
|
|
||||||
/* Associate new stream with get request */
|
|
||||||
guac_protocol_send_body(client->socket, object, stream,
|
|
||||||
GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Otherwise, send file contents */
|
|
||||||
else {
|
|
||||||
|
|
||||||
/* Open as normal file */
|
|
||||||
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
|
|
||||||
LIBSSH2_FXF_READ, 0);
|
|
||||||
if (file == NULL) {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO,
|
|
||||||
"Unable to read file \"%s\": %s",
|
|
||||||
name, libssh2_sftp_last_error(sftp));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate stream for body */
|
|
||||||
guac_stream* stream = guac_client_alloc_stream(client);
|
|
||||||
stream->ack_handler = guac_sftp_ack_handler;
|
|
||||||
stream->data = file;
|
|
||||||
|
|
||||||
/* Associate new stream with get request */
|
|
||||||
guac_protocol_send_body(client->socket, object, stream,
|
|
||||||
"application/octet-stream", name);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_sftp_put_handler(guac_client* client, guac_object* object,
|
|
||||||
guac_stream* stream, char* mimetype, char* name) {
|
|
||||||
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
|
||||||
LIBSSH2_SFTP* sftp = client_data->sftp_session;
|
|
||||||
|
|
||||||
/* Open file via SFTP */
|
|
||||||
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
|
|
||||||
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
|
|
||||||
S_IRUSR | S_IWUSR);
|
|
||||||
|
|
||||||
/* Acknowledge stream if successful */
|
|
||||||
if (file != NULL) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "File \"%s\" opened", name);
|
|
||||||
guac_protocol_send_ack(client->socket, stream, "SFTP: File opened",
|
|
||||||
GUAC_PROTOCOL_STATUS_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Abort on failure */
|
|
||||||
else {
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s",
|
|
||||||
name, libssh2_sftp_last_error(sftp));
|
|
||||||
guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed",
|
|
||||||
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set handlers for file stream */
|
|
||||||
stream->blob_handler = guac_sftp_blob_handler;
|
|
||||||
stream->end_handler = guac_sftp_end_handler;
|
|
||||||
|
|
||||||
/* Store file within stream */
|
|
||||||
stream->data = file;
|
|
||||||
|
|
||||||
guac_socket_flush(client->socket);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -26,161 +26,65 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "guac_json.h"
|
|
||||||
|
|
||||||
#include <libssh2.h>
|
|
||||||
#include <libssh2_sftp.h>
|
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <guacamole/object.h>
|
|
||||||
#include <guacamole/protocol.h>
|
|
||||||
#include <guacamole/stream.h>
|
#include <guacamole/stream.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of bytes per path.
|
* Handles an incoming stream from a Guacamole "file" instruction, saving the
|
||||||
*/
|
* contents of that stream to the file having the given name within the
|
||||||
#define GUAC_SFTP_MAX_PATH 2048
|
* upload directory set by guac_sftp_set_upload_path().
|
||||||
|
*
|
||||||
/**
|
* @param client
|
||||||
* The current state of a directory listing operation.
|
* The client receiving the uploaded file.
|
||||||
*/
|
*
|
||||||
typedef struct guac_sftp_ls_state {
|
* @param stream
|
||||||
|
* The stream through which the uploaded file data will be received.
|
||||||
/**
|
*
|
||||||
* Reference to the directory currently being listed over SFTP. This
|
* @param mimetype
|
||||||
* directory must already be open from a call to libssh2_sftp_opendir().
|
* The mimetype of the data being received.
|
||||||
*/
|
*
|
||||||
LIBSSH2_SFTP_HANDLE* directory;
|
* @param filename
|
||||||
|
* The filename of the file to write to. This filename will always be taken
|
||||||
/**
|
* relative to the upload path set by
|
||||||
* The absolute path of the directory being listed.
|
* guac_common_ssh_sftp_set_upload_path().
|
||||||
*/
|
*
|
||||||
char directory_name[GUAC_SFTP_MAX_PATH];
|
* @return
|
||||||
|
* Zero if the incoming stream has been handled successfully, non-zero on
|
||||||
/**
|
* failure.
|
||||||
* The current state of the JSON directory object being written.
|
|
||||||
*/
|
|
||||||
guac_common_json_state json_state;
|
|
||||||
|
|
||||||
} guac_sftp_ls_state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for file messages which begins an SFTP data transfer (upload).
|
|
||||||
*/
|
*/
|
||||||
int guac_sftp_file_handler(guac_client* client, guac_stream* stream,
|
int guac_sftp_file_handler(guac_client* client, guac_stream* stream,
|
||||||
char* mimetype, char* filename);
|
char* mimetype, char* filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for blob messages which continues an SFTP data transfer (upload).
|
* Initiates an SFTP file download to the user via the Guacamole "file"
|
||||||
*/
|
* instruction. The download will be automatically monitored and continued
|
||||||
int guac_sftp_blob_handler(guac_client* client, guac_stream* stream,
|
* after this function terminates in response to "ack" instructions received by
|
||||||
void* data, int length);
|
* the client.
|
||||||
|
*
|
||||||
/**
|
* @param client
|
||||||
* Handler for end messages which ends an SFTP data transfer (upload).
|
* The client receiving the file.
|
||||||
*/
|
*
|
||||||
int guac_sftp_end_handler(guac_client* client, guac_stream* stream);
|
* @param filename
|
||||||
|
* The filename of the file to download, relative to the given filesystem.
|
||||||
/**
|
*
|
||||||
* Handler for ack messages which continues an SFTP download.
|
* @return
|
||||||
*/
|
* The file stream created for the file download, already configured to
|
||||||
int guac_sftp_ack_handler(guac_client* client, guac_stream* stream,
|
* properly handle "ack" responses, etc. from the client.
|
||||||
char* message, guac_protocol_status status);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins (and automatically continues) an SFTP file download to the user.
|
|
||||||
*/
|
*/
|
||||||
guac_stream* guac_sftp_download_file(guac_client* client, char* filename);
|
guac_stream* guac_sftp_download_file(guac_client* client, char* filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the destination directory for future uploads.
|
* Sets the destination directory for future uploads submitted via Guacamole
|
||||||
|
* "file" instruction. This function has no bearing on the destination
|
||||||
|
* directories of files uploaded with "put" instructions.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The client setting the upload path.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* The path to use for future uploads submitted via "file" instruction.
|
||||||
*/
|
*/
|
||||||
void guac_sftp_set_upload_path(guac_client* client, char* path);
|
void guac_sftp_set_upload_path(guac_client* client, char* path);
|
||||||
|
|
||||||
/**
|
|
||||||
* Exposes access to SFTP via a filesystem object, returning that object. The
|
|
||||||
* object returned must eventually be explicitly freed through a call to
|
|
||||||
* guac_client_free_object().
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The Guacamole client to expose the filesystem to.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The resulting Guacamole filesystem object, initialized and exposed to
|
|
||||||
* the client.
|
|
||||||
*/
|
|
||||||
guac_object* guac_sftp_expose_filesystem(guac_client* client);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for get messages. In context of SFTP and the filesystem exposed via
|
|
||||||
* the Guacamole protocol, get messages request the body of a file within the
|
|
||||||
* filesystem.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The client receiving the get message.
|
|
||||||
*
|
|
||||||
* @param object
|
|
||||||
* The Guacamole protocol object associated with the get request itself.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* The name of the input stream (file) being requested.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero on success, non-zero on error.
|
|
||||||
*/
|
|
||||||
int guac_sftp_get_handler(guac_client* client, guac_object* object,
|
|
||||||
char* name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for put messages. In context of SFTP and the filesystem exposed via
|
|
||||||
* the Guacamole protocol, put messages request write access to a file within
|
|
||||||
* the filesystem.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The client receiving the put message.
|
|
||||||
*
|
|
||||||
* @param object
|
|
||||||
* The Guacamole protocol object associated with the put request itself.
|
|
||||||
*
|
|
||||||
* @param stream
|
|
||||||
* The Guacamole protocol stream along which the client will be sending
|
|
||||||
* file data.
|
|
||||||
*
|
|
||||||
* @param mimetype
|
|
||||||
* The mimetype of the data being send along the stream.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* The name of the input stream (file) being requested.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero on success, non-zero on error.
|
|
||||||
*/
|
|
||||||
int guac_sftp_put_handler(guac_client* client, guac_object* object,
|
|
||||||
guac_stream* stream, char* mimetype, char* name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for ack messages received due to receipt of a "body" or "blob"
|
|
||||||
* instruction associated with a SFTP directory list operation.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The client receiving the ack message.
|
|
||||||
*
|
|
||||||
* @param stream
|
|
||||||
* The Guacamole protocol stream associated with the received ack message.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* An arbitrary human-readable message describing the nature of the
|
|
||||||
* success or failure denoted by this ack message.
|
|
||||||
*
|
|
||||||
* @param status
|
|
||||||
* The status code associated with this ack message, which may indicate
|
|
||||||
* success or an error.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero on success, non-zero on error.
|
|
||||||
*/
|
|
||||||
int guac_sftp_ls_ack_handler(guac_client* client, guac_stream* stream,
|
|
||||||
char* message, guac_protocol_status status);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
#include "guac_sftp.h"
|
||||||
|
#include "guac_ssh.h"
|
||||||
#include "sftp.h"
|
#include "sftp.h"
|
||||||
#include "ssh_key.h"
|
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
|
|
||||||
#ifdef ENABLE_SSH_AGENT
|
#ifdef ENABLE_SSH_AGENT
|
||||||
@ -56,6 +57,96 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a new user object containing a username and password or private
|
||||||
|
* key, prompting the user as necessary to obtain that information.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The Guacamole client containing any existing user data, as well as the
|
||||||
|
* terminal to use when prompting the user.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A new user object containing the user's username and other credentials.
|
||||||
|
*/
|
||||||
|
static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) {
|
||||||
|
|
||||||
|
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
||||||
|
|
||||||
|
guac_common_ssh_user* user;
|
||||||
|
|
||||||
|
/* Get username */
|
||||||
|
if (client_data->username[0] == 0)
|
||||||
|
guac_terminal_prompt(client_data->term, "Login as: ",
|
||||||
|
client_data->username, sizeof(client_data->username), true);
|
||||||
|
|
||||||
|
/* Create user object from username */
|
||||||
|
user = guac_common_ssh_create_user(client_data->username);
|
||||||
|
|
||||||
|
/* If key specified, import */
|
||||||
|
if (client_data->key_base64[0] != 0) {
|
||||||
|
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||||
|
"Attempting private key import (WITHOUT passphrase)");
|
||||||
|
|
||||||
|
/* Attempt to read key without passphrase */
|
||||||
|
if (guac_common_ssh_user_import_key(user,
|
||||||
|
client_data->key_base64, NULL)) {
|
||||||
|
|
||||||
|
/* Log failure of initial attempt */
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||||
|
"Initial import failed: %s",
|
||||||
|
guac_common_ssh_key_error());
|
||||||
|
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||||
|
"Re-attempting private key import (WITH passphrase)");
|
||||||
|
|
||||||
|
/* Prompt for passphrase if missing */
|
||||||
|
if (client_data->key_passphrase[0] == 0)
|
||||||
|
guac_terminal_prompt(client_data->term, "Key passphrase: ",
|
||||||
|
client_data->key_passphrase,
|
||||||
|
sizeof(client_data->key_passphrase), false);
|
||||||
|
|
||||||
|
/* Reattempt import with passphrase */
|
||||||
|
if (guac_common_ssh_user_import_key(user,
|
||||||
|
client_data->key_base64,
|
||||||
|
client_data->key_passphrase)) {
|
||||||
|
|
||||||
|
/* If still failing, give up */
|
||||||
|
guac_client_abort(client,
|
||||||
|
GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
|
||||||
|
"Auth key import failed: %s",
|
||||||
|
guac_common_ssh_key_error());
|
||||||
|
|
||||||
|
guac_common_ssh_destroy_user(user);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end decrypt key with passphrase */
|
||||||
|
|
||||||
|
/* Success */
|
||||||
|
guac_client_log(client, GUAC_LOG_INFO,
|
||||||
|
"Auth key successfully imported.");
|
||||||
|
|
||||||
|
} /* end if key given */
|
||||||
|
|
||||||
|
/* Otherwise, get password if not provided */
|
||||||
|
else if (client_data->password[0] == 0) {
|
||||||
|
|
||||||
|
guac_terminal_prompt(client_data->term, "Password: ",
|
||||||
|
client_data->password, sizeof(client_data->password), false);
|
||||||
|
|
||||||
|
guac_common_ssh_user_set_password(user, client_data->password);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear screen of any prompts */
|
||||||
|
guac_terminal_printf(client_data->term, "\x1B[H\x1B[J");
|
||||||
|
|
||||||
|
return user;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void* ssh_input_thread(void* data) {
|
void* ssh_input_thread(void* data) {
|
||||||
|
|
||||||
guac_client* client = (guac_client*) data;
|
guac_client* client = (guac_client*) data;
|
||||||
@ -75,363 +166,45 @@ void* ssh_input_thread(void* data) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __sign_callback(LIBSSH2_SESSION* session,
|
|
||||||
unsigned char** sig, size_t* sig_len,
|
|
||||||
const unsigned char* data, size_t data_len, void **abstract) {
|
|
||||||
|
|
||||||
ssh_key* key = (ssh_key*) abstract;
|
|
||||||
int length;
|
|
||||||
|
|
||||||
/* Allocate space for signature */
|
|
||||||
*sig = malloc(4096);
|
|
||||||
|
|
||||||
/* Sign with key */
|
|
||||||
length = ssh_key_sign(key, (const char*) data, data_len, *sig);
|
|
||||||
if (length < 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
*sig_len = length;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for the keyboard-interactive authentication method. Currently
|
|
||||||
* suports just one prompt for the password.
|
|
||||||
*/
|
|
||||||
static void __kbd_callback(const char *name, int name_len,
|
|
||||||
const char *instruction, int instruction_len,
|
|
||||||
int num_prompts,
|
|
||||||
const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
|
|
||||||
LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
|
|
||||||
void **abstract) {
|
|
||||||
|
|
||||||
guac_client* client = (guac_client*) *abstract;
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
|
||||||
|
|
||||||
if (num_prompts == 1) {
|
|
||||||
responses[0].text = strdup(client_data->password);
|
|
||||||
responses[0].length = strlen(client_data->password);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
guac_client_log(client, GUAC_LOG_WARNING,
|
|
||||||
"Unsupported number of keyboard-interactive prompts: %i",
|
|
||||||
num_prompts);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static LIBSSH2_SESSION* __guac_ssh_create_session(guac_client* client,
|
|
||||||
int* socket_fd) {
|
|
||||||
|
|
||||||
int retval;
|
|
||||||
|
|
||||||
int fd;
|
|
||||||
struct addrinfo* addresses;
|
|
||||||
struct addrinfo* current_address;
|
|
||||||
|
|
||||||
char connected_address[1024];
|
|
||||||
char connected_port[64];
|
|
||||||
char *user_authlist;
|
|
||||||
|
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
|
||||||
|
|
||||||
struct addrinfo hints = {
|
|
||||||
.ai_family = AF_UNSPEC,
|
|
||||||
.ai_socktype = SOCK_STREAM,
|
|
||||||
.ai_protocol = IPPROTO_TCP
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Get socket */
|
|
||||||
fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
|
|
||||||
/* Get addresses connection */
|
|
||||||
if ((retval = getaddrinfo(client_data->hostname, client_data->port,
|
|
||||||
&hints, &addresses))) {
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error parsing given address or port: %s",
|
|
||||||
gai_strerror(retval));
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Attempt connection to each address until success */
|
|
||||||
current_address = addresses;
|
|
||||||
while (current_address != NULL) {
|
|
||||||
|
|
||||||
int retval;
|
|
||||||
|
|
||||||
/* Resolve hostname */
|
|
||||||
if ((retval = getnameinfo(current_address->ai_addr,
|
|
||||||
current_address->ai_addrlen,
|
|
||||||
connected_address, sizeof(connected_address),
|
|
||||||
connected_port, sizeof(connected_port),
|
|
||||||
NI_NUMERICHOST | NI_NUMERICSERV)))
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Unable to resolve host: %s", gai_strerror(retval));
|
|
||||||
|
|
||||||
/* Connect */
|
|
||||||
if (connect(fd, current_address->ai_addr,
|
|
||||||
current_address->ai_addrlen) == 0) {
|
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Successfully connected to "
|
|
||||||
"host %s, port %s", connected_address, connected_port);
|
|
||||||
|
|
||||||
/* Done if successful connect */
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Otherwise log information regarding bind failure */
|
|
||||||
else
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to "
|
|
||||||
"host %s, port %s: %s",
|
|
||||||
connected_address, connected_port, strerror(errno));
|
|
||||||
|
|
||||||
current_address = current_address->ai_next;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If unable to connect to anything, fail */
|
|
||||||
if (current_address == NULL) {
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to connect to any addresses.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free addrinfo */
|
|
||||||
freeaddrinfo(addresses);
|
|
||||||
|
|
||||||
/* Open SSH session */
|
|
||||||
LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL,
|
|
||||||
NULL, client);
|
|
||||||
if (session == NULL) {
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Session allocation failed.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Perform handshake */
|
|
||||||
if (libssh2_session_handshake(session, fd)) {
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "SSH handshake failed.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save file descriptor */
|
|
||||||
if (socket_fd != NULL)
|
|
||||||
*socket_fd = fd;
|
|
||||||
|
|
||||||
/* Get list of suported authentication methods */
|
|
||||||
user_authlist = libssh2_userauth_list(session, client_data->username, strlen(client_data->username));
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Supported authentication methods: %s", user_authlist);
|
|
||||||
|
|
||||||
/* Authenticate with key if available */
|
|
||||||
if (client_data->key != NULL) {
|
|
||||||
|
|
||||||
/* Check if public key auth is suported on the server */
|
|
||||||
if (strstr(user_authlist, "publickey") == NULL) {
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
|
|
||||||
"Public key authentication not suported");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!libssh2_userauth_publickey(session, client_data->username,
|
|
||||||
(unsigned char*) client_data->key->public_key,
|
|
||||||
client_data->key->public_key_length,
|
|
||||||
__sign_callback, (void**) client_data->key))
|
|
||||||
return session;
|
|
||||||
else {
|
|
||||||
char* error_message;
|
|
||||||
libssh2_session_last_error(session, &error_message, NULL, 0);
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
|
|
||||||
"Public key authentication failed: %s", error_message);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Authenticate with password */
|
|
||||||
if (strstr(user_authlist, "password") != NULL) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Using password authentication method");
|
|
||||||
retval = libssh2_userauth_password(session, client_data->username, client_data->password);
|
|
||||||
}
|
|
||||||
else if (strstr(user_authlist, "keyboard-interactive") != NULL) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Using keyboard-interactive authentication method");
|
|
||||||
retval = libssh2_userauth_keyboard_interactive(session, client_data->username, &__kbd_callback);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE, "No known authentication methods");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (retval == 0)
|
|
||||||
return session;
|
|
||||||
|
|
||||||
else {
|
|
||||||
char* error_message;
|
|
||||||
libssh2_session_last_error(session, &error_message, NULL, 0);
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
|
|
||||||
"Password authentication failed: %s", error_message);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef LIBSSH2_USES_GCRYPT
|
|
||||||
GCRY_THREAD_OPTION_PTHREAD_IMPL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of mutexes, used by OpenSSL.
|
|
||||||
*/
|
|
||||||
static pthread_mutex_t* __openssl_locks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by OpenSSL when locking or unlocking the Nth mutex.
|
|
||||||
*/
|
|
||||||
static void __openssl_locking_callback(int mode, int n, const char* file, int line){
|
|
||||||
if (mode & CRYPTO_LOCK)
|
|
||||||
pthread_mutex_lock(&(__openssl_locks[n]));
|
|
||||||
else if (mode & CRYPTO_UNLOCK)
|
|
||||||
pthread_mutex_unlock(&(__openssl_locks[n]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by OpenSSL when determining the current thread ID.
|
|
||||||
*/
|
|
||||||
static unsigned long __openssl_id_callback() {
|
|
||||||
return (unsigned long) pthread_self();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the given number of mutexes, such that OpenSSL will have at least
|
|
||||||
* this number of mutexes at its disposal.
|
|
||||||
*/
|
|
||||||
static void __openssl_init_locks(int count) {
|
|
||||||
|
|
||||||
int i;
|
|
||||||
|
|
||||||
__openssl_locks = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks());
|
|
||||||
|
|
||||||
for (i=0; i<count; i++)
|
|
||||||
pthread_mutex_init(&(__openssl_locks[i]), NULL);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees the given number of mutexes.
|
|
||||||
*/
|
|
||||||
static void __openssl_free_locks(int count) {
|
|
||||||
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i=0; i<count; i++)
|
|
||||||
pthread_mutex_destroy(&(__openssl_locks[i]));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ssh_client_thread(void* data) {
|
void* ssh_client_thread(void* data) {
|
||||||
|
|
||||||
guac_client* client = (guac_client*) data;
|
guac_client* client = (guac_client*) data;
|
||||||
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
|
||||||
|
|
||||||
char name[1024];
|
|
||||||
|
|
||||||
guac_socket* socket = client->socket;
|
guac_socket* socket = client->socket;
|
||||||
char buffer[8192];
|
char buffer[8192];
|
||||||
int bytes_read = -1234;
|
|
||||||
|
|
||||||
int socket_fd;
|
|
||||||
|
|
||||||
pthread_t input_thread;
|
pthread_t input_thread;
|
||||||
|
|
||||||
#ifdef LIBSSH2_USES_GCRYPT
|
/* Init SSH base libraries */
|
||||||
/* Init threadsafety in libgcrypt */
|
if (guac_common_ssh_init(client))
|
||||||
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
|
|
||||||
if (!gcry_check_version(GCRYPT_VERSION)) {
|
|
||||||
guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Init threadsafety in OpenSSL */
|
/* Get user and credentials */
|
||||||
__openssl_init_locks(CRYPTO_num_locks());
|
guac_common_ssh_user* user = guac_ssh_get_user(client);
|
||||||
CRYPTO_set_id_callback(__openssl_id_callback);
|
|
||||||
CRYPTO_set_locking_callback(__openssl_locking_callback);
|
|
||||||
|
|
||||||
/* Init OpenSSL */
|
|
||||||
SSL_library_init();
|
|
||||||
ERR_load_crypto_strings();
|
|
||||||
libssh2_init(0);
|
|
||||||
|
|
||||||
/* Get username */
|
|
||||||
if (client_data->username[0] == 0)
|
|
||||||
guac_terminal_prompt(client_data->term, "Login as: ",
|
|
||||||
client_data->username, sizeof(client_data->username), true);
|
|
||||||
|
|
||||||
/* Send new name */
|
/* Send new name */
|
||||||
snprintf(name, sizeof(name)-1, "%s@%s", client_data->username, client_data->hostname);
|
char name[1024];
|
||||||
|
snprintf(name, sizeof(name)-1, "%s@%s",
|
||||||
|
client_data->username, client_data->hostname);
|
||||||
guac_protocol_send_name(socket, name);
|
guac_protocol_send_name(socket, name);
|
||||||
|
|
||||||
/* If key specified, import */
|
|
||||||
if (client_data->key_base64[0] != 0) {
|
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
||||||
"Attempting private key import (WITHOUT passphrase)");
|
|
||||||
|
|
||||||
/* Attempt to read key without passphrase */
|
|
||||||
client_data->key = ssh_key_alloc(client_data->key_base64,
|
|
||||||
strlen(client_data->key_base64), "");
|
|
||||||
|
|
||||||
/* On failure, attempt with passphrase */
|
|
||||||
if (client_data->key == NULL) {
|
|
||||||
|
|
||||||
/* Log failure of initial attempt */
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
||||||
"Initial import failed: %s", ssh_key_error());
|
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
||||||
"Re-attempting private key import (WITH passphrase)");
|
|
||||||
|
|
||||||
/* Prompt for passphrase if missing */
|
|
||||||
if (client_data->key_passphrase[0] == 0)
|
|
||||||
guac_terminal_prompt(client_data->term, "Key passphrase: ",
|
|
||||||
client_data->key_passphrase, sizeof(client_data->key_passphrase), false);
|
|
||||||
|
|
||||||
/* Import key with passphrase */
|
|
||||||
client_data->key = ssh_key_alloc(client_data->key_base64,
|
|
||||||
strlen(client_data->key_base64),
|
|
||||||
client_data->key_passphrase);
|
|
||||||
|
|
||||||
/* If still failing, give up */
|
|
||||||
if (client_data->key == NULL) {
|
|
||||||
guac_client_abort(client,
|
|
||||||
GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
|
|
||||||
"Auth key import failed: %s", ssh_key_error());
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* end decrypt key with passphrase */
|
|
||||||
|
|
||||||
/* Success */
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Auth key successfully imported.");
|
|
||||||
|
|
||||||
} /* end if key given */
|
|
||||||
|
|
||||||
/* Otherwise, get password if not provided */
|
|
||||||
else if (client_data->password[0] == 0)
|
|
||||||
guac_terminal_prompt(client_data->term, "Password: ",
|
|
||||||
client_data->password, sizeof(client_data->password), false);
|
|
||||||
|
|
||||||
/* Clear screen */
|
|
||||||
guac_terminal_printf(client_data->term, "\x1B[H\x1B[J");
|
|
||||||
|
|
||||||
/* Open SSH session */
|
/* Open SSH session */
|
||||||
client_data->session = __guac_ssh_create_session(client, &socket_fd);
|
client_data->session = guac_common_ssh_create_session(client,
|
||||||
|
client_data->hostname, client_data->port, user);
|
||||||
if (client_data->session == NULL) {
|
if (client_data->session == NULL) {
|
||||||
/* Already aborted within __guac_ssh_create_session() */
|
/* Already aborted within guac_common_ssh_create_session() */
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_init(&client_data->term_channel_lock, NULL);
|
pthread_mutex_init(&client_data->term_channel_lock, NULL);
|
||||||
|
|
||||||
/* Open channel for terminal */
|
/* Open channel for terminal */
|
||||||
client_data->term_channel = libssh2_channel_open_session(client_data->session);
|
client_data->term_channel =
|
||||||
|
libssh2_channel_open_session(client_data->session->session);
|
||||||
if (client_data->term_channel == NULL) {
|
if (client_data->term_channel == NULL) {
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to open terminal channel.");
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
|
||||||
|
"Unable to open terminal channel.");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,30 +227,26 @@ void* ssh_client_thread(void* data) {
|
|||||||
/* Start SFTP session as well, if enabled */
|
/* Start SFTP session as well, if enabled */
|
||||||
if (client_data->enable_sftp) {
|
if (client_data->enable_sftp) {
|
||||||
|
|
||||||
/* Init handlers for Guacamole-specific console codes */
|
|
||||||
client_data->term->upload_path_handler = guac_sftp_set_upload_path;
|
|
||||||
client_data->term->file_download_handler = guac_sftp_download_file;
|
|
||||||
|
|
||||||
/* Create SSH session specific for SFTP */
|
/* Create SSH session specific for SFTP */
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP...");
|
guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP...");
|
||||||
client_data->sftp_ssh_session = __guac_ssh_create_session(client, NULL);
|
guac_common_ssh_session* sftp_ssh_session =
|
||||||
if (client_data->sftp_ssh_session == NULL) {
|
guac_common_ssh_create_session(client, client_data->hostname,
|
||||||
/* Already aborted within __guac_ssh_create_session() */
|
client_data->port, user);
|
||||||
|
if (sftp_ssh_session == NULL) {
|
||||||
|
/* Already aborted within guac_common_ssh_create_session() */
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Request SFTP */
|
/* Request SFTP */
|
||||||
client_data->sftp_session = libssh2_sftp_init(client_data->sftp_ssh_session);
|
client_data->sftp_filesystem =
|
||||||
if (client_data->sftp_session == NULL) {
|
guac_common_ssh_create_sftp_filesystem(sftp_ssh_session, "/");
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to start SFTP session.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set file handler */
|
/* Set generic (non-filesystem) file upload handler */
|
||||||
client->file_handler = guac_sftp_file_handler;
|
client->file_handler = guac_sftp_file_handler;
|
||||||
|
|
||||||
/* Expose filesystem */
|
/* Init handlers for Guacamole-specific console codes */
|
||||||
client_data->sftp_filesystem = guac_sftp_expose_filesystem(client);
|
client_data->term->upload_path_handler = guac_sftp_set_upload_path;
|
||||||
|
client_data->term->file_download_handler = guac_sftp_download_file;
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized");
|
guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized");
|
||||||
|
|
||||||
@ -506,10 +275,10 @@ void* ssh_client_thread(void* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Set non-blocking */
|
/* Set non-blocking */
|
||||||
libssh2_session_set_blocking(client_data->session, 0);
|
libssh2_session_set_blocking(client_data->session->session, 0);
|
||||||
|
|
||||||
/* While data available, write to terminal */
|
/* While data available, write to terminal */
|
||||||
bytes_read = 0;
|
int bytes_read = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
/* Track total amount of data read */
|
/* Track total amount of data read */
|
||||||
@ -558,13 +327,14 @@ void* ssh_client_thread(void* data) {
|
|||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
|
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
FD_SET(socket_fd, &fds);
|
FD_SET(client_data->session->fd, &fds);
|
||||||
|
|
||||||
/* Wait for one second */
|
/* Wait for one second */
|
||||||
timeout.tv_sec = 1;
|
timeout.tv_sec = 1;
|
||||||
timeout.tv_usec = 0;
|
timeout.tv_usec = 0;
|
||||||
|
|
||||||
if (select(socket_fd+1, &fds, NULL, NULL, &timeout) < 0)
|
if (select(client_data->session->fd + 1, &fds,
|
||||||
|
NULL, NULL, &timeout) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,7 +344,6 @@ void* ssh_client_thread(void* data) {
|
|||||||
guac_client_stop(client);
|
guac_client_stop(client);
|
||||||
pthread_join(input_thread, NULL);
|
pthread_join(input_thread, NULL);
|
||||||
|
|
||||||
__openssl_free_locks(CRYPTO_num_locks());
|
|
||||||
pthread_mutex_destroy(&client_data->term_channel_lock);
|
pthread_mutex_destroy(&client_data->term_channel_lock);
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended.");
|
guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended.");
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2014 Glyptodon LLC
|
# Copyright (C) 2015 Glyptodon LLC
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -21,7 +21,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
lib_LTLIBRARIES = libguac-client-telnet.la
|
lib_LTLIBRARIES = libguac-client-telnet.la
|
||||||
@ -38,7 +37,18 @@ noinst_HEADERS = \
|
|||||||
guac_handlers.h \
|
guac_handlers.h \
|
||||||
telnet_client.h
|
telnet_client.h
|
||||||
|
|
||||||
libguac_client_telnet_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@
|
libguac_client_telnet_la_CFLAGS = \
|
||||||
libguac_client_telnet_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@
|
-Werror -Wall -Iinclude \
|
||||||
libguac_client_telnet_la_LDFLAGS = -version-info 0:0:0 @TELNET_LIBS@ @PTHREAD_LIBS@
|
@LIBGUAC_INCLUDE@ \
|
||||||
|
@TERMINAL_INCLUDE@
|
||||||
|
|
||||||
|
libguac_client_telnet_la_LIBADD = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@LIBGUAC_LTLIB@ \
|
||||||
|
@TERMINAL_LTLIB@
|
||||||
|
|
||||||
|
libguac_client_telnet_la_LDFLAGS = \
|
||||||
|
-version-info 0:0:0 \
|
||||||
|
@PTHREAD_LIBS@ \
|
||||||
|
@TELNET_LIBS@
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2013 Glyptodon LLC
|
# Copyright (C) 2015 Glyptodon LLC
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -21,11 +21,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
AM_CFLAGS = -Werror -Wall -pedantic -Iinclude \
|
|
||||||
@COMMON_INCLUDE@ \
|
|
||||||
@LIBGUAC_INCLUDE@
|
|
||||||
|
|
||||||
lib_LTLIBRARIES = libguac-client-vnc.la
|
lib_LTLIBRARIES = libguac-client-vnc.la
|
||||||
|
|
||||||
@ -47,10 +43,18 @@ libguac_client_vnc_la_SOURCES += pulse.c
|
|||||||
noinst_HEADERS += pulse.h
|
noinst_HEADERS += pulse.h
|
||||||
endif
|
endif
|
||||||
|
|
||||||
libguac_client_vnc_la_LDFLAGS = -version-info 0:0:0 \
|
libguac_client_vnc_la_CFLAGS = \
|
||||||
|
-Werror -Wall -pedantic -Iinclude \
|
||||||
|
@COMMON_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
libguac_client_vnc_la_LDFLAGS = \
|
||||||
|
-version-info 0:0:0 \
|
||||||
@CAIRO_LIBS@ \
|
@CAIRO_LIBS@ \
|
||||||
@PULSE_LIBS@ \
|
@PULSE_LIBS@ \
|
||||||
@VNC_LIBS@
|
@VNC_LIBS@
|
||||||
|
|
||||||
libguac_client_vnc_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
libguac_client_vnc_la_LIBADD = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2013 Glyptodon LLC
|
# Copyright (C) 2015 Glyptodon LLC
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
AM_CFLAGS = -Werror -Wall -pedantic @PANGO_CFLAGS@ @PANGOCAIRO_CFLAGS@ @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
|
|
||||||
|
|
||||||
noinst_LTLIBRARIES = libguac_terminal.la
|
noinst_LTLIBRARIES = libguac_terminal.la
|
||||||
|
|
||||||
@ -55,6 +54,20 @@ libguac_terminal_la_SOURCES = \
|
|||||||
terminal.c \
|
terminal.c \
|
||||||
terminal_handlers.c
|
terminal_handlers.c
|
||||||
|
|
||||||
libguac_terminal_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@
|
libguac_terminal_la_CFLAGS = \
|
||||||
libguac_terminal_la_LDFLAGS = @PTHREAD_LIBS@ @PANGO_LIBS@ @PANGOCAIRO_LIBS@ @CAIRO_LIBS@ @MATH_LIBS@
|
-Werror -Wall -pedantic \
|
||||||
|
@COMMON_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@ \
|
||||||
|
@PANGO_CFLAGS@ \
|
||||||
|
@PANGOCAIRO_CFLAGS@
|
||||||
|
|
||||||
|
libguac_terminal_la_LIBADD = \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
libguac_terminal_la_LDFLAGS = \
|
||||||
|
@CAIRO_LIBS@ \
|
||||||
|
@MATH_LIBS@ \
|
||||||
|
@PANGO_LIBS@ \
|
||||||
|
@PANGOCAIRO_LIBS@ \
|
||||||
|
@PTHREAD_LIBS@
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2013 Glyptodon LLC
|
# Copyright (C) 2015 Glyptodon LLC
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@
|
|
||||||
|
|
||||||
TESTS = test_libguac
|
TESTS = test_libguac
|
||||||
check_PROGRAMS = test_libguac
|
check_PROGRAMS = test_libguac
|
||||||
@ -51,5 +50,13 @@ test_libguac_SOURCES = \
|
|||||||
util/guac_pool.c \
|
util/guac_pool.c \
|
||||||
util/guac_unicode.c
|
util/guac_unicode.c
|
||||||
|
|
||||||
test_libguac_LDADD = @LIBGUAC_LTLIB@ @CUNIT_LIBS@ @COMMON_LTLIB@
|
test_libguac_CFLAGS = \
|
||||||
|
-Werror -Wall -pedantic \
|
||||||
|
@COMMON_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
test_libguac_LDADD = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@CUNIT_LIBS@ \
|
||||||
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user