<NME> rdp.c
<BEF> /*
 * 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
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

#include "argv.h"
#include "beep.h"
#include "bitmap.h"
#include "channels/audio-input/audio-buffer.h"
#include "channels/audio-input/audio-input.h"
#include "channels/cliprdr.h"
#include "channels/disp.h"
#include "channels/pipe-svc.h"
#include "channels/rail.h"
#include "channels/rdpdr/rdpdr.h"
#include "channels/rdpei.h"
#include "channels/rdpgfx.h"
#include "channels/rdpsnd/rdpsnd.h"
#include "client.h"
#include "color.h"
#include "common/cursor.h"
#include "common/display.h"
#include "config.h"
#include "error.h"
#include "fs.h"
#include "gdi.h"
#include "glyph.h"
#include "keyboard.h"
#include "plugins/channels.h"
#include "pointer.h"
#include "print-job.h"
#include "rdp.h"
#include "settings.h"

#ifdef ENABLE_COMMON_SSH
#include "common-ssh/sftp.h"
#include "common-ssh/ssh.h"
#include "common-ssh/user.h"
#endif

#include <freerdp/addin.h>
#include <freerdp/cache/bitmap.h>
#include <freerdp/cache/brush.h>
#include <freerdp/cache/glyph.h>
#include <freerdp/cache/offscreen.h>
#include <freerdp/cache/palette.h>
#include <freerdp/cache/pointer.h>
#include <freerdp/channels/channels.h>
#include <freerdp/client/channels.h>
#include <freerdp/freerdp.h>
#include <freerdp/gdi/gdi.h>
#include <freerdp/graphics.h>
#include <freerdp/primary.h>
#include <freerdp/settings.h>
#include <freerdp/update.h>
#include <guacamole/argv.h>
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/recording.h>
#include <guacamole/socket.h>
#include <guacamole/string.h>
#include <guacamole/timestamp.h>
#include <guacamole/wol.h>
#include <winpr/error.h>
#include <winpr/synch.h>
#include <winpr/wtypes.h>

#include <stdlib.h>
#include <time.h>

BOOL rdp_freerdp_pre_connect(freerdp* instance) {

    rdpContext* context = instance->context;
    rdpGraphics* graphics = context->graphics;

    guac_client* client = ((rdp_freerdp_context*) context)->client;
    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    guac_rdp_settings* settings = rdp_client->settings;

    /* Push desired settings to FreeRDP */
    guac_rdp_push_settings(client, settings, instance);

    /* Init FreeRDP add-in provider */
    freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0);

    /* Load "disp" plugin for display update */
    if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE)
        guac_rdp_disp_load_plugin(context);

    /* Load "rdpei" plugin for multi-touch support */
    if (settings->enable_touch)
        guac_rdp_rdpei_load_plugin(context);

    /* Load "AUDIO_INPUT" plugin for audio input*/
    if (settings->enable_audio_input) {
        rdp_client->audio_input = guac_rdp_audio_buffer_alloc(client);
        guac_rdp_audio_load_plugin(instance->context);
    }

    /* Load "cliprdr" service if not disabled */
    if (!(settings->disable_copy && settings->disable_paste))
        guac_rdp_clipboard_load_plugin(rdp_client->clipboard, context);

    /* If RDPSND/RDPDR required, load them */
    if (settings->printing_enabled
        || settings->drive_enabled
        || settings->audio_enabled) {
        guac_rdpdr_load_plugin(context);
        guac_rdpsnd_load_plugin(context);
    }

    /* Load RAIL plugin if RemoteApp in use */
    if (settings->remote_app != NULL)
        guac_rdp_rail_load_plugin(context);

    /* Load SVC plugin instances for all static channels */
    if (settings->svc_names != NULL) {

        char** current = settings->svc_names;
        do {
            guac_rdp_pipe_svc_load_plugin(context, *current);
        } while (*(++current) != NULL);

    }

    /* Init FreeRDP internal GDI implementation */
    if (!gdi_init(instance, guac_rdp_get_native_pixel_format(FALSE)))
        return FALSE;

    /* Set up bitmap handling */
    rdpBitmap bitmap = *graphics->Bitmap_Prototype;
    bitmap.size = sizeof(guac_rdp_bitmap);
    bitmap.New = guac_rdp_bitmap_new;
    bitmap.Free = guac_rdp_bitmap_free;
    bitmap.Paint = guac_rdp_bitmap_paint;
    bitmap.SetSurface = guac_rdp_bitmap_setsurface;
    graphics_register_bitmap(graphics, &bitmap);

    /* Set up glyph handling */
    rdpGlyph glyph = *graphics->Glyph_Prototype;
    glyph.size = sizeof(guac_rdp_glyph);
    glyph.New = guac_rdp_glyph_new;
    glyph.Free = guac_rdp_glyph_free;
    glyph.Draw = guac_rdp_glyph_draw;
    glyph.BeginDraw = guac_rdp_glyph_begindraw;
    glyph.EndDraw = guac_rdp_glyph_enddraw;
    graphics_register_glyph(graphics, &glyph);

    /* Set up pointer handling */
    rdpPointer pointer = *graphics->Pointer_Prototype;
    pointer.size = sizeof(guac_rdp_pointer);
    pointer.New = guac_rdp_pointer_new;
    pointer.Free = guac_rdp_pointer_free;
    pointer.Set = guac_rdp_pointer_set;
    pointer.SetNull = guac_rdp_pointer_set_null;
    pointer.SetDefault = guac_rdp_pointer_set_default;
    graphics_register_pointer(graphics, &pointer);

    /* Beep on receipt of Play Sound PDU */
    instance->update->PlaySound = guac_rdp_beep_play_sound;

    /* Automatically synchronize keyboard locks when changed server-side */
    instance->update->SetKeyboardIndicators = guac_rdp_keyboard_set_indicators;

    /* Set up GDI */
    instance->update->DesktopResize = guac_rdp_gdi_desktop_resize;
    instance->update->BeginPaint = guac_rdp_gdi_begin_paint;
    instance->update->EndPaint = guac_rdp_gdi_end_paint;
    instance->update->SetBounds = guac_rdp_gdi_set_bounds;

    instance->update->SurfaceFrameMarker = guac_rdp_gdi_surface_frame_marker;
    instance->update->altsec->FrameMarker = guac_rdp_gdi_frame_marker;

    rdpPrimaryUpdate* primary = instance->update->primary;
    primary->DstBlt = guac_rdp_gdi_dstblt;
    primary->PatBlt = guac_rdp_gdi_patblt;
    primary->ScrBlt = guac_rdp_gdi_scrblt;
    primary->MemBlt = guac_rdp_gdi_memblt;
    primary->OpaqueRect = guac_rdp_gdi_opaquerect;

    pointer_cache_register_callbacks(instance->update);
    glyph_cache_register_callbacks(instance->update);
    brush_cache_register_callbacks(instance->update);
    bitmap_cache_register_callbacks(instance->update);
    offscreen_cache_register_callbacks(instance->update);
    palette_cache_register_callbacks(instance->update);

    /* Load "rdpgfx" plugin for Graphics Pipeline Extension */
    if (settings->enable_gfx)
        guac_rdp_rdpgfx_load_plugin(context);

    /* Load plugin providing Dynamic Virtual Channel support, if required */
    if (instance->settings->SupportDynamicChannels &&
            guac_freerdp_channels_load_plugin(context, "drdynvc",
                instance->settings)) {
        guac_client_log(client, GUAC_LOG_WARNING,
                "Failed to load drdynvc plugin. Display update and audio "
                "input support will be disabled.");
    }

    return TRUE;

}

