2016-03-01 05:40:39 +00:00
|
|
|
/*
|
2016-03-25 19:59:40 +00:00
|
|
|
* 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
|
2016-03-01 05:40:39 +00:00
|
|
|
*
|
2016-03-25 19:59:40 +00:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2016-03-01 05:40:39 +00:00
|
|
|
*
|
2016-03-25 19:59:40 +00:00
|
|
|
* 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.
|
2016-03-01 05:40:39 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
2018-10-19 16:30:20 +00:00
|
|
|
#include "guacamole/client.h"
|
|
|
|
#include "guacamole/error.h"
|
|
|
|
#include "guacamole/parser.h"
|
|
|
|
#include "guacamole/protocol.h"
|
|
|
|
#include "guacamole/socket.h"
|
|
|
|
#include "guacamole/user.h"
|
2019-04-11 21:11:41 +00:00
|
|
|
#include "user-handlers.h"
|
2016-03-01 05:40:39 +00:00
|
|
|
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <stdlib.h>
|
2016-09-11 20:18:27 +00:00
|
|
|
#include <string.h>
|
|
|
|
|
2017-06-11 02:16:22 +00:00
|
|
|
/**
|
|
|
|
* Parameters required by the user input thread.
|
|
|
|
*/
|
2017-07-01 22:51:15 +00:00
|
|
|
typedef struct guac_user_input_thread_params {
|
2017-06-11 02:16:22 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The parser which will be used throughout the user's session.
|
|
|
|
*/
|
|
|
|
guac_parser* parser;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A reference to the connected user.
|
|
|
|
*/
|
|
|
|
guac_user* user;
|
|
|
|
|
2017-07-01 22:24:20 +00:00
|
|
|
/**
|
|
|
|
* The number of microseconds to wait for instructions from a connected
|
|
|
|
* user before closing the connection with an error.
|
|
|
|
*/
|
|
|
|
int usec_timeout;
|
|
|
|
|
2017-07-01 22:51:15 +00:00
|
|
|
} 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_INFO,
|
|
|
|
"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));
|
|
|
|
|
|
|
|
}
|
2017-06-11 02:16:22 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The thread which handles all user input, calling event handlers for received
|
|
|
|
* instructions.
|
|
|
|
*
|
|
|
|
* @param data
|
2017-07-01 22:51:15 +00:00
|
|
|
* A pointer to a guac_user_input_thread_params structure describing the
|
2017-06-11 02:16:22 +00:00
|
|
|
* user whose input is being handled and the guac_parser with which to
|
|
|
|
* handle it.
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
* Always NULL.
|
|
|
|
*/
|
2017-07-01 22:51:15 +00:00
|
|
|
static void* guac_user_input_thread(void* data) {
|
2016-03-01 05:40:39 +00:00
|
|
|
|
2017-07-01 22:51:15 +00:00
|
|
|
guac_user_input_thread_params* params =
|
|
|
|
(guac_user_input_thread_params*) data;
|
2017-07-01 22:24:20 +00:00
|
|
|
|
|
|
|
int usec_timeout = params->usec_timeout;
|
2016-03-01 05:40:39 +00:00
|
|
|
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 */
|
2017-07-01 22:24:20 +00:00
|
|
|
if (guac_parser_read(parser, socket, usec_timeout)) {
|
2016-03-01 05:40:39 +00:00
|
|
|
|
|
|
|
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)
|
2017-07-01 22:51:15 +00:00
|
|
|
guac_user_log_guac_error(user, GUAC_LOG_WARNING,
|
2016-03-01 05:40:39 +00:00
|
|
|
"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 */
|
2019-04-11 21:11:41 +00:00
|
|
|
if (__guac_user_call_opcode_handler(__guac_instruction_handler_map,
|
2019-04-25 02:21:18 +00:00
|
|
|
user, parser->opcode, parser->argc, parser->argv)) {
|
2016-03-01 05:40:39 +00:00
|
|
|
|
|
|
|
/* Log error */
|
2017-07-01 22:51:15 +00:00
|
|
|
guac_user_log_guac_error(user, GUAC_LOG_WARNING,
|
2016-03-01 05:40:39 +00:00
|
|
|
"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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-06-11 02:16:22 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2017-07-01 22:24:20 +00:00
|
|
|
* @param usec_timeout
|
|
|
|
* The number of microseconds to wait for instructions from the given
|
|
|
|
* user before closing the connection with an error.
|
|
|
|
*
|
2017-06-11 02:16:22 +00:00
|
|
|
* @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.
|
|
|
|
*/
|
2017-07-01 22:51:15 +00:00
|
|
|
static int guac_user_start(guac_parser* parser, guac_user* user,
|
2017-07-01 22:24:20 +00:00
|
|
|
int usec_timeout) {
|
2016-03-01 05:40:39 +00:00
|
|
|
|
2017-07-01 22:51:15 +00:00
|
|
|
guac_user_input_thread_params params = {
|
2016-03-01 05:40:39 +00:00
|
|
|
.parser = parser,
|
2017-07-01 22:24:20 +00:00
|
|
|
.user = user,
|
|
|
|
.usec_timeout = usec_timeout
|
2016-03-01 05:40:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
pthread_t input_thread;
|
|
|
|
|
2017-07-01 22:51:15 +00:00
|
|
|
if (pthread_create(&input_thread, NULL, guac_user_input_thread, (void*) ¶ms)) {
|
2016-03-01 05:40:39 +00:00
|
|
|
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);
|
|
|
|
|
2016-11-07 18:29:00 +00:00
|
|
|
/* Explicitly signal disconnect */
|
|
|
|
guac_protocol_send_disconnect(user->socket);
|
|
|
|
guac_socket_flush(user->socket);
|
|
|
|
|
2016-03-01 05:40:39 +00:00
|
|
|
/* Done */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-04-22 15:49:46 +00:00
|
|
|
/**
|
|
|
|
* 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) {
|
2019-04-14 01:30:57 +00:00
|
|
|
|
2019-04-22 15:49:46 +00:00
|
|
|
guac_socket* socket = user->socket;
|
2019-04-17 18:56:51 +00:00
|
|
|
|
2019-03-29 00:09:32 +00:00
|
|
|
/* Handle each of the opcodes. */
|
2019-04-22 15:49:46 +00:00
|
|
|
while (guac_parser_read(parser, socket, usec_timeout) == 0) {
|
2019-03-25 01:36:44 +00:00
|
|
|
|
2019-03-29 00:09:32 +00:00
|
|
|
/* If we receive the connect opcode, we're done. */
|
|
|
|
if (strcmp(parser->opcode, "connect") == 0)
|
2019-04-22 15:49:46 +00:00
|
|
|
return 0;
|
2019-03-25 01:36:44 +00:00
|
|
|
|
2019-03-30 17:43:01 +00:00
|
|
|
guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s",
|
|
|
|
parser->opcode);
|
|
|
|
|
2019-04-03 01:38:28 +00:00
|
|
|
/* Run instruction handler for opcode with arguments. */
|
2019-04-11 21:11:41 +00:00
|
|
|
if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user,
|
2019-04-03 01:38:28 +00:00
|
|
|
parser->opcode, parser->argc, parser->argv)) {
|
2019-03-29 00:09:32 +00:00
|
|
|
|
2019-04-03 01:38:28 +00:00
|
|
|
guac_user_log_handshake_failure(user);
|
|
|
|
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
|
2019-04-25 02:21:18 +00:00
|
|
|
"Error handling instruction during handshake.");
|
2019-04-03 01:38:28 +00:00
|
|
|
guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s",
|
|
|
|
parser->opcode);
|
2016-09-11 20:18:27 +00:00
|
|
|
|
2019-04-03 01:38:28 +00:00
|
|
|
guac_parser_free(parser);
|
|
|
|
return 1;
|
2019-03-29 00:09:32 +00:00
|
|
|
|
|
|
|
}
|
2019-04-03 01:38:28 +00:00
|
|
|
|
2016-09-11 20:18:27 +00:00
|
|
|
}
|
2019-04-22 15:49:46 +00:00
|
|
|
|
2019-04-25 02:21:18 +00:00
|
|
|
/* 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.");
|
2019-04-22 15:49:46 +00:00
|
|
|
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;
|
|
|
|
|
2019-04-25 02:21:18 +00:00
|
|
|
/* Count number of arguments. */
|
2019-04-28 01:37:26 +00:00
|
|
|
int num_args;
|
|
|
|
for (num_args = 0; client->args[num_args] != NULL; num_args++);
|
2019-04-25 02:21:18 +00:00
|
|
|
|
2019-04-22 15:49:46 +00:00
|
|
|
/* 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);
|
2019-04-25 02:21:18 +00:00
|
|
|
return 1;
|
2019-04-22 15:49:46 +00:00
|
|
|
}
|
2016-09-11 20:18:27 +00:00
|
|
|
|
|
|
|
/* Acknowledge connection availability */
|
|
|
|
guac_protocol_send_ready(socket, client->connection_id);
|
|
|
|
guac_socket_flush(socket);
|
2019-04-14 01:30:57 +00:00
|
|
|
|
2019-04-25 02:21:18 +00:00
|
|
|
/* Verify argument count. */
|
2019-04-28 01:37:26 +00:00
|
|
|
if (parser->argc != (num_args + 1)) {
|
2019-04-25 02:21:18 +00:00
|
|
|
guac_client_log(client, GUAC_LOG_ERROR, "Client did not return the "
|
|
|
|
"expected number of arguments.");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-04-17 19:12:46 +00:00
|
|
|
/* Attempt to join user to connection. */
|
|
|
|
if (guac_client_add_user(client, user, (parser->argc - 1), parser->argv + 1))
|
2016-09-11 20:18:27 +00:00
|
|
|
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);
|
2019-04-25 02:21:18 +00:00
|
|
|
if (strcmp(parser->argv[0],"") != 0)
|
|
|
|
guac_client_log(client, GUAC_LOG_DEBUG, "Client is using protocol "
|
|
|
|
"version \"%s\"", parser->argv[0]);
|
|
|
|
else
|
|
|
|
guac_client_log(client, GUAC_LOG_DEBUG, "Client has not defined "
|
|
|
|
"its protocol version.");
|
2016-09-11 20:18:27 +00:00
|
|
|
|
|
|
|
/* Handle user I/O, wait for connection to terminate */
|
2017-07-01 22:51:15 +00:00
|
|
|
guac_user_start(parser, user, usec_timeout);
|
2016-09-11 20:18:27 +00:00
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
|
|
}
|
2019-03-25 01:55:45 +00:00
|
|
|
|
2019-03-29 00:09:32 +00:00
|
|
|
/* 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);
|
|
|
|
|
2016-09-11 20:18:27 +00:00
|
|
|
guac_parser_free(parser);
|
|
|
|
|
|
|
|
/* Successful disconnect */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|