GUAC-1389: Add screen sharing support to SSH.

This commit is contained in:
Michael Jumper 2016-02-29 21:51:42 -08:00
parent a236e92444
commit 075b7ffba9
17 changed files with 1020 additions and 609 deletions

View File

@ -54,7 +54,7 @@ SUBDIRS += src/protocols/rdp
endif endif
if ENABLE_SSH if ENABLE_SSH
#SUBDIRS += src/protocols/ssh SUBDIRS += src/protocols/ssh
endif endif
if ENABLE_TELNET if ENABLE_TELNET

View File

@ -28,16 +28,20 @@ lib_LTLIBRARIES = libguac-client-ssh.la
libguac_client_ssh_la_SOURCES = \ libguac_client_ssh_la_SOURCES = \
client.c \ client.c \
clipboard.c \ clipboard.c \
guac_handlers.c \ input.c \
settings.c \
sftp.c \ sftp.c \
ssh_client.c ssh.c \
user.c
noinst_HEADERS = \ noinst_HEADERS = \
client.h \ client.h \
clipboard.h \ clipboard.h \
guac_handlers.h \ input.h \
settings.h \
sftp.h \ sftp.h \
ssh_client.h ssh.h \
user.h
# Add agent sources if enabled # Add agent sources if enabled
if ENABLE_SSH_AGENT if ENABLE_SSH_AGENT

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2016 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
@ -23,246 +23,37 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "clipboard.h" #include "guac_sftp.h"
#include "guac_handlers.h" #include "ssh.h"
#include "ssh_client.h"
#include "terminal.h" #include "terminal.h"
#include "user.h"
#include <langinfo.h> #include <langinfo.h>
#include <locale.h> #include <locale.h>
#include <pthread.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#define GUAC_SSH_DEFAULT_FONT_NAME "monospace" int guac_client_init(guac_client* client) {
#define GUAC_SSH_DEFAULT_FONT_SIZE 12
#define GUAC_SSH_DEFAULT_PORT "22"
/* Client plugin arguments */ /* Set client args */
const char* GUAC_CLIENT_ARGS[] = { client->args = GUAC_SSH_CLIENT_ARGS;
"hostname",
"port",
"username",
"password",
"font-name",
"font-size",
"enable-sftp",
"private-key",
"passphrase",
#ifdef ENABLE_SSH_AGENT
"enable-agent",
#endif
"color-scheme",
"command",
"typescript-path",
"typescript-name",
"create-typescript-path",
NULL
};
enum __SSH_ARGS_IDX { /* Allocate client instance data */
guac_ssh_client* ssh_client = calloc(1, sizeof(guac_ssh_client));
client->data = ssh_client;
/** /* Set handlers */
* The hostname to connect to. Required. client->join_handler = guac_ssh_user_join_handler;
*/ client->free_handler = guac_ssh_client_free_handler;
IDX_HOSTNAME,
/**
* The port to connect to. Optional.
*/
IDX_PORT,
/**
* The name of the user to login as. Optional.
*/
IDX_USERNAME,
/**
* The password to use when logging in. Optional.
*/
IDX_PASSWORD,
/**
* The name of the font to use within the terminal.
*/
IDX_FONT_NAME,
/**
* The size of the font to use within the terminal, in points.
*/
IDX_FONT_SIZE,
/**
* Whether SFTP should be enabled.
*/
IDX_ENABLE_SFTP,
/**
* The private key to use for authentication, if any.
*/
IDX_PRIVATE_KEY,
/**
* The passphrase required to decrypt the private key, if any.
*/
IDX_PASSPHRASE,
#ifdef ENABLE_SSH_AGENT
/**
* Whether SSH agent forwarding support should be enabled.
*/
IDX_ENABLE_AGENT,
#endif
/**
* The name of the color scheme to use. Currently valid color schemes are:
* "black-white", "white-black", "gray-black", and "green-black", each
* following the "foreground-background" pattern. By default, this will be
* "gray-black".
*/
IDX_COLOR_SCHEME,
/**
* The command to run instead if the default shell. If omitted, a normal
* shell session will be created.
*/
IDX_COMMAND,
/**
* The full absolute path to the directory in which typescripts should be
* written.
*/
IDX_TYPESCRIPT_PATH,
/**
* The name that should be given to typescripts which are written in the
* given path. Each typescript will consist of two files: "NAME" and
* "NAME.timing".
*/
IDX_TYPESCRIPT_NAME,
/**
* Whether the specified typescript path should automatically be created
* if it does not yet exist.
*/
IDX_CREATE_TYPESCRIPT_PATH,
SSH_ARGS_COUNT
};
int guac_client_init(guac_client* client, int argc, char** argv) {
guac_socket* socket = client->socket;
ssh_guac_client_data* client_data = calloc(1, sizeof(ssh_guac_client_data));
/* Init client data */
client->data = client_data;
if (argc != SSH_ARGS_COUNT) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Wrong number of arguments");
return -1;
}
/* Set locale and warn if not UTF-8 */ /* Set locale and warn if not UTF-8 */
setlocale(LC_CTYPE, ""); setlocale(LC_CTYPE, "");
if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) {
guac_client_log(client, GUAC_LOG_INFO, "Current locale does not use UTF-8. Some characters may not render correctly."); guac_client_log(client, GUAC_LOG_INFO,
"Current locale does not use UTF-8. Some characters may "
/* Read parameters */ "not render correctly.");
strcpy(client_data->hostname, argv[IDX_HOSTNAME]);
strcpy(client_data->username, argv[IDX_USERNAME]);
strcpy(client_data->password, argv[IDX_PASSWORD]);
/* Init public key auth information */
strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]);
strcpy(client_data->key_passphrase, argv[IDX_PASSPHRASE]);
/* Read font name */
if (argv[IDX_FONT_NAME][0] != 0)
strcpy(client_data->font_name, argv[IDX_FONT_NAME]);
else
strcpy(client_data->font_name, GUAC_SSH_DEFAULT_FONT_NAME );
/* Read font size */
if (argv[IDX_FONT_SIZE][0] != 0)
client_data->font_size = atoi(argv[IDX_FONT_SIZE]);
else
client_data->font_size = GUAC_SSH_DEFAULT_FONT_SIZE;
/* Parse SFTP enable */
client_data->enable_sftp = strcmp(argv[IDX_ENABLE_SFTP], "true") == 0;
#ifdef ENABLE_SSH_AGENT
client_data->enable_agent = strcmp(argv[IDX_ENABLE_AGENT], "true") == 0;
#endif
/* Read port */
if (argv[IDX_PORT][0] != 0)
strcpy(client_data->port, argv[IDX_PORT]);
else
strcpy(client_data->port, GUAC_SSH_DEFAULT_PORT);
/* Read command, if any */
if (argv[IDX_COMMAND][0] != 0)
client_data->command = strdup(argv[IDX_COMMAND]);
/* Create terminal */
client_data->term = guac_terminal_create(client,
client_data->font_name, client_data->font_size,
client->info.optimal_resolution,
client->info.optimal_width, client->info.optimal_height,
argv[IDX_COLOR_SCHEME]);
/* Fail if terminal init failed */
if (client_data->term == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Terminal initialization failed");
return -1;
}
/* Set up typescript, if requested */
const char* typescript_path = argv[IDX_TYPESCRIPT_PATH];
if (typescript_path[0] != 0) {
/* Default to "typescript" if no name provided */
const char* typescript_name = argv[IDX_TYPESCRIPT_NAME];
if (typescript_name[0] == 0)
typescript_name = "typescript";
/* Parse path creation flag */
int create_path =
strcmp(argv[IDX_CREATE_TYPESCRIPT_PATH], "true") == 0;
/* Create typescript */
guac_terminal_create_typescript(client_data->term, typescript_path,
typescript_name, create_path);
}
/* Ensure main socket is threadsafe */
guac_socket_require_threadsafe(socket);
/* Send initial name */
guac_protocol_send_name(socket, client_data->hostname);
guac_socket_flush(socket);
/* Set basic handlers */
client->handle_messages = ssh_guac_client_handle_messages;
client->key_handler = ssh_guac_client_key_handler;
client->mouse_handler = ssh_guac_client_mouse_handler;
client->size_handler = ssh_guac_client_size_handler;
client->free_handler = ssh_guac_client_free_handler;
client->clipboard_handler = guac_ssh_clipboard_handler;
/* Start client thread */
if (pthread_create(&(client_data->client_thread), NULL, ssh_client_thread, (void*) client)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start SSH client thread");
return -1;
} }
/* Success */ /* Success */
@ -270,3 +61,48 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
} }
int guac_ssh_client_free_handler(guac_client* client) {
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
/* Close SSH channel */
if (ssh_client->term_channel != NULL) {
libssh2_channel_send_eof(ssh_client->term_channel);
libssh2_channel_close(ssh_client->term_channel);
}
/* Free terminal (which may still be using term_channel) */
if (ssh_client->term != NULL) {
guac_terminal_free(ssh_client->term);
pthread_join(ssh_client->client_thread, NULL);
}
/* Free terminal channel now that the terminal is finished */
if (ssh_client->term_channel != NULL)
libssh2_channel_free(ssh_client->term_channel);
/* Clean up the SFTP filesystem object and session */
if (ssh_client->sftp_filesystem) {
guac_common_ssh_destroy_sftp_filesystem(ssh_client->sftp_filesystem);
guac_common_ssh_destroy_session(ssh_client->sftp_session);
}
/* Free interactive SSH session */
if (ssh_client->session != NULL)
guac_common_ssh_destroy_session(ssh_client->session);
/* Free SSH client credentials */
if (ssh_client->user != NULL)
guac_common_ssh_destroy_user(ssh_client->user);
/* Free parsed settings */
if (ssh_client->settings != NULL)
guac_ssh_settings_free(ssh_client->settings);
/* Free client structure */
free(ssh_client);
guac_common_ssh_uninit();
return 0;
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2016 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,138 +20,24 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_SSH_CLIENT_H
#define GUAC_SSH_CLIENT_H
#ifndef _SSH_GUAC_CLIENT_H #include <guacamole/client.h>
#define _SSH_GUAC_CLIENT_H
#include "config.h"
#include "guac_ssh.h"
#include "guac_ssh_user.h"
#include "sftp.h"
#include "terminal.h"
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <guacamole/object.h>
#ifdef ENABLE_SSH_AGENT
#include "ssh_agent.h"
#endif
#include <pthread.h>
#include <stdbool.h>
/** /**
* SSH-specific client data. * Handler which is invoked when the SSH client needs to be disconnected (if
* connected) and freed. This can happen if initialization fails, or all users
* have left the connection.
*
* @param client
* The client associated with the SSH client to be freed.
*
* @return
* Zero on success, non-zero if an error occurs preventing the client from
* being entirely freed.
*/ */
typedef struct ssh_guac_client_data { int guac_ssh_client_free_handler(guac_client* client);
/**
* The hostname of the SSH server to connect to.
*/
char hostname[1024];
/**
* The port of the SSH server to connect to.
*/
char port[64];
/**
* The name of the user to login as.
*/
char username[1024];
/**
* The password to give when authenticating.
*/
char password[1024];
/**
* The private key, encoded as base64.
*/
char key_base64[4096];
/**
* The password to use to decrypt the given private key.
*/
char key_passphrase[1024];
/**
* The command to run instead of the default shell. If a normal shell
* session is desired, this will be NULL.
*/
char* command;
/**
* The name of the font to use for display rendering.
*/
char font_name[1024];
/**
* The size of the font to use, in points.
*/
int font_size;
/**
* Whether SFTP is enabled.
*/
bool enable_sftp;
#ifdef ENABLE_SSH_AGENT
/**
* Whether the SSH agent is enabled.
*/
bool enable_agent;
/**
* The current agent, if any.
*/
ssh_auth_agent* auth_agent;
#endif
/**
* The SSH client thread.
*/
pthread_t client_thread;
/**
* The user and credentials to use for all SSH sessions.
*/
guac_common_ssh_user* user;
/**
* SSH session, used by the SSH client thread.
*/
guac_common_ssh_session* session;
/**
* SFTP session, used by the SFTP client/filesystem.
*/
guac_common_ssh_session* sftp_session;
/**
* The filesystem object exposed for the SFTP session.
*/
guac_object* sftp_filesystem;
/**
* SSH terminal channel, used by the SSH client thread.
*/
LIBSSH2_CHANNEL* term_channel;
/**
* Lock dictating access to the SSH terminal channel.
*/
pthread_mutex_t term_channel_lock;
/**
* The terminal which will render all output from the SSH client.
*/
guac_terminal* term;
} ssh_guac_client_data;
#endif #endif

View File

@ -21,19 +21,21 @@
*/ */
#include "config.h" #include "config.h"
#include "client.h"
#include "clipboard.h" #include "clipboard.h"
#include "ssh.h"
#include "terminal.h" #include "terminal.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/user.h>
int guac_ssh_clipboard_handler(guac_client* client, guac_stream* stream, int guac_ssh_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype) { char* mimetype) {
/* Clear clipboard and prepare for new data */ /* Clear clipboard and prepare for new data */
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; guac_client* client = user->client;
guac_terminal_clipboard_reset(client_data->term, mimetype); guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_terminal_clipboard_reset(ssh_client->term, mimetype);
/* Set handlers for clipboard stream */ /* Set handlers for clipboard stream */
stream->blob_handler = guac_ssh_clipboard_blob_handler; stream->blob_handler = guac_ssh_clipboard_blob_handler;
@ -42,17 +44,18 @@ int guac_ssh_clipboard_handler(guac_client* client, guac_stream* stream,
return 0; return 0;
} }
int guac_ssh_clipboard_blob_handler(guac_client* client, guac_stream* stream, int guac_ssh_clipboard_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) { void* data, int length) {
/* Append new data */ /* Append new data */
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; guac_client* client = user->client;
guac_terminal_clipboard_append(client_data->term, data, length); guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_terminal_clipboard_append(ssh_client->term, data, length);
return 0; return 0;
} }
int guac_ssh_clipboard_end_handler(guac_client* client, guac_stream* stream) { int guac_ssh_clipboard_end_handler(guac_user* user, guac_stream* stream) {
/* Nothing to do - clipboard is implemented within client */ /* Nothing to do - clipboard is implemented within client */

View File

@ -31,19 +31,19 @@
/** /**
* Handler for inbound clipboard data. * Handler for inbound clipboard data.
*/ */
int guac_ssh_clipboard_handler(guac_client* client, guac_stream* stream, int guac_ssh_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype); char* mimetype);
/** /**
* Handler for stream data related to clipboard. * Handler for stream data related to clipboard.
*/ */
int guac_ssh_clipboard_blob_handler(guac_client* client, guac_stream* stream, int guac_ssh_clipboard_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length); void* data, int length);
/** /**
* Handler for end-of-stream related to clipboard. * Handler for end-of-stream related to clipboard.
*/ */
int guac_ssh_clipboard_end_handler(guac_client* client, guac_stream* stream); int guac_ssh_clipboard_end_handler(guac_user* user, guac_stream* stream);
#endif #endif

