It is expected under normal circumstances that the connection may be abruptly closed, including during the handshake. For example, this will commonly occur when a TCP load balancer is performing a simple service health check. An message noting that the connection has been closed during the Guacamole protocol handshake is really only of benefit when debugging, where that information may provide useful context. If not debugging, the message amounts to log noise.
383 lines
12 KiB
C
383 lines
12 KiB
C
/*
|
|
* 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 <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/**
|
|
* 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;
|
|
|
|
}
|
|
|