forked from shaba/openuds
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e7216e8a24 | ||
|
48557f96e4 | ||
|
263071750c | ||
|
4fed22d39d | ||
|
24687fda2e | ||
|
51b0cec536 |
7
.gitignore
vendored
7
.gitignore
vendored
@ -32,6 +32,9 @@
|
|||||||
/client/administration/installer/UDSAdminInstaller/MSChart.exe
|
/client/administration/installer/UDSAdminInstaller/MSChart.exe
|
||||||
/client/administration/installer/UDSAdminInstaller/UDSAdminSetup.exe
|
/client/administration/installer/UDSAdminInstaller/UDSAdminSetup.exe
|
||||||
|
|
||||||
|
# /guacamole-tunnel/
|
||||||
|
/guacamole-tunnel/target
|
||||||
|
|
||||||
# /linuxActor/
|
# /linuxActor/
|
||||||
/linuxActor/udsactor_*
|
/linuxActor/udsactor_*
|
||||||
|
|
||||||
@ -64,6 +67,8 @@
|
|||||||
|
|
||||||
# /server/
|
# /server/
|
||||||
*_enterprise
|
*_enterprise
|
||||||
|
/server/openuds.sublime-project
|
||||||
|
/server/openuds.sublime-workspace
|
||||||
|
|
||||||
# /server/src/
|
# /server/src/
|
||||||
/server/src/taskmanager.pid
|
/server/src/taskmanager.pid
|
||||||
@ -86,6 +91,7 @@
|
|||||||
# /server/src/uds/
|
# /server/src/uds/
|
||||||
/server/src/uds/*_enterprise.py
|
/server/src/uds/*_enterprise.py
|
||||||
/server/src/uds/fixtures
|
/server/src/uds/fixtures
|
||||||
|
/server/src/uds/tests
|
||||||
|
|
||||||
# /server/src/uds/auths/
|
# /server/src/uds/auths/
|
||||||
/server/src/uds/auths/*-enterprise
|
/server/src/uds/auths/*-enterprise
|
||||||
@ -162,4 +168,3 @@
|
|||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.pytest_cache
|
|
||||||
|
29
LICENSE
29
LICENSE
@ -1,29 +0,0 @@
|
|||||||
BSD 3-Clause License
|
|
||||||
|
|
||||||
Copyright (c) 2022, 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:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. 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.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder 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.
|
|
@ -10,6 +10,7 @@ OpenUDS (Universal Desktop Services) is a multiplatform connection broker for:
|
|||||||
|
|
||||||
This is an Open Source Source project, initiated by Spanish Company Virtualcable and released Open Source with the help of several Spanish Universities.
|
This is an Open Source Source project, initiated by Spanish Company Virtualcable and released Open Source with the help of several Spanish Universities.
|
||||||
|
|
||||||
Please fell free to contribute to this project.
|
Any help provided will be welcome.
|
||||||
|
|
||||||
**Note: Master version is always under heavy development and it is not recommended for use, it will probably have unfixed bugs. Please use the latest stable branch.**
|
**Note: Master version is always under heavy development and it is not recommended for use, it will probably have unfixed bugs.
|
||||||
|
For use, please use the latest stable branch.**
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
PYTHONPATH=./src:${PYTHONPATH}
|
|
||||||
|
|
4
actor/deps.txt
Normal file
4
actor/deps.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Linux:
|
||||||
|
python3-prctl (recommended, but not required in fact)
|
||||||
|
python3-pyqt5
|
||||||
|
|
@ -11,9 +11,6 @@ dpkg-buildpackage -b
|
|||||||
cat udsactor-template.spec |
|
cat udsactor-template.spec |
|
||||||
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
||||||
sed -e s/"release 1"/"release ${RELEASE}"/g > udsactor-$VERSION.spec
|
sed -e s/"release 1"/"release ${RELEASE}"/g > udsactor-$VERSION.spec
|
||||||
cat udsactor-unmanaged-template.spec |
|
|
||||||
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
|
||||||
sed -e s/"release 1"/"release ${RELEASE}"/g > udsactor-unmanaged-$VERSION.spec
|
|
||||||
|
|
||||||
# Now fix dependencies for opensuse
|
# Now fix dependencies for opensuse
|
||||||
# Note that, although on opensuse the library is "libXss1" on newer,
|
# Note that, although on opensuse the library is "libXss1" on newer,
|
||||||
@ -25,7 +22,7 @@ cat udsactor-unmanaged-template.spec |
|
|||||||
# sed -e s/"libXScrnSaver"/"libXss1"/g > udsactor-opensuse-$VERSION.spec
|
# sed -e s/"libXScrnSaver"/"libXss1"/g > udsactor-opensuse-$VERSION.spec
|
||||||
|
|
||||||
#for pkg in udsactor-$VERSION.spec udsactor-opensuse-$VERSION.spec; do
|
#for pkg in udsactor-$VERSION.spec udsactor-opensuse-$VERSION.spec; do
|
||||||
for pkg in udsactor-*$VERSION.spec; do
|
for pkg in udsactor-$VERSION.spec; do
|
||||||
|
|
||||||
rm -rf rpm
|
rm -rf rpm
|
||||||
for folder in SOURCES BUILD RPMS SPECS SRPMS; do
|
for folder in SOURCES BUILD RPMS SPECS SRPMS; do
|
||||||
|
@ -1,21 +1,3 @@
|
|||||||
udsactor (4.0.0) stable; urgency=medium
|
|
||||||
|
|
||||||
* Upgraded to 4.0.0 release
|
|
||||||
|
|
||||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 1 Jul 2022 15:00:00 +0200
|
|
||||||
|
|
||||||
udsactor (3.6.0) stable; urgency=medium
|
|
||||||
|
|
||||||
* Upgraded to 3.6.0 release
|
|
||||||
|
|
||||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 1 Jul 2022 14:00:00 +0200
|
|
||||||
|
|
||||||
udsactor (3.5.0) stable; urgency=medium
|
|
||||||
|
|
||||||
* Upgraded to 3.5.0 release
|
|
||||||
|
|
||||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 23 Oct 2020 8:00:00 +0200
|
|
||||||
|
|
||||||
udsactor (3.0.0) stable; urgency=medium
|
udsactor (3.0.0) stable; urgency=medium
|
||||||
|
|
||||||
* Upgraded to 3.0.0 release
|
* Upgraded to 3.0.0 release
|
||||||
|
@ -10,7 +10,7 @@ Package: udsactor
|
|||||||
Section: admin
|
Section: admin
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: policykit-1(>=0.100), python3-requests (>=0.8.2), python3-pyqt5 (>=4.9), python3-six(>=1.1), python3 (>=3.6), libxss1, xscreensaver, ${misc:Depends}
|
Depends: policykit-1(>=0.100), python3-requests (>=0.8.2), python3-pyqt5 (>=4.9), python3-six(>=1.1), python3 (>=3.4), libxss1, xscreensaver, ${misc:Depends}
|
||||||
Recommends: python3-prctl(>=1.1.1)
|
Recommends: python3-prctl(>=1.1.1)
|
||||||
Description: Actor for Universal Desktop Services (UDS) Broker
|
Description: Actor for Universal Desktop Services (UDS) Broker
|
||||||
This package provides the required components to allow managed machines to work on an environment managed by UDS Broker.
|
This package provides the required components to allow managed machines to work on an environment managed by UDS Broker.
|
||||||
@ -19,7 +19,7 @@ Package: udsactor-unmanaged
|
|||||||
Section: admin
|
Section: admin
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: policykit-1(>=0.100), python3-requests (>=0.8.2), python3-pyqt5 (>=4.9), python3-six(>=1.1), python3 (>=3.6), libxss1, xscreensaver, ${misc:Depends}
|
Depends: policykit-1(>=0.100), python3-requests (>=0.8.2), python3-pyqt5 (>=4.9), python3-six(>=1.1), python3 (>=3.4), libxss1, xscreensaver, ${misc:Depends}
|
||||||
Recommends: python3-prctl(>=1.1.1)
|
Recommends: python3-prctl(>=1.1.1)
|
||||||
Description: Actor for Universal Desktop Services (UDS) Broker Static Unmanaged machines
|
Description: Actor for Universal Desktop Services (UDS) Broker Static Unmanaged machines
|
||||||
This package provides the required components to allow unmanaged machines (static, independent machines) to work on an environment managed by UDS Broker.
|
This package provides the required components to allow unmanaged machines (static, independent machines) to work on an environment managed by UDS Broker.
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
udsactor-unmanaged_3.6.0_all.deb admin optional
|
udsactor-unmanaged_3.0.0_all.deb admin optional
|
||||||
udsactor_3.6.0_all.deb admin optional
|
udsactor_3.0.0_all.deb admin optional
|
||||||
udsactor_3.6.0_amd64.buildinfo admin optional
|
udsactor_3.0.0_amd64.buildinfo admin optional
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
FOLDER=/usr/share/UDSActor
|
FOLDER=/usr/share/UDSActor
|
||||||
|
|
||||||
cd $FOLDER
|
cd $FOLDER
|
||||||
exec python3 actor_config.py -platform xcb $@
|
exec python3 actor_config.py $@
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
FOLDER=/usr/share/UDSActor
|
FOLDER=/usr/share/UDSActor
|
||||||
|
|
||||||
cd $FOLDER
|
cd $FOLDER
|
||||||
exec python3 actor_config_unmanaged.py -platform xcb $@
|
exec python3 actor_config_unmanaged.py $@
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
FOLDER=/usr/share/UDSActor
|
FOLDER=/usr/share/UDSActor
|
||||||
|
|
||||||
cd $FOLDER
|
cd $FOLDER
|
||||||
exec python3 -s actor_client.py -platform xcb $@
|
exec python3 actor_client.py $@
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>Label</key>
|
|
||||||
<string>net.virtualcable.udsactor.server</string>
|
|
||||||
|
|
||||||
<key>KeepAlive</key>
|
|
||||||
<dict>
|
|
||||||
<key>SuccessfulExit</key>
|
|
||||||
<false/>
|
|
||||||
</dict>
|
|
||||||
|
|
||||||
<key>ProgramArguments</key>
|
|
||||||
<array>
|
|
||||||
<string>/Applications/UDSActor.app/Contents/MacOS/udsactor</string>
|
|
||||||
<string>start</string>
|
|
||||||
</array>
|
|
||||||
|
|
||||||
<key>RunAtLoad</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<key>StandardErrorPath</key>
|
|
||||||
<string>/var/log/udsactor.log</string>
|
|
||||||
|
|
||||||
<key>StandardOutPath</key>
|
|
||||||
<string>/var/log/nxserver.log</string>
|
|
||||||
|
|
||||||
<key>WorkingDirectory</key>
|
|
||||||
<string>/Applications/UDSActor.app/Contents/Resources/</string>
|
|
||||||
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1 +0,0 @@
|
|||||||
service file (net.virtualcable.udsactor.server.plist) goes in /Library/LaunchDaemons
|
|
@ -29,32 +29,33 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
# pylint: disable=invalid-name
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import PyQt5 # noqa
|
import PyQt5 # pylint: disable=unused-import
|
||||||
from PyQt5.QtCore import QTimer
|
from PyQt5.QtCore import QTimer
|
||||||
from PyQt5.QtWidgets import QMainWindow
|
from PyQt5.QtWidgets import QMainWindow
|
||||||
|
|
||||||
from udsactor.log import logger, INFO
|
from udsactor.log import logger, INFO
|
||||||
from udsactor.client import UDSClientQApp
|
from udsactor.client import UDSClientQApp
|
||||||
from udsactor import platform
|
from udsactor.platform import operations
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logger.setLevel(INFO)
|
logger.setLevel(INFO)
|
||||||
|
|
||||||
# Ensure idle operations is initialized on start
|
# Ensure idle operations is initialized on start
|
||||||
platform.operations.initIdleDuration(0)
|
operations.initIdleDuration(0)
|
||||||
|
|
||||||
if platform.is_linux:
|
if 'linux' in sys.platform:
|
||||||
os.environ['QT_X11_NO_MITSHM'] = '1'
|
os.environ['QT_X11_NO_MITSHM'] = '1'
|
||||||
|
|
||||||
UDSClientQApp.setQuitOnLastWindowClosed(False)
|
UDSClientQApp.setQuitOnLastWindowClosed(False)
|
||||||
|
|
||||||
qApp = UDSClientQApp(sys.argv)
|
qApp = UDSClientQApp(sys.argv)
|
||||||
|
|
||||||
if platform.is_windows or platform.is_mac:
|
if 'linux' not in sys.platform:
|
||||||
# The "hidden window" is not needed on linux
|
# The "hidden window" is only needed to process events on Windows
|
||||||
# Not needed on Linux
|
# Not needed on Linux
|
||||||
mw = QMainWindow()
|
mw = QMainWindow()
|
||||||
mw.showMinimized() # Start minimized, will be hidden (not destroyed) as soon as qApp.init is invoked
|
mw.showMinimized() # Start minimized, will be hidden (not destroyed) as soon as qApp.init is invoked
|
||||||
@ -66,9 +67,9 @@ if __name__ == "__main__":
|
|||||||
# Note: Signals are only checked on python code execution, so we create a timer to force call back to python
|
# Note: Signals are only checked on python code execution, so we create a timer to force call back to python
|
||||||
timer = QTimer(qApp)
|
timer = QTimer(qApp)
|
||||||
timer.start(1000)
|
timer.start(1000)
|
||||||
timer.timeout.connect(lambda *a: None) # type: ignore # timeout can be connected to a callable
|
timer.timeout.connect(lambda *a: None)
|
||||||
|
|
||||||
qApp.exec()
|
qApp.exec_()
|
||||||
|
|
||||||
# On windows, if no window is created, this point will never be reached.
|
# On windows, if no window is created, this point will never be reached.
|
||||||
qApp.end()
|
qApp.end()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2020-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2020 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -35,7 +35,7 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
import PyQt5 # Ensures PyQt is included in the package
|
import PyQt5 # pylint: disable=unused-import
|
||||||
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox
|
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox
|
||||||
|
|
||||||
import udsactor
|
import udsactor
|
||||||
@ -187,9 +187,9 @@ if __name__ == "__main__":
|
|||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
if udsactor.platform.operations.checkPermissions() is False:
|
if udsactor.platform.operations.checkPermissions() is False:
|
||||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore
|
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
myapp = UDSConfigDialog()
|
myapp = UDSConfigDialog()
|
||||||
myapp.show()
|
myapp.show()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec_())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2020-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2020 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -12,7 +12,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,7 +32,7 @@
|
|||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import pickle # nosec: B403
|
import pickle
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
@ -40,7 +40,6 @@ import PyQt5 # pylint: disable=unused-import
|
|||||||
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
||||||
|
|
||||||
import udsactor
|
import udsactor
|
||||||
import udsactor.tools
|
|
||||||
|
|
||||||
from ui.setup_dialog_unmanaged_ui import Ui_UdsActorSetupDialog
|
from ui.setup_dialog_unmanaged_ui import Ui_UdsActorSetupDialog
|
||||||
|
|
||||||
@ -50,7 +49,6 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger('actor')
|
logger = logging.getLogger('actor')
|
||||||
|
|
||||||
|
|
||||||
class UDSConfigDialog(QDialog):
|
class UDSConfigDialog(QDialog):
|
||||||
_host: str = ''
|
_host: str = ''
|
||||||
_config: udsactor.types.ActorConfigurationType
|
_config: udsactor.types.ActorConfigurationType
|
||||||
@ -62,130 +60,91 @@ class UDSConfigDialog(QDialog):
|
|||||||
self.ui = Ui_UdsActorSetupDialog()
|
self.ui = Ui_UdsActorSetupDialog()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
self.ui.host.setText(self._config.host)
|
self.ui.host.setText(self._config.host)
|
||||||
self.ui.validateCertificate.setCurrentIndex(
|
self.ui.validateCertificate.setCurrentIndex(1 if self._config.validateCertificate else 0)
|
||||||
1 if self._config.validateCertificate else 0
|
|
||||||
)
|
|
||||||
self.ui.logLevelComboBox.setCurrentIndex(self._config.log_level)
|
self.ui.logLevelComboBox.setCurrentIndex(self._config.log_level)
|
||||||
self.ui.serviceToken.setText(self._config.master_token or '')
|
self.ui.serviceToken.setText(self._config.master_token)
|
||||||
self.ui.restrictNet.setText(self._config.restrict_net or '')
|
|
||||||
|
|
||||||
self.ui.testButton.setEnabled(
|
self.ui.testButton.setEnabled(bool(self._config.master_token and self._config.host))
|
||||||
bool(self._config.master_token and self._config.host)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self) -> udsactor.rest.UDSServerApi:
|
def api(self) -> udsactor.rest.UDSServerApi:
|
||||||
return udsactor.rest.UDSServerApi(
|
return udsactor.rest.UDSServerApi(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
|
||||||
self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1
|
|
||||||
)
|
|
||||||
|
|
||||||
def finish(self) -> None:
|
def finish(self) -> None:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def configChanged(self, text: str) -> None:
|
def configChanged(self, text: str) -> None:
|
||||||
self.ui.testButton.setEnabled(
|
self.ui.testButton.setEnabled(self.ui.host.text() == self._config.host and self.ui.serviceToken.text() == self._config.master_token)
|
||||||
self.ui.host.text() == self._config.host
|
|
||||||
and self.ui.serviceToken.text() == self._config.master_token
|
|
||||||
and self.ui.restrictNet.text() == self._config.restrict_net
|
|
||||||
)
|
|
||||||
|
|
||||||
def testUDSServer(self) -> None:
|
def testUDSServer(self) -> None:
|
||||||
if not self._config.master_token or not self._config.host:
|
if not self._config.master_token or not self._config.host:
|
||||||
self.ui.testButton.setEnabled(False)
|
self.ui.testButton.setEnabled(False)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
api = udsactor.rest.UDSServerApi(
|
api = udsactor.rest.UDSServerApi(self._config.host, self._config.validateCertificate)
|
||||||
self._config.host, self._config.validateCertificate
|
|
||||||
)
|
|
||||||
if not api.test(self._config.master_token, udsactor.types.UNMANAGED):
|
if not api.test(self._config.master_token, udsactor.types.UNMANAGED):
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
'UDS Test',
|
'UDS Test',
|
||||||
'Service token seems to be invalid . Please, check token validity.',
|
'Service token seems to be invalid . Please, check token validity.',
|
||||||
QMessageBox.Ok, # type: ignore
|
QMessageBox.Ok
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
'UDS Test',
|
'UDS Test',
|
||||||
'Configuration for {} seems to be correct.'.format(
|
'Configuration for {} seems to be correct.'.format(self._config.host),
|
||||||
self._config.host
|
QMessageBox.Ok
|
||||||
),
|
|
||||||
QMessageBox.Ok, # type: ignore
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
'UDS Test',
|
'UDS Test',
|
||||||
'Configured host {} seems to be inaccesible.'.format(self._config.host),
|
'Configured host {} seems to be inaccesible.'.format(self._config.host),
|
||||||
QMessageBox.Ok, # type: ignore
|
QMessageBox.Ok
|
||||||
)
|
)
|
||||||
|
|
||||||
def saveConfig(self) -> None:
|
def saveConfig(self) -> None:
|
||||||
# Ensure restrict_net is empty or a valid subnet
|
|
||||||
restrictNet = self.ui.restrictNet.text().strip()
|
|
||||||
if restrictNet:
|
|
||||||
try:
|
|
||||||
subnet = udsactor.tools.strToNoIPV4Network(restrictNet)
|
|
||||||
if not subnet:
|
|
||||||
raise Exception('Invalid subnet')
|
|
||||||
except Exception:
|
|
||||||
QMessageBox.information(
|
|
||||||
self,
|
|
||||||
'Invalid subnet',
|
|
||||||
'Invalid subnet {}. Please, check it.'.format(restrictNet),
|
|
||||||
QMessageBox.Ok, # type: ignore
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Store parameters on register for later use, notify user of registration
|
# Store parameters on register for later use, notify user of registration
|
||||||
self._config = udsactor.types.ActorConfigurationType(
|
self._config = udsactor.types.ActorConfigurationType(
|
||||||
actorType=udsactor.types.UNMANAGED,
|
actorType=udsactor.types.UNMANAGED,
|
||||||
host=self.ui.host.text(),
|
host=self.ui.host.text(),
|
||||||
validateCertificate=self.ui.validateCertificate.currentIndex() == 1,
|
validateCertificate=self.ui.validateCertificate.currentIndex() == 1,
|
||||||
master_token=self.ui.serviceToken.text().strip(),
|
master_token=self.ui.serviceToken.text(),
|
||||||
restrict_net=restrictNet,
|
log_level=self.ui.logLevelComboBox.currentIndex()
|
||||||
log_level=self.ui.logLevelComboBox.currentIndex(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
udsactor.platform.store.writeConfig(self._config)
|
udsactor.platform.store.writeConfig(self._config)
|
||||||
# Enables test button
|
# Enables test button
|
||||||
self.ui.testButton.setEnabled(True)
|
self.ui.testButton.setEnabled(True)
|
||||||
# Informs the user
|
# Informs the user
|
||||||
QMessageBox.information(
|
QMessageBox.information(self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok)
|
||||||
self,
|
|
||||||
'UDS Configuration',
|
|
||||||
'Configuration saved.',
|
|
||||||
QMessageBox.Ok, # type: ignore
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# If run as "sudo" on linux, we will need this to avoid problems
|
# If to be run as "sudo" on linux, we will need this to avoid problems
|
||||||
if 'linux' in sys.platform:
|
if 'linux' in sys.platform:
|
||||||
os.environ['QT_X11_NO_MITSHM'] = '1'
|
os.environ['QT_X11_NO_MITSHM'] = '1'
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
if udsactor.platform.operations.checkPermissions() is False:
|
if udsactor.platform.operations.checkPermissions() is False:
|
||||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore
|
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if len(sys.argv) > 2:
|
if len(sys.argv) > 2:
|
||||||
if sys.argv[1] == 'export':
|
if sys.argv[1] == 'export':
|
||||||
try:
|
try:
|
||||||
with open(sys.argv[2], 'wb') as export_:
|
with open(sys.argv[2], 'wb') as f:
|
||||||
pickle.dump(
|
pickle.dump(udsactor.platform.store.readConfig(), f, protocol=3)
|
||||||
udsactor.platform.store.readConfig(), export_, protocol=3
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Error exporting configuration file: {}'.format(e))
|
print('Error exporting configuration file: {}'.format(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif sys.argv[1] == 'import':
|
if sys.argv[1] == 'import':
|
||||||
try:
|
try:
|
||||||
with open(sys.argv[2], 'rb') as import_:
|
with open(sys.argv[2], 'rb') as f:
|
||||||
config = pickle.load(import_) # nosec: B301: the file is provided by user, so it's not a security issue
|
config = pickle.load(f)
|
||||||
udsactor.platform.store.writeConfig(config)
|
udsactor.platform.store.writeConfig(config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Error importing configuration file: {}'.format(e))
|
print('Error importing configuration file: {}'.format(e))
|
||||||
@ -194,4 +153,4 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
myapp = UDSConfigDialog()
|
myapp = UDSConfigDialog()
|
||||||
myapp.show()
|
myapp.show()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec_())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2020-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2020 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -12,7 +12,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.
|
||||||
#
|
#
|
||||||
@ -29,8 +29,12 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
from udsactor import platform
|
import sys
|
||||||
|
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
from udsactor.windows import runner
|
||||||
|
else:
|
||||||
|
from udsactor.linux import runner
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
platform.runner.run()
|
runner.run()
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>601</width>
|
<width>595</width>
|
||||||
<height>243</height>
|
<height>220</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -55,7 +55,7 @@
|
|||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>210</y>
|
<y>180</y>
|
||||||
<width>181</width>
|
<width>181</width>
|
||||||
<height>23</height>
|
<height>23</height>
|
||||||
</rect>
|
</rect>
|
||||||
@ -83,7 +83,7 @@
|
|||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>410</x>
|
<x>410</x>
|
||||||
<y>210</y>
|
<y>180</y>
|
||||||
<width>171</width>
|
<width>171</width>
|
||||||
<height>23</height>
|
<height>23</height>
|
||||||
</rect>
|
</rect>
|
||||||
@ -117,7 +117,7 @@
|
|||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>210</x>
|
<x>210</x>
|
||||||
<y>210</y>
|
<y>180</y>
|
||||||
<width>181</width>
|
<width>181</width>
|
||||||
<height>23</height>
|
<height>23</height>
|
||||||
</rect>
|
</rect>
|
||||||
@ -144,7 +144,7 @@
|
|||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>10</y>
|
<y>10</y>
|
||||||
<width>571</width>
|
<width>571</width>
|
||||||
<height>191</height>
|
<height>161</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QFormLayout" name="formLayout">
|
<layout class="QFormLayout" name="formLayout">
|
||||||
@ -214,21 +214,21 @@
|
|||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QLineEdit" name="serviceToken">
|
<widget class="QLineEdit" name="serviceToken">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>UDS Service Token</string>
|
<string>UDS user with administration rights (Will not be stored on template)</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="whatsThis">
|
<property name="whatsThis">
|
||||||
<string><html><head/><body><p>Token of the service on UDS platform</p><p>This token can be obtainend from the service configuration on UDS.</p></body></html></string>
|
<string><html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="label_loglevel">
|
<widget class="QLabel" name="label_loglevel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Log Level</string>
|
<string>Log Level</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="QComboBox" name="logLevelComboBox">
|
<widget class="QComboBox" name="logLevelComboBox">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>1</number>
|
<number>1</number>
|
||||||
@ -258,23 +258,6 @@
|
|||||||
</item>
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="label_restrictNet">
|
|
||||||
<property name="text">
|
|
||||||
<string>Restrict Net</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QLineEdit" name="restrictNet">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Restrict valid detection of network interfaces to this network.</string>
|
|
||||||
</property>
|
|
||||||
<property name="whatsThis">
|
|
||||||
<string><html><head/><body><p>Restrics valid detection of network interfaces.</p><p>Note: Use this field only in case of several network interfaces, so UDS knows which one is the interface where the user will be connected..</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
<zorder>label_host</zorder>
|
<zorder>label_host</zorder>
|
||||||
<zorder>host</zorder>
|
<zorder>host</zorder>
|
||||||
@ -284,8 +267,6 @@
|
|||||||
<zorder>label_security</zorder>
|
<zorder>label_security</zorder>
|
||||||
<zorder>label_loglevel</zorder>
|
<zorder>label_loglevel</zorder>
|
||||||
<zorder>logLevelComboBox</zorder>
|
<zorder>logLevelComboBox</zorder>
|
||||||
<zorder>label_restrictNet</zorder>
|
|
||||||
<zorder>restrictNet</zorder>
|
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
@ -372,22 +353,6 @@
|
|||||||
</hint>
|
</hint>
|
||||||
</hints>
|
</hints>
|
||||||
</connection>
|
</connection>
|
||||||
<connection>
|
|
||||||
<sender>restrictNet</sender>
|
|
||||||
<signal>textChanged(QString)</signal>
|
|
||||||
<receiver>UdsActorSetupDialog</receiver>
|
|
||||||
<slot>configChanged()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>341</x>
|
|
||||||
<y>139</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>295</x>
|
|
||||||
<y>121</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
</connections>
|
||||||
<slots>
|
<slots>
|
||||||
<slot>finish()</slot>
|
<slot>finish()</slot>
|
||||||
|
@ -35,4 +35,4 @@ from . import platform
|
|||||||
__title__ = 'udsactor'
|
__title__ = 'udsactor'
|
||||||
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
|
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
|
||||||
__license__ = "BSD 3-clause"
|
__license__ = "BSD 3-clause"
|
||||||
__copyright__ = "Copyright 2014-2022 VirtualCable S.L.U."
|
__copyright__ = "Copyright 2014-2020 VirtualCable S.L.U."
|
||||||
|
@ -65,9 +65,9 @@ class UDSClientQApp(QApplication):
|
|||||||
self._initialized = False
|
self._initialized = False
|
||||||
|
|
||||||
# This will be invoked on session close
|
# This will be invoked on session close
|
||||||
self.commitDataRequest.connect(self.end) # type: ignore # Will be invoked on session close, to gracely close app
|
self.commitDataRequest.connect(self.end) # Will be invoked on session close, to gracely close app
|
||||||
# self.aboutToQuit.connect(self.end)
|
# self.aboutToQuit.connect(self.end)
|
||||||
self.message.connect(self.showMessage) # type: ignore # there are problems with Pylance and connects on PyQt5... :)
|
self.message.connect(self.showMessage)
|
||||||
|
|
||||||
# Execute backgroup thread for actions
|
# Execute backgroup thread for actions
|
||||||
self._app = UDSActorClient(self)
|
self._app = UDSActorClient(self)
|
||||||
@ -94,7 +94,7 @@ class UDSClientQApp(QApplication):
|
|||||||
self._app.join()
|
self._app.join()
|
||||||
|
|
||||||
def showMessage(self, message: str) -> None:
|
def showMessage(self, message: str) -> None:
|
||||||
QMessageBox.information(None, 'Message', message) # type: ignore
|
QMessageBox.information(None, 'Message', message)
|
||||||
|
|
||||||
def setMainWindow(self, mw: 'QMainWindow'):
|
def setMainWindow(self, mw: 'QMainWindow'):
|
||||||
self._mainWindow = mw
|
self._mainWindow = mw
|
||||||
@ -108,7 +108,6 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
|||||||
_listener: client.HTTPServerThread
|
_listener: client.HTTPServerThread
|
||||||
_loginInfo: typing.Optional['types.LoginResultInfoType']
|
_loginInfo: typing.Optional['types.LoginResultInfoType']
|
||||||
_notified: bool
|
_notified: bool
|
||||||
_notifiedDeadline: bool
|
|
||||||
_sessionStartTime: datetime.datetime
|
_sessionStartTime: datetime.datetime
|
||||||
api: rest.UDSClientApi
|
api: rest.UDSClientApi
|
||||||
|
|
||||||
@ -116,14 +115,13 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.api = rest.UDSClientApi() # Self initialized
|
self.api = rest.UDSClientApi() # Self initialized
|
||||||
self._qApp = typing.cast(UDSClientQApp, qApp)
|
self._qApp = qApp
|
||||||
self._running = False
|
self._running = False
|
||||||
self._forceLogoff = False
|
self._forceLogoff = False
|
||||||
self._extraLogoff = ''
|
self._extraLogoff = ''
|
||||||
self._listener = client.HTTPServerThread(self)
|
self._listener = client.HTTPServerThread(self)
|
||||||
self._loginInfo = None
|
self._loginInfo = None
|
||||||
self._notified = False
|
self._notified = False
|
||||||
self._notifiedDeadline = False
|
|
||||||
|
|
||||||
# Capture stop signals..
|
# Capture stop signals..
|
||||||
logger.debug('Setting signals...')
|
logger.debug('Setting signals...')
|
||||||
@ -141,8 +139,8 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
|||||||
remainingTime = self._loginInfo.dead_line - (datetime.datetime.now() - self._sessionStartTime).total_seconds()
|
remainingTime = self._loginInfo.dead_line - (datetime.datetime.now() - self._sessionStartTime).total_seconds()
|
||||||
logger.debug('Remaining time: {}'.format(remainingTime))
|
logger.debug('Remaining time: {}'.format(remainingTime))
|
||||||
|
|
||||||
if not self._notifiedDeadline and remainingTime < 300: # With five minutes, show a warning message
|
if not self._notified and remainingTime < 300: # With five minutes, show a warning message
|
||||||
self._notifiedDeadline = True
|
self._notified = True
|
||||||
self._showMessage('Your session will expire in less that 5 minutes. Please, save your work and disconnect.')
|
self._showMessage('Your session will expire in less that 5 minutes. Please, save your work and disconnect.')
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -185,8 +183,7 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Notify loging and mark it
|
# Notify loging and mark it
|
||||||
user, sessionType = platform.operations.getCurrentUser(), platform.operations.getSessionType()
|
self._loginInfo = self.api.login(platform.operations.getCurrentUser(), platform.operations.getSessionType())
|
||||||
self._loginInfo = self.api.login(user, sessionType)
|
|
||||||
|
|
||||||
if self._loginInfo.max_idle:
|
if self._loginInfo.max_idle:
|
||||||
platform.operations.initIdleDuration(self._loginInfo.max_idle)
|
platform.operations.initIdleDuration(self._loginInfo.max_idle)
|
||||||
@ -196,13 +193,10 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
|||||||
self.checkIdle()
|
self.checkIdle()
|
||||||
self.checkDeadLine()
|
self.checkDeadLine()
|
||||||
|
|
||||||
time.sleep(1.22) # Sleeps between loop iterations
|
time.sleep(1.3) # Sleeps between loop iterations
|
||||||
|
|
||||||
self.api.logout(user + self._extraLogoff, sessionType)
|
|
||||||
logger.info('Notified logout for %s (%s)', user, sessionType) # Log logout
|
|
||||||
|
|
||||||
# Clean up login info
|
|
||||||
self._loginInfo = None
|
self._loginInfo = None
|
||||||
|
self.api.logout(platform.operations.getCurrentUser() + self._extraLogoff)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error on client loop: %s', e)
|
logger.error('Error on client loop: %s', e)
|
||||||
|
|
||||||
@ -216,7 +210,7 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
|||||||
platform.operations.loggoff()
|
platform.operations.loggoff()
|
||||||
|
|
||||||
def _showMessage(self, message: str) -> None:
|
def _showMessage(self, message: str) -> None:
|
||||||
self._qApp.message.emit(message) # type: ignore # there are problems with Pylance and connects on PyQt5... :)
|
self._qApp.message.emit(message)
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
logger.debug('Stopping client Service')
|
logger.debug('Stopping client Service')
|
||||||
@ -236,13 +230,13 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
|||||||
On windows, an RDP session with minimized screen will render "black screen"
|
On windows, an RDP session with minimized screen will render "black screen"
|
||||||
So only when user is using RDP connection will return an "actual" screenshot
|
So only when user is using RDP connection will return an "actual" screenshot
|
||||||
'''
|
'''
|
||||||
pixmap: 'QPixmap' = self._qApp.primaryScreen().grabWindow(0) # type: ignore
|
pixmap: 'QPixmap' = self._qApp.primaryScreen().grabWindow(0)
|
||||||
ba = QByteArray()
|
ba = QByteArray()
|
||||||
buffer = QBuffer(ba)
|
buffer = QBuffer(ba)
|
||||||
buffer.open(QIODevice.WriteOnly) # type: ignore
|
buffer.open(QIODevice.WriteOnly)
|
||||||
pixmap.save(buffer, 'PNG')
|
pixmap.save(buffer, 'PNG')
|
||||||
buffer.close()
|
buffer.close()
|
||||||
scrBase64 = bytes(ba.toBase64()).decode() # type: ignore # there are problems with Pylance and connects on PyQt5... :)
|
scrBase64 = bytes(ba.toBase64()).decode()
|
||||||
logger.debug('Screenshot length: %s', len(scrBase64))
|
logger.debug('Screenshot length: %s', len(scrBase64))
|
||||||
return scrBase64 # 'result' of JSON will contain base64 of screen
|
return scrBase64 # 'result' of JSON will contain base64 of screen
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ class HTTPServerThread(threading.Thread):
|
|||||||
self._app = app
|
self._app = app
|
||||||
|
|
||||||
self.port = -1
|
self.port = -1
|
||||||
self.id = secrets.token_urlsafe(24)
|
self.id = secrets.token_urlsafe(16)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self) -> str:
|
def url(self) -> str:
|
||||||
|
@ -33,8 +33,8 @@ import json
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from udsactor import tools, types
|
|
||||||
from udsactor.log import logger
|
from ..log import logger
|
||||||
|
|
||||||
# For avoid proxy on localhost connections
|
# For avoid proxy on localhost connections
|
||||||
NO_PROXY = {
|
NO_PROXY = {
|
||||||
@ -42,108 +42,55 @@ NO_PROXY = {
|
|||||||
'https': None,
|
'https': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UDSActorClientPool:
|
||||||
class UDSActorClientPool(metaclass=tools.Singleton):
|
_clientUrl: typing.List[str]
|
||||||
_clients: typing.List[types.ClientInfo]
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._clients = []
|
self._clientUrl = []
|
||||||
|
|
||||||
def _post(
|
def _post(self, method: str, data: typing.MutableMapping[str, str], timeout=2) -> typing.List[requests.Response]:
|
||||||
self,
|
removables: typing.List[str] = []
|
||||||
session_id: typing.Optional[str],
|
result: typing.List[typing.Any] = []
|
||||||
method: str,
|
for clientUrl in self._clientUrl:
|
||||||
data: typing.MutableMapping[str, str],
|
|
||||||
timeout: int = 2,
|
|
||||||
) -> typing.List[
|
|
||||||
typing.Tuple[types.ClientInfo, typing.Optional[requests.Response]]
|
|
||||||
]:
|
|
||||||
result: typing.List[
|
|
||||||
typing.Tuple[types.ClientInfo, typing.Optional[requests.Response]]
|
|
||||||
] = []
|
|
||||||
for client in self._clients:
|
|
||||||
# Skip if session id is provided but does not match
|
|
||||||
if session_id and client.session_id != session_id:
|
|
||||||
continue
|
|
||||||
clientUrl = client.url
|
|
||||||
try:
|
try:
|
||||||
result.append(
|
result.append(requests.post(clientUrl + '/' + method, data=json.dumps(data), verify=False, timeout=timeout, proxies=NO_PROXY))
|
||||||
(
|
|
||||||
client,
|
|
||||||
requests.post(
|
|
||||||
clientUrl + '/' + method,
|
|
||||||
data=json.dumps(data),
|
|
||||||
verify=False,
|
|
||||||
timeout=timeout,
|
|
||||||
proxies=NO_PROXY, # type: ignore
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.info(
|
# If cannot request to a clientUrl, remove it from list
|
||||||
'Could not connect with client %s: %s. ',
|
logger.info('Could not connect with client %s: %s. Removed from registry.', e, clientUrl)
|
||||||
e,
|
removables.append(clientUrl)
|
||||||
clientUrl,
|
|
||||||
)
|
# Remove failed connections
|
||||||
result.append((client, None))
|
for clientUrl in removables:
|
||||||
|
self.unregister(clientUrl)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@property
|
def register(self, clientUrl: str) -> None:
|
||||||
def clients(self) -> typing.List[types.ClientInfo]:
|
|
||||||
return self._clients
|
|
||||||
|
|
||||||
def register(self, client_url: str) -> None:
|
|
||||||
# Remove first if exists, to avoid duplicates
|
# Remove first if exists, to avoid duplicates
|
||||||
self.unregister(client_url)
|
self.unregister(clientUrl)
|
||||||
# And add it again
|
# And add it again
|
||||||
self._clients.append(types.ClientInfo(client_url, ''))
|
self._clientUrl.append(clientUrl)
|
||||||
|
|
||||||
def set_session_id(self, client_url: str, session_id: typing.Optional[str]) -> None:
|
def unregister(self, clientUrl: str) -> None:
|
||||||
"""Set the session id for a client
|
self._clientUrl = list((i for i in self._clientUrl if i != clientUrl))
|
||||||
|
|
||||||
Args:
|
def executeScript(self, script: str) -> None:
|
||||||
clientUrl (str): _description_
|
self._post('script', {'script': script}, timeout=30)
|
||||||
session_id (str): _description_
|
|
||||||
"""
|
|
||||||
for client in self._clients:
|
|
||||||
if client.url == client_url:
|
|
||||||
# remove existing client from list, create a new one and insert it
|
|
||||||
self._clients.remove(client)
|
|
||||||
self._clients.append(types.ClientInfo(client_url, session_id or ''))
|
|
||||||
break
|
|
||||||
|
|
||||||
def unregister(self, client_url: str) -> None:
|
def logout(self) -> None:
|
||||||
# remove client url from array if found
|
self._post('logout', {})
|
||||||
for i, client in enumerate(self._clients):
|
|
||||||
if client.url == client_url:
|
|
||||||
self._clients.pop(i)
|
|
||||||
return
|
|
||||||
|
|
||||||
def executeScript(self, session_id: typing.Optional[str], script: str) -> None:
|
def message(self, message: str) -> None:
|
||||||
self._post(session_id, 'script', {'script': script}, timeout=30)
|
self._post('message', {'message': message})
|
||||||
|
|
||||||
def logout(self, session_id: typing.Optional[str]) -> None:
|
def ping(self) -> bool:
|
||||||
self._post(session_id, 'logout', {})
|
if not self._clientUrl:
|
||||||
|
return True # No clients, ping ok
|
||||||
|
self._post('ping', {}, timeout=1)
|
||||||
|
return bool(self._clientUrl) # There was clients, but they are now lost!!!
|
||||||
|
|
||||||
def message(self, session_id: typing.Optional[str], message: str) -> None:
|
def screenshot(self) -> typing.Optional[str]: # Screenshot are returned as base64
|
||||||
self._post(session_id, 'message', {'message': message})
|
for r in self._post('screenshot', {}, timeout=3):
|
||||||
|
|
||||||
def lost_clients(
|
|
||||||
self,
|
|
||||||
session_id: typing.Optional[str] = None,
|
|
||||||
) -> typing.Iterable[types.ClientInfo]: # returns the list of "lost" clients
|
|
||||||
# Port ping to every client
|
|
||||||
for i in self._post(session_id, 'ping', {}, timeout=1):
|
|
||||||
if i[1] is None:
|
|
||||||
yield i[0]
|
|
||||||
|
|
||||||
def screenshot(
|
|
||||||
self, session_id: typing.Optional[str]
|
|
||||||
) -> typing.Optional[str]: # Screenshot are returned as base64
|
|
||||||
for client, r in self._post(session_id, 'screenshot', {}, timeout=3):
|
|
||||||
if not r:
|
|
||||||
continue # Missing client, so we ignore it
|
|
||||||
try:
|
try:
|
||||||
return r.json()['result']
|
return r.json()['result']
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -30,23 +30,19 @@
|
|||||||
'''
|
'''
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from udsactor.http import handler, clients_pool
|
from . import handler
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from udsactor.service import CommonService
|
from ..service import CommonService
|
||||||
|
|
||||||
class LocalProvider(handler.Handler):
|
class LocalProvider(handler.Handler):
|
||||||
|
|
||||||
def post_login(self) -> typing.Any:
|
def post_login(self) -> typing.Any:
|
||||||
result = self._service.login(self._params['username'], self._params['session_type'])
|
result = self._service.login(self._params['username'], self._params['session_type'])
|
||||||
# if callback_url is provided, record it in the clients pool
|
|
||||||
if 'callback_url' in self._params and result.session_id:
|
|
||||||
# If no session id is returned, then no login is acounted for
|
|
||||||
clients_pool.UDSActorClientPool().set_session_id(self._params['callback_url'], result.session_id)
|
|
||||||
return result._asdict()
|
return result._asdict()
|
||||||
|
|
||||||
def post_logout(self) -> typing.Any:
|
def post_logout(self) -> typing.Any:
|
||||||
self._service.logout(self._params['username'], self._params['session_type'], self._params['session_id'])
|
self._service.logout(self._params['username'])
|
||||||
return 'ok'
|
return 'ok'
|
||||||
|
|
||||||
def post_ping(self) -> typing.Any:
|
def post_ping(self) -> typing.Any:
|
||||||
|
@ -38,7 +38,6 @@ from ..log import logger
|
|||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from ..service import CommonService
|
from ..service import CommonService
|
||||||
|
|
||||||
|
|
||||||
class PublicProvider(handler.Handler):
|
class PublicProvider(handler.Handler):
|
||||||
def post_logout(self) -> typing.Any:
|
def post_logout(self) -> typing.Any:
|
||||||
logger.debug('Sending LOGOFF to clients')
|
logger.debug('Sending LOGOFF to clients')
|
||||||
@ -52,9 +51,7 @@ class PublicProvider(handler.Handler):
|
|||||||
logger.debug('Sending MESSAGE to clients')
|
logger.debug('Sending MESSAGE to clients')
|
||||||
if 'message' not in self._params:
|
if 'message' not in self._params:
|
||||||
raise Exception('Invalid message parameters')
|
raise Exception('Invalid message parameters')
|
||||||
self._service._clientsPool.message(
|
self._service._clientsPool.message(self._params['message']) # pylint: disable=protected-access
|
||||||
self._params['message']
|
|
||||||
) # pylint: disable=protected-access
|
|
||||||
return 'ok'
|
return 'ok'
|
||||||
|
|
||||||
def post_script(self) -> typing.Any:
|
def post_script(self) -> typing.Any:
|
||||||
@ -63,9 +60,7 @@ class PublicProvider(handler.Handler):
|
|||||||
raise Exception('Invalid script parameters')
|
raise Exception('Invalid script parameters')
|
||||||
if self._params.get('user', False):
|
if self._params.get('user', False):
|
||||||
logger.debug('Sending SCRIPT to client')
|
logger.debug('Sending SCRIPT to client')
|
||||||
self._service._clientsPool.executeScript(
|
self._service._clientsPool.executeScript(self._params['script']) # pylint: disable=protected-access
|
||||||
self._params['script']
|
|
||||||
) # pylint: disable=protected-access
|
|
||||||
else:
|
else:
|
||||||
# Execute script at server space, that is, here
|
# Execute script at server space, that is, here
|
||||||
# as a parallel thread
|
# as a parallel thread
|
||||||
@ -77,22 +72,14 @@ class PublicProvider(handler.Handler):
|
|||||||
logger.debug('Received Pre connection')
|
logger.debug('Received Pre connection')
|
||||||
if 'user' not in self._params or 'protocol' not in self._params:
|
if 'user' not in self._params or 'protocol' not in self._params:
|
||||||
raise Exception('Invalid preConnect parameters')
|
raise Exception('Invalid preConnect parameters')
|
||||||
return self._service.preConnect(
|
return self._service.preConnect(self._params['user'], self._params['protocol'], self._params.get('ip', 'unknown'), self._params.get('hostname', 'unknown'))
|
||||||
self._params['user'],
|
|
||||||
self._params['protocol'],
|
|
||||||
self._params.get('ip', 'unknown'),
|
|
||||||
self._params.get('hostname', 'unknown'),
|
|
||||||
self._params.get('udsuser', 'unknown'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_information(self) -> typing.Any:
|
def get_information(self) -> typing.Any:
|
||||||
# Return something useful? :)
|
# Return something useful? :)
|
||||||
return 'UDS Actor Secure Server'
|
return 'UDS Actor Secure Server'
|
||||||
|
|
||||||
def get_screenshot(self) -> typing.Any:
|
def get_screenshot(self) -> typing.Any:
|
||||||
return (
|
return self._service._clientsPool.screenshot() # pylint: disable=protected-access
|
||||||
self._service._clientsPool.screenshot()
|
|
||||||
) # pylint: disable=protected-access
|
|
||||||
|
|
||||||
def get_uuid(self) -> typing.Any:
|
def get_uuid(self) -> typing.Any:
|
||||||
if self._service.isManaged():
|
if self._service.isManaged():
|
||||||
|
@ -71,7 +71,7 @@ class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
# Very simple path & params splitter
|
# Very simple path & params splitter
|
||||||
path = self.path.split('?')[0][1:].split('/')
|
path = self.path.split('?')[0][1:].split('/')
|
||||||
|
|
||||||
logger.debug('Path: %s, ip: %s, params: %s', path, self.client_address, params)
|
logger.debug('Path: %s, params: %s', path, params)
|
||||||
|
|
||||||
handlerType: typing.Optional[typing.Type['Handler']] = None
|
handlerType: typing.Optional[typing.Type['Handler']] = None
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ class HTTPServerThread(threading.Thread):
|
|||||||
# self._server.socket = ssl.wrap_socket(self._server.socket, certfile=self.certFile, server_side=True)
|
# self._server.socket = ssl.wrap_socket(self._server.socket, certfile=self.certFile, server_side=True)
|
||||||
|
|
||||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
# context.options = ssl.CERT_NONE
|
context.options = ssl.CERT_NONE
|
||||||
context.load_cert_chain(certfile=self._certFile, password=password)
|
context.load_cert_chain(certfile=self._certFile, password=password)
|
||||||
self._server.socket = context.wrap_socket(self._server.socket, server_side=True)
|
self._server.socket = context.wrap_socket(self._server.socket, server_side=True)
|
||||||
|
|
||||||
|
1
actor/src/udsactor/info.py
Normal file
1
actor/src/udsactor/info.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
VERSION = '3.0.0'
|
@ -101,7 +101,7 @@ class Daemon:
|
|||||||
def removePidFile(self) -> None:
|
def removePidFile(self) -> None:
|
||||||
try:
|
try:
|
||||||
os.remove(self.pidfile)
|
os.remove(self.pidfile)
|
||||||
except Exception: # nosec: Not interested in exception
|
except Exception:
|
||||||
# Not found/not permissions or whatever, ignore it
|
# Not found/not permissions or whatever, ignore it
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||||
# 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.
|
||||||
#
|
#
|
||||||
@ -35,9 +35,8 @@ import logging
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
class LocalLogger: # pylint: disable=too-few-public-methods
|
class LocalLogger: # pylint: disable=too-few-public-methods
|
||||||
linux = True
|
linux = False
|
||||||
windows = False
|
windows = True
|
||||||
serviceLogger = False
|
|
||||||
|
|
||||||
logger: typing.Optional[logging.Logger]
|
logger: typing.Optional[logging.Logger]
|
||||||
|
|
||||||
@ -59,8 +58,7 @@ class LocalLogger: # pylint: disable=too-few-public-methods
|
|||||||
self.logger = logging.getLogger('udsactor')
|
self.logger = logging.getLogger('udsactor')
|
||||||
os.chmod(fname, 0o0600)
|
os.chmod(fname, 0o0600)
|
||||||
return
|
return
|
||||||
except Exception: # nosec: B110: we don't care about exceptions here
|
except Exception:
|
||||||
# Ignore and try next
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Logger can't be set
|
# Logger can't be set
|
||||||
|
@ -34,7 +34,7 @@ import platform
|
|||||||
import socket
|
import socket
|
||||||
import fcntl # Only available on Linux. Expect complains if edited from windows
|
import fcntl # Only available on Linux. Expect complains if edited from windows
|
||||||
import os
|
import os
|
||||||
import subprocess # nosec
|
import subprocess
|
||||||
import struct
|
import struct
|
||||||
import array
|
import array
|
||||||
import typing
|
import typing
|
||||||
@ -53,9 +53,7 @@ def _getMacAddr(ifname: str) -> typing.Optional[str]:
|
|||||||
ifnameBytes = ifname.encode('utf-8')
|
ifnameBytes = ifname.encode('utf-8')
|
||||||
try:
|
try:
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
info = bytearray(
|
info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifnameBytes[:15])))
|
||||||
fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifnameBytes[:15]))
|
|
||||||
)
|
|
||||||
return str(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
|
return str(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
@ -69,15 +67,11 @@ def _getIpAddr(ifname: str) -> typing.Optional[str]:
|
|||||||
ifnameBytes = ifname.encode('utf-8')
|
ifnameBytes = ifname.encode('utf-8')
|
||||||
try:
|
try:
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
return str(
|
return str(socket.inet_ntoa(fcntl.ioctl(
|
||||||
socket.inet_ntoa(
|
s.fileno(),
|
||||||
fcntl.ioctl(
|
0x8915, # SIOCGIFADDR
|
||||||
s.fileno(),
|
struct.pack(str('256s'), ifnameBytes[:15])
|
||||||
0x8915, # SIOCGIFADDR
|
)[20:24]))
|
||||||
struct.pack(str('256s'), ifnameBytes[:15]),
|
|
||||||
)[20:24]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -97,32 +91,22 @@ def _getInterfaces() -> typing.List[str]:
|
|||||||
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
names = array.array(str('B'), b'\0' * space)
|
names = array.array(str('B'), b'\0' * space)
|
||||||
outbytes = struct.unpack(
|
outbytes = struct.unpack(str('iL'), fcntl.ioctl(
|
||||||
'iL',
|
s.fileno(),
|
||||||
fcntl.ioctl(
|
0x8912, # SIOCGIFCONF
|
||||||
s.fileno(),
|
struct.pack(str('iL'), space, names.buffer_info()[0])
|
||||||
0x8912, # SIOCGIFCONF
|
))[0]
|
||||||
struct.pack('iL', space, names.buffer_info()[0]),
|
|
||||||
),
|
|
||||||
)[0]
|
|
||||||
namestr = names.tobytes()
|
namestr = names.tobytes()
|
||||||
# return namestr, outbytes
|
# return namestr, outbytes
|
||||||
return [
|
return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)]
|
||||||
namestr[i : i + offset].split(b'\0', 1)[0].decode('utf-8')
|
|
||||||
for i in range(0, outbytes, length)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _getIpAndMac(
|
def _getIpAndMac(ifname: str) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]:
|
||||||
ifname: str,
|
|
||||||
) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]:
|
|
||||||
ip, mac = _getIpAddr(ifname), _getMacAddr(ifname)
|
ip, mac = _getIpAddr(ifname), _getMacAddr(ifname)
|
||||||
return (ip, mac)
|
return (ip, mac)
|
||||||
|
|
||||||
|
|
||||||
def checkPermissions() -> bool:
|
def checkPermissions() -> bool:
|
||||||
return os.getuid() == 0
|
return os.getuid() == 0 # getuid only available on linux. Expect "complaioins" if edited from Windows
|
||||||
|
|
||||||
|
|
||||||
def getComputerName() -> str:
|
def getComputerName() -> str:
|
||||||
'''
|
'''
|
||||||
@ -130,23 +114,15 @@ def getComputerName() -> str:
|
|||||||
'''
|
'''
|
||||||
return socket.gethostname().split('.')[0]
|
return socket.gethostname().split('.')[0]
|
||||||
|
|
||||||
|
|
||||||
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
|
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
|
||||||
for ifname in _getInterfaces():
|
for ifname in _getInterfaces():
|
||||||
ip, mac = _getIpAndMac(ifname)
|
ip, mac = _getIpAndMac(ifname)
|
||||||
if (
|
if mac != '00:00:00:00:00:00' and mac and ip and ip.startswith('169.254') is False: # Skips local interfaces & interfaces with no dhcp IPs
|
||||||
mac != '00:00:00:00:00:00'
|
|
||||||
and mac
|
|
||||||
and ip
|
|
||||||
and ip.startswith('169.254') is False
|
|
||||||
): # Skips local interfaces & interfaces with no dhcp IPs
|
|
||||||
yield types.InterfaceInfoType(name=ifname, mac=mac, ip=ip)
|
yield types.InterfaceInfoType(name=ifname, mac=mac, ip=ip)
|
||||||
|
|
||||||
|
|
||||||
def getDomainName() -> str:
|
def getDomainName() -> str:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def getLinuxOs() -> str:
|
def getLinuxOs() -> str:
|
||||||
try:
|
try:
|
||||||
with open('/etc/os-release', 'r') as f:
|
with open('/etc/os-release', 'r') as f:
|
||||||
@ -157,22 +133,18 @@ def getLinuxOs() -> str:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
||||||
|
|
||||||
def getVersion() -> str:
|
|
||||||
return 'Linux ' + getLinuxOs()
|
|
||||||
|
|
||||||
def reboot(flags: int = 0):
|
def reboot(flags: int = 0):
|
||||||
'''
|
'''
|
||||||
Simple reboot using os command
|
Simple reboot using os command
|
||||||
'''
|
'''
|
||||||
subprocess.call(['/sbin/shutdown', 'now', '-r']) # nosec: Fine, all under control
|
subprocess.call(['/sbin/shutdown', 'now', '-r'])
|
||||||
|
|
||||||
|
|
||||||
def loggoff() -> None:
|
def loggoff() -> None:
|
||||||
'''
|
'''
|
||||||
Right now restarts the machine...
|
Right now restarts the machine...
|
||||||
'''
|
'''
|
||||||
subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']]) # nosec: Fine, all under control
|
subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']])
|
||||||
# subprocess.call(['/sbin/shutdown', 'now', '-r'])
|
# subprocess.call(['/sbin/shutdown', 'now', '-r'])
|
||||||
# subprocess.call(['/usr/bin/systemctl', 'reboot', '-i'])
|
# subprocess.call(['/usr/bin/systemctl', 'reboot', '-i'])
|
||||||
|
|
||||||
@ -183,12 +155,10 @@ def renameComputer(newName: str) -> bool:
|
|||||||
Returns True if reboot needed
|
Returns True if reboot needed
|
||||||
'''
|
'''
|
||||||
rename(newName)
|
rename(newName)
|
||||||
return True # Always reboot right now. Not much slower but much more convenient
|
return True # Always reboot right now. Not much slower but much more better
|
||||||
|
|
||||||
|
|
||||||
def joinDomain(
|
def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False):
|
||||||
domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -196,11 +166,7 @@ def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
|
|||||||
'''
|
'''
|
||||||
Simple password change for user using command line
|
Simple password change for user using command line
|
||||||
'''
|
'''
|
||||||
|
os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword))
|
||||||
subprocess.run( # nosec: Fine, all under control
|
|
||||||
'echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword),
|
|
||||||
shell=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def initIdleDuration(atLeastSeconds: int) -> None:
|
def initIdleDuration(atLeastSeconds: int) -> None:
|
||||||
@ -215,22 +181,16 @@ def getCurrentUser() -> str:
|
|||||||
'''
|
'''
|
||||||
Returns current logged in user
|
Returns current logged in user
|
||||||
'''
|
'''
|
||||||
return os.getlogin()
|
return os.environ['USER']
|
||||||
|
|
||||||
|
|
||||||
def getSessionType() -> str:
|
def getSessionType() -> str:
|
||||||
'''
|
'''
|
||||||
Known values:
|
Known values:
|
||||||
* Unknown -> No XDG_SESSION_TYPE environment variable
|
* Unknown -> No SESSIONNAME environment variable
|
||||||
* xrdp --> xrdp session
|
* Console -> Local session
|
||||||
* other types
|
* RDP-Tcp#[0-9]+ -> RDP Session
|
||||||
'''
|
'''
|
||||||
return (
|
return 'xrdp' if 'XRDP_SESSION' in os.environ else os.environ.get('XDG_SESSION_TYPE', 'unknown')
|
||||||
'xrdp'
|
|
||||||
if 'XRDP_SESSION' in os.environ
|
|
||||||
else os.environ.get('XDG_SESSION_TYPE', 'unknown')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def forceTimeSync() -> None:
|
def forceTimeSync() -> None:
|
||||||
return
|
return
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||||
# 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.
|
||||||
#
|
#
|
||||||
@ -28,7 +28,7 @@
|
|||||||
'''
|
'''
|
||||||
@author: Alexey Shabalin, shaba at altlinux dot org
|
@author: Alexey Shabalin, shaba at altlinux dot org
|
||||||
'''
|
'''
|
||||||
import subprocess # nosec
|
import os
|
||||||
|
|
||||||
from .common import renamers
|
from .common import renamers
|
||||||
from ...log import logger
|
from ...log import logger
|
||||||
@ -46,8 +46,8 @@ def rename(newName: str) -> bool:
|
|||||||
hostname.write(newName)
|
hostname.write(newName)
|
||||||
|
|
||||||
# Force system new name
|
# Force system new name
|
||||||
subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: subprocess
|
os.system('/bin/hostname {}'.format(newName))
|
||||||
subprocess.run(['/bin/hostname', newName]) # nosec: subprocess
|
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName))
|
||||||
|
|
||||||
# add name to "hosts"
|
# add name to "hosts"
|
||||||
with open('/etc/hosts', 'r') as hosts:
|
with open('/etc/hosts', 'r') as hosts:
|
||||||
|
@ -29,6 +29,9 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import pkgutil
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from .. import operations
|
from .. import operations
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
import subprocess # nosec
|
import os
|
||||||
|
|
||||||
from .common import renamers
|
from .common import renamers
|
||||||
from ...log import logger
|
from ...log import logger
|
||||||
@ -45,8 +45,8 @@ def rename(newName: str) -> bool:
|
|||||||
hostname.write(newName)
|
hostname.write(newName)
|
||||||
|
|
||||||
# Force system new name
|
# Force system new name
|
||||||
subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root
|
os.system('/bin/hostname {}'.format(newName))
|
||||||
subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root
|
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName))
|
||||||
|
|
||||||
# add name to "hosts"
|
# add name to "hosts"
|
||||||
with open('/etc/hosts', 'r') as hosts:
|
with open('/etc/hosts', 'r') as hosts:
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
import subprocess # nosec
|
import os
|
||||||
|
|
||||||
from .common import renamers
|
from .common import renamers
|
||||||
from ...log import logger
|
from ...log import logger
|
||||||
@ -46,8 +46,8 @@ def rename(newName: str) -> bool:
|
|||||||
hostname.write(newName)
|
hostname.write(newName)
|
||||||
|
|
||||||
# Force system new name
|
# Force system new name
|
||||||
subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root
|
os.system('/bin/hostname {}'.format(newName))
|
||||||
subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root
|
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName))
|
||||||
|
|
||||||
# add name to "hosts"
|
# add name to "hosts"
|
||||||
with open('/etc/hosts', 'r') as hosts:
|
with open('/etc/hosts', 'r') as hosts:
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
import subprocess # nosec
|
import os
|
||||||
|
|
||||||
from .common import renamers
|
from .common import renamers
|
||||||
from ...log import logger
|
from ...log import logger
|
||||||
@ -46,8 +46,8 @@ def rename(newName: str) -> bool:
|
|||||||
hostname.write(newName)
|
hostname.write(newName)
|
||||||
|
|
||||||
# Force system new name
|
# Force system new name
|
||||||
subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root
|
os.system('/bin/hostname {}'.format(newName))
|
||||||
subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root
|
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName))
|
||||||
|
|
||||||
# add name to "hosts"
|
# add name to "hosts"
|
||||||
with open('/etc/hosts', 'r') as hosts:
|
with open('/etc/hosts', 'r') as hosts:
|
||||||
|
@ -50,7 +50,7 @@ def run() -> None:
|
|||||||
r = client.login(sys.argv[2], platform.operations.getSessionType())
|
r = client.login(sys.argv[2], platform.operations.getSessionType())
|
||||||
print('{},{},{},{}\n'.format(r.ip, r.hostname, r.max_idle, r.dead_line or ''))
|
print('{},{},{},{}\n'.format(r.ip, r.hostname, r.max_idle, r.dead_line or ''))
|
||||||
elif sys.argv[1] == 'logout':
|
elif sys.argv[1] == 'logout':
|
||||||
client.logout(sys.argv[2], platform.operations.getSessionType())
|
client.logout(sys.argv[2])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception()
|
logger.exception()
|
||||||
logger.error('Got exception while processing command: %s', e)
|
logger.error('Got exception while processing command: %s', e)
|
||||||
|
@ -37,7 +37,7 @@ from ..log import logger
|
|||||||
from ..service import CommonService
|
from ..service import CommonService
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from prctl import set_proctitle # type: ignore
|
from prctl import set_proctitle # @UnresolvedImport
|
||||||
except ImportError: # Platform may not include prctl, so in case it's not available, we let the "name" as is
|
except ImportError: # Platform may not include prctl, so in case it's not available, we let the "name" as is
|
||||||
def set_proctitle(_):
|
def set_proctitle(_):
|
||||||
pass
|
pass
|
||||||
|
@ -32,13 +32,12 @@
|
|||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
import base64
|
import base64
|
||||||
import pickle # nosec
|
import pickle
|
||||||
|
|
||||||
from .. import types
|
from .. import types
|
||||||
|
|
||||||
CONFIGFILE = '/etc/udsactor/udsactor.cfg'
|
CONFIGFILE = '/etc/udsactor/udsactor.cfg'
|
||||||
|
|
||||||
|
|
||||||
def readConfig() -> types.ActorConfigurationType:
|
def readConfig() -> types.ActorConfigurationType:
|
||||||
try:
|
try:
|
||||||
cfg = configparser.ConfigParser()
|
cfg = configparser.ConfigParser()
|
||||||
@ -46,22 +45,10 @@ def readConfig() -> types.ActorConfigurationType:
|
|||||||
uds: configparser.SectionProxy = cfg['uds']
|
uds: configparser.SectionProxy = cfg['uds']
|
||||||
# Extract data:
|
# Extract data:
|
||||||
base64Config = uds.get('config', None)
|
base64Config = uds.get('config', None)
|
||||||
config = (
|
config = pickle.loads(base64.b64decode(base64Config.encode())) if base64Config else None
|
||||||
pickle.loads( # nosec: file is restricted
|
|
||||||
base64.b64decode(base64Config.encode())
|
|
||||||
)
|
|
||||||
if base64Config
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
base64Data = uds.get('data', None)
|
base64Data = uds.get('data', None)
|
||||||
data = (
|
data = pickle.loads(base64.b64decode(base64Data.encode())) if base64Data else None
|
||||||
pickle.loads( # nosec: file is restricted
|
|
||||||
base64.b64decode(base64Data.encode())
|
|
||||||
)
|
|
||||||
if base64Data
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
return types.ActorConfigurationType(
|
return types.ActorConfigurationType(
|
||||||
actorType=uds.get('type', types.MANAGED),
|
actorType=uds.get('type', types.MANAGED),
|
||||||
@ -69,33 +56,28 @@ def readConfig() -> types.ActorConfigurationType:
|
|||||||
validateCertificate=uds.getboolean('validate', fallback=False),
|
validateCertificate=uds.getboolean('validate', fallback=False),
|
||||||
master_token=uds.get('master_token', None),
|
master_token=uds.get('master_token', None),
|
||||||
own_token=uds.get('own_token', None),
|
own_token=uds.get('own_token', None),
|
||||||
restrict_net=uds.get('restrict_net', None),
|
|
||||||
pre_command=uds.get('pre_command', None),
|
pre_command=uds.get('pre_command', None),
|
||||||
runonce_command=uds.get('runonce_command', None),
|
runonce_command=uds.get('runonce_command', None),
|
||||||
post_command=uds.get('post_command', None),
|
post_command=uds.get('post_command', None),
|
||||||
log_level=int(uds.get('log_level', '2')),
|
log_level=int(uds.get('log_level', '2')),
|
||||||
config=config,
|
config=config,
|
||||||
data=data,
|
data=data
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return types.ActorConfigurationType('', False)
|
return types.ActorConfigurationType('', False)
|
||||||
|
|
||||||
|
|
||||||
def writeConfig(config: types.ActorConfigurationType) -> None:
|
def writeConfig(config: types.ActorConfigurationType) -> None:
|
||||||
cfg = configparser.ConfigParser()
|
cfg = configparser.ConfigParser()
|
||||||
cfg.add_section('uds')
|
cfg.add_section('uds')
|
||||||
uds: configparser.SectionProxy = cfg['uds']
|
uds: configparser.SectionProxy = cfg['uds']
|
||||||
uds['host'] = config.host
|
uds['host'] = config.host
|
||||||
uds['validate'] = 'yes' if config.validateCertificate else 'no'
|
uds['validate'] = 'yes' if config.validateCertificate else 'no'
|
||||||
|
|
||||||
def writeIfValue(val, name):
|
def writeIfValue(val, name):
|
||||||
if val:
|
if val:
|
||||||
uds[name] = val
|
uds[name] = val
|
||||||
|
|
||||||
writeIfValue(config.actorType, 'type')
|
writeIfValue(config.actorType, 'type')
|
||||||
writeIfValue(config.master_token, 'master_token')
|
writeIfValue(config.master_token, 'master_token')
|
||||||
writeIfValue(config.own_token, 'own_token')
|
writeIfValue(config.own_token, 'own_token')
|
||||||
writeIfValue(config.restrict_net, 'restrict_net')
|
|
||||||
writeIfValue(config.pre_command, 'pre_command')
|
writeIfValue(config.pre_command, 'pre_command')
|
||||||
writeIfValue(config.post_command, 'post_command')
|
writeIfValue(config.post_command, 'post_command')
|
||||||
writeIfValue(config.runonce_command, 'runonce_command')
|
writeIfValue(config.runonce_command, 'runonce_command')
|
||||||
@ -109,19 +91,12 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
|
|||||||
# Ensures exists destination folder
|
# Ensures exists destination folder
|
||||||
dirname = os.path.dirname(CONFIGFILE)
|
dirname = os.path.dirname(CONFIGFILE)
|
||||||
if not os.path.exists(dirname):
|
if not os.path.exists(dirname):
|
||||||
os.mkdir(
|
os.mkdir(dirname, mode=0o700) # Will create only if route to path already exists, for example, /etc (that must... :-))
|
||||||
dirname, mode=0o700
|
|
||||||
) # Will create only if route to path already exists, for example, /etc (that must... :-))
|
|
||||||
|
|
||||||
with open(CONFIGFILE, 'w') as f:
|
with open(CONFIGFILE, 'w') as f:
|
||||||
cfg.write(f)
|
cfg.write(f)
|
||||||
|
|
||||||
os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root
|
os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root
|
||||||
|
|
||||||
|
|
||||||
def useOldJoinSystem() -> bool:
|
def useOldJoinSystem() -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def invokeScriptOnLogin() -> str:
|
|
||||||
return ''
|
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
import ctypes
|
import ctypes
|
||||||
import ctypes.util
|
import ctypes.util
|
||||||
import subprocess # nosec
|
import subprocess
|
||||||
|
|
||||||
xlib = None
|
xlib = None
|
||||||
xss = None
|
xss = None
|
||||||
@ -39,22 +39,17 @@ display = None
|
|||||||
xssInfo = None
|
xssInfo = None
|
||||||
initialized = False
|
initialized = False
|
||||||
|
|
||||||
|
|
||||||
class XScreenSaverInfo(ctypes.Structure): # pylint: disable=too-few-public-methods
|
class XScreenSaverInfo(ctypes.Structure): # pylint: disable=too-few-public-methods
|
||||||
_fields_ = [
|
_fields_ = [('window', ctypes.c_long),
|
||||||
('window', ctypes.c_long),
|
('state', ctypes.c_int),
|
||||||
('state', ctypes.c_int),
|
('kind', ctypes.c_int),
|
||||||
('kind', ctypes.c_int),
|
('til_or_since', ctypes.c_ulong),
|
||||||
('til_or_since', ctypes.c_ulong),
|
('idle', ctypes.c_ulong),
|
||||||
('idle', ctypes.c_ulong),
|
('eventMask', ctypes.c_ulong)]
|
||||||
('eventMask', ctypes.c_ulong),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class c_ptr(ctypes.c_void_p):
|
class c_ptr(ctypes.c_void_p):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _ensureInitialized():
|
def _ensureInitialized():
|
||||||
global xlib, xss, xssInfo, display, initialized # pylint: disable=global-statement
|
global xlib, xss, xssInfo, display, initialized # pylint: disable=global-statement
|
||||||
|
|
||||||
@ -78,15 +73,13 @@ def _ensureInitialized():
|
|||||||
xss.XScreenSaverQueryExtension.argtypes = [
|
xss.XScreenSaverQueryExtension.argtypes = [
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.POINTER(ctypes.c_int),
|
ctypes.POINTER(ctypes.c_int),
|
||||||
ctypes.POINTER(ctypes.c_int),
|
ctypes.POINTER(ctypes.c_int)
|
||||||
]
|
]
|
||||||
xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(
|
xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) # Result in a XScreenSaverInfo structure
|
||||||
XScreenSaverInfo
|
|
||||||
) # Result in a XScreenSaverInfo structure
|
|
||||||
xss.XScreenSaverQueryInfo.argtypes = [
|
xss.XScreenSaverQueryInfo.argtypes = [
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.POINTER(XScreenSaverInfo),
|
ctypes.POINTER(XScreenSaverInfo)
|
||||||
]
|
]
|
||||||
xlib.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
xlib.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
||||||
xlib.XOpenDisplay.restype = c_ptr
|
xlib.XOpenDisplay.restype = c_ptr
|
||||||
@ -102,9 +95,7 @@ def _ensureInitialized():
|
|||||||
event_base = ctypes.c_int()
|
event_base = ctypes.c_int()
|
||||||
error_base = ctypes.c_int()
|
error_base = ctypes.c_int()
|
||||||
|
|
||||||
available = xss.XScreenSaverQueryExtension(
|
available = xss.XScreenSaverQueryExtension(display, ctypes.byref(event_base), ctypes.byref(error_base))
|
||||||
display, ctypes.byref(event_base), ctypes.byref(error_base)
|
|
||||||
)
|
|
||||||
|
|
||||||
if available != 1:
|
if available != 1:
|
||||||
raise Exception('ScreenSaver not available')
|
raise Exception('ScreenSaver not available')
|
||||||
@ -116,11 +107,9 @@ def _ensureInitialized():
|
|||||||
def initIdleDuration(atLeastSeconds: int) -> None:
|
def initIdleDuration(atLeastSeconds: int) -> None:
|
||||||
_ensureInitialized()
|
_ensureInitialized()
|
||||||
if atLeastSeconds:
|
if atLeastSeconds:
|
||||||
subprocess.call( # nosec, controlled params
|
subprocess.call(['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)])
|
||||||
['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)]
|
|
||||||
)
|
|
||||||
# And now reset it
|
# And now reset it
|
||||||
subprocess.call(['/usr/bin/xset', 's', 'reset']) # nosec: fixed command
|
subprocess.call(['/usr/bin/xset', 's', 'reset'])
|
||||||
|
|
||||||
|
|
||||||
def getIdleDuration() -> float:
|
def getIdleDuration() -> float:
|
||||||
@ -133,11 +122,7 @@ def getIdleDuration() -> float:
|
|||||||
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo)
|
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo)
|
||||||
|
|
||||||
# States: 0 = off, 1 = On, 2 = Cycle, 3 = Disabled, ...?
|
# States: 0 = off, 1 = On, 2 = Cycle, 3 = Disabled, ...?
|
||||||
if (
|
if xssInfo.contents.state == 1: # state = 1 means "active", so idle is not a valid state
|
||||||
xssInfo.contents.state == 1
|
return 3600 * 100 * 1000 # If screen saver is active, return a high enough value
|
||||||
): # state = 1 means "active", so idle is not a valid state
|
|
||||||
return (
|
|
||||||
3600 * 100 * 1000
|
|
||||||
) # If screen saver is active, return a high enough value
|
|
||||||
|
|
||||||
return xssInfo.contents.idle / 1000.0
|
return xssInfo.contents.idle / 1000.0
|
||||||
|
@ -35,8 +35,6 @@ import typing
|
|||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
from .windows.log import LocalLogger
|
from .windows.log import LocalLogger
|
||||||
elif sys.platform == 'darwin':
|
|
||||||
from .macos.log import LocalLogger
|
|
||||||
else:
|
else:
|
||||||
from .linux.log import LocalLogger
|
from .linux.log import LocalLogger
|
||||||
|
|
||||||
@ -57,7 +55,7 @@ class Logger:
|
|||||||
self.logLevel = INFO
|
self.logLevel = INFO
|
||||||
self.localLogger = LocalLogger()
|
self.localLogger = LocalLogger()
|
||||||
self.remoteLogger = None
|
self.remoteLogger = None
|
||||||
self.own_token = '' # nosec: This is no password at all
|
self.own_token = ''
|
||||||
|
|
||||||
def setLevel(self, level: typing.Union[str, int]) -> None:
|
def setLevel(self, level: typing.Union[str, int]) -> None:
|
||||||
'''
|
'''
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (c) 2022 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.U. 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
|
|
||||||
'''
|
|
@ -1,185 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (c) 2022 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.U. 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
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Note. most methods are not implemented, as they are not needed for this platform (macos)
|
|
||||||
# that only supports unmanaged machines
|
|
||||||
|
|
||||||
import socket
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess # nosec
|
|
||||||
import typing
|
|
||||||
|
|
||||||
import psutil
|
|
||||||
|
|
||||||
from udsactor import types, tools
|
|
||||||
|
|
||||||
MACVER_RE = re.compile(
|
|
||||||
r"<key>ProductVersion</key>\s*<string>(.*)</string>", re.MULTILINE
|
|
||||||
)
|
|
||||||
MACVER_FILE = '/System/Library/CoreServices/SystemVersion.plist'
|
|
||||||
|
|
||||||
|
|
||||||
def checkPermissions() -> bool:
|
|
||||||
return os.getuid() == 0
|
|
||||||
|
|
||||||
|
|
||||||
def getComputerName() -> str:
|
|
||||||
'''
|
|
||||||
Returns computer name, with no domain
|
|
||||||
'''
|
|
||||||
return socket.gethostname().split('.')[0]
|
|
||||||
|
|
||||||
|
|
||||||
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
|
|
||||||
ifdata: typing.List['psutil._common.snicaddr']
|
|
||||||
for ifname, ifdata in psutil.net_if_addrs().items():
|
|
||||||
name, ip, mac = '', '', ''
|
|
||||||
# Get IP address, interface name and MAC address whenever possible
|
|
||||||
for row in ifdata:
|
|
||||||
if row.family == socket.AF_INET:
|
|
||||||
ip = row.address
|
|
||||||
name = ifname
|
|
||||||
elif row.family == socket.AF_LINK:
|
|
||||||
mac = row.address
|
|
||||||
|
|
||||||
# if all data is available, stop iterating
|
|
||||||
if ip and name and mac:
|
|
||||||
if (
|
|
||||||
mac != '00:00:00:00:00:00'
|
|
||||||
and mac
|
|
||||||
and ip
|
|
||||||
and ip.startswith('169.254') is False
|
|
||||||
): # Skips local interfaces & interfaces with no dhcp IPs
|
|
||||||
yield types.InterfaceInfoType(name=name, ip=ip, mac=mac)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def getDomainName() -> str:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def getMacOs() -> str:
|
|
||||||
try:
|
|
||||||
with open(MACVER_FILE, 'r') as f:
|
|
||||||
data = f.read()
|
|
||||||
m = MACVER_RE.search(data)
|
|
||||||
if m:
|
|
||||||
return m.group(1)
|
|
||||||
except Exception: # nosec: B110: ignore exception because we are not interested in it
|
|
||||||
pass
|
|
||||||
|
|
||||||
return 'unknown'
|
|
||||||
|
|
||||||
|
|
||||||
def getVersion() -> str:
|
|
||||||
return 'MacOS ' + getMacOs()
|
|
||||||
|
|
||||||
|
|
||||||
def reboot(flags: int = 0) -> None:
|
|
||||||
'''
|
|
||||||
Simple reboot using os command
|
|
||||||
'''
|
|
||||||
subprocess.call(['/sbin/shutdown', '-r', 'now']) # nosec: Command line is fixed
|
|
||||||
|
|
||||||
|
|
||||||
def loggoff() -> None:
|
|
||||||
'''
|
|
||||||
Right now restarts the machine...
|
|
||||||
'''
|
|
||||||
subprocess.run(
|
|
||||||
"/bin/launchctl bootout gui/$(id -u $USER)", shell=True
|
|
||||||
) # nosec: Command line is fixed
|
|
||||||
# Ignores output, as it may fail if user is not logged in
|
|
||||||
|
|
||||||
|
|
||||||
def renameComputer(newName: str) -> bool:
|
|
||||||
'''
|
|
||||||
Changes the computer name
|
|
||||||
Returns True if reboot needed
|
|
||||||
Note: For macOS, no configuration is supported, only "unmanaged" actor
|
|
||||||
'''
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def joinDomain(
|
|
||||||
domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False
|
|
||||||
):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def initIdleDuration(atLeastSeconds: int) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# se we cache for 20 seconds the result, that is enough for our needs
|
|
||||||
# and we avoid calling a system command every time we need it
|
|
||||||
@tools.cache(20)
|
|
||||||
def getIdleDuration() -> float:
|
|
||||||
# Execute:
|
|
||||||
try:
|
|
||||||
return (
|
|
||||||
int(
|
|
||||||
next(
|
|
||||||
filter(
|
|
||||||
lambda x: b"HIDIdleTime" in x,
|
|
||||||
subprocess.check_output(
|
|
||||||
["/usr/sbin/ioreg", "-c", "IOHIDSystem"]
|
|
||||||
).split(b"\n"),
|
|
||||||
)
|
|
||||||
).split(b"=")[1]
|
|
||||||
)
|
|
||||||
/ 1000000000
|
|
||||||
) # nosec: Command line is fixed
|
|
||||||
except Exception: # nosec: B110: ignore exception because we are not interested in it
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def getCurrentUser() -> str:
|
|
||||||
'''
|
|
||||||
Returns current logged in user
|
|
||||||
'''
|
|
||||||
return os.getlogin()
|
|
||||||
|
|
||||||
|
|
||||||
def getSessionType() -> str:
|
|
||||||
'''
|
|
||||||
Returns the session type. Currently, only "macos" (console) is supported
|
|
||||||
'''
|
|
||||||
return 'macos'
|
|
||||||
|
|
||||||
|
|
||||||
def forceTimeSync() -> None:
|
|
||||||
return
|
|
@ -1,71 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (c) 2022 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
|
|
||||||
'''
|
|
||||||
import sys
|
|
||||||
import typing
|
|
||||||
|
|
||||||
from .. import rest
|
|
||||||
from .. import platform
|
|
||||||
from ..log import logger
|
|
||||||
from .service import UDSActorSvc
|
|
||||||
|
|
||||||
def usage() -> typing.NoReturn:
|
|
||||||
sys.stderr.write('usage: udsactor start|login "username"|logout "username"\n')
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
def run() -> None:
|
|
||||||
logger.setLevel(20000)
|
|
||||||
|
|
||||||
if len(sys.argv) == 3 and sys.argv[1] in ('login', 'logout'):
|
|
||||||
logger.debug('Running client udsactor')
|
|
||||||
try:
|
|
||||||
client: rest.UDSClientApi = rest.UDSClientApi()
|
|
||||||
if sys.argv[1] == 'login':
|
|
||||||
r = client.login(sys.argv[2], platform.operations.getSessionType())
|
|
||||||
print('{},{},{},{}\n'.format(r.ip, r.hostname, r.max_idle, r.dead_line or ''))
|
|
||||||
elif sys.argv[1] == 'logout':
|
|
||||||
client.logout(sys.argv[2], platform.operations.getSessionType())
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception()
|
|
||||||
logger.error('Got exception while processing command: %s', e)
|
|
||||||
sys.exit(0)
|
|
||||||
elif len(sys.argv) != 2:
|
|
||||||
usage()
|
|
||||||
|
|
||||||
daemonSvr = UDSActorSvc()
|
|
||||||
if len(sys.argv) == 2:
|
|
||||||
# Daemon mode...
|
|
||||||
if sys.argv[1] in ('start', 'start-foreground'):
|
|
||||||
daemonSvr.run() # execute in foreground
|
|
||||||
else:
|
|
||||||
usage()
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
usage()
|
|
@ -1,108 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (c) 2022 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.U. 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
|
|
||||||
'''
|
|
||||||
import typing
|
|
||||||
import signal
|
|
||||||
|
|
||||||
from ..log import logger
|
|
||||||
from ..service import CommonService
|
|
||||||
|
|
||||||
|
|
||||||
class UDSActorSvc(CommonService):
|
|
||||||
def __init__(self) -> None:
|
|
||||||
CommonService.__init__(self)
|
|
||||||
|
|
||||||
# Captures signals so we can stop gracefully
|
|
||||||
signal.signal(signal.SIGINT, self.markForExit)
|
|
||||||
signal.signal(signal.SIGTERM, self.markForExit)
|
|
||||||
|
|
||||||
def markForExit(self, signum, frame) -> None: # pylint: disable=unused-argument
|
|
||||||
self._isAlive = False
|
|
||||||
|
|
||||||
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
domain: str,
|
|
||||||
ou: str,
|
|
||||||
account: str,
|
|
||||||
password: str
|
|
||||||
) -> None:
|
|
||||||
pass # Not implemented for unmanaged machines
|
|
||||||
|
|
||||||
def rename(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
userName: typing.Optional[str] = None,
|
|
||||||
oldPassword: typing.Optional[str] = None,
|
|
||||||
newPassword: typing.Optional[str] = None,
|
|
||||||
) -> None:
|
|
||||||
pass # Not implemented for unmanaged machines
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
logger.debug('Running Daemon: {}'.format(self._isAlive))
|
|
||||||
|
|
||||||
# Linux daemon will continue running unless something is requested to
|
|
||||||
# Unmanaged services does not initializes "on start", but rather when user logs in (because userservice does not exists "as such" before that)
|
|
||||||
if self.isManaged(): # Currently, managed is not implemented for UDS on M
|
|
||||||
logger.error('Managed machines not supported on MacOS')
|
|
||||||
# Wait a bit, this is mac os and will be run by launchd
|
|
||||||
# If the daemon shuts down too quickly, launchd may think it is a crash.
|
|
||||||
self.doWait(10000)
|
|
||||||
|
|
||||||
self.finish()
|
|
||||||
return # Stop daemon if initializes told to do so
|
|
||||||
if not self.initializeUnmanaged():
|
|
||||||
# Wait a bit, this is mac os and will be run by launchd
|
|
||||||
# If the daemon shuts down too quickly, launchd may think it is a crash.
|
|
||||||
self.doWait(10000)
|
|
||||||
self.finish()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Start listening for petitions
|
|
||||||
self.startHttpServer()
|
|
||||||
|
|
||||||
# *********************
|
|
||||||
# * Main Service loop *
|
|
||||||
# *********************
|
|
||||||
# Counter used to check ip changes only once every 10 seconds, for
|
|
||||||
# example
|
|
||||||
counter = 0
|
|
||||||
while self._isAlive:
|
|
||||||
counter += 1
|
|
||||||
try:
|
|
||||||
if counter % 5 == 0:
|
|
||||||
self.loop()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error('Got exception on main loop: %s', e)
|
|
||||||
# In milliseconds, will break
|
|
||||||
self.doWait(1000)
|
|
||||||
|
|
||||||
self.finish()
|
|
@ -1,106 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (c) 2022 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.U. 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
|
|
||||||
'''
|
|
||||||
import os
|
|
||||||
import configparser
|
|
||||||
import base64
|
|
||||||
import pickle # nosec
|
|
||||||
|
|
||||||
from .. import types
|
|
||||||
|
|
||||||
CONFIGFILE = '/etc/udsactor/udsactor.cfg'
|
|
||||||
|
|
||||||
def readConfig() -> types.ActorConfigurationType:
|
|
||||||
try:
|
|
||||||
cfg = configparser.ConfigParser()
|
|
||||||
cfg.read(CONFIGFILE)
|
|
||||||
uds: configparser.SectionProxy = cfg['uds']
|
|
||||||
# Extract data:
|
|
||||||
base64Config = uds.get('config', None)
|
|
||||||
config = pickle.loads(base64.b64decode(base64Config.encode())) if base64Config else None # nosec: Read from root controled file, secure
|
|
||||||
|
|
||||||
base64Data = uds.get('data', None)
|
|
||||||
data = pickle.loads(base64.b64decode(base64Data.encode())) if base64Data else None # nosec: Read from root controled file, secure
|
|
||||||
|
|
||||||
return types.ActorConfigurationType(
|
|
||||||
actorType=uds.get('type', types.MANAGED),
|
|
||||||
host=uds.get('host', ''),
|
|
||||||
validateCertificate=uds.getboolean('validate', fallback=False),
|
|
||||||
master_token=uds.get('master_token', None),
|
|
||||||
own_token=uds.get('own_token', None),
|
|
||||||
restrict_net=uds.get('restrict_net', None),
|
|
||||||
pre_command=uds.get('pre_command', None),
|
|
||||||
runonce_command=uds.get('runonce_command', None),
|
|
||||||
post_command=uds.get('post_command', None),
|
|
||||||
log_level=int(uds.get('log_level', '2')),
|
|
||||||
config=config,
|
|
||||||
data=data
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
return types.ActorConfigurationType('', False)
|
|
||||||
|
|
||||||
def writeConfig(config: types.ActorConfigurationType) -> None:
|
|
||||||
cfg = configparser.ConfigParser()
|
|
||||||
cfg.add_section('uds')
|
|
||||||
uds: configparser.SectionProxy = cfg['uds']
|
|
||||||
uds['host'] = config.host
|
|
||||||
uds['validate'] = 'yes' if config.validateCertificate else 'no'
|
|
||||||
def writeIfValue(val, name):
|
|
||||||
if val:
|
|
||||||
uds[name] = val
|
|
||||||
writeIfValue(config.actorType, 'type')
|
|
||||||
writeIfValue(config.master_token, 'master_token')
|
|
||||||
writeIfValue(config.own_token, 'own_token')
|
|
||||||
writeIfValue(config.restrict_net, 'restrict_net')
|
|
||||||
writeIfValue(config.pre_command, 'pre_command')
|
|
||||||
writeIfValue(config.post_command, 'post_command')
|
|
||||||
writeIfValue(config.runonce_command, 'runonce_command')
|
|
||||||
uds['log_level'] = str(config.log_level)
|
|
||||||
if config.config: # Special case, encoded & dumped
|
|
||||||
uds['config'] = base64.b64encode(pickle.dumps(config.config)).decode()
|
|
||||||
|
|
||||||
if config.data: # Special case, encoded & dumped
|
|
||||||
uds['data'] = base64.b64encode(pickle.dumps(config.data)).decode()
|
|
||||||
|
|
||||||
# Ensures exists destination folder
|
|
||||||
dirname = os.path.dirname(CONFIGFILE)
|
|
||||||
if not os.path.exists(dirname):
|
|
||||||
os.mkdir(dirname, mode=0o700) # Will create only if route to path already exists, for example, /etc (that must... :-))
|
|
||||||
|
|
||||||
with open(CONFIGFILE, 'w') as f:
|
|
||||||
cfg.write(f)
|
|
||||||
|
|
||||||
os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root
|
|
||||||
|
|
||||||
def useOldJoinSystem() -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def invokeScriptOnLogin() -> str:
|
|
||||||
return ''
|
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2014 Virtual Cable S.L.
|
||||||
# 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.
|
||||||
#
|
#
|
||||||
@ -31,15 +31,7 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
name = sys.platform
|
name = sys.platform
|
||||||
is_windows = is_linux = is_mac = False
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
from .windows import operations, store, runner
|
from .windows import operations, store # pylint: disable=unused-import
|
||||||
is_windows = True
|
|
||||||
elif sys.platform == 'darwin':
|
|
||||||
from .macos import operations, store, runner
|
|
||||||
is_mac = True
|
|
||||||
elif sys.platform == 'linux':
|
|
||||||
from .linux import operations, store, runner
|
|
||||||
is_linux = True
|
|
||||||
else:
|
else:
|
||||||
raise Exception('Unsupported platform: {0}'.format(sys.platform))
|
from .linux import operations, store # pylint: disable=unused-import
|
||||||
|
@ -36,52 +36,42 @@ import typing
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from udsactor import types, tools
|
from . import types
|
||||||
from udsactor.version import VERSION, BUILD
|
from .info import VERSION
|
||||||
|
|
||||||
# Default public listen port
|
# Default public listen port
|
||||||
LISTEN_PORT = 43910
|
LISTEN_PORT = 43910
|
||||||
|
|
||||||
# Default timeout
|
# Default timeout
|
||||||
TIMEOUT = 5 # 5 seconds is more than enought
|
TIMEOUT = 5 # 5 seconds is more than enought
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
UNKNOWN = 'unknown'
|
UNKNOWN = 'unknown'
|
||||||
|
|
||||||
|
|
||||||
class RESTError(Exception):
|
class RESTError(Exception):
|
||||||
ERRCODE = 0
|
ERRCODE = 0
|
||||||
|
|
||||||
|
|
||||||
class RESTConnectionError(RESTError):
|
class RESTConnectionError(RESTError):
|
||||||
ERRCODE = -1
|
ERRCODE = -1
|
||||||
|
|
||||||
|
|
||||||
# Errors ""raised"" from broker
|
# Errors ""raised"" from broker
|
||||||
class RESTInvalidKeyError(RESTError):
|
class RESTInvalidKeyError(RESTError):
|
||||||
ERRCODE = 1
|
ERRCODE = 1
|
||||||
|
|
||||||
|
|
||||||
class RESTUnmanagedHostError(RESTError):
|
class RESTUnmanagedHostError(RESTError):
|
||||||
ERRCODE = 2
|
ERRCODE = 2
|
||||||
|
|
||||||
|
|
||||||
class RESTUserServiceNotFoundError(RESTError):
|
class RESTUserServiceNotFoundError(RESTError):
|
||||||
ERRCODE = 3
|
ERRCODE = 3
|
||||||
|
|
||||||
|
|
||||||
class RESTOsManagerError(RESTError):
|
class RESTOsManagerError(RESTError):
|
||||||
ERRCODE = 4
|
ERRCODE = 4
|
||||||
|
|
||||||
|
|
||||||
# For avoid proxy on localhost connections
|
# For avoid proxy on localhost connections
|
||||||
NO_PROXY = {
|
NO_PROXY = {
|
||||||
'http': None,
|
'http': None,
|
||||||
'https': None,
|
'https': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
UDS_BASE_URL = 'https://{}/uds/rest/'
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Basic UDS Api
|
# Basic UDS Api
|
||||||
#
|
#
|
||||||
@ -89,51 +79,48 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
|||||||
"""
|
"""
|
||||||
Base for remote api accesses
|
Base for remote api accesses
|
||||||
"""
|
"""
|
||||||
|
_host: str
|
||||||
_host: str = ''
|
_validateCert: bool
|
||||||
_validateCert: bool = True
|
_url: str
|
||||||
_url: str = ''
|
|
||||||
|
|
||||||
def __init__(self, host: str, validateCert: bool) -> None:
|
def __init__(self, host: str, validateCert: bool) -> None:
|
||||||
self._host = host
|
self._host = host
|
||||||
self._validateCert = validateCert
|
self._validateCert = validateCert
|
||||||
self._url = UDS_BASE_URL.format(self._host)
|
self._url = "https://{}/uds/rest/".format(self._host)
|
||||||
# Disable logging requests messages except for errors, ...
|
# Disable logging requests messages except for errors, ...
|
||||||
logging.getLogger('request').setLevel(logging.CRITICAL)
|
logging.getLogger("requests").setLevel(logging.CRITICAL)
|
||||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
logging.getLogger("urllib3").setLevel(logging.ERROR)
|
||||||
try:
|
try:
|
||||||
warnings.simplefilter('ignore') # Disables all warnings
|
warnings.simplefilter("ignore") # Disables all warnings
|
||||||
except Exception: # nosec: not interested in exceptions
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _headers(self) -> typing.MutableMapping[str, str]:
|
def _headers(self) -> typing.MutableMapping[str, str]:
|
||||||
return {
|
return {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'User-Agent': 'UDS Actor v{}/{}'.format(VERSION, BUILD),
|
'User-Agent': 'UDS Actor v{}'.format(VERSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
def _api_url(self, method: str) -> str:
|
def _apiURL(self, method: str) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _doPost(
|
def _doPost(
|
||||||
self,
|
self,
|
||||||
method: str, # i.e. 'initialize', 'ready', ....
|
method: str, # i.e. 'initialize', 'ready', ....
|
||||||
payLoad: typing.MutableMapping[str, typing.Any],
|
payLoad: typing.MutableMapping[str, typing.Any],
|
||||||
headers: typing.Optional[typing.MutableMapping[str, str]] = None,
|
headers: typing.Optional[typing.MutableMapping[str, str]] = None,
|
||||||
disableProxy: bool = False,
|
disableProxy: bool = False
|
||||||
) -> typing.Any:
|
) -> typing.Any:
|
||||||
headers = headers or self._headers
|
headers = headers or self._headers
|
||||||
try:
|
try:
|
||||||
result = requests.post(
|
result = requests.post(
|
||||||
self._api_url(method),
|
self._apiURL(method),
|
||||||
data=json.dumps(payLoad),
|
data=json.dumps(payLoad),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=self._validateCert,
|
verify=self._validateCert,
|
||||||
timeout=TIMEOUT,
|
timeout=TIMEOUT,
|
||||||
proxies=NO_PROXY # type: ignore
|
proxies=NO_PROXY if disableProxy else None # if not proxies wanted, enforce it
|
||||||
if disableProxy
|
|
||||||
else None, # if not proxies wanted, enforce it
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.ok:
|
if result.ok:
|
||||||
@ -152,22 +139,16 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
|||||||
|
|
||||||
raise RESTError(data)
|
raise RESTError(data)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# UDS Broker API access
|
# UDS Broker API access
|
||||||
#
|
#
|
||||||
class UDSServerApi(UDSApi):
|
class UDSServerApi(UDSApi):
|
||||||
def _api_url(self, method: str) -> str:
|
def _apiURL(self, method: str) -> str:
|
||||||
return self._url + 'actor/v3/' + method
|
return self._url + 'actor/v3/' + method
|
||||||
|
|
||||||
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
|
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
|
||||||
try:
|
try:
|
||||||
result = requests.get(
|
result = requests.get(self._url + 'auth/auths', headers=self._headers, verify=self._validateCert, timeout=4)
|
||||||
self._url + 'auth/auths',
|
|
||||||
headers=self._headers,
|
|
||||||
verify=self._validateCert,
|
|
||||||
timeout=4,
|
|
||||||
)
|
|
||||||
if result.ok:
|
if result.ok:
|
||||||
for v in sorted(result.json(), key=lambda x: x['priority']):
|
for v in sorted(result.json(), key=lambda x: x['priority']):
|
||||||
yield types.AuthenticatorType(
|
yield types.AuthenticatorType(
|
||||||
@ -176,24 +157,24 @@ class UDSServerApi(UDSApi):
|
|||||||
auth=v['auth'],
|
auth=v['auth'],
|
||||||
type=v['type'],
|
type=v['type'],
|
||||||
priority=v['priority'],
|
priority=v['priority'],
|
||||||
isCustom=v['isCustom'],
|
isCustom=v['isCustom']
|
||||||
)
|
)
|
||||||
except Exception: # nosec: not interested in exceptions
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def register(
|
def register( #pylint: disable=too-many-arguments, too-many-locals
|
||||||
self,
|
self,
|
||||||
auth: str,
|
auth: str,
|
||||||
username: str,
|
username: str,
|
||||||
password: str,
|
password: str,
|
||||||
hostname: str,
|
hostname: str,
|
||||||
ip: str,
|
ip: str,
|
||||||
mac: str,
|
mac: str,
|
||||||
preCommand: str,
|
preCommand: str,
|
||||||
runOnceCommand: str,
|
runOnceCommand: str,
|
||||||
postCommand: str,
|
postCommand: str,
|
||||||
logLevel: int,
|
logLevel: int
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Raises an exception if could not register, or registers and returns the "authorization token"
|
Raises an exception if could not register, or registers and returns the "authorization token"
|
||||||
"""
|
"""
|
||||||
@ -205,7 +186,7 @@ class UDSServerApi(UDSApi):
|
|||||||
'pre_command': preCommand,
|
'pre_command': preCommand,
|
||||||
'run_once_command': runOnceCommand,
|
'run_once_command': runOnceCommand,
|
||||||
'post_command': postCommand,
|
'post_command': postCommand,
|
||||||
'log_level': logLevel,
|
'log_level': logLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
# First, try to login to REST api
|
# First, try to login to REST api
|
||||||
@ -213,23 +194,13 @@ class UDSServerApi(UDSApi):
|
|||||||
# First, try to login
|
# First, try to login
|
||||||
authInfo = {'auth': auth, 'username': username, 'password': password}
|
authInfo = {'auth': auth, 'username': username, 'password': password}
|
||||||
headers = self._headers
|
headers = self._headers
|
||||||
result = requests.post(
|
result = requests.post(self._url + 'auth/login', data=json.dumps(authInfo), headers=headers, verify=self._validateCert)
|
||||||
self._url + 'auth/login',
|
|
||||||
data=json.dumps(authInfo),
|
|
||||||
headers=headers,
|
|
||||||
verify=self._validateCert,
|
|
||||||
)
|
|
||||||
if not result.ok or result.json()['result'] == 'error':
|
if not result.ok or result.json()['result'] == 'error':
|
||||||
raise Exception() # Invalid credentials
|
raise Exception() # Invalid credentials
|
||||||
|
|
||||||
headers['X-Auth-Token'] = result.json()['token']
|
headers['X-Auth-Token'] = result.json()['token']
|
||||||
|
|
||||||
result = requests.post(
|
result = requests.post(self._apiURL('register'), data=json.dumps(data), headers=headers, verify=self._validateCert)
|
||||||
self._api_url('register'),
|
|
||||||
data=json.dumps(data),
|
|
||||||
headers=headers,
|
|
||||||
verify=self._validateCert,
|
|
||||||
)
|
|
||||||
if result.ok:
|
if result.ok:
|
||||||
return result.json()['result']
|
return result.json()['result']
|
||||||
except requests.ConnectionError as e:
|
except requests.ConnectionError as e:
|
||||||
@ -241,19 +212,13 @@ class UDSServerApi(UDSApi):
|
|||||||
|
|
||||||
raise RESTError(result.content.decode())
|
raise RESTError(result.content.decode())
|
||||||
|
|
||||||
def initialize(
|
def initialize(self, token: str, interfaces: typing.Iterable[types.InterfaceInfoType], actorType: typing.Optional[str]) -> types.InitializationResultType:
|
||||||
self,
|
|
||||||
token: str,
|
|
||||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
|
||||||
actor_type: typing.Optional[str],
|
|
||||||
) -> types.InitializationResultType:
|
|
||||||
# Generate id list from netork cards
|
# Generate id list from netork cards
|
||||||
payload = {
|
payload = {
|
||||||
'type': actor_type or types.MANAGED,
|
'type': actorType or types.MANAGED,
|
||||||
'token': token,
|
'token': token,
|
||||||
'version': VERSION,
|
'version': VERSION,
|
||||||
'build': BUILD,
|
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces]
|
||||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
|
||||||
}
|
}
|
||||||
r = self._doPost('initialize', payload)
|
r = self._doPost('initialize', payload)
|
||||||
os = r['os']
|
os = r['os']
|
||||||
@ -267,115 +232,95 @@ class UDSServerApi(UDSApi):
|
|||||||
password=os.get('password'),
|
password=os.get('password'),
|
||||||
new_password=os.get('new_password'),
|
new_password=os.get('new_password'),
|
||||||
ad=os.get('ad'),
|
ad=os.get('ad'),
|
||||||
ou=os.get('ou'),
|
ou=os.get('ou')
|
||||||
)
|
) if r['os'] else None
|
||||||
if r['os']
|
|
||||||
else None,
|
|
||||||
alias_token=r.get('alias_token'), # Possible alias for unmanaged
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def ready(
|
def ready(self, own_token: str, secret: str, ip: str, port: int) -> types.CertificateInfoType:
|
||||||
self, own_token: str, secret: str, ip: str, port: int
|
payload = {
|
||||||
) -> types.CertificateInfoType:
|
'token': own_token,
|
||||||
payload = {'token': own_token, 'secret': secret, 'ip': ip, 'port': port}
|
'secret': secret,
|
||||||
|
'ip': ip,
|
||||||
|
'port': port
|
||||||
|
}
|
||||||
result = self._doPost('ready', payload)
|
result = self._doPost('ready', payload)
|
||||||
|
|
||||||
return types.CertificateInfoType(
|
return types.CertificateInfoType(
|
||||||
private_key=result['private_key'],
|
private_key=result['private_key'],
|
||||||
server_certificate=result['server_certificate'],
|
server_certificate=result['server_certificate'],
|
||||||
password=result['password'],
|
password=result['password']
|
||||||
)
|
)
|
||||||
|
|
||||||
def notifyIpChange(
|
def notifyIpChange(self, own_token: str, secret: str, ip: str, port: int) -> types.CertificateInfoType:
|
||||||
self, own_token: str, secret: str, ip: str, port: int
|
payload = {
|
||||||
) -> types.CertificateInfoType:
|
'token': own_token,
|
||||||
payload = {'token': own_token, 'secret': secret, 'ip': ip, 'port': port}
|
'secret': secret,
|
||||||
|
'ip': ip,
|
||||||
|
'port': port
|
||||||
|
}
|
||||||
result = self._doPost('ipchange', payload)
|
result = self._doPost('ipchange', payload)
|
||||||
|
|
||||||
return types.CertificateInfoType(
|
return types.CertificateInfoType(
|
||||||
private_key=result['private_key'],
|
private_key=result['private_key'],
|
||||||
server_certificate=result['server_certificate'],
|
server_certificate=result['server_certificate'],
|
||||||
password=result['password'],
|
password=result['password']
|
||||||
)
|
)
|
||||||
|
|
||||||
def notifyUnmanagedCallback(
|
def notifyUnmanagedCallback(self, master_token: str, secret: str, interfaces: typing.Iterable[types.InterfaceInfoType], port: int) -> types.CertificateInfoType:
|
||||||
self,
|
|
||||||
master_token: str,
|
|
||||||
secret: str,
|
|
||||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
|
||||||
port: int,
|
|
||||||
) -> types.CertificateInfoType:
|
|
||||||
payload = {
|
payload = {
|
||||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||||
'token': master_token,
|
'token': master_token,
|
||||||
'secret': secret,
|
'secret': secret,
|
||||||
'port': port,
|
'port': port
|
||||||
}
|
}
|
||||||
result = self._doPost('unmanaged', payload)
|
result = self._doPost('unmanaged', payload)
|
||||||
|
|
||||||
return types.CertificateInfoType(
|
return types.CertificateInfoType(
|
||||||
private_key=result['private_key'],
|
private_key=result['private_key'],
|
||||||
server_certificate=result['server_certificate'],
|
server_certificate=result['server_certificate'],
|
||||||
password=result['password'],
|
password=result['password']
|
||||||
)
|
)
|
||||||
|
|
||||||
def login(
|
|
||||||
self,
|
def login(self, own_token: str, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||||
actor_type: typing.Optional[str],
|
if not own_token:
|
||||||
token: str,
|
|
||||||
username: str,
|
|
||||||
session_type: str,
|
|
||||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
|
||||||
secret: typing.Optional[str],
|
|
||||||
) -> types.LoginResultInfoType:
|
|
||||||
if not token:
|
|
||||||
return types.LoginResultInfoType(
|
return types.LoginResultInfoType(
|
||||||
ip='0.0.0.0', hostname=UNKNOWN, dead_line=None, max_idle=None, session_id=None
|
ip='0.0.0.0',
|
||||||
|
hostname=UNKNOWN,
|
||||||
|
dead_line=None,
|
||||||
|
max_idle=None
|
||||||
)
|
)
|
||||||
payload = {
|
payload = {
|
||||||
'type': actor_type or types.MANAGED,
|
'token': own_token,
|
||||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
|
||||||
'token': token,
|
|
||||||
'username': username,
|
'username': username,
|
||||||
'session_type': session_type,
|
'session_type': sessionType or UNKNOWN
|
||||||
'secret': secret or '',
|
|
||||||
}
|
}
|
||||||
result = self._doPost('login', payload)
|
result = self._doPost('login', payload)
|
||||||
return types.LoginResultInfoType(
|
return types.LoginResultInfoType(
|
||||||
ip=result['ip'],
|
ip=result['ip'],
|
||||||
hostname=result['hostname'],
|
hostname=result['hostname'],
|
||||||
dead_line=result['dead_line'],
|
dead_line=result['dead_line'],
|
||||||
max_idle=result['max_idle'],
|
max_idle=result['max_idle']
|
||||||
session_id=result.get('session_id', ''),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def logout(
|
def logout(self, own_token: str, username: str) -> None:
|
||||||
self,
|
if not own_token:
|
||||||
actor_type: typing.Optional[str],
|
return
|
||||||
token: str,
|
|
||||||
username: str,
|
|
||||||
session_id: str,
|
|
||||||
session_type: str,
|
|
||||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
|
||||||
secret: typing.Optional[str],
|
|
||||||
) -> typing.Optional[str]:
|
|
||||||
if not token:
|
|
||||||
return None
|
|
||||||
payload = {
|
payload = {
|
||||||
'type': actor_type or types.MANAGED,
|
'token': own_token,
|
||||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
'username': username
|
||||||
'token': token,
|
|
||||||
'username': username,
|
|
||||||
'session_type': session_type,
|
|
||||||
'session_id': session_id,
|
|
||||||
'secret': secret or '',
|
|
||||||
}
|
}
|
||||||
return self._doPost('logout', payload) # Can be 'ok' or 'notified'
|
self._doPost('logout', payload)
|
||||||
|
|
||||||
|
|
||||||
def log(self, own_token: str, level: int, message: str) -> None:
|
def log(self, own_token: str, level: int, message: str) -> None:
|
||||||
if not own_token:
|
if not own_token:
|
||||||
return
|
return
|
||||||
payLoad = {'token': own_token, 'level': level, 'message': message}
|
payLoad = {
|
||||||
|
'token': own_token,
|
||||||
|
'level': level,
|
||||||
|
'message': message
|
||||||
|
}
|
||||||
self._doPost('log', payLoad) # Ignores result...
|
self._doPost('log', payLoad) # Ignores result...
|
||||||
|
|
||||||
def test(self, master_token: str, actorType: typing.Optional[str]) -> bool:
|
def test(self, master_token: str, actorType: typing.Optional[str]) -> bool:
|
||||||
@ -386,62 +331,50 @@ class UDSServerApi(UDSApi):
|
|||||||
return self._doPost('test', payLoad) == 'ok'
|
return self._doPost('test', payLoad) == 'ok'
|
||||||
|
|
||||||
|
|
||||||
class UDSClientApi(UDSApi, metaclass=tools.Singleton):
|
class UDSClientApi(UDSApi):
|
||||||
_session_id: str = ''
|
|
||||||
_callback_url: str = ''
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__('127.0.0.1:{}'.format(LISTEN_PORT), False)
|
super().__init__('127.0.0.1:{}'.format(LISTEN_PORT), False)
|
||||||
|
# Override base url
|
||||||
# Replace base url
|
|
||||||
self._url = "https://{}/ui/".format(self._host)
|
self._url = "https://{}/ui/".format(self._host)
|
||||||
|
|
||||||
def _api_url(self, method: str) -> str:
|
def _apiURL(self, method: str) -> str:
|
||||||
return self._url + method
|
return self._url + method
|
||||||
|
|
||||||
def post(
|
def post(
|
||||||
self,
|
self,
|
||||||
method: str, # i.e. 'initialize', 'ready', ....
|
method: str, # i.e. 'initialize', 'ready', ....
|
||||||
payLoad: typing.MutableMapping[str, typing.Any],
|
payLoad: typing.MutableMapping[str, typing.Any]
|
||||||
) -> typing.Any:
|
) -> typing.Any:
|
||||||
return self._doPost(method=method, payLoad=payLoad, disableProxy=True)
|
return self._doPost(method=method, payLoad=payLoad, disableProxy=True)
|
||||||
|
|
||||||
def register(self, callback_url: str) -> None:
|
def register(self, callbackUrl: str) -> None:
|
||||||
self._callback_url = callback_url
|
payLoad = {
|
||||||
payLoad = {'callback_url': callback_url}
|
'callback_url': callbackUrl
|
||||||
|
}
|
||||||
self.post('register', payLoad)
|
self.post('register', payLoad)
|
||||||
|
|
||||||
def unregister(self, callback_url: str) -> None:
|
def unregister(self, callbackUrl: str) -> None:
|
||||||
payLoad = {'callback_url': callback_url}
|
payLoad = {
|
||||||
|
'callback_url': callbackUrl
|
||||||
|
}
|
||||||
self.post('unregister', payLoad)
|
self.post('unregister', payLoad)
|
||||||
self._callback_url = ''
|
|
||||||
|
|
||||||
def login(
|
def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||||
self, username: str, sessionType: typing.Optional[str] = None
|
|
||||||
) -> types.LoginResultInfoType:
|
|
||||||
payLoad = {
|
payLoad = {
|
||||||
'username': username,
|
'username': username,
|
||||||
'session_type': sessionType or UNKNOWN,
|
'session_type': sessionType or UNKNOWN,
|
||||||
'callback_url': self._callback_url, # So we identify ourselves
|
|
||||||
}
|
}
|
||||||
result = self.post('login', payLoad)
|
result = self.post('login', payLoad)
|
||||||
res = types.LoginResultInfoType(
|
return types.LoginResultInfoType(
|
||||||
ip=result['ip'],
|
ip=result['ip'],
|
||||||
hostname=result['hostname'],
|
hostname=result['hostname'],
|
||||||
dead_line=result['dead_line'],
|
dead_line=result['dead_line'],
|
||||||
max_idle=result['max_idle'],
|
max_idle=result['max_idle']
|
||||||
session_id=result['session_id'],
|
|
||||||
)
|
)
|
||||||
# Store session id for future use
|
|
||||||
self._session_id = res.session_id or ''
|
|
||||||
return res
|
|
||||||
|
|
||||||
def logout(self, username: str, sessionType: typing.Optional[str]) -> None:
|
def logout(self, username: str) -> None:
|
||||||
payLoad = {
|
payLoad = {
|
||||||
'username': username,
|
'username': username
|
||||||
'session_type': sessionType or UNKNOWN,
|
|
||||||
'callback_url': self._callback_url, # So we identify ourselves
|
|
||||||
'session_id': self._session_id, # We now know the session id, provided on login
|
|
||||||
}
|
}
|
||||||
self.post('logout', payLoad)
|
self.post('logout', payLoad)
|
||||||
|
|
||||||
|
@ -36,13 +36,12 @@ import secrets
|
|||||||
import subprocess
|
import subprocess
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from udsactor import platform
|
from . import platform
|
||||||
from udsactor import rest
|
from . import rest
|
||||||
from udsactor import types
|
from . import types
|
||||||
from udsactor import tools
|
|
||||||
|
|
||||||
from udsactor.log import logger, DEBUG, INFO, ERROR, FATAL
|
from .log import logger, DEBUG, INFO, ERROR, FATAL
|
||||||
from udsactor.http import clients_pool, server, cert
|
from .http import clients_pool, server, cert
|
||||||
|
|
||||||
# def setup() -> None:
|
# def setup() -> None:
|
||||||
# cfg = platform.store.readConfig()
|
# cfg = platform.store.readConfig()
|
||||||
@ -56,16 +55,18 @@ from udsactor.http import clients_pool, server, cert
|
|||||||
# else:
|
# else:
|
||||||
# logger.setLevel(20000)
|
# logger.setLevel(20000)
|
||||||
|
|
||||||
|
|
||||||
class CommonService: # pylint: disable=too-many-instance-attributes
|
class CommonService: # pylint: disable=too-many-instance-attributes
|
||||||
_isAlive: bool = True
|
_isAlive: bool = True
|
||||||
_rebootRequested: bool = False
|
_rebootRequested: bool = False
|
||||||
|
_loggedIn: bool = False
|
||||||
_initialized: bool = False
|
_initialized: bool = False
|
||||||
|
|
||||||
_cfg: types.ActorConfigurationType
|
_cfg: types.ActorConfigurationType
|
||||||
_api: rest.UDSServerApi
|
_api: rest.UDSServerApi
|
||||||
_interfaces: typing.List[types.InterfaceInfoType]
|
_interfaces: typing.List[types.InterfaceInfoType]
|
||||||
_secret: str
|
_secret: str
|
||||||
_certificate: types.CertificateInfoType
|
_certificate: types.CertificateInfoType
|
||||||
|
_clientsPool: clients_pool.UDSActorClientPool
|
||||||
_http: typing.Optional[server.HTTPServerThread]
|
_http: typing.Optional[server.HTTPServerThread]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -74,9 +75,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
logger.debug('Executing command on {}: {}'.format(section, cmdLine))
|
logger.debug('Executing command on {}: {}'.format(section, cmdLine))
|
||||||
res = subprocess.check_call(cmdLine, shell=True)
|
res = subprocess.check_call(cmdLine, shell=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error('Got exception executing: {} - {} - {}'.format(section, cmdLine, e))
|
||||||
'Got exception executing: {} - {} - {}'.format(section, cmdLine, e)
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
logger.debug('Result of executing cmd for {} was {}'.format(section, res))
|
logger.debug('Result of executing cmd for {} was {}'.format(section, res))
|
||||||
return True
|
return True
|
||||||
@ -87,9 +86,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
self._api = rest.UDSServerApi(self._cfg.host, self._cfg.validateCertificate)
|
self._api = rest.UDSServerApi(self._cfg.host, self._cfg.validateCertificate)
|
||||||
self._secret = secrets.token_urlsafe(33)
|
self._secret = secrets.token_urlsafe(33)
|
||||||
self._clientsPool = clients_pool.UDSActorClientPool()
|
self._clientsPool = clients_pool.UDSActorClientPool()
|
||||||
self._certificate = (
|
self._certificate = cert.defaultCertificate # For being used on "unmanaged" hosts only
|
||||||
cert.defaultCertificate
|
|
||||||
) # For being used on "unmanaged" hosts only
|
|
||||||
self._http = None
|
self._http = None
|
||||||
|
|
||||||
# Initialzies loglevel and serviceLogger
|
# Initialzies loglevel and serviceLogger
|
||||||
@ -98,7 +95,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
# 0 = OTHER, 10000 = DEBUG, 20000 = WARN, 30000 = INFO, 40000 = ERROR, 50000 = FATAL
|
# 0 = OTHER, 10000 = DEBUG, 20000 = WARN, 30000 = INFO, 40000 = ERROR, 50000 = FATAL
|
||||||
# So this comes:
|
# So this comes:
|
||||||
logger.setLevel([DEBUG, INFO, ERROR, FATAL][self._cfg.log_level])
|
logger.setLevel([DEBUG, INFO, ERROR, FATAL][self._cfg.log_level])
|
||||||
# If windows, enable service logger FOR SERVICE only
|
# If windows, enable service logger
|
||||||
logger.enableServiceLogger()
|
logger.enableServiceLogger()
|
||||||
|
|
||||||
socket.setdefaulttimeout(20)
|
socket.setdefaulttimeout(20)
|
||||||
@ -115,24 +112,16 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
self._http.start()
|
self._http.start()
|
||||||
|
|
||||||
def isManaged(self) -> bool:
|
def isManaged(self) -> bool:
|
||||||
return (
|
return self._cfg.actorType != types.UNMANAGED # Only "unmanaged" hosts are unmanaged, the rest are "managed"
|
||||||
self._cfg.actorType != types.UNMANAGED
|
|
||||||
) # Only "unmanaged" hosts are unmanaged, the rest are "managed"
|
|
||||||
|
|
||||||
def serviceInterfaceInfo(
|
def serviceInterfaceInfo(self, interfaces: typing.Optional[typing.List[types.InterfaceInfoType]] = None) -> typing.Optional[types.InterfaceInfoType]:
|
||||||
self, interfaces: typing.Optional[typing.List[types.InterfaceInfoType]] = None
|
|
||||||
) -> typing.Optional[types.InterfaceInfoType]:
|
|
||||||
"""
|
"""
|
||||||
returns the inteface with unique_id mac or first interface or None if no interfaces...
|
returns the inteface with unique_id mac or first interface or None if no interfaces...
|
||||||
"""
|
"""
|
||||||
interfaces = (
|
interfaces = interfaces or self._interfaces # Emty interfaces is like "no ip change" because cannot be notified
|
||||||
interfaces or self._interfaces
|
|
||||||
) # Emty interfaces is like "no ip change" because cannot be notified
|
|
||||||
if self._cfg.config and interfaces:
|
if self._cfg.config and interfaces:
|
||||||
try:
|
try:
|
||||||
return next(
|
return next(x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id)
|
||||||
x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id
|
|
||||||
)
|
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return interfaces[0]
|
return interfaces[0]
|
||||||
|
|
||||||
@ -163,12 +152,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
while self._isAlive:
|
while self._isAlive:
|
||||||
counter -= 1
|
counter -= 1
|
||||||
try:
|
try:
|
||||||
self._certificate = self._api.ready(
|
self._certificate = self._api.ready(self._cfg.own_token, self._secret, srvInterface.ip, rest.LISTEN_PORT)
|
||||||
self._cfg.own_token,
|
|
||||||
self._secret,
|
|
||||||
srvInterface.ip,
|
|
||||||
rest.LISTEN_PORT,
|
|
||||||
)
|
|
||||||
except rest.RESTConnectionError as e:
|
except rest.RESTConnectionError as e:
|
||||||
if not logged: # Only log connection problems ONCE
|
if not logged: # Only log connection problems ONCE
|
||||||
logged = True
|
logged = True
|
||||||
@ -184,9 +168,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
|
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error('Could not locate IP address!!!. (Not registered with UDS)')
|
||||||
'Could not locate IP address!!!. (Not registered with UDS)'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Do not continue if not alive...
|
# Do not continue if not alive...
|
||||||
if not self._isAlive:
|
if not self._isAlive:
|
||||||
@ -194,9 +176,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
# Cleans sensible data
|
# Cleans sensible data
|
||||||
if self._cfg.config:
|
if self._cfg.config:
|
||||||
self._cfg = self._cfg._replace(
|
self._cfg = self._cfg._replace(config=self._cfg.config._replace(os=None), data=None)
|
||||||
config=self._cfg.config._replace(os=None), data=None
|
|
||||||
)
|
|
||||||
platform.store.writeConfig(self._cfg)
|
platform.store.writeConfig(self._cfg)
|
||||||
|
|
||||||
logger.info('Service ready')
|
logger.info('Service ready')
|
||||||
@ -215,10 +195,10 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
self._cfg = self._cfg._replace(runonce_command=None)
|
self._cfg = self._cfg._replace(runonce_command=None)
|
||||||
platform.store.writeConfig(self._cfg)
|
platform.store.writeConfig(self._cfg)
|
||||||
if self.execute(runOnce, "runOnce"):
|
if self.execute(runOnce, "runOnce"):
|
||||||
# If runonce is present, will not do anythin more
|
# If runonce is present, will not do anythin more
|
||||||
# So we have to ensure that, when runonce command is finished, reboots the machine.
|
# So we have to ensure that, when runonce command is finished, reboots the machine.
|
||||||
# That is, the COMMAND itself has to restart the machine!
|
# That is, the COMMAND itself has to restart the machine!
|
||||||
return False # If the command fails, continue with the rest of the operations...
|
return False # If the command fails, continue with the rest of the operations...
|
||||||
|
|
||||||
# Retry configuration while not stop service, config in case of error 10 times, reboot vm
|
# Retry configuration while not stop service, config in case of error 10 times, reboot vm
|
||||||
counter = 10
|
counter = 10
|
||||||
@ -228,20 +208,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
if self._cfg.config and self._cfg.config.os:
|
if self._cfg.config and self._cfg.config.os:
|
||||||
osData = self._cfg.config.os
|
osData = self._cfg.config.os
|
||||||
if osData.action == 'rename':
|
if osData.action == 'rename':
|
||||||
self.rename(
|
self.rename(osData.name, osData.username, osData.password, osData.new_password)
|
||||||
osData.name,
|
|
||||||
osData.username,
|
|
||||||
osData.password,
|
|
||||||
osData.new_password,
|
|
||||||
)
|
|
||||||
elif osData.action == 'rename_ad':
|
elif osData.action == 'rename_ad':
|
||||||
self.joinDomain(
|
self.joinDomain(osData.name, osData.ad or '', osData.ou or '', osData.username or '', osData.password or '')
|
||||||
osData.name,
|
|
||||||
osData.ad or '',
|
|
||||||
osData.ou or '',
|
|
||||||
osData.username or '',
|
|
||||||
osData.password or '',
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._rebootRequested:
|
if self._rebootRequested:
|
||||||
try:
|
try:
|
||||||
@ -265,12 +234,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
self.getInterfaces() # Ensure we have interfaces
|
self.getInterfaces() # Ensure we have interfaces
|
||||||
if self._cfg.master_token:
|
if self._cfg.master_token:
|
||||||
try:
|
try:
|
||||||
self._certificate = self._api.notifyUnmanagedCallback(
|
self._certificate = self._api.notifyUnmanagedCallback(self._cfg.master_token, self._secret, self._interfaces, rest.LISTEN_PORT)
|
||||||
self._cfg.master_token,
|
|
||||||
self._secret,
|
|
||||||
self._interfaces,
|
|
||||||
rest.LISTEN_PORT,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Couuld not notify unmanaged callback: %s', e)
|
logger.error('Couuld not notify unmanaged callback: %s', e)
|
||||||
|
|
||||||
@ -281,17 +245,13 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
return
|
return
|
||||||
|
|
||||||
while self._isAlive:
|
while self._isAlive:
|
||||||
self._interfaces = tools.validNetworkCards(
|
self._interfaces = list(platform.operations.getNetworkInfo())
|
||||||
self._cfg.restrict_net, platform.operations.getNetworkInfo()
|
|
||||||
)
|
|
||||||
if self._interfaces:
|
if self._interfaces:
|
||||||
break
|
break
|
||||||
self.doWait(5000)
|
self.doWait(5000)
|
||||||
|
|
||||||
def initialize(self) -> bool:
|
def initialize(self) -> bool:
|
||||||
if (
|
if self._initialized or not self._cfg.host or not self._isAlive: # Not configured or not running
|
||||||
self._initialized or not self._cfg.host or not self._isAlive
|
|
||||||
): # Not configured or not running
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
@ -308,37 +268,25 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
try:
|
try:
|
||||||
# If master token is present, initialize and get configuration data
|
# If master token is present, initialize and get configuration data
|
||||||
if self._cfg.master_token:
|
if self._cfg.master_token:
|
||||||
initResult: types.InitializationResultType = self._api.initialize(
|
initResult: types.InitializationResultType = self._api.initialize(self._cfg.master_token, self._interfaces, self._cfg.actorType)
|
||||||
self._cfg.master_token, self._interfaces, self._cfg.actorType
|
|
||||||
)
|
|
||||||
if not initResult.own_token: # Not managed
|
if not initResult.own_token: # Not managed
|
||||||
logger.debug(
|
logger.debug('This host is not managed by UDS Broker (ids: {})'.format(self._interfaces))
|
||||||
'This host is not managed by UDS Broker (ids: {})'.format(
|
|
||||||
self._interfaces
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Only removes master token for managed machines (will need it on next client execution)
|
# Only removes token for managed machines
|
||||||
# For unmanaged, if alias is present, replace master token with it
|
master_token = None if self.isManaged() else self._cfg.master_token
|
||||||
master_token = (
|
|
||||||
None
|
|
||||||
if self.isManaged()
|
|
||||||
else (initResult.alias_token or self._cfg.master_token)
|
|
||||||
)
|
|
||||||
# Replace master token with alias token if present
|
|
||||||
self._cfg = self._cfg._replace(
|
self._cfg = self._cfg._replace(
|
||||||
master_token=master_token,
|
master_token=master_token,
|
||||||
own_token=initResult.own_token,
|
own_token=initResult.own_token,
|
||||||
config=types.ActorDataConfigurationType(
|
config=types.ActorDataConfigurationType(
|
||||||
unique_id=initResult.unique_id, os=initResult.os
|
unique_id=initResult.unique_id,
|
||||||
),
|
os=initResult.os
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# On first successfull initialization request, master token will dissapear for managed hosts
|
# On first successfull initialization request, master token will dissapear for managed hosts so it will be no more available (not needed anyway)
|
||||||
# so it will be no more available (not needed anyway). For unmanaged, the master token will
|
if self.isManaged():
|
||||||
# be replaced with an alias token.
|
platform.store.writeConfig(self._cfg)
|
||||||
platform.store.writeConfig(self._cfg)
|
|
||||||
|
|
||||||
# Setup logger now
|
# Setup logger now
|
||||||
if self._cfg.own_token:
|
if self._cfg.own_token:
|
||||||
@ -346,51 +294,29 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
break # Initial configuration done..
|
break # Initial configuration done..
|
||||||
except rest.RESTConnectionError as e:
|
except rest.RESTConnectionError as e:
|
||||||
logger.info(
|
logger.info('Trying to inititialize connection with broker (last error: {})'.format(e))
|
||||||
'Trying to inititialize connection with broker (last error: {})'.format(
|
|
||||||
e
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.doWait(5000) # Wait a bit and retry
|
self.doWait(5000) # Wait a bit and retry
|
||||||
except rest.RESTError as e: # Invalid key?
|
except rest.RESTError as e: # Invalid key?
|
||||||
logger.error(
|
logger.error('Error validating with broker. (Invalid token?): {}'.format(e))
|
||||||
'Error validating with broker. (Invalid token?): {}'.format(e)
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
except Exception:
|
|
||||||
logger.exception()
|
|
||||||
self.doWait(5000) # Wait a bit and retry...
|
|
||||||
|
|
||||||
return self.configureMachine()
|
return self.configureMachine()
|
||||||
|
|
||||||
def uninitialize(self):
|
def uninitialize(self):
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
self._cfg = self._cfg._replace(
|
self._cfg = self._cfg._replace(own_token=None) # Ensures assigned token is cleared
|
||||||
own_token=None
|
|
||||||
) # Ensures assigned token is cleared
|
|
||||||
|
|
||||||
def finish(self) -> None:
|
def finish(self) -> None:
|
||||||
if self._http:
|
if self._http:
|
||||||
self._http.stop()
|
self._http.stop()
|
||||||
|
|
||||||
# If logged in, notify UDS of logout (daemon stoped = no control = logout)
|
# If logged in, notify UDS of logout (daemon stoped = no control = logout)
|
||||||
# For every connected client...
|
if self._loggedIn and self._cfg.own_token:
|
||||||
if self._cfg.own_token:
|
self._loggedIn = False
|
||||||
for client in clients_pool.UDSActorClientPool().clients:
|
try:
|
||||||
if client.session_id:
|
self._api.logout(self._cfg.own_token, '')
|
||||||
try:
|
except Exception as e:
|
||||||
self._api.logout(
|
logger.error('Error notifying final logout to UDS: %s', e)
|
||||||
self._cfg.actorType,
|
|
||||||
self._cfg.own_token,
|
|
||||||
'',
|
|
||||||
client.session_id
|
|
||||||
or 'stop', # If no session id, pass "stop"
|
|
||||||
'',
|
|
||||||
self._interfaces,
|
|
||||||
self._secret,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error('Error notifying final logout to UDS: %s', e)
|
|
||||||
|
|
||||||
self.notifyStop()
|
self.notifyStop()
|
||||||
|
|
||||||
@ -399,33 +325,19 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
return # Unamanaged hosts does not changes ips. (The full initialize-login-logout process is done in a row, so at login the IP is correct)
|
return # Unamanaged hosts does not changes ips. (The full initialize-login-logout process is done in a row, so at login the IP is correct)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if (
|
if not self._cfg.own_token or not self._cfg.config or not self._cfg.config.unique_id:
|
||||||
not self._cfg.own_token
|
|
||||||
or not self._cfg.config
|
|
||||||
or not self._cfg.config.unique_id
|
|
||||||
):
|
|
||||||
# Not enouth data do check
|
# Not enouth data do check
|
||||||
return
|
return
|
||||||
currentInterfaces = tools.validNetworkCards(
|
currentInterfaces = list(platform.operations.getNetworkInfo())
|
||||||
self._cfg.restrict_net, platform.operations.getNetworkInfo()
|
|
||||||
)
|
|
||||||
old = self.serviceInterfaceInfo()
|
old = self.serviceInterfaceInfo()
|
||||||
new = self.serviceInterfaceInfo(currentInterfaces)
|
new = self.serviceInterfaceInfo(currentInterfaces)
|
||||||
if not new or not old:
|
if not new or not old:
|
||||||
raise Exception(
|
raise Exception('No ip currently available for {}'.format(self._cfg.config.unique_id))
|
||||||
'No ip currently available for {}'.format(
|
|
||||||
self._cfg.config.unique_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if old.ip != new.ip:
|
if old.ip != new.ip:
|
||||||
self._certificate = self._api.notifyIpChange(
|
self._certificate = self._api.notifyIpChange(self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT)
|
||||||
self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT
|
|
||||||
)
|
|
||||||
# Now store new addresses & interfaces...
|
# Now store new addresses & interfaces...
|
||||||
self._interfaces = currentInterfaces
|
self._interfaces = currentInterfaces
|
||||||
logger.info(
|
logger.info('Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip))
|
||||||
'Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip)
|
|
||||||
)
|
|
||||||
# Stop the running HTTP Thread and start a new one, with new generated cert
|
# Stop the running HTTP Thread and start a new one, with new generated cert
|
||||||
self.startHttpServer()
|
self.startHttpServer()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -433,34 +345,29 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
logger.warn('Checking ips failed: {}'.format(e))
|
logger.warn('Checking ips failed: {}'.format(e))
|
||||||
|
|
||||||
def rename(
|
def rename(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
userName: typing.Optional[str] = None,
|
userName: typing.Optional[str] = None,
|
||||||
oldPassword: typing.Optional[str] = None,
|
oldPassword: typing.Optional[str] = None,
|
||||||
newPassword: typing.Optional[str] = None,
|
newPassword: typing.Optional[str] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
Invoked when broker requests a rename action
|
Invoked when broker requests a rename action
|
||||||
default does nothing
|
default does nothing
|
||||||
'''
|
'''
|
||||||
hostName = platform.operations.getComputerName()
|
hostName = platform.operations.getComputerName()
|
||||||
|
|
||||||
|
if hostName.lower() == name.lower():
|
||||||
|
logger.info('Computer name is already {}'.format(hostName))
|
||||||
|
return
|
||||||
|
|
||||||
# Check for password change request for an user
|
# Check for password change request for an user
|
||||||
if userName and newPassword:
|
if userName and newPassword:
|
||||||
logger.info('Setting password for configured user')
|
logger.info('Setting password for configured user')
|
||||||
try:
|
try:
|
||||||
platform.operations.changeUserPassword(
|
platform.operations.changeUserPassword(userName, oldPassword or '', newPassword)
|
||||||
userName, oldPassword or '', newPassword
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Logs error, but continue renaming computer
|
raise Exception('Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(userName, str(e)))
|
||||||
logger.error(
|
|
||||||
'Could not change password for user {}: {}'.format(userName, e)
|
|
||||||
)
|
|
||||||
|
|
||||||
if hostName.lower() == name.lower():
|
|
||||||
logger.info('Computer name is already {}'.format(hostName))
|
|
||||||
return
|
|
||||||
|
|
||||||
if platform.operations.renameComputer(name):
|
if platform.operations.renameComputer(name):
|
||||||
self.reboot()
|
self.reboot()
|
||||||
@ -472,9 +379,8 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
self.checkIpsChanged()
|
self.checkIpsChanged()
|
||||||
|
|
||||||
# Now check if every registered client is already there (if logged in OFC)
|
# Now check if every registered client is already there (if logged in OFC)
|
||||||
for lost_client in clients_pool.UDSActorClientPool().lost_clients():
|
if self._loggedIn and not self._clientsPool.ping():
|
||||||
logger.info('Lost client: {}'.format(lost_client))
|
self.logout('client_unavailable')
|
||||||
self.logout('client_unavailable', '', lost_client.session_id or '') # '' means "all clients"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Exception on main service loop: %s', e)
|
logger.error('Exception on main service loop: %s', e)
|
||||||
|
|
||||||
@ -482,8 +388,13 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
# Methods that can be overriden by linux & windows Actor
|
# Methods that can be overriden by linux & windows Actor
|
||||||
# ******************************************************
|
# ******************************************************
|
||||||
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
|
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
|
||||||
self, name: str, domain: str, ou: str, account: str, password: str
|
self,
|
||||||
) -> None:
|
name: str,
|
||||||
|
domain: str,
|
||||||
|
ou: str,
|
||||||
|
account: str,
|
||||||
|
password: str
|
||||||
|
) -> None:
|
||||||
'''
|
'''
|
||||||
Invoked when broker requests a "domain" action
|
Invoked when broker requests a "domain" action
|
||||||
default does nothing
|
default does nothing
|
||||||
@ -491,86 +402,30 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
logger.debug('Base join invoked: {} on {}, {}'.format(name, domain, ou))
|
logger.debug('Base join invoked: {} on {}, {}'.format(name, domain, ou))
|
||||||
|
|
||||||
# Client notifications
|
# Client notifications
|
||||||
def login(
|
def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||||
self, username: str, sessionType: typing.Optional[str] = None
|
result = types.LoginResultInfoType(ip='', hostname='', dead_line=None, max_idle=None)
|
||||||
) -> types.LoginResultInfoType:
|
self._loggedIn = True
|
||||||
result = types.LoginResultInfoType(
|
|
||||||
ip='', hostname='', dead_line=None, max_idle=None, session_id=None
|
|
||||||
)
|
|
||||||
master_token = None
|
|
||||||
secret = None
|
|
||||||
# If unmanaged, do initialization now, because we don't know before this
|
|
||||||
# Also, even if not initialized, get a "login" notification token
|
|
||||||
if not self.isManaged():
|
if not self.isManaged():
|
||||||
self._initialized = (
|
self.initialize()
|
||||||
self.initialize()
|
|
||||||
) # Maybe it's a local login by an unmanaged host.... On real login, will execute initilize again
|
|
||||||
# Unamanaged, need the master token
|
|
||||||
master_token = self._cfg.master_token
|
|
||||||
secret = self._secret
|
|
||||||
|
|
||||||
# Own token will not be set if UDS did not assigned the initialized VM to an user
|
if self._cfg.own_token:
|
||||||
# In that case, take master token (if machine is Unamanaged version)
|
result = self._api.login(self._cfg.own_token, username, sessionType)
|
||||||
token = self._cfg.own_token or master_token
|
|
||||||
if token:
|
|
||||||
result = self._api.login(
|
|
||||||
self._cfg.actorType,
|
|
||||||
token,
|
|
||||||
username,
|
|
||||||
sessionType or '',
|
|
||||||
self._interfaces,
|
|
||||||
secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
result.session_id
|
|
||||||
): # If logged in, process it. client_pool will take account of login response to client and session
|
|
||||||
script = platform.store.invokeScriptOnLogin()
|
|
||||||
if script:
|
|
||||||
logger.info('Executing script on login: {}'.format(script))
|
|
||||||
script += f'{username} {sessionType or "unknown"} {self._cfg.actorType}'
|
|
||||||
self.execute(script, 'Logon')
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def logout(
|
def logout(self, username: str) -> None:
|
||||||
self,
|
self._loggedIn = False
|
||||||
username: str,
|
if self._cfg.own_token:
|
||||||
session_type: typing.Optional[str],
|
self._api.logout(self._cfg.own_token, username)
|
||||||
session_id: typing.Optional[str],
|
|
||||||
) -> None:
|
|
||||||
master_token = self._cfg.master_token
|
|
||||||
|
|
||||||
# Own token will not be set if UDS did not assigned the initialized VM to an user
|
self.onLogout(username)
|
||||||
# In that case, take master token (if machine is Unamanaged version)
|
|
||||||
token = self._cfg.own_token or master_token
|
|
||||||
if token:
|
|
||||||
# If logout is not processed (that is, not ok result), the logout has not been processed
|
|
||||||
if (
|
|
||||||
self._api.logout(
|
|
||||||
self._cfg.actorType,
|
|
||||||
token,
|
|
||||||
username,
|
|
||||||
session_id or '',
|
|
||||||
session_type or '',
|
|
||||||
self._interfaces,
|
|
||||||
self._secret,
|
|
||||||
)
|
|
||||||
!= 'ok' # Can return also "notified", that means the logout has not been processed by UDS
|
|
||||||
):
|
|
||||||
logger.info(
|
|
||||||
'Logout from %s ignored as required by uds broker', username
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.onLogout(username, session_id or '')
|
|
||||||
|
|
||||||
if not self.isManaged():
|
if not self.isManaged():
|
||||||
self.uninitialize()
|
self.uninitialize()
|
||||||
|
|
||||||
# ******************************************************
|
# ****************************************
|
||||||
# Methods that CAN BE overriden by specific OS Actor
|
# Methods that CAN BE overriden by actors
|
||||||
# ******************************************************
|
# ****************************************
|
||||||
def doWait(self, miliseconds: int) -> None:
|
def doWait(self, miliseconds: int) -> None:
|
||||||
'''
|
'''
|
||||||
Invoked to wait a bit
|
Invoked to wait a bit
|
||||||
@ -589,27 +444,15 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
|||||||
'''
|
'''
|
||||||
logger.info('Service stopped')
|
logger.info('Service stopped')
|
||||||
|
|
||||||
def preConnect(
|
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str: # pylint: disable=unused-argument
|
||||||
self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str
|
|
||||||
) -> str:
|
|
||||||
'''
|
'''
|
||||||
Invoked when received a PRE Connection request via REST
|
Invoked when received a PRE Connection request via REST
|
||||||
Base preconnect executes the preconnect command
|
Base preconnect executes the preconnect command
|
||||||
'''
|
'''
|
||||||
if self._cfg.pre_command:
|
if self._cfg.pre_command:
|
||||||
self.execute(
|
self.execute(self._cfg.pre_command + ' {} {} {} {}'.format(userName.replace('"', '%22'), protocol, ip, hostname), 'preConnect')
|
||||||
self._cfg.pre_command
|
|
||||||
+ ' {} {} {} {} {}'.format(
|
|
||||||
userName.replace('"', '%22'),
|
|
||||||
protocol,
|
|
||||||
ip,
|
|
||||||
hostname,
|
|
||||||
udsUserName.replace('"', '%22'),
|
|
||||||
),
|
|
||||||
'preConnect',
|
|
||||||
)
|
|
||||||
|
|
||||||
return 'ok'
|
return 'ok'
|
||||||
|
|
||||||
def onLogout(self, userName: str, session_id: str) -> None:
|
def onLogout(self, userName: str) -> None:
|
||||||
logger.debug('On logout invoked for {}'.format(userName))
|
logger.debug('On logout invoked for {}'.format(userName))
|
||||||
|
@ -28,113 +28,20 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
# pylint: disable=invalid-name
|
||||||
import threading
|
import threading
|
||||||
import ipaddress
|
|
||||||
import time
|
|
||||||
import typing
|
|
||||||
import functools
|
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
from udsactor.log import logger
|
||||||
from udsactor.types import InterfaceInfoType
|
|
||||||
|
|
||||||
# Simple cache for n seconds (default = 30) decorator
|
|
||||||
def cache(seconds: int = 30) -> typing.Callable:
|
|
||||||
'''
|
|
||||||
Simple cache for n seconds (default = 30) decorator
|
|
||||||
'''
|
|
||||||
def decorator(func) -> typing.Callable:
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwargs) -> typing.Any:
|
|
||||||
if not hasattr(wrapper, 'cache'):
|
|
||||||
wrapper.cache = {} # type: ignore
|
|
||||||
cache = wrapper.cache # type: ignore
|
|
||||||
|
|
||||||
# Compose a key for the cache
|
|
||||||
key = '{}:{}'.format(args, kwargs)
|
|
||||||
if key in cache:
|
|
||||||
if time.time() - cache[key][0] < seconds:
|
|
||||||
return cache[key][1]
|
|
||||||
|
|
||||||
# Call the function
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
cache[key] = (time.time(), result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
# Simple sub-script exectution thread
|
|
||||||
class ScriptExecutorThread(threading.Thread):
|
class ScriptExecutorThread(threading.Thread):
|
||||||
def __init__(self, script: str) -> None:
|
def __init__(self, script: str) -> None:
|
||||||
super(ScriptExecutorThread, self).__init__()
|
super(ScriptExecutorThread, self).__init__()
|
||||||
self.script = script
|
self.script = script
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
from udsactor.log import logger
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug('Executing script: {}'.format(self.script))
|
logger.debug('Executing script: {}'.format(self.script))
|
||||||
exec(
|
exec(self.script, globals(), None) # pylint: disable=exec-used
|
||||||
self.script, globals(), None
|
|
||||||
) # nosec: exec is fine, it's a "trusted" script
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error executing script: {}'.format(e))
|
logger.error('Error executing script: {}'.format(e))
|
||||||
logger.exception()
|
logger.exception()
|
||||||
|
|
||||||
|
|
||||||
class Singleton(type):
|
|
||||||
'''
|
|
||||||
Metaclass for singleton pattern
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
class MyClass(metaclass=Singleton):
|
|
||||||
...
|
|
||||||
'''
|
|
||||||
|
|
||||||
_instance: typing.Optional[typing.Any]
|
|
||||||
|
|
||||||
# We use __init__ so we customise the created class from this metaclass
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
|
||||||
self._instance = None
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs) -> typing.Any:
|
|
||||||
if self._instance is None:
|
|
||||||
self._instance = super().__call__(*args, **kwargs)
|
|
||||||
return self._instance
|
|
||||||
|
|
||||||
|
|
||||||
# Convert "X.X.X.X/X" to ipaddress.IPv4Network
|
|
||||||
def strToNoIPV4Network(
|
|
||||||
net: typing.Optional[str],
|
|
||||||
) -> typing.Optional[ipaddress.IPv4Network]:
|
|
||||||
if not net: # Empty or None
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return ipaddress.IPv4Interface(net).network
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def validNetworkCards(
|
|
||||||
net: typing.Optional[str], cards: typing.Iterable['InterfaceInfoType']
|
|
||||||
) -> typing.List['InterfaceInfoType']:
|
|
||||||
try:
|
|
||||||
subnet = strToNoIPV4Network(net)
|
|
||||||
except Exception as e:
|
|
||||||
subnet = None
|
|
||||||
|
|
||||||
if subnet is None:
|
|
||||||
return list(cards)
|
|
||||||
|
|
||||||
def isValid(ip: str, subnet: ipaddress.IPv4Network) -> bool:
|
|
||||||
if not ip:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
return ipaddress.IPv4Address(ip) in subnet
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return [c for c in cards if isValid(c.ip, subnet)]
|
|
||||||
|
@ -35,7 +35,6 @@ class ActorConfigurationType(typing.NamedTuple):
|
|||||||
actorType: typing.Optional[str] = None
|
actorType: typing.Optional[str] = None
|
||||||
master_token: typing.Optional[str] = None
|
master_token: typing.Optional[str] = None
|
||||||
own_token: typing.Optional[str] = None
|
own_token: typing.Optional[str] = None
|
||||||
restrict_net: typing.Optional[str] = None
|
|
||||||
|
|
||||||
pre_command: typing.Optional[str] = None
|
pre_command: typing.Optional[str] = None
|
||||||
runonce_command: typing.Optional[str] = None
|
runonce_command: typing.Optional[str] = None
|
||||||
@ -51,22 +50,12 @@ class InitializationResultType(typing.NamedTuple):
|
|||||||
own_token: typing.Optional[str] = None
|
own_token: typing.Optional[str] = None
|
||||||
unique_id: typing.Optional[str] = None
|
unique_id: typing.Optional[str] = None
|
||||||
os: typing.Optional[ActorOsConfigurationType] = None
|
os: typing.Optional[ActorOsConfigurationType] = None
|
||||||
alias_token: typing.Optional[str] = None
|
|
||||||
|
|
||||||
class LoginResultInfoType(typing.NamedTuple):
|
class LoginResultInfoType(typing.NamedTuple):
|
||||||
ip: str
|
ip: str
|
||||||
hostname: str
|
hostname: str
|
||||||
dead_line: typing.Optional[int]
|
dead_line: typing.Optional[int]
|
||||||
max_idle: typing.Optional[int]
|
max_idle: typing.Optional[int] # Not provided by broker
|
||||||
session_id: typing.Optional[str]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def logged_in(self) -> bool:
|
|
||||||
return bool(self.session_id)
|
|
||||||
|
|
||||||
class ClientInfo(typing.NamedTuple):
|
|
||||||
url: str
|
|
||||||
session_id: str
|
|
||||||
|
|
||||||
class CertificateInfoType(typing.NamedTuple):
|
class CertificateInfoType(typing.NamedTuple):
|
||||||
private_key: str
|
private_key: str
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
VERSION = '4.0.0'
|
|
||||||
BUILD = '20220901'
|
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||||
# 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.
|
||||||
#
|
#
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2014 Virtual Cable S.L.
|
||||||
# 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.
|
||||||
#
|
#
|
||||||
@ -34,7 +34,7 @@ import os
|
|||||||
import tempfile
|
import tempfile
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
import servicemanager
|
import servicemanager # pylint: disable=import-error
|
||||||
|
|
||||||
# Valid logging levels, from UDS Broker (uds.core.utils.log).
|
# Valid logging levels, from UDS Broker (uds.core.utils.log).
|
||||||
from .. import loglevel
|
from .. import loglevel
|
||||||
@ -42,7 +42,6 @@ from .. import loglevel
|
|||||||
class LocalLogger: # pylint: disable=too-few-public-methods
|
class LocalLogger: # pylint: disable=too-few-public-methods
|
||||||
linux = False
|
linux = False
|
||||||
windows = True
|
windows = True
|
||||||
serviceLogger = False
|
|
||||||
|
|
||||||
logger: typing.Optional[logging.Logger]
|
logger: typing.Optional[logging.Logger]
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||||
# 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.
|
||||||
#
|
#
|
||||||
@ -45,46 +45,32 @@ import win32con
|
|||||||
from .. import types
|
from .. import types
|
||||||
from ..log import logger
|
from ..log import logger
|
||||||
|
|
||||||
|
|
||||||
def checkPermissions() -> bool:
|
def checkPermissions() -> bool:
|
||||||
return shell.IsUserAnAdmin()
|
return shell.IsUserAnAdmin()
|
||||||
|
|
||||||
|
|
||||||
def getErrorMessage(resultCode: int = 0) -> str:
|
def getErrorMessage(resultCode: int = 0) -> str:
|
||||||
# sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
# sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
||||||
msg = win32api.FormatMessage(resultCode)
|
msg = win32api.FormatMessage(resultCode)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def getComputerName() -> str:
|
def getComputerName() -> str:
|
||||||
return win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
|
return win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
|
||||||
|
|
||||||
|
|
||||||
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
|
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
|
||||||
obj = win32com.client.Dispatch("WbemScripting.SWbemLocator")
|
obj = win32com.client.Dispatch("WbemScripting.SWbemLocator")
|
||||||
wmobj = obj.ConnectServer("localhost", "root\\cimv2")
|
wmobj = obj.ConnectServer("localhost", "root\\cimv2")
|
||||||
adapters = wmobj.ExecQuery(
|
adapters = wmobj.ExecQuery("Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True")
|
||||||
"Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
for obj in adapters:
|
for obj in adapters:
|
||||||
for ip in obj.IPAddress:
|
for ip in obj.IPAddress:
|
||||||
if ':' in ip: # Is IPV6, skip this
|
if ':' in ip: # Is IPV6, skip this
|
||||||
continue
|
continue
|
||||||
if (
|
if ip is None or ip == '' or ip.startswith('169.254') or ip.startswith('0.'): # If single link ip, or no ip
|
||||||
ip is None
|
|
||||||
or ip == ''
|
|
||||||
or ip.startswith('169.254')
|
|
||||||
or ip.startswith('0.')
|
|
||||||
): # If single link ip, or no ip
|
|
||||||
continue
|
continue
|
||||||
yield types.InterfaceInfoType(
|
yield types.InterfaceInfoType(name=obj.Caption, mac=obj.MACAddress, ip=ip)
|
||||||
name=obj.Caption, mac=obj.MACAddress, ip=ip
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def getDomainName() -> str:
|
def getDomainName() -> str:
|
||||||
'''
|
'''
|
||||||
Will return the domain name if we belong a domain, else None
|
Will return the domain name if we belong a domain, else None
|
||||||
@ -101,19 +87,9 @@ def getDomainName() -> str:
|
|||||||
|
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
|
|
||||||
def getWindowsVersion() -> typing.Tuple[int, int, int, int, str]:
|
def getWindowsVersion() -> typing.Tuple[int, int, int, int, str]:
|
||||||
return win32api.GetVersionEx()
|
return win32api.GetVersionEx()
|
||||||
|
|
||||||
|
|
||||||
def getVersion() -> str:
|
|
||||||
verinfo = getWindowsVersion()
|
|
||||||
# Remove platform id i
|
|
||||||
return 'Windows-{}.{} Build {} ({})'.format(
|
|
||||||
verinfo[0], verinfo[1], verinfo[2], verinfo[4]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
EWX_LOGOFF = 0x00000000
|
EWX_LOGOFF = 0x00000000
|
||||||
EWX_SHUTDOWN = 0x00000001
|
EWX_SHUTDOWN = 0x00000001
|
||||||
EWX_REBOOT = 0x00000002
|
EWX_REBOOT = 0x00000002
|
||||||
@ -121,53 +97,31 @@ EWX_FORCE = 0x00000004
|
|||||||
EWX_POWEROFF = 0x00000008
|
EWX_POWEROFF = 0x00000008
|
||||||
EWX_FORCEIFHUNG = 0x00000010
|
EWX_FORCEIFHUNG = 0x00000010
|
||||||
|
|
||||||
|
|
||||||
def reboot(flags: int = EWX_FORCEIFHUNG | EWX_REBOOT) -> None:
|
def reboot(flags: int = EWX_FORCEIFHUNG | EWX_REBOOT) -> None:
|
||||||
hproc = win32api.GetCurrentProcess()
|
hproc = win32api.GetCurrentProcess()
|
||||||
htok = win32security.OpenProcessToken(
|
htok = win32security.OpenProcessToken(hproc, win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY)
|
||||||
hproc, win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY
|
privs = ((win32security.LookupPrivilegeValue(None, win32security.SE_SHUTDOWN_NAME), win32security.SE_PRIVILEGE_ENABLED),)
|
||||||
)
|
|
||||||
privs = (
|
|
||||||
(
|
|
||||||
win32security.LookupPrivilegeValue(None, win32security.SE_SHUTDOWN_NAME),
|
|
||||||
win32security.SE_PRIVILEGE_ENABLED,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
win32security.AdjustTokenPrivileges(htok, 0, privs)
|
win32security.AdjustTokenPrivileges(htok, 0, privs)
|
||||||
win32api.ExitWindowsEx(flags, 0)
|
win32api.ExitWindowsEx(flags, 0)
|
||||||
|
|
||||||
|
|
||||||
def loggoff() -> None:
|
def loggoff() -> None:
|
||||||
win32api.ExitWindowsEx(EWX_LOGOFF)
|
win32api.ExitWindowsEx(EWX_LOGOFF)
|
||||||
|
|
||||||
|
|
||||||
def renameComputer(newName: str) -> bool:
|
def renameComputer(newName: str) -> bool:
|
||||||
'''
|
'''
|
||||||
Changes the computer name
|
Changes the computer name
|
||||||
Returns True if reboot needed
|
Returns True if reboot needed
|
||||||
'''
|
'''
|
||||||
# Needs admin privileges to work
|
# Needs admin privileges to work
|
||||||
if (
|
if ctypes.windll.kernel32.SetComputerNameExW(DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)) == 0: # @UndefinedVariable
|
||||||
ctypes.windll.kernel32.SetComputerNameExW(
|
|
||||||
DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)
|
|
||||||
)
|
|
||||||
== 0
|
|
||||||
): # @UndefinedVariable
|
|
||||||
# win32api.FormatMessage -> returns error string
|
# win32api.FormatMessage -> returns error string
|
||||||
# win32api.GetLastError -> returns error code
|
# win32api.GetLastError -> returns error code
|
||||||
# (just put this comment here to remember to log this when logger is available)
|
# (just put this comment here to remember to log this when logger is available)
|
||||||
error = getErrorMessage()
|
error = getErrorMessage()
|
||||||
computerName = win32api.GetComputerNameEx(
|
computerName = win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
|
||||||
win32con.ComputerNamePhysicalDnsHostname
|
raise Exception('Error renaming computer from {} to {}: {}'.format(computerName, newName, error))
|
||||||
)
|
|
||||||
raise Exception(
|
|
||||||
'Error renaming computer from {} to {}: {}'.format(
|
|
||||||
computerName, newName, error
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
NETSETUP_JOIN_DOMAIN = 0x00000001
|
NETSETUP_JOIN_DOMAIN = 0x00000001
|
||||||
NETSETUP_ACCT_CREATE = 0x00000002
|
NETSETUP_ACCT_CREATE = 0x00000002
|
||||||
NETSETUP_ACCT_DELETE = 0x00000004
|
NETSETUP_ACCT_DELETE = 0x00000004
|
||||||
@ -178,10 +132,7 @@ NETSETUP_MACHINE_PWD_PASSED = 0x00000080
|
|||||||
NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400
|
NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400
|
||||||
NETSETUP_DEFER_SPN_SET = 0x1000000
|
NETSETUP_DEFER_SPN_SET = 0x1000000
|
||||||
|
|
||||||
|
def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False) -> None:
|
||||||
def joinDomain(
|
|
||||||
domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False
|
|
||||||
) -> None:
|
|
||||||
'''
|
'''
|
||||||
Joins machine to a windows domain
|
Joins machine to a windows domain
|
||||||
:param domain: Domain to join to
|
:param domain: Domain to join to
|
||||||
@ -198,9 +149,7 @@ def joinDomain(
|
|||||||
account = domain + '\\' + account
|
account = domain + '\\' + account
|
||||||
|
|
||||||
# Do log
|
# Do log
|
||||||
flags: typing.Any = (
|
flags: typing.Any = NETSETUP_ACCT_CREATE | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN
|
||||||
NETSETUP_ACCT_CREATE | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN
|
|
||||||
)
|
|
||||||
|
|
||||||
if executeInOneStep:
|
if executeInOneStep:
|
||||||
flags |= NETSETUP_JOIN_WITH_NEW_NAME
|
flags |= NETSETUP_JOIN_WITH_NEW_NAME
|
||||||
@ -214,31 +163,18 @@ def joinDomain(
|
|||||||
lpAccount = LPCWSTR(account)
|
lpAccount = LPCWSTR(account)
|
||||||
lpPassword = LPCWSTR(password)
|
lpPassword = LPCWSTR(password)
|
||||||
|
|
||||||
res = ctypes.windll.netapi32.NetJoinDomain(
|
res = ctypes.windll.netapi32.NetJoinDomain(None, lpDomain, lpOu, lpAccount, lpPassword, flags)
|
||||||
None, lpDomain, lpOu, lpAccount, lpPassword, flags
|
|
||||||
)
|
|
||||||
# Machine found in another ou, use it and warn this on log
|
# Machine found in another ou, use it and warn this on log
|
||||||
if res == 2224:
|
if res == 2224:
|
||||||
flags = DWORD(NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN)
|
flags = DWORD(NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN)
|
||||||
res = ctypes.windll.netapi32.NetJoinDomain(
|
res = ctypes.windll.netapi32.NetJoinDomain(None, lpDomain, None, lpAccount, lpPassword, flags)
|
||||||
None, lpDomain, None, lpAccount, lpPassword, flags
|
|
||||||
)
|
|
||||||
if res:
|
if res:
|
||||||
# Log the error
|
# Log the error
|
||||||
error = getErrorMessage(res)
|
error = getErrorMessage(res)
|
||||||
if res == 1355:
|
if res == 1355:
|
||||||
error = "DC Is not reachable"
|
error = "DC Is not reachable"
|
||||||
logger.error('Error joining domain: {}, {}'.format(error, res))
|
logger.error('Error joining domain: {}, {}'.format(error, res))
|
||||||
raise Exception(
|
raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain, account, ', under OU {}'.format(ou) if ou is not None else '', res, error))
|
||||||
'Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(
|
|
||||||
domain,
|
|
||||||
account,
|
|
||||||
', under OU {}'.format(ou) if ou is not None else '',
|
|
||||||
res,
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
|
def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
|
||||||
# lpUser = LPCWSTR(user)
|
# lpUser = LPCWSTR(user)
|
||||||
@ -252,10 +188,7 @@ def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
|
|||||||
if res:
|
if res:
|
||||||
# Log the error, and raise exception to parent
|
# Log the error, and raise exception to parent
|
||||||
error = getErrorMessage(res)
|
error = getErrorMessage(res)
|
||||||
raise Exception(
|
raise Exception('Error changing password for user {}: {} {}'.format(user, res, error))
|
||||||
'Error changing password for user {}: {} {}'.format(user, res, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LASTINPUTINFO(ctypes.Structure): # pylint: disable=too-few-public-methods
|
class LASTINPUTINFO(ctypes.Structure): # pylint: disable=too-few-public-methods
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
@ -263,20 +196,16 @@ class LASTINPUTINFO(ctypes.Structure): # pylint: disable=too-few-public-methods
|
|||||||
('dwTime', ctypes.c_uint),
|
('dwTime', ctypes.c_uint),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def initIdleDuration(atLeastSeconds: int): # pylint: disable=unused-argument
|
def initIdleDuration(atLeastSeconds: int): # pylint: disable=unused-argument
|
||||||
'''
|
'''
|
||||||
In windows, there is no need to set screensaver
|
In windows, there is no need to set screensaver
|
||||||
'''
|
'''
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def getIdleDuration() -> float:
|
def getIdleDuration() -> float:
|
||||||
try:
|
try:
|
||||||
lastInputInfo = LASTINPUTINFO()
|
lastInputInfo = LASTINPUTINFO()
|
||||||
lastInputInfo.cbSize = ctypes.sizeof(
|
lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) # pylint: disable=attribute-defined-outside-init
|
||||||
lastInputInfo
|
|
||||||
) # pylint: disable=attribute-defined-outside-init
|
|
||||||
if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) == 0:
|
if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) == 0:
|
||||||
return 0
|
return 0
|
||||||
current = ctypes.c_uint(ctypes.windll.kernel32.GetTickCount()).value
|
current = ctypes.c_uint(ctypes.windll.kernel32.GetTickCount()).value
|
||||||
@ -288,27 +217,22 @@ def getIdleDuration() -> float:
|
|||||||
logger.error('Getting idle duration: {}'.format(e))
|
logger.error('Getting idle duration: {}'.format(e))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def getCurrentUser() -> str:
|
def getCurrentUser() -> str:
|
||||||
'''
|
'''
|
||||||
Returns current logged in username
|
Returns current logged in username
|
||||||
'''
|
'''
|
||||||
return os.environ['USERNAME']
|
return os.environ['USERNAME']
|
||||||
|
|
||||||
|
|
||||||
def getSessionType() -> str:
|
def getSessionType() -> str:
|
||||||
'''
|
'''
|
||||||
Known values:
|
Known values:
|
||||||
* Unknown -> No SESSIONNAME environment variable
|
* Unknown -> No SESSIONNAME environment variable
|
||||||
* Console -> Local session
|
* Console -> Local session
|
||||||
* RDP-Tcp#[0-9]+ -> RDP Session
|
* RDP-Tcp#[0-9]+ -> RDP Session
|
||||||
'''
|
'''
|
||||||
return os.environ.get('SESSIONNAME', 'unknown')
|
return os.environ.get('SESSIONNAME', 'unknown')
|
||||||
|
|
||||||
|
def writeToPipe(pipeName: str, bytesPayload: bytes, waitForResponse: bool) -> typing.Optional[bytes]:
|
||||||
def writeToPipe(
|
|
||||||
pipeName: str, bytesPayload: bytes, waitForResponse: bool
|
|
||||||
) -> typing.Optional[bytes]:
|
|
||||||
# (str, bytes, bool) -> Optional[bytes]
|
# (str, bytes, bool) -> Optional[bytes]
|
||||||
try:
|
try:
|
||||||
with open(pipeName, 'r+b', 0) as f:
|
with open(pipeName, 'r+b', 0) as f:
|
||||||
@ -320,11 +244,8 @@ def writeToPipe(
|
|||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def forceTimeSync() -> None:
|
def forceTimeSync() -> None:
|
||||||
try:
|
try:
|
||||||
subprocess.call(
|
subprocess.call([r'c:\WINDOWS\System32\w32tm.exe', ' /resync']) # , '/rediscover'])
|
||||||
[r'c:\WINDOWS\System32\w32tm.exe', ' /resync']
|
|
||||||
) # , '/rediscover'])
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error invoking time sync command: %s', e)
|
logger.error('Error invoking time sync command: %s', e)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2019-2022 Virtual Cable S.L.U.
|
# Copyright (c) 2019 Virtual Cable S.L.
|
||||||
# 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.
|
||||||
#
|
#
|
||||||
@ -41,8 +41,6 @@ from .service import UDSActorSvc
|
|||||||
def setupRecoverService():
|
def setupRecoverService():
|
||||||
svc_name = UDSActorSvc._svc_name_ # pylint: disable=protected-access
|
svc_name = UDSActorSvc._svc_name_ # pylint: disable=protected-access
|
||||||
|
|
||||||
hs = None
|
|
||||||
hscm = None
|
|
||||||
try:
|
try:
|
||||||
hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
|
hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
|
||||||
|
|
||||||
@ -59,11 +57,9 @@ def setupRecoverService():
|
|||||||
}
|
}
|
||||||
win32service.ChangeServiceConfig2(hs, win32service.SERVICE_CONFIG_FAILURE_ACTIONS, service_failure_actions)
|
win32service.ChangeServiceConfig2(hs, win32service.SERVICE_CONFIG_FAILURE_ACTIONS, service_failure_actions)
|
||||||
finally:
|
finally:
|
||||||
if hs:
|
win32service.CloseServiceHandle(hs)
|
||||||
win32service.CloseServiceHandle(hs)
|
|
||||||
finally:
|
finally:
|
||||||
if hscm:
|
win32service.CloseServiceHandle(hscm)
|
||||||
win32service.CloseServiceHandle(hscm)
|
|
||||||
|
|
||||||
|
|
||||||
def run() -> None:
|
def run() -> None:
|
||||||
|
@ -39,7 +39,6 @@ import win32net
|
|||||||
import win32event
|
import win32event
|
||||||
import pythoncom
|
import pythoncom
|
||||||
import servicemanager
|
import servicemanager
|
||||||
import winreg as wreg
|
|
||||||
|
|
||||||
from . import operations
|
from . import operations
|
||||||
from . import store
|
from . import store
|
||||||
@ -139,7 +138,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
|||||||
logger.info('Using multiple step join because configuration requests to do so')
|
logger.info('Using multiple step join because configuration requests to do so')
|
||||||
self.multiStepJoin(name, domain, ou, account, password)
|
self.multiStepJoin(name, domain, ou, account, password)
|
||||||
|
|
||||||
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str) -> str:
|
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str:
|
||||||
logger.debug('Pre connect invoked')
|
logger.debug('Pre connect invoked')
|
||||||
|
|
||||||
if protocol == 'rdp': # If connection is not using rdp, skip adding user
|
if protocol == 'rdp': # If connection is not using rdp, skip adding user
|
||||||
@ -168,7 +167,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
|||||||
self._user = None
|
self._user = None
|
||||||
logger.debug('User {} already in group'.format(userName))
|
logger.debug('User {} already in group'.format(userName))
|
||||||
|
|
||||||
return super().preConnect(userName, protocol, ip, hostname, udsUserName)
|
return super().preConnect(userName, protocol, ip, hostname)
|
||||||
|
|
||||||
def ovLogon(self, username: str, password: str) -> str:
|
def ovLogon(self, username: str, password: str) -> str:
|
||||||
"""
|
"""
|
||||||
@ -183,7 +182,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
|||||||
operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
|
operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
|
||||||
return 'done'
|
return 'done'
|
||||||
|
|
||||||
def onLogout(self, userName: str, session_id: str) -> None:
|
def onLogout(self, userName) -> None:
|
||||||
logger.debug('Windows onLogout invoked: {}, {}'.format(userName, self._user))
|
logger.debug('Windows onLogout invoked: {}, {}'.format(userName, self._user))
|
||||||
try:
|
try:
|
||||||
p = win32security.GetBinarySid(REMOTE_USERS_SID)
|
p = win32security.GetBinarySid(REMOTE_USERS_SID)
|
||||||
@ -198,18 +197,6 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Exception removing user from Remote Desktop Users: {}'.format(e))
|
logger.error('Exception removing user from Remote Desktop Users: {}'.format(e))
|
||||||
|
|
||||||
def isInstallationRunning(self):
|
|
||||||
'''
|
|
||||||
Detect if windows is installing anything, so we can delay the execution of Service
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
key = wreg.OpenKey(wreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State')
|
|
||||||
data, _ = wreg.QueryValueEx(key, 'ImageState')
|
|
||||||
logger.debug('State: %s', data)
|
|
||||||
return data != 'IMAGE_STATE_COMPLETE' # If ImageState is different of ImageStateComplete, there is something running on installation
|
|
||||||
except Exception: # If not found, means that no installation is running
|
|
||||||
return False
|
|
||||||
|
|
||||||
def SvcDoRun(self) -> None: # pylint: disable=too-many-statements, too-many-branches
|
def SvcDoRun(self) -> None: # pylint: disable=too-many-statements, too-many-branches
|
||||||
'''
|
'''
|
||||||
Main service loop
|
Main service loop
|
||||||
@ -222,17 +209,6 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
|||||||
|
|
||||||
pythoncom.CoInitialize() # pylint: disable=no-member
|
pythoncom.CoInitialize() # pylint: disable=no-member
|
||||||
|
|
||||||
# Check if some install is running on windows before proceeding
|
|
||||||
while self._isAlive:
|
|
||||||
if self.isInstallationRunning():
|
|
||||||
win32event.WaitForSingleObject(self._hWaitStop, 1000) # Wait a bit, and check again
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
|
|
||||||
if not self._isAlive: # Has been stopped while waiting windows installations
|
|
||||||
self.finish()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Unmanaged services does not initializes "on start", but rather when user logs in (because userservice does not exists "as such" before that)
|
# Unmanaged services does not initializes "on start", but rather when user logs in (because userservice does not exists "as such" before that)
|
||||||
if self.isManaged():
|
if self.isManaged():
|
||||||
if not self.initialize():
|
if not self.initialize():
|
||||||
|
@ -76,9 +76,9 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
|
|||||||
except Exception:
|
except Exception:
|
||||||
key = wreg.CreateKeyEx(BASEKEY, PATH, 0, wreg.KEY_ALL_ACCESS)
|
key = wreg.CreateKeyEx(BASEKEY, PATH, 0, wreg.KEY_ALL_ACCESS)
|
||||||
|
|
||||||
fixRegistryPermissions(key.handle) # type: ignore
|
fixRegistryPermissions(key.handle)
|
||||||
|
|
||||||
wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, pickle.dumps(config)) # type: ignore
|
wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, pickle.dumps(config))
|
||||||
wreg.CloseKey(key)
|
wreg.CloseKey(key)
|
||||||
|
|
||||||
|
|
||||||
@ -94,16 +94,3 @@ def useOldJoinSystem() -> bool:
|
|||||||
data = ''
|
data = ''
|
||||||
|
|
||||||
return data == 'old'
|
return data == 'old'
|
||||||
|
|
||||||
def invokeScriptOnLogin() -> str:
|
|
||||||
try:
|
|
||||||
key = wreg.OpenKey(BASEKEY, PATH, 0, wreg.KEY_QUERY_VALUE)
|
|
||||||
try:
|
|
||||||
data, _ = wreg.QueryValueEx(key, 'logonScript')
|
|
||||||
except Exception:
|
|
||||||
data = ''
|
|
||||||
wreg.CloseKey(key)
|
|
||||||
except Exception:
|
|
||||||
data = ''
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
# Form implementation generated from reading ui file 'setup-dialog.ui'
|
# Form implementation generated from reading ui file 'setup-dialog.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt5 UI code generator 5.15.2
|
# Created by: PyQt5 UI code generator 5.13.2
|
||||||
#
|
#
|
||||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
# WARNING! All changes made in this file will be lost!
|
||||||
# run again. Do not edit this file unless you know what you are doing.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
# Form implementation generated from reading ui file 'setup-dialog-unmanaged.ui'
|
# Form implementation generated from reading ui file 'setup-dialog-unmanaged.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt5 UI code generator 5.15.2
|
# Created by: PyQt5 UI code generator 5.13.2
|
||||||
#
|
#
|
||||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
# WARNING! All changes made in this file will be lost!
|
||||||
# run again. Do not edit this file unless you know what you are doing.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
@ -15,7 +14,7 @@ class Ui_UdsActorSetupDialog(object):
|
|||||||
def setupUi(self, UdsActorSetupDialog):
|
def setupUi(self, UdsActorSetupDialog):
|
||||||
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
|
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
|
||||||
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
|
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||||
UdsActorSetupDialog.resize(601, 243)
|
UdsActorSetupDialog.resize(595, 220)
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||||
sizePolicy.setHorizontalStretch(0)
|
sizePolicy.setHorizontalStretch(0)
|
||||||
sizePolicy.setVerticalStretch(0)
|
sizePolicy.setVerticalStretch(0)
|
||||||
@ -35,12 +34,12 @@ class Ui_UdsActorSetupDialog(object):
|
|||||||
UdsActorSetupDialog.setModal(True)
|
UdsActorSetupDialog.setModal(True)
|
||||||
self.saveButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
self.saveButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||||
self.saveButton.setEnabled(True)
|
self.saveButton.setEnabled(True)
|
||||||
self.saveButton.setGeometry(QtCore.QRect(10, 210, 181, 23))
|
self.saveButton.setGeometry(QtCore.QRect(10, 180, 181, 23))
|
||||||
self.saveButton.setMinimumSize(QtCore.QSize(181, 0))
|
self.saveButton.setMinimumSize(QtCore.QSize(181, 0))
|
||||||
self.saveButton.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
|
self.saveButton.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
|
||||||
self.saveButton.setObjectName("saveButton")
|
self.saveButton.setObjectName("saveButton")
|
||||||
self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||||
self.closeButton.setGeometry(QtCore.QRect(410, 210, 171, 23))
|
self.closeButton.setGeometry(QtCore.QRect(410, 180, 171, 23))
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||||
sizePolicy.setHorizontalStretch(0)
|
sizePolicy.setHorizontalStretch(0)
|
||||||
sizePolicy.setVerticalStretch(0)
|
sizePolicy.setVerticalStretch(0)
|
||||||
@ -50,11 +49,11 @@ class Ui_UdsActorSetupDialog(object):
|
|||||||
self.closeButton.setObjectName("closeButton")
|
self.closeButton.setObjectName("closeButton")
|
||||||
self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||||
self.testButton.setEnabled(False)
|
self.testButton.setEnabled(False)
|
||||||
self.testButton.setGeometry(QtCore.QRect(210, 210, 181, 23))
|
self.testButton.setGeometry(QtCore.QRect(210, 180, 181, 23))
|
||||||
self.testButton.setMinimumSize(QtCore.QSize(181, 0))
|
self.testButton.setMinimumSize(QtCore.QSize(181, 0))
|
||||||
self.testButton.setObjectName("testButton")
|
self.testButton.setObjectName("testButton")
|
||||||
self.layoutWidget = QtWidgets.QWidget(UdsActorSetupDialog)
|
self.layoutWidget = QtWidgets.QWidget(UdsActorSetupDialog)
|
||||||
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 571, 191))
|
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 571, 161))
|
||||||
self.layoutWidget.setObjectName("layoutWidget")
|
self.layoutWidget.setObjectName("layoutWidget")
|
||||||
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
|
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
|
||||||
self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
|
self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
|
||||||
@ -85,7 +84,7 @@ class Ui_UdsActorSetupDialog(object):
|
|||||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.serviceToken)
|
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.serviceToken)
|
||||||
self.label_loglevel = QtWidgets.QLabel(self.layoutWidget)
|
self.label_loglevel = QtWidgets.QLabel(self.layoutWidget)
|
||||||
self.label_loglevel.setObjectName("label_loglevel")
|
self.label_loglevel.setObjectName("label_loglevel")
|
||||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
|
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
|
||||||
self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget)
|
self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget)
|
||||||
self.logLevelComboBox.setFrame(True)
|
self.logLevelComboBox.setFrame(True)
|
||||||
self.logLevelComboBox.setObjectName("logLevelComboBox")
|
self.logLevelComboBox.setObjectName("logLevelComboBox")
|
||||||
@ -97,13 +96,7 @@ class Ui_UdsActorSetupDialog(object):
|
|||||||
self.logLevelComboBox.setItemText(2, "ERROR")
|
self.logLevelComboBox.setItemText(2, "ERROR")
|
||||||
self.logLevelComboBox.addItem("")
|
self.logLevelComboBox.addItem("")
|
||||||
self.logLevelComboBox.setItemText(3, "FATAL")
|
self.logLevelComboBox.setItemText(3, "FATAL")
|
||||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
|
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
|
||||||
self.label_restrictNet = QtWidgets.QLabel(self.layoutWidget)
|
|
||||||
self.label_restrictNet.setObjectName("label_restrictNet")
|
|
||||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_restrictNet)
|
|
||||||
self.restrictNet = QtWidgets.QLineEdit(self.layoutWidget)
|
|
||||||
self.restrictNet.setObjectName("restrictNet")
|
|
||||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.restrictNet)
|
|
||||||
self.label_host.raise_()
|
self.label_host.raise_()
|
||||||
self.host.raise_()
|
self.host.raise_()
|
||||||
self.label_serviceToken.raise_()
|
self.label_serviceToken.raise_()
|
||||||
@ -112,8 +105,6 @@ class Ui_UdsActorSetupDialog(object):
|
|||||||
self.label_security.raise_()
|
self.label_security.raise_()
|
||||||
self.label_loglevel.raise_()
|
self.label_loglevel.raise_()
|
||||||
self.logLevelComboBox.raise_()
|
self.logLevelComboBox.raise_()
|
||||||
self.label_restrictNet.raise_()
|
|
||||||
self.restrictNet.raise_()
|
|
||||||
|
|
||||||
self.retranslateUi(UdsActorSetupDialog)
|
self.retranslateUi(UdsActorSetupDialog)
|
||||||
self.logLevelComboBox.setCurrentIndex(1)
|
self.logLevelComboBox.setCurrentIndex(1)
|
||||||
@ -122,7 +113,6 @@ class Ui_UdsActorSetupDialog(object):
|
|||||||
self.saveButton.clicked.connect(UdsActorSetupDialog.saveConfig)
|
self.saveButton.clicked.connect(UdsActorSetupDialog.saveConfig)
|
||||||
self.host.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
self.host.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||||
self.serviceToken.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
self.serviceToken.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||||
self.restrictNet.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
|
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
|
||||||
|
|
||||||
def retranslateUi(self, UdsActorSetupDialog):
|
def retranslateUi(self, UdsActorSetupDialog):
|
||||||
@ -146,10 +136,7 @@ class Ui_UdsActorSetupDialog(object):
|
|||||||
self.host.setToolTip(_translate("UdsActorSetupDialog", "Uds Broker Server Addres. Use IP or FQDN"))
|
self.host.setToolTip(_translate("UdsActorSetupDialog", "Uds Broker Server Addres. Use IP or FQDN"))
|
||||||
self.host.setWhatsThis(_translate("UdsActorSetupDialog", "Enter here the UDS Broker Addres using either its IP address or its FQDN address"))
|
self.host.setWhatsThis(_translate("UdsActorSetupDialog", "Enter here the UDS Broker Addres using either its IP address or its FQDN address"))
|
||||||
self.label_serviceToken.setText(_translate("UdsActorSetupDialog", "Service Token"))
|
self.label_serviceToken.setText(_translate("UdsActorSetupDialog", "Service Token"))
|
||||||
self.serviceToken.setToolTip(_translate("UdsActorSetupDialog", "UDS Service Token"))
|
self.serviceToken.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
|
||||||
self.serviceToken.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html>"))
|
self.serviceToken.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html>"))
|
||||||
self.label_loglevel.setText(_translate("UdsActorSetupDialog", "Log Level"))
|
self.label_loglevel.setText(_translate("UdsActorSetupDialog", "Log Level"))
|
||||||
self.label_restrictNet.setText(_translate("UdsActorSetupDialog", "Restrict Net"))
|
|
||||||
self.restrictNet.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
|
|
||||||
self.restrictNet.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html>"))
|
|
||||||
from ui import uds_rc
|
from ui import uds_rc
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Resource object code
|
# Resource object code
|
||||||
#
|
#
|
||||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
|
# Created by: The Resource Compiler for PyQt5 (Qt v5.13.2)
|
||||||
#
|
#
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
PYTHONPATH=./src:${PYTHONPATH}
|
|
||||||
|
|
4
client-py3/full/linux/.gitignore
vendored
4
client-py3/full/linux/.gitignore
vendored
@ -2,7 +2,3 @@
|
|||||||
/udsclient-[0-9]*.spec
|
/udsclient-[0-9]*.spec
|
||||||
/debian/udsclient
|
/debian/udsclient
|
||||||
/targz
|
/targz
|
||||||
/UDSClientDir
|
|
||||||
/UDSClient*.AppImage
|
|
||||||
/appimage*
|
|
||||||
/UDSClient.desktop
|
|
||||||
|
@ -14,8 +14,6 @@ APPSDIR := $(DESTDIR)/usr/share/applications
|
|||||||
PYC := $(shell find $(SOURCEDIR) -name '*.py[co]')
|
PYC := $(shell find $(SOURCEDIR) -name '*.py[co]')
|
||||||
CACHES := $(shell find $(SOURCEDIR) -name '__pycache__')
|
CACHES := $(shell find $(SOURCEDIR) -name '__pycache__')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(PYC) $(CACHES) $(DESTDIR)
|
rm -rf $(PYC) $(CACHES) $(DESTDIR)
|
||||||
install:
|
install:
|
||||||
@ -48,60 +46,8 @@ endif
|
|||||||
ifeq ($(DISTRO),rh)
|
ifeq ($(DISTRO),rh)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# chmod 0755 $(BINDIR)/udsclient
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -rf $(LIBDIR)
|
rm -rf $(LIBDIR)
|
||||||
# rm -f $(BINDIR)/udsclient
|
# rm -f $(BINDIR)/udsclient
|
||||||
# rm -rf $(CFGDIR)
|
# rm -rf $(CFGDIR)
|
||||||
|
|
||||||
build-appimage:
|
|
||||||
ifeq ($(DISTRO),x86_64)
|
|
||||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g > appimage.recipe
|
|
||||||
endif
|
|
||||||
ifeq ($(DISTRO),armhf)
|
|
||||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g | sed -e s/amd64/armhf/g | sed -e s/x86_64/armhf/g > appimage.recipe
|
|
||||||
endif
|
|
||||||
ifeq ($(DISTRO),i686)
|
|
||||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g | sed -e s/amd64/i386/g | sed -e s/x86_64/i686/g > appimage.recipe
|
|
||||||
endif
|
|
||||||
# Ensure all working folders are "clean"
|
|
||||||
-rm -rf appimage appimage-builder-cache /tmp/UDSClientDir
|
|
||||||
|
|
||||||
appimage-builder --recipe appimage.recipe
|
|
||||||
# Now create dist and move appimage
|
|
||||||
rm -rf $(DESTDIR)
|
|
||||||
mkdir -p $(DESTDIR)
|
|
||||||
cp UDSClient-$(VERSION)-$(DISTRO).AppImage $(DESTDIR)
|
|
||||||
# Generate the .desktop fixed for new path
|
|
||||||
cat desktop/UDSClient.desktop | sed -e s/".usr.lib.UDSClient.UDSClient.py"/"\/usr\/bin\/UDSClient-$(VERSION)-$(DISTRO).AppImage"/g > $(DESTDIR)/UDSClient.desktop
|
|
||||||
# And also, generater installer
|
|
||||||
cat installer-appimage-template.sh | sed -e s/"0.0.0"/"$(VERSION)"/g | sed -e s/x86_64/$(DISTRO)/g > $(DESTDIR)/installer.sh
|
|
||||||
chmod 755 $(DESTDIR)/installer.sh
|
|
||||||
tar czvf ../udsclient3-$(DISTRO)-$(VERSION).tar.gz -C $(DESTDIR) .
|
|
||||||
|
|
||||||
# cleanup
|
|
||||||
-rm -rf appimage appimage-builder-cache /tmp/UDSClientDir
|
|
||||||
|
|
||||||
build-igel:
|
|
||||||
rm -rf $(DESTDIR)
|
|
||||||
mkdir -p $(DESTDIR)
|
|
||||||
# Calculate the size of the custom partition (15 megas more than the appimage size)
|
|
||||||
@$(eval APPIMAGE_SIZE=$(shell du -sm UDSClient-$(VERSION)-x86_64.AppImage | cut -f1))
|
|
||||||
@$(eval APPIMAGE_SIZE=$(shell expr $(APPIMAGE_SIZE) + 15))
|
|
||||||
cat igel/UDSClient-Profile-template.xml | sed -e s/"_SIZE_"/"$(APPIMAGE_SIZE)M"/g > $(DESTDIR)/UDSClient-Profile.xml
|
|
||||||
cat igel/UDSClient-template.inf | sed -e s/"_SIZE_"/"$(APPIMAGE_SIZE)M"/g > $(DESTDIR)/UDSClient.inf
|
|
||||||
cp UDSClient-$(VERSION)-x86_64.AppImage $(DESTDIR)/UDSClient
|
|
||||||
cp igel/UDSClient.desktop $(DESTDIR)/UDSClient.desktop
|
|
||||||
cp igel/init.sh $(DESTDIR)/init.sh
|
|
||||||
tar cjvf $(DESTDIR)/UDSClient.tar.bz2 -C $(DESTDIR) UDSClient UDSClient.desktop init.sh
|
|
||||||
zip -j ../udsclient3-$(VERSION)-igel.zip $(DESTDIR)/UDSClient-Profile.xml $(DESTDIR)/UDSClient.inf $(DESTDIR)/UDSClient.tar.bz2
|
|
||||||
cd ..
|
|
||||||
rm -rf $(DESTDIR)
|
|
||||||
|
|
||||||
build-thinpro:
|
|
||||||
rm -rf $(DESTDIR)
|
|
||||||
mkdir -p $(DESTDIR)
|
|
||||||
cp -r thinpro/* $(DESTDIR)
|
|
||||||
cp UDSClient-$(VERSION)-x86_64.AppImage $(DESTDIR)/UDSClient
|
|
||||||
tar czvf ../udsclient3-$(VERSION)-thinpro.tar.gz -C $(DESTDIR) .
|
|
||||||
rm -rf $(DESTDIR)
|
|
||||||
|
|
||||||
|
@ -12,9 +12,6 @@ cat udsclient-template.spec |
|
|||||||
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
||||||
sed -e s/"release 1"/"release ${RELEASE}"/g > udsclient-$VERSION.spec
|
sed -e s/"release 1"/"release ${RELEASE}"/g > udsclient-$VERSION.spec
|
||||||
|
|
||||||
cat appimage-udsclient.recipe |
|
|
||||||
sed -e s/"version: 0.0.0"/"version: ${VERSION}"/g > appimage.recipe
|
|
||||||
|
|
||||||
# Now fix dependencies for opensuse
|
# Now fix dependencies for opensuse
|
||||||
# Note: Right now, opensuse & rh seems to have same dependencies, only 1 package needed
|
# Note: Right now, opensuse & rh seems to have same dependencies, only 1 package needed
|
||||||
# cat udsclient-template.spec |
|
# cat udsclient-template.spec |
|
||||||
@ -35,19 +32,6 @@ done
|
|||||||
|
|
||||||
#rm udsclient-$VERSION
|
#rm udsclient-$VERSION
|
||||||
|
|
||||||
# Make .tar.gz with source
|
|
||||||
make DESTDIR=targz DISTRO=targz VERSION=${VERSION} install
|
make DESTDIR=targz DISTRO=targz VERSION=${VERSION} install
|
||||||
|
|
||||||
# And make FULL CLIENT .tar.gz for x86 and raspberry
|
|
||||||
make DESTDIR=appimage DISTRO=x86_64 VERSION=${VERSION} build-appimage
|
|
||||||
make DESTDIR=appimage DISTRO=armhf VERSION=${VERSION} build-appimage
|
|
||||||
make DESTDIR=appimage DISTRO=i686 VERSION=${VERSION} build-appimage
|
|
||||||
|
|
||||||
# Now create igel version
|
|
||||||
# we need first to create the Appimage for x86_64
|
|
||||||
make DESTDIR=igelimage DISTRO=x86_64 VERSION=${VERSION} build-igel
|
|
||||||
|
|
||||||
# Create the thinpro version
|
|
||||||
make DESTDIR=thinproimage DISTRO=x86_64 VERSION=${VERSION} build-thinpro
|
|
||||||
|
|
||||||
rpm --addsign ../*rpm
|
rpm --addsign ../*rpm
|
||||||
|
@ -1,21 +1,3 @@
|
|||||||
udsclient3 (4.0.0) stable; urgency=medium
|
|
||||||
|
|
||||||
* Upgraded to 4.0.0 release
|
|
||||||
|
|
||||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 1 Jul 2022 15:12:10 +0200
|
|
||||||
|
|
||||||
udsclient3 (4.0.0) stable; urgency=medium
|
|
||||||
|
|
||||||
* Upgraded to 3.6.0 release
|
|
||||||
|
|
||||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 1 Jul 2022 14:12:10 +0200
|
|
||||||
|
|
||||||
udsclient3 (3.5.0) stable; urgency=medium
|
|
||||||
|
|
||||||
* Upgraded to 3.5.0 release
|
|
||||||
|
|
||||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 23 Oct 2020 08:12:10 +0200
|
|
||||||
|
|
||||||
udsclient3 (3.0.0) stable; urgency=medium
|
udsclient3 (3.0.0) stable; urgency=medium
|
||||||
|
|
||||||
* Upgraded to 3.0.0 release
|
* Upgraded to 3.0.0 release
|
||||||
|
@ -1 +1 @@
|
|||||||
10
|
9
|
@ -10,6 +10,6 @@ Package: udsclient3
|
|||||||
Section: admin
|
Section: admin
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: python3-paramiko (>=2.0.0), python3-certifi, python3-cryptography, python3-psutil, python3-pyqt5 (>=5.0), python3 (>=3.6), freerdp2-x11 | freerdp-x11 | freerdp-nightly, desktop-file-utils, ${misc:Depends}
|
Depends: python3-paramiko (>=2.0.0), python3-crypto, python3-pyqt5 (>=5.0), python3-six(>=1.1), python3 (>=3.6), freerdp2-x11 | freerdp-x11, desktop-file-utils, ${misc:Depends}
|
||||||
Description: Client connector for Universal Desktop Services (UDS) Broker
|
Description: Client connector for Universal Desktop Services (UDS) Broker
|
||||||
This package provides the required components to allow this machine to connect to services provided by UDS Broker.
|
This package provides the required components to allow this machine to connect to services provided by UDS Broker.
|
||||||
|
@ -1,38 +1,26 @@
|
|||||||
Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
|
Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
|
||||||
Name: udsclient3
|
Name: udsclient3
|
||||||
Maintainer: Adolfo Gómez García
|
Maintainer: Adolfo Gómez García
|
||||||
Source: http://github.com/dkmstr/openuds/client-py3
|
Source: http://www.udsenterprise.com/
|
||||||
|
|
||||||
Files: *
|
Copyright: 2014 Virtual Cable S.L.U.
|
||||||
Copyright: (c) 2014-2022, Virtual Cable S.L.U.
|
License: BSD-3-clause
|
||||||
License: 3-BSD
|
|
||||||
|
|
||||||
License: 3-BSD
|
License: GPL-2+
|
||||||
All rights reserved.
|
This program is free software; you can redistribute it and/or modify
|
||||||
.
|
it under the terms of the GNU General Public License as published by
|
||||||
Redistribution and use in source and binary forms, with or without
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
modification, are permitted provided that the following conditions are met:
|
(at your option) any later version.
|
||||||
.
|
.
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
This program is distributed in the hope that it will be useful,
|
||||||
list of conditions and the following disclaimer.
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
.
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
GNU General Public License for more details.
|
||||||
this list of conditions and the following disclaimer in the documentation
|
.
|
||||||
and/or other materials provided with the distribution.
|
You should have received a copy of the GNU General Public License along
|
||||||
.
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
* Neither the name of pg_query nor the names of its contributors may be used
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
to endorse or promote products derived from this software without specific
|
.
|
||||||
prior written permission.
|
On Debian systems, the full text of the GNU General Public
|
||||||
.
|
License version 2 can be found in the file
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
`/usr/share/common-licenses/GPL-2'.
|
||||||
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.
|
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
udsclient3_4.0.0_all.deb admin optional
|
udsclient3_3.0.0_all.deb admin optional
|
||||||
udsclient3_4.0.0_amd64.buildinfo admin optional
|
udsclient3_3.0.0_amd64.buildinfo admin optional
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
Name=UDSClient
|
Name=UDSClient
|
||||||
Comment=UDS Helper
|
Comment=UDS Helper
|
||||||
Keywords=uds;client;vdi;
|
Keywords=uds;client;vdi;
|
||||||
Exec=/usr/lib/UDSClient/UDSClient.py %u -platform xcb
|
Exec=/usr/lib/UDSClient/UDSClient.py %u
|
||||||
Icon=help-browser
|
Icon=help-browser
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
Terminal=false
|
Terminal=false
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<profile>
|
|
||||||
<profile_id>1126</profile_id>
|
|
||||||
<profilename>UDSClient</profilename>
|
|
||||||
<firmware>
|
|
||||||
<model>IGEL OS 11</model>
|
|
||||||
<version>11.05.120.01</version>
|
|
||||||
</firmware>
|
|
||||||
<description></description>
|
|
||||||
<overwritesessions>false</overwritesessions>
|
|
||||||
<is_master_profile>false</is_master_profile>
|
|
||||||
<is_igel_os>true</is_igel_os>
|
|
||||||
<settings>
|
|
||||||
<pclass name="custom_partition.enabled">
|
|
||||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">true</pvalue>
|
|
||||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
|
||||||
</pclass>
|
|
||||||
<pclass name="system.security.apparmor">
|
|
||||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">false</pvalue>
|
|
||||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
|
||||||
</pclass>
|
|
||||||
<pclass name="custom_partition.mountpoint">
|
|
||||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">/UDSClient</pvalue>
|
|
||||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
|
||||||
</pclass>
|
|
||||||
<pclass name="custom_partition.size">
|
|
||||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">_SIZE_</pvalue>
|
|
||||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
|
||||||
</pclass>
|
|
||||||
</settings>
|
|
||||||
<instancesettings>
|
|
||||||
<instance classprefix="custom_partition.source%" serialnumber="-719cadfe:17ca470644a:-7fff127.0.1.1">
|
|
||||||
<ivalue classname="custom_partition.source%.autoupdate" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="custom_partition.source%.crypt_password" variableExpression="" variableSubstitutionActive="false">000d4317311f2c0031133c4d3e4c3d</ivalue>
|
|
||||||
<ivalue classname="custom_partition.source%.final_action" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
|
||||||
<ivalue classname="custom_partition.source%.init_action" variableExpression="" variableSubstitutionActive="false">/UDSClient/init.sh</ivalue>
|
|
||||||
<ivalue classname="custom_partition.source%.password" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
|
||||||
<ivalue classname="custom_partition.source%.url" variableExpression="" variableSubstitutionActive="false">https://[UMS_SERVER]:8443/ums_filetransfer/UDSClient-igel.inf</ivalue>
|
|
||||||
<ivalue classname="custom_partition.source%.username" variableExpression="" variableSubstitutionActive="false">[UMS_USERNAME]</ivalue>
|
|
||||||
</instance>
|
|
||||||
<instance classprefix="sessions.chromium%" serialnumber="-6b5264e9:17ca6f65505:-8000127.0.1.1">
|
|
||||||
<ivalue classname="sessions.chromium%.app.browser_startup_page" variableExpression="" variableSubstitutionActive="false">1</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.app.homepage" variableExpression="" variableSubstitutionActive="false">https://demo.udsenterprise.com</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.applaunch" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.applaunch_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.applaunch_system" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.autostart" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.autostartnotify" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.desktop" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.desktop_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.hotkey" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.hotkeymodifier" variableExpression="" variableSubstitutionActive="false">None</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.icon" variableExpression="" variableSubstitutionActive="false">chromium</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.menu_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.name" variableExpression="UDS" variableSubstitutionActive="true">###LOC_DEFAULT###</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.position" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.pulldown" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.pwprotected" variableExpression="" variableSubstitutionActive="false">none</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.quick_start" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.scardautostart" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.snotify" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.startmenu" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.startmenu_system" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.usehotkey" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.waittime2autostart" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
|
||||||
<ivalue classname="sessions.chromium%.waittime2restart" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
|
||||||
</instance>
|
|
||||||
</instancesettings>
|
|
||||||
</profile>
|
|
@ -1,7 +0,0 @@
|
|||||||
[INFO]
|
|
||||||
[PART]
|
|
||||||
file="UDSClient.tar.bz2"
|
|
||||||
version="1.1_igel1"
|
|
||||||
size="_SIZE_"
|
|
||||||
name="UDSClient"
|
|
||||||
minfw="11.05.120"
|
|
@ -1,2 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
cp /UDSClient/UDSClient.desktop /usr/share/applications.mime
|
|
@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Check for root
|
|
||||||
if ! [ $(id -u) = 0 ]; then
|
|
||||||
echo "This script must be run as root"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Installing UDSClient Portable..."
|
|
||||||
|
|
||||||
cp UDSClient-0.0.0-x86_64.AppImage /usr/bin
|
|
||||||
cp UDSClient.desktop /usr/share/applications
|
|
||||||
update-desktop-database
|
|
||||||
|
|
||||||
echo "Installation process done."
|
|
@ -8,8 +8,6 @@ echo "Installation process done."
|
|||||||
echo "Remember that the following packages must be installed on system:"
|
echo "Remember that the following packages must be installed on system:"
|
||||||
echo "* Python3 paramiko"
|
echo "* Python3 paramiko"
|
||||||
echo "* Python3 PyQt5"
|
echo "* Python3 PyQt5"
|
||||||
echo "* Python3 six"
|
|
||||||
echo "* Python3 requests"
|
echo "* Python3 requests"
|
||||||
echo "* Python3 cryptography"
|
|
||||||
echo "Theese packages (as their names), are dependent on your platform, so you must locate and install them"
|
echo "Theese packages (as their names), are dependent on your platform, so you must locate and install them"
|
||||||
echo "Also, ensure that a /media folder exists on your machine, that will be redirected on RDP connections"
|
echo "Also, ensure that a /media folder exists on your machine, that will be redirected on RDP connections"
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# UDS handlers.json
|
|
||||||
cp "/lib/UDSClient/firefox/handlers.json" "$FIREFOX_PROFILE_HANDLERS"
|
|
||||||
ffset "network.protocol-handler.external.uds" "true"
|
|
||||||
ffset "network.protocol-handler.external.udss" "true"
|
|
@ -1,98 +0,0 @@
|
|||||||
{
|
|
||||||
"defaultHandlersVersion": {
|
|
||||||
"en-US": 4
|
|
||||||
},
|
|
||||||
"mimeTypes": {
|
|
||||||
"application/pdf": {
|
|
||||||
"action": 3,
|
|
||||||
"extensions": [
|
|
||||||
"pdf"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"application/x-ica": {
|
|
||||||
"action": 2,
|
|
||||||
"extensions": [
|
|
||||||
"ica"
|
|
||||||
],
|
|
||||||
"handlers": [
|
|
||||||
{
|
|
||||||
"name": "wfica",
|
|
||||||
"path": "/usr/share/hptc-firefox-mgr/handlers/citrix"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"application/x-rdp": {
|
|
||||||
"action": 2,
|
|
||||||
"extensions": [
|
|
||||||
"rdp"
|
|
||||||
],
|
|
||||||
"handlers": [
|
|
||||||
{
|
|
||||||
"name": "HP xfreerdp",
|
|
||||||
"path": "/usr/share/hptc-firefox-mgr/handlers/rdp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"text/lic": {
|
|
||||||
"action": 2,
|
|
||||||
"extensions": [
|
|
||||||
"lic"
|
|
||||||
],
|
|
||||||
"handlers": [
|
|
||||||
{
|
|
||||||
"name": "Copy license to ThinPro",
|
|
||||||
"path": "/usr/share/hptc-firefox-mgr/handlers/copy_lic"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"text/xml": {
|
|
||||||
"action": 3,
|
|
||||||
"extensions": [
|
|
||||||
"xml"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"image/svg+xml": {
|
|
||||||
"action": 3,
|
|
||||||
"extensions": [
|
|
||||||
"svg"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"image/webp": {
|
|
||||||
"action": 3,
|
|
||||||
"extensions": [
|
|
||||||
"webp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"schemes": {
|
|
||||||
"vmware-view": {
|
|
||||||
"action": 2,
|
|
||||||
"handlers": [
|
|
||||||
{
|
|
||||||
"name": "VMWare Horizon View",
|
|
||||||
"path": "/usr/share/hptc-firefox-mgr/handlers/vmware"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"uds": {
|
|
||||||
"action": 2,
|
|
||||||
"handlers": [
|
|
||||||
{
|
|
||||||
"name": "UDS Client for ThinPro (SSL)",
|
|
||||||
"path": "/usr/share/hptc-firefox-mgr/handlers/uds"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"udss": {
|
|
||||||
"action": 2,
|
|
||||||
"handlers": [
|
|
||||||
{
|
|
||||||
"name": "UDS Client for ThinPro",
|
|
||||||
"path": "/usr/share/hptc-firefox-mgr/handlers/uds"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
export LD_PRELOAD=""
|
|
||||||
/bin/udsclient $*
|
|
||||||
exit 0
|
|
@ -1,2 +0,0 @@
|
|||||||
# UDS handlers.json
|
|
||||||
restore "/lib/UDSClient/firefox/handlers.json" "$FIREFOX_PROFILE_HANDLERS"
|
|
@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"defaultHandlersVersion":{
|
|
||||||
"en-US":4
|
|
||||||
},
|
|
||||||
"mimeTypes":{
|
|
||||||
"application/pdf":{
|
|
||||||
"action":3,
|
|
||||||
"extensions":["pdf"]
|
|
||||||
},
|
|
||||||
"application/x-ica":{
|
|
||||||
"action":2,
|
|
||||||
"handlers":[{
|
|
||||||
"name":"wfica",
|
|
||||||
"path":"/usr/bin/hptc-firefox-run-wfica.sh"
|
|
||||||
}],
|
|
||||||
"extensions":["ica"]
|
|
||||||
},
|
|
||||||
"application/x-rdp":{
|
|
||||||
"action":2,
|
|
||||||
"handlers":[{
|
|
||||||
"name":"HP xfreerdp",
|
|
||||||
"path":"/usr/bin/hptc-run-rdp-file-freerdp.sh"
|
|
||||||
}],
|
|
||||||
"extensions":["rdp"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"schemes":{
|
|
||||||
"vmware-view":{
|
|
||||||
"action":2,
|
|
||||||
"handlers":[{
|
|
||||||
"name":"VMWare Horizon View",
|
|
||||||
"path":"/usr/bin/vmware-view"
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"udss":{
|
|
||||||
"action":2,
|
|
||||||
"handlers":[{
|
|
||||||
"name":"UDS Client",
|
|
||||||
"path":"/bin/udsclient"
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"uds":{
|
|
||||||
"action":2,
|
|
||||||
"handlers":[{
|
|
||||||
"name":"UDS Client",
|
|
||||||
"path":"/bin/udsclient"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
// This file can be used to configure global preferences for Firefox
|
|
||||||
// Example: Homepage
|
|
||||||
//pref("browser.startup.homepage", "http://www.weebls-stuff.com/wab/");
|
|
||||||
pref("plugin.default.state", 2);
|
|
||||||
pref("xpinstall.signatures.required", false, locked);
|
|
||||||
pref("extensions.autoDisableScopes", 0, locked);
|
|
||||||
pref("extensions.pocket.enabled", false, locked);
|
|
||||||
pref("extensions.screenshots.disabled", true, locked);
|
|
||||||
pref("datareporting.policy.dataSubmissionEnabled", false, locked);
|
|
||||||
pref("datareporting.policy.dataSubmissionEnabled.v2", false, locked);
|
|
||||||
|
|
||||||
pref("app.update.auto", false, locked);
|
|
||||||
pref("app.update.enabled", false, locked);
|
|
||||||
pref("browser.download.manager.closeWhenDone", true, locked);
|
|
||||||
pref("browser.helperApps.neverAsk.openFile", "application/x-rdp, application/x-java-jnlp-file", locked);
|
|
||||||
pref("browser.EULA.3.accepted", true, locked);
|
|
||||||
pref("browser.rights.3.shown", true, locked);
|
|
||||||
pref("browser.safebrowsing.enabled", false, locked);
|
|
||||||
pref("browser.search.update", false, locked);
|
|
||||||
pref("browser.sessionstore.enabled", false, locked);
|
|
||||||
pref("browser.sessionhistory.cache_subframes", false, locked);
|
|
||||||
pref("datareporting.healthreport.service.enabled", false, locked);
|
|
||||||
pref("datareporting.healthreport.uploadEnabled", false, locked);
|
|
||||||
pref("devtools.toolbox.host", "none", locked);
|
|
||||||
pref("extensions.autoDisableScopes", 14, locked);
|
|
||||||
pref("extensions.blocklist.enabled", false, locked);
|
|
||||||
pref("extensions.update.enabled", false, locked);
|
|
||||||
pref("intl.charsetmenu.browser.cache", "UTF-8", locked);
|
|
||||||
|
|
||||||
pref("network.protocol-handler.external.mailto", false, locked);
|
|
||||||
pref("network.protocol-handler.external.news", false, locked);
|
|
||||||
pref("network.protocol-handler.external.snews", false, locked);
|
|
||||||
pref("network.protocol-handler.external.nntp", false, locked);
|
|
||||||
pref("network.protocol-handler.external-default", false, locked);
|
|
||||||
pref("network.protocol-handler.external.vmware-view", true, locked);
|
|
||||||
pref("network.protocol-handler.external.uds", true, locked);
|
|
||||||
pref("network.protocol-handler.external.udss", true, locked);
|
|
@ -1,38 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Common part
|
|
||||||
|
|
||||||
# unlocks so we can write on TC
|
|
||||||
fsunlock
|
|
||||||
|
|
||||||
cp UDSClient /bin/udsclient
|
|
||||||
chmod 755 /bin/udsclient
|
|
||||||
# RDP Script for UDSClient. Launchs udsclient using the "Template_UDS" profile
|
|
||||||
cp udsrdp /usr/bin
|
|
||||||
|
|
||||||
INSTALLED=0
|
|
||||||
# Installation for 7.1.x version
|
|
||||||
grep -q "7.1" /etc/issue
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "Installing for thinpro version 7.1"
|
|
||||||
# Allow UDS apps without asking
|
|
||||||
cp firefox7.1/syspref.js /etc/firefox
|
|
||||||
# Copy handlers.json for firefox
|
|
||||||
mkdir -p /lib/UDSClient/firefox/ > /dev/null 2>&1
|
|
||||||
cp firefox7.1/handlers.json /lib/UDSClient/firefox/
|
|
||||||
# and runner
|
|
||||||
cp firefox7.1/45-uds /etc/hptc-firefox-mgr/prestart
|
|
||||||
else
|
|
||||||
echo "Installing for thinpro version 7.2 or later"
|
|
||||||
# Copy handlers for firefox
|
|
||||||
mkdir -p /lib/UDSClient/firefox/ > /dev/null 2>&1
|
|
||||||
# Copy handlers.json for firefox
|
|
||||||
cp firefox/handlers.json /lib/UDSClient/firefox/
|
|
||||||
cp firefox/45-uds /etc/hptc-firefox-mgr/prestart
|
|
||||||
# copy uds handler for firefox
|
|
||||||
cp firefox/uds /usr/share/hptc-firefox-mgr/handlers/uds
|
|
||||||
chmod 755 /usr/share/hptc-firefox-mgr/handlers/uds
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Common part
|
|
||||||
fslock
|
|
@ -1,390 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
function clearParams {
|
|
||||||
mclient set $REGKEY/address ""
|
|
||||||
mclient set $REGKEY/username ""
|
|
||||||
mclient set $REGKEY/password ""
|
|
||||||
mclient set $REGKEY/domain ""
|
|
||||||
|
|
||||||
mclient set $REGKEY/authorizations/user/execution 0
|
|
||||||
|
|
||||||
mclient commit
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRegKey {
|
|
||||||
# Get Template_UDS
|
|
||||||
for key in `mclient get root/ConnectionType/freerdp/connections | sed "s/dir //g"`; do
|
|
||||||
val=`mclient get $key/label | sed "s/value //g"`
|
|
||||||
if [ "$val" == "Template_UDS" ]; then
|
|
||||||
REGKEY=$key
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
function createUDSConnectionTemplate {
|
|
||||||
TMPFILE=$(mktemp /tmp/udsexport.XXXXXX)
|
|
||||||
cat > $TMPFILE << EOF
|
|
||||||
<Profile>
|
|
||||||
<ProfileSettings>
|
|
||||||
<Name>UDS Template Profile</Name>
|
|
||||||
<RegistryRoot>root/ConnectionType/freerdp/connections/{ff064bd9-047a-45ec-b70f-04ab218186ff}</RegistryRoot>
|
|
||||||
<Target>
|
|
||||||
<Hardware>t420</Hardware>
|
|
||||||
<ImageId>T7X62022</ImageId>
|
|
||||||
<Version>6.2.0</Version>
|
|
||||||
<Config>standard</Config>
|
|
||||||
</Target>
|
|
||||||
</ProfileSettings>
|
|
||||||
<ProfileRegistry>
|
|
||||||
<NodeDir name="{ff064bd9-047a-45ec-b70f-04ab218186ff}">
|
|
||||||
<NodeDir name="rdWebFeed">
|
|
||||||
<NodeKey name="keepResourcesWindowOpened">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="autoStartSingleResource">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="autoDisconnectTimeout">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
</NodeDir>
|
|
||||||
<NodeDir name="loginfields">
|
|
||||||
<NodeKey name="username">
|
|
||||||
<NodeParam name="value">3</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="rememberme">
|
|
||||||
<NodeParam name="value">2</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="password">
|
|
||||||
<NodeParam name="value">3</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="domain">
|
|
||||||
<NodeParam name="value">3</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
</NodeDir>
|
|
||||||
<NodeDir name="authorizations">
|
|
||||||
<NodeDir name="user">
|
|
||||||
<NodeKey name="execution">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="edit">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
</NodeDir>
|
|
||||||
</NodeDir>
|
|
||||||
<NodeKey name="address">
|
|
||||||
<NodeParam name="value"/>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="username">
|
|
||||||
<NodeParam name="value"/>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="password">
|
|
||||||
<NodeParam name="value">NLCR.1</NodeParam>
|
|
||||||
<NodeParam name="type">rc4</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="domain">
|
|
||||||
<NodeParam name="value"/>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="label">
|
|
||||||
<NodeParam name="value">Template_UDS</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="credentialsType">
|
|
||||||
<NodeParam name="value">password</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="gatewayEnabled">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="gatewayPort">
|
|
||||||
<NodeParam name="value">443</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="gatewayUsesSameCredentials">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="gatewayCredentialsType">
|
|
||||||
<NodeParam name="value">password</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="remoteDesktopService">
|
|
||||||
<NodeParam name="value">Remote Computer</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="windowMode">
|
|
||||||
<NodeParam name="value">Remote Application</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="seamlessWindow">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="windowType">
|
|
||||||
<NodeParam name="value">full</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="windowSizePercentage">
|
|
||||||
<NodeParam name="value">70</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="windowSizeWidth">
|
|
||||||
<NodeParam name="value">1024</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="windowSizeHeight">
|
|
||||||
<NodeParam name="value">768</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="mouseMotionEvents">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="compression">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="rdpEncryption">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="offScreenBitmaps">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="attachToConsole">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="clipboardExtension">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="rdp6Buffering">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="rdpProgressiveCodec">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="securityLevel">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="tlsVersion">
|
|
||||||
<NodeParam name="value">auto</NodeParam>
|
|
||||||
<NodeParam name="type">string</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="sound">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="printerMapping">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="portMapping">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="usbStorageRedirection">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="localPartitionRedirection">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="scRedirection">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="usbMiscRedirection">
|
|
||||||
<NodeParam name="value">2</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="perfFlagNoWallpaper">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="perfFlagFontSmoothing">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="perfFlagDesktopComposition">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="perfFlagNoWindowDrag">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="perfFlagNoMenuAnimations">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="perfFlagNoTheming">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="timeoutsEnabled">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="timeoutWarning">
|
|
||||||
<NodeParam name="value">6000</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="timeoutWarningDialog">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="timeoutRecovery">
|
|
||||||
<NodeParam name="value">30000</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="timeoutError">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="showRDPDashboard">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="showConnectionGraph">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="x11Synchronous">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="x11Logging">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="x11LogAutoflush">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="x11Capture">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="SingleSignOn">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="autostart">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">number</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="waitForNetwork">
|
|
||||||
<NodeParam name="value">1</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="hasDesktopIcon">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
<NodeKey name="autoReconnect">
|
|
||||||
<NodeParam name="value">0</NodeParam>
|
|
||||||
<NodeParam name="type">bool</NodeParam>
|
|
||||||
</NodeKey>
|
|
||||||
</NodeDir>
|
|
||||||
</ProfileRegistry>
|
|
||||||
<ProfileFiles/>
|
|
||||||
</Profile>
|
|
||||||
EOF
|
|
||||||
mclient import $TMPFILE
|
|
||||||
rm $TMPFILE
|
|
||||||
}
|
|
||||||
|
|
||||||
ADDRESS=
|
|
||||||
USERNAME=
|
|
||||||
PASSWORD=
|
|
||||||
DOMAIN=
|
|
||||||
REGKEY=
|
|
||||||
CLEAR=0
|
|
||||||
|
|
||||||
# Try to locate registry key for UDS Template
|
|
||||||
getRegKey
|
|
||||||
|
|
||||||
if [ "$REGKEY" == "" ]; then
|
|
||||||
# Not found, create on based on our template
|
|
||||||
createUDSConnectionTemplate
|
|
||||||
getRegKey
|
|
||||||
fi
|
|
||||||
|
|
||||||
for param in $@; do
|
|
||||||
if [ "/u:" == "${param:0:3}" ]; then
|
|
||||||
USERNAME=${param:3}
|
|
||||||
CLEAR=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "/p:" == "${param:0:3}" ]; then
|
|
||||||
PASSWORD=${param:3}
|
|
||||||
CLEAR=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "/d:" == "${param:0:3}" ]; then
|
|
||||||
DOMAIN=${param:3}
|
|
||||||
CLEAR=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "/v:" == "${param:0:3}" ]; then
|
|
||||||
ADDRESS=${param:3}
|
|
||||||
CLEAR=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$CLEAR" -eq 1 ]; then
|
|
||||||
clearParams
|
|
||||||
fi
|
|
||||||
|
|
||||||
ID=`basename $REGKEY`
|
|
||||||
RESPAWN=0
|
|
||||||
|
|
||||||
if [ "" != "$ADDRESS" ]; then
|
|
||||||
mclient set $REGKEY/address "${ADDRESS}"
|
|
||||||
RESPAWN=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "" != "$USERNAME" ]; then
|
|
||||||
mclient set $REGKEY/username "${USERNAME}"
|
|
||||||
RESPAWN=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "" != "$PASSWORD" ]; then
|
|
||||||
mclient set $REGKEY/password "${PASSWORD}"
|
|
||||||
RESPAWN=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "" != "$DOMAIN" ]; then
|
|
||||||
mclient set $REGKEY/domain "${DOMAIN}"
|
|
||||||
RESPAWN=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$RESPAWN" -eq 1 ]; then
|
|
||||||
mclient set $REGKEY/authorizations/user/execution 1
|
|
||||||
mclient commit
|
|
||||||
exec $0 # Restart without command line
|
|
||||||
fi
|
|
||||||
|
|
||||||
process-connection $ID
|
|
||||||
|
|
||||||
clearParams
|
|
@ -1,62 +0,0 @@
|
|||||||
version: 1
|
|
||||||
script:
|
|
||||||
# Remove any previous build
|
|
||||||
- rm -rf /tmp/UDSClientDir | true
|
|
||||||
# Make usr and icons dirs
|
|
||||||
- mkdir -p /tmp/UDSClientDir/usr/src
|
|
||||||
# Copy the python application code into the UDSClientDir
|
|
||||||
- cp ../src/UDS*.py /tmp/UDSClientDir/usr/src
|
|
||||||
- cp -r ../src/uds /tmp/UDSClientDir/usr/src
|
|
||||||
# Remove __pycache__ and .mypy if exists
|
|
||||||
- rm /tmp/UDSClientDir/usr/src/.mypy_cache -rf 2>&1 > /dev/null
|
|
||||||
- rm /tmp/UDSClientDir/usr/src/uds/.mypy_cache -rf 2>&1 > /dev/null
|
|
||||||
- rm /tmp/UDSClientDir/usr/src/__pycache__ -rf 2>&1 > /dev/null
|
|
||||||
- rm /tmp/UDSClientDir/usr/src/uds/__pycache__ -rf 2>&1 > /dev/null
|
|
||||||
|
|
||||||
AppDir:
|
|
||||||
# On /tmp, that is an ext4 filesystem. On btrfs squashfs complains with "Unrecognised xattr prefix btrfs.compression"
|
|
||||||
path: /tmp/UDSClientDir
|
|
||||||
|
|
||||||
app_info:
|
|
||||||
id: com.udsenterprise.UDSClient3
|
|
||||||
name: UDSClient
|
|
||||||
icon: utilities-terminal
|
|
||||||
version: 0.0.0
|
|
||||||
# Set the python executable as entry point
|
|
||||||
exec: usr/bin/python3
|
|
||||||
# Set the application main script path as argument. Use '$@' to forward CLI parameters
|
|
||||||
exec_args: "$APPDIR/usr/src/UDSClient.py $@"
|
|
||||||
|
|
||||||
apt:
|
|
||||||
arch: amd64
|
|
||||||
sources:
|
|
||||||
- sourceline: 'deb [arch=amd64] http://ftp.de.debian.org/debian/ bullseye main contrib non-free'
|
|
||||||
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x648ACFD622F3D138'
|
|
||||||
|
|
||||||
include:
|
|
||||||
- python3
|
|
||||||
- python3-pkg-resources
|
|
||||||
- python3-pyqt5
|
|
||||||
- python3-paramiko
|
|
||||||
- python3-cryptography
|
|
||||||
- python3-certifi
|
|
||||||
- python3-psutil
|
|
||||||
- freerdp2-x11
|
|
||||||
- freerdp2-wayland
|
|
||||||
- x2goclient
|
|
||||||
- openssh-sftp-server
|
|
||||||
exclude: []
|
|
||||||
|
|
||||||
runtime:
|
|
||||||
env:
|
|
||||||
# Set python home
|
|
||||||
# See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHOME
|
|
||||||
PYTHONHOME: '${APPDIR}/usr'
|
|
||||||
# Path to the site-packages dir or other modules dirs
|
|
||||||
# See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH
|
|
||||||
PYTHONPATH: '${APPDIR}/usr/lib/python3.9/site-packages'
|
|
||||||
|
|
||||||
AppImage:
|
|
||||||
update-information: None
|
|
||||||
sign-key: None
|
|
||||||
arch: x86_64
|
|
@ -11,7 +11,7 @@ Release: %{release}
|
|||||||
Summary: Client for Universal Desktop Services (UDS) Broker
|
Summary: Client for Universal Desktop Services (UDS) Broker
|
||||||
License: BSD3
|
License: BSD3
|
||||||
Group: Applications/Productivity
|
Group: Applications/Productivity
|
||||||
Requires: python3-paramiko python3-qt5 python3-cryptography python3-certifi python3-psutil
|
Requires: python3-six python3-requests python3-paramiko python3-qt5 (python3-crypto or python3-pycrypto)
|
||||||
Vendor: Virtual Cable S.L.U.
|
Vendor: Virtual Cable S.L.U.
|
||||||
URL: http://www.udsenterprise.com
|
URL: http://www.udsenterprise.com
|
||||||
Provides: udsclient
|
Provides: udsclient
|
||||||
|
6
client-py3/full/src/.gitignore
vendored
6
client-py3/full/src/.gitignore
vendored
@ -1,6 +1,4 @@
|
|||||||
/build
|
/build
|
||||||
/dist
|
/dist
|
||||||
/UDSClient*.pkg
|
UDSClient.dmg
|
||||||
/UDSClient*.dist
|
UDSClient.pkg
|
||||||
/UDSClient*.build
|
|
||||||
/.eggs
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env -S python3 -s
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
# Copyright (c) 2014-2017 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -12,7 +12,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,45 +31,41 @@
|
|||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
import sys
|
import sys
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import time
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import threading
|
import json
|
||||||
import typing
|
import base64, bz2
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtGui, QtWidgets # @UnresolvedImport
|
||||||
from PyQt5.QtCore import QSettings
|
import six
|
||||||
|
|
||||||
from uds.rest import RestApi, RetryException, InvalidVersion, UDSException
|
|
||||||
|
|
||||||
# Just to ensure there are available on runtime
|
|
||||||
from uds.forward import forward as ssh_forward # type: ignore
|
|
||||||
from uds.tunnel import forward as tunnel_forwards # type: ignore
|
|
||||||
|
|
||||||
|
from uds.rest import RestRequest
|
||||||
|
from uds.forward import forward # pylint: disable=unused-import
|
||||||
from uds.log import logger
|
from uds.log import logger
|
||||||
from uds import tools
|
from uds import tools
|
||||||
from uds import VERSION
|
from uds import VERSION
|
||||||
|
|
||||||
from UDSWindow import Ui_MainWindow
|
from UDSWindow import Ui_MainWindow
|
||||||
|
|
||||||
|
# Server before this version uses "unsigned" scripts
|
||||||
|
OLD_METHOD_VERSION = '2.4.0'
|
||||||
|
|
||||||
|
class RetryException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class UDSClient(QtWidgets.QMainWindow):
|
class UDSClient(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
ticket: str = ''
|
ticket = None
|
||||||
scrambler: str = ''
|
scrambler = None
|
||||||
withError = False
|
withError = False
|
||||||
animTimer: typing.Optional[QtCore.QTimer] = None
|
animTimer = None
|
||||||
anim: int = 0
|
anim = 0
|
||||||
animInverted: bool = False
|
animInverted = False
|
||||||
api: RestApi
|
serverVersion = 'X.Y.Z' # Will be overwriten on getVersion
|
||||||
|
req = None
|
||||||
|
|
||||||
def __init__(self, api: RestApi, ticket: str, scrambler: str):
|
def __init__(self):
|
||||||
QtWidgets.QMainWindow.__init__(self)
|
QtWidgets.QMainWindow.__init__(self)
|
||||||
self.api = api
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||||
self.ticket = ticket
|
|
||||||
self.scrambler = scrambler
|
|
||||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) # type: ignore
|
|
||||||
|
|
||||||
self.ui = Ui_MainWindow()
|
self.ui = Ui_MainWindow()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
@ -79,36 +75,41 @@ class UDSClient(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
self.ui.info.setText('Initializing...')
|
self.ui.info.setText('Initializing...')
|
||||||
|
|
||||||
screen_geometry = QtGui.QGuiApplication.primaryScreen().geometry()
|
screen = QtWidgets.QDesktopWidget().screenGeometry()
|
||||||
mysize = self.geometry()
|
mysize = self.geometry()
|
||||||
hpos = (screen_geometry.width() - mysize.width()) // 2
|
hpos = (screen.width() - mysize.width()) // 2
|
||||||
vpos = (screen_geometry.height() - mysize.height() - mysize.height()) // 2
|
vpos = (screen.height() - mysize.height() - mysize.height()) // 2
|
||||||
self.move(hpos, vpos)
|
self.move(hpos, vpos)
|
||||||
|
|
||||||
self.animTimer = QtCore.QTimer()
|
self.animTimer = QtCore.QTimer()
|
||||||
self.animTimer.timeout.connect(self.updateAnim) # type: ignore
|
self.animTimer.timeout.connect(self.updateAnim)
|
||||||
# QtCore.QObject.connect(self.animTimer, QtCore.SIGNAL('timeout()'), self.updateAnim)
|
# QtCore.QObject.connect(self.animTimer, QtCore.SIGNAL('timeout()'), self.updateAnim)
|
||||||
|
|
||||||
self.activateWindow()
|
self.activateWindow()
|
||||||
|
|
||||||
self.startAnim()
|
self.startAnim()
|
||||||
|
|
||||||
|
|
||||||
def closeWindow(self):
|
def closeWindow(self):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
def processError(self, data):
|
||||||
|
if 'error' in data:
|
||||||
|
# QtWidgets.QMessageBox.critical(self, 'Request error {}'.format(data.get('retryable', '0')), data['error'], QtWidgets.QMessageBox.Ok)
|
||||||
|
if data.get('retryable', '0') == '1':
|
||||||
|
raise RetryException(data['error'])
|
||||||
|
|
||||||
|
raise Exception(data['error'])
|
||||||
|
# QtWidgets.QMessageBox.critical(self, 'Request error', rest.data['error'], QtWidgets.QMessageBox.Ok)
|
||||||
|
# self.closeWindow()
|
||||||
|
# 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, # type: ignore
|
|
||||||
'UDS Plugin Error',
|
|
||||||
'{}'.format(error),
|
|
||||||
QtWidgets.QMessageBox.Ok,
|
|
||||||
)
|
|
||||||
self.withError = True
|
self.withError = True
|
||||||
|
|
||||||
def cancelPushed(self):
|
def cancelPushed(self):
|
||||||
@ -124,227 +125,165 @@ class UDSClient(QtWidgets.QMainWindow):
|
|||||||
self.ui.progressBar.setValue(self.anim)
|
self.ui.progressBar.setValue(self.anim)
|
||||||
|
|
||||||
def startAnim(self):
|
def startAnim(self):
|
||||||
self.ui.progressBar.invertedAppearance = False # type: ignore
|
self.ui.progressBar.invertedAppearance = False
|
||||||
self.anim = 0
|
self.anim = 0
|
||||||
self.animInverted = False
|
self.animInverted = False
|
||||||
self.ui.progressBar.setInvertedAppearance(self.animInverted)
|
self.ui.progressBar.setInvertedAppearance(self.animInverted)
|
||||||
if self.animTimer:
|
self.animTimer.start(40)
|
||||||
self.animTimer.start(40)
|
|
||||||
|
|
||||||
def stopAnim(self):
|
def stopAnim(self):
|
||||||
self.ui.progressBar.invertedAppearance = False # type: ignore
|
self.ui.progressBar.invertedAppearance = False
|
||||||
if self.animTimer:
|
self.animTimer.stop()
|
||||||
self.animTimer.stop()
|
|
||||||
|
|
||||||
def getVersion(self):
|
def getVersion(self):
|
||||||
|
self.req = RestRequest('', self, self.version)
|
||||||
|
self.req.get()
|
||||||
|
|
||||||
|
def version(self, data):
|
||||||
try:
|
try:
|
||||||
self.api.getVersion()
|
self.processError(data)
|
||||||
except InvalidVersion as e:
|
self.ui.info.setText('Processing...')
|
||||||
QtWidgets.QMessageBox.critical(
|
|
||||||
self,
|
if data['result']['requiredVersion'] > VERSION:
|
||||||
'Upgrade required',
|
QtWidgets.QMessageBox.critical(self, 'Upgrade required', 'A newer connector version is required.\nA browser will be opened to download it.', QtWidgets.QMessageBox.Ok)
|
||||||
'A newer connector version is required.\nA browser will be opened to download it.',
|
webbrowser.open(data['result']['downloadUrl'])
|
||||||
QtWidgets.QMessageBox.Ok,
|
self.closeWindow()
|
||||||
)
|
return
|
||||||
webbrowser.open(e.downloadUrl)
|
|
||||||
self.closeWindow()
|
self.serverVersion = data['result']['requiredVersion']
|
||||||
return
|
self.getTransportData()
|
||||||
|
|
||||||
|
except RetryException as e:
|
||||||
|
self.ui.info.setText(str(e))
|
||||||
|
QtCore.QTimer.singleShot(1000, self.getVersion)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.showError(e)
|
self.showError(e)
|
||||||
|
|
||||||
self.getTransportData()
|
|
||||||
|
|
||||||
def getTransportData(self):
|
def getTransportData(self):
|
||||||
try:
|
try:
|
||||||
script, params = self.api.getScriptAndParams(self.ticket, self.scrambler)
|
self.req = RestRequest('/{}/{}'.format(self.ticket, self.scrambler), self, self.transportDataReceived, params={'hostname': tools.getHostName(), 'version': VERSION})
|
||||||
|
self.req.get()
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception('Got exception on getTransportData')
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def transportDataReceived(self, data):
|
||||||
|
logger.debug('Transport data received')
|
||||||
|
try:
|
||||||
|
self.processError(data)
|
||||||
|
|
||||||
|
params = None
|
||||||
|
|
||||||
|
if self.serverVersion <= OLD_METHOD_VERSION:
|
||||||
|
script = bz2.decompress(base64.b64decode(data['result']))
|
||||||
|
# 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'version)', b'version.decode("utf-8"))')
|
||||||
|
else:
|
||||||
|
res = data['result']
|
||||||
|
# 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
|
||||||
|
#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'])))
|
||||||
|
if tools.verifySignature(script, signature) is False:
|
||||||
|
logger.error('Signature is invalid')
|
||||||
|
|
||||||
|
raise Exception('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()
|
||||||
self.closeWindow()
|
|
||||||
|
|
||||||
exec(script, globals(), {'parent': self, 'sp': params})
|
six.exec_(script.decode("utf-8"), globals(), {'parent': self, 'sp': params})
|
||||||
|
|
||||||
# Execute the waiting tasks...
|
|
||||||
threading.Thread(target=endScript).start()
|
|
||||||
|
|
||||||
except RetryException as e:
|
except RetryException as e:
|
||||||
self.ui.info.setText(str(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:')
|
||||||
self.showError(e)
|
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):
|
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 endScript():
|
def done(data):
|
||||||
# Wait a bit before start processing ending sequence
|
QtWidgets.QMessageBox.critical(None, 'Notice', six.text_type(data.data), QtWidgets.QMessageBox.Ok)
|
||||||
time.sleep(3)
|
sys.exit(0)
|
||||||
try:
|
|
||||||
# Remove early stage files...
|
|
||||||
tools.unlinkFiles(early=True)
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug('Unlinking files on early stage: %s', e)
|
|
||||||
|
|
||||||
# After running script, wait for stuff
|
|
||||||
try:
|
|
||||||
logger.debug('Wating for tasks to finish...')
|
|
||||||
tools.waitForTasks()
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug('Watiting for tasks to finish: %s', e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.debug('Unlinking files')
|
|
||||||
tools.unlinkFiles(early=False)
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug('Unlinking files on later stage: %s', e)
|
|
||||||
|
|
||||||
# Removing
|
|
||||||
try:
|
|
||||||
logger.debug('Executing threads before exit')
|
|
||||||
tools.execBeforeExit()
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug('execBeforeExit: %s', e)
|
|
||||||
|
|
||||||
logger.debug('endScript done')
|
|
||||||
|
|
||||||
|
|
||||||
# Ask user to approve endpoint
|
# Ask user to approve endpoint
|
||||||
def approveHost(hostName: str):
|
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 not approved:
|
if approved or QtWidgets.QMessageBox.warning(parentWindow, 'ACCESS Warning', errorString, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.Yes:
|
||||||
if (
|
settings.setValue(hostName, True)
|
||||||
QtWidgets.QMessageBox.warning(
|
|
||||||
None, # type: ignore
|
|
||||||
'ACCESS Warning',
|
|
||||||
errorString,
|
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, # type: ignore
|
|
||||||
)
|
|
||||||
== QtWidgets.QMessageBox.Yes
|
|
||||||
):
|
|
||||||
settings.setValue(hostName, True)
|
|
||||||
approved = True
|
|
||||||
|
|
||||||
settings.endGroup()
|
|
||||||
return approved
|
|
||||||
|
|
||||||
|
|
||||||
def sslError(hostname: str, serial):
|
|
||||||
settings = QSettings()
|
|
||||||
settings.beginGroup('ssl')
|
|
||||||
|
|
||||||
approved = settings.value(serial, False)
|
|
||||||
|
|
||||||
if (
|
|
||||||
approved
|
|
||||||
or QtWidgets.QMessageBox.warning(
|
|
||||||
None, # type: ignore
|
|
||||||
'SSL Warning',
|
|
||||||
f'Could not check SSL certificate for {hostname}.\nDo you trust this host?',
|
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, # type: ignore
|
|
||||||
)
|
|
||||||
== QtWidgets.QMessageBox.Yes
|
|
||||||
):
|
|
||||||
approved = True
|
approved = True
|
||||||
settings.setValue(serial, True)
|
|
||||||
|
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
return approved
|
return approved
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
# Used only if command line says so
|
logger.debug('Initializing connector')
|
||||||
def minimal(api: RestApi, ticket: str, scrambler: str):
|
|
||||||
try:
|
# Initialize app
|
||||||
logger.info('Minimal Execution')
|
|
||||||
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.\nA browser will be opened to download it.',
|
|
||||||
QtWidgets.QMessageBox.Ok,
|
|
||||||
)
|
|
||||||
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(
|
|
||||||
None, # type: ignore
|
|
||||||
'Service not ready',
|
|
||||||
'{}'.format('.\n'.join(str(e).split('.')))
|
|
||||||
+ '\n\nPlease, retry again in a while.',
|
|
||||||
QtWidgets.QMessageBox.Ok,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
# logger.exception('Got exception on getTransportData')
|
|
||||||
QtWidgets.QMessageBox.critical(
|
|
||||||
None, # type: ignore
|
|
||||||
'Error',
|
|
||||||
'{}'.format(str(e)) + '\n\nPlease, retry again in a while.',
|
|
||||||
QtWidgets.QMessageBox.Ok,
|
|
||||||
)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def main(args: typing.List[str]):
|
|
||||||
app = QtWidgets.QApplication(sys.argv)
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
logger.debug('Initializing connector for %s(%s)', sys.platform, platform.machine())
|
|
||||||
|
|
||||||
logger.debug('Arguments: %s', args)
|
|
||||||
# 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') # type: ignore
|
app.setStyle('plastique')
|
||||||
else:
|
|
||||||
logger.debug('Platform is Mac OS, adding homebrew possible paths')
|
if six.PY3 is False:
|
||||||
os.environ['PATH'] += ''.join(
|
logger.debug('Fixing threaded execution of commands')
|
||||||
os.pathsep + i
|
import threading
|
||||||
for i in (
|
threading._DummyThread._Thread__stop = lambda x: 42 # type: ignore # pylint: disable=protected-access
|
||||||
'/usr/local/bin',
|
|
||||||
'/opt/homebrew/bin',
|
|
||||||
)
|
|
||||||
)
|
|
||||||
logger.debug('Now path is %s', os.environ['PATH'])
|
|
||||||
|
|
||||||
# First parameter must be url
|
# First parameter must be url
|
||||||
useMinimal = False
|
|
||||||
try:
|
try:
|
||||||
uri = args[1]
|
uri = sys.argv[1]
|
||||||
|
|
||||||
if uri == '--minimal':
|
|
||||||
useMinimal = True
|
|
||||||
uri = args[2] # And get URI
|
|
||||||
|
|
||||||
if uri == '--test':
|
if uri == '--test':
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@ -354,28 +293,17 @@ def main(args: typing.List[str]):
|
|||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
ssl = uri[3] == 's'
|
ssl = uri[3] == 's'
|
||||||
host, ticket, 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,
|
|
||||||
ticket,
|
|
||||||
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, # type: ignore
|
|
||||||
'Notice',
|
|
||||||
'UDS Client Version {}'.format(VERSION),
|
|
||||||
QtWidgets.QMessageBox.Ok,
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Setup REST api endpoint
|
# Setup REST api endpoint
|
||||||
api = RestApi(
|
RestRequest.restApiUrl = '{}://{}/rest/client'.format(['http', 'https'][ssl], host)
|
||||||
'{}://{}/uds/rest/client'.format(['http', 'https'][ssl], host), sslError
|
logger.debug('Setting request URL to %s', RestRequest.restApiUrl)
|
||||||
)
|
# RestRequest.restApiUrl = 'https://172.27.0.1/rest/client'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug('Starting execution')
|
logger.debug('Starting execution')
|
||||||
@ -384,23 +312,18 @@ def main(args: typing.List[str]):
|
|||||||
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(api, ticket, scrambler)
|
win = UDSClient()
|
||||||
win.show()
|
win.show()
|
||||||
|
|
||||||
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', str(e), QtWidgets.QMessageBox.Ok # type: ignore
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug('Exiting')
|
logger.debug('Exiting')
|
||||||
sys.exit(exitVal)
|
sys.exit(exitVal)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(sys.argv)
|
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
import sys
|
|
||||||
import os.path
|
|
||||||
import subprocess
|
|
||||||
import typing
|
|
||||||
|
|
||||||
from uds.log import logger
|
|
||||||
import UDSClient
|
|
||||||
from UDSLauncherMac import Ui_MacLauncher
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
|
||||||
|
|
||||||
SCRIPT_NAME = 'UDSClientLauncher'
|
|
||||||
|
|
||||||
class UdsApplication(QtWidgets.QApplication):
|
|
||||||
path: str
|
|
||||||
tunnels: typing.List[subprocess.Popen]
|
|
||||||
|
|
||||||
def __init__(self, argv: typing.List[str]) -> None:
|
|
||||||
super().__init__(argv)
|
|
||||||
self.path = os.path.join(os.path.dirname(sys.argv[0]).replace('Resources', 'MacOS'), SCRIPT_NAME)
|
|
||||||
self.tunnels = []
|
|
||||||
self.lastWindowClosed.connect(self.closeTunnels) # type: ignore
|
|
||||||
|
|
||||||
def cleanTunnels(self) -> None:
|
|
||||||
def isRunning(p: subprocess.Popen):
|
|
||||||
try:
|
|
||||||
if p.poll() is None:
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug('Got error polling subprocess: %s', e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
for k in [i for i, tunnel in enumerate(self.tunnels) if not isRunning(tunnel)]:
|
|
||||||
try:
|
|
||||||
del self.tunnels[k]
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug('Error closing tunnel: %s', e)
|
|
||||||
|
|
||||||
def closeTunnels(self) -> None:
|
|
||||||
logger.debug('Closing remaining tunnels')
|
|
||||||
for tunnel in self.tunnels:
|
|
||||||
logger.debug('Checking %s - "%s"', tunnel, tunnel.poll())
|
|
||||||
if tunnel.poll() is None: # Running
|
|
||||||
logger.info('Found running tunnel %s, closing it', tunnel.pid)
|
|
||||||
tunnel.kill()
|
|
||||||
|
|
||||||
def event(self, evnt: QtCore.QEvent) -> bool:
|
|
||||||
if evnt.type() == QtCore.QEvent.FileOpen: # type: ignore
|
|
||||||
fe = typing.cast(QtGui.QFileOpenEvent, evnt)
|
|
||||||
logger.debug('Got url: %s', fe.url().url())
|
|
||||||
fe.accept()
|
|
||||||
logger.debug('Spawning %s', self.path)
|
|
||||||
# First, remove all finished tunnel processed from check queue
|
|
||||||
self.cleanTunnels()
|
|
||||||
# And now add a new one
|
|
||||||
self.tunnels.append(subprocess.Popen([self.path, fe.url().url()]))
|
|
||||||
|
|
||||||
return super().event(evnt)
|
|
||||||
|
|
||||||
|
|
||||||
def main(args: typing.List[str]):
|
|
||||||
if len(args) > 1:
|
|
||||||
UDSClient.main(args)
|
|
||||||
else:
|
|
||||||
app = UdsApplication(sys.argv)
|
|
||||||
window = QtWidgets.QMainWindow()
|
|
||||||
Ui_MacLauncher().setupUi(window)
|
|
||||||
|
|
||||||
window.showMinimized()
|
|
||||||
|
|
||||||
sys.exit(app.exec())
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(args=sys.argv)
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Form implementation generated from reading ui file 'UDSLauncherMac.ui'
|
|
||||||
#
|
|
||||||
# Created by: PyQt5 UI code generator 5.15.2
|
|
||||||
#
|
|
||||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
|
||||||
# run again. Do not edit this file unless you know what you are doing.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
||||||
|
|
||||||
|
|
||||||
class Ui_MacLauncher(object):
|
|
||||||
def setupUi(self, MacLauncher):
|
|
||||||
MacLauncher.setObjectName("MacLauncher")
|
|
||||||
MacLauncher.setWindowModality(QtCore.Qt.NonModal)
|
|
||||||
MacLauncher.resize(235, 120)
|
|
||||||
MacLauncher.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
|
|
||||||
icon = QtGui.QIcon()
|
|
||||||
icon.addPixmap(QtGui.QPixmap(":/images/logo-uds-small"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
||||||
MacLauncher.setWindowIcon(icon)
|
|
||||||
MacLauncher.setWindowOpacity(1.0)
|
|
||||||
self.centralwidget = QtWidgets.QWidget(MacLauncher)
|
|
||||||
self.centralwidget.setAutoFillBackground(True)
|
|
||||||
self.centralwidget.setObjectName("centralwidget")
|
|
||||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
|
|
||||||
self.verticalLayout_2.setContentsMargins(4, 4, 4, 4)
|
|
||||||
self.verticalLayout_2.setSpacing(4)
|
|
||||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
|
||||||
self.frame = QtWidgets.QFrame(self.centralwidget)
|
|
||||||
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
|
||||||
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
|
|
||||||
self.frame.setObjectName("frame")
|
|
||||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.frame)
|
|
||||||
self.verticalLayout.setObjectName("verticalLayout")
|
|
||||||
self.topLabel = QtWidgets.QLabel(self.frame)
|
|
||||||
self.topLabel.setTextFormat(QtCore.Qt.RichText)
|
|
||||||
self.topLabel.setObjectName("topLabel")
|
|
||||||
self.verticalLayout.addWidget(self.topLabel)
|
|
||||||
self.image = QtWidgets.QLabel(self.frame)
|
|
||||||
self.image.setMinimumSize(QtCore.QSize(0, 32))
|
|
||||||
self.image.setAutoFillBackground(True)
|
|
||||||
self.image.setText("")
|
|
||||||
self.image.setPixmap(QtGui.QPixmap(":/images/logo-uds-small"))
|
|
||||||
self.image.setScaledContents(False)
|
|
||||||
self.image.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
self.image.setObjectName("image")
|
|
||||||
self.verticalLayout.addWidget(self.image)
|
|
||||||
self.label_2 = QtWidgets.QLabel(self.frame)
|
|
||||||
self.label_2.setTextFormat(QtCore.Qt.RichText)
|
|
||||||
self.label_2.setObjectName("label_2")
|
|
||||||
self.verticalLayout.addWidget(self.label_2)
|
|
||||||
self.verticalLayout_2.addWidget(self.frame)
|
|
||||||
MacLauncher.setCentralWidget(self.centralwidget)
|
|
||||||
|
|
||||||
self.retranslateUi(MacLauncher)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(MacLauncher)
|
|
||||||
|
|
||||||
def retranslateUi(self, MacLauncher):
|
|
||||||
_translate = QtCore.QCoreApplication.translate
|
|
||||||
MacLauncher.setWindowTitle(_translate("MacLauncher", "UDS Launcher"))
|
|
||||||
self.topLabel.setText(_translate("MacLauncher", "<html><head/><body><p align=\"center\"><span style=\" font-size:12pt; font-weight:600;\">UDS Launcher</span></p></body></html>"))
|
|
||||||
self.label_2.setText(_translate("MacLauncher", "<html><head/><body><p align=\"center\"><span style=\" font-size:6pt;\">Closing this window will end all UDS tunnels</span></p></body></html>"))
|
|
||||||
import UDSResources_rc
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import sys
|
|
||||||
app = QtWidgets.QApplication(sys.argv)
|
|
||||||
MacLauncher = QtWidgets.QMainWindow()
|
|
||||||
ui = Ui_MacLauncher()
|
|
||||||
ui.setupUi(MacLauncher)
|
|
||||||
MacLauncher.show()
|
|
||||||
sys.exit(app.exec())
|
|
@ -1,113 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>MacLauncher</class>
|
|
||||||
<widget class="QMainWindow" name="MacLauncher">
|
|
||||||
<property name="windowModality">
|
|
||||||
<enum>Qt::NonModal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>235</width>
|
|
||||||
<height>120</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="cursor">
|
|
||||||
<cursorShape>ArrowCursor</cursorShape>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>UDS Launcher</string>
|
|
||||||
</property>
|
|
||||||
<property name="windowIcon">
|
|
||||||
<iconset resource="UDSResources.qrc">
|
|
||||||
<normaloff>:/images/logo-uds-small</normaloff>:/images/logo-uds-small</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="windowOpacity">
|
|
||||||
<double>1.000000000000000</double>
|
|
||||||
</property>
|
|
||||||
<widget class="QWidget" name="centralwidget">
|
|
||||||
<property name="autoFillBackground">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<property name="spacing">
|
|
||||||
<number>4</number>
|
|
||||||
</property>
|
|
||||||
<property name="leftMargin">
|
|
||||||
<number>4</number>
|
|
||||||
</property>
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>4</number>
|
|
||||||
</property>
|
|
||||||
<property name="rightMargin">
|
|
||||||
<number>4</number>
|
|
||||||
</property>
|
|
||||||
<property name="bottomMargin">
|
|
||||||
<number>4</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QFrame" name="frame">
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::StyledPanel</enum>
|
|
||||||
</property>
|
|
||||||
<property name="frameShadow">
|
|
||||||
<enum>QFrame::Raised</enum>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="topLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string><html><head/><body><p align="center"><span style=" font-size:12pt; font-weight:600;">UDS Launcher</span></p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::RichText</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="image">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="autoFillBackground">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string notr="true"/>
|
|
||||||
</property>
|
|
||||||
<property name="pixmap">
|
|
||||||
<pixmap resource="UDSResources.qrc">:/images/logo-uds-small</pixmap>
|
|
||||||
</property>
|
|
||||||
<property name="scaledContents">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string><html><head/><body><p align="center"><span style=" font-size:6pt;">Closing this window will end all UDS tunnels</span></p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::RichText</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
<resources>
|
|
||||||
<include location="UDSResources.qrc"/>
|
|
||||||
</resources>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Resource object code
|
# Resource object code
|
||||||
#
|
#
|
||||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
|
# Created by: The Resource Compiler for PyQt5 (Qt v5.13.2)
|
||||||
#
|
#
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
# Form implementation generated from reading ui file 'UDSWindow.ui'
|
# Form implementation generated from reading ui file 'UDSWindow.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt5 UI code generator 5.15.2
|
# Created by: PyQt5 UI code generator 5.13.2
|
||||||
#
|
#
|
||||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
# WARNING! All changes made in this file will be lost!
|
||||||
# run again. Do not edit this file unless you know what you are doing.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
@ -90,4 +89,4 @@ if __name__ == "__main__":
|
|||||||
ui = Ui_MainWindow()
|
ui = Ui_MainWindow()
|
||||||
ui.setupUi(MainWindow)
|
ui.setupUi(MainWindow)
|
||||||
MainWindow.show()
|
MainWindow.show()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec_())
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2021 Virtual Cable S.L.
|
# Copyright (c) 2014-2017 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -29,11 +29,13 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
VERSION = '3.6.0'
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
VERSION = '3.0.0'
|
||||||
|
|
||||||
__title__ = 'udclient'
|
__title__ = 'udclient'
|
||||||
__version__ = VERSION
|
__version__ = VERSION
|
||||||
__build__ = 0x010712
|
__build__ = 0x010760
|
||||||
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
|
__author__ = 'Adolfo Gómez'
|
||||||
__license__ = "BSD 3-clause"
|
__license__ = "BSD 3-clause"
|
||||||
__copyright__ = "Copyright 2014-2022 VirtualCable S.L.U."
|
__copyright__ = "Copyright 2014-2017 VirtualCable S.L.U."
|
||||||
|
@ -9,7 +9,6 @@ import random
|
|||||||
import time
|
import time
|
||||||
import select
|
import select
|
||||||
import socketserver
|
import socketserver
|
||||||
import typing
|
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
|
|
||||||
@ -25,11 +24,7 @@ class CheckfingerPrints(paramiko.MissingHostKeyPolicy):
|
|||||||
if self.fingerPrints:
|
if self.fingerPrints:
|
||||||
remotefingerPrints = hexlify(key.get_fingerprint()).decode().lower()
|
remotefingerPrints = hexlify(key.get_fingerprint()).decode().lower()
|
||||||
if remotefingerPrints not in self.fingerPrints.split(','):
|
if remotefingerPrints not in self.fingerPrints.split(','):
|
||||||
logger.error(
|
logger.error("Server {!r} has invalid fingerPrints. ({} vs {})".format(hostname, remotefingerPrints, self.fingerPrints))
|
||||||
"Server {!r} has invalid fingerPrints. ({} vs {})".format(
|
|
||||||
hostname, remotefingerPrints, self.fingerPrints
|
|
||||||
)
|
|
||||||
)
|
|
||||||
raise paramiko.SSHException(
|
raise paramiko.SSHException(
|
||||||
"Server {!r} has invalid fingerPrints".format(hostname)
|
"Server {!r} has invalid fingerPrints".format(hostname)
|
||||||
)
|
)
|
||||||
@ -41,49 +36,26 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
|||||||
|
|
||||||
|
|
||||||
class Handler(socketserver.BaseRequestHandler):
|
class Handler(socketserver.BaseRequestHandler):
|
||||||
event: threading.Event
|
|
||||||
thread: 'ForwardThread'
|
|
||||||
ssh_transport: paramiko.Transport
|
|
||||||
chain_host: str
|
|
||||||
chain_port: int
|
|
||||||
|
|
||||||
def handle(self):
|
def handle(self):
|
||||||
self.thread.currentConnections += 1
|
self.thread.currentConnections += 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
chan = self.ssh_transport.open_channel(
|
chan = self.ssh_transport.open_channel('direct-tcpip',
|
||||||
'direct-tcpip',
|
(self.chain_host, self.chain_port),
|
||||||
(self.chain_host, self.chain_port),
|
self.request.getpeername())
|
||||||
self.request.getpeername(),
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception('Incoming request to %s:%d failed: %s', self.chain_host, self.chain_port, repr(e))
|
||||||
'Incoming request to %s:%d failed: %s',
|
|
||||||
self.chain_host,
|
|
||||||
self.chain_port,
|
|
||||||
repr(e),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
if chan is None:
|
if chan is None:
|
||||||
logger.error(
|
logger.error('Incoming request to %s:%d was rejected by the SSH server.', self.chain_host, self.chain_port)
|
||||||
'Incoming request to %s:%d was rejected by the SSH server.',
|
|
||||||
self.chain_host,
|
|
||||||
self.chain_port,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug(
|
logger.debug('Connected! Tunnel open %r -> %r -> %r', self.request.getpeername(), chan.getpeername(), (self.chain_host, self.chain_port))
|
||||||
'Connected! Tunnel open %r -> %r -> %r',
|
|
||||||
self.request.getpeername(),
|
|
||||||
chan.getpeername(),
|
|
||||||
(self.chain_host, self.chain_port),
|
|
||||||
)
|
|
||||||
# self.ssh_transport.set_keepalive(10) # Keep alive every 10 seconds...
|
# self.ssh_transport.set_keepalive(10) # Keep alive every 10 seconds...
|
||||||
try:
|
try:
|
||||||
while self.event.is_set() is False:
|
while self.event.is_set() is False:
|
||||||
r, _w, _x = select.select(
|
r, _w, _x = select.select([self.request, chan], [], [], 1) # pylint: disable=unused-variable
|
||||||
[self.request, chan], [], [], 1
|
|
||||||
) # pylint: disable=unused-variable
|
|
||||||
|
|
||||||
if self.request in r:
|
if self.request in r:
|
||||||
data = self.request.recv(1024)
|
data = self.request.recv(1024)
|
||||||
@ -102,10 +74,7 @@ class Handler(socketserver.BaseRequestHandler):
|
|||||||
peername = self.request.getpeername()
|
peername = self.request.getpeername()
|
||||||
chan.close()
|
chan.close()
|
||||||
self.request.close()
|
self.request.close()
|
||||||
logger.debug(
|
logger.debug('Tunnel closed from %r', peername,)
|
||||||
'Tunnel closed from %r',
|
|
||||||
peername,
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -117,21 +86,8 @@ class Handler(socketserver.BaseRequestHandler):
|
|||||||
|
|
||||||
class ForwardThread(threading.Thread):
|
class ForwardThread(threading.Thread):
|
||||||
status = 0 # Connecting
|
status = 0 # Connecting
|
||||||
client: typing.Optional[paramiko.SSHClient]
|
|
||||||
fs: typing.Optional[ForwardServer]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, server, port, username, password, localPort, redirectHost, redirectPort, waitTime, fingerPrints):
|
||||||
self,
|
|
||||||
server,
|
|
||||||
port,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
localPort,
|
|
||||||
redirectHost,
|
|
||||||
redirectPort,
|
|
||||||
waitTime,
|
|
||||||
fingerPrints,
|
|
||||||
):
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.client = None
|
self.client = None
|
||||||
self.fs = None
|
self.fs = None
|
||||||
@ -146,7 +102,7 @@ class ForwardThread(threading.Thread):
|
|||||||
self.redirectPort = redirectPort
|
self.redirectPort = redirectPort
|
||||||
|
|
||||||
self.waitTime = waitTime
|
self.waitTime = waitTime
|
||||||
|
|
||||||
self.fingerPrints = fingerPrints
|
self.fingerPrints = fingerPrints
|
||||||
|
|
||||||
self.stopEvent = threading.Event()
|
self.stopEvent = threading.Event()
|
||||||
@ -160,19 +116,9 @@ class ForwardThread(threading.Thread):
|
|||||||
if localPort is None:
|
if localPort is None:
|
||||||
localPort = random.randrange(33000, 53000)
|
localPort = random.randrange(33000, 53000)
|
||||||
|
|
||||||
ft = ForwardThread(
|
ft = ForwardThread(self.server, self.port, self.username, self.password, localPort, redirectHost, redirectPort, self.waitTime, self.fingerPrints)
|
||||||
self.server,
|
|
||||||
self.port,
|
|
||||||
self.username,
|
|
||||||
self.password,
|
|
||||||
localPort,
|
|
||||||
redirectHost,
|
|
||||||
redirectPort,
|
|
||||||
self.waitTime,
|
|
||||||
self.fingerPrints,
|
|
||||||
)
|
|
||||||
ft.client = self.client
|
ft.client = self.client
|
||||||
self.client.useCount += 1 # type: ignore
|
self.client.useCount += 1 # One more using this client
|
||||||
ft.start()
|
ft.start()
|
||||||
|
|
||||||
while ft.status == 0:
|
while ft.status == 0:
|
||||||
@ -180,6 +126,7 @@ class ForwardThread(threading.Thread):
|
|||||||
|
|
||||||
return (ft, localPort)
|
return (ft, localPort)
|
||||||
|
|
||||||
|
|
||||||
def _timerFnc(self):
|
def _timerFnc(self):
|
||||||
self.timer = None
|
self.timer = None
|
||||||
logger.debug('Timer fnc: %s', self.currentConnections)
|
logger.debug('Timer fnc: %s', self.currentConnections)
|
||||||
@ -191,23 +138,14 @@ class ForwardThread(threading.Thread):
|
|||||||
if self.client is None:
|
if self.client is None:
|
||||||
try:
|
try:
|
||||||
self.client = paramiko.SSHClient()
|
self.client = paramiko.SSHClient()
|
||||||
self.client.useCount = 1 # type: ignore
|
self.client.useCount = 1 # Custom added variable, to keep track on when to close tunnel
|
||||||
self.client.load_system_host_keys()
|
self.client.load_system_host_keys()
|
||||||
self.client.set_missing_host_key_policy(
|
self.client.set_missing_host_key_policy(CheckfingerPrints(self.fingerPrints))
|
||||||
CheckfingerPrints(self.fingerPrints)
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug('Connecting to ssh host %s:%d ...', self.server, self.port)
|
logger.debug('Connecting to ssh host %s:%d ...', self.server, self.port)
|
||||||
|
|
||||||
# To disable ssh-ageng asking for passwords: allow_agent=False
|
# To disable ssh-ageng asking for passwords: allow_agent=False
|
||||||
self.client.connect(
|
self.client.connect(self.server, self.port, username=self.username, password=self.password, timeout=5, allow_agent=False)
|
||||||
self.server,
|
|
||||||
self.port,
|
|
||||||
username=self.username,
|
|
||||||
password=self.password,
|
|
||||||
timeout=5,
|
|
||||||
allow_agent=False,
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception('Exception connecting: ')
|
logger.exception('Exception connecting: ')
|
||||||
self.status = 2 # Error
|
self.status = 2 # Error
|
||||||
@ -235,30 +173,18 @@ class ForwardThread(threading.Thread):
|
|||||||
self.timer.cancel()
|
self.timer.cancel()
|
||||||
|
|
||||||
self.stopEvent.set()
|
self.stopEvent.set()
|
||||||
|
self.fs.shutdown()
|
||||||
if self.fs:
|
|
||||||
self.fs.shutdown()
|
|
||||||
|
|
||||||
if self.client is not None:
|
if self.client is not None:
|
||||||
self.client.useCount -= 1 # type: ignore
|
self.client.useCount -= 1
|
||||||
if self.client.useCount == 0: # type: ignore
|
if self.client.useCount == 0:
|
||||||
self.client.close()
|
self.client.close()
|
||||||
self.client = None # Clean up
|
self.client = None # Clean up
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception('Exception stopping')
|
logger.exception('Exception stopping')
|
||||||
|
|
||||||
|
|
||||||
def forward(
|
def forward(server, port, username, password, redirectHost, redirectPort, localPort=None, waitTime=10, fingerPrints=None):
|
||||||
server,
|
|
||||||
port,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
redirectHost,
|
|
||||||
redirectPort,
|
|
||||||
localPort=None,
|
|
||||||
waitTime=10,
|
|
||||||
fingerPrints=None,
|
|
||||||
):
|
|
||||||
'''
|
'''
|
||||||
Instantiates an ssh connection to server:port
|
Instantiates an ssh connection to server:port
|
||||||
Returns the Thread created and the local redirected port as a list: (thread, port)
|
Returns the Thread created and the local redirected port as a list: (thread, port)
|
||||||
@ -268,28 +194,10 @@ def forward(
|
|||||||
if localPort is None:
|
if localPort is None:
|
||||||
localPort = random.randrange(40000, 50000)
|
localPort = random.randrange(40000, 50000)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug('Connecting to %s:%s using %s/%s redirecting to %s:%s, listening on 127.0.0.1:%s',
|
||||||
'Connecting to %s:%s using %s/%s redirecting to %s:%s, listening on 127.0.0.1:%s',
|
server, port, username, password, redirectHost, redirectPort, localPort)
|
||||||
server,
|
|
||||||
port,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
redirectHost,
|
|
||||||
redirectPort,
|
|
||||||
localPort,
|
|
||||||
)
|
|
||||||
|
|
||||||
ft = ForwardThread(
|
ft = ForwardThread(server, port, username, password, localPort, redirectHost, redirectPort, waitTime, fingerPrints)
|
||||||
server,
|
|
||||||
port,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
localPort,
|
|
||||||
redirectHost,
|
|
||||||
redirectPort,
|
|
||||||
waitTime,
|
|
||||||
fingerPrints,
|
|
||||||
)
|
|
||||||
|
|
||||||
ft.start()
|
ft.start()
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
# Copyright (c) 2014 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -29,35 +29,27 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import os.path
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
LOGLEVEL = logging.INFO
|
if sys.platform.startswith('linux'):
|
||||||
DEBUG = False
|
from os.path import expanduser # pylint: disable=ungrouped-imports
|
||||||
|
logFile = expanduser('~/udsclient.log')
|
||||||
# Update debug level if uds-debug-on exists
|
|
||||||
if 'linux' in sys.platform or 'darwin' in sys.platform:
|
|
||||||
logFile = os.path.expanduser('~/udsclient.log')
|
|
||||||
if os.path.isfile(os.path.expanduser('~/uds-debug-on')):
|
|
||||||
LOGLEVEL = logging.DEBUG
|
|
||||||
DEBUG = True
|
|
||||||
else:
|
else:
|
||||||
logFile = os.path.join(tempfile.gettempdir(), 'udsclient.log')
|
logFile = os.path.join(tempfile.gettempdir(), b'udsclient.log')
|
||||||
if os.path.isfile(os.path.join(tempfile.gettempdir(), 'uds-debug-on')):
|
|
||||||
LOGLEVEL = logging.DEBUG
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename=logFile,
|
filename=logFile,
|
||||||
filemode='a',
|
filemode='a',
|
||||||
format='%(levelname)s %(asctime)s %(message)s',
|
format='%(levelname)s %(asctime)s %(message)s',
|
||||||
level=LOGLEVEL,
|
level=logging.INFO
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.basicConfig(format='%(levelname)s %(asctime)s %(message)s', level=LOGLEVEL)
|
logging.basicConfig(format='%(levelname)s %(asctime)s %(message)s', level=logging.INFO)
|
||||||
|
|
||||||
logger = logging.getLogger('udsclient')
|
logger = logging.getLogger('udsclient')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright (c) 2017-2021 Virtual Cable S.L.U.
|
# Copyright (c) 2017 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -30,13 +30,14 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
LINUX = 'Linux'
|
LINUX = 'Linux'
|
||||||
WINDOWS = 'Windows'
|
WINDOWS = 'Windows'
|
||||||
MAC_OS_X = 'Mac os x'
|
MAC_OS_X = 'Mac os x'
|
||||||
|
|
||||||
|
|
||||||
def getOs():
|
def getOs():
|
||||||
if sys.platform.startswith('linux'):
|
if sys.platform.startswith('linux'):
|
||||||
return LINUX
|
return LINUX
|
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright (c) 2017-2021 Virtual Cable S.L.U.
|
# Copyright (c) 2017 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -29,224 +30,95 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
# pylint: disable=c-extension-no-member,no-name-in-module
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import bz2
|
|
||||||
import base64
|
|
||||||
import urllib
|
import urllib
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
|
||||||
import ssl
|
|
||||||
import socket
|
|
||||||
import typing
|
|
||||||
|
|
||||||
from cryptography import x509
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||||
from cryptography.hazmat.backends import default_backend
|
from PyQt5.QtCore import QObject, QUrl, QSettings
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QSslCertificate
|
||||||
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
from . import osDetector
|
||||||
|
|
||||||
from . import os_detector
|
|
||||||
from . import tools
|
|
||||||
from . import VERSION
|
from . import VERSION
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
# Server before this version uses "unsigned" scripts
|
|
||||||
OLD_METHOD_VERSION = '2.4.0'
|
|
||||||
|
|
||||||
# Callback for error on cert
|
|
||||||
# parameters are hostname, serial
|
|
||||||
# If returns True, ignores error
|
|
||||||
CertCallbackType = typing.Callable[[str, str], bool]
|
|
||||||
|
|
||||||
# Exceptions
|
|
||||||
class UDSException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RetryException(UDSException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
class RestRequest(QObject):
|
||||||
|
|
||||||
class InvalidVersion(UDSException):
|
restApiUrl = '' #
|
||||||
downloadUrl: str
|
|
||||||
|
|
||||||
def __init__(self, downloadUrl: str) -> None:
|
done = pyqtSignal(dict, name='done')
|
||||||
super().__init__(downloadUrl)
|
|
||||||
self.downloadUrl = downloadUrl
|
|
||||||
|
|
||||||
|
|
||||||
class RestApi:
|
|
||||||
|
|
||||||
_restApiUrl: str # base Rest API URL
|
|
||||||
_callbackInvalidCert: typing.Optional[CertCallbackType]
|
|
||||||
_serverVersion: str
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
restApiUrl,
|
|
||||||
callbackInvalidCert: typing.Optional[CertCallbackType] = None,
|
|
||||||
) -> None: # parent not used
|
|
||||||
logger.debug('Setting request URL to %s', restApiUrl)
|
|
||||||
|
|
||||||
self._restApiUrl = restApiUrl
|
|
||||||
self._callbackInvalidCert = callbackInvalidCert
|
|
||||||
self._serverVersion = ''
|
|
||||||
|
|
||||||
def get(
|
|
||||||
self, url: str, params: typing.Optional[typing.Mapping[str, str]] = None
|
|
||||||
) -> typing.Any:
|
|
||||||
if params:
|
|
||||||
url += '?' + '&'.join(
|
|
||||||
'{}={}'.format(k, urllib.parse.quote(str(v).encode('utf8')))
|
|
||||||
for k, v in params.items()
|
|
||||||
)
|
|
||||||
|
|
||||||
return json.loads(
|
|
||||||
RestApi.getUrl(self._restApiUrl + url, self._callbackInvalidCert)
|
|
||||||
)
|
|
||||||
|
|
||||||
def processError(self, data: typing.Any) -> None:
|
|
||||||
if 'error' in data:
|
|
||||||
if data.get('retryable', '0') == '1':
|
|
||||||
raise RetryException(data['error'])
|
|
||||||
|
|
||||||
raise UDSException(data['error'])
|
|
||||||
|
|
||||||
def getVersion(self) -> str:
|
|
||||||
'''Gets and stores the serverVersion.
|
|
||||||
Also checks that the version is valid for us. If not,
|
|
||||||
will raise an "InvalidVersion' exception'''
|
|
||||||
|
|
||||||
downloadUrl = ''
|
|
||||||
if not self._serverVersion:
|
|
||||||
data = self.get('')
|
|
||||||
self.processError(data)
|
|
||||||
self._serverVersion = data['result']['requiredVersion']
|
|
||||||
downloadUrl = data['result']['downloadUrl']
|
|
||||||
|
|
||||||
|
def __init__(self, url, parentWindow, done, params=None): # parent not used
|
||||||
|
super(RestRequest, self).__init__()
|
||||||
|
# private
|
||||||
|
self._manager = QNetworkAccessManager()
|
||||||
try:
|
try:
|
||||||
if self._serverVersion > VERSION:
|
if os.path.exists('/etc/ssl/certs/ca-certificates.crt'):
|
||||||
raise InvalidVersion(downloadUrl)
|
pass
|
||||||
|
# os.environ['REQUESTS_CA_BUNDLE'] = '/etc/ssl/certs/ca-certificates.crt'
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
return self._serverVersion
|
|
||||||
|
if params is not None:
|
||||||
|
url += '?' + '&'.join('{}={}'.format(k, urllib.parse.quote(str(v).encode('utf8'))) for k, v in params.items())
|
||||||
|
|
||||||
|
self.url = QUrl(RestRequest.restApiUrl + url)
|
||||||
|
|
||||||
|
# connect asynchronous result, when a request finishes
|
||||||
|
self._manager.finished.connect(self._finished)
|
||||||
|
self._manager.sslErrors.connect(self._sslError)
|
||||||
|
self._parentWindow = parentWindow
|
||||||
|
|
||||||
|
self.done.connect(done, Qt.QueuedConnection)
|
||||||
|
|
||||||
|
def _finished(self, reply):
|
||||||
|
'''
|
||||||
|
Handle signal 'finished'. A network request has finished.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
if reply.error() != QNetworkReply.NoError:
|
||||||
|
raise Exception(reply.errorString())
|
||||||
|
data = bytes(reply.readAll())
|
||||||
|
data = json.loads(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise UDSException(e)
|
data = {
|
||||||
|
'result': None,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
def getScriptAndParams(
|
self.done.emit(data)
|
||||||
self, ticket: str, scrambler: str
|
|
||||||
) -> typing.Tuple[str, typing.Any]:
|
|
||||||
'''Gets the transport script, validates it if necesary
|
|
||||||
and returns it'''
|
|
||||||
try:
|
|
||||||
data = self.get(
|
|
||||||
'/{}/{}'.format(ticket, scrambler),
|
|
||||||
params={'hostname': tools.getHostName(), 'version': VERSION},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception('Got exception on getTransportData')
|
|
||||||
raise e
|
|
||||||
|
|
||||||
logger.debug('Transport data received')
|
reply.deleteLater() # schedule for delete from main event loop
|
||||||
self.processError(data)
|
|
||||||
|
|
||||||
params = None
|
def _sslError(self, reply, errors):
|
||||||
|
settings = QSettings()
|
||||||
|
settings.beginGroup('ssl')
|
||||||
|
cert = errors[0].certificate()
|
||||||
|
digest = str(cert.digest().toHex())
|
||||||
|
|
||||||
if self._serverVersion <= OLD_METHOD_VERSION:
|
approved = settings.value(digest, False)
|
||||||
script = bz2.decompress(base64.b64decode(data['result']))
|
|
||||||
# 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'version)', b'version.decode("utf-8"))')
|
|
||||||
else:
|
|
||||||
res = data['result']
|
|
||||||
# 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
|
|
||||||
# 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']))),
|
|
||||||
)
|
|
||||||
if tools.verifySignature(script, signature) is False:
|
|
||||||
logger.error('Signature is invalid')
|
|
||||||
|
|
||||||
raise Exception(
|
errorString = '<p>The certificate for <b>{}</b> has the following errors:</p><ul>'.format(cert.subjectInfo(QSslCertificate.CommonName))
|
||||||
'Invalid UDS code signature. Please, report to administrator'
|
|
||||||
)
|
|
||||||
|
|
||||||
return script.decode(), params
|
for err in errors:
|
||||||
|
errorString += '<li>' + err.errorString() + '</li>'
|
||||||
|
|
||||||
# exec(script.decode("utf-8"), globals(), {'parent': self, 'sp': params})
|
errorString += '</ul>'
|
||||||
|
|
||||||
@staticmethod
|
if approved or QMessageBox.warning(self._parentWindow, 'SSL Warning', errorString, QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
|
||||||
def _open(
|
settings.setValue(digest, True)
|
||||||
url: str, certErrorCallback: typing.Optional[CertCallbackType] = None
|
reply.ignoreSslErrors()
|
||||||
) -> typing.Any:
|
|
||||||
ctx = ssl.create_default_context()
|
|
||||||
ctx.check_hostname = False
|
|
||||||
ctx.verify_mode = ssl.CERT_NONE
|
|
||||||
# If we have the certificates file, we use it
|
|
||||||
if tools.getCaCertsFile() is not None:
|
|
||||||
ctx.load_verify_locations(tools.getCaCertsFile())
|
|
||||||
hostname = urllib.parse.urlparse(url)[1]
|
|
||||||
serial = ''
|
|
||||||
|
|
||||||
port = ''
|
settings.endGroup()
|
||||||
if ':' in hostname:
|
|
||||||
hostname, port = hostname.split(':')
|
|
||||||
|
|
||||||
if url.startswith('https'):
|
def get(self):
|
||||||
port = port or '443'
|
request = QNetworkRequest(self.url)
|
||||||
with ctx.wrap_socket(
|
request.setRawHeader(b'User-Agent', osDetector.getOs().encode('utf-8') + b" - UDS Connector " + VERSION.encode('utf-8'))
|
||||||
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
|
self._manager.get(request)
|
||||||
server_hostname=hostname,
|
|
||||||
) as s:
|
|
||||||
s.connect((hostname, int(port)))
|
|
||||||
# Get binary certificate
|
|
||||||
binCert = s.getpeercert(True)
|
|
||||||
if binCert:
|
|
||||||
cert = x509.load_der_x509_certificate(binCert, default_backend())
|
|
||||||
else:
|
|
||||||
raise Exception('Certificate not found!')
|
|
||||||
|
|
||||||
serial = hex(cert.serial_number)[2:]
|
|
||||||
|
|
||||||
response = None
|
|
||||||
ctx.verify_mode = ssl.CERT_REQUIRED
|
|
||||||
ctx.check_hostname = True
|
|
||||||
|
|
||||||
def urlopen(url: str):
|
|
||||||
# Generate the request with the headers
|
|
||||||
req = urllib.request.Request(
|
|
||||||
url,
|
|
||||||
headers={
|
|
||||||
'User-Agent': os_detector.getOs() + " - UDS Connector " + VERSION
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return urllib.request.urlopen(req, context=ctx)
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = urlopen(url)
|
|
||||||
except urllib.error.URLError as e:
|
|
||||||
if isinstance(e.reason, ssl.SSLCertVerificationError):
|
|
||||||
# Ask about invalid certificate
|
|
||||||
if certErrorCallback:
|
|
||||||
if certErrorCallback(hostname, serial):
|
|
||||||
ctx.check_hostname = False
|
|
||||||
ctx.verify_mode = ssl.CERT_NONE
|
|
||||||
response = urlopen(url)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getUrl(
|
|
||||||
url: str, certErrorCallback: typing.Optional[CertCallbackType] = None
|
|
||||||
) -> bytes:
|
|
||||||
with RestApi._open(url, certErrorCallback) as response:
|
|
||||||
resp = response.read()
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright (c) 2015-2021 Virtual Cable S.L.U.
|
# Copyright (c) 2015 Virtual Cable S.L.
|
||||||
# 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,
|
||||||
@ -29,36 +30,31 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from base64 import b64decode
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
import os
|
import os
|
||||||
import os.path
|
|
||||||
import sys
|
|
||||||
import socket
|
import socket
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import base64
|
|
||||||
import typing
|
|
||||||
|
|
||||||
import certifi
|
import six
|
||||||
|
|
||||||
try:
|
|
||||||
import psutil
|
|
||||||
except ImportError:
|
|
||||||
psutil = None
|
|
||||||
|
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
|
||||||
_unlinkFiles: typing.List[typing.Tuple[str, bool]] = []
|
_unlinkFiles = []
|
||||||
_tasksToWait: typing.List[typing.Tuple[typing.Any, bool]] = []
|
_tasksToWait = []
|
||||||
_execBeforeExit: typing.List[typing.Callable[[], None]] = []
|
_execBeforeExit = []
|
||||||
|
|
||||||
sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
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
|
||||||
@ -74,13 +70,15 @@ nVgtClKcDDlSaBsO875WDR0CAwEAAQ==
|
|||||||
-----END PUBLIC KEY-----'''
|
-----END PUBLIC KEY-----'''
|
||||||
|
|
||||||
|
|
||||||
def saveTempFile(content: str, filename: typing.Optional[str] = None) -> str:
|
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'
|
||||||
|
|
||||||
|
if 'win32' in sys.platform:
|
||||||
|
logger.info('Fixing for win32')
|
||||||
|
filename = filename.encode(sys_fs_enc)
|
||||||
|
|
||||||
filename = os.path.join(tempfile.gettempdir(), filename)
|
filename = os.path.join(tempfile.gettempdir(), filename)
|
||||||
|
|
||||||
with open(filename, 'w') as f:
|
with open(filename, 'w') as f:
|
||||||
@ -90,7 +88,10 @@ def saveTempFile(content: str, filename: typing.Optional[str] = None) -> str:
|
|||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def readTempFile(filename: str) -> typing.Optional[str]:
|
def readTempFile(filename):
|
||||||
|
if 'win32' in sys.platform:
|
||||||
|
filename = filename.encode('utf-8')
|
||||||
|
|
||||||
filename = os.path.join(tempfile.gettempdir(), filename)
|
filename = os.path.join(tempfile.gettempdir(), filename)
|
||||||
try:
|
try:
|
||||||
with open(filename, 'r') as f:
|
with open(filename, 'r') as f:
|
||||||
@ -99,7 +100,7 @@ def readTempFile(filename: str) -> typing.Optional[str]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def testServer(host: str, port: typing.Union[str, int], timeOut: int = 4) -> bool:
|
def testServer(host, port, timeOut=4):
|
||||||
try:
|
try:
|
||||||
sock = socket.create_connection((host, int(port)), timeOut)
|
sock = socket.create_connection((host, int(port)), timeOut)
|
||||||
sock.close()
|
sock.close()
|
||||||
@ -108,11 +109,11 @@ def testServer(host: str, port: typing.Union[str, int], timeOut: int = 4) -> boo
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def findApp(
|
def findApp(appName, extraPath=None):
|
||||||
appName: str, extraPath: typing.Optional[str] = None
|
if 'win32' in sys.platform and isinstance(appName, six.text_type):
|
||||||
) -> typing.Optional[str]:
|
appName = appName.encode(sys_fs_enc)
|
||||||
searchPath = os.environ['PATH'].split(os.pathsep)
|
searchPath = os.environ['PATH'].split(os.pathsep)
|
||||||
if extraPath:
|
if extraPath is not None:
|
||||||
searchPath += list(extraPath)
|
searchPath += list(extraPath)
|
||||||
|
|
||||||
for path in searchPath:
|
for path in searchPath:
|
||||||
@ -122,101 +123,68 @@ def findApp(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def getHostName() -> str:
|
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()
|
||||||
|
if 'win32' in sys.platform:
|
||||||
|
hostname = hostname.decode(sys_fs_enc)
|
||||||
|
|
||||||
|
hostname = six.text_type(hostname)
|
||||||
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: str, early: bool = False) -> None:
|
def addFileToUnlink(filename):
|
||||||
'''
|
'''
|
||||||
Adds a file to the wait-and-unlink list
|
Adds a file to the wait-and-unlink list
|
||||||
'''
|
'''
|
||||||
logger.debug(
|
_unlinkFiles.append(filename)
|
||||||
'Added file %s to unlink on %s stage', filename, 'early' if early else 'later'
|
|
||||||
)
|
|
||||||
_unlinkFiles.append((filename, early))
|
|
||||||
|
|
||||||
|
|
||||||
def unlinkFiles(early: bool = False) -> None:
|
def unlinkFiles():
|
||||||
'''
|
'''
|
||||||
Removes all wait-and-unlink files
|
Removes all wait-and-unlink files
|
||||||
'''
|
'''
|
||||||
logger.debug('Unlinking files on %s stage', 'early' if early else 'later')
|
if _unlinkFiles:
|
||||||
filesToUnlink = list(filter(lambda x: x[1] == early, _unlinkFiles))
|
time.sleep(5) # Wait 5 seconds before deleting anything
|
||||||
if filesToUnlink:
|
|
||||||
logger.debug('Files to unlink: %s', filesToUnlink)
|
|
||||||
# Wait 2 seconds before deleting anything on early and 5 on later stages
|
|
||||||
time.sleep(1 + 2 * (1 + int(early)))
|
|
||||||
|
|
||||||
for f in filesToUnlink:
|
for f in _unlinkFiles:
|
||||||
try:
|
try:
|
||||||
os.unlink(f[0])
|
os.unlink(f)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.debug('File %s not deleted: %s', f[0], e)
|
pass
|
||||||
|
|
||||||
|
|
||||||
def addTaskToWait(task: typing.Any, includeSubprocess: bool = False) -> None:
|
def addTaskToWait(taks):
|
||||||
logger.debug(
|
_tasksToWait.append(taks)
|
||||||
'Added task %s to wait %s',
|
|
||||||
task,
|
|
||||||
'with subprocesses' if includeSubprocess else '',
|
|
||||||
)
|
|
||||||
_tasksToWait.append((task, includeSubprocess))
|
|
||||||
|
|
||||||
|
|
||||||
def waitForTasks() -> None:
|
def waitForTasks():
|
||||||
logger.debug('Started to wait %s', _tasksToWait)
|
for t in _tasksToWait:
|
||||||
for task, waitForSubp in _tasksToWait:
|
|
||||||
logger.debug('Waiting for task %s, subprocess wait: %s', task, waitForSubp)
|
|
||||||
try:
|
try:
|
||||||
if hasattr(task, 'join'):
|
if hasattr(t, 'join'):
|
||||||
task.join()
|
t.join()
|
||||||
elif hasattr(task, 'wait'):
|
elif hasattr(t, 'wait'):
|
||||||
task.wait()
|
t.wait()
|
||||||
# If wait for spanwed process (look for process with task pid) and we can look for them...
|
except Exception:
|
||||||
logger.debug(
|
pass
|
||||||
'Psutil: %s, waitForSubp: %s, hasattr: %s',
|
|
||||||
psutil,
|
|
||||||
waitForSubp,
|
|
||||||
hasattr(task, 'pid'),
|
|
||||||
)
|
|
||||||
if psutil and waitForSubp and hasattr(task, 'pid'):
|
|
||||||
subProcesses = list(
|
|
||||||
filter(
|
|
||||||
lambda x: x.ppid() == task.pid, # type: ignore
|
|
||||||
psutil.process_iter(attrs=('ppid',)),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
'Waiting for subprocesses... %s, %s', task.pid, subProcesses
|
|
||||||
)
|
|
||||||
for i in subProcesses:
|
|
||||||
logger.debug('Found %s', i)
|
|
||||||
i.wait()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error('Waiting for tasks to finish error: %s', e)
|
|
||||||
|
|
||||||
|
|
||||||
def addExecBeforeExit(fnc: typing.Callable[[], None]) -> None:
|
def addExecBeforeExit(fnc):
|
||||||
logger.debug('Added exec before exit: %s', fnc)
|
|
||||||
_execBeforeExit.append(fnc)
|
_execBeforeExit.append(fnc)
|
||||||
|
|
||||||
|
|
||||||
def execBeforeExit() -> None:
|
def execBeforeExit():
|
||||||
logger.debug('Esecuting exec before exit: %s', _execBeforeExit)
|
|
||||||
for fnc in _execBeforeExit:
|
for fnc in _execBeforeExit:
|
||||||
fnc()
|
fnc.__call__()
|
||||||
|
|
||||||
|
|
||||||
def verifySignature(script: bytes, signature: bytes) -> bool:
|
def verifySignature(script, signature):
|
||||||
'''
|
'''
|
||||||
Verifies with a public key from whom the data came that it was indeed
|
Verifies with a public key from whom the data came that it was indeed
|
||||||
signed by their private key
|
signed by their private key
|
||||||
@ -225,45 +193,13 @@ def verifySignature(script: bytes, signature: bytes) -> bool:
|
|||||||
return: Boolean. True if the signature is valid; False otherwise.
|
return: Boolean. True if the signature is valid; False otherwise.
|
||||||
'''
|
'''
|
||||||
# For signature checking
|
# For signature checking
|
||||||
from cryptography.hazmat.backends import default_backend
|
from Crypto.PublicKey import RSA
|
||||||
from cryptography.hazmat.primitives import serialization, hashes
|
from Crypto.Signature import PKCS1_v1_5
|
||||||
from cryptography.hazmat.primitives.asymmetric import utils, padding
|
from Crypto.Hash import SHA256
|
||||||
|
|
||||||
public_key = serialization.load_pem_public_key(
|
rsakey = RSA.importKey(PUBLIC_KEY)
|
||||||
data=PUBLIC_KEY, backend=default_backend()
|
signer = PKCS1_v1_5.new(rsakey)
|
||||||
)
|
digest = SHA256.new(script) # Script is "binary string" here
|
||||||
|
if signer.verify(digest, b64decode(signature)):
|
||||||
try:
|
return True
|
||||||
public_key.verify( # type: ignore
|
return False
|
||||||
base64.b64decode(signature), script, padding.PKCS1v15(), hashes.SHA256() # type: ignore
|
|
||||||
)
|
|
||||||
except Exception: # InvalidSignature
|
|
||||||
return False
|
|
||||||
|
|
||||||
# If no exception, the script was fine...
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def getCaCertsFile() -> typing.Optional[str]:
|
|
||||||
# First, try certifi...
|
|
||||||
|
|
||||||
# If environment contains CERTIFICATE_BUNDLE_PATH, use it
|
|
||||||
if 'CERTIFICATE_BUNDLE_PATH' in os.environ:
|
|
||||||
return os.environ['CERTIFICATE_BUNDLE_PATH']
|
|
||||||
|
|
||||||
try:
|
|
||||||
if os.path.exists(certifi.where()):
|
|
||||||
return certifi.where()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
logger.info('Certifi file does not exists: %s', certifi.where())
|
|
||||||
|
|
||||||
# Check if "standard" paths are valid for linux systems
|
|
||||||
if 'linux' in sys.platform:
|
|
||||||
for path in ('/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/certs/ca-certificates.crt', '/etc/ssl/ca-bundle.pem'):
|
|
||||||
if os.path.exists(path):
|
|
||||||
logger.info('Found certifi path: %s', path)
|
|
||||||
return path
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
@ -1,289 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (c) 2022 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
|
|
||||||
'''
|
|
||||||
import socket
|
|
||||||
import socketserver
|
|
||||||
import ssl
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import random
|
|
||||||
import threading
|
|
||||||
import select
|
|
||||||
import typing
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from . import tools
|
|
||||||
|
|
||||||
HANDSHAKE_V1 = b'\x5AMGB\xA5\x01\x00'
|
|
||||||
BUFFER_SIZE = 1024 * 16 # Max buffer length
|
|
||||||
DEBUG = True
|
|
||||||
LISTEN_ADDRESS = '0.0.0.0' if DEBUG else '127.0.0.1'
|
|
||||||
|
|
||||||
# ForwarServer states
|
|
||||||
TUNNEL_LISTENING, TUNNEL_OPENING, TUNNEL_PROCESSING, TUNNEL_ERROR = 0, 1, 2, 3
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ForwardServer(socketserver.ThreadingTCPServer):
|
|
||||||
daemon_threads = True
|
|
||||||
allow_reuse_address = True
|
|
||||||
|
|
||||||
remote: typing.Tuple[str, int]
|
|
||||||
ticket: str
|
|
||||||
stop_flag: threading.Event
|
|
||||||
can_stop: bool
|
|
||||||
timeout: int
|
|
||||||
timer: typing.Optional[threading.Timer]
|
|
||||||
check_certificate: bool
|
|
||||||
current_connections: int
|
|
||||||
status: int
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
remote: typing.Tuple[str, int],
|
|
||||||
ticket: str,
|
|
||||||
timeout: int = 0,
|
|
||||||
local_port: int = 0,
|
|
||||||
check_certificate: bool = True,
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
local_port = local_port or random.randrange(33000, 53000)
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
server_address=(LISTEN_ADDRESS, local_port), RequestHandlerClass=Handler
|
|
||||||
)
|
|
||||||
self.remote = remote
|
|
||||||
self.ticket = ticket
|
|
||||||
# Negative values for timeout, means "accept always connections"
|
|
||||||
# "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.stop_flag = threading.Event() # False initial
|
|
||||||
self.current_connections = 0
|
|
||||||
|
|
||||||
self.status = TUNNEL_LISTENING
|
|
||||||
self.can_stop = False
|
|
||||||
|
|
||||||
timeout = abs(timeout) or 60
|
|
||||||
self.timer = threading.Timer(
|
|
||||||
abs(timeout), ForwardServer.__checkStarted, args=(self,)
|
|
||||||
)
|
|
||||||
self.timer.start()
|
|
||||||
|
|
||||||
def stop(self) -> None:
|
|
||||||
if not self.stop_flag.is_set():
|
|
||||||
logger.debug('Stopping servers')
|
|
||||||
self.stop_flag.set()
|
|
||||||
if self.timer:
|
|
||||||
self.timer.cancel()
|
|
||||||
self.timer = None
|
|
||||||
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)
|
|
||||||
|
|
||||||
rsocket.sendall(HANDSHAKE_V1) # No response expected, just the handshake
|
|
||||||
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
|
|
||||||
# Do not "recompress" data, use only "base protocol" compression
|
|
||||||
context.options |= ssl.OP_NO_COMPRESSION
|
|
||||||
if tools.getCaCertsFile() is not None:
|
|
||||||
context.load_verify_locations(
|
|
||||||
tools.getCaCertsFile()
|
|
||||||
) # Load certifi certificates
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
logger.debug('Checking tunnel availability')
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self.connect() as ssl_socket:
|
|
||||||
ssl_socket.sendall(b'TEST')
|
|
||||||
resp = ssl_socket.recv(2)
|
|
||||||
if resp != b'OK':
|
|
||||||
raise Exception({'Invalid tunnelresponse: {resp}'})
|
|
||||||
logger.debug('Tunnel is available!')
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
'Error connecting to tunnel server %s: %s', self.server_address, e
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stoppable(self) -> bool:
|
|
||||||
logger.debug('Is stoppable: %s', self.can_stop)
|
|
||||||
return self.can_stop or (self.timeout != 0 and int(time.time()) > self.timeout)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __checkStarted(fs: 'ForwardServer') -> None:
|
|
||||||
logger.debug('New connection limit reached')
|
|
||||||
fs.timer = None
|
|
||||||
fs.can_stop = True
|
|
||||||
if fs.current_connections <= 0:
|
|
||||||
fs.stop()
|
|
||||||
|
|
||||||
|
|
||||||
class Handler(socketserver.BaseRequestHandler):
|
|
||||||
# Override Base type
|
|
||||||
server: ForwardServer
|
|
||||||
|
|
||||||
# server: ForwardServer
|
|
||||||
def handle(self) -> None:
|
|
||||||
self.server.status = TUNNEL_OPENING
|
|
||||||
|
|
||||||
# If server processing is over time
|
|
||||||
if self.server.stoppable:
|
|
||||||
self.server.status = TUNNEL_ERROR
|
|
||||||
logger.info('Rejected timedout connection')
|
|
||||||
self.request.close() # End connection without processing it
|
|
||||||
return
|
|
||||||
|
|
||||||
self.server.current_connections += 1
|
|
||||||
|
|
||||||
# Open remote connection
|
|
||||||
try:
|
|
||||||
logger.debug('Ticket %s', self.server.ticket)
|
|
||||||
with self.server.connect() as ssl_socket:
|
|
||||||
# Send handhshake + command + ticket
|
|
||||||
ssl_socket.sendall(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
|
|
||||||
self.process(remote=ssl_socket)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f'Error connecting to {self.server.remote!s}: {e!s}')
|
|
||||||
self.server.status = TUNNEL_ERROR
|
|
||||||
self.server.stop()
|
|
||||||
finally:
|
|
||||||
self.server.current_connections -= 1
|
|
||||||
|
|
||||||
if self.server.current_connections <= 0 and self.server.stoppable:
|
|
||||||
self.server.stop()
|
|
||||||
|
|
||||||
# Processes data forwarding
|
|
||||||
def process(self, remote: ssl.SSLSocket):
|
|
||||||
self.server.status = TUNNEL_PROCESSING
|
|
||||||
logger.debug('Processing tunnel with ticket %s', self.server.ticket)
|
|
||||||
# Process data until stop requested or connection closed
|
|
||||||
try:
|
|
||||||
while not self.server.stop_flag.is_set():
|
|
||||||
r, _w, _x = select.select([self.request, remote], [], [], 1.0)
|
|
||||||
if self.request in r:
|
|
||||||
data = self.request.recv(BUFFER_SIZE)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
remote.sendall(data)
|
|
||||||
if remote in r:
|
|
||||||
data = remote.recv(BUFFER_SIZE)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
self.request.sendall(data)
|
|
||||||
logger.debug('Finished tunnel with ticket %s', self.server.ticket)
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _run(server: ForwardServer) -> None:
|
|
||||||
logger.debug(
|
|
||||||
'Starting forwarder: %s -> %s, timeout: %d',
|
|
||||||
server.server_address,
|
|
||||||
server.remote,
|
|
||||||
server.timeout,
|
|
||||||
)
|
|
||||||
server.serve_forever()
|
|
||||||
logger.debug('Stoped forwarder %s -> %s', server.server_address, server.remote)
|
|
||||||
|
|
||||||
|
|
||||||
def forward(
|
|
||||||
remote: typing.Tuple[str, int],
|
|
||||||
ticket: str,
|
|
||||||
timeout: int = 0,
|
|
||||||
local_port: int = 0,
|
|
||||||
check_certificate=True,
|
|
||||||
) -> ForwardServer:
|
|
||||||
|
|
||||||
fs = ForwardServer(
|
|
||||||
remote=remote,
|
|
||||||
ticket=ticket,
|
|
||||||
timeout=timeout,
|
|
||||||
local_port=local_port,
|
|
||||||
check_certificate=check_certificate,
|
|
||||||
)
|
|
||||||
# Starts a new thread
|
|
||||||
threading.Thread(target=_run, args=(fs,)).start()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
ticket = 'mffqg7q4s61fvx0ck2pe0zke6k0c5ipb34clhbkbs4dasb4g'
|
|
||||||
|
|
||||||
fs = forward(
|
|
||||||
('172.27.0.1', 7777),
|
|
||||||
ticket,
|
|
||||||
local_port=49999,
|
|
||||||
timeout=-20,
|
|
||||||
check_certificate=False,
|
|
||||||
)
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user