forked from shaba/openuds
Adding osDetector to UDSClient
This commit is contained in:
parent
98293bba75
commit
3f6d12c89f
@ -27,17 +27,15 @@
|
|||||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
# 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.
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
"""
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
"""
|
'''
|
||||||
import sys
|
import sys
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64, bz2
|
||||||
import bz2
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets # @UnresolvedImport
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from uds.rest import RestRequest
|
from uds.rest import RestRequest
|
||||||
@ -50,13 +48,11 @@ from uds import VERSION
|
|||||||
from UDSWindow import Ui_MainWindow
|
from UDSWindow import Ui_MainWindow
|
||||||
|
|
||||||
# Server before this version uses "unsigned" scripts
|
# Server before this version uses "unsigned" scripts
|
||||||
OLD_METHOD_VERSION = "2.4.0"
|
OLD_METHOD_VERSION = '2.4.0'
|
||||||
|
|
||||||
|
|
||||||
class RetryException(Exception):
|
class RetryException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UDSClient(QtWidgets.QMainWindow):
|
class UDSClient(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
ticket = None
|
ticket = None
|
||||||
@ -65,14 +61,12 @@ class UDSClient(QtWidgets.QMainWindow):
|
|||||||
animTimer = None
|
animTimer = None
|
||||||
anim = 0
|
anim = 0
|
||||||
animInverted = False
|
animInverted = False
|
||||||
serverVersion = "X.Y.Z" # Will be overwriten on getVersion
|
serverVersion = 'X.Y.Z' # Will be overwriten on getVersion
|
||||||
req = None
|
req = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtWidgets.QMainWindow.__init__(self)
|
QtWidgets.QMainWindow.__init__(self)
|
||||||
self.setWindowFlags(
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||||
QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint
|
|
||||||
)
|
|
||||||
|
|
||||||
self.ui = Ui_MainWindow()
|
self.ui = Ui_MainWindow()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
@ -80,7 +74,7 @@ class UDSClient(QtWidgets.QMainWindow):
|
|||||||
self.ui.progressBar.setValue(0)
|
self.ui.progressBar.setValue(0)
|
||||||
self.ui.cancelButton.clicked.connect(self.cancelPushed)
|
self.ui.cancelButton.clicked.connect(self.cancelPushed)
|
||||||
|
|
||||||
self.ui.info.setText("Initializing...")
|
self.ui.info.setText('Initializing...')
|
||||||
|
|
||||||
screen = QtWidgets.QDesktopWidget().screenGeometry()
|
screen = QtWidgets.QDesktopWidget().screenGeometry()
|
||||||
mysize = self.geometry()
|
mysize = self.geometry()
|
||||||
@ -96,30 +90,27 @@ class UDSClient(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
self.startAnim()
|
self.startAnim()
|
||||||
|
|
||||||
|
|
||||||
def closeWindow(self):
|
def closeWindow(self):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def processError(self, data):
|
def processError(self, data):
|
||||||
if "error" in data:
|
if 'error' in data:
|
||||||
# QtWidgets.QMessageBox.critical(self, 'Request error {}'.format(data.get('retryable', '0')), data['error'], QtWidgets.QMessageBox.Ok)
|
# QtWidgets.QMessageBox.critical(self, 'Request error {}'.format(data.get('retryable', '0')), data['error'], QtWidgets.QMessageBox.Ok)
|
||||||
if data.get("retryable", "0") == "1":
|
if data.get('retryable', '0') == '1':
|
||||||
raise RetryException(data["error"])
|
raise RetryException(data['error'])
|
||||||
|
|
||||||
raise Exception(data["error"])
|
raise Exception(data['error'])
|
||||||
# QtWidgets.QMessageBox.critical(self, 'Request error', rest.data['error'], QtWidgets.QMessageBox.Ok)
|
# QtWidgets.QMessageBox.critical(self, 'Request error', rest.data['error'], QtWidgets.QMessageBox.Ok)
|
||||||
# self.closeWindow()
|
# self.closeWindow()
|
||||||
# return
|
# return
|
||||||
|
|
||||||
def showError(self, error):
|
def showError(self, error):
|
||||||
logger.error("got error: %s", error)
|
logger.error('got error: %s', error)
|
||||||
self.stopAnim()
|
self.stopAnim()
|
||||||
self.ui.info.setText(
|
self.ui.info.setText('UDS Plugin Error') # In fact, main window is hidden, so this is not visible... :)
|
||||||
"UDS Plugin Error"
|
|
||||||
) # In fact, main window is hidden, so this is not visible... :)
|
|
||||||
self.closeWindow()
|
self.closeWindow()
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(None, 'UDS Plugin Error', '{}'.format(error), QtWidgets.QMessageBox.Ok)
|
||||||
None, "UDS Plugin Error", "{}".format(error), QtWidgets.QMessageBox.Ok
|
|
||||||
)
|
|
||||||
self.withError = True
|
self.withError = True
|
||||||
|
|
||||||
def cancelPushed(self):
|
def cancelPushed(self):
|
||||||
@ -146,26 +137,21 @@ class UDSClient(QtWidgets.QMainWindow):
|
|||||||
self.animTimer.stop()
|
self.animTimer.stop()
|
||||||
|
|
||||||
def getVersion(self):
|
def getVersion(self):
|
||||||
self.req = RestRequest("", self, self.version)
|
self.req = RestRequest('', self, self.version)
|
||||||
self.req.get()
|
self.req.get()
|
||||||
|
|
||||||
def version(self, data):
|
def version(self, data):
|
||||||
try:
|
try:
|
||||||
self.processError(data)
|
self.processError(data)
|
||||||
self.ui.info.setText("Processing...")
|
self.ui.info.setText('Processing...')
|
||||||
|
|
||||||
if data["result"]["requiredVersion"] > VERSION:
|
if data['result']['requiredVersion'] > VERSION:
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(self, 'Upgrade required', 'A newer connector version is required.\nA browser will be opened to download it.', QtWidgets.QMessageBox.Ok)
|
||||||
self,
|
webbrowser.open(data['result']['downloadUrl'])
|
||||||
"Upgrade required",
|
|
||||||
"A newer connector version is required.\nA browser will be opened to download it.",
|
|
||||||
QtWidgets.QMessageBox.Ok,
|
|
||||||
)
|
|
||||||
webbrowser.open(data["result"]["downloadUrl"])
|
|
||||||
self.closeWindow()
|
self.closeWindow()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.serverVersion = data["result"]["requiredVersion"]
|
self.serverVersion = data['result']['requiredVersion']
|
||||||
self.getTransportData()
|
self.getTransportData()
|
||||||
|
|
||||||
except RetryException as e:
|
except RetryException as e:
|
||||||
@ -177,66 +163,55 @@ class UDSClient(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
def getTransportData(self):
|
def getTransportData(self):
|
||||||
try:
|
try:
|
||||||
self.req = RestRequest(
|
self.req = RestRequest('/{}/{}'.format(self.ticket, self.scrambler), self, self.transportDataReceived, params={'hostname': tools.getHostName(), 'version': VERSION})
|
||||||
"/{}/{}".format(self.ticket, self.scrambler),
|
|
||||||
self,
|
|
||||||
self.transportDataReceived,
|
|
||||||
params={"hostname": tools.getHostName(), "version": VERSION},
|
|
||||||
)
|
|
||||||
self.req.get()
|
self.req.get()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("Got exception on getTransportData")
|
logger.exception('Got exception on getTransportData')
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def transportDataReceived(self, data):
|
def transportDataReceived(self, data):
|
||||||
logger.debug("Transport data received")
|
logger.debug('Transport data received')
|
||||||
try:
|
try:
|
||||||
self.processError(data)
|
self.processError(data)
|
||||||
|
|
||||||
params = None
|
params = None
|
||||||
|
|
||||||
if self.serverVersion <= OLD_METHOD_VERSION:
|
if self.serverVersion <= OLD_METHOD_VERSION:
|
||||||
script = bz2.decompress(base64.b64decode(data["result"]))
|
script = bz2.decompress(base64.b64decode(data['result']))
|
||||||
# This fixes uds 2.2 "write" string on binary streams on some transport
|
# This fixes uds 2.2 "write" string on binary streams on some transport
|
||||||
script = script.replace(b'stdin.write("', b'stdin.write(b"')
|
script = script.replace(b'stdin.write("', b'stdin.write(b"')
|
||||||
script = script.replace(b"version)", b'version.decode("utf-8"))')
|
script = script.replace(b'version)', b'version.decode("utf-8"))')
|
||||||
else:
|
else:
|
||||||
res = data["result"]
|
res = data['result']
|
||||||
# We have three elements on result:
|
# We have three elements on result:
|
||||||
# * Script
|
# * Script
|
||||||
# * Signature
|
# * Signature
|
||||||
# * Script data
|
# * Script data
|
||||||
# We test that the Script has correct signature, and them execute it with the parameters
|
# We test that the Script has correct signature, and them execute it with the parameters
|
||||||
# script, signature, params = res['script'].decode('base64').decode('bz2'), res['signature'], json.loads(res['params'].decode('base64').decode('bz2'))
|
#script, signature, params = res['script'].decode('base64').decode('bz2'), res['signature'], json.loads(res['params'].decode('base64').decode('bz2'))
|
||||||
script, signature, params = (
|
script, signature, params = bz2.decompress(base64.b64decode(res['script'])), res['signature'], json.loads(bz2.decompress(base64.b64decode(res['params'])))
|
||||||
bz2.decompress(base64.b64decode(res["script"])),
|
|
||||||
res["signature"],
|
|
||||||
json.loads(bz2.decompress(base64.b64decode(res["params"]))),
|
|
||||||
)
|
|
||||||
if tools.verifySignature(script, signature) is False:
|
if tools.verifySignature(script, signature) is False:
|
||||||
logger.error("Signature is invalid")
|
logger.error('Signature is invalid')
|
||||||
|
|
||||||
raise Exception(
|
raise Exception('Invalid UDS code signature. Please, report to administrator')
|
||||||
"Invalid UDS code signature. Please, report to administrator"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.stopAnim()
|
self.stopAnim()
|
||||||
|
|
||||||
if "darwin" in sys.platform:
|
if 'darwin' in sys.platform:
|
||||||
self.showMinimized()
|
self.showMinimized()
|
||||||
|
|
||||||
QtCore.QTimer.singleShot(3000, self.endScript)
|
QtCore.QTimer.singleShot(3000, self.endScript)
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
six.exec_(script.decode("utf-8"), globals(), {"parent": self, "sp": params})
|
six.exec_(script.decode("utf-8"), globals(), {'parent': self, 'sp': params})
|
||||||
|
|
||||||
except RetryException as e:
|
except RetryException as e:
|
||||||
self.ui.info.setText(six.text_type(e) + ", retrying access...")
|
self.ui.info.setText(six.text_type(e) + ', retrying access...')
|
||||||
# Retry operation in ten seconds
|
# Retry operation in ten seconds
|
||||||
QtCore.QTimer.singleShot(10000, self.getTransportData)
|
QtCore.QTimer.singleShot(10000, self.getTransportData)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# logger.exception('Got exception executing script:')
|
#logger.exception('Got exception executing script:')
|
||||||
self.showError(e)
|
self.showError(e)
|
||||||
|
|
||||||
def endScript(self):
|
def endScript(self):
|
||||||
@ -259,111 +234,84 @@ class UDSClient(QtWidgets.QMainWindow):
|
|||||||
self.closeWindow()
|
self.closeWindow()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
'''
|
||||||
Starts proccess by requesting version info
|
Starts proccess by requesting version info
|
||||||
"""
|
'''
|
||||||
self.ui.info.setText("Initializing...")
|
self.ui.info.setText('Initializing...')
|
||||||
QtCore.QTimer.singleShot(100, self.getVersion)
|
QtCore.QTimer.singleShot(100, self.getVersion)
|
||||||
|
|
||||||
|
|
||||||
def done(data):
|
def done(data):
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(None, 'Notice', six.text_type(data.data), QtWidgets.QMessageBox.Ok)
|
||||||
None, "Notice", six.text_type(data.data), QtWidgets.QMessageBox.Ok
|
|
||||||
)
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
# Ask user to approve endpoint
|
# Ask user to approve endpoint
|
||||||
def approveHost(hostName, parentWindow=None):
|
def approveHost(hostName, parentWindow=None):
|
||||||
settings = QtCore.QSettings()
|
settings = QtCore.QSettings()
|
||||||
settings.beginGroup("endpoints")
|
settings.beginGroup('endpoints')
|
||||||
|
|
||||||
# approved = settings.value(hostName, False).toBool()
|
#approved = settings.value(hostName, False).toBool()
|
||||||
approved = bool(settings.value(hostName, False))
|
approved = bool(settings.value(hostName, False))
|
||||||
|
|
||||||
errorString = "<p>The server <b>{}</b> must be approved:</p>".format(hostName)
|
errorString = '<p>The server <b>{}</b> must be approved:</p>'.format(hostName)
|
||||||
errorString += (
|
errorString += '<p>Only approve UDS servers that you trust to avoid security issues.</p>'
|
||||||
"<p>Only approve UDS servers that you trust to avoid security issues.</p>"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
if approved or QtWidgets.QMessageBox.warning(parentWindow, 'ACCESS Warning', errorString, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.Yes:
|
||||||
approved
|
|
||||||
or QtWidgets.QMessageBox.warning(
|
|
||||||
parentWindow,
|
|
||||||
"ACCESS Warning",
|
|
||||||
errorString,
|
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
|
||||||
)
|
|
||||||
== QtWidgets.QMessageBox.Yes
|
|
||||||
):
|
|
||||||
settings.setValue(hostName, True)
|
settings.setValue(hostName, True)
|
||||||
approved = True
|
approved = True
|
||||||
|
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
return approved
|
return approved
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logger.debug("Initializing connector")
|
logger.debug('Initializing connector')
|
||||||
|
|
||||||
# Initialize app
|
# Initialize app
|
||||||
app = QtWidgets.QApplication(sys.argv)
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
|
|
||||||
# Set several info for settings
|
# Set several info for settings
|
||||||
QtCore.QCoreApplication.setOrganizationName("Virtual Cable S.L.U.")
|
QtCore.QCoreApplication.setOrganizationName('Virtual Cable S.L.U.')
|
||||||
QtCore.QCoreApplication.setApplicationName("UDS Connector")
|
QtCore.QCoreApplication.setApplicationName('UDS Connector')
|
||||||
|
|
||||||
if "darwin" not in sys.platform:
|
if 'darwin' not in sys.platform:
|
||||||
logger.debug("Mac OS *NOT* Detected")
|
logger.debug('Mac OS *NOT* Detected')
|
||||||
app.setStyle("plastique")
|
app.setStyle('plastique')
|
||||||
|
|
||||||
if six.PY3 is False:
|
if six.PY3 is False:
|
||||||
logger.debug("Fixing threaded execution of commands")
|
logger.debug('Fixing threaded execution of commands')
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
threading._DummyThread._Thread__stop = lambda x: 42 # type: ignore # pylint: disable=protected-access
|
threading._DummyThread._Thread__stop = lambda x: 42 # type: ignore # pylint: disable=protected-access
|
||||||
|
|
||||||
# First parameter must be url
|
# First parameter must be url
|
||||||
try:
|
try:
|
||||||
uri = sys.argv[1]
|
uri = sys.argv[1]
|
||||||
|
|
||||||
if uri == "--test":
|
if uri == '--test':
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
logger.debug("URI: %s", uri)
|
logger.debug('URI: %s', uri)
|
||||||
if uri[:6] != "uds://" and uri[:7] != "udss://":
|
if uri[:6] != 'uds://' and uri[:7] != 'udss://':
|
||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
ssl = uri[3] == "s"
|
ssl = uri[3] == 's'
|
||||||
host, UDSClient.ticket, UDSClient.scrambler = uri.split("//")[1].split("/") # type: ignore
|
host, UDSClient.ticket, UDSClient.scrambler = uri.split('//')[1].split('/') # type: ignore
|
||||||
logger.debug(
|
logger.debug('ssl:%s, host:%s, ticket:%s, scrambler:%s', ssl, host, UDSClient.ticket, UDSClient.scrambler)
|
||||||
"ssl:%s, host:%s, ticket:%s, scrambler:%s",
|
|
||||||
ssl,
|
|
||||||
host,
|
|
||||||
UDSClient.ticket,
|
|
||||||
UDSClient.scrambler,
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.debug("Detected execution without valid URI, exiting")
|
logger.debug('Detected execution without valid URI, exiting')
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(None, 'Notice', 'UDS Client Version {}'.format(VERSION), QtWidgets.QMessageBox.Ok)
|
||||||
None,
|
|
||||||
"Notice",
|
|
||||||
"UDS Client Version {}".format(VERSION),
|
|
||||||
QtWidgets.QMessageBox.Ok,
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Setup REST api endpoint
|
# Setup REST api endpoint
|
||||||
RestRequest.restApiUrl = "{}://{}/rest/client".format(["http", "https"][ssl], host)
|
RestRequest.restApiUrl = '{}://{}/rest/client'.format(['http', 'https'][ssl], host)
|
||||||
logger.debug("Setting request URL to %s", RestRequest.restApiUrl)
|
logger.debug('Setting request URL to %s', RestRequest.restApiUrl)
|
||||||
# RestRequest.restApiUrl = 'https://172.27.0.1/rest/client'
|
# RestRequest.restApiUrl = 'https://172.27.0.1/rest/client'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug("Starting execution")
|
logger.debug('Starting execution')
|
||||||
|
|
||||||
# Approbe before going on
|
# Approbe before going on
|
||||||
if approveHost(host) is False:
|
if approveHost(host) is False:
|
||||||
raise Exception("Host {} was not approved".format(host))
|
raise Exception('Host {} was not approved'.format(host))
|
||||||
|
|
||||||
win = UDSClient()
|
win = UDSClient()
|
||||||
win.show()
|
win.show()
|
||||||
@ -371,14 +319,12 @@ if __name__ == "__main__":
|
|||||||
win.start()
|
win.start()
|
||||||
|
|
||||||
exitVal = app.exec_()
|
exitVal = app.exec_()
|
||||||
logger.debug("Execution finished correctly")
|
logger.debug('Execution finished correctly')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("Got an exception executing client:")
|
logger.exception('Got an exception executing client:')
|
||||||
exitVal = 128
|
exitVal = 128
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(None, 'Error', six.text_type(e), QtWidgets.QMessageBox.Ok)
|
||||||
None, "Error", six.text_type(e), QtWidgets.QMessageBox.Ok
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug("Exiting")
|
logger.debug('Exiting')
|
||||||
sys.exit(exitVal)
|
sys.exit(exitVal)
|
||||||
|
48
client-py3/full/src/uds/osDetector.py
Normal file
48
client-py3/full/src/uds/osDetector.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2017-2021 Virtual Cable S.L.U.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
'''
|
||||||
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
LINUX = 'Linux'
|
||||||
|
WINDOWS = 'Windows'
|
||||||
|
MAC_OS_X = 'Mac os x'
|
||||||
|
|
||||||
|
def getOs():
|
||||||
|
if sys.platform.startswith('linux'):
|
||||||
|
return LINUX
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
return WINDOWS
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
return MAC_OS_X
|
||||||
|
return 'other'
|
@ -11,7 +11,7 @@
|
|||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
# * 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
|
# may be used to endorse or promote products derived from this software
|
||||||
# without specific prior written permission.
|
# without specific prior written permission.
|
||||||
#
|
#
|
||||||
@ -32,27 +32,22 @@
|
|||||||
# pylint: disable=c-extension-no-member,no-name-in-module
|
# pylint: disable=c-extension-no-member,no-name-in-module
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
import certifi
|
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal
|
from PyQt5.QtCore import pyqtSignal
|
||||||
from PyQt5.QtCore import QObject, QUrl, QSettings
|
from PyQt5.QtCore import QObject, QUrl, QSettings
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtNetwork import (
|
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QSslCertificate
|
||||||
QNetworkAccessManager,
|
|
||||||
QNetworkRequest,
|
|
||||||
QNetworkReply,
|
|
||||||
QSslCertificate,
|
|
||||||
)
|
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from . import os_detector
|
from . import osDetector
|
||||||
|
|
||||||
from . import VERSION
|
from . import VERSION
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RestRequest(QObject):
|
class RestRequest(QObject):
|
||||||
|
|
||||||
restApiUrl = '' #
|
restApiUrl = '' #
|
||||||
@ -63,12 +58,16 @@ class RestRequest(QObject):
|
|||||||
super(RestRequest, self).__init__()
|
super(RestRequest, self).__init__()
|
||||||
# private
|
# private
|
||||||
self._manager = QNetworkAccessManager()
|
self._manager = QNetworkAccessManager()
|
||||||
|
try:
|
||||||
|
if os.path.exists('/etc/ssl/certs/ca-certificates.crt'):
|
||||||
|
pass
|
||||||
|
# os.environ['REQUESTS_CA_BUNDLE'] = '/etc/ssl/certs/ca-certificates.crt'
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if params is not None:
|
if params is not None:
|
||||||
url += '?' + '&'.join(
|
url += '?' + '&'.join('{}={}'.format(k, urllib.parse.quote(str(v).encode('utf8'))) for k, v in params.items())
|
||||||
'{}={}'.format(k, urllib.parse.quote(str(v).encode('utf8')))
|
|
||||||
for k, v in params.items()
|
|
||||||
)
|
|
||||||
|
|
||||||
self.url = QUrl(RestRequest.restApiUrl + url)
|
self.url = QUrl(RestRequest.restApiUrl + url)
|
||||||
|
|
||||||
@ -77,21 +76,24 @@ class RestRequest(QObject):
|
|||||||
self._manager.sslErrors.connect(self._sslError)
|
self._manager.sslErrors.connect(self._sslError)
|
||||||
self._parentWindow = parentWindow
|
self._parentWindow = parentWindow
|
||||||
|
|
||||||
self.done.connect(done, Qt.QueuedConnection)
|
self.done.connect(done, Qt.QueuedConnection) # type: ignore
|
||||||
|
|
||||||
def _finished(self, reply):
|
def _finished(self, reply):
|
||||||
"""
|
'''
|
||||||
Handle signal 'finished'. A network request has finished.
|
Handle signal 'finished'. A network request has finished.
|
||||||
"""
|
'''
|
||||||
try:
|
try:
|
||||||
if reply.error() != QNetworkReply.NoError:
|
if reply.error() != QNetworkReply.NoError:
|
||||||
raise Exception(reply.errorString())
|
raise Exception(reply.errorString())
|
||||||
data = bytes(reply.readAll())
|
data = bytes(reply.readAll())
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
data = {'result': None, 'error': str(e)}
|
data = {
|
||||||
|
'result': None,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
self.done.emit(data)
|
self.done.emit(data) # type: ignore
|
||||||
|
|
||||||
reply.deleteLater() # schedule for delete from main event loop
|
reply.deleteLater() # schedule for delete from main event loop
|
||||||
|
|
||||||
@ -103,27 +105,14 @@ class RestRequest(QObject):
|
|||||||
|
|
||||||
approved = settings.value(digest, False)
|
approved = settings.value(digest, False)
|
||||||
|
|
||||||
errorString = (
|
errorString = '<p>The certificate for <b>{}</b> has the following errors:</p><ul>'.format(cert.subjectInfo(QSslCertificate.CommonName))
|
||||||
'<p>The certificate for <b>{}</b> has the following errors:</p><ul>'.format(
|
|
||||||
cert.subjectInfo(QSslCertificate.CommonName)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for err in errors:
|
for err in errors:
|
||||||
errorString += '<li>' + err.errorString() + '</li>'
|
errorString += '<li>' + err.errorString() + '</li>'
|
||||||
|
|
||||||
errorString += '</ul>'
|
errorString += '</ul>'
|
||||||
|
|
||||||
if (
|
if approved or QMessageBox.warning(self._parentWindow, 'SSL Warning', errorString, QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: # type: ignore
|
||||||
approved
|
|
||||||
or QMessageBox.warning(
|
|
||||||
self._parentWindow,
|
|
||||||
'SSL Warning',
|
|
||||||
errorString,
|
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
|
||||||
)
|
|
||||||
== QMessageBox.Yes
|
|
||||||
):
|
|
||||||
settings.setValue(digest, True)
|
settings.setValue(digest, True)
|
||||||
reply.ignoreSslErrors()
|
reply.ignoreSslErrors()
|
||||||
|
|
||||||
@ -131,14 +120,5 @@ class RestRequest(QObject):
|
|||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
request = QNetworkRequest(self.url)
|
request = QNetworkRequest(self.url)
|
||||||
# Ensure loads certifi certificates
|
request.setRawHeader(b'User-Agent', osDetector.getOs().encode('utf-8') + b" - UDS Connector " + VERSION.encode('utf-8'))
|
||||||
sslCfg = request.sslConfiguration()
|
|
||||||
sslCfg.addCaCertificates(certifi.where())
|
|
||||||
request.setSslConfiguration(sslCfg)
|
|
||||||
request.setRawHeader(
|
|
||||||
b'User-Agent',
|
|
||||||
os_detector.getOs().encode('utf-8')
|
|
||||||
+ b" - UDS Connector "
|
|
||||||
+ VERSION.encode('utf-8'),
|
|
||||||
)
|
|
||||||
self._manager.get(request)
|
self._manager.get(request)
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
# * 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
|
# may be used to endorse or promote products derived from this software
|
||||||
# without specific prior written permission.
|
# without specific prior written permission.
|
||||||
#
|
#
|
||||||
@ -31,20 +31,18 @@
|
|||||||
'''
|
'''
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import base64
|
from base64 import b64decode
|
||||||
import os
|
|
||||||
|
import tempfile
|
||||||
|
import string
|
||||||
import random
|
import random
|
||||||
|
import os
|
||||||
import socket
|
import socket
|
||||||
import stat
|
import stat
|
||||||
import string
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
import six
|
||||||
from cryptography.hazmat.primitives import hashes
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
|
||||||
|
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
|
||||||
@ -52,8 +50,10 @@ _unlinkFiles = []
|
|||||||
_tasksToWait = []
|
_tasksToWait = []
|
||||||
_execBeforeExit = []
|
_execBeforeExit = []
|
||||||
|
|
||||||
|
sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
||||||
|
|
||||||
# Public key for scripts
|
# Public key for scripts
|
||||||
PUBLIC_KEY = b'''-----BEGIN PUBLIC KEY-----
|
PUBLIC_KEY = '''-----BEGIN PUBLIC KEY-----
|
||||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuNURlGjBpqbglkTTg2lh
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuNURlGjBpqbglkTTg2lh
|
||||||
dU5qPbg9Q+RofoDDucGfrbY0pjB9ULgWXUetUWDZhFG241tNeKw+aYFTEorK5P+g
|
dU5qPbg9Q+RofoDDucGfrbY0pjB9ULgWXUetUWDZhFG241tNeKw+aYFTEorK5P+g
|
||||||
ud7h9KfyJ6huhzln9eyDu3k+kjKUIB1PLtA3lZLZnBx7nmrHRody1u5lRaLVplsb
|
ud7h9KfyJ6huhzln9eyDu3k+kjKUIB1PLtA3lZLZnBx7nmrHRody1u5lRaLVplsb
|
||||||
@ -71,19 +71,13 @@ nVgtClKcDDlSaBsO875WDR0CAwEAAQ==
|
|||||||
|
|
||||||
def saveTempFile(content, filename=None):
|
def saveTempFile(content, filename=None):
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = ''.join(
|
filename = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
|
||||||
random.choice(string.ascii_lowercase + string.digits) for _ in range(16)
|
|
||||||
)
|
|
||||||
filename = filename + '.uds'
|
filename = filename + '.uds'
|
||||||
|
|
||||||
filename = os.path.join(tempfile.gettempdir(), filename)
|
filename = os.path.join(tempfile.gettempdir(), filename)
|
||||||
|
|
||||||
try:
|
with open(filename, 'w') as f:
|
||||||
with open(filename, 'w') as f:
|
f.write(content)
|
||||||
f.write(content)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error('Error saving temporary file %s: %s', filename, e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
logger.info('Returning filename')
|
logger.info('Returning filename')
|
||||||
return filename
|
return filename
|
||||||
@ -94,8 +88,7 @@ def readTempFile(filename):
|
|||||||
try:
|
try:
|
||||||
with open(filename, 'r') as f:
|
with open(filename, 'r') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.warning('Could not read file %s: %s', filename, e)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -117,34 +110,32 @@ def findApp(appName, extraPath=None):
|
|||||||
fileName = os.path.join(path, appName)
|
fileName = os.path.join(path, appName)
|
||||||
if os.path.isfile(fileName) and (os.stat(fileName).st_mode & stat.S_IXUSR) != 0:
|
if os.path.isfile(fileName) and (os.stat(fileName).st_mode & stat.S_IXUSR) != 0:
|
||||||
return fileName
|
return fileName
|
||||||
logger.warning('Application %s not found on path %s', appName, searchPath)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def getHostName():
|
def getHostName():
|
||||||
"""
|
'''
|
||||||
Returns current host name
|
Returns current host name
|
||||||
In fact, it's a wrapper for socket.gethostname()
|
In fact, it's a wrapper for socket.gethostname()
|
||||||
"""
|
'''
|
||||||
hostname = socket.gethostname()
|
hostname = socket.gethostname()
|
||||||
logger.info('Hostname: %s', hostname)
|
logger.info('Hostname: %s', hostname)
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
|
|
||||||
# Queing operations (to be executed before exit)
|
# Queing operations (to be executed before exit)
|
||||||
|
|
||||||
|
|
||||||
def addFileToUnlink(filename):
|
def addFileToUnlink(filename):
|
||||||
"""
|
'''
|
||||||
Adds a file to the wait-and-unlink list
|
Adds a file to the wait-and-unlink list
|
||||||
"""
|
'''
|
||||||
_unlinkFiles.append(filename)
|
_unlinkFiles.append(filename)
|
||||||
|
|
||||||
|
|
||||||
def unlinkFiles():
|
def unlinkFiles():
|
||||||
"""
|
'''
|
||||||
Removes all wait-and-unlink files
|
Removes all wait-and-unlink files
|
||||||
"""
|
'''
|
||||||
if _unlinkFiles:
|
if _unlinkFiles:
|
||||||
time.sleep(5) # Wait 5 seconds before deleting anything
|
time.sleep(5) # Wait 5 seconds before deleting anything
|
||||||
|
|
||||||
@ -180,14 +171,21 @@ def execBeforeExit():
|
|||||||
|
|
||||||
|
|
||||||
def verifySignature(script, signature):
|
def verifySignature(script, signature):
|
||||||
public_key = load_pem_public_key(backend=default_backend(), data=PUBLIC_KEY)
|
'''
|
||||||
|
Verifies with a public key from whom the data came that it was indeed
|
||||||
|
signed by their private key
|
||||||
|
param: public_key_loc Path to public key
|
||||||
|
param: signature String signature to be verified
|
||||||
|
return: Boolean. True if the signature is valid; False otherwise.
|
||||||
|
'''
|
||||||
|
# For signature checking
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Signature import PKCS1_v1_5
|
||||||
|
from Crypto.Hash import SHA256
|
||||||
|
|
||||||
# Message option
|
rsakey = RSA.importKey(PUBLIC_KEY)
|
||||||
try:
|
signer = PKCS1_v1_5.new(rsakey)
|
||||||
public_key.verify(
|
digest = SHA256.new(script) # Script is "binary string" here
|
||||||
base64.b64decode(signature), script, padding.PKCS1v15(), hashes.SHA256()
|
if signer.verify(digest, b64decode(signature)):
|
||||||
)
|
return True
|
||||||
except Exception: # InvalidSignature
|
return False
|
||||||
logger.error('Invalid signature for UDS plugin code. Contact Administrator.')
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2021 Virtual Cable S.L.U.
|
# Copyright (c) 2020 Virtual Cable S.L.U.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without modification,
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
@ -11,7 +11,7 @@
|
|||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
# * 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
|
# may be used to endorse or promote products derived from this software
|
||||||
# without specific prior written permission.
|
# without specific prior written permission.
|
||||||
#
|
#
|
||||||
@ -33,7 +33,6 @@ import socketserver
|
|||||||
import ssl
|
import ssl
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import random
|
|
||||||
import threading
|
import threading
|
||||||
import select
|
import select
|
||||||
import typing
|
import typing
|
||||||
@ -49,7 +48,6 @@ TUNNEL_LISTENING, TUNNEL_OPENING, TUNNEL_PROCESSING, TUNNEL_ERROR = 0, 1, 2, 3
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ForwardServer(socketserver.ThreadingTCPServer):
|
class ForwardServer(socketserver.ThreadingTCPServer):
|
||||||
daemon_threads = True
|
daemon_threads = True
|
||||||
allow_reuse_address = True
|
allow_reuse_address = True
|
||||||
@ -57,7 +55,6 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
|||||||
remote: typing.Tuple[str, int]
|
remote: typing.Tuple[str, int]
|
||||||
ticket: str
|
ticket: str
|
||||||
stop_flag: threading.Event
|
stop_flag: threading.Event
|
||||||
can_stop: bool
|
|
||||||
timeout: int
|
timeout: int
|
||||||
timer: typing.Optional[threading.Timer]
|
timer: typing.Optional[threading.Timer]
|
||||||
check_certificate: bool
|
check_certificate: bool
|
||||||
@ -72,30 +69,23 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
|||||||
local_port: int = 0,
|
local_port: int = 0,
|
||||||
check_certificate: bool = True,
|
check_certificate: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
local_port = local_port or random.randrange(33000, 53000)
|
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
server_address=(LISTEN_ADDRESS, local_port), RequestHandlerClass=Handler
|
server_address=(LISTEN_ADDRESS, local_port), RequestHandlerClass=Handler
|
||||||
)
|
)
|
||||||
self.remote = remote
|
self.remote = remote
|
||||||
self.ticket = ticket
|
self.ticket = ticket
|
||||||
# Negative values for timeout, means "accept always connections"
|
self.timeout = int(time.time()) + timeout if timeout else 0
|
||||||
# "but if no connection is stablished on timeout (positive)"
|
|
||||||
# "stop the listener"
|
|
||||||
self.timeout = int(time.time()) + timeout if timeout > 0 else 0
|
|
||||||
self.check_certificate = check_certificate
|
self.check_certificate = check_certificate
|
||||||
self.stop_flag = threading.Event() # False initial
|
self.stop_flag = threading.Event() # False initial
|
||||||
self.current_connections = 0
|
self.current_connections = 0
|
||||||
|
|
||||||
self.status = TUNNEL_LISTENING
|
self.status = TUNNEL_LISTENING
|
||||||
self.can_stop = False
|
|
||||||
|
|
||||||
timeout = abs(timeout) or 60
|
if timeout:
|
||||||
self.timer = threading.Timer(
|
self.timer = threading.Timer(timeout, ForwardServer.__checkStarted, args=(self,))
|
||||||
abs(timeout), ForwardServer.__checkStarted, args=(self,)
|
self.timer.start()
|
||||||
)
|
else:
|
||||||
self.timer.start()
|
self.timer = None
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
if not self.stop_flag.is_set():
|
if not self.stop_flag.is_set():
|
||||||
@ -106,52 +96,13 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
|||||||
self.timer = None
|
self.timer = None
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
|
|
||||||
def connect(self) -> ssl.SSLSocket:
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as rsocket:
|
|
||||||
logger.info('CONNECT to %s', self.remote)
|
|
||||||
|
|
||||||
rsocket.connect(self.remote)
|
|
||||||
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
|
|
||||||
# Do not "recompress" data, use only "base protocol" compression
|
|
||||||
context.options |= ssl.OP_NO_COMPRESSION
|
|
||||||
|
|
||||||
# If ignore remote certificate
|
|
||||||
if self.check_certificate is False:
|
|
||||||
context.check_hostname = False
|
|
||||||
context.verify_mode = ssl.CERT_NONE
|
|
||||||
logger.warning('Certificate checking is disabled!')
|
|
||||||
|
|
||||||
return context.wrap_socket(rsocket, server_hostname=self.remote[0])
|
|
||||||
|
|
||||||
def check(self) -> bool:
|
|
||||||
if self.status == TUNNEL_ERROR:
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self.connect() as ssl_socket:
|
|
||||||
ssl_socket.sendall(HANDSHAKE_V1 + b'TEST')
|
|
||||||
resp = ssl_socket.recv(2)
|
|
||||||
if resp != b'OK':
|
|
||||||
raise Exception({'Invalid tunnelresponse: {resp}'})
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
'Error connecting to tunnel server %s: %s', self.server_address, e
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stoppable(self) -> bool:
|
def stoppable(self) -> bool:
|
||||||
logger.debug('Is stoppable: %s', self.can_stop)
|
return self.timeout != 0 and int(time.time()) > self.timeout
|
||||||
return self.can_stop or (self.timeout != 0 and int(time.time()) > self.timeout)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __checkStarted(fs: 'ForwardServer') -> None:
|
def __checkStarted(fs: 'ForwardServer') -> None:
|
||||||
logger.debug('New connection limit reached')
|
|
||||||
fs.timer = None
|
fs.timer = None
|
||||||
fs.can_stop = True
|
|
||||||
if fs.current_connections <= 0:
|
if fs.current_connections <= 0:
|
||||||
fs.stop()
|
fs.stop()
|
||||||
|
|
||||||
@ -162,33 +113,46 @@ class Handler(socketserver.BaseRequestHandler):
|
|||||||
|
|
||||||
# server: ForwardServer
|
# server: ForwardServer
|
||||||
def handle(self) -> None:
|
def handle(self) -> None:
|
||||||
|
self.server.current_connections += 1
|
||||||
self.server.status = TUNNEL_OPENING
|
self.server.status = TUNNEL_OPENING
|
||||||
|
|
||||||
# If server processing is over time
|
# If server processing is over time
|
||||||
if self.server.stoppable:
|
if self.server.stoppable:
|
||||||
self.server.status = TUNNEL_ERROR
|
logger.info('Rejected timedout connection try')
|
||||||
logger.info('Rejected timedout connection')
|
|
||||||
self.request.close() # End connection without processing it
|
self.request.close() # End connection without processing it
|
||||||
return
|
return
|
||||||
|
|
||||||
self.server.current_connections += 1
|
|
||||||
|
|
||||||
# Open remote connection
|
# Open remote connection
|
||||||
try:
|
try:
|
||||||
logger.debug('Ticket %s', self.server.ticket)
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as rsocket:
|
||||||
with self.server.connect() as ssl_socket:
|
logger.info('CONNECT to %s', self.server.remote)
|
||||||
# Send handhshake + command + ticket
|
logger.debug('Ticket %s', self.server.ticket)
|
||||||
ssl_socket.sendall(HANDSHAKE_V1 + b'OPEN' + self.server.ticket.encode())
|
|
||||||
# Check response is OK
|
|
||||||
data = ssl_socket.recv(2)
|
|
||||||
if data != b'OK':
|
|
||||||
data += ssl_socket.recv(128)
|
|
||||||
raise Exception(
|
|
||||||
f'Error received: {data.decode(errors="ignore")}'
|
|
||||||
) # Notify error
|
|
||||||
|
|
||||||
# All is fine, now we can tunnel data
|
rsocket.connect(self.server.remote)
|
||||||
self.process(remote=ssl_socket)
|
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
|
||||||
|
# If ignore remote certificate
|
||||||
|
if self.server.check_certificate is False:
|
||||||
|
context.check_hostname = False
|
||||||
|
context.verify_mode = ssl.CERT_NONE
|
||||||
|
logger.warning('Certificate checking is disabled!')
|
||||||
|
|
||||||
|
with context.wrap_socket(
|
||||||
|
rsocket, server_hostname=self.server.remote[0]
|
||||||
|
) as ssl_socket:
|
||||||
|
# Send handhshake + command + ticket
|
||||||
|
ssl_socket.sendall(
|
||||||
|
HANDSHAKE_V1 + b'OPEN' + self.server.ticket.encode()
|
||||||
|
)
|
||||||
|
# Check response is OK
|
||||||
|
data = ssl_socket.recv(2)
|
||||||
|
if data != b'OK':
|
||||||
|
data += ssl_socket.recv(128)
|
||||||
|
raise Exception(f'Error received: {data.decode()}') # Notify error
|
||||||
|
|
||||||
|
# All is fine, now we can tunnel data
|
||||||
|
self.process(remote=ssl_socket)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Error connecting to {self.server.remote!s}: {e!s}')
|
logger.error(f'Error connecting to {self.server.remote!s}: {e!s}')
|
||||||
self.server.status = TUNNEL_ERROR
|
self.server.status = TUNNEL_ERROR
|
||||||
@ -221,17 +185,10 @@ class Handler(socketserver.BaseRequestHandler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _run(server: ForwardServer) -> None:
|
def _run(server: ForwardServer) -> None:
|
||||||
logger.debug(
|
logger.debug('Starting forwarder: %s -> %s, timeout: %d', server.server_address, server.remote, server.timeout)
|
||||||
'Starting forwarder: %s -> %s, timeout: %d',
|
|
||||||
server.server_address,
|
|
||||||
server.remote,
|
|
||||||
server.timeout,
|
|
||||||
)
|
|
||||||
server.serve_forever()
|
server.serve_forever()
|
||||||
logger.debug('Stoped forwarder %s -> %s', server.server_address, server.remote)
|
logger.debug('Stoped forwarded %s -> %s', server.server_address, server.remote)
|
||||||
|
|
||||||
|
|
||||||
def forward(
|
def forward(
|
||||||
remote: typing.Tuple[str, int],
|
remote: typing.Tuple[str, int],
|
||||||
@ -240,7 +197,6 @@ def forward(
|
|||||||
local_port: int = 0,
|
local_port: int = 0,
|
||||||
check_certificate=True,
|
check_certificate=True,
|
||||||
) -> ForwardServer:
|
) -> ForwardServer:
|
||||||
|
|
||||||
fs = ForwardServer(
|
fs = ForwardServer(
|
||||||
remote=remote,
|
remote=remote,
|
||||||
ticket=ticket,
|
ticket=ticket,
|
||||||
@ -252,3 +208,17 @@ def forward(
|
|||||||
threading.Thread(target=_run, args=(fs,)).start()
|
threading.Thread(target=_run, args=(fs,)).start()
|
||||||
|
|
||||||
return fs
|
return fs
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
log = logging.getLogger()
|
||||||
|
log.setLevel(logging.DEBUG)
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'%(levelname)s - %(message)s'
|
||||||
|
) # Basic log format, nice for syslog
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
log.addHandler(handler)
|
||||||
|
|
||||||
|
fs = forward(('172.27.0.1', 7777), '1'*64, local_port=49999, timeout=10, check_certificate=False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user