From 0efca9118550b0937e142131daf2b35b60e15d42 Mon Sep 17 00:00:00 2001 From: aschumann-virtualcable Date: Thu, 9 Apr 2026 16:33:43 +0200 Subject: [PATCH] Refactor RDP client search logic to prioritize UDSRDP, improve logging, and ensure compatibility with older clients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Janier Rodríguez --- .../transports/RDP/scripts/linux/direct.py | 72 ++++++++++--------- .../transports/RDP/scripts/linux/tunnel.py | 52 +++++++------- .../transports/RDP/scripts/macosx/direct.py | 57 ++++++++------- .../transports/RDP/scripts/macosx/tunnel.py | 54 ++++++++------ 4 files changed, 133 insertions(+), 102 deletions(-) diff --git a/server/src/uds/transports/RDP/scripts/linux/direct.py b/server/src/uds/transports/RDP/scripts/linux/direct.py index 597c20f0b..d328f346b 100644 --- a/server/src/uds/transports/RDP/scripts/linux/direct.py +++ b/server/src/uds/transports/RDP/scripts/linux/direct.py @@ -5,7 +5,7 @@ import shutil import os -# Asegura que subprocess, shutil, os, os.path y typing estén en el scope global para clientes antiguos (3.6) +# Ensures subprocess, shutil, os, os.path and typing are in global scope for old clients (3.6) globals()['subprocess'] = subprocess globals()['shutil'] = shutil globals()['os'] = os @@ -27,7 +27,7 @@ tools: typing.Any = None raise -# Asegura tools en globales +# Secure tools in globals globals()['tools'] = tools if 'sp' not in globals(): @@ -52,12 +52,17 @@ def _exec_client_with_params(executable: str, params: typing.List[str], unlink_f tools.addFileToUnlink(unlink_file) def exec_udsrdp(udsrdp: str) -> None: - params = [os.path.expandvars(i) for i in [udsrdp] + sp['as_new_xfreerdp_params'] + [f'/v:{sp["address"]}']] # type: ignore - _exec_client_with_params(udsrdp, params) + if sp.get('as_file', ''): # type: ignore + dest_filename = _prepare_rdp_file(sp['as_file'], '.rdp') # type: ignore + params = [udsrdp, dest_filename, f'/p:{sp.get("password", "")}'] # type: ignore + _exec_client_with_params(udsrdp, params, unlink_file=dest_filename) + else: + params = [os.path.expandvars(i) for i in [udsrdp] + sp['as_new_xfreerdp_params'] + [f'/v:{sp["address"]}']] # type: ignore + _exec_client_with_params(udsrdp, params) def exec_new_xfreerdp(xfreerdp: str) -> None: if sp.get('as_file', ''): # type: ignore - dest_filename = _prepare_rdp_file(sp['as_file'], '.uds.rdp') # type: ignore + dest_filename = _prepare_rdp_file(sp['as_file'], '.rdp') # type: ignore params = [xfreerdp, dest_filename, f'/p:{sp.get("password", "")}'] # type: ignore _exec_client_with_params(xfreerdp, params, unlink_file=dest_filename) else: @@ -73,7 +78,7 @@ def exec_thincast(thincast: str) -> None: params = [os.path.expandvars(i) for i in [thincast] + sp['as_new_xfreerdp_params'] + [f'/v:{sp["address"]}']] # type: ignore _exec_client_with_params(thincast, params) -# Añade las funciones al scope global para clientes antiguos (3.6) +# Add functions to global scope for old clients (3.6) globals()['_prepare_rdp_file'] = _prepare_rdp_file globals()['_exec_client_with_params'] = _exec_client_with_params globals()['exec_udsrdp'] = exec_udsrdp @@ -88,34 +93,37 @@ def exec_thincast(thincast: str) -> None: '/opt/thincast/thincast', '/snap/bin/thincast-remote-desktop-client', '/snap/bin/thincast', - '/snap/bin/thincast-client' + '/snap/bin/thincast-client', + '/var/lib/flatpak/exports/bin/com.thincast.client', ] -# Search Thincast first +# Search UDSRDP first (preferred client) executable = None kind = '' -for thincast in thincast_list: - if os.path.isfile(thincast) and os.access(thincast, os.X_OK): - executable = thincast - kind = 'thincast' - logger.debug('Found Thincast executable: %s', thincast) - break - -# If you don't find Thincast, search UDSRDP and XFREERDP +udsrdp: typing.Optional[str] = tools.findApp('udsrdp') +if udsrdp: + executable = udsrdp + kind = 'udsrdp' + logger.debug('Found UDSRDP executable: %s', udsrdp) + +# If UDSRDP not found, search Thincast +if not executable: + logger.debug('UDSRDP not found. Searching for Thincast.') + for thincast in thincast_list: + if os.path.isfile(thincast) and os.access(thincast, os.X_OK): + executable = thincast + kind = 'thincast' + logger.debug('Found Thincast executable: %s', thincast) + break + +# If still not found, search XFREERDP if not executable: - logger.debug('Thincast not found. Searching for UDSRDP and XFREERDP.') - udsrdp: typing.Optional[str] = tools.findApp('udsrdp') + logger.debug('Thincast not found. Searching for XFREERDP.') xfreerdp: typing.Optional[str] = tools.findApp('xfreerdp3') or tools.findApp('xfreerdp') or tools.findApp('xfreerdp2') - logger.debug('UDSRDP found: %s', udsrdp) - logger.debug('XFREERDP found: %s', xfreerdp) - if udsrdp: - executable = udsrdp - kind = 'udsrdp' - logger.debug('Selected UDSRDP as executable.') - elif xfreerdp: + if xfreerdp: executable = xfreerdp kind = 'xfreerdp' - logger.debug('Selected XFREERDP as executable.') + logger.debug('Selected XFREERDP as executable: %s', xfreerdp) if not executable: logger.error('No suitable RDP client found. Thincast, UDSRDP, or XFREERDP are required.') @@ -132,16 +140,16 @@ def exec_thincast(thincast: str) -> None: logger.debug(f'RDP client found: {executable} of kind {kind}') # Execute the client found - if kind == 'thincast': - if isinstance(executable, str): - exec_thincast(executable) - else: - raise TypeError("Executable must be a string for exec_thincast") - elif kind == 'udsrdp': + if kind == 'udsrdp': if isinstance(executable, str): exec_udsrdp(executable) else: raise TypeError("Executable must be a string for exec_udsrdp") + elif kind == 'thincast': + if isinstance(executable, str): + exec_thincast(executable) + else: + raise TypeError("Executable must be a string for exec_thincast") elif kind == 'xfreerdp': if isinstance(executable, str): exec_new_xfreerdp(executable) diff --git a/server/src/uds/transports/RDP/scripts/linux/tunnel.py b/server/src/uds/transports/RDP/scripts/linux/tunnel.py index 40a8a3805..d423bfcd5 100644 --- a/server/src/uds/transports/RDP/scripts/linux/tunnel.py +++ b/server/src/uds/transports/RDP/scripts/linux/tunnel.py @@ -4,7 +4,7 @@ import subprocess import os.path -# Asegura que subprocess y shutil estén en el scope global para clientes antiguos (3.6) +# Ensures subprocess and shutil are in global scope for old clients (3.6) globals()['subprocess'] = subprocess globals()['shutil'] = shutil globals()['os'] = os @@ -59,9 +59,13 @@ def _exec_client_with_params(executable: str, params: typing.List[str], unlink_f tools.addFileToUnlink(unlink_file) def exec_udsrdp(udsrdp: str, port: int) -> None: - logger.debug('UDSRDP client will use command line parameters') - params: typing.List[str] = [os.path.expandvars(i) for i in [app] + sp['as_new_xfreerdp_params'] + [f'/v:127.0.0.1:{port}']] # type: ignore - _exec_client_with_params(udsrdp, params) + if sp.get('as_file', ''): # type: ignore + dest_filename = _prepare_rdp_file(sp['as_file'], port, '.rdp') # type: ignore + params = [udsrdp, dest_filename, f'/p:{sp.get("password", "")}'] # type: ignore + _exec_client_with_params(udsrdp, params, unlink_file=dest_filename) + else: + params: typing.List[str] = [os.path.expandvars(i) for i in [app] + sp['as_new_xfreerdp_params'] + [f'/v:127.0.0.1:{port}']] # type: ignore + _exec_client_with_params(udsrdp, params) def exec_new_xfreerdp(xfreerdp: str, port: int) -> None: if sp.get('as_file', ''): # type: ignore @@ -93,7 +97,8 @@ def exec_thincast(thincast: str, port: int) -> None: '/opt/thincast/thincast', '/snap/bin/thincast-remote-desktop-client', '/snap/bin/thincast', - '/snap/bin/thincast-client' + '/snap/bin/thincast-client', + '/var/lib/flatpak/exports/bin/com.thincast.client', ] thincast_executable = None for thincast in thincast_list: @@ -110,38 +115,37 @@ def exec_thincast(thincast: str, port: int) -> None: '

