2016-03-01 05:50:00 +00:00
|
|
|
/*
|
2016-03-25 19:59:40 +00:00
|
|
|
* 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
|
2016-03-01 05:50:00 +00:00
|
|
|
*
|
2016-03-25 19:59:40 +00:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2016-03-01 05:50:00 +00:00
|
|
|
*
|
2016-03-25 19:59:40 +00:00
|
|
|
* 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.
|
2016-03-01 05:50:00 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
2019-12-23 21:29:13 +00:00
|
|
|
#include "bitmap.h"
|
2019-12-23 20:48:22 +00:00
|
|
|
#include "channels/audio-input.h"
|
|
|
|
#include "channels/cliprdr.h"
|
2019-12-23 21:29:13 +00:00
|
|
|
#include "channels/disp.h"
|
|
|
|
#include "channels/pipe-svc.h"
|
|
|
|
#include "channels/rail.h"
|
|
|
|
#include "channels/rdpdr/rdpdr.h"
|
|
|
|
#include "channels/rdpsnd/rdpsnd.h"
|
|
|
|
#include "client.h"
|
2020-01-04 09:11:05 +00:00
|
|
|
#include "color.h"
|
2016-09-12 00:28:50 +00:00
|
|
|
#include "common/cursor.h"
|
|
|
|
#include "common/display.h"
|
|
|
|
#include "common/recording.h"
|
2017-02-11 19:53:34 +00:00
|
|
|
#include "error.h"
|
2019-12-23 20:48:22 +00:00
|
|
|
#include "fs.h"
|
|
|
|
#include "gdi.h"
|
|
|
|
#include "glyph.h"
|
2019-12-23 21:29:13 +00:00
|
|
|
#include "keyboard/keyboard.h"
|
|
|
|
#include "plugins/channels.h"
|
2019-12-23 20:48:22 +00:00
|
|
|
#include "pointer.h"
|
2019-12-23 21:29:13 +00:00
|
|
|
#include "print-job.h"
|
|
|
|
#include "rdp.h"
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
#ifdef ENABLE_COMMON_SSH
|
2017-02-27 22:28:23 +00:00
|
|
|
#include "common-ssh/sftp.h"
|
|
|
|
#include "common-ssh/ssh.h"
|
|
|
|
#include "common-ssh/user.h"
|
2016-03-01 05:50:00 +00:00
|
|
|
#endif
|
|
|
|
|
2019-09-21 19:25:06 +00:00
|
|
|
#include <freerdp/addin.h>
|
2016-03-01 05:50:00 +00:00
|
|
|
#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>
|
2019-09-21 19:25:06 +00:00
|
|
|
#include <freerdp/client/channels.h>
|
|
|
|
#include <freerdp/client/cliprdr.h>
|
|
|
|
#include <freerdp/client/disp.h>
|
|
|
|
#include <freerdp/event.h>
|
2016-03-01 05:50:00 +00:00
|
|
|
#include <freerdp/freerdp.h>
|
2019-09-29 22:37:09 +00:00
|
|
|
#include <freerdp/gdi/gdi.h>
|
2019-09-21 19:25:06 +00:00
|
|
|
#include <freerdp/rail.h>
|
|
|
|
#include <freerdp/version.h>
|
2016-03-01 05:50:00 +00:00
|
|
|
#include <guacamole/audio.h>
|
|
|
|
#include <guacamole/client.h>
|
|
|
|
#include <guacamole/protocol.h>
|
|
|
|
#include <guacamole/socket.h>
|
|
|
|
#include <guacamole/timestamp.h>
|
|
|
|
#include <winpr/wtypes.h>
|
|
|
|
|
|
|
|
#include <errno.h>
|
2016-11-11 21:45:01 +00:00
|
|
|
#include <poll.h>
|
2016-03-01 05:50:00 +00:00
|
|
|
#include <pthread.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
BOOL rdp_freerdp_pre_connect(freerdp* instance) {
|
|
|
|
|
|
|
|
rdpContext* context = instance->context;
|
|
|
|
rdpChannels* channels = context->channels;
|
2019-09-29 22:02:06 +00:00
|
|
|
rdpGraphics* graphics = context->graphics;
|
2016-03-16 22:02:13 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-01-01 00:23:28 +00:00
|
|
|
/* Push desired settings to FreeRDP */
|
|
|
|
guac_rdp_push_settings(client, settings, instance);
|
|
|
|
|
2016-03-01 05:50:00 +00:00
|
|
|
/* Init FreeRDP add-in provider */
|
|
|
|
freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0);
|
|
|
|
|
2016-05-10 01:20:04 +00:00
|
|
|
/* Load "disp" plugin for display update */
|
|
|
|
if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE)
|
2019-10-13 22:30:02 +00:00
|
|
|
guac_rdp_disp_load_plugin(context);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-05-10 01:20:04 +00:00
|
|
|
/* Load "AUDIO_INPUT" plugin for audio input*/
|
|
|
|
if (settings->enable_audio_input) {
|
|
|
|
rdp_client->audio_input = guac_rdp_audio_buffer_alloc();
|
2019-10-13 22:30:02 +00:00
|
|
|
guac_rdp_audio_load_plugin(instance->context);
|
2016-05-10 01:20:04 +00:00
|
|
|
}
|
2016-04-16 22:54:26 +00:00
|
|
|
|
2019-10-12 00:01:26 +00:00
|
|
|
/* Load "cliprdr" plugin for clipboard support */
|
|
|
|
guac_rdp_clipboard_load_plugin(rdp_client->clipboard, context);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* If RDPSND/RDPDR required, load them */
|
2016-03-16 22:02:13 +00:00
|
|
|
if (settings->printing_enabled
|
|
|
|
|| settings->drive_enabled
|
|
|
|
|| settings->audio_enabled) {
|
2019-12-22 03:36:20 +00:00
|
|
|
guac_rdpdr_load_plugin(context);
|
2019-12-21 22:03:32 +00:00
|
|
|
guac_rdpsnd_load_plugin(context);
|
2016-03-01 05:50:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Load RAIL plugin if RemoteApp in use */
|
2019-10-14 18:12:53 +00:00
|
|
|
if (settings->remote_app != NULL)
|
|
|
|
guac_rdp_rail_load_plugin(context);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* Load SVC plugin instances for all static channels */
|
2016-03-16 22:02:13 +00:00
|
|
|
if (settings->svc_names != NULL) {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-03-16 22:02:13 +00:00
|
|
|
char** current = settings->svc_names;
|
2016-03-01 05:50:00 +00:00
|
|
|
do {
|
2019-12-22 21:33:08 +00:00
|
|
|
guac_rdp_pipe_svc_load_plugin(context, *current);
|
2016-03-01 05:50:00 +00:00
|
|
|
} while (*(++current) != NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-10-13 22:30:02 +00:00
|
|
|
/* Load plugin providing Dynamic Virtual Channel support, if required */
|
|
|
|
if (instance->settings->SupportDynamicChannels &&
|
|
|
|
guac_freerdp_channels_load_plugin(channels, instance->settings,
|
|
|
|
"drdynvc", instance->settings)) {
|
2016-05-10 01:20:04 +00:00
|
|
|
guac_client_log(client, GUAC_LOG_WARNING,
|
|
|
|
"Failed to load drdynvc plugin. Display update and audio "
|
|
|
|
"input support will be disabled.");
|
2019-10-13 22:30:02 +00:00
|
|
|
}
|
2016-05-10 01:20:04 +00:00
|
|
|
|
2019-09-29 22:37:09 +00:00
|
|
|
/* Init FreeRDP internal GDI implementation */
|
2020-01-04 09:11:05 +00:00
|
|
|
if (!gdi_init(instance, guac_rdp_get_native_pixel_format(FALSE)))
|
2019-09-29 22:37:09 +00:00
|
|
|
return FALSE;
|
|
|
|
|
2016-03-01 05:50:00 +00:00
|
|
|
/* Set up bitmap handling */
|
2019-09-29 22:02:06 +00:00
|
|
|
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);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* Set up glyph handling */
|
2019-09-29 22:02:06 +00:00
|
|
|
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);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* Set up pointer handling */
|
2019-09-29 22:02:06 +00:00
|
|
|
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);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* Set up GDI */
|
|
|
|
instance->update->DesktopResize = guac_rdp_gdi_desktop_resize;
|
|
|
|
instance->update->EndPaint = guac_rdp_gdi_end_paint;
|
|
|
|
instance->update->SetBounds = guac_rdp_gdi_set_bounds;
|
|
|
|
|
2019-09-29 22:02:06 +00:00
|
|
|
rdpPrimaryUpdate* primary = instance->update->primary;
|
2016-03-01 05:50:00 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-03-08 00:40:05 +00:00
|
|
|
/**
|
|
|
|
* Callback invoked by FreeRDP when authentication is required but a username
|
|
|
|
* and password has not already been given. In the case of Guacamole, this
|
|
|
|
* function always succeeds but does not populate the usename or password. The
|
|
|
|
* username/password must be given within the connection parameters.
|
|
|
|
*
|
|
|
|
* @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.
|
|
|
|
*/
|
|
|
|
static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username,
|
2016-03-01 05:50:00 +00:00
|
|
|
char** password, char** domain) {
|
|
|
|
|
|
|
|
rdpContext* context = instance->context;
|
|
|
|
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
|
|
|
|
|
|
|
/* Warn if connection is likely to fail due to lack of credentials */
|
|
|
|
guac_client_log(client, GUAC_LOG_INFO,
|
|
|
|
"Authentication requested but username or password not given");
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-03-08 00:40:05 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
* TRUE if the certificate passes verification, FALSE otherwise.
|
|
|
|
*/
|
2019-09-22 19:12:26 +00:00
|
|
|
static DWORD rdp_freerdp_verify_certificate(freerdp* instance,
|
|
|
|
const char* common_name, const char* subject, const char* issuer,
|
|
|
|
const char* fingerprint, BOOL host_mismatch) {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
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");
|
2019-09-22 19:12:26 +00:00
|
|
|
return 2; /* Accept only for this session */
|
2016-03-01 05:50:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed");
|
2019-09-22 19:12:26 +00:00
|
|
|
return 0; /* Reject certificate */
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-11-11 21:45:01 +00:00
|
|
|
* Waits for messages from the RDP server for the given number of milliseconds.
|
2016-03-01 05:50:00 +00:00
|
|
|
*
|
2016-03-08 00:40:05 +00:00
|
|
|
* @param client
|
|
|
|
* The client associated with the current RDP session.
|
|
|
|
*
|
2016-11-11 21:45:01 +00:00
|
|
|
* @param timeout_msecs
|
|
|
|
* The maximum amount of time to wait, in milliseconds.
|
2016-03-01 05:50:00 +00:00
|
|
|
*
|
|
|
|
* @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,
|
2016-11-11 21:45:01 +00:00
|
|
|
int timeout_msecs) {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
|
|
|
freerdp* rdp_inst = rdp_client->rdp_inst;
|
|
|
|
|
2019-10-06 21:32:12 +00:00
|
|
|
HANDLE handles[GUAC_RDP_MAX_FILE_DESCRIPTORS];
|
|
|
|
int num_handles = freerdp_get_event_handles(rdp_inst->context, handles,
|
|
|
|
GUAC_RDP_MAX_FILE_DESCRIPTORS);
|
2016-11-11 21:45:01 +00:00
|
|
|
|
2019-10-06 21:32:12 +00:00
|
|
|
/* Wait for data and construct a reasonable frame */
|
|
|
|
int result = WaitForMultipleObjects(num_handles, handles, FALSE,
|
|
|
|
timeout_msecs);
|
2016-11-11 21:45:01 +00:00
|
|
|
|
2019-10-06 21:32:12 +00:00
|
|
|
/* Translate WaitForMultipleObjects() return values */
|
|
|
|
switch (result) {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2019-10-06 21:32:12 +00:00
|
|
|
/* Timeout elapsed before wait could complete */
|
|
|
|
case WAIT_TIMEOUT:
|
2016-03-01 05:50:00 +00:00
|
|
|
return 0;
|
|
|
|
|
2019-10-06 21:32:12 +00:00
|
|
|
/* Attempt to wait failed due to an error */
|
|
|
|
case WAIT_FAILED:
|
|
|
|
return -1;
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-10-06 21:32:12 +00:00
|
|
|
/* Wait was successful */
|
|
|
|
return 1;
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-03-17 04:30:46 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2016-03-16 04:23:19 +00:00
|
|
|
static int guac_rdp_handle_connection(guac_client* client) {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
|
|
|
guac_rdp_settings* settings = rdp_client->settings;
|
|
|
|
|
|
|
|
/* Init random number generator */
|
|
|
|
srandom(time(NULL));
|
|
|
|
|
2016-02-29 07:51:46 +00:00
|
|
|
/* Set up screen recording, if requested */
|
|
|
|
if (settings->recording_path != NULL) {
|
2017-11-27 17:38:20 +00:00
|
|
|
rdp_client->recording = guac_common_recording_create(client,
|
2016-02-29 07:51:46 +00:00
|
|
|
settings->recording_path,
|
|
|
|
settings->recording_name,
|
2017-12-08 20:06:30 +00:00
|
|
|
settings->create_recording_path,
|
|
|
|
!settings->recording_exclude_output,
|
|
|
|
!settings->recording_exclude_mouse,
|
|
|
|
settings->recording_include_keys);
|
2016-02-29 07:51:46 +00:00
|
|
|
}
|
|
|
|
|
2016-03-01 05:50:00 +00:00
|
|
|
/* Create display */
|
|
|
|
rdp_client->display = guac_common_display_alloc(client,
|
|
|
|
rdp_client->settings->width,
|
|
|
|
rdp_client->settings->height);
|
|
|
|
|
|
|
|
rdp_client->current_surface = rdp_client->display->default_surface;
|
|
|
|
|
2016-03-16 04:23:19 +00:00
|
|
|
rdp_client->available_svc = guac_common_list_alloc();
|
|
|
|
|
2016-03-01 05:50:00 +00:00
|
|
|
/* Init client */
|
|
|
|
freerdp* rdp_inst = freerdp_new();
|
|
|
|
rdp_inst->PreConnect = rdp_freerdp_pre_connect;
|
|
|
|
rdp_inst->Authenticate = rdp_freerdp_authenticate;
|
|
|
|
rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate;
|
2019-09-21 19:25:06 +00:00
|
|
|
rdp_inst->ReceiveChannelData = freerdp_channels_data;
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* Allocate FreeRDP context */
|
|
|
|
rdp_inst->ContextSize = sizeof(rdp_freerdp_context);
|
|
|
|
|
|
|
|
freerdp_context_new(rdp_inst);
|
|
|
|
((rdp_freerdp_context*) rdp_inst->context)->client = client;
|
|
|
|
|
|
|
|
/* Load keymap into client */
|
2016-08-14 00:33:30 +00:00
|
|
|
rdp_client->keyboard = guac_rdp_keyboard_alloc(client,
|
|
|
|
settings->server_layout);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* Set default pointer */
|
|
|
|
guac_common_cursor_set_pointer(rdp_client->display->cursor);
|
|
|
|
|
|
|
|
/* Connect to RDP server */
|
|
|
|
if (!freerdp_connect(rdp_inst)) {
|
2017-02-11 19:53:34 +00:00
|
|
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND,
|
2016-03-01 05:50:00 +00:00
|
|
|
"Error connecting to RDP server");
|
2016-03-16 04:23:19 +00:00
|
|
|
return 1;
|
2016-03-01 05:50:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Connection complete */
|
|
|
|
rdp_client->rdp_inst = rdp_inst;
|
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
guac_timestamp last_frame_end = guac_timestamp_current();
|
|
|
|
|
2016-03-16 04:23:19 +00:00
|
|
|
/* Signal that reconnect has been completed */
|
|
|
|
guac_rdp_disp_reconnect_complete(rdp_client->disp);
|
|
|
|
|
2016-03-01 05:50:00 +00:00
|
|
|
/* Handle messages from RDP server while client is running */
|
2016-03-16 04:23:19 +00:00
|
|
|
while (client->state == GUAC_CLIENT_RUNNING
|
|
|
|
&& !guac_rdp_disp_reconnect_needed(rdp_client->disp)) {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* Update remote display size */
|
2016-03-16 04:23:19 +00:00
|
|
|
guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
/* Wait for data and construct a reasonable frame */
|
2016-03-08 05:12:53 +00:00
|
|
|
int wait_result = rdp_guac_client_wait_for_messages(client,
|
|
|
|
GUAC_RDP_FRAME_START_TIMEOUT);
|
2016-03-09 02:12:43 +00:00
|
|
|
if (wait_result > 0) {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
int processing_lag = guac_client_get_processing_lag(client);
|
|
|
|
guac_timestamp frame_start = guac_timestamp_current();
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
/* Read server messages until frame is built */
|
|
|
|
do {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
guac_timestamp frame_end;
|
|
|
|
int frame_remaining;
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
/* Check the libfreerdp fds */
|
2019-10-06 21:32:12 +00:00
|
|
|
if (!freerdp_check_event_handles(rdp_inst->context)) {
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2018-01-22 20:16:09 +00:00
|
|
|
/* Flag connection failure */
|
|
|
|
wait_result = -1;
|
|
|
|
break;
|
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate time remaining in frame */
|
|
|
|
frame_end = guac_timestamp_current();
|
|
|
|
frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION
|
|
|
|
- frame_end;
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
/* Calculate time that client needs to catch up */
|
|
|
|
int time_elapsed = frame_end - last_frame_end;
|
|
|
|
int required_wait = processing_lag - time_elapsed;
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
/* 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,
|
2016-11-11 21:45:01 +00:00
|
|
|
required_wait);
|
2016-03-01 05:50:00 +00:00
|
|
|
|
2016-03-09 02:12:43 +00:00
|
|
|
/* Wait again if frame remaining */
|
|
|
|
else if (frame_remaining > 0)
|
|
|
|
wait_result = rdp_guac_client_wait_for_messages(client,
|
2016-11-11 21:45:01 +00:00
|
|
|
GUAC_RDP_FRAME_TIMEOUT);
|
2016-03-09 02:12:43 +00:00
|
|
|
else
|
|
|
|
break;
|
|
|
|
|
|
|
|
} while (wait_result > 0);
|
2016-10-14 20:31:41 +00:00
|
|
|
|
|
|
|
/* Record end of frame, excluding server-side rendering time (we
|
|
|
|
* assume server-side rendering time will be consistent between any
|
|
|
|
* two subsequent frames, and that this time should thus be
|
|
|
|
* excluded from the required wait period of the next frame). */
|
|
|
|
last_frame_end = frame_start;
|
|
|
|
|
2016-03-01 05:50:00 +00:00
|
|
|
}
|
|
|
|
|
2018-01-22 20:16:09 +00:00
|
|
|
/* Test whether the RDP server is closing the connection */
|
|
|
|
int connection_closing = freerdp_shall_disconnect(rdp_inst);
|
|
|
|
|
|
|
|
/* Close connection cleanly if server is disconnecting */
|
2018-01-22 20:23:39 +00:00
|
|
|
if (connection_closing)
|
2018-01-22 20:16:09 +00:00
|
|
|
guac_rdp_client_abort(client);
|
|
|
|
|
2018-01-22 20:23:39 +00:00
|
|
|
/* If a low-level connection error occurred, fail */
|
|
|
|
else if (wait_result < 0)
|
2017-02-11 19:53:34 +00:00
|
|
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE,
|
2016-03-01 05:50:00 +00:00
|
|
|
"Connection closed.");
|
|
|
|
|
2018-01-22 20:24:07 +00:00
|
|
|
/* Flush frame only if successful */
|
|
|
|
else {
|
|
|
|
guac_common_display_flush(rdp_client->display);
|
|
|
|
guac_client_end_frame(client);
|
|
|
|
guac_socket_flush(client->socket);
|
|
|
|
}
|
2016-03-01 05:50:00 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-02-14 08:09:24 +00:00
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
2016-03-18 00:11:40 +00:00
|
|
|
/* Disconnect client and channels */
|
2016-03-16 04:23:19 +00:00
|
|
|
freerdp_disconnect(rdp_inst);
|
2016-03-18 00:11:40 +00:00
|
|
|
|
2019-10-12 03:38:46 +00:00
|
|
|
/* Clean up FreeRDP internal GDI implementation */
|
|
|
|
gdi_free(rdp_inst);
|
|
|
|
|
2016-03-18 00:11:40 +00:00
|
|
|
/* Clean up RDP client context */
|
|
|
|
freerdp_context_free(rdp_inst);
|
|
|
|
|
|
|
|
/* Clean up RDP client */
|
2016-03-16 04:23:19 +00:00
|
|
|
freerdp_free(rdp_inst);
|
2016-03-18 01:29:27 +00:00
|
|
|
rdp_client->rdp_inst = NULL;
|
2016-03-16 04:23:19 +00:00
|
|
|
|
|
|
|
/* Free SVC list */
|
|
|
|
guac_common_list_free(rdp_client->available_svc);
|
|
|
|
|
2016-08-14 00:33:30 +00:00
|
|
|
/* Free RDP keyboard state */
|
|
|
|
guac_rdp_keyboard_free(rdp_client->keyboard);
|
|
|
|
|
2016-03-16 04:23:19 +00:00
|
|
|
/* Free display */
|
|
|
|
guac_common_display_free(rdp_client->display);
|
|
|
|
|
2016-10-29 22:00:53 +00:00
|
|
|
/* Client is now disconnected */
|
|
|
|
guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected");
|
|
|
|
|
2016-03-16 04:23:19 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void* guac_rdp_client_thread(void* data) {
|
|
|
|
|
|
|
|
guac_client* client = (guac_client*) data;
|
2016-03-30 19:20:49 +00:00
|
|
|
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
|
|
|
guac_rdp_settings* settings = rdp_client->settings;
|
|
|
|
|
2016-03-31 21:25:31 +00:00
|
|
|
/* 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 */
|
|
|
|
|
2016-03-30 19:20:49 +00:00
|
|
|
/* 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);
|
|
|
|
|
|
|
|
/* Expose filesystem to owner */
|
|
|
|
guac_client_for_owner(client, guac_rdp_fs_expose,
|
|
|
|
rdp_client->filesystem);
|
|
|
|
|
|
|
|
}
|
2016-03-16 04:23:19 +00:00
|
|
|
|
2016-03-18 01:29:27 +00:00
|
|
|
#ifdef ENABLE_COMMON_SSH
|
2016-03-30 19:20:49 +00:00
|
|
|
/* Connect via SSH if SFTP is enabled */
|
|
|
|
if (settings->enable_sftp) {
|
|
|
|
|
|
|
|
/* Abort if username is missing */
|
2016-04-19 01:15:32 +00:00
|
|
|
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.");
|
2016-03-30 19:20:49 +00:00
|
|
|
return NULL;
|
2016-04-19 01:15:32 +00:00
|
|
|
}
|
2016-03-30 19:20:49 +00:00
|
|
|
|
|
|
|
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)) {
|
2016-04-19 01:15:32 +00:00
|
|
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
|
|
|
"Private key unreadable.");
|
2016-03-30 19:20:49 +00:00
|
|
|
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,
|
2018-04-05 12:52:16 +00:00
|
|
|
settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval,
|
2018-12-27 06:01:06 +00:00
|
|
|
settings->sftp_host_key, NULL);
|
2016-03-30 19:20:49 +00:00
|
|
|
|
|
|
|
/* 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 =
|
2017-06-29 22:48:23 +00:00
|
|
|
guac_common_ssh_create_sftp_filesystem(rdp_client->sftp_session,
|
|
|
|
settings->sftp_root_directory, NULL);
|
2016-03-30 19:20:49 +00:00
|
|
|
|
|
|
|
/* 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) {
|
2017-02-11 19:53:34 +00:00
|
|
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE,
|
2016-04-19 01:15:32 +00:00
|
|
|
"SFTP connection failed.");
|
2016-03-30 19:20:49 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
|
|
"SFTP connection succeeded.");
|
|
|
|
|
|
|
|
}
|
2016-03-18 01:29:27 +00:00
|
|
|
#endif
|
2016-03-16 04:23:19 +00:00
|
|
|
|
2016-03-18 01:29:27 +00:00
|
|
|
/* Continue handling connections until error or client disconnect */
|
|
|
|
while (client->state == GUAC_CLIENT_RUNNING) {
|
2016-03-16 04:23:19 +00:00
|
|
|
if (guac_rdp_handle_connection(client))
|
2016-03-18 01:29:27 +00:00
|
|
|
break;
|
2016-03-16 04:23:19 +00:00
|
|
|
}
|
|
|
|
|
2016-03-01 05:50:00 +00:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
}
|
|
|
|
|