From d32018cf87afbf04043eafdd78c08d0689d055fb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 1 Dec 2013 15:39:29 -0800 Subject: [PATCH] Partial migration, including SFTP. --- src/protocols/ssh/Makefile.am | 3 +- src/protocols/ssh/client.c | 6 -- src/protocols/ssh/client.h | 17 ++-- src/protocols/ssh/guac_handlers.c | 24 +++-- src/protocols/ssh/libssh_compat.h | 57 ------------ src/protocols/ssh/sftp.c | 36 ++++---- src/protocols/ssh/ssh_client.c | 10 +-- src/protocols/ssh/ssh_key.c | 144 ++++++++++++++++++++++++++++++ src/protocols/ssh/ssh_key.h | 98 ++++++++++++++++++++ 9 files changed, 283 insertions(+), 112 deletions(-) delete mode 100644 src/protocols/ssh/libssh_compat.h create mode 100644 src/protocols/ssh/ssh_key.c create mode 100644 src/protocols/ssh/ssh_key.h diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index de0ae565..583f9a67 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -52,6 +52,7 @@ libguac_client_ssh_la_SOURCES = \ ibar.c \ sftp.c \ ssh_client.c \ + ssh_key.c \ terminal.c \ terminal_handlers.c @@ -65,9 +66,9 @@ noinst_HEADERS = \ display.h \ guac_handlers.h \ ibar.h \ - libssh_compat.h \ sftp.h \ ssh_client.h \ + ssh_key.h \ terminal.h \ terminal_handlers.h \ types.h diff --git a/src/protocols/ssh/client.c b/src/protocols/ssh/client.c index de1e2a17..bfa90e01 100644 --- a/src/protocols/ssh/client.c +++ b/src/protocols/ssh/client.c @@ -66,10 +66,8 @@ const char* GUAC_CLIENT_ARGS[] = { "font-name", "font-size", "enable-sftp", -#ifdef ENABLE_SSH_PUBLIC_KEY "private-key", "passphrase", -#endif NULL }; @@ -110,7 +108,6 @@ enum __SSH_ARGS_IDX { */ IDX_ENABLE_SFTP, -#ifdef ENABLE_SSH_PUBLIC_KEY /** * The private key to use for authentication, if any. */ @@ -120,7 +117,6 @@ enum __SSH_ARGS_IDX { * The passphrase required to decrypt the private key, if any. */ IDX_PASSPHRASE, -#endif SSH_ARGS_COUNT }; @@ -149,12 +145,10 @@ int guac_client_init(guac_client* client, int argc, char** argv) { strcpy(client_data->username, argv[IDX_USERNAME]); strcpy(client_data->password, argv[IDX_PASSWORD]); -#ifdef ENABLE_SSH_PUBLIC_KEY /* Init public key auth information */ client_data->key = NULL; strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]); strcpy(client_data->key_passphrase, argv[IDX_PASSPHRASE]); -#endif /* Read font name */ if (argv[IDX_FONT_NAME][0] != 0) diff --git a/src/protocols/ssh/client.h b/src/protocols/ssh/client.h index 3cfa627a..560c5e28 100644 --- a/src/protocols/ssh/client.h +++ b/src/protocols/ssh/client.h @@ -40,12 +40,13 @@ #define _SSH_GUAC_CLIENT_H #include -#include -#include +#include +#include #include "terminal.h" #include "cursor.h" #include "sftp.h" +#include "ssh_key.h" /** * SSH-specific client data. @@ -72,7 +73,6 @@ typedef struct ssh_guac_client_data { */ char password[1024]; -#ifdef ENABLE_SSH_PUBLIC_KEY /** * The private key, encoded as base64. */ @@ -86,8 +86,7 @@ typedef struct ssh_guac_client_data { /** * The private key to use for authentication, if any. */ - ssh_key key; -#endif + ssh_key* key; /** * The name of the font to use for display rendering. @@ -112,17 +111,17 @@ typedef struct ssh_guac_client_data { /** * SSH session, used by the SSH client thread. */ - ssh_session session; + LIBSSH2_SESSION* session; /** * The distinct SSH session used for SFTP. */ - ssh_session sftp_ssh_session; + LIBSSH2_SESSION* sftp_ssh_session; /** * SFTP session, used for file transfers. */ - sftp_session sftp_session; + LIBSSH2_SFTP* sftp_session; /** * The path files will be sent to. @@ -132,7 +131,7 @@ typedef struct ssh_guac_client_data { /** * SSH terminal channel, used by the SSH client thread. */ - ssh_channel term_channel; + LIBSSH2_CHANNEL* term_channel; /** * The terminal which will render all output from the SSH client. diff --git a/src/protocols/ssh/guac_handlers.c b/src/protocols/ssh/guac_handlers.c index 100215b7..29f7049b 100644 --- a/src/protocols/ssh/guac_handlers.c +++ b/src/protocols/ssh/guac_handlers.c @@ -51,9 +51,7 @@ #include #include -#include -#include -#include "libssh_compat.h" +#include #include "guac_handlers.h" #include "client.h" @@ -399,7 +397,7 @@ int ssh_guac_client_size_handler(guac_client* client, int width, int height) { /* Update SSH pty size if connected */ if (guac_client_data->term_channel != NULL) - channel_change_pty_size(guac_client_data->term_channel, + libssh2_channel_request_pty_size(guac_client_data->term_channel, terminal->term_width, terminal->term_height); /* Reset scroll region */ @@ -422,8 +420,8 @@ int ssh_guac_client_free_handler(guac_client* client) { /* Close SSH channel */ if (guac_client_data->term_channel != NULL) { - ssh_channel_close(guac_client_data->term_channel); - ssh_channel_send_eof(guac_client_data->term_channel); + libssh2_channel_send_eof(guac_client_data->term_channel); + libssh2_channel_close(guac_client_data->term_channel); } /* Free terminal */ @@ -431,23 +429,23 @@ int ssh_guac_client_free_handler(guac_client* client) { pthread_join(guac_client_data->client_thread, NULL); /* Free channels */ - ssh_channel_free(guac_client_data->term_channel); + libssh2_channel_free(guac_client_data->term_channel); /* Clean up SFTP */ if (guac_client_data->sftp_session) - sftp_free(guac_client_data->sftp_session); + libssh2_sftp_shutdown(guac_client_data->sftp_session); - if (guac_client_data->sftp_ssh_session) - ssh_free(guac_client_data->sftp_ssh_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); + } /* Free session */ - ssh_free(guac_client_data->session); + libssh2_session_free(guac_client_data->session); -#ifdef ENABLE_SSH_PUBLIC_KEY /* Free auth key */ if (guac_client_data->key != NULL) ssh_key_free(guac_client_data->key); -#endif /* Free clipboard data */ free(guac_client_data->clipboard_data); diff --git a/src/protocols/ssh/libssh_compat.h b/src/protocols/ssh/libssh_compat.h deleted file mode 100644 index 82205d7a..00000000 --- a/src/protocols/ssh/libssh_compat.h +++ /dev/null @@ -1,57 +0,0 @@ - -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is libguac-client-ssh. - * - * The Initial Developer of the Original Code is - * Michael Jumper. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef _SSH_GUAC_LIBSSH_COMPAT_H -#define _SSH_GUAC_LIBSSH_COMPAT_H - -/* Define ssh_channel_close() if undefined */ -#ifndef HAVE_SSH_CHANNEL_CLOSE -#define ssh_channel_close channel_close -#endif - -/* Define ssh_channel_send_eof() if undefined */ -#ifndef HAVE_SSH_CHANNEL_SEND_EOF -#define ssh_channel_send_eof channel_send_eof -#endif - -/* Define ssh_channel_free() if undefined */ -#ifndef HAVE_SSH_CHANNEL_FREE -#define ssh_channel_free channel_free -#endif - -#endif - diff --git a/src/protocols/ssh/sftp.c b/src/protocols/ssh/sftp.c index f5b0259c..fd54be71 100644 --- a/src/protocols/ssh/sftp.c +++ b/src/protocols/ssh/sftp.c @@ -43,8 +43,7 @@ #include #include -#include -#include +#include #include #include @@ -85,7 +84,7 @@ int guac_sftp_file_handler(guac_client* client, guac_stream* stream, ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; char fullpath[GUAC_SFTP_MAX_PATH]; - sftp_file file; + LIBSSH2_SFTP_HANDLE* file; int i; /* Ensure filename is a valid filename and not a path */ @@ -129,8 +128,9 @@ int guac_sftp_file_handler(guac_client* client, guac_stream* stream, fullpath[i] = '\0'; /* Open file via SFTP */ - file = sftp_open(client_data->sftp_session, fullpath, - O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + 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) { @@ -140,7 +140,7 @@ int guac_sftp_file_handler(guac_client* client, guac_stream* stream, } else { guac_client_log_error(client, "Unable to open file \"%s\": %s", - fullpath, ssh_get_error(client_data->sftp_ssh_session)); + fullpath, libssh2_sftp_last_error(client_data->sftp_session)); guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); guac_socket_flush(client->socket); @@ -157,10 +157,10 @@ int guac_sftp_blob_handler(guac_client* client, guac_stream* stream, /* Pull file from stream */ ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - sftp_file file = (sftp_file) stream->data; + LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; /* Attempt write */ - if (sftp_write(file, data, length) == length) { + if (libssh2_sftp_write(file, data, length) == length) { guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(client->socket); @@ -169,7 +169,7 @@ int guac_sftp_blob_handler(guac_client* client, guac_stream* stream, /* Inform of any errors */ else { guac_client_log_error(client, "Unable to write to file: %s", - ssh_get_error(client_data->sftp_ssh_session)); + libssh2_sftp_last_error(client_data->sftp_session)); guac_protocol_send_ack(client->socket, stream, "SFTP: Write failed", GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); guac_socket_flush(client->socket); @@ -182,10 +182,10 @@ int guac_sftp_blob_handler(guac_client* client, guac_stream* stream, int guac_sftp_end_handler(guac_client* client, guac_stream* stream) { /* Pull file from stream */ - sftp_file file = (sftp_file) stream->data; + LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; /* Attempt to close file */ - if (sftp_close(file) == SSH_OK) { + if (libssh2_sftp_close(file) == 0) { guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(client->socket); @@ -205,14 +205,14 @@ 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; - sftp_file file = (sftp_file) stream->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 = sftp_read(file, buffer, sizeof(buffer)); + int bytes_read = libssh2_sftp_read(file, buffer, sizeof(buffer)); /* If bytes read, send as blob */ if (bytes_read > 0) @@ -228,7 +228,7 @@ int guac_sftp_ack_handler(guac_client* client, guac_stream* stream, /* Otherwise, fail stream */ else { guac_client_log_error(client, "Error reading file: %s", - ssh_get_error(client_data->sftp_ssh_session)); + libssh2_sftp_last_error(client_data->sftp_session)); guac_protocol_send_end(client->socket, stream); guac_client_free_stream(client, stream); } @@ -249,13 +249,15 @@ guac_stream* guac_sftp_download_file(guac_client* client, ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; guac_stream* stream; - sftp_file file; + LIBSSH2_SFTP_HANDLE* file; /* Attempt to open file for reading */ - file = sftp_open(client_data->sftp_session, filename, O_RDONLY, 0); + file = libssh2_sftp_open(client_data->sftp_session, filename, + LIBSSH2_FXF_READ, 0); if (file == NULL) { guac_client_log_error(client, "Unable to read file \"%s\": %s", - filename, ssh_get_error(client_data->sftp_ssh_session)); + filename, + libssh2_sftp_last_error(client_data->sftp_session)); return NULL; } diff --git a/src/protocols/ssh/ssh_client.c b/src/protocols/ssh/ssh_client.c index 3a8d67e8..5c10f7af 100644 --- a/src/protocols/ssh/ssh_client.c +++ b/src/protocols/ssh/ssh_client.c @@ -44,8 +44,7 @@ #include #include -#include -#include +#include #include "client.h" #include "common.h" @@ -153,7 +152,6 @@ static ssh_session __guac_ssh_create_session(guac_client* client) { return NULL; } -#ifdef ENABLE_SSH_PUBLIC_KEY /* Authenticate with key if available */ if (client_data->key != NULL) { if (ssh_userauth_publickey(session, NULL, client_data->key) @@ -166,7 +164,6 @@ static ssh_session __guac_ssh_create_session(guac_client* client) { return NULL; } } -#endif /* Authenticate with password */ if (ssh_userauth_password(session, NULL, client_data->password) @@ -205,7 +202,6 @@ void* ssh_client_thread(void* data) { snprintf(name, sizeof(name)-1, "%s@%s", client_data->username, client_data->hostname); guac_protocol_send_name(socket, name); -#ifdef ENABLE_SSH_PUBLIC_KEY /* If key specified, import */ if (client_data->key_base64[0] != 0) { @@ -239,10 +235,6 @@ void* ssh_client_thread(void* data) { /* Otherwise, get password if not provided */ else if (client_data->password[0] == 0) { -#else - /* Get password if not provided */ - if (client_data->password[0] == 0) { -#endif if (prompt(client, "Password: ", client_data->password, sizeof(client_data->password), false) == NULL) return NULL; diff --git a/src/protocols/ssh/ssh_key.c b/src/protocols/ssh/ssh_key.c new file mode 100644 index 00000000..b647e6fd --- /dev/null +++ b/src/protocols/ssh/ssh_key.c @@ -0,0 +1,144 @@ + +#include + +#include +#include +#include +#include + +#include "ssh_buffer.h" +#include "ssh_key.h" + +ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) { + + ssh_key* key; + BIO* key_bio; + + char* public_key; + char* pos; + + /* Create BIO for reading key from memory */ + key_bio = BIO_new_mem_buf(data, length); + + /* If RSA key, load RSA */ + if (length > sizeof(SSH_RSA_KEY_HEADER)-1 + && memcmp(SSH_RSA_KEY_HEADER, data, + sizeof(SSH_RSA_KEY_HEADER)-1) == 0) { + + RSA* rsa_key; + + /* Read key */ + rsa_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, passphrase); + if (rsa_key == NULL) + return NULL; + + /* Allocate key */ + key = malloc(sizeof(ssh_key)); + key->rsa = rsa_key; + + /* Set type */ + key->type = SSH_KEY_RSA; + + /* Allocate space for public key */ + public_key = malloc(4096); + pos = public_key; + + /* Derive public key */ + buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1); + buffer_write_bignum(&pos, rsa_key->e); + buffer_write_bignum(&pos, rsa_key->n); + + /* Save public key to structure */ + key->public_key = public_key; + key->public_key_length = pos - public_key; + + } + + /* If DSA key, load DSA */ + else if (length > sizeof(SSH_DSA_KEY_HEADER)-1 + && memcmp(SSH_DSA_KEY_HEADER, data, + sizeof(SSH_DSA_KEY_HEADER)-1) == 0) { + + DSA* dsa_key; + + /* Read key */ + dsa_key = PEM_read_bio_DSAPrivateKey(key_bio, NULL, NULL, passphrase); + if (dsa_key == NULL) + return NULL; + + /* Allocate key */ + key = malloc(sizeof(ssh_key)); + key->dsa = dsa_key; + + /* Set type */ + key->type = SSH_KEY_DSA; + + /* Allocate space for public key */ + public_key = malloc(4096); + pos = public_key; + + /* Derive public key */ + buffer_write_string(&pos, "ssh-dsa", sizeof("ssh-dsa")-1); + buffer_write_bignum(&pos, dsa_key->p); + buffer_write_bignum(&pos, dsa_key->q); + buffer_write_bignum(&pos, dsa_key->g); + buffer_write_bignum(&pos, dsa_key->pub_key); + + /* Save public key to structure */ + key->public_key = public_key; + key->public_key_length = pos - public_key; + + } + + /* Otherwise, unsupported type */ + else + return NULL; + + /* Copy private key to structure */ + key->private_key_length = length; + key->private_key = malloc(length); + memcpy(key->private_key, data, length); + + return key; + +} + +void ssh_key_free(ssh_key* key) { + free(key->public_key); + free(key); +} + +int ssh_key_sign(ssh_key* key, const char* data, int length, u_char* sig) { + + const EVP_MD* md; + EVP_MD_CTX md_ctx; + + u_char digest[EVP_MAX_MD_SIZE]; + u_int dlen, len; + + /* Get SHA1 digest */ + if ((md = EVP_get_digestbynid(NID_sha1)) == NULL) + return -1; + + /* Digest data */ + EVP_DigestInit(&md_ctx, md); + EVP_DigestUpdate(&md_ctx, data, length); + EVP_DigestFinal(&md_ctx, digest, &dlen); + + /* Sign with key */ + switch (key->type) { + + case SSH_KEY_RSA: + if (RSA_sign(NID_sha1, digest, dlen, sig, &len, key->rsa) == 1) + return len; + + case SSH_KEY_DSA: + if (DSA_sign(NID_sha1, digest, dlen, sig, &len, key->dsa) == 1) + return len; + + } + + return -1; + +} + diff --git a/src/protocols/ssh/ssh_key.h b/src/protocols/ssh/ssh_key.h new file mode 100644 index 00000000..db36cddb --- /dev/null +++ b/src/protocols/ssh/ssh_key.h @@ -0,0 +1,98 @@ + +#ifndef _GUAC_SSH_KEY_H +#define _GUAC_SSH_KEY_H + +#include +#include +#include +#include +#include + +/** + * The expected header of RSA private keys. + */ +#define SSH_RSA_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" + +/** + * The expected header of DSA private keys. + */ +#define SSH_DSA_KEY_HEADER "-----BEGIN DSA PRIVATE KEY-----" + +/** + * The type of an SSH key. + */ +typedef enum ssh_key_type { + + /** + * RSA key. + */ + SSH_KEY_RSA, + + /** + * DSA key. + */ + SSH_KEY_DSA + +} ssh_key_type; + +/** + * Abstraction of a key used for SSH authentication. + */ +typedef struct ssh_key { + + /** + * The type of this key. + */ + ssh_key_type type; + + /** + * Underlying RSA private key, if any. + */ + RSA* rsa; + + /** + * Underlying DSA private key, if any. + */ + DSA* dsa; + + /** + * The associated public key, encoded as necessary for SSH. + */ + char* public_key; + + /** + * The length of the public key, in bytes. + */ + int public_key_length; + + /** + * The private key, encoded as necessary for SSH. + */ + char* private_key; + + /** + * The length of the private key, in bytes. + */ + int private_key_length; + +} ssh_key; + +/** + * Allocates a new key containing the given private key data and specified + * passphrase. If unable to read the key, NULL is returned. + */ +ssh_key* ssh_key_alloc(char* data, int length, char* passphrase); + +/** + * Frees all memory associated with the given key. + */ +void ssh_key_free(ssh_key* key); + +/** + * Signs the given data using the given key, returning the length of the + * signature in bytes, or a value less than zero on error. + */ +int ssh_key_sign(ssh_key* key, const char* data, int length, u_char* sig); + +#endif +