/**
 * Callback invoked by FreeRDP when authentication is required but the required
 * parameters have not been provided. In the case of Guacamole clients that
 * support the "required" instruction, this function will send any of the three
 * unpopulated RDP authentication parameters back to the client so that the
 * connection owner can provide the required information.  If the values have
 * been provided in the original connection parameters the user will not be
 * prompted for updated parameters. If the version of Guacamole Client in use
 * by the connection owner does not support the "required" instruction then the
 * connection will fail. This function always returns true.
 *
 * @param instance
 *     The FreeRDP instance associated with the RDP session requesting
 *     credentials.
 *
 * @param username
 *     Pointer to a string which will receive the user's username.
 *
 * @param password
 *     Pointer to a string which will receive the user's password.
 *
 * @param domain
 *     Pointer to a string which will receive the domain associated with the
 *     user's account.
 *
 * @return
 *     Always TRUE.
        guac_client_log(client, GUAC_LOG_WARNING,
                "Failed to load cliprdr plugin. Clipboard will not work.");

    /* If audio enabled, choose an encoder */
    if (settings->audio_enabled) {

        rdp_client->audio = guac_audio_stream_alloc(client, NULL,
                GUAC_RDP_AUDIO_RATE,
                GUAC_RDP_AUDIO_CHANNELS,
                GUAC_RDP_AUDIO_BPS);

        /* Warn if no audio encoding is available */
        if (rdp_client->audio == NULL)
            guac_client_log(client, GUAC_LOG_INFO,
                    "No available audio encoding. Sound disabled.");

    } /* end if audio enabled */

    /* If RDPSND/RDPDR required, load them */
    if (settings->printing_enabled
        || settings->drive_enabled
    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    guac_rdp_settings* settings = rdp_client->settings;
    char* params[4] = {NULL};
    int i = 0;
    
    /* If the client does not support the "required" instruction, warn and
     * quit.
     */
    if (!guac_client_owner_supports_required(client)) {
        guac_client_log(client, GUAC_LOG_WARNING, "Client does not support the "
                "\"required\" instruction. No authentication parameters will "
                "be requested.");
        return TRUE;
    }

    /* If the username is undefined, add it to the requested parameters. */
    if (settings->username == NULL) {
        guac_argv_register(GUAC_RDP_ARGV_USERNAME, guac_rdp_argv_callback, NULL, 0);
        params[i] = GUAC_RDP_ARGV_USERNAME;
        i++;
        
        /* If username is undefined and domain is also undefined, request domain. */
        if (settings->domain == NULL) {
            guac_argv_register(GUAC_RDP_ARGV_DOMAIN, guac_rdp_argv_callback, NULL, 0);
            params[i] = GUAC_RDP_ARGV_DOMAIN;
            i++;
        }
        
    }
    
    /* If the password is undefined, add it to the requested parameters. */
    if (settings->password == NULL) {
        guac_argv_register(GUAC_RDP_ARGV_PASSWORD, guac_rdp_argv_callback, NULL, 0);
        params[i] = GUAC_RDP_ARGV_PASSWORD;
        i++;
    }
    
    /* NULL-terminate the array. */
    params[i] = NULL;
    
    if (i > 0) {
        
        /* Send required parameters to the owner and wait for the response. */
        guac_client_owner_send_required(client, (const char**) params);
        guac_argv_await((const char**) params);
        
        /* Free old values and get new values from settings. */
        free(*username);
        free(*password);
        free(*domain);
        *username = guac_strdup(settings->username);
        *password = guac_strdup(settings->password);
        *domain = guac_strdup(settings->domain);
        
    }
    
    /* Always return TRUE allowing connection to retry. */
    return TRUE;

}

#ifdef HAVE_FREERDP_VERIFYCERTIFICATEEX
/**
 * Callback invoked by FreeRDP when the SSL/TLS certificate of the RDP server
 * needs to be verified. If this ever happens, this function implementation
 * will always fail unless the connection has been configured to ignore
 * certificate validity.
 *
 * @param instance
 *     The FreeRDP instance associated with the RDP session whose SSL/TLS
 *     certificate needs to be verified.
 *
 * @param hostname
 *     The hostname or address of the RDP server being connected to.
 *
 * @param port
 *     The TCP port number of the RDP server being connected to.
 *
 * @param common_name
 *     The name of the server protected by the certificate. This should match
 *     the hostname/address of the RDP server.
 *
 * @param subject
 *     The subject to whom the certificate was issued.
 *
 * @param issuer
 *     The authority that issued the certificate,
 *
 * @param fingerprint
 *     The cryptographic fingerprint of the certificate.
 *
 * @param flags
 *     Bitwise OR of any applicable certificate verification flags. Valid flags are
 *     VERIFY_CERT_FLAG_NONE, VERIFY_CERT_FLAG_LEGACY, VERIFY_CERT_FLAG_REDIRECT,
 *     VERIFY_CERT_FLAG_GATEWAY, VERIFY_CERT_FLAG_CHANGED, and
 *     VERIFY_CERT_FLAG_MISMATCH.
 *
 * @return
 *     1 to accept the certificate and store within FreeRDP's configuration
 *     directory, 2 to accept the certificate only within this session, or 0 to
 *     reject the certificate.
 */
