Merge pull request #119 from glyptodon/fake-merge-screen-sharing-006-rdp

GUAC-1389: Add screen sharing support to RDP.
This commit is contained in:
James Muehlner 2016-03-07 21:31:37 -08:00
commit 3c572501a9
46 changed files with 3872 additions and 2038 deletions

View File

@ -50,7 +50,7 @@ if ENABLE_TERMINAL
endif
if ENABLE_RDP
#SUBDIRS += src/protocols/rdp
SUBDIRS += src/protocols/rdp
endif
if ENABLE_SSH

View File

@ -28,7 +28,8 @@ lib_LTLIBRARIES = libguac-client-rdp.la
libguac_client_rdp_la_SOURCES = \
_generated_keymaps.c \
client.c \
guac_handlers.c \
input.c \
rdp.c \
rdp_bitmap.c \
rdp_cliprdr.c \
rdp_color.c \
@ -42,7 +43,8 @@ libguac_client_rdp_la_SOURCES = \
rdp_stream.c \
rdp_svc.c \
resolution.c \
unicode.c
unicode.c \
user.c
guacsvc_sources = \
guac_svc/svc_service.c \
@ -80,7 +82,8 @@ noinst_HEADERS = \
guac_rdpsnd/rdpsnd_service.h \
guac_svc/svc_service.h \
client.h \
guac_handlers.h \
input.h \
rdp.h \
rdp_bitmap.h \
rdp_cliprdr.h \
rdp_color.h \
@ -95,7 +98,8 @@ noinst_HEADERS = \
rdp_stream.h \
rdp_svc.h \
resolution.h \
unicode.h
unicode.h \
user.h
# Add compatibility layer for WinPR if not available
if ! ENABLE_WINPR

File diff suppressed because it is too large Load Diff

View File

@ -20,37 +20,13 @@
* THE SOFTWARE.
*/
#ifndef _GUAC_RDP_CLIENT_H
#define _GUAC_RDP_CLIENT_H
#ifndef GUAC_RDP_CLIENT_H
#define GUAC_RDP_CLIENT_H
#include "config.h"
#include "guac_clipboard.h"
#include "guac_list.h"
#include "guac_surface.h"
#include "rdp_fs.h"
#include "rdp_keymap.h"
#include "rdp_settings.h"
#ifdef ENABLE_COMMON_SSH
#include "guac_sftp.h"
#include "guac_ssh.h"
#include "guac_ssh_user.h"
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
#include "rdp_disp.h"
#endif
#include <freerdp/freerdp.h>
#include <freerdp/codec/color.h>
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <pthread.h>
#include <stdint.h>
/**
* The maximum duration of a frame in milliseconds.
*/
@ -63,6 +39,15 @@
*/
#define GUAC_RDP_FRAME_TIMEOUT 10
/**
* The amount of time to wait for a new message from the RDP server when
* beginning a new frame. This value must be kept reasonably small such that
* a slow RDP server will not prevent external events from being handled (such
* as the stop signal from guac_client_stop()), but large enough that the
* message handling loop does not eat up CPU spinning.
*/
#define GUAC_RDP_FRAME_START_TIMEOUT 250000
/**
* The native resolution of most RDP connections. As Windows and other systems
* rely heavily on forced 96 DPI, we must assume 96 DPI.
@ -107,152 +92,9 @@
*/
#define GUAC_RDP_AUDIO_BPS 16
/**
* Client data that will remain accessible through the guac_client.
* This should generally include data commonly used by Guacamole handlers.
* Handler which frees all data associated with the guac_client.
*/
typedef struct rdp_guac_client_data {
/**
* Pointer to the FreeRDP client instance handling the current connection.
*/
freerdp* rdp_inst;
/**
* All settings associated with the current or pending RDP connection.
*/
guac_rdp_settings settings;
/**
* Button mask containing the OR'd value of all currently pressed buttons.
*/
int mouse_button_mask;
/**
* Foreground color for any future glyphs.
*/
uint32_t glyph_color;
/**
* The display.
*/
guac_common_surface* default_surface;
/**
* The surface that GDI operations should draw to. RDP messages exist which
* change this surface to allow drawing to occur off-screen.
*/
guac_common_surface* current_surface;
/**
* The keymap to use when translating keysyms into scancodes or sequences
* of scancodes for RDP.
*/
guac_rdp_static_keymap keymap;
/**
* The state of all keys, based on whether events for pressing/releasing
* particular keysyms have been received. This is necessary in order to
* determine which keys must be released/pressed when a particular
* keysym can only be typed through a sequence of scancodes (such as
* an Alt-code) because the server-side keymap does not support that
* keysym.
*/
guac_rdp_keysym_state_map keysym_state;
/**
* The current clipboard contents.
*/
guac_common_clipboard* clipboard;
/**
* The format of the clipboard which was requested. Data received from
* the RDP server should conform to this format. This will be one of
* several legal clipboard format values defined within FreeRDP, such as
* CB_FORMAT_TEXT.
*/
int requested_clipboard_format;
/**
* Audio output, if any.
*/
guac_audio_stream* audio;
/**
* The filesystem being shared, if any.
*/
guac_rdp_fs* filesystem;
#ifdef ENABLE_COMMON_SSH
/**
* The user and credentials used to authenticate for SFTP.
*/
guac_common_ssh_user* sftp_user;
/**
* The SSH session used for SFTP.
*/
guac_common_ssh_session* sftp_session;
/**
* The exposed filesystem object, implemented with SFTP.
*/
guac_object* sftp_filesystem;
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/**
* Display size update module.
*/
guac_rdp_disp* disp;
#endif
/**
* List of all available static virtual channels.
*/
guac_common_list* available_svc;
/**
* Lock which is locked and unlocked for each RDP message.
*/
pthread_mutex_t rdp_lock;
/**
* Common attributes for locks.
*/
pthread_mutexattr_t attributes;
} rdp_guac_client_data;
/**
* Client data that will remain accessible through the RDP context.
* This should generally include data commonly used by FreeRDP handlers.
*/
typedef struct rdp_freerdp_context {
/**
* The parent context. THIS MUST BE THE FIRST ELEMENT.
*/
rdpContext _p;
/**
* Pointer to the guac_client instance handling the RDP connection with
* this context.
*/
guac_client* client;
/**
* Color conversion structure to be used to convert RDP images to PNGs.
*/
CLRCONV* clrconv;
/**
* The current color palette, as received from the RDP server.
*/
UINT32 palette[256];
} rdp_freerdp_context;
guac_client_free_handler guac_rdp_client_free_handler;
#endif

View File

@ -1,525 +0,0 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "client.h"
#include "guac_clipboard.h"
#include "guac_handlers.h"
#include "guac_list.h"
#include "guac_surface.h"
#include "rdp_cliprdr.h"
#include "rdp_keymap.h"
#include "rdp_fs.h"
#include "rdp_rail.h"
#include "rdp_stream.h"
#ifdef ENABLE_COMMON_SSH
#include <guac_sftp.h>
#include <guac_ssh.h>
#include <guac_ssh_user.h>
#endif
#include <freerdp/cache/cache.h>
#include <freerdp/channels/channels.h>
#include <freerdp/codec/color.h>
#include <freerdp/freerdp.h>
#include <freerdp/input.h>
#include <freerdp/utils/event.h>
#include <guacamole/client.h>
#include <guacamole/error.h>
#include <guacamole/protocol.h>
#include <guacamole/timestamp.h>
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
#include "rdp_disp.h"
#endif
#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H
#include <freerdp/client/cliprdr.h>
#else
#include "compat/client-cliprdr.h"
#endif
#ifdef LEGACY_FREERDP
#include "compat/rail.h"
#else
#include <freerdp/rail.h>
#endif
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
void __guac_rdp_update_keysyms(guac_client* client, const int* keysym_string, int from, int to);
int __guac_rdp_send_keysym(guac_client* client, int keysym, int pressed);
int rdp_guac_client_free_handler(guac_client* client) {
rdp_guac_client_data* guac_client_data =
(rdp_guac_client_data*) client->data;
freerdp* rdp_inst = guac_client_data->rdp_inst;
rdpChannels* channels = rdp_inst->context->channels;
/* Clean up RDP client */
freerdp_channels_close(channels, rdp_inst);
freerdp_channels_free(channels);
freerdp_disconnect(rdp_inst);
freerdp_clrconv_free(((rdp_freerdp_context*) rdp_inst->context)->clrconv);
cache_free(rdp_inst->context->cache);
freerdp_free(rdp_inst);
/* Clean up filesystem, if allocated */
if (guac_client_data->filesystem != NULL)
guac_rdp_fs_free(guac_client_data->filesystem);
#ifdef ENABLE_COMMON_SSH
/* Free SFTP filesystem, if loaded */
if (guac_client_data->sftp_filesystem)
guac_common_ssh_destroy_sftp_filesystem(guac_client_data->sftp_filesystem);
/* Free SFTP session */
if (guac_client_data->sftp_session)
guac_common_ssh_destroy_session(guac_client_data->sftp_session);
/* Free SFTP user */
if (guac_client_data->sftp_user)
guac_common_ssh_destroy_user(guac_client_data->sftp_user);
guac_common_ssh_uninit();
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/* Free display update module */
guac_rdp_disp_free(guac_client_data->disp);
#endif
/* Free SVC list */
guac_common_list_free(guac_client_data->available_svc);
/* Free client data */
guac_common_clipboard_free(guac_client_data->clipboard);
guac_common_surface_free(guac_client_data->default_surface);
free(guac_client_data);
return 0;
}
static int rdp_guac_client_wait_for_messages(guac_client* client, int timeout_usecs) {
rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data;
freerdp* rdp_inst = guac_client_data->rdp_inst;
rdpChannels* channels = rdp_inst->context->channels;
int result;
int index;
int max_fd, fd;
void* read_fds[32];
void* write_fds[32];
int read_count = 0;
int write_count = 0;
fd_set rfds, wfds;
struct timeval timeout = {
.tv_sec = 0,
.tv_usec = timeout_usecs
};
/* Get RDP fds */
if (!freerdp_get_fds(rdp_inst, read_fds, &read_count, write_fds, &write_count)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to read RDP file descriptors.");
return -1;
}
/* Get channel fds */
if (!freerdp_channels_get_fds(channels, rdp_inst, read_fds, &read_count, write_fds,
&write_count)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to read RDP channel file descriptors.");
return -1;
}
/* Construct read fd_set */
max_fd = 0;
FD_ZERO(&rfds);
for (index = 0; index < read_count; index++) {
fd = (int)(long) (read_fds[index]);
if (fd > max_fd)
max_fd = fd;
FD_SET(fd, &rfds);
}
/* Construct write fd_set */
FD_ZERO(&wfds);
for (index = 0; index < write_count; index++) {
fd = (int)(long) (write_fds[index]);
if (fd > max_fd)
max_fd = fd;
FD_SET(fd, &wfds);
}
/* If no file descriptors, error */
if (max_fd == 0) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "No file descriptors associated with RDP connection.");
return -1;
}
/* Wait for all RDP file descriptors */
result = select(max_fd + 1, &rfds, &wfds, NULL, &timeout);
if (result < 0) {
/* If error ignorable, pretend timout occurred */
if (errno == EAGAIN
|| errno == EWOULDBLOCK
|| errno == EINPROGRESS
|| errno == EINTR)
return 0;
/* Otherwise, return as error */
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error waiting for file descriptor.");
return -1;
}
/* Return wait result */
return result;
}
int rdp_guac_client_handle_messages(guac_client* client) {
rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data;
freerdp* rdp_inst = guac_client_data->rdp_inst;
rdpChannels* channels = rdp_inst->context->channels;
wMessage* event;
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/* Update remote display size */
pthread_mutex_lock(&(guac_client_data->rdp_lock));
guac_rdp_disp_update_size(guac_client_data->disp, rdp_inst->context);
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
#endif
/* Wait for messages */
int wait_result = rdp_guac_client_wait_for_messages(client, 250000);
guac_timestamp frame_start = guac_timestamp_current();
while (wait_result > 0) {
guac_timestamp frame_end;
int frame_remaining;
pthread_mutex_lock(&(guac_client_data->rdp_lock));
/* Check the libfreerdp fds */
if (!freerdp_check_fds(rdp_inst)) {
guac_client_log(client, GUAC_LOG_DEBUG, "Error handling RDP file descriptors");
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
return 1;
}
/* Check channel fds */
if (!freerdp_channels_check_fds(channels, rdp_inst)) {
guac_client_log(client, GUAC_LOG_DEBUG, "Error handling RDP channel file descriptors");
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
return 1;
}
/* Check for channel events */
event = freerdp_channels_pop_event(channels);
if (event) {
/* Handle channel events (clipboard and RAIL) */
#ifdef LEGACY_EVENT
if (event->event_class == CliprdrChannel_Class)
guac_rdp_process_cliprdr_event(client, event);
else if (event->event_class == RailChannel_Class)
guac_rdp_process_rail_event(client, event);
#else
if (GetMessageClass(event->id) == CliprdrChannel_Class)
guac_rdp_process_cliprdr_event(client, event);
else if (GetMessageClass(event->id) == RailChannel_Class)
guac_rdp_process_rail_event(client, event);
#endif
freerdp_event_free(event);
}
/* Handle RDP disconnect */
if (freerdp_shall_disconnect(rdp_inst)) {
guac_client_log(client, GUAC_LOG_INFO, "RDP server closed connection");
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
return 1;
}
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
/* Calculate time remaining in frame */
frame_end = guac_timestamp_current();
frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION - frame_end;
/* Wait again if frame remaining */
if (frame_remaining > 0)
wait_result = rdp_guac_client_wait_for_messages(client,
GUAC_RDP_FRAME_TIMEOUT*1000);
else
break;
}
/* If an error occurred, fail */
if (wait_result < 0)
return 1;
/* Success */
guac_common_surface_flush(guac_client_data->default_surface);
return 0;
}
int rdp_guac_client_mouse_handler(guac_client* client, int x, int y, int mask) {
rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data;
freerdp* rdp_inst = guac_client_data->rdp_inst;
pthread_mutex_lock(&(guac_client_data->rdp_lock));
/* If button mask unchanged, just send move event */
if (mask == guac_client_data->mouse_button_mask)
rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_MOVE, x, y);
/* Otherwise, send events describing button change */
else {
/* Mouse buttons which have JUST become released */
int released_mask = guac_client_data->mouse_button_mask & ~mask;
/* Mouse buttons which have JUST become pressed */
int pressed_mask = ~guac_client_data->mouse_button_mask & mask;
/* Release event */
if (released_mask & 0x07) {
/* Calculate flags */
int flags = 0;
if (released_mask & 0x01) flags |= PTR_FLAGS_BUTTON1;
if (released_mask & 0x02) flags |= PTR_FLAGS_BUTTON3;
if (released_mask & 0x04) flags |= PTR_FLAGS_BUTTON2;
rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y);
}
/* Press event */
if (pressed_mask & 0x07) {
/* Calculate flags */
int flags = PTR_FLAGS_DOWN;
if (pressed_mask & 0x01) flags |= PTR_FLAGS_BUTTON1;
if (pressed_mask & 0x02) flags |= PTR_FLAGS_BUTTON3;
if (pressed_mask & 0x04) flags |= PTR_FLAGS_BUTTON2;
if (pressed_mask & 0x08) flags |= PTR_FLAGS_WHEEL | 0x78;
if (pressed_mask & 0x10) flags |= PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88;
/* Send event */
rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y);
}
/* Scroll event */
if (pressed_mask & 0x18) {
/* Down */
if (pressed_mask & 0x08)
rdp_inst->input->MouseEvent(
rdp_inst->input,
PTR_FLAGS_WHEEL | 0x78,
x, y);
/* Up */
if (pressed_mask & 0x10)
rdp_inst->input->MouseEvent(
rdp_inst->input,
PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88,
x, y);
}
guac_client_data->mouse_button_mask = mask;
}
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
return 0;
}
int __guac_rdp_send_keysym(guac_client* client, int keysym, int pressed) {
rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data;
freerdp* rdp_inst = guac_client_data->rdp_inst;
/* If keysym can be in lookup table */
if (GUAC_RDP_KEYSYM_STORABLE(keysym)) {
int pressed_flags;
/* Look up scancode mapping */
const guac_rdp_keysym_desc* keysym_desc =
&GUAC_RDP_KEYSYM_LOOKUP(guac_client_data->keymap, keysym);
/* If defined, send event */
if (keysym_desc->scancode != 0) {
pthread_mutex_lock(&(guac_client_data->rdp_lock));
/* If defined, send any prerequesite keys that must be set */
if (keysym_desc->set_keysyms != NULL)
__guac_rdp_update_keysyms(client, keysym_desc->set_keysyms, 0, 1);
/* If defined, release any keys that must be cleared */
if (keysym_desc->clear_keysyms != NULL)
__guac_rdp_update_keysyms(client, keysym_desc->clear_keysyms, 1, 0);
/* Determine proper event flag for pressed state */
if (pressed)
pressed_flags = KBD_FLAGS_DOWN;
else
pressed_flags = KBD_FLAGS_RELEASE;
/* Send actual key */
rdp_inst->input->KeyboardEvent(rdp_inst->input, keysym_desc->flags | pressed_flags,
keysym_desc->scancode);
/* If defined, release any keys that were originally released */
if (keysym_desc->set_keysyms != NULL)
__guac_rdp_update_keysyms(client, keysym_desc->set_keysyms, 0, 0);
/* If defined, send any keys that were originally set */
if (keysym_desc->clear_keysyms != NULL)
__guac_rdp_update_keysyms(client, keysym_desc->clear_keysyms, 1, 1);
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
return 0;
}
}
/* Fall back to unicode events if undefined inside current keymap */
/* Only send when key pressed - Unicode events do not have
* DOWN/RELEASE flags */
if (pressed) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Sending keysym 0x%x as Unicode", keysym);
/* Translate keysym into codepoint */
int codepoint;
if (keysym <= 0xFF)
codepoint = keysym;
else if (keysym >= 0x1000000)
codepoint = keysym & 0xFFFFFF;
else {
guac_client_log(client, GUAC_LOG_DEBUG,
"Unmapped keysym has no equivalent unicode "
"value: 0x%x", keysym);
return 0;
}
pthread_mutex_lock(&(guac_client_data->rdp_lock));
/* Send Unicode event */
rdp_inst->input->UnicodeKeyboardEvent(
rdp_inst->input,
0, codepoint);
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
}
return 0;
}
void __guac_rdp_update_keysyms(guac_client* client, const int* keysym_string, int from, int to) {
rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data;
int keysym;
/* Send all keysyms in string, NULL terminated */
while ((keysym = *keysym_string) != 0) {
/* Get current keysym state */
int current_state = GUAC_RDP_KEYSYM_LOOKUP(guac_client_data->keysym_state, keysym);
/* If key is currently in given state, send event for changing it to specified "to" state */
if (current_state == from)
__guac_rdp_send_keysym(client, *keysym_string, to);
/* Next keysym */
keysym_string++;
}
}
int rdp_guac_client_key_handler(guac_client* client, int keysym, int pressed) {
rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data;
/* Update keysym state */
if (GUAC_RDP_KEYSYM_STORABLE(keysym))
GUAC_RDP_KEYSYM_LOOKUP(guac_client_data->keysym_state, keysym) = pressed;
return __guac_rdp_send_keysym(client, keysym, pressed);
}
int rdp_guac_client_size_handler(guac_client* client, int width, int height) {
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
rdp_guac_client_data* guac_client_data =
(rdp_guac_client_data*) client->data;
freerdp* rdp_inst = guac_client_data->rdp_inst;
/* Convert client pixels to remote pixels */
width = width * guac_client_data->settings.resolution
/ client->info.optimal_resolution;
height = height * guac_client_data->settings.resolution
/ client->info.optimal_resolution;
/* Send display update */
pthread_mutex_lock(&(guac_client_data->rdp_lock));
guac_rdp_disp_set_size(guac_client_data->disp, rdp_inst->context,
width, height);
pthread_mutex_unlock(&(guac_client_data->rdp_lock));
#endif
return 0;
}