Could not connect to tunnel server.

Please, check your network settings.

' ) -# If thincast exists, use it. If not, continue with UDSRDP/XFREERDP as before -if thincast_executable: +# Priority: udsrdp > thincast > xfreerdp +udsrdp = tools.findApp('udsrdp') +xfreerdp: typing.Optional[str] = tools.findApp('xfreerdp3') or tools.findApp('xfreerdp') or tools.findApp('xfreerdp2') +fnc, app = None, None + +if udsrdp: + logger.debug('udsrdp found: %s', udsrdp) + fnc, app = exec_udsrdp, udsrdp +elif thincast_executable: logger.debug('Thincast client found, using it') fnc, app = exec_thincast, thincast_executable +elif xfreerdp: + logger.debug('xfreerdp found: %s', xfreerdp) + fnc, app = exec_new_xfreerdp, xfreerdp else: - logger.debug('Thincast not found, searching for xfreerdp and udsrdp') - xfreerdp: typing.Optional[str] = tools.findApp('xfreerdp3') or tools.findApp('xfreerdp') or tools.findApp('xfreerdp2') - udsrdp = tools.findApp('udsrdp') - fnc, app = None, None - if xfreerdp: - logger.debug('xfreerdp found: %s', xfreerdp) - fnc, app = exec_new_xfreerdp, xfreerdp - if udsrdp: - logger.debug('udsrdp found: %s', udsrdp) - fnc, app = exec_udsrdp, udsrdp - if app is None or fnc is None: - logger.error('No suitable RDP client found (Thincast, xfreerdp, udsrdp)') - raise Exception( - '''

