guacamole-spice-protocol/src/protocols/ssh/ssh.c

393 lines
13 KiB
C
Raw Normal View History

/*
* Copyright (C) 2013 Glyptodon LLC
2013-05-18 03:47:05 +00:00
*
* 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:
2013-05-18 03:47:05 +00:00
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
2013-05-18 03:47:05 +00:00
*
* 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.
*/
2014-01-01 22:44:28 +00:00
#include "config.h"
2013-05-18 03:47:05 +00:00
#include "guac_recording.h"
#include "guac_sftp.h"
#include "guac_ssh.h"
#include "settings.h"
2014-01-01 22:44:28 +00:00
#include "sftp.h"
#include "ssh.h"
2014-06-11 17:06:18 +00:00
#include "terminal.h"
#ifdef ENABLE_SSH_AGENT
#include "ssh_agent.h"
#endif
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <guacamole/client.h>
#include <openssl/err.h>
2014-06-11 17:06:18 +00:00
#include <openssl/ssl.h>
2014-01-01 22:44:28 +00:00
#ifdef LIBSSH2_USES_GCRYPT
#include <gcrypt.h>
#endif
2014-01-01 22:44:28 +00:00
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
2014-06-11 17:06:18 +00:00
#include <stdbool.h>
#include <stddef.h>
2013-05-18 03:47:05 +00:00
#include <stdio.h>
2014-06-11 17:06:18 +00:00
#include <stdlib.h>
2013-05-18 05:53:13 +00:00
#include <string.h>
2014-04-11 20:45:19 +00:00
#include <sys/select.h>
#include <sys/socket.h>
2014-06-11 17:06:18 +00:00
#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) {
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_ssh_settings* settings = ssh_client->settings;
2013-12-02 00:26:41 +00:00
guac_common_ssh_user* user;
/* Get username */
if (settings->username == NULL)
settings->username = guac_terminal_prompt(ssh_client->term,
"Login as: ", true);
/* Create user object from username */
user = guac_common_ssh_create_user(settings->username);
2013-12-02 00:26:41 +00:00
/* If key specified, import */
if (settings->key_base64 != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Attempting private key import (WITHOUT passphrase)");
2013-12-02 00:26:41 +00:00
/* Attempt to read key without passphrase */
if (guac_common_ssh_user_import_key(user,
settings->key_base64, NULL)) {
2013-12-02 00:26:41 +00:00
/* 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)");
2013-12-02 00:26:41 +00:00
/* Prompt for passphrase if missing */
if (settings->key_passphrase == NULL)
settings->key_passphrase =
guac_terminal_prompt(ssh_client->term,
"Key passphrase: ", false);
2013-12-02 00:26:41 +00:00
/* Reattempt import with passphrase */
if (guac_common_ssh_user_import_key(user,
settings->key_base64,
settings->key_passphrase)) {
2013-12-02 00:26:41 +00:00
/* If still failing, give up */
guac_client_abort(client,
GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"Auth key import failed: %s",
guac_common_ssh_key_error());
2013-12-02 00:26:41 +00:00
guac_common_ssh_destroy_user(user);
return NULL;
2013-12-02 00:26:41 +00:00
}
2013-12-02 00:26:41 +00:00
} /* end decrypt key with passphrase */
2013-12-02 00:26:41 +00:00
/* Success */
guac_client_log(client, GUAC_LOG_INFO,
"Auth key successfully imported.");
2013-12-02 00:26:41 +00:00
} /* end if key given */
2013-12-02 00:26:41 +00:00
/* Otherwise, use password */
else {
/* Get password if not provided */
if (settings->password == NULL)
settings->password = guac_terminal_prompt(ssh_client->term,
"Password: ", false);
/* Set provided password */
guac_common_ssh_user_set_password(user, settings->password);
2013-12-02 17:23:31 +00:00
}
/* Clear screen of any prompts */
guac_terminal_printf(ssh_client->term, "\x1B[H\x1B[J");
return user;
}
void* ssh_input_thread(void* data) {
2014-07-17 18:49:24 +00:00
guac_client* client = (guac_client*) data;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
char buffer[8192];
int bytes_read;
2013-10-31 06:19:11 +00:00
/* Write all data read */
while ((bytes_read = guac_terminal_read_stdin(ssh_client->term, buffer, sizeof(buffer))) > 0) {
pthread_mutex_lock(&(ssh_client->term_channel_lock));
libssh2_channel_write(ssh_client->term_channel, buffer, bytes_read);
pthread_mutex_unlock(&(ssh_client->term_channel_lock));
2014-07-17 18:49:24 +00:00
}
return NULL;
}
2013-05-18 05:53:13 +00:00
void* ssh_client_thread(void* data) {
guac_client* client = (guac_client*) data;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_ssh_settings* settings = ssh_client->settings;
2013-05-20 08:23:21 +00:00
char buffer[8192];
pthread_t input_thread;
2013-05-18 05:53:13 +00:00
/* Init SSH base libraries */
if (guac_common_ssh_init(client))
return NULL;
2013-12-02 00:56:36 +00:00
/* Set up screen recording, if requested */
if (settings->recording_path != NULL) {
guac_common_recording_create(client,
settings->recording_path,
settings->recording_name,
settings->create_recording_path);
}
/* Create terminal */
ssh_client->term = guac_terminal_create(client,
settings->font_name, settings->font_size,
settings->resolution, settings->width, settings->height,
settings->color_scheme);
/* Fail if terminal init failed */
if (ssh_client->term == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Terminal initialization failed");
return NULL;
}
2013-05-18 05:53:13 +00:00
/* 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);
2013-05-20 08:23:21 +00:00
/* Open SSH session */
ssh_client->session = guac_common_ssh_create_session(client,
settings->hostname, settings->port, ssh_client->user);
if (ssh_client->session == NULL) {
/* Already aborted within guac_common_ssh_create_session() */
2013-05-20 08:23:21 +00:00
return NULL;
}
pthread_mutex_init(&ssh_client->term_channel_lock, NULL);
2013-05-20 08:23:21 +00:00
/* Open channel for terminal */
ssh_client->term_channel =
libssh2_channel_open_session(ssh_client->session->session);
if (ssh_client->term_channel == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Unable to open terminal channel.");
2013-05-18 05:53:13 +00:00
return NULL;
2013-05-20 08:23:21 +00:00
}
2013-05-18 05:53:13 +00:00
#ifdef ENABLE_SSH_AGENT
2013-12-02 10:07:17 +00:00
/* Start SSH agent forwarding, if enabled */
if (ssh_client->enable_agent) {
libssh2_session_callback_set(ssh_client->session,
2013-12-02 10:07:17 +00:00
LIBSSH2_CALLBACK_AUTH_AGENT, (void*) ssh_auth_agent_callback);
/* Request agent forwarding */
if (libssh2_channel_request_auth_agent(ssh_client->term_channel))
guac_client_log(client, GUAC_LOG_ERROR, "Agent forwarding request failed");
2013-12-02 10:07:17 +00:00
else
guac_client_log(client, GUAC_LOG_INFO, "Agent forwarding enabled.");
2013-12-02 10:07:17 +00:00
}
ssh_client->auth_agent = NULL;
#endif
/* Start SFTP session as well, if enabled */
if (settings->enable_sftp) {
/* Create SSH session specific for SFTP */
guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP...");
ssh_client->sftp_session =
guac_common_ssh_create_session(client, settings->hostname,
settings->port, ssh_client->user);
if (ssh_client->sftp_session == NULL) {
/* Already aborted within guac_common_ssh_create_session() */
2014-03-22 02:47:42 +00:00
return NULL;
}
/* Request SFTP */
ssh_client->sftp_filesystem = guac_common_ssh_create_sftp_filesystem(
ssh_client->sftp_session, "/");
/* Expose filesystem to connection owner */
guac_client_for_owner(client,
guac_common_ssh_expose_sftp_filesystem,
ssh_client->sftp_filesystem);
2013-10-19 01:20:46 +00:00
/* Init handlers for Guacamole-specific console codes */
ssh_client->term->upload_path_handler = guac_sftp_set_upload_path;
ssh_client->term->file_download_handler = guac_sftp_download_file;
guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized");
}
2013-05-20 08:23:21 +00:00
/* Request PTY */
if (libssh2_channel_request_pty_ex(ssh_client->term_channel, "linux", sizeof("linux")-1, NULL, 0,
ssh_client->term->term_width, ssh_client->term->term_height, 0, 0)) {
2014-03-22 02:47:42 +00:00
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to allocate PTY.");
2013-05-20 08:23:21 +00:00
return NULL;
}
/* If a command is specified, run that instead of a shell */
if (settings->command != NULL) {
if (libssh2_channel_exec(ssh_client->term_channel, settings->command)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Unable to execute command.");
return NULL;
}
}
/* Otherwise, request a shell */
else if (libssh2_channel_shell(ssh_client->term_channel)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Unable to associate shell with PTY.");
2013-05-20 08:23:21 +00:00
return NULL;
}
/* Logged in */
guac_client_log(client, GUAC_LOG_INFO, "SSH connection successful.");
2013-05-20 08:23:21 +00:00
/* Start input thread */
if (pthread_create(&(input_thread), NULL, ssh_input_thread, (void*) client)) {
2014-03-22 02:47:42 +00:00
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread");
2013-05-20 08:23:21 +00:00
return NULL;
}
/* Set non-blocking */
libssh2_session_set_blocking(ssh_client->session->session, 0);
2013-05-20 08:23:21 +00:00
/* While data available, write to terminal */
int bytes_read = 0;
for (;;) {
/* Track total amount of data read */
int total_read = 0;
pthread_mutex_lock(&(ssh_client->term_channel_lock));
/* Stop reading at EOF */
if (libssh2_channel_eof(ssh_client->term_channel)) {
pthread_mutex_unlock(&(ssh_client->term_channel_lock));
break;
}
/* Read terminal data */
bytes_read = libssh2_channel_read(ssh_client->term_channel,
buffer, sizeof(buffer));
2013-05-20 08:23:21 +00:00
pthread_mutex_unlock(&(ssh_client->term_channel_lock));
2013-05-26 06:05:58 +00:00
/* Attempt to write data received. Exit on failure. */
if (bytes_read > 0) {
int written = guac_terminal_write_stdout(ssh_client->term, buffer, bytes_read);
2013-05-26 06:05:58 +00:00
if (written < 0)
break;
total_read += bytes_read;
}
else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN)
break;
2013-12-03 17:21:47 +00:00
#ifdef ENABLE_SSH_AGENT
/* If agent open, handle any agent packets */
if (ssh_client->auth_agent != NULL) {
bytes_read = ssh_auth_agent_read(ssh_client->auth_agent);
if (bytes_read > 0)
total_read += bytes_read;
else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN)
ssh_client->auth_agent = NULL;
}
2013-12-03 17:21:47 +00:00
#endif
/* Wait for more data if reads turn up empty */
if (total_read == 0) {
fd_set fds;
struct timeval timeout;
FD_ZERO(&fds);
FD_SET(ssh_client->session->fd, &fds);
/* Wait for one second */
timeout.tv_sec = 1;
timeout.tv_usec = 0;
if (select(ssh_client->session->fd + 1, &fds,
NULL, NULL, &timeout) < 0)
break;
2013-05-26 06:05:58 +00:00
}
2013-05-20 08:23:21 +00:00
}
2013-05-18 05:53:13 +00:00
/* Kill client and Wait for input thread to die */
guac_client_stop(client);
pthread_join(input_thread, NULL);
pthread_mutex_destroy(&ssh_client->term_channel_lock);
guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended.");
2013-05-18 03:47:05 +00:00
return NULL;
}