2020-03-10 16:23:38 +01:00
#!/usr/bin/env python3
2020-03-10 14:04:27 +01:00
# -*- coding: utf-8 -*-
#
2021-01-14 08:23:34 +01:00
# Copyright (c) 2014-2021 Virtual Cable S.L.
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.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# 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-01-20 12:42:30 +01:00
"""
2020-03-10 14:04:27 +01:00
@author : Adolfo Gómez , dkmaster at dkmon dot com
2021-01-20 12:42:30 +01:00
"""
2020-03-10 14:04:27 +01:00
import sys
import webbrowser
import json
2021-01-20 12:42:30 +01:00
import base64
import bz2
from PyQt5 import QtCore , QtWidgets
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-01-20 12:42:30 +01:00
OLD_METHOD_VERSION = " 2.4.0 "
2020-03-10 14:04:27 +01:00
class RetryException ( Exception ) :
pass
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
self . setWindowFlags (
QtCore . Qt . FramelessWindowHint | QtCore . Qt . WindowStaysOnTopHint
)
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-01-20 12:42:30 +01: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-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
if data . get ( " retryable " , " 0 " ) == " 1 " :
raise RetryException ( data [ " error " ] )
2020-03-10 14:04:27 +01:00
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
logger . error ( " got error: %s " , error )
2020-03-10 14:04:27 +01:00
self . stopAnim ( )
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
QtWidgets . QMessageBox . critical (
None , " UDS Plugin Error " , " {} " . format ( error ) , QtWidgets . QMessageBox . Ok
)
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 ) :
self . ui . progressBar . invertedAppearance = False
self . anim = 0
self . animInverted = False
self . ui . progressBar . setInvertedAppearance ( self . animInverted )
self . animTimer . start ( 40 )
def stopAnim ( self ) :
self . ui . progressBar . invertedAppearance = False
self . animTimer . stop ( )
def getVersion ( self ) :
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
self . ui . info . setText ( " Processing... " )
if data [ " result " ] [ " requiredVersion " ] > VERSION :
QtWidgets . QMessageBox . critical (
self ,
" Upgrade required " ,
" A newer connector version is required. \n A browser will be opened to download it. " ,
QtWidgets . QMessageBox . Ok ,
)
webbrowser . open ( data [ " result " ] [ " downloadUrl " ] )
2020-03-10 14:04:27 +01:00
self . closeWindow ( )
return
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
logger . exception ( " Got exception on getTransportData " )
2020-03-10 14:04:27 +01:00
raise e
def transportDataReceived ( self , data ) :
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
script = bz2 . decompress ( base64 . b64decode ( data [ " result " ] ) )
# 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-01-20 12:42:30 +01:00
script = script . replace ( b " version) " , b ' version.decode( " utf-8 " )) ' )
2020-03-10 14:04:27 +01:00
else :
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
logger . error ( " Signature is invalid " )
2020-03-10 14:04:27 +01:00
2021-01-20 12:42:30 +01:00
raise Exception (
" Invalid UDS code signature. Please, report to administrator "
)
2020-03-10 14:04:27 +01:00
self . stopAnim ( )
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01: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-01-20 12:42:30 +01: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-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
"""
2020-03-10 14:04:27 +01:00
Starts proccess by requesting version info
2021-01-20 12:42:30 +01:00
"""
self . ui . info . setText ( " Initializing... " )
2020-03-10 14:04:27 +01:00
QtCore . QTimer . singleShot ( 100 , self . getVersion )
def done ( data ) :
2021-01-20 12:42:30 +01:00
QtWidgets . QMessageBox . critical (
None , " Notice " , six . text_type ( data . data ) , QtWidgets . QMessageBox . Ok
)
2020-03-10 14:04:27 +01:00
sys . exit ( 0 )
2021-01-20 12:42:30 +01:00
2020-03-10 14:04:27 +01:00
# Ask user to approve endpoint
def approveHost ( hostName , parentWindow = None ) :
settings = QtCore . QSettings ( )
2021-01-20 12:42:30 +01:00
settings . beginGroup ( " endpoints " )
2020-03-10 14:04:27 +01:00
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
errorString = " <p>The server <b> {} </b> must be approved:</p> " . format ( hostName )
errorString + = (
" <p>Only approve UDS servers that you trust to avoid security issues.</p> "
)
if (
approved
or QtWidgets . QMessageBox . warning (
parentWindow ,
" ACCESS Warning " ,
errorString ,
QtWidgets . QMessageBox . Yes | QtWidgets . QMessageBox . No ,
)
== QtWidgets . QMessageBox . Yes
) :
2020-03-10 14:04:27 +01:00
settings . setValue ( hostName , True )
approved = True
settings . endGroup ( )
return approved
2021-01-20 12:42:30 +01:00
2020-03-10 14:04:27 +01:00
if __name__ == " __main__ " :
2021-01-20 12:42:30 +01:00
logger . debug ( " Initializing connector " )
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-01-20 12:42:30 +01:00
QtCore . QCoreApplication . setOrganizationName ( " Virtual Cable S.L.U. " )
QtCore . QCoreApplication . setApplicationName ( " UDS Connector " )
2020-03-10 14:04:27 +01:00
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
logger . debug ( " Fixing threaded execution of commands " )
2020-03-10 14:04:27 +01:00
import threading
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
if uri == " --test " :
2020-03-10 14:04:27 +01:00
sys . exit ( 0 )
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
ssl = uri [ 3 ] == " s "
host , UDSClient . ticket , UDSClient . scrambler = uri . split ( " // " ) [ 1 ] . split ( " / " ) # type: ignore
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-01-20 12:42:30 +01:00
logger . debug ( " Detected execution without valid URI, exiting " )
QtWidgets . QMessageBox . critical (
None ,
" 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-01-20 12:42:30 +01:00
RestRequest . restApiUrl = " {} :// {} /rest/client " . format ( [ " http " , " https " ] [ ssl ] , host )
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-01-20 12:42:30 +01:00
logger . debug ( " Starting execution " )
2020-03-10 14:04:27 +01:00
# Approbe before going on
if approveHost ( host ) is False :
2021-01-20 12:42:30 +01: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-01-20 12:42:30 +01:00
logger . debug ( " Execution finished correctly " )
2020-03-10 14:04:27 +01:00
except Exception as e :
2021-01-20 12:42:30 +01:00
logger . exception ( " Got an exception executing client: " )
2020-03-10 14:04:27 +01:00
exitVal = 128
2021-01-20 12:42:30 +01:00
QtWidgets . QMessageBox . critical (
None , " Error " , six . text_type ( e ) , QtWidgets . QMessageBox . Ok
)
2020-03-10 14:04:27 +01:00
2021-01-20 12:42:30 +01:00
logger . debug ( " Exiting " )
2020-03-10 14:04:27 +01:00
sys . exit ( exitVal )