diff --git a/actors/.gitignore b/actors/.gitignore index bbe31bbe..bd0cb678 100644 --- a/actors/.gitignore +++ b/actors/.gitignore @@ -1,2 +1,5 @@ bin *_enterprise* +udsactor*.deb +udsactor*.build +udsactor*.changes diff --git a/actors/linux/Makefile b/actors/linux/Makefile new file mode 100644 index 00000000..8221cd31 --- /dev/null +++ b/actors/linux/Makefile @@ -0,0 +1,24 @@ +SRCDIR = ../src +LIBDIR = $(DESTDIR)/usr/share/pyshared/UDSActor +BINDIR = $(DESTDIR)/usr/bin +CFGDIR = $(DESTDIR)/etc/udsactor +clean: + rm -rf $(DESTDIR) +install: + mkdir -p $(LIBDIR) + mkdir -p $(BINDIR) + mkdir -p $(CFGDIR) + + mkdir -p $(LIBDIR)/ + cp -r $(SRCDIR)/udsactor $(LIBDIR)/udsactor + rm -rf $(LIBDIR)/*.py[co] + + cp $(SRCDIR)/UDSActorConfig.py $(LIBDIR) + cp $(SRCDIR)/UDSActorUser.py $(LIBDIR) + cp $(SRCDIR)/setup_dialog_ui.py $(LIBDIR) + + # chmod 0755 $(BINDIR)/udsactor +uninstall: + rm -rf $(LIBDIR) + # rm -f $(BINDIR)/udsactor + rm -rf $(CFGDIR) \ No newline at end of file diff --git a/actors/linux/UDS_Actor_Configuration.desktop b/actors/linux/UDS_Actor_Configuration.desktop new file mode 100644 index 00000000..1e4f44f1 --- /dev/null +++ b/actors/linux/UDS_Actor_Configuration.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=UDS Actor Configuration +Version=1.0 +Exec=/usr/bin/UDSActorConfig +Comment=UDS Actor Configuration Application. (Must be executed as root) +Icon=/usr/share/pyshared/UDSActor/img/uds.png +Type=Application +Terminal=false +StartupNotify=true +Encoding=UTF-8 +Categories=Settings;System; \ No newline at end of file diff --git a/actors/linux/debian/changelog b/actors/linux/debian/changelog new file mode 100644 index 00000000..ff6d5eef --- /dev/null +++ b/actors/linux/debian/changelog @@ -0,0 +1,5 @@ +udsactor (1.7.0) UNRELEASED; urgency=medium + + * Initial release. + + -- Adolfo Gómez García Mon, 17 Nov 2014 05:32:41 +0100 diff --git a/actors/linux/debian/compat b/actors/linux/debian/compat new file mode 100644 index 00000000..f11c82a4 --- /dev/null +++ b/actors/linux/debian/compat @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/actors/linux/debian/config b/actors/linux/debian/config new file mode 100755 index 00000000..fc8c69c2 --- /dev/null +++ b/actors/linux/debian/config @@ -0,0 +1,45 @@ +#!/bin/sh -e + +. /usr/share/debconf/confmodule +db_version 2.0 + +# This conf script is capable of backing up +db_capb backup + +# Although configuration file can be modified "by hand", is not responsability of this config script +# to manage that changes (we have an user space configuration tool...) +#omsk=`umask` +#umask 0077 +#if [ -f /etc/udsactor/udsactor.cfg ]; then +# TMPFILE=$(mktemp /tmp/udsactor.cfg.XXXXX) +# trap "rm -f $TMPFILE" 0 +# cat /etc/udsactor/udsactor.cfg | sed -e "s/\\[.*\\]//; s/ *= */=/; s/False/false/; s/True/true/" > $TMPFILE +# . $TMPFILE +# db_set udsactor/host $host +# db_set udsactor/secure $ssl +# db_set udsactor/masterKey $masterKey +#fi +#umask $omsk + + +STATE=1 +while [ "$STATE" != 0 -a "$STATE" != 4 ]; do + case "$STATE" in + 1) + db_input high udsactor/host || true + ;; + 2) + db_input high udsactor/secure || true + ;; + 3) + db_input high udsactor/masterKey || true + ;; + esac + + if db_go; then + STATE=$(($STATE + 1)) + else + STATE=$(($STATE - 1)) + fi + +done diff --git a/actors/linux/debian/configfiles b/actors/linux/debian/configfiles new file mode 100644 index 00000000..d0518fed --- /dev/null +++ b/actors/linux/debian/configfiles @@ -0,0 +1 @@ +/etc/udsactor/udsactor.cfg diff --git a/actors/linux/debian/control b/actors/linux/debian/control new file mode 100644 index 00000000..6e92c18f --- /dev/null +++ b/actors/linux/debian/control @@ -0,0 +1,17 @@ +Source: udsactor +Section: admin +Priority: optional +Maintainer: Adolfo Gómez García +Build-Depends: debhelper (>= 7), po-debconf +Standards-Version: 3.9.2 +Homepage: http://www.virtualcable.es + +Package: udsactor +Section: admin +Priority: optional +Architecture: all +Depends: python-requests (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python (>=2.7), ${shlibs:Depends}, ${misc:Depends} +Description: Actor for Universal Desktop Services (UDS) Broker + This package provides the required components to allow this machine to work on an environment managed by UDS Broker. + . + diff --git a/actors/linux/debian/copyright b/actors/linux/debian/copyright new file mode 100644 index 00000000..cef2d43f --- /dev/null +++ b/actors/linux/debian/copyright @@ -0,0 +1,26 @@ +Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 +Name: udsactor +Maintainer: Adolfo Gómez García +Source: http://www.udsenterprise.com/ + +Copyright: 2014 Virtual Cable S.L.U. +License: BSD-3-clause + +License: GPL-2+ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +. +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +. +On Debian systems, the full text of the GNU General Public +License version 2 can be found in the file +`/usr/share/common-licenses/GPL-2'. \ No newline at end of file diff --git a/actors/linux/debian/dirs b/actors/linux/debian/dirs new file mode 100644 index 00000000..2c662cfd --- /dev/null +++ b/actors/linux/debian/dirs @@ -0,0 +1,2 @@ +/usr/bin/ +/usr/share/pyshared/udsactor diff --git a/actors/linux/debian/docs b/actors/linux/debian/docs new file mode 100644 index 00000000..b2b2a781 --- /dev/null +++ b/actors/linux/debian/docs @@ -0,0 +1 @@ +readme.txt diff --git a/actors/linux/debian/files b/actors/linux/debian/files new file mode 100644 index 00000000..3ec59737 --- /dev/null +++ b/actors/linux/debian/files @@ -0,0 +1 @@ +udsactor_1.7.0_all.deb admin optional diff --git a/actors/linux/debian/init b/actors/linux/debian/init new file mode 100755 index 00000000..2d6ed885 --- /dev/null +++ b/actors/linux/debian/init @@ -0,0 +1,21 @@ +#!/bin/sh -e +### BEGIN INIT INFO +# Provides: uds-actor +# Required-Start: $local_fs $remote_fs $network $syslog $named +# Required-Stop: $local_fs $remote_fs $network $syslog $named +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: UDS Actor +### END INIT INFO +# + +case "$1" in + start|stop|restart) + /usr/bin/udsactor $1 + ;; + force-reload) + ./actor restart + ;; + *) echo "Usage: $0 {start|stop|restart|force-reload}" >&2; exit 1 ;; +esac + diff --git a/actors/linux/debian/postinst b/actors/linux/debian/postinst new file mode 100755 index 00000000..f455eac2 --- /dev/null +++ b/actors/linux/debian/postinst @@ -0,0 +1,5 @@ +#! /bin/bash -e + +ln -fs "/usr/share/pyshared/UDSActor/UDSActorUser.py" "/usr/bin/UDSActorUser" + +ln -fs "/usr/share/pyshared/UDSActor/UDSActorConfig.py" "/usr/bin/UDSActorConfig" \ No newline at end of file diff --git a/actors/linux/debian/prerm b/actors/linux/debian/prerm new file mode 100755 index 00000000..c25ff88d --- /dev/null +++ b/actors/linux/debian/prerm @@ -0,0 +1,5 @@ +#! /bin/bash -e + +rm "/usr/bin/UDSActorUser" + +rm "/usr/bin/UDSActorConfig" \ No newline at end of file diff --git a/actors/linux/debian/rules b/actors/linux/debian/rules new file mode 100755 index 00000000..cacfb707 --- /dev/null +++ b/actors/linux/debian/rules @@ -0,0 +1,44 @@ +#!/usr/bin/make -f +# -*- makefile -*- +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp +build: build-arch build-indep +build-arch: build-stamp +build-indep: build-stamp +build-stamp: configure-stamp + dh_testdir + $(MAKE) + touch $@ +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + dh_clean +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + $(MAKE) DESTDIR=$(CURDIR)/debian/udsactor install +binary-arch: build install + # emptyness +binary-indep: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installdebconf + dh_installinit --no-start + dh_python2=python + dh_compress + dh_link + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb +binary: binary-indep +.PHONY: build clean binary-indep binary install configure diff --git a/actors/linux/debian/source/format b/actors/linux/debian/source/format new file mode 100644 index 00000000..46ebe026 --- /dev/null +++ b/actors/linux/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) \ No newline at end of file diff --git a/actors/linux/debian/templates b/actors/linux/debian/templates new file mode 100644 index 00000000..4310a4b2 --- /dev/null +++ b/actors/linux/debian/templates @@ -0,0 +1,20 @@ +Template: udsactor/host +Type: string +Default: +Description: UDS Server address: + The actor needs the address of the server in order to communicate with it. + Provide here full address (or i) of the UDS server + +Template: udsactor/secure +Type: boolean +Default: true +Description: Use secure (https) connection to communicate with UDS server? + If selected, the communication will be done using https. + If not selected, the communication will be done using http + +Template: udsactor/masterKey +Type: string +Default: +Description: Master Key: + This key is available on UDS Administration interface. + Look for it under configuration, on Security tab. diff --git a/actors/linux/debian/udsactor/DEBIAN/conffiles b/actors/linux/debian/udsactor/DEBIAN/conffiles new file mode 100644 index 00000000..4277cc95 --- /dev/null +++ b/actors/linux/debian/udsactor/DEBIAN/conffiles @@ -0,0 +1 @@ +/etc/init.d/udsactor diff --git a/actors/linux/debian/udsactor/DEBIAN/config b/actors/linux/debian/udsactor/DEBIAN/config new file mode 100755 index 00000000..f0c90500 --- /dev/null +++ b/actors/linux/debian/udsactor/DEBIAN/config @@ -0,0 +1,43 @@ +#!/bin/sh -e + +. /usr/share/debconf/confmodule +db_version 2.0 + +# This conf script is capable of backing up +db_capb backup + +omsk=`umask` +umask 0077 +if [ -f /etc/udsactor/udsactor.cfg ]; then + TMPFILE=$(mktemp /tmp/udsactor.cfg.XXXXX) + trap "rm -f $TMPFILE" 0 + cat /etc/udsactor/udsactor.cfg | sed -e "s/\\[.*\\]//; s/ *= */=/; s/False/false/; s/True/true/" > $TMPFILE + . $TMPFILE + db_set udsactor/host $host + db_set udsactor/secure $ssl + db_set udsactor/masterKey $masterKey +fi +umask $omsk + + +STATE=1 +while [ "$STATE" != 0 -a "$STATE" != 4 ]; do + case "$STATE" in + 1) + db_input high udsactor/host || true + ;; + 2) + db_input high udsactor/secure || true + ;; + 3) + db_input high udsactor/masterKey || true + ;; + esac + + if db_go; then + STATE=$(($STATE + 1)) + else + STATE=$(($STATE - 1)) + fi + +done diff --git a/actors/linux/debian/udsactor/DEBIAN/control b/actors/linux/debian/udsactor/DEBIAN/control new file mode 100644 index 00000000..ed009c1f --- /dev/null +++ b/actors/linux/debian/udsactor/DEBIAN/control @@ -0,0 +1,11 @@ +Package: udsactor +Version: 1.7.0 +Architecture: all +Maintainer: Adolfo Gómez García +Installed-Size: 234 +Depends: python-requests (>= 0.8.2), python-qt4 (>= 4.9), python-six (>= 1.1), python (>= 2.7), debconf (>= 0.5) | debconf-2.0 +Section: admin +Priority: optional +Homepage: http://www.virtualcable.es +Description: Actor for Universal Desktop Services (UDS) Broker + This package provides the required components to allow this machine to work on an environment managed by UDS Broker. diff --git a/actors/linux/debian/udsactor/DEBIAN/md5sums b/actors/linux/debian/udsactor/DEBIAN/md5sums new file mode 100644 index 00000000..7a8f6a83 --- /dev/null +++ b/actors/linux/debian/udsactor/DEBIAN/md5sums @@ -0,0 +1,38 @@ +ca7e77fff581f83a326087af73def0af usr/share/doc/udsactor/changelog.gz +bb1b64d1c0e2a59e0a46180df5a33edf usr/share/doc/udsactor/copyright +fc4e41285d388d33b5f149ebe4b93236 usr/share/doc/udsactor/readme.txt +86020414868d2ede37e76a911aa211c5 usr/share/pyshared/UDSActor/UDSActorConfig.py +b14dee09070d95cac6321a13442f15b8 usr/share/pyshared/UDSActor/UDSActorUser.py +db1be7dd5fec00a3c31587fe8ce8e56f usr/share/pyshared/UDSActor/setup_dialog_ui.py +d677aa5ec15d9497b654a04f3d671958 usr/share/pyshared/UDSActor/udsactor/REST.py +f9e493d70d160a8efef1b4a451bbf3a9 usr/share/pyshared/UDSActor/udsactor/REST.pyc +68b329da9893e34099c7d8ad5cb9c940 usr/share/pyshared/UDSActor/udsactor/__init__.py +6ce52a1d20242671989a4f6f77dae7c9 usr/share/pyshared/UDSActor/udsactor/__init__.pyc +976710a766859591a543eebdf351537b usr/share/pyshared/UDSActor/udsactor/certs.py +bcfca2062d45ec44b77b1e65d611bcc1 usr/share/pyshared/UDSActor/udsactor/httpserver.py +0350137ae586a7babe54a75e68fa1fe7 usr/share/pyshared/UDSActor/udsactor/ipc.py +73082793d61ef3ce40736de43c9b3314 usr/share/pyshared/UDSActor/udsactor/linux/UDSActorService.py +3ef55d64ebda86651c3d882c5c649389 usr/share/pyshared/UDSActor/udsactor/linux/__init__.py +cb8c315488747aa8ed72e4812193d05d usr/share/pyshared/UDSActor/udsactor/linux/__init__.pyc +fee6ea5f27ec7aa294c2c0741b22c5e6 usr/share/pyshared/UDSActor/udsactor/linux/daemon.py +c599495230cbd3f6495ba4a6edc169d9 usr/share/pyshared/UDSActor/udsactor/linux/log.py +f8d0998e7eeb8632ca6f6cc921a2dcae usr/share/pyshared/UDSActor/udsactor/linux/log.pyc +49dccc874df32d0dbeaf7ffcbc9cc176 usr/share/pyshared/UDSActor/udsactor/linux/operations.py +1234f7f854901dc53a6d67105e72404d usr/share/pyshared/UDSActor/udsactor/linux/renamer/__init__.py +e958b3a317d41f350a412992f5016e4d usr/share/pyshared/UDSActor/udsactor/linux/renamer/debian.py +93e6a3d18c34fda4a42d96b42407e52b usr/share/pyshared/UDSActor/udsactor/linux/store.py +6c9138dd95b06cf3c9202e52367022e8 usr/share/pyshared/UDSActor/udsactor/linux/store.pyc +28b691cb635888d496598bedf184cdb0 usr/share/pyshared/UDSActor/udsactor/log.py +34e5967293a350b454ec3c3b3fb7ff0a usr/share/pyshared/UDSActor/udsactor/log.pyc +9bfbf09895f4319c4a5ebef858ff9faa usr/share/pyshared/UDSActor/udsactor/operations.py +d284d7b4f544b0d7efe27188e843a783 usr/share/pyshared/UDSActor/udsactor/service.py +c88b37b89a88734788e23b2f71a7fcd5 usr/share/pyshared/UDSActor/udsactor/store.py +70d9f2cf3ed936cc728a24e72d9104fa usr/share/pyshared/UDSActor/udsactor/store.pyc +68b513e35d67d3e783947ec35b60cfa7 usr/share/pyshared/UDSActor/udsactor/utils.py +29fa19836ed1b7b607d34c450510bafd usr/share/pyshared/UDSActor/udsactor/utils.pyc +517440806336069b121ea587ec0ed80f usr/share/pyshared/UDSActor/udsactor/windows/SENS.py +046cf7d994c0b635157016f3eb66d501 usr/share/pyshared/UDSActor/udsactor/windows/UDSActorService.py +3ef55d64ebda86651c3d882c5c649389 usr/share/pyshared/UDSActor/udsactor/windows/__init__.py +65bd1af1cf3744b17c35f6f77753a847 usr/share/pyshared/UDSActor/udsactor/windows/log.py +21bc9f1dc13170e181926b23f788829a usr/share/pyshared/UDSActor/udsactor/windows/operations.py +a659e2092e1abcf06155c35986707eb0 usr/share/pyshared/UDSActor/udsactor/windows/store.py diff --git a/actors/linux/debian/udsactor/DEBIAN/postinst b/actors/linux/debian/udsactor/DEBIAN/postinst new file mode 100755 index 00000000..f455eac2 --- /dev/null +++ b/actors/linux/debian/udsactor/DEBIAN/postinst @@ -0,0 +1,5 @@ +#! /bin/bash -e + +ln -fs "/usr/share/pyshared/UDSActor/UDSActorUser.py" "/usr/bin/UDSActorUser" + +ln -fs "/usr/share/pyshared/UDSActor/UDSActorConfig.py" "/usr/bin/UDSActorConfig" \ No newline at end of file diff --git a/actors/linux/debian/udsactor/DEBIAN/postrm b/actors/linux/debian/udsactor/DEBIAN/postrm new file mode 100755 index 00000000..169d8952 --- /dev/null +++ b/actors/linux/debian/udsactor/DEBIAN/postrm @@ -0,0 +1,20 @@ +#!/bin/sh +set -e +# Automatically added by dh_installinit +if [ "$1" = "purge" ] ; then + update-rc.d udsactor remove >/dev/null +fi + + +# In case this system is running systemd, we make systemd reload the unit files +# to pick up changes. +if [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section +# Automatically added by dh_installdebconf +if [ "$1" = purge ] && [ -e /usr/share/debconf/confmodule ]; then + . /usr/share/debconf/confmodule + db_purge +fi +# End automatically added section diff --git a/actors/linux/debian/udsactor/DEBIAN/prerm b/actors/linux/debian/udsactor/DEBIAN/prerm new file mode 100755 index 00000000..c25ff88d --- /dev/null +++ b/actors/linux/debian/udsactor/DEBIAN/prerm @@ -0,0 +1,5 @@ +#! /bin/bash -e + +rm "/usr/bin/UDSActorUser" + +rm "/usr/bin/UDSActorConfig" \ No newline at end of file diff --git a/actors/linux/debian/udsactor/DEBIAN/templates b/actors/linux/debian/udsactor/DEBIAN/templates new file mode 100644 index 00000000..4310a4b2 --- /dev/null +++ b/actors/linux/debian/udsactor/DEBIAN/templates @@ -0,0 +1,20 @@ +Template: udsactor/host +Type: string +Default: +Description: UDS Server address: + The actor needs the address of the server in order to communicate with it. + Provide here full address (or i) of the UDS server + +Template: udsactor/secure +Type: boolean +Default: true +Description: Use secure (https) connection to communicate with UDS server? + If selected, the communication will be done using https. + If not selected, the communication will be done using http + +Template: udsactor/masterKey +Type: string +Default: +Description: Master Key: + This key is available on UDS Administration interface. + Look for it under configuration, on Security tab. diff --git a/actors/linux/debian/udsactor/etc/init.d/udsactor b/actors/linux/debian/udsactor/etc/init.d/udsactor new file mode 100755 index 00000000..2d6ed885 --- /dev/null +++ b/actors/linux/debian/udsactor/etc/init.d/udsactor @@ -0,0 +1,21 @@ +#!/bin/sh -e +### BEGIN INIT INFO +# Provides: uds-actor +# Required-Start: $local_fs $remote_fs $network $syslog $named +# Required-Stop: $local_fs $remote_fs $network $syslog $named +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: UDS Actor +### END INIT INFO +# + +case "$1" in + start|stop|restart) + /usr/bin/udsactor $1 + ;; + force-reload) + ./actor restart + ;; + *) echo "Usage: $0 {start|stop|restart|force-reload}" >&2; exit 1 ;; +esac + diff --git a/actors/linux/debian/udsactor/usr/share/doc/udsactor/changelog.gz b/actors/linux/debian/udsactor/usr/share/doc/udsactor/changelog.gz new file mode 100644 index 00000000..1615d1c9 Binary files /dev/null and b/actors/linux/debian/udsactor/usr/share/doc/udsactor/changelog.gz differ diff --git a/actors/linux/debian/udsactor/usr/share/doc/udsactor/copyright b/actors/linux/debian/udsactor/usr/share/doc/udsactor/copyright new file mode 100644 index 00000000..cef2d43f --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/doc/udsactor/copyright @@ -0,0 +1,26 @@ +Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 +Name: udsactor +Maintainer: Adolfo Gómez García +Source: http://www.udsenterprise.com/ + +Copyright: 2014 Virtual Cable S.L.U. +License: BSD-3-clause + +License: GPL-2+ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +. +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +. +On Debian systems, the full text of the GNU General Public +License version 2 can be found in the file +`/usr/share/common-licenses/GPL-2'. \ No newline at end of file diff --git a/actors/linux/debian/udsactor/usr/share/doc/udsactor/readme.txt b/actors/linux/debian/udsactor/usr/share/doc/udsactor/readme.txt new file mode 100644 index 00000000..64c14f77 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/doc/udsactor/readme.txt @@ -0,0 +1 @@ +This package provides the actor needed for using with UDS infrastructure. diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/UDSActorConfig.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/UDSActorConfig.py new file mode 100755 index 00000000..1165e019 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/UDSActorConfig.py @@ -0,0 +1,112 @@ +# -*- 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 __future__ import unicode_literals + +import sys +from PyQt4 import QtCore, QtGui +import six + +from udsactor import store +from udsactor import REST +from udsactor import utils +from udsactor.log import logger + +from setup_dialog_ui import Ui_UdsActorSetupDialog + + +class MyForm(QtGui.QDialog): + + def __init__(self, data, parent=None): + QtGui.QDialog.__init__(self, parent) + self.ui = Ui_UdsActorSetupDialog() + self.ui.setupUi(self) + if data is not None: + logger.debug('Setting configuration parameters in form: {}'.format(data)) + self.ui.host.setText(data.get('host', '')) + self.ui.masterKey.setText(data.get('masterKey', '')) + self.ui.useSSl.setCurrentIndex(1 if data.get('ssl', False) is True else 0) + self.ui.logLevelComboBox.setCurrentIndex(int(data.get('logLevel', '10000')) / 10000 - 1) + + def _getCfg(self): + return { + 'host': six.text_type(self.ui.host.text()), + 'masterKey': six.text_type(self.ui.masterKey.text()), + 'ssl': self.ui.useSSl.currentIndex() == 1, + 'logLevel': (self.ui.logLevelComboBox.currentIndex() + 1) * 10000 + } + + def textChanged(self): + enableButtons = self.ui.host.text() != '' and self.ui.masterKey.text() != '' + self.ui.testButton.setEnabled(enableButtons) + self.ui.saveButton.setEnabled(enableButtons) + + def cancelAndDiscard(self): + logger.debug('Cancelling changes') + self.close() + + def testParameters(self): + logger.debug('Testing connection') + try: + cfg = self._getCfg() + api = REST.Api( + cfg['host'], cfg['masterKey'], cfg['ssl'], scrambledResponses=True) + api.test() + QtGui.QMessageBox.information( + self, 'Test Passed', 'The test was executed successfully', QtGui.QMessageBox.Ok) + logger.info('Test was passed successfully') + except Exception as e: + logger.info('Test error: {}'.format(utils.exceptionToMessage(e))) + QtGui.QMessageBox.critical(self, 'Test Error', utils.exceptionToMessage(e), QtGui.QMessageBox.Ok) + + def acceptAndSave(self): + cfg = self._getCfg() + store.writeConfig(cfg) + self.close() + +if __name__ == "__main__": + app = QtGui.QApplication(sys.argv) + + if store.checkPermissions() is False: + QtGui.QMessageBox.critical(None, 'Notice', 'This Program must be executed as administrator', QtGui.QMessageBox.Ok) + sys.exit(1) + + # Read configuration + cfg = store.readConfig() + + if cfg is not None: + logger.setLevel(int(cfg.get('logLevel', 20000))) + else: + logger.setLevel(20000) + + myapp = MyForm(cfg) + myapp.show() + sys.exit(app.exec_()) diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/UDSActorUser.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/UDSActorUser.py new file mode 100755 index 00000000..13c94359 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/UDSActorUser.py @@ -0,0 +1,160 @@ +# -*- 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 __future__ import unicode_literals + +import sys +from PyQt4 import QtGui +from PyQt4 import QtCore +import cPickle +from udsactor import ipc +from udsactor import utils +from udsactor.log import logger +from udsactor.service import IPC_PORT + + +class MessagesProcessor(QtCore.QThread): + + logoff = QtCore.pyqtSignal(name='logoff') + displayMessage = QtCore.pyqtSignal(QtCore.QString, name='displayMessage') + script = QtCore.pyqtSignal(QtCore.QString, name='script') + exit = QtCore.pyqtSignal(name='exit') + information = QtCore.pyqtSignal(dict, name='information') + + def __init__(self): + super(self.__class__, self).__init__() + try: + self.ipc = ipc.ClientIPC(IPC_PORT) + self.ipc.start() + except Exception: + self.ipc = None + + self.running = False + + def stop(self): + self.running = False + if self.ipc: + self.ipc.stop() + + def requestInformation(self): + if self.ipc: + info = self.ipc.requestInformation() + logger.debug('Request information: {}'.format(info)) + + def run(self): + if self.ipc is None: + return + self.running = True + while self.running and self.ipc.running: + try: + msg = self.ipc.getMessage() + if msg is None: + break + msgId, data = msg + logger.debug('Got Message on User Space: {}:{}'.format(msgId, data)) + if msgId == ipc.MSG_MESSAGE: + self.displayMessage.emit(QtCore.QString.fromUtf8(data)) + elif msgId == ipc.MSG_LOGOFF: + self.logoff.emit() + elif msgId == ipc.MSG_SCRIPT: + self.script.emit(QtCore.QString.fromUtf8(data)) + elif msgId == ipc.MSG_INFORMATION: + self.information.emit(cPickle.loads(data)) + except Exception as e: + try: + logger.error('Got error on IPC thread {}'.format(utils.exceptionToMessage(e))) + except: + logger.error('Got error on IPC thread (an unicode error??)') + + if self.ipc.running is False and self.running is True: + logger.warn('Lost connection with Service, closing program') + + self.exit.emit() + + +class UDSSystemTray(QtGui.QSystemTrayIcon): + def __init__(self, app_, parent=None): + self.app = app_ + + style = app.style() + icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_DesktopIcon)) + + QtGui.QSystemTrayIcon.__init__(self, icon, parent) + self.menu = QtGui.QMenu(parent) + exitAction = self.menu.addAction("Exit") + exitAction.triggered.connect(self.quit) + self.setContextMenu(self.menu) + self.ipc = MessagesProcessor() + self.ipc.start() + + self.ipc.displayMessage.connect(self.displayMessage) + self.ipc.exit.connect(self.quit) + self.ipc.script.connect(self.executeScript) + self.ipc.logoff.connect(self.loggof) + self.ipc.information.connect(self.information) + + # Pre generate a request for information (general parameters) to daemon/service + self.ipc.requestInformation() + + self.counter = 0 + + def displayMessage(self, message): + self.counter += 1 + print(message.toUtf8(), '--', self.counter) + + def executeScript(self, message): + self.counter += 1 + print(message.toUtf8(), '--', self.counter) + + def loggof(self): + self.counter += 1 + print("Loggof --", self.counter) + + def information(self, info): + self.counter += 1 + print("Information:", info, '--', self.counter) + + def quit(self): + self.ipc.stop() + + self.app.quit() + +if __name__ == '__main__': + app = QtGui.QApplication(sys.argv) + + if not QtGui.QSystemTrayIcon.isSystemTrayAvailable(): + QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.") + sys.exit(1) + + trayIcon = UDSSystemTray(app) + + trayIcon.show() + sys.exit(app.exec_()) diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/setup_dialog_ui.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/setup_dialog_ui.py new file mode 100644 index 00000000..460a5feb --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/setup_dialog_ui.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'setup-dialog.ui' +# +# Created: Wed Nov 12 04:50:26 2014 +# by: PyQt4 UI code generator 4.11.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_UdsActorSetupDialog(object): + def setupUi(self, UdsActorSetupDialog): + UdsActorSetupDialog.setObjectName(_fromUtf8("UdsActorSetupDialog")) + UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal) + UdsActorSetupDialog.resize(400, 243) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(UdsActorSetupDialog.sizePolicy().hasHeightForWidth()) + UdsActorSetupDialog.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Verdana")) + font.setPointSize(9) + UdsActorSetupDialog.setFont(font) + UdsActorSetupDialog.setAutoFillBackground(False) + UdsActorSetupDialog.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates)) + UdsActorSetupDialog.setSizeGripEnabled(False) + UdsActorSetupDialog.setModal(True) + self.testButton = QtGui.QPushButton(UdsActorSetupDialog) + self.testButton.setEnabled(False) + self.testButton.setGeometry(QtCore.QRect(20, 160, 361, 23)) + self.testButton.setObjectName(_fromUtf8("testButton")) + self.saveButton = QtGui.QPushButton(UdsActorSetupDialog) + self.saveButton.setEnabled(False) + self.saveButton.setGeometry(QtCore.QRect(20, 190, 101, 23)) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.saveButton.sizePolicy().hasHeightForWidth()) + self.saveButton.setSizePolicy(sizePolicy) + self.saveButton.setObjectName(_fromUtf8("saveButton")) + self.cancelButton = QtGui.QPushButton(UdsActorSetupDialog) + self.cancelButton.setGeometry(QtCore.QRect(260, 190, 121, 23)) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cancelButton.sizePolicy().hasHeightForWidth()) + self.cancelButton.setSizePolicy(sizePolicy) + self.cancelButton.setObjectName(_fromUtf8("cancelButton")) + self.layoutWidget = QtGui.QWidget(UdsActorSetupDialog) + self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 361, 146)) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.formLayout = QtGui.QFormLayout(self.layoutWidget) + self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setMargin(0) + self.formLayout.setVerticalSpacing(16) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.label = QtGui.QLabel(self.layoutWidget) + self.label.setObjectName(_fromUtf8("label")) + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label) + self.host = QtGui.QLineEdit(self.layoutWidget) + self.host.setAcceptDrops(False) + self.host.setObjectName(_fromUtf8("host")) + self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.host) + self.label_3 = QtGui.QLabel(self.layoutWidget) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.label_3) + self.masterKey = QtGui.QLineEdit(self.layoutWidget) + self.masterKey.setObjectName(_fromUtf8("masterKey")) + self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.masterKey) + self.label_4 = QtGui.QLabel(self.layoutWidget) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.label_4) + self.useSSl = QtGui.QComboBox(self.layoutWidget) + self.useSSl.setObjectName(_fromUtf8("useSSl")) + self.useSSl.addItem(_fromUtf8("")) + self.useSSl.addItem(_fromUtf8("")) + self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.useSSl) + self.logLevelLabel = QtGui.QLabel(self.layoutWidget) + self.logLevelLabel.setObjectName(_fromUtf8("logLevelLabel")) + self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.logLevelLabel) + self.logLevelComboBox = QtGui.QComboBox(self.layoutWidget) + self.logLevelComboBox.setObjectName(_fromUtf8("logLevelComboBox")) + self.logLevelComboBox.addItem(_fromUtf8("")) + self.logLevelComboBox.setItemText(0, _fromUtf8("DEBUG")) + self.logLevelComboBox.addItem(_fromUtf8("")) + self.logLevelComboBox.setItemText(1, _fromUtf8("INFO")) + self.logLevelComboBox.addItem(_fromUtf8("")) + self.logLevelComboBox.setItemText(2, _fromUtf8("EROR")) + self.logLevelComboBox.addItem(_fromUtf8("")) + self.logLevelComboBox.setItemText(3, _fromUtf8("FATAL")) + self.formLayout.setWidget(3, QtGui.QFormLayout.FieldRole, self.logLevelComboBox) + + self.retranslateUi(UdsActorSetupDialog) + self.logLevelComboBox.setCurrentIndex(1) + QtCore.QObject.connect(self.host, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), UdsActorSetupDialog.textChanged) + QtCore.QObject.connect(self.masterKey, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), UdsActorSetupDialog.textChanged) + QtCore.QObject.connect(self.cancelButton, QtCore.SIGNAL(_fromUtf8("pressed()")), UdsActorSetupDialog.cancelAndDiscard) + QtCore.QObject.connect(self.testButton, QtCore.SIGNAL(_fromUtf8("pressed()")), UdsActorSetupDialog.testParameters) + QtCore.QObject.connect(self.saveButton, QtCore.SIGNAL(_fromUtf8("pressed()")), UdsActorSetupDialog.acceptAndSave) + QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog) + + def retranslateUi(self, UdsActorSetupDialog): + UdsActorSetupDialog.setWindowTitle(_translate("UdsActorSetupDialog", "UDS Actor Configuration", None)) + self.testButton.setToolTip(_translate("UdsActorSetupDialog", "Click to test the selecter parameters", None)) + self.testButton.setWhatsThis(_translate("UdsActorSetupDialog", "