View File

@ -23,7 +23,7 @@
#include "config.h"
#include "client.h"
#include "rdp.h"
#include "rdpdr_fs_messages.h"
#include "rdpdr_messages.h"
#include "rdpdr_service.h"
@ -135,7 +135,8 @@ static void guac_rdpdr_device_fs_free_handler(guac_rdpdr_device* device) {
void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr) {
rdp_guac_client_data* data = (rdp_guac_client_data*) rdpdr->client->data;
guac_client* client = rdpdr->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
int id = rdpdr->devices_registered++;
/* Get new device */
@ -152,12 +153,10 @@ void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr) {
device->free_handler = guac_rdpdr_device_fs_free_handler;
/* Init data */
device->data = data->filesystem;
device->data = rdp_client->filesystem;
/* Announce filesystem to client */
guac_protocol_send_filesystem(rdpdr->client->socket,
data->filesystem->object, "Shared Drive");
guac_socket_flush(rdpdr->client->socket);
/* Announce filesystem to owner */
guac_client_for_owner(client, guac_rdp_fs_expose, rdp_client->filesystem);
}

View File

@ -282,9 +282,9 @@ void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device,
(guac_rdpdr_printer_data*) device->data;
wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 1);
completion_id, STATUS_SUCCESS, 4);
Stream_Write_UINT32(output_stream, 0); /* padding*/
Stream_Write_UINT32(output_stream, 0); /* Padding */
/* Close input and wait for output thread to finish */
close(printer_data->printer_input);

View File

@ -22,7 +22,7 @@
#include "config.h"
#include "client.h"
#include "rdp.h"
#include "rdp_fs.h"
#include "rdp_settings.h"
#include "rdp_stream.h"
@ -91,18 +91,18 @@ void guac_rdpdr_process_connect(rdpSvcPlugin* plugin) {
plugin->channel_entry_points.pExtendedData = NULL;
/* Get data from client */
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Init plugin */
rdpdr->client = client;
rdpdr->devices_registered = 0;
/* Register printer if enabled */
if (client_data->settings.printing_enabled)
if (rdp_client->settings->printing_enabled)
guac_rdpdr_register_printer(rdpdr);
/* Register drive if enabled */
if (client_data->settings.drive_enabled)
if (rdp_client->settings->drive_enabled)
guac_rdpdr_register_fs(rdpdr);
/* Log that printing, etc. has been loaded */
@ -222,12 +222,38 @@ wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device,
}
void guac_rdpdr_start_download(guac_rdpdr_device* device, const char* path) {
/**
* Callback invoked on the current connection owner (if any) when a file
* download is being initiated using the magic "Download" folder.
*
* @param owner
* The guac_user that is the owner of the connection, or NULL if the
* connection owner has left.
*
* @param data
* The full absolute path to the file that should be downloaded.
*
* @return
* The stream allocated for the file download, or NULL if the download has
* failed to start.
*/
static void* guac_rdpdr_download_to_owner(guac_user* owner, void* data) {
/* Get client and stream */
guac_client* client = device->rdpdr->client;
/* Do not bother attempting the download if the owner has left */
if (owner == NULL)
return NULL;
int file_id = guac_rdp_fs_open((guac_rdp_fs*) device->data, path,
guac_client* client = owner->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_fs* filesystem = rdp_client->filesystem;
/* Ignore download if filesystem has been unloaded */
if (filesystem == NULL)
return NULL;
/* Attempt to open requested file */
char* path = (char*) data;
int file_id = guac_rdp_fs_open(filesystem, path,
ACCESS_FILE_READ_DATA, 0, DISP_FILE_OPEN, 0);
/* If file opened successfully, start stream */
@ -240,7 +266,7 @@ void guac_rdpdr_start_download(guac_rdpdr_device* device, const char* path) {
char c;
/* Associate stream with transfer status */
guac_stream* stream = guac_client_alloc_stream(client);
guac_stream* stream = guac_user_alloc_stream(owner);
stream->data = rdp_stream = malloc(sizeof(guac_rdp_stream));
stream->ack_handler = guac_rdp_download_ack_handler;
rdp_stream->type = GUAC_RDP_DOWNLOAD_STREAM;
@ -260,17 +286,31 @@ void guac_rdpdr_start_download(guac_rdpdr_device* device, const char* path) {
} while (c != '\0');
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG,
"%s: Initiating download of \"%s\"", __func__, path);
guac_user_log(owner, GUAC_LOG_DEBUG, "%s: Initiating download "
"of \"%s\"", __func__, path);
/* Begin stream */
guac_protocol_send_file(client->socket, stream,
guac_protocol_send_file(owner->socket, stream,
"application/octet-stream", basename);
guac_socket_flush(client->socket);
guac_socket_flush(owner->socket);
/* Download started successfully */
return stream;
}
else
guac_client_log(client, GUAC_LOG_ERROR, "Unable to download \"%s\"", path);
/* Download failed */
guac_user_log(owner, GUAC_LOG_ERROR, "Unable to download \"%s\"", path);
return NULL;
}
void guac_rdpdr_start_download(guac_rdpdr_device* device, char* path) {
guac_client* client = device->rdpdr->client;
/* Initiate download to the owner of the connection */
guac_client_for_owner(client, guac_rdpdr_download_to_owner, path);
}

View File

@ -166,7 +166,7 @@ wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device,
/**
* Begins streaming the given file to the user via a Guacamole file stream.
*/
void guac_rdpdr_start_download(guac_rdpdr_device* device, const char* path);
void guac_rdpdr_start_download(guac_rdpdr_device* device, char* path);
#endif

View File

@ -22,7 +22,7 @@
#include "config.h"
#include "client.h"
#include "rdp.h"
#include "rdpsnd_messages.h"
#include "rdpsnd_service.h"
@ -56,10 +56,10 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
/* Get associated client data */
guac_client* client = rdpsnd->client;
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get audio stream from client data */
guac_audio_stream* audio = client_data->audio;
guac_audio_stream* audio = rdp_client->audio;
/* Format header */
Stream_Seek(input_stream, 14);
@ -188,7 +188,7 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
Stream_SetPointer(output_stream, output_stream_end);
/* Send accepted formats */
pthread_mutex_lock(&(client_data->rdp_lock));
pthread_mutex_lock(&(rdp_client->rdp_lock));
svc_plugin_send((rdpSvcPlugin*)rdpsnd, output_stream);
/* If version greater than 6, must send Quality Mode PDU */
@ -205,7 +205,7 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
svc_plugin_send((rdpSvcPlugin*)rdpsnd, output_stream);
}
pthread_mutex_unlock(&(client_data->rdp_lock));
pthread_mutex_unlock(&(rdp_client->rdp_lock));
}
@ -218,7 +218,7 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd,
/* Get associated client data */
guac_client* client = rdpsnd->client;
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Read timestamp and data size */
Stream_Read_UINT16(input_stream, rdpsnd->server_timestamp);
@ -232,9 +232,9 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd,
Stream_Write_UINT16(output_stream, rdpsnd->server_timestamp);
Stream_Write_UINT16(output_stream, data_size);
pthread_mutex_lock(&(client_data->rdp_lock));
pthread_mutex_lock(&(rdp_client->rdp_lock));
svc_plugin_send((rdpSvcPlugin*) rdpsnd, output_stream);
pthread_mutex_unlock(&(client_data->rdp_lock));
pthread_mutex_unlock(&(rdp_client->rdp_lock));
}
@ -245,10 +245,10 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
/* Get associated client data */
guac_client* client = rdpsnd->client;
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get audio stream from client data */
guac_audio_stream* audio = client_data->audio;
guac_audio_stream* audio = rdp_client->audio;
/* Read wave information */
Stream_Read_UINT16(input_stream, rdpsnd->server_timestamp);
@ -283,10 +283,10 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd,
/* Get associated client data */
guac_client* client = rdpsnd->client;
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get audio stream from client data */
guac_audio_stream* audio = client_data->audio;
guac_audio_stream* audio = rdp_client->audio;
/* Wave Confirmation PDU */
wStream* output_stream = Stream_New(NULL, 8);
@ -313,9 +313,9 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd,
Stream_Write_UINT8(output_stream, 0);
/* Send Wave Confirmation PDU */
pthread_mutex_lock(&(client_data->rdp_lock));
pthread_mutex_lock(&(rdp_client->rdp_lock));
svc_plugin_send(plugin, output_stream);
pthread_mutex_unlock(&(client_data->rdp_lock));
pthread_mutex_unlock(&(rdp_client->rdp_lock));
/* We no longer expect to receive wave data */
rdpsnd->next_pdu_is_wave = FALSE;

View File

@ -97,8 +97,9 @@ void guac_svc_process_connect(rdpSvcPlugin* plugin) {
/* Create pipe */
svc->output_pipe = guac_client_alloc_stream(svc->client);
guac_protocol_send_pipe(svc->client->socket, svc->output_pipe,
"application/octet-stream", svc->name);
/* Notify of pipe's existence */
guac_rdp_svc_send_pipe(svc->client->socket, svc);
/* Log connection to static channel */
guac_client_log(svc->client, GUAC_LOG_INFO,

280
src/protocols/rdp/input.c Normal file
View File

@ -0,0 +1,280 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "client.h"
#include "input.h"
#include "rdp.h"
#include "rdp_keymap.h"
#include <freerdp/freerdp.h>
#include <freerdp/input.h>
#include <guacamole/client.h>
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
#include "rdp_disp.h"
#endif
#include <pthread.h>
#include <stdlib.h>
int guac_rdp_send_keysym(guac_client* client, int keysym, int pressed) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
freerdp* rdp_inst = rdp_client->rdp_inst;
/* Skip if not yet connected */
if (rdp_inst == NULL)
return 0;
/* If keysym can be in lookup table */
if (GUAC_RDP_KEYSYM_STORABLE(keysym)) {
int pressed_flags;
/* Look up scancode mapping */
const guac_rdp_keysym_desc* keysym_desc =
&GUAC_RDP_KEYSYM_LOOKUP(rdp_client->keymap, keysym);
/* If defined, send event */
if (keysym_desc->scancode != 0) {
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* If defined, send any prerequesite keys that must be set */
if (keysym_desc->set_keysyms != NULL)
guac_rdp_update_keysyms(client, keysym_desc->set_keysyms, 0, 1);
/* If defined, release any keys that must be cleared */
if (keysym_desc->clear_keysyms != NULL)
guac_rdp_update_keysyms(client, keysym_desc->clear_keysyms, 1, 0);
/* Determine proper event flag for pressed state */
if (pressed)
pressed_flags = KBD_FLAGS_DOWN;
else
pressed_flags = KBD_FLAGS_RELEASE;
/* Send actual key */
rdp_inst->input->KeyboardEvent(rdp_inst->input, keysym_desc->flags | pressed_flags,
keysym_desc->scancode);
/* If defined, release any keys that were originally released */
if (keysym_desc->set_keysyms != NULL)
guac_rdp_update_keysyms(client, keysym_desc->set_keysyms, 0, 0);
/* If defined, send any keys that were originally set */
if (keysym_desc->clear_keysyms != NULL)
guac_rdp_update_keysyms(client, keysym_desc->clear_keysyms, 1, 1);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return 0;
}
}
/* Fall back to unicode events if undefined inside current keymap */
/* Only send when key pressed - Unicode events do not have
* DOWN/RELEASE flags */
if (pressed) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Sending keysym 0x%x as Unicode", keysym);
/* Translate keysym into codepoint */
int codepoint;
if (keysym <= 0xFF)
codepoint = keysym;
else if (keysym >= 0x1000000)
codepoint = keysym & 0xFFFFFF;
else {
guac_client_log(client, GUAC_LOG_DEBUG,
"Unmapped keysym has no equivalent unicode "
"value: 0x%x", keysym);
return 0;
}
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* Send Unicode event */
rdp_inst->input->UnicodeKeyboardEvent(
rdp_inst->input,
0, codepoint);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
}
return 0;
}
void guac_rdp_update_keysyms(guac_client* client, const int* keysym_string, int from, int to) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
int keysym;
/* Send all keysyms in string, NULL terminated */
while ((keysym = *keysym_string) != 0) {
/* Get current keysym state */
int current_state = GUAC_RDP_KEYSYM_LOOKUP(rdp_client->keysym_state, keysym);
/* If key is currently in given state, send event for changing it to specified "to" state */
if (current_state == from)
guac_rdp_send_keysym(client, *keysym_string, to);
/* Next keysym */
keysym_string++;
}
}
int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
freerdp* rdp_inst = rdp_client->rdp_inst;
/* Store current mouse location */
guac_common_cursor_move(rdp_client->display->cursor, user, x, y);
/* Skip if not yet connected */
if (rdp_inst == NULL)
return 0;
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* If button mask unchanged, just send move event */
if (mask == rdp_client->mouse_button_mask)
rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_MOVE, x, y);
/* Otherwise, send events describing button change */
else {
/* Mouse buttons which have JUST become released */
int released_mask = rdp_client->mouse_button_mask & ~mask;
/* Mouse buttons which have JUST become pressed */
int pressed_mask = ~rdp_client->mouse_button_mask & mask;
/* Release event */
if (released_mask & 0x07) {
/* Calculate flags */
int flags = 0;
if (released_mask & 0x01) flags |= PTR_FLAGS_BUTTON1;
if (released_mask & 0x02) flags |= PTR_FLAGS_BUTTON3;
if (released_mask & 0x04) flags |= PTR_FLAGS_BUTTON2;
rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y);
}
/* Press event */
if (pressed_mask & 0x07) {
/* Calculate flags */
int flags = PTR_FLAGS_DOWN;
if (pressed_mask & 0x01) flags |= PTR_FLAGS_BUTTON1;
if (pressed_mask & 0x02) flags |= PTR_FLAGS_BUTTON3;
if (pressed_mask & 0x04) flags |= PTR_FLAGS_BUTTON2;
if (pressed_mask & 0x08) flags |= PTR_FLAGS_WHEEL | 0x78;
if (pressed_mask & 0x10) flags |= PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88;
/* Send event */
rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y);
}
/* Scroll event */
if (pressed_mask & 0x18) {
/* Down */
if (pressed_mask & 0x08)
rdp_inst->input->MouseEvent(
rdp_inst->input,
PTR_FLAGS_WHEEL | 0x78,
x, y);
/* Up */
if (pressed_mask & 0x10)
rdp_inst->input->MouseEvent(
rdp_inst->input,
PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88,
x, y);
}
rdp_client->mouse_button_mask = mask;
}
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return 0;
}
int guac_rdp_user_key_handler(guac_user* user, int keysym, int pressed) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Update keysym state */
if (GUAC_RDP_KEYSYM_STORABLE(keysym))
GUAC_RDP_KEYSYM_LOOKUP(rdp_client->keysym_state, keysym) = pressed;
return guac_rdp_send_keysym(client, keysym, pressed);
}
int guac_rdp_user_size_handler(guac_user* user, int width, int height) {
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
freerdp* rdp_inst = rdp_client->rdp_inst;
/* Skip if not yet connected */
if (rdp_inst == NULL)
return 0;
/* Convert client pixels to remote pixels */
width = width * rdp_client->settings->resolution
/ user->info.optimal_resolution;
height = height * rdp_client->settings->resolution
/ user->info.optimal_resolution;
/* Send display update */
pthread_mutex_lock(&(rdp_client->rdp_lock));
guac_rdp_disp_set_size(rdp_client->disp, rdp_inst->context, width, height);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
#endif
return 0;
}

87
src/protocols/rdp/input.h Normal file
View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUAC_RDP_INPUT_H
#define GUAC_RDP_INPUT_H
#include <guacamole/client.h>
#include <guacamole/user.h>
/**
* Presses or releases the given keysym, sending an appropriate set of key
* events to the RDP server. The key events sent will depend on the current
* keymap.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param keysym
* The keysym being pressed or released.
*
* @param pressed
* Zero if the keysym is being released, non-zero otherwise.
*
* @return
* Zero if the keys were successfully sent, non-zero otherwise.
*/
int guac_rdp_send_keysym(guac_client* client, int keysym, int pressed);
/**
* For every keysym in the given NULL-terminated array of keysyms, update
* the current state of that key conditionally. For each key in the "from"
* state (0 being released and 1 being pressed), that key will be updated
* to the "to" state.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param keysym_string
* A NULL-terminated array of keysyms, each of which will be updated.
*
* @param from
* 0 if the state of currently-released keys should be updated, or 1 if
* the state of currently-pressed keys should be updated.
*
* @param to
* 0 if the keys being updated should be marked as released, or 1 if
* the keys being updated should be marked as pressed.
*/
void guac_rdp_update_keysyms(guac_client* client, const int* keysym_string,
int from, int to);
/**
* Handler for Guacamole user mouse events.
*/
guac_user_mouse_handler guac_rdp_user_mouse_handler;
/**
* Handler for Guacamole user key events.
*/
guac_user_key_handler guac_rdp_user_key_handler;
/**
* Handler for Guacamole user size events.
*/
guac_user_size_handler guac_rdp_user_size_handler;
#endif

932
src/protocols/rdp/rdp.c Normal file
View File

