From a38716e1b0c1e66e9febcb3e26bde23013e7771f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Mon, 17 Jul 2023 16:23:25 +0200 Subject: [PATCH] Adding support for teams optimizations on virtual machines. To be tested!!! --- server/src/uds/core/services/exceptions.py | 3 + server/src/uds/transports/RDP/rdp.py | 9 ++- server/src/uds/transports/RDP/rdp_base.py | 42 +++++------- server/src/uds/transports/RDP/rdp_file.py | 5 +- server/src/uds/transports/RDP/rdptunnel.py | 2 + .../transports/RDP/scripts/windows/direct.py | 39 +++++++---- .../RDP/scripts/windows/direct.py.signature | 2 +- .../transports/RDP/scripts/windows/tunnel.py | 65 +++++++++++-------- .../RDP/scripts/windows/tunnel.py.signature | 2 +- 9 files changed, 97 insertions(+), 72 deletions(-) diff --git a/server/src/uds/core/services/exceptions.py b/server/src/uds/core/services/exceptions.py index bd22c8d70..f38b548d5 100644 --- a/server/src/uds/core/services/exceptions.py +++ b/server/src/uds/core/services/exceptions.py @@ -42,6 +42,9 @@ class ServiceException(UDSException): """ Base class for all service exceptions """ + def __init__(self, *args, **kwargs): + # Eats "kwargs" to avoid "unexpected keyword argument" error + super().__init__(*args) class UnsupportedException(ServiceException): diff --git a/server/src/uds/transports/RDP/rdp.py b/server/src/uds/transports/RDP/rdp.py index 25c23b973..3d061ae7f 100644 --- a/server/src/uds/transports/RDP/rdp.py +++ b/server/src/uds/transports/RDP/rdp.py @@ -94,6 +94,7 @@ class RDPTransport(BaseRDPTransport): customParameters = BaseRDPTransport.customParameters customParametersMAC = BaseRDPTransport.customParametersMAC customParametersWindows = BaseRDPTransport.customParametersWindows + optimizeTeams = BaseRDPTransport.optimizeTeams def getUDSTransportScript( # pylint: disable=too-many-locals self, @@ -145,6 +146,7 @@ class RDPTransport(BaseRDPTransport): r.printerString = self.printerString.value r.enforcedShares = self.enforceDrives.value r.redirectUSB = self.usbRedirection.value + r.optimizeTeams = self.optimizeTeams.isTrue() sp: typing.MutableMapping[str, typing.Any] = { 'password': password, @@ -154,16 +156,17 @@ class RDPTransport(BaseRDPTransport): 'address': r.address, } - if os == os_detector.KnownOS.WINDOWS: + if os.os == os_detector.KnownOS.WINDOWS: r.customParameters = self.customParametersWindows.value if password: r.password = '{password}' # nosec: password is not hardcoded sp.update( { 'as_file': r.as_file, + 'optimize_teams': self.optimizeTeams.isTrue(), } ) - elif os == os_detector.KnownOS.LINUX: + elif os.os == os_detector.KnownOS.LINUX: r.customParameters = self.customParameters.value sp.update( { @@ -171,7 +174,7 @@ class RDPTransport(BaseRDPTransport): 'address': r.address, } ) - elif os == os_detector.KnownOS.MAC_OS: + elif os.os == os_detector.KnownOS.MAC_OS: r.customParameters = self.customParametersMAC.value sp.update( { diff --git a/server/src/uds/transports/RDP/rdp_base.py b/server/src/uds/transports/RDP/rdp_base.py index 72f7fb50c..784e6d421 100644 --- a/server/src/uds/transports/RDP/rdp_base.py +++ b/server/src/uds/transports/RDP/rdp_base.py @@ -87,9 +87,7 @@ class BaseRDPTransport(transports.Transport): fixedDomain = gui.TextField( label=_('Domain'), order=15, - tooltip=_( - 'If not empty, this domain will be always used as credential (used as DOMAIN\\user)' - ), + tooltip=_('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'), tab=gui.Tab.CREDENTIALS, ) @@ -265,34 +263,26 @@ class BaseRDPTransport(transports.Transport): multimedia = gui.CheckBoxField( label=_('Multimedia sync'), order=40, - tooltip=_( - 'If checked. Linux client will use multimedia parameter for xfreerdp' - ), + tooltip=_('If checked. Linux client will use multimedia parameter for xfreerdp'), tab='Linux Client', ) alsa = gui.CheckBoxField( label=_('Use Alsa'), order=41, - tooltip=_( - 'If checked, Linux client will try to use ALSA, otherwise Pulse will be used' - ), + tooltip=_('If checked, Linux client will try to use ALSA, otherwise Pulse will be used'), tab='Linux Client', ) printerString = gui.TextField( label=_('Printer string'), order=43, - tooltip=_( - 'If printer is checked, the printer string used with xfreerdp client' - ), + tooltip=_('If printer is checked, the printer string used with xfreerdp client'), tab='Linux Client', length=256, ) smartcardString = gui.TextField( label=_('Smartcard string'), order=44, - tooltip=_( - 'If smartcard is checked, the smartcard string used with xfreerdp client' - ), + tooltip=_('If smartcard is checked, the smartcard string used with xfreerdp client'), tab='Linux Client', length=256, ) @@ -309,9 +299,7 @@ class BaseRDPTransport(transports.Transport): allowMacMSRDC = gui.CheckBoxField( label=_('Allow Microsoft Rdp Client'), order=50, - tooltip=_( - 'If checked, allows use of Microsoft Remote Desktop Client. PASSWORD WILL BE PROMPTED!' - ), + tooltip=_('If checked, allows use of Microsoft Remote Desktop Client. PASSWORD WILL BE PROMPTED!'), tab='Mac OS X', defvalue=gui.FALSE, ) @@ -329,14 +317,18 @@ class BaseRDPTransport(transports.Transport): customParametersWindows = gui.TextField( label=_('Custom parameters'), order=45, - tooltip=_( - 'If not empty, extra parameters to include for Windows Client' - ), + tooltip=_('If not empty, extra parameters to include for Windows Client'), length=4096, multiline=10, tab='Windows Client', ) + optimizeTeams = gui.CheckBoxField( + label=_('Optimize Teams'), + order=46, + tooltip=_('If checked, Teams will be optimized (only works on Windows clients)'), + tab='Windows Client', + ) def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool: """ @@ -353,9 +345,7 @@ class BaseRDPTransport(transports.Transport): self.cache.put(ip, 'N', READY_CACHE_TIMEOUT) return ready == 'Y' - def processedUser( - self, userService: 'models.UserService', user: 'models.User' - ) -> str: + def processedUser(self, userService: 'models.UserService', user: 'models.User') -> str: v = self.processUserPassword(userService, user, '', altUsername=None) return v['username'] @@ -412,6 +402,9 @@ class BaseRDPTransport(transports.Transport): if azureAd: username = 'AzureAD\\' + username + if self.optimizeTeams.isTrue(): + password = '' # nosec + return { 'protocol': self.protocol, 'username': username, @@ -425,7 +418,6 @@ class BaseRDPTransport(transports.Transport): user: 'models.User', password: str, ) -> typing.Mapping[str, str]: - username = None if isinstance(userService, UserService): cdata = userService.getInstance().getConnectionData() diff --git a/server/src/uds/transports/RDP/rdp_file.py b/server/src/uds/transports/RDP/rdp_file.py index 63effebf9..15ea67d64 100644 --- a/server/src/uds/transports/RDP/rdp_file.py +++ b/server/src/uds/transports/RDP/rdp_file.py @@ -71,6 +71,7 @@ class RDPFile: enableClipboard = False customParameters: typing.Optional[str] = None enforcedShares: typing.Optional[str] = None + optimizeTeams = False def __init__( self, @@ -193,7 +194,7 @@ class RDPFile: params += shlex.split(self.customParameters.strip()) # On MacOSX, /rfx /gfx:rfx are almost inprescindible, as it seems the only way to get a decent performance - if self.target == OsDetector.KnownOS.Macintosh: + if self.target == OsDetector.KnownOS.MAC_OS: for i in ('/rfx', '/gfx:rfx'): if i not in params: params.append(i) @@ -233,7 +234,7 @@ class RDPFile: if self.username: res += 'username:s:' + self.username + '\n' res += 'domain:s:' + self.domain + '\n' - if self.target == OsDetector.KnownOS.WINDOWS: + if self.target == OsDetector.KnownOS.WINDOWS and not self.optimizeTeams: res += 'password 51:b:' + password + '\n' res += 'alternate shell:s:' + '\n' diff --git a/server/src/uds/transports/RDP/rdptunnel.py b/server/src/uds/transports/RDP/rdptunnel.py index 031073ae2..55b9a5f82 100644 --- a/server/src/uds/transports/RDP/rdptunnel.py +++ b/server/src/uds/transports/RDP/rdptunnel.py @@ -131,6 +131,7 @@ class TRDPTransport(BaseRDPTransport): customParameters = BaseRDPTransport.customParameters customParametersMAC = BaseRDPTransport.customParametersMAC customParametersWindows = BaseRDPTransport.customParametersWindows + optimizeTeams = BaseRDPTransport.optimizeTeams def initialize(self, values: 'Module.ValuesType'): if values: @@ -193,6 +194,7 @@ class TRDPTransport(BaseRDPTransport): r.printerString = self.printerString.value r.enforcedShares = self.enforceDrives.value r.redirectUSB = self.usbRedirection.value + r.optimizeTeams = self.optimizeTeams.isTrue() sp: typing.MutableMapping[str, typing.Any] = { 'tunHost': tunHost, diff --git a/server/src/uds/transports/RDP/scripts/windows/direct.py b/server/src/uds/transports/RDP/scripts/windows/direct.py index fb5722688..d056445d4 100644 --- a/server/src/uds/transports/RDP/scripts/windows/direct.py +++ b/server/src/uds/transports/RDP/scripts/windows/direct.py @@ -1,4 +1,5 @@ -import subprocess +import os +import subprocess # nosec: B404 import win32crypt # type: ignore import codecs @@ -23,27 +24,39 @@ except Exception: ).decode() try: - key = wreg.OpenKey( - wreg.HKEY_CURRENT_USER, + key = wreg.OpenKey( # type: ignore + wreg.HKEY_CURRENT_USER, # type: ignore 'Software\\Microsoft\\Terminal Server Client\\LocalDevices', 0, - wreg.KEY_SET_VALUE, + wreg.KEY_SET_VALUE, # type: ignore ) wreg.SetValueEx(key, sp['ip'], 0, wreg.REG_DWORD, 255) # type: ignore - wreg.CloseKey(key) -except Exception as e: + wreg.CloseKey(key) # type: ignore +except Exception as e: # nosec: Not really interested in the exception # logger.warn('Exception fixing redirection dialog: %s', e) pass # Key does not exists, ok... # The password must be encoded, to be included in a .rdp file, as 'UTF-16LE' before protecting (CtrpyProtectData) it in order to work with mstsc theFile = sp['as_file'].format(password=password) # type: ignore filename = tools.saveTempFile(theFile) -executable = tools.findApp('mstsc.exe') -if executable is None: - raise Exception( - 'Unable to find mstsc.exe. Check that path points to your SYSTEM32 folder' - ) +if sp['optimize_teams'] == True: # type: ignore + try: + h = wreg.OpenKey(wreg.HKEY_CLASSES_ROOT, '.rdp\OpenWithProgids', 0, wreg.KEY_READ) # type: ignore + h.Close() + except Exception: + raise Exception('Required Microsoft RDP Client is not found. Please, install it from Microsoft store.') + # Add .rdp to filename for open with + os.rename(filename, filename + '.rdp') + filename = filename + '.rdp' + os.startfile(filename) # type: ignore # nosec +else: + executable = tools.findApp('mstsc.exe') + if executable is None: + raise Exception( + 'Unable to find mstsc.exe. Check that path points to your SYSTEM32 folder' + ) -subprocess.Popen([executable, filename]) -tools.addFileToUnlink(filename) + subprocess.Popen([executable, filename]) # nosec + +# tools.addFileToUnlink(filename) diff --git a/server/src/uds/transports/RDP/scripts/windows/direct.py.signature b/server/src/uds/transports/RDP/scripts/windows/direct.py.signature index db8bc119a..b0eb48fdc 100644 --- a/server/src/uds/transports/RDP/scripts/windows/direct.py.signature +++ b/server/src/uds/transports/RDP/scripts/windows/direct.py.signature @@ -1 +1 @@ -qKyGy2EIPrAzb4VsZ4fTAjMDyGb82LJXm+u04ro6IzddOyhEuPz1NleV3x8acIgPlx1K0A128voYabD78nQBfJ5Xm/TzPcwuN2qtjGtTTkc04urpsMwxVXjl4gsFAhY3G9KDqkg5Ik3wkskBDq1ahZI95uVufUejefl+r1J94g5VjhRUMVM3hctvPQFM4/6LY8VQrnGx19LGbHf4T1yUXrZMUeodReqkDBfWnJOqQOQ+9+USRxWlglgLNb/9zYbIn/Ca6qVyCsuYS1L9Gms/ioDVUd8R0jWxCCzmvHsVw9q0Lq3BZAqmFA8xonjpP+dVR53O2R/76/XY82f3vWQaL1oC5DJf5R29x6FFi9CbhRnmxfYZRjedNLYhUBeXL59SKYh83ZEqkzMmUha9j7lThVtidhk9PUPDaV8KlVJnoQwOTXrG/hfok181nvoPTt4QfADoxne4JeTWqrfDgNwLWhcpryY/yXfnUYEZChZOUqm5MF7raHsF5P3lZRazq1Zaces630pPgOfw8Zb/J2deUd0vf5BZnBgug5h6clibPude70iepv7Gt/9ks82hl7gFAYguY5AzeQFsQ9RVngmFoyQjYaAv+BSb9Z4Qbf+DB/gTCGZx7rn7YsZgD5PFaVAlRq0J6ZR3fRn88YSBliXh9HcD5CD/yRW6koXm3tTvLKo= \ No newline at end of file +hPF/H21BzZ0VU9HjimiOa1go9TJVsqmKsgGqB4V4X3cjB6G3NkSa1O45ub3gf+gOriElibPRbXzet4zma4qnBACLzAZQjmmxDRK9gC7eN6p0z0TKg18U6/hKf5gs6ICjQZyONg2gvBBrYUz7JhYoefq9SZRAna7373N0minKoJoBPLB+ylkemBNhKcsN5bRQlEGU03mLREGk/h642qN6ebQhIzY+JMo3c9CwtRS6SCiLhiaYkPFxdEX9e7AGSpTgGSFh4+Mc3QEge4sOquWszX4ocJJc23SHl5GdZSfhn3Bst5lLKILVUT5w7zaZOqa/xx/HjpCmxROJKm/bkR+HK8VMRh4N5c5yvavXid0IwgN8Uc+SOIUh4+wxbOyJI3ySS/77gUI/1TDw+r/gRkSUPwcRSFKuyQMN2yR6yr2BgOXlGmlNwZ/MzsDIIwgGeMlkKZZzLI64aj9VG2QsWHrlw2HWuFfBGGIX4T49p0tvdRm5o1jg/cdsLsHC8of5Vn4wgwyZwlScndDLcBJq76v4wnWUMg4IQpeOoI/PEbgeUfZDuntSt/ZT5icB78nN07hUo56d5Z5q1ktuWEHJSWCVSg59KGOcvaaPub50+anm6Uzac5rynZ0/gqA8Y0IV3dgRalBMZCiCy89lHW1YyO52A4DBka6hG4gKt5VUs+eT+vI= \ No newline at end of file diff --git a/server/src/uds/transports/RDP/scripts/windows/tunnel.py b/server/src/uds/transports/RDP/scripts/windows/tunnel.py index 7b1902d81..9ac523235 100644 --- a/server/src/uds/transports/RDP/scripts/windows/tunnel.py +++ b/server/src/uds/transports/RDP/scripts/windows/tunnel.py @@ -1,4 +1,5 @@ -import subprocess +import os +import subprocess # nosec import win32crypt # type: ignore import codecs @@ -16,21 +17,28 @@ fs = forward(remote=(sp['tunHost'], int(sp['tunPort'])), ticket=sp['ticket'], ti # Check that tunnel works.. if fs.check() is False: - raise Exception( - '

