cscg24-guacamole

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

daemon.c (17727B)


      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 "conf.h"
     23#include "conf-args.h"
     24#include "conf-file.h"
     25#include "connection.h"
     26#include "log.h"
     27#include "proc-map.h"
     28
     29#include <guacamole/mem.h>
     30
     31#ifdef ENABLE_SSL
     32#include <openssl/ssl.h>
     33#endif
     34
     35#include <errno.h>
     36#include <fcntl.h>
     37#include <libgen.h>
     38#include <netdb.h>
     39#include <netinet/in.h>
     40#include <signal.h>
     41#include <stdio.h>
     42#include <stdlib.h>
     43#include <string.h>
     44#include <syslog.h>
     45#include <sys/socket.h>
     46#include <sys/stat.h>
     47#include <sys/types.h>
     48#include <sys/wait.h>
     49#include <unistd.h>
     50
     51#define GUACD_DEV_NULL "/dev/null"
     52#define GUACD_ROOT     "/"
     53
     54/**
     55 * Redirects the given file descriptor to /dev/null. The given flags must match
     56 * the read/write flags of the file descriptor given (if the given file
     57 * descriptor was opened write-only, flags here must be O_WRONLY, etc.).
     58 *
     59 * @param fd
     60 *     The file descriptor to redirect to /dev/null.
     61 *
     62 * @param flags
     63 *     The flags to use when opening /dev/null as the target for redirection.
     64 *     These flags must match the flags of the file descriptor given.
     65 *
     66 * @return
     67 *     Zero on success, non-zero if redirecting the file descriptor fails.
     68 */
     69static int redirect_fd(int fd, int flags) {
     70
     71    /* Attempt to open bit bucket */
     72    int new_fd = open(GUACD_DEV_NULL, flags);
     73    if (new_fd < 0)
     74        return 1;
     75
     76    /* If descriptor is different, redirect old to new and close new */
     77    if (new_fd != fd) {
     78        dup2(new_fd, fd);
     79        close(new_fd);
     80    }
     81
     82    return 0;
     83
     84}
     85
     86/**
     87 * Turns the current process into a daemon through a series of fork() calls.
     88 * The standard I/O file desriptors for STDIN, STDOUT, and STDERR will be
     89 * redirected to /dev/null, and the working directory is changed to root.
     90 * Execution within the caller of this function will terminate before this
     91 * function returns, while execution within the daemonized child process will
     92 * continue.
     93 *
     94 * @return
     95 *    Zero if the daemonization process succeeded and we are now in the
     96 *    daemonized child process, or non-zero if daemonization failed and we are
     97 *    still the original caller. This function does not return for the original
     98 *    caller if daemonization succeeds.
     99 */
    100static int daemonize() {
    101
    102    pid_t pid;
    103
    104    /* Fork once to ensure we aren't the process group leader */
    105    pid = fork();
    106    if (pid < 0) {
    107        guacd_log(GUAC_LOG_ERROR, "Could not fork() parent: %s", strerror(errno));
    108        return 1;
    109    }
    110
    111    /* Exit if we are the parent */
    112    if (pid > 0) {
    113        guacd_log(GUAC_LOG_DEBUG, "Exiting and passing control to PID %i", pid);
    114        _exit(0);
    115    }
    116
    117    /* Start a new session (if not already group leader) */
    118    setsid();
    119
    120    /* Fork again so the session group leader exits */
    121    pid = fork();
    122    if (pid < 0) {
    123        guacd_log(GUAC_LOG_ERROR, "Could not fork() group leader: %s", strerror(errno));
    124        return 1;
    125    }
    126
    127    /* Exit if we are the parent */
    128    if (pid > 0) {
    129        guacd_log(GUAC_LOG_DEBUG, "Exiting and passing control to PID %i", pid);
    130        _exit(0);
    131    }
    132
    133    /* Change to root directory */
    134    if (chdir(GUACD_ROOT) < 0) {
    135        guacd_log(GUAC_LOG_ERROR, 
    136                "Unable to change working directory to "
    137                GUACD_ROOT);
    138        return 1;
    139    }
    140
    141    /* Reopen the 3 stdxxx to /dev/null */
    142
    143    if (redirect_fd(STDIN_FILENO, O_RDONLY)
    144    || redirect_fd(STDOUT_FILENO, O_WRONLY)
    145    || redirect_fd(STDERR_FILENO, O_WRONLY)) {
    146
    147        guacd_log(GUAC_LOG_ERROR, 
    148                "Unable to redirect standard file descriptors to "
    149                GUACD_DEV_NULL);
    150        return 1;
    151    }
    152
    153    /* Success */
    154    return 0;
    155
    156}
    157
    158#ifdef ENABLE_SSL
    159#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
    160/**
    161 * Array of mutexes, used by OpenSSL.
    162 */
    163static pthread_mutex_t* guacd_openssl_locks = NULL;
    164
    165/**
    166 * Called by OpenSSL when locking or unlocking the Nth mutex.
    167 *
    168 * @param mode
    169 *     A bitmask denoting the action to be taken on the Nth lock, such as
    170 *     CRYPTO_LOCK or CRYPTO_UNLOCK.
    171 *
    172 * @param n
    173 *     The index of the lock to lock or unlock.
    174 *
    175 * @param file
    176 *     The filename of the function setting the lock, for debugging purposes.
    177 *
    178 * @param line
    179 *     The line number of the function setting the lock, for debugging
    180 *     purposes.
    181 */
    182static void guacd_openssl_locking_callback(int mode, int n,
    183        const char* file, int line){
    184
    185    /* Lock given mutex upon request */
    186    if (mode & CRYPTO_LOCK)
    187        pthread_mutex_lock(&(guacd_openssl_locks[n]));
    188
    189    /* Unlock given mutex upon request */
    190    else if (mode & CRYPTO_UNLOCK)
    191        pthread_mutex_unlock(&(guacd_openssl_locks[n]));
    192
    193}
    194
    195/**
    196 * Called by OpenSSL when determining the current thread ID.
    197 *
    198 * @return
    199 *     An ID which uniquely identifies the current thread.
    200 */
    201static unsigned long guacd_openssl_id_callback() {
    202    return (unsigned long) pthread_self();
    203}
    204
    205/**
    206 * Creates the given number of mutexes, such that OpenSSL will have at least
    207 * this number of mutexes at its disposal.
    208 *
    209 * @param count
    210 *     The number of mutexes (locks) to create.
    211 */
    212static void guacd_openssl_init_locks(int count) {
    213
    214    int i;
    215
    216    /* Allocate required number of locks */
    217    guacd_openssl_locks = guac_mem_alloc(sizeof(pthread_mutex_t), count);
    218
    219    /* Initialize each lock */
    220    for (i=0; i < count; i++)
    221        pthread_mutex_init(&(guacd_openssl_locks[i]), NULL);
    222
    223}
    224
    225/**
    226 * Frees the given number of mutexes.
    227 *
    228 * @param count
    229 *     The number of mutexes (locks) to free.
    230 */
    231static void guacd_openssl_free_locks(int count) {
    232
    233    int i;
    234
    235    /* SSL lock array was not initialized */
    236    if (guacd_openssl_locks == NULL)
    237        return;
    238
    239    /* Free all locks */
    240    for (i=0; i < count; i++)
    241        pthread_mutex_destroy(&(guacd_openssl_locks[i]));
    242
    243    /* Free lock array */
    244    guac_mem_free(guacd_openssl_locks);
    245
    246}
    247#endif
    248#endif
    249
    250/**
    251 * A flag that, if non-zero, indicates that the daemon should immediately stop
    252 * accepting new connections.
    253 */
    254int stop_everything = 0;
    255
    256/**
    257 * A signal handler that will set a flag telling the daemon to immediately stop
    258 * accepting new connections. Note that the signal itself will cause any pending
    259 * accept() calls to be interrupted, causing the daemon to unlock and begin
    260 * cleaning up.
    261 *
    262 * @param signal
    263 *     The signal that was received. Unused in this function since only
    264 *     signals that should result in stopping the daemon should invoke this.
    265 */
    266static void signal_stop_handler(int signal) {
    267
    268    /* Instruct the daemon to stop accepting new connections */
    269    stop_everything = 1;
    270
    271}
    272
    273/**
    274 * A callback for guacd_proc_map_foreach which will stop every process in the
    275 * map.
    276 *
    277 * @param proc
    278 *     The guacd process to stop.
    279 *
    280 * @param data
    281 *     Unused.
    282 */
    283static void stop_process_callback(guacd_proc* proc, void* data) {
    284
    285    guacd_log(GUAC_LOG_DEBUG,
    286            "Killing connection %s (%i)\n",
    287            proc->client->connection_id, (int) proc->pid);
    288
    289    guacd_proc_stop(proc);
    290
    291}
    292
    293int main(int argc, char* argv[]) {
    294
    295    /* Server */
    296    int socket_fd;
    297    struct addrinfo* addresses;
    298    struct addrinfo* current_address;
    299    char bound_address[1024];
    300    char bound_port[64];
    301    int opt_on = 1;
    302
    303    struct addrinfo hints = {
    304        .ai_family   = AF_UNSPEC,
    305        .ai_socktype = SOCK_STREAM,
    306        .ai_protocol = IPPROTO_TCP
    307    };
    308
    309    /* Client */
    310    struct sockaddr_in client_addr;
    311    socklen_t client_addr_len;
    312    int connected_socket_fd;
    313
    314#ifdef ENABLE_SSL
    315    SSL_CTX* ssl_context = NULL;
    316#endif
    317
    318    guacd_proc_map* map = guacd_proc_map_alloc();
    319
    320    /* General */
    321    int retval;
    322
    323    /* Load configuration */
    324    guacd_config* config = guacd_conf_load();
    325    if (config == NULL || guacd_conf_parse_args(config, argc, argv))
    326       exit(EXIT_FAILURE);
    327
    328    /* If requested, simply print version and exit, without initializing the
    329     * logging system, etc. */
    330    if (config->print_version) {
    331        printf("Guacamole proxy daemon (guacd) version " VERSION "\n");
    332        exit(EXIT_SUCCESS);
    333    }
    334
    335    /* Init logging as early as possible */
    336    guacd_log_level = config->max_log_level;
    337    openlog(GUACD_LOG_NAME, LOG_PID, LOG_DAEMON);
    338
    339    /* Log start */
    340    guacd_log(GUAC_LOG_INFO, "Guacamole proxy daemon (guacd) version " VERSION " started");
    341
    342    /* Get addresses for binding */
    343    if ((retval = getaddrinfo(config->bind_host, config->bind_port,
    344                    &hints, &addresses))) {
    345
    346        guacd_log(GUAC_LOG_ERROR, "Error parsing given address or port: %s",
    347                gai_strerror(retval));
    348        exit(EXIT_FAILURE);
    349
    350    }
    351
    352    /* Attempt binding of each address until success */
    353    current_address = addresses;
    354    while (current_address != NULL) {
    355
    356        int retval;
    357
    358        /* Resolve hostname */
    359        if ((retval = getnameinfo(current_address->ai_addr,
    360                current_address->ai_addrlen,
    361                bound_address, sizeof(bound_address),
    362                bound_port, sizeof(bound_port),
    363                NI_NUMERICHOST | NI_NUMERICSERV)))
    364            guacd_log(GUAC_LOG_ERROR, "Unable to resolve host: %s",
    365                    gai_strerror(retval));
    366
    367        /* Get socket */
    368        socket_fd = socket(current_address->ai_family, SOCK_STREAM, 0);
    369        if (socket_fd < 0) {
    370            guacd_log(GUAC_LOG_ERROR, "Error opening socket: %s", strerror(errno));
    371
    372            /* Unable to get a socket for the resolved address family, try next */
    373            current_address = current_address->ai_next;
    374            continue;
    375        }
    376
    377        /* Allow socket reuse */
    378        if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
    379                    (void*) &opt_on, sizeof(opt_on))) {
    380            guacd_log(GUAC_LOG_WARNING, "Unable to set socket options for reuse: %s",
    381                    strerror(errno));
    382        }
    383
    384        /* Attempt to bind socket to address */
    385        if (bind(socket_fd,
    386                    current_address->ai_addr,
    387                    current_address->ai_addrlen) == 0) {
    388
    389            guacd_log(GUAC_LOG_DEBUG, "Successfully bound "
    390                    "%s socket to host %s, port %s",
    391                    (current_address->ai_family == AF_INET) ? "AF_INET" : "AF_INET6",
    392                    bound_address, bound_port);
    393
    394            /* Done if successful bind */
    395            break;
    396        }
    397
    398        /* Otherwise log information regarding bind failure */
    399        close(socket_fd);
    400        socket_fd = -1;
    401        guacd_log(GUAC_LOG_DEBUG, "Unable to bind %s socket to "
    402                "host %s, port %s: %s",
    403                (current_address->ai_family == AF_INET) ? "AF_INET" : "AF_INET6",
    404                bound_address, bound_port, strerror(errno));
    405
    406        /* Try next address */
    407        current_address = current_address->ai_next;
    408    }
    409
    410    /* If unable to bind to anything, fail */
    411    if (current_address == NULL) {
    412        guacd_log(GUAC_LOG_ERROR, "Unable to bind socket to any addresses.");
    413        exit(EXIT_FAILURE);
    414    }
    415
    416#ifdef ENABLE_SSL
    417    /* Init SSL if enabled */
    418    if (config->key_file != NULL || config->cert_file != NULL) {
    419
    420        guacd_log(GUAC_LOG_INFO, "Communication will require SSL/TLS.");
    421
    422#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
    423        /* Init threadsafety in OpenSSL */
    424        guacd_openssl_init_locks(CRYPTO_num_locks());
    425        CRYPTO_set_id_callback(guacd_openssl_id_callback);
    426        CRYPTO_set_locking_callback(guacd_openssl_locking_callback);
    427#endif
    428
    429        /* Init SSL */
    430        SSL_library_init();
    431        SSL_load_error_strings();
    432        ssl_context = SSL_CTX_new(SSLv23_server_method());
    433
    434        /* Load key */
    435        if (config->key_file != NULL) {
    436            guacd_log(GUAC_LOG_INFO, "Using PEM keyfile %s", config->key_file);
    437            if (!SSL_CTX_use_PrivateKey_file(ssl_context, config->key_file, SSL_FILETYPE_PEM)) {
    438                guacd_log(GUAC_LOG_ERROR, "Unable to load keyfile.");
    439                exit(EXIT_FAILURE);
    440            }
    441        }
    442        else
    443            guacd_log(GUAC_LOG_WARNING, "No PEM keyfile given - SSL/TLS may not work.");
    444
    445        /* Load cert file if specified */
    446        if (config->cert_file != NULL) {
    447            guacd_log(GUAC_LOG_INFO, "Using certificate file %s", config->cert_file);
    448            if (!SSL_CTX_use_certificate_chain_file(ssl_context, config->cert_file)) {
    449                guacd_log(GUAC_LOG_ERROR, "Unable to load certificate.");
    450                exit(EXIT_FAILURE);
    451            }
    452        }
    453        else
    454            guacd_log(GUAC_LOG_WARNING, "No certificate file given - SSL/TLS may not work.");
    455
    456    }
    457#endif
    458
    459    /* Daemonize if requested */
    460    if (!config->foreground) {
    461
    462        /* Attempt to daemonize process */
    463        if (daemonize()) {
    464            guacd_log(GUAC_LOG_ERROR, "Could not become a daemon.");
    465            exit(EXIT_FAILURE);
    466        }
    467
    468    }
    469
    470    /* Write PID file if requested */
    471    if (config->pidfile != NULL) {
    472
    473        /* Attempt to open pidfile and write PID */
    474        FILE* pidf = fopen(config->pidfile, "w");
    475        if (pidf) {
    476            fprintf(pidf, "%d\n", getpid());
    477            fclose(pidf);
    478        }
    479        
    480        /* Fail if could not write PID file*/
    481        else {
    482            guacd_log(GUAC_LOG_ERROR, "Could not write PID file: %s", strerror(errno));
    483            exit(EXIT_FAILURE);
    484        }
    485
    486    }
    487
    488    /* Ignore SIGPIPE */
    489    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
    490        guacd_log(GUAC_LOG_INFO, "Could not set handler for SIGPIPE to ignore. "
    491                "SIGPIPE may cause termination of the daemon.");
    492    }
    493
    494    /* Ignore SIGCHLD (force automatic removal of children) */
    495    if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
    496        guacd_log(GUAC_LOG_INFO, "Could not set handler for SIGCHLD to ignore. "
    497                "Child processes may pile up in the process table.");
    498    }
    499
    500    /* Clean up and exit if SIGINT or SIGTERM signals are caught */
    501    struct sigaction signal_stop_action = { .sa_handler = signal_stop_handler };
    502    sigaction(SIGINT, &signal_stop_action, NULL);
    503    sigaction(SIGTERM, &signal_stop_action, NULL);
    504
    505    /* Log listening status */
    506    guacd_log(GUAC_LOG_INFO, "Listening on host %s, port %s", bound_address, bound_port);
    507
    508    /* Free addresses */
    509    freeaddrinfo(addresses);
    510
    511    /* Listen for connections */
    512    if (listen(socket_fd, 5) < 0) {
    513        guacd_log(GUAC_LOG_ERROR, "Could not listen on socket: %s", strerror(errno));
    514        return 3;
    515    }
    516
    517    /* Daemon loop */
    518    while (!stop_everything) {
    519
    520        pthread_t child_thread;
    521
    522        /* Accept connection */
    523        client_addr_len = sizeof(client_addr);
    524        connected_socket_fd = accept(socket_fd,
    525                (struct sockaddr*) &client_addr, &client_addr_len);
    526
    527        if (connected_socket_fd < 0) {
    528            if (errno == EINTR)
    529                guacd_log(GUAC_LOG_DEBUG, "Accepting of further client connection(s) interrupted by signal.");
    530            else
    531                guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", strerror(errno));
    532            continue;
    533        }
    534
    535        /* Create parameters for connection thread */
    536        guacd_connection_thread_params* params = guac_mem_alloc(sizeof(guacd_connection_thread_params));
    537        if (params == NULL) {
    538            guacd_log(GUAC_LOG_ERROR, "Could not create connection thread: %s", strerror(errno));
    539            continue;
    540        }
    541
    542        params->map = map;
    543        params->connected_socket_fd = connected_socket_fd;
    544
    545#ifdef ENABLE_SSL
    546        params->ssl_context = ssl_context;
    547#endif
    548
    549        /* Spawn thread to handle connection */
    550        pthread_create(&child_thread, NULL, guacd_connection_thread, params);
    551        pthread_detach(child_thread);
    552
    553    }
    554
    555    /* Stop all connections */
    556    if (map != NULL) {
    557
    558        guacd_proc_map_foreach(map, stop_process_callback, NULL);
    559
    560        /*
    561         * FIXME: Clean up the proc map. This is not as straightforward as it
    562         * might seem, since the detached connection threads will attempt to
    563         * remove the connection proccesses from the map when they complete,
    564         * which will also happen upon shutdown. So there's a good chance that
    565         * this map cleanup will happen at the same time as the thread cleanup.
    566         * The map _does_ have locking mechanisms in place for ensuring thread
    567         * safety, but cleaning up the map also requires destroying those locks,
    568         * making them unusable for this case. One potential fix could be to
    569         * join every one of the connection threads instead of detaching them,
    570         * but that does complicate the cleanup of thread resources.
    571         */
    572
    573    }
    574
    575    /* Close socket */
    576    if (close(socket_fd) < 0) {
    577        guacd_log(GUAC_LOG_ERROR, "Could not close socket: %s", strerror(errno));
    578        return 3;
    579    }
    580
    581#ifdef ENABLE_SSL
    582    if (ssl_context != NULL) {
    583#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
    584        guacd_openssl_free_locks(CRYPTO_num_locks());
    585#endif
    586        SSL_CTX_free(ssl_context);
    587    }
    588#endif
    589
    590    return 0;
    591
    592}
    593