diff --git a/Cargo.lock b/Cargo.lock index 9500a47f9..b2625f35a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2352,6 +2352,7 @@ dependencies = [ "ironrdp-graphics", "ironrdp-input", "ironrdp-pdu", + "ironrdp-rdcleanpath", "ironrdp-rdpdr", "ironrdp-rdpsnd", "ironrdp-server", diff --git a/crates/ironrdp/Cargo.toml b/crates/ironrdp/Cargo.toml index e68c98d66..7bff94d95 100644 --- a/crates/ironrdp/Cargo.toml +++ b/crates/ironrdp/Cargo.toml @@ -31,6 +31,7 @@ dvc = ["dep:ironrdp-dvc"] rdpdr = ["dep:ironrdp-rdpdr"] rdpsnd = ["dep:ironrdp-rdpsnd"] displaycontrol = ["dep:ironrdp-displaycontrol"] +rdcleanpath = ["dep:ironrdp-rdcleanpath"] # Internal (PRIVATE!) features used to aid testing. # Don't rely on these whatsoever. They may disappear at any time. __bench = ["ironrdp-server/__bench"] @@ -50,6 +51,7 @@ ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.3", optional = true } # pu ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.3", optional = true } # public ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.5", optional = true } # public ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.3", optional = true } # public +ironrdp-rdcleanpath = { path = "../ironrdp-rdcleanpath", version = "0.1", optional = true } # public [dev-dependencies] ironrdp-blocking = { path = "../ironrdp-blocking", version = "0.5.0" } diff --git a/crates/ironrdp/src/lib.rs b/crates/ironrdp/src/lib.rs index 11a3bdd8c..5f94902c0 100644 --- a/crates/ironrdp/src/lib.rs +++ b/crates/ironrdp/src/lib.rs @@ -63,3 +63,7 @@ pub use ironrdp_session as session; #[cfg(feature = "svc")] #[doc(inline)] pub use ironrdp_svc as svc; + +#[cfg(feature = "rdcleanpath")] +#[doc(inline)] +pub use ironrdp_rdcleanpath as rdclean_path; \ No newline at end of file diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index fbefb2e76..0c31901a9 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -14,7 +14,7 @@ doctest = false [dependencies] diplomat = "0.7" diplomat-runtime = "0.7" -ironrdp = { path = "../crates/ironrdp", features = ["session", "connector", "dvc", "svc", "rdpdr", "rdpsnd", "graphics", "input", "cliprdr", "displaycontrol"] } +ironrdp = { path = "../crates/ironrdp", features = ["session", "connector", "dvc", "svc", "rdpdr", "rdpsnd", "graphics", "input", "cliprdr", "displaycontrol","rdcleanpath"] } ironrdp-cliprdr-native.path = "../crates/ironrdp-cliprdr-native" ironrdp-core = { path = "../crates/ironrdp-core", features = ["alloc"] } sspi = { version = "0.15", features = ["network_client"] } diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs index 747f19545..e910a5738 100644 --- a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs +++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.IO; using System.Net.Security; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -22,7 +23,7 @@ public partial class MainWindow : Window readonly InputDatabase? _inputDatabase = InputDatabase.New(); ActiveStage? _activeStage; DecodedImage? _decodedImage; - Framed? _framed; + Framed? _framed; WinCliprdr? _cliprdr; private readonly RendererModel _renderModel; private Image? _imageControl; @@ -79,8 +80,10 @@ private void OnOpened(object? sender, EventArgs e) var password = Environment.GetEnvironmentVariable("IRONRDP_PASSWORD"); var domain = Environment.GetEnvironmentVariable("IRONRDP_DOMAIN"); var server = Environment.GetEnvironmentVariable("IRONRDP_SERVER"); + var wsProxy = Environment.GetEnvironmentVariable("IRONRDP_PROXY"); + var wsProxyToken = Environment.GetEnvironmentVariable("IRONRDP_PROXY_TOKEN"); - if (username == null || password == null || domain == null || server == null) + if (username == null || password == null || server == null) { var errorMessage = "Please set the IRONRDP_USERNAME, IRONRDP_PASSWORD, IRONRDP_DOMAIN, and RONRDP_SERVER environment variables"; @@ -106,15 +109,41 @@ private void OnOpened(object? sender, EventArgs e) BeforeConnectSetup(); Task.Run(async () => { - var (res, framed) = await Connection.Connect(config, server, factory); - this._decodedImage = DecodedImage.New(PixelFormat.RgbA32, res.GetDesktopSize().GetWidth(), - res.GetDesktopSize().GetHeight()); - this._activeStage = ActiveStage.New(res); - this._framed = framed; - ReadPduAndProcessActiveStage(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + try + { + + ConnectionResult res; + Framed framed; + //wsProxy = null; + if (wsProxy != null && wsProxyToken != null) + { + Debug.WriteLine("Connecting via WebSocket proxy"); + (res, framed) = await Connection.ConnectWs( + config, + new RdCleanPathConfig(new Uri(wsProxy), wsProxyToken), + server, + factory); + } + else + { + Debug.WriteLine("Connecting directly to server"); + (res, framed) = await Connection.Connect(config, server, factory); + } + Debug.WriteLine("Connection success"); + this._decodedImage = DecodedImage.New(PixelFormat.RgbA32, res.GetDesktopSize().GetWidth(), + res.GetDesktopSize().GetHeight()); + this._activeStage = ActiveStage.New(res); + this._framed = framed; + ReadPduAndProcessActiveStage(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + HandleClipboardEvents(); + } + } + catch (Exception e) { - HandleClipboardEvents(); + Debug.WriteLine(e); + this.Close(); } }); } @@ -260,12 +289,17 @@ private void HandleClipboardEvents() }); } - private static Config BuildConfig(string username, string password, string domain, int width, int height) + private static Config BuildConfig(string username, string password, string? domain, int width, int height) { ConfigBuilder configBuilder = ConfigBuilder.New(); configBuilder.WithUsernameAndPassword(username, password); - configBuilder.SetDomain(domain); + if (domain != null) + { + configBuilder.SetDomain(domain); + } + configBuilder.SetEnableCredssp(true); + configBuilder.SetEnableTls(true); configBuilder.SetDesktopSize((ushort)height, (ushort)width); configBuilder.SetClientName("IronRdp"); configBuilder.SetClientDir("C:\\"); @@ -395,6 +429,7 @@ private async Task HandleActiveStageOutput(ActiveStageOutputIterator outpu var output = outputIterator .Next()!; // outputIterator.Next() is not null since outputIterator.IsEmpty() is false + Debug.WriteLine($"Output type: {output.GetType()}, Output enum type : {output.GetEnumType()}"); if (output.GetEnumType() == ActiveStageOutputType.Terminate) { return false; @@ -418,7 +453,7 @@ private async Task HandleActiveStageOutput(ActiveStageOutputIterator outpu var writeBuf = WriteBuf.New(); while (true) { - await Connection.SingleSequenceStep(activationSequence, writeBuf,_framed!); + await Connection.SingleSequenceStep(activationSequence, writeBuf, _framed!); if (activationSequence.GetState().GetType() != ConnectionActivationStateType.Finalized) continue; diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj index 1ef474af5..b0fc96a3b 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj @@ -19,4 +19,19 @@ + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index b685455ca..8c83fd95e 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -1,4 +1,5 @@ -using SixLabors.ImageSharp; +using System.Diagnostics; +using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; namespace Devolutions.IronRdp.ConnectExample @@ -19,11 +20,30 @@ static async Task Main(string[] args) var serverName = arguments["--serverName"]; var username = arguments["--username"]; var password = arguments["--password"]; - var domain = arguments["--domain"]; + arguments.TryGetValue("--domain", out var domain); + arguments.TryGetValue("--proxy", out var wsProxy); + arguments.TryGetValue("--proxyToken", out var wsProxyToken); try { - var (res, framed) = await Connection.Connect(buildConfig(serverName, username, password, domain, 1980, 1080), serverName, null); + ConnectionResult res; + Framed framed; + if (wsProxyToken != null && wsProxy != null) + { + (res, framed) = await Connection.ConnectWs( + buildConfig(serverName, username, password, domain, 1980, 1080), + new RdCleanPathConfig(new Uri(wsProxy), wsProxyToken), + serverName, + null); + } + else + { + (res, framed) = await Connection.Connect( + buildConfig(serverName, username, password, domain, 1980, 1080), + serverName, + null); + } + var decodedImage = DecodedImage.New(PixelFormat.RgbA32, res.GetDesktopSize().GetWidth(), res.GetDesktopSize().GetHeight()); var activeState = ActiveStage.New(res); var keepLooping = true; @@ -37,11 +57,11 @@ static async Task Main(string[] args) var pduReadTask = await readPduTask; action = pduReadTask.Item1; payload = pduReadTask.Item2; - Console.WriteLine($"Action: {action}"); + Debug.WriteLine($"Action: {action}"); } else { - Console.WriteLine("Timeout"); + Debug.WriteLine("Timeout"); break; } var outputIterator = activeState.Process(decodedImage, action, payload); @@ -49,10 +69,10 @@ static async Task Main(string[] args) while (!outputIterator.IsEmpty()) { var output = outputIterator.Next()!; // outputIterator.Next() is not null since outputIterator.IsEmpty() is false - Console.WriteLine($"Output type: {output.GetType()}"); + Debug.WriteLine($"Output type: {output.GetType()}, Output enum type : {output.GetEnumType()}"); if (output.GetEnumType() == ActiveStageOutputType.Terminate) { - Console.WriteLine("Connection terminated."); + Debug.WriteLine("Connection terminated."); keepLooping = false; } @@ -66,8 +86,7 @@ static async Task Main(string[] args) } } - saveImage(decodedImage, "output.png"); - + saveImage(decodedImage, "C:\\dev\\IronRDP\\output.bmp"); } catch (Exception e) { @@ -104,7 +123,7 @@ private static void saveImage(DecodedImage decodedImage, string v) } // Save the image as bitmap. - image.Save("./output.bmp"); + image.Save(v); } static Dictionary? ParseArguments(string[] args) @@ -134,6 +153,10 @@ private static void saveImage(DecodedImage decodedImage, string v) return null; } lastKey = arg; + } + else if (arg == "\\" || arg == "//") + { + } else { @@ -160,7 +183,15 @@ private static void saveImage(DecodedImage decodedImage, string v) static bool IsValidArgument(string argument) { - var validArguments = new List { "--serverName", "--username", "--password", "--domain" }; + var validArguments = new List + { + "--serverName", + "--username", + "--password", + "--domain", + "--proxy", + "--proxyToken" + }; return validArguments.Contains(argument); } @@ -168,19 +199,26 @@ static void PrintHelp() { Console.WriteLine("Usage: dotnet run -- [OPTIONS]"); Console.WriteLine("Options:"); - Console.WriteLine(" --serverName The name of the server to connect to."); - Console.WriteLine(" --username The username for connection."); - Console.WriteLine(" --password The password for connection."); - Console.WriteLine(" --domain The domain of the server."); - Console.WriteLine(" --help Show this message and exit."); + Console.WriteLine(" --serverName The name of the server to connect to."); + Console.WriteLine(" --username The username for connection."); + Console.WriteLine(" --password The password for connection."); + Console.WriteLine(" --domain The domain of the server."); + Console.WriteLine(" --proxy WebSocket proxy URL."); + Console.WriteLine(" --proxyToken Authentication token for the proxy."); + Console.WriteLine(" --help Show this message and exit."); } - private static Config buildConfig(string servername, string username, string password, string domain, int width, int height) + private static Config buildConfig(string servername, string username, string password, string? domain, int width, int height) { ConfigBuilder configBuilder = ConfigBuilder.New(); configBuilder.WithUsernameAndPassword(username, password); - configBuilder.SetDomain(domain); + if (domain != null) + { + configBuilder.SetDomain(domain); + } + configBuilder.SetEnableCredssp(true); + configBuilder.SetEnableTls(true); configBuilder.SetDesktopSize((ushort)height, (ushort)width); configBuilder.SetClientName("IronRdp"); configBuilder.SetClientDir("C:\\"); @@ -188,6 +226,5 @@ private static Config buildConfig(string servername, string username, string pas return configBuilder.Build(); } - } } diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Properties/Resources.Designer.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Properties/Resources.Designer.cs new file mode 100644 index 000000000..eb168c12f --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Devolutions.IronRdp.ConnectExample.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Devolutions.IronRdp.ConnectExample.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Properties/Resources.resx b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Properties/Resources.resx new file mode 100644 index 000000000..4fdb1b6af --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Properties/Resources.resx @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs index 0f9ec353e..2efd9328f 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs @@ -24,4 +24,5 @@ public enum IronRdpErrorKind IncorrectEnumType = 8, Clipboard = 9, WrongOS = 10, + MissingRequiredField = 11, } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs index 1e86b93a1..9a146e447 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs @@ -24,4 +24,5 @@ public enum IronRdpErrorKind IncorrectEnumType = 8, Clipboard = 9, WrongOS = 10, + MissingRequiredField = 11, } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdCleanPathPdu.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdCleanPathPdu.cs new file mode 100644 index 000000000..3455481a5 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdCleanPathPdu.cs @@ -0,0 +1,39 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct RdCleanPathPdu +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathPdu_to_der", ExactSpelling = true)] + public static unsafe extern RdcleanpathFfiResultBoxVecU8BoxIronRdpError ToDer(RdCleanPathPdu* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathPdu_get_hint", ExactSpelling = true)] + public static unsafe extern PduHint* GetHint(); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathPdu_from_der", ExactSpelling = true)] + public static unsafe extern RdcleanpathFfiResultBoxRdCleanPathPduBoxIronRdpError FromDer(byte* der, nuint derSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathPdu_get_x224_connection_pdu", ExactSpelling = true)] + public static unsafe extern RdcleanpathFfiResultBoxVecU8BoxIronRdpError GetX224ConnectionPdu(RdCleanPathPdu* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathPdu_get_server_cert_chain", ExactSpelling = true)] + public static unsafe extern RdcleanpathFfiResultBoxServerCertChainBoxIronRdpError GetServerCertChain(RdCleanPathPdu* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathPdu_get_server_addr", ExactSpelling = true)] + public static unsafe extern RdcleanpathFfiResultVoidBoxIronRdpError GetServerAddr(RdCleanPathPdu* self, DiplomatWriteable* serverAddr); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathPdu_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(RdCleanPathPdu* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdCleanPathRequestBuilder.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdCleanPathRequestBuilder.cs new file mode 100644 index 000000000..d1f3f5f2a --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdCleanPathRequestBuilder.cs @@ -0,0 +1,39 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct RdCleanPathRequestBuilder +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathRequestBuilder_new", ExactSpelling = true)] + public static unsafe extern RdCleanPathRequestBuilder* New(); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathRequestBuilder_with_x224_pdu", ExactSpelling = true)] + public static unsafe extern void WithX224Pdu(RdCleanPathRequestBuilder* self, VecU8* x224Pdu); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathRequestBuilder_with_destination", ExactSpelling = true)] + public static unsafe extern void WithDestination(RdCleanPathRequestBuilder* self, byte* destination, nuint destinationSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathRequestBuilder_with_proxy_auth", ExactSpelling = true)] + public static unsafe extern void WithProxyAuth(RdCleanPathRequestBuilder* self, byte* proxyAuth, nuint proxyAuthSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathRequestBuilder_with_pcb", ExactSpelling = true)] + public static unsafe extern void WithPcb(RdCleanPathRequestBuilder* self, byte* pcb, nuint pcbSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathRequestBuilder_build", ExactSpelling = true)] + public static unsafe extern RdcleanpathFfiResultBoxRdCleanPathPduBoxIronRdpError Build(RdCleanPathRequestBuilder* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "RdCleanPathRequestBuilder_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(RdCleanPathRequestBuilder* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxRdCleanPathPduBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxRdCleanPathPduBoxIronRdpError.cs new file mode 100644 index 000000000..74bf82a09 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxRdCleanPathPduBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct RdcleanpathFfiResultBoxRdCleanPathPduBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal RdCleanPathPdu* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe RdCleanPathPdu* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxServerCertChainBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxServerCertChainBoxIronRdpError.cs new file mode 100644 index 000000000..c78b2d558 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxServerCertChainBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct RdcleanpathFfiResultBoxServerCertChainBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal ServerCertChain* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe ServerCertChain* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxVecU8BoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxVecU8BoxIronRdpError.cs new file mode 100644 index 000000000..7cce2e6ad --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultBoxVecU8BoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct RdcleanpathFfiResultBoxVecU8BoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal VecU8* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe VecU8* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultVoidBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultVoidBoxIronRdpError.cs new file mode 100644 index 000000000..c1e085512 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawRdcleanpathFfiResultVoidBoxIronRdpError.cs @@ -0,0 +1,36 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct RdcleanpathFfiResultVoidBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerCertChain.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerCertChain.cs new file mode 100644 index 000000000..bd3bf3913 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerCertChain.cs @@ -0,0 +1,30 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ServerCertChain +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ServerCertChain_get_len", ExactSpelling = true)] + public static unsafe extern nuint GetLen(ServerCertChain* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ServerCertChain_get_vecu8", ExactSpelling = true)] + public static unsafe extern UtilsFfiResultBoxVecU8BoxIronRdpError GetVecu8(ServerCertChain* self, nuint index); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ServerCertChain_get_slice", ExactSpelling = true)] + public static unsafe extern UtilsFfiResultBoxBytesSliceBoxIronRdpError GetSlice(ServerCertChain* self, nuint index); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ServerCertChain_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ServerCertChain* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxBytesSliceBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxBytesSliceBoxIronRdpError.cs new file mode 100644 index 000000000..5527183b6 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxBytesSliceBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct UtilsFfiResultBoxBytesSliceBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal BytesSlice* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe BytesSlice* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxVecU8BoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxVecU8BoxIronRdpError.cs new file mode 100644 index 000000000..823dd2517 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxVecU8BoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct UtilsFfiResultBoxVecU8BoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal VecU8* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe VecU8* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RdCleanPathPdu.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RdCleanPathPdu.cs new file mode 100644 index 000000000..7b4f816b6 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RdCleanPathPdu.cs @@ -0,0 +1,225 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class RdCleanPathPdu: IDisposable +{ + private unsafe Raw.RdCleanPathPdu* _inner; + + public string ServerAddr + { + get + { + return GetServerAddr(); + } + } + + public ServerCertChain ServerCertChain + { + get + { + return GetServerCertChain(); + } + } + + public VecU8 X224ConnectionPdu + { + get + { + return GetX224ConnectionPdu(); + } + } + + /// + /// Creates a managed RdCleanPathPdu from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe RdCleanPathPdu(Raw.RdCleanPathPdu* handle) + { + _inner = handle; + } + + /// + /// + /// A VecU8 allocated on Rust side. + /// + public VecU8 ToDer() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathPdu"); + } + Raw.RdcleanpathFfiResultBoxVecU8BoxIronRdpError result = Raw.RdCleanPathPdu.ToDer(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.VecU8* retVal = result.Ok; + return new VecU8(retVal); + } + } + + /// + /// A PduHint allocated on Rust side. + /// + public static PduHint GetHint() + { + unsafe + { + Raw.PduHint* retVal = Raw.RdCleanPathPdu.GetHint(); + return new PduHint(retVal); + } + } + + /// + /// + /// A RdCleanPathPdu allocated on Rust side. + /// + public static RdCleanPathPdu FromDer(byte[] der) + { + unsafe + { + nuint derLength = (nuint)der.Length; + fixed (byte* derPtr = der) + { + Raw.RdcleanpathFfiResultBoxRdCleanPathPduBoxIronRdpError result = Raw.RdCleanPathPdu.FromDer(derPtr, derLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.RdCleanPathPdu* retVal = result.Ok; + return new RdCleanPathPdu(retVal); + } + } + } + + /// + /// + /// A VecU8 allocated on Rust side. + /// + public VecU8 GetX224ConnectionPdu() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathPdu"); + } + Raw.RdcleanpathFfiResultBoxVecU8BoxIronRdpError result = Raw.RdCleanPathPdu.GetX224ConnectionPdu(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.VecU8* retVal = result.Ok; + return new VecU8(retVal); + } + } + + /// + /// + /// A ServerCertChain allocated on Rust side. + /// + public ServerCertChain GetServerCertChain() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathPdu"); + } + Raw.RdcleanpathFfiResultBoxServerCertChainBoxIronRdpError result = Raw.RdCleanPathPdu.GetServerCertChain(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.ServerCertChain* retVal = result.Ok; + return new ServerCertChain(retVal); + } + } + + /// + public void GetServerAddr(DiplomatWriteable serverAddr) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathPdu"); + } + Raw.RdcleanpathFfiResultVoidBoxIronRdpError result = Raw.RdCleanPathPdu.GetServerAddr(_inner, &serverAddr); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + + /// + public string GetServerAddr() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathPdu"); + } + DiplomatWriteable writeable = new DiplomatWriteable(); + Raw.RdcleanpathFfiResultVoidBoxIronRdpError result = Raw.RdCleanPathPdu.GetServerAddr(_inner, &writeable); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + string retVal = writeable.ToUnicode(); + writeable.Dispose(); + return retVal; + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.RdCleanPathPdu* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.RdCleanPathPdu.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~RdCleanPathPdu() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RdCleanPathRequestBuilder.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RdCleanPathRequestBuilder.cs new file mode 100644 index 000000000..cb0df5499 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RdCleanPathRequestBuilder.cs @@ -0,0 +1,166 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class RdCleanPathRequestBuilder: IDisposable +{ + private unsafe Raw.RdCleanPathRequestBuilder* _inner; + + /// + /// Creates a managed RdCleanPathRequestBuilder from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe RdCleanPathRequestBuilder(Raw.RdCleanPathRequestBuilder* handle) + { + _inner = handle; + } + + /// + /// A RdCleanPathRequestBuilder allocated on Rust side. + /// + public static RdCleanPathRequestBuilder New() + { + unsafe + { + Raw.RdCleanPathRequestBuilder* retVal = Raw.RdCleanPathRequestBuilder.New(); + return new RdCleanPathRequestBuilder(retVal); + } + } + + public void WithX224Pdu(VecU8 x224Pdu) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathRequestBuilder"); + } + Raw.VecU8* x224PduRaw; + x224PduRaw = x224Pdu.AsFFI(); + if (x224PduRaw == null) + { + throw new ObjectDisposedException("VecU8"); + } + Raw.RdCleanPathRequestBuilder.WithX224Pdu(_inner, x224PduRaw); + } + } + + public void WithDestination(string destination) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathRequestBuilder"); + } + byte[] destinationBuf = DiplomatUtils.StringToUtf8(destination); + nuint destinationBufLength = (nuint)destinationBuf.Length; + fixed (byte* destinationBufPtr = destinationBuf) + { + Raw.RdCleanPathRequestBuilder.WithDestination(_inner, destinationBufPtr, destinationBufLength); + } + } + } + + public void WithProxyAuth(string proxyAuth) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathRequestBuilder"); + } + byte[] proxyAuthBuf = DiplomatUtils.StringToUtf8(proxyAuth); + nuint proxyAuthBufLength = (nuint)proxyAuthBuf.Length; + fixed (byte* proxyAuthBufPtr = proxyAuthBuf) + { + Raw.RdCleanPathRequestBuilder.WithProxyAuth(_inner, proxyAuthBufPtr, proxyAuthBufLength); + } + } + } + + public void WithPcb(string pcb) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathRequestBuilder"); + } + byte[] pcbBuf = DiplomatUtils.StringToUtf8(pcb); + nuint pcbBufLength = (nuint)pcbBuf.Length; + fixed (byte* pcbBufPtr = pcbBuf) + { + Raw.RdCleanPathRequestBuilder.WithPcb(_inner, pcbBufPtr, pcbBufLength); + } + } + } + + /// + /// + /// A RdCleanPathPdu allocated on Rust side. + /// + public RdCleanPathPdu Build() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("RdCleanPathRequestBuilder"); + } + Raw.RdcleanpathFfiResultBoxRdCleanPathPduBoxIronRdpError result = Raw.RdCleanPathRequestBuilder.Build(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.RdCleanPathPdu* retVal = result.Ok; + return new RdCleanPathPdu(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.RdCleanPathRequestBuilder* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.RdCleanPathRequestBuilder.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~RdCleanPathRequestBuilder() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ServerCertChain.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ServerCertChain.cs new file mode 100644 index 000000000..835bf2341 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ServerCertChain.cs @@ -0,0 +1,128 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ServerCertChain: IDisposable +{ + private unsafe Raw.ServerCertChain* _inner; + + public nuint Len + { + get + { + return GetLen(); + } + } + + /// + /// Creates a managed ServerCertChain from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ServerCertChain(Raw.ServerCertChain* handle) + { + _inner = handle; + } + + public nuint GetLen() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ServerCertChain"); + } + nuint retVal = Raw.ServerCertChain.GetLen(_inner); + return retVal; + } + } + + /// + /// + /// A VecU8 allocated on Rust side. + /// + public VecU8 GetVecu8(nuint index) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ServerCertChain"); + } + Raw.UtilsFfiResultBoxVecU8BoxIronRdpError result = Raw.ServerCertChain.GetVecu8(_inner, index); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.VecU8* retVal = result.Ok; + return new VecU8(retVal); + } + } + + /// + /// + /// A BytesSlice allocated on Rust side. + /// + public BytesSlice GetSlice(nuint index) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ServerCertChain"); + } + Raw.UtilsFfiResultBoxBytesSliceBoxIronRdpError result = Raw.ServerCertChain.GetSlice(_inner, index); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.BytesSlice* retVal = result.Ok; + return new BytesSlice(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ServerCertChain* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ServerCertChain.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ServerCertChain() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/src/Connection.cs b/ffi/dotnet/Devolutions.IronRdp/src/Connection.cs index ed638b934..31bbe3caa 100644 --- a/ffi/dotnet/Devolutions.IronRdp/src/Connection.cs +++ b/ffi/dotnet/Devolutions.IronRdp/src/Connection.cs @@ -1,21 +1,24 @@ +using System.Diagnostics; +using Devolutions.IronRdp.src; using System.Net; using System.Net.Security; using System.Net.Sockets; +using System.Net.WebSockets; +using System.Security.Cryptography.X509Certificates; namespace Devolutions.IronRdp; public static class Connection { - public static async Task<(ConnectionResult, Framed)> Connect(Config config, string serverName, + public static async Task<(ConnectionResult, Framed)> Connect(Config config, string serverName, CliprdrBackendFactory? factory, int port = 3389) { var client = await CreateTcpConnection(serverName, port); - string clientAddr = client.Client.LocalEndPoint.ToString(); - Console.WriteLine(clientAddr); + var clientAddr = client.Client.LocalEndPoint?.ToString(); var framed = new Framed(client.GetStream()); - var connector = ClientConnector.New(config, clientAddr); + var connector = ClientConnector.New(config, clientAddr!); connector.WithDynamicChannelDisplayControl(); @@ -26,13 +29,93 @@ public static class Connection } await ConnectBegin(framed, connector); - var (serverPublicKey, framedSsl) = await SecurityUpgrade(framed, connector); + (var serverPublicKey,Framed framedSsl) = await SecurityUpgrade(framed, connector); var result = await ConnectFinalize(serverName, connector, serverPublicKey, framedSsl); return (result, framedSsl); } - private static async Task<(byte[], Framed)> SecurityUpgrade(Framed framed, + public static async Task<(ConnectionResult, Framed)> ConnectWs(Config config, RdCleanPathConfig rdCleanPathConfig, string serverName, CliprdrBackendFactory? factory, int port = 3389, CancellationToken token = default) + { + var websocket = new ClientWebSocket(); + + await websocket.ConnectAsync(rdCleanPathConfig.GatewayUri, token); + var stream = new WebsocketStream(websocket); + var framed = new Framed(stream); + + var client = await CreateTcpConnection(serverName, port); + var clientAddr = client.Client.LocalEndPoint?.ToString(); + + var connector = ClientConnector.New(config, clientAddr!); + + connector.WithDynamicChannelDisplayControl(); + + if (factory != null) + { + var cliprdr = factory.BuildCliprdr(); + connector.AttachStaticCliprdr(cliprdr); + } + + string destination = serverName + ":" + port; + + var serverPublicKey = await ConnectRdCleanPath(framed, connector, destination, rdCleanPathConfig.AuthToken); + + var result = await ConnectFinalize(serverName, connector, serverPublicKey, framed); + + + return (result, framed); + } + + private static async Task ConnectRdCleanPath( + Framed framed, + ClientConnector connector, + string destination, + string proxyAuth + ) + where S : Stream + { + var writeBuf = WriteBuf.New(); + connector.StepNoInput(writeBuf); + var x224Pdu = writeBuf.GetFilled(); + + + // Create RdCleanPathRequest + var rdCleanPathReqBuilder = RdCleanPathRequestBuilder.New(); + rdCleanPathReqBuilder.WithX224Pdu(x224Pdu); + rdCleanPathReqBuilder.WithDestination(destination); + rdCleanPathReqBuilder.WithProxyAuth(proxyAuth); + var rdcleanPathReq = rdCleanPathReqBuilder.Build(); + + // Send RdCleanPathRequest + var rdcleanPathPdu = rdcleanPathReq.ToDer(); + var bytes = Utils.VecU8ToByte(rdcleanPathPdu); + await framed.Write(bytes); + + + // Read RdCleanPathResponse + var pduHint = RdCleanPathPdu.GetHint(); + + var pdu = await framed.ReadByHint(pduHint); + var rdCleanPathResponse = RdCleanPathPdu.FromDer(pdu); + + var x224ConnectionResponse = rdCleanPathResponse.GetX224ConnectionPdu(); + var serverCertChain = rdCleanPathResponse.GetServerCertChain(); + + // Handle X224 connection response + writeBuf.Clear(); + connector.Step(Utils.VecU8ToByte(x224ConnectionResponse), writeBuf); + + var serverCertVec = serverCertChain.GetVecu8(0); + var serverCertByte = Utils.VecU8ToByte(serverCertVec); + var serverCert = new X509Certificate2(serverCertByte); + var serverPublicKey = serverCert.GetPublicKey(); + + connector.MarkSecurityUpgradeAsDone(); + + return serverPublicKey; + } + + private static async Task<(byte[], Framed)> SecurityUpgrade(Framed framed, ClientConnector connector) { var (streamRequireUpgrade, _) = framed.GetInner(); @@ -47,7 +130,7 @@ await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions() AllowTlsResume = false }); var serverPublicKey = await promise.Task; - Framed framedSsl = new(sslStream); + Framed framedSsl = new(sslStream); connector.MarkSecurityUpgradeAsDone(); return (serverPublicKey, framedSsl); @@ -63,18 +146,18 @@ private static async Task ConnectBegin(Framed framed, ClientConne } - private static async Task ConnectFinalize(string serverName, ClientConnector connector, - byte[] serverPubKey, Framed framedSsl) + private static async Task ConnectFinalize(string serverName, ClientConnector connector, + byte[] serverPubKey, Framed upgradedFramed) where S : System.IO.Stream { var writeBuf2 = WriteBuf.New(); if (connector.ShouldPerformCredssp()) { - await PerformCredsspSteps(connector, serverName, writeBuf2, framedSsl, serverPubKey); + await PerformCredsspSteps(connector, serverName, writeBuf2, upgradedFramed, serverPubKey); } while (!connector.GetDynState().IsTerminal()) { - await SingleSequenceStep(connector, writeBuf2, framedSsl); + await SingleSequenceStep(connector, writeBuf2, upgradedFramed); } ClientConnectorState state = connector.ConsumeAndCastToClientConnectorState(); @@ -83,14 +166,12 @@ private static async Task ConnectFinalize(string serverName, C { return state.GetConnectedResult(); } - else - { - throw new IronRdpLibException(IronRdpLibExceptionType.ConnectionFailed, "Connection failed"); - } + + throw new IronRdpLibException(IronRdpLibExceptionType.ConnectionFailed, "Connection failed"); } - private static async Task PerformCredsspSteps(ClientConnector connector, string serverName, WriteBuf writeBuf, - Framed framedSsl, byte[] serverpubkey) + private static async Task PerformCredsspSteps(ClientConnector connector, string serverName, WriteBuf writeBuf, + Framed framedSsl, byte[] serverpubkey) where S : Stream { var credsspSequenceInitResult = CredsspSequence.Init(connector, serverName, serverpubkey, null); var credsspSequence = credsspSequenceInitResult.GetCredsspSequence(); @@ -227,6 +308,7 @@ static async Task CreateTcpConnection(String servername, int port) return client; } + } public static class Utils diff --git a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs index f03530f3d..cde759d58 100644 --- a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs +++ b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.InteropServices; namespace Devolutions.IronRdp; @@ -32,13 +33,11 @@ public Framed(TS stream) var action = pduInfo.GetAction(); return (action, frame); } - else + + var len = await this.Read(); + if (len == 0) { - var len = await this.Read(); - if (len == 0) - { - throw new IronRdpLibException(IronRdpLibExceptionType.EndOfFile, "EOF on ReadPdu"); - } + throw new IronRdpLibException(IronRdpLibExceptionType.EndOfFile, "EOF on ReadPdu"); } } } diff --git a/ffi/dotnet/Devolutions.IronRdp/src/RdcleanPath.cs b/ffi/dotnet/Devolutions.IronRdp/src/RdcleanPath.cs new file mode 100644 index 000000000..49a743704 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/src/RdcleanPath.cs @@ -0,0 +1,14 @@ +namespace Devolutions.IronRdp; + +public class RdCleanPathConfig +{ + public Uri GatewayUri { get; private set; } + + public string AuthToken { get; private set; } + + public RdCleanPathConfig(Uri url, string authToken) + { + GatewayUri = url; + AuthToken = authToken; + } +} \ No newline at end of file diff --git a/ffi/dotnet/Devolutions.IronRdp/src/WebsocketStream.cs b/ffi/dotnet/Devolutions.IronRdp/src/WebsocketStream.cs new file mode 100644 index 000000000..419669183 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/src/WebsocketStream.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Devolutions.IronRdp.src +{ + public class WebsocketStream : Stream + { + private readonly ClientWebSocket _webSocket; + + public WebsocketStream(ClientWebSocket webSocket) + { + _webSocket = webSocket; + } + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var result = await _webSocket.ReceiveAsync( + new ArraySegment(buffer, offset, count), cancellationToken); + + if (result.CloseStatus.HasValue) // remote sent a CLOSE frame + { + return 0; // treat as end-of-stream + } + + return result.Count; + } + + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (count == 0) + { + // Note: this is particularly important, if we send a zero-length frame, + // somehow Gateway will raise TLS issue during the proxy. + return; // Nothing to write + } + + await _webSocket.SendAsync( + new ArraySegment(buffer, offset, count), + WebSocketMessageType.Binary, + true, + cancellationToken); + } + + public override void Flush() + { + // No need. the third parameter of SendAsync is set to true, which means the frame is sent immediately. + // Also, this method is not called in practice ever somehow. + // However, since it's not blocking any functionality, we can leave it empty. + } + + // Not supported + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + } +} diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 9312ba45f..ffdc6c41f 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -139,6 +139,8 @@ pub mod ffi { Clipboard, #[error("wrong platform error")] WrongOS, + #[error("Missing required field")] + MissingRequiredField, } /// Stringified Picky error along with an error kind. diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 407847ce4..44009cd5a 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -11,6 +11,7 @@ pub mod graphics; pub mod input; pub mod log; pub mod pdu; +pub mod rdcleanpath; pub mod session; pub mod svc; pub mod utils; diff --git a/ffi/src/rdcleanpath.rs b/ffi/src/rdcleanpath.rs new file mode 100644 index 000000000..963d2e44c --- /dev/null +++ b/ffi/src/rdcleanpath.rs @@ -0,0 +1,136 @@ +#[derive(Clone, Copy, Debug)] +struct RDCleanPathHint; + +const RDCLEANPATH_HINT: RDCleanPathHint = RDCleanPathHint; + +impl ironrdp::pdu::PduHint for RDCleanPathHint { + fn find_size(&self, bytes: &[u8]) -> ironrdp::core::DecodeResult> { + match ironrdp::rdclean_path::RDCleanPathPdu::detect(bytes) { + ironrdp::rdclean_path::DetectionResult::Detected { total_length, .. } => Ok(Some((true, total_length))), + ironrdp::rdclean_path::DetectionResult::NotEnoughBytes => Ok(None), + ironrdp::rdclean_path::DetectionResult::Failed => Err(ironrdp::core::other_err!( + "RDCleanPathHint", + "detection failed (invalid PDU)" + )), + } + } +} + +#[diplomat::bridge] +pub mod ffi { + + use crate::error::ffi::{IronRdpError, IronRdpErrorKind}; + use crate::error::ValueConsumedError; + use crate::utils::ffi::{ServerCertChain, VecU8}; + use core::fmt::Write; + + #[diplomat::opaque] + pub struct RdCleanPathPdu(pub ironrdp::rdclean_path::RDCleanPathPdu); + + #[diplomat::opaque] + pub struct RdCleanPathRequestBuilder { + x224_pdu: Option>, + destination: Option, + proxy_auth: Option, + pcb: Option, + } + + impl RdCleanPathRequestBuilder { + pub fn new() -> Box { + Box::new(RdCleanPathRequestBuilder { + x224_pdu: None, + destination: None, + proxy_auth: None, + pcb: None, + }) + } + + pub fn with_x224_pdu(&mut self, x224_pdu: &VecU8) { + self.x224_pdu = Some(x224_pdu.0.clone()); + } + + pub fn with_destination(&mut self, destination: &str) { + self.destination = Some(destination.to_owned()); + } + + pub fn with_proxy_auth(&mut self, proxy_auth: &str) { + self.proxy_auth = Some(proxy_auth.to_owned()); + } + + pub fn with_pcb(&mut self, pcb: &str) { + self.pcb = Some(pcb.to_owned()); + } + + pub fn build(&self) -> Result, Box> { + let RdCleanPathRequestBuilder { + x224_pdu, + destination, + proxy_auth, + pcb, + } = self; + + let request = ironrdp::rdclean_path::RDCleanPathPdu::new_request( + x224_pdu.to_owned().ok_or(IronRdpErrorKind::MissingRequiredField)?, + destination.to_owned().ok_or(IronRdpErrorKind::MissingRequiredField)?, + proxy_auth.to_owned().ok_or(IronRdpErrorKind::MissingRequiredField)?, + pcb.to_owned(), + ) + .map_err(|_| IronRdpErrorKind::EncodeError)?; + + Ok(Box::new(RdCleanPathPdu(request))) + } + } + + impl RdCleanPathPdu { + pub fn to_der(&self) -> Result, Box> { + let der = self.0.to_der().map_err(|_| IronRdpErrorKind::EncodeError)?; + Ok(Box::new(VecU8(der))) + } + + pub fn get_hint<'a>() -> Box> { + Box::new(crate::connector::ffi::PduHint(&super::RDCLEANPATH_HINT)) + } + + pub fn from_der(der: &[u8]) -> Result, Box> { + let pdu = + ironrdp::rdclean_path::RDCleanPathPdu::from_der(der).map_err(|_| IronRdpErrorKind::DecodeError)?; + Ok(Box::new(RdCleanPathPdu(pdu))) + } + + pub fn get_x224_connection_pdu(&self) -> Result, Box> { + let Some(x224_pdu_response) = self.0.x224_connection_pdu.as_ref() else { + return Err(ValueConsumedError::for_item("RdCleanPathPdu").into()); + }; + + let result = x224_pdu_response.as_bytes().to_vec(); + + Ok(Box::new(VecU8(result))) + } + + pub fn get_server_cert_chain(&self) -> Result, Box> { + let Some(server_cert_chain) = self.0.server_cert_chain.as_ref() else { + return Err(ValueConsumedError::for_item("ServerCertChain").into()); + }; + + let vecs = server_cert_chain + .iter() + .map(|cert| cert.as_bytes().to_vec()) + .collect::>(); + + Ok(Box::new(ServerCertChain(vecs))) + } + + pub fn get_server_addr( + &self, + server_addr: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), Box> { + let Some(server_addr_str) = self.0.server_addr.as_ref() else { + return Err(ValueConsumedError::for_item("server_addr").into()); + }; + + write!(server_addr, "{server_addr_str}").map_err(|_| IronRdpErrorKind::IO)?; + + Ok(()) + } + } +} diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index 672c5653e..b60e40284 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -3,6 +3,29 @@ pub mod ffi { use crate::error::ffi::IronRdpError; + #[diplomat::opaque] + pub struct ServerCertChain(pub Vec>); + + impl ServerCertChain { + pub fn get_len(&self) -> usize { + self.0.len() + } + + pub fn get_vecu8(&self, index: usize) -> Result, Box> { + if index >= self.0.len() { + return Err("index out of bounds".into()); + } + Ok(Box::new(VecU8(self.0[index].clone()))) + } + + pub fn get_slice<'a>(&'a self, index: usize) -> Result>, Box> { + if index >= self.0.len() { + return Err("index out of bounds".into()); + } + Ok(Box::new(BytesSlice(&self.0[index]))) + } + } + #[diplomat::opaque] pub struct VecU8(pub Vec);