2022-01-17 15:46:14 +03:00
#!/usr/bin/env -S python3 -s
2020-03-10 16:04:27 +03:00
# -*- coding: utf-8 -*-
#
2021-06-07 22:10:57 +03:00
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
2020-03-10 16:04:27 +03: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 22:10:57 +03:00
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
2020-03-10 16:04:27 +03: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 14:05:53 +03:00
'''
2020-03-10 16:04:27 +03:00
@author : Adolfo Gómez , dkmaster at dkmon dot com
2021-05-04 14:05:53 +03:00
'''
2020-03-10 16:04:27 +03:00
import sys
2021-06-21 11:53:09 +03:00
import os
2021-06-19 16:16:49 +03:00
import platform
import time
2020-03-10 16:04:27 +03:00
import webbrowser
2021-06-19 15:45:51 +03:00
import threading
2021-06-19 16:16:49 +03:00
import typing
2020-03-10 16:04:27 +03:00
2022-08-14 22:57:28 +03:00
from PyQt5 import QtCore , QtWidgets , QtGui
2021-06-19 13:41:51 +03:00
from PyQt5 . QtCore import QSettings
2020-03-10 16:04:27 +03:00
2021-06-19 15:45:51 +03:00
from uds . rest import RestApi , RetryException , InvalidVersion , UDSException
2021-06-19 13:41:51 +03:00
# Just to ensure there are available on runtime
2021-07-02 16:18:35 +03:00
from uds . forward import forward as ssh_forward # type: ignore
from uds . tunnel import forward as tunnel_forwards # type: ignore
2021-06-19 13:41:51 +03:00
2021-07-02 16:18:35 +03:00
from uds . log import logger
2020-03-10 16:04:27 +03:00
from uds import tools
from uds import VERSION
from UDSWindow import Ui_MainWindow
2020-03-10 18:23:38 +03:00
class UDSClient ( QtWidgets . QMainWindow ) :
2020-03-10 16:04:27 +03:00
2021-06-19 15:45:51 +03:00
ticket : str = ' '
scrambler : str = ' '
2020-03-10 16:04:27 +03:00
withError = False
2021-06-19 15:45:51 +03:00
animTimer : typing . Optional [ QtCore . QTimer ] = None
anim : int = 0
animInverted : bool = False
api : RestApi
2020-03-10 16:04:27 +03:00
2021-06-19 16:16:49 +03:00
def __init__ ( self , api : RestApi , ticket : str , scrambler : str ) :
2020-03-10 18:23:38 +03:00
QtWidgets . QMainWindow . __init__ ( self )
2021-06-19 15:45:51 +03:00
self . api = api
2021-06-19 16:16:49 +03:00
self . ticket = ticket
self . scrambler = scrambler
2021-06-07 22:10:57 +03:00
self . setWindowFlags ( QtCore . Qt . FramelessWindowHint | QtCore . Qt . WindowStaysOnTopHint ) # type: ignore
2020-03-10 16:04:27 +03:00
self . ui = Ui_MainWindow ( )
self . ui . setupUi ( self )
self . ui . progressBar . setValue ( 0 )
self . ui . cancelButton . clicked . connect ( self . cancelPushed )
2021-05-04 14:05:53 +03:00
self . ui . info . setText ( ' Initializing... ' )
2020-03-10 16:04:27 +03:00
2022-08-14 22:57:28 +03:00
screen_geometry = QtGui . QGuiApplication . primaryScreen ( ) . geometry ( )
2020-03-10 16:04:27 +03:00
mysize = self . geometry ( )
2022-08-14 22:57:28 +03:00
hpos = ( screen_geometry . width ( ) - mysize . width ( ) ) / / 2
vpos = ( screen_geometry . height ( ) - mysize . height ( ) - mysize . height ( ) ) / / 2
2020-03-10 16:04:27 +03:00
self . move ( hpos , vpos )
self . animTimer = QtCore . QTimer ( )
2021-06-19 16:16:49 +03:00
self . animTimer . timeout . connect ( self . updateAnim ) # type: ignore
2020-03-10 18:23:38 +03:00
# QtCore.QObject.connect(self.animTimer, QtCore.SIGNAL('timeout()'), self.updateAnim)
2020-03-10 16:04:27 +03:00
self . activateWindow ( )
self . startAnim ( )
def closeWindow ( self ) :
self . close ( )
def showError ( self , error ) :
2021-05-04 14:05:53 +03:00
logger . error ( ' got error: %s ' , error )
2020-03-10 16:04:27 +03:00
self . stopAnim ( )
2021-06-07 22:10:57 +03:00
self . ui . info . setText (
' UDS Plugin Error '
) # In fact, main window is hidden, so this is not visible... :)
2020-03-10 16:04:27 +03:00
self . closeWindow ( )
2021-06-19 16:16:49 +03:00
QtWidgets . QMessageBox . critical (
None , # type: ignore
' UDS Plugin Error ' ,
' {} ' . format ( error ) ,
2021-06-19 16:26:05 +03:00
QtWidgets . QMessageBox . Ok ,
2021-06-19 16:16:49 +03:00
)
2020-03-10 16:04:27 +03: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 22:10:57 +03:00
self . ui . progressBar . invertedAppearance = False # type: ignore
2020-03-10 16:04:27 +03:00
self . anim = 0
self . animInverted = False
self . ui . progressBar . setInvertedAppearance ( self . animInverted )
2021-07-01 23:04:24 +03:00
if self . animTimer :
self . animTimer . start ( 40 )
2020-03-10 16:04:27 +03:00
def stopAnim ( self ) :
2021-06-07 22:10:57 +03:00
self . ui . progressBar . invertedAppearance = False # type: ignore
2021-07-01 23:04:24 +03:00
if self . animTimer :
self . animTimer . stop ( )
2020-03-10 16:04:27 +03:00
def getVersion ( self ) :
try :
2021-06-19 15:45:51 +03:00
self . api . getVersion ( )
except InvalidVersion as e :
2021-06-19 16:16:49 +03:00
QtWidgets . QMessageBox . critical (
self ,
' Upgrade required ' ,
' A newer connector version is required. \n A browser will be opened to download it. ' ,
2021-06-19 16:26:05 +03:00
QtWidgets . QMessageBox . Ok ,
2021-06-19 16:16:49 +03:00
)
2021-06-19 15:45:51 +03:00
webbrowser . open ( e . downloadUrl )
self . closeWindow ( )
return
2020-03-10 16:04:27 +03:00
except Exception as e :
self . showError ( e )
2021-06-19 15:45:51 +03:00
self . getTransportData ( )
2020-03-10 16:04:27 +03:00
2021-06-19 15:45:51 +03:00
def getTransportData ( self ) :
2020-03-10 16:04:27 +03:00
try :
2021-06-19 15:45:51 +03:00
script , params = self . api . getScriptAndParams ( self . ticket , self . scrambler )
2020-03-10 16:04:27 +03:00
self . stopAnim ( )
2021-05-04 14:05:53 +03:00
if ' darwin ' in sys . platform :
2020-03-10 16:04:27 +03:00
self . showMinimized ( )
2021-06-19 15:45:51 +03:00
# QtCore.QTimer.singleShot(3000, self.endScript)
# self.hide()
self . closeWindow ( )
2020-03-10 16:04:27 +03:00
2021-06-19 15:45:51 +03:00
exec ( script , globals ( ) , { ' parent ' : self , ' sp ' : params } )
2021-06-19 16:16:49 +03:00
# Execute the waiting tasks...
threading . Thread ( target = endScript ) . start ( )
2020-03-10 16:04:27 +03:00
except RetryException as e :
2021-06-09 13:39:06 +03:00
self . ui . info . setText ( str ( e ) + ' , retrying access... ' )
2020-03-10 16:04:27 +03:00
# Retry operation in ten seconds
QtCore . QTimer . singleShot ( 10000 , self . getTransportData )
except Exception as e :
2021-06-19 16:26:05 +03:00
self . showError ( e )
2020-03-10 16:04:27 +03:00
def start ( self ) :
2021-06-07 22:10:57 +03:00
"""
2020-03-10 16:04:27 +03:00
Starts proccess by requesting version info
2021-06-07 22:10:57 +03:00
"""
2021-05-04 14:05:53 +03:00
self . ui . info . setText ( ' Initializing... ' )
2020-03-10 16:04:27 +03:00
QtCore . QTimer . singleShot ( 100 , self . getVersion )
2021-06-19 16:26:05 +03:00
2021-06-19 15:45:51 +03:00
def endScript ( ) :
2021-06-19 16:16:49 +03:00
# Wait a bit before start processing ending sequence
time . sleep ( 3 )
2021-06-21 18:50:12 +03:00
try :
# Remove early stage files...
tools . unlinkFiles ( early = True )
except Exception as e :
logger . debug ( ' Unlinking files on early stage: %s ' , e )
2021-06-19 15:45:51 +03:00
# After running script, wait for stuff
try :
2021-06-19 16:16:49 +03:00
logger . debug ( ' Wating for tasks to finish... ' )
2021-06-19 15:45:51 +03:00
tools . waitForTasks ( )
2021-06-21 18:11:32 +03:00
except Exception as e :
logger . debug ( ' Watiting for tasks to finish: %s ' , e )
2021-06-19 13:41:51 +03:00
2021-06-19 15:45:51 +03:00
try :
2021-06-19 16:16:49 +03:00
logger . debug ( ' Unlinking files ' )
2021-06-21 18:50:12 +03:00
tools . unlinkFiles ( early = False )
2021-06-21 18:11:32 +03:00
except Exception as e :
2021-06-21 18:50:12 +03:00
logger . debug ( ' Unlinking files on later stage: %s ' , e )
2021-06-19 13:41:51 +03:00
2021-06-19 16:26:05 +03:00
# Removing
2021-06-19 15:45:51 +03:00
try :
2021-06-19 16:16:49 +03:00
logger . debug ( ' Executing threads before exit ' )
2021-06-19 15:45:51 +03:00
tools . execBeforeExit ( )
2021-06-21 18:11:32 +03:00
except Exception as e :
logger . debug ( ' execBeforeExit: %s ' , e )
2021-06-19 13:41:51 +03:00
2021-06-19 16:16:49 +03:00
logger . debug ( ' endScript done ' )
2020-03-10 16:04:27 +03:00
2021-06-07 22:10:57 +03:00
2020-03-10 16:04:27 +03:00
# Ask user to approve endpoint
2021-06-19 15:45:51 +03:00
def approveHost ( hostName : str ) :
2020-03-10 16:04:27 +03:00
settings = QtCore . QSettings ( )
2021-05-04 14:05:53 +03:00
settings . beginGroup ( ' endpoints ' )
2020-03-10 16:04:27 +03:00
2021-06-07 22:10:57 +03:00
# approved = settings.value(hostName, False).toBool()
2020-03-10 18:23:38 +03:00
approved = bool ( settings . value ( hostName , False ) )
2020-03-10 16:04:27 +03:00
2021-05-04 14:05:53 +03:00
errorString = ' <p>The server <b> {} </b> must be approved:</p> ' . format ( hostName )
2021-06-07 22:10:57 +03:00
errorString + = (
' <p>Only approve UDS servers that you trust to avoid security issues.</p> '
)
2021-05-04 14:05:53 +03:00
2021-06-19 15:45:51 +03:00
if not approved :
2021-06-19 16:26:05 +03:00
if (
QtWidgets . QMessageBox . warning (
None , # type: ignore
' ACCESS Warning ' ,
errorString ,
QtWidgets . QMessageBox . Yes | QtWidgets . QMessageBox . No , # type: ignore
)
== QtWidgets . QMessageBox . Yes
) :
2021-06-19 15:45:51 +03:00
settings . setValue ( hostName , True )
approved = True
settings . endGroup ( )
return approved
2021-06-19 16:26:05 +03:00
2021-06-19 15:45:51 +03:00
def sslError ( hostname : str , serial ) :
settings = QSettings ( )
settings . beginGroup ( ' ssl ' )
approved = settings . value ( serial , False )
if (
approved
or QtWidgets . QMessageBox . warning (
2021-06-19 16:26:05 +03:00
None , # type: ignore
2021-06-19 15:45:51 +03:00
' SSL Warning ' ,
2021-06-25 14:26:56 +03:00
f ' Could not check SSL certificate for { hostname } . \n Do you trust this host? ' ,
2021-06-19 15:45:51 +03:00
QtWidgets . QMessageBox . Yes | QtWidgets . QMessageBox . No , # type: ignore
)
== QtWidgets . QMessageBox . Yes
) :
2020-03-10 16:04:27 +03:00
approved = True
2021-06-19 15:45:51 +03:00
settings . setValue ( serial , True )
2020-03-10 16:04:27 +03:00
settings . endGroup ( )
return approved
2021-06-21 12:05:38 +03:00
2021-06-21 11:53:09 +03:00
# Used only if command line says so
2021-06-19 16:16:49 +03:00
def minimal ( api : RestApi , ticket : str , scrambler : str ) :
try :
2021-06-25 14:26:56 +03:00
logger . info ( ' Minimal Execution ' )
2021-06-19 16:16:49 +03:00
logger . debug ( ' Getting version ' )
try :
api . getVersion ( )
except InvalidVersion as e :
QtWidgets . QMessageBox . critical (
None , # type: ignore
' Upgrade required ' ,
' A newer connector version is required. \n A browser will be opened to download it. ' ,
2021-06-19 16:26:05 +03:00
QtWidgets . QMessageBox . Ok ,
2021-06-19 16:16:49 +03:00
)
webbrowser . open ( e . downloadUrl )
return 0
logger . debug ( ' Transport data ' )
script , params = api . getScriptAndParams ( ticket , scrambler )
# Execute UDS transport script
exec ( script , globals ( ) , { ' parent ' : None , ' sp ' : params } )
# Execute the waiting task...
threading . Thread ( target = endScript ) . start ( )
except RetryException as e :
QtWidgets . QMessageBox . warning (
2021-06-19 16:26:05 +03:00
None , # type: ignore
' Service not ready ' ,
' {} ' . format ( ' . \n ' . join ( str ( e ) . split ( ' . ' ) ) )
+ ' \n \n Please, retry again in a while. ' ,
QtWidgets . QMessageBox . Ok ,
)
2021-06-19 16:16:49 +03:00
except Exception as e :
2021-06-19 16:26:05 +03:00
# logger.exception('Got exception on getTransportData')
QtWidgets . QMessageBox . critical (
None , # type: ignore
' Error ' ,
2021-06-21 12:05:38 +03:00
' {} ' . format ( str ( e ) ) + ' \n \n Please, retry again in a while. ' ,
2021-06-19 16:26:05 +03:00
QtWidgets . QMessageBox . Ok ,
)
2021-06-19 16:38:19 +03:00
return 0
2021-06-19 16:16:49 +03:00
2021-06-07 22:10:57 +03:00
2021-07-02 16:18:35 +03:00
def main ( args : typing . List [ str ] ) :
2020-03-10 18:23:38 +03:00
app = QtWidgets . QApplication ( sys . argv )
2021-07-02 16:18:35 +03:00
logger . debug ( ' Initializing connector for %s ( %s ) ' , sys . platform , platform . machine ( ) )
2020-03-10 16:04:27 +03:00
2021-07-02 16:18:35 +03:00
logger . debug ( ' Arguments: %s ' , args )
2020-03-10 16:04:27 +03:00
# Set several info for settings
2021-05-04 14:05:53 +03:00
QtCore . QCoreApplication . setOrganizationName ( ' Virtual Cable S.L.U. ' )
QtCore . QCoreApplication . setApplicationName ( ' UDS Connector ' )
2020-03-10 16:04:27 +03:00
2021-05-04 14:05:53 +03:00
if ' darwin ' not in sys . platform :
logger . debug ( ' Mac OS *NOT* Detected ' )
2021-06-09 13:39:06 +03:00
app . setStyle ( ' plastique ' ) # type: ignore
2021-06-21 11:53:09 +03:00
else :
logger . debug ( ' Platform is Mac OS, adding homebrew possible paths ' )
2021-06-21 18:50:12 +03:00
os . environ [ ' PATH ' ] + = ' ' . join (
os . pathsep + i
for i in (
' /usr/local/bin ' ,
' /opt/homebrew/bin ' ,
)
)
2021-06-21 11:53:09 +03:00
logger . debug ( ' Now path is %s ' , os . environ [ ' PATH ' ] )
2020-03-10 16:04:27 +03:00
# First parameter must be url
2021-06-21 11:53:09 +03:00
useMinimal = False
2020-03-10 16:04:27 +03:00
try :
2021-07-02 16:18:35 +03:00
uri = args [ 1 ]
2020-03-10 16:04:27 +03:00
2021-06-21 11:53:09 +03:00
if uri == ' --minimal ' :
useMinimal = True
2021-07-02 16:18:35 +03:00
uri = args [ 2 ] # And get URI
2021-06-21 11:53:09 +03:00
2021-05-04 14:05:53 +03:00
if uri == ' --test ' :
2020-03-10 16:04:27 +03:00
sys . exit ( 0 )
2021-05-04 14:05:53 +03:00
logger . debug ( ' URI: %s ' , uri )
if uri [ : 6 ] != ' uds:// ' and uri [ : 7 ] != ' udss:// ' :
2020-03-10 16:04:27 +03:00
raise Exception ( )
2021-05-04 14:05:53 +03:00
ssl = uri [ 3 ] == ' s '
2021-06-19 16:16:49 +03:00
host , ticket , scrambler = uri . split ( ' // ' ) [ 1 ] . split ( ' / ' ) # type: ignore
2021-06-07 22:10:57 +03:00
logger . debug (
' ssl: %s , host: %s , ticket: %s , scrambler: %s ' ,
ssl ,
host ,
2021-07-02 16:18:35 +03:00
ticket ,
scrambler ,
2021-06-07 22:10:57 +03:00
)
2020-03-10 16:04:27 +03:00
except Exception :
2021-05-04 14:05:53 +03:00
logger . debug ( ' Detected execution without valid URI, exiting ' )
2021-06-07 22:10:57 +03:00
QtWidgets . QMessageBox . critical (
None , # type: ignore
' Notice ' ,
' UDS Client Version {} ' . format ( VERSION ) ,
QtWidgets . QMessageBox . Ok ,
)
2020-03-10 16:04:27 +03:00
sys . exit ( 1 )
# Setup REST api endpoint
2021-06-19 16:26:05 +03:00
api = RestApi (
' {} :// {} /uds/rest/client ' . format ( [ ' http ' , ' https ' ] [ ssl ] , host ) , sslError
)
2020-03-10 16:04:27 +03:00
try :
2021-05-04 14:05:53 +03:00
logger . debug ( ' Starting execution ' )
2020-03-10 16:04:27 +03:00
# Approbe before going on
if approveHost ( host ) is False :
2021-05-04 14:05:53 +03:00
raise Exception ( ' Host {} was not approved ' . format ( host ) )
2020-03-10 16:04:27 +03:00
2021-06-19 16:16:49 +03:00
win = UDSClient ( api , ticket , scrambler )
2020-03-10 16:04:27 +03:00
win . show ( )
win . start ( )
2021-08-22 00:06:19 +03:00
exitVal = app . exec ( )
2021-05-04 14:05:53 +03:00
logger . debug ( ' Execution finished correctly ' )
2020-03-10 16:04:27 +03:00
except Exception as e :
2021-05-04 14:05:53 +03:00
logger . exception ( ' Got an exception executing client: ' )
2020-03-10 16:04:27 +03:00
exitVal = 128
2021-06-07 22:10:57 +03:00
QtWidgets . QMessageBox . critical (
2021-06-09 13:39:06 +03:00
None , ' Error ' , str ( e ) , QtWidgets . QMessageBox . Ok # type: ignore
2021-06-07 22:10:57 +03:00
)
2020-03-10 16:04:27 +03:00
2021-05-04 14:05:53 +03:00
logger . debug ( ' Exiting ' )
2020-03-10 16:04:27 +03:00
sys . exit ( exitVal )
2021-07-02 16:18:35 +03:00
if __name__ == " __main__ " :
main ( sys . argv )