View File

@ -1,128 +0,0 @@
/*
* Copyright (C) 2013 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 "config.h"
#include "client.h"
#include "guac_handlers.h"
#include "guac_sftp.h"
#include "guac_ssh.h"
#include "terminal.h"
#include <cairo/cairo.h>
#include <guacamole/client.h>
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <pthread.h>
#include <stdlib.h>
int ssh_guac_client_handle_messages(guac_client* client) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
return guac_terminal_render_frame(client_data->term);
}
int ssh_guac_client_mouse_handler(guac_client* client, int x, int y, int mask) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
guac_terminal* term = client_data->term;
/* Send mouse event */
guac_terminal_send_mouse(term, x, y, mask);
return 0;
}
int ssh_guac_client_key_handler(guac_client* client, int keysym, int pressed) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data;
guac_terminal* term = client_data->term;
/* Send key */
guac_terminal_send_key(term, keysym, pressed);
return 0;
}
int ssh_guac_client_size_handler(guac_client* client, int width, int height) {
/* Get terminal */
ssh_guac_client_data* guac_client_data = (ssh_guac_client_data*) client->data;
guac_terminal* terminal = guac_client_data->term;
/* Resize terminal */
guac_terminal_resize(terminal, width, height);
/* Update SSH pty size if connected */
if (guac_client_data->term_channel != NULL) {
pthread_mutex_lock(&(guac_client_data->term_channel_lock));
libssh2_channel_request_pty_size(guac_client_data->term_channel,
terminal->term_width, terminal->term_height);
pthread_mutex_unlock(&(guac_client_data->term_channel_lock));
}
return 0;
}
int ssh_guac_client_free_handler(guac_client* client) {
ssh_guac_client_data* guac_client_data = (ssh_guac_client_data*) client->data;
/* Close SSH channel */
if (guac_client_data->term_channel != NULL) {
libssh2_channel_send_eof(guac_client_data->term_channel);
libssh2_channel_close(guac_client_data->term_channel);
}
/* Free terminal */
guac_terminal_free(guac_client_data->term);
pthread_join(guac_client_data->client_thread, NULL);
/* Free channels */
libssh2_channel_free(guac_client_data->term_channel);
/* Clean up the SFTP filesystem object and session */
if (guac_client_data->sftp_filesystem) {
guac_common_ssh_destroy_sftp_filesystem(guac_client_data->sftp_filesystem);
guac_common_ssh_destroy_session(guac_client_data->sftp_session);
}
/* Free session */
if (guac_client_data->session != NULL)
guac_common_ssh_destroy_session(guac_client_data->session);
/* Free user */
if (guac_client_data->user != NULL)
guac_common_ssh_destroy_user(guac_client_data->user);
/* Free copied settings */
free(guac_client_data->command);
/* Free generic data struct */
free(client->data);
guac_common_ssh_uninit();
return 0;
}