@ -0,0 +1,932 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "client.h"
#include "guac_cursor.h"
#include "guac_display.h"
#include "rdp.h"
#include "rdp_bitmap.h"
#include "rdp_cliprdr.h"
#include "rdp_gdi.h"
#include "rdp_glyph.h"
#include "rdp_keymap.h"
#include "rdp_pointer.h"
#include "rdp_rail.h"
#include "rdp_stream.h"
#include "rdp_svc.h"
#ifdef ENABLE_COMMON_SSH
#include <guac_sftp.h>
#include <guac_ssh.h>
#include <guac_ssh_user.h>
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
#include "rdp_disp.h"
#endif
#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/freerdp.h>
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/timestamp.h>
#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H
#include <freerdp/client/cliprdr.h>
#else
#include "compat/client-cliprdr.h"
#endif
#ifdef HAVE_FREERDP_CLIENT_DISP_H
#include <freerdp/client/disp.h>
#endif
#ifdef HAVE_FREERDP_EVENT_PUBSUB
#include <freerdp/event.h>
#endif
#ifdef LEGACY_FREERDP
#include "compat/rail.h"
#else
#include <freerdp/rail.h>
#endif
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h>
#else
#include "compat/winpr-wtypes.h"
#endif
#ifdef HAVE_FREERDP_ADDIN_H
#include <freerdp/addin.h>
#endif
#ifdef HAVE_FREERDP_CLIENT_CHANNELS_H
#include <freerdp/client/channels.h>
#endif
#ifdef HAVE_FREERDP_VERSION_H
#include <freerdp/version.h>
#endif
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
/**
* Callback invoked by FreeRDP for data received along a channel. This is the
* most recent version of the callback and uses a 16-bit unsigned integer for
* the channel ID, as well as different type naming for the datatype of the
* data itself. This function does nothing more than invoke
* freerdp_channels_data() with the given arguments. The prototypes of these
* functions are compatible in 1.2 and later, but not necessarily prior to
* that, hence the conditional compilation of differing prototypes.
*
* Beware that the official purpose of these parameters is an undocumented
* mystery. The meanings below are derived from looking at how the function is
* used within FreeRDP.
*
* @param rdp_inst
* The RDP client instance associated with the channel receiving the data.
*
* @param channelId
* The integer ID of the channel that received the data.
*
* @param data
* A buffer containing the received data.
*
* @param size
* The number of bytes received and contained in the given buffer (the
* number of bytes received within the PDU that resulted in this function
* being inboked).
*
* @param flags
* Channel control flags, as defined by the CHANNEL_PDU_HEADER in the RDP
* specification.
*
* @param total_size
* The total length of the chanel data being received, which may span
* multiple PDUs (see the "length" field of CHANNEL_PDU_HEADER).
*
* @return
* Zero if the received channel data was successfully handled, non-zero
* otherwise. Note that this return value is discarded in practice.
*/
#if defined(FREERDP_VERSION_MAJOR) \
&& (FREERDP_VERSION_MAJOR > 1 || FREERDP_VERSION_MINOR >= 2)
static int __guac_receive_channel_data(freerdp* rdp_inst, UINT16 channelId,
BYTE* data, int size, int flags, int total_size) {
#else
static int __guac_receive_channel_data(freerdp* rdp_inst, int channelId,
UINT8* data, int size, int flags, int total_size) {
#endif
return freerdp_channels_data(rdp_inst, channelId,
data, size, flags, total_size);
}
#ifdef HAVE_FREERDP_EVENT_PUBSUB
/**
* Called whenever a channel connects via the PubSub event system within
* FreeRDP.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param e
* Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP.
*/
static void guac_rdp_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* e) {
#ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL
/* Store reference to the display update plugin once it's connected */
if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) {
DispClientContext* disp = (DispClientContext*) e->pInterface;
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Init module with current display size */
guac_rdp_disp_set_size(rdp_client->disp, context,
guac_rdp_get_width(context->instance),
guac_rdp_get_height(context->instance));
/* Store connected channel */
guac_rdp_disp_connect(rdp_client->disp, disp);
guac_client_log(client, GUAC_LOG_DEBUG,
"Display update channel connected.");
}
#endif
}
#endif
BOOL rdp_freerdp_pre_connect(freerdp* instance) {
rdpContext* context = instance->context;
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdpChannels* channels = context->channels;
rdpBitmap* bitmap;
rdpGlyph* glyph;
rdpPointer* pointer;
rdpPrimaryUpdate* primary;
CLRCONV* clrconv;
guac_rdp_client* rdp_client =
(guac_rdp_client*) client->data;
#ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER
/* Init FreeRDP add-in provider */
freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0);
#endif
#ifdef HAVE_FREERDP_EVENT_PUBSUB
/* Subscribe to and handle channel connected events */
PubSub_SubscribeChannelConnected(context->pubSub,
(pChannelConnectedEventHandler) guac_rdp_channel_connected);
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/* Load virtual channel management plugin */
if (freerdp_channels_load_plugin(channels, instance->settings,
"drdynvc", instance->settings))
guac_client_log(client, GUAC_LOG_WARNING,
"Failed to load drdynvc plugin.");
/* Init display update plugin */
rdp_client->disp = guac_rdp_disp_alloc();
guac_rdp_disp_load_plugin(instance->context);
#endif
/* Load clipboard plugin */
if (freerdp_channels_load_plugin(channels, instance->settings,
"cliprdr", NULL))
guac_client_log(client, GUAC_LOG_WARNING,
"Failed to load cliprdr plugin. Clipboard will not work.");
/* If audio enabled, choose an encoder */
if (rdp_client->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 (rdp_client->settings->drive_enabled)
rdp_client->filesystem =
guac_rdp_fs_alloc(client, rdp_client->settings->drive_path,
rdp_client->settings->create_drive_path);
/* If RDPSND/RDPDR required, load them */
if (rdp_client->settings->printing_enabled
|| rdp_client->settings->drive_enabled
|| rdp_client->settings->audio_enabled) {
/* Load RDPDR plugin */
if (freerdp_channels_load_plugin(channels, instance->settings,
"guacdr", client))
guac_client_log(client, GUAC_LOG_WARNING,
"Failed to load guacdr plugin. Drive redirection and "
"printing will not work. Sound MAY not work.");
/* Load RDPSND plugin */
if (freerdp_channels_load_plugin(channels, instance->settings,
"guacsnd", client))
guac_client_log(client, GUAC_LOG_WARNING,
"Failed to load guacsnd alongside guacdr plugin. Sound "
"will not work. Drive redirection and printing MAY not "
"work.");
}
/* Load RAIL plugin if RemoteApp in use */
if (rdp_client->settings->remote_app != NULL) {
#ifdef LEGACY_FREERDP
RDP_PLUGIN_DATA* plugin_data = malloc(sizeof(RDP_PLUGIN_DATA) * 2);
plugin_data[0].size = sizeof(RDP_PLUGIN_DATA);
plugin_data[0].data[0] = rdp_client->settings->remote_app;
plugin_data[0].data[1] = rdp_client->settings->remote_app_dir;
plugin_data[0].data[2] = rdp_client->settings->remote_app_args;
plugin_data[0].data[3] = NULL;
plugin_data[1].size = 0;
/* Attempt to load rail */
if (freerdp_channels_load_plugin(channels, instance->settings,
"rail", plugin_data))
guac_client_log(client, GUAC_LOG_WARNING,
"Failed to load rail plugin. RemoteApp will not work.");
#else
/* Attempt to load rail */
if (freerdp_channels_load_plugin(channels, instance->settings,
"rail", instance->settings))
guac_client_log(client, GUAC_LOG_WARNING,
"Failed to load rail plugin. RemoteApp will not work.");
#endif
}
/* Load SVC plugin instances for all static channels */
if (rdp_client->settings->svc_names != NULL) {
char** current = rdp_client->settings->svc_names;
do {
guac_rdp_svc* svc = guac_rdp_alloc_svc(client, *current);
/* Attempt to load guacsvc plugin for new static channel */
if (freerdp_channels_load_plugin(channels, instance->settings,
"guacsvc", svc)) {
guac_client_log(client, GUAC_LOG_WARNING,
"Cannot create static channel \"%s\": failed to load guacsvc plugin.",
svc->name);
guac_rdp_free_svc(svc);
}
/* Store and log on success */
else {
guac_rdp_add_svc(client, svc);
guac_client_log(client, GUAC_LOG_INFO, "Created static channel \"%s\"...",
svc->name);
}
} while (*(++current) != NULL);
}
/* Init color conversion structure */
clrconv = calloc(1, sizeof(CLRCONV));
clrconv->alpha = 1;
clrconv->invert = 0;
clrconv->rgb555 = 0;
clrconv->palette = calloc(1, sizeof(rdpPalette));
((rdp_freerdp_context*) context)->clrconv = clrconv;
/* Init FreeRDP cache */
instance->context->cache = cache_new(instance->settings);
/* Set up bitmap handling */
bitmap = calloc(1, sizeof(rdpBitmap));
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->Decompress = guac_rdp_bitmap_decompress;
bitmap->SetSurface = guac_rdp_bitmap_setsurface;
graphics_register_bitmap(context->graphics, bitmap);
free(bitmap);
/* Set up glyph handling */
glyph = calloc(1, sizeof(rdpGlyph));
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(context->graphics, glyph);
free(glyph);
/* Set up pointer handling */
pointer = calloc(1, sizeof(rdpPointer));
pointer->size = sizeof(guac_rdp_pointer);
pointer->New = guac_rdp_pointer_new;
pointer->Free = guac_rdp_pointer_free;
pointer->Set = guac_rdp_pointer_set;
#ifdef HAVE_RDPPOINTER_SETNULL
pointer->SetNull = guac_rdp_pointer_set_null;
#endif
#ifdef HAVE_RDPPOINTER_SETDEFAULT
pointer->SetDefault = guac_rdp_pointer_set_default;
#endif
graphics_register_pointer(context->graphics, pointer);
free(pointer);
/* Set up GDI */
instance->update->DesktopResize = guac_rdp_gdi_desktop_resize;
instance->update->EndPaint = guac_rdp_gdi_end_paint;
instance->update->Palette = guac_rdp_gdi_palette_update;
instance->update->SetBounds = guac_rdp_gdi_set_bounds;
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);
/* Init channels (pre-connect) */
if (freerdp_channels_pre_connect(channels, instance)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error initializing RDP client channel manager");
return FALSE;
}
return TRUE;
}
/**
* Callback invoked by FreeRDP just after the connection is established with
* the RDP server. Implementations are required to manually invoke
* freerdp_channels_post_connect().
*
* @param instance
* The FreeRDP instance that has just connected.
*
* @return
* TRUE if successful, FALSE if an error occurs.
*/
static BOOL rdp_freerdp_post_connect(freerdp* instance) {
rdpContext* context = instance->context;
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdpChannels* channels = instance->context->channels;
/* Init channels (post-connect) */
if (freerdp_channels_post_connect(channels, instance)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error initializing RDP client channel manager");
return FALSE;
}
return TRUE;
}
/**
* 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,
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;
}
/**
* 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.
*/
static BOOL rdp_freerdp_verify_certificate(freerdp* instance, char* subject,
char* issuer, char* fingerprint) {
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 TRUE;
}
guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed");
return FALSE;
}
/**
* Callback invoked by FreeRDP after a new rdpContext has been allocated and
* associated with the current FreeRDP instance. Implementations are required
* to manually invoke freerdp_channels_new() at this point.
*
* @param instance
* The FreeRDP instance whose context has just been allocated.
*
* @param context
* The newly-allocated FreeRDP context.
*/
static void rdp_freerdp_context_new(freerdp* instance, rdpContext* context) {
context->channels = freerdp_channels_new();
}
/**
* Callback invoked by FreeRDP when the rdpContext is being freed. This must be
* provided, but there is no Guacamole-specific data associated with the
* FreeRDP context, so nothing is done here.
*
* @param instance
* The FreeRDP instance whose context is being freed.
*
* @param context
* The FreeRDP context being freed.
*/
static void rdp_freerdp_context_free(freerdp* instance, rdpContext* context) {
/* EMPTY */
}
/**
* Loads all keysym/scancode mappings declared within the given keymap and its
* parent keymap, if any. These mappings are stored within the guac_rdp_client
* structure associated with the given guac_client for future use in
* translating keysyms to the scancodes required by RDP key events.
*
* @param client
* The guac_client whose associated guac_rdp_client should be initialized
* with the keysym/scancode mapping defined in the given keymap.
*
* @param keymap
* The keymap to use to populate the given client's keysym/scancode
* mapping.
*/
static void __guac_rdp_client_load_keymap(guac_client* client,
const guac_rdp_keymap* keymap) {
guac_rdp_client* rdp_client =
(guac_rdp_client*) client->data;
/* Get mapping */
const guac_rdp_keysym_desc* mapping = keymap->mapping;
/* If parent exists, load parent first */
if (keymap->parent != NULL)
__guac_rdp_client_load_keymap(client, keymap->parent);
/* Log load */
guac_client_log(client, GUAC_LOG_INFO, "Loading keymap \"%s\"", keymap->name);
/* Load mapping into keymap */
while (mapping->keysym != 0) {
/* Copy mapping */
GUAC_RDP_KEYSYM_LOOKUP(rdp_client->keymap, mapping->keysym) =
*mapping;
/* Next keysym */
mapping++;
}
}
/**
* Waits for messages from the RDP server for the given number of microseconds.
*
* @param client
* The client associated with the current RDP session.
*
* @param timeout_usecs
* The maximum amount of time to wait, in microseconds.
*
* @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_usecs) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
freerdp* rdp_inst = rdp_client->rdp_inst;
rdpChannels* channels = rdp_inst->context->channels;
int result;
int index;
int max_fd, fd;
void* read_fds[32];
void* write_fds[32];
int read_count = 0;
int write_count = 0;
fd_set rfds, wfds;
struct timeval timeout = {
.tv_sec = 0,
.tv_usec = timeout_usecs
};
/* Get RDP fds */
if (!freerdp_get_fds(rdp_inst, read_fds, &read_count, write_fds, &write_count)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to read RDP file descriptors.");
return -1;
}
/* Get channel fds */
if (!freerdp_channels_get_fds(channels, rdp_inst, read_fds, &read_count, write_fds,
&write_count)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to read RDP channel file descriptors.");
return -1;
}
/* Construct read fd_set */
max_fd = 0;
FD_ZERO(&rfds);
for (index = 0; index < read_count; index++) {
fd = (int)(long) (read_fds[index]);
if (fd > max_fd)
max_fd = fd;
FD_SET(fd, &rfds);
}
/* Construct write fd_set */
FD_ZERO(&wfds);
for (index = 0; index < write_count; index++) {
fd = (int)(long) (write_fds[index]);
if (fd > max_fd)
max_fd = fd;
FD_SET(fd, &wfds);
}
/* If no file descriptors, error */
if (max_fd == 0) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "No file descriptors associated with RDP connection.");
return -1;
}
/* Wait for all RDP file descriptors */
result = select(max_fd + 1, &rfds, &wfds, NULL, &timeout);
if (result < 0) {
/* If error ignorable, pretend timout occurred */
if (errno == EAGAIN
|| errno == EWOULDBLOCK
|| errno == EINPROGRESS
|| errno == EINTR)
return 0;
/* Otherwise, return as error */
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error waiting for file descriptor.");
return -1;
}
/* Return wait result */
return result;
}
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;
/* Init random number generator */
srandom(time(NULL));
/* 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;
#ifdef HAVE_FREERDP_CHANNELS_GLOBAL_INIT
freerdp_channels_global_init();
#endif
/* Init client */
freerdp* rdp_inst = freerdp_new();
rdp_inst->PreConnect = rdp_freerdp_pre_connect;
rdp_inst->PostConnect = rdp_freerdp_post_connect;
rdp_inst->Authenticate = rdp_freerdp_authenticate;
rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate;
rdp_inst->ReceiveChannelData = __guac_receive_channel_data;
/* Allocate FreeRDP context */
#ifdef LEGACY_FREERDP
rdp_inst->context_size = sizeof(rdp_freerdp_context);
#else
rdp_inst->ContextSize = sizeof(rdp_freerdp_context);
#endif
rdp_inst->ContextNew = (pContextNew) rdp_freerdp_context_new;
rdp_inst->ContextFree = (pContextFree) rdp_freerdp_context_free;
freerdp_context_new(rdp_inst);
((rdp_freerdp_context*) rdp_inst->context)->client = client;
/* Load keymap into client */
__guac_rdp_client_load_keymap(client, settings->server_layout);
#ifdef ENABLE_COMMON_SSH
guac_common_ssh_init(client);
/* Connect via SSH if SFTP is enabled */
if (settings->enable_sftp) {
/* Abort if username is missing */
if (settings->sftp_username == NULL)
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_common_ssh_destroy_user(rdp_client->sftp_user);
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);
/* Fail if SSH connection does not succeed */
if (rdp_client->sftp_session == NULL) {
/* Already aborted within guac_common_ssh_create_session() */
guac_common_ssh_destroy_user(rdp_client->sftp_user);
return NULL;
}
/* Load and expose filesystem */
rdp_client->sftp_filesystem =
guac_common_ssh_create_sftp_filesystem(
rdp_client->sftp_session, "/");
/* 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_common_ssh_destroy_session(rdp_client->sftp_session);
guac_common_ssh_destroy_user(rdp_client->sftp_user);
return NULL;
}
guac_client_log(client, GUAC_LOG_DEBUG,
"SFTP connection succeeded.");
}
#endif
/* Send connection name */
guac_protocol_send_name(client->socket, settings->hostname);
/* Set default pointer */
guac_common_cursor_set_pointer(rdp_client->display->cursor);
/* Push desired settings to FreeRDP */
guac_rdp_push_settings(settings, rdp_inst);
/* Connect to RDP server */
if (!freerdp_connect(rdp_inst)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Error connecting to RDP server");
return NULL;
}
/* Connection complete */
rdp_client->rdp_inst = rdp_inst;
rdpChannels* channels = rdp_inst->context->channels;
/* Handle messages from RDP server while client is running */
while (client->state == GUAC_CLIENT_RUNNING) {
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/* Update remote display size */
pthread_mutex_lock(&(rdp_client->rdp_lock));
guac_rdp_disp_update_size(rdp_client->disp, rdp_inst->context);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
#endif
/* Wait for data and construct a reasonable frame */
int wait_result = rdp_guac_client_wait_for_messages(client,
GUAC_RDP_FRAME_START_TIMEOUT);
guac_timestamp frame_start = guac_timestamp_current();
while (wait_result > 0) {
guac_timestamp frame_end;
int frame_remaining;
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* Check the libfreerdp fds */
if (!freerdp_check_fds(rdp_inst)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Error handling RDP file descriptors");
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return NULL;
}
/* Check channel fds */
if (!freerdp_channels_check_fds(channels, rdp_inst)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Error handling RDP channel file descriptors");
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return NULL;
}
/* Check for channel events */
wMessage* event = freerdp_channels_pop_event(channels);
if (event) {
/* Handle channel events (clipboard and RAIL) */
#ifdef LEGACY_EVENT
if (event->event_class == CliprdrChannel_Class)
guac_rdp_process_cliprdr_event(client, event);
else if (event->event_class == RailChannel_Class)
guac_rdp_process_rail_event(client, event);
#else
if (GetMessageClass(event->id) == CliprdrChannel_Class)
guac_rdp_process_cliprdr_event(client, event);
else if (GetMessageClass(event->id) == RailChannel_Class)
guac_rdp_process_rail_event(client, event);
#endif
freerdp_event_free(event);
}
/* Handle RDP disconnect */
if (freerdp_shall_disconnect(rdp_inst)) {
guac_client_log(client, GUAC_LOG_INFO,
"RDP server closed connection");
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return NULL;
}
pthread_mutex_unlock(&(rdp_client->rdp_lock));
/* Calculate time remaining in frame */
frame_end = guac_timestamp_current();
frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION - frame_end;
/* Wait again if frame remaining */
if (frame_remaining > 0)
wait_result = rdp_guac_client_wait_for_messages(client,
GUAC_RDP_FRAME_TIMEOUT*1000);
else
break;
}
/* If an error occurred, fail */
if (wait_result < 0)
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
"Connection closed.");
/* End of frame */
guac_common_display_flush(rdp_client->display);
guac_client_end_frame(client);
guac_socket_flush(client->socket);
}
guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected");
return NULL;
}

219
src/protocols/rdp/rdp.h Normal file
View File