Click on this button to test the server host and master key parameters.

A window will be displayed with results after the test is executed.


This button will only be active if all parameters are filled.

", None)) + self.testButton.setText(_translate("UdsActorSetupDialog", "Test parameters", None)) + self.saveButton.setToolTip(_translate("UdsActorSetupDialog", "Accepts changes and saves them", None)) + self.saveButton.setWhatsThis(_translate("UdsActorSetupDialog", "Clicking on this button will accept all changes and save them, closing the configuration window", None)) + self.saveButton.setText(_translate("UdsActorSetupDialog", "Accept && Save", None)) + self.cancelButton.setToolTip(_translate("UdsActorSetupDialog", "Cancel all changes and discard them", None)) + self.cancelButton.setWhatsThis(_translate("UdsActorSetupDialog", "Discards all changes and closes the configuration window", None)) + self.cancelButton.setText(_translate("UdsActorSetupDialog", "Cancel && Discard", None)) + self.label.setText(_translate("UdsActorSetupDialog", "UDS Server Host", None)) + self.host.setToolTip(_translate("UdsActorSetupDialog", "Uds Broker Server Addres. Use IP or FQDN", None)) + self.host.setWhatsThis(_translate("UdsActorSetupDialog", "Enter here the UDS Broker Addres using either its IP address or its FQDN address", None)) + self.label_3.setText(_translate("UdsActorSetupDialog", "UDS Master Key", None)) + self.masterKey.setToolTip(_translate("UdsActorSetupDialog", "Master key to communicate with UDS Broker", None)) + self.masterKey.setWhatsThis(_translate("UdsActorSetupDialog", "