90
src/protocols/ssh/input.c Normal file
View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2016 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 "config.h"
#include "guac_cursor.h"
#include "guac_display.h"
#include "ssh.h"
#include "terminal.h"
#include <guacamole/client.h>
#include <guacamole/user.h>
#include <libssh2.h>
#include <pthread.h>
int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask) {
guac_client* client = user->client;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_terminal* term = ssh_client->term;
/* Skip if terminal not yet ready */
if (term == NULL)
return 0;
/* Send mouse event */
guac_terminal_send_mouse(term, user, x, y, mask);
return 0;
}
int guac_ssh_user_key_handler(guac_user* user, int keysym, int pressed) {
guac_client* client = user->client;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_terminal* term = ssh_client->term;
/* Skip if terminal not yet ready */
if (term == NULL)
return 0;
/* Send key */
guac_terminal_send_key(term, keysym, pressed);
return 0;
}
int guac_ssh_user_size_handler(guac_user* user, int width, int height) {
/* Get terminal */
guac_client* client = user->client;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_terminal* terminal = ssh_client->term;
/* Skip if terminal not yet ready */
if (terminal == NULL)
return 0;
/* Resize terminal */
guac_terminal_resize(terminal, width, height);
/* Update SSH pty size if connected */
if (ssh_client->term_channel != NULL) {
pthread_mutex_lock(&(ssh_client->term_channel_lock));
libssh2_channel_request_pty_size(ssh_client->term_channel,
terminal->term_width, terminal->term_height);
pthread_mutex_unlock(&(ssh_client->term_channel_lock));
}
return 0;
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2016 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,19 +20,27 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_SSH_INPUT_H
#ifndef _SSH_GUAC_HANDLERS_H #define GUAC_SSH_INPUT_H
#define _SSH_GUAC_HANDLERS_H
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <guacamole/user.h>
int ssh_guac_client_handle_messages(guac_client* client); /**
int ssh_guac_client_key_handler(guac_client* client, int keysym, int pressed); * Handler for Guacamole user mouse events.
int ssh_guac_client_mouse_handler(guac_client* client, int x, int y, int mask); */
int ssh_guac_client_size_handler(guac_client* client, int width, int height); int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask);
int ssh_guac_client_free_handler(guac_client* client);
/**
* Handler for Guacamole user key events.
*/
int guac_ssh_user_key_handler(guac_user* user, int keysym, int pressed);
/**
* Handler for Guacamole user size events.
*/
int guac_ssh_user_size_handler(guac_user* user, int width, int height);
#endif #endif

