From 7369bed22c49ad54fd44dd300efd4a8ec5d7a9d3 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Fri, 9 Aug 2019 21:09:11 -0400 Subject: [PATCH] GUACAMOLE-221: Add support for sending multiple params in required. --- src/libguac/guacamole/protocol.h | 4 +- src/libguac/protocol.c | 28 +++++++++++--- src/protocols/rdp/argv.c | 13 ++++--- src/protocols/rdp/client.c | 1 + src/protocols/rdp/rdp.c | 53 ++++++++++++++++++--------- src/protocols/rdp/rdp.h | 21 +++++++++++ src/protocols/ssh/ssh.c | 2 +- src/protocols/vnc/argv.c | 29 ++++++++++++++- src/protocols/vnc/auth.c | 63 +++++++++++++++++++++++++++++--- src/protocols/vnc/client.c | 1 + src/protocols/vnc/vnc.h | 15 ++++++++ 11 files changed, 191 insertions(+), 39 deletions(-) diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 54f7b072..54e8d6f6 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -803,12 +803,12 @@ int guac_protocol_send_rect(guac_socket* socket, const guac_layer* layer, * The guac_socket connection to use. * * @param required - * The name of the parameter that is required. + * A NULL-terminated array of required parameters. * * @return * Zero on success, non-zero on error. */ -int guac_protocol_send_required(guac_socket* socket, const char* required); +int guac_protocol_send_required(guac_socket* socket, const char** required); /** * Sends a reset instruction over the given guac_socket connection. diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 972ac547..b592ebc3 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -961,17 +961,33 @@ int guac_protocol_send_rect(guac_socket* socket, } -int guac_protocol_send_required(guac_socket* socket, const char* required) { +static int __guac_protocol_send_required(guac_socket* socket, + const char** required) { + + if (guac_socket_write_string(socket, "8.required")) return -1; + + for (int i=0; required[i] != NULL; i++) { + + if (guac_socket_write_string(socket, ",")) + return -1; + + if (__guac_socket_write_length_string(socket, required[i])) + return -1; + + } + + return guac_socket_write_string(socket, ";"); + +} + +int guac_protocol_send_required(guac_socket* socket, const char** required) { int ret_val; guac_socket_instruction_begin(socket); - ret_val = - guac_socket_write_string(socket, "8.required,") - || __guac_socket_write_length_string(socket, required) - || guac_socket_write_string(socket, ";"); - + ret_val = __guac_protocol_send_required(socket, required); guac_socket_instruction_end(socket); + return ret_val; } diff --git a/src/protocols/rdp/argv.c b/src/protocols/rdp/argv.c index a79a64c4..96db4b9c 100644 --- a/src/protocols/rdp/argv.c +++ b/src/protocols/rdp/argv.c @@ -48,8 +48,8 @@ typedef enum guac_rdp_argv_setting { /** * The domain to use for connection authentication. */ - GUAC_RDP_ARGV_SETTING_DOMAIN - + GUAC_RDP_ARGV_SETTING_DOMAIN, + } guac_rdp_argv_setting; /** @@ -125,24 +125,27 @@ static int guac_rdp_argv_end_handler(guac_user* user, free(settings->username); settings->username = malloc(strlen(argv->buffer) * sizeof(char)); strcpy(settings->username, argv->buffer); - pthread_cond_broadcast(&(rdp_client->rdp_cond)); + rdp_client->rdp_cond_flags ^= GUAC_RDP_COND_FLAG_USERNAME; break; case GUAC_RDP_ARGV_SETTING_PASSWORD: free(settings->password); settings->password = malloc(strlen(argv->buffer) * sizeof(char)); strcpy(settings->password, argv->buffer); - pthread_cond_broadcast(&(rdp_client->rdp_cond)); + rdp_client->rdp_cond_flags ^= GUAC_RDP_COND_FLAG_PASSWORD; break; case GUAC_RDP_ARGV_SETTING_DOMAIN: free(settings->domain); settings->domain = malloc(strlen(argv->buffer) * sizeof(char)); strcpy(settings->domain, argv->buffer); - pthread_cond_broadcast(&(rdp_client->rdp_cond)); + rdp_client->rdp_cond_flags ^= GUAC_RDP_COND_FLAG_DOMAIN; break; } + + if (!rdp_client->rdp_cond_flags) + pthread_cond_signal(&(rdp_client->rdp_cond)); free(argv); return 0; diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 14b4c7f2..f232e6a8 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -161,6 +161,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) { /* Init RDP credential lock and condition */ pthread_mutex_init(&(rdp_client->rdp_credential_lock), &(rdp_client->attributes)); pthread_cond_init(&(rdp_client->rdp_credential_cond), NULL);; + rdp_client->rdp_credential_flags = 0; /* Set handlers */ client->join_handler = guac_rdp_user_join_handler; diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 8b3bdebe..4441d66c 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -230,32 +230,51 @@ static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, guac_client* client = ((rdp_freerdp_context*) context)->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_settings* settings = rdp_client->settings; + char* params[4] = {}; + int i = 0; - pthread_mutex_lock(&(rdp_client->rdp_lock)); + if (settings->username == NULL || strcmp(settings->username, "") == 0) { + params[i] = "username"; + rdp_client->rdp_cond_flags |= GUAC_RDP_COND_FLAG_USERNAME; + i++; + } - while (settings->username == NULL || strcmp(settings->username, "") == 0) { - guac_protocol_send_required(client->socket, "username"); + if (settings->password == NULL || strcmp(settings->password, "") == 0) { + params[i] = "password"; + rdp_client->rdp_cond_flags |= GUAC_RDP_COND_FLAG_PASSWORD; + i++; + } + + if (settings->domain == NULL || strcmp(settings->domain, "") == 0) { + params[i] = "domain"; + rdp_client->rdp_cond_flags |= GUAC_RDP_COND_FLAG_DOMAIN; + i++; + } + + /* NULL-terminate the array. */ + params[i] = NULL; + + if (i > 0) { + /* Lock the client thread. */ + pthread_mutex_lock(&(rdp_client->rdp_lock)); + + /* Send require params and flush socket. */ + guac_protocol_send_required(client->socket, (const char**) params); guac_socket_flush(client->socket); + + /* Wait for condition. */ pthread_cond_wait(&(rdp_client->rdp_cond), &(rdp_client->rdp_lock)); + + /* Get new values from settings. */ *username = settings->username; - } - - while (settings->password == NULL || strcmp(settings->password, "") == 0) { - guac_protocol_send_required(client->socket, "password"); - guac_socket_flush(client->socket); - pthread_cond_wait(&(rdp_client->rdp_cond), &(rdp_client->rdp_lock)); *password = settings->password; - } - - while (settings->domain == NULL || strcmp(settings->domain, "") == 0) { - guac_protocol_send_required(client->socket, "domain"); - guac_socket_flush(client->socket); - pthread_cond_wait(&(rdp_client->rdp_cond), &(rdp_client->rdp_lock)); *domain = settings->domain; + + /* Unlock the thread. */ + pthread_mutex_unlock(&(rdp_client->rdp_lock)); } - pthread_mutex_unlock(&(rdp_client->rdp_lock)); - + /* Always return TRUE allowing connection to retry. */ return TRUE; } diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 68c4cb00..418c908a 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -49,6 +49,21 @@ #include #include +/** + * A flag for tracking if we are waiting conditionally on a username. + */ +#define GUAC_RDP_COND_FLAG_USERNAME 1 + +/** + * A flag for tracking if we are waiting conditionally on a password. + */ +#define GUAC_RDP_COND_FLAG_PASSWORD 2 + +/** + * A flag for tracking if we are waiting conditionally on a domain. + */ +#define GUAC_RDP_COND_FLAG_DOMAIN 3 + /** * RDP-specific client data. */ @@ -164,6 +179,12 @@ typedef struct guac_rdp_client { * particular credential to be provided. */ pthread_cond_t rdp_credential_cond; + + /** + * Flags for tracking events related to the rdp_credential_cond + * pthread condition. + */ + unsigned rdp_credential_flags; /** * Common attributes for locks. diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 7ee18c4b..f3acedb3 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -75,7 +75,7 @@ static void guac_ssh_get_credential(guac_client *client, char* cred_name) { pthread_mutex_lock(&(ssh_client->term_channel_lock)); - guac_protocol_send_required(client->socket, cred_name); + guac_protocol_send_required(client->socket, (const char* []) {cred_name, NULL}); guac_socket_flush(client->socket); pthread_cond_wait(&(ssh_client->ssh_cond), &(ssh_client->term_channel_lock)); diff --git a/src/protocols/vnc/argv.c b/src/protocols/vnc/argv.c index 3b147c7f..24ac33ed 100644 --- a/src/protocols/vnc/argv.c +++ b/src/protocols/vnc/argv.c @@ -35,6 +35,11 @@ */ typedef enum guac_vnc_argv_setting { + /** + * The username for the connection. + */ + GUAC_VNC_ARGV_SETTING_USERNAME, + /** * The password for the connection. */ @@ -109,6 +114,19 @@ static int guac_vnc_argv_end_handler(guac_user* user, guac_stream* stream) { /* Apply changes to chosen setting */ switch (argv->setting) { + /* Update username */ + case GUAC_VNC_ARGV_SETTING_USERNAME: + + /* Update username in settings. */ + if (settings->username != NULL) + free(settings->username); + settings->username = malloc(strlen(argv->buffer) * sizeof(char)); + strcpy(settings->username, argv->buffer); + + /* Remove the username conditional flag. */ + vnc_client->argv_cond_flags ^= GUAC_VNC_COND_FLAG_USERNAME; + break; + /* Update password */ case GUAC_VNC_ARGV_SETTING_PASSWORD: @@ -118,10 +136,15 @@ static int guac_vnc_argv_end_handler(guac_user* user, guac_stream* stream) { settings->password = malloc(strlen(argv->buffer) * sizeof(char)); strcpy(settings->password, argv->buffer); - pthread_cond_broadcast(&(vnc_client->argv_cond)); + /* Remove the password conditional flag. */ + vnc_client->argv_cond_flags ^= GUAC_VNC_COND_FLAG_PASSWORD; break; } + + /* If no flags are set, signal the conditional. */ + if (!vnc_client->argv_cond_flags) + pthread_cond_broadcast(&(vnc_client->argv_cond)); free(argv); return 0; @@ -134,7 +157,9 @@ int guac_vnc_argv_handler(guac_user* user, guac_stream* stream, char* mimetype, guac_vnc_argv_setting setting; /* Allow users to update authentication information */ - if (strcmp(name, "password") == 0) + if (strcmp(name, "username") == 0) + setting = GUAC_VNC_ARGV_SETTING_USERNAME; + else if (strcmp(name, "password") == 0) setting = GUAC_VNC_ARGV_SETTING_PASSWORD; /* No other connection parameters may be updated */ diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index 54d2f2e7..c0ae2b98 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -36,11 +36,23 @@ char* guac_vnc_get_password(rfbClient* client) { guac_vnc_client* vnc_client = ((guac_vnc_client*) gc->data); guac_vnc_settings* settings = vnc_client->settings; + /* If password isn't around, prompt for it. */ if (settings->password == NULL || strcmp(settings->password, "") == 0) { + /* Lock the thread. */ pthread_mutex_lock(&(vnc_client->argv_lock)); - guac_protocol_send_required(gc->socket, "password"); + + /* Send the request for password and flush the socket. */ + guac_protocol_send_required(gc->socket, + (const char* []) {"password", NULL}); guac_socket_flush(gc->socket); + + /* Set the conditional flag. */ + vnc_client->argv_cond_flags |= GUAC_VNC_COND_FLAG_PASSWORD; + + /* Wait for the condition. */ pthread_cond_wait(&(vnc_client->argv_cond), &(vnc_client->argv_lock)); + + /* Unlock the thread. */ pthread_mutex_unlock(&(vnc_client->argv_lock)); } @@ -50,13 +62,52 @@ char* guac_vnc_get_password(rfbClient* client) { rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); - guac_vnc_settings* settings = ((guac_vnc_client*) gc->data)->settings; - + guac_vnc_client* vnc_client = ((guac_vnc_client*) gc->data); + guac_vnc_settings* settings = vnc_client->settings; + + /* Handle request for Username/Password credentials */ if (credentialType == rfbCredentialTypeUser) { rfbCredential *creds = malloc(sizeof(rfbCredential)); - creds->userCredential.username = settings->username; - creds->userCredential.password = settings->password; - return creds; + char* params[2] = {NULL}; + int i = 0; + + /* Check if username is null or empty. */ + if (settings->username == NULL || strcmp(settings->username, "") == 0) { + params[i] = "username"; + i++; + vnc_client->argv_cond_flags |= GUAC_VNC_COND_FLAG_USERNAME; + } + + /* Check if password is null or empty. */ + if (settings->password == NULL || strcmp(settings->password, "") == 0) { + params[i] = "password"; + i++; + vnc_client->argv_cond_flags |= GUAC_VNC_COND_FLAG_PASSWORD; + } + + /* If we have empty parameters, request them. */ + if (i > 0) { + /* Lock the thread. */ + pthread_mutex_lock(&(vnc_client->argv_lock)); + + /* Send required parameters to client and flush the socket. */ + guac_protocol_send_required(gc->socket, (const char**) params); + guac_socket_flush(gc->socket); + + /* Wait for the parameters to be returned. */ + pthread_cond_wait(&(vnc_client->argv_cond), &(vnc_client->argv_lock)); + + /* Pull the credentials from updated settings. */ + creds->userCredential.username = settings->username; + creds->userCredential.password = settings->password; + + /* Unlock the thread. */ + pthread_mutex_unlock(&(vnc_client->argv_lock)); + + return creds; + + } + } guac_client_abort(gc, GUAC_PROTOCOL_STATUS_SERVER_ERROR, diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 691c490c..e52dfc55 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -57,6 +57,7 @@ int guac_client_init(guac_client* client) { /* Initialize argv lock and condition */ pthread_mutex_init(&(vnc_client->argv_lock), NULL); pthread_cond_init(&(vnc_client->argv_cond), NULL); + vnc_client->argv_cond_flags = 0; /* Init clipboard */ vnc_client->clipboard = guac_common_clipboard_alloc(GUAC_VNC_CLIPBOARD_MAX_LENGTH); diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index e36ac66d..c30205e8 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -45,6 +45,16 @@ #include +/** + * A flag for tracking status of requesting username from client. + */ +#define GUAC_VNC_COND_FLAG_USERNAME 1 + +/** + * A flag for tracking status of requesting password from client. + */ +#define GUAC_VNC_COND_FLAG_PASSWORD 2 + /** * VNC-specific client data. */ @@ -71,6 +81,11 @@ typedef struct guac_vnc_client { * The condition for signaling argv updates. */ pthread_cond_t argv_cond; + + /** + * Flags for conditional signaling for argv updates; + */ + unsigned argv_cond_flags; /** * The underlying VNC client.