You need to have Thincast Remote Desktop Client o xfreerdp (>= 2.0) installed on your system, y tenerlo en tu PATH para conectar con este servicio UDS.

+ logger.error('No suitable RDP client found (udsrdp, Thincast, xfreerdp)') + raise Exception( + '''

You need to have udsrdp, Thincast Remote Desktop Client or xfreerdp (>= 2.0) installed on your system, and available in your PATH to connect to this UDS service.

Please install the right package for your system.

''' - ) + ) -# Asegura que app y fnc sean globales para clientes antiguos (3.6) +# Ensure app and fnc are in global scope for old clients (3.6) globals()['app'] = app globals()['fnc'] = fnc -# Añade las funciones al scope global para clientes antiguos (3.6) +# Add functions to global scope for old clients (3.6) globals()['_prepare_rdp_file'] = _prepare_rdp_file globals()['_exec_client_with_params'] = _exec_client_with_params globals()['exec_udsrdp'] = exec_udsrdp diff --git a/server/src/uds/transports/RDP/scripts/macosx/direct.py b/server/src/uds/transports/RDP/scripts/macosx/direct.py index bbe2f6aaa..5c9ea8d04 100644 --- a/server/src/uds/transports/RDP/scripts/macosx/direct.py +++ b/server/src/uds/transports/RDP/scripts/macosx/direct.py @@ -50,7 +50,6 @@ def fix_resolution() -> typing.List[str]: ] xfreerdp_list = [ - 'udsrdp', 'xfreerdp', 'xfreerdp3', 'xfreerdp2', @@ -60,34 +59,44 @@ def fix_resolution() -> typing.List[str]: executable = None kind = '' -# Check first thincast (better option right now, prefer it) -logger.debug('Searching for Thincast in: %s', thincast_list) -for thincast in thincast_list: - if os.path.isdir(thincast): - logger.debug('Thincast found: %s', thincast) - executable = thincast - kind = 'thincast' - break +# Search UDSRDP first (preferred client) +logger.debug('Searching for UDSRDP') +udsrdp = tools.findApp('udsrdp') # type: ignore +if udsrdp and os.path.isfile(udsrdp): # type: ignore + logger.debug('UDSRDP found: %s', udsrdp) + executable = udsrdp # type: ignore + kind = 'udsrdp' +# If UDSRDP not found, search Thincast +if not executable: + logger.debug('Searching for Thincast in: %s', thincast_list) + for thincast in thincast_list: + if os.path.isdir(thincast): + logger.debug('Thincast found: %s', thincast) + executable = thincast + kind = 'thincast' + break + +# If still not found, search xfreerdp variants if not executable: logger.debug('Searching for xfreerdp in: %s', xfreerdp_list) - found_xfreerdp = False for xfreerdp_executable in xfreerdp_list: - xfreerdp = tools.findApp(xfreerdp_executable) # type: ignore - logger.debug('tools.findApp(%s) result: %s', xfreerdp_executable, xfreerdp) # type: ignore - if xfreerdp and os.path.isfile(xfreerdp): # type: ignore - logger.debug('xfreerdp found: %s', xfreerdp) # type: ignore - executable = xfreerdp # type: ignore - # Ensure that the kind is 'xfreerdp' and not 'xfreerdp3' or 'xfreerdp2' + xfreerdp = tools.findApp(xfreerdp_executable) # type: ignore + logger.debug('tools.findApp(%s) result: %s', xfreerdp_executable, xfreerdp) # type: ignore + if xfreerdp and os.path.isfile(xfreerdp): # type: ignore + logger.debug('xfreerdp found: %s', xfreerdp) # type: ignore + executable = xfreerdp # type: ignore kind = xfreerdp_executable.rstrip('3').rstrip('2') break - if not found_xfreerdp: - logger.debug('Searching for MSRDC in: %s', msrdc_list) - for msrdc in msrdc_list: - if os.path.isdir(msrdc) and sp['as_file']: # type: ignore - executable = msrdc - kind = 'msrdc' - break + +# If still not found, search MSRDC +if not executable: + logger.debug('Searching for MSRDC in: %s', msrdc_list) + for msrdc in msrdc_list: + if os.path.isdir(msrdc) and sp['as_file']: # type: ignore + executable = msrdc + kind = 'msrdc' + break if not executable: logger.debug('No compatible executable found (Thincast, xfreerdp, MSRDC)') @@ -97,7 +106,7 @@ def fix_resolution() -> typing.List[str]: msrd_li = '
  • {} from Apple Store

  • '.format(msrd) raise Exception( - f'''

    xfreerdp{msrd} or thincast client not found

    + f'''

    xfreerdp, {msrd} or thincast client not found

    In order to connect to UDS RDP Sessions, you need to have a