View File

@ -0,0 +1,269 @@
/*
* Copyright (C) 2016 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 "config.h"
#include "client.h"
#include "settings.h"
#include <guacamole/user.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* Client plugin arguments */
const char* GUAC_SSH_CLIENT_ARGS[] = {
"hostname",
"port",
"username",
"password",
"font-name",
"font-size",
"enable-sftp",
"private-key",
"passphrase",
#ifdef ENABLE_SSH_AGENT
"enable-agent",
#endif
"color-scheme",
"command",
"typescript-path",
"typescript-name",
"create-typescript-path",
NULL
};
enum SSH_ARGS_IDX {
/**
* The hostname to connect to. Required.
*/
IDX_HOSTNAME,
/**
* The port to connect to. Optional.
*/
IDX_PORT,
/**
* The name of the user to login as. Optional.
*/
IDX_USERNAME,
/**
* The password to use when logging in. Optional.
*/
IDX_PASSWORD,
/**
* The name of the font to use within the terminal.
*/
IDX_FONT_NAME,
/**
* The size of the font to use within the terminal, in points.
*/
IDX_FONT_SIZE,
/**
* Whether SFTP should be enabled.
*/
IDX_ENABLE_SFTP,
/**
* The private key to use for authentication, if any.
*/
IDX_PRIVATE_KEY,
/**
* The passphrase required to decrypt the private key, if any.
*/
IDX_PASSPHRASE,
#ifdef ENABLE_SSH_AGENT
/**
* Whether SSH agent forwarding support should be enabled.
*/
IDX_ENABLE_AGENT,
#endif
/**
* The name of the color scheme to use. Currently valid color schemes are:
* "black-white", "white-black", "gray-black", and "green-black", each
* following the "foreground-background" pattern. By default, this will be
* "gray-black".
*/
IDX_COLOR_SCHEME,
/**
* The command to run instead if the default shell. If omitted, a normal
* shell session will be created.
*/
IDX_COMMAND,
/**
* The full absolute path to the directory in which typescripts should be
* written.
*/
IDX_TYPESCRIPT_PATH,
/**
* The name that should be given to typescripts which are written in the
* given path. Each typescript will consist of two files: "NAME" and
* "NAME.timing".
*/
IDX_TYPESCRIPT_NAME,
/**
* Whether the specified typescript path should automatically be created
* if it does not yet exist.
*/
IDX_CREATE_TYPESCRIPT_PATH,
SSH_ARGS_COUNT
};
guac_ssh_settings* guac_ssh_parse_args(guac_user* user,
int argc, const char** argv) {
/* Validate arg count */
if (argc != SSH_ARGS_COUNT) {
guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection "
"parameters provided: expected %i, got %i.",
SSH_ARGS_COUNT, argc);
return NULL;
}
guac_ssh_settings* settings = calloc(1, sizeof(guac_ssh_settings));
/* Read parameters */
settings->hostname =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_HOSTNAME, "");
settings->username =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_USERNAME, NULL);
settings->password =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_PASSWORD, NULL);
/* Init public key auth information */
settings->key_base64 =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_PRIVATE_KEY, NULL);
settings->key_passphrase =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_PASSPHRASE, NULL);
/* Read font name */
settings->font_name =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_FONT_NAME, GUAC_SSH_DEFAULT_FONT_NAME);
/* Read font size */
settings->font_size =
guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_FONT_SIZE, GUAC_SSH_DEFAULT_FONT_SIZE);
/* Copy requested color scheme */
settings->color_scheme =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_COLOR_SCHEME, "");
/* Pull width/height/resolution directly from user */
settings->width = user->info.optimal_width;
settings->height = user->info.optimal_height;
settings->resolution = user->info.optimal_resolution;
/* Parse SFTP enable */
settings->enable_sftp =
guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_ENABLE_SFTP, false);
#ifdef ENABLE_SSH_AGENT
settings->enable_agent =
guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_ENABLE_AGENT, false);
#endif
/* Read port */
settings->port =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_PORT, GUAC_SSH_DEFAULT_PORT);
/* Read command, if any */
settings->command =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_COMMAND, NULL);
/* Read typescript path */
settings->typescript_path =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_TYPESCRIPT_PATH, NULL);
/* Read typescript name */
settings->typescript_name =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_TYPESCRIPT_NAME, GUAC_SSH_DEFAULT_TYPESCRIPT_NAME);
/* Parse path creation flag */
settings->create_typescript_path =
guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_CREATE_TYPESCRIPT_PATH, false);
/* Parsing was successful */
return settings;
}
void guac_ssh_settings_free(guac_ssh_settings* settings) {
/* Free network connection information */
free(settings->hostname);
free(settings->port);
/* Free credentials */
free(settings->username);
free(settings->password);
free(settings->key_base64);
free(settings->key_passphrase);
/* Free display preferences */
free(settings->font_name);
free(settings->color_scheme);
/* Free requested command */
free(settings->command);
/* Free typescript settings */
free(settings->typescript_name);
free(settings->typescript_path);
/* Free overall structure */
free(settings);
}