Enter the Master Key (found on UDS Configuration section) of the UDS Broker to allow communication of the Actor with Broker

", None)) + self.label_4.setText(_translate("UdsActorSetupDialog", "Security", None)) + self.useSSl.setToolTip(_translate("UdsActorSetupDialog", "Select communication security with broker", None)) + self.useSSl.setWhatsThis(_translate("UdsActorSetupDialog", "

Select the security for communications with UDS Broker.

The recommended method of communication is Use SSL, but selection needs to be acording to your broker configuration.

", None)) + self.useSSl.setItemText(0, _translate("UdsActorSetupDialog", "Do not use SSL", None)) + self.useSSl.setItemText(1, _translate("UdsActorSetupDialog", "Use SSL", None)) + self.logLevelLabel.setText(_translate("UdsActorSetupDialog", "Log Level", None)) + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/REST.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/REST.py new file mode 100644 index 00000000..e006d8fd --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/REST.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 201 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-msg=E1101,W0703 + +from __future__ import unicode_literals + +import requests +import logging +import json +import uuid +import six +import codecs + +from .utils import exceptionToMessage + +VERIFY_CERT = False + + +class RESTError(Exception): + ERRCODE = 0 + + +class ConnectionError(RESTError): + ERRCODE = -1 + + +# Errors ""raised"" from broker +class InvalidKeyError(RESTError): + ERRCODE = 1 + + +class UnmanagedHostError(RESTError): + ERRCODE = 2 + + +class UserServiceNotFoundError(RESTError): + ERRCODE = 3 + + +class OsManagerError(RESTError): + ERRCODE = 4 + + +# Disable warnings log messages +try: + import urllib3 # @UnusedImport +except Exception: + from requests.packages import urllib3 # @Reimport + +try: + urllib3.disable_warnings() # @UndefinedVariable +except Exception: + pass # In fact, isn't too important, but wil log warns to logging file + + +def ensureResultIsOk(result): + if 'error' not in result: + return + + for i in (InvalidKeyError, UnmanagedHostError, UserServiceNotFoundError, OsManagerError): + if result['error'] == i.ERRCODE: + raise i(result['result']) + + err = RESTError(result['result']) + err.ERRCODE = result['error'] + raise err + + +def unscramble(value): + if value is None or value == '': + return value + + value = bytearray(codecs.decode(value, 'hex')) + + n = 0x32 + result = [] + for ch in value: + c = ch ^ n + n = (n + c) & 0xFF + result.append(six.int2byte(c)) + + return b''.join(result)[::-1].decode('utf8') + + +class Api(object): + def __init__(self, host, masterKey, ssl, scrambledResponses=False): + self.host = host + self.masterKey = masterKey + self.useSSL = ssl + self.scrambledResponses = scrambledResponses + self.uuid = None + self.url = "{}://{}/rest/actor/".format(('http', 'https')[ssl], self.host) + self.secretKey = six.text_type(uuid.uuid4()) + self.newerRequestLib = 'verify' in requests.sessions.Session.__attrs__ + # Disable logging requests messages except for errors, ... + logging.getLogger("requests").setLevel(logging.ERROR) + + def _getUrl(self, method, key=None, ids=None): + url = self.url + method + params = [] + if key is not None: + params.append('key=' + key) + if ids is not None: + params.append('id=' + ids) + + if len(params) > 0: + url += '?' + '&'.join(params) + + return url + + def _request(self, url, data=None): + try: + if data is None: + # Old requests version does not support verify, but they do not checks ssl certificate + if self.newerRequestLib: + r = requests.get(url, verify=VERIFY_CERT) + else: + r = requests.get(url) # Always ignore certs?? + else: + if self.newerRequestLib: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=VERIFY_CERT) + else: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}) + + r = json.loads(r.content) # Using instead of r.json() to make compatible with oooold rquests lib versions + except requests.exceptions.RequestException as e: + raise ConnectionError(e) + except Exception as e: + raise ConnectionError(exceptionToMessage(e)) + + ensureResultIsOk(r) + + if self.scrambledResponses is True: + # test && init are not scrambled, even if rest of messages are + try: + r['result'] = unscramble(r['result']) + except Exception as e: # Can't unscramble, return result "as is" + r['warning'] = True + + return r + + @property + def isConnected(self): + return self.uuid is not None + + def test(self): + url = self._getUrl('test', self.masterKey) + return self._request(url)['result'] + + def init(self, ids): + ''' + Ids is a comma separated values indicating MAC=ip + ''' + url = self._getUrl('init', key=self.masterKey, ids=ids) + self.uuid, self.mac = self._request(url)['result'] + return self.uuid + + def postMessage(self, msg, data, processData=True): + if self.uuid is None: + raise ConnectionError('REST api has not been initialized') + + if processData: + data = json.dumps({'data': data}) + url = self._getUrl('/'.join([self.uuid, msg])) + return self._request(url, data)['result'] + + def notifyComm(self, url): + return self.postMessage('notifyComms', url) + + def login(self, username): + return self.postMessage('login', username) + + def logout(self, username): + return self.postMessage('logout', username) + + def information(self): + return self.postMessage('information', '') + + def setReady(self, ipsInfo): + data = ','.join(['{}={}'.format(v[0], v[1]) for v in ipsInfo]) + return self.postMessage('ready', data) + + def notifyIpChanges(self, ipsInfo): + data = ','.join(['{}={}'.format(v[0], v[1]) for v in ipsInfo]) + return self.postMessage('ip', data) + + def log(self, logLevel, message): + data = json.dumps({'message': message, 'level': logLevel}) + return self.postMessage('log', data, processData=False) + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/__init__.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/__init__.py @@ -0,0 +1 @@ + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/certs.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/certs.py new file mode 100644 index 00000000..d98dc8eb --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/certs.py @@ -0,0 +1,103 @@ +# -*- 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 tempfile import gettempdir +from os.path import exists, join + +CERTFILE = 'UDSActor.pem' + +def createSelfSignedCert(force=False): + + certFile = join(gettempdir(), CERTFILE) + + if exists(certFile) and not force: + return certFile + + certData = '''-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCb50K3mIznNklz +yVAD7xSQOSJQ6+NPXj7U9/4zLZ+TvmbQ7RqUUsxbfxHbeRnoYTWV2nKk4+tHqmvz +ujLSS/loFhTSMqtrLn7rowSYJoQhKOUkAiQlWkqCfItWgL5pJopDpNHFul9Rn3ds +PMWQTiGeUNR4Y3RnBhr1Q1BsqAzf4m6zFUmgLPPmVLdF4uJ3Tuz8TSy2gWLs5aSr +5do4WamwUfYjRSVMJECmwjUM4rQ8SQgg0sHBeBuDUGNBvBQFac1G7qUcMReeu8Zr +DUtMsXma/l4rA8NB5CRmTrQbTBF4l+jb2BDFebDqDUK1Oqs9X35yOQfDOAFYHiix +PX0IsXOZAgMBAAECggEBAJi3000RrIUZUp6Ph0gzPMuCjDEEwWiQA7CPNX1gpb8O +dp0WhkDhUroWIaICYPSXtOwUTtVjRqivMoxPy1Thg3EIoGC/rdeSdlXRHMEGicwJ +yVyalFnatr5Xzg5wkxVh4XMd0zeDt7e3JD7s0QLo5lm1CEzd77qz6lhzFic5/1KX +bzdULtTlq60dazg2hEbcS4OmM1UMCtRVDAsOIUIZPL0M9j1C1d1iEdYnh2xshKeG +/GOfo95xsgdMlGjtv3hUT5ryKVoEsu+36rGb4VfhPfUvvoVbRx5QZpW+QvxaYh5E +Fi0JEROozFwG31Y++8El7J3yQko8cFBa1lYYUwwpNAECgYEAykT+GiM2YxJ4uVF1 +OoKiE9BD53i0IG5j87lGPnWqzEwYBwnqjEKDTou+uzMGz3MDV56UEFNho7wUWh28 +LpEkjJB9QgbsugjxIBr4JoL/rYk036e/6+U8I95lvYWrzb+rBMIkRDYI7kbQD/mQ +piYUpuCkTymNAu2RisK6bBzJslkCgYEAxVE23OQvkCeOV8hJNPZGpJ1mDS+TiOow +oOScMZmZpail181eYbAfMsCr7ri812lSj98NvA2GNVLpddil6LtS1cQ5p36lFBtV +xQUMZiFz4qVbEak+izL+vPaev/mXXsOcibAIQ+qI/0txFpNhJjpaaSy6vRCBYFmc +8pgSoBnBI0ECgYAUKCn2atnpp5aWSTLYgNosBU4vDA1PShD14dnJMaqyr0aZtPhF +v/8b3btFJoGgPMLxgWEZ+2U4ju6sSFhPf7FXvLJu2QfQRkHZRDbEh7t5DLpTK4Fp +va9vl6Ml7uM/HsGpOLuqfIQJUs87OFCc7iCSvMJDDU37I7ekT2GKkpfbCQKBgBrE +0NeY0WcSJrp7/oqD2sOcYurpCG/rrZs2SIZmGzUhMxaa0vIXzbO59dlWELB8pmnE +Tf20K//x9qA5OxDe0PcVPukdQlH+/1zSOYNliG44FqnHtyd1TJ/gKVtMBiAiE4uO +aSClod5Yosf4SJbCFd/s5Iyfv52NqsAyp1w3Aj/BAoGAVCnEiGUfyHlIR+UH4zZW +GXJMeqdZLfcEIszMxLePkml4gUQhoq9oIs/Kw+L1DDxUwzkXN4BNTlFbOSu9gzK1 +dhuIUGfS6RPL88U+ivC3A0y2jT43oUMqe3hiRt360UQ1GXzp2dMnR9odSRB1wHoO +IOjEBZ8341/c9ZHc5PCGAG8= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIJAIrEIthCfxUCMA0GCSqGSIb3DQEBCwUAMIGNMQswCQYD +VQQGEwJFUzEPMA0GA1UECAwGTWFkcmlkMREwDwYDVQQHDAhBbGNvcmNvbjEMMAoG +A1UECgwDVURTMQ4wDAYDVQQLDAVBY3RvcjESMBAGA1UEAwwJVURTIEFjdG9yMSgw +JgYJKoZIhvcNAQkBFhlzdXBwb3J0QHVkc2VudGVycHJpc2UuY29tMB4XDTE0MTAy +NjIzNDEyNFoXDTI0MTAyMzIzNDEyNFowgY0xCzAJBgNVBAYTAkVTMQ8wDQYDVQQI +DAZNYWRyaWQxETAPBgNVBAcMCEFsY29yY29uMQwwCgYDVQQKDANVRFMxDjAMBgNV +BAsMBUFjdG9yMRIwEAYDVQQDDAlVRFMgQWN0b3IxKDAmBgkqhkiG9w0BCQEWGXN1 +cHBvcnRAdWRzZW50ZXJwcmlzZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCb50K3mIznNklzyVAD7xSQOSJQ6+NPXj7U9/4zLZ+TvmbQ7RqUUsxb +fxHbeRnoYTWV2nKk4+tHqmvzujLSS/loFhTSMqtrLn7rowSYJoQhKOUkAiQlWkqC +fItWgL5pJopDpNHFul9Rn3dsPMWQTiGeUNR4Y3RnBhr1Q1BsqAzf4m6zFUmgLPPm +VLdF4uJ3Tuz8TSy2gWLs5aSr5do4WamwUfYjRSVMJECmwjUM4rQ8SQgg0sHBeBuD +UGNBvBQFac1G7qUcMReeu8ZrDUtMsXma/l4rA8NB5CRmTrQbTBF4l+jb2BDFebDq +DUK1Oqs9X35yOQfDOAFYHiixPX0IsXOZAgMBAAGjUDBOMB0GA1UdDgQWBBRShS90 +5lJTNvYPIEqP3GxWwG5iiDAfBgNVHSMEGDAWgBRShS905lJTNvYPIEqP3GxWwG5i +iDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAU0Sp4gXhQmRVzq+7+ +vRFUkQuPj4Ga/d9r5Wrbg3hck3+5pwe9/7APoq0P/M0DBhQpiJKjrD6ydUevC+Y/ +43ZOJPhMlNw0o6TdQxOkX6FDwQanLLs7sfvJvqtVzYn3nuRFKT3dvl7Zg44QMw2M +ay42q59fAcpB4LaDx/i7gOYSS5eca3lYW7j7YSr/+ozXK2KlgUkuCUHN95lOq+dF +trmV9mjzM4CNPZqKSE7kpHRywgrXGPCO000NvEGSYf82AtgRSFKiU8NWLQSEPdcB +k//2dsQZw2cRZ8DrC2B6Tb3M+3+CA6wVyqfqZh1SZva3LfGvq/C+u+ItguzPqNpI +xtvM +-----END CERTIFICATE-----''' + with open(certFile, "wt") as f: + f.write(certData) + + return certFile + +# At beginning, force certificate creation +createSelfSignedCert(force=True) diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/httpserver.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/httpserver.py new file mode 100644 index 00000000..d514289e --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/httpserver.py @@ -0,0 +1,196 @@ +# -*- 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 +''' + + +import threading +import uuid +import json +import six +from six.moves import socketserver # @UnresolvedImport, pylint: disable=import-error +from six.moves import BaseHTTPServer # @UnresolvedImport, pylint: disable=import-error +import time + +from udsactor.log import logger +from udsactor import utils +from udsactor.certs import createSelfSignedCert +import ssl + +startTime = time.time() + + +class HTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): + uuid = None + ipc = None + lock = threading.Lock() + + def sendJsonError(self, code, message): + self.send_response(code) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps({'error': message})) + return + + def do_GET(self): + # Very simple path & params splitter + path = self.path.split('?')[0][1:].split('/') + try: + params = dict((v.split('=') for v in self.path.split('?')[1].split('&'))) + except: + params = {} + + if path[0] != HTTPServerHandler.uuid: + self.sendJsonError(403, 'Forbidden') + return + + if len(path) != 2: + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + # Send the html message + self.wfile.write(json.dumps("UDS Actor has been running for {} seconds".format(time.time() - startTime))) + return + + try: + operation = getattr(self, 'get_' + path[1]) + result = operation(params) # Protect not POST methods + except AttributeError: + self.sendJsonError(404, 'Method not found') + return + except Exception as e: + logger.error('Got exception executing GET {}: {}'.format(path[1], utils.toUnicode(e.message))) + self.sendJsonError(500, str(e)) + return + + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + # Send the html message + self.wfile.write(json.dumps(result)) + + def do_POST(self): + path = self.path.split('?')[0][1:].split('/') + if path[0] != HTTPServerHandler.uuid: + self.sendJsonError(403, 'Forbidden') + return + + if len(path) != 2: + self.sendJsonError(400, 'Invalid request') + return + + try: + HTTPServerHandler.lock.acquire() + length = int(self.headers.getheader('content-length')) + content = self.rfile.read(length) + print(length, ">>", content, '<<') + params = json.loads(content) + + operation = getattr(self, 'post_' + path[1]) + result = operation(params) # Protect not POST methods + except AttributeError: + self.sendJsonError(404, 'Method not found') + return + except Exception as e: + logger.error('Got exception executing POST {}: {}'.format(path[1], utils.toUnicode(e.message))) + self.sendJsonError(500, str(e)) + return + finally: + HTTPServerHandler.lock.release() + + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + # Send the html message + self.wfile.write(json.dumps(result)) + + def post_logoff(self, params): + logger.debug('Sending LOGOFF to clients') + HTTPServerHandler.ipc.sendLoggofMessage() + return 'ok' + + # Alias + post_logout = post_logoff + + def post_message(self, params): + logger.debug('Sending MESSAGE to clients') + if 'message' not in params: + raise Exception('Invalid message parameters') + HTTPServerHandler.ipc.sendMessageMessage(params['message']) + return 'ok' + + def post_script(self, params): + if 'script' not in params: + raise Exception('Invalid script parameters') + if 'user' in params: + logger.debug('Sending SCRIPT to clients') + HTTPServerHandler.ipc.sendScriptMessage(params['script']) + else: + # Execute script at server space, that is, here + # as a secondary thread + script = params['script'] + + def executor(): + logger.debug('Executing script: {}'.format(script)) + try: + six.exec_(script, None, None) + except Exception as e: + logger.error('Error executing script: {}'.format(e)) + th = threading.Thread(target=executor) + th.start() + return 'ok' + + def get_information(self, params): + # TODO: Return something useful? :) + return 'Information' + + +class HTTPServerThread(threading.Thread): + def __init__(self, address, ipc): + super(self.__class__, self).__init__() + + if HTTPServerHandler.uuid is None: + HTTPServerHandler.uuid = uuid.uuid4().get_hex() + + HTTPServerHandler.ipc = ipc + + self.certFile = createSelfSignedCert() + self.server = socketserver.TCPServer(address, HTTPServerHandler) + self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True) + + def getServerUrl(self): + return 'https://{}:{}/{}'.format(self.server.server_address[0], self.server.server_address[1], HTTPServerHandler.uuid) + + def stop(self): + logger.debug('Stopping REST Service') + self.server.shutdown() + + def run(self): + self.server.serve_forever() diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/ipc.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/ipc.py new file mode 100644 index 00000000..32d1b16d --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/ipc.py @@ -0,0 +1,389 @@ +# -*- 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 __future__ import unicode_literals + +import socket +import threading +import sys +import six +import traceback +import pickle + +from udsactor.utils import toUnicode +from udsactor.log import logger + +# The IPC Server will wait for connections from clients +# Clients will open socket, and wait for data from server +# The messages sent (from server) will be the following (subject to future changes): +# Message_id Data Action +# ------------ -------- -------------------------- +# MSG_LOGOFF None Logout user from session +# MSG_MESSAGE message,level Display a message with level (INFO, WARN, ERROR, FATAL) # TODO: Include level, right now only has message +# MSG_SCRIPT python script Execute an specific python script INSIDE CLIENT environment (this messages is not sent right now) +# The messages received (sent from client) will be the following: +# Message_id Data Action +# ------------ -------- -------------------------- +# REQ_LOGOUT Logout user from session +# REQ_INFORMATION None Request information from ipc server (maybe configuration parameters in a near future) +# REQ_LOGIN python script Execute an specific python script INSIDE CLIENT environment (this messages is not sent right now) +# +# All messages are in the form: +# BYTE +# 0 1-2 3 4 ... +# MSG_ID DATA_LENGTH (little endian) Data (can be 0 length) +# With a previos "MAGIC" header in fron of each message + +MSG_LOGOFF = 0xA1 +MSG_MESSAGE = 0xB2 +MSG_SCRIPT = 0xC3 +MSG_INFORMATION = 0xD4 + +# Request messages +REQ_INFORMATION = MSG_INFORMATION +REQ_LOGIN = 0xE5 +REQ_LOGOUT = MSG_LOGOFF + +VALID_MESSAGES = (MSG_LOGOFF, MSG_MESSAGE, MSG_SCRIPT, MSG_INFORMATION) + +REQ_INFORMATION = 0xAA + +MAGIC = b'\x55\x44\x53\x00' # UDS in hexa with a padded 0 to the right + + +# Allows notifying login/logout from client for linux platform +ALLOW_LOG_METHODS = sys.platform != 'win32' + + +# States for client processor +ST_SECOND_BYTE = 0x01 +ST_RECEIVING = 0x02 +ST_PROCESS_MESSAGE = 0x02 + + +class ClientProcessor(threading.Thread): + def __init__(self, parent, clientSocket): + super(self.__class__, self).__init__() + self.parent = parent + self.clientSocket = clientSocket + self.running = False + self.messages = six.moves.queue.Queue(32) # @UndefinedVariable + + def stop(self): + logger.debug('Stoping client processor') + self.running = False + + def processRequest(self, msg, data): + print('Got message {}, with data {}'.format(msg, data)) + + def run(self): + self.running = True + self.clientSocket.setblocking(0) + + state = None + recv_msg = None + recv_data = None + while self.running: + try: + counter = 1024 + while counter > 0: # So we process at least the incoming queue every XX bytes readed + counter -= 1 + b = self.clientSocket.recv(1) + if b == b'': + break + buf = six.byte2int(b) # Empty buffer, this is set as non-blocking + if state is None: + if buf in (REQ_INFORMATION, REQ_LOGIN, REQ_LOGOUT): + print('State set to {}'.format(buf)) + state = buf + recv_msg = buf + continue # Get next byte + else: + logger.debug('Got unexpected data {}'.format(buf)) + elif state in (REQ_INFORMATION, REQ_LOGIN, REQ_LOGOUT): + print('First length byte is {}'.format(buf)) + msg_len = buf + state = ST_SECOND_BYTE + continue + elif state == ST_SECOND_BYTE: + msg_len += buf << 8 + print('Second length byte is {}, len is {}'.format(buf, msg_len)) + if msg_len == 0: + self.processRequest(recv_msg, None) + state = None + break + state = ST_RECEIVING + recv_data = b'' + continue + elif state == ST_RECEIVING: + recv_data += six.int2byte(buf) + msg_len -= 1 + if msg_len == 0: + self.processRequest(recv_msg, recv_data) + recv_data = None + state = None + break + else: + logger.debug('Got invalid message from request: {}, state: {}'.format(buf, state)) + except socket.error as e: + # If no data is present, no problem at all, pass to check messages + pass + + try: + msg = self.messages.get(block=False) + except six.moves.queue.Empty: # No message got in time @UndefinedVariable + continue + + logger.debug('Got message {}'.format(msg)) + + try: + m = msg[1] if msg[1] is not None else b'' + l = len(m) + data = MAGIC + six.int2byte(msg[0]) + six.int2byte(l & 0xFF) + six.int2byte(l >> 8) + m + try: + self.clientSocket.sendall(data) + except socket.error as e: + # Send data error + logger.debug('Socket connection is no more available: {}'.format(e.args)) + self.running = False + except Exception as e: + logger.error('Invalid message in queue: {}'.format(e)) + + try: + self.clientSocket.close() + except Exception: + pass # If can't close, nothing happens, just end thread + + +class ServerIPC(threading.Thread): + + def __init__(self, listenPort, infoParams=None): + super(self.__class__, self).__init__() + self.port = listenPort + self.running = False + self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.threads = [] + self.infoParams = infoParams + + def stop(self): + logger.debug('Stopping Server IPC') + self.running = False + for t in self.threads: + t.stop() + socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(('localhost', self.port)) + self.serverSocket.close() + + for t in self.threads: + t.join() + + def sendMessage(self, msgId, msgData): + ''' + Notify message to all listening threads + ''' + logger.debug('Sending message {},{} to all clients'.format(msgId, msgData)) + + # Convert to bytes so length is correctly calculated + if isinstance(msgData, six.text_type): + msgData = msgData.encode('utf8') + + for t in self.threads: + if t.isAlive(): + logger.debug('Sending to {}'.format(t)) + t.messages.put((msgId, msgData)) + + def sendLoggofMessage(self): + self.sendMessage(MSG_LOGOFF, '') + + def sendMessageMessage(self, message): + self.sendMessage(MSG_MESSAGE, message) + + def sendScriptMessage(self, script): + self.sendMessage(MSG_SCRIPT, script) + + def cleanupFinishedThreads(self): + ''' + Cleans up current threads list + ''' + aliveThreads = [] + for t in self.threads: + if t.isAlive(): + logger.debug('Thread {} is alive'.format(t)) + aliveThreads.append(t) + self.threads[:] = aliveThreads + + def run(self): + self.running = True + + self.serverSocket.bind(('localhost', self.port)) + self.serverSocket.setblocking(1) + self.serverSocket.listen(4) + + while True: + try: + (clientSocket, address) = self.serverSocket.accept() + # Stop processiong if thread is mean to stop + if self.running is False: + break + logger.debug('Got connection from {}'.format(address)) + + self.cleanupFinishedThreads() # House keeping + + t = ClientProcessor(self, clientSocket) + self.threads.append(t) + t.start() + except Exception as e: + logger.error('Got an exception on Server ipc thread: {}'.format(e)) + + +class ClientIPC(threading.Thread): + def __init__(self, listenPort): + super(ClientIPC, self).__init__() + self.port = listenPort + self.running = False + self.clientSocket = None + self.messages = six.moves.queue.Queue(32) # @UndefinedVariable + + self.connect() + + def stop(self): + self.running = False + + def getMessage(self): + while self.running: + try: + return self.messages.get(timeout=1) + except six.moves.queue.Empty: # @UndefinedVariable + continue + + return None + + def sendRequestMessage(self, msg, data=None): + if data is None: + data = b'' + + if isinstance(data, six.text_type): # Convert to bytes if necessary + data = data.encode('utf-8') + + l = len(data) + msg = six.int2byte(msg) + six.int2byte(l & 0xFF) + six.int2byte(l >> 8) + data + self.clientSocket.sendall(msg) + + def requestInformation(self): + self.sendRequestMessage(REQ_INFORMATION) + + def sendLogin(self, username): + self.sendRequestMessage(REQ_LOGIN, username) + + def sendLogout(self, username): + self.sendRequestMessage(REQ_LOGOUT, username) + + def messageReceived(self): + ''' + Override this method to automatically get notified on new message + received. Message is at self.messages queue + ''' + pass # Messa + + def receiveBytes(self, number): + msg = b'' + while self.running and len(msg) < number: + try: + buf = self.clientSocket.recv(number - len(msg)) + if buf == b'': + self.running = False + break + msg += buf + except socket.timeout: + pass + + if self.running is False: + return None + return msg + + def connect(self): + self.clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.clientSocket.connect(('localhost', self.port)) + self.clientSocket.settimeout(2) # 2 seconds timeout + + def run(self): + self.running = True + + while self.running: + try: + msg = b'' + # We look for magic message header + while self.running: # Wait for MAGIC + try: + buf = self.clientSocket.recv(len(MAGIC) - len(msg)) + if buf == b'': + self.running = False + break + msg += buf + if len(msg) != len(MAGIC): + continue # Do not have message + if msg != MAGIC: # Skip first byte an continue searchong + msg = msg[1:] + continue + break + except socket.timeout: # Timeout is here so we can get stop thread + continue + + # Now we get message basic data (msg + datalen) + msg = bytearray(self.receiveBytes(3)) + + # We have the magic header, here comes the message itself + if msg is None: + continue + + msgId = msg[0] + dataLen = msg[1] + (msg[2] << 8) + if msgId not in VALID_MESSAGES: + raise Exception('Invalid message id: {}'.format(msgId)) + + data = self.receiveBytes(dataLen) + if data is None: + continue + + self.messages.put((msgId, data)) + self.messageReceived() + + except socket.error as e: + logger.error('Communication with server got an error: {}'.format(toUnicode(e.strerror))) + self.running = False + return + except Exception as e: + logger.error('Error: {}'.format(e.args)) + + try: + self.clientSocket.close() + except Exception: + pass # If can't close, nothing happens, just end thread + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/UDSActorService.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/UDSActorService.py new file mode 100644 index 00000000..4bca8688 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/UDSActorService.py @@ -0,0 +1,154 @@ +# -*- 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 __future__ import unicode_literals + +from udsactor import operations + +from udsactor.service import CommonService +from udsactor.service import initCfg +from udsactor.service import IPC_PORT +from udsactor import ipc + +from udsactor.log import logger + +from udsactor.linux.daemon import Daemon +from udsactor.linux import renamer + +import sys + + +class UDSActorSvc(Daemon, CommonService): + def __init__(self, args=None): + Daemon.__init__(self, '/var/run/udsa.pid') + CommonService.__init__(self) + + def rename(self, name, user=None, oldPassword=None, newPassword=None): + ''' + Renames the computer, and optionally sets a password for an user + before this + ''' + + # Check for password change request for an user + if user is not None: + logger.info('Setting password for user {}'.format(user)) + try: + operations.changeUserPassword(user, oldPassword, newPassword) + except Exception as e: + # We stop here without even renaming computer, because the + # process has failed + raise Exception( + 'Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(user, unicode(e))) + + renamer.rename(name) + self.setReady() + + def joinDomain(self, name, domain, ou, account, password): + logger.fatal('Join domain is not supported on linux platforms right now') + + def run(self): + initCfg() + + logger.debug('Running Daemon') + + # Linux daemon will continue running unless something is requested to + if self.interactWithBroker() is False: + logger.debug('Interact with broker returned false, stopping service after a while') + return + + if self.isAlive is False: + logger.debug('The service is not alive after broker interaction, stopping it') + return + + if self.rebootRequested is True: + logger.debug('Reboot has been requested, stopping service') + return + + self.initIPC() + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + counter = 0 + while self.isAlive: + counter += 1 + if counter % 10 == 0: + self.checkIpsChanged() + # In milliseconds, will break + self.doWait(1000) + + self.endIPC() + self.endAPI() + + self.notifyStop() + + +def usage(): + sys.stderr.write("usage: {} start|stop|restart|login 'username'|logout 'username'".format(sys.argv[0])) + sys.exit(2) + +if __name__ == '__main__': + initCfg() + + if len(sys.argv) == 3: + try: + client = ipc.ClientIPC(IPC_PORT) + client.start() + if 'login' == sys.argv[1]: + client.sendLogin(sys.argv[2]) + sys.exit(0) + elif 'logout' == sys.argv[1]: + client.sendLogout(sys.argv[2]) + sys.exit(0) + else: + usage() + except Exception as e: + logger.error(e) + finally: + client.stop() + usage() + + logger.debug('Executing actor') + daemon = UDSActorSvc() + if len(sys.argv) == 2: + if 'start' == sys.argv[1]: + daemon.start() + elif 'stop' == sys.argv[1]: + daemon.stop() + elif 'restart' == sys.argv[1]: + daemon.restart() + else: + usage() + sys.exit(0) + else: + usage() diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/__init__.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/__init__.py new file mode 100644 index 00000000..3a98c780 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/__init__.py @@ -0,0 +1,32 @@ +# -*- 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 __future__ import unicode_literals diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/daemon.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/daemon.py new file mode 100644 index 00000000..9c62b1b8 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/daemon.py @@ -0,0 +1,166 @@ +# -*- 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: : http://www.jejik.com/authors/sander_marechal/ +@see: : http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ +''' + +from __future__ import unicode_literals +import sys +import os +import time +import atexit + + +from signal import SIGTERM + + +class Daemon: + """ + A generic daemon class. + + Usage: subclass the Daemon class and override the run() method + """ + def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """ + 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: + sys.stderr.write("fork #1 failed: {} ({})\n".format(e.errno, e.strerror)) + 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: + sys.stderr.write("fork #2 failed: {} ({})\n".format(e.errno, e.strerror)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = open(self.stdin, 'r') + so = open(self.stdout, 'a+') + se = open(self.stderr, 'a+', 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.delpid) + pid = str(os.getpid()) + with open(self.pidfile, 'w+') as f: + f.write("{}\n".format(pid)) + + def delpid(self): + os.remove(self.pidfile) + + def start(self): + """ + Start the daemon + """ + # Check for a pidfile to see if the daemon already runs + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile %s already exist. Daemon already running?\n" + sys.stderr.write(message % self.pidfile) + sys.exit(1) + + # Start the daemon + self.daemonize() + self.run() + + def stop(self): + """ + Stop the daemon + """ + # Get the pid from the pidfile + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid is None: + sys.stderr.write("pidfile {} does not exist. Daemon not running?\n".format(self.pidfile)) + return # not an error in a restart + + # Try killing the daemon process + try: + while True: + os.kill(pid, SIGTERM) + time.sleep(1) + 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(err) + sys.exit(1) + + def restart(self): + """ + Restart the daemon + """ + self.stop() + self.start() + + # Overridables + def run(self): + """ + You should override this method when you subclass Daemon. It will be called after the process has been + daemonized by start() or restart(). + """ diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/log.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/log.py new file mode 100644 index 00000000..542ef9bf --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/log.py @@ -0,0 +1,80 @@ +# -*- 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 __future__ import unicode_literals + +import logging +import os +import tempfile +import six + +# Valid logging levels, from UDS Broker (uds.core.utils.log) +OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in six.moves.xrange(6)) # @UndefinedVariable + + +class LocalLogger(object): + 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 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: + pass + + # Logger can't be set + self.logger = None + + def log(self, level, message): + # Debug messages are logged to a file + # our loglevels are 10000 (other), 20000 (debug), .... + # logging levels are 10 (debug), 20 (info) + # OTHER = logging.NOTSET + self.logger.log(int(level / 1000) - 10, message) + + def isWindows(self): + return False + + def isLinux(self): + return True diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/operations.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/operations.py new file mode 100644 index 00000000..2a967f09 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/operations.py @@ -0,0 +1,160 @@ +# -*- 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 __future__ import unicode_literals + +import socket +import platform +import fcntl +import os +import struct +import array +import six +from udsactor import utils + + +def _getMacAddr(ifname): + ''' + Returns the mac address of an interface + Mac is returned as unicode utf-8 encoded + ''' + if isinstance(ifname, list): + return dict([(name, _getMacAddr(name)) for name in ifname]) + if isinstance(ifname, six.text_type): + ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))) + return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1]) + except Exception: + return None + + +def _getIpAddr(ifname): + ''' + Returns the ip address of an interface + Ip is returned as unicode utf-8 encoded + ''' + if isinstance(ifname, list): + return dict([(name, _getIpAddr(name)) for name in ifname]) + if isinstance(ifname, six.text_type): + ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return six.text_type(socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack('256s', ifname[:15]) + )[20:24])) + except Exception: + return None + + +def _getInterfaces(): + ''' + 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.tostring() + # 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): + ip, mac = _getIpAddr(ifname), _getMacAddr(ifname) + return (ip, mac) + + +def getComputerName(): + ''' + Returns computer name, with no domain + ''' + return socket.gethostname().split('.')[0] + + +def getNetworkInfo(): + for ifname in _getInterfaces(): + ip, mac = _getIpAndMac(ifname) + yield utils.Bunch(name=ifname, mac=mac, ip=ip) + + +def getDomainName(): + return '' + + +def getLinuxVersion(): + lv = platform.linux_distribution() + return lv[0] + ', ' + lv[1] + + +def reboot(flags=0): + ''' + Simple reboot using os command + ''' + os.system('/sbin/shutdown now -r') + + +def loggoff(): + pass + + +def renameComputer(newName): + pass + + +def joinDomain(domain, ou, account, password, executeInOneStep=False): + pass + + +def changeUserPassword(user, oldPassword, newPassword): + ''' + Simple password change for user using command line + ''' + os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword)) + + +def getIdleDuration(): + return 0 diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/__init__.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/__init__.py new file mode 100644 index 00000000..5d221177 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/__init__.py @@ -0,0 +1,59 @@ +# -*- 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 __future__ import unicode_literals + +import platform +import os +import sys +import pkgutil + +from udsactor.log import logger + +renamers = {} + + +def rename(newName): + distribution = platform.linux_distribution()[0].lower() + if distribution in renamers: + return renamers[distribution](newName) + + logger.error('Renamer for platform "{0}" not found'.format(distribution)) + return False + + +# Do load of packages +def _init(): + pkgpath = os.path.dirname(sys.modules[__name__].__file__) + for _, name, _ in pkgutil.iter_modules([pkgpath]): + __import__(__name__ + '.' + name, globals(), locals()) + +_init() \ No newline at end of file diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/debian.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/debian.py new file mode 100644 index 00000000..c1761656 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/debian.py @@ -0,0 +1,68 @@ +# -*- 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 __future__ import unicode_literals + +from udsactor.linux.renamer import renamers +from udsactor.log import logger + +import os + + +def rename(newName): + ''' + Debian renamer + Expects new host name on newName + Host does not needs to be rebooted after renaming + ''' + logger.debug('using Debian renamer') + + with open('/etc/hostname', 'w') as hostname: + hostname.write(newName) + + # Force system new name + os.system('/bin/hostname %s' % newName) + + # 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 diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/store.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/store.py new file mode 100644 index 00000000..ca2d5d17 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/store.py @@ -0,0 +1,80 @@ +# -*- 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 +''' + + +import six +import os + +DEBUG = False + +CONFIGFILE = '/etc/udsactor/udsactor.cfg' if DEBUG is False else '/tmp/udsactor.cfg' + + +def checkPermissions(): + return True if DEBUG else os.getuid() == 0 + + +def readConfig(): + res = {} + try: + cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable + cfg.optionxform = six.text_type + cfg.read(CONFIGFILE) + # Just reads 'uds' section + for key in cfg.options('uds'): + res[key] = cfg.get('uds', key) + if res[key].lower() in ('true', 'yes', 'si'): + res[key] = True + elif res[key].lower() in ('false', 'no'): + res[key] = False + except Exception: + pass + + return res + + +def writeConfig(data): + cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable + cfg.optionxform = six.text_type + cfg.add_section('uds') + for key, val in data.items(): + cfg.set('uds', key, str(val)) + + # 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) diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/log.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/log.py new file mode 100644 index 00000000..3f2cd3c8 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/log.py @@ -0,0 +1,91 @@ +# -*- 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 __future__ import unicode_literals + +import sys +import six + +if sys.platform == 'win32': + from udsactor.windows.log import LocalLogger # @UnusedImport +else: + from udsactor.linux.log import LocalLogger # @Reimport + +# Valid logging levels, from UDS Broker (uds.core.utils.log) +OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in six.moves.xrange(6)) # @UndefinedVariable + + +class Logger(object): + def __init__(self): + self.logLevel = OTHER + self.logger = LocalLogger() + self.remoteLogger = None + + def setLevel(self, level): + self.logLevel = level + self.logger.log(INFO, 'Setting LogLevel to {}'.format(level)) + + def setRemoteLogger(self, remoteLogger): + self.remoteLogger = remoteLogger + + def log(self, level, message): + if level < self.logLevel: # Skip not wanted messages + return + + # If remote logger is available, notify message to it + try: + if self.remoteLogger is not None and self.remoteLogger.isConnected: + self.remoteLogger.log(level, message) + except Exception as e: + self.logger.log(FATAL, 'Error notifying log to broker: {}'.format(e.message)) + + self.logger.log(level, message) + + def debug(self, message): + self.log(DEBUG, message) + + def warn(self, message): + self.log(WARN, message) + + def info(self, message): + self.log(WARN, message) + + def error(self, message): + self.log(ERROR, message) + + def fatal(self, message): + self.log(FATAL, message) + + def flush(self): + pass + + +logger = Logger() diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/operations.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/operations.py new file mode 100644 index 00000000..fc241ab4 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/operations.py @@ -0,0 +1,40 @@ +# -*- 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=unused-wildcard-import,wildcard-import + +from __future__ import unicode_literals + +import sys +if sys.platform == 'win32': + from .windows.operations import * # @UnusedWildImport +else: + from .linux.operations import * # @UnusedWildImport diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/service.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/service.py new file mode 100644 index 00000000..73290f2a --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/service.py @@ -0,0 +1,270 @@ +# -*- 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 __future__ import unicode_literals + +from udsactor.log import logger + +from . import operations +from . import store +from . import REST +from . import ipc +from . import httpserver +from .utils import exceptionToMessage + +import socket +import time +import random + +IPC_PORT = 39188 + +cfg = None + + +def initCfg(): + global cfg # pylint: disable=global-statement + cfg = store.readConfig() + + if logger.logger.isWindows(): + # Logs will also go to windows event log for services + logger.logger.serviceLogger = True + + if cfg is not None: + logger.setLevel(cfg.get('logLevel', 20000)) + else: + logger.setLevel(20000) + + +class CommonService(object): + def __init__(self): + self.isAlive = True + self.api = None + self.ipc = None + self.httpServer = None + self.rebootRequested = False + self.knownIps = [] + socket.setdefaulttimeout(20) + + def reboot(self): + self.rebootRequested = True + + def setReady(self): + self.api.setReady([(v.mac, v.ip) for v in operations.getNetworkInfo()]) + + def interactWithBroker(self, scrambledResponses=False): + ''' + Returns True to continue to main loop, false to stop & exit service + ''' + # If no configuration is found, stop service + if cfg is None: + logger.fatal('No configuration found, stopping service') + return False + + self.api = REST.Api(cfg['host'], cfg['masterKey'], cfg['ssl'], scrambledResponses=scrambledResponses) + + # Wait for Broker to be ready + counter = 0 + while self.isAlive: + try: + # getNetworkInfo is a generator function + netInfo = tuple(operations.getNetworkInfo()) + self.knownIps = dict(((i.mac, i.ip) for i in netInfo)) + ids = ','.join([i.mac for i in netInfo]) + if ids == '': + # Wait for any network interface to be ready + logger.debug('No network interfaces found, retrying in a while...') + raise Exception() + logger.debug('Ids: {}'.format(ids)) + self.api.init(ids) + # Set remote logger to notify log info to broker + logger.setRemoteLogger(self.api) + + break + except REST.InvalidKeyError: + logger.fatal('Can\'t sync with broker: Invalid broker Master Key') + return False + except REST.UnmanagedHostError: + # Maybe interface that is registered with broker is not enabled already? + # Right now, we thing that the interface connected to broker is + # the interface that broker will know, let's see how this works + logger.fatal('This host is not managed by UDS Broker (ids: {})'.format(ids)) + return False + except Exception as e: + logger.debug('Exception caugh: {}, retrying'.format(exceptionToMessage(e))) + # Any other error is expectable and recoverable, so let's wait a bit and retry again + # but, if too many errors, will log it (one every minute, for + # example) + counter += 1 + if counter % 60 == 0: # Every 5 minutes, raise a log + logger.info('Trying to inititialize connection with broker (last error: {})'.format(exceptionToMessage(e))) + # Wait a bit before next check + self.doWait(5000) + + # Broker connection is initialized, now get information about what to + # do + counter = 0 + while self.isAlive: + try: + logger.debug('Requesting information of what to do now') + info = self.api.information() + data = info.split('\r') + if len(data) != 2: + logger.error('The format of the information message is not correct (got {})'.format(info)) + raise Exception + params = data[1].split('\t') + if data[0] == 'rename': + try: + if len(params) == 1: # Simple rename + self.rename(params[0]) + # Rename with change password for an user + elif len(params) == 4: + self.rename(params[0], params[1], params[2], params[3]) + else: + logger.error('Got invalid parameter for rename operation: {}'.format(params)) + return False + break + except Exception as e: + logger.error('Error at computer renaming stage: {}'.format(e.message)) + return False + elif data[0] == 'domain': + if len(params) != 5: + logger.error('Got invalid parameters for domain message: {}'.format(params)) + return False + self.joinDomain(params[0], params[1], params[2], params[3], params[4]) + break + else: + logger.error('Unrecognized action sent from broker: {}'.format(data[0])) + return False # Stop running service + except REST.UserServiceNotFoundError: + logger.error('The host has lost the sync state with broker! (host uuid changed?)') + return False + except Exception: + counter += 1 + if counter % 60 == 0: + logger.warn('Too many retries in progress, though still trying (last error: {})'.format(e.message.decode('windows-1250', 'ignore'))) + # Any other error is expectable and recoverable, so let's wait + # a bit and retry again + # Wait a bit before next check + self.doWait(5000) + + if self.rebootRequested: + try: + operations.reboot() + except Exception as e: + logger.error('Exception on reboot: {}'.format(e.message)) + return False # Stops service + + return True + + def checkIpsChanged(self): + netInfo = tuple(operations.getNetworkInfo()) + for i in netInfo: + # If at least one ip has changed + if i.mac in self.knownIps and self.knownIps[i.mac] != i.ip: + logger.info('Notifying ip change to broker (mac {}, from {} to {})'.format(i.mac, self.knownIps[i.mac], i.ip)) + try: + # Notifies all interfaces IPs + self.api.notifyIpChanges(((v.mac, v.ip) for v in netInfo)) + # Regenerates Known ips + self.knownIps = dict(((i.mac, i.ip) for i in netInfo)) + except Exception as e: + logger.warn('Got an error notifiying IPs to broker: {} (will retry in a bit)'.format(e.message.decode('windows-1250', 'ignore'))) + + def initIPC(self): + # ****************************************** + # * Initialize listener IPC & REST threads * + # ****************************************** + logger.debug('Starting IPC listener at {}'.format(IPC_PORT)) + self.ipc = ipc.ServerIPC(IPC_PORT) + self.ipc.start() + + if self.api.mac in self.knownIps: + address = (self.knownIps[self.api.mac], random.randrange(32000, 64000)) + logger.debug('Starting REST listener at {}'.format(address)) + self.httpServer = httpserver.HTTPServerThread(address, self.ipc) + self.httpServer.start() + # And notify it to broker + self.api.notifyComm(self.httpServer.getServerUrl()) + + def endIPC(self): + # Remove IPC threads + if self.ipc is not None: + try: + self.ipc.stop() + except: + logger.error('Couln\'t stop ipc server') + if self.httpServer is not None: + try: + self.httpServer.stop() + except: + logger.error('Couln\'t stop REST server') + + def endAPI(self): + if self.api is not None: + try: + self.api.notifyComm(None) + except: + logger.error('Couln\'t remove comms url from broker') + + self.notifyStop() + + # *************************************************** + # Methods that ARE overriden by linux & windows Actor + # *************************************************** + def rename(self, name, user=None, oldPassword=None, newPassword=None): + ''' + Invoked when broker requests a rename action + MUST BE OVERRIDEN + ''' + raise NotImplementedError('Method renamed has not been implemented!') + + def joinDomain(self, name, domain, ou, account, password): + ''' + Invoked when broker requests a "domain" action + MUST BE OVERRIDEN + ''' + raise NotImplementedError('Method renamed has not been implemented!') + + # **************************************** + # Methods that CAN BE overriden by actors + # **************************************** + def doWait(self, miliseconds): + ''' + Invoked to wait a bit + CAN be OVERRIDEN + ''' + time.sleep(float(miliseconds) / 1000) + + def notifyStop(self): + ''' + Overriden to log stop + ''' + logger.info('Service is being stopped') diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/store.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/store.py new file mode 100644 index 00000000..6281eaa8 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/store.py @@ -0,0 +1,39 @@ +# -*- 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=unused-wildcard-import, wildcard-import +from __future__ import unicode_literals + +import sys +if sys.platform == 'win32': + from udsactor.windows.store import * # @UnusedWildImport +else: + from udsactor.linux.store import * # @UnusedWildImport diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/utils.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/utils.py new file mode 100644 index 00000000..9480a6ab --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/utils.py @@ -0,0 +1,72 @@ +# -*- 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 __future__ import unicode_literals + +import sys +import six + +if sys.platform == 'win32': + _fromEncoding = 'windows-1250' +else: + _fromEncoding = 'utf-8' + + +def toUnicode(msg): + try: + if not isinstance(msg, six.text_type): + if isinstance(msg, six.binary_type): + return msg.decode(_fromEncoding, 'ignore') + return six.text_type(msg) + else: + return msg + except Exception: + try: + return six.text_type(msg) + except Exception: + return '' + + +def exceptionToMessage(e): + msg = '' + for arg in e.args: + if isinstance(arg, Exception): + msg = msg + exceptionToMessage(arg) + else: + msg = msg + toUnicode(arg) + '. ' + return msg + + +class Bunch(dict): + def __init__(self, **kw): + dict.__init__(self, kw) + self.__dict__ = self + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/SENS.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/SENS.py new file mode 100644 index 00000000..232706c3 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/SENS.py @@ -0,0 +1,139 @@ +# -*- 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 +''' +# _*_ coding: iso-8859-1 _*_ + +from __future__ import unicode_literals + +import win32com.client +import win32com.server.policy +import os + +from udsactor.log import logger + +# based on python SENS example from +# http://timgolden.me.uk/python/win32_how_do_i/track-session-events.html + +# from Sens.h +SENSGUID_PUBLISHER = "{5fee1bd6-5b9b-11d1-8dd2-00aa004abd5e}" +SENSGUID_EVENTCLASS_LOGON = "{d5978630-5b9f-11d1-8dd2-00aa004abd5e}" + +# from EventSys.h +PROGID_EventSystem = "EventSystem.EventSystem" +PROGID_EventSubscription = "EventSystem.EventSubscription" + +IID_ISensLogon = "{d597bab3-5b9f-11d1-8dd2-00aa004abd5e}" + + +class SensLogon(win32com.server.policy.DesignatedWrapPolicy): + _com_interfaces_ = [IID_ISensLogon] + _public_methods_ = [ + 'Logon', + 'Logoff', + 'StartShell', + 'DisplayLock', + 'DisplayUnlock', + 'StartScreenSaver', + 'StopScreenSaver' + ] + + def __init__(self, api): + self._wrap_(self) + self.api = api + + def Logon(self, *args): + logger.debug('Logon event: {}'.format(args)) + if self.api is not None and self.api.isConnected: + try: + data = self.api.login(args[0]) + logger.debug('Data received for login: {}'.format(data)) + data = data.split('\t') + if len(data) == 2: + logger.debug('Data is valid: {}'.format(data)) + windir = os.environ['windir'] + with open(os.path.join(windir, 'remoteip.txt'), 'w') as f: + f.write(data[0]) + with open(os.path.join(windir, 'remoteh.txt'), 'w') as f: + f.write(data[1]) + except Exception as e: + logger.fatal('Error notifying logon to server: {}'.format(e)) + + def Logoff(self, *args): + logger.debug('Logoff event: {}'.format(args)) + if self.api is not None and self.api.isConnected: + try: + self.api.logout(args[0]) + except Exception as e: + logger.fatal('Error notifying logon to server: {}'.format(e)) + + def StartShell(self, *args): + logevent('StartShell : %s' % [args]) + + def DisplayLock(self, *args): + logevent('DisplayLock : %s' % [args]) + + def DisplayUnlock(self, *args): + logevent('DisplayUnlock : %s' % [args]) + + def StartScreenSaver(self, *args): + # When finished basic actor, we will use this to provide a new parameter: logout on screensaver + # This will allow to easily close sessions of idle users + logevent('StartScreenSaver : %s' % [args]) + + def StopScreenSaver(self, *args): + logevent('StopScreenSaver : %s' % [args]) + + +def logevent(msg): + logger.info(msg) + +# def register(): + # call the CoInitialize to allow the registration to run in an other + # thread + # pythoncom.CoInitialize() + + #logevent('Registring ISensLogon') + + # sl=SensLogon() + # subscription_interface=pythoncom.WrapObject(sl) + + # event_system=win32com.client.Dispatch(PROGID_EventSystem) + + # event_subscription=win32com.client.Dispatch(PROGID_EventSubscription) + # event_subscription.EventClassID=SENSGUID_EVENTCLASS_LOGON + # event_subscription.PublisherID=SENSGUID_PUBLISHER + #event_subscription.SubscriptionName='Python subscription' + # event_subscription.SubscriberInterface=subscription_interface + + #event_system.Store(PROGID_EventSubscription, event_subscription) + + # pythoncom.PumpMessages() + ##logevent('ISensLogon stopped') diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/UDSActorService.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/UDSActorService.py new file mode 100644 index 00000000..aac3c3e9 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/UDSActorService.py @@ -0,0 +1,254 @@ +# -*- 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 __future__ import unicode_literals +# pylint: disable=unused-wildcard-import, wildcard-import + +import win32serviceutil # @UnresolvedImport, pylint: disable=import-error +import win32service # @UnresolvedImport, pylint: disable=import-error +import win32event # @UnresolvedImport, pylint: disable=import-error +import win32com.client # @UnresolvedImport, @UnusedImport, pylint: disable=import-error +import pythoncom # @UnresolvedImport, pylint: disable=import-error +import servicemanager # @UnresolvedImport, pylint: disable=import-error + + +from udsactor import operations +from udsactor.service import CommonService +from udsactor.service import initCfg + +from udsactor.log import logger + +from .SENS import SensLogon +from .SENS import logevent +from .SENS import SENSGUID_EVENTCLASS_LOGON +from .SENS import SENSGUID_PUBLISHER +from .SENS import PROGID_EventSubscription +from .SENS import PROGID_EventSystem + + +class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService): + ''' + This class represents a Windows Service for managing actor interactions + with UDS Broker and Machine + ''' + _svc_name_ = "UDSActor" + _svc_display_name_ = "UDS Actor Service" + _svc_description_ = "UDS Actor for machines managed by UDS Broker" + # 'System Event Notification' is the SENS service + _svc_deps_ = ['EventLog', 'SENS'] + + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + CommonService.__init__(self) + self.hWaitStop = win32event.CreateEvent(None, 1, 0, None) + + def SvcStop(self): + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + self.isAlive = False + win32event.SetEvent(self.hWaitStop) + + SvcShutdown = SvcStop + + def notifyStop(self): + servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, + servicemanager.PYS_SERVICE_STOPPED, + (self._svc_name_, '')) + + def doWait(self, miliseconds): + win32event.WaitForSingleObject(self.hWaitStop, miliseconds) + + def rename(self, name, user=None, oldPassword=None, newPassword=None): + ''' + Renames the computer, and optionally sets a password for an user + before this + ''' + hostName = operations.getComputerName() + + if hostName.lower() == name.lower(): + logger.info('Computer name is now {}'.format(hostName)) + self.setReady() + return + + # Check for password change request for an user + if user is not None: + logger.info('Setting password for user {}'.format(user)) + try: + operations.changeUserPassword(user, oldPassword, newPassword) + except Exception as e: + # We stop here without even renaming computer, because the + # process has failed + raise Exception( + 'Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(user, unicode(e))) + + operations.renameComputer(name) + # Reboot just after renaming + logger.info('Rebooting computer got activate new name {}'.format(name)) + self.reboot() + + def oneStepJoin(self, name, domain, ou, account, password): + ''' + 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) + else: + 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, domain, ou, account, password): + currName = operations.getComputerName() + if currName.lower() == name.lower(): + currDomain = operations.getDomainName() + if currDomain is not None and currDomain.lower() == domain.lower(): + logger.info( + 'Machine {} is part of domain {}'.format(name, domain)) + self.setReady() + else: + operations.joinDomain( + domain, ou, account, password, executeInOneStep=False) + else: + operations.renameComputer(name) + logger.info( + 'Rebooting computer got activate new name {}'.format(name)) + self.reboot() + + def joinDomain(self, name, domain, ou, account, password): + ver = operations.getWindowsVersion() + ver = ver[0] * 10 + ver[1] + logger.info('Starting joining domain {} with name {} (detected operating version: {})'.format( + domain, name, ver)) + # 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 ver >= 60: + self.oneStepJoin(name, domain, ou, account, password) + else: + self.multiStepJoin(name, domain, ou, account, password) + + def SvcDoRun(self): + ''' + Main service loop + ''' + initCfg() + + logger.debug('running SvcDoRun') + 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 com...') + pythoncom.CoInitialize() + + # ******************************************************** + # * Ask brokers what to do before proceding to main loop * + # ******************************************************** + if self.interactWithBroker(scrambledResponses=True) is False: + logger.debug('Interact with broker returned false, stopping service after a while') + self.notifyStop() + win32event.WaitForSingleObject(self.hWaitStop, 5000) + return + + if self.isAlive is False: + logger.debug('The service is not alive after broker interaction, stopping it') + self.notifyStop() + return + + if self.rebootRequested is True: + logger.debug('Reboot has been requested, stopping service') + self.notifyStop() + return + + self.initIPC() + + # ******************************** + # * Registers SENS subscriptions * + # ******************************** + logevent('Registering ISensLogon') + subscription_guid = '{41099152-498E-11E4-8FD3-10FEED05884B}' + sl = SensLogon(self.api) + subscription_interface = pythoncom.WrapObject(sl) + + event_system = win32com.client.Dispatch(PROGID_EventSystem) + + event_subscription = win32com.client.Dispatch(PROGID_EventSubscription) + event_subscription.EventClassID = SENSGUID_EVENTCLASS_LOGON + event_subscription.PublisherID = SENSGUID_PUBLISHER + event_subscription.SubscriptionName = 'UDS Actor subscription' + event_subscription.SubscriptionID = subscription_guid + event_subscription.SubscriberInterface = subscription_interface + + event_system.Store(PROGID_EventSubscription, event_subscription) + + logger.debug('Registered SENS, running main loop') + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + counter = 0 + while self.isAlive: + counter += 1 + # Process SENS messages, This will be a bit asyncronous (1 second + # delay) + pythoncom.PumpWaitingMessages() + if counter % 10 == 0: + self.checkIpsChanged() + # In milliseconds, will break + win32event.WaitForSingleObject(self.hWaitStop, 1000) + + logger.debug('Exited main loop, deregistering SENS') + + # ******************************************* + # * Remove SENS subscription before exiting * + # ******************************************* + event_system.Remove( + PROGID_EventSubscription, "SubscriptionID == " + subscription_guid) + + self.endIPC() # Ends IPC servers + self.endAPI() # And deinitializes REST api if needed + + self.notifyStop() + + +if __name__ == '__main__': + initCfg() + win32serviceutil.HandleCommandLine(UDSActorSvc) diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/__init__.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/__init__.py new file mode 100644 index 00000000..3a98c780 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/__init__.py @@ -0,0 +1,32 @@ +# -*- 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 __future__ import unicode_literals diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/log.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/log.py new file mode 100644 index 00000000..97d57887 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/log.py @@ -0,0 +1,77 @@ +# -*- 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 __future__ import unicode_literals + +import servicemanager # @UnresolvedImport, pylint: disable=import-error +import logging +import os +import tempfile + +# Valid logging levels, from UDS Broker (uds.core.utils.log) +OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in xrange(6)) + + +class LocalLogger(object): + 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 + logging.basicConfig( + filename=os.path.join(tempfile.gettempdir(), 'udsactor.log'), + filemode='a', + format='%(levelname)s %(asctime)s %(message)s', + level=logging.DEBUG + ) + self.logger = logging.getLogger('udsactor') + self.serviceLogger = False + + def log(self, level, message): + # Debug messages are logged to a file + # our loglevels are 10000 (other), 20000 (debug), .... + # logging levels are 10 (debug), 20 (info) + # OTHER = logging.NOTSET + self.logger.log(level / 1000 - 10, message) + + if level < INFO or self.serviceLogger is False: # Only information and above will be on event log + return + + if level < WARN: # Info + servicemanager.LogInfoMsg(message) + elif level < ERROR: # WARN + servicemanager.LogWarningMsg(message) + else: # Error & Fatal + servicemanager.LogErrorMsg(message) + + def isWindows(self): + return True + + def isLinux(self): + return False diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/operations.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/operations.py new file mode 100644 index 00000000..d7929c31 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/operations.py @@ -0,0 +1,194 @@ +# -*- 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 __future__ import unicode_literals + +import win32com.client # @UnresolvedImport, pylint: disable=import-error +import win32net # @UnresolvedImport, pylint: disable=import-error +import win32security # @UnresolvedImport, pylint: disable=import-error +import win32api # @UnresolvedImport, pylint: disable=import-error +import win32con # @UnresolvedImport, pylint: disable=import-error +import ctypes +from ctypes.wintypes import DWORD, LPCWSTR + +from udsactor import utils +from udsactor.log import logger + + +def getErrorMessage(res=0): + msg = win32api.FormatMessage(res) + return msg.decode('windows-1250', 'ignore') + + +def getComputerName(): + return win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname) + + +def getNetworkInfo(): + 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 == '' or ip is None: + continue + yield utils.Bunch(name=obj.Caption, mac=obj.MACAddress, ip=ip) + except Exception: + return + + +def getDomainName(): + ''' + 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(): + return win32api.GetVersionEx() + +EWX_LOGOFF = 0x00000000 +EWX_SHUTDOWN = 0x00000001 +EWX_REBOOT = 0x00000002 +EWX_FORCE = 0x00000004 +EWX_POWEROFF = 0x00000008 +EWX_FORCEIFHUNG = 0x00000010 + + +def reboot(flags=EWX_FORCEIFHUNG | EWX_REBOOT): + 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(): + win32api.ExitWindowsEx(EWX_LOGOFF) + + +def renameComputer(newName): + # Needs admin privileges to work + if ctypes.windll.kernel32.SetComputerNameExW(DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)) == 0: + # 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)) + +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, ou, account, password, executeInOneStep=False): + # 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 = NETSETUP_ACCT_CREATE | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN + + if executeInOneStep: + flags |= NETSETUP_JOIN_WITH_NEW_NAME + + flags = DWORD(flags) + + domain = LPCWSTR(domain) + + # Must be in format "ou=.., ..., dc=...," + ou = LPCWSTR(ou) if ou is not None and ou != '' else None + account = LPCWSTR(account) + password = LPCWSTR(password) + + res = ctypes.windll.netapi32.NetJoinDomain(None, domain, ou, account, password, 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, domain, None, account, password, flags) + if res != 0: + # Log the error + error = getErrorMessage(res) + print res, error + raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain.value, account.value, ', under OU {}'.format(ou.value) if ou.value != None else '', res, error)) + + +def changeUserPassword(user, oldPassword, newPassword): + computerName = LPCWSTR(getComputerName()) + user = LPCWSTR(user) + oldPassword = LPCWSTR(oldPassword) + newPassword = LPCWSTR(newPassword) + + res = ctypes.windll.netapi32.NetUserChangePassword(computerName, user, oldPassword, newPassword) + + if res != 0: + # Log the error, and raise exception to parent + error = getErrorMessage() + raise Exception('Error changing password for user {}: {}'.format(user.value, error)) + + +class LASTINPUTINFO(ctypes.Structure): + _fields_ = [ + ('cbSize', ctypes.c_uint), + ('dwTime', ctypes.c_uint), + ] + + +def getIdleDuration(): + lastInputInfo = LASTINPUTINFO() + lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) + ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) + millis = ctypes.windll.kernel32.GetTickCount() - lastInputInfo.dwTime + return millis / 1000.0 diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/store.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/store.py new file mode 100644 index 00000000..60eb015f --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/store.py @@ -0,0 +1,94 @@ +# -*- 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 __future__ import unicode_literals + +from win32com.shell import shell # @UnresolvedImport, pylint: disable=import-error +import _winreg as wreg # @UnresolvedImport, pylint: disable=import-error +import win32security # @UnresolvedImport, pylint: disable=import-error +import cPickle + + +# Can be changed to whatever we want, but registry key is protected by permissions +def encoder(data): + return data.encode('bz2') + + +def decoder(data): + return data.decode('bz2') + +DEBUG = True + +path = 'Software\\UDSActor' +baseKey = wreg.HKEY_CURRENT_USER if DEBUG is True else wreg.HKEY_LOCAL_MACHINE # @UndefinedVariable + + +def checkPermissions(): + return True if DEBUG else shell.IsUserAnAdmin() + + +def fixRegistryPermissions(handle): + if DEBUG: + return + # 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 unicode(dacl.GetAce(n)[2]) == u'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(): + try: + key = wreg.OpenKey(baseKey, path, 0, wreg.KEY_QUERY_VALUE) # @UndefinedVariable + data, _ = wreg.QueryValueEx(key, '') # @UndefinedVariable + wreg.CloseKey(key) # @UndefinedVariable + return cPickle.loads(decoder(data)) + except Exception: + return None + + +def writeConfig(data): + try: + key = wreg.OpenKey(baseKey, path, 0, wreg.KEY_ALL_ACCESS) # @UndefinedVariable + except Exception: + key = wreg.CreateKeyEx(baseKey, path, 0, wreg.KEY_ALL_ACCESS) # @UndefinedVariable + fixRegistryPermissions(key.handle) + + wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, encoder(cPickle.dumps(data))) # @UndefinedVariable + wreg.CloseKey(key) # @UndefinedVariable diff --git a/actors/linux/readme.txt b/actors/linux/readme.txt new file mode 100644 index 00000000..64c14f77 --- /dev/null +++ b/actors/linux/readme.txt @@ -0,0 +1 @@ +This package provides the actor needed for using with UDS infrastructure. diff --git a/actors/src/UDSActorConfig.py b/actors/src/UDSActorConfig.py old mode 100644 new mode 100755 diff --git a/actors/src/UDSActorUser.py b/actors/src/UDSActorUser.py old mode 100644 new mode 100755 index 30d54b40..13c94359 --- a/actors/src/UDSActorUser.py +++ b/actors/src/UDSActorUser.py @@ -38,6 +38,7 @@ import cPickle from udsactor import ipc from udsactor import utils from udsactor.log import logger +from udsactor.service import IPC_PORT class MessagesProcessor(QtCore.QThread): @@ -51,7 +52,7 @@ class MessagesProcessor(QtCore.QThread): def __init__(self): super(self.__class__, self).__init__() try: - self.ipc = ipc.ClientIPC(39188) + self.ipc = ipc.ClientIPC(IPC_PORT) self.ipc.start() except Exception: self.ipc = None @@ -91,7 +92,7 @@ class MessagesProcessor(QtCore.QThread): try: logger.error('Got error on IPC thread {}'.format(utils.exceptionToMessage(e))) except: - logger.error('Got error on IPC thread (and unicode error??)') + logger.error('Got error on IPC thread (an unicode error??)') if self.ipc.running is False and self.running is True: logger.warn('Lost connection with Service, closing program') @@ -99,9 +100,13 @@ class MessagesProcessor(QtCore.QThread): self.exit.emit() -class SystemTrayIconApp(QtGui.QSystemTrayIcon): - def __init__(self, icon, app, parent=None): - self.app = app +class UDSSystemTray(QtGui.QSystemTrayIcon): + def __init__(self, app_, parent=None): + self.app = app_ + + style = app.style() + icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_DesktopIcon)) + QtGui.QSystemTrayIcon.__init__(self, icon, parent) self.menu = QtGui.QMenu(parent) exitAction = self.menu.addAction("Exit") @@ -149,9 +154,7 @@ if __name__ == '__main__': QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.") sys.exit(1) - style = app.style() - icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_DesktopIcon)) - trayIcon = SystemTrayIconApp(icon, app) + trayIcon = UDSSystemTray(app) trayIcon.show() sys.exit(app.exec_()) diff --git a/actors/src/udsactor/REST.py b/actors/src/udsactor/REST.py index 8e485fe4..e006d8fd 100644 --- a/actors/src/udsactor/REST.py +++ b/actors/src/udsactor/REST.py @@ -38,7 +38,6 @@ import requests import logging import json import uuid -import urllib3 import six import codecs @@ -72,6 +71,18 @@ class OsManagerError(RESTError): ERRCODE = 4 +# Disable warnings log messages +try: + import urllib3 # @UnusedImport +except Exception: + from requests.packages import urllib3 # @Reimport + +try: + urllib3.disable_warnings() # @UndefinedVariable +except Exception: + pass # In fact, isn't too important, but wil log warns to logging file + + def ensureResultIsOk(result): if 'error' not in result: return @@ -110,8 +121,8 @@ class Api(object): self.uuid = None self.url = "{}://{}/rest/actor/".format(('http', 'https')[ssl], self.host) self.secretKey = six.text_type(uuid.uuid4()) + self.newerRequestLib = 'verify' in requests.sessions.Session.__attrs__ # Disable logging requests messages except for errors, ... - urllib3.disable_warnings() logging.getLogger("requests").setLevel(logging.ERROR) def _getUrl(self, method, key=None, ids=None): @@ -130,11 +141,18 @@ class Api(object): def _request(self, url, data=None): try: if data is None: - r = requests.get(url, verify=VERIFY_CERT) + # Old requests version does not support verify, but they do not checks ssl certificate + if self.newerRequestLib: + r = requests.get(url, verify=VERIFY_CERT) + else: + r = requests.get(url) # Always ignore certs?? else: - r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=VERIFY_CERT) + if self.newerRequestLib: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=VERIFY_CERT) + else: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}) - r = r.json() + r = json.loads(r.content) # Using instead of r.json() to make compatible with oooold rquests lib versions except requests.exceptions.RequestException as e: raise ConnectionError(e) except Exception as e: @@ -148,7 +166,6 @@ class Api(object): r['result'] = unscramble(r['result']) except Exception as e: # Can't unscramble, return result "as is" r['warning'] = True - pass return r diff --git a/actors/src/udsactor/linux/UDSActorService.py b/actors/src/udsactor/linux/UDSActorService.py index 6688ad5c..4bca8688 100644 --- a/actors/src/udsactor/linux/UDSActorService.py +++ b/actors/src/udsactor/linux/UDSActorService.py @@ -31,27 +31,23 @@ ''' from __future__ import unicode_literals -from udsactor import store -from udsactor import REST from udsactor import operations -from udsactor import httpserver -from udsactor import ipc -from udsactor import operations from udsactor.service import CommonService from udsactor.service import initCfg +from udsactor.service import IPC_PORT +from udsactor import ipc from udsactor.log import logger -from .daemon import Daemon -from . import renamer +from udsactor.linux.daemon import Daemon +from udsactor.linux import renamer -import time -import random +import sys class UDSActorSvc(Daemon, CommonService): - def __init__(self, args): + def __init__(self, args=None): Daemon.__init__(self, '/var/run/udsa.pid') CommonService.__init__(self) @@ -83,6 +79,7 @@ class UDSActorSvc(Daemon, CommonService): logger.debug('Running Daemon') + # Linux daemon will continue running unless something is requested to if self.interactWithBroker() is False: logger.debug('Interact with broker returned false, stopping service after a while') return @@ -96,3 +93,62 @@ class UDSActorSvc(Daemon, CommonService): return self.initIPC() + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + counter = 0 + while self.isAlive: + counter += 1 + if counter % 10 == 0: + self.checkIpsChanged() + # In milliseconds, will break + self.doWait(1000) + + self.endIPC() + self.endAPI() + + self.notifyStop() + + +def usage(): + sys.stderr.write("usage: {} start|stop|restart|login 'username'|logout 'username'".format(sys.argv[0])) + sys.exit(2) + +if __name__ == '__main__': + initCfg() + + if len(sys.argv) == 3: + try: + client = ipc.ClientIPC(IPC_PORT) + client.start() + if 'login' == sys.argv[1]: + client.sendLogin(sys.argv[2]) + sys.exit(0) + elif 'logout' == sys.argv[1]: + client.sendLogout(sys.argv[2]) + sys.exit(0) + else: + usage() + except Exception as e: + logger.error(e) + finally: + client.stop() + usage() + + logger.debug('Executing actor') + daemon = UDSActorSvc() + if len(sys.argv) == 2: + if 'start' == sys.argv[1]: + daemon.start() + elif 'stop' == sys.argv[1]: + daemon.stop() + elif 'restart' == sys.argv[1]: + daemon.restart() + else: + usage() + sys.exit(0) + else: + usage() diff --git a/actors/src/udsactor/linux/daemon.py b/actors/src/udsactor/linux/daemon.py index 5c95ddff..9c62b1b8 100644 --- a/actors/src/udsactor/linux/daemon.py +++ b/actors/src/udsactor/linux/daemon.py @@ -158,6 +158,7 @@ class Daemon: self.stop() self.start() + # Overridables def run(self): """ You should override this method when you subclass Daemon. It will be called after the process has been diff --git a/actors/src/udsactor/linux/log.py b/actors/src/udsactor/linux/log.py index 8a1d2fda..542ef9bf 100644 --- a/actors/src/udsactor/linux/log.py +++ b/actors/src/udsactor/linux/log.py @@ -44,13 +44,27 @@ class LocalLogger(object): 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 - logging.basicConfig( - filename=os.path.join(tempfile.gettempdir(), 'udsactor.log'), - filemode='a', - format='%(levelname)s %(asctime)s %(message)s', - level=logging.DEBUG - ) - self.logger = logging.getLogger('udsactor') + # 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: + pass + + # Logger can't be set + self.logger = None def log(self, level, message): # Debug messages are logged to a file diff --git a/actors/src/udsactor/windows/UDSActorService.py b/actors/src/udsactor/windows/UDSActorService.py index c173d782..aac3c3e9 100644 --- a/actors/src/udsactor/windows/UDSActorService.py +++ b/actors/src/udsactor/windows/UDSActorService.py @@ -230,8 +230,8 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService): # Process SENS messages, This will be a bit asyncronous (1 second # delay) pythoncom.PumpWaitingMessages() - # if counter % 10 == 0: - # self.checkIpsChanged() + if counter % 10 == 0: + self.checkIpsChanged() # In milliseconds, will break win32event.WaitForSingleObject(self.hWaitStop, 1000)