Compare commits

..

7 Commits

Author SHA1 Message Date
Adolfo Gómez García
a1f1932e0e added .gitignore to version 2014-09-03 18:28:08 +02:00
Adolfo Gómez
a9270a560a Upgraded 1.1 tag 2013-03-19 15:45:43 +00:00
Adolfo Gómez
4e6a03a34f Fixes for 1.1u1 2013-03-15 08:22:19 +00:00
Adolfo Gómez
2c2b0a291a Bug fixing & updated (forgotten commit :( ) sources 2013-03-13 05:20:28 +00:00
Adolfo Gómez
eaf210a7bf Uptated tag 1.1 2013-03-12 00:20:14 +00:00
Adolfo Gómez
cdc08a46f2 Added ALLOWED_HOSTS to sample settings.py (needed for django 1.5) 2013-03-11 14:59:47 +00:00
Adolfo Gómez
b2bcb86c30 Tag 1.1 upgrade 1 2013-03-10 21:47:05 +00:00
1761 changed files with 239402 additions and 204379 deletions

28
.gitignore vendored
View File

@ -1,19 +1,17 @@
*.pyc
*.pyo
*.orig
*~
*.swp
.DS_Store
*_enterprise.*
.settings/
.ipynb_checkpoints
.idea/
# Debian buildings
*.debhelper*
*-stamp
*.substvars
nxtransport/bin/
nxtuntransport/bin/
rdptransport/java/bin/
server/src/log/
ssh-tunnel/tunnelLaucher/bin/
# /client/administration/
/client/administration/*.suo
@ -32,6 +30,9 @@
/client/administration/installer/UDSAdminInstaller/MSChart.exe
/client/administration/installer/UDSAdminInstaller/UDSAdminSetup.exe
# /guacamole-tunnel/
/guacamole-tunnel/target
# /linuxActor/
/linuxActor/udsactor_*
@ -63,7 +64,9 @@
/rdptransport/java/jar/*.jar
# /server/
*_enterprise
/server/*_enterprise
/server/openuds.sublime-project
/server/openuds.sublime-workspace
# /server/src/
/server/src/taskmanager.pid
@ -86,6 +89,7 @@
# /server/src/uds/
/server/src/uds/*_enterprise.py
/server/src/uds/fixtures
/server/src/uds/tests
# /server/src/uds/auths/
/server/src/uds/auths/*-enterprise
@ -159,7 +163,3 @@
/udsService/udsgui/obj/Debug
/udsService/udsgui/obj/Release
/udsService/udsgui/obj/x86
.vscode
.mypy_cache
.pytest_cache

29
LICENSE
View File

@ -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.

View File

@ -1,15 +0,0 @@
![UDS Logo](https://www.udsenterprise.com/static//img/logoUDSNav.png)
openuds
=======
OpenUDS (Universal Desktop Services) is a multiplatform connection broker for:
- VDI: Windows and Linux virtual desktops administration and deployment
- App virtualization
- Desktop services consolidation
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.
**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.**

View File

@ -1 +0,0 @@

View File

@ -1 +0,0 @@
4.0.0

View File

@ -1,2 +0,0 @@
PYTHONPATH=./src:${PYTHONPATH}

8
actor/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Debian source builds
udsactor_*.dsc
udsactor_*.tar.xz
udsactor_*.buildinfo
udsactor_*.changes
# And binaries
udsactor*.deb
udsactor*.rpm

View File

@ -1 +0,0 @@
/udsactor-*[1-9].*.spec

View File

@ -1,91 +0,0 @@
#!/usr/bin/make -f
# -*- makefile -*-
.PHONY: install-udsactor install-udsactor-unmanaged
# Directories
SOURCEDIR := ../src
LIBDIR := $(DESTDIR)/usr/share/UDSActor
BINDIR := $(DESTDIR)/usr/bin
SBINDIR = $(DESTDIR)/usr/sbin
APPSDIR := $(DESTDIR)/usr/share/applications
CFGDIR := $(DESTDIR)/etc/udsactor
SYSTEMDIR := $(DESTDIR)/etc/systemd/system
POLKITDIR := $(DESTDIR)/usr/share/polkit-1/actions/
XDGAUTOSTARTDIR := $(DESTDIR)/etc/xdg/autostart
KDEAUTOSTARTDIR := $(DESTDIR)/usr/share/autostart
PYC := $(shell find $(SOURCEDIR) -name '*.py[co]')
CACHES := $(shell find $(SOURCEDIR) -name '__pycache__' -o -name '.mypy_cache')
clean:
rm -rf $(PYC) $(CACHES) $(DESTDIR)
install-udsactor: udsactor
install-udsactor-unmanaged: udsactor udsactor-unmanaged
udsactor-unmanaged:
# Overwrite udsactor config is what to be done
cp scripts/UDSActorConfig-unmanaged $(SBINDIR)/UDSActorConfig
udsactor:
rm -rf $(DESTDIR)
mkdir -p $(LIBDIR)
mkdir -p $(BINDIR)
mkdir -p $(SBINDIR)
mkdir -p $(APPSDIR)
mkdir -p $(CFGDIR)
mkdir -p $(POLKITDIR)
mkdir -p $(XDGAUTOSTARTDIR)
mkdir -p $(KDEAUTOSTARTDIR)
mkdir $(LIBDIR)/img
# Cleans up .pyc and cache folders
rm -f $(PYC) $(CACHES)
cp -r $(SOURCEDIR)/udsactor $(LIBDIR)/udsactor
cp $(SOURCEDIR)/img/uds-icon.png $(LIBDIR)/img
cp $(SOURCEDIR)/actor_*.py $(LIBDIR)
# QT Dialogs & resources
cp -r $(SOURCEDIR)/ui $(LIBDIR)/ui
# Menu GUI app
cp desktop/UDS_Actor_Configuration.desktop $(APPSDIR)
# Autostart elements for gnome/kde
cp desktop/UDSActorTool.desktop $(XDGAUTOSTARTDIR)
cp desktop/UDSActorTool.desktop $(KDEAUTOSTARTDIR)
# scripts
cp scripts/udsactor $(BINDIR)
cp scripts/UDSActorConfig $(SBINDIR)
cp scripts/UDSActorConfig-pkexec $(SBINDIR)
cp scripts/UDSActorTool $(BINDIR)
cp scripts/UDSActorTool-startup $(BINDIR)
cp scripts/udsvapp ${BINDIR}
# Policy to run as administrator
cp policy/org.openuds.pkexec.UDSActorConfig.policy $(POLKITDIR)
# Fix permissions
chmod 755 $(BINDIR)/udsactor
chmod 755 $(BINDIR)/udsvapp
chmod 755 $(BINDIR)/UDSActorTool-startup
chmod 755 $(SBINDIR)/UDSActor*
chmod 755 $(LIBDIR)/actor_*.py
chmod 644 $(POLKITDIR)/org.openuds.pkexec.UDSActorConfig.policy
# If for red hat based, copy init.d
ifeq ($(DISTRO),rh)
mkdir -p $(SYSTEMDIR)
cp debian/udsactor.service $(SYSTEMDIR)/
endif
# chmod 0755 $(BINDIR)/udsactor
uninstall:
rm -rf $(LIBDIR)
# rm -f $(BINDIR)/udsactor
rm -rf $(CFGDIR)

View File

@ -1,39 +0,0 @@
#!/bin/bash
VERSION=`cat ../../VERSION`
RELEASE=1
top=`pwd`
# Debian based
dpkg-buildpackage -b
cat udsactor-template.spec |
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
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
# Note that, although on opensuse the library is "libXss1" on newer,
# the LibXscrnSaver is a "capability" and gets libXss1 installed
# So right now, we only need 1 uds actor for both platforms.
# cat udsactor-template.spec |
# sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
# sed -e s/"name udsactor"/"name udsactor-opensuse"/g |
# 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; do
rm -rf rpm
for folder in SOURCES BUILD RPMS SPECS SRPMS; do
mkdir -p rpm/$folder
done
rpmbuild -v -bb --clean --buildroot=$top/rpm/BUILD/$pkg-root --target noarch $pkg 2>&1
done
rpm --addsign ../*rpm
#rm udsactor-$VERSION

View File

@ -1,2 +0,0 @@
/udsactor/
/udsactor-unmanaged/

View File

@ -1,71 +0,0 @@
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
* Upgraded to 3.0.0 release
-- Adolfo Gómez García <agomez@virtualcable.es> Wed, 10 Jul 2019 9:24:10 +0200
udsactor (2.2.1) stable; urgency=medium
* Upgraded to 2.2.1 release
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 2 Oct 2018 12:44:12 +0200
udsactor (2.2.0) stable; urgency=medium
* Upgraded to 2.2.0 release
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 19 Oct 2017 16:44:12 +0200
udsactor (2.1.0) stable; urgency=medium
* Fixes for 2.1.0 release
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 19 Jan 2017 08:00:22 +0200
udsactor (2.0.0) stable; urgency=medium
* Upgrade for 2.0.0
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 03:39:21 +0100
udsactor (1.9.1) stable; urgency=medium
* Upgrade for 1.9.1
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 03:19:21 +0100
udsactor (1.9.0) stable; urgency=medium
* Upgrade for 1.9.0 (fixed package version)
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 05 May 2015 07:10:27 +0200
udsactor (1.7.5) stable; urgency=medium
* Upgrade for 1.7.5
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 23 Apr 2015 06:08:53 +0200
udsactor (1.7.0) stable; urgency=medium
* Initial release.
-- Adolfo Gómez García <agomez@virtualcable.es> Mon, 17 Nov 2014 05:32:41 +0100

View File

@ -1 +0,0 @@
10

View File

@ -1,26 +0,0 @@
Source: udsactor
Section: admin
Priority: optional
Maintainer: Adolfo Gómez García <agomez@virtualcable.net>
Build-Depends: debhelper (>= 7), po-debconf, dh-systemd (>= 1.5)
Standards-Version: 4.1.4
Homepage: http://www.udsenterprise.com
Package: udsactor
Section: admin
Priority: optional
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}
Recommends: python3-prctl(>=1.1.1)
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.
Package: udsactor-unmanaged
Section: admin
Priority: optional
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}
Recommends: python3-prctl(>=1.1.1)
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.

View File

@ -1,3 +0,0 @@
udsactor-unmanaged_3.6.0_all.deb admin optional
udsactor_3.6.0_all.deb admin optional
udsactor_3.6.0_amd64.buildinfo admin optional

View File

@ -1 +0,0 @@
3.0 (native)

View File

@ -1 +0,0 @@
udsactor.postinst

View File

@ -1 +0,0 @@
udsactor.service

View File

@ -1,3 +0,0 @@
#!/bin/sh -e
exit 0

View File

@ -1,28 +0,0 @@
#!/bin/sh
. /usr/share/debconf/confmodule
set -e
case "$1" in
configure)
/usr/bin/python3 -m compileall /usr/share/UDSActor > /dev/nul 2>&1
# Fix perms so only root can access "masterKey"
chmod 0700 /etc/udsactor
# chmod 0600 /etc/udsactor/udsactor.cfg
chown root:root /etc/udsactor
# chown root:root /etc/udsactor/udsactor.cfg
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
#DEBHELPER#
exit 0

View File

@ -1,14 +0,0 @@
#!/bin/sh -e
. /usr/share/debconf/confmodule
set -e
if [ "$1" = "purge" ] ; then
if [ -f /etc/udsactor/udsactor.cfg ]; then
mv /etc/udsactor/udsactor.cfg /etc/udsactor/udsactor.cfg.dpkg-backup
# Remove .pyc leaved behind
rm -rf /usr/share/UDSActor || true > /dev/null 2>&1
fi
fi

View File

@ -1 +0,0 @@
#! /bin/bash -e

View File

@ -1,14 +0,0 @@
[Unit]
Description=UDS Broker actor
After=network.target
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/usr/share/UDSActor
ExecStart=/usr/bin/python3 actor_service.py start-foreground
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View File

@ -1,12 +0,0 @@
[Desktop Entry]
Name=UDS Actor Tool
Comment=UDS Actor Userspace tools
Exec=/usr/bin/UDSActorTool-startup
Icon=/usr/share/UDSActor/img/uds-icon.png
Terminal=false
Type=Application
NoDisplay=true
X-KDE-autostart-after=panel
X-KDE-StartupNotify=false
X-DBUS-StartupType=None
X-KDE-UniqueApplet=false

View File

@ -1,11 +0,0 @@
[Desktop Entry]
Name=UDS Actor Configuration
Version=1.0
Exec=/usr/sbin/UDSActorConfig-pkexec
Comment=UDS Actor Configuration Application. (Must be executed as root)
Icon=/usr/share/UDSActor/img/uds-icon.png
Type=Application
Terminal=false
StartupNotify=true
Encoding=UTF-8
Categories=Settings;System;

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<action id="org.freedesktop.policykit.pkexec.run-UDSActorConfig">
<description>Run UDS Actor Configuration Program</description>
<message>Authentication is required to run UDS Actor Configuration</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/UDSActorConfig</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">TRUE</annotate>
</action>
</policyconfig>

View File

@ -1,3 +0,0 @@
UDSActor is the client actor needed to get machines managed by UDS Broker.
Please, visit http://www.udsenterprise.com for more information

View File

@ -1,6 +0,0 @@
#!/bin/sh
FOLDER=/usr/share/UDSActor
cd $FOLDER
exec python3 actor_config.py -platform xcb $@

View File

@ -1,3 +0,0 @@
#!/bin/sh
# pkexec env DISPLAY=$DISPLAY QT_X11_NO_MITSHM=1 "/usr/sbin/UDSActorConfig" "$@"
pkexec "/usr/sbin/UDSActorConfig" "$@"

View File

@ -1,6 +0,0 @@
#!/bin/sh
FOLDER=/usr/share/UDSActor
cd $FOLDER
exec python3 actor_config_unmanaged.py -platform xcb $@

View File

@ -1,6 +0,0 @@
#!/bin/sh
FOLDER=/usr/share/UDSActor
cd $FOLDER
exec python3 -s actor_client.py -platform xcb $@

View File

@ -1,3 +0,0 @@
#!/bin/sh
exec /usr/bin/UDSActorTool

View File

@ -1,13 +0,0 @@
#!/bin/sh
env > /tmp/env.txt
if [ "$PAM_TYPE" = "open_session" ]; then
nohup /usr/bin/udsactor login $PAM_USER &
# Wait in backgroud to TTY to close (close_session is not being invoked right now)
nohup /usr/bin/uds-wait-session &
elif [ "$PAM_TYPE" = "close_session" ]; then
nohup /usr/bin/udsactor logout $PAM_USER &
fi
return 0

View File

@ -1,12 +0,0 @@
#!/bin/sh
while :
do
sleep 5 # Wait 5 seconds between checks
found=`ps -f -u$PAM_USER | grep -v grep | grep -v uds-wait-session | grep "$PAM_TTY" | wc -l`
if [ "$found" = "0" ]; then
/usr/bin/udsactor logout $PAM_USER
exit 0
fi
done

View File

@ -1,6 +0,0 @@
#!/bin/sh
FOLDER=/usr/share/UDSActor
cd $FOLDER
exec python3 actor_service.py $@

View File

@ -1,3 +0,0 @@
#!/bin/sh
exec /usr/bin/udsactor login $2 &

View File

@ -1,3 +0,0 @@
#!/bin/sh
exec /usr/bin/udsactor logout $2 &

View File

@ -1,5 +0,0 @@
#!/bin/sh
/usr/bin/udsactor login "$USER"
$@
/usr/bin/udsactor logout "$USER"

View File

@ -1,70 +0,0 @@
%define _topdir %(echo $PWD)/rpm
%define name udsactor
%define version 0.0.0
%define release 1
%define buildroot %{_topdir}/%{name}-%{version}-%{release}-root
BuildRoot: %{buildroot}
Name: %{name}
Version: %{version}
Release: %{release}
Summary: Actor for Universal Desktop Services (UDS) Broker
License: BSD3
Group: Admin
Requires: python3-six python3-requests python3-qt5 libXScrnSaver
Vendor: Virtual Cable S.L.U.
URL: http://www.udsenterprise.com
Provides: udsactor
%define _rpmdir ../
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
%install
curdir=`pwd`
cd ../..
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh install-udsactor
cd $curdir
%clean
rm -rf $RPM_BUILD_ROOT
curdir=`pwd`
cd ../..
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh clean
cd $curdir
%post
systemctl enable udsactor.service > /dev/null 2>&1
%preun
systemctl disable udsactor.service > /dev/null 2>&1
systemctl stop udsactor.service > /dev/null 2>&1
%postun
# $1 == 0 on uninstall, == 1 on upgrade for preun and postun (just a reminder for me... :) )
if [ $1 -eq 0 ]; then
rm -rf /etc/udsactor
rm /var/log/udsactor.log
fi
# And, posibly, the .pyc leaved behind on /usr/share/UDSActor
rm -rf /usr/share/UDSActor > /dev/null 2>&1
%description
This package provides the required components to allow this machine to work on an environment managed by UDS Broker.
%files
%defattr(-,root,root)
/etc/udsactor
/etc/xdg/autostart/UDSActorTool.desktop
/etc/systemd/system/udsactor.service
/usr/bin/UDSActorTool-startup
/usr/bin/udsactor
/usr/bin/udsvapp
/usr/bin/UDSActorTool
/usr/sbin/UDSActorConfig
/usr/sbin/UDSActorConfig-pkexec
/usr/share/UDSActor/*
/usr/share/applications/UDS_Actor_Configuration.desktop
/usr/share/autostart/UDSActorTool.desktop
/usr/share/polkit-1/actions/org.openuds.pkexec.UDSActorConfig.policy

View File

@ -1,70 +0,0 @@
%define _topdir %(echo $PWD)/rpm
%define name udsactor-unmanaged
%define version 0.0.0
%define release 1
%define buildroot %{_topdir}/%{name}-%{version}-%{release}-root
BuildRoot: %{buildroot}
Name: %{name}
Version: %{version}
Release: %{release}
Summary: Actor for Universal Desktop Services (UDS) Broker
License: BSD3
Group: Admin
Requires: python3-six python3-requests python3-qt5 libXScrnSaver
Vendor: Virtual Cable S.L.U.
URL: http://www.udsenterprise.com
Provides: udsactor
%define _rpmdir ../
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
%install
curdir=`pwd`
cd ../..
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh install-udsactor-unmanaged
cd $curdir
%clean
rm -rf $RPM_BUILD_ROOT
curdir=`pwd`
cd ../..
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh clean
cd $curdir
%post
systemctl enable udsactor.service > /dev/null 2>&1
%preun
systemctl disable udsactor.service > /dev/null 2>&1
systemctl stop udsactor.service > /dev/null 2>&1
%postun
# $1 == 0 on uninstall, == 1 on upgrade for preun and postun (just a reminder for me... :) )
if [ $1 -eq 0 ]; then
rm -rf /etc/udsactor
rm /var/log/udsactor.log
fi
# And, posibly, the .pyc leaved behind on /usr/share/UDSActor
rm -rf /usr/share/UDSActor > /dev/null 2>&1
%description
This package provides the required components to allow this unmanaged machine to work on an environment managed by UDS Broker.
%files
%defattr(-,root,root)
/etc/udsactor
/etc/xdg/autostart/UDSActorTool.desktop
/etc/systemd/system/udsactor.service
/usr/bin/UDSActorTool-startup
/usr/bin/udsactor
/usr/bin/udsvapp
/usr/bin/UDSActorTool
/usr/sbin/UDSActorConfig
/usr/sbin/UDSActorConfig-pkexec
/usr/share/UDSActor/*
/usr/share/applications/UDS_Actor_Configuration.desktop
/usr/share/autostart/UDSActorTool.desktop
/usr/share/polkit-1/actions/org.openuds.pkexec.UDSActorConfig.policy

View File

@ -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>

View File

@ -1 +0,0 @@
service file (net.virtualcable.udsactor.server.plist) goes in /Library/LaunchDaemons

View File

@ -1,3 +0,0 @@
dist
build
*.spec

View File

@ -1,76 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020 Virtual Cable S.L.
# 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 os
import PyQt5 # noqa
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QMainWindow
from udsactor.log import logger, INFO
from udsactor.client import UDSClientQApp
from udsactor import platform
if __name__ == "__main__":
logger.setLevel(INFO)
# Ensure idle operations is initialized on start
platform.operations.initIdleDuration(0)
if platform.is_linux:
os.environ['QT_X11_NO_MITSHM'] = '1'
UDSClientQApp.setQuitOnLastWindowClosed(False)
qApp = UDSClientQApp(sys.argv)
if platform.is_windows or platform.is_mac:
# The "hidden window" is not needed on linux
# Not needed on Linux
mw = QMainWindow()
mw.showMinimized() # Start minimized, will be hidden (not destroyed) as soon as qApp.init is invoked
qApp.setMainWindow(mw)
qApp.init()
# Crate a timer to a "dummy" function, so python can check signals from time to time by executing the python interpreter
# Note: Signals are only checked on python code execution, so we create a timer to force call back to python
timer = QTimer(qApp)
timer.start(1000)
timer.timeout.connect(lambda *a: None) # type: ignore # timeout can be connected to a callable
qApp.exec()
# On windows, if no window is created, this point will never be reached.
qApp.end()
logger.debug('Exiting...')

View File

@ -1,195 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020-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
'''
# pylint: disable=invalid-name
import sys
import os
import logging
import typing
import PyQt5 # Ensures PyQt is included in the package
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox
import udsactor
from ui.setup_dialog_ui import Ui_UdsActorSetupDialog
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from PyQt5.QtWidgets import QLineEdit # pylint: disable=ungrouped-imports
logger = logging.getLogger('actor')
class UDSConfigDialog(QDialog):
_host: str = ''
def __init__(self) -> None:
QDialog.__init__(self, None)
# Get local config config
config: udsactor.types.ActorConfigurationType = udsactor.platform.store.readConfig()
self.ui = Ui_UdsActorSetupDialog()
self.ui.setupUi(self)
self.ui.host.setText(config.host)
self.ui.validateCertificate.setCurrentIndex(1 if config.validateCertificate else 0)
self.ui.postConfigCommand.setText(config.post_command or '')
self.ui.preCommand.setText(config.pre_command or '')
self.ui.runonceCommand.setText(config.runonce_command or '')
self.ui.logLevelComboBox.setCurrentIndex(config.log_level)
if config.host:
self.updateAuthenticators()
self.ui.username.setText('')
self.ui.password.setText('')
self.ui.testButton.setEnabled(bool(config.master_token and config.host))
@property
def api(self) -> udsactor.rest.UDSServerApi:
return udsactor.rest.UDSServerApi(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
def browse(self, lineEdit: 'QLineEdit', caption: str) -> None:
name = QFileDialog.getOpenFileName(parent=self, caption=caption, directory=os.path.dirname(lineEdit.text()))[0]
if name:
if ' ' in name:
name = '"' + name + '"'
lineEdit.setText(os.path.normpath(name))
def browsePreconnect(self) -> None:
self.browse(self.ui.preCommand, 'Select Preconnect command')
def browseRunOnce(self) -> None:
self.browse(self.ui.runonceCommand, 'Select Runonce command')
def browsePostConfig(self) -> None:
self.browse(self.ui.postConfigCommand, 'Select Postconfig command')
def updateAuthenticators(self) -> None:
if self.ui.host.text() != self._host:
self._host = self.ui.host.text()
self.ui.authenticators.clear()
auth: udsactor.types.AuthenticatorType
auths = list(self.api.enumerateAuthenticators())
if auths:
for auth in auths:
self.ui.authenticators.addItem(auth.auth, userData=auth)
# Last, add "admin" authenticator (for uds root user)
self.ui.authenticators.addItem('Administration', userData=udsactor.types.AuthenticatorType('admin', 'admin', 'admin', 'admin', 1, False))
def textChanged(self) -> None:
enableButtons = bool(self.ui.host.text() and self.ui.username.text() and self.ui.password.text() and self.ui.authenticators.currentText())
self.ui.registerButton.setEnabled(enableButtons)
self.ui.testButton.setEnabled(False) # Only registered information can be checked
def finish(self) -> None:
self.close()
def testUDSServer(self) -> None:
config: udsactor.types.ActorConfigurationType = udsactor.platform.store.readConfig()
if not config.master_token or not config.host:
self.ui.testButton.setEnabled(False)
return
try:
api = udsactor.rest.UDSServerApi(config.host, config.validateCertificate)
if not api.test(config.master_token, udsactor.types.MANAGED):
QMessageBox.information(
self,
'UDS Test',
'Current configured token seems to be invalid for {}. Please, request a new one.'.format(config.host),
QMessageBox.Ok
)
else:
QMessageBox.information(
self,
'UDS Test',
'Configuration for {} seems to be correct.'.format(config.host),
QMessageBox.Ok
)
except Exception:
QMessageBox.information(
self,
'UDS Test',
'Configured host {} seems to be inaccesible.'.format(config.host),
QMessageBox.Ok
)
def registerWithUDS(self) -> None:
# Get network card. Will fail if no network card is available, but don't mind (not contempled)
data: udsactor.types.InterfaceInfoType = next(udsactor.platform.operations.getNetworkInfo())
try:
token = self.api.register(
self.ui.authenticators.currentData().auth,
self.ui.username.text(),
self.ui.password.text(),
udsactor.platform.operations.getComputerName(),
data.ip or '', # IP
data.mac or '', # MAC
self.ui.preCommand.text(),
self.ui.runonceCommand.text(),
self.ui.postConfigCommand.text(),
self.ui.logLevelComboBox.currentIndex() # Loglevel
)
# Store parameters on register for later use, notify user of registration
udsactor.platform.store.writeConfig(
udsactor.types.ActorConfigurationType(
actorType=udsactor.types.MANAGED,
host=self.ui.host.text(),
validateCertificate=self.ui.validateCertificate.currentIndex() == 1,
master_token=token,
pre_command=self.ui.preCommand.text(),
post_command=self.ui.postConfigCommand.text(),
runonce_command=self.ui.runonceCommand.text(),
log_level=self.ui.logLevelComboBox.currentIndex()
)
)
# Enables test button
self.ui.testButton.setEnabled(True)
# Informs the user
QMessageBox.information(self, 'UDS Registration', 'Registration with UDS completed.', QMessageBox.Ok)
except udsactor.rest.RESTError as e:
self.ui.testButton.setEnabled(False)
QMessageBox.critical(self, 'UDS Registration', 'UDS Registration error: {}'.format(e), QMessageBox.Ok)
if __name__ == "__main__":
# If to be run as "sudo" on linux, we will need this to avoid problems
if 'linux' in sys.platform:
os.environ['QT_X11_NO_MITSHM'] = '1'
app = QApplication(sys.argv)
if udsactor.platform.operations.checkPermissions() is False:
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore
sys.exit(1)
myapp = UDSConfigDialog()
myapp.show()
sys.exit(app.exec())

View File

@ -1,197 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020-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
'''
# pylint: disable=invalid-name
import sys
import os
import pickle # nosec: B403
import logging
import typing
import PyQt5 # pylint: disable=unused-import
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
import udsactor
import udsactor.tools
from ui.setup_dialog_unmanaged_ui import Ui_UdsActorSetupDialog
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from PyQt5.QtWidgets import QLineEdit # pylint: disable=ungrouped-imports
logger = logging.getLogger('actor')
class UDSConfigDialog(QDialog):
_host: str = ''
_config: udsactor.types.ActorConfigurationType
def __init__(self) -> None:
QDialog.__init__(self, None)
# Get local config config
self._config = udsactor.platform.store.readConfig()
self.ui = Ui_UdsActorSetupDialog()
self.ui.setupUi(self)
self.ui.host.setText(self._config.host)
self.ui.validateCertificate.setCurrentIndex(
1 if self._config.validateCertificate else 0
)
self.ui.logLevelComboBox.setCurrentIndex(self._config.log_level)
self.ui.serviceToken.setText(self._config.master_token or '')
self.ui.restrictNet.setText(self._config.restrict_net or '')
self.ui.testButton.setEnabled(
bool(self._config.master_token and self._config.host)
)
@property
def api(self) -> udsactor.rest.UDSServerApi:
return udsactor.rest.UDSServerApi(
self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1
)
def finish(self) -> None:
self.close()
def configChanged(self, text: str) -> None:
self.ui.testButton.setEnabled(
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:
if not self._config.master_token or not self._config.host:
self.ui.testButton.setEnabled(False)
return
try:
api = udsactor.rest.UDSServerApi(
self._config.host, self._config.validateCertificate
)
if not api.test(self._config.master_token, udsactor.types.UNMANAGED):
QMessageBox.information(
self,
'UDS Test',
'Service token seems to be invalid . Please, check token validity.',
QMessageBox.Ok, # type: ignore
)
else:
QMessageBox.information(
self,
'UDS Test',
'Configuration for {} seems to be correct.'.format(
self._config.host
),
QMessageBox.Ok, # type: ignore
)
except Exception:
QMessageBox.information(
self,
'UDS Test',
'Configured host {} seems to be inaccesible.'.format(self._config.host),
QMessageBox.Ok, # type: ignore
)
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
self._config = udsactor.types.ActorConfigurationType(
actorType=udsactor.types.UNMANAGED,
host=self.ui.host.text(),
validateCertificate=self.ui.validateCertificate.currentIndex() == 1,
master_token=self.ui.serviceToken.text().strip(),
restrict_net=restrictNet,
log_level=self.ui.logLevelComboBox.currentIndex(),
)
udsactor.platform.store.writeConfig(self._config)
# Enables test button
self.ui.testButton.setEnabled(True)
# Informs the user
QMessageBox.information(
self,
'UDS Configuration',
'Configuration saved.',
QMessageBox.Ok, # type: ignore
)
if __name__ == "__main__":
# If run as "sudo" on linux, we will need this to avoid problems
if 'linux' in sys.platform:
os.environ['QT_X11_NO_MITSHM'] = '1'
app = QApplication(sys.argv)
if udsactor.platform.operations.checkPermissions() is False:
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore
sys.exit(1)
if len(sys.argv) > 2:
if sys.argv[1] == 'export':
try:
with open(sys.argv[2], 'wb') as export_:
pickle.dump(
udsactor.platform.store.readConfig(), export_, protocol=3
)
except Exception as e:
print('Error exporting configuration file: {}'.format(e))
sys.exit(1)
sys.exit(0)
elif sys.argv[1] == 'import':
try:
with open(sys.argv[2], 'rb') as import_:
config = pickle.load(import_) # nosec: B301: the file is provided by user, so it's not a security issue
udsactor.platform.store.writeConfig(config)
except Exception as e:
print('Error importing configuration file: {}'.format(e))
sys.exit(1)
sys.exit(0)
myapp = UDSConfigDialog()
myapp.show()
sys.exit(app.exec())

View File

@ -1,36 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020-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
'''
from udsactor import platform
if __name__ == "__main__":
platform.runner.run()

View File

@ -1,398 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>Adolfo Gómez</author>
<class>UdsActorSetupDialog</class>
<widget class="QDialog" name="UdsActorSetupDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>243</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Verdana</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="windowTitle">
<string>UDS Actor Configuration Tool</string>
</property>
<property name="windowIcon">
<iconset resource="uds.qrc">
<normaloff>:/img/img/uds-icon.png</normaloff>:/img/img/uds-icon.png</iconset>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QPushButton" name="saveButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>10</x>
<y>210</y>
<width>181</width>
<height>23</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>181</width>
<height>0</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="toolTip">
<string>Click to register Actor with UDS Broker</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Click on this button to register Actor with UDS Broker.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Save Configuration</string>
</property>
</widget>
<widget class="QPushButton" name="closeButton">
<property name="geometry">
<rect>
<x>410</x>
<y>210</y>
<width>171</width>
<height>23</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>171</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Closes UDS Actor Configuration (discard pending changes if any)</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Exits the UDS Actor Configuration Tool&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
<widget class="QPushButton" name="testButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>210</x>
<y>210</y>
<width>181</width>
<height>23</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>181</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Click to test existing configuration (disabled if no config found)</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Click on this button to test the server host and assigned toen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Test configuration</string>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>571</width>
<height>191</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="verticalSpacing">
<number>16</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_security">
<property name="text">
<string>SSL Validation</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="validateCertificate">
<property name="toolTip">
<string>Select communication security with broker</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the security for communications with UDS Broker.&lt;/p&gt;&lt;p&gt;The recommended method of communication is &lt;span style=&quot; font-weight:600;&quot;&gt;Use SSL&lt;/span&gt;, but selection needs to be acording to your broker configuration.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>Ignore certificate</string>
</property>
</item>
<item>
<property name="text">
<string>Verify certificate</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_host">
<property name="text">
<string>UDS Server</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="host">
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Uds Broker Server Addres. Use IP or FQDN</string>
</property>
<property name="whatsThis">
<string>Enter here the UDS Broker Addres using either its IP address or its FQDN address</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_serviceToken">
<property name="text">
<string>Service Token</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="serviceToken">
<property name="toolTip">
<string>UDS Service Token</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Token of the service on UDS platform&lt;/p&gt;&lt;p&gt;This token can be obtainend from the service configuration on UDS.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_loglevel">
<property name="text">
<string>Log Level</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="logLevelComboBox">
<property name="currentIndex">
<number>1</number>
</property>
<property name="frame">
<bool>true</bool>
</property>
<item>
<property name="text">
<string notr="true">DEBUG</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">INFO</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">ERROR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">FATAL</string>
</property>
</item>
</widget>
</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Restrics valid detection of network interfaces.&lt;/p&gt;&lt;p&gt;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..&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
<zorder>label_host</zorder>
<zorder>host</zorder>
<zorder>label_serviceToken</zorder>
<zorder>serviceToken</zorder>
<zorder>validateCertificate</zorder>
<zorder>label_security</zorder>
<zorder>label_loglevel</zorder>
<zorder>logLevelComboBox</zorder>
<zorder>label_restrictNet</zorder>
<zorder>restrictNet</zorder>
</widget>
</widget>
<resources>
<include location="uds.qrc"/>
</resources>
<connections>
<connection>
<sender>closeButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>finish()</slot>
<hints>
<hint type="sourcelabel">
<x>315</x>
<y>165</y>
</hint>
<hint type="destinationlabel">
<x>231</x>
<y>161</y>
</hint>
</hints>
</connection>
<connection>
<sender>testButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>testUDSServer()</slot>
<hints>
<hint type="sourcelabel">
<x>300</x>
<y>281</y>
</hint>
<hint type="destinationlabel">
<x>294</x>
<y>153</y>
</hint>
</hints>
</connection>
<connection>
<sender>saveButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>saveConfig()</slot>
<hints>
<hint type="sourcelabel">
<x>100</x>
<y>191</y>
</hint>
<hint type="destinationlabel">
<x>297</x>
<y>109</y>
</hint>
</hints>
</connection>
<connection>
<sender>host</sender>
<signal>textChanged(QString)</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>configChanged()</slot>
<hints>
<hint type="sourcelabel">
<x>341</x>
<y>61</y>
</hint>
<hint type="destinationlabel">
<x>297</x>
<y>109</y>
</hint>
</hints>
</connection>
<connection>
<sender>serviceToken</sender>
<signal>textChanged(QString)</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>configChanged()</slot>
<hints>
<hint type="sourcelabel">
<x>341</x>
<y>100</y>
</hint>
<hint type="destinationlabel">
<x>297</x>
<y>109</y>
</hint>
</hints>
</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>
<slots>
<slot>finish()</slot>
<slot>saveConfig()</slot>
<slot>testUDSServer()</slot>
<slot>configChanged()</slot>
</slots>
</ui>

View File

@ -1,660 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>Adolfo Gómez</author>
<class>UdsActorSetupDialog</class>
<widget class="QDialog" name="UdsActorSetupDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>590</width>
<height>307</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Verdana</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="windowTitle">
<string>UDS Actor Configuration Tool</string>
</property>
<property name="windowIcon">
<iconset resource="uds.qrc">
<normaloff>:/img/img/uds-icon.png</normaloff>:/img/img/uds-icon.png</iconset>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QPushButton" name="registerButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>10</x>
<y>270</y>
<width>181</width>
<height>23</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>181</width>
<height>0</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="toolTip">
<string>Click to register Actor with UDS Broker</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Click on this button to register Actor with UDS Broker.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Register with UDS</string>
</property>
</widget>
<widget class="QPushButton" name="closeButton">
<property name="geometry">
<rect>
<x>410</x>
<y>270</y>
<width>171</width>
<height>23</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>171</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Closes UDS Actor Configuration (discard pending changes if any)</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Exits the UDS Actor Configuration Tool&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
<widget class="QTabWidget" name="tabWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>571</width>
<height>241</height>
</rect>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_uds">
<attribute name="title">
<string>UDS Server</string>
</attribute>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>551</width>
<height>191</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="verticalSpacing">
<number>16</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_host">
<property name="text">
<string>UDS Server</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="host">
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Uds Broker Server Addres. Use IP or FQDN</string>
</property>
<property name="whatsThis">
<string>Enter here the UDS Broker Addres using either its IP address or its FQDN address</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_auth">
<property name="text">
<string>Authenticator</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="authenticators">
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the UDS Broker authenticator for credentials validation&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_username">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="username">
<property name="toolTip">
<string>UDS user with administration rights (Will not be stored on template)</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Administrator user on UDS Server.&lt;/p&gt;&lt;p&gt;Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_password">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="password">
<property name="toolTip">
<string>Password for user (Will not be stored on template)</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Administrator password for the user on UDS Server.&lt;/p&gt;&lt;p&gt;Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="validateCertificate">
<property name="toolTip">
<string>Select communication security with broker</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the security for communications with UDS Broker.&lt;/p&gt;&lt;p&gt;The recommended method of communication is &lt;span style=&quot; font-weight:600;&quot;&gt;Use SSL&lt;/span&gt;, but selection needs to be acording to your broker configuration.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>Ignore certificate</string>
</property>
</item>
<item>
<property name="text">
<string>Verify certificate</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_security">
<property name="text">
<string>SSL Validation</string>
</property>
</widget>
</item>
</layout>
<zorder>label_host</zorder>
<zorder>host</zorder>
<zorder>label_auth</zorder>
<zorder>label_username</zorder>
<zorder>username</zorder>
<zorder>label_password</zorder>
<zorder>password</zorder>
<zorder>validateCertificate</zorder>
<zorder>label_security</zorder>
<zorder>authenticators</zorder>
</widget>
</widget>
<widget class="QWidget" name="tab_advanced">
<attribute name="title">
<string>Advanced</string>
</attribute>
<widget class="QWidget" name="layoutWidget_2">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>551</width>
<height>161</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="verticalSpacing">
<number>16</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_host_2">
<property name="text">
<string>Preconnect</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>4</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="preCommand">
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Pre connection command. Executed just before the user is connected to machine.</string>
</property>
<property name="whatsThis">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browsePreconnectButton">
<property name="text">
<string>Browse</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_username_2">
<property name="text">
<string>Runonce</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>4</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="runonceCommand">
<property name="toolTip">
<string>Run once command. Executed on first boot, just before UDS does anything.</string>
</property>
<property name="whatsThis">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browseRunOnceButton">
<property name="text">
<string>Browse</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_password_2">
<property name="text">
<string>Postconfig</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>4</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="postConfigCommand">
<property name="toolTip">
<string>Command to execute after UDS finalizes the VM configuration.</string>
</property>
<property name="whatsThis">
<string/>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browsePostConfigButton">
<property name="text">
<string>Browse</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_loglevel">
<property name="text">
<string>Log Level</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="logLevelComboBox">
<property name="currentIndex">
<number>1</number>
</property>
<property name="frame">
<bool>true</bool>
</property>
<item>
<property name="text">
<string notr="true">DEBUG</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">INFO</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">ERROR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">FATAL</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
<widget class="QPushButton" name="testButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>210</x>
<y>270</y>
<width>181</width>
<height>23</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>181</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Click to test existing configuration (disabled if no config found)</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Click on this button to test the server host and assigned toen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Test configuration</string>
</property>
</widget>
</widget>
<resources>
<include location="uds.qrc"/>
</resources>
<connections>
<connection>
<sender>closeButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>finish()</slot>
<hints>
<hint type="sourcelabel">
<x>315</x>
<y>165</y>
</hint>
<hint type="destinationlabel">
<x>231</x>
<y>161</y>
</hint>
</hints>
</connection>
<connection>
<sender>registerButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>registerWithUDS()</slot>
<hints>
<hint type="sourcelabel">
<x>239</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>285</x>
<y>185</y>
</hint>
</hints>
</connection>
<connection>
<sender>host</sender>
<signal>textChanged(QString)</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>textChanged()</slot>
<hints>
<hint type="sourcelabel">
<x>239</x>
<y>59</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>150</y>
</hint>
</hints>
</connection>
<connection>
<sender>username</sender>
<signal>textChanged(QString)</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>textChanged()</slot>
<hints>
<hint type="sourcelabel">
<x>239</x>
<y>98</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>150</y>
</hint>
</hints>
</connection>
<connection>
<sender>password</sender>
<signal>textChanged(QString)</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>textChanged()</slot>
<hints>
<hint type="sourcelabel">
<x>239</x>
<y>137</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>150</y>
</hint>
</hints>
</connection>
<connection>
<sender>browsePreconnectButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>browsePreconnect()</slot>
<hints>
<hint type="sourcelabel">
<x>430</x>
<y>60</y>
</hint>
<hint type="destinationlabel">
<x>243</x>
<y>150</y>
</hint>
</hints>
</connection>
<connection>
<sender>browsePostConfigButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>browsePostConfig()</slot>
<hints>
<hint type="sourcelabel">
<x>430</x>
<y>142</y>
</hint>
<hint type="destinationlabel">
<x>243</x>
<y>150</y>
</hint>
</hints>
</connection>
<connection>
<sender>browseRunOnceButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>browseRunOnce()</slot>
<hints>
<hint type="sourcelabel">
<x>430</x>
<y>101</y>
</hint>
<hint type="destinationlabel">
<x>243</x>
<y>150</y>
</hint>
</hints>
</connection>
<connection>
<sender>host</sender>
<signal>editingFinished()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>updateAuthenticators()</slot>
<hints>
<hint type="sourcelabel">
<x>343</x>
<y>98</y>
</hint>
<hint type="destinationlabel">
<x>294</x>
<y>153</y>
</hint>
</hints>
</connection>
<connection>
<sender>authenticators</sender>
<signal>currentTextChanged(QString)</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>textChanged()</slot>
<hints>
<hint type="sourcelabel">
<x>343</x>
<y>137</y>
</hint>
<hint type="destinationlabel">
<x>294</x>
<y>153</y>
</hint>
</hints>
</connection>
<connection>
<sender>testButton</sender>
<signal>clicked()</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>testUDSServer()</slot>
<hints>
<hint type="sourcelabel">
<x>300</x>
<y>281</y>
</hint>
<hint type="destinationlabel">
<x>294</x>
<y>153</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>textChanged()</slot>
<slot>finish()</slot>
<slot>registerWithUDS()</slot>
<slot>browsePreconnect()</slot>
<slot>browseRunOnce()</slot>
<slot>browsePostConfig()</slot>
<slot>updateAuthenticators()</slot>
<slot>testUDSServer()</slot>
</slots>
</ui>

View File

@ -1,5 +0,0 @@
<RCC>
<qresource prefix="img">
<file>../img/uds-icon.png</file>
</qresource>
</RCC>

View File

@ -1,16 +0,0 @@
#!/bin/bash
function process {
# pyuic4 about-dialog.ui -o about_dialog_ui.py -x
# pyuic4 message-dialog.ui -o message_dialog_ui.py
pyuic5 setup-dialog.ui -o ../ui/setup_dialog_ui.py --import-from=ui
pyuic5 setup-dialog-unmanaged.ui -o ../ui/setup_dialog_unmanaged_ui.py --import-from=ui
}
pyrcc5 uds.qrc -o ../ui/uds_rc.py
# process current directory ui's
process

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from . import types
from . import rest
from . import platform
__title__ = 'udsactor'
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
__license__ = "BSD 3-clause"
__copyright__ = "Copyright 2014-2022 VirtualCable S.L.U."

View File

@ -1,51 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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 tempfile
import os.path
import secrets
import typing
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from . import types
def saveCertificate(certInfo: 'types.CertificateInfoType') -> typing.Tuple[str, str]:
"""
Returns CertificateFile, Password tuple generated from certInfo
"""
certFile = os.path.join(tempfile.gettempdir(), secrets.token_hex(16))
with open(certFile, "w") as f:
f.write(certInfo.private_key+certInfo.server_certificate)
return certFile, certInfo.password

View File

@ -1,251 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# 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 threading
import time
import datetime
import signal
import typing
from PyQt5.QtWidgets import QApplication, QMessageBox
from PyQt5.QtCore import QByteArray, QBuffer, QIODevice, pyqtSignal
from . import rest
from . import tools
from . import platform
from .log import logger
from .http import client
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from . import types
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QMainWindow
class UDSClientQApp(QApplication):
_app: 'UDSActorClient'
_initialized: bool
_mainWindow: typing.Optional['QMainWindow']
message = pyqtSignal(str, name='message')
def __init__(self, args) -> None:
super().__init__(args)
self._mainWindow = None
self._initialized = False
# 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.aboutToQuit.connect(self.end)
self.message.connect(self.showMessage) # type: ignore # there are problems with Pylance and connects on PyQt5... :)
# Execute backgroup thread for actions
self._app = UDSActorClient(self)
def init(self) -> None:
# Notify loging and mark it
logger.debug('Starting APP')
if self._mainWindow:
self._mainWindow.hide()
self._app.start()
self._initialized = True
def end(self, sessionManager=None) -> None: # pylint: disable=unused-argument
if not self._initialized:
return
self._initialized = False
logger.debug('Stopping app thread')
self._app.stop()
self._app.join()
def showMessage(self, message: str) -> None:
QMessageBox.information(None, 'Message', message) # type: ignore
def setMainWindow(self, mw: 'QMainWindow'):
self._mainWindow = mw
class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-attributes
_running: bool
_forceLogoff: bool
_extraLogoff: str
_qApp: UDSClientQApp
_listener: client.HTTPServerThread
_loginInfo: typing.Optional['types.LoginResultInfoType']
_notified: bool
_notifiedDeadline: bool
_sessionStartTime: datetime.datetime
api: rest.UDSClientApi
def __init__(self, qApp: QApplication):
super().__init__()
self.api = rest.UDSClientApi() # Self initialized
self._qApp = typing.cast(UDSClientQApp, qApp)
self._running = False
self._forceLogoff = False
self._extraLogoff = ''
self._listener = client.HTTPServerThread(self)
self._loginInfo = None
self._notified = False
self._notifiedDeadline = False
# Capture stop signals..
logger.debug('Setting signals...')
signal.signal(signal.SIGINT, self.stopSignal)
signal.signal(signal.SIGTERM, self.stopSignal)
def stopSignal(self, signum, frame) -> None: # pylint: disable=unused-argument
logger.info('Stop signal received')
self.stop()
def checkDeadLine(self):
if self._loginInfo is None or not self._loginInfo.dead_line: # No deadline check
return
remainingTime = self._loginInfo.dead_line - (datetime.datetime.now() - self._sessionStartTime).total_seconds()
logger.debug('Remaining time: {}'.format(remainingTime))
if not self._notifiedDeadline and remainingTime < 300: # With five minutes, show a warning message
self._notifiedDeadline = True
self._showMessage('Your session will expire in less that 5 minutes. Please, save your work and disconnect.')
return
if remainingTime <= 0:
logger.debug('Session dead line reached. Logging out')
self._running = False
self._forceLogoff = True
def checkIdle(self) -> None:
if self._loginInfo is None or not self._loginInfo.max_idle: # No idle check
return
idleTime = platform.operations.getIdleDuration()
remainingTime = self._loginInfo.max_idle - idleTime
logger.debug('Idle: %s Remaining: %s', idleTime, remainingTime)
if remainingTime > 120: # Reset show Warning dialog if we have more than 5 minutes left
self._notified = False
return
if not self._notified and remainingTime < 120: # With two minutes, show a warning message
self._notified = True
self._showMessage('You have been idle for too long. The session will end if you don\'t resume operations.')
if remainingTime <= 0:
logger.info('User has been idle for too long, exiting from session')
self._extraLogoff = ' (idle: {} vs {})'.format(int(idleTime), self._loginInfo.max_idle)
self._running = False
self._forceLogoff = True
def run(self) -> None:
logger.debug('UDS Actor thread')
self._listener.start() # async listener for service
self._running = True
self._sessionStartTime = datetime.datetime.now()
time.sleep(0.4) # Wait a bit before sending login
try:
# Notify loging and mark it
user, sessionType = platform.operations.getCurrentUser(), platform.operations.getSessionType()
self._loginInfo = self.api.login(user, sessionType)
if self._loginInfo.max_idle:
platform.operations.initIdleDuration(self._loginInfo.max_idle)
while self._running:
# Check Idle & dead line
self.checkIdle()
self.checkDeadLine()
time.sleep(1.22) # 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
except Exception as e:
logger.error('Error on client loop: %s', e)
self._listener.stop() # async listener for service
# Notify exit to qt
QApplication.quit()
if self._forceLogoff:
time.sleep(1.3) # Wait a bit before forcing logoff
platform.operations.loggoff()
def _showMessage(self, message: str) -> None:
self._qApp.message.emit(message) # type: ignore # there are problems with Pylance and connects on PyQt5... :)
def stop(self) -> None:
logger.debug('Stopping client Service')
self._running = False
def logout(self) -> typing.Any:
self._forceLogoff = True
self._running = False
return 'ok'
def message(self, msg: str) -> typing.Any:
threading.Thread(target=self._showMessage, args=(msg,)).start()
return 'ok'
def screenshot(self) -> typing.Any:
'''
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
'''
pixmap: 'QPixmap' = self._qApp.primaryScreen().grabWindow(0) # type: ignore
ba = QByteArray()
buffer = QBuffer(ba)
buffer.open(QIODevice.WriteOnly) # type: ignore
pixmap.save(buffer, 'PNG')
buffer.close()
scrBase64 = bytes(ba.toBase64()).decode() # type: ignore # there are problems with Pylance and connects on PyQt5... :)
logger.debug('Screenshot length: %s', len(scrBase64))
return scrBase64 # 'result' of JSON will contain base64 of screen
def script(self, script: str) -> typing.Any:
tools.ScriptExecutorThread(script).start()
return 'ok'

View File

@ -1,7 +0,0 @@
from .. import types
defaultCertificate = types.CertificateInfoType(
private_key='-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFHTBPBgkqhkiG9w0BBQ0wQjApBgkqhkiG9w0BBQwwHAQIfG2+iMYJBswCAggA\nMAwGCCqGSIb3DQIJBQAwFQYJKwYBBAGXVQECBAhCusU5R8ulZQSCBMgheyZ81Qkq\n+TcbPeBlUGCFllSUOo7xQ/OuwYSmzLx8LpN0hQNv4azF6MYH+I8eMSPd3A547yW3\nJE4GjIBfRvcq2X1UZ2FQfECU9UP0ShPuPrVhIh6ZZklmlRjbIF8hGfSzXAuafQb+\n4wXXsofahi/SPgqK1Gw65nRiMcoeRZchJkx8pBgKVWED6Cbh6aAkeqkVKPnsebiV\n6kE+0C7+hgNUbyRd46R+/5NXzPjg4ItfSak+PLzQ1KeRv4Cu6DdzRKJ4V9/MlNdU\nNNEkSVSEaRn4sv+eByU4uxBMaSmD1tLc/A7OmaAeRpIQvls3Zcf2+V0+anAtjbjd\n6eIb2nceey+dKFm4ewlR4mXuzj1QowRTHceOIkvKIrOODxdy9M5hNBZ7VLum29tY\nRhqtmEH2BZZJ8SpM2SsEZzPxqJFiVZbvpeOKjxlMyn1dFWn1rP8uMnfuMKqBaj5D\nd5clOPlwebYw5UpM6Vvawu4nGqxECTSWcfNlDYO5U/0Fsm9+JIrJ7Buukgv2+rhs\nD/6oUK9NB8AW9qnDr7UxbC/ujhkKQG3woaZlPbiMs5WQaS+DrTg4N49wPzS0h+ME\nF8ZzuPnd6+sMGQioCIrQAZ08rk54oCijBhFh8/EQhQKGsMFw2swi9t6+FVU5Bvil\nlhmBd3LA5EuQ5y1X0jRL/+GDiUiZw1gOJP8d/XzhUJL9AmamdqJ6/rAU7lUTNWkM\ndzmFonUO2Mh2zgEEudHsTOH8udZ2l64LIHc6fCkDmM8QzghjrEFyci6R8333DSSM\nwbM0MvyTLM7TTqZUD60EgD+Ihyr/wJcBZY7GVn7hTq7ee14zeI+dZFmTMYOnt0mA\ngof19t0naPPZU+zyl/ambNF5mmSkGOAl4IBHNvPt5ztEVbNpwW3DHbmdYW71Ax+z\nCDlr4iKZahv21o1PCesPV2IlaHZFD6aBRt0DxzMqtq9cpWsI1g7aEaAjRbSvqhMY\npUeqFXz/GfR9rjRkufr48//ll0/Q/Ogx7m1TjQ6mAEQrklI7pa2W0u3H0BpSZSis\nR6ST3ulE+wfsp8cau6q2er+BSsDhBjSn9FeCUjHzY56u9ud/kb6/jLEdgxNpj0na\n3WVqCCCL/dAFSWznBmdracZsRMXapXInHCiiOEkXXbXIXvRKiTPJXdN+w2/U2j2B\nwXZuazVSpmM+xAZTAS9dtBUQJo+5px9b6P09uagvTA32ezbpPXf+hSfmTdUwbmAY\nrmE9SW85tzX+cD17loygBBRrjOr4uQy/s/9FqLx8bM73jly05rdOmX28ECKwEA05\n8aCFkfqrl9J9doVapaUlywpJVPFtE6W6tCF+ULMfb16vEjT1du1+epEnbGGLRQxg\n3aFLyKlvFaNvR38fiQFUGtBgGOaBN3rhGpbMwjch3oReXv9X/4UCL6sVIiOH2H3c\nVSZdC3O5g6CMVe4zckUe1k9mLDb5524IHDFfptZ6Bw+uzrqIy3GHW8dJF2AK471b\nMUnCojTpdbFHaUs2u/rNKVUyY+vLf8hkyP+znBUoPxSJtty53EWNukxjjsxx0lx3\niZGqN72lXlXuSFZAIxi307+xxE21cbzDsMidyJkbKKGm/F4BOKvX9jWmAyYmBG6A\n1L3yNRouFWsYDwYAX2nZ1is=\n-----END ENCRYPTED PRIVATE KEY-----\n',
server_certificate='-----BEGIN CERTIFICATE-----\nMIIDcTCCAlkCBDfnXU8wDQYJKoZIhvcNAQELBQAwfTELMAkGA1UEBhMCRVMxDzAN\nBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMREwDwYDVQQKDAhVRFMgQ2Vy\ndDERMA8GA1UECwwIVURTIENlcnQxEjAQBgNVBAMMCTEyNy4wLjAuMTESMBAGA1Ud\nEQwJMTI3LjAuMC4xMB4XDTIwMDIxNzExNTkzMloXDTMwMDIxNDExNTkzMlowfTEL\nMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMREw\nDwYDVQQKDAhVRFMgQ2VydDERMA8GA1UECwwIVURTIENlcnQxEjAQBgNVBAMMCTEy\nNy4wLjAuMTESMBAGA1UdEQwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA2e1cW7YtRpNLazR3f/LqLv8OB0rKh8cUPH4wuQhbBTkee8Wu\n5eMSadRCIyRbKj4b8dtVfI9QW0SrmhGuMx1KCh3CsYd9XsWiKbGkiRBHIDOn5pkF\n6PUayDJ8KjnGbfnZjp0AmxXP4r1OO8jUPqzKS9Ubf5PgwcwdFiUKVfVPwGwctwt5\nt9YpSRONw0rTsCjVHvO2dd9h6EopskLCWxpN8l9kNLwLM/6t0IqVKmn5/IYPKKN2\nCX8a7IXpxwoiUs4sBZYhUMBWikB1hKQRSYafp1Xvc5PeTFXTFqGANnqz0NoZ8tqL\n8qjQUN/PCdtzhfcP5RgT2g1qyS2RBCMYH7Zs0wIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQCUt+qlLA1N9VXMwDQAYG4Kt6/UlMHCXAajHQQGtjdyGJ4++m7EIjI96hMU\n3Cx2gp2ggR3JGnuSR+DdBvPl5iGku7J8KV0JiJg30gTY8JuUIy/PMLZWloYKrBHV\nlin2GujQ4OsIt3dbr4XtcKW1Wd7L6fBzHlq7Xyxh+gcTzTvTmq67Q9XKlBWsegMf\nv4FKy0lfcSFK3vTzswQtuTontG4TqLiT/4AnMt3D0cTQ6b6KoZwUUX/TDNhau06d\nQ4Ilz8X61ka+4HBkFSR5ahP9noCVhwO329h+6epO141E5Tep3OLc/GCF4oaKOlMR\nfqxf5f2bghU0fxmtEoNJTZkBsN1S\n-----END CERTIFICATE-----\n',
password='Pw7qbatz5u-y-Z5ora2D2ZuBCm95AHnKRcpze53k8tw'
)

View File

@ -1,166 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
import threading
import http.server
import secrets
import json
import typing
from ..log import logger
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from ..client import UDSActorClient
class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.0'
server_version = 'UDS Actor Server'
sys_version = ''
_id: typing.ClassVar[str] # Random id for server
_app: typing.ClassVar['UDSActorClient']
def sendJsonResponse(self, result: typing.Optional[typing.Any] = None, error: typing.Optional[str] = None, code: int = 200) -> None:
data = json.dumps({'result': result, 'error': error})
self.send_response(code)
self.send_header('Content-type', 'application/json')
self.send_header('Content-Length', str(len(data)))
self.send_header('Server: ', self.server_version)
self.end_headers()
try:
self.wfile.write(data.encode())
except Exception:
pass # Evict "broken pipe" when sending errors
def do_POST(self) -> None:
# Only allows requests from localhost
if self.client_address[0][:3] != '127':
self.sendJsonResponse(error='Forbidden', code=403)
# Very simple path & params splitter
path = self.path.split('?')[0][1:].split('/')
if len(path) != 2 or path[0] != HTTPServerHandler._id:
self.sendJsonResponse(error='Forbidden', code=403)
try:
length = int(str(self.headers.get('content-length', '0')))
content = self.rfile.read(length)
params: typing.MutableMapping[str, str] = json.loads(content or '{}')
except Exception as e:
logger.error('Got exception executing POST {}: {}'.format(self.path, str(e)))
self.sendJsonResponse(error='Invalid request', code=400)
return
try:
result = getattr(self, 'method_' + path[1])(params) # last part of path is method
except AttributeError as e:
logger.error('Invoked invalid method: %s: %s', path[1], e)
self.sendJsonResponse(error='Invalid request', code=400)
return
except Exception as e:
logger.error('Got exception executing {}: {}'.format('/'.join(path), str(e)))
self.sendJsonResponse(error='Internal error', code=500)
return
self.sendJsonResponse(result)
# Internal methods
def method_ping(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return 'pong'
def method_logout(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return self._app.logout()
def method_message(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return self._app.message(params['message'])
def method_screenshot(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return self._app.screenshot()
def method_script(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return self._app.script(params['script'])
def do_GET(self) -> None:
self.sendJsonResponse(error='Forbidden', code=403)
def log_error(self, format: str, *args): # pylint: disable=redefined-builtin
logger.error(format, *args)
def log_message(self, format: str, *args): # pylint: disable=redefined-builtin
logger.debug(format, *args)
class HTTPServerThread(threading.Thread):
_server: typing.Optional[http.server.HTTPServer]
_app: 'UDSActorClient'
port: int
id: str
def __init__(self, app: 'UDSActorClient'):
super().__init__()
self._server = None
self._app = app
self.port = -1
self.id = secrets.token_urlsafe(24)
@property
def url(self) -> str:
return 'http://127.0.0.1:{}/{}'.format(self.port, self.id)
def stop(self) -> None:
if self._server:
logger.debug('Stopping Http-client Service')
try:
self._app.api.unregister(self.url)
except Exception as e:
logger.error('Error unregistering on actor service: %s', e)
self._server.shutdown()
self._server = None
def run(self):
HTTPServerHandler._app = self._app # pylint: disable=protected-access
HTTPServerHandler._id = self.id # pylint: disable=protected-access
self._server = http.server.HTTPServer(('127.0.0.1', 0), HTTPServerHandler)
self.port = self._server.socket.getsockname()[1]
# Register using app api
logger.debug('Registered %s', self.url)
try:
self._app.api.register(self.url)
except Exception as e:
logger.error('Error registering on actor service: %s', e)
self._server.serve_forever()

View File

@ -1,151 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
import json
import typing
import requests
from udsactor import tools, types
from udsactor.log import logger
# For avoid proxy on localhost connections
NO_PROXY = {
'http': None,
'https': None,
}
class UDSActorClientPool(metaclass=tools.Singleton):
_clients: typing.List[types.ClientInfo]
def __init__(self) -> None:
self._clients = []
def _post(
self,
session_id: typing.Optional[str],
method: str,
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:
result.append(
(
client,
requests.post(
clientUrl + '/' + method,
data=json.dumps(data),
verify=False,
timeout=timeout,
proxies=NO_PROXY, # type: ignore
),
)
)
except Exception as e:
logger.info(
'Could not connect with client %s: %s. ',
e,
clientUrl,
)
result.append((client, None))
return result
@property
def clients(self) -> typing.List[types.ClientInfo]:
return self._clients
def register(self, client_url: str) -> None:
# Remove first if exists, to avoid duplicates
self.unregister(client_url)
# And add it again
self._clients.append(types.ClientInfo(client_url, ''))
def set_session_id(self, client_url: str, session_id: typing.Optional[str]) -> None:
"""Set the session id for a client
Args:
clientUrl (str): _description_
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:
# remove client url from array if found
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:
self._post(session_id, 'script', {'script': script}, timeout=30)
def logout(self, session_id: typing.Optional[str]) -> None:
self._post(session_id, 'logout', {})
def message(self, session_id: typing.Optional[str], message: str) -> None:
self._post(session_id, 'message', {'message': message})
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:
return r.json()['result']
except Exception:
pass
return None

View File

@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# 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 typing
from udsactor.http import handler, clients_pool
if typing.TYPE_CHECKING:
from udsactor.service import CommonService
class LocalProvider(handler.Handler):
def post_login(self) -> typing.Any:
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()
def post_logout(self) -> typing.Any:
self._service.logout(self._params['username'], self._params['session_type'], self._params['session_id'])
return 'ok'
def post_ping(self) -> typing.Any:
return 'pong'
def post_register(self) -> typing.Any:
self._service._clientsPool.register(self._params['callback_url']) # pylint: disable=protected-access
return 'ok'
def post_unregister(self) -> typing.Any:
self._service._clientsPool.unregister(self._params['callback_url']) # pylint: disable=protected-access

View File

@ -1,100 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# 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 typing
from .. import tools
from . import handler
from ..log import logger
if typing.TYPE_CHECKING:
from ..service import CommonService
class PublicProvider(handler.Handler):
def post_logout(self) -> typing.Any:
logger.debug('Sending LOGOFF to clients')
self._service._clientsPool.logout() # pylint: disable=protected-access
return 'ok'
# Alias
post_logoff = post_logout
def post_message(self) -> typing.Any:
logger.debug('Sending MESSAGE to clients')
if 'message' not in self._params:
raise Exception('Invalid message parameters')
self._service._clientsPool.message(
self._params['message']
) # pylint: disable=protected-access
return 'ok'
def post_script(self) -> typing.Any:
logger.debug('Received script: {}'.format(self._params))
if 'script' not in self._params:
raise Exception('Invalid script parameters')
if self._params.get('user', False):
logger.debug('Sending SCRIPT to client')
self._service._clientsPool.executeScript(
self._params['script']
) # pylint: disable=protected-access
else:
# Execute script at server space, that is, here
# as a parallel thread
th = tools.ScriptExecutorThread(self._params['script'])
th.start()
return 'ok'
def post_preConnect(self) -> typing.Any:
logger.debug('Received Pre connection')
if 'user' not in self._params or 'protocol' not in self._params:
raise Exception('Invalid preConnect parameters')
return self._service.preConnect(
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:
# Return something useful? :)
return 'UDS Actor Secure Server'
def get_screenshot(self) -> typing.Any:
return (
self._service._clientsPool.screenshot()
) # pylint: disable=protected-access
def get_uuid(self) -> typing.Any:
if self._service.isManaged():
return self._service._cfg.own_token # pylint: disable=protected-access
return ''

View File

@ -1,166 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# 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 os
import threading
import http.server
import json
import ssl
import typing
from ..log import logger
from .. import certs
from .. import rest
from .public import PublicProvider
from .local import LocalProvider
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from ..service import CommonService
from .handler import Handler
class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.0'
server_version = 'UDS Actor Server'
sys_version = ''
_service: typing.Optional['CommonService'] = None
def sendJsonResponse(self, result: typing.Optional[typing.Any] = None, error: typing.Optional[str] = None, code: int = 200) -> None:
data = json.dumps({'result': result, 'error': error})
self.send_response(code)
self.send_header('Content-type', 'application/json')
self.send_header('Content-Length', str(len(data)))
self.send_header('Server: ', self.server_version)
self.end_headers()
self.wfile.write(data.encode())
def process(self, method: str, params: typing.MutableMapping[str, str]) -> None:
if not self._service:
self.sendJsonResponse(error='Not initialized', code=500)
return
# Very simple path & params splitter
path = self.path.split('?')[0][1:].split('/')
logger.debug('Path: %s, ip: %s, params: %s', path, self.client_address, params)
handlerType: typing.Optional[typing.Type['Handler']] = None
if len(path) == 3 and path[0] == 'actor' and path[1] == self._service._secret: # pylint: disable=protected-access
# public method
handlerType = PublicProvider
elif len(path) == 2 and path[0] == 'ui':
# private method, only from localhost
if self.client_address[0][:3] == '127':
handlerType = LocalProvider
if not handlerType:
self.sendJsonResponse(error='Forbidden', code=403)
return
try:
result = getattr(handlerType(self._service, method, params), method + '_' + path[-1])() # last part of path is method
except AttributeError:
self.sendJsonResponse(error='Method not found', code=404)
return
except Exception as e:
logger.error('Got exception executing {} {}: {}'.format(method, '/'.join(path), str(e)))
self.sendJsonResponse(error=str(e), code=500)
return
self.sendJsonResponse(result)
def do_GET(self) -> None:
try:
params = {v.split('=')[0]: v.split('=')[1] for v in self.path.split('?')[1].split('&')}
except Exception:
params = {}
self.process('get', params)
def do_POST(self) -> None:
try:
length = int(str(self.headers.get('content-length', '0')))
content = self.rfile.read(length)
params: typing.MutableMapping[str, str] = json.loads(content)
except Exception as e:
logger.error('Got exception executing POST {}: {}'.format(self.path, str(e)))
self.sendJsonResponse(error='Invalid parameters', code=400)
return
self.process('post', params)
def log_error(self, format, *args): # pylint: disable=redefined-builtin
logger.error(format, *args)
def log_message(self, format, *args): # pylint: disable=redefined-builtin
logger.debug(format, *args)
class HTTPServerThread(threading.Thread):
_server: typing.Optional[http.server.HTTPServer]
_service: 'CommonService'
_certFile: typing.Optional[str]
def __init__(self, service: 'CommonService'):
super().__init__()
self._server = None
self._service = service
self._certFile = None
def stop(self) -> None:
logger.debug('Stopping Http-server Service')
if self._server:
self._server.shutdown()
self._server = None
if self._certFile:
try:
os.unlink(self._certFile)
except Exception as e:
logger.error('Error removing certificate file: %s', e)
logger.debug('Http-server stopped')
def run(self):
HTTPServerHandler._service = self._service # pylint: disable=protected-access
self._certFile, password = certs.saveCertificate(self._service._certificate) # pylint: disable=protected-access
self._server = http.server.HTTPServer(('0.0.0.0', rest.LISTEN_PORT), HTTPServerHandler)
# self._server.socket = ssl.wrap_socket(self._server.socket, certfile=self.certFile, server_side=True)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# context.options = ssl.CERT_NONE
context.load_cert_chain(certfile=self._certFile, password=password)
self._server.socket = context.wrap_socket(self._server.socket, server_side=True)
self._server.serve_forever()

View File

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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
'''

View File

@ -1,173 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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 os
import time
import atexit
from signal import SIGTERM, SIGKILL
from udsactor.log import logger
class Daemon:
"""
A generic daemon class.
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile: str, stdin: str = '/dev/null', stdout: str = '/dev/null', stderr: str = '/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
def daemonize(self) -> None:
"""
do the UNIX double-fork magic, see Stevens' "Advanced
Programming in the UNIX Environment" for details (ISBN 0201563177)
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
"""
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError as e:
logger.error("fork #1 error: {}".format(e))
sys.stderr.write("fork #1 failed: {}\n".format(e))
sys.exit(1)
# decouple from parent environment
os.chdir("/")
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError as e:
logger.error("fork #2 error: {}".format(e))
sys.stderr.write("fork #2 failed: {}\n".format(e))
sys.exit(1)
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = open(self.stdin, 'r')
so = open(self.stdout, 'ab+')
se = open(self.stderr, 'ab+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile
atexit.register(self.removePidFile)
pidStr = str(os.getpid())
with open(self.pidfile, 'w+') as f:
f.write("{}\n".format(pidStr))
def removePidFile(self) -> None:
try:
os.remove(self.pidfile)
except Exception: # nosec: Not interested in exception
# Not found/not permissions or whatever, ignore it
pass
def start(self) -> None:
"""
Start the daemon
"""
logger.debug('Starting daemon')
# Check for a pidfile to see if the daemon already runs
if os.path.exists(self.pidfile):
message = "pidfile {} already exist. Daemon already running?\n".format(self.pidfile)
logger.error(message)
sys.stderr.write(message)
sys.exit(1)
# Start the daemon
self.daemonize()
try:
self.run()
except Exception as e:
logger.error('Exception running process: {}'.format(e))
self.removePidFile()
def stop(self) -> None:
"""
Stop the daemon
"""
# Get the pid from the pidfile
try:
pf = open(self.pidfile, 'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
message = "pidfile {} does not exist. Daemon not running?\n".format(self.pidfile)
logger.error(message)
# sys.stderr.write(message)
return # not an error in a restart
# Try killing the daemon process
try:
cnt = 10
while cnt:
cnt -= 1
os.kill(pid, SIGTERM)
time.sleep(1)
if not cnt:
os.kill(pid, SIGKILL)
except OSError as err:
if err.errno == 3: # No such process
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
sys.stderr.write('Error: {}'.format(err))
sys.exit(1)
def restart(self) -> None:
"""
Restart the daemon
"""
self.stop()
self.start()
# Overridables
def run(self) -> None:
"""
override this to provide your own daemon
"""

View File

@ -1,75 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-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
'''
# pylint: disable=invalid-name
import os
import tempfile
import logging
import typing
class LocalLogger: # pylint: disable=too-few-public-methods
linux = True
windows = False
serviceLogger = False
logger: typing.Optional[logging.Logger]
def __init__(self) -> None:
# tempdir is different for "user application" and "service"
# service wil get c:\windows\temp, while user will get c:\users\XXX\temp
# Try to open logger at /var/log path
# If it fails (access denied normally), will try to open one at user's home folder, and if
# agaim it fails, open it at the tmpPath
for logDir in ('/var/log', os.path.expanduser('~'), tempfile.gettempdir()):
try:
fname = os.path.join(logDir, 'udsactor.log')
logging.basicConfig(
filename=fname,
filemode='a',
format='%(levelname)s %(asctime)s %(message)s',
level=logging.DEBUG
)
self.logger = logging.getLogger('udsactor')
os.chmod(fname, 0o0600)
return
except Exception: # nosec: B110: we don't care about exceptions here
# Ignore and try next
pass
# Logger can't be set
self.logger = None
def log(self, level: int, message: str) -> None:
# Debug messages are logged to a file
# our loglevels are 0 (other), 10000 (debug), ....
# logging levels are 10 (debug), 20 (info)
# OTHER = logging.NOTSET
if self.logger:
self.logger.log(int(level / 1000), message)

View File

@ -1,236 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
import configparser
import platform
import socket
import fcntl # Only available on Linux. Expect complains if edited from windows
import os
import subprocess # nosec
import struct
import array
import typing
from .. import types
from .renamer import rename
from . import xss
def _getMacAddr(ifname: str) -> typing.Optional[str]:
'''
Returns the mac address of an interface
Mac is returned as unicode utf-8 encoded
'''
ifnameBytes = ifname.encode('utf-8')
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = bytearray(
fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifnameBytes[:15]))
)
return str(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
except Exception:
return None
def _getIpAddr(ifname: str) -> typing.Optional[str]:
'''
Returns the ip address of an interface
Ip is returned as unicode utf-8 encoded
'''
ifnameBytes = ifname.encode('utf-8')
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return str(
socket.inet_ntoa(
fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack(str('256s'), ifnameBytes[:15]),
)[20:24]
)
)
except Exception:
return None
def _getInterfaces() -> typing.List[str]:
'''
Returns a list of interfaces names coded in utf-8
'''
max_possible = 128 # arbitrary. raise if needed.
space = max_possible * 16
if platform.architecture()[0] == '32bit':
offset, length = 32, 32
elif platform.architecture()[0] == '64bit':
offset, length = 16, 40
else:
raise OSError('Unknown arquitecture {0}'.format(platform.architecture()[0]))
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array.array(str('B'), b'\0' * space)
outbytes = struct.unpack(
'iL',
fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
struct.pack('iL', space, names.buffer_info()[0]),
),
)[0]
namestr = names.tobytes()
# return namestr, outbytes
return [
namestr[i : i + offset].split(b'\0', 1)[0].decode('utf-8')
for i in range(0, outbytes, length)
]
def _getIpAndMac(
ifname: str,
) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]:
ip, mac = _getIpAddr(ifname), _getMacAddr(ifname)
return (ip, mac)
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]:
for ifname in _getInterfaces():
ip, mac = _getIpAndMac(ifname)
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=ifname, mac=mac, ip=ip)
def getDomainName() -> str:
return ''
def getLinuxOs() -> str:
try:
with open('/etc/os-release', 'r') as f:
data = f.read()
cfg = configparser.ConfigParser()
cfg.read_string('[os]\n' + data)
return cfg['os'].get('id', 'unknown').replace('"', '')
except Exception:
return 'unknown'
def getVersion() -> str:
return 'Linux ' + getLinuxOs()
def reboot(flags: int = 0):
'''
Simple reboot using os command
'''
subprocess.call(['/sbin/shutdown', 'now', '-r']) # nosec: Fine, all under control
def loggoff() -> None:
'''
Right now restarts the machine...
'''
subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']]) # nosec: Fine, all under control
# subprocess.call(['/sbin/shutdown', 'now', '-r'])
# subprocess.call(['/usr/bin/systemctl', 'reboot', '-i'])
def renameComputer(newName: str) -> bool:
'''
Changes the computer name
Returns True if reboot needed
'''
rename(newName)
return True # Always reboot right now. Not much slower but much more convenient
def joinDomain(
domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False
):
pass
def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
'''
Simple password change for user using command line
'''
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:
xss.initIdleDuration(atLeastSeconds)
def getIdleDuration() -> float:
return xss.getIdleDuration()
def getCurrentUser() -> str:
'''
Returns current logged in user
'''
return os.getlogin()
def getSessionType() -> str:
'''
Known values:
* Unknown -> No XDG_SESSION_TYPE environment variable
* xrdp --> xrdp session
* other types
'''
return (
'xrdp'
if 'XRDP_SESSION' in os.environ
else os.environ.get('XDG_SESSION_TYPE', 'unknown')
)
def forceTimeSync() -> None:
return

View File

@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-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: Alexey Shabalin, shaba at altlinux dot org
'''
import subprocess # nosec
from .common import renamers
from ...log import logger
def rename(newName: str) -> bool:
'''
ALT, ALTLinux, BaseALT Renamer
Expects new host name on newName
Host does not needs to be rebooted after renaming
'''
logger.debug('using ALT renamer')
with open('/etc/hostname', 'w') as hostname:
hostname.write(newName)
# Force system new name
subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: subprocess
subprocess.run(['/bin/hostname', newName]) # nosec: subprocess
# add name to "hosts"
with open('/etc/hosts', 'r') as hosts:
lines = hosts.readlines()
with open('/etc/hosts', 'w') as hosts:
hosts.write("127.0.1.1\t{}\n".format(newName))
for l in lines:
if l[:9] != '127.0.1.1': # Skips existing 127.0.1.1. if it already exists
hosts.write(l)
with open('/etc/sysconfig/network', 'r') as net:
lines = net.readlines()
with open('/etc/sysconfig/network', 'w') as net:
net.write('HOSTNAME={}\n'.format(newName))
for l in lines:
if l[:8] != 'HOSTNAME':
net.write(l)
return True
# All names in lower case
renamers['altlinux'] = rename
renamers['alt'] = rename
renamers['basealt'] = rename

View File

@ -1,65 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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 subprocess # nosec
from .common import renamers
from ...log import logger
def rename(newName: str) -> bool:
'''
Debian renamer
Expects new host name on newName
Host does not needs to be rebooted after renaming
'''
with open('/etc/hostname', 'w') as hostname:
hostname.write(newName)
# Force system new name
subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root
subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root
# add name to "hosts"
with open('/etc/hosts', 'r') as hosts:
lines = hosts.readlines()
with open('/etc/hosts', 'w') as hosts:
hosts.write("127.0.1.1\t%s\n" % newName)
for l in lines:
if l[:9] == '127.0.1.1': # Skips existing 127.0.1.1. if it already exists
continue
hosts.write(l)
return True
# All names in lower case
renamers['debian'] = rename
renamers['ubuntu'] = rename

View File

@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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 subprocess # nosec
from .common import renamers
from ...log import logger
def rename(newName: str) -> bool:
'''
RH, Centos, Fedora Renamer
Expects new host name on newName
Host does not needs to be rebooted after renaming
'''
logger.debug('using RH renamer')
with open('/etc/hostname', 'w') as hostname:
hostname.write(newName)
# Force system new name
subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root
subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root
# add name to "hosts"
with open('/etc/hosts', 'r') as hosts:
lines = hosts.readlines()
with open('/etc/hosts', 'w') as hosts:
hosts.write("127.0.1.1\t{}\n".format(newName))
for l in lines:
if l[:9] != '127.0.1.1': # Skips existing 127.0.1.1. if it already exists
hosts.write(l)
with open('/etc/sysconfig/network', 'r') as net:
lines = net.readlines()
with open('/etc/sysconfig/network', 'w') as net:
net.write('HOSTNAME={}\n'.format(newName))
for l in lines:
if l[:8] != 'HOSTNAME':
net.write(l)
return True
# All names in lower case
renamers['centos linux'] = rename
renamers['centos'] = rename
renamers['fedora'] = rename

View File

@ -1,76 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2020 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
from .. import rest
from .. import platform
from ..log import logger
from .service import UDSActorSvc
def usage():
sys.stderr.write('usage: udsactor start|stop|restart|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] == 'start':
daemonSvr.start()
elif sys.argv[1] == 'stop':
daemonSvr.stop()
elif sys.argv[1] == 'restart':
daemonSvr.restart()
elif sys.argv[1] == 'start-foreground':
daemonSvr.run() # Execute in foreground
else:
usage()
sys.exit(0)
else:
usage()

View File

@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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 signal
from . import daemon
from ..log import logger
from ..service import CommonService
try:
from prctl import set_proctitle # type: ignore
except ImportError: # Platform may not include prctl, so in case it's not available, we let the "name" as is
def set_proctitle(_):
pass
class UDSActorSvc(daemon.Daemon, CommonService):
def __init__(self) -> None:
daemon.Daemon.__init__(self, '/run/udsactor.pid')
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:
logger.info('Join domain is not supported on linux platforms right now. Just renaming.')
self.rename(name)
def run(self) -> None:
logger.debug('Running Daemon: {}'.format(self._isAlive))
set_proctitle('UDSActorDaemon')
# 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():
if not self.initialize():
self.finish()
return # Stop daemon if initializes told to do so
# logger.debug('Initialized, setting ready')
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady()
else:
if not self.initializeUnmanaged():
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()

View File

@ -1,127 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
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( # nosec: file is restricted
base64.b64decode(base64Config.encode())
)
if base64Config
else None
)
base64Data = uds.get('data', None)
data = (
pickle.loads( # nosec: file is restricted
base64.b64decode(base64Data.encode())
)
if base64Data
else None
)
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 ''

View File

@ -1,143 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
import ctypes
import ctypes.util
import subprocess # nosec
xlib = None
xss = None
display = None
xssInfo = None
initialized = False
class XScreenSaverInfo(ctypes.Structure): # pylint: disable=too-few-public-methods
_fields_ = [
('window', ctypes.c_long),
('state', ctypes.c_int),
('kind', ctypes.c_int),
('til_or_since', ctypes.c_ulong),
('idle', ctypes.c_ulong),
('eventMask', ctypes.c_ulong),
]
class c_ptr(ctypes.c_void_p):
pass
def _ensureInitialized():
global xlib, xss, xssInfo, display, initialized # pylint: disable=global-statement
if initialized:
return
initialized = True
try:
xlibPath = ctypes.util.find_library('X11')
xssPath = ctypes.util.find_library('Xss')
xlib = xss = None
if not xlibPath or not xssPath:
raise Exception('Library Not found!!')
xlib = ctypes.cdll.LoadLibrary(xlibPath)
xss = ctypes.cdll.LoadLibrary(xssPath)
# Fix result type to XScreenSaverInfo Structure
xss.XScreenSaverQueryExtension.restype = ctypes.c_int
xss.XScreenSaverQueryExtension.argtypes = [
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_int),
ctypes.POINTER(ctypes.c_int),
]
xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(
XScreenSaverInfo
) # Result in a XScreenSaverInfo structure
xss.XScreenSaverQueryInfo.argtypes = [
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.POINTER(XScreenSaverInfo),
]
xlib.XOpenDisplay.argtypes = [ctypes.c_char_p]
xlib.XOpenDisplay.restype = c_ptr
display = xlib.XOpenDisplay(None)
if not display.value:
raise Exception('Display not found!') # Invalid display, not accesible
xssInfo = xss.XScreenSaverAllocInfo()
# Ensures screen saver extension is available
event_base = ctypes.c_int()
error_base = ctypes.c_int()
available = xss.XScreenSaverQueryExtension(
display, ctypes.byref(event_base), ctypes.byref(error_base)
)
if available != 1:
raise Exception('ScreenSaver not available')
except Exception: # Libraries not accesible, not found or whatever..
xlib = xss = display = xssInfo = None
def initIdleDuration(atLeastSeconds: int) -> None:
_ensureInitialized()
if atLeastSeconds:
subprocess.call( # nosec, controlled params
['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)]
)
# And now reset it
subprocess.call(['/usr/bin/xset', 's', 'reset']) # nosec: fixed command
def getIdleDuration() -> float:
'''
Returns idle duration, in seconds
'''
if not initialized or not xlib or not xss or not xssInfo:
return 0 # Libraries not available
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo)
# States: 0 = off, 1 = On, 2 = Cycle, 3 = Disabled, ...?
if (
xssInfo.contents.state == 1
): # 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

View File

@ -1,119 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
import traceback
import sys
import typing
if sys.platform == 'win32':
from .windows.log import LocalLogger
elif sys.platform == 'darwin':
from .macos.log import LocalLogger
else:
from .linux.log import LocalLogger
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from . import rest
# Valid logging levels, from UDS Broker (uds.core.utils.log)
from .loglevel import OTHER, DEBUG, INFO, WARN, ERROR, FATAL
class Logger:
remoteLogger: typing.Optional['rest.UDSServerApi']
own_token: str
logLevel: int
localLogger: LocalLogger
def __init__(self) -> None:
self.logLevel = INFO
self.localLogger = LocalLogger()
self.remoteLogger = None
self.own_token = '' # nosec: This is no password at all
def setLevel(self, level: typing.Union[str, int]) -> None:
'''
Sets log level filter (minimum level required for a log message to be processed)
:param level: Any message with a level below this will be filtered out
'''
self.logLevel = int(level) # Ensures level is an integer or fails
def setRemoteLogger(self, remoteLogger: 'rest.UDSServerApi', own_token: str) -> None:
self.remoteLogger = remoteLogger
self.own_token = own_token
def enableServiceLogger(self):
if self.localLogger.windows:
self.localLogger.serviceLogger = True
def log(self, level: typing.Union[str, int], message: str, *args) -> None:
level = int(level)
if level < self.logLevel: # Skip not wanted messages
return
msg = message % args
# If remote logger is available, notify message to it (except DEBUG messages OFC)
try:
if self.remoteLogger and level >= DEBUG:
self.remoteLogger.log(self.own_token, level, msg)
except Exception as e:
self.localLogger.log(DEBUG, 'Log to broker: {}'.format(e))
self.localLogger.log(level, msg)
def debug(self, message: str, *args) -> None:
self.log(DEBUG, message, *args)
def warn(self, message: str, *args) -> None:
self.log(WARN, message, *args)
def info(self, message: str, *args) -> None:
self.log(INFO, message, *args)
def error(self, message: str, *args) -> None:
self.log(ERROR, message, *args)
def fatal(self, message: str, *args) -> None:
self.log(FATAL, message, *args)
def exception(self) -> None:
try:
tb = traceback.format_exc()
except Exception:
tb = '(could not get traceback!)'
self.log(DEBUG, tb)
def flush(self) -> None:
pass
logger = Logger()

View File

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020 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
'''
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * x for x in range(6))

View File

@ -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
'''

View File

@ -1,74 +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
'''
# pylint: disable=invalid-name
import os
import tempfile
import logging
import typing
# Basically, same logger as in linux,
class LocalLogger:
linux = False
windows = False
serviceLogger = False
logger: typing.Optional[logging.Logger]
def __init__(self) -> None:
# Try to open logger at /var/log path
# If it fails (access denied normally), will try to open one at user's home folder, and if
# agaim it fails, open it at the tmpPath
for logDir in ('/var/log', os.path.expanduser('~'), tempfile.gettempdir()):
try:
fname = os.path.join(logDir, 'udsactor.log')
logging.basicConfig(
filename=fname,
filemode='a',
format='%(levelname)s %(asctime)s %(message)s',
level=logging.DEBUG
)
self.logger = logging.getLogger('udsactor')
os.chmod(fname, 0o0600)
return
except Exception: # nosec: B110: we don't care about exceptions here
# ignore and try next one
pass
# Logger can't be set
self.logger = None
def log(self, level: int, message: str) -> None:
# Debug messages are logged to a file
# our loglevels are 0 (other), 10000 (debug), ....
# logging levels are 10 (debug), 20 (info)
# OTHER = logging.NOTSET
if self.logger:
self.logger.log(int(level / 1000), message)

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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 ''

View File

@ -1,45 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-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 sys
name = sys.platform
is_windows = is_linux = is_mac = False
if sys.platform == 'win32':
from .windows import operations, store, runner
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:
raise Exception('Unsupported platform: {0}'.format(sys.platform))

View File

@ -1,449 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.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
'''
# pylint: disable=invalid-name
import warnings
import json
import logging
import typing
import requests
from udsactor import types, tools
from udsactor.version import VERSION, BUILD
# Default public listen port
LISTEN_PORT = 43910
# Default timeout
TIMEOUT = 5 # 5 seconds is more than enought
# Constants
UNKNOWN = 'unknown'
class RESTError(Exception):
ERRCODE = 0
class RESTConnectionError(RESTError):
ERRCODE = -1
# Errors ""raised"" from broker
class RESTInvalidKeyError(RESTError):
ERRCODE = 1
class RESTUnmanagedHostError(RESTError):
ERRCODE = 2
class RESTUserServiceNotFoundError(RESTError):
ERRCODE = 3
class RESTOsManagerError(RESTError):
ERRCODE = 4
# For avoid proxy on localhost connections
NO_PROXY = {
'http': None,
'https': None,
}
UDS_BASE_URL = 'https://{}/uds/rest/'
#
# Basic UDS Api
#
class UDSApi: # pylint: disable=too-few-public-methods
"""
Base for remote api accesses
"""
_host: str = ''
_validateCert: bool = True
_url: str = ''
def __init__(self, host: str, validateCert: bool) -> None:
self._host = host
self._validateCert = validateCert
self._url = UDS_BASE_URL.format(self._host)
# Disable logging requests messages except for errors, ...
logging.getLogger('request').setLevel(logging.CRITICAL)
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
warnings.simplefilter('ignore') # Disables all warnings
except Exception: # nosec: not interested in exceptions
pass
@property
def _headers(self) -> typing.MutableMapping[str, str]:
return {
'Content-Type': 'application/json',
'User-Agent': 'UDS Actor v{}/{}'.format(VERSION, BUILD),
}
def _api_url(self, method: str) -> str:
raise NotImplementedError
def _doPost(
self,
method: str, # i.e. 'initialize', 'ready', ....
payLoad: typing.MutableMapping[str, typing.Any],
headers: typing.Optional[typing.MutableMapping[str, str]] = None,
disableProxy: bool = False,
) -> typing.Any:
headers = headers or self._headers
try:
result = requests.post(
self._api_url(method),
data=json.dumps(payLoad),
headers=headers,
verify=self._validateCert,
timeout=TIMEOUT,
proxies=NO_PROXY # type: ignore
if disableProxy
else None, # if not proxies wanted, enforce it
)
if result.ok:
j = result.json()
if not j.get('error', None):
return j['result']
except requests.ConnectionError as e:
raise RESTConnectionError(str(e))
except Exception as e:
raise RESTError(str(e))
try:
data = result.json()
except Exception:
data = result.content.decode()
raise RESTError(data)
#
# UDS Broker API access
#
class UDSServerApi(UDSApi):
def _api_url(self, method: str) -> str:
return self._url + 'actor/v3/' + method
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
try:
result = requests.get(
self._url + 'auth/auths',
headers=self._headers,
verify=self._validateCert,
timeout=4,
)
if result.ok:
for v in sorted(result.json(), key=lambda x: x['priority']):
yield types.AuthenticatorType(
authId=v['authId'],
authSmallName=v['authSmallName'],
auth=v['auth'],
type=v['type'],
priority=v['priority'],
isCustom=v['isCustom'],
)
except Exception: # nosec: not interested in exceptions
pass
def register(
self,
auth: str,
username: str,
password: str,
hostname: str,
ip: str,
mac: str,
preCommand: str,
runOnceCommand: str,
postCommand: str,
logLevel: int,
) -> str:
"""
Raises an exception if could not register, or registers and returns the "authorization token"
"""
data = {
'username': username + '@' + auth,
'hostname': hostname,
'ip': ip,
'mac': mac,
'pre_command': preCommand,
'run_once_command': runOnceCommand,
'post_command': postCommand,
'log_level': logLevel,
}
# First, try to login to REST api
try:
# First, try to login
authInfo = {'auth': auth, 'username': username, 'password': password}
headers = self._headers
result = requests.post(
self._url + 'auth/login',
data=json.dumps(authInfo),
headers=headers,
verify=self._validateCert,
)
if not result.ok or result.json()['result'] == 'error':
raise Exception() # Invalid credentials
headers['X-Auth-Token'] = result.json()['token']
result = requests.post(
self._api_url('register'),
data=json.dumps(data),
headers=headers,
verify=self._validateCert,
)
if result.ok:
return result.json()['result']
except requests.ConnectionError as e:
raise RESTConnectionError(e)
except RESTError:
raise
except Exception as e:
raise RESTError('Invalid credentials')
raise RESTError(result.content.decode())
def initialize(
self,
token: str,
interfaces: typing.Iterable[types.InterfaceInfoType],
actor_type: typing.Optional[str],
) -> types.InitializationResultType:
# Generate id list from netork cards
payload = {
'type': actor_type or types.MANAGED,
'token': token,
'version': VERSION,
'build': BUILD,
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
}
r = self._doPost('initialize', payload)
os = r['os']
return types.InitializationResultType(
own_token=r['own_token'],
unique_id=r['unique_id'].lower() if r['unique_id'] else None,
os=types.ActorOsConfigurationType(
action=os['action'],
name=os['name'],
username=os.get('username'),
password=os.get('password'),
new_password=os.get('new_password'),
ad=os.get('ad'),
ou=os.get('ou'),
)
if r['os']
else None,
alias_token=r.get('alias_token'), # Possible alias for unmanaged
)
def ready(
self, own_token: str, secret: str, ip: str, port: int
) -> types.CertificateInfoType:
payload = {'token': own_token, 'secret': secret, 'ip': ip, 'port': port}
result = self._doPost('ready', payload)
return types.CertificateInfoType(
private_key=result['private_key'],
server_certificate=result['server_certificate'],
password=result['password'],
)
def notifyIpChange(
self, own_token: str, secret: str, ip: str, port: int
) -> types.CertificateInfoType:
payload = {'token': own_token, 'secret': secret, 'ip': ip, 'port': port}
result = self._doPost('ipchange', payload)
return types.CertificateInfoType(
private_key=result['private_key'],
server_certificate=result['server_certificate'],
password=result['password'],
)
def notifyUnmanagedCallback(
self,
master_token: str,
secret: str,
interfaces: typing.Iterable[types.InterfaceInfoType],
port: int,
) -> types.CertificateInfoType:
payload = {
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
'token': master_token,
'secret': secret,
'port': port,
}
result = self._doPost('unmanaged', payload)
return types.CertificateInfoType(
private_key=result['private_key'],
server_certificate=result['server_certificate'],
password=result['password'],
)
def login(
self,
actor_type: typing.Optional[str],
token: str,
username: str,
session_type: str,
interfaces: typing.Iterable[types.InterfaceInfoType],
secret: typing.Optional[str],
) -> types.LoginResultInfoType:
if not token:
return types.LoginResultInfoType(
ip='0.0.0.0', hostname=UNKNOWN, dead_line=None, max_idle=None, session_id=None
)
payload = {
'type': actor_type or types.MANAGED,
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
'token': token,
'username': username,
'session_type': session_type,
'secret': secret or '',
}
result = self._doPost('login', payload)
return types.LoginResultInfoType(
ip=result['ip'],
hostname=result['hostname'],
dead_line=result['dead_line'],
max_idle=result['max_idle'],
session_id=result.get('session_id', ''),
)
def logout(
self,
actor_type: typing.Optional[str],
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 = {
'type': actor_type or types.MANAGED,
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
'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'
def log(self, own_token: str, level: int, message: str) -> None:
if not own_token:
return
payLoad = {'token': own_token, 'level': level, 'message': message}
self._doPost('log', payLoad) # Ignores result...
def test(self, master_token: str, actorType: typing.Optional[str]) -> bool:
payLoad = {
'type': actorType or types.MANAGED,
'token': master_token,
}
return self._doPost('test', payLoad) == 'ok'
class UDSClientApi(UDSApi, metaclass=tools.Singleton):
_session_id: str = ''
_callback_url: str = ''
def __init__(self) -> None:
super().__init__('127.0.0.1:{}'.format(LISTEN_PORT), False)
# Replace base url
self._url = "https://{}/ui/".format(self._host)
def _api_url(self, method: str) -> str:
return self._url + method
def post(
self,
method: str, # i.e. 'initialize', 'ready', ....
payLoad: typing.MutableMapping[str, typing.Any],
) -> typing.Any:
return self._doPost(method=method, payLoad=payLoad, disableProxy=True)
def register(self, callback_url: str) -> None:
self._callback_url = callback_url
payLoad = {'callback_url': callback_url}
self.post('register', payLoad)
def unregister(self, callback_url: str) -> None:
payLoad = {'callback_url': callback_url}
self.post('unregister', payLoad)
self._callback_url = ''
def login(
self, username: str, sessionType: typing.Optional[str] = None
) -> types.LoginResultInfoType:
payLoad = {
'username': username,
'session_type': sessionType or UNKNOWN,
'callback_url': self._callback_url, # So we identify ourselves
}
result = self.post('login', payLoad)
res = types.LoginResultInfoType(
ip=result['ip'],
hostname=result['hostname'],
dead_line=result['dead_line'],
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:
payLoad = {
'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)
def ping(self) -> bool:
return self.post('ping', {}) == 'pong'

View File

@ -1,615 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
import socket
import time
import secrets
import subprocess
import typing
from udsactor import platform
from udsactor import rest
from udsactor import types
from udsactor import tools
from udsactor.log import logger, DEBUG, INFO, ERROR, FATAL
from udsactor.http import clients_pool, server, cert
# def setup() -> None:
# cfg = platform.store.readConfig()
# if logger.logger.windows:
# # Logs will also go to windows event log for services
# logger.logger.serviceLogger = True
# if cfg.x:
# logger.setLevel(cfg.get('logLevel', 20000))
# else:
# logger.setLevel(20000)
class CommonService: # pylint: disable=too-many-instance-attributes
_isAlive: bool = True
_rebootRequested: bool = False
_initialized: bool = False
_cfg: types.ActorConfigurationType
_api: rest.UDSServerApi
_interfaces: typing.List[types.InterfaceInfoType]
_secret: str
_certificate: types.CertificateInfoType
_http: typing.Optional[server.HTTPServerThread]
@staticmethod
def execute(cmdLine: str, section: str) -> bool:
try:
logger.debug('Executing command on {}: {}'.format(section, cmdLine))
res = subprocess.check_call(cmdLine, shell=True)
except Exception as e:
logger.error(
'Got exception executing: {} - {} - {}'.format(section, cmdLine, e)
)
return False
logger.debug('Result of executing cmd for {} was {}'.format(section, res))
return True
def __init__(self) -> None:
self._cfg = platform.store.readConfig()
self._interfaces = []
self._api = rest.UDSServerApi(self._cfg.host, self._cfg.validateCertificate)
self._secret = secrets.token_urlsafe(33)
self._clientsPool = clients_pool.UDSActorClientPool()
self._certificate = (
cert.defaultCertificate
) # For being used on "unmanaged" hosts only
self._http = None
# Initialzies loglevel and serviceLogger
# 0 = DEBUG, 1 = INFO, 2 = ERROR, 3 = FATAL in combobox
# BUT!!!:
# 0 = OTHER, 10000 = DEBUG, 20000 = WARN, 30000 = INFO, 40000 = ERROR, 50000 = FATAL
# So this comes:
logger.setLevel([DEBUG, INFO, ERROR, FATAL][self._cfg.log_level])
# If windows, enable service logger FOR SERVICE only
logger.enableServiceLogger()
socket.setdefaulttimeout(20)
def startHttpServer(self):
# Starts the http thread
if self._http:
try:
self._http.stop()
except Exception:
pass
self._http = server.HTTPServerThread(self)
self._http.start()
def isManaged(self) -> bool:
return (
self._cfg.actorType != types.UNMANAGED
) # Only "unmanaged" hosts are unmanaged, the rest are "managed"
def serviceInterfaceInfo(
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...
"""
interfaces = (
interfaces or self._interfaces
) # Emty interfaces is like "no ip change" because cannot be notified
if self._cfg.config and interfaces:
try:
return next(
x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id
)
except StopIteration:
return interfaces[0]
return None
def reboot(self) -> None:
# Reboot just after renaming
logger.info('Rebooting...')
self._rebootRequested = True
def setReady(self) -> None:
if not self._isAlive or not self.isManaged():
return
# Unamanged actor types does not set ready never (has no osmanagers, no needing for this)
# First, if postconfig is available, execute it and disable it
if self._cfg.post_command:
self.execute(self._cfg.post_command, 'postConfig')
self._cfg = self._cfg._replace(post_command=None)
platform.store.writeConfig(self._cfg)
if self._cfg.own_token and self._interfaces:
srvInterface = self.serviceInterfaceInfo()
if srvInterface:
# Rery while RESTConnectionError (that is, cannot connect)
counter = 60
logged = False
while self._isAlive:
counter -= 1
try:
self._certificate = self._api.ready(
self._cfg.own_token,
self._secret,
srvInterface.ip,
rest.LISTEN_PORT,
)
except rest.RESTConnectionError as e:
if not logged: # Only log connection problems ONCE
logged = True
logger.error('Error connecting with UDS Broker')
self.doWait(5000)
continue
except Exception as e:
logger.error('Unhandled exception while setting ready: %s', e)
if counter > 0:
self.doWait(10000) # A long wait on other error...
continue
platform.operations.reboot() # On too many errors, simply reboot
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
break
else:
logger.error(
'Could not locate IP address!!!. (Not registered with UDS)'
)
# Do not continue if not alive...
if not self._isAlive:
return
# Cleans sensible data
if self._cfg.config:
self._cfg = self._cfg._replace(
config=self._cfg.config._replace(os=None), data=None
)
platform.store.writeConfig(self._cfg)
logger.info('Service ready')
def configureMachine(self) -> bool:
if not self._isAlive:
return False
if not self.isManaged():
return True
# First, if runonce is present, honor it and remove it from config
# Return values is "True" for keep service (or daemon) running, False if Stop it.
if self._cfg.runonce_command:
runOnce = self._cfg.runonce_command
self._cfg = self._cfg._replace(runonce_command=None)
platform.store.writeConfig(self._cfg)
if self.execute(runOnce, "runOnce"):
# If runonce is present, will not do anythin more
# So we have to ensure that, when runonce command is finished, reboots 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...
# Retry configuration while not stop service, config in case of error 10 times, reboot vm
counter = 10
while self._isAlive:
counter -= 1
try:
if self._cfg.config and self._cfg.config.os:
osData = self._cfg.config.os
if osData.action == 'rename':
self.rename(
osData.name,
osData.username,
osData.password,
osData.new_password,
)
elif osData.action == 'rename_ad':
self.joinDomain(
osData.name,
osData.ad or '',
osData.ou or '',
osData.username or '',
osData.password or '',
)
if self._rebootRequested:
try:
platform.operations.reboot()
except Exception as e:
logger.error('Exception on reboot: {}'.format(e))
return False # Stops service if reboot was requested ofc
break
except Exception as e:
logger.error('Got exception operating machine: {}'.format(e))
if counter > 0:
self.doWait(5000)
else:
platform.operations.reboot()
return False
return True
def initializeUnmanaged(self) -> bool:
# Notify UDS about my callback
self.getInterfaces() # Ensure we have interfaces
if self._cfg.master_token:
try:
self._certificate = self._api.notifyUnmanagedCallback(
self._cfg.master_token,
self._secret,
self._interfaces,
rest.LISTEN_PORT,
)
except Exception as e:
logger.error('Couuld not notify unmanaged callback: %s', e)
return True
def getInterfaces(self) -> None:
if self._interfaces:
return
while self._isAlive:
self._interfaces = tools.validNetworkCards(
self._cfg.restrict_net, platform.operations.getNetworkInfo()
)
if self._interfaces:
break
self.doWait(5000)
def initialize(self) -> bool:
if (
self._initialized or not self._cfg.host or not self._isAlive
): # Not configured or not running
return False
self._initialized = True
# Force time sync, just in case...
if self.isManaged():
platform.operations.forceTimeSync()
# Wait for Broker to be ready
# Ensure we have intefaces...
self.getInterfaces()
while self._isAlive:
try:
# If master token is present, initialize and get configuration data
if self._cfg.master_token:
initResult: types.InitializationResultType = self._api.initialize(
self._cfg.master_token, self._interfaces, self._cfg.actorType
)
if not initResult.own_token: # Not managed
logger.debug(
'This host is not managed by UDS Broker (ids: {})'.format(
self._interfaces
)
)
return False
# Only removes master token for managed machines (will need it on next client execution)
# For unmanaged, if alias is present, replace master token with it
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(
master_token=master_token,
own_token=initResult.own_token,
config=types.ActorDataConfigurationType(
unique_id=initResult.unique_id, os=initResult.os
),
)
# On first successfull initialization request, master token will dissapear for managed hosts
# so it will be no more available (not needed anyway). For unmanaged, the master token will
# be replaced with an alias token.
platform.store.writeConfig(self._cfg)
# Setup logger now
if self._cfg.own_token:
logger.setRemoteLogger(self._api, self._cfg.own_token)
break # Initial configuration done..
except rest.RESTConnectionError as e:
logger.info(
'Trying to inititialize connection with broker (last error: {})'.format(
e
)
)
self.doWait(5000) # Wait a bit and retry
except rest.RESTError as e: # Invalid key?
logger.error(
'Error validating with broker. (Invalid token?): {}'.format(e)
)
return False
except Exception:
logger.exception()
self.doWait(5000) # Wait a bit and retry...
return self.configureMachine()
def uninitialize(self):
self._initialized = False
self._cfg = self._cfg._replace(
own_token=None
) # Ensures assigned token is cleared
def finish(self) -> None:
if self._http:
self._http.stop()
# If logged in, notify UDS of logout (daemon stoped = no control = logout)
# For every connected client...
if self._cfg.own_token:
for client in clients_pool.UDSActorClientPool().clients:
if client.session_id:
try:
self._api.logout(
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()
def checkIpsChanged(self) -> None:
if not self.isManaged():
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:
if (
not self._cfg.own_token
or not self._cfg.config
or not self._cfg.config.unique_id
):
# Not enouth data do check
return
currentInterfaces = tools.validNetworkCards(
self._cfg.restrict_net, platform.operations.getNetworkInfo()
)
old = self.serviceInterfaceInfo()
new = self.serviceInterfaceInfo(currentInterfaces)
if not new or not old:
raise Exception(
'No ip currently available for {}'.format(
self._cfg.config.unique_id
)
)
if old.ip != new.ip:
self._certificate = self._api.notifyIpChange(
self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT
)
# Now store new addresses & interfaces...
self._interfaces = currentInterfaces
logger.info(
'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
self.startHttpServer()
except Exception as e:
# No ip changed, log exception for info
logger.warn('Checking ips failed: {}'.format(e))
def rename(
self,
name: str,
userName: typing.Optional[str] = None,
oldPassword: typing.Optional[str] = None,
newPassword: typing.Optional[str] = None,
) -> None:
'''
Invoked when broker requests a rename action
default does nothing
'''
hostName = platform.operations.getComputerName()
# Check for password change request for an user
if userName and newPassword:
logger.info('Setting password for configured user')
try:
platform.operations.changeUserPassword(
userName, oldPassword or '', newPassword
)
except Exception as e:
# Logs error, but continue renaming computer
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):
self.reboot()
def loop(self):
# Main common loop
try:
# Checks if ips has changed
self.checkIpsChanged()
# Now check if every registered client is already there (if logged in OFC)
for lost_client in clients_pool.UDSActorClientPool().lost_clients():
logger.info('Lost client: {}'.format(lost_client))
self.logout('client_unavailable', '', lost_client.session_id or '') # '' means "all clients"
except Exception as e:
logger.error('Exception on main service loop: %s', e)
# ******************************************************
# Methods that can be overriden by linux & windows Actor
# ******************************************************
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self, name: str, domain: str, ou: str, account: str, password: str
) -> None:
'''
Invoked when broker requests a "domain" action
default does nothing
'''
logger.debug('Base join invoked: {} on {}, {}'.format(name, domain, ou))
# Client notifications
def login(
self, username: str, sessionType: typing.Optional[str] = None
) -> types.LoginResultInfoType:
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():
self._initialized = (
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
# In that case, take master token (if machine is Unamanaged version)
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
def logout(
self,
username: str,
session_type: typing.Optional[str],
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
# 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():
self.uninitialize()
# ******************************************************
# Methods that CAN BE overriden by specific OS Actor
# ******************************************************
def doWait(self, miliseconds: int) -> None:
'''
Invoked to wait a bit
CAN be OVERRIDEN
'''
seconds = miliseconds / 1000.0
# So it can be broken by "stop"
while self._isAlive and seconds > 1:
time.sleep(1)
seconds -= 1
time.sleep(seconds)
def notifyStop(self) -> None:
'''
Overriden to log stop (on windows, notify to service manager)
'''
logger.info('Service stopped')
def preConnect(
self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str
) -> str:
'''
Invoked when received a PRE Connection request via REST
Base preconnect executes the preconnect command
'''
if self._cfg.pre_command:
self.execute(
self._cfg.pre_command
+ ' {} {} {} {} {}'.format(
userName.replace('"', '%22'),
protocol,
ip,
hostname,
udsUserName.replace('"', '%22'),
),
'preConnect',
)
return 'ok'
def onLogout(self, userName: str, session_id: str) -> None:
logger.debug('On logout invoked for {}'.format(userName))

View File

@ -1,140 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# 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 threading
import ipaddress
import time
import typing
import functools
if typing.TYPE_CHECKING:
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):
def __init__(self, script: str) -> None:
super(ScriptExecutorThread, self).__init__()
self.script = script
def run(self) -> None:
from udsactor.log import logger
try:
logger.debug('Executing script: {}'.format(self.script))
exec(
self.script, globals(), None
) # nosec: exec is fine, it's a "trusted" script
except Exception as e:
logger.error('Error executing script: {}'.format(e))
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)]

View File

@ -1,74 +0,0 @@
import typing
MANAGED = 'managed'
UNMANAGED = 'unmanaged'
class InterfaceInfoType(typing.NamedTuple):
name: str
mac: str
ip: str
class AuthenticatorType(typing.NamedTuple):
authId: str
authSmallName: str
auth: str
type: str
priority: int
isCustom: bool
class ActorOsConfigurationType(typing.NamedTuple):
action: str
name: str
username: typing.Optional[str] = None
password: typing.Optional[str] = None
new_password: typing.Optional[str] = None
ad: typing.Optional[str] = None
ou: typing.Optional[str] = None
class ActorDataConfigurationType(typing.NamedTuple):
unique_id: typing.Optional[str] = None
os: typing.Optional[ActorOsConfigurationType] = None
class ActorConfigurationType(typing.NamedTuple):
host: str
validateCertificate: bool
actorType: typing.Optional[str] = None
master_token: typing.Optional[str] = None
own_token: typing.Optional[str] = None
restrict_net: typing.Optional[str] = None
pre_command: typing.Optional[str] = None
runonce_command: typing.Optional[str] = None
post_command: typing.Optional[str] = None
log_level: int = 2
config: typing.Optional[ActorDataConfigurationType] = None
data: typing.Optional[typing.Dict[str, typing.Any]] = None
class InitializationResultType(typing.NamedTuple):
own_token: typing.Optional[str] = None
unique_id: typing.Optional[str] = None
os: typing.Optional[ActorOsConfigurationType] = None
alias_token: typing.Optional[str] = None
class LoginResultInfoType(typing.NamedTuple):
ip: str
hostname: str
dead_line: typing.Optional[int]
max_idle: typing.Optional[int]
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):
private_key: str
server_certificate: str
password: str

View File

@ -1,2 +0,0 @@
VERSION = '4.0.0'
BUILD = '20220901'

View File

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-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
'''

View File

@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-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
'''
# pylint: disable=invalid-name
import logging
import os
import tempfile
import typing
import servicemanager
# Valid logging levels, from UDS Broker (uds.core.utils.log).
from .. import loglevel
class LocalLogger: # pylint: disable=too-few-public-methods
linux = False
windows = True
serviceLogger = False
logger: typing.Optional[logging.Logger]
def __init__(self):
# tempdir is different for "user application" and "service"
# service wil get c:\windows\temp, while user will get c:\users\XXX\temp
try:
logging.basicConfig(
filename=os.path.join(tempfile.gettempdir(), 'udsactor.log'),
filemode='a',
format='%(levelname)s %(asctime)s %(message)s',
level=logging.DEBUG
)
except Exception:
logging.basicConfig() # basic init
self.logger = logging.getLogger('udsactor')
self.serviceLogger = False
def log(self, level: int, message: str) -> None:
# Debug messages are logged to a file
# our loglevels are 0 (other), 10000 (debug), ....
# logging levels are 10 (debug), 20 (info)
# OTHER = logging.NOTSET
if self.logger:
self.logger.log(int(level / 1000), message)
if level < loglevel.ERROR or self.serviceLogger is False: # Only information and above will be on event log
return
# In fact, we have restricted level in windows event log to ERROR or FATAL
# but left the code for just a case in the future...
if level < loglevel.WARN: # Info
servicemanager.LogInfoMsg(message)
elif level < loglevel.ERROR: # WARN
servicemanager.LogWarningMsg(message)
else: # Error & Fatal
servicemanager.LogErrorMsg(message)

View File

@ -1,330 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-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
'''
# pylint: disable=invalid-name
import os
import subprocess
import ctypes
from ctypes.wintypes import DWORD, LPCWSTR
import typing
import win32com.client
from win32com.shell import shell # pylint: disable=no-name-in-module,import-error
import win32net
import win32security
import win32api
import win32con
from .. import types
from ..log import logger
def checkPermissions() -> bool:
return shell.IsUserAnAdmin()
def getErrorMessage(resultCode: int = 0) -> str:
# sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
msg = win32api.FormatMessage(resultCode)
return msg
def getComputerName() -> str:
return win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
obj = win32com.client.Dispatch("WbemScripting.SWbemLocator")
wmobj = obj.ConnectServer("localhost", "root\\cimv2")
adapters = wmobj.ExecQuery(
"Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True"
)
try:
for obj in adapters:
for ip in obj.IPAddress:
if ':' in ip: # Is IPV6, skip this
continue
if (
ip is None
or ip == ''
or ip.startswith('169.254')
or ip.startswith('0.')
): # If single link ip, or no ip
continue
yield types.InterfaceInfoType(
name=obj.Caption, mac=obj.MACAddress, ip=ip
)
except Exception:
return
def getDomainName() -> str:
'''
Will return the domain name if we belong a domain, else None
(if part of a network group, will also return None)
'''
# Status:
# 0 = Unknown
# 1 = Unjoined
# 2 = Workgroup
# 3 = Domain
domain, status = win32net.NetGetJoinInformation()
if status != 3:
domain = None
return domain
def getWindowsVersion() -> typing.Tuple[int, int, int, int, str]:
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_SHUTDOWN = 0x00000001
EWX_REBOOT = 0x00000002
EWX_FORCE = 0x00000004
EWX_POWEROFF = 0x00000008
EWX_FORCEIFHUNG = 0x00000010
def reboot(flags: int = EWX_FORCEIFHUNG | EWX_REBOOT) -> None:
hproc = win32api.GetCurrentProcess()
htok = win32security.OpenProcessToken(
hproc, win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY
)
privs = (
(
win32security.LookupPrivilegeValue(None, win32security.SE_SHUTDOWN_NAME),
win32security.SE_PRIVILEGE_ENABLED,
),
)
win32security.AdjustTokenPrivileges(htok, 0, privs)
win32api.ExitWindowsEx(flags, 0)
def loggoff() -> None:
win32api.ExitWindowsEx(EWX_LOGOFF)
def renameComputer(newName: str) -> bool:
'''
Changes the computer name
Returns True if reboot needed
'''
# Needs admin privileges to work
if (
ctypes.windll.kernel32.SetComputerNameExW(
DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)
)
== 0
): # @UndefinedVariable
# win32api.FormatMessage -> returns error string
# win32api.GetLastError -> returns error code
# (just put this comment here to remember to log this when logger is available)
error = getErrorMessage()
computerName = win32api.GetComputerNameEx(
win32con.ComputerNamePhysicalDnsHostname
)
raise Exception(
'Error renaming computer from {} to {}: {}'.format(
computerName, newName, error
)
)
return True
NETSETUP_JOIN_DOMAIN = 0x00000001
NETSETUP_ACCT_CREATE = 0x00000002
NETSETUP_ACCT_DELETE = 0x00000004
NETSETUP_WIN9X_UPGRADE = 0x00000010
NETSETUP_DOMAIN_JOIN_IF_JOINED = 0x00000020
NETSETUP_JOIN_UNSECURE = 0x00000040
NETSETUP_MACHINE_PWD_PASSED = 0x00000080
NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400
NETSETUP_DEFER_SPN_SET = 0x1000000
def joinDomain(
domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False
) -> None:
'''
Joins machine to a windows domain
:param domain: Domain to join to
:param ou: Ou that will hold machine
:param account: Account used to join domain
:param password: Password of account used to join domain
:param executeInOneStep: If true, means that this machine has been renamed and wants to add NETSETUP_JOIN_WITH_NEW_NAME to request so we can do rename/join in one step.
'''
# If account do not have domain, include it
if '@' not in account and '\\' not in account:
if '.' in domain:
account = account + '@' + domain
else:
account = domain + '\\' + account
# Do log
flags: typing.Any = (
NETSETUP_ACCT_CREATE | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN
)
if executeInOneStep:
flags |= NETSETUP_JOIN_WITH_NEW_NAME
flags = DWORD(flags)
lpDomain = LPCWSTR(domain)
# Must be in format "ou=.., ..., dc=...,"
lpOu = LPCWSTR(ou) if ou is not None and ou != '' else None
lpAccount = LPCWSTR(account)
lpPassword = LPCWSTR(password)
res = ctypes.windll.netapi32.NetJoinDomain(
None, lpDomain, lpOu, lpAccount, lpPassword, flags
)
# Machine found in another ou, use it and warn this on log
if res == 2224:
flags = DWORD(NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN)
res = ctypes.windll.netapi32.NetJoinDomain(
None, lpDomain, None, lpAccount, lpPassword, flags
)
if res:
# Log the error
error = getErrorMessage(res)
if res == 1355:
error = "DC Is not reachable"
logger.error('Error joining domain: {}, {}'.format(error, res))
raise Exception(
'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:
# lpUser = LPCWSTR(user)
# lpOldPassword = LPCWSTR(oldPassword)
# lpNewPassword = LPCWSTR(newPassword)
# res = ctypes.windll.netapi32.NetUserChangePassword(None, lpUser, lpOldPassword, lpNewPassword)
# Try to set new password "a las bravas", ignoring old one. This will not work with domain users
res = win32net.NetUserSetInfo(None, user, 1003, {'password': newPassword})
if res:
# Log the error, and raise exception to parent
error = getErrorMessage(res)
raise Exception(
'Error changing password for user {}: {} {}'.format(user, res, error)
)
class LASTINPUTINFO(ctypes.Structure): # pylint: disable=too-few-public-methods
_fields_ = [
('cbSize', ctypes.c_uint),
('dwTime', ctypes.c_uint),
]
def initIdleDuration(atLeastSeconds: int): # pylint: disable=unused-argument
'''
In windows, there is no need to set screensaver
'''
return
def getIdleDuration() -> float:
try:
lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = ctypes.sizeof(
lastInputInfo
) # pylint: disable=attribute-defined-outside-init
if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) == 0:
return 0
current = ctypes.c_uint(ctypes.windll.kernel32.GetTickCount()).value
if current < lastInputInfo.dwTime:
current += 4294967296 # If current has "rolled" to zero, adjust it so it is greater than lastInputInfo
millis = current - lastInputInfo.dwTime # @UndefinedVariable
return millis / 1000.0
except Exception as e:
logger.error('Getting idle duration: {}'.format(e))
return 0
def getCurrentUser() -> str:
'''
Returns current logged in username
'''
return os.environ['USERNAME']
def getSessionType() -> str:
'''
Known values:
* Unknown -> No SESSIONNAME environment variable
* Console -> Local session
* RDP-Tcp#[0-9]+ -> RDP Session
'''
return os.environ.get('SESSIONNAME', 'unknown')
def writeToPipe(
pipeName: str, bytesPayload: bytes, waitForResponse: bool
) -> typing.Optional[bytes]:
# (str, bytes, bool) -> Optional[bytes]
try:
with open(pipeName, 'r+b', 0) as f:
f.write(bytesPayload)
# f.seek(0) # As recommended on intenet, but seems to work fin without thos
if waitForResponse:
return f.read()
return b'ok'
except Exception:
return None
def forceTimeSync() -> None:
try:
subprocess.call(
[r'c:\WINDOWS\System32\w32tm.exe', ' /resync']
) # , '/rediscover'])
except Exception as e:
logger.error('Error invoking time sync command: %s', e)

View File

@ -1,77 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019-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
'''
# pylint: disable=invalid-name
import sys
import win32service
import win32serviceutil
import servicemanager
import win32timezone # pylint: disable=unused-import
from .service import UDSActorSvc
def setupRecoverService():
svc_name = UDSActorSvc._svc_name_ # pylint: disable=protected-access
hs = None
hscm = None
try:
hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
try:
hs = win32serviceutil.SmartOpenService(hscm, svc_name, win32service.SERVICE_ALL_ACCESS)
service_failure_actions = {
'ResetPeriod': 864000, # Time in ms after which to reset the failure count to zero.
'RebootMsg': u'', # Not using reboot option
'Command': u'', # Not using run-command option
'Actions': [
(win32service.SC_ACTION_RESTART, 5000), # action, delay in ms
(win32service.SC_ACTION_RESTART, 5000)
]
}
win32service.ChangeServiceConfig2(hs, win32service.SERVICE_CONFIG_FAILURE_ACTIONS, service_failure_actions)
finally:
if hs:
win32service.CloseServiceHandle(hs)
finally:
if hscm:
win32service.CloseServiceHandle(hscm)
def run() -> None:
if len(sys.argv) == 1:
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(UDSActorSvc)
servicemanager.StartServiceCtrlDispatcher()
elif sys.argv[1] == '--setup-recovery':
setupRecoverService()
else:
win32serviceutil.HandleCommandLine(UDSActorSvc)

View File

@ -1,276 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
import struct
import typing
import win32serviceutil
import win32service
import win32security
import win32net
import win32event
import pythoncom
import servicemanager
import winreg as wreg
from . import operations
from . import store
from ..service import CommonService
from ..log import logger
REMOTE_USERS_SID = 'S-1-5-32-555' # Well nown sid for remote desktop users
class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
'''
This class represents a Windows Service for managing actor interactions
with UDS Broker and Machine
'''
# ServiceeFramework related
_svc_name_ = "UDSActorNG"
_svc_display_name_ = "UDS Actor Service"
_svc_description_ = "UDS Actor Management Service"
_svc_deps_ = ['EventLog']
_user: typing.Optional[str]
_hWaitStop: typing.Any
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
CommonService.__init__(self)
self._hWaitStop = win32event.CreateEvent(None, 1, 0, None)
self._user = None
def SvcStop(self) -> None:
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
self._isAlive = False
win32event.SetEvent(self._hWaitStop)
SvcShutdown = SvcStop
def notifyStop(self) -> None:
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STOPPED, (self._svc_name_, ''))
super().notifyStop()
def doWait(self, miliseconds: int) -> None:
win32event.WaitForSingleObject(self._hWaitStop, miliseconds)
# On windows, and while on tasks, ensure that our app processes waiting messages on "wait times"
pythoncom.PumpWaitingMessages() # pylint: disable=no-member
def oneStepJoin(self, name: str, domain: str, ou: str, account: str, password: str) -> None: # pylint: disable=too-many-arguments
'''
Ejecutes the join domain in exactly one step
'''
currName = operations.getComputerName()
# If name is desired, simply execute multiStepJoin, because computer
# name will not change
if currName.lower() == name.lower():
self.multiStepJoin(name, domain, ou, account, password)
return
operations.renameComputer(name)
logger.debug('Computer renamed to {} without reboot'.format(name))
operations.joinDomain(domain, ou, account, password, executeInOneStep=True)
logger.debug('Requested join domain {} without errors'.format(domain))
self.reboot()
def multiStepJoin(self, name: str, domain: str, ou: str, account: str, password: str) -> None: # pylint: disable=too-many-arguments
currName = operations.getComputerName()
if currName.lower() == name.lower():
currDomain = operations.getDomainName()
if currDomain:
# logger.debug('Name: "{}" vs "{}", Domain: "{}" vs "{}"'.format(currName.lower(), name.lower(), currDomain.lower(), domain.lower()))
logger.debug('Machine {} is part of domain {}'.format(name, domain))
self.setReady()
else:
operations.joinDomain(domain, ou, account, password, executeInOneStep=False)
self.reboot()
else:
operations.renameComputer(name)
logger.info('Rebooting computer for activating new name {}'.format(name))
self.reboot()
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self,
name: str,
domain: str,
ou: str,
account: str,
password: str
) -> None:
versionData = operations.getWindowsVersion()
versionInt = versionData[0] * 10 + versionData[1]
logger.debug('Starting joining domain {} with name {} (detected operating version: {})'.format(domain, name, versionData))
# Accepts one step joinDomain, also remember XP is no more supported by
# microsoft, but this also must works with it because will do a "multi
# step" join
if versionInt >= 60 and not store.useOldJoinSystem():
self.oneStepJoin(name, domain, ou, account, password)
else:
logger.info('Using multiple step join because configuration requests to do so')
self.multiStepJoin(name, domain, ou, account, password)
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str) -> str:
logger.debug('Pre connect invoked')
if protocol == 'rdp': # If connection is not using rdp, skip adding user
# Well known SSID for Remote Desktop Users
groupName = win32security.LookupAccountSid(None, win32security.GetBinarySid(REMOTE_USERS_SID))[0]
useraAlreadyInGroup = False
resumeHandle = 0
while True:
users, _, resumeHandle = win32net.NetLocalGroupGetMembers(None, groupName, 1, resumeHandle, 32768)
if userName.lower() in [u['name'].lower() for u in users]:
useraAlreadyInGroup = True
break
if resumeHandle == 0:
break
if not useraAlreadyInGroup:
logger.debug('User not in group, adding it')
self._user = userName
try:
userSSID = win32security.LookupAccountName(None, userName)[0]
win32net.NetLocalGroupAddMembers(None, groupName, 0, [{'sid': userSSID}])
except Exception as e:
logger.error('Exception adding user to Remote Desktop Users: {}'.format(e))
else:
self._user = None
logger.debug('User {} already in group'.format(userName))
return super().preConnect(userName, protocol, ip, hostname, udsUserName)
def ovLogon(self, username: str, password: str) -> str:
"""
Logon on oVirt agent
currently not used.
"""
# Compose packet for ov
usernameBytes = username.encode()
passwordBytes = password.encode()
packet = struct.pack('!I', len(usernameBytes)) + usernameBytes + struct.pack('!I', len(passwordBytes)) + passwordBytes
# Send packet with username/password to ov pipe
operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
return 'done'
def onLogout(self, userName: str, session_id: str) -> None:
logger.debug('Windows onLogout invoked: {}, {}'.format(userName, self._user))
try:
p = win32security.GetBinarySid(REMOTE_USERS_SID)
groupName = win32security.LookupAccountSid(None, p)[0]
except Exception:
logger.error('Exception getting Windows Group')
return
if self._user:
try:
win32net.NetLocalGroupDelMembers(None, groupName, [self._user])
except Exception as 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
'''
Main service loop
'''
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))
# call the CoInitialize to allow the registration to run in an other
# thread
logger.debug('Initializing coms')
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)
if self.isManaged():
if not self.initialize():
logger.info('Service stopped due to init')
self.finish()
win32event.WaitForSingleObject(self._hWaitStop, 5000)
return # Stop daemon if initializes told to do so
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady()
else:
if not self.initializeUnmanaged():
self.finish()
return
# Start listening for petitions
self.startHttpServer()
# *********************
# * Main Service loop *
# *********************
# Counter used to check ip changes only once every 10 seconds
counter = 0
while self._isAlive:
counter += 1
try:
pythoncom.PumpWaitingMessages() # pylint: disable=no-member
if counter % 5 == 0: # Once every 5 seconds
self.loop()
except Exception as e:
logger.error('Got exception on main loop: %s', e)
# Continue after a while...
# In milliseconds, will break if event hWaitStop is set
win32event.WaitForSingleObject(self._hWaitStop, 1000)
logger.debug('Exited main loop')
self.finish()

View File

@ -1,109 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# 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
'''
# pylint: disable=invalid-name
import pickle
import winreg as wreg
import win32security
from .. import types
PATH = 'Software\\UDSActor'
BASEKEY = wreg.HKEY_LOCAL_MACHINE
def fixRegistryPermissions(handle) -> None:
# Fix permissions so users can't read this key
v = win32security.GetSecurityInfo(handle, win32security.SE_REGISTRY_KEY, win32security.DACL_SECURITY_INFORMATION)
dacl = v.GetSecurityDescriptorDacl()
n = 0
# Remove all normal users access permissions to the registry key
while n < dacl.GetAceCount():
if str(dacl.GetAce(n)[2]) == 'PySID:S-1-5-32-545': # Whell known Users SID
dacl.DeleteAce(n)
else:
n += 1
win32security.SetSecurityInfo(
handle,
win32security.SE_REGISTRY_KEY,
win32security.DACL_SECURITY_INFORMATION | win32security.PROTECTED_DACL_SECURITY_INFORMATION,
None,
None,
dacl,
None
)
def readConfig() -> types.ActorConfigurationType:
try:
key = wreg.OpenKey(BASEKEY, PATH, 0, wreg.KEY_QUERY_VALUE)
data, _ = wreg.QueryValueEx(key, '')
wreg.CloseKey(key)
return pickle.loads(data)
except Exception:
return types.ActorConfigurationType('', False)
def writeConfig(config: types.ActorConfigurationType) -> None:
try:
key = wreg.OpenKey(BASEKEY, PATH, 0, wreg.KEY_ALL_ACCESS)
except Exception:
key = wreg.CreateKeyEx(BASEKEY, PATH, 0, wreg.KEY_ALL_ACCESS)
fixRegistryPermissions(key.handle) # type: ignore
wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, pickle.dumps(config)) # type: ignore
wreg.CloseKey(key)
def useOldJoinSystem() -> bool:
try:
key = wreg.OpenKey(BASEKEY, PATH, 0, wreg.KEY_QUERY_VALUE)
try:
data, _ = wreg.QueryValueEx(key, 'join')
except Exception:
data = ''
wreg.CloseKey(key)
except Exception:
data = ''
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

View File

@ -1,249 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'setup-dialog.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_UdsActorSetupDialog(object):
def setupUi(self, UdsActorSetupDialog):
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
UdsActorSetupDialog.resize(590, 307)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(UdsActorSetupDialog.sizePolicy().hasHeightForWidth())
UdsActorSetupDialog.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setFamily("Verdana")
font.setPointSize(9)
UdsActorSetupDialog.setFont(font)
UdsActorSetupDialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/img/img/uds-icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
UdsActorSetupDialog.setWindowIcon(icon)
UdsActorSetupDialog.setAutoFillBackground(False)
UdsActorSetupDialog.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
UdsActorSetupDialog.setSizeGripEnabled(False)
UdsActorSetupDialog.setModal(True)
self.registerButton = QtWidgets.QPushButton(UdsActorSetupDialog)
self.registerButton.setEnabled(False)
self.registerButton.setGeometry(QtCore.QRect(10, 270, 181, 23))
self.registerButton.setMinimumSize(QtCore.QSize(181, 0))
self.registerButton.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
self.registerButton.setObjectName("registerButton")
self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog)
self.closeButton.setGeometry(QtCore.QRect(410, 270, 171, 23))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.closeButton.sizePolicy().hasHeightForWidth())
self.closeButton.setSizePolicy(sizePolicy)
self.closeButton.setMinimumSize(QtCore.QSize(171, 0))
self.closeButton.setObjectName("closeButton")
self.tabWidget = QtWidgets.QTabWidget(UdsActorSetupDialog)
self.tabWidget.setGeometry(QtCore.QRect(10, 10, 571, 241))
self.tabWidget.setObjectName("tabWidget")
self.tab_uds = QtWidgets.QWidget()
self.tab_uds.setObjectName("tab_uds")
self.layoutWidget = QtWidgets.QWidget(self.tab_uds)
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 551, 191))
self.layoutWidget.setObjectName("layoutWidget")
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setVerticalSpacing(16)
self.formLayout.setObjectName("formLayout")
self.label_host = QtWidgets.QLabel(self.layoutWidget)
self.label_host.setObjectName("label_host")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_host)
self.host = QtWidgets.QLineEdit(self.layoutWidget)
self.host.setAcceptDrops(False)
self.host.setObjectName("host")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.host)
self.label_auth = QtWidgets.QLabel(self.layoutWidget)
self.label_auth.setObjectName("label_auth")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_auth)
self.authenticators = QtWidgets.QComboBox(self.layoutWidget)
self.authenticators.setObjectName("authenticators")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.authenticators)
self.label_username = QtWidgets.QLabel(self.layoutWidget)
self.label_username.setObjectName("label_username")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_username)
self.username = QtWidgets.QLineEdit(self.layoutWidget)
self.username.setObjectName("username")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.username)
self.label_password = QtWidgets.QLabel(self.layoutWidget)
self.label_password.setObjectName("label_password")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_password)
self.password = QtWidgets.QLineEdit(self.layoutWidget)
self.password.setEchoMode(QtWidgets.QLineEdit.Password)
self.password.setObjectName("password")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.password)
self.validateCertificate = QtWidgets.QComboBox(self.layoutWidget)
self.validateCertificate.setObjectName("validateCertificate")
self.validateCertificate.addItem("")
self.validateCertificate.addItem("")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.validateCertificate)
self.label_security = QtWidgets.QLabel(self.layoutWidget)
self.label_security.setObjectName("label_security")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_security)
self.label_host.raise_()
self.host.raise_()
self.label_auth.raise_()
self.label_username.raise_()
self.username.raise_()
self.label_password.raise_()
self.password.raise_()
self.validateCertificate.raise_()
self.label_security.raise_()
self.authenticators.raise_()
self.tabWidget.addTab(self.tab_uds, "")
self.tab_advanced = QtWidgets.QWidget()
self.tab_advanced.setObjectName("tab_advanced")
self.layoutWidget_2 = QtWidgets.QWidget(self.tab_advanced)
self.layoutWidget_2.setGeometry(QtCore.QRect(10, 10, 551, 161))
self.layoutWidget_2.setObjectName("layoutWidget_2")
self.formLayout_2 = QtWidgets.QFormLayout(self.layoutWidget_2)
self.formLayout_2.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
self.formLayout_2.setContentsMargins(0, 0, 0, 0)
self.formLayout_2.setVerticalSpacing(16)
self.formLayout_2.setObjectName("formLayout_2")
self.label_host_2 = QtWidgets.QLabel(self.layoutWidget_2)
self.label_host_2.setObjectName("label_host_2")
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_host_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setContentsMargins(-1, 0, -1, -1)
self.horizontalLayout.setSpacing(4)
self.horizontalLayout.setObjectName("horizontalLayout")
self.preCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
self.preCommand.setAcceptDrops(False)
self.preCommand.setWhatsThis("")
self.preCommand.setObjectName("preCommand")
self.horizontalLayout.addWidget(self.preCommand)
self.browsePreconnectButton = QtWidgets.QPushButton(self.layoutWidget_2)
self.browsePreconnectButton.setAutoDefault(False)
self.browsePreconnectButton.setFlat(False)
self.browsePreconnectButton.setObjectName("browsePreconnectButton")
self.horizontalLayout.addWidget(self.browsePreconnectButton)
self.formLayout_2.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout)
self.label_username_2 = QtWidgets.QLabel(self.layoutWidget_2)
self.label_username_2.setObjectName("label_username_2")
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_username_2)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
self.horizontalLayout_2.setSpacing(4)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.runonceCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
self.runonceCommand.setWhatsThis("")
self.runonceCommand.setObjectName("runonceCommand")
self.horizontalLayout_2.addWidget(self.runonceCommand)
self.browseRunOnceButton = QtWidgets.QPushButton(self.layoutWidget_2)
self.browseRunOnceButton.setAutoDefault(False)
self.browseRunOnceButton.setObjectName("browseRunOnceButton")
self.horizontalLayout_2.addWidget(self.browseRunOnceButton)
self.formLayout_2.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2)
self.label_password_2 = QtWidgets.QLabel(self.layoutWidget_2)
self.label_password_2.setObjectName("label_password_2")
self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_password_2)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setContentsMargins(-1, 0, -1, -1)
self.horizontalLayout_3.setSpacing(4)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.postConfigCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
self.postConfigCommand.setWhatsThis("")
self.postConfigCommand.setEchoMode(QtWidgets.QLineEdit.Normal)
self.postConfigCommand.setObjectName("postConfigCommand")
self.horizontalLayout_3.addWidget(self.postConfigCommand)
self.browsePostConfigButton = QtWidgets.QPushButton(self.layoutWidget_2)
self.browsePostConfigButton.setAutoDefault(False)
self.browsePostConfigButton.setObjectName("browsePostConfigButton")
self.horizontalLayout_3.addWidget(self.browsePostConfigButton)
self.formLayout_2.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3)
self.label_loglevel = QtWidgets.QLabel(self.layoutWidget_2)
self.label_loglevel.setObjectName("label_loglevel")
self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget_2)
self.logLevelComboBox.setFrame(True)
self.logLevelComboBox.setObjectName("logLevelComboBox")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(0, "DEBUG")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(1, "INFO")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(2, "ERROR")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(3, "FATAL")
self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
self.tabWidget.addTab(self.tab_advanced, "")
self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
self.testButton.setEnabled(False)
self.testButton.setGeometry(QtCore.QRect(210, 270, 181, 23))
self.testButton.setMinimumSize(QtCore.QSize(181, 0))
self.testButton.setObjectName("testButton")
self.retranslateUi(UdsActorSetupDialog)
self.tabWidget.setCurrentIndex(0)
self.logLevelComboBox.setCurrentIndex(1)
self.closeButton.clicked.connect(UdsActorSetupDialog.finish)
self.registerButton.clicked.connect(UdsActorSetupDialog.registerWithUDS)
self.host.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
self.username.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
self.password.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
self.browsePreconnectButton.clicked.connect(UdsActorSetupDialog.browsePreconnect)
self.browsePostConfigButton.clicked.connect(UdsActorSetupDialog.browsePostConfig)
self.browseRunOnceButton.clicked.connect(UdsActorSetupDialog.browseRunOnce)
self.host.editingFinished.connect(UdsActorSetupDialog.updateAuthenticators)
self.authenticators.currentTextChanged['QString'].connect(UdsActorSetupDialog.textChanged)
self.testButton.clicked.connect(UdsActorSetupDialog.testUDSServer)
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
def retranslateUi(self, UdsActorSetupDialog):
_translate = QtCore.QCoreApplication.translate
UdsActorSetupDialog.setWindowTitle(_translate("UdsActorSetupDialog", "UDS Actor Configuration Tool"))
self.registerButton.setToolTip(_translate("UdsActorSetupDialog", "Click to register Actor with UDS Broker"))
self.registerButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Click on this button to register Actor with UDS Broker.</p></body></html>"))
self.registerButton.setText(_translate("UdsActorSetupDialog", "Register with UDS"))
self.closeButton.setToolTip(_translate("UdsActorSetupDialog", "Closes UDS Actor Configuration (discard pending changes if any)"))
self.closeButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Exits the UDS Actor Configuration Tool</p></body></html>"))
self.closeButton.setText(_translate("UdsActorSetupDialog", "Close"))
self.label_host.setText(_translate("UdsActorSetupDialog", "UDS Server"))
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.label_auth.setText(_translate("UdsActorSetupDialog", "Authenticator"))
self.authenticators.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Select the UDS Broker authenticator for credentials validation</p></body></html>"))
self.label_username.setText(_translate("UdsActorSetupDialog", "Username"))
self.username.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
self.username.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_password.setText(_translate("UdsActorSetupDialog", "Password"))
self.password.setToolTip(_translate("UdsActorSetupDialog", "Password for user (Will not be stored on template)"))
self.password.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator password for the user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.</p></body></html>"))
self.validateCertificate.setToolTip(_translate("UdsActorSetupDialog", "Select communication security with broker"))
self.validateCertificate.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Select the security for communications with UDS Broker.</p><p>The recommended method of communication is <span style=\" font-weight:600;\">Use SSL</span>, but selection needs to be acording to your broker configuration.</p></body></html>"))
self.validateCertificate.setItemText(0, _translate("UdsActorSetupDialog", "Ignore certificate"))
self.validateCertificate.setItemText(1, _translate("UdsActorSetupDialog", "Verify certificate"))
self.label_security.setText(_translate("UdsActorSetupDialog", "SSL Validation"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_uds), _translate("UdsActorSetupDialog", "UDS Server"))
self.label_host_2.setText(_translate("UdsActorSetupDialog", "Preconnect"))
self.preCommand.setToolTip(_translate("UdsActorSetupDialog", "Pre connection command. Executed just before the user is connected to machine."))
self.browsePreconnectButton.setText(_translate("UdsActorSetupDialog", "Browse"))
self.label_username_2.setText(_translate("UdsActorSetupDialog", "Runonce"))
self.runonceCommand.setToolTip(_translate("UdsActorSetupDialog", "Run once command. Executed on first boot, just before UDS does anything."))
self.browseRunOnceButton.setText(_translate("UdsActorSetupDialog", "Browse"))
self.label_password_2.setText(_translate("UdsActorSetupDialog", "Postconfig"))
self.postConfigCommand.setToolTip(_translate("UdsActorSetupDialog", "Command to execute after UDS finalizes the VM configuration."))
self.browsePostConfigButton.setText(_translate("UdsActorSetupDialog", "Browse"))
self.label_loglevel.setText(_translate("UdsActorSetupDialog", "Log Level"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_advanced), _translate("UdsActorSetupDialog", "Advanced"))
self.testButton.setToolTip(_translate("UdsActorSetupDialog", "Click to test existing configuration (disabled if no config found)"))
self.testButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Click on this button to test the server host and assigned toen.</p></body></html>"))
self.testButton.setText(_translate("UdsActorSetupDialog", "Test configuration"))
from ui import uds_rc

View File

@ -1,155 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'setup-dialog-unmanaged.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_UdsActorSetupDialog(object):
def setupUi(self, UdsActorSetupDialog):
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
UdsActorSetupDialog.resize(601, 243)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(UdsActorSetupDialog.sizePolicy().hasHeightForWidth())
UdsActorSetupDialog.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setFamily("Verdana")
font.setPointSize(9)
UdsActorSetupDialog.setFont(font)
UdsActorSetupDialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/img/img/uds-icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
UdsActorSetupDialog.setWindowIcon(icon)
UdsActorSetupDialog.setAutoFillBackground(False)
UdsActorSetupDialog.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
UdsActorSetupDialog.setSizeGripEnabled(False)
UdsActorSetupDialog.setModal(True)
self.saveButton = QtWidgets.QPushButton(UdsActorSetupDialog)
self.saveButton.setEnabled(True)
self.saveButton.setGeometry(QtCore.QRect(10, 210, 181, 23))
self.saveButton.setMinimumSize(QtCore.QSize(181, 0))
self.saveButton.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
self.saveButton.setObjectName("saveButton")
self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog)
self.closeButton.setGeometry(QtCore.QRect(410, 210, 171, 23))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.closeButton.sizePolicy().hasHeightForWidth())
self.closeButton.setSizePolicy(sizePolicy)
self.closeButton.setMinimumSize(QtCore.QSize(171, 0))
self.closeButton.setObjectName("closeButton")
self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
self.testButton.setEnabled(False)
self.testButton.setGeometry(QtCore.QRect(210, 210, 181, 23))
self.testButton.setMinimumSize(QtCore.QSize(181, 0))
self.testButton.setObjectName("testButton")
self.layoutWidget = QtWidgets.QWidget(UdsActorSetupDialog)
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 571, 191))
self.layoutWidget.setObjectName("layoutWidget")
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setVerticalSpacing(16)
self.formLayout.setObjectName("formLayout")
self.label_security = QtWidgets.QLabel(self.layoutWidget)
self.label_security.setObjectName("label_security")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_security)
self.validateCertificate = QtWidgets.QComboBox(self.layoutWidget)
self.validateCertificate.setObjectName("validateCertificate")
self.validateCertificate.addItem("")
self.validateCertificate.addItem("")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.validateCertificate)
self.label_host = QtWidgets.QLabel(self.layoutWidget)
self.label_host.setObjectName("label_host")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_host)
self.host = QtWidgets.QLineEdit(self.layoutWidget)
self.host.setAcceptDrops(False)
self.host.setObjectName("host")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.host)
self.label_serviceToken = QtWidgets.QLabel(self.layoutWidget)
self.label_serviceToken.setObjectName("label_serviceToken")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_serviceToken)
self.serviceToken = QtWidgets.QLineEdit(self.layoutWidget)
self.serviceToken.setObjectName("serviceToken")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.serviceToken)
self.label_loglevel = QtWidgets.QLabel(self.layoutWidget)
self.label_loglevel.setObjectName("label_loglevel")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget)
self.logLevelComboBox.setFrame(True)
self.logLevelComboBox.setObjectName("logLevelComboBox")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(0, "DEBUG")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(1, "INFO")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(2, "ERROR")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(3, "FATAL")
self.formLayout.setWidget(4, 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.host.raise_()
self.label_serviceToken.raise_()
self.serviceToken.raise_()
self.validateCertificate.raise_()
self.label_security.raise_()
self.label_loglevel.raise_()
self.logLevelComboBox.raise_()
self.label_restrictNet.raise_()
self.restrictNet.raise_()
self.retranslateUi(UdsActorSetupDialog)
self.logLevelComboBox.setCurrentIndex(1)
self.closeButton.clicked.connect(UdsActorSetupDialog.finish)
self.testButton.clicked.connect(UdsActorSetupDialog.testUDSServer)
self.saveButton.clicked.connect(UdsActorSetupDialog.saveConfig)
self.host.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
self.serviceToken.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
self.restrictNet.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
def retranslateUi(self, UdsActorSetupDialog):
_translate = QtCore.QCoreApplication.translate
UdsActorSetupDialog.setWindowTitle(_translate("UdsActorSetupDialog", "UDS Actor Configuration Tool"))
self.saveButton.setToolTip(_translate("UdsActorSetupDialog", "Click to register Actor with UDS Broker"))
self.saveButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Click on this button to register Actor with UDS Broker.</p></body></html>"))
self.saveButton.setText(_translate("UdsActorSetupDialog", "Save Configuration"))
self.closeButton.setToolTip(_translate("UdsActorSetupDialog", "Closes UDS Actor Configuration (discard pending changes if any)"))
self.closeButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Exits the UDS Actor Configuration Tool</p></body></html>"))
self.closeButton.setText(_translate("UdsActorSetupDialog", "Close"))
self.testButton.setToolTip(_translate("UdsActorSetupDialog", "Click to test existing configuration (disabled if no config found)"))
self.testButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Click on this button to test the server host and assigned toen.</p></body></html>"))
self.testButton.setText(_translate("UdsActorSetupDialog", "Test configuration"))
self.label_security.setText(_translate("UdsActorSetupDialog", "SSL Validation"))
self.validateCertificate.setToolTip(_translate("UdsActorSetupDialog", "Select communication security with broker"))
self.validateCertificate.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Select the security for communications with UDS Broker.</p><p>The recommended method of communication is <span style=\" font-weight:600;\">Use SSL</span>, but selection needs to be acording to your broker configuration.</p></body></html>"))
self.validateCertificate.setItemText(0, _translate("UdsActorSetupDialog", "Ignore certificate"))
self.validateCertificate.setItemText(1, _translate("UdsActorSetupDialog", "Verify certificate"))
self.label_host.setText(_translate("UdsActorSetupDialog", "UDS Server"))
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.label_serviceToken.setText(_translate("UdsActorSetupDialog", "Service Token"))
self.serviceToken.setToolTip(_translate("UdsActorSetupDialog", "UDS Service Token"))
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_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

View File

@ -1,195 +0,0 @@
# -*- coding: utf-8 -*-
# Resource object code
#
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore
qt_resource_data = b"\
\x00\x00\x08\x7c\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\
\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\
\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe2\x0a\
\x04\x07\x27\x0a\x6d\xd5\xd2\x21\x00\x00\x08\x1b\x49\x44\x41\x54\
\x68\xde\xed\x9a\x6d\x8c\x54\xd5\x19\xc7\x7f\xe7\xbe\xee\x2e\xfb\
\xbe\x3a\x60\x2d\x52\xd6\x62\xac\x52\x23\x68\xc7\x97\xb4\x46\x8c\
\xca\x04\x6c\x2d\xad\x56\x4d\x5d\x5a\x9b\x5a\x8d\xfd\x60\xd2\x12\
\x9b\x94\x56\xb4\x76\xc4\xa6\x68\xda\x68\xb5\x04\x62\x90\xd1\xb8\
\xc1\x12\xab\x06\x98\x6a\x33\x60\x41\x83\x53\x17\x50\x16\x10\xa5\
\xb2\x14\xd4\x65\x16\x96\x65\x67\x77\xe6\xce\x7d\x3b\xfd\xb0\xb3\
\xeb\xec\xee\xec\xb0\xb3\x77\xfa\xa1\x49\xff\xc9\x64\x66\xce\xb9\
\xf7\x9e\xe7\xbf\xcf\xcb\x79\xce\x7f\x16\xfe\xc7\x21\x2a\xfd\xc0\
\x78\x32\xa4\x01\x9a\xa2\xa0\x9d\xdf\xaa\x29\x5f\x68\xd2\x94\x23\
\xdd\x8e\x78\x27\xe9\xf0\xc1\x87\x0e\xdd\x3d\x3e\x5d\x47\x1c\x5f\
\x0c\xad\xec\x00\xd9\x44\xbb\x25\xff\xeb\x04\xe2\xc9\x90\x02\xa8\
\x80\x96\x7f\x3f\x07\x58\x04\x2c\x00\xae\x04\xa6\x0f\x5f\xeb\x79\
\xe0\x38\x92\x5d\x7b\x6c\xf6\x76\x3a\x9c\xea\xf3\xc9\x1b\x3c\xf2\
\x5e\x04\x27\x80\x38\x10\x03\x76\x00\x1e\x60\x9f\x89\x9c\x98\x84\
\xe1\x17\x00\xeb\x81\x56\xa0\x2e\x4f\x40\x2b\x76\xad\x94\xb0\x7a\
\x6d\x9a\xf4\x80\x44\x4a\x80\xe1\xb5\x25\x82\xcf\xed\x90\x88\x31\
\x4b\x17\x35\xc3\x06\xfa\x81\x87\x13\xed\xd6\x53\x65\x11\x88\x27\
\x43\x73\x81\x35\xc0\x45\x40\xfd\x64\x3c\x34\x38\x28\xf9\xd3\xea\
\x34\x00\x86\xb4\x38\x6a\xdc\x43\xbf\x3a\x1f\x57\x34\xe2\x63\x22\
\x45\x01\x67\xcf\xc6\x6c\xa8\x47\xaf\x56\x51\xdc\x5e\x0c\xb7\x9b\
\xba\x81\xd7\x69\xe8\x7f\x05\x09\x78\xaa\x39\xd6\xb4\xd3\xc0\xef\
\x12\xed\xd6\xca\x33\x12\x88\x27\x43\x6b\x80\x1f\x97\x1b\x8b\x07\
\x3e\x70\x78\x6d\xd3\x20\xbd\xfa\xcd\x74\x99\x0f\x20\x51\x11\xb8\
\x05\x5e\x18\x0e\x21\x81\x7e\xf6\x2c\x14\x73\x1a\x79\x37\x21\x51\
\x90\x8a\x81\x14\x1a\xd5\xd6\x01\x6a\x32\x6f\x51\x3b\x98\xa0\xb9\
\xef\x45\xdc\xd1\x64\xfe\x96\x68\xb7\x22\x13\x12\x88\x27\x43\xcf\
\x02\x77\x4d\x25\x99\x8e\xa7\x3c\x9e\x59\x57\xc5\xfe\x86\x2d\x28\
\x64\x8b\xbb\x5b\xd3\x30\x67\xcc\x99\x54\x6a\x4a\x61\x20\xa4\xc7\
\x79\x9f\xdc\x49\x5d\xfa\x15\xa4\xd0\x87\x27\xb7\x27\xda\xad\x6b\
\x86\xbf\xa8\xc3\x1f\x36\x6e\x3b\xcb\x30\x74\xf1\x62\xe1\x58\x39\
\x68\x6e\x52\x39\xd1\xdf\xc4\xbe\x53\xdf\x42\x88\xf1\x79\x27\x84\
\x82\x79\xee\x85\xe3\x3c\x32\x31\x05\x0f\xf0\xe9\x6b\xb8\x15\x44\
\x15\xb5\x99\x04\xa0\x00\xcc\x9a\x3d\x57\x7b\xfe\x70\xa7\x7b\x8a\
\xe1\x11\x80\xf5\x2f\x9f\x52\xb3\x96\xaf\x4c\xb5\x9c\xd9\xb6\xe4\
\x96\x85\x29\x66\x36\x1d\x2d\x3a\x6f\x9c\x7b\x01\x48\xbf\xfc\x32\
\x29\x1d\x52\x67\xfd\x9c\xbe\x86\xa5\x85\xc3\xef\x0f\x7f\x18\x31\
\xb8\xbe\x56\x91\x1b\xe3\xa7\xa5\xe7\x4d\xb9\x24\xe3\x49\x85\x55\
\xb7\xdd\x4b\x8d\x6e\x15\xd6\x26\xb4\xda\x66\x44\x80\x2d\x47\xc8\
\x1c\x3d\x2d\xcb\x50\xbd\x91\xe7\xd6\x5c\x77\x7b\x55\xf3\x28\x02\
\x40\x4e\x08\x78\x7d\xc7\x00\x9a\x16\x64\x7f\x93\xac\xfe\xc1\x9d\
\x05\x61\x24\xd0\x1a\xa7\x07\xde\x6f\x2d\xf3\x2b\xf8\x8a\x39\x2a\
\x6a\x47\x11\x88\x45\x1d\x09\x2c\x3c\x79\xca\x65\x57\x67\x06\x3d\
\x00\x09\x5d\xcd\xb1\x72\xc9\x32\x6c\xd7\x44\x28\x0a\x28\x0a\xc1\
\xe1\x63\x9b\x5f\x2e\x1c\x30\xc7\x7a\x80\x58\xd4\xd9\x2a\x04\xbb\
\x3a\x3a\xb3\x7c\x7c\xd4\x0e\xb4\xdc\xec\xd0\x41\xee\xbf\x7e\x15\
\x96\x63\x80\x2f\xa9\x04\x14\x3f\x33\x7e\x6c\xec\x40\x2c\xea\x5c\
\x66\xe8\x22\xb7\x75\x67\x1a\xcf\x0b\x10\x48\x52\x70\xc3\xc5\xaf\
\x71\xfb\xfc\x17\xf0\x5c\xb7\x12\xe6\x53\x65\x1d\x2e\x1c\xc8\x16\
\x25\x90\xc7\x52\x55\x11\xbc\xfc\x7a\x1f\x6a\x00\xef\xdb\xae\x49\
\xdb\xd7\x57\x73\x51\x55\x3c\xb0\xf9\x86\x7b\x6c\x6c\x01\xee\x9f\
\x90\x40\x2c\xea\x6c\x00\x7e\x9f\xb3\x25\x7f\xd9\xd2\x17\x68\x61\
\xdb\x35\xf9\xe5\xc2\xfb\x69\xa9\x3e\x1e\xa8\x30\xe8\xf6\x11\x0a\
\x8a\xbc\x5f\x92\x40\x9e\xc4\x03\xc0\xa7\x19\x4b\xf2\xee\xde\x6c\
\xa0\x3c\x94\xc0\x13\xd7\x5d\x4d\xad\xde\x3f\x75\x0f\xd8\x87\x0a\
\xab\x50\x6f\xa2\xdd\xb2\x4b\x12\xc8\xe3\x72\x80\x03\x87\x2c\x3e\
\x3d\xee\xa0\x88\x20\x24\x04\x4f\x2f\xbc\x94\x46\xf3\x64\xbe\x1b\
\x2d\xe7\x5e\x0d\xd3\x3e\x54\xd8\xf9\xfc\x71\xc2\x24\x1e\xe3\x85\
\xcf\x80\x9b\x14\x05\x36\x6f\x4b\xd3\x3f\xe0\x05\x2b\x84\x52\xe5\
\xa9\x1b\x2f\xa3\xb5\x7e\x3f\xae\xaf\x3b\x93\xdf\x06\x34\x4c\xfb\
\xa3\xc2\x91\xc9\x11\xc8\x93\xd8\x54\x6d\xea\x47\x4c\x43\xb0\xf9\
\xcd\x34\xaa\x4a\xa0\x58\x76\x7d\x8d\xe8\xb5\xd7\xf3\xdc\xa2\xd6\
\x27\xbb\x0f\xd6\x4d\xcb\x9f\x33\x6e\x03\x36\x03\x5d\x45\xef\x12\
\x2a\xc6\xe7\x04\x9c\xfc\xab\xf4\x81\x66\xdf\x91\xc5\x0a\x30\x0f\
\x78\xd1\x30\xb4\x39\xab\xd6\xfe\x9d\x8c\x65\x13\x6a\xd1\x58\x78\
\x4d\x1d\xbe\x4f\x25\xf0\x78\x24\x9c\x5a\x36\x76\x70\xc6\x8a\xfe\
\x5b\x80\x47\x81\x39\x00\xbe\x52\xcb\xdc\x03\xf5\x80\x0b\x30\x00\
\x34\x24\xda\x2d\x7f\x42\x02\xfb\x8e\x2c\x5e\x01\xfc\xaa\xf0\xe4\
\x65\xdb\x69\x1e\x5b\xf3\x36\xba\x26\x68\x9d\x69\x70\xf5\x65\xd3\
\x2a\x75\x8c\xfe\x75\x24\x9c\xfa\x6d\xb1\x89\x19\x2b\xfa\xeb\x80\
\xcf\x84\x94\xd3\xbe\xba\xbf\x01\x57\xab\x02\xd8\x9d\x68\xb7\xe6\
\x4f\x18\x42\x3f\x7c\xd0\x4c\xbe\xd5\xf1\xf1\x43\x9a\xaa\x68\x02\
\x1f\x01\x6c\xda\x7b\x1f\x8f\xbe\x91\x40\x34\xaf\x44\x08\x87\x8f\
\x8f\xda\x9c\xec\xf3\x2a\x45\xe0\x91\x78\x32\x74\x47\xb1\x89\xee\
\x87\xeb\xd3\x9a\x7b\xac\xb9\x36\xb3\xd5\x2d\x28\xa1\xb1\x92\x27\
\xb2\xb6\xe5\xfa\x33\x9e\xe7\xdf\x5b\x53\x05\xad\x97\x6e\xa0\xe3\
\xd8\x77\xf0\x25\x28\x02\xa4\x80\xaa\x93\xb7\xa2\xe6\x5e\x45\x4a\
\xc9\x1d\xdf\x6c\x44\x55\x2b\x26\x6c\xcc\x8f\x84\x53\xbb\x8b\x4d\
\x5c\x75\xd7\x37\xb6\x54\x67\xdf\x89\x80\x42\xa2\xdd\x12\x25\x5b\
\x09\x60\xad\xaa\x2a\xe4\x6c\x8f\xdd\x5d\xf3\x86\x2e\x1a\x56\x14\
\x24\xe4\x9a\xd6\x01\x43\x2a\xc3\xc6\xf8\xe9\x4a\x2a\x32\xbb\xe2\
\xc9\xd0\xf4\x78\x32\x34\xee\x2f\x52\x9d\x4d\xda\x79\x53\x8f\x4d\
\xa6\x17\xea\x18\x99\x74\xde\x2a\x52\xd2\xa6\x91\x0d\x75\x81\x74\
\x70\x5c\xc9\x3b\x7b\x32\x01\x2b\xd3\x28\xec\x8d\x84\x53\xb2\x68\
\x1d\x85\x34\xb0\x7d\x52\xaa\x44\xdb\x72\xfd\x3d\xf0\x2f\xf1\xaa\
\x96\x90\x6b\x7a\xa9\xa8\x96\xa1\xa7\x9f\x40\x4f\xff\x02\x5f\x0a\
\xae\x9a\x57\xc3\x9c\x59\x26\x95\xe9\x39\xd9\x10\x09\xa7\x6e\x9b\
\x7c\x8b\x57\x1c\x3b\x41\x41\x78\x5d\x13\xf6\x06\x4e\xed\xcf\x90\
\xea\x6c\x14\x21\xf9\x47\x72\x90\x8c\xe5\x57\xca\x0b\xdf\x8b\x27\
\x43\x37\x04\x25\xf0\x18\x80\xe2\x1e\x2a\x29\x7d\x65\xa7\x7f\x08\
\xa2\x1e\xd3\x10\x6c\xde\xd6\x1f\xe8\x10\x34\x06\x7f\x0e\x44\x20\
\x16\x75\x0e\x03\x5b\xf1\xfb\xd0\x06\xd7\x95\xec\xd2\xac\x96\x37\
\x11\xd2\x21\x6b\x49\x36\x6f\xeb\xaf\xcc\xe1\x0b\x5a\xe3\xc9\xd0\
\x45\x41\x3c\x00\xf0\x07\x84\x8e\x71\xfa\xbe\xbc\x4c\x39\x41\x7f\
\xa3\xcf\xc5\xae\x7b\x08\x21\x1c\x8e\x9f\x70\xf9\xa8\xcb\xae\x94\
\x17\xa2\x81\x08\xc4\xa2\xce\xab\x40\x0f\x38\xa8\xb9\x1d\xa5\x7b\
\xfe\x86\x15\xf8\xfa\xd7\x10\x02\xde\xee\x18\xc0\xb6\x2b\x92\xce\
\x97\x07\xf5\x00\xc0\xb5\x20\x30\x4f\x5c\x8b\x90\x13\xf7\xf2\xc2\
\x87\x5c\xcb\x1b\x20\x1d\x14\x45\xf0\xd2\x96\xbe\x52\x2a\xf4\x64\
\x51\x5f\x6c\x4f\x28\x8b\x40\x2c\xea\xec\x07\xb6\xa2\xe8\x18\x7d\
\x3f\x2d\x99\xd0\x52\x34\x90\x0d\x75\x0e\x37\x5c\x74\x74\x66\x83\
\xe6\xc3\xbf\x8a\xef\x09\xe5\x79\x00\xe0\x46\xc0\x57\xb3\xcf\xa3\
\x65\x5e\x28\xdd\x2c\x6b\x17\xe3\xd6\x3e\x0a\x48\x3a\x0f\x66\xe9\
\x4e\x05\x3a\xcc\x3f\x3d\x39\xc5\x68\x12\x68\x5b\xae\x5f\x02\xbc\
\x07\x0e\x56\xcb\x4e\x7c\x23\x3c\xe1\xad\x52\x40\xcd\xf1\xf3\x11\
\xde\xbf\x51\x55\x85\x3b\xbf\xdd\x84\xeb\x96\x9d\x13\x27\x23\xe1\
\xd4\x59\x95\xc8\x81\xe1\x50\x7a\x1f\xf8\x0d\x18\x54\xf5\x46\x10\
\xde\x67\x25\x64\x40\xb0\xce\xfe\x10\x30\x70\x5d\x49\xe2\xed\xf4\
\x54\x42\x29\xf0\x46\x56\x8c\xc4\x0a\x90\x1b\x90\x19\xaa\x7b\x2e\
\x44\xc8\x74\x89\x7c\x50\xc9\x35\xc5\x10\x38\x74\x7d\xe2\xd0\x7b\
\xba\xac\xd6\x7b\x29\xb0\x67\xf2\xa2\x63\x99\x68\x5b\xae\xef\x04\
\xae\x40\xba\x64\xcf\xe9\x47\x8a\xda\x09\xaf\x35\x7b\xbf\x8b\x9a\
\x7b\x0d\x5d\x17\xdc\x71\x53\x23\xde\x99\xbb\x8d\x45\x91\x70\x6a\
\x4b\x79\x72\x57\x99\x88\x45\x9d\x2b\x81\x83\x08\x8d\xea\xee\x46\
\x84\xdf\x3d\xe1\xb5\x4e\xc3\xe3\x20\x1d\x6c\x5b\x72\xf8\xcc\x52\
\xe5\xc5\xe5\x1a\x3f\x25\x02\x79\x12\x17\x0e\x1d\xc2\x15\xaa\x8f\
\xb7\xa2\x59\x7f\x2d\xea\x4b\x4f\xff\x12\xd2\x18\xda\xe0\xb6\xbf\
\x3b\x58\xca\xdd\x33\x23\xe1\xd4\xfe\xa9\x09\x8e\x53\x44\x2c\xea\
\x2c\x06\x1e\x01\x17\xa3\x77\x09\x46\xdf\xdd\xe3\x9e\x26\x7c\x70\
\xcd\xdb\x47\xbe\x0f\x64\xc6\xc5\xd0\x1e\xa0\x36\x12\x4e\x1d\x9b\
\xba\x62\x1a\x00\xb1\xa8\xf3\x20\xf0\x13\x84\xee\x68\x99\x67\xa9\
\xf9\xa4\x0e\xd5\xda\x34\xe2\x0d\xa9\x80\xe2\x6c\xfd\x5c\xcc\x1c\
\xad\x2b\xad\x8b\x84\x53\xf3\x22\xe1\xd4\x60\x10\x1b\x2a\xd2\xff\
\xb6\x2d\xd7\x67\x00\xff\x04\xbe\x38\x24\xd9\xd4\x90\x6b\x8c\xa1\
\x59\xaf\xa2\x5a\xcf\x01\x43\x3f\xd0\x2d\x5e\x50\x4f\x63\xbd\x6a\
\x03\x77\x47\xc2\xa9\xf5\x95\x58\xbb\xa2\xff\x6a\xd0\xb6\x5c\x5f\
\x0a\xac\x1d\xb1\x78\xcc\x42\xdf\xbf\xb9\xd9\xf1\xa4\xdf\xb8\xe8\
\x8a\x9e\x4c\xa5\xd6\x54\x2a\x49\x20\x16\x75\xd6\x33\xf4\x6b\xfe\
\x8f\x46\x19\x2f\x60\xf1\x82\xfa\x65\xaa\x46\x75\x25\x8d\xaf\xb8\
\x07\xc6\x78\x43\x07\xce\x93\x12\xf3\xfc\x59\x46\xcf\xc3\xf7\x0c\
\xf6\xf0\x7f\x8c\xc7\x7f\x00\xf4\xc5\x17\xbc\x3a\x13\xef\x97\x00\
\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
"
qt_resource_name = b"\
\x00\x03\
\x00\x00\x70\x37\
\x00\x69\
\x00\x6d\x00\x67\
\x00\x0c\
\x09\x57\x90\xa7\
\x00\x75\
\x00\x64\x00\x73\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
"
qt_resource_struct_v1 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
"
qt_resource_struct_v2 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x6e\x86\x31\xef\xa3\
"
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
if qt_version < [5, 8, 0]:
rcc_version = 1
qt_resource_struct = qt_resource_struct_v1
else:
rcc_version = 2
qt_resource_struct = qt_resource_struct_v2
def qInitResources():
QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()

View File

@ -1,2 +0,0 @@
PYTHONPATH=./src:${PYTHONPATH}

View File

@ -1,4 +0,0 @@
/bin
/udsclient*
/udsclient-*.tar.gz
/*.rpm

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/${PROJECT_DIR_NAME}/src</path>
</pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">system-2.7</pydev_property>
</pydev_project>

View File

@ -1,8 +0,0 @@
/udsclient-opensuse-[0-9]*.spec
/udsclient-[0-9]*.spec
/debian/udsclient
/targz
/UDSClientDir
/UDSClient*.AppImage
/appimage*
/UDSClient.desktop

Some files were not shown because too many files have changed in this diff Show More