View File

@ -0,0 +1,199 @@
/*
* Copyright (C) 2016 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_SSH_SETTINGS_H
#define GUAC_SSH_SETTINGS_H
#include "config.h"
#include <guacamole/user.h>
#include <stdbool.h>
/**
* The name of the font to use for the terminal if no name is specified.
*/
#define GUAC_SSH_DEFAULT_FONT_NAME "monospace"
/**
* The size of the font to use for the terminal if no font size is specified,
* in points.
*/
#define GUAC_SSH_DEFAULT_FONT_SIZE 12
/**
* The port to connect to when initiating any SSH connection, if no other port
* is specified.
*/
#define GUAC_SSH_DEFAULT_PORT "22"
/**
* The filename to use for the typescript, if not specified.
*/
#define GUAC_SSH_DEFAULT_TYPESCRIPT_NAME "typescript"
/**
* Settings for the SSH connection. The values for this structure are parsed
* from the arguments given during the Guacamole protocol handshake using the
* guac_ssh_parse_args() function.
*/
typedef struct guac_ssh_settings {
/**
* The hostname of the SSH server to connect to.
*/
char* hostname;
/**
* The port of the SSH server to connect to.
*/
char* port;
/**
* The name of the user to login as, if any. If no username is specified,
* this will be NULL.
*/
char* username;
/**
* The password to give when authenticating, if any. If no password is
* specified, this will be NULL.
*/
char* password;
/**
* The private key, encoded as base64, if any. If no private key is
* specified, this will be NULL.
*/
char* key_base64;
/**
* The passphrase to use to decrypt the given private key, if any. If no
* passphrase is specified, this will be NULL.
*/
char* key_passphrase;
/**
* The command to run instead of the default shell. If a normal shell
* session is desired, this will be NULL.
*/
char* command;
/**
* The name of the font to use for display rendering.
*/
char* font_name;
/**
* The size of the font to use, in points.
*/
int font_size;
/**
* The name of the color scheme to use.
*/
char* color_scheme;
/**
* The desired width of the terminal display, in pixels.
*/
int width;
/**
* The desired height of the terminal display, in pixels.
*/
int height;
/**
* The desired screen resolution, in DPI.
*/
int resolution;
/**
* Whether SFTP is enabled.
*/
bool enable_sftp;
#ifdef ENABLE_SSH_AGENT
/**
* Whether the SSH agent is enabled.
*/
bool enable_agent;
#endif
/**
* The path in which the typescript should be saved, if enabled. If no
* typescript should be saved, this will be NULL.
*/
char* typescript_path;
/**
* The filename to use for the typescript, if enabled.
*/
char* typescript_name;
/**
* Whether the typescript path should be automatically created if it does
* not already exist.
*/
bool create_typescript_path;
} guac_ssh_settings;
/**
* Parses all given args, storing them in a newly-allocated settings object. If
* the args fail to parse, NULL is returned.
*
* @param user
* The user who submitted the given arguments while joining the
* connection.
*
* @param argc
* The number of arguments within the argv array.
*
* @param argv
* The values of all arguments provided by the user.
*
* @return
* A newly-allocated settings object which must be freed with
* guac_ssh_settings_free() when no longer needed. If the arguments fail
* to parse, NULL is returned.
*/
guac_ssh_settings* guac_ssh_parse_args(guac_user* user,
int argc, const char** argv);
/**
* Frees the given guac_ssh_settings object, having been previously allocated
* via guac_ssh_parse_args().
*
* @param settings
* The settings object to free.
*/
void guac_ssh_settings_free(guac_ssh_settings* settings);
/**
* NULL-terminated array of accepted client args.
*/
extern const char* GUAC_SSH_CLIENT_ARGS[];
#endif

View File

@ -22,40 +22,75 @@
#include "config.h" #include "config.h"
#include "client.h"
#include "guac_sftp.h" #include "guac_sftp.h"
#include "sftp.h" #include "sftp.h"
#include "ssh.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/user.h>
int guac_sftp_file_handler(guac_client* client, guac_stream* stream, int guac_sftp_file_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* filename) { char* mimetype, char* filename) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; guac_client* client = user->client;
guac_object* filesystem = client_data->sftp_filesystem; guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_common_ssh_sftp_filesystem* filesystem = ssh_client->sftp_filesystem;
/* Handle file upload */ /* Handle file upload */
return guac_common_ssh_sftp_handle_file_stream(filesystem, stream, return guac_common_ssh_sftp_handle_file_stream(filesystem, user, stream,
mimetype, filename); mimetype, filename);
} }
guac_stream* guac_sftp_download_file(guac_client* client, /**
char* filename) { * Callback invoked on the current connection owner (if any) when a file
* download is being initiated through the terminal.
*
* @param owner
* The guac_user that is the owner of the connection, or NULL if the
* connection owner has left.
*
* @param data
* The filename of the file that should be downloaded.
*
* @return
* The stream allocated for the file download, or NULL if no stream could
* be allocated.
*/
static void* guac_sftp_download_to_owner(guac_user* owner, void* data) {
ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; /* Do not bother attempting the download if the owner has left */
guac_object* filesystem = client_data->sftp_filesystem; if (owner == NULL)
return NULL;
guac_client* client = owner->client;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_common_ssh_sftp_filesystem* filesystem = ssh_client->sftp_filesystem;
/* Ignore download if filesystem has been unloaded */
if (filesystem == NULL)
return NULL;
char* filename = (char*) data;
/* Initiate download of requested file */ /* Initiate download of requested file */
return guac_common_ssh_sftp_download_file(filesystem, filename); return guac_common_ssh_sftp_download_file(filesystem, owner, filename);
}
guac_stream* guac_sftp_download_file(guac_client* client, char* filename) {
/* Initiate download to the owner of the connection */
return (guac_stream*) guac_client_for_owner(client,
guac_sftp_download_to_owner, filename);
} }
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; guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_object* filesystem = client_data->sftp_filesystem; guac_common_ssh_sftp_filesystem* filesystem = ssh_client->sftp_filesystem;
/* Set upload path as specified */ /* Set upload path as specified */
guac_common_ssh_sftp_set_upload_path(filesystem, path); guac_common_ssh_sftp_set_upload_path(filesystem, path);

View File

