Merge staging/1.1.0 changes back to master.

This commit is contained in:
Nick Couchman 2020-01-14 16:27:09 -05:00
commit 956c5f293e
120 changed files with 6703 additions and 7089 deletions

View File

@ -37,9 +37,9 @@ ARG BUILD_DIR=/tmp/guacd-docker-BUILD
ARG BUILD_DEPENDENCIES=" \ ARG BUILD_DEPENDENCIES=" \
autoconf \ autoconf \
automake \ automake \
freerdp2-dev \
gcc \ gcc \
libcairo2-dev \ libcairo2-dev \
libfreerdp-dev \
libjpeg62-turbo-dev \ libjpeg62-turbo-dev \
libossp-uuid-dev \ libossp-uuid-dev \
libpango1.0-dev \ libpango1.0-dev \
@ -71,7 +71,7 @@ RUN ${PREFIX_DIR}/bin/build-guacd.sh "$BUILD_DIR" "$PREFIX_DIR"
RUN ${PREFIX_DIR}/bin/list-dependencies.sh \ RUN ${PREFIX_DIR}/bin/list-dependencies.sh \
${PREFIX_DIR}/sbin/guacd \ ${PREFIX_DIR}/sbin/guacd \
${PREFIX_DIR}/lib/libguac-client-*.so \ ${PREFIX_DIR}/lib/libguac-client-*.so \
${PREFIX_DIR}/lib/freerdp/guac*.so \ ${PREFIX_DIR}/lib/freerdp2/guac*.so \
> ${PREFIX_DIR}/DEPENDENCIES > ${PREFIX_DIR}/DEPENDENCIES
# Use same Debian as the base for the runtime image # Use same Debian as the base for the runtime image
@ -92,7 +92,6 @@ ENV GUACD_LOG_LEVEL=info
ARG RUNTIME_DEPENDENCIES=" \ ARG RUNTIME_DEPENDENCIES=" \
ca-certificates \ ca-certificates \
ghostscript \ ghostscript \
libfreerdp-plugins-standard \
fonts-liberation \ fonts-liberation \
fonts-dejavu \ fonts-dejavu \
xfonts-terminus" xfonts-terminus"
@ -108,7 +107,7 @@ RUN apt-get update && \
# Link FreeRDP plugins into proper path # Link FreeRDP plugins into proper path
RUN ${PREFIX_DIR}/bin/link-freerdp-plugins.sh \ RUN ${PREFIX_DIR}/bin/link-freerdp-plugins.sh \
${PREFIX_DIR}/lib/freerdp/guac*.so ${PREFIX_DIR}/lib/freerdp2/libguac*.so
# Expose the default listener port # Expose the default listener port
EXPOSE 4822 EXPOSE 4822

View File

@ -555,537 +555,143 @@ then
fi fi
# #
# FreeRDP # FreeRDP 2 (libfreerdp2, libfreerdp-client2, and libwinpr2)
# #
have_freerdp=disabled have_freerdp2=disabled
RDP_LIBS=
WINPR_LIBS=
AC_ARG_WITH([rdp], AC_ARG_WITH([rdp],
[AS_HELP_STRING([--with-rdp], [AS_HELP_STRING([--with-rdp],
[support RDP @<:@default=check@:>@])], [support RDP @<:@default=check@:>@])],
[], [],
[with_rdp=check]) [with_rdp=check])
# Preserve CPPFLAGS so it can be restored later, following the addition of
# options specific to FreeRDP tests
OLDCPPFLAGS="$CPPFLAGS"
if test "x$with_rdp" != "xno" if test "x$with_rdp" != "xno"
then then
have_winpr=yes have_freerdp2=yes
have_freerdp=yes PKG_CHECK_MODULES([RDP], [freerdp2 freerdp-client2 winpr2],
legacy_freerdp_extensions=no [CPPFLAGS="${RDP_CFLAGS} -Werror $CPPFLAGS"],
rdpsettings_interface=unknown
rdpsettings_audiocapture=yes
rdpsettings_audioplayback=yes
rdpsettings_deviceredirection=yes
freerdp_interface=unknown
event_interface=unknown
# libfreerdp-core / libfreerdp
AC_CHECK_LIB([freerdp-core], [freerdp_new],
[RDP_LIBS="$RDP_LIBS -lfreerdp-core"],
[AC_CHECK_LIB([freerdp], [freerdp_new],
[RDP_LIBS="$RDP_LIBS -lfreerdp -lfreerdp-client"],
[AC_MSG_WARN([ [AC_MSG_WARN([
-------------------------------------------- --------------------------------------------
Unable to find libfreerdp-core / libfreerdp Unable to find FreeRDP (libfreerdp2 / libfreerdp-client2 / libwinpr2)
RDP will be disabled. RDP will be disabled.
--------------------------------------------]) --------------------------------------------])
have_freerdp=no])]) have_freerdp2=no])
fi
# libfreerdp-cache
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_LIB([freerdp-cache], [glyph_cache_register_callbacks],
[RDP_LIBS="$RDP_LIBS -lfreerdp-cache"])
fi
# libfreerdp-channels (1.0) / libfreerdp-client + libfreerdp-core (1.1)
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_LIB([freerdp-client], [freerdp_channels_new],
[RDP_LIBS="$RDP_LIBS -lfreerdp-client"],
[AC_CHECK_LIB([freerdp-channels], [freerdp_channels_new],
[RDP_LIBS="$RDP_LIBS -lfreerdp-channels"
legacy_freerdp_extensions=yes])],
[-lfreerdp-core])
fi
# libfreerdp-utils
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_LIB([freerdp-utils], [svc_plugin_init],
[RDP_LIBS="$RDP_LIBS -lfreerdp-utils"])
fi
# libfreerdp-codec
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_LIB([freerdp-codec], [freerdp_image_convert],
[RDP_LIBS="$RDP_LIBS -lfreerdp-codec"])
fi fi
# Available color conversion functions # Available color conversion functions
if test "x${have_freerdp}" = "xyes" if test "x$have_freerdp2" = "xyes"
then then
AC_CHECK_DECL([freerdp_convert_gdi_order_color],
[AC_DEFINE([HAVE_FREERDP_CONVERT_GDI_ORDER_COLOR],, # FreeRDP 2.0.0-rc3 and older referred to FreeRDPConvertColor() as
[Whether freerdp_convert_gdi_order_color() is defined])],, # ConvertColor()
AC_CHECK_DECL([FreeRDPConvertColor],
[AC_DEFINE([HAVE_FREERDPCONVERTCOLOR],,
[Whether FreeRDPConvertColor() is defined])],,
[#include <freerdp/codec/color.h>]) [#include <freerdp/codec/color.h>])
AC_CHECK_DECL([freerdp_color_convert_drawing_order_color_to_gdi_color],
[AC_DEFINE([HAVE_FREERDP_COLOR_CONVERT_DRAWING_ORDER_COLOR_TO_GDI_COLOR],,
[Whether freerdp_color_convert_drawing_order_color_to_gdi_color() is defined])],,
[#include <freerdp/codec/color.h>])
fi fi
# Check for interval polling in plugins # Glyph callback variants
if test "x${have_freerdp}" = "xyes" if test "x${have_freerdp2}" = "xyes"
then then
AC_CHECK_MEMBERS([rdpSvcPlugin.interval_ms],,,
[[#include <freerdp/utils/svc_plugin.h>]])
fi
# Keyboard layout header # FreeRDP 2.0.0-rc3 and older used UINT32 for integer parameters to all
if test "x${have_freerdp}" = "xyes" # rdpGlyph callbacks
then AC_MSG_CHECKING([whether rdpGlyph callbacks accept INT32 integer parameters])
AC_CHECK_HEADERS([freerdp/locale/keyboard.h],, AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
[AC_CHECK_HEADERS([freerdp/kbd/layouts.h],,
[AC_MSG_WARN([
--------------------------------------------
Unable to find keyboard layout headers
RDP will be disabled.
--------------------------------------------])
have_freerdp=no])])
fi
# New headers defining addins
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_HEADERS([freerdp/addin.h freerdp/client/channels.h])
fi
# Header defining cliprdr
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_HEADERS([freerdp/client/cliprdr.h],,
[AC_CHECK_HEADERS([freerdp/plugins/cliprdr.h],,
[AC_MSG_WARN([
--------------------------------------------
Unable to find cliprdr headers
RDP will be disabled.
--------------------------------------------])
have_freerdp=no],
[#include <freerdp/types.h>])],
[#include <winpr/wtypes.h>
#include <winpr/collections.h>])
fi
# Header defining display update channel
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_HEADERS([freerdp/client/disp.h],
[AC_DEFINE([HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT],,
[Whether FreeRDP supports the display update channel])]
[AC_CHECK_MEMBERS([rdpSettings.SupportDisplayControl],,,
[[#include <freerdp/freerdp.h>]])],,
[#include <winpr/wtypes.h>
#include <winpr/collections.h>])
fi
# Support for RDP gateways
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_MEMBERS([rdpSettings.GatewayEnabled],
[AC_DEFINE([HAVE_FREERDP_GATEWAY_SUPPORT],,
[Whether FreeRDP supports RDP gateways])],,
[[#include <freerdp/freerdp.h>]])
fi
# Support for load balancing via connection brokers
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_MEMBERS([rdpSettings.LoadBalanceInfo],
[AC_DEFINE([HAVE_FREERDP_LOAD_BALANCER_SUPPORT],,
[Whether FreeRDP supports load balancers])],,
[[#include <freerdp/freerdp.h>]])
fi
# Support for "PubSub" event system
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_DECL([PubSub_SubscribeChannelConnected],
[AC_DEFINE([HAVE_FREERDP_EVENT_PUBSUB],,
[Whether this version of FreeRDP provides the PubSub event system])],,
[#include <freerdp/event.h>])
fi
# Addin registration variations
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_DECL([freerdp_register_addin_provider],
[AC_DEFINE([HAVE_FREERDP_REGISTER_ADDIN_PROVIDER],,
[Whether freerdp_register_addin_provider() is defined])],,
[#include <freerdp/addin.h>])
AC_CHECK_DECL([freerdp_channels_global_init],
[AC_DEFINE([HAVE_FREERDP_CHANNELS_GLOBAL_INIT],,
[Whether freerdp_channels_global_init() is defined])],,
[#include <freerdp/channels/channels.h>])
fi
# Availability of ADDIN_ARGV structure for configuring plugins
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_TYPE([ADDIN_ARGV],
[AC_DEFINE([HAVE_ADDIN_ARGV],,
[Whether the ADDIN_ARGV type is available])],,
[#include <freerdp/settings.h>])
fi
#
# FreeRDP: WinPR
#
# Check for stream support via WinPR
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_HEADER(winpr/stream.h,,
[have_winpr=no,
AC_CHECK_DECL([stream_write_uint8],,
[AC_MSG_WARN([
--------------------------------------------
Unable to find stream support
RDP will be disabled.
--------------------------------------------])
have_freerdp=no],
[#include <freerdp/utils/stream.h>])])
fi
# Find location of Stream_New and Stream_free
if test "x${have_freerdp}" = "xyes" -a "x${have_winpr}" = "xyes"
then
AC_CHECK_LIB([winpr], [Stream_New, Stream_Free],
[WINPR_LIBS="$WINPR_LIBS -lwinpr"],
[AC_CHECK_LIB([winpr-utils], [Stream_New, Stream_Free],
[WINPR_LIBS="$WINPR_LIBS -lwinpr-utils"],
[AC_MSG_WARN([
------------------------------------------
Unable to locate stream functions in winpr
libraries. RDP will be disabled.
------------------------------------------])
have_freerdp=no])])
fi
# Check for types in WinPR
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_HEADER(winpr/wtypes.h,,
[have_winpr=no,
AC_CHECK_HEADER(freerdp/types.h,,
[AC_MSG_WARN([
--------------------------------------------
Unable to find type definitions
RDP will be disabled.
--------------------------------------------])
have_freerdp=no])])
fi
if test "x${have_freerdp}" = "xyes" -a "x${have_winpr}" = "xyes"
then
AC_DEFINE([ENABLE_WINPR],,
[Whether library support for WinPR types was found])
fi
#
# FreeRDP: freerdp
#
if test "x${have_freerdp}" = "xyes"
then
# Check for current (as of 1.1) freerdp interface
AC_CHECK_MEMBERS([freerdp.ContextSize],
[freerdp_interface=stable],,
[[#include <freerdp/freerdp.h>]])
# If not current, check for legacy interface
if test "x${freerdp_interface}" = "xunknown"
then
AC_CHECK_MEMBERS([freerdp.context_size],
[freerdp_interface=legacy],,
[[#include <freerdp/freerdp.h>]])
fi
# Set defines based on interface type, warn if unknown
if test "x${freerdp_interface}" = "xlegacy"; then
AC_DEFINE([LEGACY_FREERDP],,
[Whether the older version of the FreeRDP API was found])
elif test "x${freerdp_interface}" = "xunknown"; then
AC_MSG_WARN([
--------------------------------------------
Unknown FreeRDP interface
RDP will be disabled.
--------------------------------------------])
have_freerdp=no
fi
fi
#
# FreeRDP: rdpSettings
#
if test "x${have_freerdp}" = "xyes"
then
# Check for current (as of 1.1) rdpSettings interface
AC_CHECK_MEMBERS([rdpSettings.Width,
rdpSettings.Height,
rdpSettings.FastPathInput,
rdpSettings.FastPathOutput,
rdpSettings.SendPreconnectionPdu,
rdpSettings.OrderSupport],
[rdpsettings_interface=stable],,
[[#include <freerdp/freerdp.h>]])
# If not current, check for legacy interface
if test "x${rdpsettings_interface}" = "xunknown"
then
AC_CHECK_MEMBERS([rdpSettings.width,
rdpSettings.height,
rdpSettings.order_support],
[rdpsettings_interface=legacy],,
[[#include <freerdp/freerdp.h>]])
fi
# Set defines based on interface type, warn if unknown
if test "x${rdpsettings_interface}" = "xlegacy"; then
AC_DEFINE([LEGACY_RDPSETTINGS],,
[Whether the legacy version of the rdpSettings API was found])
# Legacy interface may not have AudioPlayback settings
AC_CHECK_MEMBERS([rdpSettings.audio_playback],,
[rdpsettings_audioplayback=no],
[[#include <freerdp/freerdp.h>]])
# Legacy interface may not have AudioCapture settings
AC_CHECK_MEMBERS([rdpSettings.audio_capture],,
[rdpsettings_audiocapture=no],
[[#include <freerdp/freerdp.h>]])
# Legacy interface may not have DeviceRedirection settings
AC_CHECK_MEMBERS([rdpSettings.device_redirection],,
[rdpsettings_deviceredirection=no],
[[#include <freerdp/freerdp.h>]])
elif test "x${rdpsettings_interface}" = "xunknown"; then
AC_MSG_WARN([
--------------------------------------------
Unknown rdpSettings interface
RDP will be disabled.
--------------------------------------------])
have_freerdp=no
fi
fi
# Activate audio playback settings if present
if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_audioplayback}" = "xyes"; then
AC_DEFINE([HAVE_RDPSETTINGS_AUDIOPLAYBACK],,
[Whether the rdpSettings structure has AudioPlayback settings])
fi
# Activate audio capture settings if present
if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_audiocapture}" = "xyes"; then
AC_DEFINE([HAVE_RDPSETTINGS_AUDIOCAPTURE],,
[Whether the rdpSettings structure has AudioCapture settings])
fi
# Activate device redirection settings if present
if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_deviceredirection}" = "xyes"; then
AC_DEFINE([HAVE_RDPSETTINGS_DEVICEREDIRECTION],,
[Whether the rdpSettings structure has DeviceRedirection settings])
fi
# Check if the type CHANNEL_ENTRY_POINTS_FREERDP exists, if not define it to CHANNEL_ENTRY_POINTS_EX
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_TYPE([CHANNEL_ENTRY_POINTS_FREERDP],,
AC_DEFINE([CHANNEL_ENTRY_POINTS_FREERDP],[CHANNEL_ENTRY_POINTS_EX], [Type compatibility]),
[[#include <freerdp/svc.h>]])
fi
# Check if the freerdp version header exists
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_HEADERS([freerdp/version.h])
fi
#
# FreeRDP: rdpBitmap
#
if test "x${have_freerdp}" = "xyes"
then
AC_MSG_CHECKING([whether rdpBitmap.Decompress() requires the codec_id])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include <winpr/wtypes.h>
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
void __decompress(rdpContext* context, #include <freerdp/graphics.h>
rdpBitmap* bitmap, #include <winpr/wtypes.h>
UINT8* data,
int width,
int height,
int bpp,
int length,
BOOL compressed,
int codec_id);
rdpBitmap b = { .Decompress = __decompress };]])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
AC_DEFINE([LEGACY_RDPBITMAP],,
[Whether the legacy rdpBitmap API was found])])
fi
# BOOL test_begindraw(rdpContext* context, INT32 x, INT32 y,
# FreeRDP: IWTSVirtualChannelCallback INT32 width, INT32 height, UINT32 fgcolor, UINT32 bgcolor,
# BOOL redundant);
if test "x${have_freerdp}" = "xyes" rdpGlyph glyph = {
then .BeginDraw = test_begindraw
AC_MSG_CHECKING([whether IWTSVirtualChannelCallback.OnDataReceived() uses a wStream])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include <winpr/wtypes.h>
#include <freerdp/dvc.h>
#include <freerdp/freerdp.h>
int __data_received(
IWTSVirtualChannelCallback* channel_callback,
wStream* stream);
IWTSVirtualChannelCallback cb = {
.OnDataReceived = __data_received
}; };
int main() {
return
cb.OnDataReceived(NULL, NULL);
}]])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
AC_DEFINE([LEGACY_IWTSVIRTUALCHANNELCALLBACK],,
[Whether the legacy IWTSVirtualChannelCallback API was found])])
fi
#
# FreeRDP: Decompression function variants
#
# Check whether interleaved_decompress() can handle the palette
if test "x${have_freerdp}" = "xyes"
then
AC_MSG_CHECKING([whether interleaved_decompress() accepts an additional palette parameter])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include <freerdp/codec/interleaved.h>
int main() { int main() {
BYTE* palette = NULL; return (int) glyph.BeginDraw(NULL, 0, 0, 0, 0, 0, 0, FALSE);
interleaved_decompress(NULL, NULL, 0, 0, NULL, 0, 0, 0, 0, 0, 0, palette); }
}]])],
[AC_MSG_RESULT([yes]) ]])],
AC_DEFINE([INTERLEAVED_DECOMPRESS_TAKES_PALETTE],, [AC_MSG_RESULT([yes])]
[Whether interleaved_decompress() accepts an additional palette parameter])], [AC_DEFINE([FREERDP_GLYPH_CALLBACKS_ACCEPT_INT32],,
[Whether rdpGlyph callbacks accept INT32 integer parameters])],
[AC_MSG_RESULT([no])]) [AC_MSG_RESULT([no])])
fi fi
# Check whether planar_decompress() will handle flipping # CLIPRDR callback variants
if test "x${have_freerdp}" = "xyes" if test "x${have_freerdp2}" = "xyes"
then then
AC_MSG_CHECKING([whether planar_decompress() can flip])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include <freerdp/codec/planar.h> # FreeRDP 2.0.0-rc3 and older did not use const for CLIPRDR callbacks
AC_MSG_CHECKING([whether CLIPRDR callbacks require const for their final parameter])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
#include <freerdp/client/cliprdr.h>
#include <winpr/wtypes.h>
UINT test_monitor_ready(CliprdrClientContext* cliprdr,
const CLIPRDR_MONITOR_READY* monitor_ready);
CliprdrClientContext context = {
.MonitorReady = test_monitor_ready
};
int main() { int main() {
BOOL* flip = TRUE; return (int) context.MonitorReady(NULL, NULL);
planar_decompress(NULL, NULL, 0, NULL, 0, 0, 0, 0, 0, 0, flip); }
}]])],
[AC_MSG_RESULT([yes]) ]])],
AC_DEFINE([PLANAR_DECOMPRESS_CAN_FLIP],, [AC_MSG_RESULT([yes])]
[Whether planar_decompress() can flip])], [AC_DEFINE([FREERDP_CLIPRDR_CALLBACKS_REQUIRE_CONST],,
[Whether CLIPRDR callbacks require const for the final parameter])],
[AC_MSG_RESULT([no])]) [AC_MSG_RESULT([no])])
fi fi
# # RAIL callback variants
# FreeRDP: rdpContext if test "x${have_freerdp2}" = "xyes"
#
# Check for rdpContext.codecs
if test "x${have_freerdp}" = "xyes"
then then
AC_CHECK_MEMBERS([rdpContext.codecs],
[AC_DEFINE([FREERDP_BITMAP_REQUIRES_ALIGNED_MALLOC],, # FreeRDP 2.0.0-rc3 and older did not use const for RAIL callbacks
[Whether this version of FreeRDP requires _aligned_malloc() for bitmap data])],, AC_MSG_CHECKING([whether RAIL callbacks require const for their final parameter])
[[#include <freerdp/freerdp.h>]]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
#include <freerdp/client/rail.h>
#include <winpr/wtypes.h>
UINT test_server_handshake(RailClientContext* rail,
const RAIL_HANDSHAKE_ORDER* handshake);
RailClientContext context = {
.ServerHandshake = test_server_handshake
};
int main() {
return (int) context.ServerHandshake(NULL, NULL);
}
]])],
[AC_MSG_RESULT([yes])]
[AC_DEFINE([FREERDP_RAIL_CALLBACKS_REQUIRE_CONST],,
[Whether RAIL callbacks require const for the final parameter])],
[AC_MSG_RESULT([no])])
fi fi
# # Restore CPPFLAGS, removing FreeRDP-specific options needed for testing
# FreeRDP: rdpPalette CPPFLAGS="$OLDCPPFLAGS"
#
if test "x${have_freerdp}" = "xyes" AM_CONDITIONAL([ENABLE_RDP], [test "x${have_freerdp2}" = "xyes"])
then
AC_MSG_CHECKING([whether rdpPalette.entries is static])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include <freerdp/update.h>
rdpPalette p;
PALETTE_ENTRY* foo = p.entries;]])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
AC_DEFINE([LEGACY_RDPPALETTE],,
[Whether the legacy rdpPalette API was found])])
fi
#
# FreeRDP: rdpPointer
#
# Check for SetDefault and SetNull members of rdpPointer
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_MEMBERS([rdpPointer.SetDefault,
rdpPointer.SetNull],
,,
[[#include <freerdp/freerdp.h>]])
fi
#
# FreeRDP: wMessage / RDP_EVENT
#
# Check for current (as of 1.1) wMessage interface
if test "x${have_freerdp}" = "xyes"
then
AC_CHECK_MEMBERS([wMessage.id],
[event_interface=stable],,
[[#include <winpr/collections.h>]])
# If not current, check for legacy (RDP_EVENT) interface
if test "x${event_interface}" = "xunknown"
then
AC_CHECK_MEMBERS([RDP_EVENT.event_class],
[event_interface=legacy],,
[[#include <freerdp/types.h>]])
fi
# Set defines based on interface type, warn if unknown
if test "x${event_interface}" = "xlegacy"; then
AC_DEFINE([LEGACY_EVENT],,
[Whether the legacy RDP_EVENT API was found])
elif test "x${event_interface}" = "xunknown"; then
AC_MSG_WARN([
--------------------------------------------
Unknown event interface
RDP will be disabled.
--------------------------------------------])
have_freerdp=no
fi
fi
AM_CONDITIONAL([LEGACY_FREERDP_EXTENSIONS], [test "x${legacy_freerdp_extensions}" = "xyes"])
AM_CONDITIONAL([ENABLE_WINPR], [test "x${have_winpr}" = "xyes"])
AM_CONDITIONAL([ENABLE_RDP], [test "x${have_freerdp}" = "xyes"])
AC_SUBST(RDP_LIBS)
AC_SUBST(WINPR_LIBS)
# #
# libssh2 # libssh2
@ -1426,7 +1032,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION
Library status: Library status:
freerdp ............. ${have_freerdp} freerdp2 ............ ${have_freerdp2}
pango ............... ${have_pango} pango ............... ${have_pango}
libavcodec .......... ${have_libavcodec} libavcodec .......... ${have_libavcodec}
libavutil ........... ${have_libavutil} libavutil ........... ${have_libavutil}

View File

@ -260,18 +260,31 @@ static int __guac_common_surface_is_opaque(guac_common_surface* surface,
/** /**
* Returns whether the given rectangle should be combined into the existing * Returns whether the given rectangle should be combined into the existing
* dirty rectangle, to be eventually flushed as a "png" instruction. * dirty rectangle, to be eventually flushed as image data, or would be best
* kept independent of the current rectangle.
* *
* @param surface The surface to be queried. * @param surface
* @param rect The update rectangle. * The surface being updated.
* @param rect_only Non-zero if this update, by its nature, contains only *
* metainformation about the update's rectangle, zero if * @param rect
* the update also contains image data. * The bounding rectangle of the update being made to the surface.
* @return Non-zero if the update should be combined with any existing update, *
* zero otherwise. * @param rect_only
* Non-zero if this update, by its nature, contains only metainformation
* about the update's bounding rectangle, zero if the update also contains
* image data.
*
* @return
* Non-zero if the update should be combined with any existing update, zero
* otherwise.
*/ */
static int __guac_common_should_combine(guac_common_surface* surface, const guac_common_rect* rect, int rect_only) { static int __guac_common_should_combine(guac_common_surface* surface, const guac_common_rect* rect, int rect_only) {
/* Always favor combining updates if surface is currently a purely
* server-side scratch area */
if (!surface->realized)
return 1;
if (surface->dirty) { if (surface->dirty) {
int combined_cost, dirty_cost, update_cost; int combined_cost, dirty_cost, update_cost;

View File

@ -70,7 +70,7 @@ while [ -n "$1" ]; do
# Determine correct install location for FreeRDP plugins # Determine correct install location for FreeRDP plugins
FREERDP_DIR="$(where_is_freerdp "$1")" FREERDP_DIR="$(where_is_freerdp "$1")"
FREERDP_PLUGIN_DIR="${FREERDP_DIR}/freerdp" FREERDP_PLUGIN_DIR="${FREERDP_DIR}/freerdp2"
# Add symbolic link if necessary # Add symbolic link if necessary
if [ ! -e "$FREERDP_PLUGIN_DIR/$(basename "$1")" ]; then if [ ! -e "$FREERDP_PLUGIN_DIR/$(basename "$1")" ]; then

View File

@ -4,5 +4,6 @@ _generated_runner.c
test_rdp test_rdp
# Autogenerated sources # Autogenerated sources
_generated_channel_entry_wrappers.c
_generated_keymaps.c _generated_keymaps.c

View File

@ -29,217 +29,171 @@ ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libguac-client-rdp.la lib_LTLIBRARIES = libguac-client-rdp.la
SUBDIRS = . tests SUBDIRS = . tests
nodist_libguac_client_rdp_la_SOURCES = \
_generated_keymaps.c
libguac_client_rdp_la_SOURCES = \
audio_input.c \
client.c \
decompose.c \
dvc.c \
error.c \
input.c \
keyboard.c \
ptr_string.c \
rdp.c \
rdp_bitmap.c \
rdp_cliprdr.c \
rdp_color.c \
rdp_disp.c \
rdp_fs.c \
rdp_gdi.c \
rdp_glyph.c \
rdp_keymap.c \
rdp_print_job.c \
rdp_pointer.c \
rdp_rail.c \
rdp_settings.c \
rdp_stream.c \
rdp_svc.c \
resolution.c \
unicode.c \
user.c
guacai_sources = \
audio_input.c \
guac_ai/ai_messages.c \
guac_ai/ai_service.c \
ptr_string.c
guacsvc_sources = \
guac_svc/svc_service.c \
rdp_svc.c
guacsnd_sources = \
guac_rdpsnd/rdpsnd_messages.c \
guac_rdpsnd/rdpsnd_service.c
guacdr_sources = \
guac_rdpdr/rdpdr_fs_messages.c \
guac_rdpdr/rdpdr_fs_messages_dir_info.c \
guac_rdpdr/rdpdr_fs_messages_file_info.c \
guac_rdpdr/rdpdr_fs_messages_vol_info.c \
guac_rdpdr/rdpdr_fs_service.c \
guac_rdpdr/rdpdr_messages.c \
guac_rdpdr/rdpdr_printer.c \
guac_rdpdr/rdpdr_service.c \
rdp_fs.c \
rdp_print_job.c \
rdp_stream.c \
unicode.c
noinst_HEADERS = \
compat/client-cliprdr.h \
compat/rail.h \
guac_ai/ai_messages.h \
guac_ai/ai_service.h \
guac_rdpdr/rdpdr_fs_messages.h \
guac_rdpdr/rdpdr_fs_messages_dir_info.h \
guac_rdpdr/rdpdr_fs_messages_file_info.h \
guac_rdpdr/rdpdr_fs_messages_vol_info.h \
guac_rdpdr/rdpdr_fs_service.h \
guac_rdpdr/rdpdr_messages.h \
guac_rdpdr/rdpdr_printer.h \
guac_rdpdr/rdpdr_service.h \
guac_rdpsnd/rdpsnd_messages.h \
guac_rdpsnd/rdpsnd_service.h \
guac_svc/svc_service.h \
audio_input.h \
client.h \
decompose.h \
dvc.h \
error.h \
input.h \
keyboard.h \
ptr_string.h \
rdp.h \
rdp_bitmap.h \
rdp_cliprdr.h \
rdp_color.h \
rdp_disp.h \
rdp_fs.h \
rdp_gdi.h \
rdp_glyph.h \
rdp_keymap.h \
rdp_pointer.h \
rdp_print_job.h \
rdp_rail.h \
rdp_settings.h \
rdp_status.h \
rdp_stream.h \
rdp_svc.h \
resolution.h \
unicode.h \
user.h
# Add compatibility layer for WinPR if not available
if ! ENABLE_WINPR
noinst_HEADERS += compat/winpr-stream.h compat/winpr-wtypes.h
libguac_client_rdp_la_SOURCES += compat/winpr-stream.c
guacai_sources += compat/winpr-stream.c
guacsvc_sources += compat/winpr-stream.c
guacsnd_sources += compat/winpr-stream.c
guacdr_sources += compat/winpr-stream.c
endif
# #
# Main RDP client library # Main RDP client library
# #
nodist_libguac_client_rdp_la_SOURCES = \
_generated_channel_entry_wrappers.c \
_generated_keymaps.c
libguac_client_rdp_la_SOURCES = \
bitmap.c \
channels/audio-input/audio-buffer.c \
channels/audio-input/audio-input.c \
channels/cliprdr.c \
channels/common-svc.c \
channels/disp.c \
channels/pipe-svc.c \
channels/rail.c \
channels/rdpdr/rdpdr-fs-messages-dir-info.c \
channels/rdpdr/rdpdr-fs-messages-file-info.c \
channels/rdpdr/rdpdr-fs-messages-vol-info.c \
channels/rdpdr/rdpdr-fs-messages.c \
channels/rdpdr/rdpdr-fs.c \
channels/rdpdr/rdpdr-messages.c \
channels/rdpdr/rdpdr-printer.c \
channels/rdpdr/rdpdr.c \
channels/rdpsnd/rdpsnd-messages.c \
channels/rdpsnd/rdpsnd.c \
client.c \
color.c \
decompose.c \
download.c \
error.c \
fs.c \
gdi.c \
glyph.c \
input.c \
keyboard.c \
keymap.c \
log.c \
ls.c \
plugins/channels.c \
plugins/ptr-string.c \
pointer.c \
print-job.c \
rdp.c \
resolution.c \
settings.c \
unicode.c \
upload.c \
user.c
noinst_HEADERS = \
bitmap.h \
channels/audio-input/audio-buffer.h \
channels/audio-input/audio-input.h \
channels/cliprdr.h \
channels/common-svc.h \
channels/disp.h \
channels/pipe-svc.h \
channels/rail.h \
channels/rdpdr/rdpdr-fs-messages-dir-info.h \
channels/rdpdr/rdpdr-fs-messages-file-info.h \
channels/rdpdr/rdpdr-fs-messages-vol-info.h \
channels/rdpdr/rdpdr-fs-messages.h \
channels/rdpdr/rdpdr-fs.h \
channels/rdpdr/rdpdr-messages.h \
channels/rdpdr/rdpdr-printer.h \
channels/rdpdr/rdpdr.h \
channels/rdpsnd/rdpsnd-messages.h \
channels/rdpsnd/rdpsnd.h \
client.h \
color.h \
decompose.h \
download.h \
error.h \
fs.h \
gdi.h \
glyph.h \
input.h \
keyboard.h \
keymap.h \
log.h \
ls.h \
plugins/channels.h \
plugins/guacai/guacai-messages.h \
plugins/guacai/guacai.h \
plugins/ptr-string.h \
pointer.h \
print-job.h \
rdp.h \
resolution.h \
settings.h \
unicode.h \
upload.h \
user.h
libguac_client_rdp_la_CFLAGS = \ libguac_client_rdp_la_CFLAGS = \
-Werror -Wall -Iinclude \ -Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \ @COMMON_INCLUDE@ \
@COMMON_SSH_INCLUDE@ \ @COMMON_SSH_INCLUDE@ \
@LIBGUAC_INCLUDE@ @LIBGUAC_INCLUDE@ \
@RDP_CFLAGS@
libguac_client_rdp_la_LDFLAGS = \ libguac_client_rdp_la_LDFLAGS = \
-version-info 0:0:0 \ -version-info 0:0:0 \
@CAIRO_LIBS@ \ @CAIRO_LIBS@ \
@PTHREAD_LIBS@ \ @PTHREAD_LIBS@ \
@RDP_LIBS@ \ @RDP_LIBS@
@WINPR_LIBS@
libguac_client_rdp_la_LIBADD = \ libguac_client_rdp_la_LIBADD = \
@COMMON_LTLIB@ \ @COMMON_LTLIB@ \
@LIBGUAC_LTLIB@ @LIBGUAC_LTLIB@
# #
# RDPDR # Plugins for FreeRDP
# #
guacdr_cflags = \ freerdp_LTLIBRARIES = \
libguac-common-svc-client.la \
libguacai-client.la
freerdpdir = ${libdir}/freerdp2
#
# Common SVC plugin (shared by RDPDR, RDPSND, etc.)
#
libguac_common_svc_client_la_SOURCES = \
plugins/guac-common-svc/guac-common-svc.c
libguac_common_svc_client_la_CFLAGS = \
-Werror -Wall -Iinclude \ -Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \ @LIBGUAC_INCLUDE@ \
@COMMON_SSH_INCLUDE@ \ @RDP_CFLAGS@
@LIBGUAC_INCLUDE@
guacdr_ldflags = \ libguac_common_svc_client_la_LDFLAGS = \
-module -avoid-version -shared \ -module -avoid-version -shared \
@PTHREAD_LIBS@ \ @RDP_LIBS@
@RDP_LIBS@ \
@WINPR_LIBS@
guacdr_libadd = \ libguac_common_svc_client_la_LIBADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@ @LIBGUAC_LTLIB@
# #
# Audio Input # Audio Input
# #
guacai_cflags = \ libguacai_client_la_SOURCES = \
channels/audio-input/audio-buffer.c \
plugins/guacai/guacai-messages.c \
plugins/guacai/guacai.c \
plugins/ptr-string.c
libguacai_client_la_CFLAGS = \
-Werror -Wall -Iinclude \ -Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \ @COMMON_INCLUDE@ \
@COMMON_SSH_INCLUDE@ \ @COMMON_SSH_INCLUDE@ \
@LIBGUAC_INCLUDE@ @LIBGUAC_INCLUDE@ \
@RDP_CFLAGS@
guacai_ldflags = \ libguacai_client_la_LDFLAGS = \
-module -avoid-version -shared \ -module -avoid-version -shared \
@PTHREAD_LIBS@ \ @PTHREAD_LIBS@ \
@RDP_LIBS@ \ @RDP_LIBS@
@WINPR_LIBS@
guacai_libadd = \ libguacai_client_la_LIBADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
#
# RDPSND
#
guacsnd_cflags = \
-Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \
@COMMON_SSH_INCLUDE@ \
@LIBGUAC_INCLUDE@
guacsnd_ldflags = \
-module -avoid-version -shared \
@PTHREAD_LIBS@ \
@RDP_LIBS@ \
@WINPR_LIBS@
guacsnd_libadd = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
#
# Static Virtual Channels
#
guacsvc_cflags = \
-Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \
@COMMON_SSH_INCLUDE@ \
@LIBGUAC_INCLUDE@
guacsvc_ldflags = \
-module -avoid-version -shared \
@PTHREAD_LIBS@ \
@RDP_LIBS@ \
@WINPR_LIBS@
guacsvc_libadd = \
@COMMON_LTLIB@ \ @COMMON_LTLIB@ \
@LIBGUAC_LTLIB@ @LIBGUAC_LTLIB@
@ -254,11 +208,16 @@ libguac_client_rdp_la_LIBADD += @COMMON_SSH_LTLIB@
endif endif
# #
# Autogenerate keymaps # Autogenerated keymaps and channel wrapper functions
# #
CLEANFILES = _generated_keymaps.c CLEANFILES = \
BUILT_SOURCES = _generated_keymaps.c _generated_channel_entry_wrappers.c \
_generated_keymaps.c
BUILT_SOURCES = \
_generated_channel_entry_wrappers.c \
_generated_keymaps.c
rdp_keymaps = \ rdp_keymaps = \
$(srcdir)/keymaps/base.keymap \ $(srcdir)/keymaps/base.keymap \
@ -280,71 +239,13 @@ rdp_keymaps = \
$(srcdir)/keymaps/tr_tr_qwerty.keymap $(srcdir)/keymaps/tr_tr_qwerty.keymap
_generated_keymaps.c: $(rdp_keymaps) _generated_keymaps.c: $(rdp_keymaps)
$(srcdir)/keymaps/generate.pl $(rdp_keymaps) $(AM_V_GEN) $(srcdir)/keymaps/generate.pl $(rdp_keymaps)
_generated_channel_entry_wrappers.c: $(srcdir)/plugins/channels.h $(srcdir)/plugins/generate-entry-wrappers.pl
$(AM_V_GEN) $(srcdir)/plugins/generate-entry-wrappers.pl $(srcdir)/plugins/channels.h
EXTRA_DIST = \ EXTRA_DIST = \
$(rdp_keymaps) \ $(rdp_keymaps) \
keymaps/generate.pl keymaps/generate.pl \
plugins/generate-entry-wrappers.pl
if LEGACY_FREERDP_EXTENSIONS
# FreeRDP 1.0-style extensions
freerdp_LTLIBRARIES = \
guacai.la \
guacdr.la \
guacsnd.la \
guacsvc.la
guacai_la_SOURCES = ${guacai_sources}
guacai_la_CFLAGS = ${guacai_cflags}
guacai_la_LDFLAGS = ${guacai_ldflags}
guacai_la_LIBADD = ${guacai_libadd}
guacdr_la_SOURCES = ${guacdr_sources}
guacdr_la_CFLAGS = ${guacdr_cflags}
guacdr_la_LDFLAGS = ${guacdr_ldflags}
guacdr_la_LIBADD = ${guacdr_libadd}
guacsnd_la_SOURCES = ${guacsnd_sources}
guacsnd_la_CFLAGS = ${guacsnd_cflags}
guacsnd_la_LDFLAGS = ${guacsnd_ldflags}
guacsnd_la_LIBADD = ${guacsnd_libadd}
guacsvc_la_SOURCES = ${guacsvc_sources}
guacsvc_la_CFLAGS = ${guacsvc_cflags}
guacsvc_la_LDFLAGS = ${guacsvc_ldflags}
guacsvc_la_LIBADD = ${guacsvc_libadd}
else
# FreeRDP 1.1 (and hopefully onward) extensions
freerdp_LTLIBRARIES = \
guacai-client.la \
guacdr-client.la \
guacsnd-client.la \
guacsvc-client.la
guacai_client_la_SOURCES = ${guacai_sources}
guacai_client_la_CFLAGS = ${guacai_cflags}
guacai_client_la_LDFLAGS = ${guacai_ldflags}
guacai_client_la_LIBADD = ${guacai_libadd}
guacdr_client_la_SOURCES = ${guacdr_sources}
guacdr_client_la_CFLAGS = ${guacdr_cflags}
guacdr_client_la_LDFLAGS = ${guacdr_ldflags}
guacdr_client_la_LIBADD = ${guacdr_libadd}
guacsnd_client_la_SOURCES = ${guacsnd_sources}
guacsnd_client_la_CFLAGS = ${guacsnd_cflags}
guacsnd_client_la_LDFLAGS = ${guacsnd_ldflags}
guacsnd_client_la_LIBADD = ${guacsnd_libadd}
guacsvc_client_la_SOURCES = ${guacsvc_sources}
guacsvc_client_la_CFLAGS = ${guacsvc_cflags}
guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags}
guacsvc_client_la_LIBADD = ${guacsvc_libadd}
endif
freerdpdir = ${libdir}/freerdp

View File

@ -17,27 +17,16 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "bitmap.h"
#include "client.h"
#include "common/display.h" #include "common/display.h"
#include "common/surface.h" #include "common/surface.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_bitmap.h"
#include "rdp_settings.h"
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <freerdp/codec/bitmap.h>
#include <freerdp/codec/color.h>
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/socket.h> #include <winpr/crt.h>
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-wtypes.h"
#endif
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -72,30 +61,7 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) {
} }
void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { BOOL guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) {
/* Convert image data if present */
if (bitmap->data != NULL && bitmap->bpp != 32) {
/* Convert image data to 32-bit RGB */
unsigned char* image_buffer = freerdp_image_convert(bitmap->data, NULL,
bitmap->width, bitmap->height,
guac_rdp_get_depth(context->instance),
32, ((rdp_freerdp_context*) context)->clrconv);
/* Free existing image, if any */
if (image_buffer != bitmap->data) {
#ifdef FREERDP_BITMAP_REQUIRES_ALIGNED_MALLOC
_aligned_free(bitmap->data);
#else
free(bitmap->data);
#endif
}
/* Store converted image in bitmap */
bitmap->data = image_buffer;
}
/* No corresponding surface yet - caching is deferred. */ /* No corresponding surface yet - caching is deferred. */
((guac_rdp_bitmap*) bitmap)->layer = NULL; ((guac_rdp_bitmap*) bitmap)->layer = NULL;
@ -103,9 +69,11 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) {
/* Start at zero usage */ /* Start at zero usage */
((guac_rdp_bitmap*) bitmap)->used = 0; ((guac_rdp_bitmap*) bitmap)->used = 0;
return TRUE;
} }
void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) { BOOL guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -145,6 +113,8 @@ void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) {
/* Increment usage counter */ /* Increment usage counter */
((guac_rdp_bitmap*) bitmap)->used++; ((guac_rdp_bitmap*) bitmap)->used++;
return TRUE;
} }
void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap) { void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap) {
@ -157,9 +127,16 @@ void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap) {
if (buffer != NULL) if (buffer != NULL)
guac_common_display_free_buffer(rdp_client->display, buffer); guac_common_display_free_buffer(rdp_client->display, buffer);
/* NOTE: FreeRDP-allocated memory for the rdpBitmap will NOT be
* automatically released after this free handler is invoked, thus we must
* do so manually here */
_aligned_free(bitmap->data);
free(bitmap);
} }
void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary) { BOOL 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;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -172,7 +149,7 @@ void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL pri
/* Make sure that the recieved bitmap is not NULL before processing */ /* Make sure that the recieved bitmap is not NULL before processing */
if (bitmap == NULL) { if (bitmap == NULL) {
guac_client_log(client, GUAC_LOG_INFO, "NULL bitmap found in bitmap_setsurface instruction."); guac_client_log(client, GUAC_LOG_INFO, "NULL bitmap found in bitmap_setsurface instruction.");
return; return TRUE;
} }
/* If not available as a surface, make available. */ /* If not available as a surface, make available. */
@ -184,80 +161,7 @@ void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL pri
} }
} return TRUE;
#ifdef LEGACY_RDPBITMAP
void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, UINT8* data,
int width, int height, int bpp, int length, BOOL compressed) {
#else
void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, UINT8* data,
int width, int height, int bpp, int length, BOOL compressed, int codec_id) {
#endif
int size = width * height * 4;
#ifdef FREERDP_BITMAP_REQUIRES_ALIGNED_MALLOC
/* Free pre-existing data, if any (might be reused) */
if (bitmap->data != NULL)
_aligned_free(bitmap->data);
/* Allocate new data */
bitmap->data = (UINT8*) _aligned_malloc(size, 16);
#else
/* Free pre-existing data, if any (might be reused) */
free(bitmap->data);
/* Allocate new data */
bitmap->data = (UINT8*) malloc(size);
#endif
if (compressed) {
#ifdef HAVE_RDPCONTEXT_CODECS
rdpCodecs* codecs = context->codecs;
/* Decode as interleaved if less than 32 bits per pixel */
if (bpp < 32) {
freerdp_client_codecs_prepare(codecs, FREERDP_CODEC_INTERLEAVED);
#ifdef INTERLEAVED_DECOMPRESS_TAKES_PALETTE
interleaved_decompress(codecs->interleaved, data, length, bpp,
&(bitmap->data), PIXEL_FORMAT_XRGB32, -1, 0, 0, width, height,
(BYTE*) ((rdp_freerdp_context*) context)->palette);
bitmap->bpp = 32;
#else
interleaved_decompress(codecs->interleaved, data, length, bpp,
&(bitmap->data), PIXEL_FORMAT_XRGB32, -1, 0, 0, width, height);
bitmap->bpp = bpp;
#endif
}
/* Otherwise, decode as planar */
else {
freerdp_client_codecs_prepare(codecs, FREERDP_CODEC_PLANAR);
#ifdef PLANAR_DECOMPRESS_CAN_FLIP
planar_decompress(codecs->planar, data, length,
&(bitmap->data), PIXEL_FORMAT_XRGB32, -1, 0, 0, width, height,
TRUE);
bitmap->bpp = 32;
#else
planar_decompress(codecs->planar, data, length,
&(bitmap->data), PIXEL_FORMAT_XRGB32, -1, 0, 0, width, height);
bitmap->bpp = bpp;
#endif
}
#else
bitmap_decompress(data, bitmap->data, width, height, length, bpp, bpp);
bitmap->bpp = bpp;
#endif
}
else {
freerdp_image_flip(data, bitmap->data, width, height, bpp);
bitmap->bpp = bpp;
}
bitmap->compressed = FALSE;
bitmap->length = size;
} }

View File

@ -17,21 +17,16 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_BITMAP_H
#ifndef _GUAC_RDP_RDP_BITMAP_H #define GUAC_RDP_BITMAP_H
#define _GUAC_RDP_RDP_BITMAP_H
#include "config.h" #include "config.h"
#include "common/display.h" #include "common/display.h"
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/graphics.h>
#include <guacamole/layer.h> #include <guacamole/layer.h>
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-wtypes.h"
#endif
/** /**
* Guacamole-specific rdpBitmap data. * Guacamole-specific rdpBitmap data.
@ -77,8 +72,11 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap);
* *
* @param bitmap * @param bitmap
* The bitmap to initialize. * The bitmap to initialize.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap); BOOL guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap);
/** /**
* Paints the given rdpBitmap on the primary display surface. Note that this * Paints the given rdpBitmap on the primary display surface. Note that this
@ -92,8 +90,11 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap);
* The bitmap to paint. This structure will also contain the specifics of * The bitmap to paint. This structure will also contain the specifics of
* the paint operation to perform, including the destination X/Y * the paint operation to perform, including the destination X/Y
* coordinates. * coordinates.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap); BOOL guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap);
/** /**
* Frees any Guacamole-specific data associated with the given rdpBitmap. * Frees any Guacamole-specific data associated with the given rdpBitmap.
@ -122,82 +123,11 @@ void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap);
* TRUE if the bitmap parameter should be ignored, and the current drawing * TRUE if the bitmap parameter should be ignored, and the current drawing
* surface should be reset to the primary drawing surface of the remote * surface should be reset to the primary drawing surface of the remote
* display, FALSE otherwise. * display, FALSE otherwise.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap,
BOOL primary); BOOL primary);
#ifdef LEGACY_RDPBITMAP
/**
* 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
/**
* 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

View File

@ -17,181 +17,19 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/audio-input/audio-buffer.h"
#include "audio_input.h"
#include "dvc.h"
#include "ptr_string.h"
#include "rdp.h" #include "rdp.h"
#include <freerdp/freerdp.h> #include <guacamole/client.h>
#include <freerdp/channels/channels.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <assert.h> #include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <pthread.h> #include <pthread.h>
#include <stdint.h>
/** #include <stdlib.h>
* Parses the given raw audio mimetype, producing the corresponding rate,
* number of channels, and bytes per sample.
*
* @param mimetype
* The raw auduio mimetype to parse.
*
* @param rate
* A pointer to an int where the sample rate for the PCM format described
* by the given mimetype should be stored.
*
* @param channels
* A pointer to an int where the number of channels used by the PCM format
* described by the given mimetype should be stored.
*
* @param bps
* A pointer to an int where the number of bytes used the PCM format for
* each sample (independent of number of channels) described by the given
* mimetype should be stored.
*
* @return
* Zero if the given mimetype is a raw audio mimetype and has been parsed
* successfully, non-zero otherwise.
*/
static int guac_rdp_audio_parse_mimetype(const char* mimetype,
int* rate, int* channels, int* bps) {
int parsed_rate = -1;
int parsed_channels = 1;
int parsed_bps;
/* PCM audio with one byte per sample */
if (strncmp(mimetype, "audio/L8;", 9) == 0) {
mimetype += 8; /* Advance to semicolon ONLY */
parsed_bps = 1;
}
/* PCM audio with two bytes per sample */
else if (strncmp(mimetype, "audio/L16;", 10) == 0) {
mimetype += 9; /* Advance to semicolon ONLY */
parsed_bps = 2;
}
/* Unsupported mimetype */
else
return 1;
/* Parse each parameter name/value pair within the mimetype */
do {
/* Advance to first character of parameter (current is either a
* semicolon or a comma) */
mimetype++;
/* Parse number of channels */
if (strncmp(mimetype, "channels=", 9) == 0) {
mimetype += 9;
parsed_channels = strtol(mimetype, (char**) &mimetype, 10);
/* Fail if value invalid / out of range */
if (errno == EINVAL || errno == ERANGE)
return 1;
}
/* Parse number of rate */
else if (strncmp(mimetype, "rate=", 5) == 0) {
mimetype += 5;
parsed_rate = strtol(mimetype, (char**) &mimetype, 10);
/* Fail if value invalid / out of range */
if (errno == EINVAL || errno == ERANGE)
return 1;
}
/* Advance to next parameter */
mimetype = strchr(mimetype, ',');
} while (mimetype != NULL);
/* Mimetype is invalid if rate was not specified */
if (parsed_rate == -1)
return 1;
/* Parse success */
*rate = parsed_rate;
*channels = parsed_channels;
*bps = parsed_bps;
return 0;
}
int guac_rdp_audio_handler(guac_user* user, guac_stream* stream,
char* mimetype) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
int rate;
int channels;
int bps;
/* Parse mimetype, abort on parse error */
if (guac_rdp_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) {
guac_user_log(user, GUAC_LOG_WARNING, "Denying user audio stream with "
"unsupported mimetype: \"%s\"", mimetype);
guac_protocol_send_ack(user->socket, stream, "Unsupported audio "
"mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE);
return 0;
}
/* Init stream data */
stream->blob_handler = guac_rdp_audio_blob_handler;
stream->end_handler = guac_rdp_audio_end_handler;
/* Associate stream with audio buffer */
guac_rdp_audio_buffer_set_stream(rdp_client->audio_input, user, stream,
rate, channels, bps);
return 0;
}
int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Write blob to audio stream, buffering if necessary */
guac_rdp_audio_buffer_write(rdp_client->audio_input, data, length);
return 0;
}
int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) {
/* Ignore - the AUDIO_INPUT channel will simply not receive anything */
return 0;
}
void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
char client_ref[GUAC_RDP_PTR_STRING_LENGTH];
/* Add "AUDIO_INPUT" channel */
guac_rdp_ptr_to_string(client, client_ref);
guac_rdp_dvc_list_add(list, "guacai", client_ref, NULL);
}
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() { guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() {
guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer)); guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer));

View File

@ -17,16 +17,11 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_AUDIO_INPUT_H #ifndef GUAC_RDP_CHANNELS_AUDIO_INPUT_AUDIO_BUFFER_H
#define GUAC_RDP_AUDIO_INPUT_H #define GUAC_RDP_CHANNELS_AUDIO_INPUT_AUDIO_BUFFER_H
#include "config.h"
#include "dvc.h"
#include <freerdp/freerdp.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <pthread.h> #include <pthread.h>
/** /**
@ -278,36 +273,5 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer);
*/ */
void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer); void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer);
/**
* Handler for inbound audio data (audio input).
*/
guac_user_audio_handler guac_rdp_audio_handler;
/**
* Handler for stream data related to audio input.
*/
guac_user_blob_handler guac_rdp_audio_blob_handler;
/**
* Handler for end-of-stream related to audio input.
*/
guac_user_end_handler guac_rdp_audio_end_handler;
/**
* Adds Guacamole's "guacai" plugin to the list of dynamic virtual channel
* plugins to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only
* be loaded once guac_rdp_load_drdynvc() is invoked with the guac_rdp_dvc_list
* passed to this function. The "guacai" plugin ultimately adds support for the
* "AUDIO_INPUT" dynamic virtual channel.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param list
* The guac_rdp_dvc_list to which the "guacai" plugin should be added, such
* that it may later be loaded by guac_rdp_load_drdynvc().
*/
void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list);
#endif #endif

View File

@ -0,0 +1,195 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/audio-input/audio-buffer.h"
#include "channels/audio-input/audio-input.h"
#include "plugins/channels.h"
#include "plugins/ptr-string.h"
#include "rdp.h"
#include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/**
* Parses the given raw audio mimetype, producing the corresponding rate,
* number of channels, and bytes per sample.
*
* @param mimetype
* The raw auduio mimetype to parse.
*
* @param rate
* A pointer to an int where the sample rate for the PCM format described
* by the given mimetype should be stored.
*
* @param channels
* A pointer to an int where the number of channels used by the PCM format
* described by the given mimetype should be stored.
*
* @param bps
* A pointer to an int where the number of bytes used the PCM format for
* each sample (independent of number of channels) described by the given
* mimetype should be stored.
*
* @return
* Zero if the given mimetype is a raw audio mimetype and has been parsed
* successfully, non-zero otherwise.
*/
static int guac_rdp_audio_parse_mimetype(const char* mimetype,
int* rate, int* channels, int* bps) {
int parsed_rate = -1;
int parsed_channels = 1;
int parsed_bps;
/* PCM audio with one byte per sample */
if (strncmp(mimetype, "audio/L8;", 9) == 0) {
mimetype += 8; /* Advance to semicolon ONLY */
parsed_bps = 1;
}
/* PCM audio with two bytes per sample */
else if (strncmp(mimetype, "audio/L16;", 10) == 0) {
mimetype += 9; /* Advance to semicolon ONLY */
parsed_bps = 2;
}
/* Unsupported mimetype */
else
return 1;
/* Parse each parameter name/value pair within the mimetype */
do {
/* Advance to first character of parameter (current is either a
* semicolon or a comma) */
mimetype++;
/* Parse number of channels */
if (strncmp(mimetype, "channels=", 9) == 0) {
mimetype += 9;
parsed_channels = strtol(mimetype, (char**) &mimetype, 10);
/* Fail if value invalid / out of range */
if (errno == EINVAL || errno == ERANGE)
return 1;
}
/* Parse number of rate */
else if (strncmp(mimetype, "rate=", 5) == 0) {
mimetype += 5;
parsed_rate = strtol(mimetype, (char**) &mimetype, 10);
/* Fail if value invalid / out of range */
if (errno == EINVAL || errno == ERANGE)
return 1;
}
/* Advance to next parameter */
mimetype = strchr(mimetype, ',');
} while (mimetype != NULL);
/* Mimetype is invalid if rate was not specified */
if (parsed_rate == -1)
return 1;
/* Parse success */
*rate = parsed_rate;
*channels = parsed_channels;
*bps = parsed_bps;
return 0;
}
int guac_rdp_audio_handler(guac_user* user, guac_stream* stream,
char* mimetype) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
int rate;
int channels;
int bps;
/* Parse mimetype, abort on parse error */
if (guac_rdp_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) {
guac_user_log(user, GUAC_LOG_WARNING, "Denying user audio stream with "
"unsupported mimetype: \"%s\"", mimetype);
guac_protocol_send_ack(user->socket, stream, "Unsupported audio "
"mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE);
return 0;
}
/* Init stream data */
stream->blob_handler = guac_rdp_audio_blob_handler;
stream->end_handler = guac_rdp_audio_end_handler;
/* Associate stream with audio buffer */
guac_rdp_audio_buffer_set_stream(rdp_client->audio_input, user, stream,
rate, channels, bps);
return 0;
}
int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Write blob to audio stream, buffering if necessary */
guac_rdp_audio_buffer_write(rdp_client->audio_input, data, length);
return 0;
}
int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) {
/* Ignore - the AUDIO_INPUT channel will simply not receive anything */
return 0;
}
void guac_rdp_audio_load_plugin(rdpContext* context) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
char client_ref[GUAC_RDP_PTR_STRING_LENGTH];
/* Add "AUDIO_INPUT" channel */
guac_rdp_ptr_to_string(client, client_ref);
guac_freerdp_dynamic_channel_collection_add(context->settings, "guacai", client_ref, NULL);
}

View File

@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_CHANNELS_AUDIO_INPUT_H
#define GUAC_RDP_CHANNELS_AUDIO_INPUT_H
#include <freerdp/freerdp.h>
#include <guacamole/user.h>
/**
* Handler for inbound audio data (audio input).
*/
guac_user_audio_handler guac_rdp_audio_handler;
/**
* Handler for stream data related to audio input.
*/
guac_user_blob_handler guac_rdp_audio_blob_handler;
/**
* Handler for end-of-stream related to audio input.
*/
guac_user_end_handler guac_rdp_audio_end_handler;
/**
* Adds Guacamole's "guacai" plugin to the list of dynamic virtual channel
* plugins to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only
* be loaded once the "drdynvc" plugin is loaded. The "guacai" plugin
* ultimately adds support for the "AUDIO_INPUT" dynamic virtual channel.
*
* @param context
* The rdpContext associated with the active RDP session.
*/
void guac_rdp_audio_load_plugin(rdpContext* context);
#endif

View File

@ -0,0 +1,620 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/cliprdr.h"
#include "client.h"
#include "common/clipboard.h"
#include "common/iconv.h"
#include "config.h"
#include "plugins/channels.h"
#include "rdp.h"
#include <freerdp/client/cliprdr.h>
#include <freerdp/event.h>
#include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <winpr/wtsapi.h>
#include <winpr/wtypes.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#ifdef FREERDP_CLIPRDR_CALLBACKS_REQUIRE_CONST
/**
* FreeRDP 2.0.0-rc4 and newer requires the final argument for all CLIPRDR
* callbacks to be const.
*/
#define CLIPRDR_CONST const
#else
/**
* FreeRDP 2.0.0-rc3 and older requires the final argument for all CLIPRDR
* callbacks to NOT be const.
*/
#define CLIPRDR_CONST
#endif
/**
* Sends a Format List PDU to the RDP server containing the formats of
* clipboard data supported. This PDU is used both to indicate the general
* clipboard formats supported at the begining of an RDP session and to inform
* the RDP server that new clipboard data is available within the listed
* formats.
*
* @param cliprdr
* The CliprdrClientContext structure used by FreeRDP to handle the
* CLIPRDR channel for the current RDP session.
*
* @return
* CHANNEL_RC_OK (zero) if the Format List PDU was sent successfully, an
* error code (non-zero) otherwise.
*/
static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
/* This function is only invoked within FreeRDP-specific handlers for
* CLIPRDR, which are not assigned, and thus not callable, until after the
* relevant guac_rdp_clipboard structure is allocated and associated with
* the CliprdrClientContext */
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL);
/* We support CP-1252 and UTF-16 text */
CLIPRDR_FORMAT_LIST format_list = {
.formats = (CLIPRDR_FORMAT[]) {
{ .formatId = CF_TEXT },
{ .formatId = CF_UNICODETEXT }
},
.numFormats = 2
};
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending "
"format list");
return cliprdr->ClientFormatList(cliprdr, &format_list);
}
/**
* Sends a Clipboard Capabilities PDU to the RDP server describing the features
* of the CLIPRDR channel that are supported by the client.
*
* @param cliprdr
* The CliprdrClientContext structure used by FreeRDP to handle the
* CLIPRDR channel for the current RDP session.
*
* @return
* CHANNEL_RC_OK (zero) if the Clipboard Capabilities PDU was sent
* successfully, an error code (non-zero) otherwise.
*/
static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) {
CLIPRDR_GENERAL_CAPABILITY_SET cap_set = {
.capabilitySetType = CB_CAPSTYPE_GENERAL, /* CLIPRDR specification requires that this is CB_CAPSTYPE_GENERAL, the only defined set type */
.capabilitySetLength = 12, /* The size of the capability set within the PDU - for CB_CAPSTYPE_GENERAL, this is ALWAYS 12 bytes */
.version = CB_CAPS_VERSION_2, /* The version of the CLIPRDR specification supported */
.generalFlags = CB_USE_LONG_FORMAT_NAMES /* Bitwise OR of all supported feature flags */
};
CLIPRDR_CAPABILITIES caps = {
.cCapabilitiesSets = 1,
.capabilitySets = (CLIPRDR_CAPABILITY_SET*) &cap_set
};
return cliprdr->ClientCapabilities(cliprdr, &caps);
}
/**
* Callback invoked by the FreeRDP CLIPRDR plugin for received Monitor Ready
* PDUs. The Monitor Ready PDU is sent by the RDP server only during
* initialization of the CLIPRDR channel. It is part of the CLIPRDR channel
* handshake and indicates that the RDP server's handling of clipboard
* redirection is ready to proceed.
*
* @param cliprdr
* The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR
* channel for the current RDP session.
*
* @param monitor_ready
* The CLIPRDR_MONITOR_READY structure representing the Monitor Ready PDU
* that was received.
*
* @return
* CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code
* (non-zero) otherwise.
*/
static UINT guac_rdp_cliprdr_monitor_ready(CliprdrClientContext* cliprdr,
CLIPRDR_CONST CLIPRDR_MONITOR_READY* monitor_ready) {
/* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not
* callable, until after the relevant guac_rdp_clipboard structure is
* allocated and associated with the CliprdrClientContext */
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL);
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received "
"monitor ready.");
/* Respond with capabilities ... */
int status = guac_rdp_cliprdr_send_capabilities(cliprdr);
if (status != CHANNEL_RC_OK)
return status;
/* ... and supported format list */
return guac_rdp_cliprdr_send_format_list(cliprdr);
}
/**
* Sends a Format Data Request PDU to the RDP server, requesting that available
* clipboard data be sent to the client in the specified format. This PDU is
* sent when the server indicates that clipboard data is available via a Format
* List PDU.
*
* @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.
*
* @return
* CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code
* (non-zero) otherwise.
*/
static UINT guac_rdp_cliprdr_send_format_data_request(
CliprdrClientContext* cliprdr, UINT32 format) {
/* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not
* callable, until after the relevant guac_rdp_clipboard structure is
* allocated and associated with the CliprdrClientContext */
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL);
/* Create new data request */
CLIPRDR_FORMAT_DATA_REQUEST data_request = {
.requestedFormatId = format
};
/* Note the format we've requested for reference later when the requested
* data is received via a Format Data Response PDU */
clipboard->requested_format = format;
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending "
"format data request.");
/* Send request */
return cliprdr->ClientFormatDataRequest(cliprdr, &data_request);
}
/**
* Returns whether the given Format List PDU indicates support for the given
* clipboard format.
*
* @param format_list
* The CLIPRDR_FORMAT_LIST structure representing the Format List PDU
* being tested.
*
* @param format_id
* The ID of the clipboard format to test, such as CF_TEXT or
* CF_UNICODETEXT.
*
* @return
* Non-zero if the given Format List PDU indicates support for the given
* clipboard format, zero otherwise.
*/
static int guac_rdp_cliprdr_format_supported(const CLIPRDR_FORMAT_LIST* format_list,
UINT format_id) {
/* Search format list for matching ID */
for (int i = 0; i < format_list->numFormats; i++) {
if (format_list->formats[i].formatId == format_id)
return 1;
}
/* If no matching ID, format is not supported */
return 0;
}
/**
* Callback invoked by the FreeRDP CLIPRDR plugin for received Format List
* PDUs. The Format List PDU is sent by the RDP server to indicate that new
* clipboard data has been copied and is available for retrieval in the formats
* listed. A client wishing to retrieve that data responds with a Format Data
* Request PDU.
*
* @param cliprdr
* The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR
* channel for the current RDP session.
*
* @param format_list
* The CLIPRDR_FORMAT_LIST structure representing the Format List PDU that
* was received.
*
* @return
* CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code
* (non-zero) otherwise.
*/
static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr,
CLIPRDR_CONST CLIPRDR_FORMAT_LIST* format_list) {
/* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not
* callable, until after the relevant guac_rdp_clipboard structure is
* allocated and associated with the CliprdrClientContext */
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL);
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received "
"format list.");
CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = {
.msgFlags = CB_RESPONSE_OK
};
/* Report successful processing of format list */
cliprdr->ClientFormatListResponse(cliprdr, &format_list_response);
/* Prefer Unicode (in this case, UTF-16) */
if (guac_rdp_cliprdr_format_supported(format_list, CF_UNICODETEXT))
return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_UNICODETEXT);
/* Use Windows' CP-1252 if Unicode unavailable */
if (guac_rdp_cliprdr_format_supported(format_list, CF_TEXT))
return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_TEXT);
/* Ignore any unsupported data */
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Ignoring unsupported "
"clipboard data. Only Unicode and text clipboard formats are "
"currently supported.");
return CHANNEL_RC_OK;
}
/**
* Callback invoked by the FreeRDP CLIPRDR plugin for received Format Data
* Request PDUs. The Format Data Request PDU is sent by the RDP server when
* requesting that clipboard data be sent, in response to a received Format
* List PDU. The client is required to respond with a Format Data Response PDU
* containing the requested data.
*
* @param cliprdr
* The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR
* channel for the current RDP session.
*
* @param format_data_request
* The CLIPRDR_FORMAT_DATA_REQUEST structure representing the Format Data
* Request PDU that was received.
*
* @return
* CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code
* (non-zero) otherwise.
*/
static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
CLIPRDR_CONST CLIPRDR_FORMAT_DATA_REQUEST* format_data_request) {
/* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not
* callable, until after the relevant guac_rdp_clipboard structure is
* allocated and associated with the CliprdrClientContext */
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL);
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received "
"format data request.");
guac_iconv_write* writer;
const char* input = clipboard->clipboard->buffer;
char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH);
/* Map requested clipboard format to a guac_iconv writer */
switch (format_data_request->requestedFormatId) {
case CF_TEXT:
writer = GUAC_WRITE_CP1252;
break;
case CF_UNICODETEXT:
writer = GUAC_WRITE_UTF16;
break;
/* Warn if clipboard data cannot be sent as intended due to a violation
* of the CLIPRDR spec */
default:
guac_client_log(clipboard->client, GUAC_LOG_WARNING, "Received "
"clipboard data cannot be sent to the RDP server because "
"the RDP server has requested a clipboard format which "
"was not declared as available. This violates the "
"specification for the CLIPRDR channel.");
free(output);
return CHANNEL_RC_OK;
}
/* Send received clipboard data to the RDP server in the format
* requested */
BYTE* start = (BYTE*) output;
guac_iconv(GUAC_READ_UTF8, &input, clipboard->clipboard->length,
writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH);
CLIPRDR_FORMAT_DATA_RESPONSE data_response = {
.requestedFormatData = (BYTE*) start,
.dataLen = ((BYTE*) output) - start,
.msgFlags = CB_RESPONSE_OK
};
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending "
"format data response.");
return cliprdr->ClientFormatDataResponse(cliprdr, &data_response);
}
/**
* Callback invoked by the FreeRDP CLIPRDR plugin for received Format Data
* Response PDUs. The Format Data Response PDU is sent by the RDP server when
* fullfilling a request for clipboard data received via a Format Data Request
* PDU.
*
* @param cliprdr
* The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR
* channel for the current RDP session.
*
* @param format_data_response
* The CLIPRDR_FORMAT_DATA_RESPONSE structure representing the Format Data
* Response PDU that was received.
*
* @return
* CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code
* (non-zero) otherwise.
*/
static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
CLIPRDR_CONST CLIPRDR_FORMAT_DATA_RESPONSE* format_data_response) {
/* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not
* callable, until after the relevant guac_rdp_clipboard structure is
* allocated and associated with the CliprdrClientContext */
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL);
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received "
"format data response.");
char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH];
guac_iconv_read* reader;
const char* input = (char*) format_data_response->requestedFormatData;
char* output = received_data;
/* Find correct source encoding */
switch (clipboard->requested_format) {
/* Non-Unicode (Windows CP-1252) */
case CF_TEXT:
reader = GUAC_READ_CP1252;
break;
/* Unicode (UTF-16) */
case CF_UNICODETEXT:
reader = GUAC_READ_UTF16;
break;
/* If the format ID stored within the guac_rdp_clipboard structure is actually
* not supported here, then something has been implemented incorrectly.
* Either incorrect values are (somehow) being stored, or support for
* the format indicated by that value is incomplete and must be added
* here. The values which may be stored within requested_format are
* completely within our control. */
default:
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Requested "
"clipboard data in unsupported format (0x%X).",
clipboard->requested_format);
return CHANNEL_RC_OK;
}
/* Convert, store, and forward the clipboard data received from RDP
* server */
if (guac_iconv(reader, &input, format_data_response->dataLen,
GUAC_WRITE_UTF8, &output, sizeof(received_data))) {
int length = strnlen(received_data, sizeof(received_data));
guac_common_clipboard_reset(clipboard->clipboard, "text/plain");
guac_common_clipboard_append(clipboard->clipboard, received_data, length);
guac_common_clipboard_send(clipboard->clipboard, clipboard->client);
}
return CHANNEL_RC_OK;
}
/**
* Callback which associates handlers specific to Guacamole with the
* CliprdrClientContext instance allocated by FreeRDP to deal with received
* CLIPRDR (clipboard redirection) messages.
*
* This function is called whenever a channel connects via the PubSub event
* system within FreeRDP, but only has any effect if the connected channel is
* the CLIPRDR channel. This specific callback is registered with the PubSub
* system of the relevant rdpContext when guac_rdp_clipboard_load_plugin() is
* called.
*
* @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_cliprdr_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* e) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_clipboard* clipboard = rdp_client->clipboard;
/* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not
* callable, until after the relevant guac_rdp_clipboard structure is
* allocated and associated with the guac_rdp_client */
assert(clipboard != NULL);
/* Ignore connection event if it's not for the CLIPRDR channel */
if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) != 0)
return;
/* The structure pointed to by pInterface is guaranteed to be a
* CliprdrClientContext if the channel is CLIPRDR */
CliprdrClientContext* cliprdr = (CliprdrClientContext*) e->pInterface;
/* Associate FreeRDP CLIPRDR context and its Guacamole counterpart with
* eachother */
cliprdr->custom = clipboard;
clipboard->cliprdr = cliprdr;
cliprdr->MonitorReady = guac_rdp_cliprdr_monitor_ready;
cliprdr->ServerFormatList = guac_rdp_cliprdr_format_list;
cliprdr->ServerFormatDataRequest = guac_rdp_cliprdr_format_data_request;
cliprdr->ServerFormatDataResponse = guac_rdp_cliprdr_format_data_response;
guac_client_log(client, GUAC_LOG_DEBUG, "CLIPRDR (clipboard redirection) "
"channel connected.");
}
guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client) {
/* Allocate clipboard and underlying storage */
guac_rdp_clipboard* clipboard = calloc(1, sizeof(guac_rdp_clipboard));
clipboard->client = client;
clipboard->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH);
clipboard->requested_format = CF_TEXT;
return clipboard;
}
void guac_rdp_clipboard_load_plugin(guac_rdp_clipboard* clipboard,
rdpContext* context) {
/* Attempt to load FreeRDP support for the CLIPRDR channel */
if (guac_freerdp_channels_load_plugin(context, "cliprdr", NULL)) {
guac_client_log(clipboard->client, GUAC_LOG_WARNING,
"Support for the CLIPRDR channel (clipboard redirection) "
"could not be loaded. This support normally takes the form of "
"a plugin which is built into FreeRDP. Lacking this support, "
"clipboard will not work.");
return;
}
/* Complete RDP side of initialization when channel is connected */
PubSub_SubscribeChannelConnected(context->pubSub,
(pChannelConnectedEventHandler) guac_rdp_cliprdr_channel_connected);
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Support for CLIPRDR "
"(clipboard redirection) registered. Awaiting channel "
"connection.");
}
void guac_rdp_clipboard_free(guac_rdp_clipboard* clipboard) {
/* Do nothing if the clipboard is not actually allocated */
if (clipboard == NULL)
return;
/* Free clipboard and underlying storage */
guac_common_clipboard_free(clipboard->clipboard);
free(clipboard);
}
int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Ignore stream creation if no clipboard structure is available to handle
* received data */
guac_rdp_clipboard* clipboard = rdp_client->clipboard;
if (clipboard == NULL)
return 0;
/* Handle any future "blob" and "end" instructions for this stream with
* handlers that are aware of the RDP clipboard state */
stream->blob_handler = guac_rdp_clipboard_blob_handler;
stream->end_handler = guac_rdp_clipboard_end_handler;
/* Clear any current contents, assigning the mimetype the data which will
* be received */
guac_common_clipboard_reset(clipboard->clipboard, mimetype);
return 0;
}
int guac_rdp_clipboard_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Ignore received data if no clipboard structure is available to handle
* that data */
guac_rdp_clipboard* clipboard = rdp_client->clipboard;
if (clipboard == NULL)
return 0;
/* Append received data to current clipboard contents */
guac_common_clipboard_append(clipboard->clipboard, (char*) data, length);
return 0;
}
int guac_rdp_clipboard_end_handler(guac_user* user, guac_stream* stream) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Ignore end of stream if no clipboard structure is available to handle
* the data that was received */
guac_rdp_clipboard* clipboard = rdp_client->clipboard;
if (clipboard == NULL)
return 0;
/* Terminate clipboard data with NULL */
guac_common_clipboard_append(clipboard->clipboard, "", 1);
/* Notify RDP server of new data, if connected */
if (clipboard->cliprdr != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG, "Clipboard data received. "
"Reporting availability of clipboard data to RDP server.");
guac_rdp_cliprdr_send_format_list(clipboard->cliprdr);
}
else
guac_client_log(client, GUAC_LOG_DEBUG, "Clipboard data has been "
"received, but cannot be sent to the RDP server because the "
"CLIPRDR channel is not yet connected.");
return 0;
}

View File

@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_CHANNELS_CLIPRDR_H
#define GUAC_RDP_CHANNELS_CLIPRDR_H
#include "common/clipboard.h"
#include <freerdp/client/cliprdr.h>
#include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <guacamole/user.h>
#include <winpr/stream.h>
#include <winpr/wtypes.h>
/**
* RDP clipboard, leveraging the "CLIPRDR" channel.
*/
typedef struct guac_rdp_clipboard {
/**
* The guac_client associated with the RDP connection. The broadcast
* socket of this client will receive any clipboard data received from the
* RDP server.
*/
guac_client* client;
/**
* CLIPRDR control interface.
*/
CliprdrClientContext* cliprdr;
/**
* 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's WinPR
* library, such as CF_TEXT.
*/
UINT requested_format;
} guac_rdp_clipboard;
/**
* Allocates a new guac_rdp_clipboard which has been initialized for processing
* of Guacamole clipboard data. Support for the RDP side of the clipboard (the
* CLIPRDR channel) must be loaded separately during FreeRDP's PreConnect event
* using guac_rdp_clipboard_load_plugin().
*
* @param client
* The guac_client associated with the Guacamole side of the RDP
* connection.
*
* @return
* A newly-allocated instance of guac_rdp_clipboard which has been
* initialized for processing Guacamole clipboard data.
*/
guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client);
/**
* Initializes clipboard support for RDP and handling of the CLIPRDR channel.
* If failures occur, messages noting the specifics of those failures will be
* logged, and the RDP side of clipboard support will not be functional.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* for CLIPRDR support to be loaded.
*
* @param clipboard
* The guac_rdp_clipboard instance which has been allocated for the current
* RDP connection.
*
* @param context
* The rdpContext associated with the FreeRDP side of the RDP connection.
*/
void guac_rdp_clipboard_load_plugin(guac_rdp_clipboard* clipboard,
rdpContext* context);
/**
* Frees the resources associated with clipboard support for RDP and handling
* of the CLIPRDR channel. Only resources specific to Guacamole are freed.
* Resources specific to FreeRDP's handling of the CLIPRDR channel will be
* freed by FreeRDP. If the provided guac_rdp_clipboard is NULL, this function
* has no effect.
*
* @param clipboard
* The guac_rdp_clipboard instance which was been allocated for the current
* RDP connection.
*/
void guac_rdp_clipboard_free(guac_rdp_clipboard* clipboard);
/**
* Handler for inbound clipboard data, received via the stream created by an
* inbound "clipboard" instruction. This handler will assign the
* stream-specific handlers for processing "blob" and "end" instructions which
* will eventually be received as clipboard data is sent. This specific handler
* is expected to be assigned to the guac_user object of any user that may send
* clipboard data. The guac_rdp_clipboard instance which will receive this data
* MUST already be stored on the guac_rdp_client structure associated with the
* current RDP connection.
*/
guac_user_clipboard_handler guac_rdp_clipboard_handler;
/**
* Handler for stream data related to clipboard, received via "blob"
* instructions for a stream which has already been created with an inbound
* "clipboard" instruction. This specific handler is assigned to the
* guac_stream structure associated with that clipboard stream by
* guac_rdp_clipboard_handler(). The guac_rdp_clipboard instance which will
* receive this data MUST already be stored on the guac_rdp_client structure
* associated with the current RDP connection.
*/
guac_user_blob_handler guac_rdp_clipboard_blob_handler;
/**
* Handler for end-of-stream related to clipboard, indicated via an "end"
* instruction for a stream which has already been created with an inbound
* "clipboard" instruction. This specific handler is assigned to the
* guac_stream structure associated with that clipboard stream by
* guac_rdp_clipboard_handler(). The guac_rdp_clipboard instance which will
* receive this data MUST already be stored on the guac_rdp_client structure
* associated with the current RDP connection.
*/
guac_user_end_handler guac_rdp_clipboard_end_handler;
#endif

View File

@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/common-svc.h"
#include "plugins/channels.h"
#include "rdp.h"
#include <freerdp/settings.h>
#include <guacamole/client.h>
#include <guacamole/string.h>
#include <winpr/stream.h>
#include <winpr/wtsapi.h>
#include <winpr/wtypes.h>
#include <stdlib.h>
int guac_rdp_common_svc_load_plugin(rdpContext* context,
char* name, ULONG channel_options,
guac_rdp_common_svc_connect_handler* connect_handler,
guac_rdp_common_svc_receive_handler* receive_handler,
guac_rdp_common_svc_terminate_handler* terminate_handler) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_common_svc* svc = calloc(1, sizeof(guac_rdp_common_svc));
svc->client = client;
svc->name = svc->_channel_def.name;
svc->_connect_handler = connect_handler;
svc->_receive_handler = receive_handler;
svc->_terminate_handler = terminate_handler;
/* Init FreeRDP channel definition */
int name_length = guac_strlcpy(svc->_channel_def.name, name, GUAC_RDP_SVC_MAX_LENGTH);
svc->_channel_def.options =
CHANNEL_OPTION_INITIALIZED
| CHANNEL_OPTION_ENCRYPT_RDP
| channel_options;
/* Warn about name length */
if (name_length >= GUAC_RDP_SVC_MAX_LENGTH)
guac_client_log(client, GUAC_LOG_WARNING,
"Static channel name \"%s\" exceeds maximum length of %i "
"characters and will be truncated to \"%s\".",
name, GUAC_RDP_SVC_MAX_LENGTH - 1, svc->name);
/* Attempt to load the common SVC plugin for new static channel */
int result = guac_freerdp_channels_load_plugin(context, "guac-common-svc", svc);
if (result) {
guac_client_log(client, GUAC_LOG_WARNING, "Cannot create static "
"channel \"%s\": failed to load \"guac-common-svc\" plugin "
"for FreeRDP.", svc->name);
free(svc);
}
/* Store and log on success (SVC structure will be freed on channel termination) */
else
guac_client_log(client, GUAC_LOG_DEBUG, "Support for static channel "
"\"%s\" loaded.", svc->name);
return result;
}
void guac_rdp_common_svc_write(guac_rdp_common_svc* svc,
wStream* output_stream) {
/* Do not write if plugin not associated */
if (!svc->_open_handle) {
guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data "
"written to SVC \"%s\" are being dropped because the remote "
"desktop side of that SVC is not yet connected.",
Stream_Length(output_stream), svc->name);
return;
}
/* NOTE: Data sent via pVirtualChannelWriteEx MUST always be dynamically
* allocated, as it will be automatically freed using free(). If provided,
* the last parameter (user data) MUST be a pointer to a wStream, as it
* will automatically be freed by FreeRDP using Stream_Free() */
svc->_entry_points.pVirtualChannelWriteEx(svc->_init_handle,
svc->_open_handle, Stream_Buffer(output_stream),
Stream_GetPosition(output_stream), output_stream);
}

View File

@ -0,0 +1,229 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_CHANNELS_COMMON_SVC_H
#define GUAC_RDP_CHANNELS_COMMON_SVC_H
#include <freerdp/freerdp.h>
#include <freerdp/svc.h>
#include <guacamole/client.h>
#include <guacamole/stream.h>
#include <winpr/stream.h>
#include <winpr/wtsapi.h>
#include <winpr/wtypes.h>
/**
* The maximum number of bytes to allow within each channel name, including
* null terminator.
*/
#define GUAC_RDP_SVC_MAX_LENGTH 8
/**
* The maximum number of bytes that the RDP server will be allowed to send
* within any single write operation, regardless of the number of chunks that
* write is split into. Bytes beyond this limit may be dropped.
*/
#define GUAC_SVC_MAX_ASSEMBLED_LENGTH 1048576
/**
* Structure describing a static virtual channel, and the corresponding
* Guacamole pipes and FreeRDP resources.
*/
typedef struct guac_rdp_common_svc guac_rdp_common_svc;
/**
* Handler which is invoked when a CHANNEL_EVENT_CONNECTED event has been
* processed and the connection/initialization process of the SVC is now
* complete.
*
* @param svc
* The guac_rdp_common_svc structure representing the SVC that is now
* connected.
*/
typedef void guac_rdp_common_svc_connect_handler(guac_rdp_common_svc* svc);
/**
* Handler which is invoked when a logical block of data has been received
* along an SVC, having been reassembled from a series of
* CHANNEL_EVENT_DATA_RECEIVED events.
*
* @param svc
* The guac_rdp_common_svc structure representing the SVC that received the
* data.
*
* @param input_stream
* The reassembled block of data that was received.
*/
typedef void guac_rdp_common_svc_receive_handler(guac_rdp_common_svc* svc, wStream* input_stream);
/**
* Handler which is invoked when a CHANNEL_EVENT_TERMINATED event has been
* processed and all resources associated with the SVC must now be freed.
*
* @param svc
* The guac_rdp_common_svc structure representing the SVC that has been
* terminated.
*/
typedef void guac_rdp_common_svc_terminate_handler(guac_rdp_common_svc* svc);
struct guac_rdp_common_svc {
/**
* Reference to the client owning this static channel.
*/
guac_client* client;
/**
* The name of the static virtual channel, as specified to
* guac_rdp_common_svc_load_plugin(). This value is stored and defined
* internally by the CHANNEL_DEF.
*/
const char* name;
/**
* Arbitrary channel-specific data which may be assigned and referenced by
* channel implementations leveraging the "guac-common-svc" plugin.
*/
void* data;
/**
* Handler which is invoked when handling a CHANNEL_EVENT_CONNECTED event.
*/
guac_rdp_common_svc_connect_handler* _connect_handler;
/**
* Handler which is invoked when all chunks of data for a single logical
* block have been received via CHANNEL_EVENT_DATA_RECEIVED events and
* reassembled.
*/
guac_rdp_common_svc_receive_handler* _receive_handler;
/**
* Handler which is invokved when the SVC has been disconnected and is
* about to be freed.
*/
guac_rdp_common_svc_terminate_handler* _terminate_handler;
/**
* The definition of this static virtual channel, including its name.
*/
CHANNEL_DEF _channel_def;
/**
* Functions and data specific to the FreeRDP side of the virtual channel
* and plugin.
*/
CHANNEL_ENTRY_POINTS_FREERDP_EX _entry_points;
/**
* Handle which identifies the client connection, typically referred to
* within the FreeRDP source as pInitHandle. This handle is provided to the
* channel entry point and the channel init event handler. The handle must
* eventually be used within the channel open event handler to obtain a
* handle to the channel itself.
*/
PVOID _init_handle;
/**
* Handle which identifies the channel itself, typically referred to within
* the FreeRDP source as OpenHandle. This handle is obtained through a call
* to entry_points.pVirtualChannelOpenEx() in response to receiving a
* CHANNEL_EVENT_CONNECTED event via the init event handler.
*
* Data is received in CHANNEL_EVENT_DATA_RECEIVED events via the open
* event handler, and data is written through calls to
* entry_points.pVirtualChannelWriteEx().
*/
DWORD _open_handle;
/**
* All data that has been received thus far from the current RDP server
* write operation. Data received along virtual channels is sent in chunks
* (typically 1600 bytes), and thus must be gradually reassembled as it is
* received.
*/
wStream* _input_stream;
};
/**
* Initializes arbitrary static virtual channel (SVC) support for RDP, loading
* a new instance of Guacamole's arbitrary SVC plugin for FreeRDP ("guacsvc")
* supporting the channel having the given name. Data sent from within the RDP
* session using this channel will be sent along an identically-named pipe
* stream to the Guacamole client, and data sent along a pipe stream having the
* same name will be written to the SVC and received within the RDP session. If
* failures occur while loading the plugin, messages noting the specifics of
* those failures will be logged, and support for the given channel will not be
* functional.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* for static virtual channel support to be loaded.
*
* @param context
* The rdpContext associated with the FreeRDP side of the RDP connection.
*
* @param name
* The name of the SVC which should be handled by the new instance of the
* plugin.
*
* @param channel_options
* Bitwise OR of any of the several CHANNEL_OPTION_* flags. Regardless of
* whether specified here, the CHANNEL_OPTION_INTIALIZED and
* CHANNEL_OPTION_ENCRYPT_RDP flags will automatically be set.
*
* @param connect_handler
* The function to invoke when the SVC has been connected.
*
* @param receive_handler
* The function to invoke when the SVC has received a logical block of
* data, reassembled from perhaps several smaller chunks of data.
*
* @param terminate_handler
* The function to invoke when the SVC has been disconnected and is about
* to be freed.
*
* @return
* Zero if the plugin was loaded successfully, non-zero if the plugin could
* not be loaded.
*/
int guac_rdp_common_svc_load_plugin(rdpContext* context,
char* name, ULONG channel_options,
guac_rdp_common_svc_connect_handler* connect_handler,
guac_rdp_common_svc_receive_handler* receive_handler,
guac_rdp_common_svc_terminate_handler* terminate_handler);
/**
* Writes the given data to the virtual channel such that it can be received
* within the RDP session. The given data MUST be dynamically allocated, as the
* write operation may be queued and the actual write may not occur until
* later. The provided wStream and the buffer it points to will be
* automatically freed after the write occurs.
*
* @param svc
* The static virtual channel to write data to.
*
* @param output_stream
* The data to write, which MUST be dynamically allocated.
*/
void guac_rdp_common_svc_write(guac_rdp_common_svc* svc,
wStream* output_stream);
#endif

View File

@ -17,29 +17,26 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/disp.h"
#include "client.h" #include "plugins/channels.h"
#include "dvc.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_disp.h" #include "settings.h"
#include "rdp_settings.h"
#include <freerdp/client/disp.h>
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/event.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/timestamp.h> #include <guacamole/timestamp.h>
#ifdef HAVE_FREERDP_CLIENT_DISP_H #include <stdlib.h>
#include <freerdp/client/disp.h> #include <string.h>
#endif
guac_rdp_disp* guac_rdp_disp_alloc() { guac_rdp_disp* guac_rdp_disp_alloc() {
guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp)); guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp));
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/* Not yet connected */ /* Not yet connected */
disp->disp = NULL; disp->disp = NULL;
#endif
/* No requests have been made */ /* No requests have been made */
disp->last_request = guac_timestamp_current(); disp->last_request = guac_timestamp_current();
@ -55,23 +52,60 @@ void guac_rdp_disp_free(guac_rdp_disp* disp) {
free(disp); free(disp);
} }
void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list) { /**
* Callback which associates handlers specific to Guacamole with the
* DispClientContext instance allocated by FreeRDP to deal with received
* Display Update (client-initiated dynamic display resizing) messages.
*
* This function is called whenever a channel connects via the PubSub event
* system within FreeRDP, but only has any effect if the connected channel is
* the Display Update channel. This specific callback is registered with the
* PubSub system of the relevant rdpContext when guac_rdp_disp_load_plugin() is
* called.
*
* @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_disp_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* e) {
#ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL guac_client* client = ((rdp_freerdp_context*) context)->client;
context->settings->SupportDisplayControl = TRUE; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
#endif guac_rdp_disp* guac_disp = rdp_client->disp;
/* Ignore connection event if it's not for the Display Update channel */
if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) != 0)
return;
/* Init module with current display size */
guac_rdp_disp_set_size(guac_disp, rdp_client->settings,
context->instance, guac_rdp_get_width(context->instance),
guac_rdp_get_height(context->instance));
/* Store reference to the display update plugin once it's connected */
DispClientContext* disp = (DispClientContext*) e->pInterface;
guac_disp->disp = disp;
guac_client_log(client, GUAC_LOG_DEBUG, "Display update channel "
"will be used for display size changes.");
}
void guac_rdp_disp_load_plugin(rdpContext* context) {
/* Subscribe to and handle channel connected events */
PubSub_SubscribeChannelConnected(context->pubSub,
(pChannelConnectedEventHandler) guac_rdp_disp_channel_connected);
/* Add "disp" channel */ /* Add "disp" channel */
guac_rdp_dvc_list_add(list, "disp", NULL); guac_freerdp_dynamic_channel_collection_add(context->settings, "disp", NULL);
} }
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
void guac_rdp_disp_connect(guac_rdp_disp* guac_disp, DispClientContext* disp) {
guac_disp->disp = disp;
}
#endif
/** /**
* Fits a given dimension within the allowed bounds for Display Update * Fits a given dimension within the allowed bounds for Display Update
* messages, adjusting the other dimension such that aspect ratio is * messages, adjusting the other dimension such that aspect ratio is
@ -172,7 +206,6 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp,
} }
else if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) { else if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) {
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{ DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{
.Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */ .Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */
.Left = 0, .Left = 0,
@ -189,7 +222,6 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp,
/* Send display update notification if display channel is connected */ /* Send display update notification if display channel is connected */
if (disp->disp != NULL) if (disp->disp != NULL)
disp->disp->SendMonitorLayout(disp->disp, 1, monitors); disp->disp->SendMonitorLayout(disp->disp, 1, monitors);
#endif
} }
} }

View File

@ -17,17 +17,15 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_DISP_H #ifndef GUAC_RDP_CHANNELS_DISP_H
#define GUAC_RDP_DISP_H #define GUAC_RDP_CHANNELS_DISP_H
#include "dvc.h" #include "settings.h"
#include "rdp_settings.h"
#include <freerdp/freerdp.h>
#ifdef HAVE_FREERDP_CLIENT_DISP_H
#include <freerdp/client/disp.h> #include <freerdp/client/disp.h>
#endif #include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <guacamole/timestamp.h>
/** /**
* The minimum value for width or height, in pixels. * The minimum value for width or height, in pixels.
@ -50,12 +48,10 @@
*/ */
typedef struct guac_rdp_disp { typedef struct guac_rdp_disp {
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/** /**
* Display control interface. * Display control interface.
*/ */
DispClientContext* disp; DispClientContext* disp;
#endif
/** /**
* The timestamp of the last display update request, or 0 if no request * The timestamp of the last display update request, or 0 if no request
@ -83,53 +79,43 @@ typedef struct guac_rdp_disp {
/** /**
* Allocates a new display update module, which will ultimately control the * Allocates a new display update module, which will ultimately control the
* display update channel once conected. * display update channel once connected.
* *
* @return A new display update module. * @return
* A newly-allocated display update module.
*/ */
guac_rdp_disp* guac_rdp_disp_alloc(); guac_rdp_disp* guac_rdp_disp_alloc();
/** /**
* Frees the given display update module. * Frees the resources associated with support for the RDP Display Update
* channel. Only resources specific to Guacamole are freed. Resources specific
* to FreeRDP's handling of the Display Update channel will be freed by
* FreeRDP. If no resources are currently allocated for Display Update support,
* this function has no effect.
* *
* @param disp The display update module to free. * @param disp
* The display update module to free.
*/ */
void guac_rdp_disp_free(guac_rdp_disp* disp); void guac_rdp_disp_free(guac_rdp_disp* disp);
/**
* @param context The rdpContext associated with the active RDP session.
*/
/** /**
* Adds FreeRDP's "disp" plugin to the list of dynamic virtual channel plugins * Adds FreeRDP's "disp" plugin to the list of dynamic virtual channel plugins
* to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only be loaded * to be loaded by FreeRDP's "drdynvc" plugin. The context of the plugin will
* once guac_rdp_load_drdynvc() is invoked with the guac_rdp_dvc_list passed to * automatically be assicated with the guac_rdp_disp instance pointed to by the
* this function. The "disp" plugin ultimately adds support for the Display * current guac_rdp_client. The plugin will only be loaded once the "drdynvc"
* Update channel. NOTE: It is still up to external code to detect when the * plugin is loaded. The "disp" plugin ultimately adds support for the Display
* "disp" channel is connected, and update the guac_rdp_disp with a call to * Update channel.
* guac_rdp_disp_connect(). *
* If failures occur, messages noting the specifics of those failures will be
* logged, and the RDP side of Display Update support will not be functional.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* for Display Update support to be loaded.
* *
* @param context * @param context
* The rdpContext associated with the active RDP session. * The rdpContext associated with the active RDP session.
*
* @param list
* The guac_rdp_dvc_list to which the "disp" plugin should be added, such
* that it may later be loaded by guac_rdp_load_drdynvc().
*/ */
void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list); void guac_rdp_disp_load_plugin(rdpContext* context);
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/**
* Stores the given DispClientContext within the given guac_rdp_disp, such that
* display updates can be properly sent. Until this is called, changes to the
* display size will be deferred.
*
* @param guac_disp The display update module to associate with the connected
* display update channel.
* @param disp The DispClientContext associated by FreeRDP with the connected
* display update channel.
*/
void guac_rdp_disp_connect(guac_rdp_disp* guac_disp, DispClientContext* disp);
#endif
/** /**
* Requests a display size update, which may then be sent immediately to the * Requests a display size update, which may then be sent immediately to the
@ -200,6 +186,10 @@ void guac_rdp_disp_reconnect_complete(guac_rdp_disp* disp);
* Returns whether a full RDP reconnect is required for display update changes * Returns whether a full RDP reconnect is required for display update changes
* to take effect. * to take effect.
* *
* @param disp
* The display update module that should be checked to determine whether a
* reconnect is required.
*
* @return * @return
* Non-zero if a reconnect is needed, zero otherwise. * Non-zero if a reconnect is needed, zero otherwise.
*/ */

View File

@ -0,0 +1,230 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/common-svc.h"
#include "channels/pipe-svc.h"
#include "common/list.h"
#include "rdp.h"
#include <freerdp/settings.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <winpr/stream.h>
#include <stdlib.h>
#include <string.h>
void guac_rdp_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_svc* pipe_svc) {
/* Send pipe instruction for the SVC's output stream */
guac_protocol_send_pipe(socket, pipe_svc->output_pipe,
"application/octet-stream", pipe_svc->svc->name);
}
void guac_rdp_pipe_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_pipe_svc_send_pipe(user->socket, (guac_rdp_pipe_svc*) current->data);
current = current->next;
}
guac_common_list_unlock(rdp_client->available_svc);
}
void guac_rdp_pipe_svc_add(guac_client* client, guac_rdp_pipe_svc* pipe_svc) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Add to list of available SVC */
guac_common_list_lock(rdp_client->available_svc);
guac_common_list_add(rdp_client->available_svc, pipe_svc);
guac_common_list_unlock(rdp_client->available_svc);
}
guac_rdp_pipe_svc* guac_rdp_pipe_svc_get(guac_client* client, const char* name) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_list_element* current;
guac_rdp_pipe_svc* found = NULL;
/* For each available SVC */
guac_common_list_lock(rdp_client->available_svc);
current = rdp_client->available_svc->head;
while (current != NULL) {
/* If name matches, found */
guac_rdp_pipe_svc* current_svc = (guac_rdp_pipe_svc*) current->data;
if (strcmp(current_svc->svc->name, name) == 0) {
found = current_svc;
break;
}
current = current->next;
}
guac_common_list_unlock(rdp_client->available_svc);
return found;
}
guac_rdp_pipe_svc* guac_rdp_pipe_svc_remove(guac_client* client, const char* name) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_list_element* current;
guac_rdp_pipe_svc* found = NULL;
/* For each available SVC */
guac_common_list_lock(rdp_client->available_svc);
current = rdp_client->available_svc->head;
while (current != NULL) {
/* If name matches, remove entry */
guac_rdp_pipe_svc* current_svc = (guac_rdp_pipe_svc*) current->data;
if (strcmp(current_svc->svc->name, name) == 0) {
guac_common_list_remove(rdp_client->available_svc, current);
found = current_svc;
break;
}
current = current->next;
}
guac_common_list_unlock(rdp_client->available_svc);
/* Return removed entry, if any */
return found;
}
int guac_rdp_pipe_svc_pipe_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name) {
guac_rdp_pipe_svc* pipe_svc = guac_rdp_pipe_svc_get(user->client, name);
/* Fail if no such SVC */
if (pipe_svc == NULL) {
guac_user_log(user, GUAC_LOG_WARNING, "User requested non-existent "
"pipe (no such SVC configured): \"%s\"", name);
guac_protocol_send_ack(user->socket, stream, "FAIL (NO SUCH PIPE)",
GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
guac_socket_flush(user->socket);
return 0;
}
else
guac_user_log(user, GUAC_LOG_DEBUG, "Inbound half of channel \"%s\" "
"connected.", name);
/* Init stream data */
stream->data = pipe_svc;
stream->blob_handler = guac_rdp_pipe_svc_blob_handler;
return 0;
}
int guac_rdp_pipe_svc_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
guac_rdp_pipe_svc* pipe_svc = (guac_rdp_pipe_svc*) stream->data;
/* Write blob data to SVC directly */
wStream* output_stream = Stream_New(NULL, length);
Stream_Write(output_stream, data, length);
guac_rdp_common_svc_write(pipe_svc->svc, output_stream);
guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(user->socket);
return 0;
}
void guac_rdp_pipe_svc_process_connect(guac_rdp_common_svc* svc) {
/* Associate SVC with new Guacamole pipe */
guac_rdp_pipe_svc* pipe_svc = malloc(sizeof(guac_rdp_pipe_svc));
pipe_svc->svc = svc;
pipe_svc->output_pipe = guac_client_alloc_stream(svc->client);
svc->data = pipe_svc;
/* SVC may now receive data from client */
guac_rdp_pipe_svc_add(svc->client, pipe_svc);
/* Notify of pipe's existence */
guac_rdp_pipe_svc_send_pipe(svc->client->socket, pipe_svc);
}
void guac_rdp_pipe_svc_process_receive(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_rdp_pipe_svc* pipe_svc = (guac_rdp_pipe_svc*) svc->data;
/* Fail if output not created */
if (pipe_svc->output_pipe == NULL) {
guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data "
"received from within the remote desktop session for SVC "
"\"%s\" are being dropped because the outbound pipe stream "
"for that SVC is not yet open. This should NOT happen.",
Stream_Length(input_stream), svc->name);
return;
}
/* Send received data as blob */
guac_protocol_send_blob(svc->client->socket, pipe_svc->output_pipe, Stream_Buffer(input_stream), Stream_Length(input_stream));
guac_socket_flush(svc->client->socket);
}
void guac_rdp_pipe_svc_process_terminate(guac_rdp_common_svc* svc) {
guac_rdp_pipe_svc* pipe_svc = (guac_rdp_pipe_svc*) svc->data;
if (pipe_svc == NULL)
return;
/* Remove and free SVC */
guac_rdp_pipe_svc_remove(svc->client, svc->name);
free(pipe_svc);
}
void guac_rdp_pipe_svc_load_plugin(rdpContext* context, char* name) {
/* Attempt to load support for static channel */
guac_rdp_common_svc_load_plugin(context, name, CHANNEL_OPTION_COMPRESS_RDP,
guac_rdp_pipe_svc_process_connect,
guac_rdp_pipe_svc_process_receive,
guac_rdp_pipe_svc_process_terminate);
}

View File

@ -0,0 +1,188 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_CHANNELS_PIPE_SVC_H
#define GUAC_RDP_CHANNELS_PIPE_SVC_H
#include "channels/common-svc.h"
#include <freerdp/freerdp.h>
#include <freerdp/svc.h>
#include <guacamole/client.h>
#include <guacamole/stream.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <winpr/wtsapi.h>
/**
* The maximum number of bytes to allow within each channel name, including
* null terminator.
*/
#define GUAC_RDP_SVC_MAX_LENGTH 8
/**
* Structure describing a static virtual channel and a corresponding Guacamole
* pipe stream;
*/
typedef struct guac_rdp_pipe_svc {
/**
* The output pipe, opened when the RDP server receives a connection to
* the static channel.
*/
guac_stream* output_pipe;
/**
* The underlying static channel. Data written to this SVC by the RDP
* server will be forwarded along the pipe stream to the Guacamole client,
* and data written to the pipe stream by the Guacamole client will be
* forwarded along the SVC to the RDP server.
*/
guac_rdp_common_svc* svc;
} guac_rdp_pipe_svc;
/**
* Initializes arbitrary static virtual channel (SVC) support for RDP, handling
* communication for the SVC having the given name. Data sent from within the
* RDP session using this channel will be sent along an identically-named pipe
* stream to the Guacamole client, and data sent along a pipe stream having the
* same name will be written to the SVC and received within the RDP session. If
* failures occur while loading the plugin, messages noting the specifics of
* those failures will be logged, and support for the given channel will not be
* functional.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* for static virtual channel support to be loaded.
*
* @param context
* The rdpContext associated with the FreeRDP side of the RDP connection.
*
* @param name
* The name of the SVC which should be handled.
*/
void guac_rdp_pipe_svc_load_plugin(rdpContext* context, char* name);
/**
* 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_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_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_pipe_svc_send_pipes(guac_user* user);
/**
* Add the given SVC to the list of all available SVCs. This function must be
* invoked after the SVC is connected for inbound pipe streams having that
* SVC's name to result in received data being sent into the RDP session.
*
* @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_pipe_svc_add(guac_client* client, guac_rdp_pipe_svc* svc);
/**
* Retrieve the SVC with the given name from the list stored in the client. The
* requested SVC must previously have been added using guac_rdp_pipe_svc_add().
*
* @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_pipe_svc* guac_rdp_pipe_svc_get(guac_client* client, const char* name);
/**
* Removes the SVC with the given name from the list stored in the client.
* Inbound pipe streams having the given name will no longer be routed to the
* associated SVC.
*
* @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_pipe_svc* guac_rdp_pipe_svc_remove(guac_client* client, const char* name);
/**
* Handler for "blob" instructions which writes received data to the associated
* SVC using guac_rdp_pipe_svc_write().
*/
guac_user_blob_handler guac_rdp_pipe_svc_blob_handler;
/**
* Handler for "pipe" instructions which prepares received pipe streams to
* write received blobs to the SVC having the same name as the pipe stream.
* Received pipe streams are associated with the relevant guac_rdp_pipe_svc
* instance and the SVC-specific "blob" instruction handler
* (guac_rdp_pipe_svc_blob_handler).
*/
guac_user_pipe_handler guac_rdp_pipe_svc_pipe_handler;
/**
* Handler which is invoked when an SVC associated with a Guacamole pipe stream
* is connected to the RDP server.
*/
guac_rdp_common_svc_connect_handler guac_rdp_pipe_svc_process_connect;
/**
* Handler which is invoked when an SVC associated with a Guacamole pipe stream
* received data from the RDP server.
*/
guac_rdp_common_svc_receive_handler guac_rdp_pipe_svc_process_receive;
/**
* Handler which is invoked when an SVC associated with a Guacamole pipe stream
* has disconnected and is about to be freed.
*/
guac_rdp_common_svc_terminate_handler guac_rdp_pipe_svc_process_terminate;
#endif

View File

@ -0,0 +1,211 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/rail.h"
#include "plugins/channels.h"
#include "rdp.h"
#include "settings.h"
#include <freerdp/client/rail.h>
#include <freerdp/event.h>
#include <freerdp/freerdp.h>
#include <freerdp/rail.h>
#include <guacamole/client.h>
#include <winpr/wtypes.h>
#include <winpr/wtsapi.h>
#include <stddef.h>
#include <string.h>
#ifdef FREERDP_RAIL_CALLBACKS_REQUIRE_CONST
/**
* FreeRDP 2.0.0-rc4 and newer requires the final argument for all RAIL
* callbacks to be const.
*/
#define RAIL_CONST const
#else
/**
* FreeRDP 2.0.0-rc3 and older requires the final argument for all RAIL
* callbacks to NOT be const.
*/
#define RAIL_CONST
#endif
/**
* Completes initialization of the RemoteApp session, sending client system
* parameters and executing the desired RemoteApp command using the Client
* System Parameters Update PDU and Client Execute PDU respectively. These PDUs
* MUST be sent for the desired RemoteApp to run, and MUST NOT be sent until
* after a Handshake or HandshakeEx PDU has been received. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdperp/60344497-883f-4711-8b9a-828d1c580195 (System Parameters Update PDU)
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdperp/98a6e3c3-c2a9-42cc-ad91-0d9a6c211138 (Client Execute PDU)
*
* @param rail
* The RailClientContext structure used by FreeRDP to handle the RAIL
* channel for the current RDP session.
*
* @return
* CHANNEL_RC_OK (zero) if the PDUs were sent successfully, an error code
* (non-zero) otherwise.
*/
static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
guac_client* client = (guac_client*) rail->custom;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
RAIL_SYSPARAM_ORDER sysparam = {
.workArea = {
.left = 0,
.top = 0,
.right = rdp_client->settings->width,
.bottom = rdp_client->settings->height
},
.dragFullWindows = FALSE
};
/* Send client system parameters */
UINT status = rail->ClientSystemParam(rail, &sysparam);
if (status != CHANNEL_RC_OK)
return status;
RAIL_EXEC_ORDER exec = {
.RemoteApplicationProgram = rdp_client->settings->remote_app,
.RemoteApplicationWorkingDir = rdp_client->settings->remote_app_dir,
.RemoteApplicationArguments = rdp_client->settings->remote_app_args,
};
/* Execute desired RemoteApp command */
return rail->ClientExecute(rail, &exec);
}
/**
* Callback which is invoked when a Handshake PDU is received from the RDP
* server. No communication for RemoteApp may occur until the Handshake PDU
* (or, alternatively, the HandshakeEx PDU) is received. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdperp/cec4eb83-b304-43c9-8378-b5b8f5e7082a
*
* @param rail
* The RailClientContext structure used by FreeRDP to handle the RAIL
* channel for the current RDP session.
*
* @param handshake
* The RAIL_HANDSHAKE_ORDER structure representing the Handshake PDU that
* was received.
*
* @return
* CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code
* (non-zero) otherwise.
*/
static UINT guac_rdp_rail_handshake(RailClientContext* rail,
RAIL_CONST RAIL_HANDSHAKE_ORDER* handshake) {
return guac_rdp_rail_complete_handshake(rail);
}
/**
* Callback which is invoked when a HandshakeEx PDU is received from the RDP
* server. No communication for RemoteApp may occur until the HandshakeEx PDU
* (or, alternatively, the Handshake PDU) is received. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdperp/5cec5414-27de-442e-8d4a-c8f8b41f3899
*
* @param rail
* The RailClientContext structure used by FreeRDP to handle the RAIL
* channel for the current RDP session.
*
* @param handshake_ex
* The RAIL_HANDSHAKE_EX_ORDER structure representing the HandshakeEx PDU
* that was received.
*
* @return
* CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code
* (non-zero) otherwise.
*/
static UINT guac_rdp_rail_handshake_ex(RailClientContext* rail,
RAIL_CONST RAIL_HANDSHAKE_EX_ORDER* handshake_ex) {
return guac_rdp_rail_complete_handshake(rail);
}
/**
* Callback which associates handlers specific to Guacamole with the
* RailClientContext instance allocated by FreeRDP to deal with received
* RAIL (RemoteApp) messages.
*
* This function is called whenever a channel connects via the PubSub event
* system within FreeRDP, but only has any effect if the connected channel is
* the RAIL channel. This specific callback is registered with the PubSub
* system of the relevant rdpContext when guac_rdp_rail_load_plugin() is
* called.
*
* @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_rail_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* e) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
/* Ignore connection event if it's not for the RAIL channel */
if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) != 0)
return;
/* The structure pointed to by pInterface is guaranteed to be a
* RailClientContext if the channel is RAIL */
RailClientContext* rail = (RailClientContext*) e->pInterface;
/* Init FreeRDP RAIL context, ensuring the guac_client can be accessed from
* within any RAIL-specific callbacks */
rail->custom = client;
rail->ServerHandshake = guac_rdp_rail_handshake;
rail->ServerHandshakeEx = guac_rdp_rail_handshake_ex;
guac_client_log(client, GUAC_LOG_DEBUG, "RAIL (RemoteApp) channel "
"connected.");
}
void guac_rdp_rail_load_plugin(rdpContext* context) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
/* Attempt to load FreeRDP support for the RAIL channel */
if (guac_freerdp_channels_load_plugin(context, "rail", context->settings)) {
guac_client_log(client, GUAC_LOG_WARNING,
"Support for the RAIL channel (RemoteApp) could not be "
"loaded. This support normally takes the form of a plugin "
"which is built into FreeRDP. Lacking this support, "
"RemoteApp will not work.");
return;
}
/* Complete RDP side of initialization when channel is connected */
PubSub_SubscribeChannelConnected(context->pubSub,
(pChannelConnectedEventHandler) guac_rdp_rail_channel_connected);
guac_client_log(client, GUAC_LOG_DEBUG, "Support for RAIL (RemoteApp) "
"registered. Awaiting channel connection.");
}

View File

@ -17,19 +17,23 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_CHANNELS_RAIL_H
#define GUAC_RDP_CHANNELS_RAIL_H
#ifndef __GUAC_CLIENT_CLIPRDR_COMPAT_H #include <freerdp/freerdp.h>
#define __GUAC_CLIENT_CLIPRDR_COMPAT_H
#include "config.h" /**
* Initializes RemoteApp support for RDP and handling of the RAIL channel. If
#include <freerdp/plugins/cliprdr.h> * failures occur, messages noting the specifics of those failures will be
* logged, and RemoteApp support will not be functional.
#define CliprdrChannel_Class RDP_EVENT_CLASS_CLIPRDR *
#define CliprdrChannel_FormatList RDP_EVENT_TYPE_CB_FORMAT_LIST * This MUST be called within the PreConnect callback of the freerdp instance
#define CliprdrChannel_MonitorReady RDP_EVENT_TYPE_CB_MONITOR_READY * for RAIL support to be loaded.
#define CliprdrChannel_DataRequest RDP_EVENT_TYPE_CB_DATA_REQUEST *
#define CliprdrChannel_DataResponse RDP_EVENT_TYPE_CB_DATA_RESPONSE * @param context
* The rdpContext associated with the FreeRDP side of the RDP connection.
*/
void guac_rdp_rail_load_plugin(rdpContext* context);
#endif #endif

View File

@ -17,26 +17,22 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/rdpdr/rdpdr-fs-messages-dir-info.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdpdr_service.h" #include "fs.h"
#include "rdp_fs.h"
#include "rdp_status.h"
#include "unicode.h" #include "unicode.h"
#include <freerdp/utils/svc_plugin.h> #include <guacamole/client.h>
#include <guacamole/unicode.h> #include <guacamole/unicode.h>
#include <winpr/nt.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
#include <stddef.h>
#include <stddef.h> #include <stddef.h>
void guac_rdpdr_fs_process_query_directory_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_query_directory_info(guac_rdp_common_svc* svc,
const char* entry_name, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
const char* entry_name, int entry_file_id) {
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
@ -49,16 +45,17 @@ void guac_rdpdr_fs_process_query_directory_info(guac_rdpdr_device* device,
(char*) utf16_entry_name, sizeof(utf16_entry_name)); (char*) utf16_entry_name, sizeof(utf16_entry_name));
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, entry_file_id);
if (file == NULL) if (file == NULL)
return; return;
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i (entry_name=\"%s\")]", "%s: [file_id=%i (entry_name=\"%s\")]",
__func__, file_id, entry_name); __func__, entry_file_id, entry_name);
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 4 + 64 + utf16_length + 2); iorequest->completion_id, STATUS_SUCCESS,
4 + 64 + utf16_length + 2);
Stream_Write_UINT32(output_stream, Stream_Write_UINT32(output_stream,
64 + utf16_length + 2); /* Length */ 64 + utf16_length + 2); /* Length */
@ -77,12 +74,13 @@ void guac_rdpdr_fs_process_query_directory_info(guac_rdpdr_device* device,
Stream_Write(output_stream, utf16_entry_name, utf16_length); /* FileName */ Stream_Write(output_stream, utf16_entry_name, utf16_length); /* FileName */
Stream_Write(output_stream, "\0\0", 2); Stream_Write(output_stream, "\0\0", 2);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_full_directory_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_query_full_directory_info(guac_rdp_common_svc* svc,
const char* entry_name, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
const char* entry_name, int entry_file_id) {
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
@ -95,16 +93,17 @@ void guac_rdpdr_fs_process_query_full_directory_info(guac_rdpdr_device* device,
(char*) utf16_entry_name, sizeof(utf16_entry_name)); (char*) utf16_entry_name, sizeof(utf16_entry_name));
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, entry_file_id);
if (file == NULL) if (file == NULL)
return; return;
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i (entry_name=\"%s\")]", "%s: [file_id=%i (entry_name=\"%s\")]",
__func__, file_id, entry_name); __func__, entry_file_id, entry_name);
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 4 + 68 + utf16_length + 2); iorequest->completion_id, STATUS_SUCCESS,
4 + 68 + utf16_length + 2);
Stream_Write_UINT32(output_stream, Stream_Write_UINT32(output_stream,
68 + utf16_length + 2); /* Length */ 68 + utf16_length + 2); /* Length */
@ -124,12 +123,13 @@ void guac_rdpdr_fs_process_query_full_directory_info(guac_rdpdr_device* device,
Stream_Write(output_stream, utf16_entry_name, utf16_length); /* FileName */ Stream_Write(output_stream, utf16_entry_name, utf16_length); /* FileName */
Stream_Write(output_stream, "\0\0", 2); Stream_Write(output_stream, "\0\0", 2);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_both_directory_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_query_both_directory_info(guac_rdp_common_svc* svc,
const char* entry_name, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
const char* entry_name, int entry_file_id) {
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
@ -142,16 +142,17 @@ void guac_rdpdr_fs_process_query_both_directory_info(guac_rdpdr_device* device,
(char*) utf16_entry_name, sizeof(utf16_entry_name)); (char*) utf16_entry_name, sizeof(utf16_entry_name));
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, entry_file_id);
if (file == NULL) if (file == NULL)
return; return;
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i (entry_name=\"%s\")]", "%s: [file_id=%i (entry_name=\"%s\")]",
__func__, file_id, entry_name); __func__, entry_file_id, entry_name);
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 4 + 69 + 24 + utf16_length + 2); iorequest->completion_id, STATUS_SUCCESS,
4 + 69 + 24 + utf16_length + 2);
Stream_Write_UINT32(output_stream, Stream_Write_UINT32(output_stream,
69 + 24 + utf16_length + 2); /* Length */ 69 + 24 + utf16_length + 2); /* Length */
@ -175,12 +176,13 @@ void guac_rdpdr_fs_process_query_both_directory_info(guac_rdpdr_device* device,
Stream_Write(output_stream, utf16_entry_name, utf16_length); /* FileName */ Stream_Write(output_stream, utf16_entry_name, utf16_length); /* FileName */
Stream_Write(output_stream, "\0\0", 2); Stream_Write(output_stream, "\0\0", 2);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_names_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_query_names_info(guac_rdp_common_svc* svc,
const char* entry_name, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
const char* entry_name, int entry_file_id) {
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
@ -193,16 +195,17 @@ void guac_rdpdr_fs_process_query_names_info(guac_rdpdr_device* device,
(char*) utf16_entry_name, sizeof(utf16_entry_name)); (char*) utf16_entry_name, sizeof(utf16_entry_name));
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, entry_file_id);
if (file == NULL) if (file == NULL)
return; return;
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i (entry_name=\"%s\")]", "%s: [file_id=%i (entry_name=\"%s\")]",
__func__, file_id, entry_name); __func__, entry_file_id, entry_name);
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 4 + 12 + utf16_length + 2); iorequest->completion_id, STATUS_SUCCESS,
4 + 12 + utf16_length + 2);
Stream_Write_UINT32(output_stream, Stream_Write_UINT32(output_stream,
12 + utf16_length + 2); /* Length */ 12 + utf16_length + 2); /* Length */
@ -213,7 +216,7 @@ void guac_rdpdr_fs_process_query_names_info(guac_rdpdr_device* device,
Stream_Write(output_stream, utf16_entry_name, utf16_length); /* FileName */ Stream_Write(output_stream, utf16_entry_name, utf16_length); /* FileName */
Stream_Write(output_stream, "\0\0", 2); Stream_Write(output_stream, "\0\0", 2);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }

View File

@ -17,58 +17,76 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_DIR_INFO_H
#ifndef __GUAC_RDPDR_FS_MESSAGES_DIR_INFO_H #define GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_DIR_INFO_H
#define __GUAC_RDPDR_FS_MESSAGES_DIR_INFO_H
/** /**
* Handlers for directory queries received over the RDPDR channel via the * Handlers for directory queries received over the RDPDR channel via the
* IRP_MJ_DIRECTORY_CONTROL major function and the IRP_MN_QUERY_DIRECTORY minor * IRP_MJ_DIRECTORY_CONTROL major function and the IRP_MN_QUERY_DIRECTORY minor
* function. * function.
* *
* @file rdpdr_fs_messages_dir_info.h * @file rdpdr-fs-messages-dir-info.h
*/ */
#include "config.h" #include "channels/common-svc.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdpdr_service.h"
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h" /**
#endif * Handler for Device I/O Requests which query information about the files
* within a directory.
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param device
* The guac_rdpdr_device of the relevant device, as dictated by the
* deviceId field of the common RDPDR header within the received PDU.
* Within the guac_rdpdr_iorequest structure, the deviceId field is stored
* within device_id.
*
* @param iorequest
* The contents of the common RDPDR Device I/O Request header shared by all
* RDPDR devices.
*
* @param entry_name
* The filename of the file being queried.
*
* @param entry_file_id
* The ID of the file being queried.
*/
typedef void guac_rdpdr_directory_query_handler(guac_rdp_common_svc* svc,
guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
const char* entry_name, int entry_file_id);
/** /**
* Processes a query request for FileDirectoryInformation. From the * Processes a query request for FileDirectoryInformation. From the
* documentation this is "defined as the file's name, time stamp, and size, or its * documentation this is "defined as the file's name, time stamp, and size, or its
* attributes." * attributes."
*/ */
void guac_rdpdr_fs_process_query_directory_info(guac_rdpdr_device* device, guac_rdpdr_directory_query_handler guac_rdpdr_fs_process_query_directory_info;
const char* entry_name, int file_id, int completion_id);
/** /**
* Processes a query request for FileFullDirectoryInformation. From the * Processes a query request for FileFullDirectoryInformation. From the
* documentation, this is "defined as all the basic information, plus extended * documentation, this is "defined as all the basic information, plus extended
* attribute size." * attribute size."
*/ */
void guac_rdpdr_fs_process_query_full_directory_info(guac_rdpdr_device* device, guac_rdpdr_directory_query_handler guac_rdpdr_fs_process_query_full_directory_info;
const char* entry_name, int file_id, int completion_id);
/** /**
* Processes a query request for FileBothDirectoryInformation. From the * Processes a query request for FileBothDirectoryInformation. From the
* documentation, this absurdly-named request is "basic information plus * documentation, this absurdly-named request is "basic information plus
* extended attribute size and short name about a file or directory." * extended attribute size and short name about a file or directory."
*/ */
void guac_rdpdr_fs_process_query_both_directory_info(guac_rdpdr_device* device, guac_rdpdr_directory_query_handler guac_rdpdr_fs_process_query_both_directory_info;
const char* entry_name, int file_id, int completion_id);
/** /**
* Processes a query request for FileNamesInformation. From the documentation, * Processes a query request for FileNamesInformation. From the documentation,
* this is "detailed information on the names of files in a directory." * this is "detailed information on the names of files in a directory."
*/ */
void guac_rdpdr_fs_process_query_names_info(guac_rdpdr_device* device, guac_rdpdr_directory_query_handler guac_rdpdr_fs_process_query_names_info;
const char* entry_name, int file_id, int completion_id);
#endif #endif

View File

@ -17,44 +17,38 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/rdpdr/rdpdr-fs-messages-file-info.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdpdr_service.h" #include "download.h"
#include "rdp_fs.h" #include "fs.h"
#include "rdp_status.h"
#include "unicode.h" #include "unicode.h"
#include <freerdp/utils/svc_plugin.h> #include <guacamole/client.h>
#include <winpr/file.h>
#ifdef ENABLE_WINPR #include <winpr/nt.h>
#include <winpr/stream.h> #include <winpr/stream.h>
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-stream.h"
#include "compat/winpr-wtypes.h"
#endif
#include <inttypes.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
void guac_rdpdr_fs_process_query_basic_info(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_query_basic_info(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream; wStream* output_stream;
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, iorequest->file_id);
if (file == NULL) if (file == NULL)
return; return;
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 40); iorequest->completion_id, STATUS_SUCCESS, 40);
Stream_Write_UINT32(output_stream, 36); Stream_Write_UINT32(output_stream, 36);
Stream_Write_UINT64(output_stream, file->ctime); /* CreationTime */ Stream_Write_UINT64(output_stream, file->ctime); /* CreationTime */
@ -65,31 +59,31 @@ void guac_rdpdr_fs_process_query_basic_info(guac_rdpdr_device* device, wStream*
/* Reserved field must not be sent */ /* Reserved field must not be sent */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_standard_info(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_query_standard_info(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream; wStream* output_stream;
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
BOOL is_directory = FALSE; BOOL is_directory = FALSE;
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, iorequest->file_id);
if (file == NULL) if (file == NULL)
return; return;
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) if (file->attributes & FILE_ATTRIBUTE_DIRECTORY)
is_directory = TRUE; is_directory = TRUE;
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 26); iorequest->completion_id, STATUS_SUCCESS, 26);
Stream_Write_UINT32(output_stream, 22); Stream_Write_UINT32(output_stream, 22);
Stream_Write_UINT64(output_stream, file->size); /* AllocationSize */ Stream_Write_UINT64(output_stream, file->size); /* AllocationSize */
@ -100,27 +94,27 @@ void guac_rdpdr_fs_process_query_standard_info(guac_rdpdr_device* device, wStrea
/* Reserved field must not be sent */ /* Reserved field must not be sent */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_attribute_tag_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_query_attribute_tag_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream; wStream* output_stream;
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, iorequest->file_id);
if (file == NULL) if (file == NULL)
return; return;
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 12); iorequest->completion_id, STATUS_SUCCESS, 12);
Stream_Write_UINT32(output_stream, 8); Stream_Write_UINT32(output_stream, 8);
Stream_Write_UINT32(output_stream, file->attributes); /* FileAttributes */ Stream_Write_UINT32(output_stream, file->attributes); /* FileAttributes */
@ -128,12 +122,13 @@ void guac_rdpdr_fs_process_query_attribute_tag_info(guac_rdpdr_device* device,
/* Reserved field must not be sent */ /* Reserved field must not be sent */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_set_rename_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_set_rename_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id, int length) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
int length, wStream* input_stream) {
int result; int result;
int filename_length; int filename_length;
@ -149,9 +144,9 @@ void guac_rdpdr_fs_process_set_rename_info(guac_rdpdr_device* device,
guac_rdp_utf16_to_utf8(Stream_Pointer(input_stream), filename_length/2, guac_rdp_utf16_to_utf8(Stream_Pointer(input_stream), filename_length/2,
destination_path, sizeof(destination_path)); destination_path, sizeof(destination_path));
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]"
"%s: [file_id=%i] destination_path=\"%s\"", "destination_path=\"%s\"", __func__, iorequest->file_id,
__func__, file_id, destination_path); destination_path);
/* If file moving to \Download folder, start stream, do not move */ /* If file moving to \Download folder, start stream, do not move */
if (strncmp(destination_path, "\\Download\\", 10) == 0) { if (strncmp(destination_path, "\\Download\\", 10) == 0) {
@ -159,38 +154,39 @@ void guac_rdpdr_fs_process_set_rename_info(guac_rdpdr_device* device,
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, iorequest->file_id);
if (file == NULL) if (file == NULL)
return; return;
/* Initiate download, pretend move succeeded */ /* Initiate download, pretend move succeeded */
guac_rdpdr_start_download(device, file->absolute_path); guac_client_for_owner(svc->client, guac_rdp_download_to_user, file->absolute_path);
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
} }
/* Otherwise, rename as requested */ /* Otherwise, rename as requested */
else { else {
result = guac_rdp_fs_rename((guac_rdp_fs*) device->data, file_id, result = guac_rdp_fs_rename((guac_rdp_fs*) device->data,
destination_path); iorequest->file_id, destination_path);
if (result < 0) if (result < 0)
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, guac_rdp_fs_get_status(result), 4); iorequest->completion_id, guac_rdp_fs_get_status(result), 4);
else else
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
} }
Stream_Write_UINT32(output_stream, length); Stream_Write_UINT32(output_stream, length);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_set_allocation_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_set_allocation_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id, int length) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
int length, wStream* input_stream) {
int result; int result;
UINT64 size; UINT64 size;
@ -199,50 +195,50 @@ void guac_rdpdr_fs_process_set_allocation_info(guac_rdpdr_device* device,
/* Read new size */ /* Read new size */
Stream_Read_UINT64(input_stream, size); /* AllocationSize */ Stream_Read_UINT64(input_stream, size); /* AllocationSize */
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] "
"%s: [file_id=%i] size=%" PRIu64, "size=%" PRIu64, __func__, iorequest->file_id, (uint64_t) size);
__func__, file_id, (uint64_t) size);
/* Truncate file */ /* Truncate file */
result = guac_rdp_fs_truncate((guac_rdp_fs*) device->data, file_id, size); result = guac_rdp_fs_truncate((guac_rdp_fs*) device->data, iorequest->file_id, size);
if (result < 0) if (result < 0)
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, guac_rdp_fs_get_status(result), 4); iorequest->completion_id, guac_rdp_fs_get_status(result), 4);
else else
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
Stream_Write_UINT32(output_stream, length); Stream_Write_UINT32(output_stream, length);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_set_disposition_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_set_disposition_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id, int length) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
int length, wStream* input_stream) {
wStream* output_stream; wStream* output_stream;
/* Delete file */ /* Delete file */
int result = guac_rdp_fs_delete((guac_rdp_fs*) device->data, file_id); int result = guac_rdp_fs_delete((guac_rdp_fs*) device->data, iorequest->file_id);
if (result < 0) if (result < 0)
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, guac_rdp_fs_get_status(result), 4); iorequest->completion_id, guac_rdp_fs_get_status(result), 4);
else else
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
Stream_Write_UINT32(output_stream, length); Stream_Write_UINT32(output_stream, length);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_set_end_of_file_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_set_end_of_file_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id, int length) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
int length, wStream* input_stream) {
int result; int result;
UINT64 size; UINT64 size;
@ -251,38 +247,37 @@ void guac_rdpdr_fs_process_set_end_of_file_info(guac_rdpdr_device* device,
/* Read new size */ /* Read new size */
Stream_Read_UINT64(input_stream, size); /* AllocationSize */ Stream_Read_UINT64(input_stream, size); /* AllocationSize */
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] "
"%s: [file_id=%i] size=%" PRIu64, "size=%" PRIu64, __func__, iorequest->file_id, (uint64_t) size);
__func__, file_id, (uint64_t) size);
/* Truncate file */ /* Truncate file */
result = guac_rdp_fs_truncate((guac_rdp_fs*) device->data, file_id, size); result = guac_rdp_fs_truncate((guac_rdp_fs*) device->data, iorequest->file_id, size);
if (result < 0) if (result < 0)
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, guac_rdp_fs_get_status(result), 4); iorequest->completion_id, guac_rdp_fs_get_status(result), 4);
else else
output_stream = guac_rdpdr_new_io_completion(device, output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
Stream_Write_UINT32(output_stream, length); Stream_Write_UINT32(output_stream, length);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_set_basic_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_set_basic_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id, int length) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
int length, wStream* input_stream) {
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
/* Currently do nothing, just respond */ /* Currently do nothing, just respond */
Stream_Write_UINT32(output_stream, length); Stream_Write_UINT32(output_stream, length);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] IGNORED",
"%s: [file_id=%i] IGNORED", __func__, iorequest->file_id);
__func__, file_id);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }

View File

@ -17,85 +17,103 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_FILE_INFO_H
#ifndef __GUAC_RDPDR_FS_MESSAGES_FILE_INFO_H #define GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_FILE_INFO_H
#define __GUAC_RDPDR_FS_MESSAGES_FILE_INFO_H
/** /**
* Handlers for file queries received over the RDPDR channel via the * Handlers for file queries received over the RDPDR channel via the
* IRP_MJ_QUERY_INFORMATION major function. * IRP_MJ_QUERY_INFORMATION major function.
* *
* @file rdpdr_fs_messages_file_info.h * @file rdpdr-fs-messages-file-info.h
*/ */
#include "config.h" #include "channels/common-svc.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdpdr_service.h"
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h" /**
#endif * Handler for Device I/O Requests which set/update file information.
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param device
* The guac_rdpdr_device of the relevant device, as dictated by the
* deviceId field of the common RDPDR header within the received PDU.
* Within the guac_rdpdr_iorequest structure, the deviceId field is stored
* within device_id.
*
* @param iorequest
* The contents of the common RDPDR Device I/O Request header shared by all
* RDPDR devices.
*
* @param length
* The length of the SetBuffer field of the I/O request, in bytes. Whether
* the SetBuffer field is applicable to a particular request, as well as
* the specific contents of that field, depend on the type of request.
*
* @param input_stream
* The remaining data within the received PDU, following the common RDPDR
* Device I/O Request header and length field. If the SetBuffer field is
* used for this request, the first byte of SetBuffer will be the first
* byte read from this stream.
*/
typedef void guac_rdpdr_set_information_request_handler(guac_rdp_common_svc* svc,
guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
int length, wStream* input_stream);
/** /**
* Processes a query for FileBasicInformation. From the documentation, this is * Processes a query for FileBasicInformation. From the documentation, this is
* "used to query a file for the times of creation, last access, last write, * "used to query a file for the times of creation, last access, last write,
* and change, in addition to file attribute information." * and change, in addition to file attribute information."
*/ */
void guac_rdpdr_fs_process_query_basic_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_basic_info;
int file_id, int completion_id);
/** /**
* Processes a query for FileStandardInformation. From the documentation, this * Processes a query for FileStandardInformation. From the documentation, this
* is "used to query for file information such as allocation size, end-of-file * is "used to query for file information such as allocation size, end-of-file
* position, and number of links." * position, and number of links."
*/ */
void guac_rdpdr_fs_process_query_standard_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_standard_info;
int file_id, int completion_id);
/** /**
* Processes a query for FileAttributeTagInformation. From the documentation * Processes a query for FileAttributeTagInformation. From the documentation
* this is "used to query for file attribute and reparse tag information." * this is "used to query for file attribute and reparse tag information."
*/ */
void guac_rdpdr_fs_process_query_attribute_tag_info(guac_rdpdr_device* device, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_attribute_tag_info;
wStream* input_stream, int file_id, int completion_id);
/** /**
* Process a set operation for FileRenameInformation. From the documentation, * Process a set operation for FileRenameInformation. From the documentation,
* this operation is used to rename a file. * this operation is used to rename a file.
*/ */
void guac_rdpdr_fs_process_set_rename_info(guac_rdpdr_device* device, guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_rename_info;
wStream* input_stream, int file_id, int completion_id, int length);
/** /**
* Process a set operation for FileAllocationInformation. From the * Process a set operation for FileAllocationInformation. From the
* documentation, this operation is used to set a file's allocation size. * documentation, this operation is used to set a file's allocation size.
*/ */
void guac_rdpdr_fs_process_set_allocation_info(guac_rdpdr_device* device, guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_allocation_info;
wStream* input_stream, int file_id, int completion_id, int length);
/** /**
* Process a set operation for FileDispositionInformation. From the * Process a set operation for FileDispositionInformation. From the
* documentation, this operation is used to mark a file for deletion. * documentation, this operation is used to mark a file for deletion.
*/ */
void guac_rdpdr_fs_process_set_disposition_info(guac_rdpdr_device* device, guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_disposition_info;
wStream* input_stream, int file_id, int completion_id, int length);
/** /**
* Process a set operation for FileEndOfFileInformation. From the * Process a set operation for FileEndOfFileInformation. From the
* documentation, this operation is used "to set end-of-file information for * documentation, this operation is used "to set end-of-file information for
* a file." * a file."
*/ */
void guac_rdpdr_fs_process_set_end_of_file_info(guac_rdpdr_device* device, guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_end_of_file_info;
wStream* input_stream, int file_id, int completion_id, int length);
/** /**
* Process a set operation for FileBasicInformation. From the documentation, * Process a set operation for FileBasicInformation. From the documentation,
* this is "used to set file information such as the times of creation, last * this is "used to set file information such as the times of creation, last
* access, last write, and change, in addition to file attributes." * access, last write, and change, in addition to file attributes."
*/ */
void guac_rdpdr_fs_process_set_basic_info(guac_rdpdr_device* device, guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_basic_info;
wStream* input_stream, int file_id, int completion_id, int length);
#endif #endif

View File

@ -17,33 +17,29 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/common-svc.h"
#include "channels/rdpdr/rdpdr-fs-messages-vol-info.h"
#include "channels/rdpdr/rdpdr-fs.h"
#include "channels/rdpdr/rdpdr.h"
#include "fs.h"
#include "rdpdr_messages.h" #include <guacamole/client.h>
#include "rdpdr_service.h"
#include "rdp_fs.h"
#include "rdp_status.h"
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/unicode.h> #include <guacamole/unicode.h>
#include <winpr/file.h>
#ifdef ENABLE_WINPR #include <winpr/io.h>
#include <winpr/nt.h>
#include <winpr/stream.h> #include <winpr/stream.h>
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-stream.h"
#include "compat/winpr-wtypes.h"
#endif
void guac_rdpdr_fs_process_query_volume_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_query_volume_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 21 + GUAC_FILESYSTEM_LABEL_LENGTH); iorequest->completion_id, STATUS_SUCCESS, 21 + GUAC_FILESYSTEM_LABEL_LENGTH);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
Stream_Write_UINT32(output_stream, 17 + GUAC_FILESYSTEM_LABEL_LENGTH); Stream_Write_UINT32(output_stream, 17 + GUAC_FILESYSTEM_LABEL_LENGTH);
Stream_Write_UINT64(output_stream, 0); /* VolumeCreationTime */ Stream_Write_UINT64(output_stream, 0); /* VolumeCreationTime */
@ -53,22 +49,22 @@ void guac_rdpdr_fs_process_query_volume_info(guac_rdpdr_device* device,
/* Reserved field must not be sent */ /* Reserved field must not be sent */
Stream_Write(output_stream, GUAC_FILESYSTEM_LABEL, GUAC_FILESYSTEM_LABEL_LENGTH); Stream_Write(output_stream, GUAC_FILESYSTEM_LABEL, GUAC_FILESYSTEM_LABEL_LENGTH);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_size_info(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_query_size_info(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
guac_rdp_fs_info info = {0}; guac_rdp_fs_info info = {0};
guac_rdp_fs_get_info((guac_rdp_fs*) device->data, &info); guac_rdp_fs_get_info((guac_rdp_fs*) device->data, &info);
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 28); iorequest->completion_id, STATUS_SUCCESS, 28);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
Stream_Write_UINT32(output_stream, 24); Stream_Write_UINT32(output_stream, 24);
Stream_Write_UINT64(output_stream, info.blocks_total); /* TotalAllocationUnits */ Stream_Write_UINT64(output_stream, info.blocks_total); /* TotalAllocationUnits */
@ -76,39 +72,39 @@ void guac_rdpdr_fs_process_query_size_info(guac_rdpdr_device* device, wStream* i
Stream_Write_UINT32(output_stream, 1); /* SectorsPerAllocationUnit */ Stream_Write_UINT32(output_stream, 1); /* SectorsPerAllocationUnit */
Stream_Write_UINT32(output_stream, info.block_size); /* BytesPerSector */ Stream_Write_UINT32(output_stream, info.block_size); /* BytesPerSector */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_device_info(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_query_device_info(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 12); iorequest->completion_id, STATUS_SUCCESS, 12);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
Stream_Write_UINT32(output_stream, 8); Stream_Write_UINT32(output_stream, 8);
Stream_Write_UINT32(output_stream, FILE_DEVICE_DISK); /* DeviceType */ Stream_Write_UINT32(output_stream, FILE_DEVICE_DISK); /* DeviceType */
Stream_Write_UINT32(output_stream, 0); /* Characteristics */ Stream_Write_UINT32(output_stream, 0); /* Characteristics */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_attribute_info(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_query_attribute_info(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
int name_len = guac_utf8_strlen(device->device_name); int name_len = guac_utf8_strlen(device->device_name);
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 16 + name_len); iorequest->completion_id, STATUS_SUCCESS, 16 + name_len);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
Stream_Write_UINT32(output_stream, 12 + name_len); Stream_Write_UINT32(output_stream, 12 + name_len);
Stream_Write_UINT32(output_stream, Stream_Write_UINT32(output_stream,
@ -119,22 +115,22 @@ void guac_rdpdr_fs_process_query_attribute_info(guac_rdpdr_device* device, wStre
Stream_Write_UINT32(output_stream, name_len); Stream_Write_UINT32(output_stream, name_len);
Stream_Write(output_stream, device->device_name, name_len); Stream_Write(output_stream, device->device_name, name_len);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_query_full_size_info(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_query_full_size_info(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
guac_rdp_fs_info info = {0}; guac_rdp_fs_info info = {0};
guac_rdp_fs_get_info((guac_rdp_fs*) device->data, &info); guac_rdp_fs_get_info((guac_rdp_fs*) device->data, &info);
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 36); iorequest->completion_id, STATUS_SUCCESS, 36);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__,
"%s: [file_id=%i]", iorequest->file_id);
__func__, file_id);
Stream_Write_UINT32(output_stream, 32); Stream_Write_UINT32(output_stream, 32);
Stream_Write_UINT64(output_stream, info.blocks_total); /* TotalAllocationUnits */ Stream_Write_UINT64(output_stream, info.blocks_total); /* TotalAllocationUnits */
@ -143,7 +139,7 @@ void guac_rdpdr_fs_process_query_full_size_info(guac_rdpdr_device* device, wStre
Stream_Write_UINT32(output_stream, 1); /* SectorsPerAllocationUnit */ Stream_Write_UINT32(output_stream, 1); /* SectorsPerAllocationUnit */
Stream_Write_UINT32(output_stream, info.block_size); /* BytesPerSector */ Stream_Write_UINT32(output_stream, info.block_size); /* BytesPerSector */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }

View File

@ -17,59 +17,47 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_VOL_INFO_H
#ifndef __GUAC_RDPDR_FS_MESSAGES_VOL_INFO_H #define GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_VOL_INFO_H
#define __GUAC_RDPDR_FS_MESSAGES_VOL_INFO_H
/** /**
* Handlers for directory queries received over the RDPDR channel via the * Handlers for directory queries received over the RDPDR channel via the
* IRP_MJ_DIRECTORY_CONTROL major function and the IRP_MN_QUERY_DIRECTORY minor * IRP_MJ_DIRECTORY_CONTROL major function and the IRP_MN_QUERY_DIRECTORY minor
* function. * function.
* *
* @file rdpdr_fs_messages_vol_info.h * @file rdpdr-fs-messages-vol-info.h
*/ */
#include "config.h" #include "channels/rdpdr/rdpdr.h"
#include "rdpdr_service.h"
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/** /**
* Processes a query request for FileFsVolumeInformation. According to the * Processes a query request for FileFsVolumeInformation. According to the
* documentation, this is "used to query information for a volume on which a * documentation, this is "used to query information for a volume on which a
* file system is mounted." * file system is mounted."
*/ */
void guac_rdpdr_fs_process_query_volume_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_volume_info;
int file_id, int completion_id);
/** /**
* Processes a query request for FileFsSizeInformation. * Processes a query request for FileFsSizeInformation.
*/ */
void guac_rdpdr_fs_process_query_size_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_size_info;
int file_id, int completion_id);
/** /**
* Processes a query request for FileFsAttributeInformation. * Processes a query request for FileFsAttributeInformation.
*/ */
void guac_rdpdr_fs_process_query_attribute_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_attribute_info;
int file_id, int completion_id);
/** /**
* Processes a query request for FileFsFullSizeInformation. * Processes a query request for FileFsFullSizeInformation.
*/ */
void guac_rdpdr_fs_process_query_full_size_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_full_size_info;
int file_id, int completion_id);
/** /**
* Processes a query request for FileFsDeviceInformation. * Processes a query request for FileFsDeviceInformation.
*/ */
void guac_rdpdr_fs_process_query_device_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_device_info;
int file_id, int completion_id);
#endif #endif

View File

@ -17,36 +17,29 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/common-svc.h"
#include "channels/rdpdr/rdpdr-fs-messages-dir-info.h"
#include "rdpdr_fs_messages_dir_info.h" #include "channels/rdpdr/rdpdr-fs-messages-file-info.h"
#include "rdpdr_fs_messages_file_info.h" #include "channels/rdpdr/rdpdr-fs-messages-vol-info.h"
#include "rdpdr_fs_messages.h" #include "channels/rdpdr/rdpdr-fs-messages.h"
#include "rdpdr_fs_messages_vol_info.h" #include "channels/rdpdr/rdpdr.h"
#include "rdpdr_messages.h" #include "download.h"
#include "rdpdr_service.h" #include "fs.h"
#include "rdp_fs.h"
#include "rdp_status.h"
#include "unicode.h" #include "unicode.h"
#include <freerdp/utils/svc_plugin.h> #include <freerdp/channels/rdpdr.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <winpr/nt.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-stream.h"
#include "compat/winpr-wtypes.h"
#endif
#include <inttypes.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
void guac_rdpdr_fs_process_create(guac_rdpdr_device* device, void guac_rdpdr_fs_process_create(guac_rdp_common_svc* svc,
wStream* input_stream, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream; wStream* output_stream;
int file_id; int file_id;
@ -73,7 +66,7 @@ void guac_rdpdr_fs_process_create(guac_rdpdr_device* device,
desired_access, file_attributes, desired_access, file_attributes,
create_disposition, create_options); create_disposition, create_options);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i] " "%s: [file_id=%i] "
"desired_access=0x%x, file_attributes=0x%x, " "desired_access=0x%x, file_attributes=0x%x, "
"create_disposition=0x%x, create_options=0x%x, path=\"%s\"", "create_disposition=0x%x, create_options=0x%x, path=\"%s\"",
@ -83,11 +76,11 @@ void guac_rdpdr_fs_process_create(guac_rdpdr_device* device,
/* If an error occurred, notify server */ /* If an error occurred, notify server */
if (file_id < 0) { if (file_id < 0) {
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR, guac_client_log(svc->client, GUAC_LOG_ERROR,
"File open refused (%i): \"%s\"", file_id, path); "File open refused (%i): \"%s\"", file_id, path);
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
guac_rdp_fs_get_status(file_id), 5); iorequest->completion_id, guac_rdp_fs_get_status(file_id), 5);
Stream_Write_UINT32(output_stream, 0); /* fileId */ Stream_Write_UINT32(output_stream, 0); /* fileId */
Stream_Write_UINT8(output_stream, 0); /* information */ Stream_Write_UINT8(output_stream, 0); /* information */
} }
@ -97,8 +90,8 @@ void guac_rdpdr_fs_process_create(guac_rdpdr_device* device,
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 5); iorequest->completion_id, STATUS_SUCCESS, 5);
Stream_Write_UINT32(output_stream, file_id); /* fileId */ Stream_Write_UINT32(output_stream, file_id); /* fileId */
Stream_Write_UINT8(output_stream, 0); /* information */ Stream_Write_UINT8(output_stream, 0); /* information */
@ -107,8 +100,7 @@ void guac_rdpdr_fs_process_create(guac_rdpdr_device* device,
if (file != NULL && strcmp(file->absolute_path, "\\") == 0) { if (file != NULL && strcmp(file->absolute_path, "\\") == 0) {
int download_id = int download_id =
guac_rdp_fs_open((guac_rdp_fs*) device->data, "\\Download", guac_rdp_fs_open((guac_rdp_fs*) device->data, "\\Download",
ACCESS_GENERIC_READ, 0, GENERIC_READ, 0, FILE_OPEN_IF, FILE_DIRECTORY_FILE);
DISP_FILE_OPEN_IF, FILE_DIRECTORY_FILE);
if (download_id >= 0) if (download_id >= 0)
guac_rdp_fs_close((guac_rdp_fs*) device->data, download_id); guac_rdp_fs_close((guac_rdp_fs*) device->data, download_id);
@ -116,12 +108,13 @@ void guac_rdpdr_fs_process_create(guac_rdpdr_device* device,
} }
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_read(guac_rdpdr_device* device, void guac_rdpdr_fs_process_read(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
UINT32 length; UINT32 length;
UINT64 offset; UINT64 offset;
@ -134,9 +127,9 @@ void guac_rdpdr_fs_process_read(guac_rdpdr_device* device,
Stream_Read_UINT32(input_stream, length); Stream_Read_UINT32(input_stream, length);
Stream_Read_UINT64(input_stream, offset); Stream_Read_UINT64(input_stream, offset);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i] length=%i, offset=%" PRIu64, "%s: [file_id=%i] length=%i, offset=%" PRIu64,
__func__, file_id, length, (uint64_t) offset); __func__, iorequest->file_id, length, (uint64_t) offset);
/* Ensure buffer size does not exceed a safe maximum */ /* Ensure buffer size does not exceed a safe maximum */
if (length > GUAC_RDP_MAX_READ_BUFFER) if (length > GUAC_RDP_MAX_READ_BUFFER)
@ -146,31 +139,32 @@ void guac_rdpdr_fs_process_read(guac_rdpdr_device* device,
buffer = malloc(length); buffer = malloc(length);
/* Attempt read */ /* Attempt read */
bytes_read = guac_rdp_fs_read((guac_rdp_fs*) device->data, file_id, offset, bytes_read = guac_rdp_fs_read((guac_rdp_fs*) device->data,
buffer, length); iorequest->file_id, offset, buffer, length);
/* If error, return invalid parameter */ /* If error, return invalid parameter */
if (bytes_read < 0) { if (bytes_read < 0) {
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
guac_rdp_fs_get_status(bytes_read), 4); iorequest->completion_id, guac_rdp_fs_get_status(bytes_read), 4);
Stream_Write_UINT32(output_stream, 0); /* Length */ Stream_Write_UINT32(output_stream, 0); /* Length */
} }
/* Otherwise, send bytes read */ /* Otherwise, send bytes read */
else { else {
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 4+bytes_read); iorequest->completion_id, STATUS_SUCCESS, 4+bytes_read);
Stream_Write_UINT32(output_stream, bytes_read); /* Length */ Stream_Write_UINT32(output_stream, bytes_read); /* Length */
Stream_Write(output_stream, buffer, bytes_read); /* ReadData */ Stream_Write(output_stream, buffer, bytes_read); /* ReadData */
} }
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
free(buffer); free(buffer);
} }
void guac_rdpdr_fs_process_write(guac_rdpdr_device* device, void guac_rdpdr_fs_process_write(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
UINT32 length; UINT32 length;
UINT64 offset; UINT64 offset;
@ -183,69 +177,70 @@ void guac_rdpdr_fs_process_write(guac_rdpdr_device* device,
Stream_Read_UINT64(input_stream, offset); Stream_Read_UINT64(input_stream, offset);
Stream_Seek(input_stream, 20); /* Padding */ Stream_Seek(input_stream, 20); /* Padding */
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i] length=%i, offset=%" PRIu64, "%s: [file_id=%i] length=%i, offset=%" PRIu64,
__func__, file_id, length, (uint64_t) offset); __func__, iorequest->file_id, length, (uint64_t) offset);
/* Attempt write */ /* Attempt write */
bytes_written = guac_rdp_fs_write((guac_rdp_fs*) device->data, file_id, bytes_written = guac_rdp_fs_write((guac_rdp_fs*) device->data,
offset, Stream_Pointer(input_stream), length); iorequest->file_id, offset, Stream_Pointer(input_stream), length);
/* If error, return invalid parameter */ /* If error, return invalid parameter */
if (bytes_written < 0) { if (bytes_written < 0) {
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
guac_rdp_fs_get_status(bytes_written), 5); iorequest->completion_id, guac_rdp_fs_get_status(bytes_written), 5);
Stream_Write_UINT32(output_stream, 0); /* Length */ Stream_Write_UINT32(output_stream, 0); /* Length */
Stream_Write_UINT8(output_stream, 0); /* Padding */ Stream_Write_UINT8(output_stream, 0); /* Padding */
} }
/* Otherwise, send success */ /* Otherwise, send success */
else { else {
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 5); iorequest->completion_id, STATUS_SUCCESS, 5);
Stream_Write_UINT32(output_stream, bytes_written); /* Length */ Stream_Write_UINT32(output_stream, bytes_written); /* Length */
Stream_Write_UINT8(output_stream, 0); /* Padding */ Stream_Write_UINT8(output_stream, 0); /* Padding */
} }
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_close(guac_rdpdr_device* device, void guac_rdpdr_fs_process_close(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream; wStream* output_stream;
guac_rdp_fs_file* file; guac_rdp_fs_file* file;
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]",
"%s: [file_id=%i]", __func__, iorequest->file_id);
__func__, file_id);
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, iorequest->file_id);
if (file == NULL) if (file == NULL)
return; return;
/* If file was written to, and it's in the \Download folder, start stream */ /* If file was written to, and it's in the \Download folder, start stream */
if (file->bytes_written > 0 && if (file->bytes_written > 0 &&
strncmp(file->absolute_path, "\\Download\\", 10) == 0) { strncmp(file->absolute_path, "\\Download\\", 10) == 0) {
guac_rdpdr_start_download(device, file->absolute_path); guac_client_for_owner(svc->client, guac_rdp_download_to_user, file->absolute_path);
guac_rdp_fs_delete((guac_rdp_fs*) device->data, file_id); guac_rdp_fs_delete((guac_rdp_fs*) device->data, iorequest->file_id);
} }
/* Close file */ /* Close file */
guac_rdp_fs_close((guac_rdp_fs*) device->data, file_id); guac_rdp_fs_close((guac_rdp_fs*) device->data, iorequest->file_id);
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
Stream_Write(output_stream, "\0\0\0\0", 4); /* Padding */ Stream_Write(output_stream, "\0\0\0\0", 4); /* Padding */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_volume_info(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_volume_info(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
int fs_information_class; int fs_information_class;
@ -255,39 +250,35 @@ void guac_rdpdr_fs_process_volume_info(guac_rdpdr_device* device, wStream* input
switch (fs_information_class) { switch (fs_information_class) {
case FileFsVolumeInformation: case FileFsVolumeInformation:
guac_rdpdr_fs_process_query_volume_info(device, input_stream, guac_rdpdr_fs_process_query_volume_info(svc, device, iorequest, input_stream);
file_id, completion_id);
break; break;
case FileFsSizeInformation: case FileFsSizeInformation:
guac_rdpdr_fs_process_query_size_info(device, input_stream, guac_rdpdr_fs_process_query_size_info(svc, device, iorequest, input_stream);
file_id, completion_id);
break; break;
case FileFsDeviceInformation: case FileFsDeviceInformation:
guac_rdpdr_fs_process_query_device_info(device, input_stream, guac_rdpdr_fs_process_query_device_info(svc, device, iorequest, input_stream);
file_id, completion_id);
break; break;
case FileFsAttributeInformation: case FileFsAttributeInformation:
guac_rdpdr_fs_process_query_attribute_info(device, input_stream, guac_rdpdr_fs_process_query_attribute_info(svc, device, iorequest, input_stream);
file_id, completion_id);
break; break;
case FileFsFullSizeInformation: case FileFsFullSizeInformation:
guac_rdpdr_fs_process_query_full_size_info(device, input_stream, guac_rdpdr_fs_process_query_full_size_info(svc, device, iorequest, input_stream);
file_id, completion_id);
break; break;
default: default:
guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"Unknown volume information class: 0x%x", fs_information_class); "Unknown volume information class: 0x%x", fs_information_class);
} }
} }
void guac_rdpdr_fs_process_file_info(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_file_info(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
int fs_information_class; int fs_information_class;
@ -297,43 +288,42 @@ void guac_rdpdr_fs_process_file_info(guac_rdpdr_device* device, wStream* input_s
switch (fs_information_class) { switch (fs_information_class) {
case FileBasicInformation: case FileBasicInformation:
guac_rdpdr_fs_process_query_basic_info(device, input_stream, guac_rdpdr_fs_process_query_basic_info(svc, device, iorequest, input_stream);
file_id, completion_id);
break; break;
case FileStandardInformation: case FileStandardInformation:
guac_rdpdr_fs_process_query_standard_info(device, input_stream, guac_rdpdr_fs_process_query_standard_info(svc, device, iorequest, input_stream);
file_id, completion_id);
break; break;
case FileAttributeTagInformation: case FileAttributeTagInformation:
guac_rdpdr_fs_process_query_attribute_tag_info(device, input_stream, guac_rdpdr_fs_process_query_attribute_tag_info(svc, device, iorequest, input_stream);
file_id, completion_id);
break; break;
default: default:
guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"Unknown file information class: 0x%x", fs_information_class); "Unknown file information class: 0x%x", fs_information_class);
} }
} }
void guac_rdpdr_fs_process_set_volume_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_set_volume_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_NOT_SUPPORTED, 0); iorequest->completion_id, STATUS_NOT_SUPPORTED, 0);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i] Set volume info not supported", "%s: [file_id=%i] Set volume info not supported",
__func__, file_id); __func__, iorequest->file_id);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_set_file_info(guac_rdpdr_device* device, void guac_rdpdr_fs_process_set_file_info(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
int fs_information_class; int fs_information_class;
int length; int length;
@ -346,66 +336,62 @@ void guac_rdpdr_fs_process_set_file_info(guac_rdpdr_device* device,
switch (fs_information_class) { switch (fs_information_class) {
case FileBasicInformation: case FileBasicInformation:
guac_rdpdr_fs_process_set_basic_info(device, input_stream, guac_rdpdr_fs_process_set_basic_info(svc, device, iorequest, length, input_stream);
file_id, completion_id, length);
break; break;
case FileEndOfFileInformation: case FileEndOfFileInformation:
guac_rdpdr_fs_process_set_end_of_file_info(device, input_stream, guac_rdpdr_fs_process_set_end_of_file_info(svc, device, iorequest, length, input_stream);
file_id, completion_id, length);
break; break;
case FileDispositionInformation: case FileDispositionInformation:
guac_rdpdr_fs_process_set_disposition_info(device, input_stream, guac_rdpdr_fs_process_set_disposition_info(svc, device, iorequest, length, input_stream);
file_id, completion_id, length);
break; break;
case FileRenameInformation: case FileRenameInformation:
guac_rdpdr_fs_process_set_rename_info(device, input_stream, guac_rdpdr_fs_process_set_rename_info(svc, device, iorequest, length, input_stream);
file_id, completion_id, length);
break; break;
case FileAllocationInformation: case FileAllocationInformation:
guac_rdpdr_fs_process_set_allocation_info(device, input_stream, guac_rdpdr_fs_process_set_allocation_info(svc, device, iorequest, length, input_stream);
file_id, completion_id, length);
break; break;
default: default:
guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"Unknown file information class: 0x%x", "Unknown file information class: 0x%x",
fs_information_class); fs_information_class);
} }
} }
void guac_rdpdr_fs_process_device_control(guac_rdpdr_device* device, void guac_rdpdr_fs_process_device_control(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_INVALID_PARAMETER, 4); iorequest->completion_id, STATUS_INVALID_PARAMETER, 4);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] IGNORED",
"%s: [file_id=%i] IGNORED", __func__, iorequest->file_id);
__func__, file_id);
/* No content for now */ /* No content for now */
Stream_Write_UINT32(output_stream, 0); Stream_Write_UINT32(output_stream, 0);
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_notify_change_directory(guac_rdpdr_device* device, void guac_rdpdr_fs_process_notify_change_directory(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] Not "
"%s: [file_id=%i] Not implemented", "implemented", __func__, iorequest->file_id);
__func__, file_id);
} }
void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_query_directory(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream; wStream* output_stream;
@ -416,7 +402,7 @@ void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* i
const char* entry_name; const char* entry_name;
/* Get file */ /* Get file */
file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, iorequest->file_id);
if (file == NULL) if (file == NULL)
return; return;
@ -436,13 +422,13 @@ void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* i
} }
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] "
"%s: [file_id=%i] initial_query=%i, dir_pattern=\"%s\"", "initial_query=%i, dir_pattern=\"%s\"", __func__,
__func__, file_id, initial_query, file->dir_pattern); iorequest->file_id, initial_query, file->dir_pattern);
/* Find first matching entry in directory */ /* Find first matching entry in directory */
while ((entry_name = guac_rdp_fs_read_dir((guac_rdp_fs*) device->data, while ((entry_name = guac_rdp_fs_read_dir((guac_rdp_fs*) device->data,
file_id)) != NULL) { iorequest->file_id)) != NULL) {
/* Convert to absolute path */ /* Convert to absolute path */
char entry_path[GUAC_RDP_FS_MAX_PATH]; char entry_path[GUAC_RDP_FS_MAX_PATH];
@ -457,7 +443,7 @@ void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* i
/* Open directory entry */ /* Open directory entry */
entry_file_id = guac_rdp_fs_open((guac_rdp_fs*) device->data, entry_file_id = guac_rdp_fs_open((guac_rdp_fs*) device->data,
entry_path, ACCESS_FILE_READ_DATA, 0, DISP_FILE_OPEN, 0); entry_path, FILE_READ_DATA, 0, FILE_OPEN, 0);
if (entry_file_id >= 0) { if (entry_file_id >= 0) {
@ -465,27 +451,27 @@ void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* i
switch (fs_information_class) { switch (fs_information_class) {
case FileDirectoryInformation: case FileDirectoryInformation:
guac_rdpdr_fs_process_query_directory_info(device, guac_rdpdr_fs_process_query_directory_info(svc, device,
entry_name, entry_file_id, completion_id); iorequest, entry_name, entry_file_id);
break; break;
case FileFullDirectoryInformation: case FileFullDirectoryInformation:
guac_rdpdr_fs_process_query_full_directory_info(device, guac_rdpdr_fs_process_query_full_directory_info(svc,
entry_name, entry_file_id, completion_id); device, iorequest, entry_name, entry_file_id);
break; break;
case FileBothDirectoryInformation: case FileBothDirectoryInformation:
guac_rdpdr_fs_process_query_both_directory_info(device, guac_rdpdr_fs_process_query_both_directory_info(svc,
entry_name, entry_file_id, completion_id); device, iorequest, entry_name, entry_file_id);
break; break;
case FileNamesInformation: case FileNamesInformation:
guac_rdpdr_fs_process_query_names_info(device, guac_rdpdr_fs_process_query_names_info(svc, device,
entry_name, entry_file_id, completion_id); iorequest, entry_name, entry_file_id);
break; break;
default: default:
guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"Unknown dir information class: 0x%x", "Unknown dir information class: 0x%x",
fs_information_class); fs_information_class);
} }
@ -501,29 +487,30 @@ void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* i
* Handle errors as a lack of files. * Handle errors as a lack of files.
*/ */
output_stream = guac_rdpdr_new_io_completion(device, completion_id, output_stream = guac_rdpdr_new_io_completion(device,
STATUS_NO_MORE_FILES, 5); iorequest->completion_id, STATUS_NO_MORE_FILES, 5);
Stream_Write_UINT32(output_stream, 0); /* Length */ Stream_Write_UINT32(output_stream, 0); /* Length */
Stream_Write_UINT8(output_stream, 0); /* Padding */ Stream_Write_UINT8(output_stream, 0); /* Padding */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_fs_process_lock_control(guac_rdpdr_device* device, wStream* input_stream, void guac_rdpdr_fs_process_lock_control(guac_rdp_common_svc* svc,
int file_id, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_NOT_SUPPORTED, 5); iorequest->completion_id, STATUS_NOT_SUPPORTED, 5);
guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"%s: [file_id=%i] Lock not supported", "%s: [file_id=%i] Lock not supported",
__func__, file_id); __func__, iorequest->file_id);
Stream_Zero(output_stream, 5); /* Padding */ Stream_Zero(output_stream, 5); /* Padding */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }

View File

@ -17,59 +17,47 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_H
#ifndef __GUAC_RDPDR_FS_MESSAGES_H #define GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_H
#define __GUAC_RDPDR_FS_MESSAGES_H
/** /**
* Handlers for core drive I/O requests. Requests handled here may be simple * Handlers for core drive I/O requests. Requests handled here may be simple
* messages handled directly, or more complex multi-type messages handled * messages handled directly, or more complex multi-type messages handled
* elsewhere. * elsewhere.
* *
* @file rdpdr_fs_messages.h * @file rdpdr-fs-messages.h
*/ */
#include "config.h" #include "channels/rdpdr/rdpdr.h"
#include "rdpdr_service.h"
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/** /**
* Handles a Server Create Drive Request. Despite its name, this request opens * Handles a Server Create Drive Request. Despite its name, this request opens
* a file. * a file.
*/ */
void guac_rdpdr_fs_process_create(guac_rdpdr_device* device, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_create;
wStream* input_stream, int completion_id);
/** /**
* Handles a Server Close Drive Reqiest. This request closes an open file. * Handles a Server Close Drive Request. This request closes an open file.
*/ */
void guac_rdpdr_fs_process_close(guac_rdpdr_device* device, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_close;
wStream* input_stream, int file_id, int completion_id);
/** /**
* Handles a Server Drive Read Request. This request reads from a file. * Handles a Server Drive Read Request. This request reads from a file.
*/ */
void guac_rdpdr_fs_process_read(guac_rdpdr_device* device, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_read;
wStream* input_stream, int file_id, int completion_id);
/** /**
* Handles a Server Drive Write Request. This request writes to a file. * Handles a Server Drive Write Request. This request writes to a file.
*/ */
void guac_rdpdr_fs_process_write(guac_rdpdr_device* device, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_write;
wStream* input_stream, int file_id, int completion_id);
/** /**
* Handles a Server Drive Control Request. This request handles one of any * Handles a Server Drive Control Request. This request handles one of any
* number of Windows FSCTL_* control functions. * number of Windows FSCTL_* control functions.
*/ */
void guac_rdpdr_fs_process_device_control(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_device_control;
int file_id, int completion_id);
/** /**
* Handles a Server Drive Query Volume Information Request. This request * Handles a Server Drive Query Volume Information Request. This request
@ -77,53 +65,46 @@ void guac_rdpdr_fs_process_device_control(guac_rdpdr_device* device, wStream* in
* has several query types which have their own handlers defined in a * has several query types which have their own handlers defined in a
* separate file. * separate file.
*/ */
void guac_rdpdr_fs_process_volume_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_volume_info;
int file_id, int completion_id);
/** /**
* Handles a Server Drive Set Volume Information Request. Currently, this * Handles a Server Drive Set Volume Information Request. Currently, this
* RDPDR implementation does not support setting of volume information. * RDPDR implementation does not support setting of volume information.
*/ */
void guac_rdpdr_fs_process_set_volume_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_set_volume_info;
int file_id, int completion_id);
/** /**
* Handles a Server Drive Query Information Request. This request queries * Handles a Server Drive Query Information Request. This request queries
* information about a specific file. This request has several query types * information about a specific file. This request has several query types
* which have their own handlers defined in a separate file. * which have their own handlers defined in a separate file.
*/ */
void guac_rdpdr_fs_process_file_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_file_info;
int file_id, int completion_id);
/** /**
* Handles a Server Drive Set Information Request. This request sets * Handles a Server Drive Set Information Request. This request sets
* information about a specific file. Currently, this RDPDR implementation does * information about a specific file. Currently, this RDPDR implementation does
* not support setting of file information. * not support setting of file information.
*/ */
void guac_rdpdr_fs_process_set_file_info(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_set_file_info;
int file_id, int completion_id);
/** /**
* Handles a Server Drive Query Directory Request. This request queries * Handles a Server Drive Query Directory Request. This request queries
* information about a specific directory. This request has several query types * information about a specific directory. This request has several query types
* which have their own handlers defined in a separate file. * which have their own handlers defined in a separate file.
*/ */
void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_directory;
int file_id, int completion_id);
/** /**
* Handles a Server Drive NotifyChange Directory Request. This request requests * Handles a Server Drive NotifyChange Directory Request. This request requests
* directory change notification. * directory change notification.
*/ */
void guac_rdpdr_fs_process_notify_change_directory(guac_rdpdr_device* device, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_notify_change_directory;
wStream* input_stream, int file_id, int completion_id);
/** /**
* Handles a Server Drive Lock Control Request. This request locks or unlocks * Handles a Server Drive Lock Control Request. This request locks or unlocks
* portions of a file. * portions of a file.
*/ */
void guac_rdpdr_fs_process_lock_control(guac_rdpdr_device* device, wStream* input_stream, guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_lock_control;
int file_id, int completion_id);
#endif #endif

View File

@ -17,119 +17,116 @@
* under the License. * under the License.
*/ */
#include "channels/rdpdr/rdpdr-fs.h"
#include "config.h" #include "channels/rdpdr/rdpdr-fs-messages.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdp.h" #include "rdp.h"
#include "rdpdr_fs_messages.h"
#include "rdpdr_messages.h"
#include "rdpdr_service.h"
#include <freerdp/utils/svc_plugin.h> #include <freerdp/channels/rdpdr.h>
#include <freerdp/settings.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/unicode.h> #include <guacamole/unicode.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
static void guac_rdpdr_device_fs_iorequest_handler(guac_rdpdr_device* device, #include <stddef.h>
wStream* input_stream, int file_id, int completion_id, int major_func, int minor_func) {
switch (major_func) { void guac_rdpdr_device_fs_iorequest_handler(guac_rdp_common_svc* svc,
guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
switch (iorequest->major_func) {
/* File open */ /* File open */
case IRP_MJ_CREATE: case IRP_MJ_CREATE:
guac_rdpdr_fs_process_create(device, input_stream, completion_id); guac_rdpdr_fs_process_create(svc, device, iorequest, input_stream);
break; break;
/* File close */ /* File close */
case IRP_MJ_CLOSE: case IRP_MJ_CLOSE:
guac_rdpdr_fs_process_close(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_close(svc, device, iorequest, input_stream);
break; break;
/* File read */ /* File read */
case IRP_MJ_READ: case IRP_MJ_READ:
guac_rdpdr_fs_process_read(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_read(svc, device, iorequest, input_stream);
break; break;
/* File write */ /* File write */
case IRP_MJ_WRITE: case IRP_MJ_WRITE:
guac_rdpdr_fs_process_write(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_write(svc, device, iorequest, input_stream);
break; break;
/* Device control request (Windows FSCTL_ control codes) */ /* Device control request (Windows FSCTL_ control codes) */
case IRP_MJ_DEVICE_CONTROL: case IRP_MJ_DEVICE_CONTROL:
guac_rdpdr_fs_process_device_control(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_device_control(svc, device, iorequest, input_stream);
break; break;
/* Query volume (drive) information */ /* Query volume (drive) information */
case IRP_MJ_QUERY_VOLUME_INFORMATION: case IRP_MJ_QUERY_VOLUME_INFORMATION:
guac_rdpdr_fs_process_volume_info(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_volume_info(svc, device, iorequest, input_stream);
break; break;
/* Set volume (drive) information */ /* Set volume (drive) information */
case IRP_MJ_SET_VOLUME_INFORMATION: case IRP_MJ_SET_VOLUME_INFORMATION:
guac_rdpdr_fs_process_set_volume_info(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_set_volume_info(svc, device, iorequest, input_stream);
break; break;
/* Query file information */ /* Query file information */
case IRP_MJ_QUERY_INFORMATION: case IRP_MJ_QUERY_INFORMATION:
guac_rdpdr_fs_process_file_info(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_file_info(svc, device, iorequest, input_stream);
break; break;
/* Set file information */ /* Set file information */
case IRP_MJ_SET_INFORMATION: case IRP_MJ_SET_INFORMATION:
guac_rdpdr_fs_process_set_file_info(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_set_file_info(svc, device, iorequest, input_stream);
break; break;
case IRP_MJ_DIRECTORY_CONTROL: case IRP_MJ_DIRECTORY_CONTROL:
/* Enumerate directory contents */ /* Enumerate directory contents */
if (minor_func == IRP_MN_QUERY_DIRECTORY) if (iorequest->minor_func == IRP_MN_QUERY_DIRECTORY)
guac_rdpdr_fs_process_query_directory(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_query_directory(svc, device, iorequest,
input_stream);
/* Request notification of changes to directory */ /* Request notification of changes to directory */
else if (minor_func == IRP_MN_NOTIFY_CHANGE_DIRECTORY) else if (iorequest->minor_func == IRP_MN_NOTIFY_CHANGE_DIRECTORY)
guac_rdpdr_fs_process_notify_change_directory(device, input_stream, guac_rdpdr_fs_process_notify_change_directory(svc, device,
file_id, completion_id); iorequest, input_stream);
break; break;
/* Lock/unlock portions of a file */ /* Lock/unlock portions of a file */
case IRP_MJ_LOCK_CONTROL: case IRP_MJ_LOCK_CONTROL:
guac_rdpdr_fs_process_lock_control(device, input_stream, file_id, completion_id); guac_rdpdr_fs_process_lock_control(svc, device, iorequest, input_stream);
break; break;
default: default:
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR, guac_client_log(svc->client, GUAC_LOG_DEBUG,
"Unknown filesystem I/O request function: 0x%x/0x%x", "Unknown filesystem I/O request function: 0x%x/0x%x",
major_func, minor_func); iorequest->major_func, iorequest->minor_func);
} }
} }
static void guac_rdpdr_device_fs_free_handler(guac_rdpdr_device* device) { void guac_rdpdr_device_fs_free_handler(guac_rdp_common_svc* svc,
guac_rdpdr_device* device) {
Stream_Free(device->device_announce, 1); Stream_Free(device->device_announce, 1);
} }
void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr, char* drive_name) { void guac_rdpdr_register_fs(guac_rdp_common_svc* svc, char* drive_name) {
guac_client* client = rdpdr->client; guac_client* client = svc->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
int id = rdpdr->devices_registered++; int id = rdpdr->devices_registered++;
/* Get new device */ /* Get new device */
guac_rdpdr_device* device = &(rdpdr->devices[id]); guac_rdpdr_device* device = &(rdpdr->devices[id]);
/* Init device */ /* Init device */
device->rdpdr = rdpdr;
device->device_id = id; device->device_id = id;
device->device_name = drive_name; device->device_name = drive_name;
int device_name_len = guac_utf8_strlen(device->device_name); int device_name_len = guac_utf8_strlen(device->device_name);

View File

@ -17,9 +17,8 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_RDPDR_FS_H
#ifndef __GUAC_RDPDR_FS_H #define GUAC_RDP_RDPDR_FS_H
#define __GUAC_RDPDR_FS_H
/** /**
* Functions and macros specific to filesystem handling and initialization * Functions and macros specific to filesystem handling and initialization
@ -29,26 +28,35 @@
* system calls and structures, using the guac_rdpdr_device structure as a home * system calls and structures, using the guac_rdpdr_device structure as a home
* for common data. * for common data.
* *
* @file rdpdr_fs.h * @file rdpdr-fs.h
*/ */
#include "config.h" #include "channels/common-svc.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdpdr_service.h"
#include <guacamole/pool.h> #include <guacamole/pool.h>
/**
* The UTF-16 string that should be sent as the label of the filesystem.
*/
#define GUAC_FILESYSTEM_LABEL "G\0U\0A\0C\0F\0I\0L\0E\0"
/**
* The size of GUAC_FILESYSTEM_LABEL in bytes.
*/
#define GUAC_FILESYSTEM_LABEL_LENGTH 16
/** /**
* Registers a new filesystem device within the RDPDR plugin. This must be done * Registers a new filesystem device within the RDPDR plugin. This must be done
* before RDPDR connection finishes. * before RDPDR connection finishes.
* *
* @param rdpdr * @param svc
* The RDP device redirection plugin with which to register the device. * The static virtual channel instance being used for RDPDR.
* *
* @param drive_name * @param drive_name
* The name of the redirected drive to display in the RDP connection. * The name of the redirected drive to display in the RDP connection.
*/ */
void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr, char* drive_name); void guac_rdpdr_register_fs(guac_rdp_common_svc* svc, char* drive_name);
#endif #endif

View File

@ -0,0 +1,349 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/rdpdr/rdpdr-messages.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdp.h"
#include "settings.h"
#include <freerdp/channels/rdpdr.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
#include <stdlib.h>
#include <string.h>
/**
* Sends a Client Announce Reply message. The Client Announce Reply message is
* required to be sent in response to the Server Announce Request message. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/d6fe6d1b-c145-4a6f-99aa-4fe3cdcea398
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param major
* The major version of the RDPDR protocol in use. This value must always
* be 1.
*
* @param minor
* The minor version of the RDPDR protocol in use. This value must be
* either 2, 5, 10, 12, or 13.
*
* @param client_id
* The client ID received in the Server Announce Request, or a randomly
* generated ID.
*/
static void guac_rdpdr_send_client_announce_reply(guac_rdp_common_svc* svc,
unsigned int major, unsigned int minor, unsigned int client_id) {
wStream* output_stream = Stream_New(NULL, 12);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENTID_CONFIRM);
/* Write content */
Stream_Write_UINT16(output_stream, major);
Stream_Write_UINT16(output_stream, minor);
Stream_Write_UINT32(output_stream, client_id);
guac_rdp_common_svc_write(svc, output_stream);
}
/**
* Sends a Client Name Request message. The Client Name Request message is used
* by the client to announce its own name. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/902497f1-3b1c-4aee-95f8-1668f9b7b7d2
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param name
* The name that should be used for the client.
*/
static void guac_rdpdr_send_client_name_request(guac_rdp_common_svc* svc,
const char* name) {
int name_bytes = strlen(name) + 1;
wStream* output_stream = Stream_New(NULL, 16 + name_bytes);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_NAME);
/* Write content */
Stream_Write_UINT32(output_stream, 0); /* ASCII */
Stream_Write_UINT32(output_stream, 0); /* 0 required by RDPDR spec */
Stream_Write_UINT32(output_stream, name_bytes);
Stream_Write(output_stream, name, name_bytes);
guac_rdp_common_svc_write(svc, output_stream);
}
/**
* Sends a Client Core Capability Response message. The Client Core Capability
* Response message is used to announce the client's capabilities, in response
* to receiving the server's capabilities via a Server Core Capability Request.
* See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/f513bf87-cca0-488a-ac5c-18cf18f4a7e1
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*/
static void guac_rdpdr_send_client_capability(guac_rdp_common_svc* svc) {
wStream* output_stream = Stream_New(NULL, 256);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Sending capabilities...");
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_CAPABILITY);
/* Capability count + padding */
Stream_Write_UINT16(output_stream, 3);
Stream_Write_UINT16(output_stream, 0); /* Padding */
/* General capability header */
Stream_Write_UINT16(output_stream, CAP_GENERAL_TYPE);
Stream_Write_UINT16(output_stream, 44);
Stream_Write_UINT32(output_stream, GENERAL_CAPABILITY_VERSION_02);
/* General capability data */
Stream_Write_UINT32(output_stream, GUAC_OS_TYPE); /* osType - required to be ignored */
Stream_Write_UINT32(output_stream, 0); /* osVersion */
Stream_Write_UINT16(output_stream, 1); /* protocolMajor - must be set to 1 */
Stream_Write_UINT16(output_stream, RDPDR_MINOR_RDP_VERSION_5_2); /* protocolMinor */
Stream_Write_UINT32(output_stream, 0xFFFF); /* ioCode1 */
Stream_Write_UINT32(output_stream, 0); /* ioCode2 */
Stream_Write_UINT32(output_stream,
RDPDR_DEVICE_REMOVE_PDUS
| RDPDR_CLIENT_DISPLAY_NAME_PDU
| RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */
Stream_Write_UINT32(output_stream, 0); /* extraFlags1 */
Stream_Write_UINT32(output_stream, 0); /* extraFlags2 */
Stream_Write_UINT32(output_stream, 0); /* SpecialTypeDeviceCap */
/* Printer support header */
Stream_Write_UINT16(output_stream, CAP_PRINTER_TYPE);
Stream_Write_UINT16(output_stream, 8);
Stream_Write_UINT32(output_stream, PRINT_CAPABILITY_VERSION_01);
/* Drive support header */
Stream_Write_UINT16(output_stream, CAP_DRIVE_TYPE);
Stream_Write_UINT16(output_stream, 8);
Stream_Write_UINT32(output_stream, DRIVE_CAPABILITY_VERSION_02);
guac_rdp_common_svc_write(svc, output_stream);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Capabilities sent.");
}
/**
* Sends a Client Device List Announce Request message. The Client Device List
* Announce Request message is used by the client to enumerate all devices
* which should be made available within the RDP session via RDPDR. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/10ef9ada-cba2-4384-ab60-7b6290ed4a9a
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*/
static void guac_rdpdr_send_client_device_list_announce_request(guac_rdp_common_svc* svc) {
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
/* Calculate number of bytes needed for the stream */
int streamBytes = 16;
for (int i=0; i < rdpdr->devices_registered; i++)
streamBytes += rdpdr->devices[i].device_announce_len;
/* Allocate the stream */
wStream* output_stream = Stream_New(NULL, streamBytes);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_DEVICELIST_ANNOUNCE);
/* Get the stream for each of the devices. */
Stream_Write_UINT32(output_stream, rdpdr->devices_registered);
for (int i=0; i<rdpdr->devices_registered; i++) {
Stream_Write(output_stream,
Stream_Buffer(rdpdr->devices[i].device_announce),
rdpdr->devices[i].device_announce_len);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Registered device %i (%s)",
rdpdr->devices[i].device_id, rdpdr->devices[i].device_name);
}
guac_rdp_common_svc_write(svc, output_stream);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "All supported devices sent.");
}
void guac_rdpdr_process_server_announce(guac_rdp_common_svc* svc,
wStream* input_stream) {
unsigned int major, minor, client_id;
Stream_Read_UINT16(input_stream, major);
Stream_Read_UINT16(input_stream, minor);
Stream_Read_UINT32(input_stream, client_id);
/* Must choose own client ID if minor not >= 12 */
if (minor < 12)
client_id = random() & 0xFFFF;
guac_client_log(svc->client, GUAC_LOG_INFO, "Connected to RDPDR %u.%u as client 0x%04x", major, minor, client_id);
/* Respond to announce */
guac_rdpdr_send_client_announce_reply(svc, major, minor, client_id);
/* Name request */
guac_rdpdr_send_client_name_request(svc, ((guac_rdp_client*) svc->client->data)->settings->client_name);
}
void guac_rdpdr_process_clientid_confirm(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Client ID confirmed");
}
void guac_rdpdr_process_device_reply(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
unsigned int device_id, ntstatus;
int severity, c, n, facility, code;
Stream_Read_UINT32(input_stream, device_id);
Stream_Read_UINT32(input_stream, ntstatus);
severity = (ntstatus & 0xC0000000) >> 30;
c = (ntstatus & 0x20000000) >> 29;
n = (ntstatus & 0x10000000) >> 28;
facility = (ntstatus & 0x0FFF0000) >> 16;
code = ntstatus & 0x0000FFFF;
/* Log error / information */
if (device_id < rdpdr->devices_registered) {
if (severity == 0x0)
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Device %i (%s) connected successfully",
device_id, rdpdr->devices[device_id].device_name);
else
guac_client_log(svc->client, GUAC_LOG_ERROR, "Problem connecting device %i (%s): "
"severity=0x%x, c=0x%x, n=0x%x, facility=0x%x, code=0x%x",
device_id, rdpdr->devices[device_id].device_name,
severity, c, n, facility, code);
}
else
guac_client_log(svc->client, GUAC_LOG_ERROR, "Unknown device ID: 0x%08x", device_id);
}
void guac_rdpdr_process_device_iorequest(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
guac_rdpdr_iorequest iorequest;
/* Read header */
Stream_Read_UINT32(input_stream, iorequest.device_id);
Stream_Read_UINT32(input_stream, iorequest.file_id);
Stream_Read_UINT32(input_stream, iorequest.completion_id);
Stream_Read_UINT32(input_stream, iorequest.major_func);
Stream_Read_UINT32(input_stream, iorequest.minor_func);
/* If printer, run printer handlers */
if (iorequest.device_id >= 0 && iorequest.device_id < rdpdr->devices_registered) {
/* Call handler on device */
guac_rdpdr_device* device = &(rdpdr->devices[iorequest.device_id]);
device->iorequest_handler(svc, device, &iorequest, input_stream);
}
else
guac_client_log(svc->client, GUAC_LOG_ERROR, "Unknown device ID: "
"0x%08x", iorequest.device_id);
}
void guac_rdpdr_process_server_capability(guac_rdp_common_svc* svc,
wStream* input_stream) {
int count;
int i;
/* Read header */
Stream_Read_UINT16(input_stream, count);
Stream_Seek(input_stream, 2);
/* Parse capabilities */
for (i=0; i<count; i++) {
int type;
int length;
Stream_Read_UINT16(input_stream, type);
Stream_Read_UINT16(input_stream, length);
/* Ignore all for now */
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring server capability set type=0x%04x, length=%i", type, length);
Stream_Seek(input_stream, length - 4);
}
/* Send own capabilities */
guac_rdpdr_send_client_capability(svc);
}
void guac_rdpdr_process_user_loggedon(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_client_log(svc->client, GUAC_LOG_INFO, "RDPDR user logged on");
guac_rdpdr_send_client_device_list_announce_request(svc);
}
void guac_rdpdr_process_prn_cache_data(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring printer cached configuration data");
}
void guac_rdpdr_process_prn_using_xps(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_client_log(svc->client, GUAC_LOG_WARNING, "Printer unexpectedly switched to XPS mode");
}

View File

@ -0,0 +1,136 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_CHANNELS_RDPDR_MESSAGES_H
#define GUAC_RDP_CHANNELS_RDPDR_MESSAGES_H
#include "channels/common-svc.h"
#include "channels/rdpdr/rdpdr.h"
#include <winpr/stream.h>
#include <stdint.h>
/**
* A 32-bit arbitrary value for the osType field of certain requests. As this
* value is defined as completely arbitrary and required to be ignored by the
* server, we send "GUAC" as an integer.
*/
#define GUAC_OS_TYPE (*((uint32_t*) "GUAC"))
/**
* Handler which processes a message specific to the RDPDR channel.
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param input_stream
* A wStream containing the entire received message.
*/
typedef void guac_rdpdr_message_handler(guac_rdp_common_svc* svc,
wStream* input_stream);
/**
* Handler which processes a received Server Announce Request message. The
* Server Announce Request message begins the RDPDR exchange and provides a
* client ID which the RDPDR client may use. The client may also supply its
* own, randomly-generated ID, and is required to do so for older versions of
* RDPDR. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/046047aa-62d8-49f9-bf16-7fe41880aaf4
*/
guac_rdpdr_message_handler guac_rdpdr_process_server_announce;
/**
* Handler which processes a received Server Client ID Confirm message. The
* Server Client ID Confirm message is sent by the server to confirm the client
* ID requested by the client (in its response to the Server Announce Request)
* has been accepted. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/bbbb9666-6994-4cf6-8e65-0d46eb319c6e
*/
guac_rdpdr_message_handler guac_rdpdr_process_clientid_confirm;
/**
* Handler which processes a received Server Device Announce Response message.
* The Server Device Announce Response message is sent in response to a Client
* Device List Announce message to communicate the success/failure status of
* device creation. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/a4c0b619-6e87-4721-bdc4-5d2db7f485f3
*/
guac_rdpdr_message_handler guac_rdpdr_process_device_reply;
/**
* Handler which processes a received Device I/O Request message. The Device
* I/O Request message makes up the majority of traffic once RDPDR is
* established. Each I/O request consists of a device-specific major/minor
* function number pair, as well as several parameters. Device-specific
* handling of I/O requests within Guacamole is delegated to device- and
* function-specific implementations of yet another function type:
* guac_rdpdr_device_iorequest_handler.
*
* See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/a087ffa8-d0d5-4874-ac7b-0494f63e2d5d
*/
guac_rdpdr_message_handler guac_rdpdr_process_device_iorequest;
/**
* Handler which processes a received Server Core Capability Request message.
* The Server Core Capability Request message is sent by the server to
* communicate its capabilities and to request that the client communicate the
* same. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/702789c3-b924-4bc2-9280-3221bc7d6797
*/
guac_rdpdr_message_handler guac_rdpdr_process_server_capability;
/**
* Handler which processes a received Server User Logged On message. The Server
* User Logged On message is sent by the server to notify that the user has
* logged on to the session. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/dfc0e8ed-a242-4d00-bb88-e779e08f2f61
*/
guac_rdpdr_message_handler guac_rdpdr_process_user_loggedon;
/**
* Handler which processes any one of several RDPDR messages specific to cached
* printer configuration data, each of these messages having the same
* PAKID_PRN_CACHE_DATA packet ID. The Guacamole RDPDR implementation ignores
* all PAKID_PRN_CACHE_DATA messages. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpepc/7fccae60-f077-433b-9dee-9bad4238bf40
*/
guac_rdpdr_message_handler guac_rdpdr_process_prn_cache_data;
/**
* Handler which processes a received Server Printer Set XPS Mode message. The
* Server Printer Set XPS Mode message is specific to printers and requests
* that the client printer be set to XPS mode. The Guacamole RDPDR
* implementation ignores any request to set the printer to XPS mode. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpepc/f1789a66-bcd0-4df3-bfc2-6e7330d63145
*/
guac_rdpdr_message_handler guac_rdpdr_process_prn_using_xps;
#endif

View File

@ -17,41 +17,26 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/rdpdr/rdpdr-printer.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdpdr_messages.h" #include "print-job.h"
#include "rdpdr_printer.h"
#include "rdpdr_service.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_print_job.h"
#include "rdp_status.h"
#include "unicode.h" #include "unicode.h"
#include <freerdp/utils/svc_plugin.h> #include <freerdp/channels/rdpdr.h>
#include <freerdp/settings.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#include <guacamole/unicode.h> #include <guacamole/unicode.h>
#include <guacamole/user.h> #include <winpr/nt.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
#include <errno.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <unistd.h>
void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device, void guac_rdpdr_process_print_job_create(guac_rdp_common_svc* svc,
wStream* input_stream, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
guac_client* client = device->rdpdr->client; guac_client* client = svc->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Log creation of print job */ /* Log creation of print job */
@ -63,17 +48,18 @@ void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device,
/* Respond with success */ /* Respond with success */
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
Stream_Write_UINT32(output_stream, 0); /* fileId */ Stream_Write_UINT32(output_stream, 0); /* fileId */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device, void guac_rdpdr_process_print_job_write(guac_rdp_common_svc* svc,
wStream* input_stream, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
guac_client* client = device->rdpdr->client; guac_client* client = svc->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_print_job* job = (guac_rdp_print_job*) rdp_client->active_job; guac_rdp_print_job* job = (guac_rdp_print_job*) rdp_client->active_job;
@ -100,19 +86,20 @@ void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device,
} }
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, status, 5); iorequest->completion_id, status, 5);
Stream_Write_UINT32(output_stream, length); Stream_Write_UINT32(output_stream, length);
Stream_Write_UINT8(output_stream, 0); /* Padding */ Stream_Write_UINT8(output_stream, 0); /* Padding */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
} }
void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device, void guac_rdpdr_process_print_job_close(guac_rdp_common_svc* svc,
wStream* input_stream, int completion_id) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
guac_client* client = device->rdpdr->client; guac_client* client = svc->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_print_job* job = (guac_rdp_print_job*) rdp_client->active_job; guac_rdp_print_job* job = (guac_rdp_print_job*) rdp_client->active_job;
@ -123,61 +110,63 @@ void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device,
} }
wStream* output_stream = guac_rdpdr_new_io_completion(device, wStream* output_stream = guac_rdpdr_new_io_completion(device,
completion_id, STATUS_SUCCESS, 4); iorequest->completion_id, STATUS_SUCCESS, 4);
Stream_Write_UINT32(output_stream, 0); /* Padding */ Stream_Write_UINT32(output_stream, 0); /* Padding */
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); guac_rdp_common_svc_write(svc, output_stream);
/* Log end of print job */ /* Log end of print job */
guac_client_log(client, GUAC_LOG_INFO, "Print job closed"); guac_client_log(client, GUAC_LOG_INFO, "Print job closed");
} }
static void guac_rdpdr_device_printer_iorequest_handler(guac_rdpdr_device* device, void guac_rdpdr_device_printer_iorequest_handler(guac_rdp_common_svc* svc,
wStream* input_stream, int file_id, int completion_id, int major_func, int minor_func) { guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream) {
switch (major_func) { switch (iorequest->major_func) {
/* Print job create */ /* Print job create */
case IRP_MJ_CREATE: case IRP_MJ_CREATE:
guac_rdpdr_process_print_job_create(device, input_stream, completion_id); guac_rdpdr_process_print_job_create(svc, device, iorequest, input_stream);
break; break;
/* Printer job write */ /* Printer job write */
case IRP_MJ_WRITE: case IRP_MJ_WRITE:
guac_rdpdr_process_print_job_write(device, input_stream, completion_id); guac_rdpdr_process_print_job_write(svc, device, iorequest, input_stream);
break; break;
/* Printer job close */ /* Printer job close */
case IRP_MJ_CLOSE: case IRP_MJ_CLOSE:
guac_rdpdr_process_print_job_close(device, input_stream, completion_id); guac_rdpdr_process_print_job_close(svc, device, iorequest, input_stream);
break; break;
/* Log unknown */ /* Log unknown */
default: default:
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR, guac_client_log(svc->client, GUAC_LOG_ERROR, "Unknown printer "
"Unknown printer I/O request function: 0x%x/0x%x", "I/O request function: 0x%x/0x%x", iorequest->major_func,
major_func, minor_func); iorequest->minor_func);
} }
} }
static void guac_rdpdr_device_printer_free_handler(guac_rdpdr_device* device) { void guac_rdpdr_device_printer_free_handler(guac_rdp_common_svc* svc,
guac_rdpdr_device* device) {
Stream_Free(device->device_announce, 1); Stream_Free(device->device_announce, 1);
} }
void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr, char* printer_name) { void guac_rdpdr_register_printer(guac_rdp_common_svc* svc, char* printer_name) {
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
int id = rdpdr->devices_registered++; int id = rdpdr->devices_registered++;
/* Get new device */ /* Get new device */
guac_rdpdr_device* device = &(rdpdr->devices[id]); guac_rdpdr_device* device = &(rdpdr->devices[id]);
/* Init device */ /* Init device */
device->rdpdr = rdpdr;
device->device_id = id; device->device_id = id;
device->device_name = printer_name; device->device_name = printer_name;
int device_name_len = guac_utf8_strlen(device->device_name); int device_name_len = guac_utf8_strlen(device->device_name);

View File

@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_CHANNELS_RDPDR_PRINTER_H
#define GUAC_RDP_CHANNELS_RDPDR_PRINTER_H
#include "channels/common-svc.h"
#include "channels/rdpdr/rdpdr.h"
#include <winpr/stream.h>
/**
* Name of the printer driver that should be used on the server.
*/
#define GUAC_PRINTER_DRIVER "M\0S\0 \0P\0u\0b\0l\0i\0s\0h\0e\0r\0 \0I\0m\0a\0g\0e\0s\0e\0t\0t\0e\0r\0\0\0"
/**
* The size of GUAC_PRINTER_DRIVER in bytes.
*/
#define GUAC_PRINTER_DRIVER_LENGTH 50
/**
* Registers a new printer device within the RDPDR plugin. This must be done
* before RDPDR connection finishes.
*
* @param svc
* The static virtual channel instance being used for RDPDR.
*
* @param printer_name
* The name of the printer that will be registered with the RDP
* connection and passed through to the server.
*/
void guac_rdpdr_register_printer(guac_rdp_common_svc* svc, char* printer_name);
/**
* I/O request handler which processes a print job creation request.
*/
guac_rdpdr_device_iorequest_handler guac_rdpdr_process_print_job_create;
/**
* I/O request handler which processes a request to write data to an existing
* print job.
*/
guac_rdpdr_device_iorequest_handler guac_rdpdr_process_print_job_write;
/**
* I/O request handler which processes a request to close an existing print
* job.
*/
guac_rdpdr_device_iorequest_handler guac_rdpdr_process_print_job_close;
/**
* Handler for RDPDR Device I/O Requests which processes received messages on
* behalf of a printer device, in this case a simulated printer which produces
* PDF output.
*/
guac_rdpdr_device_iorequest_handler guac_rdpdr_device_printer_iorequest_handler;
/**
* Free handler which frees all data specific to the simulated printer device.
*/
guac_rdpdr_device_free_handler guac_rdpdr_device_printer_free_handler;
#endif

View File

@ -0,0 +1,184 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/rdpdr/rdpdr.h"
#include "channels/rdpdr/rdpdr-fs.h"
#include "channels/rdpdr/rdpdr-messages.h"
#include "channels/rdpdr/rdpdr-printer.h"
#include "rdp.h"
#include "settings.h"
#include <freerdp/channels/rdpdr.h>
#include <freerdp/freerdp.h>
#include <freerdp/settings.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
#include <stdlib.h>
void guac_rdpdr_process_receive(guac_rdp_common_svc* svc,
wStream* input_stream) {
int component;
int packet_id;
/* Read header */
Stream_Read_UINT16(input_stream, component);
Stream_Read_UINT16(input_stream, packet_id);
/* Core component */
if (component == RDPDR_CTYP_CORE) {
/* Dispatch handlers based on packet ID */
switch (packet_id) {
case PAKID_CORE_SERVER_ANNOUNCE:
guac_rdpdr_process_server_announce(svc, input_stream);
break;
case PAKID_CORE_CLIENTID_CONFIRM:
guac_rdpdr_process_clientid_confirm(svc, input_stream);
break;
case PAKID_CORE_DEVICE_REPLY:
guac_rdpdr_process_device_reply(svc, input_stream);
break;
case PAKID_CORE_DEVICE_IOREQUEST:
guac_rdpdr_process_device_iorequest(svc, input_stream);
break;
case PAKID_CORE_SERVER_CAPABILITY:
guac_rdpdr_process_server_capability(svc, input_stream);
break;
case PAKID_CORE_USER_LOGGEDON:
guac_rdpdr_process_user_loggedon(svc, input_stream);
break;
default:
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring "
"RDPDR core packet with unexpected ID: 0x%04x",
packet_id);
}
} /* end if core */
/* Printer component */
else if (component == RDPDR_CTYP_PRN) {
/* Dispatch handlers based on packet ID */
switch (packet_id) {
case PAKID_PRN_CACHE_DATA:
guac_rdpdr_process_prn_cache_data(svc, input_stream);
break;
case PAKID_PRN_USING_XPS:
guac_rdpdr_process_prn_using_xps(svc, input_stream);
break;
default:
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring RDPDR "
"printer packet with unexpected ID: 0x%04x",
packet_id);
}
} /* end if printer */
else
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring packet for "
"unknown RDPDR component: 0x%04x", component);
}
wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device,
int completion_id, int status, int size) {
wStream* output_stream = Stream_New(NULL, 16+size);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_DEVICE_IOCOMPLETION);
/* Write content */
Stream_Write_UINT32(output_stream, device->device_id);
Stream_Write_UINT32(output_stream, completion_id);
Stream_Write_UINT32(output_stream, status);
return output_stream;
}
void guac_rdpdr_process_connect(guac_rdp_common_svc* svc) {
/* Get data from client */
guac_client* client = svc->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdpdr* rdpdr = (guac_rdpdr*) calloc(1, sizeof(guac_rdpdr));
svc->data = rdpdr;
/* Register printer if enabled */
if (rdp_client->settings->printing_enabled)
guac_rdpdr_register_printer(svc, rdp_client->settings->printer_name);
/* Register drive if enabled */
if (rdp_client->settings->drive_enabled)
guac_rdpdr_register_fs(svc, rdp_client->settings->drive_name);
}
void guac_rdpdr_process_terminate(guac_rdp_common_svc* svc) {
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
if (rdpdr == NULL)
return;
int i;
for (i=0; i<rdpdr->devices_registered; i++) {
guac_rdpdr_device* device = &(rdpdr->devices[i]);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Unloading device %i "
"(%s)", device->device_id, device->device_name);
device->free_handler(svc, device);
}
free(rdpdr);
}
void guac_rdpdr_load_plugin(rdpContext* context) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
/* Load support for RDPDR */
if (guac_rdp_common_svc_load_plugin(context, "rdpdr",
CHANNEL_OPTION_COMPRESS_RDP, guac_rdpdr_process_connect,
guac_rdpdr_process_receive, guac_rdpdr_process_terminate)) {
guac_client_log(client, GUAC_LOG_WARNING, "Support for the RDPDR "
"channel (device redirection) could not be loaded. Drive "
"redirection and printing will not work. Sound MAY not work.");
}
}

View File

@ -0,0 +1,250 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_CHANNELS_RDPDR_H
#define GUAC_RDP_CHANNELS_RDPDR_H
#include "channels/common-svc.h"
#include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
#include <stdint.h>
/**
* The maximum number of bytes to allow for a device read.
*/
#define GUAC_RDP_MAX_READ_BUFFER 4194304
/**
* Arbitrary device forwarded over the RDPDR channel.
*/
typedef struct guac_rdpdr_device guac_rdpdr_device;
/**
* The contents of the header common to all RDPDR Device I/O Requests. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/a087ffa8-d0d5-4874-ac7b-0494f63e2d5d
*/
typedef struct guac_rdpdr_iorequest {
/**
* The unique ID assigned to the device receiving this I/O request.
*/
int device_id;
/**
* The unique ID which identifies the relevant file, as returned when the
* file was opened. This field may not be relevant to all requests.
*/
int file_id;
/**
* The unique ID that should be used to refer to this I/O request in future
* responses.
*/
int completion_id;
/**
* Integer ID which identifies the function being requested, such as
* IRP_MJ_CREATE (open a file within a shared drive) or IRP_MJ_WRITE (write
* data to an open file).
*/
int major_func;
/**
* Integer ID which identifies a variant of the function denoted by
* major_func. This value is only valid for IRP_MJ_DIRECTORY_CONTROL.
*/
int minor_func;
} guac_rdpdr_iorequest;
/**
* Handler for Device I/O Requests. RDPDR devices must provide an
* implementation of this function to be able to handle inbound I/O requests.
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param device
* The guac_rdpdr_device of the relevant device, as dictated by the
* deviceId field of the common RDPDR header within the received PDU.
* Within the guac_rdpdr_iorequest structure, the deviceId field is stored
* within device_id.
*
* @param iorequest
* The contents of the common RDPDR Device I/O Request header shared by all
* RDPDR devices.
*
* @param input_stream
* The remaining data within the received PDU, following the common RDPDR
* Device I/O Request header.
*/
typedef void guac_rdpdr_device_iorequest_handler(guac_rdp_common_svc* svc,
guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest,
wStream* input_stream);
/**
* Handler for cleaning up the dynamically-allocated portions of a device.
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param device
* The guac_rdpdr_device of the device being freed.
*/
typedef void guac_rdpdr_device_free_handler(guac_rdp_common_svc* svc,
guac_rdpdr_device* device);
struct guac_rdpdr_device {
/**
* The ID assigned to this device by the RDPDR plugin.
*/
int device_id;
/**
* Device name, used for logging and for passthrough to the
* server.
*/
const char* device_name;
/**
* The type of RDPDR device that this represents.
*/
uint32_t device_type;
/**
* The DOS name of the device. Max 8 bytes, including terminator.
*/
const char *dos_name;
/**
* The stream that stores the RDPDR device announcement for this device.
*/
wStream* device_announce;
/**
* The length of the device_announce wStream.
*/
int device_announce_len;
/**
* Handler which should be called for every I/O request received.
*/
guac_rdpdr_device_iorequest_handler* iorequest_handler;
/**
* Handler which should be called when the device is being freed.
*/
guac_rdpdr_device_free_handler* free_handler;
/**
* Arbitrary data, used internally by the handlers for this device.
*/
void* data;
};
/**
* Structure representing the current state of the Guacamole RDPDR plugin for
* FreeRDP.
*/
typedef struct guac_rdpdr {
/**
* The number of devices registered within the devices array.
*/
int devices_registered;
/**
* Array of registered devices.
*/
guac_rdpdr_device devices[8];
} guac_rdpdr;
/**
* Creates a new stream which contains the common DR_DEVICE_IOCOMPLETION header
* used for virtually all responses. Depending on the specific I/O completion
* being sent, additional space may be reserved within the resulting stream for
* additional fields. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/10ef9ada-cba2-4384-ab60-7b6290ed4a9a
*
* @param device
* The device that completed the operation requested by a prior I/O
* request.
*
* @param completion_id
* The completion ID of the I/O request that requested the operation.
*
* @param status
* An NTSTATUS code describing the success/failure of the operation that
* was completed.
*
* @param size
* The number of additional bytes to reserve at the end of the resulting
* stream for additional fields to be appended.
*
* @return
* A new wStream containing an I/O completion header, followed by the
* requested additional free space.
*/
wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device,
int completion_id, int status, int size);
/**
* Initializes device redirection support (file transfer, printing, etc.) for
* RDP and handling of the RDPDR channel. If failures occur, messages noting
* the specifics of those failures will be logged, and the RDP side of
* device redirection support will not be functional.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* for RDPDR support to be loaded.
*
* @param context
* The rdpContext associated with the FreeRDP side of the RDP connection.
*/
void guac_rdpdr_load_plugin(rdpContext* context);
/**
* Handler which is invoked when the RDPDR channel is connected to the RDP
* server.
*/
guac_rdp_common_svc_connect_handler guac_rdpdr_process_connect;
/**
* Handler which is invoked when the RDPDR channel has received data from the
* RDP server.
*/
guac_rdp_common_svc_receive_handler guac_rdpdr_process_receive;
/**
* Handler which is invoked when the RDPDR channel has disconnected and is
* about to be freed.
*/
guac_rdp_common_svc_terminate_handler guac_rdpdr_process_terminate;
#endif

View File

@ -17,30 +17,20 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/rdpsnd/rdpsnd-messages.h"
#include "channels/rdpsnd/rdpsnd.h"
#include "rdp.h" #include "rdp.h"
#include "rdpsnd_messages.h"
#include "rdpsnd_service.h"
#include <pthread.h> #include <freerdp/codec/audio.h>
#include <stdlib.h>
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/audio.h> #include <guacamole/audio.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-stream.h"
#include "compat/winpr-wtypes.h"
#endif
/* MESSAGE HANDLERS */ #include <stdlib.h>
#include <string.h>
void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_formats_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header) { wStream* input_stream, guac_rdpsnd_pdu_header* header) {
int server_format_count; int server_format_count;
@ -51,11 +41,10 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
int output_body_size; int output_body_size;
unsigned char* output_stream_end; unsigned char* output_stream_end;
/* Get associated client data */ guac_client* client = svc->client;
guac_client* client = rdpsnd->client; guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get audio stream from client data */ guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_audio_stream* audio = rdp_client->audio; guac_audio_stream* audio = rdp_client->audio;
/* Reset own format count */ /* Reset own format count */
@ -188,8 +177,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(&(rdp_client->rdp_lock)); guac_rdp_common_svc_write(svc, 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 */
if (server_version >= 6) { if (server_version >= 6) {
@ -202,23 +190,20 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
Stream_Write_UINT16(output_stream, HIGH_QUALITY); Stream_Write_UINT16(output_stream, HIGH_QUALITY);
Stream_Write_UINT16(output_stream, 0); Stream_Write_UINT16(output_stream, 0);
svc_plugin_send((rdpSvcPlugin*)rdpsnd, output_stream); guac_rdp_common_svc_write(svc, output_stream);
}
pthread_mutex_unlock(&(rdp_client->rdp_lock)); }
} }
/* server is getting a feel of the round trip time */ /* server is getting a feel of the round trip time */
void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_training_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header) { wStream* input_stream, guac_rdpsnd_pdu_header* header) {
int data_size; int data_size;
wStream* output_stream; wStream* output_stream;
/* Get associated client data */ guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data;
guac_client* client = rdpsnd->client;
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,22 +217,19 @@ 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(&(rdp_client->rdp_lock)); guac_rdp_common_svc_write(svc, output_stream);
svc_plugin_send((rdpSvcPlugin*) rdpsnd, output_stream);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
} }
void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_wave_info_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header) { wStream* input_stream, guac_rdpsnd_pdu_header* header) {
int format; int format;
/* Get associated client data */ guac_client* client = svc->client;
guac_client* client = rdpsnd->client; guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get audio stream from client data */ guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_audio_stream* audio = rdp_client->audio; guac_audio_stream* audio = rdp_client->audio;
/* Read wave information */ /* Read wave information */
@ -276,16 +258,13 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
} }
void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_wave_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header) { wStream* input_stream, guac_rdpsnd_pdu_header* header) {
rdpSvcPlugin* plugin = (rdpSvcPlugin*)rdpsnd; guac_client* client = svc->client;
guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data;
/* Get associated client data */
guac_client* client = rdpsnd->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get audio stream from client data */
guac_audio_stream* audio = rdp_client->audio; guac_audio_stream* audio = rdp_client->audio;
/* Wave Confirmation PDU */ /* Wave Confirmation PDU */
@ -313,16 +292,14 @@ 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(&(rdp_client->rdp_lock)); guac_rdp_common_svc_write(svc, output_stream);
svc_plugin_send(plugin, output_stream);
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;
} }
void guac_rdpsnd_close_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_close_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header) { wStream* input_stream, guac_rdpsnd_pdu_header* header) {
/* Do nothing */ /* Do nothing */

View File

@ -17,87 +17,12 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_CHANNELS_RDPSND_MESSAGES_H
#define GUAC_RDP_CHANNELS_RDPSND_MESSAGES_H
#ifndef __GUAC_RDPSND_MESSAGES_H #include "channels/common-svc.h"
#define __GUAC_RDPSND_MESSAGES_H
#include "config.h"
#include "rdpsnd_service.h"
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/*
* PDU Message Types
*/
/**
* Close PDU
*/
#define SNDC_CLOSE 1
/**
* WaveInfo PDU. This PDU is sent just before wave data is sent.
*/
#define SNDC_WAVE 2
/**
* Wave Confirm PDU. This PDU is sent in response to the WaveInfo PDU,
* confirming it has been received and played.
*/
#define SNDC_WAVECONFIRM 5
/**
* Training PDU. This PDU is sent by the server occasionally and must be
* responded to with another training PDU, similar to Guac's sync message.
*/
#define SNDC_TRAINING 6
/**
* Server Audio Formats and Version PDU. This PDU is sent by the server to
* advertise to the client which audio formats are supported.
*/
#define SNDC_FORMATS 7
/**
* Quality Mode PDU. This PDU must be sent by the client to select an audio
* quality mode if the server is at least version 6.
*/
#define SNDC_QUALITYMODE 12
/*
* Quality Modes
*/
/**
* Dynamic Quality. The server will choose the audio quality based on its
* perception of latency.
*/
#define DYNAMIC_QUALITY 0x0000
/**
* Medium Quality. The server prioritizes bandwidth over quality.
*/
#define MEDIUM_QUALITY 0x0001
/**
* High Quality. The server prioritizes quality over bandwidth.
*/
#define HIGH_QUALITY 0x0002
/*
* Capabilities
*/
#define TSSNDCAPS_ALIVE 1
/*
* Sound Formats
*/
#define WAVE_FORMAT_PCM 1
/** /**
* The header common to all RDPSND PDUs. * The header common to all RDPSND PDUs.
@ -121,8 +46,8 @@ typedef struct guac_rdpsnd_pdu_header {
* SNDC_FORMATS PDU describes all audio formats supported by the RDP server, as * SNDC_FORMATS PDU describes all audio formats supported by the RDP server, as
* well as the version of RDPSND implemented. * well as the version of RDPSND implemented.
* *
* @param rdpsnd * @param svc
* The Guacamole RDPSND plugin receiving the SNDC_FORMATS PDU. * The RDPSND channel receiving the SNDC_FORMATS PDU.
* *
* @param input_stream * @param input_stream
* The FreeRDP input stream containing the remaining raw bytes (after the * The FreeRDP input stream containing the remaining raw bytes (after the
@ -132,7 +57,7 @@ typedef struct guac_rdpsnd_pdu_header {
* The header content of the SNDC_FORMATS PDU. All RDPSND messages contain * The header content of the SNDC_FORMATS PDU. All RDPSND messages contain
* the same header information. * the same header information.
*/ */
void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_formats_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header); wStream* input_stream, guac_rdpsnd_pdu_header* header);
/** /**
@ -142,8 +67,8 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
* *
* https://msdn.microsoft.com/en-us/library/cc240961.aspx * https://msdn.microsoft.com/en-us/library/cc240961.aspx
* *
* @param rdpsnd * @param svc
* The Guacamole RDPSND plugin receiving the SNDC_TRAINING PDU. * The RDPSND channel receiving the SNDC_TRAINING PDU.
* *
* @param input_stream * @param input_stream
* The FreeRDP input stream containing the remaining raw bytes (after the * The FreeRDP input stream containing the remaining raw bytes (after the
@ -153,7 +78,7 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
* The header content of the SNDC_TRAINING PDU. All RDPSND messages contain * The header content of the SNDC_TRAINING PDU. All RDPSND messages contain
* the same header information. * the same header information.
*/ */
void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_training_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header); wStream* input_stream, guac_rdpsnd_pdu_header* header);
/** /**
@ -165,8 +90,8 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd,
* *
* https://msdn.microsoft.com/en-us/library/cc240963.aspx * https://msdn.microsoft.com/en-us/library/cc240963.aspx
* *
* @param rdpsnd * @param svc
* The Guacamole RDPSND plugin receiving the SNDC_WAVE PDU. * The RDPSND channel receiving the SNDC_WAVE PDU.
* *
* @param input_stream * @param input_stream
* The FreeRDP input stream containing the remaining raw bytes (after the * The FreeRDP input stream containing the remaining raw bytes (after the
@ -176,7 +101,7 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd,
* The header content of the SNDC_WAVE PDU. All RDPSND messages contain * The header content of the SNDC_WAVE PDU. All RDPSND messages contain
* the same header information. * the same header information.
*/ */
void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_wave_info_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header); wStream* input_stream, guac_rdpsnd_pdu_header* header);
/** /**
@ -184,8 +109,8 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
* PDU contains the actual audio data, less the four bytes of audio data * PDU contains the actual audio data, less the four bytes of audio data
* included in the SNDC_WAVE PDU. * included in the SNDC_WAVE PDU.
* *
* @param rdpsnd * @param svc
* The Guacamole RDPSND plugin receiving the SNDWAV PDU. * The RDPSND channel receiving the SNDWAV PDU.
* *
* @param input_stream * @param input_stream
* The FreeRDP input stream containing the remaining raw bytes (after the * The FreeRDP input stream containing the remaining raw bytes (after the
@ -195,7 +120,7 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
* The header content of the SNDWAV PDU. All RDPSND messages contain * The header content of the SNDWAV PDU. All RDPSND messages contain
* the same header information. * the same header information.
*/ */
void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_wave_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header); wStream* input_stream, guac_rdpsnd_pdu_header* header);
/** /**
@ -204,8 +129,8 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd,
* *
* https://msdn.microsoft.com/en-us/library/cc240970.aspx * https://msdn.microsoft.com/en-us/library/cc240970.aspx
* *
* @param rdpsnd * @param svc
* The Guacamole RDPSND plugin receiving the SNDC_CLOSE PDU. * The RDPSND channel receiving the SNDC_CLOSE PDU.
* *
* @param input_stream * @param input_stream
* The FreeRDP input stream containing the remaining raw bytes (after the * The FreeRDP input stream containing the remaining raw bytes (after the
@ -215,7 +140,7 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd,
* The header content of the SNDC_CLOSE PDU. All RDPSND messages contain * The header content of the SNDC_CLOSE PDU. All RDPSND messages contain
* the same header information. * the same header information.
*/ */
void guac_rdpsnd_close_handler(guac_rdpsndPlugin* rdpsnd, void guac_rdpsnd_close_handler(guac_rdp_common_svc* svc,
wStream* input_stream, guac_rdpsnd_pdu_header* header); wStream* input_stream, guac_rdpsnd_pdu_header* header);
#endif #endif

View File

@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/common-svc.h"
#include "channels/rdpsnd/rdpsnd.h"
#include "channels/rdpsnd/rdpsnd-messages.h"
#include "rdp.h"
#include <freerdp/codec/audio.h>
#include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
#include <stdlib.h>
void guac_rdpsnd_process_receive(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data;
guac_rdpsnd_pdu_header header;
/* Read RDPSND PDU header */
Stream_Read_UINT8(input_stream, header.message_type);
Stream_Seek_UINT8(input_stream);
Stream_Read_UINT16(input_stream, header.body_size);
/*
* If next PDU is SNDWAVE (due to receiving WaveInfo PDU previously),
* ignore the header and parse as a Wave PDU.
*/
if (rdpsnd->next_pdu_is_wave) {
guac_rdpsnd_wave_handler(svc, input_stream, &header);
return;
}
/* Dispatch message to standard handlers */
switch (header.message_type) {
/* Server Audio Formats and Version PDU */
case SNDC_FORMATS:
guac_rdpsnd_formats_handler(svc, input_stream, &header);
break;
/* Training PDU */
case SNDC_TRAINING:
guac_rdpsnd_training_handler(svc, input_stream, &header);
break;
/* WaveInfo PDU */
case SNDC_WAVE:
guac_rdpsnd_wave_info_handler(svc, input_stream, &header);
break;
/* Close PDU */
case SNDC_CLOSE:
guac_rdpsnd_close_handler(svc, input_stream, &header);
break;
}
}
void guac_rdpsnd_process_connect(guac_rdp_common_svc* svc) {
guac_rdpsnd* rdpsnd = (guac_rdpsnd*) calloc(1, sizeof(guac_rdpsnd));
svc->data = rdpsnd;
}
void guac_rdpsnd_process_terminate(guac_rdp_common_svc* svc) {
guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data;
free(rdpsnd);
}
void guac_rdpsnd_load_plugin(rdpContext* context) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
/* Load support for RDPSND */
if (guac_rdp_common_svc_load_plugin(context, "rdpsnd", 0,
guac_rdpsnd_process_connect, guac_rdpsnd_process_receive,
guac_rdpsnd_process_terminate)) {
guac_client_log(client, GUAC_LOG_WARNING, "Support for the RDPSND "
"channel (audio output) could not be loaded. Sound will not "
"work. Drive redirection and printing MAY not work.");
}
}

View File

@ -17,21 +17,14 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_CHANNELS_RDPSND_H
#define GUAC_RDP_CHANNELS_RDPSND_H
#ifndef __GUAC_RDPSND_SERVICE_H #include "channels/common-svc.h"
#define __GUAC_RDPSND_SERVICE_H
#include "config.h" #include <freerdp/freerdp.h>
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/** /**
* The maximum number of PCM formats to accept during the initial RDPSND * The maximum number of PCM formats to accept during the initial RDPSND
* handshake with the RDP server. * handshake with the RDP server.
@ -42,7 +35,7 @@
* Abstract representation of a PCM format, including the sample rate, number * Abstract representation of a PCM format, including the sample rate, number
* of channels, and bits per sample. * of channels, and bits per sample.
*/ */
typedef struct guac_pcm_format { typedef struct guac_rdpsnd_pcm_format {
/** /**
* The sample rate of this PCM format. * The sample rate of this PCM format.
@ -61,26 +54,13 @@ typedef struct guac_pcm_format {
*/ */
int bps; int bps;
} guac_pcm_format; } guac_rdpsnd_pcm_format;
/** /**
* Structure representing the current state of the Guacamole RDPSND plugin for * Structure representing the current state of the Guacamole RDPSND plugin for
* FreeRDP. * FreeRDP.
*/ */
typedef struct guac_rdpsndPlugin { typedef struct guac_rdpsnd {
/**
* The FreeRDP parts of this plugin. This absolutely MUST be first.
* FreeRDP depends on accessing this structure as if it were an instance
* of rdpSvcPlugin.
*/
rdpSvcPlugin plugin;
/**
* The Guacamole client associated with the guac_audio_stream that this
* plugin should use to stream received audio packets.
*/
guac_client* client;
/** /**
* The block number of the last SNDC_WAVE (WaveInfo) PDU received. * The block number of the last SNDC_WAVE (WaveInfo) PDU received.
@ -116,36 +96,45 @@ typedef struct guac_rdpsndPlugin {
* exchange. All of these formats will be PCM, which is the only format * exchange. All of these formats will be PCM, which is the only format
* guaranteed to be supported (based on the official RDP documentation). * guaranteed to be supported (based on the official RDP documentation).
*/ */
guac_pcm_format formats[GUAC_RDP_MAX_FORMATS]; guac_rdpsnd_pcm_format formats[GUAC_RDP_MAX_FORMATS];
/** /**
* The total number of formats. * The total number of formats.
*/ */
int format_count; int format_count;
} guac_rdpsndPlugin; } guac_rdpsnd;
/** /**
* Handler called when this plugin is loaded by FreeRDP. * Initializes audio output support for RDP and handling of the RDPSND channel.
* If failures occur, messages noting the specifics of those failures will be
* logged, and the RDP side of audio output support will not be functional.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* for RDPSND support to be loaded.
*
* @param context
* The rdpContext associated with the FreeRDP side of the RDP connection.
*/ */
void guac_rdpsnd_process_connect(rdpSvcPlugin* plugin); void guac_rdpsnd_load_plugin(rdpContext* context);
/** /**
* Handler called when this plugin receives data along its designated channel. * Handler which is invoked when the RDPSND channel is connected to the RDP
* server.
*/ */
void guac_rdpsnd_process_receive(rdpSvcPlugin* plugin, guac_rdp_common_svc_connect_handler guac_rdpsnd_process_connect;
wStream* input_stream);
/** /**
* Handler called when this plugin is being unloaded. * Handler which is invoked when the RDPSND channel has received data from the
* RDP server.
*/ */
void guac_rdpsnd_process_terminate(rdpSvcPlugin* plugin); guac_rdp_common_svc_receive_handler guac_rdpsnd_process_receive;
/** /**
* Handler called when this plugin receives an event. For the sake of RDPSND, * Handler which is invoked when the RDPSND channel has disconnected and is
* all events will be ignored and simply free'd. * about to be freed.
*/ */
void guac_rdpsnd_process_event(rdpSvcPlugin* plugin, wMessage* event); guac_rdp_common_svc_terminate_handler guac_rdpsnd_process_terminate;
#endif #endif

View File

@ -17,14 +17,16 @@
* under the License. * under the License.
*/ */
#include "config.h"
#include "audio_input.h"
#include "common/recording.h"
#include "client.h" #include "client.h"
#include "channels/audio-input/audio-buffer.h"
#include "channels/cliprdr.h"
#include "channels/disp.h"
#include "common/recording.h"
#include "config.h"
#include "fs.h"
#include "log.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_disp.h" #include "settings.h"
#include "rdp_fs.h"
#include "user.h" #include "user.h"
#ifdef ENABLE_COMMON_SSH #ifdef ENABLE_COMMON_SSH
@ -33,26 +35,11 @@
#include "common-ssh/user.h" #include "common-ssh/user.h"
#endif #endif
#include <freerdp/cache/cache.h>
#include <freerdp/channels/channels.h>
#include <freerdp/freerdp.h>
#include <guacamole/audio.h> #include <guacamole/audio.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/socket.h>
#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H
#include <freerdp/client/cliprdr.h>
#else
#include "compat/client-cliprdr.h"
#endif
#ifdef HAVE_FREERDP_CLIENT_CHANNELS_H
#include <freerdp/client/channels.h>
#endif
#include <pthread.h> #include <pthread.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
int guac_client_init(guac_client* client, int argc, char** argv) { int guac_client_init(guac_client* client, int argc, char** argv) {
@ -64,19 +51,19 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
client->data = rdp_client; client->data = rdp_client;
/* Init clipboard */ /* Init clipboard */
rdp_client->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); rdp_client->clipboard = guac_rdp_clipboard_alloc(client);
/* Init display update module */ /* Init display update module */
rdp_client->disp = guac_rdp_disp_alloc(); rdp_client->disp = guac_rdp_disp_alloc();
/* Redirect FreeRDP log messages to guac_client_log() */
guac_rdp_redirect_wlog(client);
/* Recursive attribute for locks */ /* Recursive attribute for locks */
pthread_mutexattr_init(&(rdp_client->attributes)); pthread_mutexattr_init(&(rdp_client->attributes));
pthread_mutexattr_settype(&(rdp_client->attributes), pthread_mutexattr_settype(&(rdp_client->attributes),
PTHREAD_MUTEX_RECURSIVE); PTHREAD_MUTEX_RECURSIVE);
/* Init RDP lock */
pthread_mutex_init(&(rdp_client->rdp_lock), &(rdp_client->attributes));
/* Set handlers */ /* Set handlers */
client->join_handler = guac_rdp_user_join_handler; client->join_handler = guac_rdp_user_join_handler;
client->free_handler = guac_rdp_client_free_handler; client->free_handler = guac_rdp_client_free_handler;
@ -100,6 +87,9 @@ int guac_rdp_client_free_handler(guac_client* client) {
if (rdp_client->settings != NULL) if (rdp_client->settings != NULL)
guac_rdp_settings_free(rdp_client->settings); guac_rdp_settings_free(rdp_client->settings);
/* Clean up clipboard */
guac_rdp_clipboard_free(rdp_client->clipboard);
/* Free display update module */ /* Free display update module */
guac_rdp_disp_free(rdp_client->disp); guac_rdp_disp_free(rdp_client->disp);
@ -136,7 +126,6 @@ int guac_rdp_client_free_handler(guac_client* client) {
guac_rdp_audio_buffer_free(rdp_client->audio_input); guac_rdp_audio_buffer_free(rdp_client->audio_input);
/* Free client data */ /* Free client data */
guac_common_clipboard_free(rdp_client->clipboard);
free(rdp_client); free(rdp_client);
return 0; return 0;

View File

@ -20,8 +20,6 @@
#ifndef GUAC_RDP_CLIENT_H #ifndef GUAC_RDP_CLIENT_H
#define GUAC_RDP_CLIENT_H #define GUAC_RDP_CLIENT_H
#include "config.h"
#include <guacamole/client.h> #include <guacamole/client.h>
/** /**

76
src/protocols/rdp/color.c Normal file
View File

@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "settings.h"
#include <freerdp/codec/color.h>
#include <freerdp/freerdp.h>
#include <freerdp/gdi/gdi.h>
#include <winpr/wtypes.h>
#include <stdint.h>
#include <string.h>
UINT32 guac_rdp_get_native_pixel_format(BOOL alpha) {
uint32_t int_value;
uint8_t raw_bytes[4] = { 0x0A, 0x0B, 0x0C, 0x0D };
memcpy(&int_value, raw_bytes, sizeof(raw_bytes));
/* Local platform stores bytes in decreasing order of significance
* (big-endian) */
if (int_value == 0x0A0B0C0D)
return alpha ? PIXEL_FORMAT_ARGB32 : PIXEL_FORMAT_XRGB32;
/* Local platform stores bytes in increasing order of significance
* (little-endian) */
else
return alpha ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_BGRX32;
}
UINT32 guac_rdp_convert_color(rdpContext* context, UINT32 color) {
int depth = guac_rdp_get_depth(context->instance);
int src_format = gdi_get_pixel_format(depth);
int dst_format = guac_rdp_get_native_pixel_format(TRUE);
rdpGdi* gdi = context->gdi;
/* Convert provided color into the intermediate representation expected by
* FreeRDPConvertColor() */
UINT32 intermed = ReadColor((BYTE*) &color, src_format);
/* Convert color from RDP source format to the native format used by Cairo,
* still maintaining intermediate representation */
#ifdef HAVE_FREERDPCONVERTCOLOR
intermed = FreeRDPConvertColor(intermed, src_format, dst_format,
&gdi->palette);
#else
intermed = ConvertColor(intermed, src_format, dst_format, &gdi->palette);
#endif
/* Convert color from intermediate representation to the actual desired
* format */
WriteColor((BYTE*) &color, dst_format, intermed);
return color;
}

View File

@ -21,12 +21,27 @@
#define GUAC_RDP_COLOR_H #define GUAC_RDP_COLOR_H
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-wtypes.h" /**
#endif * Returns the FreeRDP pixel format ID corresponding to the 32-bit RGB format
* used by the Cairo library's image surfaces. Cairo handles colors in terms of
* integers in native endianness, with CAIRO_FORMAT_ARGB32 representing a color
* format where the alpha channel is stored in the most significant byte,
* followed by red, green, and blue. FreeRDP handles colors in terms of
* absolute byte order, with PIXEL_FORMAT_ARGB32 representing a color format
* where the alpha channel is in byte 0, followed by red at byte 1, etc.
*
* @param alpha
* TRUE if the returned FreeRDP pixel format should correspond to Cairo's
* CAIRO_FORMAT_ARGB32, FALSE if the returned format should correspond to
* Cairo's CAIRO_FORMAT_RGB24.
*
* @return
* The FreeRDP pixel format ID that corresponds to the 32-bit RGB format
* used by the Cairo library.
*/
UINT32 guac_rdp_get_native_pixel_format(BOOL alpha);
/** /**
* 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

View File

@ -1,39 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef __GUAC_RAIL_COMPAT_H
#define __GUAC_RAIL_COMPAT_H
#include "config.h"
#include <freerdp/rail.h>
#define RailChannel_Class RDP_EVENT_CLASS_RAIL
#define RailChannel_ClientSystemParam RDP_EVENT_TYPE_RAIL_CLIENT_SET_SYSPARAMS
#define RailChannel_GetSystemParam RDP_EVENT_TYPE_RAIL_CHANNEL_GET_SYSPARAMS
#define RailChannel_ServerExecuteResult RDP_EVENT_TYPE_RAIL_CHANNEL_EXEC_RESULTS
#define RailChannel_ServerSystemParam RDP_EVENT_TYPE_RAIL_CHANNEL_SERVER_SYSPARAM
#define RailChannel_ServerMinMaxInfo RDP_EVENT_TYPE_RAIL_CHANNEL_SERVER_MINMAXINFO
#define RailChannel_ServerLocalMoveSize RDP_EVENT_TYPE_RAIL_CHANNEL_SERVER_LOCALMOVESIZE
#define RailChannel_ServerGetAppIdResponse RDP_EVENT_TYPE_RAIL_CHANNEL_APPID_RESP
#define RailChannel_ServerLanguageBarInfo RDP_EVENT_TYPE_RAIL_CHANNEL_LANGBARINFO
#endif

View File

@ -1,47 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "winpr-stream.h"
#include "winpr-wtypes.h"
wStream* Stream_New(BYTE* buffer, size_t size) {
/* If no buffer is provided, allocate a new stream of the given size */
if (buffer == NULL)
return stream_new(size);
/* Otherwise allocate an empty stream and assign the given buffer */
wStream* stream = stream_new(0);
stream_attach(stream, buffer, size);
return stream;
}
void Stream_Free(wStream* s, BOOL bFreeBuffer) {
/* Disassociate buffer if it will be freed externally */
if (!bFreeBuffer)
stream_detach(s);
stream_free(s);
}

View File

@ -1,71 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef __GUAC_WINPR_STREAM_COMPAT_H
#define __GUAC_WINPR_STREAM_COMPAT_H
#include "config.h"
#include "winpr-wtypes.h"
#include <freerdp/utils/stream.h>
#include <stddef.h>
/* FreeRDP 1.0 streams */
#define Stream_Write stream_write
#define Stream_Write_UINT8 stream_write_uint8
#define Stream_Write_UINT16 stream_write_uint16
#define Stream_Write_UINT32 stream_write_uint32
#define Stream_Write_UINT64 stream_write_uint64
#define Stream_Read stream_read
#define Stream_Read_UINT8 stream_read_uint8
#define Stream_Read_UINT16 stream_read_uint16
#define Stream_Read_UINT32 stream_read_uint32
#define Stream_Read_UINT64 stream_read_uint64
#define Stream_Seek stream_seek
#define Stream_Seek_UINT8 stream_seek_uint8
#define Stream_Seek_UINT16 stream_seek_uint16
#define Stream_Seek_UINT32 stream_seek_uint32
#define Stream_Seek_UINT64 stream_seek_uint64
#define Stream_GetPointer stream_get_mark
#define Stream_EnsureRemainingCapacity stream_check_size
#define Stream_Write stream_write
#define Stream_Zero stream_write_zero
#define Stream_Fill stream_set_byte
#define Stream_GetPosition stream_get_pos
#define Stream_SetPosition stream_set_pos
#define Stream_SetPointer stream_set_mark
#define Stream_Buffer stream_get_head
#define Stream_Pointer stream_get_tail
#define Stream_Length stream_get_size
#define wStream STREAM
#define wMessage RDP_EVENT
wStream* Stream_New(BYTE* buffer, size_t size);
void Stream_Free(wStream* s, BOOL bFreeBuffer);
#endif

View File

@ -17,7 +17,6 @@
* under the License. * under the License.
*/ */
#include "config.h"
#include "keyboard.h" #include "keyboard.h"
/** /**

View File

@ -0,0 +1,2 @@
!Makefile
*.exe

View File

@ -0,0 +1,43 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
LDFLAGS=-lwtsapi32
# Requires at least Windows Vista (Windows Server 2008 qualifies)
WINVER=0x600
# Windows cross compiler (MinGW)
CC=i686-w64-mingw32-gcc
all: svc-example.exe
clean:
$(RM) svc-example.exe
svc-example.exe: svc-example.c
$(CC) svc-example.c $(LDFLAGS) \
-D_WIN32_WINNT=$(WINVER) \
-DWINVER=$(WINVER) -o svc-example.exe

View File

@ -0,0 +1,169 @@
Static Virtual Channel example
==============================
Guacamole supports use of static virtual channels (SVCs) for transmission of
arbitrary data between the JavaScript client and applications running within
RDP sessions. This example is intended to demonstrate how bidirectional
communication between the Guacamole client and applications within the RDP
server can be accomplished.
Arbitrary SVCs are enabled on RDP connections by specfying their names as the
value of [the `static-channels`
parameter](http://guacamole.apache.org/doc/gug/configuring-guacamole.html#rdp-device-redirection).
Each name is limited to a maximum of 7 characters. Multiple names may be listed
by separating those names with commas.
This example consists of a single file, [`svc-example.c`](svc-example.c), which
leverages the terminal server API exposed by Windows to:
1. Open a channel called "EXAMPLE"
2. Wait for blocks of data to be received
3. Send each received block of data back, unmodified.
Building the example
--------------------
A `Makefile` is provided which uses MinGW to build the `svc-example.exe`
executable, and thus can be used to produce the example application on Linux.
The `Makefile` is not platform-independent, and changes may be needed for
`make` to succeed with your installation of MinGW. If not using MinGW, the C
source itself is standard and should compile with other tools.
To build on Linux using `make`:
```console
$ make
i686-w64-mingw32-gcc svc-example.c -lwtsapi32 \
-D_WIN32_WINNT=0x600 \
-DWINVER=0x600 -o svc-example.exe
$
```
You can then copy the resulting `svc-example.exe` to the remote desktop that
you wish to test and run it within a command prompt within the remote desktop
session.
Using the example (and SVCs in general)
---------------------------------------
On the remote desktop server side (within the Windows application leveraging
SVCs to communicate with Guacamole), the following functions are used
specifically for reading/writing to the SVC:
* [`WTSVirtualChannelOpenEx()`](https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelopenex)
* [`WTSVirtualChannelRead()`](https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelread)
* [`WTSVirtualChannelWrite()`](https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelwrite)
* [`WTSVirtualChannelClose()`](https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelclose)
On the Guacamole side, bidirectional communication is established using:
* The `static-channels` connection parameter (in the case of the example, this should be set to `EXAMPLE`).
* An [`onpipe`](http://guacamole.apache.org/doc/guacamole-common-js/Guacamole.Client.html#event:onpipe)
handler which handles inbound (server-to-client) pipe streams named identically
to the SVC. The inbound pipe stream will be received upon establishing the RDP
connection and is used to transmit any data sent along the SVC **from** within
the remote desktop session. For example:
```js
client.onpipe = function pipeReceived(stream, mimetype, name) {
// Receive output of SVC
if (name === 'EXAMPLE') {
// Log start of stream
var reader = new Guacamole.StringReader(stream);
console.log('pipe: %s: stream begins', name);
// Log each received blob of text
reader.ontext = function textReceived(text) {
console.log('pipe: %s: \"%s\"', name, text);
};
// Log end of stream
reader.onend = function streamEnded() {
console.log('pipe: %s: stream ends', name);
};
}
// All other inbound pipe streams are unsupported
else
stream.sendAck('Pipe stream not supported.',
Guacamole.Status.Code.UNSUPPORTED);
};
```
* Calls to [`createPipeStream()`](http://guacamole.apache.org/doc/guacamole-common-js/Guacamole.Client.html#createPipeStream)
as needed to establish outbound (client-to-server) pipe streams named
identically to the SVC. Outbound pipe streams with the same name as the SVC
will be automatically handled by the Guacamole server, with any received data
sent along the SVC **to** the remote desktop session. For example:
```js
var example = new Guacamole.StringWriter(client.createPipeStream('text/plain', 'EXAMPLE'));
example.sendText('This is a test.');
example.sendEnd();
```
These pipe streams may be created and destroyed as desired. As long as they
have the same name as the SVC, data sent along the pipe stream will be sent
along the SVC.
Example output
--------------
If the `static-channels` parameter is set to `EXAMPLE`, the successful creation
of the "EXAMPLE" channel should be logged by guacd when the connection is
established:
```
guacd[12057]: INFO: Created static channel "EXAMPLE"...
guacd[12057]: INFO: Static channel "EXAMPLE" connected.
```
On the client side, the `onpipe` handler should be invoked immediately. If
using the example code shown above, receipt of the pipe stream for the
"EXAMPLE" channel is logged:
```
pipe: EXAMPLE: stream begins
```
Running `svc-example.exe` within a command prompt inside the remote desktop
session, the application logs that the "EXAMPLE" channel has been successfully
opened:
```
Microsoft Windows [Version 10.0.17763.437]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Users\test>svc-example.exe
SVC "EXAMPLE" open. Reading...
```
Once `createPipeStream()` has been invoked on the Guacamole client side and
using the same name as the SVC (in this case, "EXAMPLE") guacd should log the
inbound half the channel is now connected:
```
guacd[12057]: DEBUG: Inbound half of channel "EXAMPLE" connected.
```
Sending the string `This is a test.` along the client-to-server pipe (as shown
in the example code above) results in `svc-example.exe` logging that it
received those 15 bytes and has resent the same 15 bytes back along the SVC:
```
Received 15 bytes.
Wrote 15 bytes.
```
The data sent from within the remote desktop session is received on the client
side via the server-to-client pipe stream. If using the example code shown
above, the received data is logged:
```
pipe: EXAMPLE: "This is a test."
```

View File

@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <windows.h>
#include <winbase.h>
#include <wtsapi32.h>
#include <stdio.h>
/**
* The name of the RDP static virtual channel (SVC).
*/
#define SVC_NAME "EXAMPLE"
int main() {
ULONG bytes_read;
ULONG bytes_written;
char message[4096];
/* Open SVC */
HANDLE svc = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, SVC_NAME, 0);
/* Fail if we cannot open an SVC at all */
if (svc == NULL) {
printf("Cannot open SVC \"" SVC_NAME "\"\n");
return 0;
}
printf("SVC \"" SVC_NAME "\" open. Reading...\n");
/* Continuously read from SVC */
while (WTSVirtualChannelRead(svc, INFINITE, message, sizeof(message), &bytes_read)) {
printf("Received %i bytes.\n", bytes_read);
/* Write all received data back to the SVC, possibly spreading the data
* across multiple writes */
char* current = message;
while (bytes_read > 0 && WTSVirtualChannelWrite(svc, current,
bytes_read, &bytes_written)) {
printf("Wrote %i bytes.\n", bytes_written);
bytes_read -= bytes_written;
}
}
/* Close SVC */
WTSVirtualChannelClose(svc);
printf("SVC \"" SVC_NAME "\" closed.\n");
return 1;
}

View File

@ -0,0 +1,222 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/json.h"
#include "download.h"
#include "fs.h"
#include "ls.h"
#include "rdp.h"
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#include <guacamole/string.h>
#include <guacamole/user.h>
#include <winpr/nt.h>
#include <winpr/shell.h>
#include <stdlib.h>
int guac_rdp_download_ack_handler(guac_user* user, guac_stream* stream,
char* message, guac_protocol_status status) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_download_status* download_status = (guac_rdp_download_status*) stream->data;
/* Get filesystem, return error if no filesystem */
guac_rdp_fs* fs = rdp_client->filesystem;
if (fs == NULL) {
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(user->socket);
return 0;
}
/* If successful, read data */
if (status == GUAC_PROTOCOL_STATUS_SUCCESS) {
/* Attempt read into buffer */
char buffer[4096];
int bytes_read = guac_rdp_fs_read(fs,
download_status->file_id,
download_status->offset, buffer, sizeof(buffer));
/* If bytes read, send as blob */
if (bytes_read > 0) {
download_status->offset += bytes_read;
guac_protocol_send_blob(user->socket, stream,
buffer, bytes_read);
}
/* If EOF, send end */
else if (bytes_read == 0) {
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
free(download_status);
}
/* Otherwise, fail stream */
else {
guac_user_log(user, GUAC_LOG_ERROR,
"Error reading file for download");
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
free(download_status);
}
guac_socket_flush(user->socket);
}
/* Otherwise, return stream to user */
else
guac_user_free_stream(user, stream);
return 0;
}
int guac_rdp_download_get_handler(guac_user* user, guac_object* object,
char* name) {
guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Get filesystem, ignore request if no filesystem */
guac_rdp_fs* fs = rdp_client->filesystem;
if (fs == NULL)
return 0;
/* Attempt to open file for reading */
int file_id = guac_rdp_fs_open(fs, name, GENERIC_READ, 0, FILE_OPEN, 0);
if (file_id < 0) {
guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"",
name);
return 0;
}
/* Get opened file */
guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
if (file == NULL) {
guac_client_log(fs->client, GUAC_LOG_DEBUG,
"%s: Successful open produced bad file_id: %i",
__func__, file_id);
return 0;
}
/* If directory, send contents of directory */
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) {
/* Create stream data */
guac_rdp_ls_status* ls_status = malloc(sizeof(guac_rdp_ls_status));
ls_status->fs = fs;
ls_status->file_id = file_id;
guac_strlcpy(ls_status->directory_name, name,
sizeof(ls_status->directory_name));
/* Allocate stream for body */
guac_stream* stream = guac_user_alloc_stream(user);
stream->ack_handler = guac_rdp_ls_ack_handler;
stream->data = ls_status;
/* Init JSON object state */
guac_common_json_begin_object(user, stream,
&ls_status->json_state);
/* Associate new stream with get request */
guac_protocol_send_body(user->socket, object, stream,
GUAC_USER_STREAM_INDEX_MIMETYPE, name);
}
/* Otherwise, send file contents */
else {
/* Create stream data */
guac_rdp_download_status* download_status = malloc(sizeof(guac_rdp_download_status));
download_status->file_id = file_id;
download_status->offset = 0;
/* Allocate stream for body */
guac_stream* stream = guac_user_alloc_stream(user);
stream->data = download_status;
stream->ack_handler = guac_rdp_download_ack_handler;
/* Associate new stream with get request */
guac_protocol_send_body(user->socket, object, stream,
"application/octet-stream", name);
}
guac_socket_flush(user->socket);
return 0;
}
void* guac_rdp_download_to_user(guac_user* user, void* data) {
/* Do not bother attempting the download if the user has left */
if (user == NULL)
return NULL;
guac_client* client = user->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,
FILE_READ_DATA, 0, FILE_OPEN, 0);
/* If file opened successfully, start stream */
if (file_id >= 0) {
/* Associate stream with transfer status */
guac_stream* stream = guac_user_alloc_stream(user);
guac_rdp_download_status* download_status = malloc(sizeof(guac_rdp_download_status));
stream->data = download_status;
stream->ack_handler = guac_rdp_download_ack_handler;
download_status->file_id = file_id;
download_status->offset = 0;
guac_user_log(user, GUAC_LOG_DEBUG, "%s: Initiating download "
"of \"%s\"", __func__, path);
/* Begin stream */
guac_protocol_send_file(user->socket, stream,
"application/octet-stream", guac_rdp_fs_basename(path));
guac_socket_flush(user->socket);
/* Download started successfully */
return stream;
}
/* Download failed */
guac_user_log(user, GUAC_LOG_ERROR, "Unable to download \"%s\"", path);
return NULL;
}

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_DOWNLOAD_H
#define GUAC_RDP_DOWNLOAD_H
#include "common/json.h"
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <stdint.h>
/**
* The transfer status of a file being downloaded.
*/
typedef struct guac_rdp_download_status {
/**
* The file ID of the file being downloaded.
*/
int file_id;
/**
* The current position within the file.
*/
uint64_t offset;
} guac_rdp_download_status;
/**
* Handler for acknowledgements of receipt of data related to file downloads.
*/
guac_user_ack_handler guac_rdp_download_ack_handler;
/**
* Handler for get messages. In context of downloads and the filesystem exposed
* via the Guacamole protocol, get messages request the body of a file within
* the filesystem.
*/
guac_user_get_handler guac_rdp_download_get_handler;
/**
* Callback for guac_client_for_user() and similar functions which initiates a
* file download to a specific user if that user is still connected. The path
* for the file to be downloaded must be passed as the arbitrary data parameter
* for the function invoking this callback.
*/
guac_user_callback guac_rdp_download_to_user;
#endif

View File

@ -1,183 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "common/list.h"
#include "dvc.h"
#include "rdp.h"
#include <freerdp/channels/channels.h>
#include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <assert.h>
#include <stdarg.h>
guac_rdp_dvc_list* guac_rdp_dvc_list_alloc() {
guac_rdp_dvc_list* list = malloc(sizeof(guac_rdp_dvc_list));
/* Initialize with empty backing list */
list->channels = guac_common_list_alloc();
list->channel_count = 0;
return list;
}
void guac_rdp_dvc_list_add(guac_rdp_dvc_list* list, const char* name, ...) {
va_list args;
guac_rdp_dvc* dvc = malloc(sizeof(guac_rdp_dvc));
va_start(args, name);
/* Count number of arguments (excluding terminating NULL) */
dvc->argc = 1;
while (va_arg(args, char*) != NULL)
dvc->argc++;
/* Reset va_list */
va_end(args);
va_start(args, name);
/* Copy argument values into DVC entry */
dvc->argv = malloc(sizeof(char*) * dvc->argc);
dvc->argv[0] = strdup(name);
int i;
for (i = 1; i < dvc->argc; i++)
dvc->argv[i] = strdup(va_arg(args, char*));
va_end(args);
/* Add entry to DVC list */
guac_common_list_add(list->channels, dvc);
/* Update channel count */
list->channel_count++;
}
void guac_rdp_dvc_list_free(guac_rdp_dvc_list* list) {
/* For each channel */
guac_common_list_element* current = list->channels->head;
while (current != NULL) {
/* Free arguments declaration for current channel */
guac_rdp_dvc* dvc = (guac_rdp_dvc*) current->data;
/* Free the underlying arguments list if not delegated to FreeRDP */
if (dvc->argv != NULL) {
/* Free each argument value */
for (int i = 0; i < dvc->argc; i++)
free(dvc->argv[i]);
free(dvc->argv);
}
free(dvc);
current = current->next;
}
/* Free underlying list */
guac_common_list_free(list->channels);
/* Free the DVC list itself */
free(list);
}
int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
rdpChannels* channels = context->channels;
/* Skip if no channels will be loaded */
if (list->channel_count == 0)
return 0;
#ifndef HAVE_ADDIN_ARGV
/* Allocate plugin data array */
RDP_PLUGIN_DATA* all_plugin_data =
calloc(list->channel_count + 1, sizeof(RDP_PLUGIN_DATA));
RDP_PLUGIN_DATA* current_plugin_data = all_plugin_data;
#endif
/* For each channel */
guac_common_list_element* current = list->channels->head;
while (current != NULL) {
/* Get channel arguments */
guac_rdp_dvc* dvc = (guac_rdp_dvc*) current->data;
current = current->next;
/* guac_rdp_dvc_list_add() guarantees at one argument */
assert(dvc->argc >= 1);
/* guac_rdp_load_drdynvc() MUST only be invoked once */
assert(dvc->argv != NULL);
/* Log registration of plugin for current channel */
guac_client_log(client, GUAC_LOG_DEBUG,
"Registering DVC plugin \"%s\"", dvc->argv[0]);
#ifdef HAVE_ADDIN_ARGV
/* Register plugin with FreeRDP */
ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV));
args->argc = dvc->argc;
args->argv = dvc->argv;
freerdp_dynamic_channel_collection_add(context->settings, args);
#else
/* Copy all arguments */
for (int i = 0; i < dvc->argc; i++)
current_plugin_data->data[i] = dvc->argv[i];
/* Store size of entry */
current_plugin_data->size = sizeof(*current_plugin_data);
/* Advance to next set of plugin data */
current_plugin_data++;
#endif
/* Rely on FreeRDP to free argv storage */
dvc->argv = NULL;
}
#ifdef HAVE_ADDIN_ARGV
/* Load virtual channel management plugin */
return freerdp_channels_load_plugin(channels, context->instance->settings,
"drdynvc", context->instance->settings);
#else
/* Terminate with empty RDP_PLUGIN_DATA element */
current_plugin_data->size = 0;
/* Load virtual channel management plugin */
return freerdp_channels_load_plugin(channels, context->instance->settings,
"drdynvc", all_plugin_data);
#endif
}

View File

@ -1,138 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_DVC_H
#define GUAC_RDP_DVC_H
#include "config.h"
#include "common/list.h"
#include <freerdp/freerdp.h>
/**
* The set of all arguments that should be passed to a given dynamic virtual
* channel plugin, including the name of that plugin.
*/
typedef struct guac_rdp_dvc {
/**
* The number of arguments in the argv array. This MUST be at least 1.
*/
int argc;
/**
* The argument values being passed to the dynamic virtual channel plugin.
* The first entry in this array is always the name of the plugin. If
* guac_rdp_load_drdynvc() has been invoked, and freeing the argument
* values is being delegated to FreeRDP, this will be NULL.
*/
char** argv;
} guac_rdp_dvc;
/**
* A list of dynamic virtual channels which should be provided to the DRDYNVC
* plugin once loaded via guac_rdp_load_drdynvc(). This interface exists purely
* to bridge incompatibilities between differing versions of FreeRDP and its
* DRDYNVC plugin. Any allocated guac_rdp_dvc_list is unlikely to be needed
* after the DRDYNVC plugin has been loaded.
*/
typedef struct guac_rdp_dvc_list {
/**
* Array of all dynamic virtual channels which should be registered with
* the DRDYNVC plugin once loaded. Each list element will point to a
* guac_rdp_dvc structure which must eventually be freed.
*/
guac_common_list* channels;
/**
* The number of channels within the list.
*/
int channel_count;
} guac_rdp_dvc_list;
/**
* Allocates a new, empty list of dynamic virtual channels. New channels may
* be added via guac_rdp_dvc_list_add(). The loading of those channels'
* associated plugins will be deferred until guac_rdp_load_drdynvc() is
* invoked.
*
* @return
* A newly-allocated, empty list of dynamic virtual channels.
*/
guac_rdp_dvc_list* guac_rdp_dvc_list_alloc();
/**
* Adds the given dynamic virtual channel plugin name and associated arguments
* to the list. The provied arguments list is NOT optional and MUST be
* NULL-terminated, even if there are no arguments for the named dynamic
* virtual channel plugin. Though FreeRDP requires that the arguments for a
* dynamic virtual channel plugin contain the name of the plugin itself as the
* first argument, the name must be excluded from the arguments provided here.
* This function will automatically take care of adding the plugin name to
* the arguments.
*
* @param list
* The guac_rdp_dvc_list to which the given plugin name and arguments
* should be added, for later bulk registration via
* guac_rdp_load_drdynvc().
*
* @param name
* The name of the dynamic virtual channel plugin that should be given
* the provided arguments when guac_rdp_load_drdynvc() is invoked.
*
* @param ...
* The string (char*) arguments which should be passed to the dynamic
* virtual channel plugin when it is loaded via guac_rdp_load_drdynvc(),
* excluding the plugin name itself.
*/
void guac_rdp_dvc_list_add(guac_rdp_dvc_list* list, const char* name, ...);
/**
* Frees the given list of dynamic virtual channels. Note that, while each
* individual entry within this list will be freed, it is partially up to
* FreeRDP to free the storage associated with the arguments passed to the
* virtual channels.
*
* @param list
* The list to free.
*/
void guac_rdp_dvc_list_free(guac_rdp_dvc_list* list);
/**
* Loads FreeRDP's DRDYNVC plugin and registers the dynamic virtual channel
* plugins described by the given guac_rdp_dvc_list. This function MUST be
* invoked no more than once per RDP connection. Invoking this function
* multiple times, even if the guac_rdp_dvc_list is different each time, will
* result in undefined behavior.
*
* @param context
* The rdpContext associated with the RDP connection for which the DRDYNVC
* plugin should be loaded.
*
* @param list
* A guac_rdp_dvc_list describing the dynamic virtual channel plugins that
* should be registered with the DRDYNVC plugin, along with any arguments.
*/
int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list);
#endif

View File

@ -17,8 +17,6 @@
* under the License. * under the License.
*/ */
#include "config.h"
#include "error.h" #include "error.h"
#include "rdp.h" #include "rdp.h"

View File

@ -17,11 +17,19 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "fs.h"
#include "download.h"
#include "upload.h"
#include "rdp_fs.h" #include <guacamole/client.h>
#include "rdp_status.h" #include <guacamole/object.h>
#include "rdp_stream.h" #include <guacamole/pool.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/string.h>
#include <guacamole/user.h>
#include <winpr/file.h>
#include <winpr/nt.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
@ -32,16 +40,8 @@
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/statvfs.h> #include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/pool.h>
#include <guacamole/socket.h>
#include <guacamole/string.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) {
@ -244,16 +244,16 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
} }
/* Translate access into flags */ /* Translate access into flags */
if (access & ACCESS_GENERIC_ALL) if (access & GENERIC_ALL)
flags = O_RDWR; flags = O_RDWR;
else if ((access & ( ACCESS_GENERIC_WRITE else if ((access & ( GENERIC_WRITE
| ACCESS_FILE_WRITE_DATA | FILE_WRITE_DATA
| ACCESS_FILE_APPEND_DATA)) | FILE_APPEND_DATA))
&& (access & (ACCESS_GENERIC_READ | ACCESS_FILE_READ_DATA))) && (access & (GENERIC_READ | FILE_READ_DATA)))
flags = O_RDWR; flags = O_RDWR;
else if (access & ( ACCESS_GENERIC_WRITE else if (access & ( GENERIC_WRITE
| ACCESS_FILE_WRITE_DATA | FILE_WRITE_DATA
| ACCESS_FILE_APPEND_DATA)) | FILE_APPEND_DATA))
flags = O_WRONLY; flags = O_WRONLY;
else else
flags = O_RDONLY; flags = O_RDONLY;
@ -279,32 +279,32 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
switch (create_disposition) { switch (create_disposition) {
/* Create if not exist, fail otherwise */ /* Create if not exist, fail otherwise */
case DISP_FILE_CREATE: case FILE_CREATE:
flags |= O_CREAT | O_EXCL; flags |= O_CREAT | O_EXCL;
break; break;
/* Open file if exists and do not overwrite, fail otherwise */ /* Open file if exists and do not overwrite, fail otherwise */
case DISP_FILE_OPEN: case FILE_OPEN:
/* No flag necessary - default functionality of open */ /* No flag necessary - default functionality of open */
break; break;
/* Open if exists, create otherwise */ /* Open if exists, create otherwise */
case DISP_FILE_OPEN_IF: case FILE_OPEN_IF:
flags |= O_CREAT; flags |= O_CREAT;
break; break;
/* Overwrite if exists, fail otherwise */ /* Overwrite if exists, fail otherwise */
case DISP_FILE_OVERWRITE: case FILE_OVERWRITE:
flags |= O_TRUNC; flags |= O_TRUNC;
break; break;
/* Overwrite if exists, create otherwise */ /* Overwrite if exists, create otherwise */
case DISP_FILE_OVERWRITE_IF: case FILE_OVERWRITE_IF:
flags |= O_CREAT | O_TRUNC; flags |= O_CREAT | O_TRUNC;
break; break;
/* Supersede (replace) if exists, otherwise create */ /* Supersede (replace) if exists, otherwise create */
case DISP_FILE_SUPERSEDE: case FILE_SUPERSEDE:
unlink(real_path); unlink(real_path);
flags |= O_CREAT | O_TRUNC; flags |= O_CREAT | O_TRUNC;
break; break;
@ -605,6 +605,21 @@ const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id) {
} }
const char* guac_rdp_fs_basename(const char* path) {
for (const char* c = path; *c != '\0'; c++) {
/* Reset beginning of path if a path separator is found */
if (*c == '/' || *c == '\\')
path = c + 1;
}
/* path now points to the first character after the last path separator */
return path;
}
int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { int guac_rdp_fs_normalize_path(const char* path, char* abs_path) {
int path_depth = 0; int path_depth = 0;

View File

@ -17,9 +17,8 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_FS_H
#ifndef __GUAC_RDP_FS_H #define GUAC_RDP_FS_H
#define __GUAC_RDP_FS_H
/** /**
* Functions and macros specific to filesystem handling and initialization * Functions and macros specific to filesystem handling and initialization
@ -29,13 +28,13 @@
* system calls and structures, using the guac_rdp_fs structure as a home * system calls and structures, using the guac_rdp_fs structure as a home
* for common data. * for common data.
* *
* @file rdp_fs.h * @file fs.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 <guacamole/user.h>
#include <dirent.h> #include <dirent.h>
#include <stdint.h> #include <stdint.h>
@ -109,73 +108,6 @@
*/ */
#define GUAC_RDP_FS_ENOTSUP -10 #define GUAC_RDP_FS_ENOTSUP -10
/*
* Access constants.
*/
#define ACCESS_GENERIC_READ 0x80000000
#define ACCESS_GENERIC_WRITE 0x40000000
#define ACCESS_GENERIC_ALL 0x10000000
#define ACCESS_FILE_READ_DATA 0x00000001
#define ACCESS_FILE_WRITE_DATA 0x00000002
#define ACCESS_FILE_APPEND_DATA 0x00000004
#define ACCESS_DELETE 0x00010000
/*
* Create disposition constants.
*/
#define DISP_FILE_SUPERSEDE 0x00000000
#define DISP_FILE_OPEN 0x00000001
#define DISP_FILE_CREATE 0x00000002
#define DISP_FILE_OPEN_IF 0x00000003
#define DISP_FILE_OVERWRITE 0x00000004
#define DISP_FILE_OVERWRITE_IF 0x00000005
/*
* Information constants.
* FreeRDP 1.1+ already defines those constants
*/
#ifdef LEGACY_FREERDP
#define FILE_SUPERSEDED 0x00000000
#define FILE_OPENED 0x00000001
#define FILE_OVERWRITTEN 0x00000003
#endif
/*
* File attributes.
*/
#define FILE_ATTRIBUTE_READONLY 0x00000001
#define FILE_ATTRIBUTE_HIDDEN 0x00000002
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
#define FILE_ATTRIBUTE_ARCHIVE 0x00000020
#define FILE_ATTRIBUTE_NORMAL 0x00000080
/*
* Filesystem attributes.
*/
#define FILE_CASE_SENSITIVE_SEARCH 0x00000001
#define FILE_CASE_PRESERVED_NAMES 0x00000002
#define FILE_UNICODE_ON_DISK 0x00000004
/*
* File create options.
*/
#define FILE_DIRECTORY_FILE 0x00000001
#define FILE_NON_DIRECTORY_FILE 0x00000040
/*
* File device types.
*/
#define FILE_DEVICE_DISK 0x00000007
#define SEC_TO_UNIX_EPOCH 11644473600
/** /**
* Converts a Windows timestamp (100 nanosecond intervals since Jan 1, 1601 * Converts a Windows timestamp (100 nanosecond intervals since Jan 1, 1601
* UTC) to UNIX timestamp (seconds since Jan 1, 1970 UTC). * UTC) to UNIX timestamp (seconds since Jan 1, 1970 UTC).
@ -438,10 +370,10 @@ int guac_rdp_fs_get_status(int err);
* The absolute path to the file within the simulated filesystem. * The absolute path to the file within the simulated filesystem.
* *
* @param access * @param access
* A bitwise-OR of various RDPDR access flags, such as ACCESS_GENERIC_ALL * A bitwise-OR of various RDPDR access flags, such as GENERIC_ALL or
* or ACCESS_GENERIC_WRITE. This value will ultimately be translated to a * GENERIC_WRITE. This value will ultimately be translated to a standard
* standard O_RDWR, O_WRONLY, etc. value when opening the real file on the * O_RDWR, O_WRONLY, etc. value when opening the real file on the local
* local filesystem. * filesystem.
* *
* @param file_attributes * @param file_attributes
* The attributes to apply to the file, if created. This parameter is * The attributes to apply to the file, if created. This parameter is
@ -449,9 +381,9 @@ int guac_rdp_fs_get_status(int err);
* *
* @param create_disposition * @param create_disposition
* Any one of several RDPDR file creation dispositions, such as * Any one of several RDPDR file creation dispositions, such as
* DISP_FILE_CREATE, DISP_FILE_OPEN_IF, etc. The creation disposition * FILE_CREATE, FILE_OPEN_IF, etc. The creation disposition dictates
* dictates whether a new file should be created, whether the file can * whether a new file should be created, whether the file can already
* already exist, whether existing contents should be truncated, etc. * exist, whether existing contents should be truncated, etc.
* *
* @param create_options * @param create_options
* A bitwise-OR of various RDPDR options dictating how a file is to be * A bitwise-OR of various RDPDR options dictating how a file is to be
@ -592,6 +524,20 @@ int guac_rdp_fs_truncate(guac_rdp_fs* fs, int file_id, int length);
*/ */
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, returns a pointer to the first character following
* the last path separator in the path (the basename of the path). For example,
* given "/foo/bar/baz" or "\foo\bar\baz", this function would return a pointer
* to "baz".
*
* @param path
* The path to determine the basename of.
*
* @return
* A pointer to the first character of the basename within the path.
*/
const char* guac_rdp_fs_basename(const char* path);
/** /**
* 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 ".". The given path MUST * absolute path which does NOT contain ".." or ".". The given path MUST

View File

@ -17,26 +17,21 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "bitmap.h"
#include "common/display.h"
#include "client.h"
#include "common/surface.h" #include "common/surface.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_bitmap.h" #include "settings.h"
#include "rdp_color.h"
#include "rdp_settings.h"
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/graphics.h>
#include <freerdp/primary.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-wtypes.h"
#endif
#include <stddef.h>
#include <stddef.h> #include <stddef.h>
guac_transfer_function guac_rdp_rop3_transfer_function(guac_client* client, guac_transfer_function guac_rdp_rop3_transfer_function(guac_client* client,
@ -97,7 +92,7 @@ guac_transfer_function guac_rdp_rop3_transfer_function(guac_client* client,
} }
void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt) { BOOL guac_rdp_gdi_dstblt(rdpContext* context, const DSTBLT_ORDER* dstblt) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface; guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface;
@ -140,77 +135,11 @@ void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt) {
} }
} return TRUE;
void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) {
/*
* Note that this is not a full implementation of PATBLT. This is a
* fallback implementation which only renders a solid block of background
* color using the specified ROP3 operation, ignoring whatever brush
* was actually specified.
*
* As libguac-client-rdp explicitly tells the server not to send PATBLT,
* well-behaved RDP servers will not use this operation at all, while
* others will at least have a fallback.
*/
/* Get client and current layer */
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_common_surface* current_surface =
((guac_rdp_client*) client->data)->current_surface;
int x = patblt->nLeftRect;
int y = patblt->nTopRect;
int w = patblt->nWidth;
int h = patblt->nHeight;
/*
* Warn that rendering is a fallback, as the server should not be sending
* this order.
*/
guac_client_log(client, GUAC_LOG_INFO, "Using fallback PATBLT (server is ignoring "
"negotiated client capabilities)");
/* Render rectangle based on ROP */
switch (patblt->bRop) {
/* If blackness, send black rectangle */
case 0x00:
guac_common_surface_set(current_surface, x, y, w, h,
0x00, 0x00, 0x00, 0xFF);
break;
/* If NOP, do nothing */
case 0xAA:
break;
/* If operation is just a copy, send foreground only */
case 0xCC:
case 0xF0:
guac_common_surface_set(current_surface, x, y, w, h,
(patblt->foreColor >> 16) & 0xFF,
(patblt->foreColor >> 8 ) & 0xFF,
(patblt->foreColor ) & 0xFF,
0xFF);
break;
/* If whiteness, send white rectangle */
case 0xFF:
guac_common_surface_set(current_surface, x, y, w, h,
0xFF, 0xFF, 0xFF, 0xFF);
break;
/* Otherwise, invert entire rect */
default:
guac_common_surface_transfer(current_surface, x, y, w, h,
GUAC_TRANSFER_BINARY_NDEST, current_surface, x, y);
}
} }
void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt) { BOOL guac_rdp_gdi_scrblt(rdpContext* context, const SCRBLT_ORDER* scrblt) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface; guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface;
@ -229,9 +158,11 @@ void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt) {
guac_common_surface_copy(rdp_client->display->default_surface, guac_common_surface_copy(rdp_client->display->default_surface,
x_src, y_src, w, h, current_surface, x, y); x_src, y_src, w, h, current_surface, x, y);
return TRUE;
} }
void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { BOOL 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 = ((guac_rdp_client*) client->data)->current_surface; guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface;
@ -248,7 +179,7 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
/* Make sure that the recieved bitmap is not NULL before processing */ /* Make sure that the recieved bitmap is not NULL before processing */
if (bitmap == NULL) { if (bitmap == NULL) {
guac_client_log(client, GUAC_LOG_INFO, "NULL bitmap found in memblt instruction."); guac_client_log(client, GUAC_LOG_INFO, "NULL bitmap found in memblt instruction.");
return; return TRUE;
} }
switch (memblt->bRop) { switch (memblt->bRop) {
@ -321,98 +252,11 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) {
} }
} return TRUE;
void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect) {
/* Get client data */
guac_client* client = ((rdp_freerdp_context*) context)->client;
UINT32 color = guac_rdp_convert_color(context, opaque_rect->color);
guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface;
int x = opaque_rect->nLeftRect;
int y = opaque_rect->nTopRect;
int w = opaque_rect->nWidth;
int h = opaque_rect->nHeight;
guac_common_surface_set(current_surface, x, y, w, h,
(color >> 16) & 0xFF,
(color >> 8 ) & 0xFF,
(color ) & 0xFF,
0xFF);
} }
/** BOOL guac_rdp_gdi_set_bounds(rdpContext* context, const rdpBounds* bounds) {
* Updates the palette within a FreeRDP CLRCONV object using the new palette
* entries provided by an RDP palette update.
*
* @param clrconv
* The FreeRDP CLRCONV object to update.
*
* @param palette
* An RDP palette update message containing the palette to store within the
* given CLRCONV object.
*/
static void guac_rdp_update_clrconv(CLRCONV* clrconv,
PALETTE_UPDATE* palette) {
clrconv->palette->count = palette->number;
#ifdef LEGACY_RDPPALETTE
clrconv->palette->entries = palette->entries;
#else
memcpy(clrconv->palette->entries, palette->entries,
sizeof(palette->entries));
#endif
}
/**
* Updates a raw ARGB32 palette using the new palette entries provided by an
* RDP palette update.
*
* @param guac_palette
* An array of 256 ARGB32 colors, with each entry corresponding to an
* entry in the color palette.
*
* @param palette
* An RDP palette update message containing the palette to store within the
* given array of ARGB32 colors.
*/
static void guac_rdp_update_palette(UINT32* guac_palette,
PALETTE_UPDATE* palette) {
PALETTE_ENTRY* entry = palette->entries;
int i;
/* Copy each palette entry as ARGB32 */
for (i=0; i < palette->number; i++) {
*guac_palette = 0xFF000000
| (entry->red << 16)
| (entry->green << 8)
| entry->blue;
guac_palette++;
entry++;
}
}
void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette) {
CLRCONV* clrconv = ((rdp_freerdp_context*) context)->clrconv;
UINT32* guac_palette = ((rdp_freerdp_context*) context)->palette;
/* Update internal palette representations */
guac_rdp_update_clrconv(clrconv, palette);
guac_rdp_update_palette(guac_palette, palette);
}
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;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -428,13 +272,16 @@ void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds) {
bounds->right - bounds->left + 1, bounds->right - bounds->left + 1,
bounds->bottom - bounds->top + 1); bounds->bottom - bounds->top + 1);
return TRUE;
} }
void guac_rdp_gdi_end_paint(rdpContext* context) { BOOL guac_rdp_gdi_end_paint(rdpContext* context) {
/* IGNORE */ /* IGNORE */
return TRUE;
} }
void guac_rdp_gdi_desktop_resize(rdpContext* context) { BOOL guac_rdp_gdi_desktop_resize(rdpContext* context) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -449,6 +296,8 @@ void guac_rdp_gdi_desktop_resize(rdpContext* context) {
guac_rdp_get_width(context->instance), guac_rdp_get_width(context->instance),
guac_rdp_get_height(context->instance)); guac_rdp_get_height(context->instance));
return TRUE;
} }

View File

@ -17,9 +17,8 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_GDI_H
#ifndef _GUAC_RDP_RDP_GDI_H #define GUAC_RDP_GDI_H
#define _GUAC_RDP_RDP_GDI_H
#include "config.h" #include "config.h"
@ -46,71 +45,58 @@ guac_composite_mode guac_rdp_rop3_transfer_function(guac_client* client,
int rop3); int rop3);
/** /**
* Handler for RDP DSTBLT update. * Handler for the DstBlt Primary Drawing Order. A DstBlt Primary Drawing Order
* paints a rectangle of image data using a raster operation which considers
* the destination only. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegdi/87ea30df-59d6-438e-a735-83f0225fbf91
* *
* @param context * @param context
* The rdpContext associated with the current RDP session. * The rdpContext associated with the current RDP session.
* *
* @param dstblt * @param dstblt
* The DSTBLT update to handle. * The DSTBLT update to handle.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt); BOOL guac_rdp_gdi_dstblt(rdpContext* context, const DSTBLT_ORDER* dstblt);
/** /**
* Handler for RDP PATBLT update. * Handler for the ScrBlt Primary Drawing Order. A ScrBlt Primary Drawing Order
* paints a rectangle of image data using a raster operation which considers
* the source and destination. See:
* *
* @param context * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegdi/a4e322b0-cd64-4dfc-8e1a-f24dc0edc99d
* The rdpContext associated with the current RDP session.
*
* @param patblt
* The PATBLT update to handle.
*/
void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt);
/**
* Handler for RDP SCRBLT update.
* *
* @param context * @param context
* The rdpContext associated with the current RDP session. * The rdpContext associated with the current RDP session.
* *
* @param scrblt * @param scrblt
* The SCRBLT update to handle. * The SCRBLT update to handle.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt); BOOL guac_rdp_gdi_scrblt(rdpContext* context, const SCRBLT_ORDER* scrblt);
/** /**
* Handler for RDP MEMBLT update. * Handler for the MemBlt Primary Drawing Order. A MemBlt Primary Drawing Order
* paints a rectangle of cached image data from a cached surface to the screen
* using a raster operation which considers the source and destination. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegdi/84c2ec2f-f776-405b-9b48-6894a28b1b14
* *
* @param context * @param context
* The rdpContext associated with the current RDP session. * The rdpContext associated with the current RDP session.
* *
* @param memblt * @param memblt
* The MEMBLT update to handle. * The MEMBLT update to handle.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt); BOOL guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt);
/**
* 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);
/**
* Handler called when the remote color palette is changing.
*
* @param context
* The rdpContext associated with the current RDP session.
*
* @param palette
* The PALETTE update containing the new palette.
*/
void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette);
/** /**
* Handler called prior to calling the handlers for specific updates when * Handler called prior to calling the handlers for specific updates when
@ -124,8 +110,11 @@ void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette);
* @param bounds * @param bounds
* The clipping rectangle to set, or NULL to remove any applied clipping * The clipping rectangle to set, or NULL to remove any applied clipping
* rectangle. * rectangle.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds); BOOL guac_rdp_gdi_set_bounds(rdpContext* context, const 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
@ -133,8 +122,11 @@ void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds);
* *
* @param context * @param context
* The rdpContext associated with the current RDP session. * The rdpContext associated with the current RDP session.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_gdi_end_paint(rdpContext* context); BOOL guac_rdp_gdi_end_paint(rdpContext* context);
/** /**
* Handler called when the desktop dimensions change, either from a * Handler called when the desktop dimensions change, either from a
@ -147,7 +139,10 @@ void guac_rdp_gdi_end_paint(rdpContext* context);
* *
* @param context * @param context
* The rdpContext associated with the current RDP session. * The rdpContext associated with the current RDP session.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_gdi_desktop_resize(rdpContext* context); BOOL guac_rdp_gdi_desktop_resize(rdpContext* context);
#endif #endif

View File

@ -17,25 +17,16 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "color.h"
#include "client.h"
#include "common/surface.h" #include "common/surface.h"
#include "config.h"
#include "glyph.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_color.h"
#include "rdp_glyph.h"
#include "rdp_settings.h"
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-wtypes.h"
#endif
#include <pthread.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@ -44,7 +35,7 @@
#define cairo_format_stride_for_width(format, width) (width*4) #define cairo_format_stride_for_width(format, width) (width*4)
#endif #endif
void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph) { BOOL guac_rdp_glyph_new(rdpContext* context, const rdpGlyph* glyph) {
int x, y, i; int x, y, i;
int stride; int stride;
@ -95,9 +86,15 @@ void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph) {
((guac_rdp_glyph*) glyph)->surface = cairo_image_surface_create_for_data( ((guac_rdp_glyph*) glyph)->surface = cairo_image_surface_create_for_data(
image_buffer, CAIRO_FORMAT_ARGB32, width, height, stride); image_buffer, CAIRO_FORMAT_ARGB32, width, height, stride);
return TRUE;
} }
void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y) { BOOL guac_rdp_glyph_draw(rdpContext* context, const rdpGlyph* glyph,
GLYPH_CALLBACK_INT32 x, GLYPH_CALLBACK_INT32 y,
GLYPH_CALLBACK_INT32 w, GLYPH_CALLBACK_INT32 h,
GLYPH_CALLBACK_INT32 sx, GLYPH_CALLBACK_INT32 sy,
BOOL redundant) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -110,6 +107,8 @@ void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y) {
(fgcolor & 0x00FF00) >> 8, (fgcolor & 0x00FF00) >> 8,
fgcolor & 0x0000FF); fgcolor & 0x0000FF);
return TRUE;
} }
void guac_rdp_glyph_free(rdpContext* context, rdpGlyph* glyph) { void guac_rdp_glyph_free(rdpContext* context, rdpGlyph* glyph) {
@ -121,17 +120,26 @@ void guac_rdp_glyph_free(rdpContext* context, rdpGlyph* glyph) {
cairo_surface_destroy(((guac_rdp_glyph*) glyph)->surface); cairo_surface_destroy(((guac_rdp_glyph*) glyph)->surface);
free(image_buffer); free(image_buffer);
/* NOTE: FreeRDP-allocated memory for the rdpGlyph will NOT be
* automatically released after this free handler is invoked, thus we must
* do so manually here */
free(glyph->aj);
free(glyph);
} }
void guac_rdp_glyph_begindraw(rdpContext* context, BOOL guac_rdp_glyph_begindraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor) { GLYPH_CALLBACK_INT32 x, GLYPH_CALLBACK_INT32 y,
GLYPH_CALLBACK_INT32 width, GLYPH_CALLBACK_INT32 height,
UINT32 fgcolor, UINT32 bgcolor, BOOL redundant) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = guac_rdp_client* rdp_client =
(guac_rdp_client*) 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 && !redundant) {
/* Convert background color */ /* Convert background color */
bgcolor = guac_rdp_convert_color(context, bgcolor); bgcolor = guac_rdp_convert_color(context, bgcolor);
@ -148,10 +156,15 @@ void guac_rdp_glyph_begindraw(rdpContext* context,
/* Convert foreground color */ /* Convert foreground color */
rdp_client->glyph_color = guac_rdp_convert_color(context, fgcolor); rdp_client->glyph_color = guac_rdp_convert_color(context, fgcolor);
return TRUE;
} }
void guac_rdp_glyph_enddraw(rdpContext* context, BOOL guac_rdp_glyph_enddraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor) { GLYPH_CALLBACK_INT32 x, GLYPH_CALLBACK_INT32 y,
GLYPH_CALLBACK_INT32 width, GLYPH_CALLBACK_INT32 height,
UINT32 fgcolor, UINT32 bgcolor) {
/* IGNORE */ /* IGNORE */
return TRUE;
} }

View File

@ -17,19 +17,28 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_GLYPH_H
#ifndef _GUAC_RDP_RDP_GLYPH_H #define GUAC_RDP_GLYPH_H
#define _GUAC_RDP_RDP_GLYPH_H
#include "config.h" #include "config.h"
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/graphics.h>
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#ifdef FREERDP_GLYPH_CALLBACKS_ACCEPT_INT32
/**
* FreeRDP 2.0.0-rc4 and newer requires INT32 for all integer arguments of
* glyph callbacks.
*/
#define GLYPH_CALLBACK_INT32 INT32
#else #else
#include "compat/winpr-wtypes.h" /**
* FreeRDP 2.0.0-rc3 and older requires UINT32 for all integer arguments of
* glyph callbacks.
*/
#define GLYPH_CALLBACK_INT32 UINT32
#endif #endif
/** /**
@ -58,8 +67,11 @@ typedef struct guac_rdp_glyph {
* *
* @param glyph * @param glyph
* The glyph to cache. * The glyph to cache.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph); BOOL guac_rdp_glyph_new(rdpContext* context, const rdpGlyph* glyph);
/** /**
* Draws a previously-cached glyph at the given coordinates within the current * Draws a previously-cached glyph at the given coordinates within the current
@ -76,8 +88,32 @@ void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph);
* *
* @param y * @param y
* The destination Y coordinate of the upper-left corner of the glyph. * The destination Y coordinate of the upper-left corner of the glyph.
*
* @param w
* The width of the glyph being drawn.
*
* @param h
* The height of the glyph being drawn.
*
* @param sx
* The X coordinare of the upper-left corner of the glyph within the source
* cache surface containing the glyph.
*
* @param sy
* The Y coordinare of the upper-left corner of the glyph within the source
* cache surface containing the glyph.
*
* @param redundant
* Whether the background rectangle specified is redundant (transparent).
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y); BOOL guac_rdp_glyph_draw(rdpContext* context, const rdpGlyph* glyph,
GLYPH_CALLBACK_INT32 x, GLYPH_CALLBACK_INT32 y,
GLYPH_CALLBACK_INT32 w, GLYPH_CALLBACK_INT32 h,
GLYPH_CALLBACK_INT32 sx, GLYPH_CALLBACK_INT32 sy,
BOOL redundant);
/** /**
* Frees any Guacamole-specific data associated with the given glyph, such that * Frees any Guacamole-specific data associated with the given glyph, such that
@ -125,9 +161,17 @@ void guac_rdp_glyph_free(rdpContext* context, rdpGlyph* glyph);
* colorspace of the RDP session, and may even be a palette index, and must * 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 * be translated via guac_rdp_convert_color(). If the background is
* transparent, this value is undefined. * transparent, this value is undefined.
*
* @param redundant
* Whether the background rectangle specified is redundant (transparent).
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_glyph_begindraw(rdpContext* context, BOOL guac_rdp_glyph_begindraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor); GLYPH_CALLBACK_INT32 x, GLYPH_CALLBACK_INT32 y,
GLYPH_CALLBACK_INT32 width, GLYPH_CALLBACK_INT32 height,
UINT32 fgcolor, UINT32 bgcolor, BOOL redundant);
/** /**
* Called immediately after rendering a series of glyphs. Unlike * Called immediately after rendering a series of glyphs. Unlike
@ -162,8 +206,13 @@ void guac_rdp_glyph_begindraw(rdpContext* context,
* colorspace of the RDP session, and may even be a palette index, and must * 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 * be translated via guac_rdp_convert_color(). If the background is
* transparent, this value is undefined. * transparent, this value is undefined.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_glyph_enddraw(rdpContext* context, BOOL guac_rdp_glyph_enddraw(rdpContext* context,
int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor); GLYPH_CALLBACK_INT32 x, GLYPH_CALLBACK_INT32 y,
GLYPH_CALLBACK_INT32 width, GLYPH_CALLBACK_INT32 height,
UINT32 fgcolor, UINT32 bgcolor);
#endif #endif

View File

@ -1,286 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "rdp.h"
#include "rdpdr_messages.h"
#include "rdpdr_service.h"
#include "unicode.h"
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#include <guacamole/unicode.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
#include <stdlib.h>
#include <string.h>
static void guac_rdpdr_send_client_announce_reply(guac_rdpdrPlugin* rdpdr,
unsigned int major, unsigned int minor, unsigned int client_id) {
wStream* output_stream = Stream_New(NULL, 12);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENTID_CONFIRM);
/* Write content */
Stream_Write_UINT16(output_stream, major);
Stream_Write_UINT16(output_stream, minor);
Stream_Write_UINT32(output_stream, client_id);
svc_plugin_send((rdpSvcPlugin*) rdpdr, output_stream);
}
static void guac_rdpdr_send_client_name_request(guac_rdpdrPlugin* rdpdr, const char* name) {
int name_bytes = strlen(name) + 1;
wStream* output_stream = Stream_New(NULL, 16 + name_bytes);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_NAME);
/* Write content */
Stream_Write_UINT32(output_stream, 0); /* ASCII */
Stream_Write_UINT32(output_stream, 0); /* 0 required by RDPDR spec */
Stream_Write_UINT32(output_stream, name_bytes);
Stream_Write(output_stream, name, name_bytes);
svc_plugin_send((rdpSvcPlugin*) rdpdr, output_stream);
}
static void guac_rdpdr_send_client_capability(guac_rdpdrPlugin* rdpdr) {
wStream* output_stream = Stream_New(NULL, 256);
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Sending capabilities...");
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_CAPABILITY);
/* Capability count + padding */
Stream_Write_UINT16(output_stream, 3);
Stream_Write_UINT16(output_stream, 0); /* Padding */
/* General capability header */
Stream_Write_UINT16(output_stream, CAP_GENERAL_TYPE);
Stream_Write_UINT16(output_stream, 44);
Stream_Write_UINT32(output_stream, GENERAL_CAPABILITY_VERSION_02);
/* General capability data */
Stream_Write_UINT32(output_stream, GUAC_OS_TYPE); /* osType - required to be ignored */
Stream_Write_UINT32(output_stream, 0); /* osVersion */
Stream_Write_UINT16(output_stream, RDP_CLIENT_MAJOR_ALL); /* protocolMajor */
Stream_Write_UINT16(output_stream, RDP_CLIENT_MINOR_5_2); /* protocolMinor */
Stream_Write_UINT32(output_stream, 0xFFFF); /* ioCode1 */
Stream_Write_UINT32(output_stream, 0); /* ioCode2 */
Stream_Write_UINT32(output_stream,
RDPDR_DEVICE_REMOVE_PDUS
| RDPDR_CLIENT_DISPLAY_NAME
| RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */
Stream_Write_UINT32(output_stream, 0); /* extraFlags1 */
Stream_Write_UINT32(output_stream, 0); /* extraFlags2 */
Stream_Write_UINT32(output_stream, 0); /* SpecialTypeDeviceCap */
/* Printer support header */
Stream_Write_UINT16(output_stream, CAP_PRINTER_TYPE);
Stream_Write_UINT16(output_stream, 8);
Stream_Write_UINT32(output_stream, PRINT_CAPABILITY_VERSION_01);
/* Drive support header */
Stream_Write_UINT16(output_stream, CAP_DRIVE_TYPE);
Stream_Write_UINT16(output_stream, 8);
Stream_Write_UINT32(output_stream, DRIVE_CAPABILITY_VERSION_02);
svc_plugin_send((rdpSvcPlugin*) rdpdr, output_stream);
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Capabilities sent.");
}
static void guac_rdpdr_send_client_device_list_announce_request(guac_rdpdrPlugin* rdpdr) {
/* Calculate number of bytes needed for the stream */
int streamBytes = 16;
for (int i=0; i < rdpdr->devices_registered; i++)
streamBytes += rdpdr->devices[i].device_announce_len;
/* Allocate the stream */
wStream* output_stream = Stream_New(NULL, streamBytes);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_DEVICELIST_ANNOUNCE);
/* Get the stream for each of the devices. */
Stream_Write_UINT32(output_stream, rdpdr->devices_registered);
for (int i=0; i<rdpdr->devices_registered; i++) {
Stream_Write(output_stream,
Stream_Buffer(rdpdr->devices[i].device_announce),
rdpdr->devices[i].device_announce_len);
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Registered device %i (%s)",
rdpdr->devices[i].device_id, rdpdr->devices[i].device_name);
}
svc_plugin_send((rdpSvcPlugin*) rdpdr, output_stream);
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "All supported devices sent.");
}
void guac_rdpdr_process_server_announce(guac_rdpdrPlugin* rdpdr,
wStream* input_stream) {
unsigned int major, minor, client_id;
Stream_Read_UINT16(input_stream, major);
Stream_Read_UINT16(input_stream, minor);
Stream_Read_UINT32(input_stream, client_id);
/* Must choose own client ID if minor not >= 12 */
if (minor < 12)
client_id = random() & 0xFFFF;
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Connected to RDPDR %u.%u as client 0x%04x", major, minor, client_id);
/* Respond to announce */
guac_rdpdr_send_client_announce_reply(rdpdr, major, minor, client_id);
/* Name request */
guac_rdpdr_send_client_name_request(rdpdr, ((guac_rdp_client *)rdpdr->client->data)->settings->client_name);
}
void guac_rdpdr_process_clientid_confirm(guac_rdpdrPlugin* rdpdr, wStream* input_stream) {
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Client ID confirmed");
}
void guac_rdpdr_process_device_reply(guac_rdpdrPlugin* rdpdr, wStream* input_stream) {
unsigned int device_id, ntstatus;
int severity, c, n, facility, code;
Stream_Read_UINT32(input_stream, device_id);
Stream_Read_UINT32(input_stream, ntstatus);
severity = (ntstatus & 0xC0000000) >> 30;
c = (ntstatus & 0x20000000) >> 29;
n = (ntstatus & 0x10000000) >> 28;
facility = (ntstatus & 0x0FFF0000) >> 16;
code = ntstatus & 0x0000FFFF;
/* Log error / information */
if (device_id < rdpdr->devices_registered) {
if (severity == 0x0)
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Device %i (%s) connected successfully",
device_id, rdpdr->devices[device_id].device_name);
else
guac_client_log(rdpdr->client, GUAC_LOG_ERROR, "Problem connecting device %i (%s): "
"severity=0x%x, c=0x%x, n=0x%x, facility=0x%x, code=0x%x",
device_id, rdpdr->devices[device_id].device_name,
severity, c, n, facility, code);
}
else
guac_client_log(rdpdr->client, GUAC_LOG_ERROR, "Unknown device ID: 0x%08x", device_id);
}
void guac_rdpdr_process_device_iorequest(guac_rdpdrPlugin* rdpdr, wStream* input_stream) {
int device_id, file_id, completion_id, major_func, minor_func;
/* Read header */
Stream_Read_UINT32(input_stream, device_id);
Stream_Read_UINT32(input_stream, file_id);
Stream_Read_UINT32(input_stream, completion_id);
Stream_Read_UINT32(input_stream, major_func);
Stream_Read_UINT32(input_stream, minor_func);
/* If printer, run printer handlers */
if (device_id >= 0 && device_id < rdpdr->devices_registered) {
/* Call handler on device */
guac_rdpdr_device* device = &(rdpdr->devices[device_id]);
device->iorequest_handler(device, input_stream,
file_id, completion_id, major_func, minor_func);
}
else
guac_client_log(rdpdr->client, GUAC_LOG_ERROR, "Unknown device ID: 0x%08x", device_id);
}
void guac_rdpdr_process_server_capability(guac_rdpdrPlugin* rdpdr, wStream* input_stream) {
int count;
int i;
/* Read header */
Stream_Read_UINT16(input_stream, count);
Stream_Seek(input_stream, 2);
/* Parse capabilities */
for (i=0; i<count; i++) {
int type;
int length;
Stream_Read_UINT16(input_stream, type);
Stream_Read_UINT16(input_stream, length);
/* Ignore all for now */
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Ignoring server capability set type=0x%04x, length=%i", type, length);
Stream_Seek(input_stream, length - 4);
}
/* Send own capabilities */
guac_rdpdr_send_client_capability(rdpdr);
}
void guac_rdpdr_process_user_loggedon(guac_rdpdrPlugin* rdpdr, wStream* input_stream) {
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "User logged on");
guac_rdpdr_send_client_device_list_announce_request(rdpdr);
}
void guac_rdpdr_process_prn_cache_data(guac_rdpdrPlugin* rdpdr, wStream* input_stream) {
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Ignoring printer cached configuration data");
}
void guac_rdpdr_process_prn_using_xps(guac_rdpdrPlugin* rdpdr, wStream* input_stream) {
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Printer unexpectedly switched to XPS mode");
}

View File

@ -1,221 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef __GUAC_RDPDR_MESSAGES_H
#define __GUAC_RDPDR_MESSAGES_H
#include "config.h"
#include "rdpdr_service.h"
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
#include <stdint.h>
/**
* Identifies the "core" component of RDPDR as the destination of the received
* packet.
*/
#define RDPDR_CTYP_CORE 0x4472
/**
* Identifies the printing component of RDPDR as the destination of the
* received packet.
*/
#define RDPDR_CTYP_PRN 0x5052
/*
* Packet IDs as required by the RDP spec (see: [MS-RDPEFS].pdf)
*/
#define PAKID_CORE_SERVER_ANNOUNCE 0x496E
#define PAKID_CORE_CLIENTID_CONFIRM 0x4343
#define PAKID_CORE_CLIENT_NAME 0x434E
#define PAKID_CORE_DEVICELIST_ANNOUNCE 0x4441
#define PAKID_CORE_DEVICE_REPLY 0x6472
#define PAKID_CORE_DEVICE_IOREQUEST 0x4952
#define PAKID_CORE_DEVICE_IOCOMPLETION 0x4943
#define PAKID_CORE_SERVER_CAPABILITY 0x5350
#define PAKID_CORE_CLIENT_CAPABILITY 0x4350
#define PAKID_CORE_DEVICELIST_REMOVE 0x444D
#define PAKID_PRN_CACHE_DATA 0x5043
#define PAKID_CORE_USER_LOGGEDON 0x554C
#define PAKID_PRN_USING_XPS 0x5543
/**
* A 32-bit arbitrary value for the osType field of certain requests. As this
* value is defined as completely arbitrary and required to be ignored by the
* server, we send "GUAC" as an integer.
*/
#define GUAC_OS_TYPE (*((uint32_t*) "GUAC"))
/**
* Name of the printer driver that should be used on the server.
*/
#define GUAC_PRINTER_DRIVER "M\0S\0 \0P\0u\0b\0l\0i\0s\0h\0e\0r\0 \0I\0m\0a\0g\0e\0s\0e\0t\0t\0e\0r\0\0\0"
#define GUAC_PRINTER_DRIVER_LENGTH 50
/**
* Label of the filesystem.
*/
#define GUAC_FILESYSTEM_LABEL "G\0U\0A\0C\0F\0I\0L\0E\0"
#define GUAC_FILESYSTEM_LABEL_LENGTH 16
/*
* Capability types
*/
#define CAP_GENERAL_TYPE 1
#define CAP_PRINTER_TYPE 2
#define CAP_PORT_TYPE 3
#define CAP_DRIVE_TYPE 4
#define CAP_SMARTCARD_TYPE 5
/*
* General capability header versions.
*/
#define GENERAL_CAPABILITY_VERSION_01 1
#define GENERAL_CAPABILITY_VERSION_02 2
/*
* Print capability header versions.
*/
#define PRINT_CAPABILITY_VERSION_01 1
/*
* Drive capability header versions.
*/
#define DRIVE_CAPABILITY_VERSION_01 1
#define DRIVE_CAPABILITY_VERSION_02 2
/*
* Legal client major version numbers.
*/
#define RDP_CLIENT_MAJOR_ALL 1
/*
* Legal client minor version numbers.
*/
#define RDP_CLIENT_MINOR_6_1 0xC
#define RDP_CLIENT_MINOR_5_2 0xA
#define RDP_CLIENT_MINOR_5_1 0x5
#define RDP_CLIENT_MINOR_5_0 0x2
/*
* PDU flags used by the extendedPDU field.
*/
#define RDPDR_DEVICE_REMOVE_PDUS 0x1
#define RDPDR_CLIENT_DISPLAY_NAME 0x2
#define RDPDR_USER_LOGGEDON_PDU 0x4
/*
* Device types.
*/
#define RDPDR_DTYP_SERIAL 0x00000001
#define RDPDR_DTYP_PARALLEL 0x00000002
#define RDPDR_DTYP_PRINT 0x00000004
#define RDPDR_DTYP_FILESYSTEM 0x00000008
#define RDPDR_DTYP_SMARTCARD 0x00000020
/*
* Printer flags.
*/
#define RDPDR_PRINTER_ANNOUNCE_FLAG_ASCII 0x00000001
#define RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER 0x00000002
#define RDPDR_PRINTER_ANNOUNCE_FLAG_NETWORKPRINTER 0x00000004
#define RDPDR_PRINTER_ANNOUNCE_FLAG_TSPRINTER 0x00000008
#define RDPDR_PRINTER_ANNOUNCE_FLAG_XPSFORMAT 0x00000010
/*
* I/O requests.
*/
#define IRP_MJ_CREATE 0x00000000
#define IRP_MJ_CLOSE 0x00000002
#define IRP_MJ_READ 0x00000003
#define IRP_MJ_WRITE 0x00000004
#define IRP_MJ_DEVICE_CONTROL 0x0000000E
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0000000A
#define IRP_MJ_SET_VOLUME_INFORMATION 0x0000000B
#define IRP_MJ_QUERY_INFORMATION 0x00000005
#define IRP_MJ_SET_INFORMATION 0x00000006
#define IRP_MJ_DIRECTORY_CONTROL 0x0000000C
#define IRP_MJ_LOCK_CONTROL 0x00000011
#define IRP_MN_QUERY_DIRECTORY 0x00000001
#define IRP_MN_NOTIFY_CHANGE_DIRECTORY 0x00000002
/*
* Volume information constants.
*/
#define FileFsVolumeInformation 0x00000001
#define FileFsSizeInformation 0x00000003
#define FileFsDeviceInformation 0x00000004
#define FileFsAttributeInformation 0x00000005
#define FileFsFullSizeInformation 0x00000007
/*
* File information constants.
*/
#define FileBasicInformation 0x00000004
#define FileStandardInformation 0x00000005
#define FileRenameInformation 0x0000000A
#define FileDispositionInformation 0x0000000D
#define FileAllocationInformation 0x00000013
#define FileEndOfFileInformation 0x00000014
#define FileAttributeTagInformation 0x00000023
/*
* Directory information constants.
*/
#define FileDirectoryInformation 0x00000001
#define FileFullDirectoryInformation 0x00000002
#define FileBothDirectoryInformation 0x00000003
#define FileNamesInformation 0x0000000C
/*
* Message handlers.
*/
void guac_rdpdr_process_server_announce(guac_rdpdrPlugin* rdpdr, wStream* input_stream);
void guac_rdpdr_process_clientid_confirm(guac_rdpdrPlugin* rdpdr, wStream* input_stream);
void guac_rdpdr_process_device_reply(guac_rdpdrPlugin* rdpdr, wStream* input_stream);
void guac_rdpdr_process_device_iorequest(guac_rdpdrPlugin* rdpdr, wStream* input_stream);
void guac_rdpdr_process_server_capability(guac_rdpdrPlugin* rdpdr, wStream* input_stream);
void guac_rdpdr_process_user_loggedon(guac_rdpdrPlugin* rdpdr, wStream* input_stream);
void guac_rdpdr_process_prn_cache_data(guac_rdpdrPlugin* rdpdr, wStream* input_stream);
void guac_rdpdr_process_prn_using_xps(guac_rdpdrPlugin* rdpdr, wStream* input_stream);
#endif

View File

@ -1,48 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef __GUAC_RDPDR_PRINTER_H
#define __GUAC_RDPDR_PRINTER_H
#include "config.h"
#include "rdpdr_service.h"
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/**
* Registers a new printer device within the RDPDR plugin. This must be done
* before RDPDR connection finishes.
*
* @param rdpdr
* The RDP device redirection plugin where the device is registered.
*
* @param printer_name
* The name of the printer that will be registered with the RDP
* connection and passed through to the server.
*/
void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr, char* printer_name);
#endif

View File

@ -1,313 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "rdp.h"
#include "rdp_fs.h"
#include "rdp_settings.h"
#include "rdp_stream.h"
#include "rdpdr_fs_service.h"
#include "rdpdr_messages.h"
#include "rdpdr_printer.h"
#include "rdpdr_service.h"
#include <stdlib.h>
#include <string.h>
#include <freerdp/constants.h>
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/**
* Entry point for RDPDR virtual channel.
*/
int VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints) {
/* Allocate plugin */
guac_rdpdrPlugin* rdpdr =
(guac_rdpdrPlugin*) calloc(1, sizeof(guac_rdpdrPlugin));
/* Init channel def */
strcpy(rdpdr->plugin.channel_def.name, "rdpdr");
rdpdr->plugin.channel_def.options =
CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP;
/* Set callbacks */
rdpdr->plugin.connect_callback = guac_rdpdr_process_connect;
rdpdr->plugin.receive_callback = guac_rdpdr_process_receive;
rdpdr->plugin.event_callback = guac_rdpdr_process_event;
rdpdr->plugin.terminate_callback = guac_rdpdr_process_terminate;
/* Finish init */
svc_plugin_init((rdpSvcPlugin*) rdpdr, pEntryPoints);
return 1;
}
/*
* Service Handlers
*/
void guac_rdpdr_process_connect(rdpSvcPlugin* plugin) {
/* Get RDPDR plugin */
guac_rdpdrPlugin* rdpdr = (guac_rdpdrPlugin*) plugin;
/* Get client from plugin parameters */
guac_client* client = (guac_client*)
plugin->channel_entry_points.pExtendedData;
/* NULL out pExtendedData so we don't lose our guac_client due to an
* automatic free() within libfreerdp */
plugin->channel_entry_points.pExtendedData = NULL;
/* Get data from client */
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Init plugin */
rdpdr->client = client;
rdpdr->devices_registered = 0;
/* Register printer if enabled */
if (rdp_client->settings->printing_enabled)
guac_rdpdr_register_printer(rdpdr, rdp_client->settings->printer_name);
/* Register drive if enabled */
if (rdp_client->settings->drive_enabled)
guac_rdpdr_register_fs(rdpdr, rdp_client->settings->drive_name);
/* Log that printing, etc. has been loaded */
guac_client_log(client, GUAC_LOG_INFO, "guacdr connected.");
}
void guac_rdpdr_process_terminate(rdpSvcPlugin* plugin) {
guac_rdpdrPlugin* rdpdr = (guac_rdpdrPlugin*) plugin;
int i;
for (i=0; i<rdpdr->devices_registered; i++) {
guac_rdpdr_device* device = &(rdpdr->devices[i]);
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Unloading device %i (%s)",
device->device_id, device->device_name);
device->free_handler(device);
}
free(plugin);
}
void guac_rdpdr_process_event(rdpSvcPlugin* plugin, wMessage* event) {
freerdp_event_free(event);
}
void guac_rdpdr_process_receive(rdpSvcPlugin* plugin,
wStream* input_stream) {
guac_rdpdrPlugin* rdpdr = (guac_rdpdrPlugin*) plugin;
int component;
int packet_id;
/* Read header */
Stream_Read_UINT16(input_stream, component);
Stream_Read_UINT16(input_stream, packet_id);
/* Core component */
if (component == RDPDR_CTYP_CORE) {
/* Dispatch handlers based on packet ID */
switch (packet_id) {
case PAKID_CORE_SERVER_ANNOUNCE:
guac_rdpdr_process_server_announce(rdpdr, input_stream);
break;
case PAKID_CORE_CLIENTID_CONFIRM:
guac_rdpdr_process_clientid_confirm(rdpdr, input_stream);
break;
case PAKID_CORE_DEVICE_REPLY:
guac_rdpdr_process_device_reply(rdpdr, input_stream);
break;
case PAKID_CORE_DEVICE_IOREQUEST:
guac_rdpdr_process_device_iorequest(rdpdr, input_stream);
break;
case PAKID_CORE_SERVER_CAPABILITY:
guac_rdpdr_process_server_capability(rdpdr, input_stream);
break;
case PAKID_CORE_USER_LOGGEDON:
guac_rdpdr_process_user_loggedon(rdpdr, input_stream);
break;
default:
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Ignoring RDPDR core packet with unexpected ID: 0x%04x", packet_id);
}
} /* end if core */
/* Printer component */
else if (component == RDPDR_CTYP_PRN) {
/* Dispatch handlers based on packet ID */
switch (packet_id) {
case PAKID_PRN_CACHE_DATA:
guac_rdpdr_process_prn_cache_data(rdpdr, input_stream);
break;
case PAKID_PRN_USING_XPS:
guac_rdpdr_process_prn_using_xps(rdpdr, input_stream);
break;
default:
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Ignoring RDPDR printer packet with unexpected ID: 0x%04x", packet_id);
}
} /* end if printer */
else
guac_client_log(rdpdr->client, GUAC_LOG_INFO, "Ignoring packet for unknown RDPDR component: 0x%04x", component);
}
wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device,
int completion_id, int status, int size) {
wStream* output_stream = Stream_New(NULL, 16+size);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_DEVICE_IOCOMPLETION);
/* Write content */
Stream_Write_UINT32(output_stream, device->device_id);
Stream_Write_UINT32(output_stream, completion_id);
Stream_Write_UINT32(output_stream, status);
return output_stream;
}
/**
* 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) {
/* Do not bother attempting the download if the owner has left */
if (owner == NULL)
return NULL;
guac_client* client = owner->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_fs* filesystem = rdp_client->filesystem;
/* Ignore download if filesystem has been unloaded */
if (filesystem == NULL)
return NULL;
/* Attempt to open requested file */
char* path = (char*) data;
int file_id = guac_rdp_fs_open(filesystem, path,
ACCESS_FILE_READ_DATA, 0, DISP_FILE_OPEN, 0);
/* If file opened successfully, start stream */
if (file_id >= 0) {
guac_rdp_stream* rdp_stream;
const char* basename;
int i;
char c;
/* Associate stream with transfer status */
guac_stream* stream = guac_user_alloc_stream(owner);
stream->data = rdp_stream = malloc(sizeof(guac_rdp_stream));
stream->ack_handler = guac_rdp_download_ack_handler;
rdp_stream->type = GUAC_RDP_DOWNLOAD_STREAM;
rdp_stream->download_status.file_id = file_id;
rdp_stream->download_status.offset = 0;
/* Get basename from absolute path */
i=0;
basename = path;
do {
c = path[i];
if (c == '/' || c == '\\')
basename = &(path[i+1]);
i++;
} while (c != '\0');
guac_user_log(owner, GUAC_LOG_DEBUG, "%s: Initiating download "
"of \"%s\"", __func__, path);
/* Begin stream */
guac_protocol_send_file(owner->socket, stream,
"application/octet-stream", basename);
guac_socket_flush(owner->socket);
/* Download started successfully */
return stream;
}
/* 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

@ -1,184 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef __GUAC_RDPDR_SERVICE_H
#define __GUAC_RDPDR_SERVICE_H
#include "config.h"
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/**
* The maximum number of bytes to allow for a device read.
*/
#define GUAC_RDP_MAX_READ_BUFFER 4194304
typedef struct guac_rdpdrPlugin guac_rdpdrPlugin;
typedef struct guac_rdpdr_device guac_rdpdr_device;
/**
* Handler for client device list announce. Each implementing device must write
* its announcement header and data to the given output stream.
*/
typedef void guac_rdpdr_device_announce_handler(guac_rdpdr_device* device, wStream* output_stream,
int device_id);
/**
* Handler for device I/O requests.
*/
typedef void guac_rdpdr_device_iorequest_handler(guac_rdpdr_device* device,
wStream* input_stream, int file_id, int completion_id, int major_func, int minor_func);
/**
* Handler for cleaning up the dynamically-allocated portions of a device.
*/
typedef void guac_rdpdr_device_free_handler(guac_rdpdr_device* device);
/**
* Arbitrary device forwarded over the RDPDR channel.
*/
struct guac_rdpdr_device {
/**
* The RDPDR plugin owning this device.
*/
guac_rdpdrPlugin* rdpdr;
/**
* The ID assigned to this device by the RDPDR plugin.
*/
int device_id;
/**
* Device name, used for logging and for passthrough to the
* server.
*/
const char* device_name;
/**
* The type of RDPDR device that this represents.
*/
uint32_t device_type;
/**
* The DOS name of the device. Max 8 bytes, including terminator.
*/
const char *dos_name;
/**
* The stream that stores the RDPDR device announcement for this device.
*/
wStream* device_announce;
/**
* The length of the device_announce wStream.
*/
int device_announce_len;
/**
* Handler which should be called for every I/O request received.
*/
guac_rdpdr_device_iorequest_handler* iorequest_handler;
/**
* Handler which should be called when the device is being freed.
*/
guac_rdpdr_device_free_handler* free_handler;
/**
* Arbitrary data, used internally by the handlers for this device.
*/
void* data;
};
/**
* Structure representing the current state of the Guacamole RDPDR plugin for
* FreeRDP.
*/
struct guac_rdpdrPlugin {
/**
* The FreeRDP parts of this plugin. This absolutely MUST be first.
* FreeRDP depends on accessing this structure as if it were an instance
* of rdpSvcPlugin.
*/
rdpSvcPlugin plugin;
/**
* Reference to the client owning this instance of the RDPDR plugin.
*/
guac_client* client;
/**
* The number of devices registered within the devices array.
*/
int devices_registered;
/**
* Array of registered devices.
*/
guac_rdpdr_device devices[8];
};
/**
* Handler called when this plugin is loaded by FreeRDP.
*/
void guac_rdpdr_process_connect(rdpSvcPlugin* plugin);
/**
* Handler called when this plugin receives data along its designated channel.
*/
void guac_rdpdr_process_receive(rdpSvcPlugin* plugin,
wStream* input_stream);
/**
* Handler called when this plugin is being unloaded.
*/
void guac_rdpdr_process_terminate(rdpSvcPlugin* plugin);
/**
* Handler called when this plugin receives an event. For the sake of RDPDR,
* all events will be ignored and simply free'd.
*/
void guac_rdpdr_process_event(rdpSvcPlugin* plugin, wMessage* event);
/**
* Creates a new stream which contains the common DR_DEVICE_IOCOMPLETION header
* used for virtually all responses.
*/
wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device,
int completion_id, int status, int size);
/**
* Begins streaming the given file to the user via a Guacamole file stream.
*/
void guac_rdpdr_start_download(guac_rdpdr_device* device, char* path);
#endif

View File

@ -1,145 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "rdpsnd_service.h"
#include "rdpsnd_messages.h"
#include <stdlib.h>
#include <string.h>
#include <freerdp/constants.h>
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/**
* Entry point for RDPSND virtual channel.
*/
int VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints) {
/* Allocate plugin */
guac_rdpsndPlugin* rdpsnd =
(guac_rdpsndPlugin*) calloc(1, sizeof(guac_rdpsndPlugin));
/* Init channel def */
strcpy(rdpsnd->plugin.channel_def.name, "rdpsnd");
rdpsnd->plugin.channel_def.options =
CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP;
/* Set callbacks */
rdpsnd->plugin.connect_callback = guac_rdpsnd_process_connect;
rdpsnd->plugin.receive_callback = guac_rdpsnd_process_receive;
rdpsnd->plugin.event_callback = guac_rdpsnd_process_event;
rdpsnd->plugin.terminate_callback = guac_rdpsnd_process_terminate;
/* Finish init */
svc_plugin_init((rdpSvcPlugin*) rdpsnd, pEntryPoints);
return 1;
}
/*
* Service Handlers
*/
void guac_rdpsnd_process_connect(rdpSvcPlugin* plugin) {
guac_rdpsndPlugin* rdpsnd = (guac_rdpsndPlugin*) plugin;
/* Get client from plugin parameters */
guac_client* client = rdpsnd->client =
(guac_client*) plugin->channel_entry_points.pExtendedData;
/* NULL out pExtendedData so we don't lose our guac_client due to an
* automatic free() within libfreerdp */
plugin->channel_entry_points.pExtendedData = NULL;
#ifdef RDPSVCPLUGIN_INTERVAL_MS
/* Update every 10 ms */
plugin->interval_ms = 10;
#endif
/* Log that sound has been loaded */
guac_client_log(client, GUAC_LOG_INFO, "guacsnd connected.");
}
void guac_rdpsnd_process_terminate(rdpSvcPlugin* plugin) {
free(plugin);
}
void guac_rdpsnd_process_event(rdpSvcPlugin* plugin, wMessage* event) {
freerdp_event_free(event);
}
void guac_rdpsnd_process_receive(rdpSvcPlugin* plugin,
wStream* input_stream) {
guac_rdpsndPlugin* rdpsnd = (guac_rdpsndPlugin*) plugin;
guac_rdpsnd_pdu_header header;
/* Read RDPSND PDU header */
Stream_Read_UINT8(input_stream, header.message_type);
Stream_Seek_UINT8(input_stream);
Stream_Read_UINT16(input_stream, header.body_size);
/*
* If next PDU is SNDWAVE (due to receiving WaveInfo PDU previously),
* ignore the header and parse as a Wave PDU.
*/
if (rdpsnd->next_pdu_is_wave)
guac_rdpsnd_wave_handler(rdpsnd, input_stream, &header);
/* Dispatch message to standard handlers */
else {
switch (header.message_type) {
/* Server Audio Formats and Version PDU */
case SNDC_FORMATS:
guac_rdpsnd_formats_handler(rdpsnd, input_stream, &header);
break;
/* Training PDU */
case SNDC_TRAINING:
guac_rdpsnd_training_handler(rdpsnd, input_stream, &header);
break;
/* WaveInfo PDU */
case SNDC_WAVE:
guac_rdpsnd_wave_info_handler(rdpsnd, input_stream, &header);
break;
/* Close PDU */
case SNDC_CLOSE:
guac_rdpsnd_close_handler(rdpsnd, input_stream, &header);
break;
}
}
Stream_Free(input_stream, TRUE);
}

View File

@ -1,150 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "svc_service.h"
#include <stdlib.h>
#include <string.h>
#include <freerdp/constants.h>
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/string.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/**
* Entry point for arbitrary SVC.
*/
int VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints) {
/* Gain access to plugin data */
CHANNEL_ENTRY_POINTS_FREERDP* entry_points_ex =
(CHANNEL_ENTRY_POINTS_FREERDP*) pEntryPoints;
/* Allocate plugin */
guac_svcPlugin* svc_plugin =
(guac_svcPlugin*) calloc(1, sizeof(guac_svcPlugin));
/* Get SVC descriptor from plugin parameters */
guac_rdp_svc* svc = (guac_rdp_svc*) entry_points_ex->pExtendedData;
/* Init channel def */
guac_strlcpy(svc_plugin->plugin.channel_def.name, svc->name,
GUAC_RDP_SVC_MAX_LENGTH);
svc_plugin->plugin.channel_def.options =
CHANNEL_OPTION_INITIALIZED
| CHANNEL_OPTION_ENCRYPT_RDP
| CHANNEL_OPTION_COMPRESS_RDP;
/* Init plugin */
svc_plugin->svc = svc;
/* Set callbacks */
svc_plugin->plugin.connect_callback = guac_svc_process_connect;
svc_plugin->plugin.receive_callback = guac_svc_process_receive;
svc_plugin->plugin.event_callback = guac_svc_process_event;
svc_plugin->plugin.terminate_callback = guac_svc_process_terminate;
/* Store plugin reference in SVC */
svc->plugin = (rdpSvcPlugin*) svc_plugin;
/* Finish init */
svc_plugin_init((rdpSvcPlugin*) svc_plugin, pEntryPoints);
return 1;
}
/*
* Service Handlers
*/
void guac_svc_process_connect(rdpSvcPlugin* plugin) {
/* Get corresponding guac_rdp_svc */
guac_svcPlugin* svc_plugin = (guac_svcPlugin*) plugin;
guac_rdp_svc* svc = svc_plugin->svc;
/* NULL out pExtendedData so we don't lose our guac_rdp_svc due to an
* automatic free() within libfreerdp */
plugin->channel_entry_points.pExtendedData = NULL;
/* Create pipe */
svc->output_pipe = guac_client_alloc_stream(svc->client);
/* Notify of pipe's existence */
guac_rdp_svc_send_pipe(svc->client->socket, svc);
/* Log connection to static channel */
guac_client_log(svc->client, GUAC_LOG_INFO,
"Static channel \"%s\" connected.", svc->name);
}
void guac_svc_process_terminate(rdpSvcPlugin* plugin) {
/* Get corresponding guac_rdp_svc */
guac_svcPlugin* svc_plugin = (guac_svcPlugin*) plugin;
guac_rdp_svc* svc = svc_plugin->svc;
/* Remove and free SVC */
guac_client_log(svc->client, GUAC_LOG_INFO, "Closing channel \"%s\"...", svc->name);
guac_rdp_remove_svc(svc->client, svc->name);
free(svc);
free(plugin);
}
void guac_svc_process_event(rdpSvcPlugin* plugin, wMessage* event) {
freerdp_event_free(event);
}
void guac_svc_process_receive(rdpSvcPlugin* plugin,
wStream* input_stream) {
/* Get corresponding guac_rdp_svc */
guac_svcPlugin* svc_plugin = (guac_svcPlugin*) plugin;
guac_rdp_svc* svc = svc_plugin->svc;
/* Fail if output not created */
if (svc->output_pipe == NULL) {
guac_client_log(svc->client, GUAC_LOG_ERROR,
"Output for channel \"%s\" dropped.",
svc->name);
return;
}
/* Send blob */
guac_protocol_send_blob(svc->client->socket, svc->output_pipe,
Stream_Buffer(input_stream),
Stream_Length(input_stream));
guac_socket_flush(svc->client->socket);
}

View File

@ -1,77 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef __GUAC_SVC_SERVICE_H
#define __GUAC_SVC_SERVICE_H
#include "config.h"
#include "rdp_svc.h"
#include <freerdp/utils/svc_plugin.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h>
#else
#include "compat/winpr-stream.h"
#endif
/**
* Structure representing the current state of an arbitrary static virtual
* channel.
*/
typedef struct guac_svcPlugin {
/**
* The FreeRDP parts of this plugin. This absolutely MUST be first.
* FreeRDP depends on accessing this structure as if it were an instance
* of rdpSvcPlugin.
*/
rdpSvcPlugin plugin;
/**
* The Guacamole-specific SVC structure describing the channel this
* instance represents.
*/
guac_rdp_svc* svc;
} guac_svcPlugin;
/**
* Handler called when this plugin is loaded by FreeRDP.
*/
void guac_svc_process_connect(rdpSvcPlugin* plugin);
/**
* Handler called when this plugin receives data along its designated channel.
*/
void guac_svc_process_receive(rdpSvcPlugin* plugin,
wStream* input_stream);
/**
* Handler called when this plugin is being unloaded.
*/
void guac_svc_process_terminate(rdpSvcPlugin* plugin);
/**
* Handler called when this plugin receives an event.
*/
void guac_svc_process_event(rdpSvcPlugin* plugin, wMessage* event);
#endif

View File

@ -17,20 +17,20 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/disp.h"
#include "common/cursor.h"
#include "client.h" #include "common/display.h"
#include "common/recording.h" #include "common/recording.h"
#include "input.h" #include "input.h"
#include "keyboard.h" #include "keyboard.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_disp.h" #include "settings.h"
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/input.h> #include <freerdp/input.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/user.h>
#include <pthread.h>
#include <stdlib.h> #include <stdlib.h>
int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) { int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
@ -38,14 +38,10 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
guac_client* client = user->client; guac_client* client = user->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* Skip if not yet connected */ /* Skip if not yet connected */
freerdp* rdp_inst = rdp_client->rdp_inst; freerdp* rdp_inst = rdp_client->rdp_inst;
if (rdp_inst == NULL) { if (rdp_inst == NULL)
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return 0; return 0;
}
/* Store current mouse location/state */ /* Store current mouse location/state */
guac_common_cursor_update(rdp_client->display->cursor, user, x, y, mask); guac_common_cursor_update(rdp_client->display->cursor, user, x, y, mask);
@ -118,8 +114,6 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
rdp_client->mouse_button_mask = mask; rdp_client->mouse_button_mask = mask;
} }
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return 0; return 0;
} }
@ -155,9 +149,7 @@ int guac_rdp_user_size_handler(guac_user* user, int width, int height) {
height = height * settings->resolution / user->info.optimal_resolution; height = height * settings->resolution / user->info.optimal_resolution;
/* Send display update */ /* Send display update */
pthread_mutex_lock(&(rdp_client->rdp_lock));
guac_rdp_disp_set_size(rdp_client->disp, settings, rdp_inst, width, height); guac_rdp_disp_set_size(rdp_client->disp, settings, rdp_inst, width, height);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return 0; return 0;

View File

@ -17,19 +17,15 @@
* under the License. * under the License.
*/ */
#include "config.h"
#include "client.h"
#include "decompose.h" #include "decompose.h"
#include "keyboard.h" #include "keyboard.h"
#include "keymap.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_keymap.h"
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/input.h> #include <freerdp/input.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <pthread.h>
#include <stdlib.h> #include <stdlib.h>
/** /**
@ -102,21 +98,15 @@ static void guac_rdp_send_key_event(guac_rdp_client* rdp_client,
else else
pressed_flags = KBD_FLAGS_RELEASE; pressed_flags = KBD_FLAGS_RELEASE;
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* Skip if not yet connected */ /* Skip if not yet connected */
freerdp* rdp_inst = rdp_client->rdp_inst; freerdp* rdp_inst = rdp_client->rdp_inst;
if (rdp_inst == NULL) { if (rdp_inst == NULL)
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return; return;
}
/* Send actual key */ /* Send actual key */
rdp_inst->input->KeyboardEvent(rdp_inst->input, rdp_inst->input->KeyboardEvent(rdp_inst->input,
flags | pressed_flags, scancode); flags | pressed_flags, scancode);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
} }
/** /**
@ -136,22 +126,16 @@ static void guac_rdp_send_key_event(guac_rdp_client* rdp_client,
static void guac_rdp_send_unicode_event(guac_rdp_client* rdp_client, static void guac_rdp_send_unicode_event(guac_rdp_client* rdp_client,
int codepoint) { int codepoint) {
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* Skip if not yet connected */ /* Skip if not yet connected */
freerdp* rdp_inst = rdp_client->rdp_inst; freerdp* rdp_inst = rdp_client->rdp_inst;
if (rdp_inst == NULL) { if (rdp_inst == NULL)
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return; return;
}
/* Send Unicode event */ /* Send Unicode event */
rdp_inst->input->UnicodeKeyboardEvent( rdp_inst->input->UnicodeKeyboardEvent(
rdp_inst->input, rdp_inst->input,
0, codepoint); 0, codepoint);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
} }
/** /**
@ -171,20 +155,14 @@ static void guac_rdp_send_unicode_event(guac_rdp_client* rdp_client,
static void guac_rdp_send_synchronize_event(guac_rdp_client* rdp_client, static void guac_rdp_send_synchronize_event(guac_rdp_client* rdp_client,
int flags) { int flags) {
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* Skip if not yet connected */ /* Skip if not yet connected */
freerdp* rdp_inst = rdp_client->rdp_inst; freerdp* rdp_inst = rdp_client->rdp_inst;
if (rdp_inst == NULL) { if (rdp_inst == NULL)
pthread_mutex_unlock(&(rdp_client->rdp_lock));
return; return;
}
/* Synchronize lock key states */ /* Synchronize lock key states */
rdp_inst->input->SynchronizeEvent(rdp_inst->input, flags); rdp_inst->input->SynchronizeEvent(rdp_inst->input, flags);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
} }
/** /**

View File

@ -20,7 +20,7 @@
#ifndef GUAC_RDP_KEYBOARD_H #ifndef GUAC_RDP_KEYBOARD_H
#define GUAC_RDP_KEYBOARD_H #define GUAC_RDP_KEYBOARD_H
#include "rdp_keymap.h" #include "keymap.h"
#include <guacamole/client.h> #include <guacamole/client.h>

View File

@ -17,9 +17,7 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "keymap.h"
#include "rdp_keymap.h"
#include <string.h> #include <string.h>

View File

@ -17,17 +17,10 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_KEYMAP_H
#define GUAC_RDP_KEYMAP_H
#ifndef _GUAC_RDP_RDP_KEYMAP_H
#define _GUAC_RDP_RDP_KEYMAP_H
#include "config.h"
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h> #include <winpr/wtypes.h>
#else
#include "compat/winpr-wtypes.h"
#endif
/** /**
* Represents a keysym-to-scancode mapping for RDP, with extra information * Represents a keysym-to-scancode mapping for RDP, with extra information

View File

@ -43,14 +43,9 @@ my @keymaps = ();
open OUTPUT, ">", "_generated_keymaps.c"; open OUTPUT, ">", "_generated_keymaps.c";
print OUTPUT print OUTPUT
'#include "config.h"' . "\n" '#include "config.h"' . "\n"
. '#include "rdp_keymap.h"' . "\n" . '#include "keymap.h"' . "\n"
. '#include <freerdp/input.h>' . "\n" . '#include <freerdp/input.h>' . "\n"
. "\n"
. '#ifdef HAVE_FREERDP_LOCALE_KEYBOARD_H' . "\n"
. '#include <freerdp/locale/keyboard.h>' . "\n" . '#include <freerdp/locale/keyboard.h>' . "\n"
. '#else' . "\n"
. '#include <freerdp/kbd/layouts.h>' . "\n"
. '#endif' . "\n"
. "\n" . "\n"
. '#include <stddef.h>' . "\n" . '#include <stddef.h>' . "\n"
. "\n"; . "\n";

74
src/protocols/rdp/log.c Normal file
View File

@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <guacamole/client.h>
#include <winpr/wlog.h>
#include <winpr/wtypes.h>
#include <stddef.h>
/**
* The guac_client that should be used within this process for FreeRDP log
* messages. As all Guacamole connections are isolated at the process level,
* this will only ever be set to the guac_client of the current process'
* connection.
*/
static guac_client* current_client = NULL;
/**
* Logs the text data within the given message to the logging facilities of the
* guac_client currently stored under current_client (the guac_client of the
* current process).
*
* @param message
* The message to log.
*
* @return
* TRUE if the message was successfully logged, FALSE otherwise.
*/
static BOOL guac_rdp_wlog_text_message(const wLogMessage* message) {
/* Fail if log not yet redirected */
if (current_client == NULL)
return FALSE;
/* Log all received messages at the debug level */
guac_client_log(current_client, GUAC_LOG_DEBUG, "%s", message->TextString);
return TRUE;
}
void guac_rdp_redirect_wlog(guac_client* client) {
wLogCallbacks callbacks = {
.message = guac_rdp_wlog_text_message
};
current_client = client;
/* Reconfigure root logger to use callback appender */
wLog* root = WLog_GetRoot();
WLog_SetLogAppenderType(root, WLOG_APPENDER_CALLBACK);
/* Set appender callbacks to our own */
wLogAppender* appender = WLog_GetLogAppender(root);
WLog_ConfigureAppender(appender, "callbacks", &callbacks);
}

View File

@ -17,23 +17,19 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_LOG_H
#define GUAC_RDP_LOG_H
#ifndef __GUAC_WINPR_WTYPES_COMPAT_H #include <guacamole/client.h>
#define __GUAC_WINPR_WTYPES_COMPAT_H
#include "config.h" /**
* Redirects the core FreeRDP logging facility, wLog, such that it logs all
#include <freerdp/types.h> * messages at the debug level using guac_client_log().
*
typedef uint8 BYTE; * @param client
typedef uint8 UINT8; * The guac_client that should receive all log messages.
typedef uint16 UINT16; */
typedef uint32 UINT32; void guac_rdp_redirect_wlog(guac_client* client);
typedef uint64 UINT64;
typedef boolean BOOL;
#define TRUE true
#define FALSE false
#endif #endif

122
src/protocols/rdp/ls.c Normal file
View File

@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "fs.h"
#include "ls.h"
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <winpr/nt.h>
#include <winpr/shell.h>
#include <stdlib.h>
#include <string.h>
int guac_rdp_ls_ack_handler(guac_user* user, guac_stream* stream,
char* message, guac_protocol_status status) {
int blob_written = 0;
const char* filename;
guac_rdp_ls_status* ls_status = (guac_rdp_ls_status*) stream->data;
/* If unsuccessful, free stream and abort */
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
guac_rdp_fs_close(ls_status->fs, ls_status->file_id);
guac_user_free_stream(user, stream);
free(ls_status);
return 0;
}
/* While directory entries remain */
while ((filename = guac_rdp_fs_read_dir(ls_status->fs,
ls_status->file_id)) != NULL
&& !blob_written) {
char absolute_path[GUAC_RDP_FS_MAX_PATH];
/* Skip current and parent directory entries */
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
continue;
/* Concatenate into absolute path - skip if invalid */
if (!guac_rdp_fs_append_filename(absolute_path,
ls_status->directory_name, filename)) {
guac_user_log(user, GUAC_LOG_DEBUG,
"Skipping filename \"%s\" - filename is invalid or "
"resulting path is too long", filename);
continue;
}
/* Attempt to open file to determine type */
int file_id = guac_rdp_fs_open(ls_status->fs, absolute_path,
GENERIC_READ, 0, FILE_OPEN, 0);
if (file_id < 0)
continue;
/* Get opened file */
guac_rdp_fs_file* file = guac_rdp_fs_get_file(ls_status->fs, file_id);
if (file == NULL) {
guac_user_log(user, GUAC_LOG_DEBUG, "%s: Successful open produced "
"bad file_id: %i", __func__, file_id);
return 0;
}
/* Determine mimetype */
const char* mimetype;
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY)
mimetype = GUAC_USER_STREAM_INDEX_MIMETYPE;
else
mimetype = "application/octet-stream";
/* Write entry */
blob_written |= guac_common_json_write_property(user, stream,
&ls_status->json_state, absolute_path, mimetype);
guac_rdp_fs_close(ls_status->fs, file_id);
}
/* Complete JSON and cleanup at end of directory */
if (filename == NULL) {
/* Complete JSON object */
guac_common_json_end_object(user, stream, &ls_status->json_state);
guac_common_json_flush(user, stream, &ls_status->json_state);
/* Clean up resources */
guac_rdp_fs_close(ls_status->fs, ls_status->file_id);
free(ls_status);
/* Signal of stream */
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
}
guac_socket_flush(user->socket);
return 0;
}

66
src/protocols/rdp/ls.h Normal file
View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_LS_H
#define GUAC_RDP_LS_H
#include "common/json.h"
#include "fs.h"
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <stdint.h>
/**
* The current state of a directory listing operation.
*/
typedef struct guac_rdp_ls_status {
/**
* The filesystem associated with the directory being listed.
*/
guac_rdp_fs* fs;
/**
* The file ID of the directory being listed.
*/
int file_id;
/**
* The absolute path of the directory being listed.
*/
char directory_name[GUAC_RDP_FS_MAX_PATH];
/**
* The current state of the JSON directory object being written.
*/
guac_common_json_state json_state;
} guac_rdp_ls_status;
/**
* Handler for ack messages received due to receipt of a "body" or "blob"
* instruction associated with a directory list operation.
*/
guac_user_ack_handler guac_rdp_ls_ack_handler;
#endif

View File

@ -0,0 +1,142 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "plugins/channels.h"
#include "rdp.h"
#include <freerdp/channels/channels.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <winpr/wtypes.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
int guac_rdp_wrapped_entry_ex_count = 0;
int guac_rdp_wrapped_entry_count = 0;
PVIRTUALCHANNELENTRYEX guac_rdp_wrapped_entry_ex[GUAC_RDP_MAX_CHANNELS] = { NULL };
PVIRTUALCHANNELENTRY guac_rdp_wrapped_entry[GUAC_RDP_MAX_CHANNELS] = { NULL };
PVIRTUALCHANNELENTRYEX guac_rdp_plugin_wrap_entry_ex(guac_client* client,
PVIRTUALCHANNELENTRYEX entry_ex) {
/* Do not wrap if there is insufficient space to store the wrapped
* function */
if (guac_rdp_wrapped_entry_ex_count == GUAC_RDP_MAX_CHANNELS) {
guac_client_log(client, GUAC_LOG_WARNING, "Maximum number of static "
"channels has been reached. Further FreeRDP plugins and "
"channel support may fail to load.");
return entry_ex;
}
/* Generate wrapped version of provided entry point */
PVIRTUALCHANNELENTRYEX wrapper = guac_rdp_entry_ex_wrappers[guac_rdp_wrapped_entry_ex_count];
guac_rdp_wrapped_entry_ex[guac_rdp_wrapped_entry_ex_count] = entry_ex;
guac_rdp_wrapped_entry_ex_count++;
return wrapper;
}
PVIRTUALCHANNELENTRY guac_rdp_plugin_wrap_entry(guac_client* client,
PVIRTUALCHANNELENTRY entry) {
/* Do not wrap if there is insufficient space to store the wrapped
* function */
if (guac_rdp_wrapped_entry_count == GUAC_RDP_MAX_CHANNELS) {
guac_client_log(client, GUAC_LOG_WARNING, "Maximum number of static "
"channels has been reached. Further FreeRDP plugins and "
"channel support may fail to load.");
return entry;
}
/* Generate wrapped version of provided entry point */
PVIRTUALCHANNELENTRY wrapper = guac_rdp_entry_wrappers[guac_rdp_wrapped_entry_count];
guac_rdp_wrapped_entry[guac_rdp_wrapped_entry_count] = entry;
guac_rdp_wrapped_entry_count++;
return wrapper;
}
int guac_freerdp_channels_load_plugin(rdpContext* context,
const char* name, void* data) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
/* Load plugin using "ex" version of the channel plugin entry point, if it exists */
PVIRTUALCHANNELENTRYEX entry_ex = (PVIRTUALCHANNELENTRYEX) (void*) freerdp_load_channel_addin_entry(name,
NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX);
if (entry_ex != NULL) {
entry_ex = guac_rdp_plugin_wrap_entry_ex(client, entry_ex);
return freerdp_channels_client_load_ex(context->channels, context->settings, entry_ex, data);
}
/* Lacking the "ex" entry point, attempt to load using the non-ex version */
PVIRTUALCHANNELENTRY entry = freerdp_load_channel_addin_entry(name,
NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC);
if (entry != NULL) {
entry = guac_rdp_plugin_wrap_entry(client, entry);
return freerdp_channels_client_load(context->channels, context->settings, entry, data);
}
/* The plugin does not exist / cannot be loaded */
return 1;
}
void guac_freerdp_dynamic_channel_collection_add(rdpSettings* settings,
const char* name, ...) {
va_list args;
ADDIN_ARGV* freerdp_args = malloc(sizeof(ADDIN_ARGV));
va_start(args, name);
/* Count number of arguments (excluding terminating NULL) */
freerdp_args->argc = 1;
while (va_arg(args, char*) != NULL)
freerdp_args->argc++;
/* Reset va_list */
va_end(args);
va_start(args, name);
/* Copy argument values into DVC entry */
freerdp_args->argv = malloc(sizeof(char*) * freerdp_args->argc);
freerdp_args->argv[0] = strdup(name);
int i;
for (i = 1; i < freerdp_args->argc; i++)
freerdp_args->argv[i] = strdup(va_arg(args, char*));
va_end(args);
/* Register plugin with FreeRDP */
settings->SupportDynamicChannels = TRUE;
freerdp_dynamic_channel_collection_add(settings, freerdp_args);
}

View File

@ -0,0 +1,207 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RDP_PLUGINS_CHANNELS_H
#define GUAC_RDP_PLUGINS_CHANNELS_H
#include <freerdp/channels/channels.h>
#include <freerdp/freerdp.h>
#include <freerdp/settings.h>
#include <guacamole/client.h>
#include <winpr/wtsapi.h>
/**
* The maximum number of static channels supported by Guacamole's RDP support.
* This value should be given a value which is at least the value of FreeRDP's
* CHANNEL_MAX_COUNT.
*
* NOTE: The value of this macro must be specified statically (not as a
* reference to CHANNEL_MAX_COUNT), as its value is extracted and used by the
* entry point wrapper code generator (generate-entry-wrappers.pl).
*/
#define GUAC_RDP_MAX_CHANNELS 64
/* Validate GUAC_RDP_MAX_CHANNELS is sane at compile time */
#if GUAC_RDP_MAX_CHANNELS < CHANNEL_MAX_COUNT
#error "GUAC_RDP_MAX_CHANNELS must not be less than CHANNEL_MAX_COUNT"
#endif
/** Loads the FreeRDP plugin having the given name. With the exception that
* this function requires the rdpContext rather than rdpChannels and
* rdpSettings, this function is essentially a drop-in replacement for
* freerdp_channels_load_plugin() which additionally loads plugins implementing
* the PVIRTUALCHANNELENTRYEX version of the channel plugin entry point. The
* freerdp_channels_load_plugin() function which is part of FreeRDP can load
* only plugins which implement the PVIRTUALCHANNELENTRY version of the entry
* point.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* for the referenced plugin to be loaded correctly.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param name
* The name of the plugin to load. If the plugin is not statically built
* into FreeRDP, this name will determine the filename of the library to be
* loaded dynamically. For a plugin named "NAME", the library called
* "libNAME-client" will be loaded from the "freerdp2" subdirectory of the
* main directory containing the FreeRDP libraries.
*
* @param data
* Arbitrary data to be passed to the plugin entry point. For most plugins
* which are built into FreeRDP, this will be another reference to the
* rdpSettings struct. The source of the relevant plugin must be consulted
* to determine the proper value to pass here.
*
* @return
* Zero if the plugin was loaded successfully, non-zero if the plugin could
* not be loaded.
*/
int guac_freerdp_channels_load_plugin(rdpContext* context,
const char* name, void* data);
/**
* Schedules loading of the FreeRDP dynamic virtual channel plugin having the
* given name. This function is essentially a wrapper for
* freerdp_dynamic_channel_collection_add() which additionally takes care of
* housekeeping tasks which would otherwise need to be performed manually:
*
* - The ADDIN_ARGV structure used to pass arguments to dynamic virtual
* channel plugins is automatically allocated and populated with any given
* arguments.
* - The SupportDynamicChannels member of the rdpSettings structure is
* automatically set to TRUE.
*
* The "drdynvc" plugin must still eventually be loaded for this function to
* have any effect, as it is the "drdynvc" plugin which processes the
* collection this function manipulates.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* and the "drdynvc" plugin MUST be loaded at some point after this function is
* called for the referenced dynamic channel plugin to be loaded correctly.
*
* @param settings
* The rdpSettings structure associated with the FreeRDP instance, already
* populated with any settings applicable to the plugin being loaded.
*
* @param name
* The name of the plugin to load. If the plugin is not statically built
* into FreeRDP, this name will determine the filename of the library to be
* loaded dynamically. For a plugin named "NAME", the library called
* "libNAME-client" will be loaded from the "freerdp2" subdirectory of the
* main directory containing the FreeRDP libraries.
*
* @param ...
* Arbitrary arguments to be passed to the plugin entry point. For most
* plugins which are built into FreeRDP, this will be another reference to
* the rdpSettings struct or NULL. The source of the relevant plugin must
* be consulted to determine the proper value(s) to pass here.
*/
void guac_freerdp_dynamic_channel_collection_add(rdpSettings* settings,
const char* name, ...);
/**
* The number of wrapped channel entry points currently stored within
* guac_rdp_wrapped_entry_ex.
*/
extern int guac_rdp_wrapped_entry_ex_count;
/**
* All currently wrapped entry points that use the PVIRTUALCHANNELENTRYEX
* variant.
*/
extern PVIRTUALCHANNELENTRYEX guac_rdp_wrapped_entry_ex[GUAC_RDP_MAX_CHANNELS];
/**
* Lookup table of wrapper functions for PVIRTUALCHANNELENTRYEX entry points.
* Each function within this array is generated at compile time by the entry
* point wrapper code generator (generate-entry-wrappers.pl) and automatically
* invokes the corresponding wrapped entry point stored within
* guac_rdp_wrapped_entry_ex.
*/
extern PVIRTUALCHANNELENTRYEX guac_rdp_entry_ex_wrappers[GUAC_RDP_MAX_CHANNELS];
/**
* Wraps the provided entry point function, returning a different entry point
* which simply invokes the original. As long as this function is not invoked
* more than GUAC_RDP_MAX_CHANNELS times, each returned entry point will be
* unique, even if the provided entry point is not. As FreeRDP will refuse to
* load a plugin if its entry point is already loaded, this allows a single
* FreeRDP plugin to be loaded multiple times.
*
* @param client
* The guac_client associated with the relevant RDP session.
*
* @param entry_ex
* The entry point function to wrap.
*
* @return
* A wrapped version of the provided entry point, or the unwrapped entry
* point if there is insufficient space remaining within
* guac_rdp_entry_ex_wrappers to wrap the entry point.
*/
PVIRTUALCHANNELENTRYEX guac_rdp_plugin_wrap_entry_ex(guac_client* client,
PVIRTUALCHANNELENTRYEX entry_ex);
/**
* The number of wrapped channel entry points currently stored within
* guac_rdp_wrapped_entry.
*/
extern int guac_rdp_wrapped_entry_count;
/**
* All currently wrapped entry points that use the PVIRTUALCHANNELENTRY
* variant.
*/
extern PVIRTUALCHANNELENTRY guac_rdp_wrapped_entry[GUAC_RDP_MAX_CHANNELS];
/**
* Lookup table of wrapper functions for PVIRTUALCHANNELENTRY entry points.
* Each function within this array is generated at compile time by the entry
* point wrapper code generator (generate-entry-wrappers.pl) and automatically
* invokes the corresponding wrapped entry point stored within
* guac_rdp_wrapped_entry.
*/
extern PVIRTUALCHANNELENTRY guac_rdp_entry_wrappers[GUAC_RDP_MAX_CHANNELS];
/**
* Wraps the provided entry point function, returning a different entry point
* which simply invokes the original. As long as this function is not invoked
* more than GUAC_RDP_MAX_CHANNELS times, each returned entry point will be
* unique, even if the provided entry point is not. As FreeRDP will refuse to
* load a plugin if its entry point is already loaded, this allows a single
* FreeRDP plugin to be loaded multiple times.
*
* @param client
* The guac_client associated with the relevant RDP session.
*
* @param entry
* The entry point function to wrap.
*
* @return
* A wrapped version of the provided entry point, or the unwrapped entry
* point if there is insufficient space remaining within
* guac_rdp_entry_wrappers to wrap the entry point.
*/
PVIRTUALCHANNELENTRY guac_rdp_plugin_wrap_entry(guac_client* client,
PVIRTUALCHANNELENTRY entry);
#endif

View File

@ -0,0 +1,77 @@
#!/usr/bin/env perl
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# generate-entry-wrappers.pl
#
# Generates C source which defines wrapper functions for FreeRDP plugin entry
# points, allowing multiple instances of the same plugin to be loaded despite
# otherwise always having the same entry point.
#
# The resulting source is stored within "_generated_channel_entry_wrappers.c".
#
use strict;
##
## The maximum number of static channels supported by Guacamole's RDP support.
##
my $GUAC_RDP_MAX_CHANNELS;
# Extract value of GUAC_RDP_MAX_CHANNELS macro from provided source
while (<>) {
if ((my $value) = m/^\s*#define\s+GUAC_RDP_MAX_CHANNELS\s+(\d+)\s*$/) {
$GUAC_RDP_MAX_CHANNELS = $value;
}
}
open OUTPUT, ">", "_generated_channel_entry_wrappers.c";
# Generate required headers
print OUTPUT <<"EOF";
#include "plugins/channels.h"
#include <freerdp/channels/channels.h>
#include <freerdp/freerdp.h>
EOF
# Generate wrapper definitions for PVIRTUALCHANNELENTRYEX entry point variant
print OUTPUT <<"EOF" for (1..$GUAC_RDP_MAX_CHANNELS);
static BOOL guac_rdp_plugin_entry_ex_wrapper$_(PCHANNEL_ENTRY_POINTS_EX entry_points_ex, PVOID init_handle) {
return guac_rdp_wrapped_entry_ex[$_ - 1](entry_points_ex, init_handle);
}
EOF
# Generate wrapper definitions for PVIRTUALCHANNELENTRY entry point variant
print OUTPUT <<"EOF" for (1..$GUAC_RDP_MAX_CHANNELS);
static BOOL guac_rdp_plugin_entry_wrapper$_(PCHANNEL_ENTRY_POINTS entry_points) {
return guac_rdp_wrapped_entry[$_ - 1](entry_points);
}
EOF
# Populate lookup table of PVIRTUALCHANNELENTRYEX wrapper functions
print OUTPUT "PVIRTUALCHANNELENTRYEX guac_rdp_entry_ex_wrappers[$GUAC_RDP_MAX_CHANNELS] = {\n";
print OUTPUT " guac_rdp_plugin_entry_ex_wrapper$_,\n" for (1..$GUAC_RDP_MAX_CHANNELS);
print OUTPUT "};\n";
# Populate lookup table of PVIRTUALCHANNELENTRY wrapper functions
print OUTPUT "PVIRTUALCHANNELENTRY guac_rdp_entry_wrappers[$GUAC_RDP_MAX_CHANNELS] = {\n";
print OUTPUT " guac_rdp_plugin_entry_wrapper$_,\n" for (1..$GUAC_RDP_MAX_CHANNELS);
print OUTPUT "};\n";

View File

@ -0,0 +1,306 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/common-svc.h"
#include <freerdp/svc.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
#include <winpr/wtsapi.h>
#include <winpr/wtypes.h>
#include <stdlib.h>
/**
* Event handler for events which deal with data transmitted over an open SVC.
* This specific implementation of the event handler currently handles only the
* CHANNEL_EVENT_DATA_RECEIVED event, delegating actual handling of that event
* to guac_rdp_common_svc_process_receive().
*
* The FreeRDP requirements for this function follow those of the
* VirtualChannelOpenEventEx callback defined within Microsoft's RDP API:
*
* https://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa514754%28v%3dmsdn.10%29
*
* @param user_param
* The pointer to arbitrary data originally passed via the first parameter
* of the pVirtualChannelInitEx() function call when the associated channel
* was initialized. The pVirtualChannelInitEx() function is exposed within
* the channel entry points structure.
*
* @param open_handle
* The handle which identifies the channel itself, typically referred to
* within the FreeRDP source as OpenHandle.
*
* @param event
* An integer representing the event that should be handled. This will be
* either CHANNEL_EVENT_DATA_RECEIVED, CHANNEL_EVENT_WRITE_CANCELLED, or
* CHANNEL_EVENT_WRITE_COMPLETE.
*
* @param data
* The data received, for CHANNEL_EVENT_DATA_RECEIVED events, and the value
* passed as user data to pVirtualChannelWriteEx() for
* CHANNEL_EVENT_WRITE_* events (note that user data for
* pVirtualChannelWriteEx() as implemented by FreeRDP MUST either be NULL
* or a wStream containing the data written).
*
* @param data_length
* The number of bytes of event-specific data.
*
* @param total_length
* The total number of bytes expected to be received from the RDP server
* due to this single write (from the server's perspective). Each write may
* actually be split into multiple chunks, thus resulting in multiple
* receive events for the same logical block of data. The relationship
* between chunks is indicated with the CHANNEL_FLAG_FIRST and
* CHANNEL_FLAG_LAST flags.
*
* @param data_flags
* The result of a bitwise OR of the CHANNEL_FLAG_* flags which apply to
* the data received. This value is relevant only to
* CHANNEL_EVENT_DATA_RECEIVED events. Valid flags are CHANNEL_FLAG_FIRST,
* CHANNEL_FLAG_LAST, and CHANNEL_FLAG_ONLY. The flag CHANNEL_FLAG_MIDDLE
* is not itself a flag, but the absence of both CHANNEL_FLAG_FIRST and
* CHANNEL_FLAG_LAST.
*/
static VOID guac_rdp_common_svc_handle_open_event(LPVOID user_param,
DWORD open_handle, UINT event, LPVOID data, UINT32 data_length,
UINT32 total_length, UINT32 data_flags) {
/* Ignore all events except for received data */
if (event != CHANNEL_EVENT_DATA_RECEIVED)
return;
guac_rdp_common_svc* svc = (guac_rdp_common_svc*) user_param;
/* Validate relevant handle matches that of SVC */
if (open_handle != svc->_open_handle) {
guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data "
"received from within the remote desktop session for SVC "
"\"%s\" are being dropped because the relevant open handle "
"(0x%X) does not match the open handle of the SVC (0x%X).",
data_length, svc->name, open_handle, svc->_open_handle);
return;
}
/* If receiving first chunk, allocate sufficient space for all remaining
* chunks */
if (data_flags & CHANNEL_FLAG_FIRST) {
/* Limit maximum received size */
if (total_length > GUAC_SVC_MAX_ASSEMBLED_LENGTH) {
guac_client_log(svc->client, GUAC_LOG_WARNING, "RDP server has "
"requested to send a sequence of %i bytes, but this "
"exceeds the maximum buffer space of %i bytes. Received "
"data may be truncated.", total_length,
GUAC_SVC_MAX_ASSEMBLED_LENGTH);
total_length = GUAC_SVC_MAX_ASSEMBLED_LENGTH;
}
svc->_input_stream = Stream_New(NULL, total_length);
}
/* Add chunk to buffer only if sufficient space remains */
if (Stream_EnsureRemainingCapacity(svc->_input_stream, data_length))
Stream_Write(svc->_input_stream, data, data_length);
else
guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data "
"received from within the remote desktop session for SVC "
"\"%s\" are being dropped because the maximum available "
"space for received data has been exceeded.", data_length,
svc->name);
/* Fire event once last chunk has been received */
if (data_flags & CHANNEL_FLAG_LAST) {
Stream_SealLength(svc->_input_stream);
Stream_SetPosition(svc->_input_stream, 0);
/* Handle channel-specific data receipt tasks, if any */
if (svc->_receive_handler)
svc->_receive_handler(svc, svc->_input_stream);
Stream_Free(svc->_input_stream, TRUE);
}
}
/**
* Processes a CHANNEL_EVENT_CONNECTED event, completing the
* connection/initialization process of the channel.
*
* @param rdpsnd
* The guac_rdp_common_svc structure representing the channel.
*/
static void guac_rdp_common_svc_process_connect(guac_rdp_common_svc* svc) {
/* Open FreeRDP side of connected channel */
UINT32 open_status =
svc->_entry_points.pVirtualChannelOpenEx(svc->_init_handle,
&svc->_open_handle, svc->_channel_def.name,
guac_rdp_common_svc_handle_open_event);
/* Warn if the channel cannot be opened after all */
if (open_status != CHANNEL_RC_OK) {
guac_client_log(svc->client, GUAC_LOG_WARNING, "SVC \"%s\" could not "
"be opened: %s (error %i)", svc->name,
WTSErrorToString(open_status), open_status);
return;
}
/* Handle channel-specific connect tasks, if any */
if (svc->_connect_handler)
svc->_connect_handler(svc);
/* Channel is now ready */
guac_client_log(svc->client, GUAC_LOG_DEBUG, "SVC \"%s\" connected.",
svc->name);
}
/**
* Processes a CHANNEL_EVENT_TERMINATED event, freeing all resources associated
* with the channel.
*
* @param svc
* The guac_rdp_common_svc structure representing the channel.
*/
static void guac_rdp_common_svc_process_terminate(guac_rdp_common_svc* svc) {
/* Handle channel-specific termination tasks, if any */
if (svc->_terminate_handler)
svc->_terminate_handler(svc);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "SVC \"%s\" disconnected.",
svc->name);
free(svc);
}
/**
* Event handler for events which deal with the overall lifecycle of an SVC.
* This specific implementation of the event handler currently handles only
* CHANNEL_EVENT_CONNECTED and CHANNEL_EVENT_TERMINATED events, delegating
* actual handling of those events to guac_rdp_common_svc_process_connect() and
* guac_rdp_common_svc_process_terminate() respectively.
*
* The FreeRDP requirements for this function follow those of the
* VirtualChannelInitEventEx callback defined within Microsoft's RDP API:
*
* https://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa514727%28v%3dmsdn.10%29
*
* @param user_param
* The pointer to arbitrary data originally passed via the first parameter
* of the pVirtualChannelInitEx() function call when the associated channel
* was initialized. The pVirtualChannelInitEx() function is exposed within
* the channel entry points structure.
*
* @param init_handle
* The handle which identifies the client connection, typically referred to
* within the FreeRDP source as pInitHandle.
*
* @param event
* An integer representing the event that should be handled. This will be
* either CHANNEL_EVENT_CONNECTED, CHANNEL_EVENT_DISCONNECTED,
* CHANNEL_EVENT_INITIALIZED, CHANNEL_EVENT_TERMINATED, or
* CHANNEL_EVENT_V1_CONNECTED.
*
* @param data
* NULL in all cases except the CHANNEL_EVENT_CONNECTED event, in which
* case this is a null-terminated string containing the name of the server.
*
* @param data_length
* The number of bytes of data, if any.
*/
static VOID guac_rdp_common_svc_handle_init_event(LPVOID user_param,
LPVOID init_handle, UINT event, LPVOID data, UINT data_length) {
guac_rdp_common_svc* svc = (guac_rdp_common_svc*) user_param;
/* Validate relevant handle matches that of SVC */
if (init_handle != svc->_init_handle) {
guac_client_log(svc->client, GUAC_LOG_WARNING, "An init event (#%i) "
"for SVC \"%s\" has been dropped because the relevant init "
"handle (0x%X) does not match the init handle of the SVC "
"(0x%X).", event, svc->name, init_handle, svc->_init_handle);
return;
}
switch (event) {
/* The remote desktop side of the SVC has been connected */
case CHANNEL_EVENT_CONNECTED:
guac_rdp_common_svc_process_connect(svc);
break;
/* The channel has disconnected and now must be cleaned up */
case CHANNEL_EVENT_TERMINATED:
guac_rdp_common_svc_process_terminate(svc);
break;
}
}
/**
* Entry point for FreeRDP plugins. This function is automatically invoked when
* the plugin is loaded.
*
* @param entry_points
* Functions and data specific to the FreeRDP side of the virtual channel
* and plugin. This structure must be copied within implementation-specific
* storage such that the functions it references can be invoked when
* needed.
*
* @param init_handle
* The handle which identifies the client connection, typically referred to
* within the FreeRDP source as pInitHandle. This handle is also provided
* to the channel init event handler. The handle must eventually be used
* within the channel open event handler to obtain a handle to the channel
* itself.
*
* @return
* TRUE if the plugin has initialized successfully, FALSE otherwise.
*/
BOOL VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX entry_points,
PVOID init_handle) {
CHANNEL_ENTRY_POINTS_FREERDP_EX* entry_points_ex =
(CHANNEL_ENTRY_POINTS_FREERDP_EX*) entry_points;
/* Get structure representing the Guacamole side of the SVC from plugin
* parameters */
guac_rdp_common_svc* svc = (guac_rdp_common_svc*) entry_points_ex->pExtendedData;
/* Copy FreeRDP data into SVC structure for future reference */
svc->_entry_points = *entry_points_ex;
svc->_init_handle = init_handle;
/* Complete initialization */
if (svc->_entry_points.pVirtualChannelInitEx(svc, svc, init_handle,
&svc->_channel_def, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
guac_rdp_common_svc_handle_init_event) != CHANNEL_RC_OK) {
return FALSE;
}
return TRUE;
}

View File

@ -17,24 +17,15 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/audio-input/audio-buffer.h"
#include "plugins/guacai/guacai-messages.h"
#include "ai_messages.h"
#include "audio_input.h"
#include "rdp.h" #include "rdp.h"
#include <stdlib.h>
#include <freerdp/freerdp.h>
#include <freerdp/constants.h>
#include <freerdp/dvc.h> #include <freerdp/dvc.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else
#include "compat/winpr-stream.h" #include <stdlib.h>
#endif
/** /**
* Reads AUDIO_FORMAT data from the given stream into the given struct. * Reads AUDIO_FORMAT data from the given stream into the given struct.
@ -113,6 +104,19 @@ static void guac_rdp_ai_send_incoming_data(IWTSVirtualChannel* channel) {
} }
/**
* Sends a Data PDU along the given channel. A Data PDU is used by the client
* to send actual audio data following a Data Incoming PDU.
*
* @param channel
* The channel along which the PDU should be sent.
*
* @param buffer
* The audio data to send.
*
* @param length
* The number of bytes of audio data to send.
*/
static void guac_rdp_ai_send_data(IWTSVirtualChannel* channel, static void guac_rdp_ai_send_data(IWTSVirtualChannel* channel,
char* buffer, int length) { char* buffer, int length) {
@ -286,7 +290,7 @@ void guac_rdp_ai_process_formats(guac_client* client,
} }
static void guac_rdp_ai_flush_packet(char* buffer, int length, void* data) { void guac_rdp_ai_flush_packet(char* buffer, int length, void* data) {
IWTSVirtualChannel* channel = (IWTSVirtualChannel*) data; IWTSVirtualChannel* channel = (IWTSVirtualChannel*) data;

View File

@ -17,19 +17,15 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_AI_MESSAGES_H #ifndef GUAC_RDP_PLUGINS_GUACAI_MESSAGES_H
#define GUAC_RDP_AI_MESSAGES_H #define GUAC_RDP_PLUGINS_GUACAI_MESSAGES_H
#include "config.h" #include "channels/audio-input/audio-buffer.h"
#include <freerdp/dvc.h> #include <freerdp/dvc.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else #include <winpr/wtypes.h>
#include "compat/winpr-stream.h"
#endif
/** /**
* The format tag associated with raw wave audio (WAVE_FORMAT_PCM). This format * The format tag associated with raw wave audio (WAVE_FORMAT_PCM). This format
@ -212,5 +208,14 @@ void guac_rdp_ai_process_open(guac_client* client,
void guac_rdp_ai_process_formatchange(guac_client* client, void guac_rdp_ai_process_formatchange(guac_client* client,
IWTSVirtualChannel* channel, wStream* stream); IWTSVirtualChannel* channel, wStream* stream);
/**
* Audio buffer flush handler which sends audio data along the active audio
* input channel using a Data Incoming PDU and Data PDU. The arbitrary data
* provided to the handler by the audio buffer implementation is in this case
* the IWTSVirtualChannel structure representing the active audio input
* channel.
*/
guac_rdp_audio_buffer_flush_handler guac_rdp_ai_flush_packet;
#endif #endif

View File

@ -17,27 +17,20 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "channels/audio-input/audio-buffer.h"
#include "plugins/guacai/guacai.h"
#include "ai_messages.h" #include "plugins/guacai/guacai-messages.h"
#include "ai_service.h" #include "plugins/ptr-string.h"
#include "audio_input.h"
#include "ptr_string.h"
#include "rdp.h" #include "rdp.h"
#include <stdlib.h>
#include <string.h>
#include <freerdp/freerdp.h>
#include <freerdp/constants.h>
#include <freerdp/dvc.h> #include <freerdp/dvc.h>
#include <freerdp/settings.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
#else #include <winpr/wtsapi.h>
#include "compat/winpr-stream.h" #include <winpr/wtypes.h>
#endif
#include <stdlib.h>
/** /**
* Handles the given data received along the AUDIO_INPUT channel of the RDP * Handles the given data received along the AUDIO_INPUT channel of the RDP
@ -95,43 +88,9 @@ static void guac_rdp_ai_handle_data(guac_client* client,
} }
#ifdef LEGACY_IWTSVIRTUALCHANNELCALLBACK
/** /**
* Callback which is invoked when data is received along a connection to the * Callback which is invoked when data is received along a connection to the
* AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.1 and older. * AUDIO_INPUT plugin.
*
* @param channel_callback
* The IWTSVirtualChannelCallback structure to which this callback was
* originally assigned.
*
* @param size
* The number of bytes received.
*
* @param buffer
* A buffer containing all bytes received.
*
* @return
* Always zero.
*/
static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback,
UINT32 size, BYTE* buffer) {
guac_rdp_ai_channel_callback* ai_channel_callback =
(guac_rdp_ai_channel_callback*) channel_callback;
IWTSVirtualChannel* channel = ai_channel_callback->channel;
/* Invoke generalized (API-independent) data handler */
wStream* stream = Stream_New(buffer, size);
guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream);
Stream_Free(stream, FALSE);
return 0;
}
#else
/**
* Callback which is invoked when data is received along a connection to the
* AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.2 and newer.
* *
* @param channel_callback * @param channel_callback
* The IWTSVirtualChannelCallback structure to which this callback was * The IWTSVirtualChannelCallback structure to which this callback was
@ -143,7 +102,7 @@ static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback,
* @return * @return
* Always zero. * Always zero.
*/ */
static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, static UINT guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback,
wStream* stream) { wStream* stream) {
guac_rdp_ai_channel_callback* ai_channel_callback = guac_rdp_ai_channel_callback* ai_channel_callback =
@ -153,10 +112,9 @@ static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback,
/* Invoke generalized (API-independent) data handler */ /* Invoke generalized (API-independent) data handler */
guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream); guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream);
return 0; return CHANNEL_RC_OK;
} }
#endif
/** /**
* Callback which is invoked when a connection to the AUDIO_INPUT plugin is * Callback which is invoked when a connection to the AUDIO_INPUT plugin is
@ -169,7 +127,7 @@ static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback,
* @return * @return
* Always zero. * Always zero.
*/ */
static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) { static UINT guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) {
guac_rdp_ai_channel_callback* ai_channel_callback = guac_rdp_ai_channel_callback* ai_channel_callback =
(guac_rdp_ai_channel_callback*) channel_callback; (guac_rdp_ai_channel_callback*) channel_callback;
@ -184,7 +142,7 @@ static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) {
guac_rdp_audio_buffer_end(audio_buffer); guac_rdp_audio_buffer_end(audio_buffer);
free(ai_channel_callback); free(ai_channel_callback);
return 0; return CHANNEL_RC_OK;
} }
@ -219,7 +177,7 @@ static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) {
* @return * @return
* Always zero. * Always zero.
*/ */
static int guac_rdp_ai_new_connection( static UINT guac_rdp_ai_new_connection(
IWTSListenerCallback* listener_callback, IWTSVirtualChannel* channel, IWTSListenerCallback* listener_callback, IWTSVirtualChannel* channel,
BYTE* data, int* accept, BYTE* data, int* accept,
IWTSVirtualChannelCallback** channel_callback) { IWTSVirtualChannelCallback** channel_callback) {
@ -243,7 +201,8 @@ static int guac_rdp_ai_new_connection(
/* Return callback through pointer */ /* Return callback through pointer */
*channel_callback = (IWTSVirtualChannelCallback*) ai_channel_callback; *channel_callback = (IWTSVirtualChannelCallback*) ai_channel_callback;
return 0;
return CHANNEL_RC_OK;
} }
@ -261,7 +220,7 @@ static int guac_rdp_ai_new_connection(
* @return * @return
* Always zero. * Always zero.
*/ */
static int guac_rdp_ai_initialize(IWTSPlugin* plugin, static UINT guac_rdp_ai_initialize(IWTSPlugin* plugin,
IWTSVirtualChannelManager* manager) { IWTSVirtualChannelManager* manager) {
/* Allocate new listener callback */ /* Allocate new listener callback */
@ -281,7 +240,7 @@ static int guac_rdp_ai_initialize(IWTSPlugin* plugin,
manager->CreateListener(manager, "AUDIO_INPUT", 0, manager->CreateListener(manager, "AUDIO_INPUT", 0,
(IWTSListenerCallback*) ai_listener_callback, NULL); (IWTSListenerCallback*) ai_listener_callback, NULL);
return 0; return CHANNEL_RC_OK;
} }
@ -295,7 +254,7 @@ static int guac_rdp_ai_initialize(IWTSPlugin* plugin,
* @return * @return
* Always zero. * Always zero.
*/ */
static int guac_rdp_ai_terminated(IWTSPlugin* plugin) { static UINT guac_rdp_ai_terminated(IWTSPlugin* plugin) {
guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin; guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin;
guac_client* client = ai_plugin->client; guac_client* client = ai_plugin->client;
@ -305,7 +264,7 @@ static int guac_rdp_ai_terminated(IWTSPlugin* plugin) {
free(ai_plugin); free(ai_plugin);
guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin unloaded."); guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin unloaded.");
return 0; return CHANNEL_RC_OK;
} }
@ -315,13 +274,8 @@ static int guac_rdp_ai_terminated(IWTSPlugin* plugin) {
int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) {
/* Pull guac_client from arguments */ /* Pull guac_client from arguments */
#ifdef HAVE_ADDIN_ARGV
ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints);
guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]); guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]);
#else
RDP_PLUGIN_DATA* data = pEntryPoints->GetPluginData(pEntryPoints);
guac_client* client = (guac_client*) guac_rdp_string_to_ptr(data->data[1]);
#endif
/* Pull previously-allocated plugin */ /* Pull previously-allocated plugin */
guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*)
@ -343,7 +297,7 @@ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) {
guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin loaded."); guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin loaded.");
} }
return 1; return CHANNEL_RC_OK;
} }

View File

@ -17,14 +17,12 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_AI_SERVICE_H #ifndef GUAC_RDP_PLUGINS_GUACAI_H
#define GUAC_RDP_AI_SERVICE_H #define GUAC_RDP_PLUGINS_GUACAI_H
#include "config.h"
#include <freerdp/freerdp.h>
#include <freerdp/constants.h> #include <freerdp/constants.h>
#include <freerdp/dvc.h> #include <freerdp/dvc.h>
#include <freerdp/freerdp.h>
#include <guacamole/client.h> #include <guacamole/client.h>
/** /**

View File

@ -17,13 +17,9 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "plugins/ptr-string.h"
#include "ptr_string.h"
#include <guacamole/client.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
void guac_rdp_ptr_to_string(void* data, char* str) { void guac_rdp_ptr_to_string(void* data, char* str) {

View File

@ -17,10 +17,8 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_PTR_STRING_H #ifndef GUAC_RDP_PLUGINS_PTR_STRING_H
#define GUAC_RDP_PTR_STRING_H #define GUAC_RDP_PLUGINS_PTR_STRING_H
#include "config.h"
#include <guacamole/client.h> #include <guacamole/client.h>

View File

@ -17,21 +17,21 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "color.h"
#include "client.h"
#include "common/cursor.h" #include "common/cursor.h"
#include "common/display.h" #include "common/display.h"
#include "common/surface.h"
#include "pointer.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_pointer.h"
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <freerdp/codec/color.h>
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/gdi/gdi.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <winpr/crt.h>
#include <stdlib.h> BOOL 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_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -41,17 +41,18 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) {
rdp_client->display, pointer->width, pointer->height); rdp_client->display, pointer->width, pointer->height);
/* Allocate data for image */ /* Allocate data for image */
unsigned char* data = unsigned char* data = _aligned_malloc(pointer->width * pointer->height * 4, 16);
(unsigned char*) malloc(pointer->width * pointer->height * 4);
cairo_surface_t* surface; cairo_surface_t* surface;
/* Convert to alpha cursor if mask data present */ /* Convert to alpha cursor if mask data present */
if (pointer->andMaskData && pointer->xorMaskData) if (pointer->andMaskData && pointer->xorMaskData)
freerdp_alpha_cursor_convert(data, freerdp_image_copy_from_pointer_data(data,
pointer->xorMaskData, pointer->andMaskData, guac_rdp_get_native_pixel_format(TRUE), 0, 0, 0,
pointer->width, pointer->height, pointer->xorBpp, pointer->width, pointer->height, pointer->xorMaskData,
((rdp_freerdp_context*) context)->clrconv); pointer->lengthXorMask, pointer->andMaskData,
pointer->lengthAndMask, pointer->xorBpp,
&context->gdi->palette);
/* Create surface from image data */ /* Create surface from image data */
surface = cairo_image_surface_create_for_data( surface = cairo_image_surface_create_for_data(
@ -63,14 +64,16 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) {
/* Free surface */ /* Free surface */
cairo_surface_destroy(surface); cairo_surface_destroy(surface);
free(data); _aligned_free(data);
/* Remember buffer */ /* Remember buffer */
((guac_rdp_pointer*) pointer)->layer = buffer; ((guac_rdp_pointer*) pointer)->layer = buffer;
return TRUE;
} }
void guac_rdp_pointer_set(rdpContext* context, rdpPointer* pointer) { BOOL guac_rdp_pointer_set(rdpContext* context, const rdpPointer* pointer) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -80,6 +83,8 @@ void guac_rdp_pointer_set(rdpContext* context, rdpPointer* pointer) {
pointer->xPos, pointer->yPos, pointer->xPos, pointer->yPos,
((guac_rdp_pointer*) pointer)->layer->surface); ((guac_rdp_pointer*) pointer)->layer->surface);
return TRUE;
} }
void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) { void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) {
@ -91,13 +96,31 @@ void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) {
/* Free buffer */ /* Free buffer */
guac_common_display_free_buffer(rdp_client->display, buffer); guac_common_display_free_buffer(rdp_client->display, buffer);
/* NOTE: FreeRDP-allocated memory for the rdpPointer will be automatically
* released after this free handler is invoked */
} }
void guac_rdp_pointer_set_null(rdpContext* context) { BOOL guac_rdp_pointer_set_null(rdpContext* context) {
/* STUB */
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Set cursor to empty/blank graphic */
guac_common_cursor_set_blank(rdp_client->display->cursor);
return TRUE;
} }
void guac_rdp_pointer_set_default(rdpContext* context) { BOOL guac_rdp_pointer_set_default(rdpContext* context) {
/* STUB */
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Set cursor to embedded pointer */
guac_common_cursor_set_pointer(rdp_client->display->cursor);
return TRUE;
} }

View File

@ -17,14 +17,14 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_RDP_POINTER_H
#define GUAC_RDP_POINTER_H
#ifndef _GUAC_RDP_RDP_POINTER_H
#define _GUAC_RDP_RDP_POINTER_H
#include "config.h"
#include "common/display.h" #include "common/display.h"
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/graphics.h>
#include <winpr/wtypes.h>
/** /**
* Guacamole-specific rdpPointer data. * Guacamole-specific rdpPointer data.
@ -52,8 +52,11 @@ typedef struct guac_rdp_pointer {
* *
* @param pointer * @param pointer
* The pointer to cache. * The pointer to cache.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer); BOOL guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer);
/** /**
* Sets the given cached pointer as the current pointer. The given pointer must * Sets the given cached pointer as the current pointer. The given pointer must
@ -64,8 +67,11 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer);
* *
* @param pointer * @param pointer
* The pointer to set as the current mouse pointer. * The pointer to set as the current mouse pointer.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_pointer_set(rdpContext* context, rdpPointer* pointer); BOOL guac_rdp_pointer_set(rdpContext* context, const rdpPointer* pointer);
/** /**
* Frees all Guacamole-related data associated with the given pointer, allowing * Frees all Guacamole-related data associated with the given pointer, allowing
@ -84,8 +90,11 @@ void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer);
* *
* @param context * @param context
* The rdpContext associated with the current RDP session. * The rdpContext associated with the current RDP session.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_pointer_set_null(rdpContext* context); BOOL guac_rdp_pointer_set_null(rdpContext* context);
/** /**
* Sets the system-dependent (as in dependent on the client system) default * Sets the system-dependent (as in dependent on the client system) default
@ -93,7 +102,10 @@ void guac_rdp_pointer_set_null(rdpContext* context);
* *
* @param context * @param context
* The rdpContext associated with the current RDP session. * The rdpContext associated with the current RDP session.
*
* @return
* TRUE if successful, FALSE otherwise.
*/ */
void guac_rdp_pointer_set_default(rdpContext* context); BOOL guac_rdp_pointer_set_default(rdpContext* context);
#endif #endif

View File

@ -17,9 +17,7 @@
* under the License. * under the License.
*/ */
#include "print-job.h"
#include "config.h"
#include "rdp_print_job.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>

View File

@ -20,8 +20,6 @@
#ifndef GUAC_RDP_PRINT_JOB_H #ifndef GUAC_RDP_PRINT_JOB_H
#define GUAC_RDP_PRINT_JOB_H #define GUAC_RDP_PRINT_JOB_H
#include "config.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/user.h> #include <guacamole/user.h>
@ -79,6 +77,9 @@ typedef enum guac_rdp_print_job_state {
*/ */
typedef struct guac_rdp_print_job { typedef struct guac_rdp_print_job {
/**
* The Guacamole client associated with the RDP session.
*/
guac_client* client; guac_client* client;
/** /**

View File

@ -17,28 +17,30 @@
* under the License. * under the License.
*/ */
#include "config.h" #include "bitmap.h"
#include "channels/audio-input/audio-buffer.h"
#include "audio_input.h" #include "channels/audio-input/audio-input.h"
#include "channels/cliprdr.h"
#include "channels/disp.h"
#include "channels/pipe-svc.h"
#include "channels/rail.h"
#include "channels/rdpdr/rdpdr.h"
#include "channels/rdpsnd/rdpsnd.h"
#include "client.h" #include "client.h"
#include "color.h"
#include "common/cursor.h" #include "common/cursor.h"
#include "common/display.h" #include "common/display.h"
#include "common/recording.h" #include "common/recording.h"
#include "dvc.h" #include "config.h"
#include "error.h" #include "error.h"
#include "fs.h"
#include "gdi.h"
#include "glyph.h"
#include "keyboard.h" #include "keyboard.h"
#include "plugins/channels.h"
#include "pointer.h"
#include "print-job.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_bitmap.h"
#include "rdp_cliprdr.h"
#include "rdp_disp.h"
#include "rdp_fs.h"
#include "rdp_print_job.h"
#include "rdp_gdi.h"
#include "rdp_glyph.h"
#include "rdp_pointer.h"
#include "rdp_rail.h"
#include "rdp_stream.h"
#include "rdp_svc.h"
#ifdef ENABLE_COMMON_SSH #ifdef ENABLE_COMMON_SSH
#include "common-ssh/sftp.h" #include "common-ssh/sftp.h"
@ -46,6 +48,7 @@
#include "common-ssh/user.h" #include "common-ssh/user.h"
#endif #endif
#include <freerdp/addin.h>
#include <freerdp/cache/bitmap.h> #include <freerdp/cache/bitmap.h>
#include <freerdp/cache/brush.h> #include <freerdp/cache/brush.h>
#include <freerdp/cache/glyph.h> #include <freerdp/cache/glyph.h>
@ -53,349 +56,130 @@
#include <freerdp/cache/palette.h> #include <freerdp/cache/palette.h>
#include <freerdp/cache/pointer.h> #include <freerdp/cache/pointer.h>
#include <freerdp/channels/channels.h> #include <freerdp/channels/channels.h>
#include <freerdp/client/channels.h>
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/gdi/gdi.h>
#include <freerdp/graphics.h>
#include <freerdp/primary.h>
#include <freerdp/settings.h>
#include <freerdp/update.h>
#include <guacamole/audio.h> #include <guacamole/audio.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>
#include <guacamole/timestamp.h> #include <guacamole/timestamp.h>
#include <winpr/error.h>
#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H #include <winpr/synch.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> #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 <poll.h>
#include <pthread.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <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) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_settings* settings = rdp_client->settings;
if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) {
#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;
/* Init module with current display size */
guac_rdp_disp_set_size(rdp_client->disp, rdp_client->settings,
context->instance, 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) { BOOL rdp_freerdp_pre_connect(freerdp* instance) {
rdpContext* context = instance->context; rdpContext* context = instance->context;
rdpChannels* channels = context->channels; rdpGraphics* graphics = context->graphics;
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_settings* settings = rdp_client->settings; guac_rdp_settings* settings = rdp_client->settings;
rdpBitmap* bitmap; /* Push desired settings to FreeRDP */
rdpGlyph* glyph; guac_rdp_push_settings(client, settings, instance);
rdpPointer* pointer;
rdpPrimaryUpdate* primary;
CLRCONV* clrconv;
guac_rdp_dvc_list* dvc_list = guac_rdp_dvc_list_alloc();
#ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER
/* Init FreeRDP add-in provider */ /* Init FreeRDP add-in provider */
freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); 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 "disp" plugin for display update */ /* Load "disp" plugin for display update */
if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE)
guac_rdp_disp_load_plugin(instance->context, dvc_list); guac_rdp_disp_load_plugin(context);
#endif
/* Load "AUDIO_INPUT" plugin for audio input*/ /* Load "AUDIO_INPUT" plugin for audio input*/
if (settings->enable_audio_input) { if (settings->enable_audio_input) {
rdp_client->audio_input = guac_rdp_audio_buffer_alloc(); rdp_client->audio_input = guac_rdp_audio_buffer_alloc();
guac_rdp_audio_load_plugin(instance->context, dvc_list); guac_rdp_audio_load_plugin(instance->context);
} }
/* Load clipboard plugin if not disabled */ /* Load "cliprdr" service if not disabled */
if (!(settings->disable_copy && settings->disable_paste) if (!(settings->disable_copy && settings->disable_paste))
&& freerdp_channels_load_plugin(channels, instance->settings, guac_rdp_clipboard_load_plugin(rdp_client->clipboard, context);
"cliprdr", NULL)) { else
guac_client_log(client, GUAC_LOG_WARNING, guac_client_log(client, GUAC_LOG_WARNING,
"Failed to load cliprdr plugin. Clipboard will not work."); "Copy and paste are both disabled. Clipboard plugin will not be loaded.");
}
/* If RDPSND/RDPDR required, load them */ /* If RDPSND/RDPDR required, load them */
if (settings->printing_enabled if (settings->printing_enabled
|| settings->drive_enabled || settings->drive_enabled
|| settings->audio_enabled) { || settings->audio_enabled) {
guac_rdpdr_load_plugin(context);
/* Load RDPDR plugin */ guac_rdpsnd_load_plugin(context);
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 */ /* Load RAIL plugin if RemoteApp in use */
if (settings->remote_app != NULL) { if (settings->remote_app != NULL)
guac_rdp_rail_load_plugin(context);
#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] = settings->remote_app;
plugin_data[0].data[1] = settings->remote_app_dir;
plugin_data[0].data[2] = 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 */ /* Load SVC plugin instances for all static channels */
if (settings->svc_names != NULL) { if (settings->svc_names != NULL) {
char** current = settings->svc_names; char** current = settings->svc_names;
do { do {
guac_rdp_pipe_svc_load_plugin(context, *current);
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); } while (*(++current) != NULL);
} }
/* Load DRDYNVC plugin if required */ /* Load plugin providing Dynamic Virtual Channel support, if required */
if (guac_rdp_load_drdynvc(instance->context, dvc_list)) if (instance->settings->SupportDynamicChannels &&
guac_freerdp_channels_load_plugin(context, "drdynvc",
instance->settings)) {
guac_client_log(client, GUAC_LOG_WARNING, guac_client_log(client, GUAC_LOG_WARNING,
"Failed to load drdynvc plugin. Display update and audio " "Failed to load drdynvc plugin. Display update and audio "
"input support will be disabled."); "input support will be disabled.");
}
/* Dynamic virtual channel list is no longer needed */ /* Init FreeRDP internal GDI implementation */
guac_rdp_dvc_list_free(dvc_list); if (!gdi_init(instance, guac_rdp_get_native_pixel_format(FALSE)))
return FALSE;
/* 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 */ /* Set up bitmap handling */
bitmap = calloc(1, sizeof(rdpBitmap)); rdpBitmap bitmap = *graphics->Bitmap_Prototype;
bitmap->size = sizeof(guac_rdp_bitmap); bitmap.size = sizeof(guac_rdp_bitmap);
bitmap->New = guac_rdp_bitmap_new; bitmap.New = guac_rdp_bitmap_new;
bitmap->Free = guac_rdp_bitmap_free; bitmap.Free = guac_rdp_bitmap_free;
bitmap->Paint = guac_rdp_bitmap_paint; bitmap.Paint = guac_rdp_bitmap_paint;
bitmap->Decompress = guac_rdp_bitmap_decompress; bitmap.SetSurface = guac_rdp_bitmap_setsurface;
bitmap->SetSurface = guac_rdp_bitmap_setsurface; graphics_register_bitmap(graphics, &bitmap);
graphics_register_bitmap(context->graphics, bitmap);
free(bitmap);
/* Set up glyph handling */ /* Set up glyph handling */
glyph = calloc(1, sizeof(rdpGlyph)); rdpGlyph glyph = *graphics->Glyph_Prototype;
glyph->size = sizeof(guac_rdp_glyph); glyph.size = sizeof(guac_rdp_glyph);
glyph->New = guac_rdp_glyph_new; glyph.New = guac_rdp_glyph_new;
glyph->Free = guac_rdp_glyph_free; glyph.Free = guac_rdp_glyph_free;
glyph->Draw = guac_rdp_glyph_draw; glyph.Draw = guac_rdp_glyph_draw;
glyph->BeginDraw = guac_rdp_glyph_begindraw; glyph.BeginDraw = guac_rdp_glyph_begindraw;
glyph->EndDraw = guac_rdp_glyph_enddraw; glyph.EndDraw = guac_rdp_glyph_enddraw;
graphics_register_glyph(context->graphics, glyph); graphics_register_glyph(graphics, &glyph);
free(glyph);
/* Set up pointer handling */ /* Set up pointer handling */
pointer = calloc(1, sizeof(rdpPointer)); rdpPointer pointer = *graphics->Pointer_Prototype;
pointer->size = sizeof(guac_rdp_pointer); pointer.size = sizeof(guac_rdp_pointer);
pointer->New = guac_rdp_pointer_new; pointer.New = guac_rdp_pointer_new;
pointer->Free = guac_rdp_pointer_free; pointer.Free = guac_rdp_pointer_free;
pointer->Set = guac_rdp_pointer_set; pointer.Set = guac_rdp_pointer_set;
#ifdef HAVE_RDPPOINTER_SETNULL pointer.SetNull = guac_rdp_pointer_set_null;
pointer->SetNull = guac_rdp_pointer_set_null; pointer.SetDefault = guac_rdp_pointer_set_default;
#endif graphics_register_pointer(graphics, &pointer);
#ifdef HAVE_RDPPOINTER_SETDEFAULT
pointer->SetDefault = guac_rdp_pointer_set_default;
#endif
graphics_register_pointer(context->graphics, pointer);
free(pointer);
/* Set up GDI */ /* Set up GDI */
instance->update->DesktopResize = guac_rdp_gdi_desktop_resize; instance->update->DesktopResize = guac_rdp_gdi_desktop_resize;
instance->update->EndPaint = guac_rdp_gdi_end_paint; instance->update->EndPaint = guac_rdp_gdi_end_paint;
instance->update->Palette = guac_rdp_gdi_palette_update;
instance->update->SetBounds = guac_rdp_gdi_set_bounds; instance->update->SetBounds = guac_rdp_gdi_set_bounds;
primary = instance->update->primary; rdpPrimaryUpdate* primary = instance->update->primary;
primary->DstBlt = guac_rdp_gdi_dstblt; primary->DstBlt = guac_rdp_gdi_dstblt;
primary->PatBlt = guac_rdp_gdi_patblt;
primary->ScrBlt = guac_rdp_gdi_scrblt; primary->ScrBlt = guac_rdp_gdi_scrblt;
primary->MemBlt = guac_rdp_gdi_memblt; primary->MemBlt = guac_rdp_gdi_memblt;
primary->OpaqueRect = guac_rdp_gdi_opaquerect;
pointer_cache_register_callbacks(instance->update); pointer_cache_register_callbacks(instance->update);
glyph_cache_register_callbacks(instance->update); glyph_cache_register_callbacks(instance->update);
@ -404,39 +188,6 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
offscreen_cache_register_callbacks(instance->update); offscreen_cache_register_callbacks(instance->update);
palette_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; return TRUE;
} }
@ -499,8 +250,9 @@ static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username,
* @return * @return
* TRUE if the certificate passes verification, FALSE otherwise. * TRUE if the certificate passes verification, FALSE otherwise.
*/ */
static BOOL rdp_freerdp_verify_certificate(freerdp* instance, char* subject, static DWORD rdp_freerdp_verify_certificate(freerdp* instance,
char* issuer, char* fingerprint) { const char* common_name, const char* subject, const char* issuer,
const char* fingerprint, BOOL host_mismatch) {
rdpContext* context = instance->context; rdpContext* context = instance->context;
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
@ -510,44 +262,14 @@ static BOOL rdp_freerdp_verify_certificate(freerdp* instance, char* subject,
/* Bypass validation if ignore_certificate given */ /* Bypass validation if ignore_certificate given */
if (rdp_client->settings->ignore_certificate) { if (rdp_client->settings->ignore_certificate) {
guac_client_log(client, GUAC_LOG_INFO, "Certificate validation bypassed"); guac_client_log(client, GUAC_LOG_INFO, "Certificate validation bypassed");
return TRUE; return 2; /* Accept only for this session */
} }
guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed"); guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed");
return FALSE; return 0; /* Reject certificate */
} }
/**
* 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 */
}
/** /**
* Waits for messages from the RDP server for the given number of milliseconds. * Waits for messages from the RDP server for the given number of milliseconds.
* *
@ -566,78 +288,30 @@ static int rdp_guac_client_wait_for_messages(guac_client* client,
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
freerdp* rdp_inst = rdp_client->rdp_inst; freerdp* rdp_inst = rdp_client->rdp_inst;
rdpChannels* channels = rdp_inst->context->channels;
int result; HANDLE handles[GUAC_RDP_MAX_FILE_DESCRIPTORS];
int index; int num_handles = freerdp_get_event_handles(rdp_inst->context, handles,
GUAC_RDP_MAX_FILE_DESCRIPTORS);
/* List of all file descriptors which we may read data from */ /* Wait for data and construct a reasonable frame */
void* read_fds[GUAC_RDP_MAX_FILE_DESCRIPTORS]; int result = WaitForMultipleObjects(num_handles, handles, FALSE,
int read_count = 0; timeout_msecs);
/* List of all file descriptors which data may be written to. These will /* Translate WaitForMultipleObjects() return values */
* ultimately be ignored, but FreeRDP requires that both read and write switch (result) {
* file descriptors be retrieved simultaneously. */
void* write_fds[GUAC_RDP_MAX_FILE_DESCRIPTORS];
int write_count = 0;
struct pollfd fds[GUAC_RDP_MAX_FILE_DESCRIPTORS]; /* Timeout elapsed before wait could complete */
case WAIT_TIMEOUT:
/* Get RDP file descriptors */
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 RDP channel file descriptors */
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;
}
/* If no file descriptors, error */
if (read_count == 0) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"No file descriptors associated with RDP connection.");
return -1;
}
/* Populate poll() array of read file descriptors */
for (index = 0; index < read_count; index++) {
struct pollfd* current = &fds[index];
/* Init poll() array element with RDP file descriptor */
current->fd = (int)(long) (read_fds[index]);
current->events = POLLIN;
current->revents = 0;
}
/* Wait until data can be read from RDP file descriptors */
result = poll(fds, read_count, timeout_msecs);
if (result < 0) {
/* If error ignorable, pretend timout occurred */
if (errno == EAGAIN
|| errno == EWOULDBLOCK
|| errno == EINPROGRESS
|| errno == EINTR)
return 0; return 0;
/* Otherwise, return as error */ /* Attempt to wait failed due to an error */
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, case WAIT_FAILED:
"Error waiting for file descriptor.");
return -1; return -1;
} }
/* Return wait result */ /* Wait was successful */
return result; return 1;
} }
@ -686,29 +360,16 @@ static int guac_rdp_handle_connection(guac_client* client) {
rdp_client->current_surface = rdp_client->display->default_surface; rdp_client->current_surface = rdp_client->display->default_surface;
rdp_client->requested_clipboard_format = CB_FORMAT_TEXT;
rdp_client->available_svc = guac_common_list_alloc(); rdp_client->available_svc = guac_common_list_alloc();
#ifdef HAVE_FREERDP_CHANNELS_GLOBAL_INIT
freerdp_channels_global_init();
#endif
/* Init client */ /* Init client */
freerdp* rdp_inst = freerdp_new(); freerdp* rdp_inst = freerdp_new();
rdp_inst->PreConnect = rdp_freerdp_pre_connect; rdp_inst->PreConnect = rdp_freerdp_pre_connect;
rdp_inst->PostConnect = rdp_freerdp_post_connect;
rdp_inst->Authenticate = rdp_freerdp_authenticate; rdp_inst->Authenticate = rdp_freerdp_authenticate;
rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate; rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate;
rdp_inst->ReceiveChannelData = __guac_receive_channel_data;
/* Allocate FreeRDP context */ /* Allocate FreeRDP context */
#ifdef LEGACY_FREERDP
rdp_inst->context_size = sizeof(rdp_freerdp_context);
#else
rdp_inst->ContextSize = sizeof(rdp_freerdp_context); 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); freerdp_context_new(rdp_inst);
((rdp_freerdp_context*) rdp_inst->context)->client = client; ((rdp_freerdp_context*) rdp_inst->context)->client = client;
@ -720,9 +381,6 @@ static int guac_rdp_handle_connection(guac_client* client) {
/* Set default pointer */ /* Set default pointer */
guac_common_cursor_set_pointer(rdp_client->display->cursor); guac_common_cursor_set_pointer(rdp_client->display->cursor);
/* Push desired settings to FreeRDP */
guac_rdp_push_settings(client, settings, rdp_inst);
/* Connect to RDP server */ /* Connect to RDP server */
if (!freerdp_connect(rdp_inst)) { if (!freerdp_connect(rdp_inst)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND,
@ -732,7 +390,6 @@ static int guac_rdp_handle_connection(guac_client* client) {
/* Connection complete */ /* Connection complete */
rdp_client->rdp_inst = rdp_inst; rdp_client->rdp_inst = rdp_inst;
rdpChannels* channels = rdp_inst->context->channels;
guac_timestamp last_frame_end = guac_timestamp_current(); guac_timestamp last_frame_end = guac_timestamp_current();
@ -744,9 +401,7 @@ static int guac_rdp_handle_connection(guac_client* client) {
&& !guac_rdp_disp_reconnect_needed(rdp_client->disp)) { && !guac_rdp_disp_reconnect_needed(rdp_client->disp)) {
/* Update remote display size */ /* Update remote display size */
pthread_mutex_lock(&(rdp_client->rdp_lock));
guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst); guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
/* Wait for data and construct a reasonable frame */ /* Wait for data and construct a reasonable frame */
int wait_result = rdp_guac_client_wait_for_messages(client, int wait_result = rdp_guac_client_wait_for_messages(client,
@ -762,42 +417,15 @@ static int guac_rdp_handle_connection(guac_client* client) {
guac_timestamp frame_end; guac_timestamp frame_end;
int frame_remaining; int frame_remaining;
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* Check the libfreerdp fds */ /* Check the libfreerdp fds */
if (!freerdp_check_fds(rdp_inst) if (!freerdp_check_event_handles(rdp_inst->context)) {
|| !freerdp_channels_check_fds(channels, rdp_inst)) {
/* Flag connection failure */ /* Flag connection failure */
wait_result = -1; wait_result = -1;
pthread_mutex_unlock(&(rdp_client->rdp_lock));
break; break;
} }
/* 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);
}
pthread_mutex_unlock(&(rdp_client->rdp_lock));
/* Calculate time remaining in frame */ /* Calculate time remaining in frame */
frame_end = guac_timestamp_current(); frame_end = guac_timestamp_current();
frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION
@ -830,9 +458,7 @@ static int guac_rdp_handle_connection(guac_client* client) {
} }
/* Test whether the RDP server is closing the connection */ /* Test whether the RDP server is closing the connection */
pthread_mutex_lock(&(rdp_client->rdp_lock));
int connection_closing = freerdp_shall_disconnect(rdp_inst); int connection_closing = freerdp_shall_disconnect(rdp_inst);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
/* Close connection cleanly if server is disconnecting */ /* Close connection cleanly if server is disconnecting */
if (connection_closing) if (connection_closing)
@ -858,16 +484,13 @@ static int guac_rdp_handle_connection(guac_client* client) {
guac_rdp_print_job_free(rdp_client->active_job); guac_rdp_print_job_free(rdp_client->active_job);
} }
pthread_mutex_lock(&(rdp_client->rdp_lock));
/* Disconnect client and channels */ /* Disconnect client and channels */
freerdp_channels_close(channels, rdp_inst);
freerdp_channels_free(channels);
freerdp_disconnect(rdp_inst); freerdp_disconnect(rdp_inst);
/* Clean up FreeRDP internal GDI implementation */
gdi_free(rdp_inst);
/* Clean up RDP client context */ /* Clean up RDP client context */
freerdp_clrconv_free(((rdp_freerdp_context*) rdp_inst->context)->clrconv);
cache_free(rdp_inst->context->cache);
freerdp_context_free(rdp_inst); freerdp_context_free(rdp_inst);
/* Clean up RDP client */ /* Clean up RDP client */
@ -883,8 +506,6 @@ static int guac_rdp_handle_connection(guac_client* client) {
/* Free display */ /* Free display */
guac_common_display_free(rdp_client->display); guac_common_display_free(rdp_client->display);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
/* Client is now disconnected */ /* Client is now disconnected */
guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected"); guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected");

View File

@ -20,24 +20,19 @@
#ifndef GUAC_RDP_H #ifndef GUAC_RDP_H
#define GUAC_RDP_H #define GUAC_RDP_H
#include "config.h" #include "channels/audio-input/audio-buffer.h"
#include "channels/cliprdr.h"
#include "audio_input.h" #include "channels/disp.h"
#include "common/clipboard.h" #include "common/clipboard.h"
#include "common/display.h" #include "common/display.h"
#include "common/list.h" #include "common/list.h"
#include "common/recording.h" #include "common/recording.h"
#include "common/surface.h" #include "common/surface.h"
#include "config.h"
#include "fs.h"
#include "keyboard.h" #include "keyboard.h"
#include "rdp_disp.h" #include "print-job.h"
#include "rdp_fs.h" #include "settings.h"
#include "rdp_print_job.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 #ifdef ENABLE_COMMON_SSH
#include "common-ssh/sftp.h" #include "common-ssh/sftp.h"
@ -45,6 +40,12 @@
#include "common-ssh/user.h" #include "common-ssh/user.h"
#endif #endif
#include <freerdp/codec/color.h>
#include <freerdp/freerdp.h>
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <winpr/wtypes.h>
#include <pthread.h> #include <pthread.h>
#include <stdint.h> #include <stdint.h>
@ -95,17 +96,9 @@ typedef struct guac_rdp_client {
guac_rdp_keyboard* keyboard; guac_rdp_keyboard* keyboard;
/** /**
* The current clipboard contents. * The current state of the clipboard and the CLIPRDR channel.
*/ */
guac_common_clipboard* clipboard; guac_rdp_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. * Audio output, if any.
@ -160,13 +153,6 @@ typedef struct guac_rdp_client {
*/ */
guac_common_list* available_svc; guac_common_list* available_svc;
/**
* Lock which is locked and unlocked for each RDP message, and for each
* part of the RDP client instance which may be dynamically freed and
* reallocated during reconnection.
*/
pthread_mutex_t rdp_lock;
/** /**
* Common attributes for locks. * Common attributes for locks.
*/ */
@ -191,11 +177,6 @@ typedef struct rdp_freerdp_context {
*/ */
guac_client* client; 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. * The current color palette, as received from the RDP server.
*/ */

View File

@ -1,274 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "client.h"
#include "common/clipboard.h"
#include "common/iconv.h"
#include "rdp.h"
#include "rdp_cliprdr.h"
#include <freerdp/channels/channels.h>
#include <freerdp/freerdp.h>
#include <freerdp/utils/event.h>
#include <guacamole/client.h>
#ifdef ENABLE_WINPR
#include <winpr/wtypes.h>
#else
#include "compat/winpr-wtypes.h"
#endif
#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H
#include <freerdp/client/cliprdr.h>
#else
#include "compat/client-cliprdr.h"
#endif
#include <stdlib.h>
#include <string.h>
void guac_rdp_process_cliprdr_event(guac_client* client, wMessage* event) {
#ifdef LEGACY_EVENT
switch (event->event_type) {
#else
switch (GetMessageType(event->id)) {
#endif
case CliprdrChannel_MonitorReady:
guac_rdp_process_cb_monitor_ready(client, event);
break;
case CliprdrChannel_FormatList:
guac_rdp_process_cb_format_list(client,
(RDP_CB_FORMAT_LIST_EVENT*) event);
break;
case CliprdrChannel_DataRequest:
guac_rdp_process_cb_data_request(client,
(RDP_CB_DATA_REQUEST_EVENT*) event);
break;
case CliprdrChannel_DataResponse:
guac_rdp_process_cb_data_response(client,
(RDP_CB_DATA_RESPONSE_EVENT*) event);
break;
default:
#ifdef LEGACY_EVENT
guac_client_log(client, GUAC_LOG_INFO,
"Unknown cliprdr event type: 0x%x",
event->event_type);
#else
guac_client_log(client, GUAC_LOG_INFO,
"Unknown cliprdr event type: 0x%x",
GetMessageType(event->id));
#endif
}
}
void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event) {
rdpChannels* channels =
((guac_rdp_client*) client->data)->rdp_inst->context->channels;
RDP_CB_FORMAT_LIST_EVENT* format_list =
(RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(
CliprdrChannel_Class,
CliprdrChannel_FormatList,
NULL, NULL);
/* Received notification of clipboard support. */
/* Respond with supported format list */
format_list->formats = (UINT32*) malloc(sizeof(UINT32)*2);
format_list->formats[0] = CB_FORMAT_TEXT;
format_list->formats[1] = CB_FORMAT_UNICODETEXT;
format_list->num_formats = 2;
freerdp_channels_send_event(channels, (wMessage*) format_list);
}
/**
* 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) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
rdpChannels* channels = rdp_client->rdp_inst->context->channels;
/* Create new data request */
RDP_CB_DATA_REQUEST_EVENT* data_request =
(RDP_CB_DATA_REQUEST_EVENT*) freerdp_event_new(
CliprdrChannel_Class,
CliprdrChannel_DataRequest,
NULL, NULL);
/* Set to requested format */
rdp_client->requested_clipboard_format = format;
data_request->format = format;
/* Send request */
freerdp_channels_send_event(channels, (wMessage*) data_request);
}
void guac_rdp_process_cb_format_list(guac_client* client,
RDP_CB_FORMAT_LIST_EVENT* event) {
int formats = 0;
/* Received notification of available data */
int i;
for (i=0; i<event->num_formats; i++) {
/* If plain text available, request it */
if (event->formats[i] == CB_FORMAT_TEXT)
formats |= GUAC_RDP_CLIPBOARD_FORMAT_CP1252;
else if (event->formats[i] == CB_FORMAT_UNICODETEXT)
formats |= GUAC_RDP_CLIPBOARD_FORMAT_UTF16;
}
/* Prefer Unicode to plain text */
if (formats & GUAC_RDP_CLIPBOARD_FORMAT_UTF16) {
__guac_rdp_cb_request_format(client, CB_FORMAT_UNICODETEXT);
return;
}
/* Use plain text if Unicode unavailable */
if (formats & GUAC_RDP_CLIPBOARD_FORMAT_CP1252) {
__guac_rdp_cb_request_format(client, CB_FORMAT_TEXT);
return;
}
/* Ignore if no supported format available */
guac_client_log(client, GUAC_LOG_INFO, "Ignoring unsupported clipboard data");
}
void guac_rdp_process_cb_data_request(guac_client* client,
RDP_CB_DATA_REQUEST_EVENT* event) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
rdpChannels* channels = rdp_client->rdp_inst->context->channels;
guac_iconv_write* writer;
const char* input = rdp_client->clipboard->buffer;
char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH);
RDP_CB_DATA_RESPONSE_EVENT* data_response;
/* Determine output encoding */
switch (event->format) {
case CB_FORMAT_TEXT:
writer = GUAC_WRITE_CP1252;
break;
case CB_FORMAT_UNICODETEXT:
writer = GUAC_WRITE_UTF16;
break;
default:
guac_client_log(client, GUAC_LOG_ERROR,
"Server requested unsupported clipboard data type");
free(output);
return;
}
/* Create new data response */
data_response = (RDP_CB_DATA_RESPONSE_EVENT*) freerdp_event_new(
CliprdrChannel_Class,
CliprdrChannel_DataResponse,
NULL, NULL);
/* Set data and size */
data_response->data = (BYTE*) output;
guac_iconv(GUAC_READ_UTF8, &input, rdp_client->clipboard->length,
writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH);
data_response->size = ((BYTE*) output) - data_response->data;
/* Send response */
freerdp_channels_send_event(channels, (wMessage*) data_response);
}
void guac_rdp_process_cb_data_response(guac_client* client,
RDP_CB_DATA_RESPONSE_EVENT* event) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH];
/* Ignore received text if outbound clipboard transfer is disabled */
if (rdp_client->settings->disable_copy)
return;
guac_iconv_read* reader;
const char* input = (char*) event->data;
char* output = received_data;
/* Find correct source encoding */
switch (rdp_client->requested_clipboard_format) {
/* Non-Unicode */
case CB_FORMAT_TEXT:
reader = GUAC_READ_CP1252;
break;
/* Unicode (UTF-16) */
case CB_FORMAT_UNICODETEXT:
reader = GUAC_READ_UTF16;
break;
default:
guac_client_log(client, GUAC_LOG_ERROR, "Requested clipboard data in "
"unsupported format %i",
rdp_client->requested_clipboard_format);
return;
}
/* Convert send clipboard data */
if (guac_iconv(reader, &input, event->size,
GUAC_WRITE_UTF8, &output, sizeof(received_data))) {
int length = strnlen(received_data, sizeof(received_data));
guac_common_clipboard_reset(rdp_client->clipboard, "text/plain");
guac_common_clipboard_append(rdp_client->clipboard, received_data, length);
guac_common_clipboard_send(rdp_client->clipboard, client);
}
}

Some files were not shown because too many files have changed in this diff Show More