@ -0,0 +1,219 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUAC_RDP_H
#define GUAC_RDP_H
#include "config.h"
#include "guac_clipboard.h"
#include "guac_display.h"
#include "guac_surface.h"
#include "guac_list.h"
#include "rdp_fs.h"
#include "rdp_keymap.h"
#include "rdp_settings.h"
#include <freerdp/freerdp.h>
#include <freerdp/codec/color.h>
#include <guacamole/audio.h>
#include <guacamole/client.h>
#ifdef ENABLE_COMMON_SSH
#include "guac_sftp.h"
#include "guac_ssh.h"
#include "guac_ssh_user.h"
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
#include "rdp_disp.h"
#endif
#include <pthread.h>
#include <stdint.h>
/**
* RDP-specific client data.
*/
typedef struct guac_rdp_client {
/**
* The RDP client thread.
*/
pthread_t client_thread;
/**
* Pointer to the FreeRDP client instance handling the current connection.
*/
freerdp* rdp_inst;
/**
* All settings associated with the current or pending RDP connection.
*/
guac_rdp_settings* settings;
/**
* Button mask containing the OR'd value of all currently pressed buttons.
*/
int mouse_button_mask;
/**
* Foreground color for any future glyphs.
*/
uint32_t glyph_color;
/**
* The display.
*/
guac_common_display* display;
/**
* The surface that GDI operations should draw to. RDP messages exist which
* change this surface to allow drawing to occur off-screen.
*/
guac_common_surface* current_surface;
/**
* The keymap to use when translating keysyms into scancodes or sequences
* of scancodes for RDP.
*/
guac_rdp_static_keymap keymap;
/**
* The state of all keys, based on whether events for pressing/releasing
* particular keysyms have been received. This is necessary in order to
* determine which keys must be released/pressed when a particular
* keysym can only be typed through a sequence of scancodes (such as
* an Alt-code) because the server-side keymap does not support that
* keysym.
*/
guac_rdp_keysym_state_map keysym_state;
/**
* The current clipboard contents.
*/
guac_common_clipboard* clipboard;
/**
* The format of the clipboard which was requested. Data received from
* the RDP server should conform to this format. This will be one of
* several legal clipboard format values defined within FreeRDP, such as
* CB_FORMAT_TEXT.
*/
int requested_clipboard_format;
/**
* Audio output, if any.
*/
guac_audio_stream* audio;
/**
* The filesystem being shared, if any.
*/
guac_rdp_fs* filesystem;
#ifdef ENABLE_COMMON_SSH
/**
* The user and credentials used to authenticate for SFTP.
*/
guac_common_ssh_user* sftp_user;
/**
* The SSH session used for SFTP.
*/
guac_common_ssh_session* sftp_session;
/**
* An SFTP-based filesystem.
*/
guac_common_ssh_sftp_filesystem* sftp_filesystem;
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/**
* Display size update module.
*/
guac_rdp_disp* disp;
#endif
/**
* List of all available static virtual channels.
*/
guac_common_list* available_svc;
/**
* Lock which is locked and unlocked for each RDP message.
*/
pthread_mutex_t rdp_lock;
/**
* Common attributes for locks.
*/
pthread_mutexattr_t attributes;
} guac_rdp_client;
/**
* Client data that will remain accessible through the RDP context.
* This should generally include data commonly used by FreeRDP handlers.
*/
typedef struct rdp_freerdp_context {
/**
* The parent context. THIS MUST BE THE FIRST ELEMENT.
*/
rdpContext _p;
/**
* Pointer to the guac_client instance handling the RDP connection with
* this context.
*/
guac_client* client;
/**
* Color conversion structure to be used to convert RDP images to PNGs.
*/
CLRCONV* clrconv;
/**
* The current color palette, as received from the RDP server.
*/
UINT32 palette[256];
} rdp_freerdp_context;
/**
* RDP client thread. This thread runs throughout the duration of the client,
* existing as a single instance, shared by all users.
*
* @param data
* The guac_client to associate with an RDP session, once the RDP
* connection succeeds.
*
* @return
* NULL in all cases. The return value of this thread is expected to be
* ignored.
*/
void* guac_rdp_client_thread(void* data);
#endif

View File

@ -23,7 +23,9 @@
#include "config.h"
#include "client.h"
#include "guac_display.h"
#include "guac_surface.h"
#include "rdp.h"
#include "rdp_bitmap.h"
#include "rdp_settings.h"
@ -46,12 +48,11 @@
void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_socket* socket = client->socket;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Allocate surface */
guac_layer* buffer = guac_client_alloc_buffer(client);
guac_common_surface* surface = guac_common_surface_alloc(client, socket,
buffer, bitmap->width, bitmap->height);
/* Allocate buffer */
guac_common_display_layer* buffer = guac_common_display_alloc_buffer(
rdp_client->display, bitmap->width, bitmap->height);
/* Cache image data if present */
if (bitmap->data != NULL) {
@ -62,7 +63,7 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) {
bitmap->width, bitmap->height, 4*bitmap->width);
/* Send surface to buffer */
guac_common_surface_draw(surface, 0, 0, image);
guac_common_surface_draw(buffer->surface, 0, 0, image);
/* Free surface */
cairo_surface_destroy(image);
@ -70,8 +71,7 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) {
}
/* Store buffer reference in bitmap */
((guac_rdp_bitmap*) bitmap)->buffer = buffer;
((guac_rdp_bitmap*) bitmap)->surface = surface;
((guac_rdp_bitmap*) bitmap)->layer = buffer;
}
@ -101,8 +101,7 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) {
}
/* No corresponding surface yet - caching is deferred. */
((guac_rdp_bitmap*) bitmap)->buffer = NULL;
((guac_rdp_bitmap*) bitmap)->surface = NULL;
((guac_rdp_bitmap*) bitmap)->layer = NULL;
/* Start at zero usage */
((guac_rdp_bitmap*) bitmap)->used = 0;
@ -112,21 +111,22 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) {
void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_surface* surface = ((guac_rdp_bitmap*) bitmap)->surface;
guac_common_display_layer* buffer = ((guac_rdp_bitmap*) bitmap)->layer;
int width = bitmap->right - bitmap->left + 1;
int height = bitmap->bottom - bitmap->top + 1;
/* If not cached, cache if necessary */
if (surface == NULL && ((guac_rdp_bitmap*) bitmap)->used >= 1)
if (buffer == NULL && ((guac_rdp_bitmap*) bitmap)->used >= 1)
guac_rdp_cache_bitmap(context, bitmap);
/* If cached, retrieve from cache */
if (surface != NULL)
guac_common_surface_copy(surface, 0, 0, width, height,
client_data->default_surface, bitmap->left, bitmap->top);
if (buffer != NULL)
guac_common_surface_copy(buffer->surface, 0, 0, width, height,
rdp_client->display->default_surface,
bitmap->left, bitmap->top);
/* Otherwise, draw with stored image data */
else if (bitmap->data != NULL) {
@ -137,7 +137,8 @@ void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) {
width, height, 4*bitmap->width);
/* Draw image on default surface */
guac_common_surface_draw(client_data->default_surface, bitmap->left, bitmap->top, image);
guac_common_surface_draw(rdp_client->display->default_surface,
bitmap->left, bitmap->top, image);
/* Free surface */
cairo_surface_destroy(image);
@ -152,26 +153,22 @@ void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) {
void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_layer* buffer = ((guac_rdp_bitmap*) bitmap)->buffer;
guac_common_surface* surface = ((guac_rdp_bitmap*) bitmap)->surface;
/* If cached, free surface */
if (surface != NULL)
guac_common_surface_free(surface);
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_display_layer* buffer = ((guac_rdp_bitmap*) bitmap)->layer;
/* If cached, free buffer */
if (buffer != NULL)
guac_client_free_buffer(client, buffer);
guac_common_display_free_buffer(rdp_client->display, buffer);
}
void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
if (primary)
client_data->current_surface = client_data->default_surface;
rdp_client->current_surface = rdp_client->display->default_surface;
else {
@ -182,10 +179,11 @@ void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL pri
}
/* If not available as a surface, make available. */
if (((guac_rdp_bitmap*) bitmap)->surface == NULL)
if (((guac_rdp_bitmap*) bitmap)->layer == NULL)
guac_rdp_cache_bitmap(context, bitmap);
client_data->current_surface = ((guac_rdp_bitmap*) bitmap)->surface;
rdp_client->current_surface =
((guac_rdp_bitmap*) bitmap)->layer->surface;
}

View File

@ -25,7 +25,7 @@
#define _GUAC_RDP_RDP_BITMAP_H
#include "config.h"
#include "guac_surface.h"
#include "guac_display.h"
#include <freerdp/freerdp.h>
#include <guacamole/layer.h>
@ -36,6 +36,9 @@
#include "compat/winpr-wtypes.h"
#endif
/**
* Guacamole-specific rdpBitmap data.
*/
typedef struct guac_rdp_bitmap {
/**
@ -44,14 +47,9 @@ typedef struct guac_rdp_bitmap {
rdpBitmap bitmap;
/**
* The allocated buffer which backs this bitmap.
* Layer containing cached image data.
*/
guac_layer* buffer;
/**
* Surface containing cached image data.
*/
guac_common_surface* surface;
guac_common_display_layer* layer;
/**
* The number of times a bitmap has been used.
@ -60,18 +58,149 @@ typedef struct guac_rdp_bitmap {
} guac_rdp_bitmap;
/**
* Caches the given bitmap immediately, storing its data in a remote Guacamole
* buffer. As RDP bitmaps are frequently created, used once, and immediately
* destroyed, we defer actual remote-side caching of RDP bitmaps until they are
* used at least once.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param bitmap
* The bitmap to cache.
*/
void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap);
/**
* Initializes the given newly-created rdpBitmap.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param bitmap
* The bitmap to initialize.
*/
void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap);
/**
* Paints the given rdpBitmap on the primary display surface. Note that this
* operation does NOT draw to the "current" surface set by calls to
* guac_rdp_bitmap_setsurface().
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param bitmap
* The bitmap to paint. This structure will also contain the specifics of
* the paint operation to perform, including the destination X/Y
* coordinates.
*/
void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap);
/**
* Frees any Guacamole-specific data associated with the given rdpBitmap.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param bitmap
* The bitmap whose Guacamole-specific data is to be freed.
*/
void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap);
void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary);
/**
* Sets the given rdpBitmap as the drawing surface for future operations or,
* if the primary flag is set, resets the current drawing surface to the
* primary drawing surface of the remote display.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param bitmap
* The rdpBitmap to set as the current drawing surface. This parameter is
* only valid if the primary flag is FALSE.
*
* @param primary
* TRUE if the bitmap parameter should be ignored, and the current drawing
* surface should be reset to the primary drawing surface of the remote
* display, FALSE otherwise.
*/
void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap,
BOOL primary);
#ifdef LEGACY_RDPBITMAP
void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, UINT8* data,
int width, int height, int bpp, int length, BOOL compressed);
/**
* Decompresses or copies the given image data, storing the result within the
* given bitmap, depending on the compressed flag. Note that even if the
* received data is not compressed, it is the duty of this function to also
* flip received data, if the row order is backwards.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param bitmap
* The bitmap in which the decompressed/copied data should be stored.
*
* @param data
* Possibly-compressed image data.
*
* @param width
* The width of the image data, in pixels.
*
* @param height
* The height of the image data, in pixels.
*
* @param bpp
* The number of bits per pixel in the image data.
*
* @param length
* The length of the image data, in bytes.
*
* @param compressed
* TRUE if the image data is compressed, FALSE otherwise.
*/
void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap,
UINT8* data, int width, int height, int bpp, int length,
BOOL compressed);
#else
void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, UINT8* data,
int width, int height, int bpp, int length, BOOL compressed, int codec_id);
/**
* Decompresses or copies the given image data, storing the result within the
* given bitmap, depending on the compressed flag. Note that even if the
* received data is not compressed, it is the duty of this function to also
* flip received data, if the row order is backwards.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param bitmap
* The bitmap in which the decompressed/copied data should be stored.
*
* @param data
* Possibly-compressed image data.
*
* @param width
* The width of the image data, in pixels.
*
* @param height
* The height of the image data, in pixels.
*
* @param bpp
* The number of bits per pixel in the image data.
*
* @param length
* The length of the image data, in bytes.
*
* @param compressed
* TRUE if the image data is compressed, FALSE otherwise.
*
* @param codec_id
* The ID of the codec used to compress the image data. This parameter is
* currently ignored.
*/
void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap,
UINT8* data, int width, int height, int bpp, int length,
BOOL compressed, int codec_id);
#endif
#endif

View File

