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 endif
if ENABLE_RDP if ENABLE_RDP
#SUBDIRS += src/protocols/rdp SUBDIRS += src/protocols/rdp
endif endif
if ENABLE_SSH if ENABLE_SSH

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -20,37 +20,13 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_RDP_CLIENT_H
#ifndef _GUAC_RDP_CLIENT_H #define GUAC_RDP_CLIENT_H
#define _GUAC_RDP_CLIENT_H
#include "config.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 <guacamole/client.h>
#include <pthread.h>
#include <stdint.h>
/** /**
* The maximum duration of a frame in milliseconds. * The maximum duration of a frame in milliseconds.
*/ */
@ -63,6 +39,15 @@
*/ */
#define GUAC_RDP_FRAME_TIMEOUT 10 #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 * The native resolution of most RDP connections. As Windows and other systems
* rely heavily on forced 96 DPI, we must assume 96 DPI. * rely heavily on forced 96 DPI, we must assume 96 DPI.
@ -107,152 +92,9 @@
*/ */
#define GUAC_RDP_AUDIO_BPS 16 #define GUAC_RDP_AUDIO_BPS 16
/** /**
* Client data that will remain accessible through the guac_client. * Handler which frees all data associated with the guac_client.
* This should generally include data commonly used by Guacamole handlers.
*/ */
typedef struct rdp_guac_client_data { guac_client_free_handler guac_rdp_client_free_handler;
/**
* 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;
#endif #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 "config.h"
#include "client.h" #include "rdp.h"
#include "rdpdr_fs_messages.h" #include "rdpdr_fs_messages.h"
#include "rdpdr_messages.h" #include "rdpdr_messages.h"
#include "rdpdr_service.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) { 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++; int id = rdpdr->devices_registered++;
/* Get new device */ /* Get new device */
@ -152,12 +153,10 @@ void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr) {
device->free_handler = guac_rdpdr_device_fs_free_handler; device->free_handler = guac_rdpdr_device_fs_free_handler;
/* Init data */ /* Init data */
device->data = data->filesystem; device->data = rdp_client->filesystem;
/* Announce filesystem to client */ /* Announce filesystem to owner */
guac_protocol_send_filesystem(rdpdr->client->socket, guac_client_for_owner(client, guac_rdp_fs_expose, rdp_client->filesystem);
data->filesystem->object, "Shared Drive");
guac_socket_flush(rdpdr->client->socket);
} }

View File

