From aa9ea5dec9bf32b2520b1924a4b47e48b2a52d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 29 Apr 2020 12:57:24 +0200 Subject: [PATCH 01/30] #940: Update OGAgent version number --- src/VERSION | 2 +- src/opengnsys/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VERSION b/src/VERSION index 3e153f5..26aaba0 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.1.1b +1.2.0 diff --git a/src/opengnsys/__init__.py b/src/opengnsys/__init__.py index 0197f32..7be8700 100644 --- a/src/opengnsys/__init__.py +++ b/src/opengnsys/__init__.py @@ -41,7 +41,7 @@ try: with open('../VERSION', 'r') as v: VERSION = v.read().strip() except IOError: - VERSION = '1.1.1b' + VERSION = '1.2.0' __title__ = 'OpenGnsys Agent' __version__ = VERSION From 4aa86dedef3e082dc467d944776d27c5a9c826ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 29 Apr 2020 13:19:11 +0200 Subject: [PATCH 02/30] #940: Simplify OGAgent REST route `GET /status` --- .../modules/server/OpenGnSys/__init__.py | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 70cc8c7..7d8b3bd 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -77,11 +77,10 @@ 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 - locked = {} random = None # Random string for secure connections length = 32 # Random string length @@ -213,23 +212,12 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: JSON object {"status": "status_code", "loggedin": boolean} """ - res = {'status': '', 'loggedin': self.logged_in} - 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'} + res = {'loggedin': self.loggedin} + try: + res['status'] = st[operations.os_type.lower()] + except KeyError: + res['status'] = 'ERR' return res @check_secret From e298c499c9e347152addc5c058fe1272ca31a1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 30 Apr 2020 10:48:11 +0200 Subject: [PATCH 03/30] #975: OGAgent supports multiple user sessions. --- src/opengnsys/modules/server/OpenGnSys/__init__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 7d8b3bd..3124b31 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -80,7 +80,7 @@ class OpenGnSysWorker(ServerWorker): name = 'opengnsys' # Module name interface = None # Bound interface for OpenGnsys REST = None # REST object - logged_in = False # User session flag + user = [] # User sessions random = None # Random string for secure connections length = 32 # Random string length @@ -163,7 +163,7 @@ class OpenGnSysWorker(ServerWorker): """ user, sep, language = data.partition(',') logger.debug('Received login for {} with language {}'.format(user, language)) - self.logged_in = True + self.user.append(user) self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language, 'ostype': operations.os_type, 'osversion': operations.os_version}) @@ -172,7 +172,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): @@ -213,11 +216,12 @@ class OpenGnSysWorker(ServerWorker): :return: JSON object {"status": "status_code", "loggedin": boolean} """ st = {'linux': 'LNX', 'macos': 'OSX', 'windows': 'WIN'} - res = {'loggedin': self.loggedin} try: res['status'] = st[operations.os_type.lower()] + res = {'status': st[operations.os_type.lower()], 'loggedin': len(self.user) > 0} except KeyError: - res['status'] = 'ERR' + # Unknown operating system + res = {'status': 'UNK'} return res @check_secret From a850bd12369d3b8a367c93fb523706aa946c0f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 30 Apr 2020 11:00:22 +0200 Subject: [PATCH 04/30] #975: OGAgent sends version number at startup. --- src/opengnsys/modules/server/OpenGnSys/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 3124b31..d2bdc0d 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -30,18 +30,17 @@ """ from __future__ import unicode_literals -import threading import os import platform import time import random import shutil import string +import threading import urllib from opengnsys.workers import ServerWorker -from opengnsys import REST, RESTError -from opengnsys import operations +from opengnsys import REST, RESTError, operations, VERSION from opengnsys.log import logger from opengnsys.scriptThread import ScriptExecutorThread @@ -116,7 +115,8 @@ class OpenGnSysWorker(ServerWorker): 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: # Trying to initialize on alternative server, if defined @@ -124,7 +124,8 @@ class OpenGnSysWorker(ServerWorker): 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: time.sleep(3) From 3a3b6425561bcb7cee3f3440ffe2061f3b275ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 30 Apr 2020 11:07:04 +0200 Subject: [PATCH 05/30] #975: REST route `GET /status` with `detail=true` shows detailed status Note: `GET /status?detail=true` requires the authentication token. --- .../modules/server/OpenGnSys/__init__.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index d2bdc0d..4a17ead 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -53,8 +53,11 @@ 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) + # 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: @@ -207,19 +210,25 @@ class OpenGnSysWorker(ServerWorker): raise Exception('Message processor for "{}" not found'.format(path[0])) return operation(path[1:], get_params, post_params) + @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, ...} """ st = {'linux': 'LNX', 'macos': 'OSX', 'windows': 'WIN'} try: - res['status'] = st[operations.os_type.lower()] + # Standard status res = {'status': st[operations.os_type.lower()], 'loggedin': len(self.user) > 0} + # 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'} From 0440c7c37a10bff0b2c20f4fb94861760f1b3808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 4 May 2020 11:36:58 +0200 Subject: [PATCH 06/30] #968: Define execution levels in OGAgent. --- src/cfg/ogagent.cfg | 5 ++- .../modules/server/OpenGnSys/__init__.py | 35 +++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/cfg/ogagent.cfg b/src/cfg/ogagent.cfg index 3fa38ab..0291e88 100644 --- a/src/cfg/ogagent.cfg +++ b/src/cfg/ogagent.cfg @@ -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 diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 4a17ead..466b1e3 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -31,12 +31,11 @@ from __future__ import unicode_literals import os -import platform -import time import random import shutil import string import threading +import time import urllib from opengnsys.workers import ServerWorker @@ -67,6 +66,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): @@ -85,6 +104,7 @@ class OpenGnSysWorker(ServerWorker): user = [] # User sessions random = None # Random string for secure connections length = 32 # Random string length + exec_level = None # Execution level (permitted operations) def onActivation(self): """ @@ -96,6 +116,11 @@ class OpenGnSysWorker(ServerWorker): # Ensure cfg has required configuration variables or an exception will be thrown url = self.service.config.get('opengnsys', 'remote') self.REST = REST(url) + # Execution level ('full' by default) + try: + self.exec_level = self.service.config.get('opengnsys', 'level') + except: + self.exec_level = 'full' # Get network interfaces until they are active or timeout (5 minutes) for t in range(0, 300): try: @@ -211,6 +236,7 @@ class OpenGnSysWorker(ServerWorker): return operation(path[1:], get_params, post_params) @check_secret + @execution_level('status') def process_status(self, path, get_params, post_params, server): """ Returns client status (OS type or execution status) and login status @@ -235,6 +261,7 @@ class OpenGnSysWorker(ServerWorker): return res @check_secret + @execution_level('halt') def process_reboot(self, path, get_params, post_params, server): """ Launches a system reboot operation @@ -253,6 +280,7 @@ class OpenGnSysWorker(ServerWorker): return {'op': 'launched'} @check_secret + @execution_level('halt') def process_poweroff(self, path, get_params, post_params, server): """ Launches a system power off operation @@ -272,6 +300,7 @@ class OpenGnSysWorker(ServerWorker): return {'op': 'launched'} @check_secret + @execution_level('full') def process_script(self, path, get_params, post_params, server): """ Processes an script execution (script should be encoded in base64) @@ -298,6 +327,7 @@ class OpenGnSysWorker(ServerWorker): return {'op': 'launched'} @check_secret + @execution_level('full') def process_logoff(self, path, get_params, post_params, server): """ Closes user session @@ -308,6 +338,7 @@ class OpenGnSysWorker(ServerWorker): return {'op': 'sent to client'} @check_secret + @execution_level('full') def process_popup(self, path, get_params, post_params, server): """ Shows a message popup on the user's session From 1528428d8b979c82dc6fda2e9abf5fb0c6d4470e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 4 May 2020 19:42:33 +0200 Subject: [PATCH 07/30] #940: OGAgent "about" box supporting Qt5 --- src/OGAgentUser.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/OGAgentUser.py b/src/OGAgentUser.py index 7eefbb0..478c40e 100644 --- a/src/OGAgentUser.py +++ b/src/OGAgentUser.py @@ -31,12 +31,12 @@ """ from __future__ import unicode_literals +import json import sys import time -import json +from PyQt5 import QtCore, QtGui, QtWidgets import six import atexit -from PyQt4 import QtCore, QtGui # @UnresolvedImport from opengnsys import VERSION, ipc, operations, utils from opengnsys.log import logger @@ -63,7 +63,7 @@ def sigAtExit(): # About dialog class OGAAboutDialog(QtGui.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 +72,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 +89,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): @@ -157,7 +157,7 @@ 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) @@ -174,10 +174,10 @@ class OGASystemTray(QtGui.QSystemTrayIcon): # 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) @@ -310,14 +310,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) From 53e7d458c59a44cea24b9d05ccf481feaffc0191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 4 May 2020 19:51:32 +0200 Subject: [PATCH 08/30] #940: Run `2to3` on OGAgent source code Result after running the command: `2to3 -w ogagent/src` --- src/OGAgentUser.py | 56 ++++++++----------- src/OGAgent_rc.py | 2 +- src/opengnsys/RESTApi.py | 2 +- src/opengnsys/__init__.py | 6 +- src/opengnsys/config.py | 14 ++--- src/opengnsys/httpserver.py | 2 +- src/opengnsys/ipc.py | 2 +- src/opengnsys/linux/OGAgentService.py | 11 ++-- src/opengnsys/linux/operations.py | 2 +- src/opengnsys/linux/renamer/__init__.py | 2 +- src/opengnsys/linux/renamer/debian.py | 2 +- src/opengnsys/linux/renamer/opensuse.py | 2 +- src/opengnsys/linux/renamer/redhat.py | 2 +- src/opengnsys/loader.py | 2 +- src/opengnsys/log.py | 2 +- src/opengnsys/macos/__init__.py | 2 +- src/opengnsys/macos/operations.py | 2 +- .../modules/client/OpenGnSys/__init__.py | 2 +- .../modules/server/OpenGnSys/__init__.py | 13 +++-- src/opengnsys/operations.py | 2 +- src/opengnsys/service.py | 6 +- src/opengnsys/utils.py | 2 +- src/opengnsys/windows/OGAgentService.py | 2 +- src/opengnsys/windows/__init__.py | 2 +- src/opengnsys/windows/log.py | 2 +- src/opengnsys/windows/operations.py | 12 ++-- src/opengnsys/workers/client_worker.py | 2 +- src/opengnsys/workers/server_worker.py | 2 +- src/prototypes/threaded_server.py | 2 +- src/test_modules/client/Sample1/__init__.py | 2 +- src/test_modules/server/Sample1/__init__.py | 2 +- src/test_modules/server/Sample1/sample1.py | 2 +- .../server/Sample1/sample_pkg/__init__.py | 2 +- src/test_rest_server.py | 2 +- 34 files changed, 81 insertions(+), 91 deletions(-) diff --git a/src/OGAgentUser.py b/src/OGAgentUser.py index 478c40e..3b83250 100644 --- a/src/OGAgentUser.py +++ b/src/OGAgentUser.py @@ -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 from PyQt5 import QtCore, QtGui, QtWidgets -import six -import atexit -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,7 +54,7 @@ def sigAtExit(): # About dialog -class OGAAboutDialog(QtGui.QDialog): +class OGAAboutDialog(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self, parent) self.ui = Ui_OGAAboutDialog() @@ -115,13 +108,13 @@ class MessagesProcessor(QtCore.QThread): def isAlive(self): return self.ipc is not None - def sendLogin(self, userName, language): + def sendLogin(self, username, language): if self.ipc: - self.ipc.sendLogin(userName, language) + self.ipc.sendLogin(username, language) - 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,15 +129,15 @@ class MessagesProcessor(QtCore.QThread): msg = self.ipc.getMessage() if msg is None: break - msgId, data = msg + msg_id, data = msg logger.debug('Got Message on User Space: {}:{}'.format(msgId, data)) - if msgId == ipc.MSG_MESSAGE: + if msg_id == ipc.MSG_MESSAGE: module, message, data = data.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))) @@ -161,17 +154,14 @@ 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') QtWidgets.QSystemTrayIcon.__init__(self, icon, parent) @@ -208,18 +198,16 @@ class OGASystemTray(QtWidgets.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()) @@ -259,7 +247,7 @@ class OGASystemTray(QtWidgets.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() diff --git a/src/OGAgent_rc.py b/src/OGAgent_rc.py index b0a9562..affcb8c 100644 --- a/src/OGAgent_rc.py +++ b/src/OGAgent_rc.py @@ -6,7 +6,7 @@ # # WARNING! All changes made in this file will be lost! -from PyQt4 import QtCore +from PyQt5 import QtCore qt_resource_data = b"\ \x00\x00\x0f\x42\ diff --git a/src/opengnsys/RESTApi.py b/src/opengnsys/RESTApi.py index d785dfa..8773cf5 100644 --- a/src/opengnsys/RESTApi.py +++ b/src/opengnsys/RESTApi.py @@ -32,7 +32,7 @@ # pylint: disable-msg=E1101,W0703 -from __future__ import unicode_literals + import requests import logging diff --git a/src/opengnsys/__init__.py b/src/opengnsys/__init__.py index 7be8700..1cad939 100644 --- a/src/opengnsys/__init__.py +++ b/src/opengnsys/__init__.py @@ -29,13 +29,13 @@ """ @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: diff --git a/src/opengnsys/config.py b/src/opengnsys/config.py index d1f3ede..7ffe046 100644 --- a/src/opengnsys/config.py +++ b/src/opengnsys/config.py @@ -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 - \ No newline at end of file diff --git a/src/opengnsys/httpserver.py b/src/opengnsys/httpserver.py index a06ae6b..d11a4e2 100644 --- a/src/opengnsys/httpserver.py +++ b/src/opengnsys/httpserver.py @@ -29,7 +29,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 import six diff --git a/src/opengnsys/ipc.py b/src/opengnsys/ipc.py index dd3663d..aad3921 100644 --- a/src/opengnsys/ipc.py +++ b/src/opengnsys/ipc.py @@ -28,7 +28,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import socket import threading diff --git a/src/opengnsys/linux/OGAgentService.py b/src/opengnsys/linux/OGAgentService.py index 29a238d..76bfbb8 100644 --- a/src/opengnsys/linux/OGAgentService.py +++ b/src/opengnsys/linux/OGAgentService.py @@ -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') diff --git a/src/opengnsys/linux/operations.py b/src/opengnsys/linux/operations.py index f3236e2..3b2c5d3 100644 --- a/src/opengnsys/linux/operations.py +++ b/src/opengnsys/linux/operations.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import socket import platform diff --git a/src/opengnsys/linux/renamer/__init__.py b/src/opengnsys/linux/renamer/__init__.py index 27198dc..7b3bc69 100644 --- a/src/opengnsys/linux/renamer/__init__.py +++ b/src/opengnsys/linux/renamer/__init__.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import platform import os diff --git a/src/opengnsys/linux/renamer/debian.py b/src/opengnsys/linux/renamer/debian.py index be55e00..2e6e2a1 100644 --- a/src/opengnsys/linux/renamer/debian.py +++ b/src/opengnsys/linux/renamer/debian.py @@ -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 diff --git a/src/opengnsys/linux/renamer/opensuse.py b/src/opengnsys/linux/renamer/opensuse.py index a2d29a5..3f05e4e 100644 --- a/src/opengnsys/linux/renamer/opensuse.py +++ b/src/opengnsys/linux/renamer/opensuse.py @@ -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 diff --git a/src/opengnsys/linux/renamer/redhat.py b/src/opengnsys/linux/renamer/redhat.py index 8821a81..2605c22 100644 --- a/src/opengnsys/linux/renamer/redhat.py +++ b/src/opengnsys/linux/renamer/redhat.py @@ -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 diff --git a/src/opengnsys/loader.py b/src/opengnsys/loader.py index 23988fc..ca053b6 100644 --- a/src/opengnsys/loader.py +++ b/src/opengnsys/loader.py @@ -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 diff --git a/src/opengnsys/log.py b/src/opengnsys/log.py index e34c087..c559113 100644 --- a/src/opengnsys/log.py +++ b/src/opengnsys/log.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import traceback import sys diff --git a/src/opengnsys/macos/__init__.py b/src/opengnsys/macos/__init__.py index ee5ba4c..988217f 100644 --- a/src/opengnsys/macos/__init__.py +++ b/src/opengnsys/macos/__init__.py @@ -29,4 +29,4 @@ ''' @author: Ramón M. Gómez, ramongomez at us dot es ''' -from __future__ import unicode_literals + diff --git a/src/opengnsys/macos/operations.py b/src/opengnsys/macos/operations.py index 286b6d8..15b6b11 100644 --- a/src/opengnsys/macos/operations.py +++ b/src/opengnsys/macos/operations.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import socket import platform diff --git a/src/opengnsys/modules/client/OpenGnSys/__init__.py b/src/opengnsys/modules/client/OpenGnSys/__init__.py index adeb0a6..51a5137 100644 --- a/src/opengnsys/modules/client/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/client/OpenGnSys/__init__.py @@ -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 diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 466b1e3..b7f2618 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Copyright (c) 2014 Virtual Cable S.L. @@ -28,7 +29,7 @@ """ @author: Ramón M. Gómez, ramongomez at us dot es """ -from __future__ import unicode_literals + import os import random @@ -36,12 +37,14 @@ import shutil import string import threading import time -import urllib +import urllib.error +import urllib.parse +import urllib.request -from opengnsys.workers import ServerWorker -from opengnsys import REST, RESTError, operations, VERSION +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 @@ -312,7 +315,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(post_params.get('script').decode('base64')).decode('utf8') 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')])) diff --git a/src/opengnsys/operations.py b/src/opengnsys/operations.py index be777dd..1ec9872 100644 --- a/src/opengnsys/operations.py +++ b/src/opengnsys/operations.py @@ -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. diff --git a/src/opengnsys/service.py b/src/opengnsys/service.py index 4a44028..9f8295b 100644 --- a/src/opengnsys/service.py +++ b/src/opengnsys/service.py @@ -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 .log import logger from .config import readConfig diff --git a/src/opengnsys/utils.py b/src/opengnsys/utils.py index 9480a6a..36bef63 100644 --- a/src/opengnsys/utils.py +++ b/src/opengnsys/utils.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import sys import six diff --git a/src/opengnsys/windows/OGAgentService.py b/src/opengnsys/windows/OGAgentService.py index 71716d7..857b2cc 100644 --- a/src/opengnsys/windows/OGAgentService.py +++ b/src/opengnsys/windows/OGAgentService.py @@ -29,7 +29,7 @@ ''' @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 diff --git a/src/opengnsys/windows/__init__.py b/src/opengnsys/windows/__init__.py index e662942..c47fe7d 100644 --- a/src/opengnsys/windows/__init__.py +++ b/src/opengnsys/windows/__init__.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import os import sys diff --git a/src/opengnsys/windows/log.py b/src/opengnsys/windows/log.py index 745fd03..16750ae 100644 --- a/src/opengnsys/windows/log.py +++ b/src/opengnsys/windows/log.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import servicemanager # @UnresolvedImport, pylint: disable=import-error import logging diff --git a/src/opengnsys/windows/operations.py b/src/opengnsys/windows/operations.py index 95331fd..51d56d7 100644 --- a/src/opengnsys/windows/operations.py +++ b/src/opengnsys/windows/operations.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + import os import locale @@ -102,12 +102,12 @@ def getWindowsVersion(): ''' Returns Windows version. ''' - import _winreg - reg = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion') + import winreg + reg = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion') try: - data = '{} {}'.format(_winreg.QueryValueEx(reg, 'ProductName')[0], _winreg.QueryValueEx(reg, 'ReleaseId')[0]) + data = '{} {}'.format(winreg.QueryValueEx(reg, 'ProductName')[0], winreg.QueryValueEx(reg, 'ReleaseId')[0]) except Exception: - data = '{} {}'.format(_winreg.QueryValueEx(reg, 'ProductName')[0], _winreg.QueryValueEx(reg, 'CurrentBuildNumber')[0]) + data = '{} {}'.format(winreg.QueryValueEx(reg, 'ProductName')[0], winreg.QueryValueEx(reg, 'CurrentBuildNumber')[0]) reg.Close() return data @@ -200,7 +200,7 @@ def joinDomain(domain, ou, account, password, executeInOneStep=False): error = getErrorMessage(res) if res == 1355: error = "DC Is not reachable" - print('{} {}'.format(res, error)) + print(('{} {}'.format(res, error))) raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain.value, account.value, ', under OU {}'.format(ou.value) if ou.value is not None else '', res, error)) diff --git a/src/opengnsys/workers/client_worker.py b/src/opengnsys/workers/client_worker.py index 6a08380..b294f92 100644 --- a/src/opengnsys/workers/client_worker.py +++ b/src/opengnsys/workers/client_worker.py @@ -29,7 +29,7 @@ @author: Adolfo Gómez, dkmaster at dkmon dot com ''' # pylint: disable=unused-wildcard-import,wildcard-import -from __future__ import unicode_literals + class ClientWorker(object): ''' diff --git a/src/opengnsys/workers/server_worker.py b/src/opengnsys/workers/server_worker.py index 141d657..79268b8 100644 --- a/src/opengnsys/workers/server_worker.py +++ b/src/opengnsys/workers/server_worker.py @@ -29,7 +29,7 @@ @author: Adolfo Gómez, dkmaster at dkmon dot com ''' # pylint: disable=unused-wildcard-import,wildcard-import -from __future__ import unicode_literals + class ServerWorker(object): ''' diff --git a/src/prototypes/threaded_server.py b/src/prototypes/threaded_server.py index ed5583b..b8d7751 100644 --- a/src/prototypes/threaded_server.py +++ b/src/prototypes/threaded_server.py @@ -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 diff --git a/src/test_modules/client/Sample1/__init__.py b/src/test_modules/client/Sample1/__init__.py index db174f0..a6dcf49 100644 --- a/src/test_modules/client/Sample1/__init__.py +++ b/src/test_modules/client/Sample1/__init__.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + from opengnsys.workers import ClientWorker diff --git a/src/test_modules/server/Sample1/__init__.py b/src/test_modules/server/Sample1/__init__.py index 189957e..0359b8a 100644 --- a/src/test_modules/server/Sample1/__init__.py +++ b/src/test_modules/server/Sample1/__init__.py @@ -1,2 +1,2 @@ # Module must be imported on package, so we can initialize and load it -from sample1 import Sample1 \ No newline at end of file +from .sample1 import Sample1 \ No newline at end of file diff --git a/src/test_modules/server/Sample1/sample1.py b/src/test_modules/server/Sample1/sample1.py index 61e405e..08cb90f 100644 --- a/src/test_modules/server/Sample1/sample1.py +++ b/src/test_modules/server/Sample1/sample1.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + from opengnsys.workers import ServerWorker diff --git a/src/test_modules/server/Sample1/sample_pkg/__init__.py b/src/test_modules/server/Sample1/sample_pkg/__init__.py index 1936572..571a11c 100644 --- a/src/test_modules/server/Sample1/sample_pkg/__init__.py +++ b/src/test_modules/server/Sample1/sample_pkg/__init__.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals + def test(): diff --git a/src/test_rest_server.py b/src/test_rest_server.py index ad1aede..f1bdd37 100644 --- a/src/test_rest_server.py +++ b/src/test_rest_server.py @@ -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 From 909a6260011492b4694e9a08c676903e0fe67f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 4 May 2020 19:59:36 +0200 Subject: [PATCH 09/30] #940: More `2to3` changes --- src/OGAServiceHelper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OGAServiceHelper.py b/src/OGAServiceHelper.py index ca75a09..ddcdf2d 100644 --- a/src/OGAServiceHelper.py +++ b/src/OGAServiceHelper.py @@ -29,7 +29,6 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -from __future__ import unicode_literals import win32service import win32serviceutil From 1b0abe2f6147cbb0ddff39680c0391556ed108bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 4 May 2020 20:00:45 +0200 Subject: [PATCH 10/30] #940: Adapting OGAgent for Linux to Python 3 --- linux/debian/changelog | 6 ++++++ linux/debian/control | 4 ++-- linux/ogagent-template.spec | 5 ++++- linux/scripts/OGAgentTool | 6 +++--- linux/scripts/ogagent | 6 +++--- src/update.sh | 6 +++--- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/linux/debian/changelog b/linux/debian/changelog index 9b59a25..d4db09c 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,9 @@ +ogagent (1.2.0) unstable; urgency=medium + + * Python 3 and Qt 5 compatibility + + -- Ramón M. Gómez Mon, 4 May 2020 18:00:00 +0100 + ogagent (1.1.1b) stable; urgency=medium * Use python-distro to detect the distribution version diff --git a/linux/debian/control b/linux/debian/control index 7df4dea..1dee8fb 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -11,8 +11,8 @@ 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} + policykit-1 (>= 0.100), python3 (>=3.5) | python (>= 3.5), python3-qt5, python3-requests, + python3-six, python3-prctl, python3-distro, libxss1, ${misc:Depends} Suggests: gnome-shell-extension-top-icons-plus Description: OpenGnsys Agent for Operating Systems This package provides the required components to allow this machine to work on an environment managed by OpenGnsys. diff --git a/linux/ogagent-template.spec b/linux/ogagent-template.spec index 7f55454..f88071a 100644 --- a/linux/ogagent-template.spec +++ b/linux/ogagent-template.spec @@ -11,7 +11,7 @@ Release: %{release} Summary: OpenGnsys Agent for Operating Systems License: BSD3 Group: Admin -Requires: chkconfig initscripts python-six python-requests python-distro PyQt4 libXScrnSaver +Requires: chkconfig initscripts libXScrnSaver python3-distro python3-qt5 python3-requests python3-six Vendor: OpenGnsys Project URL: https://opengnsys.es/ Provides: ogagent @@ -65,6 +65,9 @@ This package provides the required components to allow this machine to work on a /usr/share/autostart/OGAgentTool.desktop %changelog +* Mon May 04 2020 Ramón M. Gómez - 1.2.0 +- Python 3 and Qt 5 compatibility + * Fri Feb 07 2020 Ramón M. Gómez - 1.1.1b-1 - Use python-distro to detect the distribution version diff --git a/linux/scripts/OGAgentTool b/linux/scripts/OGAgentTool index da91704..5195b11 100755 --- a/linux/scripts/OGAgentTool +++ b/linux/scripts/OGAgentTool @@ -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 + [ -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 diff --git a/linux/scripts/ogagent b/linux/scripts/ogagent index 155af67..f06a004 100755 --- a/linux/scripts/ogagent +++ b/linux/scripts/ogagent @@ -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 + [ -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 diff --git a/src/update.sh b/src/update.sh index e6e02ab..f1db2ad 100755 --- a/src/update.sh +++ b/src/update.sh @@ -28,13 +28,13 @@ function process { - pyuic4 about-dialog.ui -o about_dialog_ui.py -x - pyuic4 message-dialog.ui -o message_dialog_ui.py -x + pyuic5 about-dialog.ui -o about_dialog_ui.py -x + pyuic5 message-dialog.ui -o message_dialog_ui.py -x } cd $(dirname "$0") [ -r VERSION ] && sed -i "s/Version [^<]*/Version $(cat VERSION)/" about-dialog.ui -pyrcc4 -py3 OGAgent.qrc -o OGAgent_rc.py +pyrcc5 OGAgent.qrc -o OGAgent_rc.py # process current directory ui's From 12f0b1d654ed1627091b27c2aa9b0fd042f4e196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 6 May 2020 19:27:22 +0200 Subject: [PATCH 11/30] #940: Updating dependencies to build OGAgent for Linux --- linux/build-packages.sh | 1 - linux/debian/control | 3 +-- src/about-dialog.ui | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/linux/build-packages.sh b/linux/build-packages.sh index facde2c..9c8b9d7 100755 --- a/linux/build-packages.sh +++ b/linux/build-packages.sh @@ -19,7 +19,6 @@ sed -e "s/name ogagent/name ogagent-opensuse/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 diff --git a/linux/debian/control b/linux/debian/control index 1dee8fb..e085ad8 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -11,8 +11,7 @@ Section: admin Priority: optional Architecture: all Depends: - policykit-1 (>= 0.100), python3 (>=3.5) | python (>= 3.5), python3-qt5, python3-requests, + policykit-1 (>= 0.100), python3 (>=3.4) | python (>= 3.4), python3-pyqt5, python3-requests, python3-six, python3-prctl, python3-distro, libxss1, ${misc:Depends} -Suggests: gnome-shell-extension-top-icons-plus Description: OpenGnsys Agent for Operating Systems This package provides the required components to allow this machine to work on an environment managed by OpenGnsys. diff --git a/src/about-dialog.ui b/src/about-dialog.ui index ab0cac1..ae04a66 100644 --- a/src/about-dialog.ui +++ b/src/about-dialog.ui @@ -65,7 +65,7 @@ - Version 1.1.0 + Version 1.2.0 From e274dc0ee990599fa9da05292a8b06d168de721a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 6 May 2020 19:29:31 +0200 Subject: [PATCH 12/30] #940: Fix string and byte conversions --- src/opengnsys/httpserver.py | 26 ++++++------ src/opengnsys/service.py | 80 ++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 59 deletions(-) diff --git a/src/opengnsys/httpserver.py b/src/opengnsys/httpserver.py index d11a4e2..fd21be9 100644 --- a/src/opengnsys/httpserver.py +++ b/src/opengnsys/httpserver.py @@ -32,7 +32,6 @@ # Pydev can't parse "six.moves.xxxx" because it is loaded lazy -import six from six.moves.socketserver import ThreadingMixIn # @UnresolvedImport from six.moves.BaseHTTPServer import BaseHTTPRequestHandler # @UnresolvedImport from six.moves.BaseHTTPServer import HTTPServer # @UnresolvedImport @@ -46,6 +45,7 @@ from .utils import exceptionToMessage from .certs import createSelfSignedCert from .log import logger + class HTTPServerHandler(BaseHTTPRequestHandler): service = None protocol_version = 'HTTP/1.0' @@ -56,7 +56,7 @@ 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): @@ -66,12 +66,12 @@ class HTTPServerHandler(BaseHTTPRequestHandler): self.send_header('Content-Length', 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,14 +81,14 @@ 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): - ''' + """ Locates witch module will process the message based on path (first folder on url path) - ''' + """ try: data = module.processServerMessage(path, getParams, postParams, self) self.sendJsonResponse(data) @@ -114,7 +114,6 @@ class HTTPServerHandler(BaseHTTPRequestHandler): self.sendJsonError(500, exceptionToMessage(e)) self.notifyMessage(module, path, getParams, postParams) - def log_error(self, fmt, *args): logger.error('HTTP ' + fmt % args) @@ -126,6 +125,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 +146,3 @@ class HTTPServerThread(threading.Thread): def run(self): self.server.serve_forever() - - diff --git a/src/opengnsys/service.py b/src/opengnsys/service.py index 9f8295b..e7da465 100644 --- a/src/opengnsys/service.py +++ b/src/opengnsys/service.py @@ -30,19 +30,17 @@ @author: Adolfo Gómez, dkmaster at dkmon dot com """ - -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) From de4289ae2bc85876acd5cdd12a31afe68a1c0a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 6 May 2020 19:31:33 +0200 Subject: [PATCH 13/30] #940: Proper sequence of decorators --- .../modules/server/OpenGnSys/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index b7f2618..169a588 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -54,7 +54,7 @@ def check_secret(fnc): """ def wrapper(*args, **kwargs): try: - this, path, get_params, post_params, server = args # @UnusedVariable + 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) @@ -238,8 +238,9 @@ class OpenGnSysWorker(ServerWorker): raise Exception('Message processor for "{}" not found'.format(path[0])) return operation(path[1:], get_params, post_params) - @check_secret + # 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 @@ -263,8 +264,8 @@ class OpenGnSysWorker(ServerWorker): res = {'status': 'UNK'} return res - @check_secret @execution_level('halt') + @check_secret def process_reboot(self, path, get_params, post_params, server): """ Launches a system reboot operation @@ -282,8 +283,8 @@ class OpenGnSysWorker(ServerWorker): threading.Thread(target=rebt).start() return {'op': 'launched'} - @check_secret @execution_level('halt') + @check_secret def process_poweroff(self, path, get_params, post_params, server): """ Launches a system power off operation @@ -302,8 +303,8 @@ class OpenGnSysWorker(ServerWorker): threading.Thread(target=pwoff).start() return {'op': 'launched'} - @check_secret @execution_level('full') + @check_secret def process_script(self, path, get_params, post_params, server): """ Processes an script execution (script should be encoded in base64) @@ -329,8 +330,8 @@ class OpenGnSysWorker(ServerWorker): self.sendClientMessage('script', {'code': script}) return {'op': 'launched'} - @check_secret @execution_level('full') + @check_secret def process_logoff(self, path, get_params, post_params, server): """ Closes user session @@ -340,8 +341,8 @@ class OpenGnSysWorker(ServerWorker): self.sendClientMessage('logoff', {}) return {'op': 'sent to client'} - @check_secret @execution_level('full') + @check_secret def process_popup(self, path, get_params, post_params, server): """ Shows a message popup on the user's session From 7521269654e0c5623bddf20478ca536f51686ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 6 May 2020 19:40:23 +0200 Subject: [PATCH 14/30] #940: Prepare OGAgent for Windows builder --- windows/py2exe-wine-linux.sh | 67 ------------------------------------ windows/pyinstaller-wine.sh | 56 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 67 deletions(-) delete mode 100755 windows/py2exe-wine-linux.sh create mode 100755 windows/pyinstaller-wine.sh diff --git a/windows/py2exe-wine-linux.sh b/windows/py2exe-wine-linux.sh deleted file mode 100755 index 68229b0..0000000 --- a/windows/py2exe-wine-linux.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh - -# We need: -# * Wine (32 bit) -# * winetricks (in some distributions) - -export WINEARCH=win32 WINEPREFIX=$PWD/wine WINEDEBUG=fixme-all -WINE=wine - -download() { - mkdir downloads - # Get needed software - cd downloads - wget -nd https://www.python.org/ftp/python/2.7.17/python-2.7.17.msi -O python-2.7.msi - wget -nd https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi - wget -nd https://bootstrap.pypa.io/get-pip.py - wget -nd https://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.4/PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe/download -O pyqt-install.exe - wget -nd https://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download -O nsis-install.exe - wget -nd http://nsis.sourceforge.net/mediawiki/images/d/d7/NSIS_Simple_Firewall_Plugin_1.20.zip - cd .. -} - -install_python() { - if which winetricks &>/dev/null; then - echo "Setting up wine prefix (using winetricks)" - winetricks - fi - - cd downloads - echo "Installing python" - $WINE msiexec /qn /i python-2.7.msi - echo "Installing vc for python" - $WINE msiexec /qn /i VCForPython27.msi - - echo "Installing pyqt (needs X)" - $WINE pyqt-install.exe - echo "Installing nsis (needs X?)" - $WINE nsis-install.exe - - cd .. -} - -setup_pip() { - echo "Seting up pip..." - $WINE C:\\Python27\\python -m pip install --upgrade pip -} - -install_packages() { - echo "Installing pywin32" - $WINE C:\\Python27\\python -m pip install pywin32 - echo "Installing py2exe" - $WINE C:\\Python27\\python -m pip install py2exe_py2 - echo "Installing required packages" - $WINE C:\\Python27\\python -m pip install requests six - # Using easy_install instead of pip to install pycrypto - $WINE C:\\Python27\\Scripts\\easy_install http://www.voidspace.org.uk/python/pycrypto-2.6.1/pycrypto-2.6.1.win32-py2.7.exe - # Copy nsis required NSIS_Simple_Firewall_Plugin_1 - echo "Copying simple firewall plugin for nsis installer" - unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-ansi/ - unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-unicode/ -} - -download -install_python -setup_pip -install_packages - diff --git a/windows/pyinstaller-wine.sh b/windows/pyinstaller-wine.sh new file mode 100755 index 0000000..9d4b59f --- /dev/null +++ b/windows/pyinstaller-wine.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +# We need: +# * Wine (64 bit) +# * winetricks (in some distributions) + +export WINEARCH=win64 WINEPREFIX=$PWD/wine WINEDEBUG=fixme-all +WINE=wine + +# Get needed software +download() { + mkdir -p downloads + cd downloads + wget -nd https://www.python.org/ftp/python/3.7.7/python-3.7.7-amd64.exe -O python3.msi + wget -nd https://download.visualstudio.microsoft.com/download/pr/5e397ebe-38b2-4e18-a187-ac313d07332a/00945fbb0a29f63183b70370043e249218249f83dbc82cd3b46c5646503f9e27/vs_BuildTools.exe + wget -nd https://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download -O nsis-install.exe + wget -nd http://nsis.sourceforge.net/mediawiki/images/d/d7/NSIS_Simple_Firewall_Plugin_1.20.zip + cd .. +} + +install_python() { + cd downloads + echo "Installing python" + $WINE python3.msi /quiet TargetDir=C:\\Python37 PrependPath=1 + echo "Installing Build Tools for Visual Studio" + $WINE vs_BuildTools.exe + echo "Installing NSIS (needs X?)" + $WINE nsis-install.exe /S + cd .. +} + +setup_pip() { + echo "Setting up pip and setuptools" + $WINE pip install --upgrade pip + $WINE pip install --upgrade setuptools +} + +install_packages() { + echo "Installing PyQt5" + $WINE pip install PyQt5 + echo "Installing required packages" + $WINE pip install pycrypto requests six + # Using easy_install instead of pip to install pycrypto + $WINE pip install pycrypto + echo "Installing PyInstaller" + $WINE pip install PyInstaller + echo "Copying simple firewall plugin for nsis installer" + unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-ansi/ + unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-unicode/ +} + +download +install_python +setup_pip +install_packages + From 23503892b20a7e9c4abe0d85418e3e0a032a3623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 7 May 2020 17:34:57 +0200 Subject: [PATCH 15/30] #940: Fix HTTP header bug Function `send-header` needs `str`, not `int; adding some PEP 8 code clean up. --- src/opengnsys/httpserver.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/opengnsys/httpserver.py b/src/opengnsys/httpserver.py index fd21be9..69c4317 100644 --- a/src/opengnsys/httpserver.py +++ b/src/opengnsys/httpserver.py @@ -25,22 +25,19 @@ # 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 +""" -# Pydev can't parse "six.moves.xxxx" because it is loaded lazy +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 @@ -63,7 +60,7 @@ class HTTPServerHandler(BaseHTTPRequestHandler): 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(str.encode(data)) @@ -85,12 +82,12 @@ class HTTPServerHandler(BaseHTTPRequestHandler): 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,18 +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) From 683d8d4427faafc5aba5f2ac201c35492795819b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 7 May 2020 17:44:40 +0200 Subject: [PATCH 16/30] #940: Fix Base64 conversion when reading POST parameters --- src/opengnsys/modules/server/OpenGnSys/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 169a588..b48c3f1 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -31,6 +31,7 @@ """ +import base64 import os import random import shutil @@ -316,7 +317,7 @@ class OpenGnSysWorker(ServerWorker): """ logger.debug('Processing script request') # Decoding script (Windows scripts need a subprocess call per line) - script = urllib.parse.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')])) From 9424789f6989c174f2b15ad2b1d7f010d2024f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 30 Jun 2020 14:55:35 +0200 Subject: [PATCH 17/30] #940: Adapt code to run the Linux daemon. --- src/opengnsys/linux/__init__.py | 5 ++--- src/opengnsys/linux/daemon.py | 14 ++++++-------- src/opengnsys/linux/log.py | 12 +++++------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/opengnsys/linux/__init__.py b/src/opengnsys/linux/__init__.py index 3a98c78..97ac9be 100644 --- a/src/opengnsys/linux/__init__.py +++ b/src/opengnsys/linux/__init__.py @@ -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 +""" diff --git a/src/opengnsys/linux/daemon.py b/src/opengnsys/linux/daemon.py index 3753808..a2e8960 100644 --- a/src/opengnsys/linux/daemon.py +++ b/src/opengnsys/linux/daemon.py @@ -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()) diff --git a/src/opengnsys/linux/log.py b/src/opengnsys/linux/log.py index dc54e19..396a2db 100644 --- a/src/opengnsys/linux/log.py +++ b/src/opengnsys/linux/log.py @@ -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: From 6900215e9c476bfaafca883e2ed202db9017db12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 30 Jun 2020 18:37:48 +0200 Subject: [PATCH 18/30] #940: Avoid error when Linux daemon scripts look for Python version. --- linux/scripts/OGAgentTool | 2 +- linux/scripts/ogagent | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/linux/scripts/OGAgentTool b/linux/scripts/OGAgentTool index 5195b11..0801591 100755 --- a/linux/scripts/OGAgentTool +++ b/linux/scripts/OGAgentTool @@ -1,7 +1,7 @@ #!/bin/sh for p in python python3; do - [ -z "$PYTHON" ] && [ $($p -c 'import sys; print(sys.version_info[0])') -eq 3 ] && PYTHON=$p + [ "$(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 3" &>2 diff --git a/linux/scripts/ogagent b/linux/scripts/ogagent index f06a004..ba83021 100755 --- a/linux/scripts/ogagent +++ b/linux/scripts/ogagent @@ -1,7 +1,7 @@ #!/bin/sh for p in python python3; do - [ -z "$PYTHON" ] && [ $($p -c 'import sys; print(sys.version_info[0])') -eq 3 ] && PYTHON=$p + [ "$(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 3" &>2 From 68c4c914959eeccfcfdfffbdcb74df6e1527b91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 30 Jun 2020 19:34:01 +0200 Subject: [PATCH 19/30] #940: Log error message if config parameter is missing. --- .../modules/server/OpenGnSys/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index b48c3f1..13eb5b4 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -42,6 +42,7 @@ import urllib.error import urllib.parse import urllib.request +from configparser import NoOptionError from opengnsys import REST, operations, VERSION from opengnsys.log import logger from opengnsys.scriptThread import ScriptExecutorThread @@ -114,21 +115,27 @@ class OpenGnSysWorker(ServerWorker): """ 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: + 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 time.sleep(1) From bb685d9700afa0f22edc2daddbcdff1229f59e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 7 Jul 2020 13:37:57 +0200 Subject: [PATCH 20/30] #940: Fix wrong variable name. --- src/OGAgentUser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OGAgentUser.py b/src/OGAgentUser.py index 3b83250..8c1286b 100644 --- a/src/OGAgentUser.py +++ b/src/OGAgentUser.py @@ -130,7 +130,7 @@ class MessagesProcessor(QtCore.QThread): if msg is None: break msg_id, data = msg - logger.debug('Got Message on User Space: {}:{}'.format(msgId, data)) + logger.debug('Got Message on User Space: {}:{}'.format(msg_id, data)) if msg_id == ipc.MSG_MESSAGE: module, message, data = data.split('\0') self.message.emit((module, message, data)) From 9525724449a51bd2f3f03554261b21d8d5399245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 7 Jul 2020 13:46:35 +0200 Subject: [PATCH 21/30] #940: Convert some docstring as defined in PEP 257. --- src/opengnsys/workers/client_worker.py | 35 +++++++------ src/opengnsys/workers/server_worker.py | 68 +++++++++++++------------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/opengnsys/workers/client_worker.py b/src/opengnsys/workers/client_worker.py index b294f92..3e81685 100644 --- a/src/opengnsys/workers/client_worker.py +++ b/src/opengnsys/workers/client_worker.py @@ -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 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) - \ No newline at end of file diff --git a/src/opengnsys/workers/server_worker.py b/src/opengnsys/workers/server_worker.py index 79268b8..b002655 100644 --- a/src/opengnsys/workers/server_worker.py +++ b/src/opengnsys/workers/server_worker.py @@ -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 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): From e7774214a14d214ec26edfd2908236a539ddbfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 7 Jul 2020 13:50:42 +0200 Subject: [PATCH 22/30] #940: Remove more dependencies from Python Six. --- src/opengnsys/ipc.py | 68 ++++++++++++++++++++++---------------------- src/opengnsys/log.py | 18 ++++++------ 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/opengnsys/ipc.py b/src/opengnsys/ipc.py index aad3921..3a68071 100644 --- a/src/opengnsys/ipc.py +++ b/src/opengnsys/ipc.py @@ -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 -''' +""" +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(b) # 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(): 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,15 +243,15 @@ 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(): @@ -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,34 +311,34 @@ 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, username, language): - self.sendRequestMessage(REQ_LOGIN, username+','+language) + self.sendRequestMessage(REQ_LOGIN, username + ',' + language) def sendLogout(self, username): 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 - diff --git a/src/opengnsys/log.py b/src/opengnsys/log.py index c559113..9786ff2 100644 --- a/src/opengnsys/log.py +++ b/src/opengnsys/log.py @@ -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 -''' +""" -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,16 +51,17 @@ _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): level = _levelName.get(level, INFO) From db05f930b5dbff20c950e96ff40abd7e69997d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 7 Jul 2020 13:54:16 +0200 Subject: [PATCH 23/30] #940: Add files `.gitignore` and `requirements.txt`. --- .gitignore | 4 ++++ requires.txt | 3 --- src/requirements.txt | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 .gitignore delete mode 100644 requires.txt create mode 100644 src/requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..430da91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +.editorconfig +.idea +__pycache__/ diff --git a/requires.txt b/requires.txt deleted file mode 100644 index 07ce387..0000000 --- a/requires.txt +++ /dev/null @@ -1,3 +0,0 @@ -six -requests - diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..abedc7c --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,3 @@ +netifaces +requests +urllib3 From b21ea0708b348489b737943cc8e8ffda5decce5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 7 Jul 2020 17:51:49 +0200 Subject: [PATCH 24/30] #940: Fix byte-int conversions. --- src/opengnsys/ipc.py | 6 +++--- src/opengnsys/log.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/opengnsys/ipc.py b/src/opengnsys/ipc.py index 3a68071..430ae45 100644 --- a/src/opengnsys/ipc.py +++ b/src/opengnsys/ipc.py @@ -128,7 +128,7 @@ class ClientProcessor(threading.Thread): # Client disconnected self.running = False break - buf = int(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)) @@ -153,7 +153,7 @@ class ClientProcessor(threading.Thread): recv_data = b'' continue elif state == ST_RECEIVING: - recv_data += bytes(buf) + recv_data += bytes([buf]) msg_len -= 1 if msg_len == 0: self.processRequest(recv_msg, recv_data) @@ -315,7 +315,7 @@ class ClientIPC(threading.Thread): data = str.encode(data) l = len(data) - msg = bytes(msg) + bytes(l & 0xFF) + bytes(l >> 8) + data + msg = bytes([msg]) + bytes([l & 0xFF]) + bytes([l >> 8]) + data self.clientSocket.sendall(msg) def sendLogin(self, username, language): diff --git a/src/opengnsys/log.py b/src/opengnsys/log.py index 9786ff2..5c74783 100644 --- a/src/opengnsys/log.py +++ b/src/opengnsys/log.py @@ -62,7 +62,7 @@ class Logger(object): 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 From 64c933fbb85a9cd428c07c9e9bdbb330817d0861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 8 Jul 2020 11:47:53 +0200 Subject: [PATCH 25/30] #940: Fix message encoding. --- src/OGAgentUser.py | 7 ++----- src/opengnsys/ipc.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/OGAgentUser.py b/src/OGAgentUser.py index 8c1286b..9c612cc 100644 --- a/src/OGAgentUser.py +++ b/src/OGAgentUser.py @@ -132,17 +132,14 @@ class MessagesProcessor(QtCore.QThread): 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.split('\0') + module, message, data = data.decode('utf-8').split('\0') self.message.emit((module, message, data)) elif msg_id == ipc.MSG_LOGOFF: self.logoff.emit() 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') diff --git a/src/opengnsys/ipc.py b/src/opengnsys/ipc.py index 430ae45..c421849 100644 --- a/src/opengnsys/ipc.py +++ b/src/opengnsys/ipc.py @@ -182,7 +182,7 @@ class ClientProcessor(threading.Thread): try: m = msg[1] if msg[1] is not None else b'' l = len(m) - data = MAGIC + bytes(msg[0]) + bytes(l & 0xFF) + bytes(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: From be263c6e2f809eb3d98faca99039f2dfa49283f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 23 Jul 2020 14:04:12 +0200 Subject: [PATCH 26/30] #992: Cherry-pick commit `af35fd9`. ogAgent sends the session type when user logs in. --- src/OGAgentUser.py | 7 ++++--- src/opengnsys/ipc.py | 4 ++-- src/opengnsys/linux/operations.py | 8 ++++++++ src/opengnsys/modules/server/OpenGnSys/__init__.py | 9 ++++++--- src/opengnsys/windows/operations.py | 8 ++++++++ src/setup.py | 4 ++-- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/OGAgentUser.py b/src/OGAgentUser.py index 9c612cc..86a95b0 100644 --- a/src/OGAgentUser.py +++ b/src/OGAgentUser.py @@ -108,9 +108,9 @@ class MessagesProcessor(QtCore.QThread): def isAlive(self): return self.ipc is not None - def sendLogin(self, username, language): + def sendLogin(self, user_data): if self.ipc: - self.ipc.sendLogin(username, language) + self.ipc.sendLogin(user_data) def sendLogout(self, username): if self.ipc: @@ -206,7 +206,8 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon): logger.error("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e))) 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()) + self.ipc.sendLogin((operations.getCurrentUser(), operations.getSessionLanguage(), + operations.get_session_type())) def deinitialize(self): for mod in reversed(self.modules): # Deinitialize reversed of initialization diff --git a/src/opengnsys/ipc.py b/src/opengnsys/ipc.py index c421849..677724e 100644 --- a/src/opengnsys/ipc.py +++ b/src/opengnsys/ipc.py @@ -318,8 +318,8 @@ class ClientIPC(threading.Thread): msg = bytes([msg]) + bytes([l & 0xFF]) + bytes([l >> 8]) + data self.clientSocket.sendall(msg) - def sendLogin(self, username, language): - self.sendRequestMessage(REQ_LOGIN, username + ',' + language) + def sendLogin(self, user_data): + self.sendRequestMessage(REQ_LOGIN, ','.join(user_data)) def sendLogout(self, username): self.sendRequestMessage(REQ_LOGOUT, username) diff --git a/src/opengnsys/linux/operations.py b/src/opengnsys/linux/operations.py index 3b2c5d3..9de9fa8 100644 --- a/src/opengnsys/linux/operations.py +++ b/src/opengnsys/linux/operations.py @@ -282,6 +282,14 @@ def getSessionLanguage(): return locale.getdefaultlocale()[0] +def get_session_type(): + """ + Returns the user's session type (xrdp, wayland, x11, tty,...) + :return: string + """ + return 'xrdp' if 'XRDP_SESSION' in os.environ else os.environ.get('XDG_SESSION_TYPE', 'unknown').lower() + + def showPopup(title, message): ''' Displays a message box on user's session (during 1 min). diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 13eb5b4..0befc36 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -107,6 +107,7 @@ class OpenGnSysWorker(ServerWorker): interface = None # Bound interface for OpenGnsys REST = None # REST object user = [] # User sessions + session_type = '' # User session type random = None # Random string for secure connections length = 32 # Random string length exec_level = None # Execution level (permitted operations) @@ -201,10 +202,11 @@ class OpenGnSysWorker(ServerWorker): """ Sends session login notification to OpenGnsys server """ - user, sep, language = data.partition(',') - logger.debug('Received login for {} with language {}'.format(user, language)) + 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.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}) def onLogout(self, user): @@ -261,7 +263,8 @@ class OpenGnSysWorker(ServerWorker): st = {'linux': 'LNX', 'macos': 'OSX', 'windows': 'WIN'} try: # Standard status - res = {'status': st[operations.os_type.lower()], 'loggedin': len(self.user) > 0} + 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()}) diff --git a/src/opengnsys/windows/operations.py b/src/opengnsys/windows/operations.py index 51d56d7..7be4ce1 100644 --- a/src/opengnsys/windows/operations.py +++ b/src/opengnsys/windows/operations.py @@ -254,6 +254,14 @@ def getSessionLanguage(): return locale.getdefaultlocale()[0] +def get_session_type(): + """ + returns the user's session type (Local session, RDP,...) + :return: string + """ + return os.environ.get('SESSIONNAME').lower() + + def showPopup(title, message): ''' Displays a message box on user's session (during 1 min). diff --git a/src/setup.py b/src/setup.py index 85300f7..7f5c04f 100644 --- a/src/setup.py +++ b/src/setup.py @@ -57,13 +57,13 @@ except ImportError: import os from distutils.core import setup - +import py2exe import sys # Reading version file: try: with open('VERSION', 'r') as v: - VERSION = v.read().split() + VERSION = v.read().rstrip() except IOError: VERSION = '1.1.0' From 2b257183d97d8a7ea9c7d865d7dd1a6e079b82a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 9 Sep 2020 10:56:11 +0200 Subject: [PATCH 27/30] #940: Recover files to build OGAgent for Windows Python 2-compatible. --- src/opengnsys/windows/OGAgentService.py | 2 +- src/opengnsys/windows/__init__.py | 2 +- src/opengnsys/windows/log.py | 2 +- src/opengnsys/windows/operations.py | 12 ++--- windows/py2exe-wine-linux.sh | 67 +++++++++++++++++++++++++ windows/pyinstaller-wine.sh | 56 --------------------- 6 files changed, 76 insertions(+), 65 deletions(-) create mode 100755 windows/py2exe-wine-linux.sh delete mode 100755 windows/pyinstaller-wine.sh diff --git a/src/opengnsys/windows/OGAgentService.py b/src/opengnsys/windows/OGAgentService.py index 857b2cc..71716d7 100644 --- a/src/opengnsys/windows/OGAgentService.py +++ b/src/opengnsys/windows/OGAgentService.py @@ -29,7 +29,7 @@ ''' @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 diff --git a/src/opengnsys/windows/__init__.py b/src/opengnsys/windows/__init__.py index c47fe7d..e662942 100644 --- a/src/opengnsys/windows/__init__.py +++ b/src/opengnsys/windows/__init__.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' - +from __future__ import unicode_literals import os import sys diff --git a/src/opengnsys/windows/log.py b/src/opengnsys/windows/log.py index 16750ae..745fd03 100644 --- a/src/opengnsys/windows/log.py +++ b/src/opengnsys/windows/log.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' - +from __future__ import unicode_literals import servicemanager # @UnresolvedImport, pylint: disable=import-error import logging diff --git a/src/opengnsys/windows/operations.py b/src/opengnsys/windows/operations.py index 7be4ce1..82b6522 100644 --- a/src/opengnsys/windows/operations.py +++ b/src/opengnsys/windows/operations.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' - +from __future__ import unicode_literals import os import locale @@ -102,12 +102,12 @@ def getWindowsVersion(): ''' Returns Windows version. ''' - import winreg - reg = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion') + import _winreg + reg = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion') try: - data = '{} {}'.format(winreg.QueryValueEx(reg, 'ProductName')[0], winreg.QueryValueEx(reg, 'ReleaseId')[0]) + data = '{} {}'.format(_winreg.QueryValueEx(reg, 'ProductName')[0], _winreg.QueryValueEx(reg, 'ReleaseId')[0]) except Exception: - data = '{} {}'.format(winreg.QueryValueEx(reg, 'ProductName')[0], winreg.QueryValueEx(reg, 'CurrentBuildNumber')[0]) + data = '{} {}'.format(_winreg.QueryValueEx(reg, 'ProductName')[0], _winreg.QueryValueEx(reg, 'CurrentBuildNumber')[0]) reg.Close() return data @@ -200,7 +200,7 @@ def joinDomain(domain, ou, account, password, executeInOneStep=False): error = getErrorMessage(res) if res == 1355: error = "DC Is not reachable" - print(('{} {}'.format(res, error))) + print('{} {}'.format(res, error)) raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain.value, account.value, ', under OU {}'.format(ou.value) if ou.value is not None else '', res, error)) diff --git a/windows/py2exe-wine-linux.sh b/windows/py2exe-wine-linux.sh new file mode 100755 index 0000000..68229b0 --- /dev/null +++ b/windows/py2exe-wine-linux.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +# We need: +# * Wine (32 bit) +# * winetricks (in some distributions) + +export WINEARCH=win32 WINEPREFIX=$PWD/wine WINEDEBUG=fixme-all +WINE=wine + +download() { + mkdir downloads + # Get needed software + cd downloads + wget -nd https://www.python.org/ftp/python/2.7.17/python-2.7.17.msi -O python-2.7.msi + wget -nd https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi + wget -nd https://bootstrap.pypa.io/get-pip.py + wget -nd https://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.4/PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe/download -O pyqt-install.exe + wget -nd https://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download -O nsis-install.exe + wget -nd http://nsis.sourceforge.net/mediawiki/images/d/d7/NSIS_Simple_Firewall_Plugin_1.20.zip + cd .. +} + +install_python() { + if which winetricks &>/dev/null; then + echo "Setting up wine prefix (using winetricks)" + winetricks + fi + + cd downloads + echo "Installing python" + $WINE msiexec /qn /i python-2.7.msi + echo "Installing vc for python" + $WINE msiexec /qn /i VCForPython27.msi + + echo "Installing pyqt (needs X)" + $WINE pyqt-install.exe + echo "Installing nsis (needs X?)" + $WINE nsis-install.exe + + cd .. +} + +setup_pip() { + echo "Seting up pip..." + $WINE C:\\Python27\\python -m pip install --upgrade pip +} + +install_packages() { + echo "Installing pywin32" + $WINE C:\\Python27\\python -m pip install pywin32 + echo "Installing py2exe" + $WINE C:\\Python27\\python -m pip install py2exe_py2 + echo "Installing required packages" + $WINE C:\\Python27\\python -m pip install requests six + # Using easy_install instead of pip to install pycrypto + $WINE C:\\Python27\\Scripts\\easy_install http://www.voidspace.org.uk/python/pycrypto-2.6.1/pycrypto-2.6.1.win32-py2.7.exe + # Copy nsis required NSIS_Simple_Firewall_Plugin_1 + echo "Copying simple firewall plugin for nsis installer" + unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-ansi/ + unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-unicode/ +} + +download +install_python +setup_pip +install_packages + diff --git a/windows/pyinstaller-wine.sh b/windows/pyinstaller-wine.sh deleted file mode 100755 index 9d4b59f..0000000 --- a/windows/pyinstaller-wine.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -# We need: -# * Wine (64 bit) -# * winetricks (in some distributions) - -export WINEARCH=win64 WINEPREFIX=$PWD/wine WINEDEBUG=fixme-all -WINE=wine - -# Get needed software -download() { - mkdir -p downloads - cd downloads - wget -nd https://www.python.org/ftp/python/3.7.7/python-3.7.7-amd64.exe -O python3.msi - wget -nd https://download.visualstudio.microsoft.com/download/pr/5e397ebe-38b2-4e18-a187-ac313d07332a/00945fbb0a29f63183b70370043e249218249f83dbc82cd3b46c5646503f9e27/vs_BuildTools.exe - wget -nd https://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download -O nsis-install.exe - wget -nd http://nsis.sourceforge.net/mediawiki/images/d/d7/NSIS_Simple_Firewall_Plugin_1.20.zip - cd .. -} - -install_python() { - cd downloads - echo "Installing python" - $WINE python3.msi /quiet TargetDir=C:\\Python37 PrependPath=1 - echo "Installing Build Tools for Visual Studio" - $WINE vs_BuildTools.exe - echo "Installing NSIS (needs X?)" - $WINE nsis-install.exe /S - cd .. -} - -setup_pip() { - echo "Setting up pip and setuptools" - $WINE pip install --upgrade pip - $WINE pip install --upgrade setuptools -} - -install_packages() { - echo "Installing PyQt5" - $WINE pip install PyQt5 - echo "Installing required packages" - $WINE pip install pycrypto requests six - # Using easy_install instead of pip to install pycrypto - $WINE pip install pycrypto - echo "Installing PyInstaller" - $WINE pip install PyInstaller - echo "Copying simple firewall plugin for nsis installer" - unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-ansi/ - unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-unicode/ -} - -download -install_python -setup_pip -install_packages - From 3910a842d4794dd91e2d8c3f38b504c70e378424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 9 Sep 2020 14:33:55 +0200 Subject: [PATCH 28/30] #940: Build an OGAgent for Windows Python 2-compatible. --- src/OGAgentUser-qt4.py | 351 +++++++++++++++++++++++++++++++++++++++++ src/setup.py | 8 +- src/update.sh | 8 + windows/build.bat | 1 + 4 files changed, 364 insertions(+), 4 deletions(-) create mode 100644 src/OGAgentUser-qt4.py diff --git a/src/OGAgentUser-qt4.py b/src/OGAgentUser-qt4.py new file mode 100644 index 0000000..90c92d6 --- /dev/null +++ b/src/OGAgentUser-qt4.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python +# -*- 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 time +import json +import six +import atexit +from PyQt4 import QtCore, QtGui # @UnresolvedImport + +from opengnsys import VERSION, ipc, operations, utils +from opengnsys.log import logger +from opengnsys.service import IPC_PORT +from about_dialog_qt4_ui import Ui_OGAAboutDialog +from message_dialog_qt4_ui import Ui_OGAMessageDialog +from opengnsys.scriptThread import ScriptExecutorThread +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') + +trayIcon = None + + +def sigAtExit(): + if trayIcon: + trayIcon.quit() + + +# About dialog +class OGAAboutDialog(QtGui.QDialog): + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + self.ui = Ui_OGAAboutDialog() + self.ui.setupUi(self) + self.ui.VersionLabel.setText("Version " + VERSION) + + def closeDialog(self): + self.hide() + + +class OGAMessageDialog(QtGui.QDialog): + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + self.ui = Ui_OGAMessageDialog() + self.ui.setupUi(self) + + def message(self, message): + self.ui.message.setText(message) + self.show() + + def closeDialog(self): + self.hide() + + +class MessagesProcessor(QtCore.QThread): + logoff = QtCore.pyqtSignal(name='logoff') + message = QtCore.pyqtSignal(tuple, name='message') + script = QtCore.pyqtSignal(QtCore.QString, name='script') + exit = QtCore.pyqtSignal(name='exit') + + def __init__(self, port): + super(self.__class__, self).__init__() + # Retries connection for a while + for _ in range(10): + try: + self.ipc = ipc.ClientIPC(port) + self.ipc.start() + break + except Exception: + logger.debug('IPC Server is not reachable') + self.ipc = None + time.sleep(2) + + self.running = False + + def stop(self): + self.running = False + if self.ipc: + self.ipc.stop() + + def isAlive(self): + return self.ipc is not None + + def sendLogin(self, user_data): + if self.ipc: + self.ipc.sendLogin(user_data) + + def sendLogout(self, userName): + if self.ipc: + self.ipc.sendLogout(userName) + + def run(self): + if self.ipc is None: + return + self.running = True + + # Wait a bit so we ensure IPC thread is running... + time.sleep(2) + + while self.running and self.ipc.running: + try: + msg = self.ipc.getMessage() + if msg is None: + break + 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.split('\0') + self.message.emit((module, message, data)) + elif msg_id == ipc.MSG_LOGOFF: + self.logoff.emit() + elif msg_id == ipc.MSG_SCRIPT: + self.script.emit(QtCore.QString.fromUtf8(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 OGASystemTray(QtGui.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) + exit_action = self.menu.addAction("About") + exit_action.triggered.connect(self.about) + self.setContextMenu(self.menu) + self.ipc = MessagesProcessor(self.ipcport) + + if self.ipc.isAlive() is False: + raise Exception('No connection to service, exiting.') + + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.timerFnc) + + self.stopped = False + + self.ipc.message.connect(self.message) + self.ipc.exit.connect(self.quit) + self.ipc.script.connect(self.executeScript) + self.ipc.logoff.connect(self.logoff) + + self.aboutDlg = OGAAboutDialog() + self.msgDlg = OGAMessageDialog() + + self.timer.start(1000) # Launch idle checking every 1 seconds + + self.ipc.start() + + def initialize(self): + # Load modules and activate them + # Also, sends "login" event to service + self.modules = loadModules(self, client=True) + logger.debug('Modules: {}'.format(list(v.name for v in self.modules))) + + # Send init to all modules + valid_mods = [] + for mod in self.modules: + try: + logger.debug('Activating module {}'.format(mod.name)) + mod.activate() + valid_mods.append(mod) + except Exception as e: + logger.exception() + logger.error("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e))) + + 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())) + + def deinitialize(self): + for mod in reversed(self.modules): # Deinitialize reversed of initialization + try: + logger.debug('Deactivating module {}'.format(mod.name)) + mod.deactivate() + except Exception as e: + logger.exception() + logger.error("Deactivation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e))) + + def timerFnc(self): + pass + + def message(self, msg): + """ + Processes the message sent asynchronously, msg is an QString + """ + try: + logger.debug('msg: {}, {}'.format(type(msg), msg)) + module, message, data = msg + except Exception as e: + logger.error('Got exception {} processing message {}'.format(e, msg)) + return + + for v in self.modules: + if v.name == module: # Case Sensitive!!!! + try: + logger.debug('Notifying message {} to module {} with json data {}'.format(message, v.name, data)) + v.processMessage(message, json.loads(data)) + return + except Exception as e: + logger.error('Got exception {} processing generic message on {}'.format(e, v.name)) + + logger.error('Module {} not found, messsage {} not sent'.format(module, message)) + + def executeScript(self, script): + logger.debug('Executing script') + script = six.text_type(script.toUtf8()).decode('base64') + th = ScriptExecutorThread(script) + th.start() + + def logoff(self): + logger.debug('Logoff invoked') + operations.logoff() # Invoke log off + + def about(self): + self.aboutDlg.exec_() + + def cleanup(self): + logger.debug('Quit invoked') + if self.stopped is False: + self.stopped = True + try: + self.deinitialize() + except Exception: + logger.exception() + logger.error('Got exception deinitializing modules') + + try: + # If we close Client, send Logoff to Broker + self.ipc.sendLogout(operations.getCurrentUser()) + time.sleep(1) + self.timer.stop() + self.ipc.stop() + except Exception: + # May we have lost connection with server, simply log and exit in that case + logger.exception() + logger.exception("Got an exception, processing quit") + + try: + # operations.logoff() # Uncomment this after testing to logoff user + pass + except Exception: + pass + + def quit(self): + # logger.debug("Exec quit {}".format(self.stopped)) + if self.stopped is False: + self.cleanup() + self.app.quit() + + def closeEvent(self, event): + logger.debug("Exec closeEvent") + event.accept() + self.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) + + # This is important so our app won't close on messages windows (alerts, etc...) + QtGui.QApplication.setQuitOnLastWindowClosed(False) + + try: + trayIcon = OGASystemTray(app) + except Exception as e: + logger.exception() + logger.error('OGA Service is not running, or it can\'t contact with OGA Server. User Tools stopped: {}'.format( + utils.exceptionToMessage(e))) + sys.exit(1) + + try: + trayIcon.initialize() # Initialize modules, etc.. + except Exception as e: + logger.exception() + logger.error('Exception initializing OpenGnsys User Agent {}'.format(utils.exceptionToMessage(e))) + trayIcon.quit() + sys.exit(1) + + app.aboutToQuit.connect(trayIcon.cleanup) + trayIcon.show() + + # Catch kill and logout user :) + atexit.register(sigAtExit) + + res = app.exec_() + + logger.debug('Exiting') + trayIcon.quit() + + sys.exit(res) diff --git a/src/setup.py b/src/setup.py index 7f5c04f..9a014c9 100644 --- a/src/setup.py +++ b/src/setup.py @@ -65,7 +65,7 @@ try: with open('VERSION', 'r') as v: VERSION = v.read().rstrip() except IOError: - VERSION = '1.1.0' + VERSION = '1.2.0' sys.argv.append('py2exe') @@ -99,7 +99,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 +112,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 +121,7 @@ setup( 'script': 'OGAServiceHelper.py' } ], - service=[udsservice], + service=[ogaservice], data_files=[('', [get_requests_cert_file()]), ('cfg', ['cfg/ogagent.cfg', 'cfg/ogclient.cfg'])], options={ 'py2exe': { diff --git a/src/update.sh b/src/update.sh index f1db2ad..29bf614 100755 --- a/src/update.sh +++ b/src/update.sh @@ -30,13 +30,21 @@ function process { pyuic5 about-dialog.ui -o about_dialog_ui.py -x pyuic5 message-dialog.ui -o message_dialog_ui.py -x +} + +function process_qt4 { + sed 's/OGAgent.qrc/OGAgent_qt4.qrc/' about-dialog.ui > about-dialog-qt4.ui + pyuic4 about-dialog-qt4.ui -o about_dialog_qt4_ui.py -x + pyuic4 message-dialog.ui -o message_dialog_qt4_ui.py -x } cd $(dirname "$0") [ -r VERSION ] && sed -i "s/Version [^<]*/Version $(cat VERSION)/" about-dialog.ui pyrcc5 OGAgent.qrc -o OGAgent_rc.py +[ "$(command -v pyrcc4)" ] && pyrcc4 -py3 OGAgent.qrc -o OGAgent_qt4_rc.py # process current directory ui's process +[ "$(command -v pyuic4)" ] && process_qt4 diff --git a/windows/build.bat b/windows/build.bat index 2c44474..0e0d514 100644 --- a/windows/build.bat +++ b/windows/build.bat @@ -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 From 6a01818ef7f77b20730100585243d6df4c01d001 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 19 Apr 2024 11:12:43 +0200 Subject: [PATCH 29/30] refs #330: if cannot connect to server, throw error --- .../modules/server/OpenGnSys/__init__.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 0befc36..82414b9 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -149,8 +149,10 @@ 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, @@ -158,7 +160,8 @@ class OpenGnSysWorker(ServerWorker): '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')) @@ -167,13 +170,15 @@ class OpenGnSysWorker(ServerWorker): '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: From b84ab338f516f37031ba341219cc2a5150dc01ab Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Wed, 22 May 2024 11:07:56 +0200 Subject: [PATCH 30/30] refs #247 migrate agent from py2 & qt4/qt5 to py3 & qt6 - Update installation document - No longer create rpm linux packages - Change deb maintainer from one person to one team - Remove stray debhelper files - Filter more stuff in .gitignore --- .gitignore | 16 + INSTALL.es.txt | 33 +- linux/build-packages.sh | 32 +- linux/debian/changelog | 14 +- linux/debian/compat | 2 +- linux/debian/control | 6 +- linux/debian/copyright | 2 +- linux/debian/ogagent.postinst.debhelper | 5 - linux/debian/ogagent.postrm.debhelper | 12 - linux/debian/ogagent.substvars | 2 - linux/debian/rules | 2 +- linux/ogagent-template.spec | 86 ----- src/OGAgentUser-qt4.py | 351 ------------------ src/OGAgentUser.py | 6 +- src/OGAgent_rc.py | 292 --------------- src/VERSION | 2 +- src/about-dialog.ui | 2 +- src/opengnsys/__init__.py | 6 +- src/opengnsys/ipc.py | 4 +- src/opengnsys/linux/operations.py | 2 +- .../modules/server/OpenGnSys/__init__.py | 1 + src/setup.py | 8 +- src/update.sh | 43 ++- 23 files changed, 81 insertions(+), 848 deletions(-) delete mode 100644 linux/debian/ogagent.postinst.debhelper delete mode 100644 linux/debian/ogagent.postrm.debhelper delete mode 100644 linux/debian/ogagent.substvars delete mode 100644 linux/ogagent-template.spec delete mode 100644 src/OGAgentUser-qt4.py delete mode 100644 src/OGAgent_rc.py diff --git a/.gitignore b/.gitignore index 430da91..166b8f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +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 diff --git a/INSTALL.es.txt b/INSTALL.es.txt index 9eb2e17..ad466f6 100644 --- a/INSTALL.es.txt +++ b/INSTALL.es.txt @@ -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 - - diff --git a/linux/build-packages.sh b/linux/build-packages.sh index 9c8b9d7..baf9582 100755 --- a/linux/build-packages.sh +++ b/linux/build-packages.sh @@ -1,36 +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/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 diff --git a/linux/debian/changelog b/linux/debian/changelog index d4db09c..e21205c 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,8 +1,20 @@ +ogagent (1.3.0-2) stable; urgency=medium + + * Add missing dependency on zenity + + -- OpenGnsys developers Thu, 25 Apr 2024 15:53:16 +0200 + +ogagent (1.3.0-1) stable; urgency=medium + + * Upgrade to Qt 6 + + -- OpenGnsys developers Thu, 25 Apr 2024 12:50:20 +0200 + ogagent (1.2.0) unstable; urgency=medium * Python 3 and Qt 5 compatibility - -- Ramón M. Gómez Mon, 4 May 2020 18:00:00 +0100 + -- OpenGnsys developers Mon, 4 May 2020 18:00:00 +0100 ogagent (1.1.1b) stable; urgency=medium diff --git a/linux/debian/compat b/linux/debian/compat index f11c82a..f599e28 100644 --- a/linux/debian/compat +++ b/linux/debian/compat @@ -1 +1 @@ -9 \ No newline at end of file +10 diff --git a/linux/debian/control b/linux/debian/control index e085ad8..c8d9e6e 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -1,7 +1,7 @@ Source: ogagent Section: admin Priority: optional -Maintainer: Ramón M. Gómez +Maintainer: OpenGnsys developers Build-Depends: debhelper (>= 7), po-debconf Standards-Version: 3.9.2 Homepage: https://opengnsys.es/ @@ -11,7 +11,7 @@ Section: admin Priority: optional Architecture: all Depends: - policykit-1 (>= 0.100), python3 (>=3.4) | python (>= 3.4), python3-pyqt5, python3-requests, - python3-six, python3-prctl, python3-distro, libxss1, ${misc:Depends} + 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. diff --git a/linux/debian/copyright b/linux/debian/copyright index 7b6ef31..c333aaa 100644 --- a/linux/debian/copyright +++ b/linux/debian/copyright @@ -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. diff --git a/linux/debian/ogagent.postinst.debhelper b/linux/debian/ogagent.postinst.debhelper deleted file mode 100644 index e75924d..0000000 --- a/linux/debian/ogagent.postinst.debhelper +++ /dev/null @@ -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 diff --git a/linux/debian/ogagent.postrm.debhelper b/linux/debian/ogagent.postrm.debhelper deleted file mode 100644 index 3167f1f..0000000 --- a/linux/debian/ogagent.postrm.debhelper +++ /dev/null @@ -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 diff --git a/linux/debian/ogagent.substvars b/linux/debian/ogagent.substvars deleted file mode 100644 index 978fc8b..0000000 --- a/linux/debian/ogagent.substvars +++ /dev/null @@ -1,2 +0,0 @@ -misc:Depends= -misc:Pre-Depends= diff --git a/linux/debian/rules b/linux/debian/rules index fbe82e6..ead6aa0 100755 --- a/linux/debian/rules +++ b/linux/debian/rules @@ -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 diff --git a/linux/ogagent-template.spec b/linux/ogagent-template.spec deleted file mode 100644 index f88071a..0000000 --- a/linux/ogagent-template.spec +++ /dev/null @@ -1,86 +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 libXScrnSaver python3-distro python3-qt5 python3-requests python3-six -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 -* Mon May 04 2020 Ramón M. Gómez - 1.2.0 -- Python 3 and Qt 5 compatibility - -* Fri Feb 07 2020 Ramón M. Gómez - 1.1.1b-1 -- Use python-distro to detect the distribution version - -* Thu May 23 2019 Ramón M. Gómez - 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 - 1.1.0a-1 -- Fix a bug when activating the agent with some network devices - -* Tue Oct 13 2016 Ramón M. Gómez - 1.1.0-1 -- Functional OpenGnsys Agent interacting with OpenGnsys Server 1.1.0 - -* Tue Jul 18 2015 Adolfo Gómez García - 1.0.0-1 -- Initial release for OpenGnsys Agent - diff --git a/src/OGAgentUser-qt4.py b/src/OGAgentUser-qt4.py deleted file mode 100644 index 90c92d6..0000000 --- a/src/OGAgentUser-qt4.py +++ /dev/null @@ -1,351 +0,0 @@ -#!/usr/bin/env python -# -*- 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 time -import json -import six -import atexit -from PyQt4 import QtCore, QtGui # @UnresolvedImport - -from opengnsys import VERSION, ipc, operations, utils -from opengnsys.log import logger -from opengnsys.service import IPC_PORT -from about_dialog_qt4_ui import Ui_OGAAboutDialog -from message_dialog_qt4_ui import Ui_OGAMessageDialog -from opengnsys.scriptThread import ScriptExecutorThread -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') - -trayIcon = None - - -def sigAtExit(): - if trayIcon: - trayIcon.quit() - - -# About dialog -class OGAAboutDialog(QtGui.QDialog): - def __init__(self, parent=None): - QtGui.QDialog.__init__(self, parent) - self.ui = Ui_OGAAboutDialog() - self.ui.setupUi(self) - self.ui.VersionLabel.setText("Version " + VERSION) - - def closeDialog(self): - self.hide() - - -class OGAMessageDialog(QtGui.QDialog): - def __init__(self, parent=None): - QtGui.QDialog.__init__(self, parent) - self.ui = Ui_OGAMessageDialog() - self.ui.setupUi(self) - - def message(self, message): - self.ui.message.setText(message) - self.show() - - def closeDialog(self): - self.hide() - - -class MessagesProcessor(QtCore.QThread): - logoff = QtCore.pyqtSignal(name='logoff') - message = QtCore.pyqtSignal(tuple, name='message') - script = QtCore.pyqtSignal(QtCore.QString, name='script') - exit = QtCore.pyqtSignal(name='exit') - - def __init__(self, port): - super(self.__class__, self).__init__() - # Retries connection for a while - for _ in range(10): - try: - self.ipc = ipc.ClientIPC(port) - self.ipc.start() - break - except Exception: - logger.debug('IPC Server is not reachable') - self.ipc = None - time.sleep(2) - - self.running = False - - def stop(self): - self.running = False - if self.ipc: - self.ipc.stop() - - def isAlive(self): - return self.ipc is not None - - def sendLogin(self, user_data): - if self.ipc: - self.ipc.sendLogin(user_data) - - def sendLogout(self, userName): - if self.ipc: - self.ipc.sendLogout(userName) - - def run(self): - if self.ipc is None: - return - self.running = True - - # Wait a bit so we ensure IPC thread is running... - time.sleep(2) - - while self.running and self.ipc.running: - try: - msg = self.ipc.getMessage() - if msg is None: - break - 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.split('\0') - self.message.emit((module, message, data)) - elif msg_id == ipc.MSG_LOGOFF: - self.logoff.emit() - elif msg_id == ipc.MSG_SCRIPT: - self.script.emit(QtCore.QString.fromUtf8(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 OGASystemTray(QtGui.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) - exit_action = self.menu.addAction("About") - exit_action.triggered.connect(self.about) - self.setContextMenu(self.menu) - self.ipc = MessagesProcessor(self.ipcport) - - if self.ipc.isAlive() is False: - raise Exception('No connection to service, exiting.') - - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.timerFnc) - - self.stopped = False - - self.ipc.message.connect(self.message) - self.ipc.exit.connect(self.quit) - self.ipc.script.connect(self.executeScript) - self.ipc.logoff.connect(self.logoff) - - self.aboutDlg = OGAAboutDialog() - self.msgDlg = OGAMessageDialog() - - self.timer.start(1000) # Launch idle checking every 1 seconds - - self.ipc.start() - - def initialize(self): - # Load modules and activate them - # Also, sends "login" event to service - self.modules = loadModules(self, client=True) - logger.debug('Modules: {}'.format(list(v.name for v in self.modules))) - - # Send init to all modules - valid_mods = [] - for mod in self.modules: - try: - logger.debug('Activating module {}'.format(mod.name)) - mod.activate() - valid_mods.append(mod) - except Exception as e: - logger.exception() - logger.error("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e))) - - 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())) - - def deinitialize(self): - for mod in reversed(self.modules): # Deinitialize reversed of initialization - try: - logger.debug('Deactivating module {}'.format(mod.name)) - mod.deactivate() - except Exception as e: - logger.exception() - logger.error("Deactivation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e))) - - def timerFnc(self): - pass - - def message(self, msg): - """ - Processes the message sent asynchronously, msg is an QString - """ - try: - logger.debug('msg: {}, {}'.format(type(msg), msg)) - module, message, data = msg - except Exception as e: - logger.error('Got exception {} processing message {}'.format(e, msg)) - return - - for v in self.modules: - if v.name == module: # Case Sensitive!!!! - try: - logger.debug('Notifying message {} to module {} with json data {}'.format(message, v.name, data)) - v.processMessage(message, json.loads(data)) - return - except Exception as e: - logger.error('Got exception {} processing generic message on {}'.format(e, v.name)) - - logger.error('Module {} not found, messsage {} not sent'.format(module, message)) - - def executeScript(self, script): - logger.debug('Executing script') - script = six.text_type(script.toUtf8()).decode('base64') - th = ScriptExecutorThread(script) - th.start() - - def logoff(self): - logger.debug('Logoff invoked') - operations.logoff() # Invoke log off - - def about(self): - self.aboutDlg.exec_() - - def cleanup(self): - logger.debug('Quit invoked') - if self.stopped is False: - self.stopped = True - try: - self.deinitialize() - except Exception: - logger.exception() - logger.error('Got exception deinitializing modules') - - try: - # If we close Client, send Logoff to Broker - self.ipc.sendLogout(operations.getCurrentUser()) - time.sleep(1) - self.timer.stop() - self.ipc.stop() - except Exception: - # May we have lost connection with server, simply log and exit in that case - logger.exception() - logger.exception("Got an exception, processing quit") - - try: - # operations.logoff() # Uncomment this after testing to logoff user - pass - except Exception: - pass - - def quit(self): - # logger.debug("Exec quit {}".format(self.stopped)) - if self.stopped is False: - self.cleanup() - self.app.quit() - - def closeEvent(self, event): - logger.debug("Exec closeEvent") - event.accept() - self.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) - - # This is important so our app won't close on messages windows (alerts, etc...) - QtGui.QApplication.setQuitOnLastWindowClosed(False) - - try: - trayIcon = OGASystemTray(app) - except Exception as e: - logger.exception() - logger.error('OGA Service is not running, or it can\'t contact with OGA Server. User Tools stopped: {}'.format( - utils.exceptionToMessage(e))) - sys.exit(1) - - try: - trayIcon.initialize() # Initialize modules, etc.. - except Exception as e: - logger.exception() - logger.error('Exception initializing OpenGnsys User Agent {}'.format(utils.exceptionToMessage(e))) - trayIcon.quit() - sys.exit(1) - - app.aboutToQuit.connect(trayIcon.cleanup) - trayIcon.show() - - # Catch kill and logout user :) - atexit.register(sigAtExit) - - res = app.exec_() - - logger.debug('Exiting') - trayIcon.quit() - - sys.exit(res) diff --git a/src/OGAgentUser.py b/src/OGAgentUser.py index 86a95b0..d4ec146 100644 --- a/src/OGAgentUser.py +++ b/src/OGAgentUser.py @@ -34,7 +34,7 @@ import base64 import json import sys import time -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets from about_dialog_ui import Ui_OGAAboutDialog from message_dialog_ui import Ui_OGAMessageDialog @@ -254,7 +254,7 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon): operations.logoff() # Invoke log off def about(self): - self.aboutDlg.exec_() + self.aboutDlg.exec() def cleanup(self): logger.debug('Quit invoked') @@ -327,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() diff --git a/src/OGAgent_rc.py b/src/OGAgent_rc.py deleted file mode 100644 index affcb8c..0000000 --- a/src/OGAgent_rc.py +++ /dev/null @@ -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 PyQt5 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() diff --git a/src/VERSION b/src/VERSION index 26aaba0..f0bb29e 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.2.0 +1.3.0 diff --git a/src/about-dialog.ui b/src/about-dialog.ui index ae04a66..df40da5 100644 --- a/src/about-dialog.ui +++ b/src/about-dialog.ui @@ -65,7 +65,7 @@ - Version 1.2.0 + Version 1.3.0 diff --git a/src/opengnsys/__init__.py b/src/opengnsys/__init__.py index 1cad939..e268ca7 100644 --- a/src/opengnsys/__init__.py +++ b/src/opengnsys/__init__.py @@ -37,11 +37,7 @@ import six from . import modules from .RESTApi import REST, RESTError -try: - with open('../VERSION', 'r') as v: - VERSION = v.read().strip() -except IOError: - VERSION = '1.2.0' +VERSION='1.3.0' __title__ = 'OpenGnsys Agent' __version__ = VERSION diff --git a/src/opengnsys/ipc.py b/src/opengnsys/ipc.py index 677724e..fbfee9c 100644 --- a/src/opengnsys/ipc.py +++ b/src/opengnsys/ipc.py @@ -232,7 +232,7 @@ class ServerIPC(threading.Thread): 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((msg_id, msg_data)) @@ -254,7 +254,7 @@ class ServerIPC(threading.Thread): """ 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 diff --git a/src/opengnsys/linux/operations.py b/src/opengnsys/linux/operations.py index 9de9fa8..621bc4d 100644 --- a/src/opengnsys/linux/operations.py +++ b/src/opengnsys/linux/operations.py @@ -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)] diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 82414b9..73182b6 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -139,6 +139,7 @@ class OpenGnSysWorker(ServerWorker): 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 diff --git a/src/setup.py b/src/setup.py index 9a014c9..a3b7f43 100644 --- a/src/setup.py +++ b/src/setup.py @@ -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.2.0' +# update.sh changes this value +VERSION='1.3.0' sys.argv.append('py2exe') diff --git a/src/update.sh b/src/update.sh index 29bf614..3f39fed 100755 --- a/src/update.sh +++ b/src/update.sh @@ -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,24 +28,32 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -function process { - pyuic5 about-dialog.ui -o about_dialog_ui.py -x - pyuic5 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_qt4 { - sed 's/OGAgent.qrc/OGAgent_qt4.qrc/' about-dialog.ui > about-dialog-qt4.ui - pyuic4 about-dialog-qt4.ui -o about_dialog_qt4_ui.py -x - pyuic4 message-dialog.ui -o message_dialog_qt4_ui.py -x -} +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 -pyrcc5 OGAgent.qrc -o OGAgent_rc.py -[ "$(command -v pyrcc4)" ] && pyrcc4 -py3 OGAgent.qrc -o OGAgent_qt4_rc.py - - -# process current directory ui's -process -[ "$(command -v pyuic4)" ] && process_qt4 - +update_version +process_ui +process_resources