@ -23,6 +23,7 @@
#include "config.h"
#include "client.h"
#include "rdp.h"
#include "rdp_cliprdr.h"
#include "guac_clipboard.h"
#include "guac_iconv.h"
@ -92,7 +93,7 @@ void guac_rdp_process_cliprdr_event(guac_client* client, wMessage* event) {
void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event) {
rdpChannels* channels =
((rdp_guac_client_data*) client->data)->rdp_inst->context->channels;
((guac_rdp_client*) client->data)->rdp_inst->context->channels;
RDP_CB_FORMAT_LIST_EVENT* format_list =
(RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(
@ -114,11 +115,18 @@ void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event) {
/**
* Sends a clipboard data request for the given format.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param format
* The clipboard format to request. This format must be one of the
* documented values used by the CLIPRDR channel for clipboard format IDs.
*/
static void __guac_rdp_cb_request_format(guac_client* client, int format) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
rdpChannels* channels = client_data->rdp_inst->context->channels;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
rdpChannels* channels = rdp_client->rdp_inst->context->channels;
/* Create new data request */
RDP_CB_DATA_REQUEST_EVENT* data_request =
@ -128,7 +136,7 @@ static void __guac_rdp_cb_request_format(guac_client* client, int format) {
NULL, NULL);
/* Set to requested format */
client_data->requested_clipboard_format = format;
rdp_client->requested_clipboard_format = format;
data_request->format = format;
/* Send request */
@ -174,11 +182,11 @@ void guac_rdp_process_cb_format_list(guac_client* client,
void guac_rdp_process_cb_data_request(guac_client* client,
RDP_CB_DATA_REQUEST_EVENT* event) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
rdpChannels* channels = client_data->rdp_inst->context->channels;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
rdpChannels* channels = rdp_client->rdp_inst->context->channels;
guac_iconv_write* writer;
const char* input = client_data->clipboard->buffer;
const char* input = rdp_client->clipboard->buffer;
char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH);
RDP_CB_DATA_RESPONSE_EVENT* data_response;
@ -209,7 +217,7 @@ void guac_rdp_process_cb_data_request(guac_client* client,
/* Set data and size */
data_response->data = (BYTE*) output;
guac_iconv(GUAC_READ_UTF8, &input, client_data->clipboard->length,
guac_iconv(GUAC_READ_UTF8, &input, rdp_client->clipboard->length,
writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH);
data_response->size = ((BYTE*) output) - data_response->data;
@ -221,7 +229,7 @@ void guac_rdp_process_cb_data_request(guac_client* client,
void guac_rdp_process_cb_data_response(guac_client* client,
RDP_CB_DATA_RESPONSE_EVENT* event) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH];
guac_iconv_read* reader;
@ -229,7 +237,7 @@ void guac_rdp_process_cb_data_response(guac_client* client,
char* output = received_data;
/* Find correct source encoding */
switch (client_data->requested_clipboard_format) {
switch (rdp_client->requested_clipboard_format) {
/* Non-Unicode */
case CB_FORMAT_TEXT:
@ -244,7 +252,7 @@ void guac_rdp_process_cb_data_response(guac_client* client,
default:
guac_client_log(client, GUAC_LOG_ERROR, "Requested clipboard data in "
"unsupported format %i",
client_data->requested_clipboard_format);
rdp_client->requested_clipboard_format);
return;
}
@ -254,9 +262,9 @@ void guac_rdp_process_cb_data_response(guac_client* client,
GUAC_WRITE_UTF8, &output, sizeof(received_data))) {
int length = strnlen(received_data, sizeof(received_data));
guac_common_clipboard_reset(client_data->clipboard, "text/plain");
guac_common_clipboard_append(client_data->clipboard, received_data, length);
guac_common_clipboard_send(client_data->clipboard, client);
guac_common_clipboard_reset(rdp_client->clipboard, "text/plain");
guac_common_clipboard_append(rdp_client->clipboard, received_data, length);
guac_common_clipboard_send(rdp_client->clipboard, client);
}

View File

@ -50,15 +50,72 @@
*/
#define GUAC_RDP_CLIPBOARD_FORMAT_UTF16 2
/**
* Called within the main RDP connection thread whenever a CLIPRDR message is
* received. This function will dispatch that message to an appropriate
* function, specific to that message type.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param event
* The received CLIPRDR message.
*/
void guac_rdp_process_cliprdr_event(guac_client* client, wMessage* event);
/**
* Handles the given CLIPRDR event, which MUST be a Monitor Ready event. It
* is the responsibility of this function to respond to the Monitor Ready
* event with a list of supported clipboard formats.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param event
* The received CLIPRDR message, which must be a Monitor Ready event.
*/
void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event);
/**
* Handles the given CLIPRDR event, which MUST be a Format List event. It
* is the responsibility of this function to respond to the Format List
* event with a request for clipboard data in one of the enumerated formats.
* This event is fired whenever remote clipboard data is available.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param event
* The received CLIPRDR message, which must be a Format List event.
*/
void guac_rdp_process_cb_format_list(guac_client* client,
RDP_CB_FORMAT_LIST_EVENT* event);
/**
* Handles the given CLIPRDR event, which MUST be a Data Request event. It
* is the responsibility of this function to respond to the Data Request
* event with a data response containing the current clipoard contents.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param event
* The received CLIPRDR message, which must be a Data Request event.
*/
void guac_rdp_process_cb_data_request(guac_client* client,
RDP_CB_DATA_REQUEST_EVENT* event);
/**
* Handles the given CLIPRDR event, which MUST be a Data Response event. It
* is the responsibility of this function to read and forward the received
* clipboard data to connected clients.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param event
* The received CLIPRDR message, which must be a Data Response event.
*/
void guac_rdp_process_cb_data_response(guac_client* client,
RDP_CB_DATA_RESPONSE_EVENT* event);

View File

@ -23,6 +23,7 @@
#include "config.h"
#include "client.h"
#include "rdp.h"
#include "rdp_settings.h"
#include <freerdp/codec/color.h>

View File

@ -35,6 +35,16 @@
* Converts the given color to ARGB32. The color given may be an index
* referring to the palette, a 16-bit or 32-bit color, etc. all depending on
* the current color depth of the RDP session.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param color
* A color value in the format of the current RDP session.
*
* @return
* A 32-bit ARGB color, where the low 8 bits are the blue component and
* the high 8 bits are alpha.
*/
UINT32 guac_rdp_convert_color(rdpContext* context, UINT32 color);

View File

@ -22,6 +22,8 @@
#include "config.h"
#include "client.h"
#include "rdp.h"
#include "rdp_disp.h"
#include <freerdp/freerdp.h>
#include <freerdp/client/disp.h>

View File

@ -38,8 +38,11 @@
#include <sys/types.h>
#include <unistd.h>
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/pool.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path,
int create_drive_path) {
@ -61,10 +64,6 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path,
guac_rdp_fs* fs = malloc(sizeof(guac_rdp_fs));
fs->client = client;
fs->object = guac_client_alloc_object(client);
fs->object->get_handler = guac_rdp_download_get_handler;
fs->object->put_handler = guac_rdp_upload_put_handler;
fs->drive_path = strdup(drive_path);
fs->file_id_pool = guac_pool_alloc(0);
fs->open_files = 0;
@ -74,16 +73,56 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path,
}
void guac_rdp_fs_free(guac_rdp_fs* fs) {
guac_client_free_object(fs->client, fs->object);
guac_pool_free(fs->file_id_pool);
free(fs->drive_path);
free(fs);
}
guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user) {
/* Init filesystem */
guac_object* fs_object = guac_user_alloc_object(user);
fs_object->get_handler = guac_rdp_download_get_handler;
fs_object->put_handler = guac_rdp_upload_put_handler;
fs_object->data = fs;
/* Send filesystem to user */
guac_protocol_send_filesystem(user->socket, fs_object, "Shared Drive");
guac_socket_flush(user->socket);
return fs_object;
}
void* guac_rdp_fs_expose(guac_user* user, void* data) {
guac_rdp_fs* fs = (guac_rdp_fs*) data;
/* No need to expose if there is no filesystem or the user has left */
if (user == NULL || fs == NULL)
return NULL;
/* Allocate and expose filesystem object for user */
return guac_rdp_fs_alloc_object(fs, user);
}
/**
* Translates an absolute Windows virtual_path to an absolute virtual_path
* which is within the "drive virtual_path" specified in the connection
* settings.
* Translates an absolute Windows path to an absolute path which is within the
* "drive path" specified in the connection settings. No checking is performed
* on the path provided, which is assumed to have already been normalized and
* validated as absolute.
*
* @param fs
* The filesystem containing the file whose path is being translated.
*
* @param virtual_path
* The absolute path to the file on the simulated filesystem, relative to
* the simulated filesystem root.
*
* @param real_path
* The buffer in which to store the absolute path to the real file on the
* local filesystem.
*/
static void __guac_rdp_fs_translate_path(guac_rdp_fs* fs,
const char* virtual_path, char* real_path) {
@ -701,7 +740,7 @@ int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info) {
/* Read FS information */
struct statvfs fs_stat;
if (statvfs(fs->drive_path, &fs_stat))
return guac_rdp_fs_get_status(errno);
return guac_rdp_fs_get_errorcode(errno);
/* Assign to structure */
info->blocks_available = fs_stat.f_bfree;

View File

@ -38,7 +38,6 @@
#include "config.h"
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/pool.h>
#include <dirent.h>
@ -269,15 +268,10 @@ typedef struct guac_rdp_fs_file {
typedef struct guac_rdp_fs {
/**
* The controlling client.
* The Guacamole client associated with the RDP session.
*/
guac_client* client;
/**
* The underlying filesystem object.
*/
guac_object* object;
/**
* The root of the filesystem.
*/
@ -323,34 +317,155 @@ typedef struct guac_rdp_fs_info {
} guac_rdp_fs_info;
/**
* Allocates a new filesystem given a root path.
* Allocates a new filesystem given a root path. This filesystem will behave
* as if it were a network drive.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param drive_path
* The local directory to use as the root directory of the emulated
* network drive.
*
* @param create_drive_path
* Non-zero if the drive path specified should be automatically created if
* it does not yet exist, zero otherwise.
*
* @return
* The newly-allocated filesystem.
*/
guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, int create_drive_path);
guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path,
int create_drive_path);
/**
* Frees the given filesystem.
*
* @param fs
* The filesystem to free.
*/
void guac_rdp_fs_free(guac_rdp_fs* fs);
/**
* Creates and exposes a new filesystem guac_object to the given user,
* providing access to the files within the given RDP filesystem. The
* allocated guac_object must eventually be freed via guac_user_free_object().
*
* @param fs
* The RDP filesystem object to expose.
*
* @param user
* The user that the RDP filesystem should be exposed to.
*
* @return
* A new Guacamole filesystem object, configured to use RDP for uploading
* and downloading files.
*/
guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user);
/**
* Allocates a new filesystem guac_object for the given user, returning the
* resulting guac_object. This function is provided for convenience, as it is
* can be used as the callback for guac_client_foreach_user() or
* guac_client_for_owner(). Note that this guac_object will be tracked
* internally by libguac, will be provided to us in the parameters of handlers
* related to that guac_object, and will automatically be freed when the
* associated guac_user is freed, so the return value of this function can
* safely be ignored.
*
* If either the given user or the given filesystem are NULL, then this
* function has no effect.
*
* @param user
* The use to expose the filesystem to, or NULL if nothing should be
* exposed.
*
* @param data
* A pointer to the guac_rdp_fs instance to expose to the given user, or
* NULL if nothing should be exposed.
*
* @return
* The guac_object allocated for the newly-exposed filesystem, or NULL if
* no filesystem object could be allocated.
*/
void* guac_rdp_fs_expose(guac_user* user, void* data);
/**
* Converts the given relative path to an absolute path based on the given
* parent path. If the path cannot be converted, non-zero is returned.
*
* @param parent
* The parent directory of the relative path.
*
* @param rel_path
* The relative path to convert.
*
* @return
* Zero if the path was converted successfully, non-zero otherwise.
*/
int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, char* abs_path);
int guac_rdp_fs_convert_path(const char* parent, const char* rel_path,
char* abs_path);
/**
* Translates the given errno error code to a GUAC_RDP_FS error code.
*
* @param err
* The error code, as returned within errno by a system call.
*
* @return
* A GUAC_RDP_FS error code, such as GUAC_RDP_FS_ENFILE,
* GUAC_RDP_FS_ENOENT, etc.
*/
int guac_rdp_fs_get_errorcode(int err);
/**
* Teanslates the given GUAC_RDP_FS error code to an RDPDR status code.
* Translates the given GUAC_RDP_FS error code to an RDPDR status code.
*
* @param err
* A GUAC_RDP_FS error code, such as GUAC_RDP_FS_ENFILE,
* GUAC_RDP_FS_ENOENT, etc.
*
* @return
* A status code corresponding to the given error code that an
* implementation of the RDPDR channel can understand.
*/
int guac_rdp_fs_get_status(int err);
/**
* Returns the next available file ID, or an error code less than zero
* if an error occurs.
* Opens the given file, returning the a new file ID, or an error code less
* than zero if an error occurs. The given path MUST be absolute, and will be
* translated to be relative to the drive path of the simulated filesystem.
*
* @param fs
* The filesystem to use when opening the file.
*
* @param path
* The absolute path to the file within the simulated filesystem.
*
* @param access
* A bitwise-OR of various RDPDR access flags, such as ACCESS_GENERIC_ALL
* or ACCESS_GENERIC_WRITE. This value will ultimately be translated to a
* standard O_RDWR, O_WRONLY, etc. value when opening the real file on the
* local filesystem.
*
* @param file_attributes
* The attributes to apply to the file, if created. This parameter is
* currently ignored, and has no effect.
*
* @param create_disposition
* Any one of several RDPDR file creation dispositions, such as
* DISP_FILE_CREATE, DISP_FILE_OPEN_IF, etc. The creation disposition
* dictates whether a new file should be created, whether the file can
* already exist, whether existing contents should be truncated, etc.
*
* @param create_options
* A bitwise-OR of various RDPDR options dictating how a file is to be
* created. Currently only one option is implemented, FILE_DIRECTORY_FILE,
* which specifies that the new file must be a directory.
*
* @return
* A new file ID, which will always be a positive value, or an error code
* if an error occurs. All error codes are negative values and correspond
* to GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT.
*/
int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
int access, int file_attributes, int create_disposition,
@ -360,6 +475,26 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
* Reads up to the given length of bytes from the given offset within the
* file having the given ID. Returns the number of bytes read, zero on EOF,
* and an error code if an error occurs.
*
* @param fs
* The filesystem containing the file from which data is to be read.
*
* @param file_id
* The ID of the file to read data from, as returned by guac_rdp_fs_open().
*
* @param offset
* The byte offset within the file to start reading from.
*
* @param buffer
* The buffer to fill with data from the file.
*
* @param length
* The maximum number of bytes to read from the file.
*
* @return
* The number of bytes actually read, zero on EOF, or an error code if an
* error occurs. All error codes are negative values and correspond to
* GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT.
*/
int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, int offset,
void* buffer, int length);
@ -368,6 +503,26 @@ int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, int offset,
* Writes up to the given length of bytes from the given offset within the
* file having the given ID. Returns the number of bytes written, and an
* error code if an error occurs.
*
* @param fs
* The filesystem containing the file to which data is to be written.
*
* @param file_id
* The ID of the file to write data to, as returned by guac_rdp_fs_open().
*
* @param offset
* The byte offset within the file to start writinging at.
*
* @param buffer
* The buffer containing the data to write.
*
* @param length
* The maximum number of bytes to write to the file.
*
* @return
* The number of bytes actually written, or an error code if an error
* occurs. All error codes are negative values and correspond to
* GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT.
*/
int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, int offset,
void* buffer, int length);
@ -375,56 +530,172 @@ int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, int offset,
/**
* Renames (moves) the file with the given ID to the new path specified.
* Returns zero on success, or an error code if an error occurs.
*
* @param fs
* The filesystem containing the file to rename.
*
* @param file_id
* The ID of the file to rename, as returned by guac_rdp_fs_open().
*
* @param new_path
* The absolute path to move the file to.
*
* @return
* Zero if the rename succeeded, or an error code if an error occurs. All
* error codes are negative values and correspond to GUAC_RDP_FS constants,
* such as GUAC_RDP_FS_ENOENT.
*/
int guac_rdp_fs_rename(guac_rdp_fs* fs, int file_id,
const char* new_path);
/**
* Deletes the file with the given ID.
*
* @param fs
* The filesystem containing the file to delete.
*
* @param file_id
* The ID of the file to delete, as returned by guac_rdp_fs_open().
*
* @return
* Zero if deletion succeeded, or an error code if an error occurs. All
* error codes are negative values and correspond to GUAC_RDP_FS constants,
* such as GUAC_RDP_FS_ENOENT.
*/
int guac_rdp_fs_delete(guac_rdp_fs* fs, int file_id);
/**
* Truncates the file with the given ID to the given length (in bytes), which
* may be larger.
*
* @param fs
* The filesystem containing the file to truncate.
*
* @param file_id
* The ID of the file to truncate, as returned by guac_rdp_fs_open().
*
* @param length
* The new length of the file, in bytes. Despite being named "truncate",
* this new length may be larger.
*
* @return
* Zero if truncation succeeded, or an error code if an error occurs. All
* error codes are negative values and correspond to GUAC_RDP_FS constants,
* such as GUAC_RDP_FS_ENOENT.
*/
int guac_rdp_fs_truncate(guac_rdp_fs* fs, int file_id, int length);
/**
* Frees the given file ID, allowing future open operations to reuse it.
*
* @param fs
* The filesystem containing the file to close.
*
* @param file_id
* The ID of the file to close, as returned by guac_rdp_fs_open().
*/
void guac_rdp_fs_close(guac_rdp_fs* fs, int file_id);
/**
* Given an arbitrary path, which may contain ".." and ".", creates an
* absolute path which does NOT contain ".." or ".".
* absolute path which does NOT contain ".." or ".". The given path MUST
* be absolute.
*
* @param path
* The absolute path to normalize.
*
* @param abs_path
* The buffer to populate with the normalized path. The normalized path
* will not contain relative path components like ".." or ".".
*
* @return
* Zero if normalization succeeded, non-zero otherwise.
*/
int guac_rdp_fs_normalize_path(const char* path, char* abs_path);
/**
* Given a parent path and a relative path, produces a normalized absolute path.
* Given a parent path and a relative path, produces a normalized absolute
* path.
*
* @param parent
* The absolute path of the parent directory of the relative path.
*
* @param rel_path
* The relative path to convert.
*
* @param abs_path
* The buffer to populate with the absolute, normalized path. The
* normalized path will not contain relative path components like ".." or
* ".".
*
* @return
* Zero if conversion succeeded, non-zero otherwise.
*/
int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, char* abs_path);
int guac_rdp_fs_convert_path(const char* parent, const char* rel_path,
char* abs_path);
/**
* Returns the next filename within the directory having the given file ID,
* or NULL if no more files.
*
* @param fs
* The filesystem containing the file to read directory entries from.
*
* @param file_id
* The ID of the file to read directory entries from, as returned by
* guac_rdp_fs_open().
*
* @return
* The name of the next filename within the directory, or NULL if the last
* file in the directory has already been returned by a previous call.
*/
const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id);
/**
* Returns the file having the given ID, or NULL if no such file exists.
*
* @param fs
* The filesystem containing the desired file.
*
* @param file_id
* The ID of the desired, as returned by guac_rdp_fs_open().
*
* @return
* The file having the given ID, or NULL is no such file exists.
*/
guac_rdp_fs_file* guac_rdp_fs_get_file(guac_rdp_fs* fs, int file_id);
/**
* Returns whether the given filename matches the given pattern.
* Returns whether the given filename matches the given pattern. The pattern
* given is a shell wildcard pattern as accepted by the POSIX fnmatch()
* function. Backslashes will be interpreted as literal backslashes, not
* escape characters.
*
* @param filename
* The filename to check
*
* @param pattern
* The pattern to check the filename against.
*
* @return
* Non-zero if the pattern matches, zero otherwise.
*/
int guac_rdp_fs_matches(const char* filename, const char* pattern);
/**
* Populates the given structure with information about the filesystem,
* particularly the amount of space available.
*
* @param fs
* The filesystem to obtain information from.
*
* @param info
* The guac_rdp_fs_info structure to populate.
*
* @return
* Zero if information retrieval succeeded, or an error code if an error
* occurs. All error codes are negative values and correspond to
* GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT.
*/
int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info);

View File

@ -24,6 +24,7 @@
#include "client.h"
#include "guac_surface.h"
#include "rdp.h"
#include "rdp_bitmap.h"
#include "rdp_color.h"
#include "rdp_settings.h"
@ -102,7 +103,7 @@ guac_transfer_function guac_rdp_rop3_transfer_function(guac_client* client,
void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_common_surface* current_surface = ((rdp_guac_client_data*) client->data)->current_surface;
guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface;
int x = dstblt->nLeftRect;
int y = dstblt->nTopRect;
@ -158,7 +159,7 @@ void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) {
/* Get client and current layer */
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_common_surface* current_surface =
((rdp_guac_client_data*) client->data)->current_surface;
((guac_rdp_client*) client->data)->current_surface;
int x = patblt->nLeftRect;
int y = patblt->nTopRect;
@ -210,7 +211,7 @@ void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) {
void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_common_surface* current_surface = ((rdp_guac_client_data*) client->data)->current_surface;
guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface;
int x = scrblt->nLeftRect;
int y = scrblt->nTopRect;
@ -220,18 +221,18 @@ void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt) {
int x_src = scrblt->nXSrc;
int y_src = scrblt->nYSrc;
rdp_guac_client_data* data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Copy screen rect to current surface */
guac_common_surface_copy(data->default_surface, x_src, y_src, w, h,
current_surface, x, y);
guac_common_surface_copy(rdp_client->display->default_surface,
x_src, y_src, w, h, current_surface, x, y);
}
void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_common_surface* current_surface = ((rdp_guac_client_data*) client->data)->current_surface;
guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface;
guac_rdp_bitmap* bitmap = (guac_rdp_bitmap*) memblt->bitmap;
int x = memblt->nLeftRect;
@ -263,11 +264,11 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
case 0xCC:
/* If not cached, cache if necessary */
if (bitmap->surface == NULL && bitmap->used >= 1)
if (bitmap->layer == NULL && bitmap->used >= 1)
guac_rdp_cache_bitmap(context, memblt->bitmap);
/* If not cached, send as PNG */
if (bitmap->surface == NULL) {
if (bitmap->layer == NULL) {
if (memblt->bitmap->data != NULL) {
/* Create surface from image data */
@ -286,8 +287,8 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
/* Otherwise, copy */
else
guac_common_surface_copy(bitmap->surface, x_src, y_src, w, h,
current_surface, x, y);
guac_common_surface_copy(bitmap->layer->surface,
x_src, y_src, w, h, current_surface, x, y);
/* Increment usage counter */
((guac_rdp_bitmap*) bitmap)->used++;
@ -303,10 +304,11 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
default:
/* If not available as a surface, make available. */
if (bitmap->surface == NULL)
if (bitmap->layer == NULL)
guac_rdp_cache_bitmap(context, memblt->bitmap);
guac_common_surface_transfer(bitmap->surface, x_src, y_src, w, h,
guac_common_surface_transfer(bitmap->layer->surface,
x_src, y_src, w, h,
guac_rdp_rop3_transfer_function(client, memblt->bRop),
current_surface, x, y);
@ -324,7 +326,7 @@ void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect
UINT32 color = guac_rdp_convert_color(context, opaque_rect->color);
guac_common_surface* current_surface = ((rdp_guac_client_data*) client->data)->current_surface;
guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface;
int x = opaque_rect->nLeftRect;
int y = opaque_rect->nTopRect;
@ -341,6 +343,13 @@ void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect
/**
* Updates the palette within a FreeRDP CLRCONV object using the new palette
* entries provided by an RDP palette update.
*
* @param clrconv
* The FreeRDP CLRCONV object to update.
*
* @param palette
* An RDP palette update message containing the palette to store within the
* given CLRCONV object.
*/
static void guac_rdp_update_clrconv(CLRCONV* clrconv,
PALETTE_UPDATE* palette) {
@ -358,6 +367,14 @@ static void guac_rdp_update_clrconv(CLRCONV* clrconv,
/**
* Updates a raw ARGB32 palette using the new palette entries provided by an
* RDP palette update.
*
* @param guac_palette
* An array of 256 ARGB32 colors, with each entry corresponding to an
* entry in the color palette.
*
* @param palette
* An RDP palette update message containing the palette to store within the
* given array of ARGB32 colors.
*/
static void guac_rdp_update_palette(UINT32* guac_palette,
PALETTE_UPDATE* palette) {
@ -393,16 +410,18 @@ void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette) {
void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdp_guac_client_data* data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* If no bounds given, clear bounding rect */
if (bounds == NULL)
guac_common_surface_reset_clip(data->default_surface);
guac_common_surface_reset_clip(rdp_client->display->default_surface);
/* Otherwise, set bounding rectangle */
else
guac_common_surface_clip(data->default_surface, bounds->left, bounds->top,
bounds->right - bounds->left + 1, bounds->bottom - bounds->top + 1);
guac_common_surface_clip(rdp_client->display->default_surface,
bounds->left, bounds->top,
bounds->right - bounds->left + 1,
bounds->bottom - bounds->top + 1);
}
@ -413,13 +432,13 @@ void guac_rdp_gdi_end_paint(rdpContext* context) {
void guac_rdp_gdi_desktop_resize(rdpContext* context) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdp_guac_client_data* data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_surface_resize(data->default_surface,
guac_common_surface_resize(rdp_client->display->default_surface,
guac_rdp_get_width(context->instance),
guac_rdp_get_height(context->instance));
guac_common_surface_reset_clip(data->default_surface);
guac_common_surface_reset_clip(rdp_client->display->default_surface);
guac_client_log(client, GUAC_LOG_DEBUG, "Server resized display to %ix%i",
guac_rdp_get_width(context->instance),

View File

@ -30,49 +30,112 @@
#include <guacamole/protocol.h>
/**
* Translates a standard RDP ROP3 value into a guac_composite_mode.
* Translates a standard RDP ROP3 value into a guac_composite_mode. Valid
* ROP3 operations indexes are listed in the RDP protocol specifications:
*
* http://msdn.microsoft.com/en-us/library/cc241583.aspx
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param rop3
* The ROP3 operation index to translate.
*
* @return
* The guac_composite_mode that equates to, or most closely approximates,
* the given ROP3 operation.
*/
guac_composite_mode guac_rdp_rop3_transfer_function(guac_client* client, int rop3);
guac_composite_mode guac_rdp_rop3_transfer_function(guac_client* client,
int rop3);
/**
* Handler for RDP DSTBLT update.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param dstblt
* The DSTBLT update to handle.
*/
void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt);
/**
* Handler for RDP PATBLT update.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param patblt
* The PATBLT update to handle.
*/
void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt);
/**
* Handler for RDP SCRBLT update.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param scrblt
* The SCRBLT update to handle.
*/
void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt);
/**
* Handler for RDP MEMBLT update.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param memblt
* The MEMBLT update to handle.
*/
void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt);
/**
* Handler for RDP OPAQUE_RECT update.
* Handler for RDP OPAQUE RECT update.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param opaque_rect
* The OPAQUE RECT update to handle.
*/
void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect);
void guac_rdp_gdi_opaquerect(rdpContext* context,
OPAQUE_RECT_ORDER* opaque_rect);
/**
* Handler called when the remote color palette is changing.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param palette
* The PALETTE update containing the new palette.
*/
void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette);
/**
* Handler called prior to calling the handlers for specific updates when
* those updatese are clipped by a bounding rectangle.
* those updates are clipped by a bounding rectangle. This is not a true RDP
* update, but is called by FreeRDP before and after any update involving
* clipping.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param bounds
* The clipping rectangle to set, or NULL to remove any applied clipping
* rectangle.
*/
void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds);
/**
* Handler called when a paint operation is complete. We don't actually
* use this, but FreeRDP requires it.
* use this, but FreeRDP requires it. Calling this function has no effect.
*
* @param context
* The rdpContext associated with the current RDP session.
*/
void guac_rdp_gdi_end_paint(rdpContext* context);
@ -81,6 +144,12 @@ void guac_rdp_gdi_end_paint(rdpContext* context);
* true desktop resize event received by the RDP client, or due to
* a revised size given by the server during initial connection
* negotiation.
*
* The new screen size will be made available within the settings associated
* with the given context.
*
* @param context
* The rdpContext associated with the current RDP session.
*/
void guac_rdp_gdi_desktop_resize(rdpContext* context);

View File

@ -24,6 +24,7 @@
#include "client.h"
#include "guac_surface.h"
#include "rdp.h"
#include "rdp_color.h"
#include "rdp_glyph.h"
#include "rdp_settings.h"
@ -102,9 +103,9 @@ void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph) {
void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data;
guac_common_surface* current_surface = guac_client_data->current_surface;
uint32_t fgcolor = guac_client_data->glyph_color;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_surface* current_surface = rdp_client->current_surface;
uint32_t fgcolor = rdp_client->glyph_color;
/* Paint with glyph as mask */
guac_common_surface_paint(current_surface, x, y, ((guac_rdp_glyph*) glyph)->surface,
@ -129,8 +130,8 @@ void guac_rdp_glyph_begindraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdp_guac_client_data* guac_client_data =
(rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client =
(guac_rdp_client*) client->data;
/* Fill background with color if specified */
if (width != 0 && height != 0) {
@ -138,7 +139,7 @@ void guac_rdp_glyph_begindraw(rdpContext* context,
/* Convert background color */
bgcolor = guac_rdp_convert_color(context, bgcolor);
guac_common_surface_rect(guac_client_data->current_surface, x, y, width, height,
guac_common_surface_rect(rdp_client->current_surface, x, y, width, height,
(bgcolor & 0xFF0000) >> 16,
(bgcolor & 0x00FF00) >> 8,
bgcolor & 0x0000FF);
@ -146,7 +147,7 @@ void guac_rdp_glyph_begindraw(rdpContext* context,
}
/* Convert foreground color */
guac_client_data->glyph_color = guac_rdp_convert_color(context, fgcolor);
rdp_client->glyph_color = guac_rdp_convert_color(context, fgcolor);
}

View File

@ -35,6 +35,9 @@
#include "compat/winpr-wtypes.h"
#endif
/**
* Guacamole-specific rdpGlyph data.
*/
typedef struct guac_rdp_glyph {
/**
@ -49,11 +52,120 @@ typedef struct guac_rdp_glyph {
} guac_rdp_glyph;
/**
* Caches the given glyph. Note that this caching currently only occurs server-
* side, as it is more efficient to transmit the text as PNG.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param glyph
* The glyph to cache.
*/
void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph);
/**
* Draws a previously-cached glyph at the given coordinates within the current
* drawing surface.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param glyph
* The cached glyph to draw.
*
* @param x
* The destination X coordinate of the upper-left corner of the glyph.
*
* @param y
* The destination Y coordinate of the upper-left corner of the glyph.
*/
void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y);
/**
* Frees any Guacamole-specific data associated with the given glyph, such that
* it can be safely freed by FreeRDP.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param glyph
* The cached glyph to free.
*/
void guac_rdp_glyph_free(rdpContext* context, rdpGlyph* glyph);
/**
* Called just prior to rendering a series of glyphs. After this function is
* called, the glyphs will be individually rendered by calls to
* guac_rdp_glyph_draw().
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param x
* The X coordinate of the upper-left corner of the background rectangle of
* the drawing operation, or 0 if the background is transparent.
*
* @param y
* The Y coordinate of the upper-left corner of the background rectangle of
* the drawing operation, or 0 if the background is transparent.
*
* @param width
* The width of the background rectangle of the drawing operation, or 0 if
* the background is transparent.
*
* @param height
* The height of the background rectangle of the drawing operation, or 0 if
* the background is transparent.
*
* @param fgcolor
* The foreground color of each glyph. This color will be in the colorspace
* of the RDP session, and may even be a palette index, and must be
* translated via guac_rdp_convert_color().
*
* @param bgcolor
* The background color of the drawing area. This color will be in the
* colorspace of the RDP session, and may even be a palette index, and must
* be translated via guac_rdp_convert_color(). If the background is
* transparent, this value is undefined.
*/
void guac_rdp_glyph_begindraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor);
/**
* Called immediately after rendering a series of glyphs. Unlike
* guac_rdp_glyph_begindraw(), there is no way to detect through any invocation
* of this function whether the background color is opaque or transparent. We
* currently do NOT implement this function.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param x
* The X coordinate of the upper-left corner of the background rectangle of
* the drawing operation.
*
* @param y
* The Y coordinate of the upper-left corner of the background rectangle of
* the drawing operation.
*
* @param width
* The width of the background rectangle of the drawing operation.
*
* @param height
* The height of the background rectangle of the drawing operation.
*
* @param fgcolor
* The foreground color of each glyph. This color will be in the colorspace
* of the RDP session, and may even be a palette index, and must be
* translated via guac_rdp_convert_color().
*
* @param bgcolor
* The background color of the drawing area. This color will be in the
* colorspace of the RDP session, and may even be a palette index, and must
* be translated via guac_rdp_convert_color(). If the background is
* transparent, this value is undefined.
*/
void guac_rdp_glyph_enddraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor);

View File

@ -112,6 +112,12 @@ typedef int guac_rdp_keysym_state_map[0x200][0x100];
/**
* Simple macro for determing whether a keysym can be stored (or retrieved)
* from any keymap.
*
* @param keysym
* The keysym to check.
*
* @return
* Non-zero if the keysym can be stored or retrieved, zero otherwise.
*/
#define GUAC_RDP_KEYSYM_STORABLE(keysym) ((keysym) <= 0xFFFF || ((keysym) & 0xFFFF0000) == 0x01000000)
@ -120,6 +126,13 @@ typedef int guac_rdp_keysym_state_map[0x200][0x100];
* keysym. The idea here is that a keysym of the form 0xABCD will map to
* mapping[0xAB][0xCD] while a keysym of the form 0x100ABCD will map to
* mapping[0x1AB][0xCD].
*
* @param keysym_mapping
* A 512-entry array of 256-entry arrays of arbitrary values, where the
* location of each array and value is determined by the given keysym.
*
* @param keysym
* The keysym of the entry to look up.
*/
#define GUAC_RDP_KEYSYM_LOOKUP(keysym_mapping, keysym) ( \
(keysym_mapping) \
@ -196,6 +209,12 @@ extern const guac_rdp_keymap* GUAC_KEYMAPS[];
/**
* Return the keymap having the given name, if any, or NULL otherwise.
*
* @param name
* The name of the keymap to find.
*
* @return
* The keymap having the given name, or NULL if no such keymap exists.
*/
const guac_rdp_keymap* guac_rdp_keymap_find(const char* name);

View File

@ -23,28 +23,30 @@
#include "config.h"
#include "client.h"
#include "guac_cursor.h"
#include "guac_display.h"
#include "rdp.h"
#include "rdp_pointer.h"
#include <cairo/cairo.h>
#include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <stdlib.h>
void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_socket* socket = client->socket;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Allocate buffer */
guac_common_display_layer* buffer = guac_common_display_alloc_buffer(
rdp_client->display, pointer->width, pointer->height);
/* Allocate data for image */
unsigned char* data =
(unsigned char*) malloc(pointer->width * pointer->height * 4);
/* Allocate layer */
guac_layer* buffer = guac_client_alloc_buffer(client);
cairo_surface_t* surface;
/* Convert to alpha cursor if mask data present */
@ -60,8 +62,7 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) {
pointer->width, pointer->height, 4*pointer->width);
/* Send surface to buffer */
guac_client_stream_png(client, socket, GUAC_COMP_SRC, buffer,
0, 0, surface);
guac_common_surface_draw(buffer->surface, 0, 0, surface);
/* Free surface */
cairo_surface_destroy(surface);
@ -75,19 +76,23 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) {
void guac_rdp_pointer_set(rdpContext* context, rdpPointer* pointer) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_socket* socket = client->socket;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Set cursor */
guac_protocol_send_cursor(socket, pointer->xPos, pointer->yPos,
((guac_rdp_pointer*) pointer)->layer,
0, 0, pointer->width, pointer->height);
guac_common_cursor_set_surface(rdp_client->display->cursor,
pointer->xPos, pointer->yPos,
((guac_rdp_pointer*) pointer)->layer->surface);
}
void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_client_free_buffer(client, ((guac_rdp_pointer*) pointer)->layer);
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_display_layer* buffer = ((guac_rdp_pointer*) pointer)->layer;
/* Free buffer */
guac_common_display_free_buffer(rdp_client->display, buffer);
}