@ -282,9 +282,9 @@ void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device,
(guac_rdpdr_printer_data*) device->data; (guac_rdpdr_printer_data*) device->data;
wStream* output_stream = guac_rdpdr_new_io_completion(device, 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 input and wait for output thread to finish */
close(printer_data->printer_input); close(printer_data->printer_input);

View File

@ -22,7 +22,7 @@
#include "config.h" #include "config.h"
#include "client.h" #include "rdp.h"
#include "rdp_fs.h" #include "rdp_fs.h"
#include "rdp_settings.h" #include "rdp_settings.h"
#include "rdp_stream.h" #include "rdp_stream.h"
@ -91,18 +91,18 @@ void guac_rdpdr_process_connect(rdpSvcPlugin* plugin) {
plugin->channel_entry_points.pExtendedData = NULL; plugin->channel_entry_points.pExtendedData = NULL;
/* Get data from client */ /* 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 */ /* Init plugin */
rdpdr->client = client; rdpdr->client = client;
rdpdr->devices_registered = 0; rdpdr->devices_registered = 0;
/* Register printer if enabled */ /* Register printer if enabled */
if (client_data->settings.printing_enabled) if (rdp_client->settings->printing_enabled)
guac_rdpdr_register_printer(rdpdr); guac_rdpdr_register_printer(rdpdr);
/* Register drive if enabled */ /* Register drive if enabled */
if (client_data->settings.drive_enabled) if (rdp_client->settings->drive_enabled)
guac_rdpdr_register_fs(rdpdr); guac_rdpdr_register_fs(rdpdr);
/* Log that printing, etc. has been loaded */ /* 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 */ /* Do not bother attempting the download if the owner has left */
guac_client* client = device->rdpdr->client; 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); ACCESS_FILE_READ_DATA, 0, DISP_FILE_OPEN, 0);
/* If file opened successfully, start stream */ /* If file opened successfully, start stream */
@ -240,7 +266,7 @@ void guac_rdpdr_start_download(guac_rdpdr_device* device, const char* path) {
char c; char c;
/* Associate stream with transfer status */ /* 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->data = rdp_stream = malloc(sizeof(guac_rdp_stream));
stream->ack_handler = guac_rdp_download_ack_handler; stream->ack_handler = guac_rdp_download_ack_handler;
rdp_stream->type = GUAC_RDP_DOWNLOAD_STREAM; 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'); } while (c != '\0');
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_user_log(owner, GUAC_LOG_DEBUG, "%s: Initiating download "
"%s: Initiating download of \"%s\"", __func__, path); "of \"%s\"", __func__, path);
/* Begin stream */ /* Begin stream */
guac_protocol_send_file(client->socket, stream, guac_protocol_send_file(owner->socket, stream,
"application/octet-stream", basename); "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. * 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 #endif

View File

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

View File

@ -97,8 +97,9 @@ void guac_svc_process_connect(rdpSvcPlugin* plugin) {
/* Create pipe */ /* Create pipe */
svc->output_pipe = guac_client_alloc_stream(svc->client); 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 */ /* Log connection to static channel */
guac_client_log(svc->client, GUAC_LOG_INFO, 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 "config.h"
#include "client.h" #include "client.h"
#include "guac_display.h"
#include "guac_surface.h" #include "guac_surface.h"
#include "rdp.h"
#include "rdp_bitmap.h" #include "rdp_bitmap.h"
#include "rdp_settings.h" #include "rdp_settings.h"
@ -46,12 +48,11 @@
void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) { void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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 */ /* Allocate buffer */
guac_layer* buffer = guac_client_alloc_buffer(client); guac_common_display_layer* buffer = guac_common_display_alloc_buffer(
guac_common_surface* surface = guac_common_surface_alloc(client, socket, rdp_client->display, bitmap->width, bitmap->height);
buffer, bitmap->width, bitmap->height);
/* Cache image data if present */ /* Cache image data if present */
if (bitmap->data != NULL) { if (bitmap->data != NULL) {
@ -62,7 +63,7 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) {
bitmap->width, bitmap->height, 4*bitmap->width); bitmap->width, bitmap->height, 4*bitmap->width);
/* Send surface to buffer */ /* Send surface to buffer */
guac_common_surface_draw(surface, 0, 0, image); guac_common_surface_draw(buffer->surface, 0, 0, image);
/* Free surface */ /* Free surface */
cairo_surface_destroy(image); cairo_surface_destroy(image);
@ -70,8 +71,7 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) {
} }
/* Store buffer reference in bitmap */ /* Store buffer reference in bitmap */
((guac_rdp_bitmap*) bitmap)->buffer = buffer; ((guac_rdp_bitmap*) bitmap)->layer = buffer;
((guac_rdp_bitmap*) bitmap)->surface = surface;
} }
@ -101,8 +101,7 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) {
} }
/* No corresponding surface yet - caching is deferred. */ /* No corresponding surface yet - caching is deferred. */
((guac_rdp_bitmap*) bitmap)->buffer = NULL; ((guac_rdp_bitmap*) bitmap)->layer = NULL;
((guac_rdp_bitmap*) bitmap)->surface = NULL;
/* Start at zero usage */ /* Start at zero usage */
((guac_rdp_bitmap*) bitmap)->used = 0; ((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) { void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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 width = bitmap->right - bitmap->left + 1;
int height = bitmap->bottom - bitmap->top + 1; int height = bitmap->bottom - bitmap->top + 1;
/* If not cached, cache if necessary */ /* 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); guac_rdp_cache_bitmap(context, bitmap);
/* If cached, retrieve from cache */ /* If cached, retrieve from cache */
if (surface != NULL) if (buffer != NULL)
guac_common_surface_copy(surface, 0, 0, width, height, guac_common_surface_copy(buffer->surface, 0, 0, width, height,
client_data->default_surface, bitmap->left, bitmap->top); rdp_client->display->default_surface,
bitmap->left, bitmap->top);
/* Otherwise, draw with stored image data */ /* Otherwise, draw with stored image data */
else if (bitmap->data != NULL) { else if (bitmap->data != NULL) {
@ -137,7 +137,8 @@ void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) {
width, height, 4*bitmap->width); width, height, 4*bitmap->width);
/* Draw image on default surface */ /* 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 */ /* Free surface */
cairo_surface_destroy(image); 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) { void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_layer* buffer = ((guac_rdp_bitmap*) bitmap)->buffer; 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;
/* If cached, free surface */
if (surface != NULL)
guac_common_surface_free(surface);
/* If cached, free buffer */ /* If cached, free buffer */
if (buffer != NULL) 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) { void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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) if (primary)
client_data->current_surface = client_data->default_surface; rdp_client->current_surface = rdp_client->display->default_surface;
else { 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 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); 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 #define _GUAC_RDP_RDP_BITMAP_H
#include "config.h" #include "config.h"
#include "guac_surface.h" #include "guac_display.h"
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <guacamole/layer.h> #include <guacamole/layer.h>
@ -36,6 +36,9 @@
#include "compat/winpr-wtypes.h" #include "compat/winpr-wtypes.h"
#endif #endif
/**
* Guacamole-specific rdpBitmap data.
*/
typedef struct guac_rdp_bitmap { typedef struct guac_rdp_bitmap {
/** /**
@ -44,14 +47,9 @@ typedef struct guac_rdp_bitmap {
rdpBitmap bitmap; rdpBitmap bitmap;
/** /**
* The allocated buffer which backs this bitmap. * Layer containing cached image data.
*/ */
guac_layer* buffer; guac_common_display_layer* layer;
/**
* Surface containing cached image data.
*/
guac_common_surface* surface;
/** /**
* The number of times a bitmap has been used. * The number of times a bitmap has been used.
@ -60,18 +58,149 @@ typedef struct guac_rdp_bitmap {
} 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); 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); 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); 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_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 #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 #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
#endif #endif

View File

@ -23,6 +23,7 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "rdp.h"
#include "rdp_cliprdr.h" #include "rdp_cliprdr.h"
#include "guac_clipboard.h" #include "guac_clipboard.h"
#include "guac_iconv.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) { void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event) {
rdpChannels* channels = 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* format_list =
(RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new( (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. * 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) { static void __guac_rdp_cb_request_format(guac_client* client, int format) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
rdpChannels* channels = client_data->rdp_inst->context->channels; rdpChannels* channels = rdp_client->rdp_inst->context->channels;
/* Create new data request */ /* Create new data request */
RDP_CB_DATA_REQUEST_EVENT* 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); NULL, NULL);
/* Set to requested format */ /* Set to requested format */
client_data->requested_clipboard_format = format; rdp_client->requested_clipboard_format = format;
data_request->format = format; data_request->format = format;
/* Send request */ /* 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, void guac_rdp_process_cb_data_request(guac_client* client,
RDP_CB_DATA_REQUEST_EVENT* event) { RDP_CB_DATA_REQUEST_EVENT* event) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
rdpChannels* channels = client_data->rdp_inst->context->channels; rdpChannels* channels = rdp_client->rdp_inst->context->channels;
guac_iconv_write* writer; 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); char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH);
RDP_CB_DATA_RESPONSE_EVENT* data_response; 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 */ /* Set data and size */
data_response->data = (BYTE*) output; 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); writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH);
data_response->size = ((BYTE*) output) - data_response->data; 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, void guac_rdp_process_cb_data_response(guac_client* client,
RDP_CB_DATA_RESPONSE_EVENT* event) { 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]; char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH];
guac_iconv_read* reader; guac_iconv_read* reader;
@ -229,7 +237,7 @@ void guac_rdp_process_cb_data_response(guac_client* client,
char* output = received_data; char* output = received_data;
/* Find correct source encoding */ /* Find correct source encoding */
switch (client_data->requested_clipboard_format) { switch (rdp_client->requested_clipboard_format) {
/* Non-Unicode */ /* Non-Unicode */
case CB_FORMAT_TEXT: case CB_FORMAT_TEXT:
@ -244,7 +252,7 @@ void guac_rdp_process_cb_data_response(guac_client* client,
default: default:
guac_client_log(client, GUAC_LOG_ERROR, "Requested clipboard data in " guac_client_log(client, GUAC_LOG_ERROR, "Requested clipboard data in "
"unsupported format %i", "unsupported format %i",
client_data->requested_clipboard_format); rdp_client->requested_clipboard_format);
return; return;
} }
@ -254,9 +262,9 @@ void guac_rdp_process_cb_data_response(guac_client* client,
GUAC_WRITE_UTF8, &output, sizeof(received_data))) { GUAC_WRITE_UTF8, &output, sizeof(received_data))) {
int length = strnlen(received_data, sizeof(received_data)); int length = strnlen(received_data, sizeof(received_data));
guac_common_clipboard_reset(client_data->clipboard, "text/plain"); guac_common_clipboard_reset(rdp_client->clipboard, "text/plain");
guac_common_clipboard_append(client_data->clipboard, received_data, length); guac_common_clipboard_append(rdp_client->clipboard, received_data, length);
guac_common_clipboard_send(client_data->clipboard, client); guac_common_clipboard_send(rdp_client->clipboard, client);
} }