static DWORD rdp_freerdp_verify_certificate(freerdp* instance,
        const char* hostname, UINT16 port, const char* common_name,
        const char* subject, const char* issuer, const char* fingerprint,
        DWORD flags) {
#else
/**
 * Callback invoked by FreeRDP when the SSL/TLS certificate of the RDP server
 * needs to be verified. If this ever happens, this function implementation
 * will always fail unless the connection has been configured to ignore
 * certificate validity.
 *
 * @param instance
 *     The FreeRDP instance associated with the RDP session whose SSL/TLS
 *     certificate needs to be verified.
 *
 * @param subject
 *     The subject to whom the certificate was issued.
 *
 * @param issuer
 *     The authority that issued the certificate,
 *
 * @param fingerprint
 *     The cryptographic fingerprint of the certificate.
 *
 * @param host_mismatch
 *     TRUE if the certificate does not match the destination hostname, FALSE
 *     otherwise.
 *
 * @return
 *     1 to accept the certificate and store within FreeRDP's configuration
 *     directory, 2 to accept the certificate only within this session, or 0 to
 *     reject the certificate.
 */
static DWORD rdp_freerdp_verify_certificate(freerdp* instance,
        const char* common_name, const char* subject, const char* issuer,
        const char* fingerprint, BOOL host_mismatch) {
#endif

    rdpContext* context = instance->context;
    guac_client* client = ((rdp_freerdp_context*) context)->client;
    guac_rdp_client* rdp_client =
        (guac_rdp_client*) client->data;

    /* Bypass validation if ignore_certificate given */
    if (rdp_client->settings->ignore_certificate) {
        guac_client_log(client, GUAC_LOG_INFO, "Certificate validation bypassed");
        return 2; /* Accept only for this session */
    }

    guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed");
    return 0; /* Reject certificate */

}

/**
 * Waits for messages from the RDP server for the given number of milliseconds.
 *
 * @param client
 *     The client associated with the current RDP session.
 *
 * @param timeout_msecs
 *     The maximum amount of time to wait, in milliseconds.
 *
 * @return
 *     A positive value if messages are ready, zero if the specified timeout
 *     period elapsed, or a negative value if an error occurs.
 */
static int rdp_guac_client_wait_for_messages(guac_client* client,
        int timeout_msecs) {

    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    freerdp* rdp_inst = rdp_client->rdp_inst;

    HANDLE handles[GUAC_RDP_MAX_FILE_DESCRIPTORS];
    int num_handles = freerdp_get_event_handles(rdp_inst->context, handles,
            GUAC_RDP_MAX_FILE_DESCRIPTORS);

    /* Wait for data and construct a reasonable frame */
    int result = WaitForMultipleObjects(num_handles, handles, FALSE,
            timeout_msecs);

    /* Translate WaitForMultipleObjects() return values */
    switch (result) {

        /* Timeout elapsed before wait could complete */
        case WAIT_TIMEOUT:
            return 0;

        /* Attempt to wait failed due to an error */
        case WAIT_FAILED:
            return -1;

    }

    /* Wait was successful */
    return 1;

}

/**
 * Connects to an RDP server as described by the guac_rdp_settings structure
 * associated with the given client, allocating and freeing all objects
 * directly related to the RDP connection. It is expected that all objects
 * which are independent of FreeRDP's state (the clipboard, display update
 * management, etc.) will already be allocated and associated with the
 * guac_rdp_client associated with the given guac_client. This function blocks
 * for the duration of the RDP session, returning only after the session has
 * completely disconnected.
 *
 * @param client
 *     The guac_client associated with the RDP settings describing the
 *     connection that should be established.
 *
 * @return
 *     Zero if the connection successfully terminated and a reconnect is
 *     desired, non-zero if an error occurs or the connection was disconnected
 *     and a reconnect is NOT desired.
 */
static int guac_rdp_handle_connection(guac_client* client) {

    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    guac_rdp_settings* settings = rdp_client->settings;

    /* Init random number generator */
    srandom(time(NULL));

    pthread_rwlock_wrlock(&(rdp_client->lock));

    /* Create display */
    rdp_client->display = guac_common_display_alloc(client,
            rdp_client->settings->width,
            rdp_client->settings->height);

    /* Use lossless compression only if requested (otherwise, use default
     * heuristics) */
    guac_common_display_set_lossless(rdp_client->display, settings->lossless);

    rdp_client->current_surface = rdp_client->display->default_surface;

    rdp_client->available_svc = guac_common_list_alloc();

    /* Init client */
    freerdp* rdp_inst = freerdp_new();
    rdp_inst->PreConnect = rdp_freerdp_pre_connect;
    rdp_inst->Authenticate = rdp_freerdp_authenticate;

#ifdef HAVE_FREERDP_VERIFYCERTIFICATEEX
    rdp_inst->VerifyCertificateEx = rdp_freerdp_verify_certificate;
#else
    rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate;
#endif

    /* Allocate FreeRDP context */
    rdp_inst->ContextSize = sizeof(rdp_freerdp_context);

    if (!freerdp_context_new(rdp_inst)) {
        guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
                "FreeRDP initialization failed before connecting. Please "
                "check for errors earlier in the logs and/or enable "
                "debug-level logging for guacd.");
        goto fail;
    }

    ((rdp_freerdp_context*) rdp_inst->context)->client = client;

    /* Load keymap into client */
    rdp_client->keyboard = guac_rdp_keyboard_alloc(client,
            settings->server_layout);

    /* Set default pointer */
    guac_common_cursor_set_pointer(rdp_client->display->cursor);

    /* Connect to RDP server */
    if (!freerdp_connect(rdp_inst)) {
        guac_rdp_client_abort(client, rdp_inst);
        goto fail;
    }

    /* Connection complete */
    rdp_client->rdp_inst = rdp_inst;

    guac_timestamp last_frame_end = guac_timestamp_current();

    /* Signal that reconnect has been completed */
    guac_rdp_disp_reconnect_complete(rdp_client->disp);

    pthread_rwlock_unlock(&(rdp_client->lock));

    /* Handle messages from RDP server while client is running */
    while (client->state == GUAC_CLIENT_RUNNING
            && !guac_rdp_disp_reconnect_needed(rdp_client->disp)) {

        /* Update remote display size */
        guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst);

        /* Wait for data and construct a reasonable frame */
        int wait_result = rdp_guac_client_wait_for_messages(client,
                GUAC_RDP_FRAME_START_TIMEOUT);
        if (wait_result > 0) {

            int processing_lag = guac_client_get_processing_lag(client);

            /* Read server messages until frame is built */
            do {

                guac_timestamp frame_end;
                int frame_remaining;

                /* Handle any queued FreeRDP events (this may result in RDP
                 * messages being sent) */
                pthread_mutex_lock(&(rdp_client->message_lock));
                int event_result = freerdp_check_event_handles(rdp_inst->context);
                pthread_mutex_unlock(&(rdp_client->message_lock));

                /* Abort if FreeRDP event handling fails */
                if (!event_result) {
                    wait_result = -1;
                    break;
                }

                /* Continue handling inbound data if we are in the middle of an RDP frame */
                if (rdp_client->in_frame) {
                    wait_result = rdp_guac_client_wait_for_messages(client, GUAC_RDP_FRAME_START_TIMEOUT);
                    if (wait_result >= 0)
                        continue;
                }

                /* Calculate time remaining in frame */
                guac_timestamp frame_start = client->last_sent_timestamp;
                frame_end = guac_timestamp_current();
                frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION
                                - frame_end;

                /* Calculate time that client needs to catch up */
                int time_elapsed = frame_end - last_frame_end;
                int required_wait = processing_lag - time_elapsed;

                /* Increase the duration of this frame if client is lagging */
                if (required_wait > GUAC_RDP_FRAME_TIMEOUT)
                    wait_result = rdp_guac_client_wait_for_messages(client,
                            required_wait);

                /* Wait again if frame remaining */
                else if (frame_remaining > 0)
                    wait_result = rdp_guac_client_wait_for_messages(client,
                            GUAC_RDP_FRAME_TIMEOUT);
                else
                    break;

            } while (wait_result > 0);

        }

        /* Test whether the RDP server is closing the connection */
        int connection_closing = freerdp_shall_disconnect(rdp_inst);

        /* Close connection cleanly if server is disconnecting */
        if (connection_closing)
            guac_rdp_client_abort(client, rdp_inst);

        /* If a low-level connection error occurred, fail */
        else if (wait_result < 0)
            guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE,
                    "Connection closed.");

        /* Flush frame only if successful and an RDP frame is not known to be
         * in progress */
        else if (!rdp_client->frames_supported || rdp_client->frames_received) {
            guac_common_display_flush(rdp_client->display);
            guac_client_end_multiple_frames(client, rdp_client->frames_received);
            guac_socket_flush(client->socket);
            rdp_client->frames_received = 0;
        }

    }

    pthread_rwlock_wrlock(&(rdp_client->lock));

    /* Clean up print job, if active */
    if (rdp_client->active_job != NULL) {
        guac_rdp_print_job_kill(rdp_client->active_job);
        guac_rdp_print_job_free(rdp_client->active_job);
    }

    /* Disconnect client and channels */
    pthread_mutex_lock(&(rdp_client->message_lock));
    freerdp_disconnect(rdp_inst);
    pthread_mutex_unlock(&(rdp_client->message_lock));

    /* Clean up FreeRDP internal GDI implementation */
    gdi_free(rdp_inst);

    /* Clean up RDP client context */
    freerdp_context_free(rdp_inst);

    /* Clean up RDP client */
    freerdp_free(rdp_inst);
    rdp_client->rdp_inst = NULL;

    /* Free SVC list */
    guac_common_list_free(rdp_client->available_svc);
    rdp_client->available_svc = NULL;

    /* Free RDP keyboard state */
    guac_rdp_keyboard_free(rdp_client->keyboard);
    rdp_client->keyboard = NULL;

    /* Free display */
    guac_common_display_free(rdp_client->display);
    rdp_client->display = NULL;

    pthread_rwlock_unlock(&(rdp_client->lock));

    /* Client is now disconnected */
    guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected");

    return 0;

fail:
    pthread_rwlock_unlock(&(rdp_client->lock));
    return 1;

}

void* guac_rdp_client_thread(void* data) {

    guac_client* client = (guac_client*) data;
    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    guac_rdp_settings* settings = rdp_client->settings;

    /* If Wake-on-LAN is enabled, try to wake. */
    if (settings->wol_send_packet) {
        guac_client_log(client, GUAC_LOG_DEBUG, "Sending Wake-on-LAN packet, "
                "and pausing for %d seconds.", settings->wol_wait_time);
        
        /* Send the Wake-on-LAN request. */
        if (guac_wol_wake(settings->wol_mac_addr, settings->wol_broadcast_addr,
                settings->wol_udp_port))
            return NULL;
        
        /* If wait time is specified, sleep for that amount of time. */
        if (settings->wol_wait_time > 0)
            guac_timestamp_msleep(settings->wol_wait_time * 1000);
    }
    
    /* If audio enabled, choose an encoder */
    if (settings->audio_enabled) {

        rdp_client->audio = guac_audio_stream_alloc(client, NULL,
                GUAC_RDP_AUDIO_RATE,
                GUAC_RDP_AUDIO_CHANNELS,
                GUAC_RDP_AUDIO_BPS);

        /* Warn if no audio encoding is available */
        if (rdp_client->audio == NULL)
            guac_client_log(client, GUAC_LOG_INFO,
                    "No available audio encoding. Sound disabled.");

    } /* end if audio enabled */

    /* Load filesystem if drive enabled */
    if (settings->drive_enabled) {

        /* Allocate actual emulated filesystem */
        rdp_client->filesystem =
            guac_rdp_fs_alloc(client, settings->drive_path,
                    settings->create_drive_path, settings->disable_download,
                    settings->disable_upload);

        /* Expose filesystem to owner */
        guac_client_for_owner(client, guac_rdp_fs_expose,
                rdp_client->filesystem);

    }

#ifdef ENABLE_COMMON_SSH
    /* Connect via SSH if SFTP is enabled */
    if (settings->enable_sftp) {

        /* Abort if username is missing */
        if (settings->sftp_username == NULL) {
            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
                    "A username or SFTP-specific username is required if "
                    "SFTP is enabled.");
            return NULL;
        }

        guac_client_log(client, GUAC_LOG_DEBUG,
                "Connecting via SSH for SFTP filesystem access.");

        rdp_client->sftp_user =
            guac_common_ssh_create_user(settings->sftp_username);

        /* Import private key, if given */
        if (settings->sftp_private_key != NULL) {

            guac_client_log(client, GUAC_LOG_DEBUG,
                    "Authenticating with private key.");

            /* Abort if private key cannot be read */
            if (guac_common_ssh_user_import_key(rdp_client->sftp_user,
                        settings->sftp_private_key,
                        settings->sftp_passphrase)) {
                guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
                        "Private key unreadable.");
                return NULL;
            }

        }

        /* Otherwise, use specified password */
        else {

            guac_client_log(client, GUAC_LOG_DEBUG,
                    "Authenticating with password.");

            guac_common_ssh_user_set_password(rdp_client->sftp_user,
                    settings->sftp_password);

        }

        /* Attempt SSH connection */
        rdp_client->sftp_session =
            guac_common_ssh_create_session(client, settings->sftp_hostname,
                    settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval,
                    settings->sftp_host_key, NULL);

        /* Fail if SSH connection does not succeed */
        if (rdp_client->sftp_session == NULL) {
            /* Already aborted within guac_common_ssh_create_session() */
            return NULL;
        }

        /* Load and expose filesystem */
        rdp_client->sftp_filesystem =
            guac_common_ssh_create_sftp_filesystem(rdp_client->sftp_session,
                    settings->sftp_root_directory, NULL,
                    settings->sftp_disable_download,
                    settings->sftp_disable_upload);

        /* Expose filesystem to connection owner */
        guac_client_for_owner(client,
                guac_common_ssh_expose_sftp_filesystem,
                rdp_client->sftp_filesystem);

        /* Abort if SFTP connection fails */
        if (rdp_client->sftp_filesystem == NULL) {
            guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE,
                    "SFTP connection failed.");
            return NULL;
        }

        /* Configure destination for basic uploads, if specified */
        if (settings->sftp_directory != NULL)
            guac_common_ssh_sftp_set_upload_path(
                    rdp_client->sftp_filesystem,
                    settings->sftp_directory);

        guac_client_log(client, GUAC_LOG_DEBUG,
                "SFTP connection succeeded.");

    }