View File

@ -25,10 +25,13 @@
#define _GUAC_RDP_RDP_POINTER_H
#include "config.h"
#include "guac_display.h"
#include <freerdp/freerdp.h>
#include <guacamole/layer.h>
/**
* Guacamole-specific rdpPointer data.
*/
typedef struct guac_rdp_pointer {
/**
@ -37,16 +40,63 @@ typedef struct guac_rdp_pointer {
rdpPointer pointer;
/**
* Guacamole layer containing cached image data.
* The display layer containing cached image data.
*/
guac_layer* layer;
guac_common_display_layer* layer;
} guac_rdp_pointer;
/**
* Caches a new pointer, which can later be set via guac_rdp_pointer_set() as
* the current mouse pointer.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param pointer
* The pointer to cache.
*/
void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer);
/**
* Sets the given cached pointer as the current pointer. The given pointer must
* have already been initialized through a call to guac_rdp_pointer_new().
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param pointer
* The pointer to set as the current mouse pointer.
*/
void guac_rdp_pointer_set(rdpContext* context, rdpPointer* pointer);
/**
* Frees all Guacamole-related data associated with the given pointer, allowing
* FreeRDP to free the rest safely.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param pointer
* The pointer to free.
*/
void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer);
/**
* Hides the current mouse pointer.
*
* @param context
* The rdpContext associated with the current RDP session.
*/
void guac_rdp_pointer_set_null(rdpContext* context);
/**
* Sets the system-dependent (as in dependent on the client system) default
* pointer as the current pointer, rather than a cached pointer.
*
* @param context
* The rdpContext associated with the current RDP session.
*/
void guac_rdp_pointer_set_default(rdpContext* context);
#endif

View File

@ -23,6 +23,7 @@
#include "config.h"
#include "client.h"
#include "rdp.h"
#include "rdp_rail.h"
#include "rdp_settings.h"
@ -88,8 +89,8 @@ void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event) {
RAIL_SYSPARAM_ORDER* sysparam;
/* Get channels */
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
rdpChannels* channels = client_data->rdp_inst->context->channels;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
rdpChannels* channels = rdp_client->rdp_inst->context->channels;
/* Get sysparam structure */
#ifdef LEGACY_EVENT
@ -106,8 +107,8 @@ void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event) {
/* Work area */
sysparam->workArea.left = 0;
sysparam->workArea.top = 0;
sysparam->workArea.right = client_data->settings.width;
sysparam->workArea.bottom = client_data->settings.height;
sysparam->workArea.right = rdp_client->settings->width;
sysparam->workArea.bottom = rdp_client->settings->height;
/* Taskbar */
sysparam->taskbarPos.left = 0;

View File

@ -36,11 +36,24 @@
/**
* Dispatches a given RAIL event to the appropriate handler.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param event
* The RAIL event to process.
*/
void guac_rdp_process_rail_event(guac_client* client, wMessage* event);
/**
* Handles the event sent when updating system parameters.
* Handles the event sent when updating system parameters. The event given
* MUST be a SYSPARAM event.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param event
* The system parameter event to process.
*/
void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event);

View File

