diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index adc4c4e9..137553a7 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -505,6 +505,79 @@ struct guac_user { }; +/** + * Handler for Guacamole protocol opcode specific to the handshake that + * happens between client and server at the beginning of the connection. The + * handler will be invoked when the matching opcode is received during the + * handshake process. + * + * @param user + * The user that initiated the handshake. + * + * @param parser + * The parser allocated for parsing the data provided by the client. + * + * @param timeout + * The timeout, in microseconds, for parsing the value. + * + * @return + * Zero if the handshake instruction is successfully parsed; otherwise + * false. + */ +typedef int __guac_handshake_handler(guac_user* user, int argc, char** argv); + +/** + * Structure that maps opcodes received during the handshake phase of the + * connection to callback functions used when those opcodes are received. + */ +typedef struct __guac_handshake_mapping { + + /** + * The instruction opcode which maps to the handler. + */ + char* opcode; + + /** + * The handler function used when specified opcode is received. + */ + __guac_handshake_handler* handler; + +} __guac_handshake_mapping; + +/** + * Internal handler function that is called when the size instruction is + * received during the handshake process. + */ +__guac_handshake_handler __guac_handshake_size_handler; + +/** + * Internal handler function that is called when the audio instruction is + * received during the handshake process, specifying the audio mimetypes + * available to the client. + */ +__guac_handshake_handler __guac_handshake_audio_handler; + +/** + * Internal handler function that is called when the video instruction is + * received during the handshake process, specifying the video mimetypes + * available to the client. + */ +__guac_handshake_handler __guac_handshake_video_handler; + +/** + * Internal handler function that is called when the image instruction is + * received during the handshake process, specifying the image mimetypes + * available to the client. + */ +__guac_handshake_handler __guac_handshake_image_handler; + +/** + * Internal handler function that is called when the timezone instruction is + * received during the handshake process, specifying the timezone of the + * client. + */ +__guac_handshake_handler __guac_handshake_timezone_handler; + /** * Allocates a new, blank user, not associated with any specific client or * socket. diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index e5143927..a9cf003b 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -171,6 +171,80 @@ static void guac_free_mimetypes(char** mimetypes) { } +/* Guacamole handshake handler functions. */ + +int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) { + + /* Validate size of instruction. */ + if (argc < 2) { + guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" " + "instruction lacked required arguments."); + return 1; + } + + /* Parse optimal screen dimensions from size instruction */ + user->info.optimal_width = atoi(argv[0]); + user->info.optimal_height = atoi(argv[1]); + + /* If DPI given, set the user resolution */ + if (argc >= 3) + user->info.optimal_resolution = atoi(argv[2]); + + /* Otherwise, use a safe default for rough backwards compatibility */ + else + user->info.optimal_resolution = 96; + + return 0; + +} + +int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { + + /* Store audio mimetypes */ + user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) { + + /* Store video mimetypes */ + user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) { + + /* Store image mimetypes */ + user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { + + /* Store timezone, if present */ + if (argc > 0 && strcmp(argv[0], "")) + user->info.timezone = (const char*) strdup(argv[0]); + + return 0; + +} + +/* Guacamole handshake handler mappings. */ +__guac_handshake_mapping __guac_handshake_map[] = { + {"size", __guac_handshake_size_handler}, + {"audio", __guac_handshake_audio_handler}, + {"video", __guac_handshake_video_handler}, + {"image", __guac_handshake_image_handler}, + {"timezone", __guac_handshake_timezone_handler}, + {NULL, NULL} +}; + /** * The thread which handles all user input, calling event handlers for received * instructions. @@ -305,122 +379,53 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_parser* parser = guac_parser_alloc(); - /* Get optimal screen size */ - if (guac_parser_expect(parser, socket, usec_timeout, "size")) { - - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"size\""); - - guac_parser_free(parser); - return 1; - } - - /* Validate content of size instruction */ - if (parser->argc < 2) { - guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" " - "instruction lacked required arguments."); - guac_parser_free(parser); - return 1; - } - - /* Parse optimal screen dimensions from size instruction */ - user->info.optimal_width = atoi(parser->argv[0]); - user->info.optimal_height = atoi(parser->argv[1]); - - /* If DPI given, set the user resolution */ - if (parser->argc >= 3) - user->info.optimal_resolution = atoi(parser->argv[2]); - - /* Otherwise, use a safe default for rough backwards compatibility */ - else - user->info.optimal_resolution = 96; - - /* Get supported audio formats */ - if (guac_parser_expect(parser, socket, usec_timeout, "audio")) { - - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"audio\""); - - guac_parser_free(parser); - return 1; - } - - /* Store audio mimetypes */ - char** audio_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc); - user->info.audio_mimetypes = (const char**) audio_mimetypes; - - /* Get supported video formats */ - if (guac_parser_expect(parser, socket, usec_timeout, "video")) { - - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"video\""); - - guac_parser_free(parser); - return 1; - } - - /* Store video mimetypes */ - char** video_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc); - user->info.video_mimetypes = (const char**) video_mimetypes; - - /* Get supported image formats */ - if (guac_parser_expect(parser, socket, usec_timeout, "image")) { - - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"image\""); - - guac_parser_free(parser); - return 1; - } - - /* Store image mimetypes */ - char** image_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc); - user->info.image_mimetypes = (const char**) image_mimetypes; - - /* Get client timezone */ - if (guac_parser_expect(parser, socket, usec_timeout, "timezone")) { + /* Handle each of the opcodes. */ + while (1) { + if (guac_parser_read(parser, socket, usec_timeout)) { + guac_user_log_handshake_failure(user); + guac_user_log_guac_error(user, GUAC_LOG_DEBUG, + "Error while reading opcode instruction."); + guac_parser_free(parser); + return 1; + } - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"timezone\""); + /* If we receive the connect opcode, we're done. */ + if (strcmp(parser->opcode, "connect") == 0) + break; - guac_parser_free(parser); - return 1; - - } - - /* Check number of timezone arguments */ - if (parser->argc < 1) { - guac_user_log(user, GUAC_LOG_ERROR, "Received \"timezone\" instruction " - "lacked required arguments."); - guac_parser_free(parser); - return 1; - } - - /* Store timezone, if present */ - char* timezone = parser->argv[0]; - if (!strcmp(timezone, "")) - user->info.timezone = (const char*) timezone; + /* Loop available opcodes and run handler if/when match found. */ + __guac_handshake_mapping* current = __guac_handshake_map; + while (current->opcode != NULL) { + + /* Check if loop opcode matches parsed opcode. */ + if (strcmp(parser->opcode, current->opcode) == 0) { + + /* If calling the handler fails, log it and return. */ + if (current->handler(user, parser->argc, parser->argv)) { + + guac_user_log_handshake_failure(user); + guac_user_log_guac_error(user, GUAC_LOG_DEBUG, + "Error handling handling opcode during handshake."); + guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", + current->opcode); - /* Get args from connect instruction */ - if (guac_parser_expect(parser, socket, usec_timeout, "connect")) { + guac_parser_free(parser); + return 1; + } + + /* If calling the handler has succeeded, log it and break. */ + else { + guac_user_log(user, GUAC_LOG_DEBUG, + "Successfully processed instruction: \"%s\"", + current->opcode); + break; + } - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"connect\""); - - guac_parser_free(parser); - return 1; + } + + /* Move to next opcode. */ + current++; + } } /* Acknowledge connection availability */ @@ -448,16 +453,15 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { "users remain)", user->user_id, client->connected_users); } - - /* Free mimetype lists */ - guac_free_mimetypes(audio_mimetypes); - guac_free_mimetypes(video_mimetypes); - guac_free_mimetypes(image_mimetypes); - /* Free timezone */ - if (timezone != NULL) - free(timezone); - + /* Free mimetype character arrays. */ + guac_free_mimetypes((char **) user->info.audio_mimetypes); + guac_free_mimetypes((char **) user->info.image_mimetypes); + guac_free_mimetypes((char **) user->info.video_mimetypes); + + /* Free timezone info. */ + free((char *) user->info.timezone); + guac_parser_free(parser); /* Successful disconnect */