/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "config.h" #include "guacamole/client.h" #include "guacamole/error.h" #include "guacamole/parser.h" #include "guacamole/protocol.h" #include "guacamole/socket.h" #include "guacamole/user.h" #include "user-handlers.h" #include #include #include /** * Parameters required by the user input thread. */ typedef struct guac_user_input_thread_params { /** * The parser which will be used throughout the user's session. */ guac_parser* parser; /** * A reference to the connected user. */ guac_user* user; /** * The number of microseconds to wait for instructions from a connected * user before closing the connection with an error. */ int usec_timeout; } guac_user_input_thread_params; /** * Prints an error message using the logging facilities of the given user, * automatically including any information present in guac_error. * * @param user * The guac_user associated with the error that occurred. * * @param level * The level at which to log this message. * * @param message * The message to log. */ static void guac_user_log_guac_error(guac_user* user, guac_client_log_level level, const char* message) { if (guac_error != GUAC_STATUS_SUCCESS) { /* If error message provided, include in log */ if (guac_error_message != NULL) guac_user_log(user, level, "%s: %s", message, guac_error_message); /* Otherwise just log with standard status string */ else guac_user_log(user, level, "%s: %s", message, guac_status_string(guac_error)); } /* Just log message if no status code */ else guac_user_log(user, level, "%s", message); } /** * Logs a reasonable explanatory message regarding handshake failure based on * the current value of guac_error. * * @param user * The guac_user associated with the failed Guacamole protocol handshake. */ static void guac_user_log_handshake_failure(guac_user* user) { if (guac_error == GUAC_STATUS_CLOSED) guac_user_log(user, GUAC_LOG_DEBUG, "Guacamole connection closed during handshake"); else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR) guac_user_log(user, GUAC_LOG_ERROR, "Guacamole protocol violation. Perhaps the version of " "guacamole-client is incompatible with this version of " "libguac?"); else guac_user_log(user, GUAC_LOG_WARNING, "Guacamole handshake failed: %s", guac_status_string(guac_error)); } /** * The thread which handles all user input, calling event handlers for received * instructions. * * @param data * A pointer to a guac_user_input_thread_params structure describing the * user whose input is being handled and the guac_parser with which to * handle it. * * @return * Always NULL. */ static void* guac_user_input_thread(void* data) { guac_user_input_thread_params* params = (guac_user_input_thread_params*) data; int usec_timeout = params->usec_timeout; guac_user* user = params->user; guac_parser* parser = params->parser; guac_client* client = user->client; guac_socket* socket = user->socket; /* Guacamole user input loop */ while (client->state == GUAC_CLIENT_RUNNING && user->active) { /* Read instruction, stop on error */ if (guac_parser_read(parser, socket, usec_timeout)) { if (guac_error == GUAC_STATUS_TIMEOUT) guac_user_abort(user, GUAC_PROTOCOL_STATUS_CLIENT_TIMEOUT, "User is not responding."); else { if (guac_error != GUAC_STATUS_CLOSED) guac_user_log_guac_error(user, GUAC_LOG_WARNING, "Guacamole connection failure"); guac_user_stop(user); } return NULL; } /* Reset guac_error and guac_error_message (user/client handlers are not * guaranteed to set these) */ guac_error = GUAC_STATUS_SUCCESS; guac_error_message = NULL; /* Call handler, stop on error */ if (__guac_user_call_opcode_handler(__guac_instruction_handler_map, user, parser->opcode, parser->argc, parser->argv)) { /* Log error */ guac_user_log_guac_error(user, GUAC_LOG_WARNING, "User connection aborted"); /* Log handler details */ guac_user_log(user, GUAC_LOG_DEBUG, "Failing instruction handler in user was \"%s\"", parser->opcode); guac_user_stop(user); return NULL; } } return NULL; } /** * Starts the input/output threads of a new user. This function will block * until the user disconnects. If an error prevents the input/output threads * from starting, guac_user_stop() will be invoked on the given user. * * @param parser * The guac_parser to use to handle all input from the given user. * * @param user * The user whose associated I/O transfer threads should be started. * * @param usec_timeout * The number of microseconds to wait for instructions from the given * user before closing the connection with an error. * * @return * Zero if the I/O threads started successfully and user has disconnected, * or non-zero if the I/O threads could not be started. */ static int guac_user_start(guac_parser* parser, guac_user* user, int usec_timeout) { guac_user_input_thread_params params = { .parser = parser, .user = user, .usec_timeout = usec_timeout }; pthread_t input_thread; if (pthread_create(&input_thread, NULL, guac_user_input_thread, (void*) ¶ms)) { guac_user_log(user, GUAC_LOG_ERROR, "Unable to start input thread"); guac_user_stop(user); return -1; } /* Wait for I/O threads */ pthread_join(input_thread, NULL); /* Explicitly signal disconnect */ guac_protocol_send_disconnect(user->socket); guac_socket_flush(user->socket); /* Done */ return 0; } /** * This function loops through the received instructions during the handshake * with the client attempting to join the connection, and runs the handlers * for each of the opcodes, ending when the connect instruction is received. * Returns zero if the handshake completes successfully with the connect opcode, * or a non-zero value if an error occurs. * * @param user * The guac_user attempting to join the connection. * * @param parser * The parser used to examine the received data. * * @param usec_timeout * The timeout, in microseconds, for reading the instructions. * * @return * Zero if the handshake completes successfully with the connect opcode, * or non-zero if an error occurs. */ static int __guac_user_handshake(guac_user* user, guac_parser* parser, int usec_timeout) { guac_socket* socket = user->socket; /* Handle each of the opcodes. */ while (guac_parser_read(parser, socket, usec_timeout) == 0) { /* If we receive the connect opcode, we're done. */ if (strcmp(parser->opcode, "connect") == 0) return 0; guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s", parser->opcode); /* Run instruction handler for opcode with arguments. */ if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user, parser->opcode, parser->argc, parser->argv)) { guac_user_log_handshake_failure(user); guac_user_log_guac_error(user, GUAC_LOG_DEBUG, "Error handling instruction during handshake."); guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", parser->opcode); guac_parser_free(parser); return 1; } } /* If we get here it's because we never got the connect instruction. */ guac_user_log(user, GUAC_LOG_ERROR, "Handshake failed, \"connect\" instruction was not received."); return 1; } int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_socket* socket = user->socket; guac_client* client = user->client; user->info.audio_mimetypes = NULL; user->info.image_mimetypes = NULL; user->info.video_mimetypes = NULL; user->info.timezone = NULL; /* Count number of arguments. */ int num_args; for (num_args = 0; client->args[num_args] != NULL; num_args++); /* Send args */ if (guac_protocol_send_args(socket, client->args) || guac_socket_flush(socket)) { /* Log error */ guac_user_log_handshake_failure(user); guac_user_log_guac_error(user, GUAC_LOG_DEBUG, "Error sending \"args\" to new user"); return 1; } guac_parser* parser = guac_parser_alloc(); /* Perform the handshake with the client. */ if (__guac_user_handshake(user, parser, usec_timeout)) { guac_parser_free(parser); return 1; } /* Acknowledge connection availability */ guac_protocol_send_ready(socket, client->connection_id); guac_socket_flush(socket); /* Verify argument count. */ if (parser->argc != (num_args + 1)) { guac_client_log(client, GUAC_LOG_ERROR, "Client did not return the " "expected number of arguments."); return 1; } /* Attempt to join user to connection. */ if (guac_client_add_user(client, user, (parser->argc - 1), parser->argv + 1)) guac_client_log(client, GUAC_LOG_ERROR, "User \"%s\" could NOT " "join connection \"%s\"", user->user_id, client->connection_id); /* Begin user connection if join successful */ else { guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" joined connection " "\"%s\" (%i users now present)", user->user_id, client->connection_id, client->connected_users); if (strcmp(parser->argv[0],"") != 0) { guac_client_log(client, GUAC_LOG_DEBUG, "Client is using protocol " "version \"%s\"", parser->argv[0]); user->info.protocol_version = guac_protocol_string_to_version(parser->argv[0]); } else { guac_client_log(client, GUAC_LOG_DEBUG, "Client has not defined " "its protocol version."); user->info.protocol_version = GUAC_PROTOCOL_VERSION_1_0_0; } /* Handle user I/O, wait for connection to terminate */ guac_user_start(parser, user, usec_timeout); /* Remove/free user */ guac_client_remove_user(client, user); guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" disconnected (%i " "users remain)", user->user_id, client->connected_users); } /* 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 */ return 0; }