@ -22,10 +22,15 @@
#include "config.h"
#include "client.h"
#include "guac_string.h"
#include "rdp.h"
#include "rdp_settings.h"
#include "resolution.h"
#include <freerdp/constants.h>
#include <freerdp/settings.h>
#include <guacamole/user.h>
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h>
@ -36,6 +41,677 @@
#include <stddef.h>
#include <string.h>
/* Client plugin arguments */
const char* GUAC_RDP_CLIENT_ARGS[] = {
"hostname",
"port",
"domain",
"username",
"password",
"width",
"height",
"dpi",
"initial-program",
"color-depth",
"disable-audio",
"enable-printing",
"enable-drive",
"drive-path",
"create-drive-path",
"console",
"console-audio",
"server-layout",
"security",
"ignore-cert",
"disable-auth",
"remote-app",
"remote-app-dir",
"remote-app-args",
"static-channels",
"client-name",
"enable-wallpaper",
"enable-theming",
"enable-font-smoothing",
"enable-full-window-drag",
"enable-desktop-composition",
"enable-menu-animations",
"preconnection-id",
"preconnection-blob",
#ifdef ENABLE_COMMON_SSH
"enable-sftp",
"sftp-hostname",
"sftp-port",
"sftp-username",
"sftp-password",
"sftp-private-key",
"sftp-passphrase",
"sftp-directory",
#endif
NULL
};
enum RDP_ARGS_IDX {
/**
* The hostname to connect to.
*/
IDX_HOSTNAME,
/**
* The port to connect to. If omitted, the default RDP port of 3389 will be
* used.
*/
IDX_PORT,
/**
* The domain of the user logging in.
*/
IDX_DOMAIN,
/**
* The username of the user logging in.
*/
IDX_USERNAME,
/**
* The password of the user logging in.
*/
IDX_PASSWORD,
/**
* The width of the display to request, in pixels. If omitted, a reasonable
* value will be calculated based on the user's own display size and
* resolution.
*/
IDX_WIDTH,
/**
* The height of the display to request, in pixels. If omitted, a
* reasonable value will be calculated based on the user's own display
* size and resolution.
*/
IDX_HEIGHT,
/**
* The resolution of the display to request, in DPI. If omitted, a
* reasonable value will be calculated based on the user's own display
* size and resolution.
*/
IDX_DPI,
/**
* The initial program to run, if any.
*/
IDX_INITIAL_PROGRAM,
/**
* The color depth of the display to request, in bits.
*/
IDX_COLOR_DEPTH,
/**
* "true" if audio should be disabled, "false" or blank to leave audio
* enabled.
*/
IDX_DISABLE_AUDIO,
/**
* "true" if printing should be enabled, "false" or blank otherwise.
*/
IDX_ENABLE_PRINTING,
/**
* "true" if the virtual drive should be enabled, "false" or blank
* otherwise.
*/
IDX_ENABLE_DRIVE,
/**
* The local system path which will be used to persist the
* virtual drive. This must be specified if the virtual drive is enabled.
*/
IDX_DRIVE_PATH,
/**
* "true" to automatically create the local system path used by the virtual
* drive if it does not yet exist, "false" or blank otherwise.
*/
IDX_CREATE_DRIVE_PATH,
/**
* "true" if this session is a console session, "false" or blank otherwise.
*/
IDX_CONSOLE,
/**
* "true" if audio should be allowed in console sessions, "false" or blank
* otherwise.
*/
IDX_CONSOLE_AUDIO,
/**
* The name of the keymap chosen as the layout of the server. Legal names
* are defined within the *.keymap files in the "keymaps" directory of the
* source for Guacamole's RDP support.
*/
IDX_SERVER_LAYOUT,
/**
* The type of security to use for the connection. Valid values are "rdp",
* "tls", "nla", or "any". By default, "rdp" security is used.
*/
IDX_SECURITY,
/**
* "true" if validity of the RDP server's certificate should be ignored,
* "false" or blank if invalid certificates should result in a failure to
* connect.
*/
IDX_IGNORE_CERT,
/**
* "true" if authentication should be disabled, "false" or blank otherwise.
* This is different from the authentication that takes place when a user
* provides their username and password. Authentication is required by
* definition for NLA.
*/
IDX_DISABLE_AUTH,
/**
* The application to launch, if RemoteApp is in use.
*/
IDX_REMOTE_APP,
/**
* The working directory of the remote application, if RemoteApp is in use.
*/
IDX_REMOTE_APP_DIR,
/**
* The arguments to pass to the remote application, if RemoteApp is in use.
*/
IDX_REMOTE_APP_ARGS,
/**
* Comma-separated list of the names of all static virtual channels that
* should be connected to and exposed as Guacamole pipe streams, or blank
* if no static virtual channels should be used.
*/
IDX_STATIC_CHANNELS,
/**
* The name of the client to submit to the RDP server upon connection.
*/
IDX_CLIENT_NAME,
/**
* "true" if the desktop wallpaper should be visible, "false" or blank if
* the desktop wallpaper should be hidden.
*/
IDX_ENABLE_WALLPAPER,
/**
* "true" if desktop and window theming should be allowed, "false" or blank
* if theming should be temporarily disabled on the desktop of the RDP
* server for the sake of performance.
*/
IDX_ENABLE_THEMING,
/**
* "true" if glyphs should be smoothed with antialiasing (ClearType),
* "false" or blank if glyphs should be rendered with sharp edges and using
* single colors, effectively 1-bit images.
*/
IDX_ENABLE_FONT_SMOOTHING,
/**
* "true" if windows' contents should be shown as they are moved, "false"
* or blank if only a window border should be shown during window move
* operations.
*/
IDX_ENABLE_FULL_WINDOW_DRAG,
/**
* "true" if desktop composition (Aero) should be enabled during the
* session, "false" or blank otherwise. As desktop composition provides
* alpha blending and other special effects, this increases the amount of
* bandwidth used.
*/
IDX_ENABLE_DESKTOP_COMPOSITION,
/**
* "true" if menu animations should be shown, "false" or blank menus should
* not be animated.
*/
IDX_ENABLE_MENU_ANIMATIONS,
/**
* The preconnection ID to send within the preconnection PDU when
* initiating an RDP connection, if any.
*/
IDX_PRECONNECTION_ID,
/**
* The preconnection BLOB (PCB) to send to the RDP server prior to full RDP
* connection negotiation. This value is used by Hyper-V to select the
* destination VM.
*/
IDX_PRECONNECTION_BLOB,
#ifdef ENABLE_COMMON_SSH
/**
* "true" if SFTP should be enabled for the RDP connection, "false" or
* blank otherwise.
*/
IDX_ENABLE_SFTP,
/**
* The hostname of the SSH server to connect to for SFTP. If blank, the
* hostname of the RDP server will be used.
*/
IDX_SFTP_HOSTNAME,
/**
* The port of the SSH server to connect to for SFTP. If blank, the default
* SSH port of "22" will be used.
*/
IDX_SFTP_PORT,
/**
* The username to provide when authenticating with the SSH server for
* SFTP. If blank, the username provided for the RDP user will be used.
*/
IDX_SFTP_USERNAME,
/**
* The password to provide when authenticating with the SSH server for
* SFTP (if not using a private key).
*/
IDX_SFTP_PASSWORD,
/**
* The base64-encoded private key to use when authenticating with the SSH
* server for SFTP (if not using a password).
*/
IDX_SFTP_PRIVATE_KEY,
/**
* The passphrase to use to decrypt the provided base64-encoded private
* key.
*/
IDX_SFTP_PASSPHRASE,
/**
* The default location for file uploads within the SSH server. This will
* apply only to uploads which do not use the filesystem guac_object (where
* the destination directory is otherwise ambiguous).
*/
IDX_SFTP_DIRECTORY,
#endif
RDP_ARGS_COUNT
};
guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
int argc, const char** argv) {
/* Validate arg count */
if (argc != RDP_ARGS_COUNT) {
guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection "
"parameters provided: expected %i, got %i.",
RDP_ARGS_COUNT, argc);
return NULL;
}
guac_rdp_settings* settings = calloc(1, sizeof(guac_rdp_settings));
/* Use console */
settings->console =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_CONSOLE, 0);
/* Enable/disable console audio */
settings->console_audio =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_CONSOLE_AUDIO, 0);
/* Ignore SSL/TLS certificate */
settings->ignore_certificate =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_IGNORE_CERT, 0);
/* Disable authentication */
settings->disable_authentication =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_DISABLE_AUTH, 0);
/* NLA security */
if (strcmp(argv[IDX_SECURITY], "nla") == 0) {
guac_user_log(user, GUAC_LOG_INFO, "Security mode: NLA");
settings->security_mode = GUAC_SECURITY_NLA;
}
/* TLS security */
else if (strcmp(argv[IDX_SECURITY], "tls") == 0) {
guac_user_log(user, GUAC_LOG_INFO, "Security mode: TLS");
settings->security_mode = GUAC_SECURITY_TLS;
}
/* RDP security */
else if (strcmp(argv[IDX_SECURITY], "rdp") == 0) {
guac_user_log(user, GUAC_LOG_INFO, "Security mode: RDP");
settings->security_mode = GUAC_SECURITY_RDP;
}
/* ANY security (allow server to choose) */
else if (strcmp(argv[IDX_SECURITY], "any") == 0) {
guac_user_log(user, GUAC_LOG_INFO, "Security mode: ANY");
settings->security_mode = GUAC_SECURITY_ANY;
}
/* If nothing given, default to RDP */
else {
guac_user_log(user, GUAC_LOG_INFO, "No security mode specified. Defaulting to RDP.");
settings->security_mode = GUAC_SECURITY_RDP;
}
/* Set hostname */
settings->hostname =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_HOSTNAME, "");
/* If port specified, use it */
settings->port =
guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_PORT, RDP_DEFAULT_PORT);
guac_user_log(user, GUAC_LOG_DEBUG,
"User resolution is %ix%i at %i DPI",
user->info.optimal_width,
user->info.optimal_height,
user->info.optimal_resolution);
/* Use suggested resolution unless overridden */
settings->resolution =
guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_DPI, guac_rdp_suggest_resolution(user));
/* Use optimal width unless overridden */
settings->width = user->info.optimal_width
* settings->resolution
/ user->info.optimal_resolution;
if (argv[IDX_WIDTH][0] != '\0')
settings->width = atoi(argv[IDX_WIDTH]);
/* Use default width if given width is invalid. */
if (settings->width <= 0) {
settings->width = RDP_DEFAULT_WIDTH;
guac_user_log(user, GUAC_LOG_ERROR,
"Invalid width: \"%s\". Using default of %i.",
argv[IDX_WIDTH], settings->width);
}
/* Round width down to nearest multiple of 4 */
settings->width = settings->width & ~0x3;
/* Use optimal height unless overridden */
settings->height = user->info.optimal_height
* settings->resolution
/ user->info.optimal_resolution;
if (argv[IDX_HEIGHT][0] != '\0')
settings->height = atoi(argv[IDX_HEIGHT]);
/* Use default height if given height is invalid. */
if (settings->height <= 0) {
settings->height = RDP_DEFAULT_HEIGHT;
guac_user_log(user, GUAC_LOG_ERROR,
"Invalid height: \"%s\". Using default of %i.",
argv[IDX_WIDTH], settings->height);
}
guac_user_log(user, GUAC_LOG_DEBUG,
"Using resolution of %ix%i at %i DPI",
settings->width,
settings->height,
settings->resolution);
/* Domain */
settings->domain =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_DOMAIN, NULL);
/* Username */
settings->username =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_USERNAME, NULL);
/* Password */
settings->password =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_PASSWORD, NULL);
/* Client name */
settings->client_name =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_CLIENT_NAME, NULL);
/* Initial program */
settings->initial_program =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_INITIAL_PROGRAM, NULL);
/* RemoteApp program */
settings->remote_app =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_REMOTE_APP, NULL);
/* RemoteApp working directory */
settings->remote_app_dir =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_REMOTE_APP_DIR, NULL);
/* RemoteApp arguments */
settings->remote_app_args =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_REMOTE_APP_ARGS, NULL);
/* Static virtual channels */
settings->svc_names = NULL;
if (argv[IDX_STATIC_CHANNELS][0] != '\0')
settings->svc_names = guac_split(argv[IDX_STATIC_CHANNELS], ',');
/*
* Performance flags
*/
settings->wallpaper_enabled =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_WALLPAPER, 0);
settings->theming_enabled =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_THEMING, 0);
settings->font_smoothing_enabled =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_FONT_SMOOTHING, 0);
settings->full_window_drag_enabled =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_FULL_WINDOW_DRAG, 0);
settings->desktop_composition_enabled =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_DESKTOP_COMPOSITION, 0);
settings->menu_animations_enabled =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_MENU_ANIMATIONS, 0);
/* Session color depth */
settings->color_depth =
guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_COLOR_DEPTH, RDP_DEFAULT_DEPTH);
/* Preconnection ID */
settings->preconnection_id = -1;
if (argv[IDX_PRECONNECTION_ID][0] != '\0') {
/* Parse preconnection ID, warn if invalid */
int preconnection_id = atoi(argv[IDX_PRECONNECTION_ID]);
if (preconnection_id < 0)
guac_user_log(user, GUAC_LOG_WARNING,
"Ignoring invalid preconnection ID: %i",
preconnection_id);
/* Otherwise, assign specified ID */
else {
settings->preconnection_id = preconnection_id;
guac_user_log(user, GUAC_LOG_DEBUG,
"Preconnection ID: %i", settings->preconnection_id);
}
}
/* Preconnection BLOB */
settings->preconnection_blob = NULL;
if (argv[IDX_PRECONNECTION_BLOB][0] != '\0') {
settings->preconnection_blob = strdup(argv[IDX_PRECONNECTION_BLOB]);
guac_user_log(user, GUAC_LOG_DEBUG,
"Preconnection BLOB: \"%s\"", settings->preconnection_blob);
}
#ifndef HAVE_RDPSETTINGS_SENDPRECONNECTIONPDU
/* Warn if support for the preconnection BLOB / ID is absent */
if (settings->preconnection_blob != NULL
|| settings->preconnection_id != -1) {
guac_user_log(user, GUAC_LOG_WARNING,
"Installed version of FreeRDP lacks support for the "
"preconnection PDU. The specified preconnection BLOB and/or "
"ID will be ignored.");
}
#endif
/* Audio enable/disable */
settings->audio_enabled =
!guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_DISABLE_AUDIO, 0);
/* Printing enable/disable */
settings->printing_enabled =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_PRINTING, 0);
/* Drive enable/disable */
settings->drive_enabled =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_DRIVE, 0);
settings->drive_path =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_DRIVE_PATH, "");
settings->create_drive_path =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_CREATE_DRIVE_PATH, 0);
/* Pick keymap based on argument */
settings->server_layout = NULL;
if (argv[IDX_SERVER_LAYOUT][0] != '\0')
settings->server_layout =
guac_rdp_keymap_find(argv[IDX_SERVER_LAYOUT]);
/* If no keymap requested, use default */
if (settings->server_layout == NULL)
settings->server_layout = guac_rdp_keymap_find(GUAC_DEFAULT_KEYMAP);
#ifdef ENABLE_COMMON_SSH
/* SFTP enable/disable */
settings->enable_sftp =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_ENABLE_SFTP, 0);
/* Hostname for SFTP connection */
settings->sftp_hostname =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_HOSTNAME, settings->hostname);
/* Port for SFTP connection */
settings->sftp_port =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PORT, "22");
/* Username for SSH/SFTP authentication */
settings->sftp_username =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_USERNAME,
settings->username != NULL ? settings->username : "");
/* Password for SFTP (if not using private key) */
settings->sftp_password =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PASSWORD, "");
/* Private key for SFTP (if not using password) */
settings->sftp_private_key =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PRIVATE_KEY, NULL);
/* Passphrase for decrypting the SFTP private key (if applicable */
settings->sftp_passphrase =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_PASSPHRASE, "");
/* Default upload directory */
settings->sftp_directory =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_DIRECTORY, NULL);
#endif
/* Success */
return settings;
}
void guac_rdp_settings_free(guac_rdp_settings* settings) {
/* Free settings strings */
free(settings->client_name);
free(settings->domain);
free(settings->drive_path);
free(settings->hostname);
free(settings->initial_program);
free(settings->password);
free(settings->preconnection_blob);
free(settings->remote_app);
free(settings->remote_app_args);
free(settings->remote_app_dir);
free(settings->username);
/* Free channel name array */
free(settings->svc_names);
#ifdef ENABLE_COMMON_SSH
/* Free SFTP settings */
free(settings->sftp_directory);
free(settings->sftp_hostname);
free(settings->sftp_passphrase);
free(settings->sftp_password);
free(settings->sftp_port);
free(settings->sftp_private_key);
free(settings->sftp_username);
#endif
/* Free settings structure */
free(settings);
}
int guac_rdp_get_width(freerdp* rdp) {
#ifdef LEGACY_RDPSETTINGS
return rdp->settings->width;

View File

@ -280,25 +280,133 @@ typedef struct guac_rdp_settings {
*/
char* preconnection_blob;
#ifdef ENABLE_COMMON_SSH
/**
* Whether SFTP should be enabled for the VNC connection.
*/
int enable_sftp;
/**
* The hostname of the SSH server to connect to for SFTP.
*/
char* sftp_hostname;
/**
* The port of the SSH server to connect to for SFTP.
*/
char* sftp_port;
/**
* The username to provide when authenticating with the SSH server for
* SFTP.
*/
char* sftp_username;
/**
* The password to provide when authenticating with the SSH server for
* SFTP (if not using a private key).
*/
char* sftp_password;
/**
* The base64-encoded private key to use when authenticating with the SSH
* server for SFTP (if not using a password).
*/
char* sftp_private_key;
/**
* The passphrase to use to decrypt the provided base64-encoded private
* key.
*/
char* sftp_passphrase;
/**
* The default location for file uploads within the SSH server. This will
* apply only to uploads which do not use the filesystem guac_object (where
* the destination directory is otherwise ambiguous).
*/
char* sftp_directory;
#endif
} guac_rdp_settings;
/**
* Parses all given args, storing them in a newly-allocated settings object. If
* the args fail to parse, NULL is returned.
*
* @param user
* The user who submitted the given arguments while joining the
* connection.
*
* @param argc
* The number of arguments within the argv array.
*
* @param argv
* The values of all arguments provided by the user.
*
* @return
* A newly-allocated settings object which must be freed with
* guac_rdp_settings_free() when no longer needed. If the arguments fail
* to parse, NULL is returned.
*/
guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
int argc, const char** argv);
/**
* Frees the given guac_rdp_settings object, having been previously allocated
* via guac_rdp_parse_args().
*
* @param settings
* The settings object to free.
*/
void guac_rdp_settings_free(guac_rdp_settings* settings);
/**
* NULL-terminated array of accepted client args.
*/
extern const char* GUAC_RDP_CLIENT_ARGS[];
/**
* Save all given settings to the given freerdp instance.
*
* @param guac_settings
* The guac_rdp_settings object to save.
*
* @param rdp
* The RDP instance to save settings to.
*/
void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp);
/**
* Returns the width of the RDP session display.
*
* @param rdp
* The RDP instance to retrieve the width from.
*
* @return
* The current width of the RDP display, in pixels.
*/
int guac_rdp_get_width(freerdp* rdp);
/**
* Returns the height of the RDP session display.
*
* @param rdp
* The RDP instance to retrieve the height from.
*
* @return
* The current height of the RDP display, in pixels.
*/
int guac_rdp_get_height(freerdp* rdp);
/**
* Returns the depth of the RDP session display.
*
* @param rdp
* The RDP instance to retrieve the depth from.
*
* @return
* The current depth of the RDP display, in bits per pixel.
*/
int guac_rdp_get_depth(freerdp* rdp);

View File

