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
socket.setTimeout(0);
// Phase 2: CONNECT with RDP parameters
const connectInstruction = this.buildConnectInstruction(params);
this.logger.debug(`Sending connect instruction to guacd`);
// Parse the args instruction to get expected parameter names
const argsInstruction = buffer.substring(0, semicolonIdx + 1);
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);
resolve(socket);
@ -78,7 +88,8 @@ export class GuacamoleService {
});
}
private buildConnectInstruction(params: {
private buildConnectInstruction(
params: {
hostname: string;
port: number;
username: string;
@ -90,19 +101,20 @@ export class GuacamoleService {
security?: string;
colorDepth?: number;
ignoreCert?: boolean;
}): string {
// The connect instruction sends all RDP connection parameters as positional args.
// guacd expects exactly the args it listed in the "args" response to SELECT.
// We send the full standard RDP parameter set.
const args: Record<string, string> = {
hostname: params.hostname,
port: String(params.port),
username: params.username,
password: params.password,
width: String(params.width),
height: String(params.height),
dpi: String(params.dpi || 96),
security: params.security || 'any',
},
argNames: string[],
): string {
// Map our params to guacd's expected arg names
const paramMap: Record<string, string> = {
'hostname': params.hostname,
'port': String(params.port),
'username': params.username,
'password': params.password,
'domain': params.domain || '',
'width': String(params.width),
'height': String(params.height),
'dpi': String(params.dpi || 96),
'security': params.security || 'any',
'color-depth': String(params.colorDepth || 24),
'ignore-cert': params.ignoreCert !== false ? 'true' : 'false',
'disable-audio': 'false',
@ -110,14 +122,55 @@ export class GuacamoleService {
'enable-theming': 'true',
'enable-font-smoothing': 'true',
'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) {
args['domain'] = params.domain;
}
// Build the connect instruction with opcode + all arg values
const values = Object.values(args);
// Build values array matching the exact order guacd expects
const values = argNames.map((name) => paramMap[name] ?? '');
return this.encode('connect', ...values);
}
@ -133,7 +186,6 @@ export class GuacamoleService {
/**
* Decode a Guacamole instruction string back to a string array.
* Handles multiple instructions in a single buffer chunk.
*/
decode(instruction: string): string[] {
const parts: string[] = [];