cscg24-guacamole

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

connection.c (11992B)


      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 "connection.h"
     23#include "log.h"
     24#include "move-fd.h"
     25#include "proc.h"
     26#include "proc-map.h"
     27
     28#include <guacamole/client.h>
     29#include <guacamole/error.h>
     30#include <guacamole/mem.h>
     31#include <guacamole/parser.h>
     32#include <guacamole/plugin.h>
     33#include <guacamole/protocol.h>
     34#include <guacamole/socket.h>
     35#include <guacamole/user.h>
     36
     37#ifdef ENABLE_SSL
     38#include <openssl/ssl.h>
     39#include <guacamole/socket-ssl.h>
     40#endif
     41
     42#include <errno.h>
     43#include <stdlib.h>
     44#include <string.h>
     45#include <sys/types.h>
     46#include <sys/socket.h>
     47#include <sys/wait.h>
     48
     49/**
     50 * Behaves exactly as write(), but writes as much as possible, returning
     51 * successfully only if the entire buffer was written. If the write fails for
     52 * any reason, a negative value is returned.
     53 *
     54 * @param fd
     55 *     The file descriptor to write to.
     56 *
     57 * @param buffer
     58 *     The buffer containing the data to be written.
     59 *
     60 * @param length
     61 *     The number of bytes in the buffer to write.
     62 *
     63 * @return
     64 *     The number of bytes written, or -1 if an error occurs. As this function
     65 *     is guaranteed to write ALL bytes, this will always be the number of
     66 *     bytes specified by length unless an error occurs.
     67 */
     68static int __write_all(int fd, char* buffer, int length) {
     69
     70    /* Repeatedly write() until all data is written */
     71    int remaining_length = length;
     72    while (remaining_length > 0) {
     73
     74        int written = write(fd, buffer, remaining_length);
     75        if (written < 0)
     76            return -1;
     77
     78        remaining_length -= written;
     79        buffer += written;
     80
     81    }
     82
     83    return length;
     84
     85}
     86
     87/**
     88 * Continuously reads from a guac_socket, writing all data read to a file
     89 * descriptor. Any data already buffered from that guac_socket by a given
     90 * guac_parser is read first, prior to reading further data from the
     91 * guac_socket. The provided guac_parser will be freed once its buffers have
     92 * been emptied, but the guac_socket will not.
     93 *
     94 * This thread ultimately terminates when no further data can be read from the
     95 * guac_socket.
     96 *
     97 * @param data
     98 *     A pointer to a guacd_connection_io_thread_params structure containing
     99 *     the guac_socket to read from, the file descriptor to write the read data
    100 *     to, and the guac_parser associated with the guac_socket which may have
    101 *     unhandled data in its parsing buffers.
    102 *
    103 * @return
    104 *     Always NULL.
    105 */
    106static void* guacd_connection_write_thread(void* data) {
    107
    108    guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data;
    109    char buffer[8192];
    110
    111    int length;
    112
    113    /* Read all buffered data from parser first */
    114    while ((length = guac_parser_shift(params->parser, buffer, sizeof(buffer))) > 0) {
    115        if (__write_all(params->fd, buffer, length) < 0)
    116            break;
    117    }
    118
    119    /* Parser is no longer needed */
    120    guac_parser_free(params->parser);
    121
    122    /* Transfer data from file descriptor to socket */
    123    while ((length = guac_socket_read(params->socket, buffer, sizeof(buffer))) > 0) {
    124        if (__write_all(params->fd, buffer, length) < 0)
    125            break;
    126    }
    127
    128    return NULL;
    129
    130}
    131
    132void* guacd_connection_io_thread(void* data) {
    133
    134    guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data;
    135    char buffer[8192];
    136
    137    int length;
    138
    139    pthread_t write_thread;
    140    pthread_create(&write_thread, NULL, guacd_connection_write_thread, params);
    141
    142    /* Transfer data from file descriptor to socket */
    143    while ((length = read(params->fd, buffer, sizeof(buffer))) > 0) {
    144        if (guac_socket_write(params->socket, buffer, length))
    145            break;
    146        guac_socket_flush(params->socket);
    147    }
    148
    149    /* Wait for write thread to die */
    150    pthread_join(write_thread, NULL);
    151
    152    /* Clean up */
    153    guac_socket_free(params->socket);
    154    close(params->fd);
    155    guac_mem_free(params);
    156
    157    return NULL;
    158
    159}
    160
    161/**
    162 * Adds the given socket as a new user to the given process, automatically
    163 * reading/writing from the socket via read/write threads. The given socket,
    164 * parser, and any associated resources will be freed unless the user is not
    165 * added successfully.
    166 *
    167 * If adding the user fails for any reason, non-zero is returned. Zero is
    168 * returned upon success.
    169 *
    170 * @param proc
    171 *     The existing process to add the user to.
    172 *
    173 * @param parser
    174 *     The parser associated with the given guac_socket (used to handle the
    175 *     user's connection handshake thus far).
    176 *
    177 * @param socket
    178 *     The socket associated with the user to be added to the existing
    179 *     process.
    180 *
    181 * @return
    182 *     Zero if the user was added successfully, non-zero if an error occurred.
    183 */
    184static int guacd_add_user(guacd_proc* proc, guac_parser* parser, guac_socket* socket) {
    185
    186    int sockets[2];
    187
    188    /* Set up socket pair */
    189    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
    190        guacd_log(GUAC_LOG_ERROR, "Unable to allocate file descriptors for I/O transfer: %s", strerror(errno));
    191        return 1;
    192    }
    193
    194    int user_fd = sockets[0];
    195    int proc_fd = sockets[1];
    196
    197    /* Send user file descriptor to process */
    198    if (!guacd_send_fd(proc->fd_socket, proc_fd)) {
    199        guacd_log(GUAC_LOG_ERROR, "Unable to add user.");
    200        return 1;
    201    }
    202
    203    /* Close our end of the process file descriptor */
    204    close(proc_fd);
    205
    206    guacd_connection_io_thread_params* params = guac_mem_alloc(sizeof(guacd_connection_io_thread_params));
    207    params->parser = parser;
    208    params->socket = socket;
    209    params->fd = user_fd;
    210
    211    /* Start I/O thread */
    212    pthread_t io_thread;
    213    pthread_create(&io_thread,  NULL, guacd_connection_io_thread,  params);
    214    pthread_detach(io_thread);
    215
    216    return 0;
    217
    218}
    219
    220/**
    221 * Routes the connection on the given socket according to the Guacamole
    222 * protocol, adding new users and creating new client processes as needed. If a
    223 * new process is created, this function blocks until that process terminates,
    224 * automatically deregistering the process at that point.
    225 *
    226 * The socket provided will be automatically freed when the connection
    227 * terminates unless routing fails, in which case non-zero is returned.
    228 *
    229 * @param map
    230 *     The map of existing client processes.
    231 *
    232 * @param socket
    233 *     The socket associated with the new connection that must be routed to
    234 *     a new or existing process within the given map.
    235 *
    236 * @return
    237 *     Zero if the connection was successfully routed, non-zero if routing has
    238 *     failed.
    239 */
    240static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) {
    241
    242    guac_parser* parser = guac_parser_alloc();
    243
    244    /* Reset guac_error */
    245    guac_error = GUAC_STATUS_SUCCESS;
    246    guac_error_message = NULL;
    247
    248    /* Get protocol from select instruction */
    249    if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "select")) {
    250
    251        /* Log error */
    252        guacd_log_handshake_failure();
    253        guacd_log_guac_error(GUAC_LOG_DEBUG,
    254                "Error reading \"select\"");
    255
    256        guac_parser_free(parser);
    257        return 1;
    258    }
    259
    260    /* Validate args to select */
    261    if (parser->argc != 1) {
    262
    263        /* Log error */
    264        guacd_log_handshake_failure();
    265        guacd_log(GUAC_LOG_ERROR, "Bad number of arguments to \"select\" (%i)",
    266                parser->argc);
    267
    268        guac_parser_free(parser);
    269        return 1;
    270    }
    271
    272    guacd_proc* proc;
    273    int new_process;
    274
    275    const char* identifier = parser->argv[0];
    276
    277    /* If connection ID, retrieve existing process */
    278    if (identifier[0] == GUAC_CLIENT_ID_PREFIX) {
    279
    280        proc = guacd_proc_map_retrieve(map, identifier);
    281        new_process = 0;
    282
    283        /* Warn and ward off client if requested connection does not exist */
    284        if (proc == NULL) {
    285            guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist", identifier);
    286            guac_protocol_send_error(socket, "No such connection.",
    287                    GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
    288        }
    289
    290        else
    291            guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"",
    292                    identifier);
    293
    294    }
    295
    296    /* Otherwise, create new client */
    297    else {
    298
    299        guacd_log(GUAC_LOG_INFO, "Creating new client for protocol \"%s\"",
    300                identifier);
    301
    302        /* Create new process */
    303        proc = guacd_create_proc(identifier);
    304        new_process = 1;
    305
    306    }
    307
    308    /* Abort if no process exists for the requested connection */
    309    if (proc == NULL) {
    310        guacd_log_guac_error(GUAC_LOG_INFO, "Connection did not succeed");
    311        guac_parser_free(parser);
    312        return 1;
    313    }
    314
    315    /* Add new user (in the case of a new process, this will be the owner */
    316    int add_user_failed = guacd_add_user(proc, parser, socket);
    317
    318    /* If new process was created, manage that process */
    319    if (new_process) {
    320
    321        /* The new process will only be active if the user was added */
    322        if (!add_user_failed) {
    323
    324            /* Log connection ID */
    325            guacd_log(GUAC_LOG_INFO, "Connection ID is \"%s\"",
    326                    proc->client->connection_id);
    327
    328            /* Store process, allowing other users to join */
    329            guacd_proc_map_add(map, proc);
    330
    331            /* Wait for child to finish */
    332            waitpid(proc->pid, NULL, 0);
    333
    334            /* Remove client */
    335            if (guacd_proc_map_remove(map, proc->client->connection_id) == NULL)
    336                guacd_log(GUAC_LOG_ERROR, "Internal failure removing "
    337                        "client \"%s\". Client record will never be freed.",
    338                        proc->client->connection_id);
    339            else
    340                guacd_log(GUAC_LOG_INFO, "Connection \"%s\" removed.",
    341                        proc->client->connection_id);
    342
    343        }
    344
    345        /* Parser must be manually freed if the process did not start */
    346        else
    347            guac_parser_free(parser);
    348
    349        /* Force process to stop and clean up */
    350        guacd_proc_stop(proc);
    351
    352        /* Free skeleton client */
    353        guac_client_free(proc->client);
    354
    355        /* Clean up */
    356        close(proc->fd_socket);
    357        guac_mem_free(proc);
    358
    359    }
    360
    361    /* Routing succeeded only if the user was added to a process */
    362    return add_user_failed;
    363
    364}
    365
    366void* guacd_connection_thread(void* data) {
    367
    368    guacd_connection_thread_params* params = (guacd_connection_thread_params*) data;
    369
    370    guacd_proc_map* map = params->map;
    371    int connected_socket_fd = params->connected_socket_fd;
    372
    373    guac_socket* socket;
    374
    375#ifdef ENABLE_SSL
    376
    377    SSL_CTX* ssl_context = params->ssl_context;
    378
    379    /* If SSL chosen, use it */
    380    if (ssl_context != NULL) {
    381        socket = guac_socket_open_secure(ssl_context, connected_socket_fd);
    382        if (socket == NULL) {
    383            guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to set up SSL/TLS");
    384            close(connected_socket_fd);
    385            guac_mem_free(params);
    386            return NULL;
    387        }
    388    }
    389    else
    390        socket = guac_socket_open(connected_socket_fd);
    391
    392#else
    393    /* Open guac_socket */
    394    socket = guac_socket_open(connected_socket_fd);
    395#endif
    396
    397    /* Route connection according to Guacamole, creating a new process if needed */
    398    if (guacd_route_connection(map, socket))
    399        guac_socket_free(socket);
    400
    401    guac_mem_free(params);
    402    return NULL;
    403
    404}
    405