@ -24,6 +24,7 @@
#include "config.h"
#include "client.h"
#include "guac_clipboard.h"
#include "rdp.h"
#include "rdp_fs.h"
#include "rdp_svc.h"
#include "rdp_stream.h"
@ -82,19 +83,22 @@ static void __generate_upload_path(const char* filename, char* path) {
}
int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream,
int guac_rdp_upload_file_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* filename) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
int file_id;
guac_rdp_stream* rdp_stream;
char file_path[GUAC_RDP_FS_MAX_PATH];
/* Get filesystem, return error if no filesystem */
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
guac_rdp_fs* fs = rdp_client->filesystem;
if (fs == NULL) {
guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)",
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
@ -105,9 +109,9 @@ int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream,
file_id = guac_rdp_fs_open(fs, file_path, ACCESS_GENERIC_WRITE, 0,
DISP_FILE_OVERWRITE_IF, 0);
if (file_id < 0) {
guac_protocol_send_ack(client->socket, stream, "FAIL (CANNOT OPEN)",
guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
@ -120,31 +124,31 @@ int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream,
stream->blob_handler = guac_rdp_upload_blob_handler;
stream->end_handler = guac_rdp_upload_end_handler;
guac_protocol_send_ack(client->socket, stream, "OK (STREAM BEGIN)",
guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream,
int guac_rdp_svc_pipe_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name) {
guac_rdp_stream* rdp_stream;
guac_rdp_svc* svc = guac_rdp_get_svc(client, name);
guac_rdp_svc* svc = guac_rdp_get_svc(user->client, name);
/* Fail if no such SVC */
if (svc == NULL) {
guac_client_log(client, GUAC_LOG_ERROR,
guac_user_log(user, GUAC_LOG_ERROR,
"Requested non-existent pipe: \"%s\".",
name);
guac_protocol_send_ack(client->socket, stream, "FAIL (NO SUCH PIPE)",
guac_protocol_send_ack(user->socket, stream, "FAIL (NO SUCH PIPE)",
GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
else
guac_client_log(client, GUAC_LOG_ERROR,
guac_user_log(user, GUAC_LOG_ERROR,
"Inbound half of channel \"%s\" connected.",
name);
@ -153,16 +157,16 @@ int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream,
stream->blob_handler = guac_rdp_svc_blob_handler;
rdp_stream->type = GUAC_RDP_INBOUND_SVC_STREAM;
rdp_stream->svc = svc;
svc->input_pipe = stream;
return 0;
}
int guac_rdp_clipboard_handler(guac_client* client, guac_stream* stream,
int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_stream* rdp_stream;
/* Init stream data */
@ -171,23 +175,25 @@ int guac_rdp_clipboard_handler(guac_client* client, guac_stream* stream,
stream->end_handler = guac_rdp_clipboard_end_handler;
rdp_stream->type = GUAC_RDP_INBOUND_CLIPBOARD_STREAM;
guac_common_clipboard_reset(client_data->clipboard, mimetype);
guac_common_clipboard_reset(rdp_client->clipboard, mimetype);
return 0;
}
int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream,
int guac_rdp_upload_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
int bytes_written;
guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
/* Get filesystem, return error if no filesystem 0*/
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_fs* fs = rdp_client->filesystem;
if (fs == NULL) {
guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)",
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
@ -202,10 +208,10 @@ int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream,
/* On error, abort */
if (bytes_written < 0) {
guac_protocol_send_ack(client->socket, stream,
guac_protocol_send_ack(user->socket, stream,
"FAIL (BAD WRITE)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
@ -216,14 +222,14 @@ int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream,
}
guac_protocol_send_ack(client->socket, stream, "OK (DATA RECEIVED)",
guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream,
int guac_rdp_svc_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
@ -231,32 +237,35 @@ int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream,
/* Write blob data to SVC directly */
guac_rdp_svc_write(rdp_stream->svc, data, length);
guac_protocol_send_ack(client->socket, stream, "OK (DATA RECEIVED)",
guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
int guac_rdp_clipboard_blob_handler(guac_client* client, guac_stream* stream,
int guac_rdp_clipboard_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_common_clipboard_append(client_data->clipboard, (char*) data, length);
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_clipboard_append(rdp_client->clipboard, (char*) data, length);
return 0;
}
int guac_rdp_upload_end_handler(guac_client* client, guac_stream* stream) {
int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
/* Get filesystem, return error if no filesystem */
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
guac_rdp_fs* fs = rdp_client->filesystem;
if (fs == NULL) {
guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)",
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
@ -264,19 +273,20 @@ int guac_rdp_upload_end_handler(guac_client* client, guac_stream* stream) {
guac_rdp_fs_close(fs, rdp_stream->upload_status.file_id);
/* Acknowledge stream end */
guac_protocol_send_ack(client->socket, stream, "OK (STREAM END)",
guac_protocol_send_ack(user->socket, stream, "OK (STREAM END)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
free(rdp_stream);
return 0;
}
int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream) {
int guac_rdp_clipboard_end_handler(guac_user* user, guac_stream* stream) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
rdpChannels* channels = client_data->rdp_inst->context->channels;
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
rdpChannels* channels = rdp_client->rdp_inst->context->channels;
RDP_CB_FORMAT_LIST_EVENT* format_list =
(RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(
@ -285,10 +295,10 @@ int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream) {
NULL, NULL);
/* Terminate clipboard data with NULL */
guac_common_clipboard_append(client_data->clipboard, "", 1);
guac_common_clipboard_append(rdp_client->clipboard, "", 1);
/* Notify server that text data is now available */
format_list->formats = (UINT32*) malloc(sizeof(UINT32));
format_list->formats = (UINT32*) malloc(sizeof(UINT32) * 2);
format_list->formats[0] = CB_FORMAT_TEXT;
format_list->formats[1] = CB_FORMAT_UNICODETEXT;
format_list->num_formats = 2;
@ -298,17 +308,19 @@ int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream) {
return 0;
}
int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream,
int guac_rdp_download_ack_handler(guac_user* user, guac_stream* stream,
char* message, guac_protocol_status status) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
/* Get filesystem, return error if no filesystem */
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
guac_rdp_fs* fs = rdp_client->filesystem;
if (fs == NULL) {
guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)",
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
@ -324,39 +336,39 @@ int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream,
/* If bytes read, send as blob */
if (bytes_read > 0) {
rdp_stream->download_status.offset += bytes_read;
guac_protocol_send_blob(client->socket, stream,
guac_protocol_send_blob(user->socket, stream,
buffer, bytes_read);
}
/* If EOF, send end */
else if (bytes_read == 0) {
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
free(rdp_stream);
}
/* Otherwise, fail stream */
else {
guac_client_log(client, GUAC_LOG_ERROR,
guac_user_log(user, GUAC_LOG_ERROR,
"Error reading file for download");
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
free(rdp_stream);
}
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
}
/* Otherwise, return stream to client */
/* Otherwise, return stream to user */
else
guac_client_free_stream(client, stream);
guac_user_free_stream(user, stream);
return 0;
}
int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
int guac_rdp_ls_ack_handler(guac_user* user, guac_stream* stream,
char* message, guac_protocol_status status) {
int blob_written = 0;
@ -368,7 +380,7 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
guac_rdp_fs_close(rdp_stream->ls_status.fs,
rdp_stream->ls_status.file_id);
guac_client_free_stream(client, stream);
guac_user_free_stream(user, stream);
free(rdp_stream);
return 0;
}
@ -388,7 +400,7 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
if (!guac_rdp_fs_append_filename(absolute_path,
rdp_stream->ls_status.directory_name, filename)) {
guac_client_log(client, GUAC_LOG_DEBUG,
guac_user_log(user, GUAC_LOG_DEBUG,
"Skipping filename \"%s\" - filename is invalid or "
"resulting path is too long", filename);
@ -414,12 +426,12 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
/* Determine mimetype */
const char* mimetype;
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY)
mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE;
mimetype = GUAC_USER_STREAM_INDEX_MIMETYPE;
else
mimetype = "application/octet-stream";
/* Write entry */
blob_written |= guac_common_json_write_property(client, stream,
blob_written |= guac_common_json_write_property(user, stream,
&rdp_stream->ls_status.json_state, absolute_path, mimetype);
guac_rdp_fs_close(rdp_stream->ls_status.fs, file_id);
@ -430,9 +442,9 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
if (filename == NULL) {
/* Complete JSON object */
guac_common_json_end_object(client, stream,
guac_common_json_end_object(user, stream,
&rdp_stream->ls_status.json_state);
guac_common_json_flush(client, stream,
guac_common_json_flush(user, stream,
&rdp_stream->ls_status.json_state);
/* Clean up resources */
@ -441,21 +453,24 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
free(rdp_stream);
/* Signal of stream */
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
}
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
int guac_rdp_download_get_handler(guac_user* user, guac_object* object,
char* name) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get filesystem, ignore request if no filesystem */
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
guac_rdp_fs* fs = rdp_client->filesystem;
if (fs == NULL)
return 0;
@ -463,7 +478,7 @@ int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
int file_id = guac_rdp_fs_open(fs, name, ACCESS_GENERIC_READ, 0,
DISP_FILE_OPEN, 0);
if (file_id < 0) {
guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"",
guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"",
name);
return 0;
}
@ -489,17 +504,17 @@ int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
sizeof(rdp_stream->ls_status.directory_name) - 1);
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
guac_stream* stream = guac_user_alloc_stream(user);
stream->ack_handler = guac_rdp_ls_ack_handler;
stream->data = rdp_stream;
/* Init JSON object state */
guac_common_json_begin_object(client, stream,
guac_common_json_begin_object(user, stream,
&rdp_stream->ls_status.json_state);
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name);
guac_protocol_send_body(user->socket, object, stream,
GUAC_USER_STREAM_INDEX_MIMETYPE, name);
}
@ -513,29 +528,32 @@ int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
rdp_stream->download_status.offset = 0;
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
guac_stream* stream = guac_user_alloc_stream(user);
stream->data = rdp_stream;
stream->ack_handler = guac_rdp_download_ack_handler;
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
guac_protocol_send_body(user->socket, object, stream,
"application/octet-stream", name);
}
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
int guac_rdp_upload_put_handler(guac_user* user, guac_object* object,
guac_stream* stream, char* mimetype, char* name) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get filesystem, return error if no filesystem */
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
guac_rdp_fs* fs = rdp_client->filesystem;
if (fs == NULL) {
guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)",
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
@ -545,9 +563,9 @@ int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
/* Abort on failure */
if (file_id < 0) {
guac_protocol_send_ack(client->socket, stream, "FAIL (CANNOT OPEN)",
guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}
@ -563,9 +581,9 @@ int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
stream->end_handler = guac_rdp_upload_end_handler;
/* Acknowledge stream creation */
guac_protocol_send_ack(client->socket, stream, "OK (STREAM BEGIN)",
guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
guac_socket_flush(user->socket);
return 0;
}

View File

@ -28,7 +28,7 @@
#include "guac_json.h"
#include "rdp_svc.h"
#include <guacamole/client.h>
#include <guacamole/user.h>
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
@ -163,125 +163,67 @@ typedef struct guac_rdp_stream {
/**
* Handler for inbound files related to file uploads.
*/
int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream,
char* mimetype, char* filename);
guac_user_file_handler guac_rdp_upload_file_handler;
/**
* Handler for inbound pipes related to static virtual channels.
*/
int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream,
char* mimetype, char* name);
guac_user_pipe_handler guac_rdp_svc_pipe_handler;
/**
* Handler for inbound clipboard data.
*/
int guac_rdp_clipboard_handler(guac_client* client, guac_stream* stream,
char* mimetype);
guac_user_clipboard_handler guac_rdp_clipboard_handler;
/**
* Handler for stream data related to file uploads.
*/
int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream,
void* data, int length);
guac_user_blob_handler guac_rdp_upload_blob_handler;
/**
* Handler for stream data related to static virtual channels.
*/
int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream,
void* data, int length);
guac_user_blob_handler guac_rdp_svc_blob_handler;
/**
* Handler for stream data related to clipboard.
*/
int guac_rdp_clipboard_blob_handler(guac_client* client, guac_stream* stream,
void* data, int length);
guac_user_blob_handler guac_rdp_clipboard_blob_handler;
/**
* Handler for end-of-stream related to file uploads.
*/
int guac_rdp_upload_end_handler(guac_client* client, guac_stream* stream);
guac_user_end_handler guac_rdp_upload_end_handler;
/**
* Handler for end-of-stream related to clipboard.
*/
int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream);
guac_user_end_handler guac_rdp_clipboard_end_handler;
/**
* Handler for acknowledgements of receipt of data related to file downloads.
*/
int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status);
guac_user_ack_handler guac_rdp_download_ack_handler;
/**
* Handler for ack messages received due to receipt of a "body" or "blob"
* instruction associated with a directory list operation.
*
* @param client
* The client receiving the ack message.
*
* @param stream
* The Guacamole protocol stream associated with the received ack message.
*
* @param message
* An arbitrary human-readable message describing the nature of the
* success or failure denoted by this ack message.
*
* @param status
* The status code associated with this ack message, which may indicate
* success or an error.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status);
guac_user_ack_handler guac_rdp_ls_ack_handler;
/**
* Handler for get messages. In context of downloads and the filesystem exposed
* via the Guacamole protocol, get messages request the body of a file within
* the filesystem.
*
* @param client
* The client receiving the get message.
*
* @param object
* The Guacamole protocol object associated with the get request itself.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
char* name);
guac_user_get_handler guac_rdp_download_get_handler;
/**
* Handler for put messages. In context of uploads and the filesystem exposed
* via the Guacamole protocol, put messages request write access to a file
* within the filesystem.
*
* @param client
* The client receiving the put message.
*
* @param object
* The Guacamole protocol object associated with the put request itself.
*
* @param stream
* The Guacamole protocol stream along which the client will be sending
* file data.
*
* @param mimetype
* The mimetype of the data being send along the stream.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
guac_stream* stream, char* mimetype, char* name);
guac_user_put_handler guac_rdp_upload_put_handler;
#endif

View File

@ -23,6 +23,7 @@
#include "config.h"
#include "client.h"
#include "guac_list.h"
#include "rdp.h"
#include "rdp_svc.h"
#include <freerdp/utils/svc_plugin.h>
@ -44,7 +45,6 @@ guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name) {
/* Init SVC */
svc->client = client;
svc->plugin = NULL;
svc->input_pipe = NULL;
svc->output_pipe = NULL;
/* Warn about name length */
@ -65,26 +65,52 @@ void guac_rdp_free_svc(guac_rdp_svc* svc) {
free(svc);
}
void guac_rdp_svc_send_pipe(guac_socket* socket, guac_rdp_svc* svc) {
/* Send pipe instruction for the SVC's output stream */
guac_protocol_send_pipe(socket, svc->output_pipe,
"application/octet-stream", svc->name);
}
void guac_rdp_svc_send_pipes(guac_user* user) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_list_lock(rdp_client->available_svc);
/* Send pipe for each allocated SVC's output stream */
guac_common_list_element* current = rdp_client->available_svc->head;
while (current != NULL) {
guac_rdp_svc_send_pipe(user->socket, (guac_rdp_svc*) current->data);
current = current->next;
}
guac_common_list_unlock(rdp_client->available_svc);
}
void guac_rdp_add_svc(guac_client* client, guac_rdp_svc* svc) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Add to list of available SVC */
guac_common_list_lock(client_data->available_svc);
guac_common_list_add(client_data->available_svc, svc);
guac_common_list_unlock(client_data->available_svc);
guac_common_list_lock(rdp_client->available_svc);
guac_common_list_add(rdp_client->available_svc, svc);
guac_common_list_unlock(rdp_client->available_svc);
}
guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_list_element* current;
guac_rdp_svc* found = NULL;
/* For each available SVC */
guac_common_list_lock(client_data->available_svc);
current = client_data->available_svc->head;
guac_common_list_lock(rdp_client->available_svc);
current = rdp_client->available_svc->head;
while (current != NULL) {
/* If name matches, found */
@ -97,7 +123,7 @@ guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) {
current = current->next;
}
guac_common_list_unlock(client_data->available_svc);
guac_common_list_unlock(rdp_client->available_svc);
return found;
@ -105,19 +131,19 @@ guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) {
guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_list_element* current;
guac_rdp_svc* found = NULL;
/* For each available SVC */
guac_common_list_lock(client_data->available_svc);
current = client_data->available_svc->head;
guac_common_list_lock(rdp_client->available_svc);
current = rdp_client->available_svc->head;
while (current != NULL) {
/* If name matches, remove entry */
guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data;
if (strcmp(current_svc->name, name) == 0) {
guac_common_list_remove(client_data->available_svc, current);
guac_common_list_remove(rdp_client->available_svc, current);
found = current_svc;
break;
}
@ -125,7 +151,7 @@ guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name) {
current = current->next;
}
guac_common_list_unlock(client_data->available_svc);
guac_common_list_unlock(rdp_client->available_svc);
/* Return removed entry, if any */
return found;

View File

@ -55,12 +55,6 @@ typedef struct guac_rdp_svc {
*/
char name[GUAC_RDP_SVC_MAX_LENGTH+1];
/**
* The pipe opened by the Guacamole client, if any. This should be
* opened in response to the output pipe.
*/
guac_stream* input_pipe;
/**
* The output pipe, opened when the RDP server receives a connection to
* the static channel.
@ -71,31 +65,104 @@ typedef struct guac_rdp_svc {
/**
* Allocate a new SVC with the given name.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param name
* The name of the virtual channel to allocate.
*
* @return
* A newly-allocated static virtual channel.
*/
guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name);
/**
* Free the given SVC.
*
* @param svc
* The static virtual channel to free.
*/
void guac_rdp_free_svc(guac_rdp_svc* svc);
/**
* Sends the "pipe" instruction describing the given static virtual channel
* along the given socket. This pipe instruction will relate the SVC's
* underlying output stream with the SVC's name and the mimetype
* "application/octet-stream".
*
* @param socket
* The socket along which the "pipe" instruction should be sent.
*
* @param svc
* The static virtual channel that the "pipe" instruction should describe.
*/
void guac_rdp_svc_send_pipe(guac_socket* socket, guac_rdp_svc* svc);
/**
* Sends the "pipe" instructions describing all static virtual channels
* available to the given user along that user's socket. Each pipe instruction
* will relate the associated SVC's underlying output stream with the SVC's
* name and the mimetype "application/octet-stream".
*
* @param user
* The user to send the "pipe" instructions to.
*/
void guac_rdp_svc_send_pipes(guac_user* user);
/**
* Add the given SVC to the list of all available SVCs.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param svc
* The static virtual channel to add to the list of all such channels
* available.
*/
void guac_rdp_add_svc(guac_client* client, guac_rdp_svc* svc);
/**
* Retrieve the SVC with the given name from the list stored in the client.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param name
* The name of the static virtual channel to retrieve.
*
* @return
* The static virtual channel with the given name, or NULL if no such
* virtual channel exists.
*/
guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name);
/**
* Remove the SVC with the given name from the list stored in the client.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param name
* The name of the static virtual channel to remove.
*
* @return
* The static virtual channel that was removed, or NULL if no such virtual
* channel exists.
*/
guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name);
/**
* Write the given blob of data to the virtual channel.
*
* @param svc
* The static virtual channel to write data to.
*
* @param data
* The data to write.
*
* @param length
* The number of bytes to write.
*/
void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length);

View File

@ -23,38 +23,38 @@
#include "client.h"
#include "resolution.h"
#include <guacamole/client.h>
#include <guacamole/user.h>
int guac_rdp_resolution_reasonable(guac_client* client, int resolution) {
int guac_rdp_resolution_reasonable(guac_user* user, int resolution) {
int width = client->info.optimal_width;
int height = client->info.optimal_height;
int width = user->info.optimal_width;
int height = user->info.optimal_height;
/* Convert client pixels to remote pixels */
width = width * resolution / client->info.optimal_resolution;
height = height * resolution / client->info.optimal_resolution;
/* Convert user pixels to remote pixels */
width = width * resolution / user->info.optimal_resolution;
height = height * resolution / user->info.optimal_resolution;
/*
* Resolution is reasonable if the same as the client optimal resolution
* Resolution is reasonable if the same as the user optimal resolution
* OR if the resulting display area is reasonable
*/
return client->info.optimal_resolution == resolution
return user->info.optimal_resolution == resolution
|| width*height >= GUAC_RDP_REASONABLE_AREA;
}
int guac_rdp_suggest_resolution(guac_client* client) {
int guac_rdp_suggest_resolution(guac_user* user) {
/* Prefer RDP's native resolution */
if (guac_rdp_resolution_reasonable(client, GUAC_RDP_NATIVE_RESOLUTION))
if (guac_rdp_resolution_reasonable(user, GUAC_RDP_NATIVE_RESOLUTION))
return GUAC_RDP_NATIVE_RESOLUTION;
/* If native resolution is too tiny, try higher resolution */
if (guac_rdp_resolution_reasonable(client, GUAC_RDP_HIGH_RESOLUTION))
if (guac_rdp_resolution_reasonable(user, GUAC_RDP_HIGH_RESOLUTION))
return GUAC_RDP_HIGH_RESOLUTION;
/* Fallback to client-suggested resolution */
return client->info.optimal_resolution;
/* Fallback to user-suggested resolution */
return user->info.optimal_resolution;
}

View File

@ -23,27 +23,35 @@
#ifndef GUAC_RDP_RESOLUTION_H
#define GUAC_RDP_RESOLUTION_H
#include <guacamole/client.h>
#include <guacamole/user.h>
/**
* Returns whether the given resolution is reasonable for the given client,
* Returns whether the given resolution is reasonable for the given user,
* based on arbitrary criteria for reasonability.
*
* @param client The guac_client to test the given resolution against.
* @param resolution The resolution to test, in DPI.
* @return Non-zero if the resolution is reasonable, zero otherwise.
* @param user
* The guac_user to test the given resolution against.
*
* @param resolution
* The resolution to test, in DPI.
*
* @return
* Non-zero if the resolution is reasonable, zero otherwise.
*/
int guac_rdp_resolution_reasonable(guac_client* client, int resolution);
int guac_rdp_resolution_reasonable(guac_user* user, int resolution);
/**
* Returns a reasonable resolution for the remote display, given the size and
* resolution of a guac_client.
* resolution of a guac_user.
*
* @param client The guac_client whose size and resolution shall be used to
* determine an appropriate remote display resolution.
* @return A reasonable resolution for the remote display, in DPI.
* @param user
* The guac_user whose size and resolution shall be used to determine an
* appropriate remote display resolution.
*
* @return
* A reasonable resolution for the remote display, in DPI.
*/
int guac_rdp_suggest_resolution(guac_client* client);
int guac_rdp_suggest_resolution(guac_user* user);
#endif

View File

@ -22,21 +22,23 @@
#include "config.h"
#include "client.h"
#include "guac_sftp.h"
#include "rdp.h"
#include "sftp.h"
#include <guacamole/client.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
int guac_rdp_sftp_file_handler(guac_client* client, guac_stream* stream,
int guac_rdp_sftp_file_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* filename) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data;
guac_object* filesystem = client_data->sftp_filesystem;
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_ssh_sftp_filesystem* filesystem = rdp_client->sftp_filesystem;
/* Handle file upload */
return guac_common_ssh_sftp_handle_file_stream(filesystem, stream,
return guac_common_ssh_sftp_handle_file_stream(filesystem, user, stream,
mimetype, filename);
}

View File

@ -25,15 +25,15 @@
#include "config.h"
#include <guacamole/client.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
/**
* Handles an incoming stream from a Guacamole "file" instruction, saving the
* contents of that stream to the file having the given name.
*
* @param client
* The client receiving the uploaded file.
* @param user
* The user uploading the file.
*
* @param stream
* The stream through which the uploaded file data will be received.
@ -48,7 +48,7 @@
* Zero if the incoming stream has been handled successfully, non-zero on
* failure.
*/
int guac_rdp_sftp_file_handler(guac_client* client, guac_stream* stream,
int guac_rdp_sftp_file_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* filename);
#endif

View File

@ -20,18 +20,44 @@
* THE SOFTWARE.
*/
#include "config.h"
#ifndef GUAC_RDP_UNICODE_H
#define GUAC_RDP_UNICODE_H
/**
* Convert the given number of UTF-16 characters to UTF-8 characters.
*
* @param utf16
* Arbitrary UTF-16 data.
*
* @param length
* The length of the UTF-16 data, in characters.
*
* @param utf8
* Buffer to which the converted UTF-8 data will be written.
*
* @param size
* The maximum number of bytes available in the UTF-8 buffer.
*/
void guac_rdp_utf16_to_utf8(const unsigned char* utf16, int length,
char* utf8, int size);
/**
* Convert the given number of UTF-8 characters to UTF-16 characters.
*
* @param utf8
* Arbitrary UTF-8 data.
*
* @param length
* The length of the UTF-8 data, in characters.
*
* @param utf16
* Buffer to which the converted UTF-16 data will be written.
*
* @param size
* The maximum number of bytes available in the UTF-16 buffer.
*/
void guac_rdp_utf8_to_utf16(const unsigned char* utf8, int length,
char* utf16, int size);
#endif

127
src/protocols/rdp/user.c Normal file
View File

@ -0,0 +1,127 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "input.h"
#include "guac_display.h"
#include "user.h"
#include "rdp.h"
#include "rdp_settings.h"
#include "rdp_stream.h"
#include "rdp_svc.h"
#ifdef ENABLE_COMMON_SSH
#include "sftp.h"
#endif
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <pthread.h>
int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) {
guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data;
/* Connect via RDP if owner */
if (user->owner) {
/* Parse arguments into client */
guac_rdp_settings* settings = rdp_client->settings =
guac_rdp_parse_args(user, argc, (const char**) argv);
/* Fail if settings cannot be parsed */
if (settings == NULL) {
guac_user_log(user, GUAC_LOG_INFO,
"Badly formatted client arguments.");
return 1;
}
/* Start client thread */
if (pthread_create(&rdp_client->client_thread, NULL,
guac_rdp_client_thread, user->client)) {
guac_user_log(user, GUAC_LOG_ERROR,
"Unable to start VNC client thread.");
return 1;
}
}
/* If not owner, synchronize with current state */
else {
/* Synchronize any audio stream */
if (rdp_client->audio)
guac_audio_stream_add_user(rdp_client->audio, user);
/* Bring user up to date with any registered static channels */
guac_rdp_svc_send_pipes(user);
/* Synchronize with current display */
guac_common_display_dup(rdp_client->display, user, user->socket);
guac_socket_flush(user->socket);
}
user->file_handler = guac_rdp_user_file_handler;
user->mouse_handler = guac_rdp_user_mouse_handler;
user->key_handler = guac_rdp_user_key_handler;
user->size_handler = guac_rdp_user_size_handler;
user->pipe_handler = guac_rdp_svc_pipe_handler;
user->clipboard_handler = guac_rdp_clipboard_handler;
return 0;
}
int guac_rdp_user_file_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* filename) {
guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data;
#ifdef ENABLE_COMMON_SSH
guac_rdp_settings* settings = rdp_client->settings;
/* If SFTP is enabled, it should be used for default uploads only if RDPDR
* is not enabled or its upload directory has been set */
if (rdp_client->sftp_filesystem != NULL) {
if (!settings->drive_enabled || settings->sftp_directory != NULL)
return guac_rdp_sftp_file_handler(user, stream, mimetype, filename);
}
#endif
/* Default to using RDPDR uploads (if enabled) */
if (rdp_client->filesystem != NULL)
return guac_rdp_upload_file_handler(user, stream, mimetype, filename);
/* File transfer not enabled */
guac_protocol_send_ack(user->socket, stream, "File transfer disabled",
GUAC_PROTOCOL_STATUS_UNSUPPORTED);
guac_socket_flush(user->socket);
return 0;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2013 Glyptodon LLC
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -20,19 +20,22 @@
* THE SOFTWARE.
*/
#ifndef GUAC_RDP_USER_H
#define GUAC_RDP_USER_H
#ifndef _GUAC_RDP_GUAC_HANDLERS_H
#define _GUAC_RDP_GUAC_HANDLERS_H
#include <guacamole/user.h>
#include "config.h"
/**
* Handler for joining users.
*/
guac_user_join_handler guac_rdp_user_join_handler;
#include <guacamole/client.h>
int rdp_guac_client_free_handler(guac_client* client);
int rdp_guac_client_handle_messages(guac_client* client);
int rdp_guac_client_mouse_handler(guac_client* client, int x, int y, int mask);
int rdp_guac_client_key_handler(guac_client* client, int keysym, int pressed);
int rdp_guac_client_size_handler(guac_client* client, int width, int height);
/**
* Handler for received simple file uploads. This handler will automatically
* select between RDPDR and SFTP depending on which is available and which has
* priority given associated settings.
*/
guac_user_file_handler guac_rdp_user_file_handler;
#endif