Could not connect to tunnel server.

Please, check your network settings.

' - ) + raise Exception('

Could not connect to tunnel server.

Please, check your network settings.

') thePass = sp['password'].encode('UTF-16LE') # type: ignore try: - password = codecs.encode( - win32crypt.CryptProtectData(thePass, None, None, None, None, 0x01), 'hex' - ).decode() + password = codecs.encode(win32crypt.CryptProtectData(thePass, None, None, None, None, 0x01), 'hex').decode() except Exception: # Cannot encrypt for user, trying for machine - password = codecs.encode( - win32crypt.CryptProtectData(thePass, None, None, None, None, 0x05), 'hex' - ).decode() + password = codecs.encode(win32crypt.CryptProtectData(thePass, None, None, None, None, 0x05), 'hex').decode() + +try: + key = wreg.OpenKey( # type: ignore + wreg.HKEY_CURRENT_USER, # type: ignore + 'Software\\Microsoft\\Terminal Server Client\\LocalDevices', + 0, + wreg.KEY_SET_VALUE, # type: ignore + ) + wreg.SetValueEx(key, '127.0.0.1', 0, wreg.REG_DWORD, 255) # type: ignore + wreg.CloseKey(key) # type: ignore +except Exception as e: # nosec: Not really interested in the exception + # logger.warn('Exception fixing redirection dialog: %s', e) + pass # Key does not exists, but it's ok # The password must be encoded, to be included in a .rdp file, as 'UTF-16LE' before protecting (CtrpyProtectData) it in order to work with mstsc theFile = sp['as_file'].format( # type: ignore @@ -38,24 +46,27 @@ theFile = sp['as_file'].format( # type: ignore ) filename = tools.saveTempFile(theFile) + +if sp['optimize_teams']: # type: ignore + try: + h = wreg.OpenKey(wreg.HKEY_CLASSES_ROOT, '.rdp\OpenWithProgids', 0, wreg.KEY_READ) # type: ignore + h.Close() + except Exception: + raise Exception('

