#include "connection_policy_internal.h" #include #include #pragma warning(push) #pragma warning(disable : 4471) #include "mstscax.tlh" #pragma warning(pop) namespace { // MSTSC TSC_PERF reverse mapping for IMsRdpClientAdvancedSettings8: the navop // feature bits are "enabled" flags while the MSTSC PerformanceFlags mask uses // inverted DISABLE bits plus two positive ENABLE bits. // 0x01 DISABLE_WALLPAPER, 0x02 DISABLE_FULLWINDOWDRAG, 0x04 // DISABLE_MENUANIMATIONS, 0x08 DISABLE_THEMING, 0x20 DISABLE_CURSORSHADOW, // 0x40 DISABLE_CURSORSETTINGS, 0x80 ENABLE_FONTSMOOTHING, // 0x100 ENABLE_DESKTOPCOMPOSITION. constexpr LONG kPerfDisableWallpaper = 0x01; constexpr LONG kPerfDisableFullWindowDrag = 0x02; constexpr LONG kPerfDisableMenuAnimations = 0x04; constexpr LONG kPerfDisableTheming = 0x08; constexpr LONG kPerfDisableCursorShadow = 0x20; constexpr LONG kPerfDisableCursorSettings = 0x40; constexpr LONG kPerfEnableFontSmoothing = 0x80; constexpr LONG kPerfEnableDesktopComposition = 0x100; // MSTSC NetworkConnectionType values are one-based // (CONNECTION_TYPE_MODEM=1 ... CONNECTION_TYPE_LAN=6), while the navop ABI is // zero-based. Autodetect (6) is not a settable MSTSC value, so the property is // left at the control default. constexpr LONG kNetworkConnectionTypeOffset = 1; constexpr uint32_t kNetworkConnectionTypeAutodetect = NAVOP_RDP_NETWORK_CONNECTION_AUTODETECT; LONG to_mstsc_performance_flags(uint32_t flags) noexcept { LONG mstsc_flags = 0; if ((flags & NAVOP_RDP_PERFORMANCE_FLAG_WALLPAPER) == 0) { mstsc_flags |= kPerfDisableWallpaper; } if ((flags & NAVOP_RDP_PERFORMANCE_FLAG_FULL_WINDOW_DRAG) == 0) { mstsc_flags |= kPerfDisableFullWindowDrag; } if ((flags & NAVOP_RDP_PERFORMANCE_FLAG_MENU_ANIMATIONS) == 0) { mstsc_flags |= kPerfDisableMenuAnimations; } if ((flags & NAVOP_RDP_PERFORMANCE_FLAG_THEMES) == 0) { mstsc_flags |= kPerfDisableTheming; } if ((flags & NAVOP_RDP_PERFORMANCE_FLAG_CURSOR_SHADOW) == 0) { mstsc_flags |= kPerfDisableCursorShadow; } if ((flags & NAVOP_RDP_PERFORMANCE_FLAG_CURSOR_SETTINGS) == 0) { mstsc_flags |= kPerfDisableCursorSettings; } if ((flags & NAVOP_RDP_PERFORMANCE_FLAG_FONT_SMOOTHING) != 0) { mstsc_flags |= kPerfEnableFontSmoothing; } if ((flags & NAVOP_RDP_PERFORMANCE_FLAG_DESKTOP_COMPOSITION) != 0) { mstsc_flags |= kPerfEnableDesktopComposition; } return mstsc_flags; } } // namespace NavopRdpResult configure_resource_policy( const NativeRdpConnectionPolicyContext& context, const NavopRdpConnectionOptions& options) noexcept { CComPtr advanced; NavopRdpResult result = get_advanced_settings8( context.owner, context.client, &advanced); if (result != NAVOP_RDP_RESULT_OK) { return result; } CComQIPtr non_scriptable3( context.non_scriptable3); if (non_scriptable3 == nullptr) { return record_last_error( context.owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } const struct { const wchar_t* property; const char* stage; uint32_t flag; } redirects[] = { {L"RedirectClipboard", "connect.resource.redirect_clipboard", NAVOP_RDP_RESOURCE_FLAG_CLIPBOARD}, {L"RedirectDrives", "connect.resource.redirect_drives", NAVOP_RDP_RESOURCE_FLAG_DRIVES}, {L"RedirectPrinters", "connect.resource.redirect_printers", NAVOP_RDP_RESOURCE_FLAG_PRINTERS}, {L"RedirectSmartCards", "connect.resource.redirect_smart_cards", NAVOP_RDP_RESOURCE_FLAG_SMART_CARDS}, {L"RedirectPOSDevices", "connect.resource.redirect_pos_devices", NAVOP_RDP_RESOURCE_FLAG_POS_DEVICES}, }; for (const auto& redirect : redirects) { result = configure_redirect_bool( context.owner, advanced, redirect.property, redirect.stage, (options.resource_flags & redirect.flag) != 0); if (result != NAVOP_RDP_RESULT_OK) { return result; } } trace_native_stage("connect.resource.redirect_dynamic_drives.before"); HRESULT hresult = non_scriptable3->put_RedirectDynamicDrives( (options.resource_flags & NAVOP_RDP_RESOURCE_FLAG_DYNAMIC_DRIVES) != 0 ? VARIANT_TRUE : VARIANT_FALSE); trace_native_hresult( "connect.resource.redirect_dynamic_drives.after", static_cast(hresult)); if (FAILED(hresult)) { return record_last_hresult( context.owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(hresult)); } trace_native_stage("connect.resource.redirect_dynamic_devices.before"); hresult = non_scriptable3->put_RedirectDynamicDevices( (options.resource_flags & NAVOP_RDP_RESOURCE_FLAG_DYNAMIC_DEVICES) != 0 ? VARIANT_TRUE : VARIANT_FALSE); trace_native_hresult( "connect.resource.redirect_dynamic_devices.after", static_cast(hresult)); if (FAILED(hresult)) { return record_last_hresult( context.owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(hresult)); } trace_native_stage("connect.resource.redirect_serial_ports.before"); hresult = advanced->put_RedirectPorts( (options.resource_flags & NAVOP_RDP_RESOURCE_FLAG_SERIAL_PORTS) != 0 ? VARIANT_TRUE : VARIANT_FALSE); trace_native_hresult( "connect.resource.redirect_serial_ports.after", static_cast(hresult)); if (FAILED(hresult)) { return record_last_hresult( context.owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(hresult)); } // Cameras need the IMsRdpClientNonScriptable camera collection, which the // current ABI does not transport. Never consume this request as success: // the Rust policy validator is the first line of defense and this native // check keeps the ABI fail-closed if a caller bypasses that validator. if ((options.resource_flags & NAVOP_RDP_RESOURCE_FLAG_CAMERAS) != 0) { trace_native_stage("connect.resource.cameras.unavailable"); return record_last_error( context.owner, NAVOP_RDP_RESULT_UNAVAILABLE); } // Microphones are consumed by the audio policy through // AudioCaptureRedirectionMode (see configure_audio_redirection). return NAVOP_RDP_RESULT_OK; } NavopRdpResult configure_input_policy( const NativeRdpConnectionPolicyContext& context, const NavopRdpConnectionOptions& options) noexcept { CComQIPtr client7(context.client); if (client7 == nullptr) { return record_last_error( context.owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } // KeyboardHookMode belongs to IMsRdpClientSecuredSettings. Setting it // through AdvancedSettings9 via IDispatch fails with DISP_E_UNKNOWNNAME on // current MSTSC controls. CComPtr secured_settings3; trace_native_stage("connect.input.get_secured_settings3.before"); HRESULT hresult = client7->get_SecuredSettings3(&secured_settings3); trace_native_hresult( "connect.input.get_secured_settings3.after", static_cast(hresult)); if (FAILED(hresult) || secured_settings3 == nullptr) { if (FAILED(hresult)) { return record_last_hresult( context.owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(hresult)); } return record_last_error( context.owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } trace_native_stage("connect.input.keyboard_hook_mode.before"); hresult = secured_settings3->put_KeyboardHookMode( static_cast(options.keyboard_hook_mode)); trace_native_hresult( "connect.input.keyboard_hook_mode.after", static_cast(hresult)); // KeyboardHookMode is not implemented by every mstscax.dll build even // though the interface is present in the type library. The default // control behavior is still usable, so treat an unknown property as an // optional capability instead of aborting the entire connection. if (hresult == DISP_E_UNKNOWNNAME) { trace_native_stage("connect.input.keyboard_hook_mode.unsupported"); } else if (FAILED(hresult)) { return record_last_hresult( context.owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(hresult)); } CComPtr advanced; NavopRdpResult result = get_advanced_settings8( context.owner, context.client, &advanced); if (result != NAVOP_RDP_RESULT_OK) { return result; } result = configure_redirect_bool( context.owner, advanced, L"EnableWindowsKey", "connect.input.enable_windows_key", (options.input_flags & NAVOP_RDP_INPUT_FLAG_ENABLE_WINDOWS_KEY) != 0); if (result != NAVOP_RDP_RESULT_OK) { return result; } return configure_redirect_bool( context.owner, advanced, L"GrabFocusOnConnect", "connect.input.grab_focus_on_connect", (options.input_flags & NAVOP_RDP_INPUT_FLAG_GRAB_FOCUS_ON_CONNECT) != 0); } NavopRdpResult configure_performance_policy( const NativeRdpConnectionPolicyContext& context, const NavopRdpConnectionOptions& options) noexcept { trace_native_win32( "connect.performance.preset", options.performance_preset); CComPtr advanced; NavopRdpResult result = get_advanced_settings8( context.owner, context.client, &advanced); if (result != NAVOP_RDP_RESULT_OK) { return result; } const NativeRdpDispatchTarget performance_flags{ advanced, L"PerformanceFlags", "connect.performance.flags", }; result = set_required_dispatch_long( context.owner, performance_flags, to_mstsc_performance_flags(options.performance_flags)); if (result != NAVOP_RDP_RESULT_OK) { return result; } const bool bitmap_cache = (options.performance_flags & NAVOP_RDP_PERFORMANCE_FLAG_BITMAP_CACHE) != 0; result = configure_redirect_bool( context.owner, advanced, L"BitmapPersistence", "connect.performance.bitmap_persistence", bitmap_cache); if (result != NAVOP_RDP_RESULT_OK) { return result; } const NativeRdpDispatchTarget cache_persistence{ advanced, L"CachePersistenceActive", "connect.performance.cache_persistence", }; result = set_required_dispatch_long( context.owner, cache_persistence, bitmap_cache ? 1 : 0); if (result != NAVOP_RDP_RESULT_OK) { return result; } if (options.network_connection_type == kNetworkConnectionTypeAutodetect) { trace_native_stage( "connect.performance.network_connection_type.autodetect"); return NAVOP_RDP_RESULT_OK; } const NativeRdpDispatchTarget network_type{ advanced, L"NetworkConnectionType", "connect.performance.network_connection_type", }; return set_required_dispatch_long( context.owner, network_type, static_cast(options.network_connection_type) + kNetworkConnectionTypeOffset); }