refs #247 merge pull request 'migrate agent from py2 & qt4/qt5 to py3 & qt6' (#1) from python3 into main

windows-fixes
Natalia Serrano 2024-06-21 11:49:19 +02:00
commit 5294919c98
56 changed files with 450 additions and 829 deletions

20
.gitignore vendored 100644
View File

@ -0,0 +1,20 @@
.DS_Store
.editorconfig
.idea
**/*.swp
__pycache__/
linux/debian/.debhelper/
linux/debian/files
linux/debian/ogagent/
linux/debian/ogagent.debhelper.log
linux/debian/ogagent.postinst.debhelper
linux/debian/ogagent.postrm.debhelper
linux/configure-stamp
linux/build-stamp
ogagent_*_all.deb
ogagent_*_amd64.buildinfo
ogagent_*_amd64.changes
ogagent_*_amd64.build
src/about_dialog_ui.py
src/message_dialog_ui.py
src/OGAgent_rc.py

View File

@ -1,25 +1,22 @@
OGAgent: agente OpenGnsys para sistemas operativos INSTALL.es.txt
====================================================================
Requisitos de creación
----------------------
Sisitema operativo Linux con los siguientes paquetes instalados:
- Subversion
- GNU C++, Python, librerías PyQt4
Sistema operativo Linux con los siguientes paquetes instalados:
- GNU C++, Python, librerías PyQt6
- Creación de instalador Exe (Wine 32 bits, Wine Gecko, Wine Mono, Samba Winbind, Cabextrct)
- Creación de paquetes Deb (debhelper, dpkg-dev)
- Creación de paquetes RPM (rpm-build)
- Creación de paquetes Pkg (xar, bomutils)
Crear instaladores de OGAgent
-----------------------------
- Paso previo: actaulizar componentes gráficos de PyQt para OGAgnet:
- Paso previo: actualizar componentes gráficos de PyQt para OGAgent:
src/update.sh
- Crear paquetes Deb y RPM para distribuciones Linux (requiere permisos de "root"):
sudo linux/build-packages.sh
- Crear paquetes Deb distribuciones debian/ubuntu
linux/build-packages.sh
- Crear paquete Pkg para sistemas operativos macOS X:
sudo macos/build-pkg.sh
@ -27,8 +24,8 @@ Crear instaladores de OGAgent
- Crear el programa instalador para sistemas operativos Windows:
windows/build-windows.sh
- Subir los nuevos ficheros .deb, .rpm, .pkg y .exe generados al directorio
/opt/opengnsys/www/descargas del servidor OpenGnsys.
- Subir los nuevos ficheros .deb, .pkg y .exe generados al directorio
/opt/opengnsys/www/descargas del servidor OpenGnsys.
Instalar OGAgent en cliente modelo
@ -43,20 +40,6 @@ Instalar OGAgent en cliente modelo
- Iniciar el servicio (se iniciará automáticamente en el proceso de arranque):
sudo service ogagent start
- Red Hat, Fedora y derivados (como root):
- Descargar e instalar el agente:
yum install ogagent-Version.noarch.rpm (Red Hat/CentOS)
dnf install ogagent-Version.noarch.rpm (Fedora)
- Configurar el agente:
sed -i "0,/remote=/ s,remote=.*,remote=https://IPServidorOpenGnsys/opengnsys/rest/," /usr/share/OGAgent/cfg/ogagent.cfg
- Puede ser necesario corregir permisos antes de iniciar el servicio:
chmod +x /etc/init.d/ogagent
- Iniciar el servicio (se iniciará automáticamente en el proceso de arranque):
service ogagent start
- OpenSuSE:
(en preparación)
- Windows (como usuario administrador):
- Descargar e instalar el agente ejecutando:
OGAgentSetup-Version.exe
@ -83,5 +66,3 @@ Postconfiguración para clientes clonados
- Ejecutar manualmente o configurar automáticamente OGAgent en los clientes clonados
en el script de postconfiguración tras restuarar imagen:
ogConfigureOgagent NDisco Npart

View File

@ -1,37 +1,6 @@
#!/bin/bash
cd $(dirname "$0")
top=$(pwd)
VERSION="$(cat ../src/VERSION 2>/dev/null)" || VERSION="1.1.1"
RELEASE="1"
# Debian based
dpkg-buildpackage -b -d
# Fix version number.
sed -e "s/version 0.0.0/version ${VERSION}/g" \
-e "s/release 1/release ${RELEASE}/g" ogagent-template.spec > ogagent-$VERSION.spec
# Now fix dependencies for opensuse
sed -e "s/name ogagent/name ogagent-opensuse/g" \
-e "s/version 0.0.0/version ${VERSION}/g" \
-e "s/release 1/release ${RELEASE}/g" \
-e "s/chkconfig//g" \
-e "s/initscripts/insserv/g" \
-e "s/PyQt4/python-qt4/g" \
-e "s/libXScrnSaver/libXss1/g" ogagent-template.spec > ogagent-opensuse-$VERSION.spec
# Right now, ogagent-xrdp-1.7.0.spec is not needed
for pkg in ogagent-$VERSION.spec ogagent-opensuse-$VERSION.spec; do
rm -rf rpm
for folder in SOURCES BUILD RPMS SPECS SRPMS; do
mkdir -p rpm/$folder
done
rpmbuild -v -bb --clean --buildroot=$top/rpm/BUILD/$pkg-root --target noarch $pkg 2>&1
done
#rm ogagent-$VERSION
debuild -D --build=binary --post-clean --lintian-opts --profile debian

View File

@ -1,3 +1,21 @@
ogagent (1.3.0-2) stable; urgency=medium
* Add missing dependency on zenity
-- OpenGnsys developers <info@opengnsys.es> Thu, 25 Apr 2024 15:53:16 +0200
ogagent (1.3.0-1) stable; urgency=medium
* Upgrade to Qt 6
-- OpenGnsys developers <info@opengnsys.es> Thu, 25 Apr 2024 12:50:20 +0200
ogagent (1.2.0) unstable; urgency=medium
* Python 3 and Qt 5 compatibility
-- OpenGnsys developers <info@opengnsys.es> Mon, 4 May 2020 18:00:00 +0100
ogagent (1.1.1b) stable; urgency=medium
* Use python-distro to detect the distribution version

View File

@ -1 +1 @@
9
10

View File

@ -1,7 +1,7 @@
Source: ogagent
Section: admin
Priority: optional
Maintainer: Ramón M. Gómez <ramongomez@us.es>
Maintainer: OpenGnsys developers <info@opengnsys.es>
Build-Depends: debhelper (>= 7), po-debconf
Standards-Version: 3.9.2
Homepage: https://opengnsys.es/
@ -11,8 +11,7 @@ Section: admin
Priority: optional
Architecture: all
Depends:
policykit-1 (>= 0.100), python2 (>=2.7) | python (>= 2.7), python-qt4 (>= 4.9),, python-requests (>= 0.8.2),
python-six (>= 1.1), python-prctl (>= 1.1.1), python-distro, libxss1, ${misc:Depends}
Suggests: gnome-shell-extension-top-icons-plus
policykit-1 (>= 0.100), python3 (>=3.4) | python (>= 3.4), python3-pyqt6, python3-requests,
python3-six, python3-prctl, python3-distro, libxss1, zenity, ${misc:Depends}
Description: OpenGnsys Agent for Operating Systems
This package provides the required components to allow this machine to work on an environment managed by OpenGnsys.

View File

@ -1,6 +1,6 @@
Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
Name: ogagent
Maintainer: Ramón M. Gómez
Maintainer: OpenGnsys developers
Source: https://opengnsys.es
Copyright: 2014 Virtual Cable S.L.U.

View File

@ -1,5 +0,0 @@
# Automatically added by dh_installinit
if [ -x "/etc/init.d/ogagent" ]; then
update-rc.d ogagent defaults >/dev/null || exit $?
fi
# End automatically added section

View File

@ -1,12 +0,0 @@
# Automatically added by dh_installinit
if [ "$1" = "purge" ] ; then
update-rc.d ogagent 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

View File

@ -1,2 +0,0 @@
misc:Depends=
misc:Pre-Depends=

View File

@ -31,7 +31,7 @@ binary-indep: build install
dh_installdocs
dh_installdebconf
dh_installinit --no-start
dh_python2=python
dh_python3=python
dh_compress
dh_link
dh_fixperms

View File

@ -1,83 +0,0 @@
%define _topdir %(echo $PWD)/rpm
%define name ogagent
%define version 0.0.0
%define release 1
%define buildroot %{_topdir}/%{name}-%{version}-%{release}-root
BuildRoot: %{buildroot}
Name: %{name}
Version: %{version}
Release: %{release}
Summary: OpenGnsys Agent for Operating Systems
License: BSD3
Group: Admin
Requires: chkconfig initscripts python-six python-requests python-distro PyQt4 libXScrnSaver
Vendor: OpenGnsys Project
URL: https://opengnsys.es/
Provides: ogagent
%define _rpmdir ../
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
%install
curdir=`pwd`
cd ../..
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh install-ogagent
cd $curdir
%clean
rm -rf $RPM_BUILD_ROOT
curdir=`pwd`
cd ../..
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh clean
cd $curdir
%post
systemctl enable ogagent.service > /dev/null 2>&1
%preun
systemctl disable ogagent.service > /dev/null 2>&1
systemctl stop ogagent.service > /dev/null 2>&1
%postun
# $1 == 0 on uninstall, == 1 on upgrade for preun and postun (just a reminder for me... :) )
if [ $1 -eq 0 ]; then
rm -rf /etc/ogagent
rm /var/log/ogagent.log
fi
# And, posibly, the .pyc leaved behind on /usr/share/OGAgent
rm -rf /usr/share/OGAgent > /dev/null 2>&1
%description
This package provides the required components to allow this machine to work on an environment managed by OpenGnsys.
%files
%defattr(-,root,root)
/etc/ogagent
/etc/xdg/autostart/OGAgentTool.desktop
/etc/init.d/ogagent
/usr/bin/OGAgentTool-startup
/usr/bin/ogagent
/usr/bin/OGAgentTool
/usr/share/OGAgent/*
/usr/share/autostart/OGAgentTool.desktop
%changelog
* Fri Feb 07 2020 Ramón M. Gómez <ramongomez@us.es> - 1.1.1b-1
- Use python-distro to detect the distribution version
* Thu May 23 2019 Ramón M. Gómez <ramongomez@us.es> - 1.1.1-1
- Set connection timeout
- Compatibility with "Exam Mode" from the University of Seville
* Wed May 22 2019 Ramón M. Gómez <ramongomez@us.es> - 1.1.0a-1
- Fix a bug when activating the agent with some network devices
* Tue Oct 13 2016 Ramón M. Gómez <ramongomez@us.es> - 1.1.0-1
- Functional OpenGnsys Agent interacting with OpenGnsys Server 1.1.0
* Tue Jul 18 2015 Adolfo Gómez García <agomez@virtualcable.es> - 1.0.0-1
- Initial release for OpenGnsys Agent

View File

@ -1,10 +1,10 @@
#!/bin/sh
for p in python python2; do
[ -z "$PYTHON" ] && [ $($p -c 'import sys; print(sys.version_info[0])') -eq 2 ] && PYTHON=$p
for p in python python3; do
[ "$(command -v $p)" ] && [ -z "$PYTHON" ] && [ $($p -c 'import sys; print(sys.version_info[0])') -eq 3 ] && PYTHON=$p
done
if [ -z "$PYTHON" ]; then
echo "ERROR: OGAgent needs Python 2" &>2
echo "ERROR: OGAgent needs Python 3" &>2
exit 1
fi

View File

@ -1,10 +1,10 @@
#!/bin/sh
for p in python python2; do
[ -z "$PYTHON" ] && [ $($p -c 'import sys; print(sys.version_info[0])') -eq 2 ] && PYTHON=$p
for p in python python3; do
[ "$(command -v $p)" ] && [ -z "$PYTHON" ] && [ $($p -c 'import sys; print(sys.version_info[0])') -eq 3 ] && PYTHON=$p
done
if [ -z "$PYTHON" ]; then
echo "ERROR: OGAgent needs Python 2" &>2
echo "ERROR: OGAgent needs Python 3" &>2
exit 1
fi

View File

@ -1,3 +0,0 @@
six
requests

View File

@ -29,7 +29,6 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import win32service
import win32serviceutil

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
@ -29,28 +29,21 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from __future__ import unicode_literals
import atexit
import base64
import json
import sys
import time
import json
import six
import atexit
from PyQt4 import QtCore, QtGui # @UnresolvedImport
from PyQt6 import QtCore, QtGui, QtWidgets
from opengnsys import VERSION, ipc, operations, utils
from opengnsys.log import logger
from opengnsys.service import IPC_PORT
from about_dialog_ui import Ui_OGAAboutDialog
from message_dialog_ui import Ui_OGAMessageDialog
from opengnsys.scriptThread import ScriptExecutorThread
from opengnsys import VERSION, ipc, operations, utils
from opengnsys.config import readConfig
from opengnsys.loader import loadModules
# Set default characters encoding to UTF-8
reload(sys)
if hasattr(sys, 'setdefaultencoding'):
sys.setdefaultencoding('utf-8')
from opengnsys.log import logger
from opengnsys.scriptThread import ScriptExecutorThread
from opengnsys.service import IPC_PORT
trayIcon = None
@ -61,9 +54,9 @@ def sigAtExit():
# About dialog
class OGAAboutDialog(QtGui.QDialog):
class OGAAboutDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
QtWidgets.QDialog.__init__(self, parent)
self.ui = Ui_OGAAboutDialog()
self.ui.setupUi(self)
self.ui.VersionLabel.setText("Version " + VERSION)
@ -72,9 +65,9 @@ class OGAAboutDialog(QtGui.QDialog):
self.hide()
class OGAMessageDialog(QtGui.QDialog):
class OGAMessageDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
QtWidgets.QDialog.__init__(self, parent)
self.ui = Ui_OGAMessageDialog()
self.ui.setupUi(self)
@ -89,7 +82,7 @@ class OGAMessageDialog(QtGui.QDialog):
class MessagesProcessor(QtCore.QThread):
logoff = QtCore.pyqtSignal(name='logoff')
message = QtCore.pyqtSignal(tuple, name='message')
script = QtCore.pyqtSignal(QtCore.QString, name='script')
script = QtCore.pyqtSignal(str, name='script')
exit = QtCore.pyqtSignal(name='exit')
def __init__(self, port):
@ -119,9 +112,9 @@ class MessagesProcessor(QtCore.QThread):
if self.ipc:
self.ipc.sendLogin(user_data)
def sendLogout(self, userName):
def sendLogout(self, username):
if self.ipc:
self.ipc.sendLogout(userName)
self.ipc.sendLogout(username)
def run(self):
if self.ipc is None:
@ -136,20 +129,17 @@ class MessagesProcessor(QtCore.QThread):
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:
module, message, data = data.split('\0')
msg_id, data = msg
logger.debug('Got Message on User Space: {}:{}'.format(msg_id, data))
if msg_id == ipc.MSG_MESSAGE:
module, message, data = data.decode('utf-8').split('\0')
self.message.emit((module, message, data))
elif msgId == ipc.MSG_LOGOFF:
elif msg_id == ipc.MSG_LOGOFF:
self.logoff.emit()
elif msgId == ipc.MSG_SCRIPT:
self.script.emit(QtCore.QString.fromUtf8(data))
elif msg_id == ipc.MSG_SCRIPT:
self.script.emit(data.decode('utf-8'))
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??)')
logger.error('Got error on IPC thread {}'.format(utils.exceptionToMessage(e)))
if self.ipc.running is False and self.running is True:
logger.warn('Lost connection with Service, closing program')
@ -157,27 +147,24 @@ class MessagesProcessor(QtCore.QThread):
self.exit.emit()
class OGASystemTray(QtGui.QSystemTrayIcon):
class OGASystemTray(QtWidgets.QSystemTrayIcon):
def __init__(self, app_, parent=None):
self.app = app_
self.config = readConfig(client=True)
self.modules = None
# Get opengnsys section as dict
cfg = dict(self.config.items('opengnsys'))
# Set up log level
logger.setLevel(cfg.get('log', 'INFO'))
self.ipcport = int(cfg.get('ipc_port', IPC_PORT))
# style = app.style()
# icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_ComputerIcon))
icon = QtGui.QIcon(':/images/img/oga.png')
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = QtGui.QMenu(parent)
exitAction = self.menu.addAction("About")
exitAction.triggered.connect(self.about)
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = QtWidgets.QMenu(parent)
exit_action = self.menu.addAction("About")
exit_action.triggered.connect(self.about)
self.setContextMenu(self.menu)
self.ipc = MessagesProcessor(self.ipcport)
@ -208,18 +195,16 @@ class OGASystemTray(QtGui.QSystemTrayIcon):
logger.debug('Modules: {}'.format(list(v.name for v in self.modules)))
# Send init to all modules
validMods = []
valid_mods = []
for mod in self.modules:
try:
logger.debug('Activating module {}'.format(mod.name))
mod.activate()
validMods.append(mod)
valid_mods.append(mod)
except Exception as e:
logger.exception()
logger.error("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
self.modules[:] = validMods # copy instead of assignment
self.modules[:] = valid_mods # copy instead of assignment
# If this is running, it's because he have logged in, inform service of this fact
self.ipc.sendLogin((operations.getCurrentUser(), operations.getSessionLanguage(),
operations.get_session_type()))
@ -260,7 +245,7 @@ class OGASystemTray(QtGui.QSystemTrayIcon):
def executeScript(self, script):
logger.debug('Executing script')
script = six.text_type(script.toUtf8()).decode('base64')
script = base64.b64decode(script.encode('ascii'))
th = ScriptExecutorThread(script)
th.start()
@ -269,7 +254,7 @@ class OGASystemTray(QtGui.QSystemTrayIcon):
operations.logoff() # Invoke log off
def about(self):
self.aboutDlg.exec_()
self.aboutDlg.exec()
def cleanup(self):
logger.debug('Quit invoked')
@ -311,14 +296,14 @@ class OGASystemTray(QtGui.QSystemTrayIcon):
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
app = QtWidgets.QApplication(sys.argv)
if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
if not QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
# QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.")
sys.exit(1)
# This is important so our app won't close on messages windows (alerts, etc...)
QtGui.QApplication.setQuitOnLastWindowClosed(False)
QtWidgets.QApplication.setQuitOnLastWindowClosed(False)
try:
trayIcon = OGASystemTray(app)
@ -342,7 +327,7 @@ if __name__ == '__main__':
# Catch kill and logout user :)
atexit.register(sigAtExit)
res = app.exec_()
res = app.exec()
logger.debug('Exiting')
trayIcon.quit()

View File

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

View File

@ -1 +1 @@
1.1.1b
1.3.0

View File

@ -65,7 +65,7 @@
<item>
<widget class="QLabel" name="VersionLabel">
<property name="text">
<string>Version 1.1.0</string>
<string>Version 1.3.0</string>
</property>
</widget>
</item>

View File

@ -11,7 +11,10 @@ remote=https://192.168.2.10/opengnsys/rest
# Alternate OpenGnsys Service (comment out to enable this option)
#altremote=https://10.0.2.2/opengnsys/rest
# Log Level, if ommited, will be set to INFO
# Execution level (permitted operations): status, halt, full
level=full
# Log Level, if omitted, will be set to INFO
log=DEBUG
# Module specific

View File

@ -32,7 +32,7 @@
# pylint: disable-msg=E1101,W0703
from __future__ import unicode_literals
import requests
import logging

View File

@ -29,19 +29,15 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from __future__ import unicode_literals
# On centos, old six release does not includes byte2int, nor six.PY2
import six
import modules
from RESTApi import REST, RESTError
from . import modules
from .RESTApi import REST, RESTError
try:
with open('../VERSION', 'r') as v:
VERSION = v.read().strip()
except IOError:
VERSION = '1.1.1b'
VERSION='1.3.0'
__title__ = 'OpenGnsys Agent'
__version__ = VERSION

View File

@ -26,24 +26,25 @@
# 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
from ConfigParser import SafeConfigParser
from configparser import SafeConfigParser
config = None
def readConfig(client=False):
'''
"""
Reads configuration file
If client is False, will read ogagent.cfg as configuration
If client is True, will read ogclient.cfg as configuration
This is this way so we can protect ogagent.cfg against reading for non admin users on all platforms.
'''
"""
cfg = SafeConfigParser()
if client is True:
fname = 'ogclient.cfg'
@ -55,4 +56,3 @@ def readConfig(client=False):
return None
return cfg

View File

@ -25,27 +25,24 @@
# 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, print_function
"""
# Pydev can't parse "six.moves.xxxx" because it is loaded lazy
import six
import json
import ssl
import threading
from six.moves.socketserver import ThreadingMixIn # @UnresolvedImport
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler # @UnresolvedImport
from six.moves.BaseHTTPServer import HTTPServer # @UnresolvedImport
from six.moves.urllib.parse import unquote # @UnresolvedImport
import json
import threading
import ssl
from .utils import exceptionToMessage
from .certs import createSelfSignedCert
from .log import logger
class HTTPServerHandler(BaseHTTPRequestHandler):
service = None
protocol_version = 'HTTP/1.0'
@ -56,22 +53,22 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
self.send_response(code)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({'error': message}))
self.wfile.write(str.encode(json.dumps({'error': message})))
return
def sendJsonResponse(self, data):
self.send_response(200)
data = json.dumps(data)
self.send_header('Content-type', 'application/json')
self.send_header('Content-Length', len(data))
self.send_header('Content-Length', str(len(data)))
self.end_headers()
# Send the html message
self.wfile.write(data)
# parseURL
self.wfile.write(str.encode(data))
def parseUrl(self):
# Very simple path & params splitter
"""
Very simple path & params splitter
"""
path = self.path.split('?')[0][1:].split('/')
try:
@ -81,16 +78,16 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
for v in self.service.modules:
if v.name == path[0]: # Case Sensitive!!!!
return (v, path[1:], params)
return v, path[1:], params
return (None, path, params)
return None, path, params
def notifyMessage(self, module, path, getParams, postParams):
'''
def notifyMessage(self, module, path, get_params, post_params):
"""
Locates witch module will process the message based on path (first folder on url path)
'''
"""
try:
data = module.processServerMessage(path, getParams, postParams, self)
data = module.processServerMessage(path, get_params, post_params, self)
self.sendJsonResponse(data)
except Exception as e:
logger.exception()
@ -102,19 +99,19 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
self.notifyMessage(module, path, params, None)
def do_POST(self):
module, path, getParams = self.parseUrl()
module, path, get_params = self.parseUrl()
post_params = None
# Tries to get JSON content (UTF-8 encoded)
try:
length = int(self.headers.getheader('content-length'))
length = int(self.headers.get('content-length'))
content = self.rfile.read(length).decode('utf-8')
logger.debug('length: {}, content >>{}<<'.format(length, content))
postParams = json.loads(content)
logger.debug('length: {0}, content >>{1}<<'.format(length, content))
post_params = json.loads(content)
except Exception as e:
self.sendJsonError(500, exceptionToMessage(e))
self.notifyMessage(module, path, getParams, postParams)
self.notifyMessage(module, path, get_params, post_params)
def log_error(self, fmt, *args):
logger.error('HTTP ' + fmt % args)
@ -126,6 +123,7 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
class HTTPThreadingServer(ThreadingMixIn, HTTPServer):
pass
class HTTPServerThread(threading.Thread):
def __init__(self, address, service):
super(self.__class__, self).__init__()
@ -146,5 +144,3 @@ class HTTPServerThread(threading.Thread):
def run(self):
self.server.serve_forever()

View File

@ -25,16 +25,16 @@
# 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 json
import queue
import socket
import threading
import six
import traceback
import json
from opengnsys.utils import toUnicode
from opengnsys.log import logger
@ -59,7 +59,7 @@ from opengnsys.log import logger
# 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
# With a previous "MAGIC" header in front of each message
# Client messages
MSG_LOGOFF = 0xA1 # Request log off from an user
@ -84,7 +84,7 @@ REV_DICT = {
REQ_MESSAGE: 'REQ_MESSAGE'
}
MAGIC = b'\x4F\x47\x41\x00' # OGA in hexa with a padded 0 to the right
MAGIC = b'\x4F\x47\x41\x00' # OGA in hex with a padded 0 to the right
# States for client processor
@ -99,10 +99,10 @@ class ClientProcessor(threading.Thread):
self.parent = parent
self.clientSocket = clientSocket
self.running = False
self.messages = six.moves.queue.Queue(32) # @UndefinedVariable
self.messages = queue.Queue(32)
def stop(self):
logger.debug('Stoping client processor')
logger.debug('Stopping client processor')
self.running = False
def processRequest(self, msg, data):
@ -117,6 +117,7 @@ class ClientProcessor(threading.Thread):
state = None
recv_msg = None
recv_data = None
msg_len = 0
while self.running:
try:
counter = 1024
@ -127,7 +128,7 @@ class ClientProcessor(threading.Thread):
# Client disconnected
self.running = False
break
buf = six.byte2int(b) # Empty buffer, this is set as non-blocking
buf = int.from_bytes(b, 'big') # Empty buffer, this is set as non-blocking
if state is None:
if buf in (REQ_MESSAGE, REQ_LOGIN, REQ_LOGOUT):
logger.debug('State set to {}'.format(buf))
@ -152,7 +153,7 @@ class ClientProcessor(threading.Thread):
recv_data = b''
continue
elif state == ST_RECEIVING:
recv_data += six.int2byte(buf)
recv_data += bytes([buf])
msg_len -= 1
if msg_len == 0:
self.processRequest(recv_msg, recv_data)
@ -173,7 +174,7 @@ class ClientProcessor(threading.Thread):
try:
msg = self.messages.get(block=True, timeout=1)
except six.moves.queue.Empty: # No message got in time @UndefinedVariable
except queue.Empty: # No message got in time @UndefinedVariable
continue
logger.debug('Got message {}={}'.format(msg, REV_DICT.get(msg[0])))
@ -181,7 +182,7 @@ class ClientProcessor(threading.Thread):
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
data = MAGIC + bytes([msg[0]]) + bytes([l & 0xFF]) + bytes([l >> 8]) + m
try:
self.clientSocket.sendall(data)
except socket.error as e:
@ -220,20 +221,20 @@ class ServerIPC(threading.Thread):
for t in self.threads:
t.join()
def sendMessage(self, msgId, msgData):
'''
def sendMessage(self, msg_id, msg_data):
"""
Notify message to all listening threads
'''
logger.debug('Sending message {}({}),{} to all clients'.format(msgId, REV_DICT.get(msgId), msgData))
"""
logger.debug('Sending message {}({}),{} to all clients'.format(msg_id, REV_DICT.get(msg_id), msg_data))
# Convert to bytes so length is correctly calculated
if isinstance(msgData, six.text_type):
msgData = msgData.encode('utf8')
if isinstance(msg_data, str):
msg_data = str.encode(msg_data)
for t in self.threads:
if t.isAlive():
if t.is_alive():
logger.debug('Sending to {}'.format(t))
t.messages.put((msgId, msgData))
t.messages.put((msg_id, msg_data))
def sendLoggofMessage(self):
self.sendMessage(MSG_LOGOFF, '')
@ -242,18 +243,18 @@ class ServerIPC(threading.Thread):
self.sendMessage(MSG_MESSAGE, message)
def sendPopupMessage(self, title, message):
self.sendMessage(MSG_POPUP, {'title':title, 'message':message})
self.sendMessage(MSG_POPUP, {'title': title, '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():
if t.is_alive():
logger.debug('Thread {} is alive'.format(t))
aliveThreads.append(t)
self.threads[:] = aliveThreads
@ -262,7 +263,7 @@ class ServerIPC(threading.Thread):
self.running = True
self.serverSocket.bind(('localhost', self.port))
self.serverSocket.setblocking(1)
self.serverSocket.setblocking(True)
self.serverSocket.listen(4)
while True:
@ -289,7 +290,7 @@ class ClientIPC(threading.Thread):
self.port = listenPort
self.running = False
self.clientSocket = None
self.messages = six.moves.queue.Queue(32) # @UndefinedVariable
self.messages = queue.Queue(32) # @UndefinedVariable
self.connect()
@ -300,7 +301,7 @@ class ClientIPC(threading.Thread):
while self.running:
try:
return self.messages.get(timeout=1)
except six.moves.queue.Empty: # @UndefinedVariable
except queue.Empty:
continue
return None
@ -310,11 +311,11 @@ class ClientIPC(threading.Thread):
if data is None:
data = b''
if isinstance(data, six.text_type): # Convert to bytes if necessary
data = data.encode('utf-8')
if isinstance(data, str):
data = str.encode(data)
l = len(data)
msg = six.int2byte(msg) + six.int2byte(l & 0xFF) + six.int2byte(l >> 8) + data
msg = bytes([msg]) + bytes([l & 0xFF]) + bytes([l >> 8]) + data
self.clientSocket.sendall(msg)
def sendLogin(self, user_data):
@ -324,20 +325,20 @@ class ClientIPC(threading.Thread):
self.sendRequestMessage(REQ_LOGOUT, username)
def sendMessage(self, module, message, data=None):
'''
"""
Sends a message "message" with data (data will be encoded as json, so ensure that it is serializable)
@param module: Module that will receive this message
@param message: Message to send. This message is "customized", and understand by modules
@param data: Data to be send as message companion
'''
"""
msg = '\0'.join((module, message, json.dumps(data)))
self.sendRequestMessage(REQ_MESSAGE, msg)
def messageReceived(self):
'''
"""
Override this method to automatically get notified on new message
received. Message is at self.messages queue
'''
"""
pass
def receiveBytes(self, number):
@ -420,4 +421,3 @@ class ClientIPC(threading.Thread):
self.clientSocket.close()
except Exception:
pass # If can't close, nothing happens, just end thread

View File

@ -26,10 +26,10 @@
# 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 opengnsys.service import CommonService
from opengnsys.service import IPC_PORT
@ -45,7 +45,7 @@ import json
try:
from prctl import set_proctitle # @UnresolvedImport
except Exception: # Platform may not include prctl, so in case it's not available, we let the "name" as is
except ImportError: # Platform may not include prctl, so in case it's not available, we let the "name" as is
def set_proctitle(_):
pass
@ -63,7 +63,6 @@ class OGAgentSvc(Daemon, CommonService):
# Call modules initialization
# They are called in sequence, no threading is done at this point, so ensure modules onActivate always returns
# *********************
# * Main Service loop *
@ -93,6 +92,7 @@ def usage():
sys.stderr.write("usage: {} start|stop|restart|fg|login 'username'|logout 'username'|message 'module' 'message' 'json'\n".format(sys.argv[0]))
sys.exit(2)
if __name__ == '__main__':
logger.setLevel('INFO')
@ -105,7 +105,6 @@ if __name__ == '__main__':
sys.exit(0)
except Exception as e:
logger.error(e)
if len(sys.argv) == 3 and sys.argv[1] in ('login', 'logout'):
logger.debug('Running client opengnsys')

View File

@ -26,7 +26,6 @@
# 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
"""

View File

@ -25,18 +25,16 @@
# 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
import os
import sys
import time
from opengnsys.log import logger
from signal import SIGTERM
@ -89,7 +87,7 @@ class Daemon:
sys.stderr.flush()
si = open(self.stdin, 'r')
so = open(self.stdout, 'a+')
se = open(self.stderr, 'a+', 0)
se = open(self.stderr, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

View File

@ -26,18 +26,16 @@
# 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
# Logging levels
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
class LocalLogger(object):
@ -46,7 +44,7 @@ class LocalLogger(object):
# 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
# again it fails, open it at the tmpPath
for logDir in ('/var/log', os.path.expanduser('~'), tempfile.gettempdir()):
try:

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import socket
import platform
@ -104,7 +104,7 @@ def _getInterfaces():
0x8912, # SIOCGIFCONF
struct.pack(str('iL'), space, names.buffer_info()[0])
))[0]
namestr = names.tostring()
namestr = names.tobytes()
# return namestr, outbytes
return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)]

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import platform
import os

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from opengnsys.linux.renamer import renamers
from opengnsys.log import logger

View File

@ -28,7 +28,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from opengnsys.linux.renamer import renamers
from opengnsys.log import logger

View File

@ -28,7 +28,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from opengnsys.linux.renamer import renamers
from opengnsys.log import logger

View File

@ -33,7 +33,7 @@
# This is a simple module loader, so we can add "external opengnsys" modules as addons
# Modules under "opengsnsys/modules" are always autoloaded
from __future__ import unicode_literals
import pkgutil
import os.path

View File

@ -26,22 +26,21 @@
# 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 traceback
import sys
import six
import traceback
if sys.platform == 'win32':
from opengnsys.windows.log import LocalLogger # @UnusedImport
from opengnsys.windows.log import LocalLogger
else:
from opengnsys.linux.log import LocalLogger # @Reimport
from opengnsys.linux.log import LocalLogger
# 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
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
_levelName = {
'OTHER': OTHER,
@ -52,17 +51,18 @@ _levelName = {
'FATAL': FATAL
}
class Logger(object):
def __init__(self):
self.logLevel = INFO
self.logger = LocalLogger()
def setLevel(self, level):
'''
"""
Sets log level filter (minimum level required for a log message to be processed)
:param level: Any message with a level below this will be filtered out
'''
if isinstance(level, six.string_types):
"""
if isinstance(level, str):
level = _levelName.get(level, INFO)
self.logLevel = level # Ensures level is an integer or fails

View File

@ -29,4 +29,4 @@
'''
@author: Ramón M. Gómez, ramongomez at us dot es
'''
from __future__ import unicode_literals

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import socket
import platform

View File

@ -28,7 +28,7 @@
"""
@author: Ramón M. Gómez, ramongomez at us dot es
"""
from __future__ import unicode_literals
from opengnsys.workers import ClientWorker

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
@ -28,22 +29,24 @@
"""
@author: Ramón M. Gómez, ramongomez at us dot es
"""
from __future__ import unicode_literals
import threading
import base64
import os
import platform
import time
import random
import shutil
import string
import urllib
import threading
import time
import urllib.error
import urllib.parse
import urllib.request
from opengnsys.workers import ServerWorker
from opengnsys import REST, RESTError
from opengnsys import operations
from configparser import NoOptionError
from opengnsys import REST, operations, VERSION
from opengnsys.log import logger
from opengnsys.scriptThread import ScriptExecutorThread
from opengnsys.workers import ServerWorker
# Check authorization header decorator
@ -53,9 +56,12 @@ def check_secret(fnc):
"""
def wrapper(*args, **kwargs):
try:
this, path, get_params, post_params, server = args # @UnusedVariable
if this.random == server.headers['Authorization']:
fnc(*args, **kwargs)
this, path, get_params, post_params, server = args
# Accept "status" operation with no arguments or any function with Authorization header
if fnc.__name__ == 'process_status' and not get_params:
return fnc(*args, **kwargs)
elif this.random == server.headers['Authorization']:
return fnc(*args, **kwargs)
else:
raise Exception('Unauthorized operation')
except Exception as e:
@ -65,6 +71,26 @@ def check_secret(fnc):
return wrapper
# Check if operation is permitted
def execution_level(level):
def check_permitted(fnc):
def wrapper(*args, **kwargs):
levels = ['status', 'halt', 'full']
this = args[0]
try:
if levels.index(level) <= levels.index(this.exec_level):
return fnc(*args, **kwargs)
else:
raise Exception('Unauthorized operation')
except Exception as e:
logger.error(e)
raise Exception(e)
return wrapper
return check_permitted
# Error handler decorator.
def catch_background_error(fnc):
def wrapper(*args, **kwargs):
@ -77,31 +103,43 @@ def catch_background_error(fnc):
class OpenGnSysWorker(ServerWorker):
name = 'opengnsys'
name = 'opengnsys' # Module name
interface = None # Bound interface for OpenGnsys
REST = None # REST object
logged_in = False # User session flag
user = [] # User sessions
session_type = '' # User session type
locked = {}
random = None # Random string for secure connections
length = 32 # Random string length
exec_level = None # Execution level (permitted operations)
def onActivation(self):
"""
Sends OGAgent activation notification to OpenGnsys server
"""
t = 0
e = None # Error info
t = 0 # Count of time
# Generate random secret to send on activation
self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length))
# Ensure cfg has required configuration variables or an exception will be thrown
url = self.service.config.get('opengnsys', 'remote')
try:
url = self.service.config.get('opengnsys', 'remote')
except NoOptionError as e:
logger.error("Configuration error: {}".format(e))
raise e
self.REST = REST(url)
# Execution level ('full' by default)
try:
self.exec_level = self.service.config.get('opengnsys', 'level')
except NoOptionError:
self.exec_level = 'full'
# Get network interfaces until they are active or timeout (5 minutes)
for t in range(0, 300):
try:
self.interface = list(operations.getNetworkInfo())[0] # Get first network interface
# Get the first network interface
self.interface = list(operations.getNetworkInfo())[0]
except Exception as e:
# Wait 1 sec. and retry
logger.warn (e)
time.sleep(1)
finally:
# Exit loop if interface is active
@ -112,29 +150,36 @@ class OpenGnSysWorker(ServerWorker):
# Raise error after timeout
if not self.interface:
raise e
# Loop to send initialization message
for t in range(0, 100):
init_retries = 100
for t in range(0, init_retries):
try:
try:
self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip,
'secret': self.random, 'ostype': operations.os_type,
'osversion': operations.os_version})
'osversion': operations.os_version,
'agent_version': VERSION})
break
except:
except Exception as e:
logger.warn (str (e))
# Trying to initialize on alternative server, if defined
# (used in "exam mode" from the University of Seville)
self.REST = REST(self.service.config.get('opengnsys', 'altremote'))
self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip,
'secret': self.random, 'ostype': operations.os_type,
'osversion': operations.os_version, 'alt_url': True})
'osversion': operations.os_version, 'alt_url': True,
'agent_version': VERSION})
break
except:
except Exception as e:
logger.warn (str (e))
time.sleep(3)
# Raise error after timeout
if 0 < t < 100:
if t < init_retries-1:
logger.debug('Successful connection after {} tries'.format(t))
elif t == 100:
elif t == init_retries-1:
raise Exception('Initialization error: Cannot connect to remote server')
# Delete marking files
for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']:
try:
@ -165,7 +210,7 @@ class OpenGnSysWorker(ServerWorker):
"""
user, language, self.session_type = tuple(data.split(','))
logger.debug('Received login for {0} using {2} with language {1}'.format(user, language, self.session_type))
self.logged_in = True
self.user.append(user)
self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language,
'session': self.session_type,
'ostype': operations.os_type, 'osversion': operations.os_version})
@ -175,7 +220,10 @@ class OpenGnSysWorker(ServerWorker):
Sends session logout notification to OpenGnsys server
"""
logger.debug('Received logout for {}'.format(user))
self.logged_in = False
try:
self.user.pop()
except IndexError:
pass
self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user})
def process_ogclient(self, path, get_params, post_params, server):
@ -206,34 +254,34 @@ class OpenGnSysWorker(ServerWorker):
raise Exception('Message processor for "{}" not found'.format(path[0]))
return operation(path[1:], get_params, post_params)
# Warning: the order of the decorators matters
@execution_level('status')
@check_secret
def process_status(self, path, get_params, post_params, server):
"""
Returns client status (OS type or execution status) and login status
:param path:
:param get_params:
:param get_params: optional parameter "detail" to show extended status
:param post_params:
:param server:
:return: JSON object {"status": "status_code", "loggedin": boolean}
:return: JSON object {"status": "status_code", "loggedin": boolean, ...}
"""
res = {'status': '', 'loggedin': self.logged_in, 'session': self.session_type}
if platform.system() == 'Linux': # GNU/Linux
# Check if it's OpenGnsys Client.
if os.path.exists('/scripts/oginit'):
# Check if OpenGnsys Client is busy.
if self.locked:
res['status'] = 'BSY'
else:
res['status'] = 'OPG'
else:
# Check if there is an active session.
res['status'] = 'LNX'
elif platform.system() == 'Windows': # Windows
# Check if there is an active session.
res['status'] = 'WIN'
elif platform.system() == 'Darwin': # Mac OS X ??
res['status'] = 'OSX'
st = {'linux': 'LNX', 'macos': 'OSX', 'windows': 'WIN'}
try:
# Standard status
res = {'status': st[operations.os_type.lower()], 'loggedin': len(self.user) > 0,
'session': self.session_type}
# Detailed status
if get_params.get('detail', 'false') == 'true':
res.update({'agent_version': VERSION, 'os_version': operations.os_version, 'sys_load': os.getloadavg()})
if res['loggedin']:
res.update({'sessions': len(self.user), 'current_user': self.user[-1]})
except KeyError:
# Unknown operating system
res = {'status': 'UNK'}
return res
@execution_level('halt')
@check_secret
def process_reboot(self, path, get_params, post_params, server):
"""
@ -252,6 +300,7 @@ class OpenGnSysWorker(ServerWorker):
threading.Thread(target=rebt).start()
return {'op': 'launched'}
@execution_level('halt')
@check_secret
def process_poweroff(self, path, get_params, post_params, server):
"""
@ -271,6 +320,7 @@ class OpenGnSysWorker(ServerWorker):
threading.Thread(target=pwoff).start()
return {'op': 'launched'}
@execution_level('full')
@check_secret
def process_script(self, path, get_params, post_params, server):
"""
@ -283,7 +333,7 @@ class OpenGnSysWorker(ServerWorker):
"""
logger.debug('Processing script request')
# Decoding script (Windows scripts need a subprocess call per line)
script = urllib.unquote(post_params.get('script').decode('base64')).decode('utf8')
script = urllib.parse.unquote(base64.b64decode(post_params.get('script')).decode('utf-8'))
if operations.os_type == 'Windows':
script = 'import subprocess; {0}'.format(
';'.join(['subprocess.check_output({0},shell=True)'.format(repr(c)) for c in script.split('\n')]))
@ -297,6 +347,7 @@ class OpenGnSysWorker(ServerWorker):
self.sendClientMessage('script', {'code': script})
return {'op': 'launched'}
@execution_level('full')
@check_secret
def process_logoff(self, path, get_params, post_params, server):
"""
@ -307,6 +358,7 @@ class OpenGnSysWorker(ServerWorker):
self.sendClientMessage('logoff', {})
return {'op': 'sent to client'}
@execution_level('full')
@check_secret
def process_popup(self, path, get_params, post_params, server):
"""

View File

@ -31,7 +31,7 @@
"""
# pylint: disable=unused-wildcard-import,wildcard-import
from __future__ import unicode_literals
import sys
# Importing platform operations and getting operating system data.

View File

@ -26,23 +26,21 @@
# 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 .log import logger
from .config import readConfig
from .utils import exceptionToMessage
import json
import socket
import time
from . import ipc
from . import httpserver
from .config import readConfig
from .loader import loadModules
from .log import logger
from .utils import exceptionToMessage
import socket
import time
import json
import six
IPC_PORT = 10398
@ -58,21 +56,17 @@ class CommonService(object):
logger.info('Initializing OpenGnsys Agent')
# Read configuration file before proceding & ensures minimal config is there
self.config = readConfig()
# Get opengnsys section as dict
# Get opengnsys section as dict
cfg = dict(self.config.items('opengnsys'))
# Set up log level
logger.setLevel(cfg.get('log', 'INFO'))
logger.debug('Loaded configuration from opengnsys.cfg:')
for section in self.config.sections():
logger.debug('Section {} = {}'.format(section, self.config.items(section)))
if logger.logger.isWindows():
# Logs will also go to windows event log for services
logger.logger.serviceLogger = True
@ -90,15 +84,16 @@ class CommonService(object):
logger.debug('Modules: {}'.format(list(v.name for v in self.modules)))
def stop(self):
'''
"""
Requests service termination
'''
"""
self.isAlive = False
# ********************************
# * Internal messages processors *
# ********************************
def notifyLogin(self, username):
def notifyLogin(self, data):
username = data.decode('utf-8')
for v in self.modules:
try:
logger.debug('Notifying login of user {} to module {}'.format(username, v.name))
@ -106,7 +101,8 @@ class CommonService(object):
except Exception as e:
logger.error('Got exception {} processing login message on {}'.format(e, v.name))
def notifyLogout(self, username):
def notifyLogout(self, data):
username = data.decode('utf-8')
for v in self.modules:
try:
logger.debug('Notifying logout of user {} to module {}'.format(username, v.name))
@ -115,7 +111,7 @@ class CommonService(object):
logger.error('Got exception {} processing logout message on {}'.format(e, v.name))
def notifyMessage(self, data):
module, message, data = data.split('\0')
module, message, data = data.decode('utf-8').split('\0')
for v in self.modules:
if v.name == module: # Case Sensitive!!!!
try:
@ -126,13 +122,12 @@ class CommonService(object):
logger.error('Got exception {} processing generic message on {}'.format(e, v.name))
logger.error('Module {} not found, messsage {} not sent'.format(module, message))
def clientMessageProcessor(self, msg, data):
'''
"""
Callback, invoked from IPC, on its own thread (not the main thread).
This thread will "block" communication with agent untill finished, but this should be no problem
'''
"""
logger.debug('Got message {}'.format(msg))
if msg == ipc.REQ_LOGIN:
@ -143,14 +138,9 @@ class CommonService(object):
self.notifyMessage(data)
def initialize(self):
# ******************************************
# * Initialize listeners, modules, etc...
# ******************************************
if six.PY3 is False:
import threading
threading._DummyThread._Thread__stop = lambda x: 42
"""
Initialize listeners, modules, etc...
"""
logger.debug('Starting IPC listener at {}'.format(IPC_PORT))
self.ipc = ipc.ServerIPC(self.ipcport, clientMessageProcessor=self.clientMessageProcessor)
self.ipc.start()
@ -203,47 +193,47 @@ class CommonService(object):
# Methods that CAN BE overridden by agents
# ****************************************
def doWait(self, miliseconds):
'''
"""
Invoked to wait a bit
CAN be OVERRIDDEN
'''
"""
time.sleep(float(miliseconds) / 1000)
def notifyStop(self):
'''
"""
Overridden to log stop
'''
"""
logger.info('Service is being stopped')
# ***************************************************
# * Helpers, convenient methods to facilitate comms *
# ***************************************************
def sendClientMessage(self, toModule, message, data):
'''
"""
Sends a message to the clients using IPC
The data is converted to json, so ensure that it is serializable.
All IPC is asynchronous, so if you expect a response, this will be sent by client using another message
@param toModule: Module that will receive this message
@param message: Message to send
@param data: data to send
'''
@param data: data to send
"""
self.ipc.sendMessageMessage('\0'.join((toModule, message, json.dumps(data))))
def sendScriptMessage(self, script):
'''
"""
Sends an script to be executed by client
'''
"""
self.ipc.sendScriptMessage(script)
def sendLogoffMessage(self):
'''
"""
Sends a logoff message to client
'''
"""
self.ipc.sendLoggofMessage()
def sendPopupMessage(self, title, message):
'''
Sends a poup box to be displayed by client
'''
"""
Sends a popup box to be displayed by client
"""
self.ipc.sendPopupMessage(title, message)

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import sys
import six

View File

@ -25,25 +25,25 @@
# 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
class ClientWorker(object):
'''
"""
A ServerWorker is a server module that "works" for service
Most method are invoked inside their own thread, except onActivation & onDeactivation.
Most method are invoked inside their own thread, except onActivation & onDeactivation.
This two methods are invoked inside main service thread, take that into account when creating them
* You must provide a module name (override name on your class), so we can identify the module by a "valid" name.
A valid name is like a valid python variable (do not use spaces, etc...)
* The name of the module is used as REST message destination id:
https://sampleserver:8888/[name]/....
Remember that module names and REST path are case sensitive!!!
'''
"""
name = None
service = None
@ -51,15 +51,15 @@ class ClientWorker(object):
self.service = service
def activate(self):
'''
"""
Convenient method to wrap onActivation, so we can include easyly custom common logic for activation in a future
'''
"""
self.onActivation()
def deactivate(self):
'''
"""
Convenient method to wrap onActivation, so we can include easyly custom common logic for deactivation in a future
'''
"""
self.onDeactivation()
def processMessage(self, message, params):
@ -83,32 +83,31 @@ class ClientWorker(object):
return operation(params)
def onActivation(self):
'''
"""
Invoked by Service for activation.
This MUST be overridden by modules!
This method is invoked inside main thread, so if it "hangs", complete service will hang
This should be no problem, but be advised about this
'''
"""
pass
def onDeactivation(self):
'''
"""
Invoked by Service before unloading service
This MUST be overridden by modules!
This method is invoked inside main thread, so if it "hangs", complete service will hang
This should be no problem, but be advised about this
'''
"""
pass
# *************************************
# * Helper, convenient helper methods *
# *************************************
def sendServerMessage(self, message, data):
'''
"""
Sends a message to connected ipc clients
By convenience, it uses the "current" moduel name as destination module name also.
If you need to send a message to a different module, you can use self.service.sendClientMessage(module, message, data) instead
og this helmer
'''
"""
self.service.ipc.sendMessage(self.name, message, data)

View File

@ -25,25 +25,25 @@
# 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
class ServerWorker(object):
'''
"""
A ServerWorker is a server module that "works" for service
Most method are invoked inside their own thread, except onActivation & onDeactivation.
Most method are invoked inside their own thread, except onActivation & onDeactivation.
This two methods are invoked inside main service thread, take that into account when creating them
* You must provide a module name (override name on your class), so we can identify the module by a "valid" name.
A valid name is like a valid python variable (do not use spaces, etc...)
* The name of the module is used as REST message destination id:
https://sampleserver:8888/[name]/....
Remember that module names and REST path are case sensitive!!!
'''
"""
name = None
service = None
locked = False
@ -52,43 +52,43 @@ class ServerWorker(object):
self.service = service
def activate(self):
'''
"""
Convenient method to wrap onActivation, so we can include easyly custom common logic for activation in a future
'''
"""
self.onActivation()
def deactivate(self):
'''
"""
Convenient method to wrap onActivation, so we can include easyly custom common logic for deactivation in a future
'''
"""
self.onDeactivation()
def process(self, getParams, postParams, server):
'''
"""
This method is invoked on a message received with an empty path (that means a message with only the module name, like in "http://example.com/Sample"
Override it if you expect messages with that pattern
Overriden method must return data that can be serialized to json (i.e. Ojects are not serializable to json, basic type are)
'''
"""
raise NotImplementedError('Generic message processor is not supported')
def processServerMessage(self, path, getParams, postParams, server):
'''
"""
This method can be overriden to provide your own message proccessor, or better you can
implement a method that is called exactly as "process_" + path[0] (module name has been removed from path array) and this default processMessage will invoke it
* Example:
Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z
The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this:
module.processMessage(["mazinger","Z"], getParams, postParams)
This method will process "mazinguer", and look for a "self" method that is called "process_mazinger", and invoke it this way:
return self.process_mazinger(["Z"], getParams, postParams)
In the case path is empty (that is, the path is composed only by the module name, like in "http://example.com/Sample", the "process" method
will be invoked directly
The methods must return data that can be serialized to json (i.e. Ojects are not serializable to json, basic type are)
'''
"""
if self.locked is True:
raise Exception('system is busy')
@ -103,20 +103,20 @@ class ServerWorker(object):
def processClientMessage(self, message, data):
'''
"""
Invoked by Service when a client message is received (A message from user space Agent)
This method can be overriden to provide your own message proccessor, or better you can
implement a method that is called exactly "process_client_" + message (module name has been removed from path) and this default processMessage will invoke it
* Example:
We got a message from OGAgent "Mazinger", with json params
module.processClientMessage("mazinger", jsonParams)
This method will process "mazinguer", and look for a "self" method that is called "process_client_mazinger", and invoke it this way:
self.process_client_mazinger(jsonParams)
The methods returns nothing (client communications are done asynchronously)
'''
"""
try:
operation = getattr(self, 'process_client_' + message)
except Exception:
@ -128,52 +128,52 @@ class ServerWorker(object):
def onActivation(self):
'''
"""
Invoked by Service for activation.
This MUST be overridden by modules!
This method is invoked inside main thread, so if it "hangs", complete service will hang
This should be no problem, but be advised about this
'''
"""
pass
def onDeactivation(self):
'''
"""
Invoked by Service before unloading service
This MUST be overridden by modules!
This method is invoked inside main thread, so if it "hangs", complete service will hang
This should be no problem, but be advised about this
'''
"""
pass
def onLogin(self, user):
'''
"""
Invoked by Service when an user login is detected
This CAN be overridden by modules
This method is invoked whenever the client (user space agent) notifies the server (Service) that a user has logged in.
This method is run on its own thread
'''
"""
pass
def onLogout(self, user):
'''
"""
Invoked by Service when an user login is detected
This CAN be overridden by modules
This method is invoked whenever the client (user space agent) notifies the server (Service) that a user has logged in.
This method is run on its own thread
'''
"""
pass
# *************************************
# * Helper, convenient helper methods *
# *************************************
def sendClientMessage(self, message, data):
'''
"""
Sends a message to connected ipc clients
By convenience, it uses the "current" moduel name as destination module name also.
If you need to send a message to a different module, you can use self.service.sendClientMessage(module, message, data) instead
og this helmer
'''
"""
self.service.sendClientMessage(self.name, message, data)
def sendScriptMessage(self, script):

View File

@ -3,7 +3,7 @@ Created on Jul 9, 2015
@author: dkmaster
'''
from __future__ import unicode_literals, print_function
# Pydev can't parse "six.moves.xxxx" because it is loaded lazy
from six.moves.socketserver import ThreadingMixIn # @UnresolvedImport

View File

@ -0,0 +1,3 @@
netifaces
requests
urllib3

View File

@ -60,12 +60,8 @@ from distutils.core import setup
import py2exe
import sys
# Reading version file:
try:
with open('VERSION', 'r') as v:
VERSION = v.read().rstrip()
except IOError:
VERSION = '1.1.0'
# update.sh changes this value
VERSION='1.3.0'
sys.argv.append('py2exe')
@ -99,7 +95,7 @@ class Target:
# thus don't need to run in a console.
udsservice = Target(
ogaservice = Target(
description='OpenGnsys Agent Service',
modules=['opengnsys.windows.OGAgentService'],
icon_resources=[(0, 'img\\oga.ico'), (1, 'img\\oga.ico')],
@ -112,7 +108,7 @@ HIDDEN_BY_SIX = ['SocketServer', 'SimpleHTTPServer', 'urllib']
setup(
windows=[
{
'script': 'OGAgentUser.py',
'script': 'OGAgentUser-qt4.py',
'icon_resources': [(0, 'img\\oga.ico'), (1, 'img\\oga.ico')]
},
],
@ -121,7 +117,7 @@ setup(
'script': 'OGAServiceHelper.py'
}
],
service=[udsservice],
service=[ogaservice],
data_files=[('', [get_requests_cert_file()]), ('cfg', ['cfg/ogagent.cfg', 'cfg/ogclient.cfg'])],
options={
'py2exe': {

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from opengnsys.workers import ClientWorker

View File

@ -1,2 +1,2 @@
# Module must be imported on package, so we can initialize and load it
from sample1 import Sample1
from .sample1 import Sample1

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from opengnsys.workers import ServerWorker

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
def test():

View File

@ -30,7 +30,7 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
# pylint: disable=unused-wildcard-import,wildcard-import
from __future__ import unicode_literals, print_function
# Pydev can't parse "six.moves.xxxx" because it is loaded lazy
from six.moves.socketserver import ThreadingMixIn # @UnresolvedImport

View File

@ -1,6 +1,7 @@
#!/bin/bash
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2024 Qindel Formación y Servicios S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -27,16 +28,32 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function process {
pyuic4 about-dialog.ui -o about_dialog_ui.py -x
pyuic4 message-dialog.ui -o message_dialog_ui.py -x
}
function update_version {
if [[ -r VERSION ]]; then
V="$(cat VERSION)"
sed -i "s/Version [^<]*/Version $V/" about-dialog.ui
sed -i "s/^VERSION='.*'$/VERSION='$V'/" setup.py
sed -i "s/^VERSION='.*'$/VERSION='$V'/" opengnsys/__init__.py
else
echo 'src/VERSION: No such file or directory'
exit 1
fi
}
function process_ui {
pyuic6 about-dialog.ui -o about_dialog_ui.py -x
pyuic6 message-dialog.ui -o message_dialog_ui.py -x
}
function process_resources {
## requires a virtualenv with pyside6
## you can create it by doing 'mkvirtualenv -p python3 ogpyside6'
## this will obviously go away in the future, but we need to merge the py3/qt6 change now
~/.virtualenvs/ogpyside6/bin/pyside6-rcc -o OGAgent_rc.py OGAgent.qrc
sed -i -e '/^from PySide6 import QtCore/s/PySide6/PyQt6/' OGAgent_rc.py
}
cd $(dirname "$0")
[ -r VERSION ] && sed -i "s/Version [^<]*/Version $(cat VERSION)/" about-dialog.ui
pyrcc4 -py3 OGAgent.qrc -o OGAgent_rc.py
# process current directory ui's
process
update_version
process_ui
process_resources

View File

@ -2,5 +2,6 @@ C:
CD \ogagent\src
python setup.py
CD ..
RENAME bin\OGAgentUser-qt4.exe OGAgentUser.exe
"C:\Program Files\NSIS\makensis.exe" ogagent.nsi