Required Microsoft RDP Client is not found.

Please, install it from Microsoft store.

') + # Add .rdp to filename for open with + os.rename(filename, filename + '.rdp') + filename = filename + '.rdp' + os.startfile(filename) # type: ignore # nosec +else: + executable = tools.findApp('mstsc.exe') + if executable is None: + raise Exception('Unable to find mstsc.exe. Check that path points to your SYSTEM32 folder') + + subprocess.Popen([executable, filename]) # nosec + executable = tools.findApp('mstsc.exe') if executable is None: - raise Exception( - 'Unable to find mstsc.exe. Check that path points to your SYSTEM32 folder' - ) + raise Exception('Unable to find mstsc.exe. Check that path points to your SYSTEM32 folder') -try: - key = wreg.OpenKey( - wreg.HKEY_CURRENT_USER, - 'Software\\Microsoft\\Terminal Server Client\\LocalDevices', - 0, - wreg.KEY_SET_VALUE, - ) - wreg.SetValueEx(key, '127.0.0.1', 0, wreg.REG_DWORD, 255) # type: ignore - wreg.CloseKey(key) -except Exception as e: - # logger.warn('Exception fixing redirection dialog: %s', e) - pass # Key does not exists, but it's ok - -subprocess.Popen([executable, filename]) +subprocess.Popen([executable, filename]) # nosec tools.addFileToUnlink(filename) diff --git a/server/src/uds/transports/RDP/scripts/windows/tunnel.py.signature b/server/src/uds/transports/RDP/scripts/windows/tunnel.py.signature index c8c8d3d6e..792a3697f 100644 --- a/server/src/uds/transports/RDP/scripts/windows/tunnel.py.signature +++ b/server/src/uds/transports/RDP/scripts/windows/tunnel.py.signature @@ -1 +1 @@ -RkbX3fRuVnznJL4dv27wDJw+x4T6kegHOPRrMF04TunfV+jQr6RBK1vqt6YqLL0FccxT7JLv/kHJJOkT5b//IYS9TIt/M5DdJtg/WaNpsPsaNRBIU8QiaF9zTcNnvz8Rc4XY7infR9puSwbMvEznoUovAd2YUEvj7MHWKozieQQpG/UBECUKFad+S5TgmXmC4RGBSf9Da3x+jvQ45VkhYIWvrfw5xd8dNGwCCJjohc49YvNxGguNWwa/1zB2SY6XjbDd+inHZ+13IR917uU7qYu44UlAUUW9Uazav2XNLOmLoBaDiWUy4hDM4CNvqlQeau8Ppq0SnhiGrvq9/MEbdOeHOMHJqXalA0pLCqTPj/4/4hqQw4POMNEWqAjMA59iGSe/8F9RSPPXHp1mYoQasDalSGoZEPVRYOZUlIECpKMEVCredFK7Vu9sy+yL+2amFr47L9aRNVpaVMwAyP3TukH1/SGL7lPxQXIzu2hoFd6kGLlyBF0CBbKTX9Xlk76fEObN/3dHfDEuK1O+OiwVyX3YMurH4fxvABqMJqRtT1ASSfjLXpKohIGEpUSWNkh2mu5a3OL6lLQxxwDY6WieKQtvJgPiPAOJRVk06CLOSRy7ghaCYH9yn6tShCPTAnRJmGLuttU4mt5fKOC6cHH8GJahRi5FtSuzLigszL5bCaY= \ No newline at end of file +KzlVV3agaa2aeHqWhx6myDpHRbc9YdkwfbkD00NkU+U4w09CTnvcVE4fYJWc4Xnu72EMWDqGS9kYac0MzlwFsUflEQbNBm4wq4LpqTqvDi9YilTCpQHo3auENL9pEnGLZmhM12EufDrrQiDkCSJWB45cwdt8sK0xyLflOBvwpkADWf+vzX7bM64Lz9STVyr3YYjYTCa6IOGZqybfsKUimNnIApF+LZeKhA+MuPdJ2+ml5XzoKjjLPl6FP/UNwWbKXQLbntXyZ6iJZAi9sBdQYXwjhiDWPZI4rmYnAAlpsa/K10JJV9Y2oYjM0qZp1bWp7Sq1TgwbbezM1eScD7aV8+qoFu/17HOZTYpnXA38b9s+JCsLrZEomf/AJcYrLNZ0C6NR9hnMSJaDxSSU8CPdYMbNBi2sKjhfL//DS9qFVEYjZqnf5aRQ22h4WC6GyckjFWhVskxS3jEOYvuznjetD7NS+a0kIAf+gF4+pZGey6I5OnE2//OL0KqrOco2uljibLTlpAUdjth8WCQQpb/APPzcQBNiWhASymDJDV/rTQyq6/L6GCTchw2eIXC2faNa6LCcllZ/ytnrorck89IzwverMPx/6TTKljb0yVX7aGxjEVatXLU+4ZWcsMGcAtpdGpO/GA0iNWwC2aZKKYdtDuaFpF5bJwAKGzcbTCliUWA= \ No newline at end of file