View File

@ -50,15 +50,72 @@
*/ */
#define GUAC_RDP_CLIPBOARD_FORMAT_UTF16 2 #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); 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); 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, void guac_rdp_process_cb_format_list(guac_client* client,
RDP_CB_FORMAT_LIST_EVENT* event); 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, void guac_rdp_process_cb_data_request(guac_client* client,
RDP_CB_DATA_REQUEST_EVENT* event); 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, void guac_rdp_process_cb_data_response(guac_client* client,
RDP_CB_DATA_RESPONSE_EVENT* event); RDP_CB_DATA_RESPONSE_EVENT* event);

View File

@ -23,6 +23,7 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "rdp.h"
#include "rdp_settings.h" #include "rdp_settings.h"
#include <freerdp/codec/color.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 * 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 * referring to the palette, a 16-bit or 32-bit color, etc. all depending on
* the current color depth of the RDP session. * 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); UINT32 guac_rdp_convert_color(rdpContext* context, UINT32 color);

View File

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

View File

@ -38,8 +38,11 @@
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <guacamole/client.h>
#include <guacamole/object.h> #include <guacamole/object.h>
#include <guacamole/pool.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, guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path,
int create_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)); guac_rdp_fs* fs = malloc(sizeof(guac_rdp_fs));
fs->client = client; 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->drive_path = strdup(drive_path);
fs->file_id_pool = guac_pool_alloc(0); fs->file_id_pool = guac_pool_alloc(0);
fs->open_files = 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) { void guac_rdp_fs_free(guac_rdp_fs* fs) {
guac_client_free_object(fs->client, fs->object);
guac_pool_free(fs->file_id_pool); guac_pool_free(fs->file_id_pool);
free(fs->drive_path); free(fs->drive_path);
free(fs); 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 * Translates an absolute Windows path to an absolute path which is within the
* which is within the "drive virtual_path" specified in the connection * "drive path" specified in the connection settings. No checking is performed
* settings. * 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, static void __guac_rdp_fs_translate_path(guac_rdp_fs* fs,
const char* virtual_path, char* real_path) { 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 */ /* Read FS information */
struct statvfs fs_stat; struct statvfs fs_stat;
if (statvfs(fs->drive_path, &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 */ /* Assign to structure */
info->blocks_available = fs_stat.f_bfree; info->blocks_available = fs_stat.f_bfree;

View File

@ -38,7 +38,6 @@
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/pool.h> #include <guacamole/pool.h>
#include <dirent.h> #include <dirent.h>
@ -269,15 +268,10 @@ typedef struct guac_rdp_fs_file {
typedef struct guac_rdp_fs { typedef struct guac_rdp_fs {
/** /**
* The controlling client. * The Guacamole client associated with the RDP session.
*/ */
guac_client* client; guac_client* client;
/**
* The underlying filesystem object.
*/
guac_object* object;
/** /**
* The root of the filesystem. * The root of the filesystem.
*/ */
@ -323,34 +317,155 @@ typedef struct guac_rdp_fs_info {
} 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. * Frees the given filesystem.
*
* @param fs
* The filesystem to free.
*/ */
void guac_rdp_fs_free(guac_rdp_fs* fs); 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 * 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. * 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. * 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); 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); int guac_rdp_fs_get_status(int err);
/** /**
* Returns the next available file ID, or an error code less than zero * Opens the given file, returning the a new file ID, or an error code less
* if an error occurs. * 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 guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
int access, int file_attributes, int create_disposition, 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 * 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, * file having the given ID. Returns the number of bytes read, zero on EOF,
* and an error code if an error occurs. * 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, int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, int offset,
void* buffer, int length); 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 * 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 * file having the given ID. Returns the number of bytes written, and an
* error code if an error occurs. * 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, int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, int offset,
void* buffer, int length); 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. * 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. * 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, int guac_rdp_fs_rename(guac_rdp_fs* fs, int file_id,
const char* new_path); const char* new_path);
/** /**
* Deletes the file with the given ID. * 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); 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 * Truncates the file with the given ID to the given length (in bytes), which
* may be larger. * 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); 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. * 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); void guac_rdp_fs_close(guac_rdp_fs* fs, int file_id);
/** /**
* Given an arbitrary path, which may contain ".." and ".", creates an * 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); 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, * Returns the next filename within the directory having the given file ID,
* or NULL if no more files. * 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); 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. * 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); 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); int guac_rdp_fs_matches(const char* filename, const char* pattern);
/** /**
* Populates the given structure with information about the filesystem, * Populates the given structure with information about the filesystem,
* particularly the amount of space available. * 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); 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 "client.h"
#include "guac_surface.h" #include "guac_surface.h"
#include "rdp.h"
#include "rdp_bitmap.h" #include "rdp_bitmap.h"
#include "rdp_color.h" #include "rdp_color.h"
#include "rdp_settings.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) { void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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 x = dstblt->nLeftRect;
int y = dstblt->nTopRect; int y = dstblt->nTopRect;
@ -158,7 +159,7 @@ void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) {
/* Get client and current layer */ /* Get client and current layer */
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_common_surface* current_surface = 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 x = patblt->nLeftRect;
int y = patblt->nTopRect; 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) { void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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 x = scrblt->nLeftRect;
int y = scrblt->nTopRect; int y = scrblt->nTopRect;
@ -220,18 +221,18 @@ void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt) {
int x_src = scrblt->nXSrc; int x_src = scrblt->nXSrc;
int y_src = scrblt->nYSrc; 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 */ /* Copy screen rect to current surface */
guac_common_surface_copy(data->default_surface, x_src, y_src, w, h, guac_common_surface_copy(rdp_client->display->default_surface,
current_surface, x, y); x_src, y_src, w, h, current_surface, x, y);
} }
void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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; guac_rdp_bitmap* bitmap = (guac_rdp_bitmap*) memblt->bitmap;
int x = memblt->nLeftRect; int x = memblt->nLeftRect;
@ -263,11 +264,11 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
case 0xCC: case 0xCC:
/* If not cached, cache if necessary */ /* 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); guac_rdp_cache_bitmap(context, memblt->bitmap);
/* If not cached, send as PNG */ /* If not cached, send as PNG */
if (bitmap->surface == NULL) { if (bitmap->layer == NULL) {
if (memblt->bitmap->data != NULL) { if (memblt->bitmap->data != NULL) {
/* Create surface from image data */ /* Create surface from image data */
@ -286,8 +287,8 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
/* Otherwise, copy */ /* Otherwise, copy */
else else
guac_common_surface_copy(bitmap->surface, x_src, y_src, w, h, guac_common_surface_copy(bitmap->layer->surface,
current_surface, x, y); x_src, y_src, w, h, current_surface, x, y);
/* Increment usage counter */ /* Increment usage counter */
((guac_rdp_bitmap*) bitmap)->used++; ((guac_rdp_bitmap*) bitmap)->used++;
@ -303,10 +304,11 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
default: default:
/* If not available as a surface, make available. */ /* If not available as a surface, make available. */
if (bitmap->surface == NULL) if (bitmap->layer == NULL)
guac_rdp_cache_bitmap(context, memblt->bitmap); 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), guac_rdp_rop3_transfer_function(client, memblt->bRop),
current_surface, x, y); 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); 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 x = opaque_rect->nLeftRect;
int y = opaque_rect->nTopRect; 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 * Updates the palette within a FreeRDP CLRCONV object using the new palette
* entries provided by an RDP palette update. * 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, static void guac_rdp_update_clrconv(CLRCONV* clrconv,
PALETTE_UPDATE* palette) { 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 * Updates a raw ARGB32 palette using the new palette entries provided by an
* RDP palette update. * 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, static void guac_rdp_update_palette(UINT32* guac_palette,
PALETTE_UPDATE* 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) { void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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 no bounds given, clear bounding rect */
if (bounds == NULL) 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 */ /* Otherwise, set bounding rectangle */
else else
guac_common_surface_clip(data->default_surface, bounds->left, bounds->top, guac_common_surface_clip(rdp_client->display->default_surface,
bounds->right - bounds->left + 1, bounds->bottom - bounds->top + 1); 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) { void guac_rdp_gdi_desktop_resize(rdpContext* context) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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_width(context->instance),
guac_rdp_get_height(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_client_log(client, GUAC_LOG_DEBUG, "Server resized display to %ix%i",
guac_rdp_get_width(context->instance), guac_rdp_get_width(context->instance),

View File

@ -30,49 +30,112 @@
#include <guacamole/protocol.h> #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. * 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); void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt);
/** /**
* Handler for RDP PATBLT update. * 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); void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt);
/** /**
* Handler for RDP SCRBLT update. * 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); void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt);
/** /**
* Handler for RDP MEMBLT update. * 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); 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. * 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); void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette);
/** /**
* Handler called prior to calling the handlers for specific updates when * 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); void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds);
/** /**
* Handler called when a paint operation is complete. We don't actually * 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); 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 * true desktop resize event received by the RDP client, or due to
* a revised size given by the server during initial connection * a revised size given by the server during initial connection
* negotiation. * 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); void guac_rdp_gdi_desktop_resize(rdpContext* context);

View File

@ -24,6 +24,7 @@
#include "client.h" #include "client.h"
#include "guac_surface.h" #include "guac_surface.h"
#include "rdp.h"
#include "rdp_color.h" #include "rdp_color.h"
#include "rdp_glyph.h" #include "rdp_glyph.h"
#include "rdp_settings.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) { void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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;
guac_common_surface* current_surface = guac_client_data->current_surface; guac_common_surface* current_surface = rdp_client->current_surface;
uint32_t fgcolor = guac_client_data->glyph_color; uint32_t fgcolor = rdp_client->glyph_color;
/* Paint with glyph as mask */ /* Paint with glyph as mask */
guac_common_surface_paint(current_surface, x, y, ((guac_rdp_glyph*) glyph)->surface, 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) { int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
rdp_guac_client_data* guac_client_data = guac_rdp_client* rdp_client =
(rdp_guac_client_data*) client->data; (guac_rdp_client*) client->data;
/* Fill background with color if specified */ /* Fill background with color if specified */
if (width != 0 && height != 0) { if (width != 0 && height != 0) {
@ -138,7 +139,7 @@ void guac_rdp_glyph_begindraw(rdpContext* context,
/* Convert background color */ /* Convert background color */
bgcolor = guac_rdp_convert_color(context, bgcolor); 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 & 0xFF0000) >> 16,
(bgcolor & 0x00FF00) >> 8, (bgcolor & 0x00FF00) >> 8,
bgcolor & 0x0000FF); bgcolor & 0x0000FF);
@ -146,7 +147,7 @@ void guac_rdp_glyph_begindraw(rdpContext* context,
} }
/* Convert foreground color */ /* 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" #include "compat/winpr-wtypes.h"
#endif #endif
/**
* Guacamole-specific rdpGlyph data.
*/
typedef struct guac_rdp_glyph { typedef struct guac_rdp_glyph {
/** /**
@ -49,11 +52,120 @@ typedef struct guac_rdp_glyph {
} 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); 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); 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); 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, void guac_rdp_glyph_begindraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor); 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, void guac_rdp_glyph_enddraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor); 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) * Simple macro for determing whether a keysym can be stored (or retrieved)
* from any keymap. * 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) #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 * 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[0xAB][0xCD] while a keysym of the form 0x100ABCD will map to
* mapping[0x1AB][0xCD]. * 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) ( \ #define GUAC_RDP_KEYSYM_LOOKUP(keysym_mapping, keysym) ( \
(keysym_mapping) \ (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. * 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); const guac_rdp_keymap* guac_rdp_keymap_find(const char* name);

View File

@ -23,28 +23,30 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "guac_cursor.h"
#include "guac_display.h"
#include "rdp.h"
#include "rdp_pointer.h" #include "rdp_pointer.h"
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <stdlib.h> #include <stdlib.h>
void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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 */ /* Allocate data for image */
unsigned char* data = unsigned char* data =
(unsigned char*) malloc(pointer->width * pointer->height * 4); (unsigned char*) malloc(pointer->width * pointer->height * 4);
/* Allocate layer */
guac_layer* buffer = guac_client_alloc_buffer(client);
cairo_surface_t* surface; cairo_surface_t* surface;
/* Convert to alpha cursor if mask data present */ /* 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); pointer->width, pointer->height, 4*pointer->width);
/* Send surface to buffer */ /* Send surface to buffer */
guac_client_stream_png(client, socket, GUAC_COMP_SRC, buffer, guac_common_surface_draw(buffer->surface, 0, 0, surface);
0, 0, surface);
/* Free surface */ /* Free surface */
cairo_surface_destroy(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) { void guac_rdp_pointer_set(rdpContext* context, rdpPointer* pointer) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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 */ /* Set cursor */
guac_protocol_send_cursor(socket, pointer->xPos, pointer->yPos, guac_common_cursor_set_surface(rdp_client->display->cursor,
((guac_rdp_pointer*) pointer)->layer, pointer->xPos, pointer->yPos,
0, 0, pointer->width, pointer->height); ((guac_rdp_pointer*) pointer)->layer->surface);
} }
void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) { void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) {
guac_client* client = ((rdp_freerdp_context*) context)->client; 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 #define _GUAC_RDP_RDP_POINTER_H
#include "config.h" #include "config.h"
#include "guac_display.h"
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <guacamole/layer.h>
/**
* Guacamole-specific rdpPointer data.
*/
typedef struct guac_rdp_pointer { typedef struct guac_rdp_pointer {
/** /**
@ -37,16 +40,63 @@ typedef struct guac_rdp_pointer {
rdpPointer 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; } 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); 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); 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); 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); 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); void guac_rdp_pointer_set_default(rdpContext* context);
#endif #endif

View File

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

View File

@ -36,11 +36,24 @@
/** /**
* Dispatches a given RAIL event to the appropriate handler. * 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); 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); void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event);

View File

@ -22,10 +22,15 @@
#include "config.h" #include "config.h"
#include "client.h"
#include "guac_string.h"
#include "rdp.h"
#include "rdp_settings.h" #include "rdp_settings.h"
#include "resolution.h"
#include <freerdp/constants.h> #include <freerdp/constants.h>
#include <freerdp/settings.h> #include <freerdp/settings.h>
#include <guacamole/user.h>
#ifdef ENABLE_WINPR #ifdef ENABLE_WINPR
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
@ -36,6 +41,677 @@
#include <stddef.h> #include <stddef.h>
#include <string.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) { int guac_rdp_get_width(freerdp* rdp) {
#ifdef LEGACY_RDPSETTINGS #ifdef LEGACY_RDPSETTINGS
return rdp->settings->width; return rdp->settings->width;

View File

@ -280,25 +280,133 @@ typedef struct guac_rdp_settings {
*/ */
char* preconnection_blob; 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; } 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. * 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); void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp);
/** /**
* Returns the width of the RDP session display. * 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); int guac_rdp_get_width(freerdp* rdp);
/** /**
* Returns the height of the RDP session display. * 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); int guac_rdp_get_height(freerdp* rdp);
/** /**
* Returns the depth of the RDP session display. * 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); int guac_rdp_get_depth(freerdp* rdp);

View File

@ -24,6 +24,7 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "guac_clipboard.h" #include "guac_clipboard.h"
#include "rdp.h"
#include "rdp_fs.h" #include "rdp_fs.h"
#include "rdp_svc.h" #include "rdp_svc.h"
#include "rdp_stream.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) { char* mimetype, char* filename) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
int file_id; int file_id;
guac_rdp_stream* rdp_stream; guac_rdp_stream* rdp_stream;
char file_path[GUAC_RDP_FS_MAX_PATH]; char file_path[GUAC_RDP_FS_MAX_PATH];
/* Get filesystem, return error if no filesystem */ /* 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) { 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_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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, file_id = guac_rdp_fs_open(fs, file_path, ACCESS_GENERIC_WRITE, 0,
DISP_FILE_OVERWRITE_IF, 0); DISP_FILE_OVERWRITE_IF, 0);
if (file_id < 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_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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->blob_handler = guac_rdp_upload_blob_handler;
stream->end_handler = guac_rdp_upload_end_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_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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) { char* mimetype, char* name) {
guac_rdp_stream* rdp_stream; 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 */ /* Fail if no such SVC */
if (svc == NULL) { if (svc == NULL) {
guac_client_log(client, GUAC_LOG_ERROR, guac_user_log(user, GUAC_LOG_ERROR,
"Requested non-existent pipe: \"%s\".", "Requested non-existent pipe: \"%s\".",
name); 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_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; return 0;
} }
else else
guac_client_log(client, GUAC_LOG_ERROR, guac_user_log(user, GUAC_LOG_ERROR,
"Inbound half of channel \"%s\" connected.", "Inbound half of channel \"%s\" connected.",
name); 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; stream->blob_handler = guac_rdp_svc_blob_handler;
rdp_stream->type = GUAC_RDP_INBOUND_SVC_STREAM; rdp_stream->type = GUAC_RDP_INBOUND_SVC_STREAM;
rdp_stream->svc = svc; rdp_stream->svc = svc;
svc->input_pipe = stream;
return 0; 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) { 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; guac_rdp_stream* rdp_stream;
/* Init stream data */ /* 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; stream->end_handler = guac_rdp_clipboard_end_handler;
rdp_stream->type = GUAC_RDP_INBOUND_CLIPBOARD_STREAM; 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; 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) { void* data, int length) {
int bytes_written; int bytes_written;
guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
/* Get filesystem, return error if no filesystem 0*/ /* 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) { 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_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; return 0;
} }
@ -202,10 +208,10 @@ int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream,
/* On error, abort */ /* On error, abort */
if (bytes_written < 0) { if (bytes_written < 0) {
guac_protocol_send_ack(client->socket, stream, guac_protocol_send_ack(user->socket, stream,
"FAIL (BAD WRITE)", "FAIL (BAD WRITE)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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) { void* data, int length) {
guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; 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 */ /* Write blob data to SVC directly */
guac_rdp_svc_write(rdp_stream->svc, data, length); 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_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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) { void* data, int length) {
rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; guac_client* client = user->client;
guac_common_clipboard_append(client_data->clipboard, (char*) data, length); guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_clipboard_append(rdp_client->clipboard, (char*) data, length);
return 0; 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; guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
/* Get filesystem, return error if no filesystem */ /* 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) { 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_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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); guac_rdp_fs_close(fs, rdp_stream->upload_status.file_id);
/* Acknowledge stream end */ /* 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_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
free(rdp_stream); free(rdp_stream);
return 0; 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; guac_client* client = user->client;
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;
RDP_CB_FORMAT_LIST_EVENT* format_list = RDP_CB_FORMAT_LIST_EVENT* format_list =
(RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new( (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); NULL, NULL);
/* Terminate clipboard data with 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 */ /* 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[0] = CB_FORMAT_TEXT;
format_list->formats[1] = CB_FORMAT_UNICODETEXT; format_list->formats[1] = CB_FORMAT_UNICODETEXT;
format_list->num_formats = 2; format_list->num_formats = 2;
@ -298,17 +308,19 @@ int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream) {
return 0; 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) { 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; guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
/* Get filesystem, return error if no filesystem */ /* 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) { 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_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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, send as blob */
if (bytes_read > 0) { if (bytes_read > 0) {
rdp_stream->download_status.offset += bytes_read; rdp_stream->download_status.offset += bytes_read;
guac_protocol_send_blob(client->socket, stream, guac_protocol_send_blob(user->socket, stream,
buffer, bytes_read); buffer, bytes_read);
} }
/* If EOF, send end */ /* If EOF, send end */
else if (bytes_read == 0) { else if (bytes_read == 0) {
guac_protocol_send_end(client->socket, stream); guac_protocol_send_end(user->socket, stream);
guac_client_free_stream(client, stream); guac_user_free_stream(user, stream);
free(rdp_stream); free(rdp_stream);
} }
/* Otherwise, fail stream */ /* Otherwise, fail stream */
else { else {
guac_client_log(client, GUAC_LOG_ERROR, guac_user_log(user, GUAC_LOG_ERROR,
"Error reading file for download"); "Error reading file for download");
guac_protocol_send_end(client->socket, stream); guac_protocol_send_end(user->socket, stream);
guac_client_free_stream(client, stream); guac_user_free_stream(user, stream);
free(rdp_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 else
guac_client_free_stream(client, stream); guac_user_free_stream(user, stream);
return 0; 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) { char* message, guac_protocol_status status) {
int blob_written = 0; 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) { if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
guac_rdp_fs_close(rdp_stream->ls_status.fs, guac_rdp_fs_close(rdp_stream->ls_status.fs,
rdp_stream->ls_status.file_id); rdp_stream->ls_status.file_id);
guac_client_free_stream(client, stream); guac_user_free_stream(user, stream);
free(rdp_stream); free(rdp_stream);
return 0; 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, if (!guac_rdp_fs_append_filename(absolute_path,
rdp_stream->ls_status.directory_name, filename)) { 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 " "Skipping filename \"%s\" - filename is invalid or "
"resulting path is too long", filename); "resulting path is too long", filename);
@ -414,12 +426,12 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
/* Determine mimetype */ /* Determine mimetype */
const char* mimetype; const char* mimetype;
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) if (file->attributes & FILE_ATTRIBUTE_DIRECTORY)
mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE; mimetype = GUAC_USER_STREAM_INDEX_MIMETYPE;
else else
mimetype = "application/octet-stream"; mimetype = "application/octet-stream";
/* Write entry */ /* 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); &rdp_stream->ls_status.json_state, absolute_path, mimetype);
guac_rdp_fs_close(rdp_stream->ls_status.fs, file_id); 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) { if (filename == NULL) {
/* Complete JSON object */ /* Complete JSON object */
guac_common_json_end_object(client, stream, guac_common_json_end_object(user, stream,
&rdp_stream->ls_status.json_state); &rdp_stream->ls_status.json_state);
guac_common_json_flush(client, stream, guac_common_json_flush(user, stream,
&rdp_stream->ls_status.json_state); &rdp_stream->ls_status.json_state);
/* Clean up resources */ /* Clean up resources */
@ -441,21 +453,24 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
free(rdp_stream); free(rdp_stream);
/* Signal of stream */ /* Signal of stream */
guac_protocol_send_end(client->socket, stream); guac_protocol_send_end(user->socket, stream);
guac_client_free_stream(client, stream); guac_user_free_stream(user, stream);
} }
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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) { char* name) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get filesystem, ignore request if no filesystem */ /* 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) if (fs == NULL)
return 0; 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, int file_id = guac_rdp_fs_open(fs, name, ACCESS_GENERIC_READ, 0,
DISP_FILE_OPEN, 0); DISP_FILE_OPEN, 0);
if (file_id < 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); name);
return 0; 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); sizeof(rdp_stream->ls_status.directory_name) - 1);
/* Allocate stream for body */ /* 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->ack_handler = guac_rdp_ls_ack_handler;
stream->data = rdp_stream; stream->data = rdp_stream;
/* Init JSON object state */ /* Init JSON object state */
guac_common_json_begin_object(client, stream, guac_common_json_begin_object(user, stream,
&rdp_stream->ls_status.json_state); &rdp_stream->ls_status.json_state);
/* Associate new stream with get request */ /* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream, guac_protocol_send_body(user->socket, object, stream,
GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name); 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; rdp_stream->download_status.offset = 0;
/* Allocate stream for body */ /* 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->data = rdp_stream;
stream->ack_handler = guac_rdp_download_ack_handler; stream->ack_handler = guac_rdp_download_ack_handler;
/* Associate new stream with get request */ /* 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); "application/octet-stream", name);
} }
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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_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 */ /* 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) { 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_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; return 0;
} }
@ -545,9 +563,9 @@ int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
/* Abort on failure */ /* Abort on failure */
if (file_id < 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_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; 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; stream->end_handler = guac_rdp_upload_end_handler;
/* Acknowledge stream creation */ /* 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_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket); guac_socket_flush(user->socket);
return 0; return 0;
} }

View File

@ -28,7 +28,7 @@
#include "guac_json.h" #include "guac_json.h"
#include "rdp_svc.h" #include "rdp_svc.h"
#include <guacamole/client.h> #include <guacamole/user.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
@ -163,125 +163,67 @@ typedef struct guac_rdp_stream {
/** /**
* Handler for inbound files related to file uploads. * Handler for inbound files related to file uploads.
*/ */
int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream, guac_user_file_handler guac_rdp_upload_file_handler;
char* mimetype, char* filename);
/** /**
* Handler for inbound pipes related to static virtual channels. * Handler for inbound pipes related to static virtual channels.
*/ */
int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream, guac_user_pipe_handler guac_rdp_svc_pipe_handler;
char* mimetype, char* name);
/** /**
* Handler for inbound clipboard data. * Handler for inbound clipboard data.
*/ */
int guac_rdp_clipboard_handler(guac_client* client, guac_stream* stream, guac_user_clipboard_handler guac_rdp_clipboard_handler;
char* mimetype);
/** /**
* Handler for stream data related to file uploads. * Handler for stream data related to file uploads.
*/ */
int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream, guac_user_blob_handler guac_rdp_upload_blob_handler;
void* data, int length);
/** /**
* Handler for stream data related to static virtual channels. * Handler for stream data related to static virtual channels.
*/ */
int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream, guac_user_blob_handler guac_rdp_svc_blob_handler;
void* data, int length);
/** /**
* Handler for stream data related to clipboard. * Handler for stream data related to clipboard.
*/ */
int guac_rdp_clipboard_blob_handler(guac_client* client, guac_stream* stream, guac_user_blob_handler guac_rdp_clipboard_blob_handler;
void* data, int length);
/** /**
* Handler for end-of-stream related to file uploads. * 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. * 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. * Handler for acknowledgements of receipt of data related to file downloads.
*/ */
int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream, guac_user_ack_handler guac_rdp_download_ack_handler;
char* message, guac_protocol_status status);
/** /**
* Handler for ack messages received due to receipt of a "body" or "blob" * Handler for ack messages received due to receipt of a "body" or "blob"
* instruction associated with a directory list operation. * 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, guac_user_ack_handler guac_rdp_ls_ack_handler;
char* message, guac_protocol_status status);
/** /**
* Handler for get messages. In context of downloads and the filesystem exposed * 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 * via the Guacamole protocol, get messages request the body of a file within
* the filesystem. * 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, guac_user_get_handler guac_rdp_download_get_handler;
char* name);
/** /**
* Handler for put messages. In context of uploads and the filesystem exposed * Handler for put messages. In context of uploads and the filesystem exposed
* via the Guacamole protocol, put messages request write access to a file * via the Guacamole protocol, put messages request write access to a file
* within the filesystem. * 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_user_put_handler guac_rdp_upload_put_handler;
guac_stream* stream, char* mimetype, char* name);
#endif #endif

View File

@ -23,6 +23,7 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "guac_list.h" #include "guac_list.h"
#include "rdp.h"
#include "rdp_svc.h" #include "rdp_svc.h"
#include <freerdp/utils/svc_plugin.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 */ /* Init SVC */
svc->client = client; svc->client = client;
svc->plugin = NULL; svc->plugin = NULL;
svc->input_pipe = NULL;
svc->output_pipe = NULL; svc->output_pipe = NULL;
/* Warn about name length */ /* Warn about name length */
@ -65,26 +65,52 @@ void guac_rdp_free_svc(guac_rdp_svc* svc) {
free(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) { 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 */ /* Add to list of available SVC */
guac_common_list_lock(client_data->available_svc); guac_common_list_lock(rdp_client->available_svc);
guac_common_list_add(client_data->available_svc, svc); guac_common_list_add(rdp_client->available_svc, svc);
guac_common_list_unlock(client_data->available_svc); guac_common_list_unlock(rdp_client->available_svc);
} }
guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) { 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_common_list_element* current;
guac_rdp_svc* found = NULL; guac_rdp_svc* found = NULL;
/* For each available SVC */ /* For each available SVC */
guac_common_list_lock(client_data->available_svc); guac_common_list_lock(rdp_client->available_svc);
current = client_data->available_svc->head; current = rdp_client->available_svc->head;
while (current != NULL) { while (current != NULL) {
/* If name matches, found */ /* If name matches, found */
@ -97,7 +123,7 @@ guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) {
current = current->next; current = current->next;
} }
guac_common_list_unlock(client_data->available_svc); guac_common_list_unlock(rdp_client->available_svc);
return found; 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) { 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_common_list_element* current;
guac_rdp_svc* found = NULL; guac_rdp_svc* found = NULL;
/* For each available SVC */ /* For each available SVC */
guac_common_list_lock(client_data->available_svc); guac_common_list_lock(rdp_client->available_svc);
current = client_data->available_svc->head; current = rdp_client->available_svc->head;
while (current != NULL) { while (current != NULL) {
/* If name matches, remove entry */ /* If name matches, remove entry */
guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data; guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data;
if (strcmp(current_svc->name, name) == 0) { 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; found = current_svc;
break; break;
} }
@ -125,7 +151,7 @@ guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name) {
current = current->next; 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 removed entry, if any */
return found; return found;

View File

@ -55,12 +55,6 @@ typedef struct guac_rdp_svc {
*/ */
char name[GUAC_RDP_SVC_MAX_LENGTH+1]; 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 output pipe, opened when the RDP server receives a connection to
* the static channel. * the static channel.
@ -71,31 +65,104 @@ typedef struct guac_rdp_svc {
/** /**
* Allocate a new SVC with the given name. * 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); guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name);
/** /**
* Free the given SVC. * Free the given SVC.
*
* @param svc
* The static virtual channel to free.
*/ */
void guac_rdp_free_svc(guac_rdp_svc* svc); 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. * 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); 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. * 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); 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. * 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); guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name);
/** /**
* Write the given blob of data to the virtual channel. * 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); void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length);

View File

@ -23,38 +23,38 @@
#include "client.h" #include "client.h"
#include "resolution.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 width = user->info.optimal_width;
int height = client->info.optimal_height; int height = user->info.optimal_height;
/* Convert client pixels to remote pixels */ /* Convert user pixels to remote pixels */
width = width * resolution / client->info.optimal_resolution; width = width * resolution / user->info.optimal_resolution;
height = height * resolution / client->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 * 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; || 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 */ /* 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; return GUAC_RDP_NATIVE_RESOLUTION;
/* If native resolution is too tiny, try higher 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; return GUAC_RDP_HIGH_RESOLUTION;
/* Fallback to client-suggested resolution */ /* Fallback to user-suggested resolution */
return client->info.optimal_resolution; return user->info.optimal_resolution;
} }

View File

@ -23,27 +23,35 @@
#ifndef GUAC_RDP_RESOLUTION_H #ifndef GUAC_RDP_RESOLUTION_H
#define 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. * based on arbitrary criteria for reasonability.
* *
* @param client The guac_client to test the given resolution against. * @param user
* @param resolution The resolution to test, in DPI. * The guac_user to test the given resolution against.
* @return Non-zero if the resolution is reasonable, zero otherwise. *
* @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 * 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 * @param user
* determine an appropriate remote display resolution. * The guac_user whose size and resolution shall be used to determine an
* @return A reasonable resolution for the remote display, in DPI. * 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 #endif

View File

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

View File

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

View File

@ -20,18 +20,44 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_RDP_UNICODE_H
#include "config.h" #define GUAC_RDP_UNICODE_H
/** /**
* Convert the given number of UTF-16 characters to UTF-8 characters. * 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, void guac_rdp_utf16_to_utf8(const unsigned char* utf16, int length,
char* utf8, int size); char* utf8, int size);
/** /**
* Convert the given number of UTF-8 characters to UTF-16 characters. * 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, void guac_rdp_utf8_to_utf16(const unsigned char* utf8, int length,
char* utf16, int size); 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -20,19 +20,22 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_RDP_USER_H
#define GUAC_RDP_USER_H
#ifndef _GUAC_RDP_GUAC_HANDLERS_H #include <guacamole/user.h>
#define _GUAC_RDP_GUAC_HANDLERS_H
#include "config.h" /**
* Handler for joining users.
*/
guac_user_join_handler guac_rdp_user_join_handler;
#include <guacamole/client.h> /**
* Handler for received simple file uploads. This handler will automatically
int rdp_guac_client_free_handler(guac_client* client); * select between RDPDR and SFTP depending on which is available and which has
int rdp_guac_client_handle_messages(guac_client* client); * priority given associated settings.
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); guac_user_file_handler guac_rdp_user_file_handler;
int rdp_guac_client_size_handler(guac_client* client, int width, int height);
#endif #endif