fix(rdp): parse guacd args response and send matching positional connect values

This commit is contained in:
Vantz Stockwell 2026-03-14 05:05:00 -04:00
parent 72526487c3
commit 9d3a93bea9

View File

@ -54,9 +54,19 @@ export class GuacamoleService {
// Clear the connect timeout — handshake completed // Clear the connect timeout — handshake completed
socket.setTimeout(0); socket.setTimeout(0);
// Phase 2: CONNECT with RDP parameters // Parse the args instruction to get expected parameter names
const connectInstruction = this.buildConnectInstruction(params); const argsInstruction = buffer.substring(0, semicolonIdx + 1);
this.logger.debug(`Sending connect instruction to guacd`); const argNames = this.decode(argsInstruction);
// First element is the opcode ("args"), rest are parameter names
if (argNames[0] === 'args') {
argNames.shift();
}
this.logger.log(`guacd expects ${argNames.length} args: ${argNames.join(', ')}`);
// Phase 2: CONNECT — send values in the exact order guacd expects
const connectInstruction = this.buildConnectInstruction(params, argNames);
this.logger.debug(`Sending connect instruction with ${argNames.length} values`);
socket.write(connectInstruction); socket.write(connectInstruction);
resolve(socket); resolve(socket);
@ -78,7 +88,8 @@ export class GuacamoleService {
}); });
} }
private buildConnectInstruction(params: { private buildConnectInstruction(
params: {
hostname: string; hostname: string;
port: number; port: number;
username: string; username: string;
@ -90,19 +101,20 @@ export class GuacamoleService {
security?: string; security?: string;
colorDepth?: number; colorDepth?: number;
ignoreCert?: boolean; ignoreCert?: boolean;
}): string { },
// The connect instruction sends all RDP connection parameters as positional args. argNames: string[],
// guacd expects exactly the args it listed in the "args" response to SELECT. ): string {
// We send the full standard RDP parameter set. // Map our params to guacd's expected arg names
const args: Record<string, string> = { const paramMap: Record<string, string> = {
hostname: params.hostname, 'hostname': params.hostname,
port: String(params.port), 'port': String(params.port),
username: params.username, 'username': params.username,
password: params.password, 'password': params.password,
width: String(params.width), 'domain': params.domain || '',
height: String(params.height), 'width': String(params.width),
dpi: String(params.dpi || 96), 'height': String(params.height),
security: params.security || 'any', 'dpi': String(params.dpi || 96),
'security': params.security || 'any',
'color-depth': String(params.colorDepth || 24), 'color-depth': String(params.colorDepth || 24),
'ignore-cert': params.ignoreCert !== false ? 'true' : 'false', 'ignore-cert': params.ignoreCert !== false ? 'true' : 'false',
'disable-audio': 'false', 'disable-audio': 'false',
@ -110,14 +122,55 @@ export class GuacamoleService {
'enable-theming': 'true', 'enable-theming': 'true',
'enable-font-smoothing': 'true', 'enable-font-smoothing': 'true',
'resize-method': 'reconnect', 'resize-method': 'reconnect',
'server-layout': '',
'timezone': '',
'console': '',
'initial-program': '',
'client-name': 'Wraith',
'enable-full-window-drag': 'false',
'enable-desktop-composition': 'false',
'enable-menu-animations': 'false',
'disable-bitmap-caching': 'false',
'disable-offscreen-caching': 'false',
'disable-glyph-caching': 'false',
'preconnection-id': '',
'preconnection-blob': '',
'enable-sftp': 'false',
'sftp-hostname': '',
'sftp-port': '',
'sftp-username': '',
'sftp-password': '',
'sftp-private-key': '',
'sftp-passphrase': '',
'sftp-directory': '',
'sftp-root-directory': '',
'sftp-server-alive-interval': '',
'recording-path': '',
'recording-name': '',
'recording-exclude-output': '',
'recording-exclude-mouse': '',
'recording-include-keys': '',
'create-recording-path': '',
'remote-app': '',
'remote-app-dir': '',
'remote-app-args': '',
'gateway-hostname': '',
'gateway-port': '',
'gateway-domain': '',
'gateway-username': '',
'gateway-password': '',
'load-balance-info': '',
'normalize-clipboard': '',
'force-lossless': '',
'wol-send-packet': '',
'wol-mac-addr': '',
'wol-broadcast-addr': '',
'wol-udp-port': '',
'wol-wait-time': '',
}; };
if (params.domain) { // Build values array matching the exact order guacd expects
args['domain'] = params.domain; const values = argNames.map((name) => paramMap[name] ?? '');
}
// Build the connect instruction with opcode + all arg values
const values = Object.values(args);
return this.encode('connect', ...values); return this.encode('connect', ...values);
} }
@ -133,7 +186,6 @@ export class GuacamoleService {
/** /**
* Decode a Guacamole instruction string back to a string array. * Decode a Guacamole instruction string back to a string array.
* Handles multiple instructions in a single buffer chunk.
*/ */
decode(instruction: string): string[] { decode(instruction: string): string[] {
const parts: string[] = []; const parts: string[] = [];