2020-03-10 16:23:38 +01:00
#!/usr/bin/env python3
2020-03-10 14:04:27 +01:00
# -*- coding: utf-8 -*-
#
2021-06-07 21:10:57 +02:00
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
2020-03-10 14:04:27 +01:00
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
2021-06-07 21:10:57 +02:00
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
2020-03-10 14:04:27 +01:00
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2021-05-04 13:05:53 +02:00
'''
2020-03-10 14:04:27 +01:00
@author : Adolfo Gómez , dkmaster at dkmon dot com
2021-05-04 13:05:53 +02:00
'''
2020-03-10 14:04:27 +01:00
import sys
import webbrowser
import json
2021-05-04 13:05:53 +02:00
import base64 , bz2
2020-03-10 14:04:27 +01:00
2021-05-04 13:05:53 +02:00
from PyQt5 import QtCore , QtGui , QtWidgets # @UnresolvedImport
2020-03-10 14:04:27 +01:00
import six
from uds . rest import RestRequest
from uds . forward import forward # pylint: disable=unused-import
2021-01-14 08:23:34 +01:00
from uds . tunnel import forward as f2 # pylint: disable=unused-import
2020-03-10 14:04:27 +01:00
from uds . log import logger
from uds import tools
from uds import VERSION
from UDSWindow import Ui_MainWindow
# Server before this version uses "unsigned" scripts
2021-05-04 13:05:53 +02:00
OLD_METHOD_VERSION = ' 2.4.0 '
2020-03-10 14:04:27 +01:00
2021-06-07 21:10:57 +02:00
2020-03-10 14:04:27 +01:00
class RetryException ( Exception ) :
pass
2021-06-07 21:10:57 +02:00
2020-03-10 16:23:38 +01:00
class UDSClient ( QtWidgets . QMainWindow ) :
2020-03-10 14:04:27 +01:00
ticket = None
scrambler = None
withError = False
animTimer = None
anim = 0
animInverted = False
2021-05-04 13:05:53 +02:00
serverVersion = ' X.Y.Z ' # Will be overwriten on getVersion
2020-03-10 14:04:27 +01:00
req = None
def __init__ ( self ) :
2020-03-10 16:23:38 +01:00
QtWidgets . QMainWindow . __init__ ( self )
2021-06-07 21:10:57 +02:00
self . setWindowFlags ( QtCore . Qt . FramelessWindowHint | QtCore . Qt . WindowStaysOnTopHint ) # type: ignore
2020-03-10 14:04:27 +01:00
self . ui = Ui_MainWindow ( )
self . ui . setupUi ( self )
self . ui . progressBar . setValue ( 0 )
self . ui . cancelButton . clicked . connect ( self . cancelPushed )
2021-05-04 13:05:53 +02:00
self . ui . info . setText ( ' Initializing... ' )
2020-03-10 14:04:27 +01:00
2020-03-10 16:23:38 +01:00
screen = QtWidgets . QDesktopWidget ( ) . screenGeometry ( )
2020-03-10 14:04:27 +01:00
mysize = self . geometry ( )
2020-06-08 14:35:07 +02:00
hpos = ( screen . width ( ) - mysize . width ( ) ) / / 2
vpos = ( screen . height ( ) - mysize . height ( ) - mysize . height ( ) ) / / 2
2020-03-10 14:04:27 +01:00
self . move ( hpos , vpos )
self . animTimer = QtCore . QTimer ( )
2020-03-10 16:23:38 +01:00
self . animTimer . timeout . connect ( self . updateAnim )
# QtCore.QObject.connect(self.animTimer, QtCore.SIGNAL('timeout()'), self.updateAnim)
2020-03-10 14:04:27 +01:00
self . activateWindow ( )
self . startAnim ( )
def closeWindow ( self ) :
self . close ( )
def processError ( self , data ) :
2021-05-04 13:05:53 +02:00
if ' error ' in data :
2020-03-10 16:23:38 +01:00
# QtWidgets.QMessageBox.critical(self, 'Request error {}'.format(data.get('retryable', '0')), data['error'], QtWidgets.QMessageBox.Ok)
2021-05-04 13:05:53 +02:00
if data . get ( ' retryable ' , ' 0 ' ) == ' 1 ' :
raise RetryException ( data [ ' error ' ] )
2020-03-10 14:04:27 +01:00
2021-05-04 13:05:53 +02:00
raise Exception ( data [ ' error ' ] )
2020-03-10 16:23:38 +01:00
# QtWidgets.QMessageBox.critical(self, 'Request error', rest.data['error'], QtWidgets.QMessageBox.Ok)
2020-03-10 14:04:27 +01:00
# self.closeWindow()
# return
def showError ( self , error ) :
2021-05-04 13:05:53 +02:00
logger . error ( ' got error: %s ' , error )
2020-03-10 14:04:27 +01:00
self . stopAnim ( )
2021-06-07 21:10:57 +02:00
self . ui . info . setText (
' UDS Plugin Error '
) # In fact, main window is hidden, so this is not visible... :)
2020-03-10 14:04:27 +01:00
self . closeWindow ( )
2021-06-07 21:10:57 +02:00
QtWidgets . QMessageBox . critical ( None , ' UDS Plugin Error ' , ' {} ' . format ( error ) , QtWidgets . QMessageBox . Ok ) # type: ignore
2020-03-10 14:04:27 +01:00
self . withError = True
def cancelPushed ( self ) :
self . close ( )
def updateAnim ( self ) :
self . anim + = 2
if self . anim > 99 :
self . animInverted = not self . animInverted
self . ui . progressBar . setInvertedAppearance ( self . animInverted )
self . anim = 0
self . ui . progressBar . setValue ( self . anim )
def startAnim ( self ) :
2021-06-07 21:10:57 +02:00
self . ui . progressBar . invertedAppearance = False # type: ignore
2020-03-10 14:04:27 +01:00
self . anim = 0
self . animInverted = False
self . ui . progressBar . setInvertedAppearance ( self . animInverted )
self . animTimer . start ( 40 )
def stopAnim ( self ) :
2021-06-07 21:10:57 +02:00
self . ui . progressBar . invertedAppearance = False # type: ignore
2020-03-10 14:04:27 +01:00
self . animTimer . stop ( )
def getVersion ( self ) :
2021-05-04 13:05:53 +02:00
self . req = RestRequest ( ' ' , self , self . version )
2020-03-10 14:04:27 +01:00
self . req . get ( )
def version ( self , data ) :
try :
self . processError ( data )
2021-05-04 13:05:53 +02:00
self . ui . info . setText ( ' Processing... ' )
if data [ ' result ' ] [ ' requiredVersion ' ] > VERSION :
2021-06-07 21:10:57 +02:00
QtWidgets . QMessageBox . critical (
self ,
' Upgrade required ' ,
' A newer connector version is required. \n A browser will be opened to download it. ' ,
QtWidgets . QMessageBox . Ok ,
)
2021-05-04 13:05:53 +02:00
webbrowser . open ( data [ ' result ' ] [ ' downloadUrl ' ] )
2020-03-10 14:04:27 +01:00
self . closeWindow ( )
return
2021-05-04 13:05:53 +02:00
self . serverVersion = data [ ' result ' ] [ ' requiredVersion ' ]
2020-03-10 14:04:27 +01:00
self . getTransportData ( )
except RetryException as e :
2020-07-20 13:15:00 +02:00
self . ui . info . setText ( str ( e ) )
2020-03-10 14:04:27 +01:00
QtCore . QTimer . singleShot ( 1000 , self . getVersion )
except Exception as e :
self . showError ( e )
def getTransportData ( self ) :
try :
2021-06-07 21:10:57 +02:00
self . req = RestRequest (
' / {} / {} ' . format ( self . ticket , self . scrambler ) ,
self ,
self . transportDataReceived ,
params = { ' hostname ' : tools . getHostName ( ) , ' version ' : VERSION } ,
)
2020-03-10 14:04:27 +01:00
self . req . get ( )
except Exception as e :
2021-05-04 13:05:53 +02:00
logger . exception ( ' Got exception on getTransportData ' )
2020-03-10 14:04:27 +01:00
raise e
def transportDataReceived ( self , data ) :
2021-05-04 13:05:53 +02:00
logger . debug ( ' Transport data received ' )
2020-03-10 14:04:27 +01:00
try :
self . processError ( data )
params = None
if self . serverVersion < = OLD_METHOD_VERSION :
2021-05-04 13:05:53 +02:00
script = bz2 . decompress ( base64 . b64decode ( data [ ' result ' ] ) )
2021-06-07 21:10:57 +02:00
# This fixes uds 2.2 "write" string on binary streams on some transport
2020-03-10 16:23:38 +01:00
script = script . replace ( b ' stdin.write( " ' , b ' stdin.write(b " ' )
2021-05-04 13:05:53 +02:00
script = script . replace ( b ' version) ' , b ' version.decode( " utf-8 " )) ' )
2020-03-10 14:04:27 +01:00
else :
2021-05-04 13:05:53 +02:00
res = data [ ' result ' ]
2020-03-10 14:04:27 +01:00
# We have three elements on result:
# * Script
# * Signature
# * Script data
# We test that the Script has correct signature, and them execute it with the parameters
2021-06-07 21:10:57 +02:00
# script, signature, params = res['script'].decode('base64').decode('bz2'), res['signature'], json.loads(res['params'].decode('base64').decode('bz2'))
script , signature , params = (
bz2 . decompress ( base64 . b64decode ( res [ ' script ' ] ) ) ,
res [ ' signature ' ] ,
json . loads ( bz2 . decompress ( base64 . b64decode ( res [ ' params ' ] ) ) ) ,
)
2020-03-10 14:04:27 +01:00
if tools . verifySignature ( script , signature ) is False :
2021-05-04 13:05:53 +02:00
logger . error ( ' Signature is invalid ' )
2020-03-10 14:04:27 +01:00
2021-06-07 21:10:57 +02:00
raise Exception (
' Invalid UDS code signature. Please, report to administrator '
)
2020-03-10 14:04:27 +01:00
self . stopAnim ( )
2021-05-04 13:05:53 +02:00
if ' darwin ' in sys . platform :
2020-03-10 14:04:27 +01:00
self . showMinimized ( )
QtCore . QTimer . singleShot ( 3000 , self . endScript )
self . hide ( )
2021-06-07 21:10:57 +02:00
six . exec_ ( script . decode ( " utf-8 " ) , globals ( ) , { ' parent ' : self , ' sp ' : params } )
2020-03-10 14:04:27 +01:00
except RetryException as e :
2021-05-04 13:05:53 +02:00
self . ui . info . setText ( six . text_type ( e ) + ' , retrying access... ' )
2020-03-10 14:04:27 +01:00
# Retry operation in ten seconds
QtCore . QTimer . singleShot ( 10000 , self . getTransportData )
except Exception as e :
2021-06-07 21:10:57 +02:00
# logger.exception('Got exception executing script:')
2020-03-10 14:04:27 +01:00
self . showError ( e )
def endScript ( self ) :
# After running script, wait for stuff
try :
tools . waitForTasks ( )
except Exception :
pass
try :
tools . unlinkFiles ( )
except Exception :
pass
try :
tools . execBeforeExit ( )
except Exception :
pass
self . closeWindow ( )
def start ( self ) :
2021-06-07 21:10:57 +02:00
"""
2020-03-10 14:04:27 +01:00
Starts proccess by requesting version info
2021-06-07 21:10:57 +02:00
"""
2021-05-04 13:05:53 +02:00
self . ui . info . setText ( ' Initializing... ' )
2020-03-10 14:04:27 +01:00
QtCore . QTimer . singleShot ( 100 , self . getVersion )
def done ( data ) :
2021-06-07 21:10:57 +02:00
QtWidgets . QMessageBox . critical ( None , ' Notice ' , six . text_type ( data . data ) , QtWidgets . QMessageBox . Ok ) # type: ignore
2020-03-10 14:04:27 +01:00
sys . exit ( 0 )
2021-06-07 21:10:57 +02:00
2020-03-10 14:04:27 +01:00
# Ask user to approve endpoint
def approveHost ( hostName , parentWindow = None ) :
settings = QtCore . QSettings ( )
2021-05-04 13:05:53 +02:00
settings . beginGroup ( ' endpoints ' )
2020-03-10 14:04:27 +01:00
2021-06-07 21:10:57 +02:00
# approved = settings.value(hostName, False).toBool()
2020-03-10 16:23:38 +01:00
approved = bool ( settings . value ( hostName , False ) )
2020-03-10 14:04:27 +01:00
2021-05-04 13:05:53 +02:00
errorString = ' <p>The server <b> {} </b> must be approved:</p> ' . format ( hostName )
2021-06-07 21:10:57 +02:00
errorString + = (
' <p>Only approve UDS servers that you trust to avoid security issues.</p> '
)
2021-05-04 13:05:53 +02:00
2021-06-07 21:10:57 +02:00
if approved or QtWidgets . QMessageBox . warning ( parentWindow , ' ACCESS Warning ' , errorString , QtWidgets . QMessageBox . Yes | QtWidgets . QMessageBox . No ) == QtWidgets . QMessageBox . Yes : # type: ignore
2020-03-10 14:04:27 +01:00
settings . setValue ( hostName , True )
approved = True
settings . endGroup ( )
return approved
2021-06-07 21:10:57 +02:00
2020-03-10 14:04:27 +01:00
if __name__ == " __main__ " :
2021-05-04 13:05:53 +02:00
logger . debug ( ' Initializing connector ' )
2021-06-07 21:10:57 +02:00
2020-03-10 14:04:27 +01:00
# Initialize app
2020-03-10 16:23:38 +01:00
app = QtWidgets . QApplication ( sys . argv )
2020-03-10 14:04:27 +01:00
# Set several info for settings
2021-05-04 13:05:53 +02:00
QtCore . QCoreApplication . setOrganizationName ( ' Virtual Cable S.L.U. ' )
QtCore . QCoreApplication . setApplicationName ( ' UDS Connector ' )
2020-03-10 14:04:27 +01:00
2021-05-04 13:05:53 +02:00
if ' darwin ' not in sys . platform :
logger . debug ( ' Mac OS *NOT* Detected ' )
app . setStyle ( ' plastique ' )
2020-03-10 14:04:27 +01:00
if six . PY3 is False :
2021-05-04 13:05:53 +02:00
logger . debug ( ' Fixing threaded execution of commands ' )
2020-03-10 14:04:27 +01:00
import threading
2021-06-07 21:10:57 +02:00
2020-07-12 07:03:10 +02:00
threading . _DummyThread . _Thread__stop = lambda x : 42 # type: ignore # pylint: disable=protected-access
2020-03-10 14:04:27 +01:00
# First parameter must be url
try :
uri = sys . argv [ 1 ]
2021-05-04 13:05:53 +02:00
if uri == ' --test ' :
2020-03-10 14:04:27 +01:00
sys . exit ( 0 )
2021-05-04 13:05:53 +02:00
logger . debug ( ' URI: %s ' , uri )
if uri [ : 6 ] != ' uds:// ' and uri [ : 7 ] != ' udss:// ' :
2020-03-10 14:04:27 +01:00
raise Exception ( )
2021-05-04 13:05:53 +02:00
ssl = uri [ 3 ] == ' s '
host , UDSClient . ticket , UDSClient . scrambler = uri . split ( ' // ' ) [ 1 ] . split ( ' / ' ) # type: ignore
2021-06-07 21:10:57 +02:00
logger . debug (
' ssl: %s , host: %s , ticket: %s , scrambler: %s ' ,
ssl ,
host ,
UDSClient . ticket ,
UDSClient . scrambler ,
)
2020-03-10 14:04:27 +01:00
except Exception :
2021-05-04 13:05:53 +02:00
logger . debug ( ' Detected execution without valid URI, exiting ' )
2021-06-07 21:10:57 +02:00
QtWidgets . QMessageBox . critical (
None , # type: ignore
' Notice ' ,
' UDS Client Version {} ' . format ( VERSION ) ,
QtWidgets . QMessageBox . Ok ,
)
2020-03-10 14:04:27 +01:00
sys . exit ( 1 )
# Setup REST api endpoint
2021-06-07 21:10:57 +02:00
RestRequest . restApiUrl = ' {} :// {} /uds/rest/client ' . format ( [ ' http ' , ' https ' ] [ ssl ] , host )
2021-05-04 13:05:53 +02:00
logger . debug ( ' Setting request URL to %s ' , RestRequest . restApiUrl )
2020-03-10 14:04:27 +01:00
# RestRequest.restApiUrl = 'https://172.27.0.1/rest/client'
try :
2021-05-04 13:05:53 +02:00
logger . debug ( ' Starting execution ' )
2020-03-10 14:04:27 +01:00
# Approbe before going on
if approveHost ( host ) is False :
2021-05-04 13:05:53 +02:00
raise Exception ( ' Host {} was not approved ' . format ( host ) )
2020-03-10 14:04:27 +01:00
win = UDSClient ( )
win . show ( )
win . start ( )
exitVal = app . exec_ ( )
2021-05-04 13:05:53 +02:00
logger . debug ( ' Execution finished correctly ' )
2020-03-10 14:04:27 +01:00
except Exception as e :
2021-05-04 13:05:53 +02:00
logger . exception ( ' Got an exception executing client: ' )
2020-03-10 14:04:27 +01:00
exitVal = 128
2021-06-07 21:10:57 +02:00
QtWidgets . QMessageBox . critical (
None , ' Error ' , six . text_type ( e ) , QtWidgets . QMessageBox . Ok # type: ignore
)
2020-03-10 14:04:27 +01:00
2021-05-04 13:05:53 +02:00
logger . debug ( ' Exiting ' )
2020-03-10 14:04:27 +01:00
sys . exit ( exitVal )