cscg24-guacamole

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

rdp.c (29072B)


      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 "argv.h"
     21#include "beep.h"
     22#include "bitmap.h"
     23#include "channels/audio-input/audio-buffer.h"
     24#include "channels/audio-input/audio-input.h"
     25#include "channels/cliprdr.h"
     26#include "channels/disp.h"
     27#include "channels/pipe-svc.h"
     28#include "channels/rail.h"
     29#include "channels/rdpdr/rdpdr.h"
     30#include "channels/rdpei.h"
     31#include "channels/rdpsnd/rdpsnd.h"
     32#include "client.h"
     33#include "color.h"
     34#include "common/cursor.h"
     35#include "common/display.h"
     36#include "config.h"
     37#include "error.h"
     38#include "fs.h"
     39#include "gdi.h"
     40#include "glyph.h"
     41#include "keyboard.h"
     42#include "plugins/channels.h"
     43#include "pointer.h"
     44#include "print-job.h"
     45#include "rdp.h"
     46#include "settings.h"
     47
     48#ifdef ENABLE_COMMON_SSH
     49#include "common-ssh/sftp.h"
     50#include "common-ssh/ssh.h"
     51#include "common-ssh/user.h"
     52#endif
     53
     54#include <freerdp/addin.h>
     55#include <freerdp/cache/bitmap.h>
     56#include <freerdp/cache/brush.h>
     57#include <freerdp/cache/glyph.h>
     58#include <freerdp/cache/offscreen.h>
     59#include <freerdp/cache/palette.h>
     60#include <freerdp/cache/pointer.h>
     61#include <freerdp/channels/channels.h>
     62#include <freerdp/client/channels.h>
     63#include <freerdp/freerdp.h>
     64#include <freerdp/gdi/gdi.h>
     65#include <freerdp/graphics.h>
     66#include <freerdp/primary.h>
     67#include <freerdp/settings.h>
     68#include <freerdp/update.h>
     69#include <guacamole/argv.h>
     70#include <guacamole/audio.h>
     71#include <guacamole/client.h>
     72#include <guacamole/mem.h>
     73#include <guacamole/protocol.h>
     74#include <guacamole/recording.h>
     75#include <guacamole/socket.h>
     76#include <guacamole/string.h>
     77#include <guacamole/timestamp.h>
     78#include <guacamole/wol.h>
     79#include <winpr/error.h>
     80#include <winpr/synch.h>
     81#include <winpr/wtypes.h>
     82
     83#include <stdlib.h>
     84#include <time.h>
     85
     86BOOL rdp_freerdp_pre_connect(freerdp* instance) {
     87
     88    rdpContext* context = instance->context;
     89    rdpGraphics* graphics = context->graphics;
     90
     91    guac_client* client = ((rdp_freerdp_context*) context)->client;
     92    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
     93    guac_rdp_settings* settings = rdp_client->settings;
     94
     95    /* Push desired settings to FreeRDP */
     96    guac_rdp_push_settings(client, settings, instance);
     97
     98    /* Init FreeRDP add-in provider */
     99    freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0);
    100
    101    /* Load "disp" plugin for display update */
    102    if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE)
    103        guac_rdp_disp_load_plugin(context);
    104
    105    /* Load "rdpei" plugin for multi-touch support */
    106    if (settings->enable_touch)
    107        guac_rdp_rdpei_load_plugin(context);
    108
    109    /* Load "AUDIO_INPUT" plugin for audio input*/
    110    if (settings->enable_audio_input) {
    111        rdp_client->audio_input = guac_rdp_audio_buffer_alloc(client);
    112        guac_rdp_audio_load_plugin(instance->context);
    113    }
    114
    115    /* Load "cliprdr" service if not disabled */
    116    if (!(settings->disable_copy && settings->disable_paste))
    117        guac_rdp_clipboard_load_plugin(rdp_client->clipboard, context);
    118
    119    /* If RDPSND/RDPDR required, load them */
    120    if (settings->printing_enabled
    121        || settings->drive_enabled
    122        || settings->audio_enabled) {
    123        guac_rdpdr_load_plugin(context);
    124        guac_rdpsnd_load_plugin(context);
    125    }
    126
    127    /* Load RAIL plugin if RemoteApp in use */
    128    if (settings->remote_app != NULL)
    129        guac_rdp_rail_load_plugin(context);
    130
    131    /* Load SVC plugin instances for all static channels */
    132    if (settings->svc_names != NULL) {
    133
    134        char** current = settings->svc_names;
    135        do {
    136            guac_rdp_pipe_svc_load_plugin(context, *current);
    137        } while (*(++current) != NULL);
    138
    139    }
    140
    141    /* Load plugin providing Dynamic Virtual Channel support, if required */
    142    if (instance->settings->SupportDynamicChannels &&
    143            guac_freerdp_channels_load_plugin(context, "drdynvc",
    144                instance->settings)) {
    145        guac_client_log(client, GUAC_LOG_WARNING,
    146                "Failed to load drdynvc plugin. Display update and audio "
    147                "input support will be disabled.");
    148    }
    149
    150    /* Init FreeRDP internal GDI implementation */
    151    if (!gdi_init(instance, guac_rdp_get_native_pixel_format(FALSE)))
    152        return FALSE;
    153
    154    /* Set up bitmap handling */
    155    rdpBitmap bitmap = *graphics->Bitmap_Prototype;
    156    bitmap.size = sizeof(guac_rdp_bitmap);
    157    bitmap.New = guac_rdp_bitmap_new;
    158    bitmap.Free = guac_rdp_bitmap_free;
    159    bitmap.Paint = guac_rdp_bitmap_paint;
    160    bitmap.SetSurface = guac_rdp_bitmap_setsurface;
    161    graphics_register_bitmap(graphics, &bitmap);
    162
    163    /* Set up glyph handling */
    164    rdpGlyph glyph = *graphics->Glyph_Prototype;
    165    glyph.size = sizeof(guac_rdp_glyph);
    166    glyph.New = guac_rdp_glyph_new;
    167    glyph.Free = guac_rdp_glyph_free;
    168    glyph.Draw = guac_rdp_glyph_draw;
    169    glyph.BeginDraw = guac_rdp_glyph_begindraw;
    170    glyph.EndDraw = guac_rdp_glyph_enddraw;
    171    graphics_register_glyph(graphics, &glyph);
    172
    173    /* Set up pointer handling */
    174    rdpPointer pointer = *graphics->Pointer_Prototype;
    175    pointer.size = sizeof(guac_rdp_pointer);
    176    pointer.New = guac_rdp_pointer_new;
    177    pointer.Free = guac_rdp_pointer_free;
    178    pointer.Set = guac_rdp_pointer_set;
    179    pointer.SetNull = guac_rdp_pointer_set_null;
    180    pointer.SetDefault = guac_rdp_pointer_set_default;
    181    graphics_register_pointer(graphics, &pointer);
    182
    183    /* Beep on receipt of Play Sound PDU */
    184    instance->update->PlaySound = guac_rdp_beep_play_sound;
    185
    186    /* Automatically synchronize keyboard locks when changed server-side */
    187    instance->update->SetKeyboardIndicators = guac_rdp_keyboard_set_indicators;
    188
    189    /* Set up GDI */
    190    instance->update->DesktopResize = guac_rdp_gdi_desktop_resize;
    191    instance->update->EndPaint = guac_rdp_gdi_end_paint;
    192    instance->update->SetBounds = guac_rdp_gdi_set_bounds;
    193
    194    rdpPrimaryUpdate* primary = instance->update->primary;
    195    primary->DstBlt = guac_rdp_gdi_dstblt;
    196    primary->PatBlt = guac_rdp_gdi_patblt;
    197    primary->ScrBlt = guac_rdp_gdi_scrblt;
    198    primary->MemBlt = guac_rdp_gdi_memblt;
    199    primary->OpaqueRect = guac_rdp_gdi_opaquerect;
    200
    201    pointer_cache_register_callbacks(instance->update);
    202    glyph_cache_register_callbacks(instance->update);
    203    brush_cache_register_callbacks(instance->update);
    204    bitmap_cache_register_callbacks(instance->update);
    205    offscreen_cache_register_callbacks(instance->update);
    206    palette_cache_register_callbacks(instance->update);
    207
    208    return TRUE;
    209
    210}
    211
    212/**
    213 * Callback invoked by FreeRDP when authentication is required but the required
    214 * parameters have not been provided. In the case of Guacamole clients that
    215 * support the "required" instruction, this function will send any of the three
    216 * unpopulated RDP authentication parameters back to the client so that the
    217 * connection owner can provide the required information.  If the values have
    218 * been provided in the original connection parameters the user will not be
    219 * prompted for updated parameters. If the version of Guacamole Client in use
    220 * by the connection owner does not support the "required" instruction then the
    221 * connection will fail. This function always returns true.
    222 *
    223 * @param instance
    224 *     The FreeRDP instance associated with the RDP session requesting
    225 *     credentials.
    226 *
    227 * @param username
    228 *     Pointer to a string which will receive the user's username.
    229 *
    230 * @param password
    231 *     Pointer to a string which will receive the user's password.
    232 *
    233 * @param domain
    234 *     Pointer to a string which will receive the domain associated with the
    235 *     user's account.
    236 *
    237 * @return
    238 *     Always TRUE.
    239 */
    240static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username,
    241        char** password, char** domain) {
    242
    243    rdpContext* context = instance->context;
    244    guac_client* client = ((rdp_freerdp_context*) context)->client;
    245    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    246    guac_rdp_settings* settings = rdp_client->settings;
    247    char* params[4] = {NULL};
    248    int i = 0;
    249    
    250    /* If the client does not support the "required" instruction, warn and
    251     * quit.
    252     */
    253    if (!guac_client_owner_supports_required(client)) {
    254        guac_client_log(client, GUAC_LOG_WARNING, "Client does not support the "
    255                "\"required\" instruction. No authentication parameters will "
    256                "be requested.");
    257        return TRUE;
    258    }
    259
    260    /* If the username is undefined, add it to the requested parameters. */
    261    if (settings->username == NULL) {
    262        guac_argv_register(GUAC_RDP_ARGV_USERNAME, guac_rdp_argv_callback, NULL, 0);
    263        params[i] = GUAC_RDP_ARGV_USERNAME;
    264        i++;
    265        
    266        /* If username is undefined and domain is also undefined, request domain. */
    267        if (settings->domain == NULL) {
    268            guac_argv_register(GUAC_RDP_ARGV_DOMAIN, guac_rdp_argv_callback, NULL, 0);
    269            params[i] = GUAC_RDP_ARGV_DOMAIN;
    270            i++;
    271        }
    272        
    273    }
    274    
    275    /* If the password is undefined, add it to the requested parameters. */
    276    if (settings->password == NULL) {
    277        guac_argv_register(GUAC_RDP_ARGV_PASSWORD, guac_rdp_argv_callback, NULL, 0);
    278        params[i] = GUAC_RDP_ARGV_PASSWORD;
    279        i++;
    280    }
    281    
    282    /* NULL-terminate the array. */
    283    params[i] = NULL;
    284    
    285    if (i > 0) {
    286        
    287        /* Send required parameters to the owner and wait for the response. */
    288        guac_client_owner_send_required(client, (const char**) params);
    289        guac_argv_await((const char**) params);
    290        
    291        /* Free old values and get new values from settings. */
    292        guac_mem_free(*username);
    293        guac_mem_free(*password);
    294        guac_mem_free(*domain);
    295        *username = guac_strdup(settings->username);
    296        *password = guac_strdup(settings->password);
    297        *domain = guac_strdup(settings->domain);
    298        
    299    }
    300    
    301    /* Always return TRUE allowing connection to retry. */
    302    return TRUE;
    303
    304}
    305
    306#ifdef HAVE_FREERDP_VERIFYCERTIFICATEEX
    307/**
    308 * Callback invoked by FreeRDP when the SSL/TLS certificate of the RDP server
    309 * needs to be verified. If this ever happens, this function implementation
    310 * will always fail unless the connection has been configured to ignore
    311 * certificate validity.
    312 *
    313 * @param instance
    314 *     The FreeRDP instance associated with the RDP session whose SSL/TLS
    315 *     certificate needs to be verified.
    316 *
    317 * @param hostname
    318 *     The hostname or address of the RDP server being connected to.
    319 *
    320 * @param port
    321 *     The TCP port number of the RDP server being connected to.
    322 *
    323 * @param common_name
    324 *     The name of the server protected by the certificate. This should match
    325 *     the hostname/address of the RDP server.
    326 *
    327 * @param subject
    328 *     The subject to whom the certificate was issued.
    329 *
    330 * @param issuer
    331 *     The authority that issued the certificate,
    332 *
    333 * @param fingerprint
    334 *     The cryptographic fingerprint of the certificate.
    335 *
    336 * @param flags
    337 *     Bitwise OR of any applicable certificate verification flags. Valid flags are
    338 *     VERIFY_CERT_FLAG_NONE, VERIFY_CERT_FLAG_LEGACY, VERIFY_CERT_FLAG_REDIRECT,
    339 *     VERIFY_CERT_FLAG_GATEWAY, VERIFY_CERT_FLAG_CHANGED, and
    340 *     VERIFY_CERT_FLAG_MISMATCH.
    341 *
    342 * @return
    343 *     1 to accept the certificate and store within FreeRDP's configuration
    344 *     directory, 2 to accept the certificate only within this session, or 0 to
    345 *     reject the certificate.
    346 */
    347static DWORD rdp_freerdp_verify_certificate(freerdp* instance,
    348        const char* hostname, UINT16 port, const char* common_name,
    349        const char* subject, const char* issuer, const char* fingerprint,
    350        DWORD flags) {
    351#else
    352/**
    353 * Callback invoked by FreeRDP when the SSL/TLS certificate of the RDP server
    354 * needs to be verified. If this ever happens, this function implementation
    355 * will always fail unless the connection has been configured to ignore
    356 * certificate validity.
    357 *
    358 * @param instance
    359 *     The FreeRDP instance associated with the RDP session whose SSL/TLS
    360 *     certificate needs to be verified.
    361 *
    362 * @param subject
    363 *     The subject to whom the certificate was issued.
    364 *
    365 * @param issuer
    366 *     The authority that issued the certificate,
    367 *
    368 * @param fingerprint
    369 *     The cryptographic fingerprint of the certificate.
    370 *
    371 * @param host_mismatch
    372 *     TRUE if the certificate does not match the destination hostname, FALSE
    373 *     otherwise.
    374 *
    375 * @return
    376 *     1 to accept the certificate and store within FreeRDP's configuration
    377 *     directory, 2 to accept the certificate only within this session, or 0 to
    378 *     reject the certificate.
    379 */
    380static DWORD rdp_freerdp_verify_certificate(freerdp* instance,
    381        const char* common_name, const char* subject, const char* issuer,
    382        const char* fingerprint, BOOL host_mismatch) {
    383#endif
    384
    385    rdpContext* context = instance->context;
    386    guac_client* client = ((rdp_freerdp_context*) context)->client;
    387    guac_rdp_client* rdp_client =
    388        (guac_rdp_client*) client->data;
    389
    390    /* Bypass validation if ignore_certificate given */
    391    if (rdp_client->settings->ignore_certificate) {
    392        guac_client_log(client, GUAC_LOG_INFO, "Certificate validation bypassed");
    393        return 2; /* Accept only for this session */
    394    }
    395
    396    guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed");
    397    return 0; /* Reject certificate */
    398
    399}
    400
    401/**
    402 * Waits for messages from the RDP server for the given number of milliseconds.
    403 *
    404 * @param client
    405 *     The client associated with the current RDP session.
    406 *
    407 * @param timeout_msecs
    408 *     The maximum amount of time to wait, in milliseconds.
    409 *
    410 * @return
    411 *     A positive value if messages are ready, zero if the specified timeout
    412 *     period elapsed, or a negative value if an error occurs.
    413 */
    414static int rdp_guac_client_wait_for_messages(guac_client* client,
    415        int timeout_msecs) {
    416
    417    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    418    freerdp* rdp_inst = rdp_client->rdp_inst;
    419
    420    HANDLE handles[GUAC_RDP_MAX_FILE_DESCRIPTORS];
    421    int num_handles = freerdp_get_event_handles(rdp_inst->context, handles,
    422            GUAC_RDP_MAX_FILE_DESCRIPTORS);
    423
    424    /* Wait for data and construct a reasonable frame */
    425    int result = WaitForMultipleObjects(num_handles, handles, FALSE,
    426            timeout_msecs);
    427
    428    /* Translate WaitForMultipleObjects() return values */
    429    switch (result) {
    430
    431        /* Timeout elapsed before wait could complete */
    432        case WAIT_TIMEOUT:
    433            return 0;
    434
    435        /* Attempt to wait failed due to an error */
    436        case WAIT_FAILED:
    437            return -1;
    438
    439    }
    440
    441    /* Wait was successful */
    442    return 1;
    443
    444}
    445
    446/**
    447 * Connects to an RDP server as described by the guac_rdp_settings structure
    448 * associated with the given client, allocating and freeing all objects
    449 * directly related to the RDP connection. It is expected that all objects
    450 * which are independent of FreeRDP's state (the clipboard, display update
    451 * management, etc.) will already be allocated and associated with the
    452 * guac_rdp_client associated with the given guac_client. This function blocks
    453 * for the duration of the RDP session, returning only after the session has
    454 * completely disconnected.
    455 *
    456 * @param client
    457 *     The guac_client associated with the RDP settings describing the
    458 *     connection that should be established.
    459 *
    460 * @return
    461 *     Zero if the connection successfully terminated and a reconnect is
    462 *     desired, non-zero if an error occurs or the connection was disconnected
    463 *     and a reconnect is NOT desired.
    464 */
    465static int guac_rdp_handle_connection(guac_client* client) {
    466
    467    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    468    guac_rdp_settings* settings = rdp_client->settings;
    469
    470    /* Init random number generator */
    471    srandom(time(NULL));
    472
    473    pthread_rwlock_wrlock(&(rdp_client->lock));
    474
    475    /* Create display */
    476    rdp_client->display = guac_common_display_alloc(client,
    477            rdp_client->settings->width,
    478            rdp_client->settings->height);
    479
    480    /* Use lossless compression only if requested (otherwise, use default
    481     * heuristics) */
    482    guac_common_display_set_lossless(rdp_client->display, settings->lossless);
    483
    484    rdp_client->current_surface = rdp_client->display->default_surface;
    485
    486    rdp_client->available_svc = guac_common_list_alloc();
    487
    488    /* Init client */
    489    freerdp* rdp_inst = freerdp_new();
    490    rdp_inst->PreConnect = rdp_freerdp_pre_connect;
    491    rdp_inst->Authenticate = rdp_freerdp_authenticate;
    492
    493#ifdef HAVE_FREERDP_VERIFYCERTIFICATEEX
    494    rdp_inst->VerifyCertificateEx = rdp_freerdp_verify_certificate;
    495#else
    496    rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate;
    497#endif
    498
    499    /* Allocate FreeRDP context */
    500    rdp_inst->ContextSize = sizeof(rdp_freerdp_context);
    501
    502    if (!freerdp_context_new(rdp_inst)) {
    503        guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    504                "FreeRDP initialization failed before connecting. Please "
    505                "check for errors earlier in the logs and/or enable "
    506                "debug-level logging for guacd.");
    507        goto fail;
    508    }
    509
    510    ((rdp_freerdp_context*) rdp_inst->context)->client = client;
    511
    512    /* Load keymap into client */
    513    rdp_client->keyboard = guac_rdp_keyboard_alloc(client,
    514            settings->server_layout);
    515
    516    /* Set default pointer */
    517    guac_common_cursor_set_pointer(rdp_client->display->cursor);
    518
    519    /* Connect to RDP server */
    520    if (!freerdp_connect(rdp_inst)) {
    521        guac_rdp_client_abort(client, rdp_inst);
    522        goto fail;
    523    }
    524
    525    /* Connection complete */
    526    rdp_client->rdp_inst = rdp_inst;
    527
    528    guac_timestamp last_frame_end = guac_timestamp_current();
    529
    530    /* Signal that reconnect has been completed */
    531    guac_rdp_disp_reconnect_complete(rdp_client->disp);
    532
    533    pthread_rwlock_unlock(&(rdp_client->lock));
    534
    535    /* Handle messages from RDP server while client is running */
    536    while (client->state == GUAC_CLIENT_RUNNING
    537            && !guac_rdp_disp_reconnect_needed(rdp_client->disp)) {
    538
    539        /* Update remote display size */
    540        guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst);
    541
    542        /* Wait for data and construct a reasonable frame */
    543        int wait_result = rdp_guac_client_wait_for_messages(client,
    544                GUAC_RDP_FRAME_START_TIMEOUT);
    545        if (wait_result > 0) {
    546
    547            int processing_lag = guac_client_get_processing_lag(client);
    548            guac_timestamp frame_start = guac_timestamp_current();
    549
    550            /* Read server messages until frame is built */
    551            do {
    552
    553                guac_timestamp frame_end;
    554                int frame_remaining;
    555
    556                /* Handle any queued FreeRDP events (this may result in RDP
    557                 * messages being sent) */
    558                pthread_mutex_lock(&(rdp_client->message_lock));
    559                int event_result = freerdp_check_event_handles(rdp_inst->context);
    560                pthread_mutex_unlock(&(rdp_client->message_lock));
    561
    562                /* Abort if FreeRDP event handling fails */
    563                if (!event_result) {
    564                    wait_result = -1;
    565                    break;
    566                }
    567
    568                /* Calculate time remaining in frame */
    569                frame_end = guac_timestamp_current();
    570                frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION
    571                                - frame_end;
    572
    573                /* Calculate time that client needs to catch up */
    574                int time_elapsed = frame_end - last_frame_end;
    575                int required_wait = processing_lag - time_elapsed;
    576
    577                /* Increase the duration of this frame if client is lagging */
    578                if (required_wait > GUAC_RDP_FRAME_TIMEOUT)
    579                    wait_result = rdp_guac_client_wait_for_messages(client,
    580                            required_wait);
    581
    582                /* Wait again if frame remaining */
    583                else if (frame_remaining > 0)
    584                    wait_result = rdp_guac_client_wait_for_messages(client,
    585                            GUAC_RDP_FRAME_TIMEOUT);
    586                else
    587                    break;
    588
    589            } while (wait_result > 0);
    590
    591            /* Record end of frame, excluding server-side rendering time (we
    592             * assume server-side rendering time will be consistent between any
    593             * two subsequent frames, and that this time should thus be
    594             * excluded from the required wait period of the next frame). */
    595            last_frame_end = frame_start;
    596
    597        }
    598
    599        /* Test whether the RDP server is closing the connection */
    600        int connection_closing = freerdp_shall_disconnect(rdp_inst);
    601
    602        /* Close connection cleanly if server is disconnecting */
    603        if (connection_closing)
    604            guac_rdp_client_abort(client, rdp_inst);
    605
    606        /* If a low-level connection error occurred, fail */
    607        else if (wait_result < 0)
    608            guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE,
    609                    "Connection closed.");
    610
    611        /* Flush frame only if successful */
    612        else {
    613            guac_common_display_flush(rdp_client->display);
    614            guac_client_end_frame(client);
    615            guac_socket_flush(client->socket);
    616        }
    617
    618    }
    619
    620    pthread_rwlock_wrlock(&(rdp_client->lock));
    621
    622    /* Clean up print job, if active */
    623    if (rdp_client->active_job != NULL) {
    624        guac_rdp_print_job_kill(rdp_client->active_job);
    625        guac_rdp_print_job_free(rdp_client->active_job);
    626    }
    627
    628    /* Disconnect client and channels */
    629    pthread_mutex_lock(&(rdp_client->message_lock));
    630    freerdp_disconnect(rdp_inst);
    631    pthread_mutex_unlock(&(rdp_client->message_lock));
    632
    633    /* Clean up FreeRDP internal GDI implementation */
    634    gdi_free(rdp_inst);
    635
    636    /* Clean up RDP client context */
    637    freerdp_context_free(rdp_inst);
    638
    639    /* Clean up RDP client */
    640    freerdp_free(rdp_inst);
    641    rdp_client->rdp_inst = NULL;
    642
    643    /* Free SVC list */
    644    guac_common_list_free(rdp_client->available_svc, NULL);
    645    rdp_client->available_svc = NULL;
    646
    647    /* Free RDP keyboard state */
    648    guac_rdp_keyboard_free(rdp_client->keyboard);
    649    rdp_client->keyboard = NULL;
    650
    651    /* Free display */
    652    guac_common_display_free(rdp_client->display);
    653    rdp_client->display = NULL;
    654
    655    pthread_rwlock_unlock(&(rdp_client->lock));
    656
    657    /* Client is now disconnected */
    658    guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected");
    659
    660    return 0;
    661
    662fail:
    663    pthread_rwlock_unlock(&(rdp_client->lock));
    664    return 1;
    665
    666}
    667
    668void* guac_rdp_client_thread(void* data) {
    669
    670    guac_client* client = (guac_client*) data;
    671    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    672    guac_rdp_settings* settings = rdp_client->settings;
    673
    674    /* If Wake-on-LAN is enabled, try to wake. */
    675    if (settings->wol_send_packet) {
    676        guac_client_log(client, GUAC_LOG_DEBUG, "Sending Wake-on-LAN packet, "
    677                "and pausing for %d seconds.", settings->wol_wait_time);
    678        
    679        /* Send the Wake-on-LAN request. */
    680        if (guac_wol_wake(settings->wol_mac_addr, settings->wol_broadcast_addr,
    681                settings->wol_udp_port))
    682            return NULL;
    683        
    684        /* If wait time is specified, sleep for that amount of time. */
    685        if (settings->wol_wait_time > 0)
    686            guac_timestamp_msleep(settings->wol_wait_time * 1000);
    687    }
    688    
    689    /* If audio enabled, choose an encoder */
    690    if (settings->audio_enabled) {
    691
    692        rdp_client->audio = guac_audio_stream_alloc(client, NULL,
    693                GUAC_RDP_AUDIO_RATE,
    694                GUAC_RDP_AUDIO_CHANNELS,
    695                GUAC_RDP_AUDIO_BPS);
    696
    697        /* Warn if no audio encoding is available */
    698        if (rdp_client->audio == NULL)
    699            guac_client_log(client, GUAC_LOG_INFO,
    700                    "No available audio encoding. Sound disabled.");
    701
    702    } /* end if audio enabled */
    703
    704    /* Load filesystem if drive enabled */
    705    if (settings->drive_enabled) {
    706
    707        /* Allocate actual emulated filesystem */
    708        rdp_client->filesystem =
    709            guac_rdp_fs_alloc(client, settings->drive_path,
    710                    settings->create_drive_path, settings->disable_download,
    711                    settings->disable_upload);
    712
    713        /* Expose filesystem to owner */
    714        guac_client_for_owner(client, guac_rdp_fs_expose,
    715                rdp_client->filesystem);
    716
    717    }
    718
    719#ifdef ENABLE_COMMON_SSH
    720    /* Connect via SSH if SFTP is enabled */
    721    if (settings->enable_sftp) {
    722
    723        /* Abort if username is missing */
    724        if (settings->sftp_username == NULL) {
    725            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    726                    "A username or SFTP-specific username is required if "
    727                    "SFTP is enabled.");
    728            return NULL;
    729        }
    730
    731        guac_client_log(client, GUAC_LOG_DEBUG,
    732                "Connecting via SSH for SFTP filesystem access.");
    733
    734        rdp_client->sftp_user =
    735            guac_common_ssh_create_user(settings->sftp_username);
    736
    737        /* Import private key, if given */
    738        if (settings->sftp_private_key != NULL) {
    739
    740            guac_client_log(client, GUAC_LOG_DEBUG,
    741                    "Authenticating with private key.");
    742
    743            /* Abort if private key cannot be read */
    744            if (guac_common_ssh_user_import_key(rdp_client->sftp_user,
    745                        settings->sftp_private_key,
    746                        settings->sftp_passphrase)) {
    747                guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    748                        "Private key unreadable.");
    749                return NULL;
    750            }
    751
    752        }
    753
    754        /* Otherwise, use specified password */
    755        else {
    756
    757            guac_client_log(client, GUAC_LOG_DEBUG,
    758                    "Authenticating with password.");
    759
    760            guac_common_ssh_user_set_password(rdp_client->sftp_user,
    761                    settings->sftp_password);
    762
    763        }
    764
    765        /* Attempt SSH connection */
    766        rdp_client->sftp_session =
    767            guac_common_ssh_create_session(client, settings->sftp_hostname,
    768                    settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval,
    769                    settings->sftp_host_key, NULL);
    770
    771        /* Fail if SSH connection does not succeed */
    772        if (rdp_client->sftp_session == NULL) {
    773            /* Already aborted within guac_common_ssh_create_session() */
    774            return NULL;
    775        }
    776
    777        /* Load and expose filesystem */
    778        rdp_client->sftp_filesystem =
    779            guac_common_ssh_create_sftp_filesystem(rdp_client->sftp_session,
    780                    settings->sftp_root_directory, NULL,
    781                    settings->sftp_disable_download,
    782                    settings->sftp_disable_upload);
    783
    784        /* Expose filesystem to connection owner */
    785        guac_client_for_owner(client,
    786                guac_common_ssh_expose_sftp_filesystem,
    787                rdp_client->sftp_filesystem);
    788
    789        /* Abort if SFTP connection fails */
    790        if (rdp_client->sftp_filesystem == NULL) {
    791            guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE,
    792                    "SFTP connection failed.");
    793            return NULL;
    794        }
    795
    796        /* Configure destination for basic uploads, if specified */
    797        if (settings->sftp_directory != NULL)
    798            guac_common_ssh_sftp_set_upload_path(
    799                    rdp_client->sftp_filesystem,
    800                    settings->sftp_directory);
    801
    802        guac_client_log(client, GUAC_LOG_DEBUG,
    803                "SFTP connection succeeded.");
    804
    805    }
    806#endif
    807
    808    /* Set up screen recording, if requested */
    809    if (settings->recording_path != NULL) {
    810        rdp_client->recording = guac_recording_create(client,
    811                settings->recording_path,
    812                settings->recording_name,
    813                settings->create_recording_path,
    814                !settings->recording_exclude_output,
    815                !settings->recording_exclude_mouse,
    816                !settings->recording_exclude_touch,
    817                settings->recording_include_keys);
    818    }
    819
    820    /* Continue handling connections until error or client disconnect */
    821    while (client->state == GUAC_CLIENT_RUNNING) {
    822        if (guac_rdp_handle_connection(client))
    823            break;
    824    }
    825
    826    return NULL;
    827
    828}
    829