77using System ;
88using System . ComponentModel ;
99using System . Diagnostics ;
10+ using System . IO ;
1011using System . Net . Security ;
1112using System . Runtime . CompilerServices ;
1213using System . Runtime . InteropServices ;
@@ -22,7 +23,7 @@ public partial class MainWindow : Window
2223 readonly InputDatabase ? _inputDatabase = InputDatabase . New ( ) ;
2324 ActiveStage ? _activeStage ;
2425 DecodedImage ? _decodedImage ;
25- Framed < SslStream > ? _framed ;
26+ Framed < Stream > ? _framed ;
2627 WinCliprdr ? _cliprdr ;
2728 private readonly RendererModel _renderModel ;
2829 private Image ? _imageControl ;
@@ -77,18 +78,49 @@ private void OnOpened(object? sender, EventArgs e)
7778
7879 var username = Environment . GetEnvironmentVariable ( "IRONRDP_USERNAME" ) ;
7980 var password = Environment . GetEnvironmentVariable ( "IRONRDP_PASSWORD" ) ;
80- var domain = Environment . GetEnvironmentVariable ( "IRONRDP_DOMAIN" ) ;
81+ var domain = Environment . GetEnvironmentVariable ( "IRONRDP_DOMAIN" ) ; // Optional
8182 var server = Environment . GetEnvironmentVariable ( "IRONRDP_SERVER" ) ;
83+ var portEnv = Environment . GetEnvironmentVariable ( "IRONRDP_PORT" ) ; // Optional
8284
83- if ( username == null || password == null || domain == null || server == null )
85+ // Gateway configuration (optional)
86+ var gatewayUrl = Environment . GetEnvironmentVariable ( "IRONRDP_GATEWAY_URL" ) ;
87+ var gatewayToken = Environment . GetEnvironmentVariable ( "IRONRDP_GATEWAY_TOKEN" ) ;
88+ var tokengenUrl = Environment . GetEnvironmentVariable ( "IRONRDP_TOKENGEN_URL" ) ;
89+
90+ if ( username == null || password == null || server == null )
8491 {
8592 var errorMessage =
86- "Please set the IRONRDP_USERNAME, IRONRDP_PASSWORD, IRONRDP_DOMAIN, and RONRDP_SERVER environment variables" ;
93+ "Please set the IRONRDP_USERNAME, IRONRDP_PASSWORD, and IRONRDP_SERVER environment variables" ;
8794 Trace . TraceError ( errorMessage ) ;
8895 Close ( ) ;
8996 throw new InvalidProgramException ( errorMessage ) ;
9097 }
9198
99+ // Validate server is only domain or IP (no port allowed)
100+ // i.e. "example.com" or "10.10.0.3" the port should go to the dedicated env var IRONRDP_PORT
101+ if ( server . Contains ( ':' ) )
102+ {
103+ var errorMessage = $ "IRONRDP_SERVER must be a domain or IP address only, not '{ server } '. Use IRONRDP_PORT for the port.";
104+ Trace . TraceError ( errorMessage ) ;
105+ Close ( ) ;
106+ throw new InvalidProgramException ( errorMessage ) ;
107+ }
108+
109+ // Parse port from environment variable or use default
110+ int port = 3389 ;
111+ if ( ! string . IsNullOrEmpty ( portEnv ) )
112+ {
113+ if ( ! int . TryParse ( portEnv , out port ) || port <= 0 || port > 65535 )
114+ {
115+ var errorMessage = $ "IRONRDP_PORT must be a valid port number (1-65535), got '{ portEnv } '";
116+ Trace . TraceError ( errorMessage ) ;
117+ Close ( ) ;
118+ throw new InvalidProgramException ( errorMessage ) ;
119+ }
120+ }
121+
122+ Trace . TraceInformation ( $ "Target server: { server } :{ port } ") ;
123+
92124 var config = BuildConfig ( username , password , domain , _renderModel . Width , _renderModel . Height ) ;
93125
94126 CliprdrBackendFactory ? factory = null ;
@@ -106,15 +138,81 @@ private void OnOpened(object? sender, EventArgs e)
106138 BeforeConnectSetup ( ) ;
107139 Task . Run ( async ( ) =>
108140 {
109- var ( res , framed ) = await Connection . Connect ( config , server , factory ) ;
110- this . _decodedImage = DecodedImage . New ( PixelFormat . RgbA32 , res . GetDesktopSize ( ) . GetWidth ( ) ,
111- res . GetDesktopSize ( ) . GetHeight ( ) ) ;
112- this . _activeStage = ActiveStage . New ( res ) ;
113- this . _framed = framed ;
114- ReadPduAndProcessActiveStage ( ) ;
115- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
141+ try
116142 {
117- HandleClipboardEvents ( ) ;
143+ ConnectionResult res ;
144+
145+ // Determine connection mode: Gateway or Direct
146+ if ( ! string . IsNullOrEmpty ( gatewayUrl ) )
147+ {
148+ Trace . TraceInformation ( "=== GATEWAY MODE ===" ) ;
149+ Trace . TraceInformation ( $ "Gateway URL: { gatewayUrl } ") ;
150+ Trace . TraceInformation ( $ "Destination: { server } :{ port } ") ;
151+
152+ var tokenGen = new TokenGenerator ( tokengenUrl ?? "http://localhost:8080" ) ;
153+
154+ // Generate RDP token if not provided
155+ if ( string . IsNullOrEmpty ( gatewayToken ) )
156+ {
157+ Trace . TraceInformation ( "No RDP token provided, generating token..." ) ;
158+
159+ try
160+ {
161+ gatewayToken = await tokenGen . GenerateRdpTlsToken (
162+ dstHost : server ! ,
163+ proxyUser : string . IsNullOrEmpty ( domain ) ? username : $ "{ username } @{ domain } ",
164+ proxyPassword : password ! ,
165+ destUser : username ! ,
166+ destPassword : password !
167+ ) ;
168+ Trace . TraceInformation ( $ "RDP token generated successfully (length: { gatewayToken . Length } )") ;
169+ }
170+ catch ( Exception ex )
171+ {
172+ Trace . TraceError ( $ "Failed to generate RDP token: { ex . Message } ") ;
173+ Trace . TraceInformation ( "Make sure tokengen server is running:" ) ;
174+ Trace . TraceInformation ( $ " cargo run --manifest-path tools/tokengen/Cargo.toml -- server") ;
175+ throw ;
176+ }
177+ }
178+
179+ // Connect via gateway - destination needs "hostname:port" format for RDCleanPath
180+ string destination = $ "{ server } :{ port } ";
181+
182+ var ( gatewayRes , gatewayFramed ) = await RDCleanPathConnection . ConnectRDCleanPath (
183+ config , gatewayUrl , gatewayToken ! , destination , null , factory ) ;
184+ res = gatewayRes ;
185+ this . _framed = new Framed < Stream > ( gatewayFramed . GetInner ( ) . Item1 ) ;
186+
187+ Trace . TraceInformation ( "=== GATEWAY CONNECTION SUCCESSFUL ===" ) ;
188+ }
189+ else
190+ {
191+ Trace . TraceInformation ( "=== DIRECT MODE ===" ) ;
192+
193+ // Direct connection (original behavior)
194+ var ( directRes , directFramed ) = await Connection . Connect ( config , server , factory , port ) ;
195+ res = directRes ;
196+ this . _framed = new Framed < Stream > ( directFramed . GetInner ( ) . Item1 ) ;
197+
198+ Trace . TraceInformation ( "=== DIRECT CONNECTION SUCCESSFUL ===" ) ;
199+ }
200+
201+ this . _decodedImage = DecodedImage . New ( PixelFormat . RgbA32 , res . GetDesktopSize ( ) . GetWidth ( ) ,
202+ res . GetDesktopSize ( ) . GetHeight ( ) ) ;
203+ this . _activeStage = ActiveStage . New ( res ) ;
204+ ReadPduAndProcessActiveStage ( ) ;
205+
206+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
207+ {
208+ HandleClipboardEvents ( ) ;
209+ }
210+ }
211+ catch ( Exception ex )
212+ {
213+ Trace . TraceError ( $ "Connection failed: { ex . Message } ") ;
214+ Trace . TraceError ( $ "Stack trace: { ex . StackTrace } ") ;
215+ throw ;
118216 }
119217 } ) ;
120218 }
@@ -260,12 +358,16 @@ private void HandleClipboardEvents()
260358 } ) ;
261359 }
262360
263- private static Config BuildConfig ( string username , string password , string domain , int width , int height )
361+ private static Config BuildConfig ( string username , string password , string ? domain , int width , int height )
264362 {
265363 ConfigBuilder configBuilder = ConfigBuilder . New ( ) ;
266364
267365 configBuilder . WithUsernameAndPassword ( username , password ) ;
268- configBuilder . SetDomain ( domain ) ;
366+ if ( domain != null )
367+ {
368+ configBuilder . SetDomain ( domain ) ;
369+ }
370+
269371 configBuilder . SetDesktopSize ( ( ushort ) height , ( ushort ) width ) ;
270372 configBuilder . SetClientName ( "IronRdp" ) ;
271373 configBuilder . SetClientDir ( "C:\\ " ) ;
@@ -418,7 +520,7 @@ private async Task<bool> HandleActiveStageOutput(ActiveStageOutputIterator outpu
418520 var writeBuf = WriteBuf . New ( ) ;
419521 while ( true )
420522 {
421- await Connection . SingleSequenceStep ( activationSequence , writeBuf , _framed ! ) ;
523+ await Connection . SingleSequenceStep ( activationSequence , writeBuf , _framed ! ) ;
422524
423525 if ( activationSequence . GetState ( ) . GetType ( ) != ConnectionActivationStateType . Finalized )
424526 continue ;
0 commit comments