#endif

    /* Set up screen recording, if requested */
    if (settings->recording_path != NULL) {
        rdp_client->recording = guac_recording_create(client,
                settings->recording_path,
                settings->recording_name,
                settings->create_recording_path,
                !settings->recording_exclude_output,
                !settings->recording_exclude_mouse,
                !settings->recording_exclude_touch,
                settings->recording_include_keys);
    }

    /* Continue handling connections until error or client disconnect */
    while (client->state == GUAC_CLIENT_RUNNING) {
        if (guac_rdp_handle_connection(client))
            break;
    }

    return NULL;

}

    freerdp_free(rdp_inst);
    rdp_client->rdp_inst = NULL;

    /* Clean up audio stream, if allocated */
    if (rdp_client->audio != NULL)
        guac_audio_stream_free(rdp_client->audio);

    /* Free SVC list */
    guac_common_list_free(rdp_client->available_svc);

    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
    guac_rdp_settings* settings = rdp_client->settings;

    /* Load filesystem if drive enabled */
    if (settings->drive_enabled) {


<MSG> GUAC-1517: Only allocate one audio stream for RDP connection.

<DFF> @@ -247,21 +247,6 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
         guac_client_log(client, GUAC_LOG_WARNING,
                 "Failed to load cliprdr plugin. Clipboard will not work.");
 
-    /* If audio enabled, choose an encoder */
-    if (settings->audio_enabled) {
-
-        rdp_client->audio = guac_audio_stream_alloc(client, NULL,
-                GUAC_RDP_AUDIO_RATE,
-                GUAC_RDP_AUDIO_CHANNELS,
-                GUAC_RDP_AUDIO_BPS);
-
-        /* Warn if no audio encoding is available */
-        if (rdp_client->audio == NULL)
-            guac_client_log(client, GUAC_LOG_INFO,
-                    "No available audio encoding. Sound disabled.");
-
-    } /* end if audio enabled */
-
     /* If RDPSND/RDPDR required, load them */
     if (settings->printing_enabled
         || settings->drive_enabled
@@ -917,10 +902,6 @@ static int guac_rdp_handle_connection(guac_client* client) {
     freerdp_free(rdp_inst);
     rdp_client->rdp_inst = NULL;
 
-    /* Clean up audio stream, if allocated */
-    if (rdp_client->audio != NULL)
-        guac_audio_stream_free(rdp_client->audio);
-
     /* Free SVC list */
     guac_common_list_free(rdp_client->available_svc);
 
@@ -938,6 +919,21 @@ void* guac_rdp_client_thread(void* data) {
     guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
     guac_rdp_settings* settings = rdp_client->settings;
 
+    /* If audio enabled, choose an encoder */
+    if (settings->audio_enabled) {
+
+        rdp_client->audio = guac_audio_stream_alloc(client, NULL,
+                GUAC_RDP_AUDIO_RATE,
+                GUAC_RDP_AUDIO_CHANNELS,
+                GUAC_RDP_AUDIO_BPS);
+
+        /* Warn if no audio encoding is available */
+        if (rdp_client->audio == NULL)
+            guac_client_log(client, GUAC_LOG_INFO,
+                    "No available audio encoding. Sound disabled.");
+
+    } /* end if audio enabled */
+
     /* Load filesystem if drive enabled */
     if (settings->drive_enabled) {
 

{'commit': 'diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c\nindex 9fb60427..51321257 100644\n--- a/src/protocols/rdp/client.c\n+++ b/src/protocols/rdp/client.c\n@@ -34,6 +34,7 @@\n #include <freerdp/cache/cache.h>\n #include <freerdp/channels/channels.h>\n #include <freerdp/freerdp.h>\n+#include <guacamole/audio.h>\n #include <guacamole/client.h>\n #include <guacamole/socket.h>\n \n@@ -124,6 +125,10 @@ int guac_rdp_client_free_handler(guac_client* client) {\n     guac_common_ssh_uninit();\n #endif\n \n+    /* Clean up audio stream, if allocated */\n+    if (rdp_client->audio != NULL)\n+        guac_audio_stream_free(rdp_client->audio);\n+\n     /* Free client data */\n     guac_common_clipboard_free(rdp_client->clipboard);\n     free(rdp_client);\ndiff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c\nindex 0bc368f9..753312b3 100644\n--- a/src/protocols/rdp/rdp.c\n+++ b/src/protocols/rdp/rdp.c\n@@ -247,21 +247,6 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {\n         guac_client_log(client, GUAC_LOG_WARNING,\n                 "Failed to load cliprdr plugin. Clipboard will not work.");\n \n-    /* If audio enabled, choose an encoder */\n-    if (settings->audio_enabled) {\n-\n-        rdp_client->audio = guac_audio_stream_alloc(client, NULL,\n-                GUAC_RDP_AUDIO_RATE,\n-                GUAC_RDP_AUDIO_CHANNELS,\n-                GUAC_RDP_AUDIO_BPS);\n-\n-        /* Warn if no audio encoding is available */\n-        if (rdp_client->audio == NULL)\n-            guac_client_log(client, GUAC_LOG_INFO,\n-                    "No available audio encoding. Sound disabled.");\n-\n-    } /* end if audio enabled */\n-\n     /* If RDPSND/RDPDR required, load them */\n     if (settings->printing_enabled\n         || settings->drive_enabled\n@@ -917,10 +902,6 @@ static int guac_rdp_handle_connection(guac_client* client) {\n     freerdp_free(rdp_inst);\n     rdp_client->rdp_inst = NULL;\n \n-    /* Clean up audio stream, if allocated */\n-    if (rdp_client->audio != NULL)\n-        guac_audio_stream_free(rdp_client->audio);\n-\n     /* Free SVC list */\n     guac_common_list_free(rdp_client->available_svc);\n \n@@ -938,6 +919,21 @@ void* guac_rdp_client_thread(void* data) {\n     guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;\n     guac_rdp_settings* settings = rdp_client->settings;\n \n+    /* If audio enabled, choose an encoder */\n+    if (settings->audio_enabled) {\n+\n+        rdp_client->audio = guac_audio_stream_alloc(client, NULL,\n+                GUAC_RDP_AUDIO_RATE,\n+                GUAC_RDP_AUDIO_CHANNELS,\n+                GUAC_RDP_AUDIO_BPS);\n+\n+        /* Warn if no audio encoding is available */\n+        if (rdp_client->audio == NULL)\n+            guac_client_log(client, GUAC_LOG_INFO,\n+                    "No available audio encoding. Sound disabled.");\n+\n+    } /* end if audio enabled */\n+\n     /* Load filesystem if drive enabled */\n     if (settings->drive_enabled) {\n \n', 'commit_subject': 'GUAC-1517: Only allocate one audio stream for RDP connection.', 'repo_name': 'apache/guacamole-server', 'committer': {'name': 'Michael Jumper', 'email': '75d22dcc7655e574171b76ca85cb84ed292647ff@apache.org', 'time_sec': '1459459531', 'tz_offset': '-420', 'date': {'seconds': '1459459531'}}, 'commit_tree': '6ff7ab53ed6a4e99a267c6efc430489c35a0c7c2', 'commit_message': 'GUAC-1517: Only allocate one audio stream for RDP connection.\n', 'language_name': 'C', 'license': 'apache-2.0', 'hunks': '@@ -247,21 +247,6 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {\n         guac_client_log(client, GUAC_LOG_WARNING,\n                 "Failed to load cliprdr plugin. Clipboard will not work.");\n \n-    /* If audio enabled, choose an encoder */\n-    if (settings->audio_enabled) {\n-\n-        rdp_client->audio = guac_audio_stream_alloc(client, NULL,\n-                GUAC_RDP_AUDIO_RATE,\n-                GUAC_RDP_AUDIO_CHANNELS,\n-                GUAC_RDP_AUDIO_BPS);\n-\n-        /* Warn if no audio encoding is available */\n-        if (rdp_client->audio == NULL)\n-            guac_client_log(client, GUAC_LOG_INFO,\n-                    "No available audio encoding. Sound disabled.");\n-\n-    } /* end if audio enabled */\n-\n     /* If RDPSND/RDPDR required, load them */\n     if (settings->printing_enabled\n         || settings->drive_enabled\n@@ -917,10 +902,6 @@ static int guac_rdp_handle_connection(guac_client* client) {\n     freerdp_free(rdp_inst);\n     rdp_client->rdp_inst = NULL;\n \n-    /* Clean up audio stream, if allocated */\n-    if (rdp_client->audio != NULL)\n-        guac_audio_stream_free(rdp_client->audio);\n-\n     /* Free SVC list */\n     guac_common_list_free(rdp_client->available_svc);\n \n@@ -938,6 +919,21 @@ void* guac_rdp_client_thread(void* data) {\n     guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;\n     guac_rdp_settings* settings = rdp_client->settings;\n \n+    /* If audio enabled, choose an encoder */\n+    if (settings->audio_enabled) {\n+\n+        rdp_client->audio = guac_audio_stream_alloc(client, NULL,\n+                GUAC_RDP_AUDIO_RATE,\n+                GUAC_RDP_AUDIO_CHANNELS,\n+                GUAC_RDP_AUDIO_BPS);\n+\n+        /* Warn if no audio encoding is available */\n+        if (rdp_client->audio == NULL)\n+            guac_client_log(client, GUAC_LOG_INFO,\n+                    "No available audio encoding. Sound disabled.");\n+\n+    } /* end if audio enabled */\n+\n     /* Load filesystem if drive enabled */\n     if (settings->drive_enabled) {\n \n', 'addition_count': 15, 'deletion_count': 19, 'src_file': 'a/src/protocols/rdp/rdp.c', 'tgt_file': 'b/src/protocols/rdp/rdp.c', 'file_extension': '.c', 'before_file': '/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * "License"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\n#include "argv.h"\n#include "beep.h"\n#include "bitmap.h"\n#include "channels/audio-input/audio-buffer.h"\n#include "channels/audio-input/audio-input.h"\n#include "channels/cliprdr.h"\n#include "channels/disp.h"\n#include "channels/pipe-svc.h"\n#include "channels/rail.h"\n#include "channels/rdpdr/rdpdr.h"\n#include "channels/rdpei.h"\n#include "channels/rdpgfx.h"\n#include "channels/rdpsnd/rdpsnd.h"\n#include "client.h"\n#include "color.h"\n#include "common/cursor.h"\n#include "common/display.h"\n#include "config.h"\n#include "error.h"\n#include "fs.h"\n#include "gdi.h"\n#include "glyph.h"\n#include "keyboard.h"\n#include "plugins/channels.h"\n#include "pointer.h"\n#include "print-job.h"\n#include "rdp.h"\n#include "settings.h"\n\n#ifdef ENABLE_COMMON_SSH\n#include "common-ssh/sftp.h"\n#include "common-ssh/ssh.h"\n#include "common-ssh/user.h"\n#endif\n\n#include <freerdp/addin.h>\n#include <freerdp/cache/bitmap.h>\n#include <freerdp/cache/brush.h>\n#include <freerdp/cache/glyph.h>\n#include <freerdp/cache/offscreen.h>\n#include <freerdp/cache/palette.h>\n#include <freerdp/cache/pointer.h>\n#include <freerdp/channels/channels.h>\n#include <freerdp/client/channels.h>\n#include <freerdp/freerdp.h>\n#include <freerdp/gdi/gdi.h>\n#include <freerdp/graphics.h>\n#include <freerdp/primary.h>\n#include <freerdp/settings.h>\n#include <freerdp/update.h>\n#include <guacamole/argv.h>\n#include <guacamole/audio.h>\n#include <guacamole/client.h>\n#include <guacamole/protocol.h>\n#include <guacamole/recording.h>\n#include <guacamole/socket.h>\n#include <guacamole/string.h>\n#include <guacamole/timestamp.h>\n#include <guacamole/wol.h>\n#include <winpr/error.h>\n#include <winpr/synch.h>\n#include <winpr/wtypes.h>\n\n#include <stdlib.h>\n#include <time.h>\n\nBOOL rdp_freerdp_pre_connect(freerdp* instance) {\n\n    rdpContext* context = instance->context;\n    rdpGraphics* graphics = context->graphics;\n\n    guac_client* client = ((rdp_freerdp_context*) context)->client;\n    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;\n    guac_rdp_settings* settings = rdp_client->settings;\n\n    /* Push desired settings to FreeRDP */\n    guac_rdp_push_settings(client, settings, instance);\n\n    /* Init FreeRDP add-in provider */\n    freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0);\n\n    /* Load "disp" plugin for display update */\n    if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE)\n        guac_rdp_disp_load_plugin(context);\n\n    /* Load "rdpei" plugin for multi-touch support */\n    if (settings->enable_touch)\n        guac_rdp_rdpei_load_plugin(context);\n\n    /* Load "AUDIO_INPUT" plugin for audio input*/\n    if (settings->enable_audio_input) {\n        rdp_client->audio_input = guac_rdp_audio_buffer_alloc(client);\n        guac_rdp_audio_load_plugin(instance->context);\n    }\n\n    /* Load "cliprdr" service if not disabled */\n    if (!(settings->disable_copy && settings->disable_paste))\n        guac_rdp_clipboard_load_plugin(rdp_client->clipboard, context);\n\n    /* If RDPSND/RDPDR required, load them */\n    if (settings->printing_enabled\n        || settings->drive_enabled\n        || settings->audio_enabled) {\n        guac_rdpdr_load_plugin(context);\n        guac_rdpsnd_load_plugin(context);\n    }\n\n    /* Load RAIL plugin if RemoteApp in use */\n    if (settings->remote_app != NULL)\n        guac_rdp_rail_load_plugin(context);\n\n    /* Load SVC plugin instances for all static channels */\n    if (settings->svc_names != NULL) {\n\n        char** current = settings->svc_names;\n        do {\n            guac_rdp_pipe_svc_load_plugin(context, *current);\n        } while (*(++current) != NULL);\n\n    }\n\n    /* Init FreeRDP internal GDI implementation */\n    if (!gdi_init(instance, guac_rdp_get_native_pixel_format(FALSE)))\n        return FALSE;\n\n    /* Set up bitmap handling */\n    rdpBitmap bitmap = *graphics->Bitmap_Prototype;\n    bitmap.size = sizeof(guac_rdp_bitmap);\n    bitmap.New = guac_rdp_bitmap_new;\n    bitmap.Free = guac_rdp_bitmap_free;\n    bitmap.Paint = guac_rdp_bitmap_paint;\n    bitmap.SetSurface = guac_rdp_bitmap_setsurface;\n    graphics_register_bitmap(graphics, &bitmap);\n\n    /* Set up glyph handling */\n    rdpGlyph glyph = *graphics->Glyph_Prototype;\n    glyph.size = sizeof(guac_rdp_glyph);\n    glyph.New = guac_rdp_glyph_new;\n    glyph.Free = guac_rdp_glyph_free;\n    glyph.Draw = guac_rdp_glyph_draw;\n    glyph.BeginDraw = guac_rdp_glyph_begindraw;\n    glyph.EndDraw = guac_rdp_glyph_enddraw;\n    graphics_register_glyph(graphics, &glyph);\n\n    /* Set up pointer handling */\n    rdpPointer pointer = *graphics->Pointer_Prototype;\n    pointer.size = sizeof(guac_rdp_pointer);\n    pointer.New = guac_rdp_pointer_new;\n    pointer.Free = guac_rdp_pointer_free;\n    pointer.Set = guac_rdp_pointer_set;\n    pointer.SetNull = guac_rdp_pointer_set_null;\n    pointer.SetDefault = guac_rdp_pointer_set_default;\n    graphics_register_pointer(graphics, &pointer);\n\n    /* Beep on receipt of Play Sound PDU */\n    instance->update->PlaySound = guac_rdp_beep_play_sound;\n\n    /* Automatically synchronize keyboard locks when changed server-side */\n    instance->update->SetKeyboardIndicators = guac_rdp_keyboard_set_indicators;\n\n    /* Set up GDI */\n    instance->update->DesktopResize = guac_rdp_gdi_desktop_resize;\n    instance->update->BeginPaint = guac_rdp_gdi_begin_paint;\n    instance->update->EndPaint = guac_rdp_gdi_end_paint;\n    instance->update->SetBounds = guac_rdp_gdi_set_bounds;\n\n    instance->update->SurfaceFrameMarker = guac_rdp_gdi_surface_frame_marker;\n    instance->update->altsec->FrameMarker = guac_rdp_gdi_frame_marker;\n\n    rdpPrimaryUpdate* primary = instance->update->primary;\n    primary->DstBlt = guac_rdp_gdi_dstblt;\n    primary->PatBlt = guac_rdp_gdi_patblt;\n    primary->ScrBlt = guac_rdp_gdi_scrblt;\n    primary->MemBlt = guac_rdp_gdi_memblt;\n    primary->OpaqueRect = guac_rdp_gdi_opaquerect;\n\n    pointer_cache_register_callbacks(instance->update);\n    glyph_cache_register_callbacks(instance->update);\n    brush_cache_register_callbacks(instance->update);\n    bitmap_cache_register_callbacks(instance->update);\n    offscreen_cache_register_callbacks(instance->update);\n    palette_cache_register_callbacks(instance->update);\n\n    /* Load "rdpgfx" plugin for Graphics Pipeline Extension */\n    if (settings->enable_gfx)\n        guac_rdp_rdpgfx_load_plugin(context);\n\n    /* Load plugin providing Dynamic Virtual Channel support, if required */\n    if (instance->settings->SupportDynamicChannels &&\n            guac_freerdp_channels_load_plugin(context, "drdynvc",\n                instance->settings)) {\n        guac_client_log(client, GUAC_LOG_WARNING,\n                "Failed to load drdynvc plugin. Display update and audio "\n                "input support will be disabled.");\n    }\n\n    return TRUE;\n\n}\n\n/**\n * Callback invoked by FreeRDP when authentication is required but the required\n * parameters have not been provided. In the case of Guacamole clients that\n * support the "required" instruction, this function will send any of the three\n * unpopulated RDP authentication parameters back to the client so that the\n * connection owner can provide the required information.  If the values have\n * been provided in the original connection parameters the user will not be\n * prompted for updated parameters. If the version of Guacamole Client in use\n * by the connection owner does not support the "required" instruction then the\n * connection will fail. This function always returns true.\n *\n * @param instance\n *     The FreeRDP instance associated with the RDP session requesting\n *     credentials.\n *\n * @param username\n *     Pointer to a string which will receive the user\'s username.\n *\n * @param password\n *     Pointer to a string which will receive the user\'s password.\n *\n * @param domain\n *     Pointer to a string which will receive the domain associated with the\n *     user\'s account.\n *\n * @return\n *     Always TRUE.\n        guac_client_log(client, GUAC_LOG_WARNING,\n                "Failed to load cliprdr plugin. Clipboard will not work.");\n\n    /* If audio enabled, choose an encoder */\n    if (settings->audio_enabled) {\n\n        rdp_client->audio = guac_audio_stream_alloc(client, NULL,\n                GUAC_RDP_AUDIO_RATE,\n                GUAC_RDP_AUDIO_CHANNELS,\n                GUAC_RDP_AUDIO_BPS);\n\n        /* Warn if no audio encoding is available */\n        if (rdp_client->audio == NULL)\n            guac_client_log(client, GUAC_LOG_INFO,\n                    "No available audio encoding. Sound disabled.");\n\n    } /* end if audio enabled */\n\n    /* If RDPSND/RDPDR required, load them */\n    if (settings->printing_enabled\n        || settings->drive_enabled\n    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;\n    guac_rdp_settings* settings = rdp_client->settings;\n    char* params[4] = {NULL};\n    int i = 0;\n    \n    /* If the client does not support the "required" instruction, warn and\n     * quit.\n     */\n    if (!guac_client_owner_supports_required(client)) {\n        guac_client_log(client, GUAC_LOG_WARNING, "Client does not support the "\n                "\\"required\\" instruction. No authentication parameters will "\n                "be requested.");\n        return TRUE;\n    }\n\n    /* If the username is undefined, add it to the requested parameters. */\n    if (settings->username == NULL) {\n        guac_argv_register(GUAC_RDP_ARGV_USERNAME, guac_rdp_argv_callback, NULL, 0);\n        params[i] = GUAC_RDP_ARGV_USERNAME;\n        i++;\n        \n        /* If username is undefined and domain is also undefined, request domain. */\n        if (settings->domain == NULL) {\n            guac_argv_register(GUAC_RDP_ARGV_DOMAIN, guac_rdp_argv_callback, NULL, 0);\n            params[i] = GUAC_RDP_ARGV_DOMAIN;\n            i++;\n        }\n        \n    }\n    \n    /* If the password is undefined, add it to the requested parameters. */\n    if (settings->password == NULL) {\n        guac_argv_register(GUAC_RDP_ARGV_PASSWORD, guac_rdp_argv_callback, NULL, 0);\n        params[i] = GUAC_RDP_ARGV_PASSWORD;\n        i++;\n    }\n    \n    /* NULL-terminate the array. */\n    params[i] = NULL;\n    \n    if (i > 0) {\n        \n        /* Send required parameters to the owner and wait for the response. */\n        guac_client_owner_send_required(client, (const char**) params);\n        guac_argv_await((const char**) params);\n        \n        /* Free old values and get new values from settings. */\n        free(*username);\n        free(*password);\n        free(*domain);\n        *username = guac_strdup(settings->username);\n        *password = guac_strdup(settings->password);\n        *domain = guac_strdup(settings->domain);\n        \n    }\n    \n    /* Always return TRUE allowing connection to retry. */\n    return TRUE;\n\n}\n\n#ifdef HAVE_FREERDP_VERIFYCERTIFICATEEX\n/**\n * Callback invoked by FreeRDP when the SSL/TLS certificate of the RDP server\n * needs to be verified. If this ever happens, this function implementation\n * will always fail unless the connection has been configured to ignore\n * certificate validity.\n *\n * @param instance\n *     The FreeRDP instance associated with the RDP session whose SSL/TLS\n *     certificate needs to be verified.\n *\n * @param hostname\n *     The hostname or address of the RDP server being connected to.\n *\n * @param port\n *     The TCP port number of the RDP server being connected to.\n *\n * @param common_name\n *     The name of the server protected by the certificate. This should match\n *     the hostname/address of the RDP server.\n *\n * @param subject\n *     The subject to whom the certificate was issued.\n *\n * @param issuer\n *     The authority that issued the certificate,\n *\n * @param fingerprint\n *     The cryptographic fingerprint of the certificate.\n *\n * @param flags\n *     Bitwise OR of any applicable certificate verification flags. Valid flags are\n *     VERIFY_CERT_FLAG_NONE, VERIFY_CERT_FLAG_LEGACY, VERIFY_CERT_FLAG_REDIRECT,\n *     VERIFY_CERT_FLAG_GATEWAY, VERIFY_CERT_FLAG_CHANGED, and\n *     VERIFY_CERT_FLAG_MISMATCH.\n *\n * @return\n *     1 to accept the certificate and store within FreeRDP\'s configuration\n *     directory, 2 to accept the certificate only within this session, or 0 to\n *     reject the certificate.\n */\nstatic DWORD rdp_freerdp_verify_certificate(freerdp* instance,\n        const char* hostname, UINT16 port, const char* common_name,\n        const char* subject, const char* issuer, const char* fingerprint,\n        DWORD flags) {\n#else\n/**\n * Callback invoked by FreeRDP when the SSL/TLS certificate of the RDP server\n * needs to be verified. If this ever happens, this function implementation\n * will always fail unless the connection has been configured to ignore\n * certificate validity.\n *\n * @param instance\n *     The FreeRDP instance associated with the RDP session whose SSL/TLS\n *     certificate needs to be verified.\n *\n * @param subject\n *     The subject to whom the certificate was issued.\n *\n * @param issuer\n *     The authority that issued the certificate,\n *\n * @param fingerprint\n *     The cryptographic fingerprint of the certificate.\n *\n * @param host_mismatch\n *     TRUE if the certificate does not match the destination hostname, FALSE\n *     otherwise.\n *\n * @return\n *     1 to accept the certificate and store within FreeRDP\'s configuration\n *     directory, 2 to accept the certificate only within this session, or 0 to\n *     reject the certificate.\n */\nstatic DWORD rdp_freerdp_verify_certificate(freerdp* instance,\n        const char* common_name, const char* subject, const char* issuer,\n        const char* fingerprint, BOOL host_mismatch) {\n#endif\n\n    rdpContext* context = instance->context;\n    guac_client* client = ((rdp_freerdp_context*) context)->client;\n    guac_rdp_client* rdp_client =\n        (guac_rdp_client*) client->data;\n\n    /* Bypass validation if ignore_certificate given */\n    if (rdp_client->settings->ignore_certificate) {\n        guac_client_log(client, GUAC_LOG_INFO, "Certificate validation bypassed");\n        return 2; /* Accept only for this session */\n    }\n\n    guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed");\n    return 0; /* Reject certificate */\n\n}\n\n/**\n * Waits for messages from the RDP server for the given number of milliseconds.\n *\n * @param client\n *     The client associated with the current RDP session.\n *\n * @param timeout_msecs\n *     The maximum amount of time to wait, in milliseconds.\n *\n * @return\n *     A positive value if messages are ready, zero if the specified timeout\n *     period elapsed, or a negative value if an error occurs.\n */\nstatic int rdp_guac_client_wait_for_messages(guac_client* client,\n        int timeout_msecs) {\n\n    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;\n    freerdp* rdp_inst = rdp_client->rdp_inst;\n\n    HANDLE handles[GUAC_RDP_MAX_FILE_DESCRIPTORS];\n    int num_handles = freerdp_get_event_handles(rdp_inst->context, handles,\n            GUAC_RDP_MAX_FILE_DESCRIPTORS);\n\n    /* Wait for data and construct a reasonable frame */\n    int result = WaitForMultipleObjects(num_handles, handles, FALSE,\n            timeout_msecs);\n\n    /* Translate WaitForMultipleObjects() return values */\n    switch (result) {\n\n        /* Timeout elapsed before wait could complete */\n        case WAIT_TIMEOUT:\n            return 0;\n\n        /* Attempt to wait failed due to an error */\n        case WAIT_FAILED:\n            return -1;\n\n    }\n\n    /* Wait was successful */\n    return 1;\n\n}\n\n/**\n * Connects to an RDP server as described by the guac_rdp_settings structure\n * associated with the given client, allocating and freeing all objects\n * directly related to the RDP connection. It is expected that all objects\n * which are independent of FreeRDP\'s state (the clipboard, display update\n * management, etc.) will already be allocated and associated with the\n * guac_rdp_client associated with the given guac_client. This function blocks\n * for the duration of the RDP session, returning only after the session has\n * completely disconnected.\n *\n * @param client\n *     The guac_client associated with the RDP settings describing the\n *     connection that should be established.\n *\n * @return\n *     Zero if the connection successfully terminated and a reconnect is\n *     desired, non-zero if an error occurs or the connection was disconnected\n *     and a reconnect is NOT desired.\n */\nstatic int guac_rdp_handle_connection(guac_client* client) {\n\n    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;\n    guac_rdp_settings* settings = rdp_client->settings;\n\n    /* Init random number generator */\n    srandom(time(NULL));\n\n    pthread_rwlock_wrlock(&(rdp_client->lock));\n\n    /* Create display */\n    rdp_client->display = guac_common_display_alloc(client,\n            rdp_client->settings->width,\n            rdp_client->settings->height);\n\n    /* Use lossless compression only if requested (otherwise, use default\n     * heuristics) */\n    guac_common_display_set_lossless(rdp_client->display, settings->lossless);\n\n    rdp_client->current_surface = rdp_client->display->default_surface;\n\n    rdp_client->available_svc = guac_common_list_alloc();\n\n    /* Init client */\n    freerdp* rdp_inst = freerdp_new();\n    rdp_inst->PreConnect = rdp_freerdp_pre_connect;\n    rdp_inst->Authenticate = rdp_freerdp_authenticate;\n\n#ifdef HAVE_FREERDP_VERIFYCERTIFICATEEX\n    rdp_inst->VerifyCertificateEx = rdp_freerdp_verify_certificate;\n#else\n    rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate;\n#endif\n\n    /* Allocate FreeRDP context */\n    rdp_inst->ContextSize = sizeof(rdp_freerdp_context);\n\n    if (!freerdp_context_new(rdp_inst)) {\n        guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,\n                "FreeRDP initialization failed before connecting. Please "\n                "check for errors earlier in the logs and/or enable "\n                "debug-level logging for guacd.");\n        goto fail;\n    }\n\n    ((rdp_freerdp_context*) rdp_inst->context)->client = client;\n\n    /* Load keymap into client */\n    rdp_client->keyboard = guac_rdp_keyboard_alloc(client,\n            settings->server_layout);\n\n    /* Set default pointer */\n    guac_common_cursor_set_pointer(rdp_client->display->cursor);\n\n    /* Connect to RDP server */\n    if (!freerdp_connect(rdp_inst)) {\n        guac_rdp_client_abort(client, rdp_inst);\n        goto fail;\n    }\n\n    /* Connection complete */\n    rdp_client->rdp_inst = rdp_inst;\n\n    guac_timestamp last_frame_end = guac_timestamp_current();\n\n    /* Signal that reconnect has been completed */\n    guac_rdp_disp_reconnect_complete(rdp_client->disp);\n\n    pthread_rwlock_unlock(&(rdp_client->lock));\n\n    /* Handle messages from RDP server while client is running */\n    while (client->state == GUAC_CLIENT_RUNNING\n            && !guac_rdp_disp_reconnect_needed(rdp_client->disp)) {\n\n        /* Update remote display size */\n        guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst);\n\n        /* Wait for data and construct a reasonable frame */\n        int wait_result = rdp_guac_client_wait_for_messages(client,\n                GUAC_RDP_FRAME_START_TIMEOUT);\n        if (wait_result > 0) {\n\n            int processing_lag = guac_client_get_processing_lag(client);\n\n            /* Read server messages until frame is built */\n            do {\n\n                guac_timestamp frame_end;\n                int frame_remaining;\n\n                /* Handle any queued FreeRDP events (this may result in RDP\n                 * messages being sent) */\n                pthread_mutex_lock(&(rdp_client->message_lock));\n                int event_result = freerdp_check_event_handles(rdp_inst->context);\n                pthread_mutex_unlock(&(rdp_client->message_lock));\n\n                /* Abort if FreeRDP event handling fails */\n                if (!event_result) {\n                    wait_result = -1;\n                    break;\n                }\n\n                /* Continue handling inbound data if we are in the middle of an RDP frame */\n                if (rdp_client->in_frame) {\n                    wait_result = rdp_guac_client_wait_for_messages(client, GUAC_RDP_FRAME_START_TIMEOUT);\n                    if (wait_result >= 0)\n                        continue;\n                }\n\n                /* Calculate time remaining in frame */\n                guac_timestamp frame_start = client->last_sent_timestamp;\n                frame_end = guac_timestamp_current();\n                frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION\n                                - frame_end;\n\n                /* Calculate time that client needs to catch up */\n                int time_elapsed = frame_end - last_frame_end;\n                int required_wait = processing_lag - time_elapsed;\n\n                /* Increase the duration of this frame if client is lagging */\n                if (required_wait > GUAC_RDP_FRAME_TIMEOUT)\n                    wait_result = rdp_guac_client_wait_for_messages(client,\n                            required_wait);\n\n                /* Wait again if frame remaining */\n                else if (frame_remaining > 0)\n                    wait_result = rdp_guac_client_wait_for_messages(client,\n                            GUAC_RDP_FRAME_TIMEOUT);\n                else\n                    break;\n\n            } while (wait_result > 0);\n\n        }\n\n        /* Test whether the RDP server is closing the connection */\n        int connection_closing = freerdp_shall_disconnect(rdp_inst);\n\n        /* Close connection cleanly if server is disconnecting */\n        if (connection_closing)\n            guac_rdp_client_abort(client, rdp_inst);\n\n        /* If a low-level connection error occurred, fail */\n        else if (wait_result < 0)\n            guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE,\n                    "Connection closed.");\n\n        /* Flush frame only if successful and an RDP frame is not known to be\n         * in progress */\n        else if (!rdp_client->frames_supported || rdp_client->frames_received) {\n            guac_common_display_flush(rdp_client->display);\n            guac_client_end_multiple_frames(client, rdp_client->frames_received);\n            guac_socket_flush(client->socket);\n            rdp_client->frames_received = 0;\n        }\n\n    }\n\n    pthread_rwlock_wrlock(&(rdp_client->lock));\n\n    /* Clean up print job, if active */\n    if (rdp_client->active_job != NULL) {\n        guac_rdp_print_job_kill(rdp_client->active_job);\n        guac_rdp_print_job_free(rdp_client->active_job);\n    }\n\n    /* Disconnect client and channels */\n    pthread_mutex_lock(&(rdp_client->message_lock));\n    freerdp_disconnect(rdp_inst);\n    pthread_mutex_unlock(&(rdp_client->message_lock));\n\n    /* Clean up FreeRDP internal GDI implementation */\n    gdi_free(rdp_inst);\n\n    /* Clean up RDP client context */\n    freerdp_context_free(rdp_inst);\n\n    /* Clean up RDP client */\n    freerdp_free(rdp_inst);\n    rdp_client->rdp_inst = NULL;\n\n    /* Free SVC list */\n    guac_common_list_free(rdp_client->available_svc);\n    rdp_client->available_svc = NULL;\n\n    /* Free RDP keyboard state */\n    guac_rdp_keyboard_free(rdp_client->keyboard);\n    rdp_client->keyboard = NULL;\n\n    /* Free display */\n    guac_common_display_free(rdp_client->display);\n    rdp_client->display = NULL;\n\n    pthread_rwlock_unlock(&(rdp_client->lock));\n\n    /* Client is now disconnected */\n    guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected");\n\n    return 0;\n\nfail:\n    pthread_rwlock_unlock(&(rdp_client->lock));\n    return 1;\n\n}\n\nvoid* guac_rdp_client_thread(void* data) {\n\n    guac_client* client = (guac_client*) data;\n    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;\n    guac_rdp_settings* settings = rdp_client->settings;\n\n    /* If Wake-on-LAN is enabled, try to wake. */\n    if (settings->wol_send_packet) {\n        guac_client_log(client, GUAC_LOG_DEBUG, "Sending Wake-on-LAN packet, "\n                "and pausing for %d seconds.", settings->wol_wait_time);\n        \n        /* Send the Wake-on-LAN request. */\n        if (guac_wol_wake(settings->wol_mac_addr, settings->wol_broadcast_addr,\n                settings->wol_udp_port))\n            return NULL;\n        \n        /* If wait time is specified, sleep for that amount of time. */\n        if (settings->wol_wait_time > 0)\n            guac_timestamp_msleep(settings->wol_wait_time * 1000);\n    }\n    \n    /* If audio enabled, choose an encoder */\n    if (settings->audio_enabled) {\n\n        rdp_client->audio = guac_audio_stream_alloc(client, NULL,\n                GUAC_RDP_AUDIO_RATE,\n                GUAC_RDP_AUDIO_CHANNELS,\n                GUAC_RDP_AUDIO_BPS);\n\n        /* Warn if no audio encoding is available */\n        if (rdp_client->audio == NULL)\n            guac_client_log(client, GUAC_LOG_INFO,\n                    "No available audio encoding. Sound disabled.");\n\n    } /* end if audio enabled */\n\n    /* Load filesystem if drive enabled */\n    if (settings->drive_enabled) {\n\n        /* Allocate actual emulated filesystem */\n        rdp_client->filesystem =\n            guac_rdp_fs_alloc(client, settings->drive_path,\n                    settings->create_drive_path, settings->disable_download,\n                    settings->disable_upload);\n\n        /* Expose filesystem to owner */\n        guac_client_for_owner(client, guac_rdp_fs_expose,\n                rdp_client->filesystem);\n\n    }\n\n#ifdef ENABLE_COMMON_SSH\n    /* Connect via SSH if SFTP is enabled */\n    if (settings->enable_sftp) {\n\n        /* Abort if username is missing */\n        if (settings->sftp_username == NULL) {\n            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,\n                    "A username or SFTP-specific username is required if "\n                    "SFTP is enabled.");\n            return NULL;\n        }\n\n        guac_client_log(client, GUAC_LOG_DEBUG,\n                "Connecting via SSH for SFTP filesystem access.");\n\n        rdp_client->sftp_user =\n            guac_common_ssh_create_user(settings->sftp_username);\n\n        /* Import private key, if given */\n        if (settings->sftp_private_key != NULL) {\n\n            guac_client_log(client, GUAC_LOG_DEBUG,\n                    "Authenticating with private key.");\n\n            /* Abort if private key cannot be read */\n            if (guac_common_ssh_user_import_key(rdp_client->sftp_user,\n                        settings->sftp_private_key,\n                        settings->sftp_passphrase)) {\n                guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,\n                        "Private key unreadable.");\n                return NULL;\n            }\n\n        }\n\n        /* Otherwise, use specified password */\n        else {\n\n            guac_client_log(client, GUAC_LOG_DEBUG,\n                    "Authenticating with password.");\n\n            guac_common_ssh_user_set_password(rdp_client->sftp_user,\n                    settings->sftp_password);\n\n        }\n\n        /* Attempt SSH connection */\n        rdp_client->sftp_session =\n            guac_common_ssh_create_session(client, settings->sftp_hostname,\n                    settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval,\n                    settings->sftp_host_key, NULL);\n\n        /* Fail if SSH connection does not succeed */\n        if (rdp_client->sftp_session == NULL) {\n            /* Already aborted within guac_common_ssh_create_session() */\n            return NULL;\n        }\n\n        /* Load and expose filesystem */\n        rdp_client->sftp_filesystem =\n            guac_common_ssh_create_sftp_filesystem(rdp_client->sftp_session,\n                    settings->sftp_root_directory, NULL,\n                    settings->sftp_disable_download,\n                    settings->sftp_disable_upload);\n\n        /* Expose filesystem to connection owner */\n        guac_client_for_owner(client,\n                guac_common_ssh_expose_sftp_filesystem,\n                rdp_client->sftp_filesystem);\n\n        /* Abort if SFTP connection fails */\n        if (rdp_client->sftp_filesystem == NULL) {\n            guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE,\n                    "SFTP connection failed.");\n            return NULL;\n        }\n\n        /* Configure destination for basic uploads, if specified */\n        if (settings->sftp_directory != NULL)\n            guac_common_ssh_sftp_set_upload_path(\n                    rdp_client->sftp_filesystem,\n                    settings->sftp_directory);\n\n        guac_client_log(client, GUAC_LOG_DEBUG,\n                "SFTP connection succeeded.");\n\n    }\n#endif\n\n    /* Set up screen recording, if requested */\n    if (settings->recording_path != NULL) {\n        rdp_client->recording = guac_recording_create(client,\n                settings->recording_path,\n                settings->recording_name,\n                settings->create_recording_path,\n                !settings->recording_exclude_output,\n                !settings->recording_exclude_mouse,\n                !settings->recording_exclude_touch,\n                settings->recording_include_keys);\n    }\n\n    /* Continue handling connections until error or client disconnect */\n    while (client->state == GUAC_CLIENT_RUNNING) {\n        if (guac_rdp_handle_connection(client))\n            break;\n    }\n\n    return NULL;\n\n}\n\n    freerdp_free(rdp_inst);\n    rdp_client->rdp_inst = NULL;\n\n    /* Clean up audio stream, if allocated */\n    if (rdp_client->audio != NULL)\n        guac_audio_stream_free(rdp_client->audio);\n\n    /* Free SVC list */\n    guac_common_list_free(rdp_client->available_svc);\n\n    guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;\n    guac_rdp_settings* settings = rdp_client->settings;\n\n    /* Load filesystem if drive enabled */\n    if (settings->drive_enabled) {\n\n', 'message': 'GUAC-1517: Only allocate one audio stream for RDP connection.\n', 'author_name': 'Michael Jumper', 'lang': 'c'}