@ -28,14 +28,15 @@
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/user.h>
/** /**
* Handles an incoming stream from a Guacamole "file" instruction, saving the * Handles an incoming stream from a Guacamole "file" instruction, saving the
* contents of that stream to the file having the given name within the * contents of that stream to the file having the given name within the
* upload directory set by guac_sftp_set_upload_path(). * upload directory set by guac_sftp_set_upload_path().
* *
* @param client * @param user
* The client receiving the uploaded file. * The user receiving the uploaded file.
* *
* @param stream * @param stream
* The stream through which the uploaded file data will be received. * The stream through which the uploaded file data will be received.
@ -52,7 +53,7 @@
* Zero if the incoming stream has been handled successfully, non-zero on * Zero if the incoming stream has been handled successfully, non-zero on
* failure. * failure.
*/ */
int guac_sftp_file_handler(guac_client* client, guac_stream* stream, int guac_sftp_file_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* filename); char* mimetype, char* filename);
/** /**
@ -62,7 +63,7 @@ int guac_sftp_file_handler(guac_client* client, guac_stream* stream,
* the client. * the client.
* *
* @param client * @param client
* The client receiving the file. * The client associated with the terminal emulator receiving the file.
* *
* @param filename * @param filename
* The filename of the file to download, relative to the given filesystem. * The filename of the file to download, relative to the given filesystem.

View File

@ -22,10 +22,11 @@
#include "config.h" #include "config.h"
#include "client.h"
#include "guac_sftp.h" #include "guac_sftp.h"
#include "guac_ssh.h" #include "guac_ssh.h"
#include "settings.h"
#include "sftp.h" #include "sftp.h"
#include "ssh.h"
#include "terminal.h" #include "terminal.h"
#ifdef ENABLE_SSH_AGENT #ifdef ENABLE_SSH_AGENT
@ -35,8 +36,6 @@
#include <libssh2.h> #include <libssh2.h>
#include <libssh2_sftp.h> #include <libssh2_sftp.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
@ -70,27 +69,28 @@
*/ */
static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { 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_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_ssh_settings* settings = ssh_client->settings;
guac_common_ssh_user* user; guac_common_ssh_user* user;
/* Get username */ /* Get username */
if (client_data->username[0] == 0) if (settings->username == NULL)
guac_terminal_prompt(client_data->term, "Login as: ", settings->username = guac_terminal_prompt(ssh_client->term,
client_data->username, sizeof(client_data->username), true); "Login as: ", true);
/* Create user object from username */ /* Create user object from username */
user = guac_common_ssh_create_user(client_data->username); user = guac_common_ssh_create_user(settings->username);
/* If key specified, import */ /* If key specified, import */
if (client_data->key_base64[0] != 0) { if (settings->key_base64 != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG, guac_client_log(client, GUAC_LOG_DEBUG,
"Attempting private key import (WITHOUT passphrase)"); "Attempting private key import (WITHOUT passphrase)");
/* Attempt to read key without passphrase */ /* Attempt to read key without passphrase */
if (guac_common_ssh_user_import_key(user, if (guac_common_ssh_user_import_key(user,
client_data->key_base64, NULL)) { settings->key_base64, NULL)) {
/* Log failure of initial attempt */ /* Log failure of initial attempt */
guac_client_log(client, GUAC_LOG_DEBUG, guac_client_log(client, GUAC_LOG_DEBUG,
@ -101,15 +101,15 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) {
"Re-attempting private key import (WITH passphrase)"); "Re-attempting private key import (WITH passphrase)");
/* Prompt for passphrase if missing */ /* Prompt for passphrase if missing */
if (client_data->key_passphrase[0] == 0) if (settings->key_passphrase == NULL)
guac_terminal_prompt(client_data->term, "Key passphrase: ", settings->key_passphrase =
client_data->key_passphrase, guac_terminal_prompt(ssh_client->term,
sizeof(client_data->key_passphrase), false); "Key passphrase: ", false);
/* Reattempt import with passphrase */ /* Reattempt import with passphrase */
if (guac_common_ssh_user_import_key(user, if (guac_common_ssh_user_import_key(user,
client_data->key_base64, settings->key_base64,
client_data->key_passphrase)) { settings->key_passphrase)) {
/* If still failing, give up */ /* If still failing, give up */
guac_client_abort(client, guac_client_abort(client,
@ -134,18 +134,17 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) {
else { else {
/* Get password if not provided */ /* Get password if not provided */
if (client_data->password[0] == 0) if (settings->password == NULL)
guac_terminal_prompt(client_data->term, "Password: ", settings->password = guac_terminal_prompt(ssh_client->term,
client_data->password, sizeof(client_data->password), "Password: ", false);
false);
/* Set provided password */ /* Set provided password */
guac_common_ssh_user_set_password(user, client_data->password); guac_common_ssh_user_set_password(user, settings->password);
} }
/* Clear screen of any prompts */ /* Clear screen of any prompts */
guac_terminal_printf(client_data->term, "\x1B[H\x1B[J"); guac_terminal_printf(ssh_client->term, "\x1B[H\x1B[J");
return user; return user;
@ -154,16 +153,16 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) {
void* ssh_input_thread(void* data) { void* ssh_input_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; guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
char buffer[8192]; char buffer[8192];
int bytes_read; int bytes_read;
/* Write all data read */ /* Write all data read */
while ((bytes_read = guac_terminal_read_stdin(client_data->term, buffer, sizeof(buffer))) > 0) { while ((bytes_read = guac_terminal_read_stdin(ssh_client->term, buffer, sizeof(buffer))) > 0) {
pthread_mutex_lock(&(client_data->term_channel_lock)); pthread_mutex_lock(&(ssh_client->term_channel_lock));
libssh2_channel_write(client_data->term_channel, buffer, bytes_read); libssh2_channel_write(ssh_client->term_channel, buffer, bytes_read);
pthread_mutex_unlock(&(client_data->term_channel_lock)); pthread_mutex_unlock(&(ssh_client->term_channel_lock));
} }
return NULL; return NULL;
@ -173,9 +172,9 @@ void* ssh_input_thread(void* data) {
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; guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_ssh_settings* settings = ssh_client->settings;
guac_socket* socket = client->socket;
char buffer[8192]; char buffer[8192];
pthread_t input_thread; pthread_t input_thread;
@ -184,29 +183,44 @@ void* ssh_client_thread(void* data) {
if (guac_common_ssh_init(client)) if (guac_common_ssh_init(client))
return NULL; return NULL;
/* Get user and credentials */ /* Create terminal */
client_data->user = guac_ssh_get_user(client); ssh_client->term = guac_terminal_create(client,
settings->font_name, settings->font_size,
settings->resolution, settings->width, settings->height,
settings->color_scheme);
/* Send new name */ /* Fail if terminal init failed */
char name[1024]; if (ssh_client->term == NULL) {
snprintf(name, sizeof(name)-1, "%s@%s", guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
client_data->username, client_data->hostname); "Terminal initialization failed");
guac_protocol_send_name(socket, name); return NULL;
}
/* Set up typescript, if requested */
if (settings->typescript_path != NULL) {
guac_terminal_create_typescript(ssh_client->term,
settings->typescript_path,
settings->typescript_name,
settings->create_typescript_path);
}
/* Get user and credentials */
ssh_client->user = guac_ssh_get_user(client);
/* Open SSH session */ /* Open SSH session */
client_data->session = guac_common_ssh_create_session(client, ssh_client->session = guac_common_ssh_create_session(client,
client_data->hostname, client_data->port, client_data->user); settings->hostname, settings->port, ssh_client->user);
if (client_data->session == NULL) { if (ssh_client->session == NULL) {
/* Already aborted within guac_common_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(&ssh_client->term_channel_lock, NULL);
/* Open channel for terminal */ /* Open channel for terminal */
client_data->term_channel = ssh_client->term_channel =
libssh2_channel_open_session(client_data->session->session); libssh2_channel_open_session(ssh_client->session->session);
if (client_data->term_channel == NULL) { if (ssh_client->term_channel == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Unable to open terminal channel."); "Unable to open terminal channel.");
return NULL; return NULL;
@ -214,59 +228,60 @@ void* ssh_client_thread(void* data) {
#ifdef ENABLE_SSH_AGENT #ifdef ENABLE_SSH_AGENT
/* Start SSH agent forwarding, if enabled */ /* Start SSH agent forwarding, if enabled */
if (client_data->enable_agent) { if (ssh_client->enable_agent) {
libssh2_session_callback_set(client_data->session, libssh2_session_callback_set(ssh_client->session,
LIBSSH2_CALLBACK_AUTH_AGENT, (void*) ssh_auth_agent_callback); LIBSSH2_CALLBACK_AUTH_AGENT, (void*) ssh_auth_agent_callback);
/* Request agent forwarding */ /* Request agent forwarding */
if (libssh2_channel_request_auth_agent(client_data->term_channel)) if (libssh2_channel_request_auth_agent(ssh_client->term_channel))
guac_client_log(client, GUAC_LOG_ERROR, "Agent forwarding request failed"); guac_client_log(client, GUAC_LOG_ERROR, "Agent forwarding request failed");
else else
guac_client_log(client, GUAC_LOG_INFO, "Agent forwarding enabled."); guac_client_log(client, GUAC_LOG_INFO, "Agent forwarding enabled.");
} }
client_data->auth_agent = NULL; ssh_client->auth_agent = NULL;
#endif #endif
/* Start SFTP session as well, if enabled */ /* Start SFTP session as well, if enabled */
if (client_data->enable_sftp) { if (settings->enable_sftp) {
/* 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_session = ssh_client->sftp_session =
guac_common_ssh_create_session(client, client_data->hostname, guac_common_ssh_create_session(client, settings->hostname,
client_data->port, client_data->user); settings->port, ssh_client->user);
if (client_data->sftp_session == NULL) { if (ssh_client->sftp_session == NULL) {
/* Already aborted within guac_common_ssh_create_session() */ /* Already aborted within guac_common_ssh_create_session() */
return NULL; return NULL;
} }
/* Request SFTP */ /* Request SFTP */
client_data->sftp_filesystem = ssh_client->sftp_filesystem = guac_common_ssh_create_sftp_filesystem(
guac_common_ssh_create_sftp_filesystem( ssh_client->sftp_session, "/");
client_data->sftp_session, "/");
/* Set generic (non-filesystem) file upload handler */ /* Expose filesystem to connection owner */
client->file_handler = guac_sftp_file_handler; guac_client_for_owner(client,
guac_common_ssh_expose_sftp_filesystem,
ssh_client->sftp_filesystem);
/* Init handlers for Guacamole-specific console codes */ /* Init handlers for Guacamole-specific console codes */
client_data->term->upload_path_handler = guac_sftp_set_upload_path; ssh_client->term->upload_path_handler = guac_sftp_set_upload_path;
client_data->term->file_download_handler = guac_sftp_download_file; ssh_client->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");
} }
/* Request PTY */ /* Request PTY */
if (libssh2_channel_request_pty_ex(client_data->term_channel, "linux", sizeof("linux")-1, NULL, 0, if (libssh2_channel_request_pty_ex(ssh_client->term_channel, "linux", sizeof("linux")-1, NULL, 0,
client_data->term->term_width, client_data->term->term_height, 0, 0)) { ssh_client->term->term_width, ssh_client->term->term_height, 0, 0)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to allocate PTY."); guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to allocate PTY.");
return NULL; return NULL;
} }
/* If a command is specified, run that instead of a shell */ /* If a command is specified, run that instead of a shell */
if (client_data->command != NULL) { if (settings->command != NULL) {
if (libssh2_channel_exec(client_data->term_channel, client_data->command)) { if (libssh2_channel_exec(ssh_client->term_channel, settings->command)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Unable to execute command."); "Unable to execute command.");
return NULL; return NULL;
@ -274,7 +289,7 @@ void* ssh_client_thread(void* data) {
} }
/* Otherwise, request a shell */ /* Otherwise, request a shell */
else if (libssh2_channel_shell(client_data->term_channel)) { else if (libssh2_channel_shell(ssh_client->term_channel)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Unable to associate shell with PTY."); "Unable to associate shell with PTY.");
return NULL; return NULL;
@ -290,7 +305,7 @@ void* ssh_client_thread(void* data) {
} }
/* Set non-blocking */ /* Set non-blocking */
libssh2_session_set_blocking(client_data->session->session, 0); libssh2_session_set_blocking(ssh_client->session->session, 0);
/* While data available, write to terminal */ /* While data available, write to terminal */
int bytes_read = 0; int bytes_read = 0;
@ -299,23 +314,23 @@ void* ssh_client_thread(void* data) {
/* Track total amount of data read */ /* Track total amount of data read */
int total_read = 0; int total_read = 0;
pthread_mutex_lock(&(client_data->term_channel_lock)); pthread_mutex_lock(&(ssh_client->term_channel_lock));
/* Stop reading at EOF */ /* Stop reading at EOF */
if (libssh2_channel_eof(client_data->term_channel)) { if (libssh2_channel_eof(ssh_client->term_channel)) {
pthread_mutex_unlock(&(client_data->term_channel_lock)); pthread_mutex_unlock(&(ssh_client->term_channel_lock));
break; break;
} }
/* Read terminal data */ /* Read terminal data */
bytes_read = libssh2_channel_read(client_data->term_channel, bytes_read = libssh2_channel_read(ssh_client->term_channel,
buffer, sizeof(buffer)); buffer, sizeof(buffer));
pthread_mutex_unlock(&(client_data->term_channel_lock)); pthread_mutex_unlock(&(ssh_client->term_channel_lock));
/* Attempt to write data received. Exit on failure. */ /* Attempt to write data received. Exit on failure. */
if (bytes_read > 0) { if (bytes_read > 0) {
int written = guac_terminal_write_stdout(client_data->term, buffer, bytes_read); int written = guac_terminal_write_stdout(ssh_client->term, buffer, bytes_read);
if (written < 0) if (written < 0)
break; break;
@ -327,12 +342,12 @@ void* ssh_client_thread(void* data) {
#ifdef ENABLE_SSH_AGENT #ifdef ENABLE_SSH_AGENT
/* If agent open, handle any agent packets */ /* If agent open, handle any agent packets */
if (client_data->auth_agent != NULL) { if (ssh_client->auth_agent != NULL) {
bytes_read = ssh_auth_agent_read(client_data->auth_agent); bytes_read = ssh_auth_agent_read(ssh_client->auth_agent);
if (bytes_read > 0) if (bytes_read > 0)
total_read += bytes_read; total_read += bytes_read;
else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN) else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN)
client_data->auth_agent = NULL; ssh_client->auth_agent = NULL;
} }
#endif #endif
@ -342,13 +357,13 @@ void* ssh_client_thread(void* data) {
struct timeval timeout; struct timeval timeout;
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(client_data->session->fd, &fds); FD_SET(ssh_client->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(client_data->session->fd + 1, &fds, if (select(ssh_client->session->fd + 1, &fds,
NULL, NULL, &timeout) < 0) NULL, NULL, &timeout) < 0)
break; break;
} }
@ -359,7 +374,7 @@ void* ssh_client_thread(void* data) {
guac_client_stop(client); guac_client_stop(client);
pthread_join(input_thread, NULL); pthread_join(input_thread, NULL);
pthread_mutex_destroy(&client_data->term_channel_lock); pthread_mutex_destroy(&ssh_client->term_channel_lock);
guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended."); guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended.");
return NULL; return NULL;

107
src/protocols/ssh/ssh.h Normal file
View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2013 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_SSH_H
#define GUAC_SSH_H
#include "config.h"
#include "guac_sftp.h"
#include "guac_ssh.h"
#include "guac_ssh_user.h"
#include "settings.h"
#include "terminal.h"
#ifdef ENABLE_SSH_AGENT
#include "ssh_agent.h"
#endif
#include <guacamole/client.h>
#include <pthread.h>
/**
* SSH-specific client data.
*/
typedef struct guac_ssh_client {
/**
* SSH connection settings.
*/
guac_ssh_settings* settings;
#ifdef ENABLE_SSH_AGENT
/**
* The current agent, if any.
*/
ssh_auth_agent* auth_agent;
#endif
/**
* The SSH client thread.
*/
pthread_t client_thread;
/**
* The user and credentials to use for all SSH sessions.
*/
guac_common_ssh_user* user;
/**
* SSH session, used by the SSH client thread.
*/
guac_common_ssh_session* session;
/**
* SFTP session, used by the SFTP client/filesystem.
*/
guac_common_ssh_session* sftp_session;
/**
* The filesystem object exposed for the SFTP session.
*/
guac_common_ssh_sftp_filesystem* sftp_filesystem;
/**
* SSH terminal channel, used by the SSH client thread.
*/
LIBSSH2_CHANNEL* term_channel;
/**
* Lock dictating access to the SSH terminal channel.
*/
pthread_mutex_t term_channel_lock;
/**
* The terminal which will render all output from the SSH client.
*/
guac_terminal* term;
} guac_ssh_client ;
/**
* Main SSH client thread, handling transfer of SSH output to STDOUT.
*/
void* ssh_client_thread(void* data);
#endif

87
src/protocols/ssh/user.c Normal file
View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2014 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 "config.h"
#include "clipboard.h"
#include "input.h"
#include "guac_display.h"
#include "user.h"
#include "sftp.h"
#include "ssh.h"
#include "settings.h"
#include <guacamole/client.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <pthread.h>
#include <string.h>
int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) {
guac_client* client = user->client;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
/* Connect via SSH if owner */
if (user->owner) {
/* Parse arguments into client */
guac_ssh_settings* settings = ssh_client->settings =
guac_ssh_parse_args(user, argc, (const char**) argv);
/* Fail if settings cannot be parsed */
if (settings == NULL) {
guac_user_log(user, GUAC_LOG_INFO,
"Badly formatted client arguments.");
return 1;
}
/* Start client thread */
if (pthread_create(&(ssh_client->client_thread), NULL,
ssh_client_thread, (void*) client)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Unable to start SSH client thread");
return 1;
}
}
/* If not owner, synchronize with current display */
else {
guac_terminal_dup(ssh_client->term, user, user->socket);
guac_socket_flush(user->socket);
}
/* Set per-user event handlers */
user->key_handler = guac_ssh_user_key_handler;
user->mouse_handler = guac_ssh_user_mouse_handler;
user->size_handler = guac_ssh_user_size_handler;
user->clipboard_handler = guac_ssh_clipboard_handler;
/* Set generic (non-filesystem) file upload handler */
user->file_handler = guac_sftp_file_handler;
return 0;
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2014 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,18 +20,17 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_VNC_USER_H
#ifndef __SSH_CLIENT_H #define GUAC_VNC_USER_H
#define __SSH_CLIENT_H
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <guacamole/user.h>
/** /**
* Main SSH client thread, handling transfer of SSH output to STDOUT. * Handler for joining users.
*/ */
void* ssh_client_thread(void* data); int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv);
#endif #endif