diff --git a/Dockerfile b/Dockerfile index 094842cc..83bce0fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,9 +37,9 @@ ARG BUILD_DIR=/tmp/guacd-docker-BUILD ARG BUILD_DEPENDENCIES=" \ autoconf \ automake \ + freerdp2-dev \ gcc \ libcairo2-dev \ - libfreerdp-dev \ libjpeg62-turbo-dev \ libossp-uuid-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 \ ${PREFIX_DIR}/sbin/guacd \ ${PREFIX_DIR}/lib/libguac-client-*.so \ - ${PREFIX_DIR}/lib/freerdp/guac*.so \ + ${PREFIX_DIR}/lib/freerdp2/guac*.so \ > ${PREFIX_DIR}/DEPENDENCIES # Use same Debian as the base for the runtime image @@ -92,7 +92,6 @@ ENV GUACD_LOG_LEVEL=info ARG RUNTIME_DEPENDENCIES=" \ ca-certificates \ ghostscript \ - libfreerdp-plugins-standard \ fonts-liberation \ fonts-dejavu \ xfonts-terminus" @@ -108,7 +107,7 @@ RUN apt-get update && \ # Link FreeRDP plugins into proper path 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 4822 diff --git a/configure.ac b/configure.ac index 7fc7d33e..5b0fbe8a 100644 --- a/configure.ac +++ b/configure.ac @@ -555,537 +555,143 @@ then fi # -# FreeRDP +# FreeRDP 2 (libfreerdp2, libfreerdp-client2, and libwinpr2) # -have_freerdp=disabled -RDP_LIBS= -WINPR_LIBS= +have_freerdp2=disabled AC_ARG_WITH([rdp], [AS_HELP_STRING([--with-rdp], [support RDP @<:@default=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" then - have_winpr=yes - have_freerdp=yes - legacy_freerdp_extensions=no - 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([ + have_freerdp2=yes + PKG_CHECK_MODULES([RDP], [freerdp2 freerdp-client2 winpr2], + [CPPFLAGS="${RDP_CFLAGS} -Werror $CPPFLAGS"], + [AC_MSG_WARN([ -------------------------------------------- - Unable to find libfreerdp-core / libfreerdp + Unable to find FreeRDP (libfreerdp2 / libfreerdp-client2 / libwinpr2) RDP will be disabled. --------------------------------------------]) - have_freerdp=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"]) + have_freerdp2=no]) fi # Available color conversion functions -if test "x${have_freerdp}" = "xyes" +if test "x$have_freerdp2" = "xyes" then - AC_CHECK_DECL([freerdp_convert_gdi_order_color], - [AC_DEFINE([HAVE_FREERDP_CONVERT_GDI_ORDER_COLOR],, - [Whether freerdp_convert_gdi_order_color() is defined])],, - [#include ]) - 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 2.0.0-rc3 and older referred to FreeRDPConvertColor() as + # ConvertColor() + AC_CHECK_DECL([FreeRDPConvertColor], + [AC_DEFINE([HAVE_FREERDPCONVERTCOLOR],, + [Whether FreeRDPConvertColor() is defined])],, + [#include ]) + fi -# Check for interval polling in plugins -if test "x${have_freerdp}" = "xyes" +# Glyph callback variants +if test "x${have_freerdp2}" = "xyes" then - AC_CHECK_MEMBERS([rdpSvcPlugin.interval_ms],,, - [[#include ]]) + + # FreeRDP 2.0.0-rc3 and older used UINT32 for integer parameters to all + # rdpGlyph callbacks + AC_MSG_CHECKING([whether rdpGlyph callbacks accept INT32 integer parameters]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ + + #include + #include + #include + + BOOL test_begindraw(rdpContext* context, INT32 x, INT32 y, + INT32 width, INT32 height, UINT32 fgcolor, UINT32 bgcolor, + BOOL redundant); + + rdpGlyph glyph = { + .BeginDraw = test_begindraw + }; + + int main() { + return (int) glyph.BeginDraw(NULL, 0, 0, 0, 0, 0, 0, FALSE); + } + + ]])], + [AC_MSG_RESULT([yes])] + [AC_DEFINE([FREERDP_GLYPH_CALLBACKS_ACCEPT_INT32],, + [Whether rdpGlyph callbacks accept INT32 integer parameters])], + [AC_MSG_RESULT([no])]) + fi -# Keyboard layout header -if test "x${have_freerdp}" = "xyes" +# CLIPRDR callback variants +if test "x${have_freerdp2}" = "xyes" then - AC_CHECK_HEADERS([freerdp/locale/keyboard.h],, - [AC_CHECK_HEADERS([freerdp/kbd/layouts.h],, - [AC_MSG_WARN([ - -------------------------------------------- - Unable to find keyboard layout headers - RDP will be disabled. - --------------------------------------------]) - have_freerdp=no])]) + + # 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 + #include + + UINT test_monitor_ready(CliprdrClientContext* cliprdr, + const CLIPRDR_MONITOR_READY* monitor_ready); + + CliprdrClientContext context = { + .MonitorReady = test_monitor_ready + }; + + int main() { + return (int) context.MonitorReady(NULL, NULL); + } + + ]])], + [AC_MSG_RESULT([yes])] + [AC_DEFINE([FREERDP_CLIPRDR_CALLBACKS_REQUIRE_CONST],, + [Whether CLIPRDR callbacks require const for the final parameter])], + [AC_MSG_RESULT([no])]) + fi -# New headers defining addins -if test "x${have_freerdp}" = "xyes" +# RAIL callback variants +if test "x${have_freerdp2}" = "xyes" then - AC_CHECK_HEADERS([freerdp/addin.h freerdp/client/channels.h]) + + # FreeRDP 2.0.0-rc3 and older did not use const for RAIL callbacks + AC_MSG_CHECKING([whether RAIL callbacks require const for their final parameter]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ + + #include + #include + + 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 -# 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 ])], - [#include - #include ]) -fi +# Restore CPPFLAGS, removing FreeRDP-specific options needed for testing +CPPFLAGS="$OLDCPPFLAGS" -# 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 ]])],, - [#include - #include ]) -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 ]]) -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 ]]) -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 ]) -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 ]) - - AC_CHECK_DECL([freerdp_channels_global_init], - [AC_DEFINE([HAVE_FREERDP_CHANNELS_GLOBAL_INIT],, - [Whether freerdp_channels_global_init() is defined])],, - [#include ]) -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 ]) -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 ])]) -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 ]]) - - # If not current, check for legacy interface - if test "x${freerdp_interface}" = "xunknown" - then - AC_CHECK_MEMBERS([freerdp.context_size], - [freerdp_interface=legacy],, - [[#include ]]) - 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 ]]) - - # 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 ]]) - 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 ]]) - - # Legacy interface may not have AudioCapture settings - AC_CHECK_MEMBERS([rdpSettings.audio_capture],, - [rdpsettings_audiocapture=no], - [[#include ]]) - - # Legacy interface may not have DeviceRedirection settings - AC_CHECK_MEMBERS([rdpSettings.device_redirection],, - [rdpsettings_deviceredirection=no], - [[#include ]]) - - 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 ]]) -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 - #include - void __decompress(rdpContext* context, - rdpBitmap* bitmap, - 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 - -# -# FreeRDP: IWTSVirtualChannelCallback -# - -if test "x${have_freerdp}" = "xyes" -then - AC_MSG_CHECKING([whether IWTSVirtualChannelCallback.OnDataReceived() uses a wStream]) - AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include - #include - #include - 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 - - int main() { - BYTE* palette = NULL; - interleaved_decompress(NULL, NULL, 0, 0, NULL, 0, 0, 0, 0, 0, 0, palette); - }]])], - [AC_MSG_RESULT([yes]) - AC_DEFINE([INTERLEAVED_DECOMPRESS_TAKES_PALETTE],, - [Whether interleaved_decompress() accepts an additional palette parameter])], - [AC_MSG_RESULT([no])]) -fi - -# Check whether planar_decompress() will handle flipping -if test "x${have_freerdp}" = "xyes" -then - AC_MSG_CHECKING([whether planar_decompress() can flip]) - AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include - - int main() { - BOOL* flip = TRUE; - planar_decompress(NULL, NULL, 0, NULL, 0, 0, 0, 0, 0, 0, flip); - }]])], - [AC_MSG_RESULT([yes]) - AC_DEFINE([PLANAR_DECOMPRESS_CAN_FLIP],, - [Whether planar_decompress() can flip])], - [AC_MSG_RESULT([no])]) -fi - -# -# FreeRDP: rdpContext -# - -# Check for rdpContext.codecs -if test "x${have_freerdp}" = "xyes" -then - AC_CHECK_MEMBERS([rdpContext.codecs], - [AC_DEFINE([FREERDP_BITMAP_REQUIRES_ALIGNED_MALLOC],, - [Whether this version of FreeRDP requires _aligned_malloc() for bitmap data])],, - [[#include ]]) -fi - -# -# FreeRDP: rdpPalette -# - -if test "x${have_freerdp}" = "xyes" -then - AC_MSG_CHECKING([whether rdpPalette.entries is static]) - AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include - 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 ]]) -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 ]]) - - # 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 ]]) - 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) +AM_CONDITIONAL([ENABLE_RDP], [test "x${have_freerdp2}" = "xyes"]) # # libssh2 @@ -1426,7 +1032,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION Library status: - freerdp ............. ${have_freerdp} + freerdp2 ............ ${have_freerdp2} pango ............... ${have_pango} libavcodec .......... ${have_libavcodec} libavutil ........... ${have_libavutil} diff --git a/src/common/surface.c b/src/common/surface.c index c31dfbf0..c86ca806 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -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 - * 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 rect The update rectangle. - * @param rect_only Non-zero if this update, by its nature, contains only - * metainformation about the update's rectangle, zero if - * the update also contains image data. - * @return Non-zero if the update should be combined with any existing update, - * zero otherwise. + * @param surface + * The surface being updated. + * + * @param rect + * The bounding rectangle of the update being made to the surface. + * + * @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) { + /* Always favor combining updates if surface is currently a purely + * server-side scratch area */ + if (!surface->realized) + return 1; + if (surface->dirty) { int combined_cost, dirty_cost, update_cost; diff --git a/src/guacd-docker/bin/link-freerdp-plugins.sh b/src/guacd-docker/bin/link-freerdp-plugins.sh index 332d4c0f..625e85b3 100755 --- a/src/guacd-docker/bin/link-freerdp-plugins.sh +++ b/src/guacd-docker/bin/link-freerdp-plugins.sh @@ -70,7 +70,7 @@ while [ -n "$1" ]; do # Determine correct install location for FreeRDP plugins FREERDP_DIR="$(where_is_freerdp "$1")" - FREERDP_PLUGIN_DIR="${FREERDP_DIR}/freerdp" + FREERDP_PLUGIN_DIR="${FREERDP_DIR}/freerdp2" # Add symbolic link if necessary if [ ! -e "$FREERDP_PLUGIN_DIR/$(basename "$1")" ]; then diff --git a/src/protocols/rdp/.gitignore b/src/protocols/rdp/.gitignore index 9f87ecb2..5cb764a7 100644 --- a/src/protocols/rdp/.gitignore +++ b/src/protocols/rdp/.gitignore @@ -4,5 +4,6 @@ _generated_runner.c test_rdp # Autogenerated sources +_generated_channel_entry_wrappers.c _generated_keymaps.c diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 97b551d0..70f7f588 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -29,218 +29,172 @@ ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-rdp.la 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 # +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 = \ -Werror -Wall -Iinclude \ @COMMON_INCLUDE@ \ @COMMON_SSH_INCLUDE@ \ - @LIBGUAC_INCLUDE@ + @LIBGUAC_INCLUDE@ \ + @RDP_CFLAGS@ libguac_client_rdp_la_LDFLAGS = \ -version-info 0:0:0 \ @CAIRO_LIBS@ \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ \ - @WINPR_LIBS@ + @RDP_LIBS@ -libguac_client_rdp_la_LIBADD = \ - @COMMON_LTLIB@ \ +libguac_client_rdp_la_LIBADD = \ + @COMMON_LTLIB@ \ @LIBGUAC_LTLIB@ # -# RDPDR +# Plugins for FreeRDP # -guacdr_cflags = \ - -Werror -Wall -Iinclude \ - @COMMON_INCLUDE@ \ - @COMMON_SSH_INCLUDE@ \ - @LIBGUAC_INCLUDE@ +freerdp_LTLIBRARIES = \ + libguac-common-svc-client.la \ + libguacai-client.la -guacdr_ldflags = \ - -module -avoid-version -shared \ - @PTHREAD_LIBS@ \ - @RDP_LIBS@ \ - @WINPR_LIBS@ +freerdpdir = ${libdir}/freerdp2 -guacdr_libadd = \ - @COMMON_LTLIB@ \ +# +# 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 \ + @LIBGUAC_INCLUDE@ \ + @RDP_CFLAGS@ + +libguac_common_svc_client_la_LDFLAGS = \ + -module -avoid-version -shared \ + @RDP_LIBS@ + +libguac_common_svc_client_la_LIBADD = \ @LIBGUAC_LTLIB@ # # Audio Input # -guacai_cflags = \ - -Werror -Wall -Iinclude \ - @COMMON_INCLUDE@ \ - @COMMON_SSH_INCLUDE@ \ - @LIBGUAC_INCLUDE@ +libguacai_client_la_SOURCES = \ + channels/audio-input/audio-buffer.c \ + plugins/guacai/guacai-messages.c \ + plugins/guacai/guacai.c \ + plugins/ptr-string.c -guacai_ldflags = \ +libguacai_client_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @COMMON_INCLUDE@ \ + @COMMON_SSH_INCLUDE@ \ + @LIBGUAC_INCLUDE@ \ + @RDP_CFLAGS@ + +libguacai_client_la_LDFLAGS = \ -module -avoid-version -shared \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ \ - @WINPR_LIBS@ + @RDP_LIBS@ -guacai_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@ \ +libguacai_client_la_LIBADD = \ + @COMMON_LTLIB@ \ @LIBGUAC_LTLIB@ # @@ -254,13 +208,18 @@ libguac_client_rdp_la_LIBADD += @COMMON_SSH_LTLIB@ endif # -# Autogenerate keymaps +# Autogenerated keymaps and channel wrapper functions # -CLEANFILES = _generated_keymaps.c -BUILT_SOURCES = _generated_keymaps.c +CLEANFILES = \ + _generated_channel_entry_wrappers.c \ + _generated_keymaps.c -rdp_keymaps = \ +BUILT_SOURCES = \ + _generated_channel_entry_wrappers.c \ + _generated_keymaps.c + +rdp_keymaps = \ $(srcdir)/keymaps/base.keymap \ $(srcdir)/keymaps/failsafe.keymap \ $(srcdir)/keymaps/de_de_qwertz.keymap \ @@ -280,71 +239,13 @@ rdp_keymaps = \ $(srcdir)/keymaps/tr_tr_qwerty.keymap _generated_keymaps.c: $(rdp_keymaps) - $(srcdir)/keymaps/generate.pl $(rdp_keymaps) + $(AM_V_GEN) $(srcdir)/keymaps/generate.pl $(rdp_keymaps) -EXTRA_DIST = \ - $(rdp_keymaps) \ - keymaps/generate.pl +_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 -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 +EXTRA_DIST = \ + $(rdp_keymaps) \ + keymaps/generate.pl \ + plugins/generate-entry-wrappers.pl diff --git a/src/protocols/rdp/rdp_bitmap.c b/src/protocols/rdp/bitmap.c similarity index 56% rename from src/protocols/rdp/rdp_bitmap.c rename to src/protocols/rdp/bitmap.c index f77bbaea..db293162 100644 --- a/src/protocols/rdp/rdp_bitmap.c +++ b/src/protocols/rdp/bitmap.c @@ -17,27 +17,16 @@ * under the License. */ -#include "config.h" - -#include "client.h" +#include "bitmap.h" #include "common/display.h" #include "common/surface.h" #include "rdp.h" -#include "rdp_bitmap.h" -#include "rdp_settings.h" #include -#include -#include #include #include -#include - -#ifdef ENABLE_WINPR +#include #include -#else -#include "compat/winpr-wtypes.h" -#endif #include #include @@ -72,30 +61,7 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) { } -void 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; - - } +BOOL guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { /* No corresponding surface yet - caching is deferred. */ ((guac_rdp_bitmap*) bitmap)->layer = NULL; @@ -103,9 +69,11 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { /* Start at zero usage */ ((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_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 */ ((guac_rdp_bitmap*) bitmap)->used++; + return TRUE; + } 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) 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_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 */ if (bitmap == NULL) { 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. */ @@ -184,80 +161,7 @@ void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL pri } -} - -#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; + return TRUE; } diff --git a/src/protocols/rdp/rdp_bitmap.h b/src/protocols/rdp/bitmap.h similarity index 57% rename from src/protocols/rdp/rdp_bitmap.h rename to src/protocols/rdp/bitmap.h index dda3f763..297230c5 100644 --- a/src/protocols/rdp/rdp_bitmap.h +++ b/src/protocols/rdp/bitmap.h @@ -17,21 +17,16 @@ * under the License. */ - -#ifndef _GUAC_RDP_RDP_BITMAP_H -#define _GUAC_RDP_RDP_BITMAP_H +#ifndef GUAC_RDP_BITMAP_H +#define GUAC_RDP_BITMAP_H #include "config.h" #include "common/display.h" #include +#include #include - -#ifdef ENABLE_WINPR #include -#else -#include "compat/winpr-wtypes.h" -#endif /** * Guacamole-specific rdpBitmap data. @@ -77,8 +72,11 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap); * * @param bitmap * 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 @@ -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 paint operation to perform, including the destination X/Y * 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. @@ -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 * surface should be reset to the primary drawing surface of the remote * 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); -#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 diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/channels/audio-input/audio-buffer.c similarity index 69% rename from src/protocols/rdp/audio_input.c rename to src/protocols/rdp/channels/audio-input/audio-buffer.c index 014ea2db..30513419 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/channels/audio-input/audio-buffer.c @@ -17,181 +17,19 @@ * under the License. */ -#include "config.h" -#include "audio_input.h" -#include "dvc.h" -#include "ptr_string.h" +#include "channels/audio-input/audio-buffer.h" #include "rdp.h" -#include -#include +#include #include #include #include #include #include -#include -#include #include - -/** - * 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); - -} +#include +#include guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() { guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer)); diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/channels/audio-input/audio-buffer.h similarity index 88% rename from src/protocols/rdp/audio_input.h rename to src/protocols/rdp/channels/audio-input/audio-buffer.h index 62806628..32f7def8 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/channels/audio-input/audio-buffer.h @@ -17,16 +17,11 @@ * under the License. */ -#ifndef GUAC_RDP_AUDIO_INPUT_H -#define GUAC_RDP_AUDIO_INPUT_H +#ifndef GUAC_RDP_CHANNELS_AUDIO_INPUT_AUDIO_BUFFER_H +#define GUAC_RDP_CHANNELS_AUDIO_INPUT_AUDIO_BUFFER_H -#include "config.h" -#include "dvc.h" - -#include #include #include - #include /** @@ -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); -/** - * 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 diff --git a/src/protocols/rdp/channels/audio-input/audio-input.c b/src/protocols/rdp/channels/audio-input/audio-input.c new file mode 100644 index 00000000..51fa1188 --- /dev/null +++ b/src/protocols/rdp/channels/audio-input/audio-input.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/** + * 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); + +} + diff --git a/src/protocols/rdp/channels/audio-input/audio-input.h b/src/protocols/rdp/channels/audio-input/audio-input.h new file mode 100644 index 00000000..a3b705f7 --- /dev/null +++ b/src/protocols/rdp/channels/audio-input/audio-input.h @@ -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 +#include + +/** + * 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 + diff --git a/src/protocols/rdp/channels/cliprdr.c b/src/protocols/rdp/channels/cliprdr.c new file mode 100644 index 00000000..6911c197 --- /dev/null +++ b/src/protocols/rdp/channels/cliprdr.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; + +} + diff --git a/src/protocols/rdp/channels/cliprdr.h b/src/protocols/rdp/channels/cliprdr.h new file mode 100644 index 00000000..4c920bfb --- /dev/null +++ b/src/protocols/rdp/channels/cliprdr.h @@ -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 +#include +#include +#include +#include +#include + +/** + * 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 + diff --git a/src/protocols/rdp/channels/common-svc.c b/src/protocols/rdp/channels/common-svc.c new file mode 100644 index 00000000..b73dd6e1 --- /dev/null +++ b/src/protocols/rdp/channels/common-svc.c @@ -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 +#include +#include +#include +#include +#include + +#include + +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); + +} + diff --git a/src/protocols/rdp/channels/common-svc.h b/src/protocols/rdp/channels/common-svc.h new file mode 100644 index 00000000..0a420bc7 --- /dev/null +++ b/src/protocols/rdp/channels/common-svc.h @@ -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 +#include +#include +#include +#include +#include +#include + +/** + * 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 + diff --git a/src/protocols/rdp/rdp_disp.c b/src/protocols/rdp/channels/disp.c similarity index 70% rename from src/protocols/rdp/rdp_disp.c rename to src/protocols/rdp/channels/disp.c index 7c7e059d..a25f6fa9 100644 --- a/src/protocols/rdp/rdp_disp.c +++ b/src/protocols/rdp/channels/disp.c @@ -17,29 +17,26 @@ * under the License. */ -#include "config.h" -#include "client.h" -#include "dvc.h" +#include "channels/disp.h" +#include "plugins/channels.h" #include "rdp.h" -#include "rdp_disp.h" -#include "rdp_settings.h" +#include "settings.h" +#include #include +#include #include #include -#ifdef HAVE_FREERDP_CLIENT_DISP_H -#include -#endif +#include +#include guac_rdp_disp* guac_rdp_disp_alloc() { guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp)); -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /* Not yet connected */ disp->disp = NULL; -#endif /* No requests have been made */ disp->last_request = guac_timestamp_current(); @@ -55,23 +52,60 @@ void guac_rdp_disp_free(guac_rdp_disp* 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 - context->settings->SupportDisplayControl = TRUE; -#endif + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + 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 */ - 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 * 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) { -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{ .Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */ .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 */ if (disp->disp != NULL) disp->disp->SendMonitorLayout(disp->disp, 1, monitors); -#endif } } diff --git a/src/protocols/rdp/rdp_disp.h b/src/protocols/rdp/channels/disp.h similarity index 75% rename from src/protocols/rdp/rdp_disp.h rename to src/protocols/rdp/channels/disp.h index 0f34fe12..2aff7c2d 100644 --- a/src/protocols/rdp/rdp_disp.h +++ b/src/protocols/rdp/channels/disp.h @@ -17,17 +17,15 @@ * under the License. */ -#ifndef GUAC_RDP_DISP_H -#define GUAC_RDP_DISP_H +#ifndef GUAC_RDP_CHANNELS_DISP_H +#define GUAC_RDP_CHANNELS_DISP_H -#include "dvc.h" -#include "rdp_settings.h" +#include "settings.h" -#include - -#ifdef HAVE_FREERDP_CLIENT_DISP_H #include -#endif +#include +#include +#include /** * The minimum value for width or height, in pixels. @@ -50,12 +48,10 @@ */ typedef struct guac_rdp_disp { -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /** * Display control interface. */ DispClientContext* disp; -#endif /** * 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 - * 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(); /** - * 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); -/** - * @param context The rdpContext associated with the active RDP session. - */ /** * 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 - * once guac_rdp_load_drdynvc() is invoked with the guac_rdp_dvc_list passed to - * this function. The "disp" plugin ultimately adds support for the Display - * Update channel. NOTE: It is still up to external code to detect when the - * "disp" channel is connected, and update the guac_rdp_disp with a call to - * guac_rdp_disp_connect(). + * to be loaded by FreeRDP's "drdynvc" plugin. The context of the plugin will + * automatically be assicated with the guac_rdp_disp instance pointed to by the + * current guac_rdp_client. The plugin will only be loaded once the "drdynvc" + * plugin is loaded. The "disp" plugin ultimately adds support for the Display + * Update channel. + * + * 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 * 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); - -#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 +void guac_rdp_disp_load_plugin(rdpContext* context); /** * 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 * to take effect. * + * @param disp + * The display update module that should be checked to determine whether a + * reconnect is required. + * * @return * Non-zero if a reconnect is needed, zero otherwise. */ diff --git a/src/protocols/rdp/channels/pipe-svc.c b/src/protocols/rdp/channels/pipe-svc.c new file mode 100644 index 00000000..2db42d68 --- /dev/null +++ b/src/protocols/rdp/channels/pipe-svc.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +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); + +} + diff --git a/src/protocols/rdp/channels/pipe-svc.h b/src/protocols/rdp/channels/pipe-svc.h new file mode 100644 index 00000000..242d4e50 --- /dev/null +++ b/src/protocols/rdp/channels/pipe-svc.h @@ -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 +#include +#include +#include +#include +#include +#include + +/** + * 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 + diff --git a/src/protocols/rdp/channels/rail.c b/src/protocols/rdp/channels/rail.c new file mode 100644 index 00000000..3e800075 --- /dev/null +++ b/src/protocols/rdp/channels/rail.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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."); + +} + diff --git a/src/protocols/rdp/compat/client-cliprdr.h b/src/protocols/rdp/channels/rail.h similarity index 58% rename from src/protocols/rdp/compat/client-cliprdr.h rename to src/protocols/rdp/channels/rail.h index f1e2aabe..08560343 100644 --- a/src/protocols/rdp/compat/client-cliprdr.h +++ b/src/protocols/rdp/channels/rail.h @@ -17,19 +17,23 @@ * under the License. */ +#ifndef GUAC_RDP_CHANNELS_RAIL_H +#define GUAC_RDP_CHANNELS_RAIL_H -#ifndef __GUAC_CLIENT_CLIPRDR_COMPAT_H -#define __GUAC_CLIENT_CLIPRDR_COMPAT_H +#include -#include "config.h" - -#include - -#define CliprdrChannel_Class RDP_EVENT_CLASS_CLIPRDR -#define CliprdrChannel_FormatList RDP_EVENT_TYPE_CB_FORMAT_LIST -#define CliprdrChannel_MonitorReady RDP_EVENT_TYPE_CB_MONITOR_READY -#define CliprdrChannel_DataRequest RDP_EVENT_TYPE_CB_DATA_REQUEST -#define CliprdrChannel_DataResponse RDP_EVENT_TYPE_CB_DATA_RESPONSE +/** + * Initializes RemoteApp support for RDP and handling of the RAIL channel. If + * failures occur, messages noting the specifics of those failures will be + * logged, and RemoteApp support will not be functional. + * + * This MUST be called within the PreConnect callback of the freerdp instance + * for RAIL support to be loaded. + * + * @param context + * The rdpContext associated with the FreeRDP side of the RDP connection. + */ +void guac_rdp_rail_load_plugin(rdpContext* context); #endif diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_dir_info.c b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-dir-info.c similarity index 71% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_dir_info.c rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-dir-info.c index c53eedc6..70eb8cd5 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_dir_info.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-dir-info.c @@ -17,26 +17,22 @@ * under the License. */ -#include "config.h" - -#include "rdpdr_service.h" -#include "rdp_fs.h" -#include "rdp_status.h" +#include "channels/rdpdr/rdpdr-fs-messages-dir-info.h" +#include "channels/rdpdr/rdpdr.h" +#include "fs.h" #include "unicode.h" -#include +#include #include - -#ifdef ENABLE_WINPR +#include #include -#else -#include "compat/winpr-stream.h" -#endif +#include #include -void guac_rdpdr_fs_process_query_directory_info(guac_rdpdr_device* device, - const char* entry_name, int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_directory_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + const char* entry_name, int entry_file_id) { 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)); /* 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) 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\")]", - __func__, file_id, entry_name); + __func__, entry_file_id, entry_name); - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 4 + 64 + utf16_length + 2); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, + 4 + 64 + utf16_length + 2); Stream_Write_UINT32(output_stream, 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, "\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, - const char* entry_name, int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_full_directory_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + const char* entry_name, int entry_file_id) { 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)); /* 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) 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\")]", - __func__, file_id, entry_name); + __func__, entry_file_id, entry_name); - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 4 + 68 + utf16_length + 2); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, + 4 + 68 + utf16_length + 2); Stream_Write_UINT32(output_stream, 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, "\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, - const char* entry_name, int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_both_directory_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + const char* entry_name, int entry_file_id) { 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)); /* 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) 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\")]", - __func__, file_id, entry_name); + __func__, entry_file_id, entry_name); - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 4 + 69 + 24 + utf16_length + 2); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, + 4 + 69 + 24 + utf16_length + 2); Stream_Write_UINT32(output_stream, 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, "\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, - const char* entry_name, int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_names_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + const char* entry_name, int entry_file_id) { 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)); /* 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) 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\")]", - __func__, file_id, entry_name); + __func__, entry_file_id, entry_name); - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 4 + 12 + utf16_length + 2); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, + 4 + 12 + utf16_length + 2); Stream_Write_UINT32(output_stream, 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, "\0\0", 2); - svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); + guac_rdp_common_svc_write(svc, output_stream); } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_dir_info.h b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-dir-info.h similarity index 54% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_dir_info.h rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-dir-info.h index 06e2074e..2f259e91 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_dir_info.h +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-dir-info.h @@ -17,58 +17,76 @@ * under the License. */ - -#ifndef __GUAC_RDPDR_FS_MESSAGES_DIR_INFO_H -#define __GUAC_RDPDR_FS_MESSAGES_DIR_INFO_H +#ifndef GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_DIR_INFO_H +#define GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_DIR_INFO_H /** * Handlers for directory queries received over the RDPDR channel via the * IRP_MJ_DIRECTORY_CONTROL major function and the IRP_MN_QUERY_DIRECTORY minor * 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 -#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 * documentation this is "defined as the file's name, time stamp, and size, or its * attributes." */ -void guac_rdpdr_fs_process_query_directory_info(guac_rdpdr_device* device, - const char* entry_name, int file_id, int completion_id); +guac_rdpdr_directory_query_handler guac_rdpdr_fs_process_query_directory_info; /** * Processes a query request for FileFullDirectoryInformation. From the * documentation, this is "defined as all the basic information, plus extended * attribute size." */ -void guac_rdpdr_fs_process_query_full_directory_info(guac_rdpdr_device* device, - const char* entry_name, int file_id, int completion_id); +guac_rdpdr_directory_query_handler guac_rdpdr_fs_process_query_full_directory_info; /** * Processes a query request for FileBothDirectoryInformation. From the * documentation, this absurdly-named request is "basic information plus * extended attribute size and short name about a file or directory." */ -void guac_rdpdr_fs_process_query_both_directory_info(guac_rdpdr_device* device, - const char* entry_name, int file_id, int completion_id); +guac_rdpdr_directory_query_handler guac_rdpdr_fs_process_query_both_directory_info; /** * Processes a query request for FileNamesInformation. From the documentation, * this is "detailed information on the names of files in a directory." */ -void guac_rdpdr_fs_process_query_names_info(guac_rdpdr_device* device, - const char* entry_name, int file_id, int completion_id); +guac_rdpdr_directory_query_handler guac_rdpdr_fs_process_query_names_info; #endif diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_file_info.c b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-file-info.c similarity index 51% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_file_info.c rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-file-info.c index b23e7bd2..0f453038 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_file_info.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-file-info.c @@ -17,44 +17,38 @@ * under the License. */ -#include "config.h" - -#include "rdpdr_service.h" -#include "rdp_fs.h" -#include "rdp_status.h" +#include "channels/rdpdr/rdpdr-fs-messages-file-info.h" +#include "channels/rdpdr/rdpdr.h" +#include "download.h" +#include "fs.h" #include "unicode.h" -#include - -#ifdef ENABLE_WINPR +#include +#include +#include #include #include -#else -#include "compat/winpr-stream.h" -#include "compat/winpr-wtypes.h" -#endif -#include #include #include -void guac_rdpdr_fs_process_query_basic_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_basic_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { wStream* output_stream; guac_rdp_fs_file* 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) return; - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 40); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, 40); Stream_Write_UINT32(output_stream, 36); 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 */ - 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, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_standard_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { wStream* output_stream; guac_rdp_fs_file* file; BOOL is_directory = FALSE; /* 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) return; - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) is_directory = TRUE; - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 26); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, 26); Stream_Write_UINT32(output_stream, 22); 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 */ - 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, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_attribute_tag_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { wStream* output_stream; guac_rdp_fs_file* 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) return; - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 12); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, 12); Stream_Write_UINT32(output_stream, 8); 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 */ - 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, - wStream* input_stream, int file_id, int completion_id, int length) { +void guac_rdpdr_fs_process_set_rename_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + int length, wStream* input_stream) { int result; 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, destination_path, sizeof(destination_path)); - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i] destination_path=\"%s\"", - __func__, file_id, destination_path); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]" + "destination_path=\"%s\"", __func__, iorequest->file_id, + destination_path); /* If file moving to \Download folder, start stream, do not move */ 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; /* 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) return; /* 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, - completion_id, STATUS_SUCCESS, 4); + iorequest->completion_id, STATUS_SUCCESS, 4); } /* Otherwise, rename as requested */ else { - result = guac_rdp_fs_rename((guac_rdp_fs*) device->data, file_id, - destination_path); + result = guac_rdp_fs_rename((guac_rdp_fs*) device->data, + iorequest->file_id, destination_path); if (result < 0) 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 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); - 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, - wStream* input_stream, int file_id, int completion_id, int length) { +void guac_rdpdr_fs_process_set_allocation_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + int length, wStream* input_stream) { int result; UINT64 size; @@ -199,50 +195,50 @@ void guac_rdpdr_fs_process_set_allocation_info(guac_rdpdr_device* device, /* Read new size */ Stream_Read_UINT64(input_stream, size); /* AllocationSize */ - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i] size=%" PRIu64, - __func__, file_id, (uint64_t) size); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] " + "size=%" PRIu64, __func__, iorequest->file_id, (uint64_t) size); /* 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) 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 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); - 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, - wStream* input_stream, int file_id, int completion_id, int length) { +void guac_rdpdr_fs_process_set_disposition_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + int length, wStream* input_stream) { wStream* output_stream; /* 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) 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 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, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); 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, - wStream* input_stream, int file_id, int completion_id, int length) { +void guac_rdpdr_fs_process_set_end_of_file_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + int length, wStream* input_stream) { int result; UINT64 size; @@ -251,38 +247,37 @@ void guac_rdpdr_fs_process_set_end_of_file_info(guac_rdpdr_device* device, /* Read new size */ Stream_Read_UINT64(input_stream, size); /* AllocationSize */ - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i] size=%" PRIu64, - __func__, file_id, (uint64_t) size); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] " + "size=%" PRIu64, __func__, iorequest->file_id, (uint64_t) size); /* 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) 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 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); - 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, - wStream* input_stream, int file_id, int completion_id, int length) { +void guac_rdpdr_fs_process_set_basic_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + int length, wStream* input_stream) { 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 */ Stream_Write_UINT32(output_stream, length); - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i] IGNORED", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] IGNORED", + __func__, iorequest->file_id); - svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); + guac_rdp_common_svc_write(svc, output_stream); } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_file_info.h b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-file-info.h similarity index 51% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_file_info.h rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-file-info.h index aa53eadf..f7a8784f 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_file_info.h +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-file-info.h @@ -17,85 +17,103 @@ * under the License. */ - -#ifndef __GUAC_RDPDR_FS_MESSAGES_FILE_INFO_H -#define __GUAC_RDPDR_FS_MESSAGES_FILE_INFO_H +#ifndef GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_FILE_INFO_H +#define GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_FILE_INFO_H /** * Handlers for file queries received over the RDPDR channel via the * 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 -#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 * "used to query a file for the times of creation, last access, last write, * and change, in addition to file attribute information." */ -void guac_rdpdr_fs_process_query_basic_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_basic_info; /** * Processes a query for FileStandardInformation. From the documentation, this * is "used to query for file information such as allocation size, end-of-file * position, and number of links." */ -void guac_rdpdr_fs_process_query_standard_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_standard_info; /** * Processes a query for FileAttributeTagInformation. From the documentation * 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, - wStream* input_stream, int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_attribute_tag_info; /** * Process a set operation for FileRenameInformation. From the documentation, * this operation is used to rename a file. */ -void guac_rdpdr_fs_process_set_rename_info(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id, int length); +guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_rename_info; /** * Process a set operation for FileAllocationInformation. From the * documentation, this operation is used to set a file's allocation size. */ -void guac_rdpdr_fs_process_set_allocation_info(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id, int length); +guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_allocation_info; /** * Process a set operation for FileDispositionInformation. From the * documentation, this operation is used to mark a file for deletion. */ -void guac_rdpdr_fs_process_set_disposition_info(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id, int length); +guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_disposition_info; /** * Process a set operation for FileEndOfFileInformation. From the * documentation, this operation is used "to set end-of-file information for * a file." */ -void guac_rdpdr_fs_process_set_end_of_file_info(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id, int length); +guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_end_of_file_info; /** * Process a set operation for FileBasicInformation. From the documentation, * this is "used to set file information such as the times of creation, last * access, last write, and change, in addition to file attributes." */ -void guac_rdpdr_fs_process_set_basic_info(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id, int length); +guac_rdpdr_set_information_request_handler guac_rdpdr_fs_process_set_basic_info; #endif diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_vol_info.c b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-vol-info.c similarity index 60% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_vol_info.c rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-vol-info.c index c1072957..0ce46844 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_vol_info.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-vol-info.c @@ -17,33 +17,29 @@ * 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 "rdpdr_service.h" -#include "rdp_fs.h" -#include "rdp_status.h" - -#include +#include #include - -#ifdef ENABLE_WINPR +#include +#include +#include #include #include -#else -#include "compat/winpr-stream.h" -#include "compat/winpr-wtypes.h" -#endif -void guac_rdpdr_fs_process_query_volume_info(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_volume_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { 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, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); Stream_Write_UINT32(output_stream, 17 + GUAC_FILESYSTEM_LABEL_LENGTH); 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 */ 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, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_size_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { guac_rdp_fs_info info = {0}; guac_rdp_fs_get_info((guac_rdp_fs*) device->data, &info); 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, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); Stream_Write_UINT32(output_stream, 24); 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, 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, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_device_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { 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, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); Stream_Write_UINT32(output_stream, 8); Stream_Write_UINT32(output_stream, FILE_DEVICE_DISK); /* DeviceType */ 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, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_attribute_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { int name_len = guac_utf8_strlen(device->device_name); 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, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); Stream_Write_UINT32(output_stream, 12 + name_len); 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(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, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_full_size_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { guac_rdp_fs_info info = {0}; guac_rdp_fs_get_info((guac_rdp_fs*) device->data, &info); 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, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", __func__, + iorequest->file_id); Stream_Write_UINT32(output_stream, 32); 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, info.block_size); /* BytesPerSector */ - svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); + guac_rdp_common_svc_write(svc, output_stream); } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_vol_info.h b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-vol-info.h similarity index 61% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_vol_info.h rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-vol-info.h index 0c82a2f1..b8c9522e 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages_vol_info.h +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages-vol-info.h @@ -17,59 +17,47 @@ * under the License. */ - -#ifndef __GUAC_RDPDR_FS_MESSAGES_VOL_INFO_H -#define __GUAC_RDPDR_FS_MESSAGES_VOL_INFO_H +#ifndef GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_VOL_INFO_H +#define GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_VOL_INFO_H /** * Handlers for directory queries received over the RDPDR channel via the * IRP_MJ_DIRECTORY_CONTROL major function and the IRP_MN_QUERY_DIRECTORY minor * 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 -#else -#include "compat/winpr-stream.h" -#endif /** * Processes a query request for FileFsVolumeInformation. According to the * documentation, this is "used to query information for a volume on which a * file system is mounted." */ -void guac_rdpdr_fs_process_query_volume_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_volume_info; /** * Processes a query request for FileFsSizeInformation. */ -void guac_rdpdr_fs_process_query_size_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_size_info; /** * Processes a query request for FileFsAttributeInformation. */ -void guac_rdpdr_fs_process_query_attribute_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_attribute_info; /** * Processes a query request for FileFsFullSizeInformation. */ -void guac_rdpdr_fs_process_query_full_size_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_full_size_info; /** * Processes a query request for FileFsDeviceInformation. */ -void guac_rdpdr_fs_process_query_device_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_device_info; #endif diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages.c b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.c similarity index 56% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages.c rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.c index 54d30861..529eea57 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.c @@ -17,36 +17,29 @@ * under the License. */ -#include "config.h" - -#include "rdpdr_fs_messages_dir_info.h" -#include "rdpdr_fs_messages_file_info.h" -#include "rdpdr_fs_messages.h" -#include "rdpdr_fs_messages_vol_info.h" -#include "rdpdr_messages.h" -#include "rdpdr_service.h" -#include "rdp_fs.h" -#include "rdp_status.h" +#include "channels/common-svc.h" +#include "channels/rdpdr/rdpdr-fs-messages-dir-info.h" +#include "channels/rdpdr/rdpdr-fs-messages-file-info.h" +#include "channels/rdpdr/rdpdr-fs-messages-vol-info.h" +#include "channels/rdpdr/rdpdr-fs-messages.h" +#include "channels/rdpdr/rdpdr.h" +#include "download.h" +#include "fs.h" #include "unicode.h" -#include +#include #include - -#ifdef ENABLE_WINPR +#include #include #include -#else -#include "compat/winpr-stream.h" -#include "compat/winpr-wtypes.h" -#endif -#include #include #include #include -void guac_rdpdr_fs_process_create(guac_rdpdr_device* device, - wStream* input_stream, int completion_id) { +void guac_rdpdr_fs_process_create(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { wStream* output_stream; int file_id; @@ -73,7 +66,7 @@ void guac_rdpdr_fs_process_create(guac_rdpdr_device* device, desired_access, file_attributes, 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] " "desired_access=0x%x, file_attributes=0x%x, " "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 (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); - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - guac_rdp_fs_get_status(file_id), 5); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, guac_rdp_fs_get_status(file_id), 5); Stream_Write_UINT32(output_stream, 0); /* fileId */ 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; - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 5); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, 5); Stream_Write_UINT32(output_stream, file_id); /* fileId */ 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) { int download_id = guac_rdp_fs_open((guac_rdp_fs*) device->data, "\\Download", - ACCESS_GENERIC_READ, 0, - DISP_FILE_OPEN_IF, FILE_DIRECTORY_FILE); + GENERIC_READ, 0, FILE_OPEN_IF, FILE_DIRECTORY_FILE); if (download_id >= 0) 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, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_read(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { UINT32 length; UINT64 offset; @@ -134,9 +127,9 @@ void guac_rdpdr_fs_process_read(guac_rdpdr_device* device, Stream_Read_UINT32(input_stream, length); 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, - __func__, file_id, length, (uint64_t) offset); + __func__, iorequest->file_id, length, (uint64_t) offset); /* Ensure buffer size does not exceed a safe maximum */ if (length > GUAC_RDP_MAX_READ_BUFFER) @@ -146,31 +139,32 @@ void guac_rdpdr_fs_process_read(guac_rdpdr_device* device, buffer = malloc(length); /* Attempt read */ - bytes_read = guac_rdp_fs_read((guac_rdp_fs*) device->data, file_id, offset, - buffer, length); + bytes_read = guac_rdp_fs_read((guac_rdp_fs*) device->data, + iorequest->file_id, offset, buffer, length); /* If error, return invalid parameter */ if (bytes_read < 0) { - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - guac_rdp_fs_get_status(bytes_read), 4); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, guac_rdp_fs_get_status(bytes_read), 4); Stream_Write_UINT32(output_stream, 0); /* Length */ } /* Otherwise, send bytes read */ else { - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 4+bytes_read); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, 4+bytes_read); Stream_Write_UINT32(output_stream, bytes_read); /* Length */ 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); } -void guac_rdpdr_fs_process_write(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_write(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { UINT32 length; UINT64 offset; @@ -183,69 +177,70 @@ void guac_rdpdr_fs_process_write(guac_rdpdr_device* device, Stream_Read_UINT64(input_stream, offset); 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, - __func__, file_id, length, (uint64_t) offset); + __func__, iorequest->file_id, length, (uint64_t) offset); /* Attempt write */ - bytes_written = guac_rdp_fs_write((guac_rdp_fs*) device->data, file_id, - offset, Stream_Pointer(input_stream), length); + bytes_written = guac_rdp_fs_write((guac_rdp_fs*) device->data, + iorequest->file_id, offset, Stream_Pointer(input_stream), length); /* If error, return invalid parameter */ if (bytes_written < 0) { - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - guac_rdp_fs_get_status(bytes_written), 5); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, guac_rdp_fs_get_status(bytes_written), 5); Stream_Write_UINT32(output_stream, 0); /* Length */ Stream_Write_UINT8(output_stream, 0); /* Padding */ } /* Otherwise, send success */ else { - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_SUCCESS, 5); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, 5); Stream_Write_UINT32(output_stream, bytes_written); /* Length */ 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, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_close(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { wStream* output_stream; guac_rdp_fs_file* file; - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i]", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", + __func__, iorequest->file_id); /* 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) return; /* If file was written to, and it's in the \Download folder, start stream */ if (file->bytes_written > 0 && strncmp(file->absolute_path, "\\Download\\", 10) == 0) { - guac_rdpdr_start_download(device, file->absolute_path); - guac_rdp_fs_delete((guac_rdp_fs*) device->data, file_id); + guac_client_for_owner(svc->client, guac_rdp_download_to_user, file->absolute_path); + guac_rdp_fs_delete((guac_rdp_fs*) device->data, iorequest->file_id); } /* 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, - STATUS_SUCCESS, 4); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_SUCCESS, 4); 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, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_volume_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { 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) { case FileFsVolumeInformation: - guac_rdpdr_fs_process_query_volume_info(device, input_stream, - file_id, completion_id); + guac_rdpdr_fs_process_query_volume_info(svc, device, iorequest, input_stream); break; case FileFsSizeInformation: - guac_rdpdr_fs_process_query_size_info(device, input_stream, - file_id, completion_id); + guac_rdpdr_fs_process_query_size_info(svc, device, iorequest, input_stream); break; case FileFsDeviceInformation: - guac_rdpdr_fs_process_query_device_info(device, input_stream, - file_id, completion_id); + guac_rdpdr_fs_process_query_device_info(svc, device, iorequest, input_stream); break; case FileFsAttributeInformation: - guac_rdpdr_fs_process_query_attribute_info(device, input_stream, - file_id, completion_id); + guac_rdpdr_fs_process_query_attribute_info(svc, device, iorequest, input_stream); break; case FileFsFullSizeInformation: - guac_rdpdr_fs_process_query_full_size_info(device, input_stream, - file_id, completion_id); + guac_rdpdr_fs_process_query_full_size_info(svc, device, iorequest, input_stream); break; 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); } } -void guac_rdpdr_fs_process_file_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_file_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { 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) { case FileBasicInformation: - guac_rdpdr_fs_process_query_basic_info(device, input_stream, - file_id, completion_id); + guac_rdpdr_fs_process_query_basic_info(svc, device, iorequest, input_stream); break; case FileStandardInformation: - guac_rdpdr_fs_process_query_standard_info(device, input_stream, - file_id, completion_id); + guac_rdpdr_fs_process_query_standard_info(svc, device, iorequest, input_stream); break; case FileAttributeTagInformation: - guac_rdpdr_fs_process_query_attribute_tag_info(device, input_stream, - file_id, completion_id); + guac_rdpdr_fs_process_query_attribute_tag_info(svc, device, iorequest, input_stream); break; 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); } } -void guac_rdpdr_fs_process_set_volume_info(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_set_volume_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { 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", - __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, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_set_file_info(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { int fs_information_class; int length; @@ -346,66 +336,62 @@ void guac_rdpdr_fs_process_set_file_info(guac_rdpdr_device* device, switch (fs_information_class) { case FileBasicInformation: - guac_rdpdr_fs_process_set_basic_info(device, input_stream, - file_id, completion_id, length); + guac_rdpdr_fs_process_set_basic_info(svc, device, iorequest, length, input_stream); break; case FileEndOfFileInformation: - guac_rdpdr_fs_process_set_end_of_file_info(device, input_stream, - file_id, completion_id, length); + guac_rdpdr_fs_process_set_end_of_file_info(svc, device, iorequest, length, input_stream); break; case FileDispositionInformation: - guac_rdpdr_fs_process_set_disposition_info(device, input_stream, - file_id, completion_id, length); + guac_rdpdr_fs_process_set_disposition_info(svc, device, iorequest, length, input_stream); break; case FileRenameInformation: - guac_rdpdr_fs_process_set_rename_info(device, input_stream, - file_id, completion_id, length); + guac_rdpdr_fs_process_set_rename_info(svc, device, iorequest, length, input_stream); break; case FileAllocationInformation: - guac_rdpdr_fs_process_set_allocation_info(device, input_stream, - file_id, completion_id, length); + guac_rdpdr_fs_process_set_allocation_info(svc, device, iorequest, length, input_stream); break; 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); } } -void guac_rdpdr_fs_process_device_control(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_device_control(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { 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, - "%s: [file_id=%i] IGNORED", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] IGNORED", + __func__, iorequest->file_id); /* No content for now */ 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, - wStream* input_stream, int file_id, int completion_id) { +void guac_rdpdr_fs_process_notify_change_directory(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { - guac_client_log(device->rdpdr->client, GUAC_LOG_DEBUG, - "%s: [file_id=%i] Not implemented", - __func__, file_id); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] Not " + "implemented", __func__, iorequest->file_id); } -void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_query_directory(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_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; /* 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) 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, - "%s: [file_id=%i] initial_query=%i, dir_pattern=\"%s\"", - __func__, file_id, initial_query, file->dir_pattern); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] " + "initial_query=%i, dir_pattern=\"%s\"", __func__, + iorequest->file_id, initial_query, file->dir_pattern); /* Find first matching entry in directory */ while ((entry_name = guac_rdp_fs_read_dir((guac_rdp_fs*) device->data, - file_id)) != NULL) { + iorequest->file_id)) != NULL) { /* Convert to absolute 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 */ 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) { @@ -465,27 +451,27 @@ void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* i switch (fs_information_class) { case FileDirectoryInformation: - guac_rdpdr_fs_process_query_directory_info(device, - entry_name, entry_file_id, completion_id); + guac_rdpdr_fs_process_query_directory_info(svc, device, + iorequest, entry_name, entry_file_id); break; case FileFullDirectoryInformation: - guac_rdpdr_fs_process_query_full_directory_info(device, - entry_name, entry_file_id, completion_id); + guac_rdpdr_fs_process_query_full_directory_info(svc, + device, iorequest, entry_name, entry_file_id); break; case FileBothDirectoryInformation: - guac_rdpdr_fs_process_query_both_directory_info(device, - entry_name, entry_file_id, completion_id); + guac_rdpdr_fs_process_query_both_directory_info(svc, + device, iorequest, entry_name, entry_file_id); break; case FileNamesInformation: - guac_rdpdr_fs_process_query_names_info(device, - entry_name, entry_file_id, completion_id); + guac_rdpdr_fs_process_query_names_info(svc, device, + iorequest, entry_name, entry_file_id); break; default: - guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, + guac_client_log(svc->client, GUAC_LOG_DEBUG, "Unknown dir information class: 0x%x", 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. */ - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - STATUS_NO_MORE_FILES, 5); + output_stream = guac_rdpdr_new_io_completion(device, + iorequest->completion_id, STATUS_NO_MORE_FILES, 5); Stream_Write_UINT32(output_stream, 0); /* Length */ 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, - int file_id, int completion_id) { +void guac_rdpdr_fs_process_lock_control(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { 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", - __func__, file_id); + __func__, iorequest->file_id); Stream_Zero(output_stream, 5); /* Padding */ - svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); + guac_rdp_common_svc_write(svc, output_stream); } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages.h b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.h similarity index 59% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages.h rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.h index 4e484b4d..63fcbb9a 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_messages.h +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.h @@ -17,59 +17,47 @@ * under the License. */ - -#ifndef __GUAC_RDPDR_FS_MESSAGES_H -#define __GUAC_RDPDR_FS_MESSAGES_H +#ifndef GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_H +#define GUAC_RDP_CHANNELS_RDPDR_FS_MESSAGES_H /** * Handlers for core drive I/O requests. Requests handled here may be simple * messages handled directly, or more complex multi-type messages handled * 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 -#else -#include "compat/winpr-stream.h" -#endif /** * Handles a Server Create Drive Request. Despite its name, this request opens * a file. */ -void guac_rdpdr_fs_process_create(guac_rdpdr_device* device, - wStream* input_stream, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_create; /** - * 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, - wStream* input_stream, int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_close; /** * Handles a Server Drive Read Request. This request reads from a file. */ -void guac_rdpdr_fs_process_read(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_read; /** * Handles a Server Drive Write Request. This request writes to a file. */ -void guac_rdpdr_fs_process_write(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_write; /** * Handles a Server Drive Control Request. This request handles one of any * number of Windows FSCTL_* control functions. */ -void guac_rdpdr_fs_process_device_control(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_device_control; /** * 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 * separate file. */ -void guac_rdpdr_fs_process_volume_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_volume_info; /** * Handles a Server Drive Set Volume Information Request. Currently, this * RDPDR implementation does not support setting of volume information. */ -void guac_rdpdr_fs_process_set_volume_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_set_volume_info; /** * Handles a Server Drive Query Information Request. This request queries * information about a specific file. This request has several query types * which have their own handlers defined in a separate file. */ -void guac_rdpdr_fs_process_file_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_file_info; /** * Handles a Server Drive Set Information Request. This request sets * information about a specific file. Currently, this RDPDR implementation does * not support setting of file information. */ -void guac_rdpdr_fs_process_set_file_info(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_set_file_info; /** * Handles a Server Drive Query Directory Request. This request queries * information about a specific directory. This request has several query types * which have their own handlers defined in a separate file. */ -void guac_rdpdr_fs_process_query_directory(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_query_directory; /** * Handles a Server Drive NotifyChange Directory Request. This request requests * directory change notification. */ -void guac_rdpdr_fs_process_notify_change_directory(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_notify_change_directory; /** * Handles a Server Drive Lock Control Request. This request locks or unlocks * portions of a file. */ -void guac_rdpdr_fs_process_lock_control(guac_rdpdr_device* device, wStream* input_stream, - int file_id, int completion_id); +guac_rdpdr_device_iorequest_handler guac_rdpdr_fs_process_lock_control; #endif diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c b/src/protocols/rdp/channels/rdpdr/rdpdr-fs.c similarity index 61% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs.c index e064d4de..ed67ac6f 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs.c @@ -17,119 +17,116 @@ * under the License. */ - -#include "config.h" - +#include "channels/rdpdr/rdpdr-fs.h" +#include "channels/rdpdr/rdpdr-fs-messages.h" +#include "channels/rdpdr/rdpdr.h" #include "rdp.h" -#include "rdpdr_fs_messages.h" -#include "rdpdr_messages.h" -#include "rdpdr_service.h" -#include +#include +#include #include -#include -#include #include - -#ifdef ENABLE_WINPR #include -#else -#include "compat/winpr-stream.h" -#endif -static void guac_rdpdr_device_fs_iorequest_handler(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id, int major_func, int minor_func) { +#include - 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 */ 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; /* File 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; /* File 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; /* File 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; /* Device control request (Windows FSCTL_ control codes) */ 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; /* Query volume (drive) 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; /* Set volume (drive) 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; /* Query file 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; /* Set file 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; case IRP_MJ_DIRECTORY_CONTROL: /* Enumerate directory contents */ - if (minor_func == IRP_MN_QUERY_DIRECTORY) - guac_rdpdr_fs_process_query_directory(device, input_stream, file_id, completion_id); + if (iorequest->minor_func == IRP_MN_QUERY_DIRECTORY) + guac_rdpdr_fs_process_query_directory(svc, device, iorequest, + input_stream); /* Request notification of changes to directory */ - else if (minor_func == IRP_MN_NOTIFY_CHANGE_DIRECTORY) - guac_rdpdr_fs_process_notify_change_directory(device, input_stream, - file_id, completion_id); + else if (iorequest->minor_func == IRP_MN_NOTIFY_CHANGE_DIRECTORY) + guac_rdpdr_fs_process_notify_change_directory(svc, device, + iorequest, input_stream); break; /* Lock/unlock portions of a file */ 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; 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", - 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); } -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_rdpdr* rdpdr = (guac_rdpdr*) svc->data; int id = rdpdr->devices_registered++; /* Get new device */ guac_rdpdr_device* device = &(rdpdr->devices[id]); /* Init device */ - device->rdpdr = rdpdr; device->device_id = id; device->device_name = drive_name; int device_name_len = guac_utf8_strlen(device->device_name); diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.h b/src/protocols/rdp/channels/rdpdr/rdpdr-fs.h similarity index 70% rename from src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.h rename to src/protocols/rdp/channels/rdpdr/rdpdr-fs.h index f990806b..ae90045f 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.h +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs.h @@ -17,38 +17,46 @@ * under the License. */ - -#ifndef __GUAC_RDPDR_FS_H -#define __GUAC_RDPDR_FS_H +#ifndef GUAC_RDP_RDPDR_FS_H +#define GUAC_RDP_RDPDR_FS_H /** * Functions and macros specific to filesystem handling and initialization - * independent of RDP. The functions here may deal with the RDPDR device + * independent of RDP. The functions here may deal with the RDPDR device * directly, but their semantics must not deal with RDP protocol messaging. * Functions here represent a virtual Windows-style filesystem on top of UNIX * system calls and structures, using the guac_rdpdr_device structure as a home * for common data. * - * @file rdpdr_fs.h + * @file rdpdr-fs.h */ -#include "config.h" - -#include "rdpdr_service.h" +#include "channels/common-svc.h" +#include "channels/rdpdr/rdpdr.h" #include +/** + * 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 * before RDPDR connection finishes. * - * @param rdpdr - * The RDP device redirection plugin with which to register the device. + * @param svc + * The static virtual channel instance being used for RDPDR. * * @param drive_name * 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 diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr-messages.c b/src/protocols/rdp/channels/rdpdr/rdpdr-messages.c new file mode 100644 index 00000000..28a27e67 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-messages.c @@ -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 +#include +#include + +#include +#include + +/** + * 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; idevices_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; iclient, 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"); +} diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr-messages.h b/src/protocols/rdp/channels/rdpdr/rdpdr-messages.h new file mode 100644 index 00000000..a79c83b4 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-messages.h @@ -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 + +#include + +/** + * 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 + diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c b/src/protocols/rdp/channels/rdpdr/rdpdr-printer.c similarity index 71% rename from src/protocols/rdp/guac_rdpdr/rdpdr_printer.c rename to src/protocols/rdp/channels/rdpdr/rdpdr-printer.c index ace025c4..d90116f5 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-printer.c @@ -17,41 +17,26 @@ * under the License. */ -#include "config.h" - -#include "rdpdr_messages.h" -#include "rdpdr_printer.h" -#include "rdpdr_service.h" +#include "channels/rdpdr/rdpdr-printer.h" +#include "channels/rdpdr/rdpdr.h" +#include "print-job.h" #include "rdp.h" -#include "rdp_print_job.h" -#include "rdp_status.h" #include "unicode.h" -#include +#include +#include #include -#include -#include -#include #include -#include - -#ifdef ENABLE_WINPR +#include #include -#else -#include "compat/winpr-stream.h" -#endif -#include -#include -#include #include -#include -#include -void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device, - wStream* input_stream, int completion_id) { +void guac_rdpdr_process_print_job_create(guac_rdp_common_svc* svc, + 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; /* Log creation of print job */ @@ -63,17 +48,18 @@ void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device, /* Respond with success */ 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 */ - 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, - wStream* input_stream, int completion_id) { +void guac_rdpdr_process_print_job_write(guac_rdp_common_svc* svc, + 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_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, - completion_id, status, 5); + iorequest->completion_id, status, 5); Stream_Write_UINT32(output_stream, length); 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, - wStream* input_stream, int completion_id) { +void guac_rdpdr_process_print_job_close(guac_rdp_common_svc* svc, + 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_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, - completion_id, STATUS_SUCCESS, 4); + iorequest->completion_id, STATUS_SUCCESS, 4); 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 */ guac_client_log(client, GUAC_LOG_INFO, "Print job closed"); } -static void guac_rdpdr_device_printer_iorequest_handler(guac_rdpdr_device* device, - wStream* input_stream, int file_id, int completion_id, int major_func, int minor_func) { +void guac_rdpdr_device_printer_iorequest_handler(guac_rdp_common_svc* svc, + guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { - switch (major_func) { + switch (iorequest->major_func) { /* Print job 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; /* Printer job 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; /* Printer job 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; /* Log unknown */ default: - guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR, - "Unknown printer I/O request function: 0x%x/0x%x", - major_func, minor_func); + guac_client_log(svc->client, GUAC_LOG_ERROR, "Unknown printer " + "I/O request function: 0x%x/0x%x", iorequest->major_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); } -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++; /* Get new device */ guac_rdpdr_device* device = &(rdpdr->devices[id]); /* Init device */ - device->rdpdr = rdpdr; device->device_id = id; device->device_name = printer_name; int device_name_len = guac_utf8_strlen(device->device_name); diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr-printer.h b/src/protocols/rdp/channels/rdpdr/rdpdr-printer.h new file mode 100644 index 00000000..a6865d84 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-printer.h @@ -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 + +/** + * 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 + diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr.c b/src/protocols/rdp/channels/rdpdr/rdpdr.c new file mode 100644 index 00000000..e04bc9d7 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/rdpdr.c @@ -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 +#include +#include +#include +#include + +#include + +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; idevices_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."); + } + +} + diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr.h b/src/protocols/rdp/channels/rdpdr/rdpdr.h new file mode 100644 index 00000000..b6f9da31 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/rdpdr.h @@ -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 +#include +#include + +#include + +/** + * 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 + diff --git a/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c b/src/protocols/rdp/channels/rdpsnd/rdpsnd-messages.c similarity index 85% rename from src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c rename to src/protocols/rdp/channels/rdpsnd/rdpsnd-messages.c index ba0224c1..1d69e069 100644 --- a/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c +++ b/src/protocols/rdp/channels/rdpsnd/rdpsnd-messages.c @@ -17,30 +17,20 @@ * under the License. */ -#include "config.h" - +#include "channels/rdpsnd/rdpsnd-messages.h" +#include "channels/rdpsnd/rdpsnd.h" #include "rdp.h" -#include "rdpsnd_messages.h" -#include "rdpsnd_service.h" -#include -#include - -#include +#include #include #include - -#ifdef ENABLE_WINPR #include #include -#else -#include "compat/winpr-stream.h" -#include "compat/winpr-wtypes.h" -#endif -/* MESSAGE HANDLERS */ +#include +#include -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) { int server_format_count; @@ -51,11 +41,10 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, int output_body_size; unsigned char* output_stream_end; - /* Get associated client data */ - guac_client* client = rdpsnd->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_client* client = svc->client; + guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data; - /* Get audio stream from client data */ + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_audio_stream* audio = rdp_client->audio; /* Reset own format count */ @@ -188,8 +177,7 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, Stream_SetPointer(output_stream, output_stream_end); /* Send accepted formats */ - pthread_mutex_lock(&(rdp_client->rdp_lock)); - svc_plugin_send((rdpSvcPlugin*)rdpsnd, output_stream); + guac_rdp_common_svc_write(svc, output_stream); /* If version greater than 6, must send Quality Mode PDU */ 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, 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 */ -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) { int data_size; wStream* output_stream; - /* Get associated client data */ - guac_client* client = rdpsnd->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data; /* Read timestamp and data size */ 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, data_size); - pthread_mutex_lock(&(rdp_client->rdp_lock)); - svc_plugin_send((rdpSvcPlugin*) rdpsnd, output_stream); - pthread_mutex_unlock(&(rdp_client->rdp_lock)); + guac_rdp_common_svc_write(svc, output_stream); } -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) { int format; - /* Get associated client data */ - guac_client* client = rdpsnd->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_client* client = svc->client; + guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data; - /* Get audio stream from client data */ + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_audio_stream* audio = rdp_client->audio; /* 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) { - 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; - - /* Get audio stream from client data */ guac_audio_stream* audio = rdp_client->audio; /* Wave Confirmation PDU */ @@ -313,16 +292,14 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd, Stream_Write_UINT8(output_stream, 0); /* Send Wave Confirmation PDU */ - pthread_mutex_lock(&(rdp_client->rdp_lock)); - svc_plugin_send(plugin, output_stream); - pthread_mutex_unlock(&(rdp_client->rdp_lock)); + guac_rdp_common_svc_write(svc, output_stream); /* We no longer expect to receive wave data */ 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) { /* Do nothing */ diff --git a/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.h b/src/protocols/rdp/channels/rdpsnd/rdpsnd-messages.h similarity index 65% rename from src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.h rename to src/protocols/rdp/channels/rdpsnd/rdpsnd-messages.h index 7083b844..9271151f 100644 --- a/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.h +++ b/src/protocols/rdp/channels/rdpsnd/rdpsnd-messages.h @@ -17,87 +17,12 @@ * under the License. */ +#ifndef GUAC_RDP_CHANNELS_RDPSND_MESSAGES_H +#define GUAC_RDP_CHANNELS_RDPSND_MESSAGES_H -#ifndef __GUAC_RDPSND_MESSAGES_H -#define __GUAC_RDPSND_MESSAGES_H +#include "channels/common-svc.h" -#include "config.h" - -#include "rdpsnd_service.h" - -#ifdef ENABLE_WINPR #include -#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. @@ -121,8 +46,8 @@ typedef struct guac_rdpsnd_pdu_header { * SNDC_FORMATS PDU describes all audio formats supported by the RDP server, as * well as the version of RDPSND implemented. * - * @param rdpsnd - * The Guacamole RDPSND plugin receiving the SNDC_FORMATS PDU. + * @param svc + * The RDPSND channel receiving the SNDC_FORMATS PDU. * * @param input_stream * 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 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); /** @@ -142,8 +67,8 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, * * https://msdn.microsoft.com/en-us/library/cc240961.aspx * - * @param rdpsnd - * The Guacamole RDPSND plugin receiving the SNDC_TRAINING PDU. + * @param svc + * The RDPSND channel receiving the SNDC_TRAINING PDU. * * @param input_stream * 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 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); /** @@ -165,8 +90,8 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd, * * https://msdn.microsoft.com/en-us/library/cc240963.aspx * - * @param rdpsnd - * The Guacamole RDPSND plugin receiving the SNDC_WAVE PDU. + * @param svc + * The RDPSND channel receiving the SNDC_WAVE PDU. * * @param input_stream * 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 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); /** @@ -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 * included in the SNDC_WAVE PDU. * - * @param rdpsnd - * The Guacamole RDPSND plugin receiving the SNDWAV PDU. + * @param svc + * The RDPSND channel receiving the SNDWAV PDU. * * @param input_stream * 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 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); /** @@ -204,8 +129,8 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd, * * https://msdn.microsoft.com/en-us/library/cc240970.aspx * - * @param rdpsnd - * The Guacamole RDPSND plugin receiving the SNDC_CLOSE PDU. + * @param svc + * The RDPSND channel receiving the SNDC_CLOSE PDU. * * @param input_stream * 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 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); #endif diff --git a/src/protocols/rdp/channels/rdpsnd/rdpsnd.c b/src/protocols/rdp/channels/rdpsnd/rdpsnd.c new file mode 100644 index 00000000..be6034d2 --- /dev/null +++ b/src/protocols/rdp/channels/rdpsnd/rdpsnd.c @@ -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 +#include +#include +#include + +#include + +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."); + } + +} + diff --git a/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.h b/src/protocols/rdp/channels/rdpsnd/rdpsnd.h similarity index 66% rename from src/protocols/rdp/guac_rdpsnd/rdpsnd_service.h rename to src/protocols/rdp/channels/rdpsnd/rdpsnd.h index b6a93962..cd7fe233 100644 --- a/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.h +++ b/src/protocols/rdp/channels/rdpsnd/rdpsnd.h @@ -17,21 +17,14 @@ * under the License. */ +#ifndef GUAC_RDP_CHANNELS_RDPSND_H +#define GUAC_RDP_CHANNELS_RDPSND_H -#ifndef __GUAC_RDPSND_SERVICE_H -#define __GUAC_RDPSND_SERVICE_H +#include "channels/common-svc.h" -#include "config.h" - -#include +#include #include -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-stream.h" -#endif - /** * The maximum number of PCM formats to accept during the initial RDPSND * handshake with the RDP server. @@ -42,7 +35,7 @@ * Abstract representation of a PCM format, including the sample rate, number * of channels, and bits per sample. */ -typedef struct guac_pcm_format { +typedef struct guac_rdpsnd_pcm_format { /** * The sample rate of this PCM format. @@ -61,26 +54,13 @@ typedef struct guac_pcm_format { */ int bps; -} guac_pcm_format; +} guac_rdpsnd_pcm_format; /** * Structure representing the current state of the Guacamole RDPSND plugin for * FreeRDP. */ -typedef struct guac_rdpsndPlugin { - - /** - * 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; +typedef struct guac_rdpsnd { /** * 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 * 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. */ 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, - wStream* input_stream); +guac_rdp_common_svc_connect_handler guac_rdpsnd_process_connect; /** - * 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, - * all events will be ignored and simply free'd. + * Handler which is invoked when the RDPSND channel has disconnected and is + * about to be freed. */ -void guac_rdpsnd_process_event(rdpSvcPlugin* plugin, wMessage* event); +guac_rdp_common_svc_terminate_handler guac_rdpsnd_process_terminate; #endif diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 6b9a0a6b..c79e59d8 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -17,14 +17,16 @@ * under the License. */ -#include "config.h" - -#include "audio_input.h" -#include "common/recording.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_disp.h" -#include "rdp_fs.h" +#include "settings.h" #include "user.h" #ifdef ENABLE_COMMON_SSH @@ -33,26 +35,11 @@ #include "common-ssh/user.h" #endif -#include -#include -#include #include #include -#include - -#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H -#include -#else -#include "compat/client-cliprdr.h" -#endif - -#ifdef HAVE_FREERDP_CLIENT_CHANNELS_H -#include -#endif #include #include -#include 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; /* 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 */ rdp_client->disp = guac_rdp_disp_alloc(); + /* Redirect FreeRDP log messages to guac_client_log() */ + guac_rdp_redirect_wlog(client); + /* Recursive attribute for locks */ pthread_mutexattr_init(&(rdp_client->attributes)); pthread_mutexattr_settype(&(rdp_client->attributes), PTHREAD_MUTEX_RECURSIVE); - /* Init RDP lock */ - pthread_mutex_init(&(rdp_client->rdp_lock), &(rdp_client->attributes)); - /* Set handlers */ client->join_handler = guac_rdp_user_join_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) guac_rdp_settings_free(rdp_client->settings); + /* Clean up clipboard */ + guac_rdp_clipboard_free(rdp_client->clipboard); + /* Free display update module */ 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); /* Free client data */ - guac_common_clipboard_free(rdp_client->clipboard); free(rdp_client); return 0; diff --git a/src/protocols/rdp/client.h b/src/protocols/rdp/client.h index 4f6c2662..9acd33ab 100644 --- a/src/protocols/rdp/client.h +++ b/src/protocols/rdp/client.h @@ -20,8 +20,6 @@ #ifndef GUAC_RDP_CLIENT_H #define GUAC_RDP_CLIENT_H -#include "config.h" - #include /** diff --git a/src/protocols/rdp/color.c b/src/protocols/rdp/color.c new file mode 100644 index 00000000..964310af --- /dev/null +++ b/src/protocols/rdp/color.c @@ -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 +#include +#include +#include + +#include +#include + +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; + +} + diff --git a/src/protocols/rdp/rdp_color.h b/src/protocols/rdp/color.h similarity index 61% rename from src/protocols/rdp/rdp_color.h rename to src/protocols/rdp/color.h index 40712ba8..43910a58 100644 --- a/src/protocols/rdp/rdp_color.h +++ b/src/protocols/rdp/color.h @@ -21,12 +21,27 @@ #define GUAC_RDP_COLOR_H #include - -#ifdef ENABLE_WINPR #include -#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 diff --git a/src/protocols/rdp/compat/rail.h b/src/protocols/rdp/compat/rail.h deleted file mode 100644 index 32d75b43..00000000 --- a/src/protocols/rdp/compat/rail.h +++ /dev/null @@ -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 - -#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 - diff --git a/src/protocols/rdp/compat/winpr-stream.c b/src/protocols/rdp/compat/winpr-stream.c deleted file mode 100644 index 8be45466..00000000 --- a/src/protocols/rdp/compat/winpr-stream.c +++ /dev/null @@ -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); - -} - diff --git a/src/protocols/rdp/compat/winpr-stream.h b/src/protocols/rdp/compat/winpr-stream.h deleted file mode 100644 index e2195c0d..00000000 --- a/src/protocols/rdp/compat/winpr-stream.h +++ /dev/null @@ -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 - -#include - -/* 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 - diff --git a/src/protocols/rdp/decompose.c b/src/protocols/rdp/decompose.c index 5f559d99..e42a7697 100644 --- a/src/protocols/rdp/decompose.c +++ b/src/protocols/rdp/decompose.c @@ -17,7 +17,6 @@ * under the License. */ -#include "config.h" #include "keyboard.h" /** diff --git a/src/protocols/rdp/doc/svc-example/.gitignore b/src/protocols/rdp/doc/svc-example/.gitignore new file mode 100644 index 00000000..51e8bef6 --- /dev/null +++ b/src/protocols/rdp/doc/svc-example/.gitignore @@ -0,0 +1,2 @@ +!Makefile +*.exe diff --git a/src/protocols/rdp/doc/svc-example/Makefile b/src/protocols/rdp/doc/svc-example/Makefile new file mode 100644 index 00000000..8c38e594 --- /dev/null +++ b/src/protocols/rdp/doc/svc-example/Makefile @@ -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 + diff --git a/src/protocols/rdp/doc/svc-example/README.md b/src/protocols/rdp/doc/svc-example/README.md new file mode 100644 index 00000000..337a733e --- /dev/null +++ b/src/protocols/rdp/doc/svc-example/README.md @@ -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." +``` + diff --git a/src/protocols/rdp/doc/svc-example/svc-example.c b/src/protocols/rdp/doc/svc-example/svc-example.c new file mode 100644 index 00000000..681b3f91 --- /dev/null +++ b/src/protocols/rdp/doc/svc-example/svc-example.c @@ -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 +#include +#include + +#include + +/** + * 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; + +} + diff --git a/src/protocols/rdp/download.c b/src/protocols/rdp/download.c new file mode 100644 index 00000000..34a8c8a1 --- /dev/null +++ b/src/protocols/rdp/download.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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; + +} + diff --git a/src/protocols/rdp/download.h b/src/protocols/rdp/download.h new file mode 100644 index 00000000..2989658c --- /dev/null +++ b/src/protocols/rdp/download.h @@ -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 +#include +#include + +#include + +/** + * 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 + diff --git a/src/protocols/rdp/dvc.c b/src/protocols/rdp/dvc.c deleted file mode 100644 index 34406c9b..00000000 --- a/src/protocols/rdp/dvc.c +++ /dev/null @@ -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 -#include -#include - -#include -#include - -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 - -} - diff --git a/src/protocols/rdp/dvc.h b/src/protocols/rdp/dvc.h deleted file mode 100644 index 63694f53..00000000 --- a/src/protocols/rdp/dvc.h +++ /dev/null @@ -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 - -/** - * 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 - diff --git a/src/protocols/rdp/error.c b/src/protocols/rdp/error.c index e3403625..de0e1cd3 100644 --- a/src/protocols/rdp/error.c +++ b/src/protocols/rdp/error.c @@ -17,8 +17,6 @@ * under the License. */ -#include "config.h" - #include "error.h" #include "rdp.h" diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/fs.c similarity index 95% rename from src/protocols/rdp/rdp_fs.c rename to src/protocols/rdp/fs.c index 24d7c3ef..5223b5a6 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/fs.c @@ -17,11 +17,19 @@ * under the License. */ -#include "config.h" +#include "fs.h" +#include "download.h" +#include "upload.h" -#include "rdp_fs.h" -#include "rdp_status.h" -#include "rdp_stream.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -32,16 +40,8 @@ #include #include #include -#include #include -#include -#include -#include -#include -#include -#include - guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* 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 */ - if (access & ACCESS_GENERIC_ALL) + if (access & GENERIC_ALL) flags = O_RDWR; - else if ((access & ( ACCESS_GENERIC_WRITE - | ACCESS_FILE_WRITE_DATA - | ACCESS_FILE_APPEND_DATA)) - && (access & (ACCESS_GENERIC_READ | ACCESS_FILE_READ_DATA))) + else if ((access & ( GENERIC_WRITE + | FILE_WRITE_DATA + | FILE_APPEND_DATA)) + && (access & (GENERIC_READ | FILE_READ_DATA))) flags = O_RDWR; - else if (access & ( ACCESS_GENERIC_WRITE - | ACCESS_FILE_WRITE_DATA - | ACCESS_FILE_APPEND_DATA)) + else if (access & ( GENERIC_WRITE + | FILE_WRITE_DATA + | FILE_APPEND_DATA)) flags = O_WRONLY; else flags = O_RDONLY; @@ -279,32 +279,32 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path, switch (create_disposition) { /* Create if not exist, fail otherwise */ - case DISP_FILE_CREATE: + case FILE_CREATE: flags |= O_CREAT | O_EXCL; break; /* Open file if exists and do not overwrite, fail otherwise */ - case DISP_FILE_OPEN: + case FILE_OPEN: /* No flag necessary - default functionality of open */ break; /* Open if exists, create otherwise */ - case DISP_FILE_OPEN_IF: + case FILE_OPEN_IF: flags |= O_CREAT; break; /* Overwrite if exists, fail otherwise */ - case DISP_FILE_OVERWRITE: + case FILE_OVERWRITE: flags |= O_TRUNC; break; /* Overwrite if exists, create otherwise */ - case DISP_FILE_OVERWRITE_IF: + case FILE_OVERWRITE_IF: flags |= O_CREAT | O_TRUNC; break; /* Supersede (replace) if exists, otherwise create */ - case DISP_FILE_SUPERSEDE: + case FILE_SUPERSEDE: unlink(real_path); flags |= O_CREAT | O_TRUNC; 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 path_depth = 0; diff --git a/src/protocols/rdp/rdp_fs.h b/src/protocols/rdp/fs.h similarity index 89% rename from src/protocols/rdp/rdp_fs.h rename to src/protocols/rdp/fs.h index f1e42956..83a0b3bd 100644 --- a/src/protocols/rdp/rdp_fs.h +++ b/src/protocols/rdp/fs.h @@ -17,25 +17,24 @@ * under the License. */ - -#ifndef __GUAC_RDP_FS_H -#define __GUAC_RDP_FS_H +#ifndef GUAC_RDP_FS_H +#define GUAC_RDP_FS_H /** * Functions and macros specific to filesystem handling and initialization - * independent of RDP. The functions here may deal with the filesystem device + * independent of RDP. The functions here may deal with the filesystem device * directly, but their semantics must not deal with RDP protocol messaging. * Functions here represent a virtual Windows-style filesystem on top of UNIX * system calls and structures, using the guac_rdp_fs structure as a home * for common data. * - * @file rdp_fs.h + * @file fs.h */ -#include "config.h" - #include +#include #include +#include #include #include @@ -109,73 +108,6 @@ */ #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 * 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. * * @param access - * A bitwise-OR of various RDPDR access flags, such as ACCESS_GENERIC_ALL - * or ACCESS_GENERIC_WRITE. This value will ultimately be translated to a - * standard O_RDWR, O_WRONLY, etc. value when opening the real file on the - * local filesystem. + * A bitwise-OR of various RDPDR access flags, such as GENERIC_ALL or + * GENERIC_WRITE. This value will ultimately be translated to a standard + * O_RDWR, O_WRONLY, etc. value when opening the real file on the local + * filesystem. * * @param file_attributes * The attributes to apply to the file, if created. This parameter is @@ -449,9 +381,9 @@ int guac_rdp_fs_get_status(int err); * * @param create_disposition * Any one of several RDPDR file creation dispositions, such as - * DISP_FILE_CREATE, DISP_FILE_OPEN_IF, etc. The creation disposition - * dictates whether a new file should be created, whether the file can - * already exist, whether existing contents should be truncated, etc. + * FILE_CREATE, FILE_OPEN_IF, etc. The creation disposition dictates + * whether a new file should be created, whether the file can already + * exist, whether existing contents should be truncated, etc. * * @param create_options * A bitwise-OR of various RDPDR options dictating how a file is to be @@ -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); +/** + * 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 * absolute path which does NOT contain ".." or ".". The given path MUST diff --git a/src/protocols/rdp/rdp_gdi.c b/src/protocols/rdp/gdi.c similarity index 61% rename from src/protocols/rdp/rdp_gdi.c rename to src/protocols/rdp/gdi.c index 59fe64cd..feb6505d 100644 --- a/src/protocols/rdp/rdp_gdi.c +++ b/src/protocols/rdp/gdi.c @@ -17,26 +17,21 @@ * under the License. */ -#include "config.h" - -#include "client.h" +#include "bitmap.h" +#include "common/display.h" #include "common/surface.h" #include "rdp.h" -#include "rdp_bitmap.h" -#include "rdp_color.h" -#include "rdp_settings.h" +#include "settings.h" #include #include +#include +#include #include #include - -#ifdef ENABLE_WINPR #include -#else -#include "compat/winpr-wtypes.h" -#endif +#include #include 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_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) { } -} - -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); - - } + return TRUE; } -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_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, 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_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 */ if (bitmap == NULL) { guac_client_log(client, GUAC_LOG_INFO, "NULL bitmap found in memblt instruction."); - return; + return TRUE; } switch (memblt->bRop) { @@ -321,98 +252,11 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { } -} - -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); + return TRUE; } -/** - * 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) { +BOOL guac_rdp_gdi_set_bounds(rdpContext* context, const rdpBounds* bounds) { guac_client* client = ((rdp_freerdp_context*) context)->client; 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->bottom - bounds->top + 1); + return TRUE; + } -void guac_rdp_gdi_end_paint(rdpContext* context) { +BOOL guac_rdp_gdi_end_paint(rdpContext* context) { /* 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_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_height(context->instance)); + return TRUE; + } diff --git a/src/protocols/rdp/rdp_gdi.h b/src/protocols/rdp/gdi.h similarity index 63% rename from src/protocols/rdp/rdp_gdi.h rename to src/protocols/rdp/gdi.h index cbe3c753..9ad5713c 100644 --- a/src/protocols/rdp/rdp_gdi.h +++ b/src/protocols/rdp/gdi.h @@ -17,9 +17,8 @@ * under the License. */ - -#ifndef _GUAC_RDP_RDP_GDI_H -#define _GUAC_RDP_RDP_GDI_H +#ifndef GUAC_RDP_GDI_H +#define GUAC_RDP_GDI_H #include "config.h" @@ -46,71 +45,58 @@ guac_composite_mode guac_rdp_rop3_transfer_function(guac_client* client, 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 * The rdpContext associated with the current RDP session. * * @param dstblt * 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 - * 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. + * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegdi/a4e322b0-cd64-4dfc-8e1a-f24dc0edc99d * * @param context * The rdpContext associated with the current RDP session. * * @param scrblt * 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 * The rdpContext associated with the current RDP session. * * @param memblt * The MEMBLT update to handle. + * + * @return + * TRUE if successful, FALSE otherwise. */ -void 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); +BOOL guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt); /** * 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 * The clipping rectangle to set, or NULL to remove any applied clipping * 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 @@ -133,8 +122,11 @@ void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds); * * @param context * 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 @@ -147,7 +139,10 @@ void guac_rdp_gdi_end_paint(rdpContext* context); * * @param context * 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 diff --git a/src/protocols/rdp/rdp_glyph.c b/src/protocols/rdp/glyph.c similarity index 77% rename from src/protocols/rdp/rdp_glyph.c rename to src/protocols/rdp/glyph.c index 8ba1087b..d8413841 100644 --- a/src/protocols/rdp/rdp_glyph.c +++ b/src/protocols/rdp/glyph.c @@ -17,25 +17,16 @@ * under the License. */ -#include "config.h" - -#include "client.h" +#include "color.h" #include "common/surface.h" +#include "config.h" +#include "glyph.h" #include "rdp.h" -#include "rdp_color.h" -#include "rdp_glyph.h" -#include "rdp_settings.h" #include #include - -#ifdef ENABLE_WINPR #include -#else -#include "compat/winpr-wtypes.h" -#endif -#include #include #include @@ -44,7 +35,7 @@ #define cairo_format_stride_for_width(format, width) (width*4) #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 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( 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_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 & 0x0000FF); + return TRUE; + } 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); 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, - int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor) { +BOOL guac_rdp_glyph_begindraw(rdpContext* context, + 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_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Fill background with color if specified */ - if (width != 0 && height != 0) { + if (width != 0 && height != 0 && !redundant) { /* Convert background color */ bgcolor = guac_rdp_convert_color(context, bgcolor); @@ -148,10 +156,15 @@ void guac_rdp_glyph_begindraw(rdpContext* context, /* Convert foreground color */ rdp_client->glyph_color = guac_rdp_convert_color(context, fgcolor); + return TRUE; + } -void guac_rdp_glyph_enddraw(rdpContext* context, - int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor) { +BOOL guac_rdp_glyph_enddraw(rdpContext* context, + GLYPH_CALLBACK_INT32 x, GLYPH_CALLBACK_INT32 y, + GLYPH_CALLBACK_INT32 width, GLYPH_CALLBACK_INT32 height, + UINT32 fgcolor, UINT32 bgcolor) { /* IGNORE */ + return TRUE; } diff --git a/src/protocols/rdp/rdp_glyph.h b/src/protocols/rdp/glyph.h similarity index 71% rename from src/protocols/rdp/rdp_glyph.h rename to src/protocols/rdp/glyph.h index 0fe0eeff..85f2080d 100644 --- a/src/protocols/rdp/rdp_glyph.h +++ b/src/protocols/rdp/glyph.h @@ -17,19 +17,28 @@ * under the License. */ - -#ifndef _GUAC_RDP_RDP_GLYPH_H -#define _GUAC_RDP_RDP_GLYPH_H +#ifndef GUAC_RDP_GLYPH_H +#define GUAC_RDP_GLYPH_H #include "config.h" #include #include - -#ifdef ENABLE_WINPR +#include #include + +#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 -#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 /** @@ -58,8 +67,11 @@ typedef struct guac_rdp_glyph { * * @param glyph * 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 @@ -76,8 +88,32 @@ void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph); * * @param y * 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 @@ -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 * be translated via guac_rdp_convert_color(). If the background is * 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, - int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor); +BOOL guac_rdp_glyph_begindraw(rdpContext* context, + 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 @@ -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 * be translated via guac_rdp_convert_color(). If the background is * transparent, this value is undefined. + * + * @return + * TRUE if successful, FALSE otherwise. */ -void guac_rdp_glyph_enddraw(rdpContext* context, - int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor); +BOOL guac_rdp_glyph_enddraw(rdpContext* context, + GLYPH_CALLBACK_INT32 x, GLYPH_CALLBACK_INT32 y, + GLYPH_CALLBACK_INT32 width, GLYPH_CALLBACK_INT32 height, + UINT32 fgcolor, UINT32 bgcolor); #endif diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_messages.c b/src/protocols/rdp/guac_rdpdr/rdpdr_messages.c deleted file mode 100644 index 66626adb..00000000 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_messages.c +++ /dev/null @@ -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 -#include -#include - -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-stream.h" -#endif - -#include -#include - -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; idevices_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; iclient, 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"); -} diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_messages.h b/src/protocols/rdp/guac_rdpdr/rdpdr_messages.h deleted file mode 100644 index 1e489245..00000000 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_messages.h +++ /dev/null @@ -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 -#else -#include "compat/winpr-stream.h" -#endif - -#include - -/** - * 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 - diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h deleted file mode 100644 index 9dbeb42c..00000000 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h +++ /dev/null @@ -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 -#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 - diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_service.c b/src/protocols/rdp/guac_rdpdr/rdpdr_service.c deleted file mode 100644 index 4901deca..00000000 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_service.c +++ /dev/null @@ -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 -#include - -#include -#include -#include -#include -#include -#include - -#ifdef ENABLE_WINPR -#include -#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; idevices_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); - -} - diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_service.h b/src/protocols/rdp/guac_rdpdr/rdpdr_service.h deleted file mode 100644 index ea5cb577..00000000 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_service.h +++ /dev/null @@ -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 -#include - -#ifdef ENABLE_WINPR -#include -#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 - diff --git a/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.c b/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.c deleted file mode 100644 index 45de442d..00000000 --- a/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.c +++ /dev/null @@ -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 -#include - -#include -#include -#include - -#ifdef ENABLE_WINPR -#include -#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); -} - diff --git a/src/protocols/rdp/guac_svc/svc_service.c b/src/protocols/rdp/guac_svc/svc_service.c deleted file mode 100644 index c40c6c5f..00000000 --- a/src/protocols/rdp/guac_svc/svc_service.c +++ /dev/null @@ -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 -#include - -#include -#include -#include -#include -#include -#include - -#ifdef ENABLE_WINPR -#include -#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); - -} - diff --git a/src/protocols/rdp/guac_svc/svc_service.h b/src/protocols/rdp/guac_svc/svc_service.h deleted file mode 100644 index 219aaece..00000000 --- a/src/protocols/rdp/guac_svc/svc_service.h +++ /dev/null @@ -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 - -#ifdef ENABLE_WINPR -#include -#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 - diff --git a/src/protocols/rdp/input.c b/src/protocols/rdp/input.c index 96507db2..8f77dc06 100644 --- a/src/protocols/rdp/input.c +++ b/src/protocols/rdp/input.c @@ -17,20 +17,20 @@ * under the License. */ -#include "config.h" - -#include "client.h" +#include "channels/disp.h" +#include "common/cursor.h" +#include "common/display.h" #include "common/recording.h" #include "input.h" #include "keyboard.h" #include "rdp.h" -#include "rdp_disp.h" +#include "settings.h" #include #include #include +#include -#include #include 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_rdp_client* rdp_client = (guac_rdp_client*) client->data; - pthread_mutex_lock(&(rdp_client->rdp_lock)); - /* Skip if not yet connected */ freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst == NULL) { - pthread_mutex_unlock(&(rdp_client->rdp_lock)); + if (rdp_inst == NULL) return 0; - } /* Store current mouse location/state */ 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; } - pthread_mutex_unlock(&(rdp_client->rdp_lock)); - 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; /* Send display update */ - pthread_mutex_lock(&(rdp_client->rdp_lock)); guac_rdp_disp_set_size(rdp_client->disp, settings, rdp_inst, width, height); - pthread_mutex_unlock(&(rdp_client->rdp_lock)); return 0; diff --git a/src/protocols/rdp/keyboard.c b/src/protocols/rdp/keyboard.c index 88cf2daf..42cc4db6 100644 --- a/src/protocols/rdp/keyboard.c +++ b/src/protocols/rdp/keyboard.c @@ -17,19 +17,15 @@ * under the License. */ -#include "config.h" - -#include "client.h" #include "decompose.h" #include "keyboard.h" +#include "keymap.h" #include "rdp.h" -#include "rdp_keymap.h" #include #include #include -#include #include /** @@ -102,21 +98,15 @@ static void guac_rdp_send_key_event(guac_rdp_client* rdp_client, else pressed_flags = KBD_FLAGS_RELEASE; - pthread_mutex_lock(&(rdp_client->rdp_lock)); - /* Skip if not yet connected */ freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst == NULL) { - pthread_mutex_unlock(&(rdp_client->rdp_lock)); + if (rdp_inst == NULL) return; - } /* Send actual key */ rdp_inst->input->KeyboardEvent(rdp_inst->input, 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, int codepoint) { - pthread_mutex_lock(&(rdp_client->rdp_lock)); - /* Skip if not yet connected */ freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst == NULL) { - pthread_mutex_unlock(&(rdp_client->rdp_lock)); + if (rdp_inst == NULL) return; - } /* Send Unicode event */ rdp_inst->input->UnicodeKeyboardEvent( rdp_inst->input, 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, int flags) { - pthread_mutex_lock(&(rdp_client->rdp_lock)); - /* Skip if not yet connected */ freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst == NULL) { - pthread_mutex_unlock(&(rdp_client->rdp_lock)); + if (rdp_inst == NULL) return; - } /* Synchronize lock key states */ rdp_inst->input->SynchronizeEvent(rdp_inst->input, flags); - pthread_mutex_unlock(&(rdp_client->rdp_lock)); - } /** diff --git a/src/protocols/rdp/keyboard.h b/src/protocols/rdp/keyboard.h index 79eed758..6296a71d 100644 --- a/src/protocols/rdp/keyboard.h +++ b/src/protocols/rdp/keyboard.h @@ -20,7 +20,7 @@ #ifndef GUAC_RDP_KEYBOARD_H #define GUAC_RDP_KEYBOARD_H -#include "rdp_keymap.h" +#include "keymap.h" #include @@ -94,7 +94,7 @@ typedef struct guac_rdp_keyboard { /** * The local state of all keys, as well as the necessary information to - * translate received keysyms into scancodes or sequences of scancodes for + * translate received keysyms into scancodes or sequences of scancodes for * RDP. The state of each key is updated based on received Guacamole key * events, while the information describing the behavior and scancode * mapping of each key is populated based on an associated keymap. @@ -180,7 +180,7 @@ int guac_rdp_keyboard_send_event(guac_rdp_keyboard* keyboard, /** * For every keysym in the given NULL-terminated array of keysyms, send the RDP * key events required to update the remote state of those keys as specified, - * depending on the current local state of those keysyms. For each key in the + * depending on the current local state of those keysyms. For each key in the * "from" state, that key will be updated to the "to" state. The locally-stored * state of each key is remains untouched. * diff --git a/src/protocols/rdp/rdp_keymap.c b/src/protocols/rdp/keymap.c similarity index 97% rename from src/protocols/rdp/rdp_keymap.c rename to src/protocols/rdp/keymap.c index 3be48a7d..b71b402d 100644 --- a/src/protocols/rdp/rdp_keymap.c +++ b/src/protocols/rdp/keymap.c @@ -17,9 +17,7 @@ * under the License. */ -#include "config.h" - -#include "rdp_keymap.h" +#include "keymap.h" #include diff --git a/src/protocols/rdp/rdp_keymap.h b/src/protocols/rdp/keymap.h similarity index 96% rename from src/protocols/rdp/rdp_keymap.h rename to src/protocols/rdp/keymap.h index f15ddea0..bac1a7fa 100644 --- a/src/protocols/rdp/rdp_keymap.h +++ b/src/protocols/rdp/keymap.h @@ -17,17 +17,10 @@ * 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 -#else -#include "compat/winpr-wtypes.h" -#endif /** * Represents a keysym-to-scancode mapping for RDP, with extra information diff --git a/src/protocols/rdp/keymaps/generate.pl b/src/protocols/rdp/keymaps/generate.pl index 263b616a..f1059a75 100755 --- a/src/protocols/rdp/keymaps/generate.pl +++ b/src/protocols/rdp/keymaps/generate.pl @@ -43,14 +43,9 @@ my @keymaps = (); open OUTPUT, ">", "_generated_keymaps.c"; print OUTPUT '#include "config.h"' . "\n" - . '#include "rdp_keymap.h"' . "\n" + . '#include "keymap.h"' . "\n" . '#include ' . "\n" - . "\n" - . '#ifdef HAVE_FREERDP_LOCALE_KEYBOARD_H' . "\n" . '#include ' . "\n" - . '#else' . "\n" - . '#include ' . "\n" - . '#endif' . "\n" . "\n" . '#include ' . "\n" . "\n"; diff --git a/src/protocols/rdp/log.c b/src/protocols/rdp/log.c new file mode 100644 index 00000000..29414c17 --- /dev/null +++ b/src/protocols/rdp/log.c @@ -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 +#include +#include + +#include + +/** + * 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); + +} + diff --git a/src/protocols/rdp/compat/winpr-wtypes.h b/src/protocols/rdp/log.h similarity index 70% rename from src/protocols/rdp/compat/winpr-wtypes.h rename to src/protocols/rdp/log.h index e1d3e1ba..3b40aeef 100644 --- a/src/protocols/rdp/compat/winpr-wtypes.h +++ b/src/protocols/rdp/log.h @@ -17,23 +17,19 @@ * under the License. */ +#ifndef GUAC_RDP_LOG_H +#define GUAC_RDP_LOG_H -#ifndef __GUAC_WINPR_WTYPES_COMPAT_H -#define __GUAC_WINPR_WTYPES_COMPAT_H +#include -#include "config.h" - -#include - -typedef uint8 BYTE; -typedef uint8 UINT8; -typedef uint16 UINT16; -typedef uint32 UINT32; -typedef uint64 UINT64; -typedef boolean BOOL; - -#define TRUE true -#define FALSE false +/** + * Redirects the core FreeRDP logging facility, wLog, such that it logs all + * messages at the debug level using guac_client_log(). + * + * @param client + * The guac_client that should receive all log messages. + */ +void guac_rdp_redirect_wlog(guac_client* client); #endif diff --git a/src/protocols/rdp/ls.c b/src/protocols/rdp/ls.c new file mode 100644 index 00000000..300bf93c --- /dev/null +++ b/src/protocols/rdp/ls.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +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; + +} + diff --git a/src/protocols/rdp/ls.h b/src/protocols/rdp/ls.h new file mode 100644 index 00000000..be6a10c7 --- /dev/null +++ b/src/protocols/rdp/ls.h @@ -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 +#include +#include + +#include + +/** + * 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 + diff --git a/src/protocols/rdp/plugins/channels.c b/src/protocols/rdp/plugins/channels.c new file mode 100644 index 00000000..0048b891 --- /dev/null +++ b/src/protocols/rdp/plugins/channels.c @@ -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 +#include +#include +#include + +#include +#include +#include + +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); + +} + diff --git a/src/protocols/rdp/plugins/channels.h b/src/protocols/rdp/plugins/channels.h new file mode 100644 index 00000000..b1207e2a --- /dev/null +++ b/src/protocols/rdp/plugins/channels.h @@ -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 +#include +#include +#include +#include + +/** + * 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 + diff --git a/src/protocols/rdp/plugins/generate-entry-wrappers.pl b/src/protocols/rdp/plugins/generate-entry-wrappers.pl new file mode 100755 index 00000000..f3fc0038 --- /dev/null +++ b/src/protocols/rdp/plugins/generate-entry-wrappers.pl @@ -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 +#include +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"; + diff --git a/src/protocols/rdp/plugins/guac-common-svc/guac-common-svc.c b/src/protocols/rdp/plugins/guac-common-svc/guac-common-svc.c new file mode 100644 index 00000000..fbba4842 --- /dev/null +++ b/src/protocols/rdp/plugins/guac-common-svc/guac-common-svc.c @@ -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 +#include +#include +#include +#include + +#include + +/** + * 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; + +} + diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/plugins/guacai/guacai-messages.c similarity index 95% rename from src/protocols/rdp/guac_ai/ai_messages.c rename to src/protocols/rdp/plugins/guacai/guacai-messages.c index c3465878..38f7a7c4 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.c +++ b/src/protocols/rdp/plugins/guacai/guacai-messages.c @@ -17,24 +17,15 @@ * under the License. */ -#include "config.h" - -#include "ai_messages.h" -#include "audio_input.h" +#include "channels/audio-input/audio-buffer.h" +#include "plugins/guacai/guacai-messages.h" #include "rdp.h" -#include - -#include -#include #include #include - -#ifdef ENABLE_WINPR #include -#else -#include "compat/winpr-stream.h" -#endif + +#include /** * 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, 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; diff --git a/src/protocols/rdp/guac_ai/ai_messages.h b/src/protocols/rdp/plugins/guacai/guacai-messages.h similarity index 92% rename from src/protocols/rdp/guac_ai/ai_messages.h rename to src/protocols/rdp/plugins/guacai/guacai-messages.h index 55cf6e9a..6a2333fc 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.h +++ b/src/protocols/rdp/plugins/guacai/guacai-messages.h @@ -17,19 +17,15 @@ * under the License. */ -#ifndef GUAC_RDP_AI_MESSAGES_H -#define GUAC_RDP_AI_MESSAGES_H +#ifndef GUAC_RDP_PLUGINS_GUACAI_MESSAGES_H +#define GUAC_RDP_PLUGINS_GUACAI_MESSAGES_H -#include "config.h" +#include "channels/audio-input/audio-buffer.h" #include #include - -#ifdef ENABLE_WINPR #include -#else -#include "compat/winpr-stream.h" -#endif +#include /** * 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, 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 diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/plugins/guacai/guacai.c similarity index 82% rename from src/protocols/rdp/guac_ai/ai_service.c rename to src/protocols/rdp/plugins/guacai/guacai.c index 5058ea0a..15de8655 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/plugins/guacai/guacai.c @@ -17,27 +17,20 @@ * under the License. */ -#include "config.h" - -#include "ai_messages.h" -#include "ai_service.h" -#include "audio_input.h" -#include "ptr_string.h" +#include "channels/audio-input/audio-buffer.h" +#include "plugins/guacai/guacai.h" +#include "plugins/guacai/guacai-messages.h" +#include "plugins/ptr-string.h" #include "rdp.h" -#include -#include - -#include -#include #include +#include #include - -#ifdef ENABLE_WINPR #include -#else -#include "compat/winpr-stream.h" -#endif +#include +#include + +#include /** * 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 - * AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.1 and older. - * - * @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. + * AUDIO_INPUT plugin. * * @param channel_callback * The IWTSVirtualChannelCallback structure to which this callback was @@ -143,7 +102,7 @@ static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, * @return * Always zero. */ -static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, +static UINT guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, wStream* stream) { 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 */ 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 @@ -169,7 +127,7 @@ static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, * @return * 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*) channel_callback; @@ -184,7 +142,7 @@ static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) { guac_rdp_audio_buffer_end(audio_buffer); free(ai_channel_callback); - return 0; + return CHANNEL_RC_OK; } @@ -219,7 +177,7 @@ static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) { * @return * Always zero. */ -static int guac_rdp_ai_new_connection( +static UINT guac_rdp_ai_new_connection( IWTSListenerCallback* listener_callback, IWTSVirtualChannel* channel, BYTE* data, int* accept, IWTSVirtualChannelCallback** channel_callback) { @@ -243,7 +201,8 @@ static int guac_rdp_ai_new_connection( /* Return callback through pointer */ *channel_callback = (IWTSVirtualChannelCallback*) ai_channel_callback; - return 0; + + return CHANNEL_RC_OK; } @@ -261,7 +220,7 @@ static int guac_rdp_ai_new_connection( * @return * Always zero. */ -static int guac_rdp_ai_initialize(IWTSPlugin* plugin, +static UINT guac_rdp_ai_initialize(IWTSPlugin* plugin, IWTSVirtualChannelManager* manager) { /* Allocate new listener callback */ @@ -281,7 +240,7 @@ static int guac_rdp_ai_initialize(IWTSPlugin* plugin, manager->CreateListener(manager, "AUDIO_INPUT", 0, (IWTSListenerCallback*) ai_listener_callback, NULL); - return 0; + return CHANNEL_RC_OK; } @@ -295,7 +254,7 @@ static int guac_rdp_ai_initialize(IWTSPlugin* plugin, * @return * 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_client* client = ai_plugin->client; @@ -305,7 +264,7 @@ static int guac_rdp_ai_terminated(IWTSPlugin* plugin) { free(ai_plugin); 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) { /* Pull guac_client from arguments */ -#ifdef HAVE_ADDIN_ARGV ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); 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 */ 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."); } - return 1; + return CHANNEL_RC_OK; } diff --git a/src/protocols/rdp/guac_ai/ai_service.h b/src/protocols/rdp/plugins/guacai/guacai.h similarity index 97% rename from src/protocols/rdp/guac_ai/ai_service.h rename to src/protocols/rdp/plugins/guacai/guacai.h index 28f2227e..d26563cd 100644 --- a/src/protocols/rdp/guac_ai/ai_service.h +++ b/src/protocols/rdp/plugins/guacai/guacai.h @@ -17,14 +17,12 @@ * under the License. */ -#ifndef GUAC_RDP_AI_SERVICE_H -#define GUAC_RDP_AI_SERVICE_H +#ifndef GUAC_RDP_PLUGINS_GUACAI_H +#define GUAC_RDP_PLUGINS_GUACAI_H -#include "config.h" - -#include #include #include +#include #include /** diff --git a/src/protocols/rdp/ptr_string.c b/src/protocols/rdp/plugins/ptr-string.c similarity index 92% rename from src/protocols/rdp/ptr_string.c rename to src/protocols/rdp/plugins/ptr-string.c index 5ec1b623..324a5195 100644 --- a/src/protocols/rdp/ptr_string.c +++ b/src/protocols/rdp/plugins/ptr-string.c @@ -17,13 +17,9 @@ * under the License. */ -#include "config.h" -#include "ptr_string.h" - -#include +#include "plugins/ptr-string.h" #include -#include void guac_rdp_ptr_to_string(void* data, char* str) { diff --git a/src/protocols/rdp/ptr_string.h b/src/protocols/rdp/plugins/ptr-string.h similarity index 96% rename from src/protocols/rdp/ptr_string.h rename to src/protocols/rdp/plugins/ptr-string.h index 9a9156bf..ef682b1d 100644 --- a/src/protocols/rdp/ptr_string.h +++ b/src/protocols/rdp/plugins/ptr-string.h @@ -17,10 +17,8 @@ * under the License. */ -#ifndef GUAC_RDP_PTR_STRING_H -#define GUAC_RDP_PTR_STRING_H - -#include "config.h" +#ifndef GUAC_RDP_PLUGINS_PTR_STRING_H +#define GUAC_RDP_PLUGINS_PTR_STRING_H #include diff --git a/src/protocols/rdp/rdp_pointer.c b/src/protocols/rdp/pointer.c similarity index 62% rename from src/protocols/rdp/rdp_pointer.c rename to src/protocols/rdp/pointer.c index f510b578..5a72c787 100644 --- a/src/protocols/rdp/rdp_pointer.c +++ b/src/protocols/rdp/pointer.c @@ -17,21 +17,21 @@ * under the License. */ -#include "config.h" - -#include "client.h" +#include "color.h" #include "common/cursor.h" #include "common/display.h" +#include "common/surface.h" +#include "pointer.h" #include "rdp.h" -#include "rdp_pointer.h" #include +#include #include +#include #include +#include -#include - -void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { +BOOL guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { guac_client* client = ((rdp_freerdp_context*) context)->client; 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); /* Allocate data for image */ - unsigned char* data = - (unsigned char*) malloc(pointer->width * pointer->height * 4); + unsigned char* data = _aligned_malloc(pointer->width * pointer->height * 4, 16); cairo_surface_t* surface; /* Convert to alpha cursor if mask data present */ if (pointer->andMaskData && pointer->xorMaskData) - freerdp_alpha_cursor_convert(data, - pointer->xorMaskData, pointer->andMaskData, - pointer->width, pointer->height, pointer->xorBpp, - ((rdp_freerdp_context*) context)->clrconv); + freerdp_image_copy_from_pointer_data(data, + guac_rdp_get_native_pixel_format(TRUE), 0, 0, 0, + pointer->width, pointer->height, pointer->xorMaskData, + pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, + &context->gdi->palette); /* Create surface from image data */ surface = cairo_image_surface_create_for_data( @@ -63,14 +64,16 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { /* Free surface */ cairo_surface_destroy(surface); - free(data); + _aligned_free(data); /* Remember 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_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, ((guac_rdp_pointer*) pointer)->layer->surface); + return TRUE; + } void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) { @@ -91,13 +96,31 @@ void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) { /* Free 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) { - /* STUB */ +BOOL guac_rdp_pointer_set_null(rdpContext* context) { + + 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) { - /* STUB */ +BOOL guac_rdp_pointer_set_default(rdpContext* context) { + + 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; } diff --git a/src/protocols/rdp/rdp_pointer.h b/src/protocols/rdp/pointer.h similarity index 80% rename from src/protocols/rdp/rdp_pointer.h rename to src/protocols/rdp/pointer.h index 995c8d6f..b5fa6a23 100644 --- a/src/protocols/rdp/rdp_pointer.h +++ b/src/protocols/rdp/pointer.h @@ -17,14 +17,14 @@ * 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 +#include +#include /** * Guacamole-specific rdpPointer data. @@ -52,8 +52,11 @@ typedef struct guac_rdp_pointer { * * @param pointer * 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 @@ -64,8 +67,11 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer); * * @param 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 @@ -84,8 +90,11 @@ void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer); * * @param context * 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 @@ -93,7 +102,10 @@ void guac_rdp_pointer_set_null(rdpContext* context); * * @param context * 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 diff --git a/src/protocols/rdp/rdp_print_job.c b/src/protocols/rdp/print-job.c similarity index 99% rename from src/protocols/rdp/rdp_print_job.c rename to src/protocols/rdp/print-job.c index a3b51bc1..5a75c6eb 100644 --- a/src/protocols/rdp/rdp_print_job.c +++ b/src/protocols/rdp/print-job.c @@ -17,9 +17,7 @@ * under the License. */ - -#include "config.h" -#include "rdp_print_job.h" +#include "print-job.h" #include #include diff --git a/src/protocols/rdp/rdp_print_job.h b/src/protocols/rdp/print-job.h similarity index 98% rename from src/protocols/rdp/rdp_print_job.h rename to src/protocols/rdp/print-job.h index 5682f186..fb990a82 100644 --- a/src/protocols/rdp/rdp_print_job.h +++ b/src/protocols/rdp/print-job.h @@ -20,8 +20,6 @@ #ifndef GUAC_RDP_PRINT_JOB_H #define GUAC_RDP_PRINT_JOB_H -#include "config.h" - #include #include #include @@ -79,6 +77,9 @@ typedef enum guac_rdp_print_job_state { */ typedef struct guac_rdp_print_job { + /** + * The Guacamole client associated with the RDP session. + */ guac_client* client; /** diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index d0df0531..6d8381d3 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -17,28 +17,30 @@ * under the License. */ -#include "config.h" - -#include "audio_input.h" +#include "bitmap.h" +#include "channels/audio-input/audio-buffer.h" +#include "channels/audio-input/audio-input.h" +#include "channels/cliprdr.h" +#include "channels/disp.h" +#include "channels/pipe-svc.h" +#include "channels/rail.h" +#include "channels/rdpdr/rdpdr.h" +#include "channels/rdpsnd/rdpsnd.h" #include "client.h" +#include "color.h" #include "common/cursor.h" #include "common/display.h" #include "common/recording.h" -#include "dvc.h" +#include "config.h" #include "error.h" +#include "fs.h" +#include "gdi.h" +#include "glyph.h" #include "keyboard.h" +#include "plugins/channels.h" +#include "pointer.h" +#include "print-job.h" #include "rdp.h" -#include "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 #include "common-ssh/sftp.h" @@ -46,6 +48,7 @@ #include "common-ssh/user.h" #endif +#include #include #include #include @@ -53,349 +56,130 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include #include #include #include #include #include - -#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H -#include -#else -#include "compat/client-cliprdr.h" -#endif - -#ifdef HAVE_FREERDP_CLIENT_DISP_H -#include -#endif - -#ifdef HAVE_FREERDP_EVENT_PUBSUB -#include -#endif - -#ifdef LEGACY_FREERDP -#include "compat/rail.h" -#else -#include -#endif - -#ifdef ENABLE_WINPR +#include +#include #include -#else -#include "compat/winpr-wtypes.h" -#endif -#ifdef HAVE_FREERDP_ADDIN_H -#include -#endif - -#ifdef HAVE_FREERDP_CLIENT_CHANNELS_H -#include -#endif - -#ifdef HAVE_FREERDP_VERSION_H -#include -#endif - -#include -#include -#include #include -#include -#include #include -/** - * 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) { rdpContext* context = instance->context; - rdpChannels* channels = context->channels; + rdpGraphics* graphics = context->graphics; guac_client* client = ((rdp_freerdp_context*) context)->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_settings* settings = rdp_client->settings; - rdpBitmap* bitmap; - rdpGlyph* glyph; - rdpPointer* pointer; - rdpPrimaryUpdate* primary; - CLRCONV* clrconv; + /* Push desired settings to FreeRDP */ + guac_rdp_push_settings(client, settings, instance); - guac_rdp_dvc_list* dvc_list = guac_rdp_dvc_list_alloc(); - -#ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER /* Init FreeRDP add-in provider */ freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); -#endif -#ifdef HAVE_FREERDP_EVENT_PUBSUB - /* Subscribe to and handle channel connected events */ - PubSub_SubscribeChannelConnected(context->pubSub, - (pChannelConnectedEventHandler) guac_rdp_channel_connected); -#endif - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /* Load "disp" plugin for display update */ if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) - guac_rdp_disp_load_plugin(instance->context, dvc_list); -#endif + guac_rdp_disp_load_plugin(context); /* Load "AUDIO_INPUT" plugin for audio input*/ if (settings->enable_audio_input) { 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 */ - if (!(settings->disable_copy && settings->disable_paste) - && freerdp_channels_load_plugin(channels, instance->settings, - "cliprdr", NULL)) { + /* Load "cliprdr" service if not disabled */ + if (!(settings->disable_copy && settings->disable_paste)) + guac_rdp_clipboard_load_plugin(rdp_client->clipboard, context); + else 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 (settings->printing_enabled || settings->drive_enabled || settings->audio_enabled) { - - /* Load RDPDR plugin */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "guacdr", client)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load guacdr plugin. Drive redirection and " - "printing will not work. Sound MAY not work."); - - /* Load RDPSND plugin */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "guacsnd", client)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load guacsnd alongside guacdr plugin. Sound " - "will not work. Drive redirection and printing MAY not " - "work."); - + guac_rdpdr_load_plugin(context); + guac_rdpsnd_load_plugin(context); } /* Load RAIL plugin if RemoteApp in use */ - if (settings->remote_app != NULL) { - -#ifdef LEGACY_FREERDP - RDP_PLUGIN_DATA* plugin_data = malloc(sizeof(RDP_PLUGIN_DATA) * 2); - - plugin_data[0].size = sizeof(RDP_PLUGIN_DATA); - plugin_data[0].data[0] = 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 - - } + if (settings->remote_app != NULL) + guac_rdp_rail_load_plugin(context); /* Load SVC plugin instances for all static channels */ if (settings->svc_names != NULL) { char** current = settings->svc_names; do { - - guac_rdp_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); - } - + guac_rdp_pipe_svc_load_plugin(context, *current); } while (*(++current) != NULL); } - /* Load DRDYNVC plugin if required */ - if (guac_rdp_load_drdynvc(instance->context, dvc_list)) + /* Load plugin providing Dynamic Virtual Channel support, if required */ + if (instance->settings->SupportDynamicChannels && + guac_freerdp_channels_load_plugin(context, "drdynvc", + instance->settings)) { guac_client_log(client, GUAC_LOG_WARNING, "Failed to load drdynvc plugin. Display update and audio " "input support will be disabled."); + } - /* Dynamic virtual channel list is no longer needed */ - guac_rdp_dvc_list_free(dvc_list); - - /* 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); + /* Init FreeRDP internal GDI implementation */ + if (!gdi_init(instance, guac_rdp_get_native_pixel_format(FALSE))) + return FALSE; /* Set up bitmap handling */ - bitmap = calloc(1, sizeof(rdpBitmap)); - bitmap->size = sizeof(guac_rdp_bitmap); - bitmap->New = guac_rdp_bitmap_new; - bitmap->Free = guac_rdp_bitmap_free; - bitmap->Paint = guac_rdp_bitmap_paint; - bitmap->Decompress = guac_rdp_bitmap_decompress; - bitmap->SetSurface = guac_rdp_bitmap_setsurface; - graphics_register_bitmap(context->graphics, bitmap); - free(bitmap); + rdpBitmap bitmap = *graphics->Bitmap_Prototype; + bitmap.size = sizeof(guac_rdp_bitmap); + bitmap.New = guac_rdp_bitmap_new; + bitmap.Free = guac_rdp_bitmap_free; + bitmap.Paint = guac_rdp_bitmap_paint; + bitmap.SetSurface = guac_rdp_bitmap_setsurface; + graphics_register_bitmap(graphics, &bitmap); /* Set up glyph handling */ - glyph = calloc(1, sizeof(rdpGlyph)); - glyph->size = sizeof(guac_rdp_glyph); - glyph->New = guac_rdp_glyph_new; - glyph->Free = guac_rdp_glyph_free; - glyph->Draw = guac_rdp_glyph_draw; - glyph->BeginDraw = guac_rdp_glyph_begindraw; - glyph->EndDraw = guac_rdp_glyph_enddraw; - graphics_register_glyph(context->graphics, glyph); - free(glyph); + rdpGlyph glyph = *graphics->Glyph_Prototype; + glyph.size = sizeof(guac_rdp_glyph); + glyph.New = guac_rdp_glyph_new; + glyph.Free = guac_rdp_glyph_free; + glyph.Draw = guac_rdp_glyph_draw; + glyph.BeginDraw = guac_rdp_glyph_begindraw; + glyph.EndDraw = guac_rdp_glyph_enddraw; + graphics_register_glyph(graphics, &glyph); /* Set up pointer handling */ - pointer = calloc(1, sizeof(rdpPointer)); - pointer->size = sizeof(guac_rdp_pointer); - pointer->New = guac_rdp_pointer_new; - pointer->Free = guac_rdp_pointer_free; - pointer->Set = guac_rdp_pointer_set; -#ifdef HAVE_RDPPOINTER_SETNULL - pointer->SetNull = guac_rdp_pointer_set_null; -#endif -#ifdef HAVE_RDPPOINTER_SETDEFAULT - pointer->SetDefault = guac_rdp_pointer_set_default; -#endif - graphics_register_pointer(context->graphics, pointer); - free(pointer); + rdpPointer pointer = *graphics->Pointer_Prototype; + pointer.size = sizeof(guac_rdp_pointer); + pointer.New = guac_rdp_pointer_new; + pointer.Free = guac_rdp_pointer_free; + pointer.Set = guac_rdp_pointer_set; + pointer.SetNull = guac_rdp_pointer_set_null; + pointer.SetDefault = guac_rdp_pointer_set_default; + graphics_register_pointer(graphics, &pointer); /* Set up GDI */ instance->update->DesktopResize = guac_rdp_gdi_desktop_resize; instance->update->EndPaint = guac_rdp_gdi_end_paint; - instance->update->Palette = guac_rdp_gdi_palette_update; instance->update->SetBounds = guac_rdp_gdi_set_bounds; - primary = instance->update->primary; + rdpPrimaryUpdate* primary = instance->update->primary; primary->DstBlt = guac_rdp_gdi_dstblt; - primary->PatBlt = guac_rdp_gdi_patblt; primary->ScrBlt = guac_rdp_gdi_scrblt; primary->MemBlt = guac_rdp_gdi_memblt; - primary->OpaqueRect = guac_rdp_gdi_opaquerect; pointer_cache_register_callbacks(instance->update); glyph_cache_register_callbacks(instance->update); @@ -404,39 +188,6 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { offscreen_cache_register_callbacks(instance->update); palette_cache_register_callbacks(instance->update); - /* Init channels (pre-connect) */ - if (freerdp_channels_pre_connect(channels, instance)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error initializing RDP client channel manager"); - return FALSE; - } - - return TRUE; - -} - -/** - * Callback invoked by FreeRDP just after the connection is established with - * the RDP server. Implementations are required to manually invoke - * freerdp_channels_post_connect(). - * - * @param instance - * The FreeRDP instance that has just connected. - * - * @return - * TRUE if successful, FALSE if an error occurs. - */ -static BOOL rdp_freerdp_post_connect(freerdp* instance) { - - rdpContext* context = instance->context; - guac_client* client = ((rdp_freerdp_context*) context)->client; - rdpChannels* channels = instance->context->channels; - - /* Init channels (post-connect) */ - if (freerdp_channels_post_connect(channels, instance)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error initializing RDP client channel manager"); - return FALSE; - } - return TRUE; } @@ -499,8 +250,9 @@ static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, * @return * TRUE if the certificate passes verification, FALSE otherwise. */ -static BOOL rdp_freerdp_verify_certificate(freerdp* instance, char* subject, - char* issuer, char* fingerprint) { +static DWORD rdp_freerdp_verify_certificate(freerdp* instance, + const char* common_name, const char* subject, const char* issuer, + const char* fingerprint, BOOL host_mismatch) { rdpContext* context = instance->context; 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 */ if (rdp_client->settings->ignore_certificate) { 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"); - 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. * @@ -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; freerdp* rdp_inst = rdp_client->rdp_inst; - rdpChannels* channels = rdp_inst->context->channels; - int result; - int index; + HANDLE handles[GUAC_RDP_MAX_FILE_DESCRIPTORS]; + 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 */ - void* read_fds[GUAC_RDP_MAX_FILE_DESCRIPTORS]; - int read_count = 0; + /* Wait for data and construct a reasonable frame */ + int result = WaitForMultipleObjects(num_handles, handles, FALSE, + timeout_msecs); - /* List of all file descriptors which data may be written to. These will - * ultimately be ignored, but FreeRDP requires that both read and write - * file descriptors be retrieved simultaneously. */ - void* write_fds[GUAC_RDP_MAX_FILE_DESCRIPTORS]; - int write_count = 0; + /* Translate WaitForMultipleObjects() return values */ + switch (result) { - struct pollfd fds[GUAC_RDP_MAX_FILE_DESCRIPTORS]; - - /* 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) + /* Timeout elapsed before wait could complete */ + case WAIT_TIMEOUT: return 0; - /* Otherwise, return as error */ - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, - "Error waiting for file descriptor."); - return -1; + /* Attempt to wait failed due to an error */ + case WAIT_FAILED: + return -1; } - /* Return wait result */ - return result; + /* Wait was successful */ + 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->requested_clipboard_format = CB_FORMAT_TEXT; rdp_client->available_svc = guac_common_list_alloc(); -#ifdef HAVE_FREERDP_CHANNELS_GLOBAL_INIT - freerdp_channels_global_init(); -#endif - /* Init client */ freerdp* rdp_inst = freerdp_new(); rdp_inst->PreConnect = rdp_freerdp_pre_connect; - rdp_inst->PostConnect = rdp_freerdp_post_connect; rdp_inst->Authenticate = rdp_freerdp_authenticate; rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate; - rdp_inst->ReceiveChannelData = __guac_receive_channel_data; /* Allocate FreeRDP context */ -#ifdef LEGACY_FREERDP - rdp_inst->context_size = sizeof(rdp_freerdp_context); -#else rdp_inst->ContextSize = sizeof(rdp_freerdp_context); -#endif - rdp_inst->ContextNew = (pContextNew) rdp_freerdp_context_new; - rdp_inst->ContextFree = (pContextFree) rdp_freerdp_context_free; freerdp_context_new(rdp_inst); ((rdp_freerdp_context*) rdp_inst->context)->client = client; @@ -720,9 +381,6 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Set default pointer */ 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 */ if (!freerdp_connect(rdp_inst)) { 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 */ rdp_client->rdp_inst = rdp_inst; - rdpChannels* channels = rdp_inst->context->channels; 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)) { /* Update remote display size */ - pthread_mutex_lock(&(rdp_client->rdp_lock)); 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 */ 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; int frame_remaining; - pthread_mutex_lock(&(rdp_client->rdp_lock)); - /* Check the libfreerdp fds */ - if (!freerdp_check_fds(rdp_inst) - || !freerdp_channels_check_fds(channels, rdp_inst)) { + if (!freerdp_check_event_handles(rdp_inst->context)) { /* Flag connection failure */ wait_result = -1; - pthread_mutex_unlock(&(rdp_client->rdp_lock)); 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 */ frame_end = guac_timestamp_current(); 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 */ - pthread_mutex_lock(&(rdp_client->rdp_lock)); int connection_closing = freerdp_shall_disconnect(rdp_inst); - pthread_mutex_unlock(&(rdp_client->rdp_lock)); /* Close connection cleanly if server is disconnecting */ 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); } - pthread_mutex_lock(&(rdp_client->rdp_lock)); - /* Disconnect client and channels */ - freerdp_channels_close(channels, rdp_inst); - freerdp_channels_free(channels); freerdp_disconnect(rdp_inst); + /* Clean up FreeRDP internal GDI implementation */ + gdi_free(rdp_inst); + /* 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); /* Clean up RDP client */ @@ -883,8 +506,6 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Free display */ guac_common_display_free(rdp_client->display); - pthread_mutex_unlock(&(rdp_client->rdp_lock)); - /* Client is now disconnected */ guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected"); diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 1d2eb110..32f72df7 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -20,24 +20,19 @@ #ifndef GUAC_RDP_H #define GUAC_RDP_H -#include "config.h" - -#include "audio_input.h" +#include "channels/audio-input/audio-buffer.h" +#include "channels/cliprdr.h" +#include "channels/disp.h" #include "common/clipboard.h" #include "common/display.h" #include "common/list.h" #include "common/recording.h" #include "common/surface.h" +#include "config.h" +#include "fs.h" #include "keyboard.h" -#include "rdp_disp.h" -#include "rdp_fs.h" -#include "rdp_print_job.h" -#include "rdp_settings.h" - -#include -#include -#include -#include +#include "print-job.h" +#include "settings.h" #ifdef ENABLE_COMMON_SSH #include "common-ssh/sftp.h" @@ -45,6 +40,12 @@ #include "common-ssh/user.h" #endif +#include +#include +#include +#include +#include + #include #include @@ -95,17 +96,9 @@ typedef struct guac_rdp_client { guac_rdp_keyboard* keyboard; /** - * The current clipboard contents. + * The current state of the clipboard and the CLIPRDR channel. */ - guac_common_clipboard* clipboard; - - /** - * The format of the clipboard which was requested. Data received from - * the RDP server should conform to this format. This will be one of - * several legal clipboard format values defined within FreeRDP, such as - * CB_FORMAT_TEXT. - */ - int requested_clipboard_format; + guac_rdp_clipboard* clipboard; /** * Audio output, if any. @@ -160,13 +153,6 @@ typedef struct guac_rdp_client { */ 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. */ @@ -191,11 +177,6 @@ typedef struct rdp_freerdp_context { */ guac_client* client; - /** - * Color conversion structure to be used to convert RDP images to PNGs. - */ - CLRCONV* clrconv; - /** * The current color palette, as received from the RDP server. */ diff --git a/src/protocols/rdp/rdp_cliprdr.c b/src/protocols/rdp/rdp_cliprdr.c deleted file mode 100644 index 903752c8..00000000 --- a/src/protocols/rdp/rdp_cliprdr.c +++ /dev/null @@ -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 -#include -#include -#include - -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-wtypes.h" -#endif - -#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H -#include -#else -#include "compat/client-cliprdr.h" -#endif - -#include -#include - -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; inum_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); - - } - -} - diff --git a/src/protocols/rdp/rdp_cliprdr.h b/src/protocols/rdp/rdp_cliprdr.h deleted file mode 100644 index b66e1b76..00000000 --- a/src/protocols/rdp/rdp_cliprdr.h +++ /dev/null @@ -1,120 +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_RDP_CLIPRDR_H -#define __GUAC_RDP_RDP_CLIPRDR_H - -#include "config.h" - -#include - -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-stream.h" -#endif - -#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H -#include -#else -#include "compat/client-cliprdr.h" -#endif - -/** - * Clipboard format for text encoded in Windows CP1252. - */ -#define GUAC_RDP_CLIPBOARD_FORMAT_CP1252 1 - -/** - * Clipboard format for text encoded in UTF-16. - */ -#define GUAC_RDP_CLIPBOARD_FORMAT_UTF16 2 - -/** - * Called within the main RDP connection thread whenever a CLIPRDR message is - * received. This function will dispatch that message to an appropriate - * function, specific to that message type. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message. - */ -void guac_rdp_process_cliprdr_event(guac_client* client, wMessage* event); - -/** - * Handles the given CLIPRDR event, which MUST be a Monitor Ready event. It - * is the responsibility of this function to respond to the Monitor Ready - * event with a list of supported clipboard formats. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message, which must be a Monitor Ready event. - */ -void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event); - -/** - * Handles the given CLIPRDR event, which MUST be a Format List event. It - * is the responsibility of this function to respond to the Format List - * event with a request for clipboard data in one of the enumerated formats. - * This event is fired whenever remote clipboard data is available. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message, which must be a Format List event. - */ -void guac_rdp_process_cb_format_list(guac_client* client, - RDP_CB_FORMAT_LIST_EVENT* event); - -/** - * Handles the given CLIPRDR event, which MUST be a Data Request event. It - * is the responsibility of this function to respond to the Data Request - * event with a data response containing the current clipoard contents. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message, which must be a Data Request event. - */ -void guac_rdp_process_cb_data_request(guac_client* client, - RDP_CB_DATA_REQUEST_EVENT* event); - -/** - * Handles the given CLIPRDR event, which MUST be a Data Response event. It - * is the responsibility of this function to read and forward the received - * clipboard data to connected clients. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message, which must be a Data Response event. - */ -void guac_rdp_process_cb_data_response(guac_client* client, - RDP_CB_DATA_RESPONSE_EVENT* event); - -#endif - diff --git a/src/protocols/rdp/rdp_color.c b/src/protocols/rdp/rdp_color.c deleted file mode 100644 index 0b7de7fc..00000000 --- a/src/protocols/rdp/rdp_color.c +++ /dev/null @@ -1,62 +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 "rdp.h" -#include "rdp_settings.h" - -#include -#include - -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-wtypes.h" -#endif - -UINT32 guac_rdp_convert_color(rdpContext* context, UINT32 color) { - -#ifdef HAVE_FREERDP_CONVERT_GDI_ORDER_COLOR - UINT32* palette = ((rdp_freerdp_context*) context)->palette; - - /* Convert given color to ARGB32 */ - return freerdp_convert_gdi_order_color(color, - guac_rdp_get_depth(context->instance), PIXEL_FORMAT_ARGB32, - (BYTE*) palette); - -#elif defined(HAVE_FREERDP_COLOR_CONVERT_DRAWING_ORDER_COLOR_TO_GDI_COLOR) - CLRCONV* clrconv = ((rdp_freerdp_context*) context)->clrconv; - - /* Convert given color to ARGB32 */ - return freerdp_color_convert_drawing_order_color_to_gdi_color(color, - guac_rdp_get_depth(context->instance), clrconv); - -#else - CLRCONV* clrconv = ((rdp_freerdp_context*) context)->clrconv; - - /* Convert given color to ARGB32 */ - return freerdp_color_convert_var(color, - guac_rdp_get_depth(context->instance), 32, - clrconv); -#endif - -} - diff --git a/src/protocols/rdp/rdp_rail.c b/src/protocols/rdp/rdp_rail.c deleted file mode 100644 index 671be8db..00000000 --- a/src/protocols/rdp/rdp_rail.c +++ /dev/null @@ -1,122 +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 "rdp.h" -#include "rdp_rail.h" -#include "rdp_settings.h" - -#include -#include -#include -#include - -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-wtypes.h" -#endif - -#ifdef LEGACY_FREERDP -#include "compat/rail.h" -#else -#include -#endif - -#include - -void guac_rdp_process_rail_event(guac_client* client, wMessage* event) { - -#ifdef LEGACY_EVENT - switch (event->event_type) { -#else - switch (GetMessageType(event->id)) { -#endif - - /* Get system parameters */ - case RailChannel_GetSystemParam: - guac_rdp_process_rail_get_sysparam(client, event); - break; - - /* Currently ignored events */ - case RailChannel_ServerSystemParam: - case RailChannel_ServerExecuteResult: - case RailChannel_ServerMinMaxInfo: - case RailChannel_ServerLocalMoveSize: - case RailChannel_ServerGetAppIdResponse: - case RailChannel_ServerLanguageBarInfo: - break; - - default: -#ifdef LEGACY_EVENT - guac_client_log(client, GUAC_LOG_INFO, - "Unknown rail event type: 0x%x", - event->event_type); -#else - guac_client_log(client, GUAC_LOG_INFO, - "Unknown rail event type: 0x%x", - GetMessageType(event->id)); -#endif - - } - -} - -void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event) { - - wMessage* response; - RAIL_SYSPARAM_ORDER* sysparam; - - /* Get channels */ - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - rdpChannels* channels = rdp_client->rdp_inst->context->channels; - - /* Get sysparam structure */ -#ifdef LEGACY_EVENT - sysparam = (RAIL_SYSPARAM_ORDER*) event->user_data; -#else - sysparam = (RAIL_SYSPARAM_ORDER*) event->wParam; -#endif - - response = freerdp_event_new(RailChannel_Class, - RailChannel_ClientSystemParam, - NULL, - sysparam); - - /* Work area */ - sysparam->workArea.left = 0; - sysparam->workArea.top = 0; - sysparam->workArea.right = rdp_client->settings->width; - sysparam->workArea.bottom = rdp_client->settings->height; - - /* Taskbar */ - sysparam->taskbarPos.left = 0; - sysparam->taskbarPos.top = 0; - sysparam->taskbarPos.right = 0; - sysparam->taskbarPos.bottom = 0; - - sysparam->dragFullWindows = FALSE; - - /* Send response */ - freerdp_channels_send_event(channels, response); - -} - diff --git a/src/protocols/rdp/rdp_rail.h b/src/protocols/rdp/rdp_rail.h deleted file mode 100644 index 949eb3e4..00000000 --- a/src/protocols/rdp/rdp_rail.h +++ /dev/null @@ -1,58 +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_RDP_RAIL_H -#define __GUAC_RDP_RDP_RAIL_H - -#include "config.h" - -#include - -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-stream.h" -#endif - -/** - * Dispatches a given RAIL event to the appropriate handler. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The RAIL event to process. - */ -void guac_rdp_process_rail_event(guac_client* client, wMessage* event); - -/** - * Handles the event sent when updating system parameters. The event given - * MUST be a SYSPARAM event. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The system parameter event to process. - */ -void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event); - -#endif - diff --git a/src/protocols/rdp/rdp_status.h b/src/protocols/rdp/rdp_status.h deleted file mode 100644 index 6a2d5b6b..00000000 --- a/src/protocols/rdp/rdp_status.h +++ /dev/null @@ -1,66 +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_STATUS_H -#define __GUAC_RDP_STATUS_H - -/** - * RDP-specific status constants. - * - * @file rdp_status.h - */ - -#include "config.h" - -/* Include any constants from winpr/file.h, if available */ - -#ifdef ENABLE_WINPR -#include -#endif - -/* Constants which MAY be defined within FreeRDP */ - -#ifndef STATUS_SUCCESS -#define STATUS_SUCCESS 0x00000000 -#define STATUS_NO_MORE_FILES 0x80000006 -#define STATUS_DEVICE_OFF_LINE 0x80000010 -#define STATUS_NOT_IMPLEMENTED 0xC0000002 -#define STATUS_INVALID_PARAMETER 0xC000000D -#define STATUS_NO_SUCH_FILE 0xC000000F -#define STATUS_END_OF_FILE 0xC0000011 -#define STATUS_ACCESS_DENIED 0xC0000022 -#define STATUS_OBJECT_NAME_COLLISION 0xC0000035 -#define STATUS_DISK_FULL 0xC000007F -#define STATUS_FILE_INVALID 0xC0000098 -#define STATUS_FILE_IS_A_DIRECTORY 0xC00000BA -#define STATUS_NOT_SUPPORTED 0xC00000BB -#define STATUS_NOT_A_DIRECTORY 0xC0000103 -#define STATUS_TOO_MANY_OPENED_FILES 0xC000011F -#define STATUS_CANNOT_DELETE 0xC0000121 -#define STATUS_FILE_DELETED 0xC0000123 -#define STATUS_FILE_CLOSED 0xC0000128 -#endif - -/* Constants which are NEVER defined within FreeRDP */ - -#define STATUS_FILE_SYSTEM_LIMITATION 0xC0000427 -#define STATUS_FILE_TOO_LARGE 0xC0000904 - -#endif diff --git a/src/protocols/rdp/rdp_stream.c b/src/protocols/rdp/rdp_stream.c deleted file mode 100644 index 533ff07e..00000000 --- a/src/protocols/rdp/rdp_stream.c +++ /dev/null @@ -1,594 +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 "rdp.h" -#include "rdp_fs.h" -#include "rdp_svc.h" -#include "rdp_stream.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H -#include -#else -#include "compat/client-cliprdr.h" -#endif - -#ifdef ENABLE_WINPR -#include -#include -#else -#include "compat/winpr-stream.h" -#include "compat/winpr-wtypes.h" -#endif - -#include - -/** - * Writes the given filename to the given upload path, sanitizing the filename - * and translating the filename to the root directory. - */ -static void __generate_upload_path(const char* filename, char* path) { - - int i; - - /* Add initial backslash */ - *(path++) = '\\'; - - for (i=1; iclient; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - - int file_id; - guac_rdp_stream* rdp_stream; - char file_path[GUAC_RDP_FS_MAX_PATH]; - - /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = rdp_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; - } - - /* Translate name */ - __generate_upload_path(filename, file_path); - - /* Open file */ - file_id = guac_rdp_fs_open(fs, file_path, ACCESS_GENERIC_WRITE, 0, - DISP_FILE_OVERWRITE_IF, 0); - if (file_id < 0) { - guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)", - GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); - guac_socket_flush(user->socket); - return 0; - } - - /* Init upload status */ - rdp_stream = malloc(sizeof(guac_rdp_stream)); - rdp_stream->type = GUAC_RDP_UPLOAD_STREAM; - rdp_stream->upload_status.offset = 0; - rdp_stream->upload_status.file_id = file_id; - stream->data = rdp_stream; - stream->blob_handler = guac_rdp_upload_blob_handler; - stream->end_handler = guac_rdp_upload_end_handler; - - guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - return 0; - -} - -int guac_rdp_svc_pipe_handler(guac_user* user, guac_stream* stream, - char* mimetype, char* name) { - - guac_rdp_stream* rdp_stream; - guac_rdp_svc* svc = guac_rdp_get_svc(user->client, name); - - /* Fail if no such SVC */ - if (svc == NULL) { - guac_user_log(user, GUAC_LOG_ERROR, - "Requested non-existent pipe: \"%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_ERROR, - "Inbound half of channel \"%s\" connected.", - name); - - /* Init stream data */ - stream->data = rdp_stream = malloc(sizeof(guac_rdp_stream)); - stream->blob_handler = guac_rdp_svc_blob_handler; - rdp_stream->type = GUAC_RDP_INBOUND_SVC_STREAM; - rdp_stream->svc = svc; - - return 0; - -} - -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; - guac_rdp_stream* rdp_stream; - - /* Init stream data */ - stream->data = rdp_stream = malloc(sizeof(guac_rdp_stream)); - stream->blob_handler = guac_rdp_clipboard_blob_handler; - stream->end_handler = guac_rdp_clipboard_end_handler; - rdp_stream->type = GUAC_RDP_INBOUND_CLIPBOARD_STREAM; - - guac_common_clipboard_reset(rdp_client->clipboard, mimetype); - return 0; - -} - -int guac_rdp_upload_blob_handler(guac_user* user, guac_stream* stream, - void* data, int length) { - - int bytes_written; - guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; - - /* Get filesystem, return error if no filesystem 0*/ - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_rdp_fs* fs = rdp_client->filesystem; - if (fs == NULL) { - guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)", - GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(user->socket); - return 0; - } - - /* Write entire block */ - while (length > 0) { - - /* Attempt write */ - bytes_written = guac_rdp_fs_write(fs, - rdp_stream->upload_status.file_id, - rdp_stream->upload_status.offset, - data, length); - - /* On error, abort */ - if (bytes_written < 0) { - guac_protocol_send_ack(user->socket, stream, - "FAIL (BAD WRITE)", - GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); - guac_socket_flush(user->socket); - return 0; - } - - /* Update counters */ - rdp_stream->upload_status.offset += bytes_written; - data += bytes_written; - length -= bytes_written; - - } - - guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - return 0; - -} - -int guac_rdp_svc_blob_handler(guac_user* user, guac_stream* stream, - void* data, int length) { - - guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; - - /* Write blob data to SVC directly */ - guac_rdp_svc_write(rdp_stream->svc, data, length); - - guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - 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; - guac_common_clipboard_append(rdp_client->clipboard, (char*) data, length); - - return 0; -} - -int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream) { - - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; - - /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = rdp_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; - } - - /* Close file */ - guac_rdp_fs_close(fs, rdp_stream->upload_status.file_id); - - /* Acknowledge stream end */ - guac_protocol_send_ack(user->socket, stream, "OK (STREAM END)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - - free(rdp_stream); - 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; - - /* Terminate clipboard data with NULL */ - guac_common_clipboard_append(rdp_client->clipboard, "", 1); - - /* Notify RDP server of new data, if connected */ - freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst != NULL) { - - rdpChannels* channels = 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); - - /* Notify server that text data is now available */ - 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); - - } - - return 0; -} - -int guac_rdp_download_ack_handler(guac_user* user, guac_stream* stream, - char* message, guac_protocol_status status) { - - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; - - /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = rdp_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, - rdp_stream->download_status.file_id, - rdp_stream->download_status.offset, buffer, sizeof(buffer)); - - /* If bytes read, send as blob */ - if (bytes_read > 0) { - rdp_stream->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(rdp_stream); - } - - /* 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(rdp_stream); - } - - guac_socket_flush(user->socket); - - } - - /* Otherwise, return stream to user */ - else - guac_user_free_stream(user, stream); - - return 0; - -} - -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_stream* rdp_stream = (guac_rdp_stream*) stream->data; - - /* If unsuccessful, free stream and abort */ - if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { - guac_rdp_fs_close(rdp_stream->ls_status.fs, - rdp_stream->ls_status.file_id); - guac_user_free_stream(user, stream); - free(rdp_stream); - return 0; - } - - /* While directory entries remain */ - while ((filename = guac_rdp_fs_read_dir(rdp_stream->ls_status.fs, - rdp_stream->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, - rdp_stream->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(rdp_stream->ls_status.fs, absolute_path, - ACCESS_GENERIC_READ, 0, DISP_FILE_OPEN, 0); - if (file_id < 0) - continue; - - /* Get opened file */ - guac_rdp_fs_file* file = guac_rdp_fs_get_file(rdp_stream->ls_status.fs, - file_id); - if (file == NULL) { - guac_client_log(rdp_stream->ls_status.fs->client, 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, - &rdp_stream->ls_status.json_state, absolute_path, mimetype); - - guac_rdp_fs_close(rdp_stream->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, - &rdp_stream->ls_status.json_state); - guac_common_json_flush(user, stream, - &rdp_stream->ls_status.json_state); - - /* Clean up resources */ - guac_rdp_fs_close(rdp_stream->ls_status.fs, - rdp_stream->ls_status.file_id); - free(rdp_stream); - - /* Signal of stream */ - guac_protocol_send_end(user->socket, stream); - guac_user_free_stream(user, stream); - - } - - guac_socket_flush(user->socket); - 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, ACCESS_GENERIC_READ, 0, - DISP_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_stream* rdp_stream = malloc(sizeof(guac_rdp_stream)); - rdp_stream->type = GUAC_RDP_LS_STREAM; - rdp_stream->ls_status.fs = fs; - rdp_stream->ls_status.file_id = file_id; - guac_strlcpy(rdp_stream->ls_status.directory_name, name, - sizeof(rdp_stream->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 = rdp_stream; - - /* Init JSON object state */ - guac_common_json_begin_object(user, stream, - &rdp_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_stream* rdp_stream = malloc(sizeof(guac_rdp_stream)); - rdp_stream->type = GUAC_RDP_DOWNLOAD_STREAM; - rdp_stream->download_status.file_id = file_id; - rdp_stream->download_status.offset = 0; - - /* Allocate stream for body */ - guac_stream* stream = guac_user_alloc_stream(user); - stream->data = rdp_stream; - stream->ack_handler = guac_rdp_download_ack_handler; - - /* Associate new stream with get request */ - guac_protocol_send_body(user->socket, object, stream, - "application/octet-stream", name); - - } - - guac_socket_flush(user->socket); - return 0; -} - -int guac_rdp_upload_put_handler(guac_user* user, guac_object* object, - guac_stream* stream, char* mimetype, char* name) { - - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - - /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = rdp_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; - } - - /* Open file */ - int file_id = guac_rdp_fs_open(fs, name, ACCESS_GENERIC_WRITE, 0, - DISP_FILE_OVERWRITE_IF, 0); - - /* Abort on failure */ - if (file_id < 0) { - guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)", - GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); - guac_socket_flush(user->socket); - return 0; - } - - /* Init upload stream data */ - guac_rdp_stream* rdp_stream = malloc(sizeof(guac_rdp_stream)); - rdp_stream->type = GUAC_RDP_UPLOAD_STREAM; - rdp_stream->upload_status.offset = 0; - rdp_stream->upload_status.file_id = file_id; - - /* Allocate stream, init for file upload */ - stream->data = rdp_stream; - stream->blob_handler = guac_rdp_upload_blob_handler; - stream->end_handler = guac_rdp_upload_end_handler; - - /* Acknowledge stream creation */ - guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - return 0; -} - diff --git a/src/protocols/rdp/rdp_stream.h b/src/protocols/rdp/rdp_stream.h deleted file mode 100644 index deec4f9a..00000000 --- a/src/protocols/rdp/rdp_stream.h +++ /dev/null @@ -1,226 +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_STREAM_H -#define _GUAC_RDP_STREAM_H - -#include "config.h" -#include "common/json.h" -#include "rdp_svc.h" - -#include -#include -#include - -#include - -/** - * 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; - -/** - * Structure which represents the current state of an upload. - */ -typedef struct guac_rdp_upload_status { - - /** - * The overall offset within the file that the next write should - * occur at. - */ - uint64_t offset; - - /** - * The ID of the file being written to. - */ - int file_id; - -} guac_rdp_upload_status; - -/** - * 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; - -/** - * All available stream types. - */ -typedef enum guac_rdp_stream_type { - - /** - * An in-progress file upload. - */ - GUAC_RDP_UPLOAD_STREAM, - - /** - * An in-progress file download. - */ - GUAC_RDP_DOWNLOAD_STREAM, - - /** - * An in-progress stream of a directory listing. - */ - GUAC_RDP_LS_STREAM, - - /** - * The inbound half of a static virtual channel. - */ - GUAC_RDP_INBOUND_SVC_STREAM, - - /** - * An inbound stream of clipboard data. - */ - GUAC_RDP_INBOUND_CLIPBOARD_STREAM - -} guac_rdp_stream_type; - -/** - * Variable-typed stream data. - */ -typedef struct guac_rdp_stream { - - /** - * The type of this stream. - */ - guac_rdp_stream_type type; - - /** - * The file upload status. Only valid for GUAC_RDP_UPLOAD_STREAM. - */ - guac_rdp_upload_status upload_status; - - /** - * The file upload status. Only valid for GUAC_RDP_DOWNLOAD_STREAM. - */ - guac_rdp_download_status download_status; - - /** - * The directory list status. Only valid for GUAC_RDP_LS_STREAM. - */ - guac_rdp_ls_status ls_status; - - /** - * Associated SVC instance. Only valid for GUAC_RDP_INBOUND_SVC_STREAM. - */ - guac_rdp_svc* svc; - -} guac_rdp_stream; - -/** - * Handler for inbound files related to file uploads. - */ -guac_user_file_handler guac_rdp_upload_file_handler; - -/** - * Handler for inbound pipes related to static virtual channels. - */ -guac_user_pipe_handler guac_rdp_svc_pipe_handler; - -/** - * Handler for inbound clipboard data. - */ -guac_user_clipboard_handler guac_rdp_clipboard_handler; - -/** - * Handler for stream data related to file uploads. - */ -guac_user_blob_handler guac_rdp_upload_blob_handler; - -/** - * Handler for stream data related to static virtual channels. - */ -guac_user_blob_handler guac_rdp_svc_blob_handler; - -/** - * Handler for stream data related to clipboard. - */ -guac_user_blob_handler guac_rdp_clipboard_blob_handler; - -/** - * Handler for end-of-stream related to file uploads. - */ -guac_user_end_handler guac_rdp_upload_end_handler; - -/** - * Handler for end-of-stream related to clipboard. - */ -guac_user_end_handler guac_rdp_clipboard_end_handler; - -/** - * Handler for acknowledgements of receipt of data related to file downloads. - */ -guac_user_ack_handler guac_rdp_download_ack_handler; - -/** - * Handler for ack messages received due to receipt of a "body" or "blob" - * instruction associated with a directory list operation. - */ -guac_user_ack_handler guac_rdp_ls_ack_handler; - -/** - * Handler for get messages. In context of downloads and the filesystem exposed - * via the Guacamole protocol, get messages request the body of a file within - * the filesystem. - */ -guac_user_get_handler guac_rdp_download_get_handler; - -/** - * Handler for put messages. In context of uploads and the filesystem exposed - * via the Guacamole protocol, put messages request write access to a file - * within the filesystem. - */ -guac_user_put_handler guac_rdp_upload_put_handler; - -#endif - diff --git a/src/protocols/rdp/rdp_svc.c b/src/protocols/rdp/rdp_svc.c deleted file mode 100644 index 0a6eb246..00000000 --- a/src/protocols/rdp/rdp_svc.c +++ /dev/null @@ -1,176 +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/list.h" -#include "rdp.h" -#include "rdp_svc.h" - -#include -#include -#include - -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-stream.h" -#endif - -#include - -guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name) { - - guac_rdp_svc* svc = malloc(sizeof(guac_rdp_svc)); - - /* Init SVC */ - svc->client = client; - svc->plugin = NULL; - svc->output_pipe = NULL; - - /* Init name */ - int name_length = guac_strlcpy(svc->name, name, GUAC_RDP_SVC_MAX_LENGTH); - - /* Warn about name length */ - if (name_length >= GUAC_RDP_SVC_MAX_LENGTH) - guac_client_log(client, GUAC_LOG_INFO, - "Static channel name \"%s\" exceeds maximum of %i characters " - "and will be truncated", name, GUAC_RDP_SVC_MAX_LENGTH - 1); - - return svc; -} - -void guac_rdp_free_svc(guac_rdp_svc* svc) { - free(svc); -} - -void guac_rdp_svc_send_pipe(guac_socket* socket, guac_rdp_svc* svc) { - - /* Send pipe instruction for the SVC's output stream */ - guac_protocol_send_pipe(socket, svc->output_pipe, - "application/octet-stream", svc->name); - -} - -void guac_rdp_svc_send_pipes(guac_user* user) { - - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - - guac_common_list_lock(rdp_client->available_svc); - - /* Send pipe for each allocated SVC's output stream */ - guac_common_list_element* current = rdp_client->available_svc->head; - while (current != NULL) { - guac_rdp_svc_send_pipe(user->socket, (guac_rdp_svc*) current->data); - current = current->next; - } - - guac_common_list_unlock(rdp_client->available_svc); - -} - -void guac_rdp_add_svc(guac_client* client, guac_rdp_svc* svc) { - - 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, svc); - guac_common_list_unlock(rdp_client->available_svc); - -} - -guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) { - - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_common_list_element* current; - guac_rdp_svc* found = NULL; - - /* For each available SVC */ - guac_common_list_lock(rdp_client->available_svc); - current = rdp_client->available_svc->head; - while (current != NULL) { - - /* If name matches, found */ - guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data; - if (strcmp(current_svc->name, name) == 0) { - found = current_svc; - break; - } - - current = current->next; - - } - guac_common_list_unlock(rdp_client->available_svc); - - return found; - -} - -guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name) { - - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_common_list_element* current; - guac_rdp_svc* found = NULL; - - /* For each available SVC */ - guac_common_list_lock(rdp_client->available_svc); - current = rdp_client->available_svc->head; - while (current != NULL) { - - /* If name matches, remove entry */ - guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data; - if (strcmp(current_svc->name, name) == 0) { - guac_common_list_remove(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; - -} - -void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length) { - - wStream* output_stream; - - /* Do not write of plugin not associated */ - if (svc->plugin == NULL) { - guac_client_log(svc->client, GUAC_LOG_ERROR, - "Channel \"%s\" output dropped.", - svc->name); - return; - } - - /* Build packet */ - output_stream = Stream_New(NULL, length); - Stream_Write(output_stream, data, length); - - /* Send packet */ - svc_plugin_send(svc->plugin, output_stream); - -} - diff --git a/src/protocols/rdp/rdp_svc.h b/src/protocols/rdp/rdp_svc.h deleted file mode 100644 index ebb3a13f..00000000 --- a/src/protocols/rdp/rdp_svc.h +++ /dev/null @@ -1,168 +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_RDP_SVC_H -#define __GUAC_RDP_RDP_SVC_H - -#include "config.h" - -#include -#include -#include - -/** - * 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 the corresponding - * Guacamole pipes. - */ -typedef struct guac_rdp_svc { - - /** - * Reference to the client owning this static channel. - */ - guac_client* client; - - /** - * Reference to associated SVC plugin. - */ - rdpSvcPlugin* plugin; - - /** - * The name of the RDP channel in use, and the name to use for each pipe. - */ - char name[GUAC_RDP_SVC_MAX_LENGTH]; - - /** - * The output pipe, opened when the RDP server receives a connection to - * the static channel. - */ - guac_stream* output_pipe; - -} guac_rdp_svc; - -/** - * Allocate a new SVC with the given name. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param name - * The name of the virtual channel to allocate. - * - * @return - * A newly-allocated static virtual channel. - */ -guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name); - -/** - * Free the given SVC. - * - * @param svc - * The static virtual channel to free. - */ -void guac_rdp_free_svc(guac_rdp_svc* svc); - -/** - * Sends the "pipe" instruction describing the given static virtual channel - * along the given socket. This pipe instruction will relate the SVC's - * underlying output stream with the SVC's name and the mimetype - * "application/octet-stream". - * - * @param socket - * The socket along which the "pipe" instruction should be sent. - * - * @param svc - * The static virtual channel that the "pipe" instruction should describe. - */ -void guac_rdp_svc_send_pipe(guac_socket* socket, guac_rdp_svc* svc); - -/** - * Sends the "pipe" instructions describing all static virtual channels - * available to the given user along that user's socket. Each pipe instruction - * will relate the associated SVC's underlying output stream with the SVC's - * name and the mimetype "application/octet-stream". - * - * @param user - * The user to send the "pipe" instructions to. - */ -void guac_rdp_svc_send_pipes(guac_user* user); - -/** - * Add the given SVC to the list of all available SVCs. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param svc - * The static virtual channel to add to the list of all such channels - * available. - */ -void guac_rdp_add_svc(guac_client* client, guac_rdp_svc* svc); - -/** - * Retrieve the SVC with the given name from the list stored in the client. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param name - * The name of the static virtual channel to retrieve. - * - * @return - * The static virtual channel with the given name, or NULL if no such - * virtual channel exists. - */ -guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name); - -/** - * Remove the SVC with the given name from the list stored in the client. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param name - * The name of the static virtual channel to remove. - * - * @return - * The static virtual channel that was removed, or NULL if no such virtual - * channel exists. - */ -guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name); - -/** - * Write the given blob of data to the virtual channel. - * - * @param svc - * The static virtual channel to write data to. - * - * @param data - * The data to write. - * - * @param length - * The number of bytes to write. - */ -void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length); - -#endif - diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/settings.c similarity index 82% rename from src/protocols/rdp/rdp_settings.c rename to src/protocols/rdp/settings.c index 11cc8610..fe2cf672 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/settings.c @@ -17,27 +17,23 @@ * under the License. */ -#include "config.h" - -#include "client.h" #include "common/string.h" -#include "rdp.h" -#include "rdp_settings.h" +#include "config.h" #include "resolution.h" +#include "settings.h" #include #include +#include +#include #include #include - -#ifdef ENABLE_WINPR +#include #include -#else -#include "compat/winpr-wtypes.h" -#endif #include #include +#include #include /* Client plugin arguments */ @@ -107,17 +103,13 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "enable-audio-input", "read-only", -#ifdef HAVE_FREERDP_GATEWAY_SUPPORT "gateway-hostname", "gateway-port", "gateway-domain", "gateway-username", "gateway-password", -#endif -#ifdef HAVE_FREERDP_LOAD_BALANCER_SUPPORT "load-balance-info", -#endif "disable-copy", "disable-paste", @@ -243,7 +235,8 @@ enum RDP_ARGS_IDX { /** * The type of security to use for the connection. Valid values are "rdp", - * "tls", "nla", or "any". By default, "rdp" security is used. + * "tls", "nla", "nla-ext", or "any". By default, the security mode is + * negotiated ("any"). */ IDX_SECURITY, @@ -318,7 +311,7 @@ enum RDP_ARGS_IDX { /** * "true" if desktop composition (Aero) should be enabled during the - * session, "false" or blank otherwise. As desktop composition provides + * session, "false" or blank otherwise. As desktop composition provides * alpha blending and other special effects, this increases the amount of * bandwidth used. */ @@ -363,7 +356,7 @@ enum RDP_ARGS_IDX { /** * The timezone to pass through to the RDP connection, in IANA format, which - * will be translated into Windows formats. See the following page for + * will be translated into Windows formats. See the following page for * information and list of valid values: * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones */ @@ -383,7 +376,7 @@ enum RDP_ARGS_IDX { IDX_SFTP_HOSTNAME, /** - * The public SSH host key of the SFTP server. Optional. + * The public SSH host key of the SFTP server. Optional. */ IDX_SFTP_HOST_KEY, @@ -432,7 +425,7 @@ enum RDP_ARGS_IDX { /** * The interval at which SSH keepalive messages are sent to the server for - * SFTP connections. The default is 0 (disabling keepalives), and a value + * SFTP connections. The default is 0 (disabling keepalives), and a value * of 1 is automatically increased to 2 by libssh2 to avoid busy loop corner * cases. */ @@ -470,7 +463,7 @@ enum RDP_ARGS_IDX { /** * Whether keys pressed and released should be included in the session * recording. Key events are NOT included by default within the recording, - * as doing so has privacy and security implications. Including key events + * as doing so has privacy and security implications. Including key events * may be necessary in certain auditing contexts, but should only be done * with caution. Key events can easily contain sensitive information, such * as passwords, credit card numbers, etc. @@ -501,7 +494,6 @@ enum RDP_ARGS_IDX { */ IDX_READ_ONLY, -#ifdef HAVE_FREERDP_GATEWAY_SUPPORT /** * The hostname of the remote desktop gateway that should be used as an * intermediary for the remote desktop connection. If omitted, a gateway @@ -538,15 +530,12 @@ enum RDP_ARGS_IDX { * gateway, if a gateway is being used. */ IDX_GATEWAY_PASSWORD, -#endif -#ifdef HAVE_FREERDP_LOAD_BALANCER_SUPPORT /** * The load balancing information/cookie which should be provided to * the connection broker, if a connection broker is being used. */ IDX_LOAD_BALANCE_INFO, -#endif /** * Whether outbound clipboard access should be blocked. If set to "true", @@ -604,6 +593,12 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, settings->security_mode = GUAC_SECURITY_NLA; } + /* Extended NLA security */ + else if (strcmp(argv[IDX_SECURITY], "nla-ext") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Security mode: Extended NLA"); + settings->security_mode = GUAC_SECURITY_EXTENDED_NLA; + } + /* TLS security */ else if (strcmp(argv[IDX_SECURITY], "tls") == 0) { guac_user_log(user, GUAC_LOG_INFO, "Security mode: TLS"); @@ -616,16 +611,16 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, settings->security_mode = GUAC_SECURITY_RDP; } - /* ANY security (allow server to choose) */ + /* Negotiate security (allow server to choose) */ else if (strcmp(argv[IDX_SECURITY], "any") == 0) { - guac_user_log(user, GUAC_LOG_INFO, "Security mode: ANY"); + guac_user_log(user, GUAC_LOG_INFO, "Security mode: Negotiate (ANY)"); settings->security_mode = GUAC_SECURITY_ANY; } /* If nothing given, default to RDP */ else { - guac_user_log(user, GUAC_LOG_INFO, "No security mode specified. Defaulting to RDP."); - settings->security_mode = GUAC_SECURITY_RDP; + guac_user_log(user, GUAC_LOG_INFO, "No security mode specified. Defaulting to security mode negotiation with server."); + settings->security_mode = GUAC_SECURITY_ANY; } /* Set hostname */ @@ -813,7 +808,6 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, "Preconnection BLOB: \"%s\"", settings->preconnection_blob); } -#ifndef HAVE_RDPSETTINGS_SENDPRECONNECTIONPDU /* Warn if support for the preconnection BLOB / ID is absent */ if (settings->preconnection_blob != NULL || settings->preconnection_id != -1) { @@ -822,7 +816,6 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, "preconnection PDU. The specified preconnection BLOB and/or " "ID will be ignored."); } -#endif /* Audio enable/disable */ settings->audio_enabled = @@ -990,7 +983,6 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_ENABLE_AUDIO_INPUT, 0); -#ifdef HAVE_FREERDP_GATEWAY_SUPPORT /* Set gateway hostname */ settings->gateway_hostname = guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, @@ -1015,14 +1007,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, settings->gateway_password = guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_GATEWAY_PASSWORD, NULL); -#endif -#ifdef HAVE_FREERDP_LOAD_BALANCER_SUPPORT /* Set load balance info */ settings->load_balance_info = guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_LOAD_BALANCE_INFO, NULL); -#endif /* Parse clipboard copy disable flag */ settings->disable_copy = @@ -1087,18 +1076,14 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { free(settings->sftp_username); #endif -#ifdef HAVE_FREERDP_GATEWAY_SUPPORT /* Free RD gateway information */ free(settings->gateway_hostname); free(settings->gateway_domain); free(settings->gateway_username); free(settings->gateway_password); -#endif -#ifdef HAVE_FREERDP_LOAD_BALANCER_SUPPORT /* Free load balancer information string */ free(settings->load_balance_info); -#endif /* Free settings structure */ free(settings); @@ -1106,27 +1091,15 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { } int guac_rdp_get_width(freerdp* rdp) { -#ifdef LEGACY_RDPSETTINGS - return rdp->settings->width; -#else return rdp->settings->DesktopWidth; -#endif } int guac_rdp_get_height(freerdp* rdp) { -#ifdef LEGACY_RDPSETTINGS - return rdp->settings->height; -#else return rdp->settings->DesktopHeight; -#endif } int guac_rdp_get_depth(freerdp* rdp) { -#ifdef LEGACY_RDPSETTINGS - return rdp->settings->color_depth; -#else return rdp->settings->ColorDepth; -#endif } /** @@ -1199,60 +1172,25 @@ static char* guac_rdp_strdup(const char* str) { void guac_rdp_push_settings(guac_client* client, guac_rdp_settings* guac_settings, freerdp* rdp) { - BOOL bitmap_cache = !guac_settings->disable_bitmap_caching; rdpSettings* rdp_settings = rdp->settings; /* Authentication */ -#ifdef LEGACY_RDPSETTINGS - rdp_settings->domain = guac_rdp_strdup(guac_settings->domain); - rdp_settings->username = guac_rdp_strdup(guac_settings->username); - rdp_settings->password = guac_rdp_strdup(guac_settings->password); -#else rdp_settings->Domain = guac_rdp_strdup(guac_settings->domain); rdp_settings->Username = guac_rdp_strdup(guac_settings->username); rdp_settings->Password = guac_rdp_strdup(guac_settings->password); -#endif /* Connection */ -#ifdef LEGACY_RDPSETTINGS - rdp_settings->hostname = guac_rdp_strdup(guac_settings->hostname); - rdp_settings->port = guac_settings->port; -#else rdp_settings->ServerHostname = guac_rdp_strdup(guac_settings->hostname); rdp_settings->ServerPort = guac_settings->port; -#endif /* Session */ -#ifdef LEGACY_RDPSETTINGS - rdp_settings->color_depth = guac_settings->color_depth; - rdp_settings->width = guac_settings->width; - rdp_settings->height = guac_settings->height; - rdp_settings->shell = guac_rdp_strdup(guac_settings->initial_program); - rdp_settings->kbd_layout = guac_settings->server_layout->freerdp_keyboard_layout; -#else rdp_settings->ColorDepth = guac_settings->color_depth; rdp_settings->DesktopWidth = guac_settings->width; rdp_settings->DesktopHeight = guac_settings->height; rdp_settings->AlternateShell = guac_rdp_strdup(guac_settings->initial_program); rdp_settings->KeyboardLayout = guac_settings->server_layout->freerdp_keyboard_layout; -#endif /* Performance flags */ -#ifdef LEGACY_RDPSETTINGS - - /* Explicitly set flag value */ - rdp_settings->performance_flags = guac_rdp_get_performance_flags(guac_settings); - - /* Set individual flags - some FreeRDP versions overwrite the above */ - rdp_settings->smooth_fonts = guac_settings->font_smoothing_enabled; - rdp_settings->disable_wallpaper = !guac_settings->wallpaper_enabled; - rdp_settings->disable_full_window_drag = !guac_settings->full_window_drag_enabled; - rdp_settings->disable_menu_animations = !guac_settings->menu_animations_enabled; - rdp_settings->disable_theming = !guac_settings->theming_enabled; - rdp_settings->desktop_composition = guac_settings->desktop_composition_enabled; - -#else - /* Explicitly set flag value */ rdp_settings->PerformanceFlags = guac_rdp_get_performance_flags(guac_settings); @@ -1264,49 +1202,25 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->DisableThemes = !guac_settings->theming_enabled; rdp_settings->AllowDesktopComposition = guac_settings->desktop_composition_enabled; -#endif - /* Client name */ if (guac_settings->client_name != NULL) { -#ifdef LEGACY_RDPSETTINGS - guac_strlcpy(rdp_settings->client_hostname, guac_settings->client_name, - RDP_CLIENT_HOSTNAME_SIZE); -#else guac_strlcpy(rdp_settings->ClientHostname, guac_settings->client_name, RDP_CLIENT_HOSTNAME_SIZE); -#endif } /* Console */ -#ifdef LEGACY_RDPSETTINGS - rdp_settings->console_session = guac_settings->console; - rdp_settings->console_audio = guac_settings->console_audio; -#else rdp_settings->ConsoleSession = guac_settings->console; rdp_settings->RemoteConsoleAudio = guac_settings->console_audio; -#endif /* Audio */ -#ifdef LEGACY_RDPSETTINGS -#ifdef HAVE_RDPSETTINGS_AUDIOPLAYBACK - rdp_settings->audio_playback = guac_settings->audio_enabled; -#endif -#else -#ifdef HAVE_RDPSETTINGS_AUDIOPLAYBACK rdp_settings->AudioPlayback = guac_settings->audio_enabled; -#endif -#endif /* Audio capture */ -#ifdef LEGACY_RDPSETTINGS -#ifdef HAVE_RDPSETTINGS_AUDIOCAPTURE - rdp_settings->audio_capture = guac_settings->enable_audio_input; -#endif -#else -#ifdef HAVE_RDPSETTINGS_AUDIOCAPTURE rdp_settings->AudioCapture = guac_settings->enable_audio_input; -#endif -#endif + + /* Display Update channel */ + rdp_settings->SupportDisplayControl = + (guac_settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE); /* Timezone redirection */ if (guac_settings->timezone) { @@ -1318,115 +1232,75 @@ void guac_rdp_push_settings(guac_client* client, } /* Device redirection */ -#ifdef LEGACY_RDPSETTINGS -#ifdef HAVE_RDPSETTINGS_DEVICEREDIRECTION - rdp_settings->device_redirection = guac_settings->audio_enabled - || guac_settings->drive_enabled - || guac_settings->printing_enabled; -#endif -#else -#ifdef HAVE_RDPSETTINGS_DEVICEREDIRECTION rdp_settings->DeviceRedirection = guac_settings->audio_enabled || guac_settings->drive_enabled || guac_settings->printing_enabled; -#endif -#endif /* Security */ switch (guac_settings->security_mode) { - /* Standard RDP encryption */ + /* Legacy RDP encryption */ case GUAC_SECURITY_RDP: -#ifdef LEGACY_RDPSETTINGS - rdp_settings->rdp_security = TRUE; - rdp_settings->tls_security = FALSE; - rdp_settings->nla_security = FALSE; - rdp_settings->encryption_level = ENCRYPTION_LEVEL_CLIENT_COMPATIBLE; - rdp_settings->encryption_method = - ENCRYPTION_METHOD_40BIT - | ENCRYPTION_METHOD_128BIT - | ENCRYPTION_METHOD_FIPS; -#else rdp_settings->RdpSecurity = TRUE; rdp_settings->TlsSecurity = FALSE; rdp_settings->NlaSecurity = FALSE; + rdp_settings->ExtSecurity = FALSE; + rdp_settings->UseRdpSecurityLayer = TRUE; rdp_settings->EncryptionLevel = ENCRYPTION_LEVEL_CLIENT_COMPATIBLE; rdp_settings->EncryptionMethods = ENCRYPTION_METHOD_40BIT | ENCRYPTION_METHOD_128BIT | ENCRYPTION_METHOD_FIPS; -#endif break; /* TLS encryption */ case GUAC_SECURITY_TLS: -#ifdef LEGACY_RDPSETTINGS - rdp_settings->rdp_security = FALSE; - rdp_settings->tls_security = TRUE; - rdp_settings->nla_security = FALSE; -#else rdp_settings->RdpSecurity = FALSE; rdp_settings->TlsSecurity = TRUE; rdp_settings->NlaSecurity = FALSE; -#endif + rdp_settings->ExtSecurity = FALSE; break; /* Network level authentication */ case GUAC_SECURITY_NLA: -#ifdef LEGACY_RDPSETTINGS - rdp_settings->rdp_security = FALSE; - rdp_settings->tls_security = FALSE; - rdp_settings->nla_security = TRUE; -#else rdp_settings->RdpSecurity = FALSE; rdp_settings->TlsSecurity = FALSE; rdp_settings->NlaSecurity = TRUE; -#endif + rdp_settings->ExtSecurity = FALSE; + break; + + /* Extended network level authentication */ + case GUAC_SECURITY_EXTENDED_NLA: + rdp_settings->RdpSecurity = FALSE; + rdp_settings->TlsSecurity = FALSE; + rdp_settings->NlaSecurity = FALSE; + rdp_settings->ExtSecurity = TRUE; break; /* All security types */ case GUAC_SECURITY_ANY: -#ifdef LEGACY_RDPSETTINGS - rdp_settings->rdp_security = TRUE; - rdp_settings->tls_security = TRUE; - rdp_settings->nla_security = TRUE; -#else rdp_settings->RdpSecurity = TRUE; rdp_settings->TlsSecurity = TRUE; - rdp_settings->NlaSecurity = TRUE; -#endif + rdp_settings->NlaSecurity = guac_settings->username && guac_settings->password; + rdp_settings->ExtSecurity = FALSE; break; } /* Authentication */ -#ifdef LEGACY_RDPSETTINGS - rdp_settings->authentication = !guac_settings->disable_authentication; - rdp_settings->ignore_certificate = guac_settings->ignore_certificate; - rdp_settings->encryption = TRUE; -#else rdp_settings->Authentication = !guac_settings->disable_authentication; rdp_settings->IgnoreCertificate = guac_settings->ignore_certificate; - rdp_settings->DisableEncryption = FALSE; -#endif /* RemoteApp */ if (guac_settings->remote_app != NULL) { -#ifdef LEGACY_RDPSETTINGS - rdp_settings->workarea = TRUE; - rdp_settings->remote_app = TRUE; - rdp_settings->rail_langbar_supported = TRUE; -#else rdp_settings->Workarea = TRUE; rdp_settings->RemoteApplicationMode = TRUE; rdp_settings->RemoteAppLanguageBarSupported = TRUE; rdp_settings->RemoteApplicationProgram = guac_settings->remote_app; rdp_settings->ShellWorkingDirectory = guac_rdp_strdup(guac_settings->remote_app_dir); rdp_settings->RemoteApplicationCmdLine = guac_settings->remote_app_args; -#endif } -#ifdef HAVE_RDPSETTINGS_SENDPRECONNECTIONPDU /* Preconnection ID */ if (guac_settings->preconnection_id != -1) { rdp_settings->NegotiateSecurityLayer = FALSE; @@ -1440,9 +1314,7 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->SendPreconnectionPdu = TRUE; rdp_settings->PreconnectionBlob = guac_settings->preconnection_blob; } -#endif -#ifdef HAVE_FREERDP_GATEWAY_SUPPORT /* Enable use of RD gateway if a gateway hostname is provided */ if (guac_settings->gateway_hostname != NULL) { @@ -1460,80 +1332,29 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->GatewayPassword = guac_rdp_strdup(guac_settings->gateway_password); } -#endif -#ifdef HAVE_FREERDP_LOAD_BALANCER_SUPPORT /* Store load balance info (and calculate length) if provided */ if (guac_settings->load_balance_info != NULL) { rdp_settings->LoadBalanceInfo = (BYTE*) guac_rdp_strdup(guac_settings->load_balance_info); rdp_settings->LoadBalanceInfoLength = strlen(guac_settings->load_balance_info); } -#endif - /* Order support */ -#ifdef LEGACY_RDPSETTINGS - rdp_settings->bitmap_cache = bitmap_cache; - rdp_settings->offscreen_bitmap_cache = !guac_settings->disable_offscreen_caching; - rdp_settings->glyph_cache = !guac_settings->disable_glyph_caching; - rdp_settings->os_major_type = OSMAJORTYPE_UNSPECIFIED; - rdp_settings->os_minor_type = OSMINORTYPE_UNSPECIFIED; - rdp_settings->desktop_resize = TRUE; - rdp_settings->order_support[NEG_DSTBLT_INDEX] = TRUE; - rdp_settings->order_support[NEG_PATBLT_INDEX] = FALSE; /* PATBLT not yet supported */ - rdp_settings->order_support[NEG_SCRBLT_INDEX] = TRUE; - rdp_settings->order_support[NEG_OPAQUE_RECT_INDEX] = TRUE; - rdp_settings->order_support[NEG_DRAWNINEGRID_INDEX] = FALSE; - rdp_settings->order_support[NEG_MULTIDSTBLT_INDEX] = FALSE; - rdp_settings->order_support[NEG_MULTIPATBLT_INDEX] = FALSE; - rdp_settings->order_support[NEG_MULTISCRBLT_INDEX] = FALSE; - rdp_settings->order_support[NEG_MULTIOPAQUERECT_INDEX] = FALSE; - rdp_settings->order_support[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE; - rdp_settings->order_support[NEG_LINETO_INDEX] = FALSE; - rdp_settings->order_support[NEG_POLYLINE_INDEX] = FALSE; - rdp_settings->order_support[NEG_MEMBLT_INDEX] = bitmap_cache; - rdp_settings->order_support[NEG_MEM3BLT_INDEX] = FALSE; - rdp_settings->order_support[NEG_MEMBLT_V2_INDEX] = bitmap_cache; - rdp_settings->order_support[NEG_MEM3BLT_V2_INDEX] = FALSE; - rdp_settings->order_support[NEG_SAVEBITMAP_INDEX] = FALSE; - rdp_settings->order_support[NEG_GLYPH_INDEX_INDEX] = TRUE; - rdp_settings->order_support[NEG_FAST_INDEX_INDEX] = TRUE; - rdp_settings->order_support[NEG_FAST_GLYPH_INDEX] = TRUE; - rdp_settings->order_support[NEG_POLYGON_SC_INDEX] = FALSE; - rdp_settings->order_support[NEG_POLYGON_CB_INDEX] = FALSE; - rdp_settings->order_support[NEG_ELLIPSE_SC_INDEX] = FALSE; - rdp_settings->order_support[NEG_ELLIPSE_CB_INDEX] = FALSE; -#else - rdp_settings->BitmapCacheEnabled = bitmap_cache; + rdp_settings->BitmapCacheEnabled = !guac_settings->disable_bitmap_caching; rdp_settings->OffscreenSupportLevel = !guac_settings->disable_offscreen_caching; rdp_settings->GlyphSupportLevel = !guac_settings->disable_glyph_caching ? GLYPH_SUPPORT_FULL : GLYPH_SUPPORT_NONE; rdp_settings->OsMajorType = OSMAJORTYPE_UNSPECIFIED; rdp_settings->OsMinorType = OSMINORTYPE_UNSPECIFIED; rdp_settings->DesktopResize = TRUE; + + /* Claim support only for specific updates, independent of FreeRDP defaults */ + ZeroMemory(rdp_settings->OrderSupport, GUAC_RDP_ORDER_SUPPORT_LENGTH); rdp_settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE; - rdp_settings->OrderSupport[NEG_PATBLT_INDEX] = FALSE; /* PATBLT not yet supported */ rdp_settings->OrderSupport[NEG_SCRBLT_INDEX] = TRUE; - rdp_settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE; - rdp_settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_LINETO_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_POLYLINE_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_MEMBLT_INDEX] = bitmap_cache; - rdp_settings->OrderSupport[NEG_MEM3BLT_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = bitmap_cache; - rdp_settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = TRUE; - rdp_settings->OrderSupport[NEG_FAST_INDEX_INDEX] = TRUE; - rdp_settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = TRUE; - rdp_settings->OrderSupport[NEG_POLYGON_SC_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_POLYGON_CB_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = FALSE; - rdp_settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = FALSE; -#endif + rdp_settings->OrderSupport[NEG_MEMBLT_INDEX] = !guac_settings->disable_bitmap_caching; + rdp_settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = !guac_settings->disable_bitmap_caching; + rdp_settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = !guac_settings->disable_glyph_caching; + rdp_settings->OrderSupport[NEG_FAST_INDEX_INDEX] = !guac_settings->disable_glyph_caching; + rdp_settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = !guac_settings->disable_glyph_caching; } diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/settings.h similarity index 93% rename from src/protocols/rdp/rdp_settings.h rename to src/protocols/rdp/settings.h index 9edbedeb..e4c579ed 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/settings.h @@ -17,15 +17,15 @@ * under the License. */ - -#ifndef __GUAC_RDP_SETTINGS_H -#define __GUAC_RDP_SETTINGS_H +#ifndef GUAC_RDP_SETTINGS_H +#define GUAC_RDP_SETTINGS_H #include "config.h" - -#include "rdp_keymap.h" +#include "keymap.h" #include +#include +#include /** * The maximum number of bytes in the client hostname claimed during @@ -58,13 +58,23 @@ */ #define GUAC_RDP_DEFAULT_RECORDING_NAME "recording" +/** + * The number of entries contained within the OrderSupport BYTE array + * referenced by the rdpSettings structure. This value is defined by the RDP + * negotiation process (there are 32 bytes available within the order + * negotiation field sent during the connection handshake) and is hard-coded + * within FreeRDP. There is no public constant for this value defined within + * the FreeRDP headers. + */ +#define GUAC_RDP_ORDER_SUPPORT_LENGTH 32 + /** * All supported combinations of security types. */ typedef enum guac_rdp_security { /** - * Standard RDP encryption. + * Legacy RDP encryption. */ GUAC_SECURITY_RDP, @@ -79,7 +89,12 @@ typedef enum guac_rdp_security { GUAC_SECURITY_NLA, /** - * Any method supported by the server. + * Extended network level authentication. + */ + GUAC_SECURITY_EXTENDED_NLA, + + /** + * Negotiate a security method supported by both server and client. */ GUAC_SECURITY_ANY @@ -324,19 +339,19 @@ typedef struct guac_rdp_settings { int menu_animations_enabled; /** - * Whether bitmap caching should be disabled. By default it is + * Whether bitmap caching should be disabled. By default it is * enabled - this allows users to explicitly disable it. */ int disable_bitmap_caching; /** - * Whether offscreen caching should be disabled. By default it is + * Whether offscreen caching should be disabled. By default it is * enabled - this allows users to explicitly disable it. */ int disable_offscreen_caching; /** - * Whether glyph caching should be disabled. By default it is enabled + * Whether glyph caching should be disabled. By default it is enabled * - this allows users to explicitly disable it. */ int disable_glyph_caching; @@ -420,7 +435,7 @@ typedef struct guac_rdp_settings { /** * The interval at which SSH keepalive messages are sent to the server for - * SFTP connections. The default is 0 (disabling keepalives), and a value + * SFTP connections. The default is 0 (disabling keepalives), and a value * of 1 is automatically increased to 2 by libssh2 to avoid busy loop corner * cases. */ @@ -480,7 +495,6 @@ typedef struct guac_rdp_settings { */ int enable_audio_input; -#ifdef HAVE_FREERDP_GATEWAY_SUPPORT /** * The hostname of the remote desktop gateway that should be used as an * intermediary for the remote desktop connection. If no gateway should @@ -515,15 +529,12 @@ typedef struct guac_rdp_settings { * gateway, if a gateway is being used. */ char* gateway_password; -#endif -#ifdef HAVE_FREERDP_LOAD_BALANCER_SUPPORT /** * The load balancing information/cookie which should be provided to * the connection broker, if a connection broker is being used. */ char* load_balance_info; -#endif } guac_rdp_settings; diff --git a/src/protocols/rdp/sftp.c b/src/protocols/rdp/sftp.c index ecfe35fe..0fc25803 100644 --- a/src/protocols/rdp/sftp.c +++ b/src/protocols/rdp/sftp.c @@ -17,14 +17,11 @@ * under the License. */ -#include "config.h" - #include "common-ssh/sftp.h" #include "rdp.h" #include "sftp.h" #include -#include #include int guac_rdp_sftp_file_handler(guac_user* user, guac_stream* stream, diff --git a/src/protocols/rdp/sftp.h b/src/protocols/rdp/sftp.h index d7683243..3f7f9e2c 100644 --- a/src/protocols/rdp/sftp.h +++ b/src/protocols/rdp/sftp.h @@ -20,8 +20,6 @@ #ifndef GUAC_RDP_SFTP_H #define GUAC_RDP_SFTP_H -#include "config.h" - #include #include diff --git a/src/protocols/rdp/tests/Makefile.am b/src/protocols/rdp/tests/Makefile.am index 3f57bbf3..33136696 100644 --- a/src/protocols/rdp/tests/Makefile.am +++ b/src/protocols/rdp/tests/Makefile.am @@ -34,6 +34,7 @@ check_PROGRAMS = test_rdp TESTS = $(check_PROGRAMS) test_rdp_SOURCES = \ + fs/basename.c \ fs/normalize_path.c test_rdp_CFLAGS = \ diff --git a/src/protocols/rdp/tests/fs/basename.c b/src/protocols/rdp/tests/fs/basename.c new file mode 100644 index 00000000..9ac60977 --- /dev/null +++ b/src/protocols/rdp/tests/fs/basename.c @@ -0,0 +1,59 @@ +/* + * 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 +#include + +/** + * Test which verifies basenames are correctly extracted from Windows-style + * paths. + */ +void test_fs__basename_windows() { + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("\\foo\\bar\\baz"), "baz") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("\\foo\\bar\\..\\baz\\"), "") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("bar\\..\\..\\baz\\a\\..\\b"), "b") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename(".\\bar\\potato"), "potato") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("..\\..\\..\\..\\..\\..\\baz"), "baz") +} + +/** + * Test which verifies basenames are correctly extracted from UNIX-style paths. + */ +void test_fs__basename_unix() { + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("/foo/bar/baz"), "baz") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("/foo/bar/../baz/"), "") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("bar/../../baz/a/../b"), "b") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("./bar/potato"), "potato") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("../../../../../../baz"), "baz") +} + +/** + * Test which verifies basenames are correctly extracted from paths consisting + * of mixed Windows and UNIX path separators. + */ +void test_fs__basename_mixed() { + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("\\foo/bar\\baz"), "baz") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("/foo\\bar/..\\baz/"), "") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("bar\\../../baz\\a\\..\\b"), "b") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename(".\\bar/potato"), "potato") + CU_ASSERT_STRING_EQUAL(guac_rdp_fs_basename("../..\\..\\..\\../..\\baz"), "baz") +} + diff --git a/src/protocols/rdp/tests/fs/normalize_path.c b/src/protocols/rdp/tests/fs/normalize_path.c index ccf23e01..22a2d809 100644 --- a/src/protocols/rdp/tests/fs/normalize_path.c +++ b/src/protocols/rdp/tests/fs/normalize_path.c @@ -17,7 +17,7 @@ * under the License. */ -#include "rdp_fs.h" +#include "fs.h" #include #include diff --git a/src/protocols/rdp/unicode.c b/src/protocols/rdp/unicode.c index 628615ac..f055bea6 100644 --- a/src/protocols/rdp/unicode.c +++ b/src/protocols/rdp/unicode.c @@ -17,12 +17,10 @@ * under the License. */ -#include "config.h" +#include #include -#include - void guac_rdp_utf16_to_utf8(const unsigned char* utf16, int length, char* utf8, int size) { diff --git a/src/protocols/rdp/upload.c b/src/protocols/rdp/upload.c new file mode 100644 index 00000000..5317edbc --- /dev/null +++ b/src/protocols/rdp/upload.c @@ -0,0 +1,236 @@ +/* + * 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 "rdp.h" +#include "upload.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * Writes the given filename to the given upload path, sanitizing the filename + * and translating the filename to the root directory. + * + * @param filename + * The filename to sanitize and move to the root directory. + * + * @param path + * A pointer to a buffer which should receive the sanitized path. The + * buffer must have at least GUAC_RDP_FS_MAX_PATH bytes available. + */ +static void __generate_upload_path(const char* filename, char* path) { + + int i; + + /* Add initial backslash */ + *(path++) = '\\'; + + for (i=1; iclient; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + int file_id; + char file_path[GUAC_RDP_FS_MAX_PATH]; + + /* 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; + } + + /* Translate name */ + __generate_upload_path(filename, file_path); + + /* Open file */ + file_id = guac_rdp_fs_open(fs, file_path, GENERIC_WRITE, 0, + FILE_OVERWRITE_IF, 0); + if (file_id < 0) { + guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + /* Init upload status */ + guac_rdp_upload_status* upload_status = malloc(sizeof(guac_rdp_upload_status)); + upload_status->offset = 0; + upload_status->file_id = file_id; + stream->data = upload_status; + stream->blob_handler = guac_rdp_upload_blob_handler; + stream->end_handler = guac_rdp_upload_end_handler; + + guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + return 0; + +} + +int guac_rdp_upload_blob_handler(guac_user* user, guac_stream* stream, + void* data, int length) { + + int bytes_written; + guac_rdp_upload_status* upload_status = (guac_rdp_upload_status*) stream->data; + + /* Get filesystem, return error if no filesystem */ + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_fs* fs = rdp_client->filesystem; + if (fs == NULL) { + guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)", + GUAC_PROTOCOL_STATUS_SERVER_ERROR); + guac_socket_flush(user->socket); + return 0; + } + + /* Write entire block */ + while (length > 0) { + + /* Attempt write */ + bytes_written = guac_rdp_fs_write(fs, upload_status->file_id, + upload_status->offset, data, length); + + /* On error, abort */ + if (bytes_written < 0) { + guac_protocol_send_ack(user->socket, stream, + "FAIL (BAD WRITE)", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + /* Update counters */ + upload_status->offset += bytes_written; + data += bytes_written; + length -= bytes_written; + + } + + guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + return 0; + +} + +int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_upload_status* upload_status = (guac_rdp_upload_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; + } + + /* Close file */ + guac_rdp_fs_close(fs, upload_status->file_id); + + /* Acknowledge stream end */ + guac_protocol_send_ack(user->socket, stream, "OK (STREAM END)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + + free(upload_status); + return 0; + +} + +int guac_rdp_upload_put_handler(guac_user* user, guac_object* object, + guac_stream* stream, char* mimetype, char* name) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Get filesystem, return error if no filesystem */ + guac_rdp_fs* fs = rdp_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; + } + + /* Open file */ + int file_id = guac_rdp_fs_open(fs, name, GENERIC_WRITE, 0, + FILE_OVERWRITE_IF, 0); + + /* Abort on failure */ + if (file_id < 0) { + guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + /* Init upload stream data */ + guac_rdp_upload_status* upload_status = malloc(sizeof(guac_rdp_upload_status)); + upload_status->offset = 0; + upload_status->file_id = file_id; + + /* Allocate stream, init for file upload */ + stream->data = upload_status; + stream->blob_handler = guac_rdp_upload_blob_handler; + stream->end_handler = guac_rdp_upload_end_handler; + + /* Acknowledge stream creation */ + guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + return 0; +} + diff --git a/src/protocols/rdp/upload.h b/src/protocols/rdp/upload.h new file mode 100644 index 00000000..254538f6 --- /dev/null +++ b/src/protocols/rdp/upload.h @@ -0,0 +1,72 @@ +/* + * 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_UPLOAD_H +#define GUAC_RDP_UPLOAD_H + +#include "common/json.h" + +#include +#include +#include + +#include + +/** + * Structure which represents the current state of an upload. + */ +typedef struct guac_rdp_upload_status { + + /** + * The overall offset within the file that the next write should + * occur at. + */ + int offset; + + /** + * The ID of the file being written to. + */ + int file_id; + +} guac_rdp_upload_status; + +/** + * Handler for inbound files related to file uploads. + */ +guac_user_file_handler guac_rdp_upload_file_handler; + +/** + * Handler for stream data related to file uploads. + */ +guac_user_blob_handler guac_rdp_upload_blob_handler; + +/** + * Handler for end-of-stream related to file uploads. + */ +guac_user_end_handler guac_rdp_upload_end_handler; + +/** + * Handler for put messages. In context of uploads and the filesystem exposed + * via the Guacamole protocol, put messages request write access to a file + * within the filesystem. + */ +guac_user_put_handler guac_rdp_upload_put_handler; + +#endif + diff --git a/src/protocols/rdp/user.c b/src/protocols/rdp/user.c index 025848aa..c2b487f6 100644 --- a/src/protocols/rdp/user.c +++ b/src/protocols/rdp/user.c @@ -17,16 +17,17 @@ * under the License. */ -#include "config.h" - -#include "audio_input.h" +#include "channels/audio-input/audio-input.h" +#include "channels/cliprdr.h" +#include "channels/pipe-svc.h" +#include "common/cursor.h" #include "common/display.h" +#include "config.h" #include "input.h" -#include "user.h" #include "rdp.h" -#include "rdp_settings.h" -#include "rdp_stream.h" -#include "rdp_svc.h" +#include "settings.h" +#include "upload.h" +#include "user.h" #ifdef ENABLE_COMMON_SSH #include "sftp.h" @@ -36,9 +37,11 @@ #include #include #include +#include #include #include +#include int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { @@ -86,7 +89,7 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { guac_audio_stream_add_user(rdp_client->audio, user); /* Bring user up to date with any registered static channels */ - guac_rdp_svc_send_pipes(user); + guac_rdp_pipe_svc_send_pipes(user); /* Synchronize with current display */ guac_common_display_dup(rdp_client->display, user, user->socket); @@ -112,7 +115,7 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { user->file_handler = guac_rdp_user_file_handler; /* Inbound arbitrary named pipes */ - user->pipe_handler = guac_rdp_svc_pipe_handler; + user->pipe_handler = guac_rdp_pipe_svc_pipe_handler; }