cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

user-handshake.c (12367B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include "config.h"
     21
     22#include "guacamole/mem.h"
     23#include "guacamole/client.h"
     24#include "guacamole/error.h"
     25#include "guacamole/parser.h"
     26#include "guacamole/protocol.h"
     27#include "guacamole/socket.h"
     28#include "guacamole/user.h"
     29#include "user-handlers.h"
     30
     31#include <pthread.h>
     32#include <stdlib.h>
     33#include <string.h>
     34
     35/**
     36 * Parameters required by the user input thread.
     37 */
     38typedef struct guac_user_input_thread_params {
     39
     40    /**
     41     * The parser which will be used throughout the user's session.
     42     */
     43    guac_parser* parser;
     44
     45    /**
     46     * A reference to the connected user.
     47     */
     48    guac_user* user;
     49
     50    /**
     51     * The number of microseconds to wait for instructions from a connected
     52     * user before closing the connection with an error.
     53     */
     54    int usec_timeout;
     55
     56} guac_user_input_thread_params;
     57
     58/**
     59 * Prints an error message using the logging facilities of the given user,
     60 * automatically including any information present in guac_error.
     61 *
     62 * @param user
     63 *     The guac_user associated with the error that occurred.
     64 *
     65 * @param level
     66 *     The level at which to log this message.
     67 *
     68 * @param message
     69 *     The message to log.
     70 */
     71static void guac_user_log_guac_error(guac_user* user,
     72        guac_client_log_level level, const char* message) {
     73
     74    if (guac_error != GUAC_STATUS_SUCCESS) {
     75
     76        /* If error message provided, include in log */
     77        if (guac_error_message != NULL)
     78            guac_user_log(user, level, "%s: %s", message,
     79                    guac_error_message);
     80
     81        /* Otherwise just log with standard status string */
     82        else
     83            guac_user_log(user, level, "%s: %s", message,
     84                    guac_status_string(guac_error));
     85
     86    }
     87
     88    /* Just log message if no status code */
     89    else
     90        guac_user_log(user, level, "%s", message);
     91
     92}
     93
     94/**
     95 * Logs a reasonable explanatory message regarding handshake failure based on
     96 * the current value of guac_error.
     97 *
     98 * @param user
     99 *     The guac_user associated with the failed Guacamole protocol handshake.
    100 */
    101static void guac_user_log_handshake_failure(guac_user* user) {
    102
    103    if (guac_error == GUAC_STATUS_CLOSED)
    104        guac_user_log(user, GUAC_LOG_DEBUG,
    105                "Guacamole connection closed during handshake");
    106    else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR)
    107        guac_user_log(user, GUAC_LOG_ERROR,
    108                "Guacamole protocol violation. Perhaps the version of "
    109                "guacamole-client is incompatible with this version of "
    110                "libguac?");
    111    else
    112        guac_user_log(user, GUAC_LOG_WARNING,
    113                "Guacamole handshake failed: %s",
    114                guac_status_string(guac_error));
    115
    116}
    117
    118/**
    119 * The thread which handles all user input, calling event handlers for received
    120 * instructions.
    121 *
    122 * @param data
    123 *     A pointer to a guac_user_input_thread_params structure describing the
    124 *     user whose input is being handled and the guac_parser with which to
    125 *     handle it.
    126 *
    127 * @return
    128 *     Always NULL.
    129 */
    130static void* guac_user_input_thread(void* data) {
    131
    132    guac_user_input_thread_params* params =
    133        (guac_user_input_thread_params*) data;
    134
    135    int usec_timeout = params->usec_timeout;
    136    guac_user* user = params->user;
    137    guac_parser* parser = params->parser;
    138    guac_client* client = user->client;
    139    guac_socket* socket = user->socket;
    140
    141    /* Guacamole user input loop */
    142    while (client->state == GUAC_CLIENT_RUNNING && user->active) {
    143
    144        /* Read instruction, stop on error */
    145        if (guac_parser_read(parser, socket, usec_timeout)) {
    146
    147            if (guac_error == GUAC_STATUS_TIMEOUT)
    148                guac_user_abort(user, GUAC_PROTOCOL_STATUS_CLIENT_TIMEOUT, "User is not responding.");
    149
    150            else {
    151                if (guac_error != GUAC_STATUS_CLOSED)
    152                    guac_user_log_guac_error(user, GUAC_LOG_WARNING,
    153                            "Guacamole connection failure");
    154                guac_user_stop(user);
    155            }
    156
    157            return NULL;
    158        }
    159
    160        /* Reset guac_error and guac_error_message (user/client handlers are not
    161         * guaranteed to set these) */
    162        guac_error = GUAC_STATUS_SUCCESS;
    163        guac_error_message = NULL;
    164
    165        /* Call handler, stop on error */
    166        if (__guac_user_call_opcode_handler(__guac_instruction_handler_map, 
    167                user, parser->opcode, parser->argc, parser->argv)) {
    168
    169            /* Log error */
    170            guac_user_log_guac_error(user, GUAC_LOG_WARNING,
    171                    "User connection aborted");
    172
    173            /* Log handler details */
    174            guac_user_log(user, GUAC_LOG_DEBUG, "Failing instruction handler in user was \"%s\"", parser->opcode);
    175
    176            guac_user_stop(user);
    177            return NULL;
    178        }
    179
    180    }
    181
    182    return NULL;
    183
    184}
    185
    186/**
    187 * Starts the input/output threads of a new user. This function will block
    188 * until the user disconnects. If an error prevents the input/output threads
    189 * from starting, guac_user_stop() will be invoked on the given user.
    190 *
    191 * @param parser
    192 *     The guac_parser to use to handle all input from the given user.
    193 *
    194 * @param user
    195 *     The user whose associated I/O transfer threads should be started.
    196 *
    197 * @param usec_timeout
    198 *     The number of microseconds to wait for instructions from the given
    199 *     user before closing the connection with an error.
    200 *
    201 * @return
    202 *     Zero if the I/O threads started successfully and user has disconnected,
    203 *     or non-zero if the I/O threads could not be started.
    204 */
    205static int guac_user_start(guac_parser* parser, guac_user* user,
    206        int usec_timeout) {
    207
    208    guac_user_input_thread_params params = {
    209        .parser = parser,
    210        .user = user,
    211        .usec_timeout = usec_timeout
    212    };
    213
    214    pthread_t input_thread;
    215
    216    if (pthread_create(&input_thread, NULL, guac_user_input_thread, (void*) &params)) {
    217        guac_user_log(user, GUAC_LOG_ERROR, "Unable to start input thread");
    218        guac_user_stop(user);
    219        return -1;
    220    }
    221
    222    /* Wait for I/O threads */
    223    pthread_join(input_thread, NULL);
    224
    225    /* Explicitly signal disconnect */
    226    guac_protocol_send_disconnect(user->socket);
    227    guac_socket_flush(user->socket);
    228
    229    /* Done */
    230    return 0;
    231
    232}
    233
    234/**
    235 * This function loops through the received instructions during the handshake
    236 * with the client attempting to join the connection, and runs the handlers
    237 * for each of the opcodes, ending when the connect instruction is received.
    238 * Returns zero if the handshake completes successfully with the connect opcode,
    239 * or a non-zero value if an error occurs.
    240 * 
    241 * @param user
    242 *     The guac_user attempting to join the connection.
    243 * 
    244 * @param parser
    245 *     The parser used to examine the received data.
    246 * 
    247 * @param usec_timeout
    248 *     The timeout, in microseconds, for reading the instructions.
    249 * 
    250 * @return
    251 *     Zero if the handshake completes successfully with the connect opcode,
    252 *     or non-zero if an error occurs.
    253 */
    254static int __guac_user_handshake(guac_user* user, guac_parser* parser,
    255        int usec_timeout) {
    256    
    257    guac_socket* socket = user->socket;
    258    
    259    /* Handle each of the opcodes. */
    260    while (guac_parser_read(parser, socket, usec_timeout) == 0) {
    261        
    262        /* If we receive the connect opcode, we're done. */
    263        if (strcmp(parser->opcode, "connect") == 0)
    264            return 0;
    265        
    266        guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s",
    267                parser->opcode);
    268        
    269        /* Run instruction handler for opcode with arguments. */
    270        if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user,
    271                parser->opcode, parser->argc, parser->argv)) {
    272            
    273            guac_user_log_handshake_failure(user);
    274            guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
    275                    "Error handling instruction during handshake.");
    276            guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s",
    277                    parser->opcode);
    278
    279            guac_parser_free(parser);
    280            return 1;
    281            
    282        }
    283        
    284    }
    285    
    286    /* If we get here it's because we never got the connect instruction. */
    287    guac_user_log(user, GUAC_LOG_ERROR,
    288            "Handshake failed, \"connect\" instruction was not received.");
    289    return 1;
    290}
    291
    292int guac_user_handle_connection(guac_user* user, int usec_timeout) {
    293
    294    guac_socket* socket = user->socket;
    295    guac_client* client = user->client;
    296    
    297    user->info.audio_mimetypes = NULL;
    298    user->info.image_mimetypes = NULL;
    299    user->info.video_mimetypes = NULL;
    300    user->info.name = NULL;
    301    user->info.timezone = NULL;
    302    
    303    /* Count number of arguments. */
    304    int num_args;
    305    for (num_args = 0; client->args[num_args] != NULL; num_args++);
    306    
    307    /* Send args */
    308    if (guac_protocol_send_args(socket, client->args)
    309            || guac_socket_flush(socket)) {
    310
    311        /* Log error */
    312        guac_user_log_handshake_failure(user);
    313        guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
    314                "Error sending \"args\" to new user");
    315
    316        return 1;
    317    }
    318
    319    guac_parser* parser = guac_parser_alloc();
    320
    321    /* Perform the handshake with the client. */
    322    if (__guac_user_handshake(user, parser, usec_timeout)) {
    323        guac_parser_free(parser);
    324        return 1;
    325    }
    326
    327    /* Acknowledge connection availability */
    328    guac_protocol_send_ready(socket, client->connection_id);
    329    guac_socket_flush(socket);
    330    
    331    /* Verify argument count. */
    332    if (parser->argc != (num_args + 1)) {
    333        guac_client_log(client, GUAC_LOG_ERROR, "Client did not return the "
    334                "expected number of arguments.");
    335        return 1;
    336    }
    337    
    338    /* Attempt to join user to connection. */
    339    if (guac_client_add_user(client, user, (parser->argc - 1), parser->argv + 1))
    340        guac_client_log(client, GUAC_LOG_ERROR, "User \"%s\" could NOT "
    341                "join connection \"%s\"", user->user_id, client->connection_id);
    342
    343    /* Begin user connection if join successful */
    344    else {
    345
    346        guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" joined connection "
    347                "\"%s\" (%i users now present)", user->user_id,
    348                client->connection_id, client->connected_users);
    349        if (strcmp(parser->argv[0],"") != 0) {
    350            guac_client_log(client, GUAC_LOG_DEBUG, "Client is using protocol "
    351                    "version \"%s\"", parser->argv[0]);
    352            user->info.protocol_version = guac_protocol_string_to_version(parser->argv[0]);
    353        }
    354        else {
    355            guac_client_log(client, GUAC_LOG_DEBUG, "Client has not defined "
    356                    "its protocol version.");
    357            user->info.protocol_version = GUAC_PROTOCOL_VERSION_1_0_0;
    358        }
    359
    360        /* Handle user I/O, wait for connection to terminate */
    361        guac_user_start(parser, user, usec_timeout);
    362
    363        /* Remove/free user */
    364        guac_client_remove_user(client, user);
    365        guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" disconnected (%i "
    366                "users remain)", user->user_id, client->connected_users);
    367
    368    }
    369    
    370    /* Free mimetype character arrays. */
    371    guac_free_mimetypes((char **) user->info.audio_mimetypes);
    372    guac_free_mimetypes((char **) user->info.image_mimetypes);
    373    guac_free_mimetypes((char **) user->info.video_mimetypes);
    374    
    375    /* Free name and timezone info. */
    376    guac_mem_free_const(user->info.name);
    377    guac_mem_free_const(user->info.timezone);
    378    
    379    guac_parser_free(parser);
    380
    381    /* Successful disconnect */
    382    return 0;
    383
    384}
    385