From bf061b13dbd03ad74582500c4f2305bb5030e04d Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Wed, 24 Jul 2024 15:15:21 +0200 Subject: [PATCH 01/15] refs #522 #527 begin integrating ogAdmClient.c into the agent --- src/cfg/ogagent.cfg | 15 +- src/opengnsys/linux/log.py | 4 +- src/opengnsys/linux/ogAdmClient.py | 114 ++++++ src/opengnsys/loader.py | 4 +- src/opengnsys/windows/log.py | 4 +- .../server/ogAdmClient/__init__.py | 385 ++++++++++++++++++ 6 files changed, 516 insertions(+), 10 deletions(-) create mode 100755 src/opengnsys/linux/ogAdmClient.py create mode 100644 src/test_modules/server/ogAdmClient/__init__.py diff --git a/src/cfg/ogagent.cfg b/src/cfg/ogagent.cfg index 0291e88..38f8d86 100644 --- a/src/cfg/ogagent.cfg +++ b/src/cfg/ogagent.cfg @@ -20,7 +20,14 @@ log=DEBUG # Module specific # The sections must match the module name # This section will be passes on activation to module -#[Sample1] -#value1=Mariete -#value2=Yo -#remote=https://172.27.0.1:9999/rest +[ogAdmClient] +#path=test_modules/server +## this URL will probably be left equal to the other one, but let's see +remote=https://192.168.1.249/opengnsys/rest/__ogAdmClient +log=DEBUG + +#servidorAdm=192.168.2.1 +#puerto=2008 +pathinterface=/opt/opengnsys/interfaceAdm +urlMenu=https://192.168.2.1/opengnsys/varios/menubrowser.php +urlMsg=http://localhost/cgi-bin/httpd-log.sh diff --git a/src/opengnsys/linux/log.py b/src/opengnsys/linux/log.py index 396a2db..393f5e8 100644 --- a/src/opengnsys/linux/log.py +++ b/src/opengnsys/linux/log.py @@ -52,7 +52,7 @@ class LocalLogger(object): logging.basicConfig( filename=fname, filemode='a', - format='%(levelname)s %(asctime)s %(message)s', + format='%(levelname)s %(asctime)s (%(threadName)s) (%(funcName)s) %(message)s', level=logging.DEBUG ) self.logger = logging.getLogger('opengnsys') @@ -69,7 +69,7 @@ class LocalLogger(object): # our loglevels are 10000 (other), 20000 (debug), .... # logging levels are 10 (debug), 20 (info) # OTHER = logging.NOTSET - self.logger.log(int(level / 1000) - 10, message) + self.logger.log(int(level / 1000) - 10, message, stacklevel=4) def isWindows(self): return False diff --git a/src/opengnsys/linux/ogAdmClient.py b/src/opengnsys/linux/ogAdmClient.py new file mode 100755 index 0000000..831dcfe --- /dev/null +++ b/src/opengnsys/linux/ogAdmClient.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# +# 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, +# 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 +@author: Natalia Serrano, nserrano at qindel dot com +""" + + +from opengnsys.service import CommonService +from opengnsys.log import logger +from opengnsys.linux.daemon import Daemon + +import sys +import signal +import json + +try: + from prctl import set_proctitle # @UnresolvedImport +except ImportError: + def set_proctitle(_): + pass + + +class OGAgentSvc(Daemon, CommonService): + def __init__(self, args=None): + Daemon.__init__(self, '/var/run/opengnsys-agent.pid') + CommonService.__init__(self) + + def run(self): + logger.debug('** Running Daemon **') + set_proctitle('ogAdmClient') + + self.initialize() + + # Call modules initialization + # They are called in sequence, no threading is done at this point, so ensure modules onActivate always returns + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + try: + while self.isAlive: + self.doWait(1000) + except (KeyboardInterrupt, SystemExit) as e: + logger.error('Requested exit of main loop') + except Exception as e: + logger.exception() + logger.error('Caught exception on main loop: {}'.format(e)) + + self.terminate() + self.notifyStop() + + def signal_handler(self, signal, frame): + self.isAlive = False + sys.stderr.write("signal handler: {}".format(signal)) + + +def usage(): + sys.stderr.write("usage: {} start|stop|restart|fg\n".format(sys.argv[0])) + sys.exit(2) + + +if __name__ == '__main__': + logger.setLevel('DEBUG') + + logger.debug('Executing actor') + daemon = OGAgentSvc() + + signal.signal(signal.SIGTERM, daemon.signal_handler) + signal.signal(signal.SIGINT, daemon.signal_handler) + + if len(sys.argv) == 2: + if 'start' == sys.argv[1]: + daemon.start() + elif 'stop' == sys.argv[1]: + daemon.stop() + elif 'restart' == sys.argv[1]: + daemon.restart() + elif 'fg' == sys.argv[1]: + daemon.run() + else: + usage() + sys.exit(0) + else: + usage() diff --git a/src/opengnsys/loader.py b/src/opengnsys/loader.py index ca053b6..577fcf7 100644 --- a/src/opengnsys/loader.py +++ b/src/opengnsys/loader.py @@ -100,12 +100,12 @@ def loadModules(controller, client=False): # paths += (os.path.dirname(sys.modules[modPath].__file__),) - logger.debug('Loading modules from {}'.format(paths)) - # Load modules + logger.debug('Loading modules from {}'.format(paths)) doLoad(paths) # Add to list of available modules + logger.debug('Adding {} classes'.format('server' if modType == ServerWorker else 'client')) recursiveAdd(modType) return ogModules diff --git a/src/opengnsys/windows/log.py b/src/opengnsys/windows/log.py index 7b2bec2..ea875fd 100644 --- a/src/opengnsys/windows/log.py +++ b/src/opengnsys/windows/log.py @@ -47,7 +47,7 @@ class LocalLogger(object): logging.basicConfig( filename=os.path.join(tempfile.gettempdir(), 'opengnsys.log'), filemode='a', - format='%(levelname)s %(asctime)s (%(threadName)s) %(message)s', + format='%(levelname)s %(asctime)s (%(threadName)s) (%(funcName)s) %(message)s', level=logging.DEBUG ) self.logger = logging.getLogger('opengnsys') @@ -58,7 +58,7 @@ class LocalLogger(object): # our loglevels are 10000 (other), 20000 (debug), .... # logging levels are 10 (debug), 20 (info) # OTHER = logging.NOTSET - self.logger.log(int(level / 1000 - 10), message) + self.logger.log(int(level / 1000 - 10), message, stacklevel=4) if level < INFO or self.serviceLogger is False: # Only information and above will be on event log return diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py new file mode 100644 index 0000000..55aa49c --- /dev/null +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -0,0 +1,385 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# 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, +# 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: Ramón M. Gómez, ramongomez at us dot es +@author: Natalia Serrano, nserrano at qindel dot com +""" + + +import base64 +import os +import random +import shutil +import string +import threading +import time +import subprocess +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 +from opengnsys.workers import ServerWorker + +pathinterface = None + +# Check authorization header decorator +def check_secret(fnc): + """ + Decorator to check for received secret key and raise exception if it isn't valid. + """ + def wrapper(*args, **kwargs): + return fnc(*args, **kwargs) + #try: + # this, path, get_params, post_params, server = args + # # Accept "status" operation with no arguments or any function with Authorization header + # if fnc.__name__ == 'process_status' and not get_params: + # return fnc(*args, **kwargs) + # elif this.random == server.headers['Authorization']: + # return fnc(*args, **kwargs) + # else: + # raise Exception('Unauthorized operation') + #except Exception as e: + # logger.error(str(e)) + # raise Exception(e) + + return wrapper + +def interfaceAdmin (exe, parametros=[]): + devel_bash_prefix = ''' + PATH=/opt/opengnsys/scripts/:$PATH; + for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; + for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; + ''' + if 'nati' == os.environ['USER'] or 'nati' == os.environ['SUDO_USER']: ## DO NOT COMMIT + devel_bash_prefix = ''' + PATH=/home/nati/Downloads/work/opengnsys/opengnsys/client/shared/scripts:$PATH; + for I in /home/nati/Downloads/work/opengnsys/opengnsys/client/engine/*.lib; do source $I; done; + for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; + ''' + if parametros: + proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] + else: + proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)] + logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) + return subprocess.run (proc, capture_output=True).stdout.strip().decode ('utf-8') + +def tomaIPlocal(): + logger.debug (__name__) + interface = '{}/getIpAddress'.format (pathinterface) + try: + IPlocal = interfaceAdmin (interface); + logger.info (IPlocal) + except Exception as e: + logger.error (e) + logger.error ('No se ha podido recuperar la dirección IP del cliente') + return False + return True + + +class ogAdmClientWorker(ServerWorker): + name = 'ogAdmClient' # Module name + 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 + + def onActivation(self): + """ + Sends OGAgent activation notification to OpenGnsys server + """ + 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 + try: + global pathinterface + url = self.service.config.get('ogAdmClient', 'remote') + loglevel = self.service.config.get('ogAdmClient', 'log') + #servidorAdm = self.service.config.get('ogAdmClient', 'servidorAdm') + #puerto = self.service.config.get('ogAdmClient', 'puerto') + pathinterface = self.service.config.get('ogAdmClient', 'pathinterface') + urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') + urlMsg = self.service.config.get('ogAdmClient', 'urlMsg') + logger.setLevel(loglevel) + except NoOptionError as e: + logger.error("Configuration error: {}".format(e)) + raise e + self.REST = REST(url) + + # Get network interfaces until they are active or timeout (5 minutes) + for t in range(0, 300): + try: + # Get the first network interface + self.interface = list(operations.getNetworkInfo())[0] + except Exception as e: + # Wait 1 sec. and retry + logger.warn (e) + time.sleep(1) + finally: + # Exit loop if interface is active + if self.interface: + if t > 0: + logger.debug("Fetch connection data after {} tries".format(t)) + break + # Raise error after timeout + if not self.interface: + raise Exception ('not self.interface') + + # Loop to send initialization message + init_retries = 100 + for t in range(0, init_retries): + 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, + 'agent_version': VERSION}) + break + except Exception as e: + logger.warn (str (e)) + time.sleep(3) + # Raise error after timeout + if t < init_retries-1: + logger.debug('Successful connection after {} tries'.format(t)) + elif t == init_retries-1: + raise Exception('Initialization error: Cannot connect to remote server') + + tomaIPlocal() + + def onDeactivation(self): + """ + Sends OGAgent stopping notification to OpenGnsys server + """ + logger.debug('onDeactivation') + self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'ostype': operations.os_type, 'osversion': operations.os_version}) + + def processClientMessage(self, message, data): + logger.debug('Got OpenGnsys message from client: {}, data {}'.format(message, data)) + + def onLogin(self, data): + logger.warn('in onLogin, should not happen') + + def onLogout(self, user): + logger.warn('in onLogout, should not happen') + + def process_ogclient(self, path, get_params, post_params, server): + """ + This method can be overridden to provide your own message processor, 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"], get_params, post_params) + + This method will process "mazinger", and look for a "self" method that is called "process_mazinger", + and invoke it this way: + return self.process_mazinger(["Z"], get_params, post_params) + + 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. Objects are not serializable to json, + basic type are) + """ + if not path: + return "ok" + try: + operation = getattr(self, 'ogclient_' + path[0]) + except Exception: + 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): + return {'ogAdmClient': 'in process_status'} + + @check_secret + def process_reboot(self, path, get_params, post_params, server): + """ + Launches a system reboot operation + :param path: + :param get_params: + :param post_params: + :param server: authorization header + :return: JSON object {"op": "launched"} + """ + logger.debug('Received reboot operation') + + # Rebooting thread + def rebt(): + operations.reboot() + threading.Thread(target=rebt).start() + return {'op': 'launched'} + + @check_secret + def process_poweroff(self, path, get_params, post_params, server): + """ + Launches a system power off operation + :param path: + :param get_params: + :param post_params: + :param server: authorization header + :return: JSON object {"op": "launched"} + """ + logger.debug('Received poweroff operation') + + # Powering off thread + def pwoff(): + time.sleep(2) + operations.poweroff() + threading.Thread(target=pwoff).start() + return {'op': 'launched'} + + @check_secret + def process_script(self, path, get_params, post_params, server): + """ + Processes an script execution (script should be encoded in base64) + :param path: + :param get_params: + :param post_params: JSON object {"script": "commands"} + :param server: authorization header + :return: JSON object {"op": "launched"} + """ + logger.debug('Processing script request') + # Decoding script + script = urllib.parse.unquote(base64.b64decode(post_params.get('script')).decode('utf-8')) + logger.debug('received script {}'.format(script)) + if operations.os_type == 'Windows': + ## for windows, we turn the script into utf16le, then to b64 again, and feed the blob to powershell + u16 = script.encode ('utf-16le') ## utf16 + b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8)) + script = """ +import os +import tempfile +import subprocess +cp = subprocess.run ("powershell -WindowStyle Hidden -EncodedCommand {}", capture_output=True) +subprocs_log = os.path.join (tempfile.gettempdir(), 'opengnsys-subprocs.log') +with open (subprocs_log, 'ab') as fd: ## TODO improve this logging + fd.write (cp.stdout) + fd.write (cp.stderr) +""".format (b64) + else: + script = 'import subprocess; subprocess.check_output("""{0}""",shell=True)'.format(script) + # Executing script. + if post_params.get('client', 'false') == 'false': + thr = ScriptExecutorThread(script) + thr.start() + else: + self.sendClientMessage('script', {'code': script}) + return {'op': 'launched'} + + @check_secret + def process_logoff(self, path, get_params, post_params, server): + logger.warn('in process_logoff, should not happen') + + @check_secret + def process_popup(self, path, get_params, post_params, server): + logger.warn('in process_popup, should not happen') + + #def process_client_popup(self, params): + # logger.warn('in process_client_popup') + + + ## process_* are invoked from opengnsys/httpserver.py:99 "data = module.processServerMessage(path, get_params, post_params, self)" (via opengnsys/workers/server_worker.py) + ## process_client_* are invoked from opengnsys/service.py:123 "v.processClientMessage(message, json.loads(data))" (via opengnsys/workers/server_worker.py) + + + def process_RESPUESTA_AutoexecCliente(self, path, get_params, post_params, server): + logger.warn('in process_RESPUESTA_AutoexecCliente') + + def process_RESPUESTA_InclusionCliente(self, path, get_params, post_params, server): + logger.warn('in process_RESPUESTA_InclusionCliente') + + def process_NoComandosPtes(self, path, get_params, post_params, server): + logger.warn('in process_NoComandosPtes') + + def process_Actualizar(self, path, get_params, post_params, server): + logger.warn('in process_Actualizar') + + def process_Purgar(self, path, get_params, post_params, server): + logger.warn('in process_Purgar') + + def process_ConsolaRemota(self, path, get_params, post_params, server): + logger.warn('in process_ConsolaRemota') + + def process_Sondeo(self, path, get_params, post_params, server): + logger.warn('in process_Sondeo') + + def process_Arrancar(self, path, get_params, post_params, server): + logger.warn('in process_Arrancar') + + def process_Apagar(self, path, get_params, post_params, server): + logger.warn('in process_Apagar') + + def process_Reiniciar(self, path, get_params, post_params, server): + logger.warn('in process_Reiniciar') + + def process_IniciarSesion(self, path, get_params, post_params, server): + logger.warn('in process_IniciarSesion') + + def process_CrearImagen(self, path, get_params, post_params, server): + logger.warn('in process_CrearImagen') + + def process_CrearImagenBasica(self, path, get_params, post_params, server): + logger.warn('in process_CrearImagenBasica') + + def process_CrearSoftIncremental(self, path, get_params, post_params, server): + logger.warn('in process_CrearSoftIncremental') + + def process_RestaurarImagen(self, path, get_params, post_params, server): + logger.warn('in process_RestaurarImagen') + + def process_RestaurarImagenBasica(self, path, get_params, post_params, server): + logger.warn('in process_RestaurarImagenBasica') + + def process_RestaurarSoftIncremental(self, path, get_params, post_params, server): + logger.warn('in process_RestaurarSoftIncremental') + + def process_Configurar(self, path, get_params, post_params, server): + logger.warn('in process_Configurar') + + def process_EjecutarScript(self, path, get_params, post_params, server): + logger.warn('in process_EjecutarScript') + + def process_InventarioHardware(self, path, get_params, post_params, server): + logger.warn('in process_InventarioHardware') + + def process_InventarioSoftware(self, path, get_params, post_params, server): + logger.warn('in process_InventarioSoftware') + + def process_EjecutaComandosPendientes(self, path, get_params, post_params, server): + logger.warn('in process_EjecutaComandosPendientes') From 886bf5e616175b1e12c70530108843739773dffc Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 25 Jul 2024 12:43:07 +0200 Subject: [PATCH 02/15] refs #523 implement inclusionCliente() --- src/opengnsys/log.py | 2 +- .../server/ogAdmClient/__init__.py | 104 +++++++++++++----- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/src/opengnsys/log.py b/src/opengnsys/log.py index 5c74783..eb1f309 100644 --- a/src/opengnsys/log.py +++ b/src/opengnsys/log.py @@ -94,7 +94,7 @@ class Logger(object): except Exception: tb = '(could not get traceback!)' - self.log(DEBUG, tb) + self.log(DEBUG, 'traceback follows: "{}"'.format(tb)) def flush(self): pass diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index 55aa49c..0d34e67 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -51,8 +51,6 @@ from opengnsys.log import logger from opengnsys.scriptThread import ScriptExecutorThread from opengnsys.workers import ServerWorker -pathinterface = None - # Check authorization header decorator def check_secret(fnc): """ @@ -94,27 +92,21 @@ def interfaceAdmin (exe, parametros=[]): logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) return subprocess.run (proc, capture_output=True).stdout.strip().decode ('utf-8') -def tomaIPlocal(): - logger.debug (__name__) - interface = '{}/getIpAddress'.format (pathinterface) - try: - IPlocal = interfaceAdmin (interface); - logger.info (IPlocal) - except Exception as e: - logger.error (e) - logger.error ('No se ha podido recuperar la dirección IP del cliente') - return False - return True - - class ogAdmClientWorker(ServerWorker): - name = 'ogAdmClient' # Module name - 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 + name = 'ogAdmClient' # Module name + interface = None # Bound interface for OpenGnsys + REST = None # REST object + random = None # Random string for secure connections + length = 32 # Random string length + + pathinterface = None + IPlocal = None + idordenador = None + nombreordenador = None + cache = None + idproautoexec = None + idcentro = None + idaula = None def onActivation(self): """ @@ -125,12 +117,11 @@ class ogAdmClientWorker(ServerWorker): 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 try: - global pathinterface url = self.service.config.get('ogAdmClient', 'remote') loglevel = self.service.config.get('ogAdmClient', 'log') #servidorAdm = self.service.config.get('ogAdmClient', 'servidorAdm') #puerto = self.service.config.get('ogAdmClient', 'puerto') - pathinterface = self.service.config.get('ogAdmClient', 'pathinterface') + self.pathinterface = self.service.config.get('ogAdmClient', 'pathinterface') urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') urlMsg = self.service.config.get('ogAdmClient', 'urlMsg') logger.setLevel(loglevel) @@ -162,7 +153,7 @@ class ogAdmClientWorker(ServerWorker): init_retries = 100 for t in range(0, init_retries): try: - self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, + self.REST.sendMessage('started', {'mac': self.interface.mac, 'ip': self.interface.ip, 'secret': self.random, 'ostype': operations.os_type, 'osversion': operations.os_version, 'agent_version': VERSION}) @@ -176,14 +167,18 @@ class ogAdmClientWorker(ServerWorker): elif t == init_retries-1: raise Exception('Initialization error: Cannot connect to remote server') - tomaIPlocal() + if (not self.tomaIPlocal()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + if (not self.inclusionCliente()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') def onDeactivation(self): """ Sends OGAgent stopping notification to OpenGnsys server """ logger.debug('onDeactivation') - self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, + self.REST.sendMessage('stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, 'ostype': operations.os_type, 'osversion': operations.os_version}) def processClientMessage(self, message, data): @@ -318,6 +313,61 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging ## process_client_* are invoked from opengnsys/service.py:123 "v.processClientMessage(message, json.loads(data))" (via opengnsys/workers/server_worker.py) + def tomaIPlocal(self): + logger.debug (__name__) + interface = '{}/getIpAddress'.format (self.pathinterface) + try: + self.IPlocal = interfaceAdmin (interface); + logger.info (self.IPlocal) + except Exception as e: + logger.error (e) + logger.error ('No se ha podido recuperar la dirección IP del cliente') + return False + return True + + def LeeConfiguracion(self): + parametroscfg = interfaceAdmin ('{}/getConfiguration'.format (self.pathinterface)) ## Configuración de los Sistemas Operativos del cliente + logger.debug ('parametroscfg ({})'.format (parametroscfg)) + return (parametroscfg) + + def enviaMensajeServidor(self, path, obj): + obj['iph'] = self.IPlocal ## Ip del ordenador + obj['ido'] = self.idordenador ## Identificador del ordenador + obj['npc'] = self.nombreordenador ## Nombre del ordenador + obj['idc'] = self.idcentro ## Identificador del centro + obj['ida'] = self.idaula ## Identificador del aula + + res = self.REST.sendMessage (path, obj) + + if (not res): ## TODO + #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor + logger.error ('Error al enviar trama ***send() fallo') + return False + + return res + + def inclusionCliente(self): + cfg = self.LeeConfiguracion() + res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': cfg }) + logger.debug ('res ({})'.format (res)) + + if (not res or 0 == res['res']) : + logger.error ('Ha ocurrido algún problema en el proceso de inclusión del cliente') + return False + + if (not res['ido'] or not res['npc']): + logger.error ('Se han recibido parámetros con valores no válidos') + return False + + self.idordenador = res['ido'] ## Identificador del ordenador + self.nombreordenador = res['npc'] ## Nombre del ordenador + self.cache = res['che'] ## Tamaño de la caché reservada al cliente + self.idproautoexec = res['exe'] ## Procedimento de inicio (Autoexec) + self.idcentro = res['idc'] ## Identificador de la Unidad Organizativa + self.idaula = res['ida'] ## Identificador del aula + + return True + def process_RESPUESTA_AutoexecCliente(self, path, get_params, post_params, server): logger.warn('in process_RESPUESTA_AutoexecCliente') From 5f7ca5be15899116b71dfd41707a45284e2af24e Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 26 Jul 2024 10:45:02 +0200 Subject: [PATCH 03/15] refs #524 implement autoexecCliente() and its companion ejecutaArchivo() --- .../server/ogAdmClient/__init__.py | 160 ++++++++++++++---- 1 file changed, 130 insertions(+), 30 deletions(-) diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index 0d34e67..8a9021c 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -73,25 +73,6 @@ def check_secret(fnc): return wrapper -def interfaceAdmin (exe, parametros=[]): - devel_bash_prefix = ''' - PATH=/opt/opengnsys/scripts/:$PATH; - for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; - for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; - ''' - if 'nati' == os.environ['USER'] or 'nati' == os.environ['SUDO_USER']: ## DO NOT COMMIT - devel_bash_prefix = ''' - PATH=/home/nati/Downloads/work/opengnsys/opengnsys/client/shared/scripts:$PATH; - for I in /home/nati/Downloads/work/opengnsys/opengnsys/client/engine/*.lib; do source $I; done; - for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; - ''' - if parametros: - proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] - else: - proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)] - logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) - return subprocess.run (proc, capture_output=True).stdout.strip().decode ('utf-8') - class ogAdmClientWorker(ServerWorker): name = 'ogAdmClient' # Module name interface = None # Bound interface for OpenGnsys @@ -122,8 +103,8 @@ class ogAdmClientWorker(ServerWorker): #servidorAdm = self.service.config.get('ogAdmClient', 'servidorAdm') #puerto = self.service.config.get('ogAdmClient', 'puerto') self.pathinterface = self.service.config.get('ogAdmClient', 'pathinterface') - urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') - urlMsg = self.service.config.get('ogAdmClient', 'urlMsg') + #urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') + #urlMsg = self.service.config.get('ogAdmClient', 'urlMsg') logger.setLevel(loglevel) except NoOptionError as e: logger.error("Configuration error: {}".format(e)) @@ -170,9 +151,24 @@ class ogAdmClientWorker(ServerWorker): if (not self.tomaIPlocal()): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + logger.info ('Inicio de sesion') + logger.info ('Abriendo sesión en el servidor de Administración') if (not self.inclusionCliente()): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + logger.info ('Cliente iniciado') + logger.info ('Procesando caché') + if (not self.cuestionCache()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + if (self.idproautoexec > 0): + logger.info ('Ejecución de archivo Autoexec') + if (not self.autoexecCliente()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + #logger.info ('Procesa comandos pendientes') + #logger.info ('Acciones pendientes procesadas') + def onDeactivation(self): """ Sends OGAgent stopping notification to OpenGnsys server @@ -303,7 +299,11 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging @check_secret def process_popup(self, path, get_params, post_params, server): - logger.warn('in process_popup, should not happen') + logger.debug('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format(path, get_params, post_params, server)) + logger.debug('type(post_params) "{}"'.format(type(post_params))) + ## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "" + ## type(post_params) "" + return {'debug':'test'} #def process_client_popup(self, params): # logger.warn('in process_client_popup') @@ -313,11 +313,23 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging ## process_client_* are invoked from opengnsys/service.py:123 "v.processClientMessage(message, json.loads(data))" (via opengnsys/workers/server_worker.py) + def interfaceAdmin (self, method, parametros=[]): + exe = '{}/{}'.format (self.pathinterface, method) + devel_bash_prefix = ''' + PATH=/opt/opengnsys/scripts/:$PATH; + for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; + for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; + ''' + if parametros: + proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] + else: + proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)] + logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) + return subprocess.run (proc, capture_output=True).stdout.strip().decode ('utf-8') + def tomaIPlocal(self): - logger.debug (__name__) - interface = '{}/getIpAddress'.format (self.pathinterface) try: - self.IPlocal = interfaceAdmin (interface); + self.IPlocal = self.interfaceAdmin ('getIpAddress'); logger.info (self.IPlocal) except Exception as e: logger.error (e) @@ -326,7 +338,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging return True def LeeConfiguracion(self): - parametroscfg = interfaceAdmin ('{}/getConfiguration'.format (self.pathinterface)) ## Configuración de los Sistemas Operativos del cliente + parametroscfg = self.interfaceAdmin ('getConfiguration') ## Configuración de los Sistemas Operativos del cliente logger.debug ('parametroscfg ({})'.format (parametroscfg)) return (parametroscfg) @@ -346,11 +358,64 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging return res + def ejecutaArchivo(self,fn): + logger.debug ('fn ({})'.format (fn)) + + ## TODO hay que entender este codigo (ogAdmClient.c:2111) para poder traducirlo a python + ## en una funcion "ejecutaArchivo" esperaba que se ejecutara un archivo, pero solo hay una llamada a gestionaTrama() que no sé dónde termina + #char* buffer,*lineas[MAXIMAS_LINEAS]; + #int i,numlin; + #char modulo[] = "ejecutaArchivo()"; + + #buffer = leeArchivo(filecmd); + #if (buffer): + # numlin = splitCadena(lineas, buffer, '@'); + # initParametros(ptrTrama,0); + # for (i = 0; i < numlin; i++) { + # if(strlen(lineas[i])>0){ + # strcpy(ptrTrama->parametros,lineas[i]); + # if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + # errorLog(modulo,39,FALSE); + # //return(FALSE); + # } + # } + # } + #liberaMemoria(buffer); + + ## voy a probar algo, asumiendo que en el archivo no hay simplemente un bash, sino secuencias de parametros tal que "nfn=Funcion\rparam1=foo\rparam2=bar" + buffer = subprocess.run (['cat', fn], capture_output=True).stdout.strip().decode ('utf-8') + logger.debug ('buffer ({})'.format (buffer.replace('\r', '\\r'))) ## sustituimos \r para que el log tenga sentido + if buffer: + for l in buffer.split('@'): + if not len(l): continue + logger.debug ('line ({})'.format (l)) + ## en este punto, una opción sería pegar un curl a localhost, pero también podemos parsear los parámetros y llamar localmente a la función que sea: + post_params = {} + for param in l.split("\r"): + k, v = param.split('=') + post_params[k] = v + logger.debug ('post_params "{}"'.format (post_params)) + + func_name = post_params.pop ('nfn', None) + if func_name is None: + logger.error ('Ha ocurrido algún problema al procesar la trama recibida') + break + func = getattr (self, 'process_' + func_name) + ## func ya es una referencia a self.func, de modo que ahora no hay que hacer self.func(...) ni tampoco func(self, ...) + + logger.debug ('calling function "{}" with post_params "{}"'.format (func_name, post_params)) + output = func ([], {}, post_params, None) + logger.debug ('output "{}"'.format (output)) + if not output: + logger.error ('Ha ocurrido algún problema al procesar la trama recibida') + break + def inclusionCliente(self): cfg = self.LeeConfiguracion() res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': cfg }) logger.debug ('res ({})'.format (res)) + ## RESPUESTA_InclusionCliente if (not res or 0 == res['res']) : logger.error ('Ha ocurrido algún problema en el proceso de inclusión del cliente') return False @@ -368,11 +433,46 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging return True - def process_RESPUESTA_AutoexecCliente(self, path, get_params, post_params, server): - logger.warn('in process_RESPUESTA_AutoexecCliente') + def cuestionCache(self): + return True ## ogAdmClient.c:425 + #>>>>>>>>>>>>>>>>>>>>>>>>>> + #try: + # self.interfaceAdmin ('procesaCache', [ self.cache ]); + #except Exception as e: + # logger.error ('Ha habido algún problerma al procesar la caché') + # return False + # + #return True - def process_RESPUESTA_InclusionCliente(self, path, get_params, post_params, server): - logger.warn('in process_RESPUESTA_InclusionCliente') + def autoexecCliente(self): + res = self.enviaMensajeServidor ('AutoexecCliente', { 'exe': self.idproautoexec }) + logger.debug ('res ({})'.format (res)) + + if (not res): + logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') + logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración') + return False + + ## RESPUESTA_AutoexecCliente + if (not res or 0 == res['res']) : + logger.error ('Ha ocurrido algún problema al procesar la trama recibida') + return False + + logger.info (res) + res = self.enviaMensajeServidor ('enviaArchivo', { 'nfl': res['nfl'] }) + if (not res): + logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') + logger.error ('Ha ocurrido algún problema al recibir un archivo por la red') + return False + + fileautoexec = '/tmp/_autoexec_{}'.format (self.IPlocal) + logger.debug ('fileautoexec ({})'.format (fileautoexec)) + with open (fileautoexec, 'w') as fd: + fd.write (res['contents']) + + self.ejecutaArchivo (fileautoexec); + + return True def process_NoComandosPtes(self, path, get_params, post_params, server): logger.warn('in process_NoComandosPtes') From 5482d25116ede020b5a98836c488cfe71c46d5b0 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 26 Jul 2024 12:17:02 +0200 Subject: [PATCH 04/15] refs #525 partially implement comandosPendientes() --- .../server/ogAdmClient/__init__.py | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index 8a9021c..c3f01bc 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -166,8 +166,11 @@ class ogAdmClientWorker(ServerWorker): if (not self.autoexecCliente()): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') - #logger.info ('Procesa comandos pendientes') - #logger.info ('Acciones pendientes procesadas') + logger.info ('Procesa comandos pendientes') + if (not self.comandosPendientes()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + logger.info ('Acciones pendientes procesadas') def onDeactivation(self): """ @@ -342,7 +345,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging logger.debug ('parametroscfg ({})'.format (parametroscfg)) return (parametroscfg) - def enviaMensajeServidor(self, path, obj): + def enviaMensajeServidor(self, path, obj={}): obj['iph'] = self.IPlocal ## Ip del ordenador obj['ido'] = self.idordenador ## Identificador del ordenador obj['npc'] = self.nombreordenador ## Nombre del ordenador @@ -474,6 +477,28 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging return True + def comandosPendientes(self): + while (True): + res = self.enviaMensajeServidor ('ComandosPendientes') ## recibe un solo comando + if (not res): + logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') + logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración') + return False + + logger.info (res) + if ('NoComandosPtes' == res['nfn']): + break + + ## TODO gestionar los demás casos... igual toca hacer algo parecido a ejecutaArchivo + #if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + # logger.error ('Ha ocurrido algún problema al procesar la trama recibida') + # return False + #} + ## de momento le pongo un return False para evitar un posible bucle infinito + return False + + return True + def process_NoComandosPtes(self, path, get_params, post_params, server): logger.warn('in process_NoComandosPtes') From 97246759c1c3a5dff52b21ae8e149a3164c6ae29 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 26 Jul 2024 13:14:21 +0200 Subject: [PATCH 05/15] refs #529 remove useless ogAdmClient.py --- src/opengnsys/linux/ogAdmClient.py | 114 ----------------------------- 1 file changed, 114 deletions(-) delete mode 100755 src/opengnsys/linux/ogAdmClient.py diff --git a/src/opengnsys/linux/ogAdmClient.py b/src/opengnsys/linux/ogAdmClient.py deleted file mode 100755 index 831dcfe..0000000 --- a/src/opengnsys/linux/ogAdmClient.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# -# 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, -# 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 -@author: Natalia Serrano, nserrano at qindel dot com -""" - - -from opengnsys.service import CommonService -from opengnsys.log import logger -from opengnsys.linux.daemon import Daemon - -import sys -import signal -import json - -try: - from prctl import set_proctitle # @UnresolvedImport -except ImportError: - def set_proctitle(_): - pass - - -class OGAgentSvc(Daemon, CommonService): - def __init__(self, args=None): - Daemon.__init__(self, '/var/run/opengnsys-agent.pid') - CommonService.__init__(self) - - def run(self): - logger.debug('** Running Daemon **') - set_proctitle('ogAdmClient') - - self.initialize() - - # Call modules initialization - # They are called in sequence, no threading is done at this point, so ensure modules onActivate always returns - - # ********************* - # * Main Service loop * - # ********************* - # Counter used to check ip changes only once every 10 seconds, for - # example - try: - while self.isAlive: - self.doWait(1000) - except (KeyboardInterrupt, SystemExit) as e: - logger.error('Requested exit of main loop') - except Exception as e: - logger.exception() - logger.error('Caught exception on main loop: {}'.format(e)) - - self.terminate() - self.notifyStop() - - def signal_handler(self, signal, frame): - self.isAlive = False - sys.stderr.write("signal handler: {}".format(signal)) - - -def usage(): - sys.stderr.write("usage: {} start|stop|restart|fg\n".format(sys.argv[0])) - sys.exit(2) - - -if __name__ == '__main__': - logger.setLevel('DEBUG') - - logger.debug('Executing actor') - daemon = OGAgentSvc() - - signal.signal(signal.SIGTERM, daemon.signal_handler) - signal.signal(signal.SIGINT, daemon.signal_handler) - - if len(sys.argv) == 2: - if 'start' == sys.argv[1]: - daemon.start() - elif 'stop' == sys.argv[1]: - daemon.stop() - elif 'restart' == sys.argv[1]: - daemon.restart() - elif 'fg' == sys.argv[1]: - daemon.run() - else: - usage() - sys.exit(0) - else: - usage() From e92d8855a1620efa23834d59cd6a471f694ec956 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 26 Jul 2024 13:40:46 +0200 Subject: [PATCH 06/15] refs #526 implement muestraMenu() --- .../server/ogAdmClient/__init__.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index c3f01bc..64f481b 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -103,7 +103,7 @@ class ogAdmClientWorker(ServerWorker): #servidorAdm = self.service.config.get('ogAdmClient', 'servidorAdm') #puerto = self.service.config.get('ogAdmClient', 'puerto') self.pathinterface = self.service.config.get('ogAdmClient', 'pathinterface') - #urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') + urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') #urlMsg = self.service.config.get('ogAdmClient', 'urlMsg') logger.setLevel(loglevel) except NoOptionError as e: @@ -172,6 +172,8 @@ class ogAdmClientWorker(ServerWorker): logger.info ('Acciones pendientes procesadas') + self.muestraMenu() + def onDeactivation(self): """ Sends OGAgent stopping notification to OpenGnsys server @@ -499,6 +501,26 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging return True + def cargaPaginaWeb(url=None): + if (not url): url = self.urlMenu + os.system ('pkill -9 browser'); + + #p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) + p = subprocess.Popen (['/usr/bin/xeyes']) + try: + p.wait (2) ## si el proceso se muere antes de 2 segundos... + logger.error ('Error al ejecutar la llamada a la interface de administración') + logger.error ('Error en la creación del proceso hijo') + logger.error ('return code "{}"'.format (p.returncode)) + return False + except subprocess.TimeoutExpired: + pass + + return True + + def muestraMenu(self): + self.cargaPaginaWeb() + def process_NoComandosPtes(self, path, get_params, post_params, server): logger.warn('in process_NoComandosPtes') From 1d0057fd3823f59c247d1604985e2955bb82fd23 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 26 Jul 2024 13:43:30 +0200 Subject: [PATCH 07/15] refs #526 move onActivation() below --- .../server/ogAdmClient/__init__.py | 180 +++++++++--------- 1 file changed, 95 insertions(+), 85 deletions(-) diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index 64f481b..e775275 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -89,91 +89,6 @@ class ogAdmClientWorker(ServerWorker): idcentro = None idaula = None - def onActivation(self): - """ - Sends OGAgent activation notification to OpenGnsys server - """ - 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 - try: - url = self.service.config.get('ogAdmClient', 'remote') - loglevel = self.service.config.get('ogAdmClient', 'log') - #servidorAdm = self.service.config.get('ogAdmClient', 'servidorAdm') - #puerto = self.service.config.get('ogAdmClient', 'puerto') - self.pathinterface = self.service.config.get('ogAdmClient', 'pathinterface') - urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') - #urlMsg = self.service.config.get('ogAdmClient', 'urlMsg') - logger.setLevel(loglevel) - except NoOptionError as e: - logger.error("Configuration error: {}".format(e)) - raise e - self.REST = REST(url) - - # Get network interfaces until they are active or timeout (5 minutes) - for t in range(0, 300): - try: - # Get the first network interface - self.interface = list(operations.getNetworkInfo())[0] - except Exception as e: - # Wait 1 sec. and retry - logger.warn (e) - time.sleep(1) - finally: - # Exit loop if interface is active - if self.interface: - if t > 0: - logger.debug("Fetch connection data after {} tries".format(t)) - break - # Raise error after timeout - if not self.interface: - raise Exception ('not self.interface') - - # Loop to send initialization message - init_retries = 100 - for t in range(0, init_retries): - try: - self.REST.sendMessage('started', {'mac': self.interface.mac, 'ip': self.interface.ip, - 'secret': self.random, 'ostype': operations.os_type, - 'osversion': operations.os_version, - 'agent_version': VERSION}) - break - except Exception as e: - logger.warn (str (e)) - time.sleep(3) - # Raise error after timeout - if t < init_retries-1: - logger.debug('Successful connection after {} tries'.format(t)) - elif t == init_retries-1: - raise Exception('Initialization error: Cannot connect to remote server') - - if (not self.tomaIPlocal()): - raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') - - logger.info ('Inicio de sesion') - logger.info ('Abriendo sesión en el servidor de Administración') - if (not self.inclusionCliente()): - raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') - - logger.info ('Cliente iniciado') - logger.info ('Procesando caché') - if (not self.cuestionCache()): - raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') - - if (self.idproautoexec > 0): - logger.info ('Ejecución de archivo Autoexec') - if (not self.autoexecCliente()): - raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') - - logger.info ('Procesa comandos pendientes') - if (not self.comandosPendientes()): - raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') - - logger.info ('Acciones pendientes procesadas') - - self.muestraMenu() - def onDeactivation(self): """ Sends OGAgent stopping notification to OpenGnsys server @@ -325,6 +240,16 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; ''' + + ## DO NOT COMMIT + if 'nati' == os.environ['USER'] or 'nati' == os.environ['SUDO_USER']: + devel_bash_prefix = ''' + PATH=/home/nati/Downloads/work/opengnsys/opengnsys/client/shared/scripts:$PATH; + for I in /home/nati/Downloads/work/opengnsys/opengnsys/client/engine/*.lib; do source $I; done; + for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; + ''' + ## DO NOT COMMIT + if parametros: proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] else: @@ -521,6 +446,91 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging def muestraMenu(self): self.cargaPaginaWeb() + def onActivation(self): + """ + Sends OGAgent activation notification to OpenGnsys server + """ + 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 + try: + url = self.service.config.get('ogAdmClient', 'remote') + loglevel = self.service.config.get('ogAdmClient', 'log') + #servidorAdm = self.service.config.get('ogAdmClient', 'servidorAdm') + #puerto = self.service.config.get('ogAdmClient', 'puerto') + self.pathinterface = self.service.config.get('ogAdmClient', 'pathinterface') + urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') + #urlMsg = self.service.config.get('ogAdmClient', 'urlMsg') + logger.setLevel(loglevel) + except NoOptionError as e: + logger.error("Configuration error: {}".format(e)) + raise e + self.REST = REST(url) + + # Get network interfaces until they are active or timeout (5 minutes) + for t in range(0, 300): + try: + # Get the first network interface + self.interface = list(operations.getNetworkInfo())[0] + except Exception as e: + # Wait 1 sec. and retry + logger.warn (e) + time.sleep(1) + finally: + # Exit loop if interface is active + if self.interface: + if t > 0: + logger.debug("Fetch connection data after {} tries".format(t)) + break + # Raise error after timeout + if not self.interface: + raise Exception ('not self.interface') + + # Loop to send initialization message + init_retries = 100 + for t in range(0, init_retries): + try: + self.REST.sendMessage('started', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'secret': self.random, 'ostype': operations.os_type, + 'osversion': operations.os_version, + 'agent_version': VERSION}) + break + except Exception as e: + logger.warn (str (e)) + time.sleep(3) + # Raise error after timeout + if t < init_retries-1: + logger.debug('Successful connection after {} tries'.format(t)) + elif t == init_retries-1: + raise Exception('Initialization error: Cannot connect to remote server') + + if (not self.tomaIPlocal()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + logger.info ('Inicio de sesion') + logger.info ('Abriendo sesión en el servidor de Administración') + if (not self.inclusionCliente()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + logger.info ('Cliente iniciado') + logger.info ('Procesando caché') + if (not self.cuestionCache()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + if (self.idproautoexec > 0): + logger.info ('Ejecución de archivo Autoexec') + if (not self.autoexecCliente()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + logger.info ('Procesa comandos pendientes') + if (not self.comandosPendientes()): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + logger.info ('Acciones pendientes procesadas') + + self.muestraMenu() + def process_NoComandosPtes(self, path, get_params, post_params, server): logger.warn('in process_NoComandosPtes') From 94eaba7688df8948f3ccc640bef95087dcf1a960 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 26 Jul 2024 14:38:19 +0200 Subject: [PATCH 08/15] refs #526 implement procesaComandos() --- .../server/ogAdmClient/__init__.py | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index e775275..d89c6fb 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -281,7 +281,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging res = self.REST.sendMessage (path, obj) - if (not res): ## TODO + if (type(res) is not dict): #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor logger.error ('Error al enviar trama ***send() fallo') return False @@ -346,7 +346,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging logger.debug ('res ({})'.format (res)) ## RESPUESTA_InclusionCliente - if (not res or 0 == res['res']) : + if (type(res) is not dict or 0 == res['res']) : logger.error ('Ha ocurrido algún problema en el proceso de inclusión del cliente') return False @@ -378,19 +378,19 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging res = self.enviaMensajeServidor ('AutoexecCliente', { 'exe': self.idproautoexec }) logger.debug ('res ({})'.format (res)) - if (not res): + if (type(res) is not dict): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración') return False ## RESPUESTA_AutoexecCliente - if (not res or 0 == res['res']) : + if (type(res) is not dict or 0 == res['res']) : logger.error ('Ha ocurrido algún problema al procesar la trama recibida') return False logger.info (res) res = self.enviaMensajeServidor ('enviaArchivo', { 'nfl': res['nfl'] }) - if (not res): + if (type(res) is not dict): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') logger.error ('Ha ocurrido algún problema al recibir un archivo por la red') return False @@ -407,7 +407,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging def comandosPendientes(self): while (True): res = self.enviaMensajeServidor ('ComandosPendientes') ## recibe un solo comando - if (not res): + if (type(res) is not dict): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración') return False @@ -446,6 +446,33 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging def muestraMenu(self): self.cargaPaginaWeb() + def procesaComandos(self): + res = self.enviaMensajeServidor ('DisponibilidadComandos', { 'tpc': 'OPG' }) ## Activar disponibilidad + logger.debug ('res ({})'.format (res)) + + if (type(res) is not dict): + logger.error ('Ha ocurrido algún problema al enviar una petición de comandos interactivos al Servidor de Administración') + return False + + logger.info ('Disponibilidad de comandos activada') ## Disponibilidad de cliente activada + + ## y hacemos return true y el agente es quien espera peticiones + ## TODO el tema es, ogAdmClient hace comandosPendientes() cada vez, ¿cómo lo metemos aquí? + # + #ptrTrama=recibeMensaje(&socket_c); + #if(!ptrTrama){ + # errorLog(modulo,46,FALSE); 'Ha ocurrido algún problema al recibir un comando interactivo desde el Servidor de Administración' + # return; + #} + #close(socket_c); + #if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + # errorLog(modulo,39,FALSE); 'Ha ocurrido algún problema al procesar la trama recibida' + # return; + #} + #if(!comandosPendientes(ptrTrama)){ + # errorLog(modulo,42,FALSE); 'Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración' + #} + def onActivation(self): """ Sends OGAgent activation notification to OpenGnsys server @@ -530,6 +557,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging logger.info ('Acciones pendientes procesadas') self.muestraMenu() + self.procesaComandos() def process_NoComandosPtes(self, path, get_params, post_params, server): logger.warn('in process_NoComandosPtes') From bfe563d902166dd89a6317a00636c2ba707e085b Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 26 Jul 2024 14:38:58 +0200 Subject: [PATCH 09/15] refs #526 remove unwanted code --- src/test_modules/server/ogAdmClient/__init__.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index d89c6fb..9149f36 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -240,16 +240,6 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; ''' - - ## DO NOT COMMIT - if 'nati' == os.environ['USER'] or 'nati' == os.environ['SUDO_USER']: - devel_bash_prefix = ''' - PATH=/home/nati/Downloads/work/opengnsys/opengnsys/client/shared/scripts:$PATH; - for I in /home/nati/Downloads/work/opengnsys/opengnsys/client/engine/*.lib; do source $I; done; - for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; - ''' - ## DO NOT COMMIT - if parametros: proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] else: From f25252fcf9d92a476f7c81a2680f80a5963d5ca3 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Sat, 27 Jul 2024 10:08:42 +0200 Subject: [PATCH 10/15] refs #526 translate my comments --- .../server/ogAdmClient/__init__.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index 9149f36..c91f73b 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -281,8 +281,8 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging def ejecutaArchivo(self,fn): logger.debug ('fn ({})'.format (fn)) - ## TODO hay que entender este codigo (ogAdmClient.c:2111) para poder traducirlo a python - ## en una funcion "ejecutaArchivo" esperaba que se ejecutara un archivo, pero solo hay una llamada a gestionaTrama() que no sé dónde termina + ## TODO need to understand this code (ogAdmClient.c:2111) before translating it to python + ## in a function called "ejecutaArchivo" I'd expect a file to be run, however there's only a call to gestionaTrama() that I don't know where it leads to #char* buffer,*lineas[MAXIMAS_LINEAS]; #int i,numlin; #char modulo[] = "ejecutaArchivo()"; @@ -302,14 +302,14 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging # } #liberaMemoria(buffer); - ## voy a probar algo, asumiendo que en el archivo no hay simplemente un bash, sino secuencias de parametros tal que "nfn=Funcion\rparam1=foo\rparam2=bar" + ## let's test something, assuming that in the "file" there's not just some bash, but a sequence of parameters such as "nfn=Function\rparam1=foo\rparam2=bar" buffer = subprocess.run (['cat', fn], capture_output=True).stdout.strip().decode ('utf-8') - logger.debug ('buffer ({})'.format (buffer.replace('\r', '\\r'))) ## sustituimos \r para que el log tenga sentido + logger.debug ('buffer ({})'.format (buffer.replace('\r', '\\r'))) ## change \r so as not to mess with the log if buffer: for l in buffer.split('@'): if not len(l): continue logger.debug ('line ({})'.format (l)) - ## en este punto, una opción sería pegar un curl a localhost, pero también podemos parsear los parámetros y llamar localmente a la función que sea: + ## at this point, an option would be fire up a curl to localhost, but we can also parse the params and locally call the desired function: post_params = {} for param in l.split("\r"): k, v = param.split('=') @@ -321,7 +321,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging logger.error ('Ha ocurrido algún problema al procesar la trama recibida') break func = getattr (self, 'process_' + func_name) - ## func ya es una referencia a self.func, de modo que ahora no hay que hacer self.func(...) ni tampoco func(self, ...) + ## func is already a ref to self.func, so we don't have to call self.func(...) or func(self, ...) logger.debug ('calling function "{}" with post_params "{}"'.format (func_name, post_params)) output = func ([], {}, post_params, None) @@ -396,7 +396,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging def comandosPendientes(self): while (True): - res = self.enviaMensajeServidor ('ComandosPendientes') ## recibe un solo comando + res = self.enviaMensajeServidor ('ComandosPendientes') ## receives just one command if (type(res) is not dict): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración') @@ -406,12 +406,12 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging if ('NoComandosPtes' == res['nfn']): break - ## TODO gestionar los demás casos... igual toca hacer algo parecido a ejecutaArchivo + ## TODO manage the rest of cases... we might have to do something similar to ejecutaArchivo #if(!gestionaTrama(ptrTrama)){ // Análisis de la trama # logger.error ('Ha ocurrido algún problema al procesar la trama recibida') # return False #} - ## de momento le pongo un return False para evitar un posible bucle infinito + ## ATM let's just return false to avoid a possible infinite loop return False return True @@ -423,7 +423,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging #p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) p = subprocess.Popen (['/usr/bin/xeyes']) try: - p.wait (2) ## si el proceso se muere antes de 2 segundos... + p.wait (2) ## if the process dies before 2 seconds... logger.error ('Error al ejecutar la llamada a la interface de administración') logger.error ('Error en la creación del proceso hijo') logger.error ('return code "{}"'.format (p.returncode)) @@ -446,8 +446,8 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging logger.info ('Disponibilidad de comandos activada') ## Disponibilidad de cliente activada - ## y hacemos return true y el agente es quien espera peticiones - ## TODO el tema es, ogAdmClient hace comandosPendientes() cada vez, ¿cómo lo metemos aquí? + ## we now return true and the outer agent code gets to wait for requests from outside + ## TODO thing is, ogAdmClient always calls comandosPendientes() after every received request. How do we do that here? # #ptrTrama=recibeMensaje(&socket_c); #if(!ptrTrama){ From b6945279bc144558b449fda2fb8804be74732837 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 1 Aug 2024 12:01:07 +0200 Subject: [PATCH 11/15] refs #522 remove duplicated initialisation code --- src/cfg/ogagent.cfg | 2 +- .../server/ogAdmClient/__init__.py | 42 +------------------ 2 files changed, 2 insertions(+), 42 deletions(-) diff --git a/src/cfg/ogagent.cfg b/src/cfg/ogagent.cfg index 38f8d86..be977a2 100644 --- a/src/cfg/ogagent.cfg +++ b/src/cfg/ogagent.cfg @@ -23,7 +23,7 @@ log=DEBUG [ogAdmClient] #path=test_modules/server ## this URL will probably be left equal to the other one, but let's see -remote=https://192.168.1.249/opengnsys/rest/__ogAdmClient +remote=https://192.168.2.10/opengnsys/rest log=DEBUG #servidorAdm=192.168.2.1 diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index c91f73b..0e7673c 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -94,8 +94,6 @@ class ogAdmClientWorker(ServerWorker): Sends OGAgent stopping notification to OpenGnsys server """ logger.debug('onDeactivation') - self.REST.sendMessage('stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, - 'ostype': operations.os_type, 'osversion': operations.os_version}) def processClientMessage(self, message, data): logger.debug('Got OpenGnsys message from client: {}, data {}'.format(message, data)) @@ -250,7 +248,7 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging def tomaIPlocal(self): try: self.IPlocal = self.interfaceAdmin ('getIpAddress'); - logger.info (self.IPlocal) + logger.info ('local IP is "{}"'.format (self.IPlocal)) except Exception as e: logger.error (e) logger.error ('No se ha podido recuperar la dirección IP del cliente') @@ -467,7 +465,6 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging """ Sends OGAgent activation notification to OpenGnsys server """ - 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 @@ -485,43 +482,6 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging raise e self.REST = REST(url) - # Get network interfaces until they are active or timeout (5 minutes) - for t in range(0, 300): - try: - # Get the first network interface - self.interface = list(operations.getNetworkInfo())[0] - except Exception as e: - # Wait 1 sec. and retry - logger.warn (e) - time.sleep(1) - finally: - # Exit loop if interface is active - if self.interface: - if t > 0: - logger.debug("Fetch connection data after {} tries".format(t)) - break - # Raise error after timeout - if not self.interface: - raise Exception ('not self.interface') - - # Loop to send initialization message - init_retries = 100 - for t in range(0, init_retries): - try: - self.REST.sendMessage('started', {'mac': self.interface.mac, 'ip': self.interface.ip, - 'secret': self.random, 'ostype': operations.os_type, - 'osversion': operations.os_version, - 'agent_version': VERSION}) - break - except Exception as e: - logger.warn (str (e)) - time.sleep(3) - # Raise error after timeout - if t < init_retries-1: - logger.debug('Successful connection after {} tries'.format(t)) - elif t == init_retries-1: - raise Exception('Initialization error: Cannot connect to remote server') - if (not self.tomaIPlocal()): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') From f7fd5d5570dca10e5363d93fa47d33aa6f54a7c8 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 1 Aug 2024 12:01:37 +0200 Subject: [PATCH 12/15] refs #534 add ogcore-mock --- ogcore-mock.py | 197 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 ogcore-mock.py diff --git a/ogcore-mock.py b/ogcore-mock.py new file mode 100644 index 0000000..5982355 --- /dev/null +++ b/ogcore-mock.py @@ -0,0 +1,197 @@ +from flask import Flask, request, jsonify, render_template_string, abort +import os +import logging +import json +import subprocess + +## FLASK_APP=/path/to/ogcore-mock.py FLASK_ENV=development FLASK_RUN_CERT=adhoc sudo --preserve-env flask run --host 192.168.1.249 --port 443 + + +app = Flask(__name__) +logging.basicConfig(level=logging.INFO) + +## agente lin/win/mac + +@app.route('/opengnsys/rest/ogagent/', methods=['POST']) +def og_agent(cucu): + c = request + logging.info(f"{request.get_json()}") + return jsonify({}) + + + +## agente oglive + +@app.route('/opengnsys/rest/__ogAdmClient/InclusionCliente', methods=['POST']) +def inclusion_cliente(): + c = request + logging.info(f"{request.get_json()}") + #procesoInclusionCliente() or { return (jsonify { 'res': 0 }) } + j = request.get_json(force=True) + iph = j['iph'] ## Toma ip + cfg = j['cfg'] ## Toma configuracion + logging.info(f"iph ({iph}) cfg ({cfg})") + + # dbi->query (sprintf "SELECT ordenadores.*,aulas.idaula,centros.idcentro FROM ordenadores INNER JOIN aulas ON aulas.idaula=ordenadores.idaula INNER JOIN centros ON centros.idcentro=aulas.idcentro WHERE ordenadores.ip = '%s'", iph); + # if (!dbi_result_next_row(result)) { log_error ('client does not exist in database') } + # log_debug (sprintf 'Client %s requesting inclusion', iph); + + idordenador = 42 #dbi_result_get_uint(result, "idordenador") + nombreordenador = "hal9000" #dbi_result_get_string(result, "nombreordenador"); + cache = 42 #dbi_result_get_uint(result, "cache"); + idproautoexec = 42 #dbi_result_get_uint(result, "idproautoexec"); + idaula = 42 #dbi_result_get_uint(result, "idaula"); + idcentro = 42 #dbi_result_get_uint(result, "idcentro"); + + # resul = actualizaConfiguracion(dbi, cfg, idordenador); ## Actualiza la configuración del ordenador + # if (!resul) { log_error ('Cannot add client to database') } + # if (!registraCliente(iph)) { log_error ('client table is full') } ## Incluyendo al cliente en la tabla de sokets + + return jsonify({ + 'res': 1, ## int, Confirmación proceso correcto + 'ido': idordenador, ## int + 'npc': nombreordenador, ## string + 'che': cache, ## int + 'exe': idproautoexec, ## int + 'ida': idaula, ## int + 'idc': idcentro ## int + }) + +def _recorreProcedimientos(parametros, fileexe, idp): + #char idprocedimiento[LONPRM]; + #int procedimientoid, lsize; + #const char *msglog, *param; + #dbi_result result; + + #dbi->query (sprintf 'SELECT procedimientoid,parametros FROM procedimientos_acciones WHERE idprocedimiento=%s ORDER BY orden', $idp); + #if (!result) { log_error ('failed to query database'); return 0; } + if 1: #while (dbi_result_next_row(result)) { + procedimientoid = 0 ## dbi_result_get_uint(result, "procedimientoid"); + if (procedimientoid > 0): ## Procedimiento recursivo + if (not _recorreProcedimientos (parametros, fileexe, procedimientoid)): + return 0 + else: + #param = '@'.join (["nfn=EjecutarScript\rscp=uptime", "nfn=EjecutarScript\rscp=cat /proc/uptime"]) ## dbi_result_get_string(result, "parametros"); + param = '@'.join (["nfn=popup\rtitle=my title\rmessage=my message"]) + parametros = '{}@'.format (param) + fileexe.write (parametros) + #} + + return 1 + +@app.route('/opengnsys/rest/__ogAdmClient/AutoexecCliente', methods=['POST']) +def autoexec_client(): + c = request + logging.info(f"{request.get_json()}") + j = request.get_json(force=True) + iph = j['iph'] ## Toma dirección IP del cliente + exe = j['exe'] ## Toma identificador del procedimiento inicial + logging.info(f"iph ({iph}) exe ({exe})") + + fileautoexec = '/tmp/Sautoexec-{}'.format(iph) + logging.info ("fileautoexec ({})".format (fileautoexec)); + try: + fileexe = open (fileautoexec, 'w') + except Exception as e: + logging.error ("cannot create temporary file: {}".format (e)) + return jsonify({}) + + if (_recorreProcedimientos ('', fileexe, exe)): + res = jsonify ({ 'res': 1, 'nfl': fileautoexec }) + else: + res = jsonify ({ 'res': 0 }) + + fileexe.close() + return res + +@app.route('/opengnsys/rest/__ogAdmClient/enviaArchivo', methods=['POST']) +def envia_archivo(): + c = request + logging.info(f"{request.get_json()}") + j = request.get_json(force=True) + nfl = j['nfl'] ## Toma nombre completo del archivo + logging.info(f"nfl ({nfl})") + + return jsonify({'contents': subprocess.run (['cat', nfl], capture_output=True).stdout.decode('utf-8')}) + +def clienteExistente(iph): + ## esto queda totalmente del lado del servidor, no lo implemento en python + return 42 + +def buscaComandos(ido): + #dbi->query (sprintf "SELECT sesion, parametros FROM acciones WHERE idordenador=%s AND estado='%d' ORDER BY idaccion", ido, ACCION_INICIADA); + #dbi_result_next_row(result) ## cogemos solo una fila + + #if not row { return; } + return + + #ids = 42 #dbi_result_get_uint(result, "sesion"); + #param = "nfn=popup\rtitle=my title\rmessage=my message" #dbi_result_get_string(result, "parametros"); + ## convertirlo a json, aqui lo pongo a capon + #return jsonify ({ 'nfn': 'popup', 'title': 'my title', 'message': 'my message', 'ids': ids }) + +@app.route('/opengnsys/rest/__ogAdmClient/ComandosPendientes', methods=['POST']) +def comandos_pendientes(): + c = request + logging.info(f"{request.get_json()}") + j = request.get_json(force=True) + iph = j['iph'] ## Toma dirección IP + ido = j['ido'] ## Toma identificador del ordenador + logging.info(f"iph ({iph}) ido ({ido})") + + idx = clienteExistente(iph) ## Busca índice del cliente + if not idx: + ## que devuelvo?? pongamos un 404... + abort(404, "Client does not exist") + + param = buscaComandos(ido) ## Existen comandos pendientes, buscamos solo uno + if param is None: + return jsonify({'nfn': 'NoComandosPtes'}) + + #strcpy(tbsockets[idx].estado, CLIENTE_OCUPADO); ## esto queda totalmente del lado del servidor, no lo implemento en python + + return jsonify(param) + +@app.route('/opengnsys/rest/__ogAdmClient/DisponibilidadComandos', methods=['POST']) +def disponibilidad_comandos(): + c = request + logging.info(f"{request.get_json()}") + j = request.get_json(force=True) + iph = j['iph'] + tpc = j['tpc'] + logging.info(f"iph ({iph}) tpc ({tpc})") + + idx = clienteExistente(iph) ## Busca índice del cliente + if not idx: + ## que devuelvo?? pongamos un 404... + abort(404, "Client does not exist") + + #strcpy(tbsockets[idx].estado, tpc); ## esto queda totalmente del lado del servidor, no lo implemento en python + + return jsonify({}) + +@app.route('/opengnsys/rest/__ogAdmClient/', methods=['POST']) +def cucu(cucu): + c = request + j = c.get_json(force=True) + logging.info(f"{request.get_json()} {j}") + if 'cucu' not in j: + abort(400, "missing parameter 'cucu'") + + return jsonify({'cucu': j['cucu']}) + +@app.errorhandler(404) +def _page_not_found(e): + return render_template_string('''not found''') + +@app.errorhandler(500) +def _internal_server_error(e): + return render_template_string('''err''') + +@app.errorhandler(Exception) +def _exception(e): + print(e) + return render_template_string('''exception''') + +if __name__ == '__main__': + app.run(host = '192.168.1.249', port = 443, debug=True) From b7788d9c1df9c32a301e2e29a78d41aa53fc7132 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 2 Aug 2024 13:49:02 +0200 Subject: [PATCH 13/15] refs #538 fix updating the ogagent version across the codebase --- src/about-dialog.ui | 2 +- src/opengnsys/__init__.py | 2 +- src/update.py | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/about-dialog.ui b/src/about-dialog.ui index df40da5..93f4817 100644 --- a/src/about-dialog.ui +++ b/src/about-dialog.ui @@ -65,7 +65,7 @@ - Version 1.3.0 + Version 0 diff --git a/src/opengnsys/__init__.py b/src/opengnsys/__init__.py index e268ca7..cf7a335 100644 --- a/src/opengnsys/__init__.py +++ b/src/opengnsys/__init__.py @@ -37,7 +37,7 @@ import six from . import modules from .RESTApi import REST, RESTError -VERSION='1.3.0' +VERSION='0' __title__ = 'OpenGnsys Agent' __version__ = VERSION diff --git a/src/update.py b/src/update.py index 11f2648..caf18f1 100755 --- a/src/update.py +++ b/src/update.py @@ -17,11 +17,13 @@ def update_version(): with fileinput.FileInput ('about-dialog.ui', inplace=True) as file: for line in file: - print (line.replace ('Version [^<]*', f'Version {version}'), end='') + new = re.sub (r'Version [^<]*', 'Version {}'.format(version), line) + print (new, end='') with fileinput.FileInput ('opengnsys/__init__.py', inplace=True) as file: for line in file: - print(line.replace ('VERSION=.*', f"VERSION='{version}'"), end='') + new = re.sub (r'VERSION=.*', "VERSION='{}'".format(version), line) + print (new, end='') with open ('../windows/VERSION', 'w') as outfile: outfile.write (win_version + '\n') From 25cfb317253a1bf08df46cac79a58ba7e782470f Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Wed, 21 Aug 2024 14:44:30 +0200 Subject: [PATCH 14/15] refs #527 remove unused code --- src/opengnsys/linux/OGAgentService.py | 0 .../server/ogAdmClient/__init__.py | 46 +++---------------- 2 files changed, 7 insertions(+), 39 deletions(-) mode change 100644 => 100755 src/opengnsys/linux/OGAgentService.py diff --git a/src/opengnsys/linux/OGAgentService.py b/src/opengnsys/linux/OGAgentService.py old mode 100644 new mode 100755 diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index 0e7673c..ad31007 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -48,7 +48,6 @@ import urllib.request from configparser import NoOptionError from opengnsys import REST, operations, VERSION from opengnsys.log import logger -from opengnsys.scriptThread import ScriptExecutorThread from opengnsys.workers import ServerWorker # Check authorization header decorator @@ -173,44 +172,6 @@ class ogAdmClientWorker(ServerWorker): threading.Thread(target=pwoff).start() return {'op': 'launched'} - @check_secret - def process_script(self, path, get_params, post_params, server): - """ - Processes an script execution (script should be encoded in base64) - :param path: - :param get_params: - :param post_params: JSON object {"script": "commands"} - :param server: authorization header - :return: JSON object {"op": "launched"} - """ - logger.debug('Processing script request') - # Decoding script - script = urllib.parse.unquote(base64.b64decode(post_params.get('script')).decode('utf-8')) - logger.debug('received script {}'.format(script)) - if operations.os_type == 'Windows': - ## for windows, we turn the script into utf16le, then to b64 again, and feed the blob to powershell - u16 = script.encode ('utf-16le') ## utf16 - b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8)) - script = """ -import os -import tempfile -import subprocess -cp = subprocess.run ("powershell -WindowStyle Hidden -EncodedCommand {}", capture_output=True) -subprocs_log = os.path.join (tempfile.gettempdir(), 'opengnsys-subprocs.log') -with open (subprocs_log, 'ab') as fd: ## TODO improve this logging - fd.write (cp.stdout) - fd.write (cp.stderr) -""".format (b64) - else: - script = 'import subprocess; subprocess.check_output("""{0}""",shell=True)'.format(script) - # Executing script. - if post_params.get('client', 'false') == 'false': - thr = ScriptExecutorThread(script) - thr.start() - else: - self.sendClientMessage('script', {'code': script}) - return {'op': 'launched'} - @check_secret def process_logoff(self, path, get_params, post_params, server): logger.warn('in process_logoff, should not happen') @@ -223,6 +184,13 @@ with open (subprocs_log, 'ab') as fd: ## TODO improve this logging ## type(post_params) "" return {'debug':'test'} + + + + + + + #def process_client_popup(self, params): # logger.warn('in process_client_popup') From 023886cea320b2b8dca11761010dbb4a60328589 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Tue, 27 Aug 2024 11:36:16 +0200 Subject: [PATCH 15/15] refs #527 decode a base64 blob from ogcore --- src/test_modules/server/ogAdmClient/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test_modules/server/ogAdmClient/__init__.py b/src/test_modules/server/ogAdmClient/__init__.py index ad31007..b107431 100644 --- a/src/test_modules/server/ogAdmClient/__init__.py +++ b/src/test_modules/server/ogAdmClient/__init__.py @@ -239,6 +239,7 @@ class ogAdmClientWorker(ServerWorker): if (type(res) is not dict): #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor + logger.debug (f'res ({res})') logger.error ('Error al enviar trama ***send() fallo') return False @@ -350,11 +351,12 @@ class ogAdmClientWorker(ServerWorker): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') logger.error ('Ha ocurrido algún problema al recibir un archivo por la red') return False + logger.debug (f'res ({res})') fileautoexec = '/tmp/_autoexec_{}'.format (self.IPlocal) logger.debug ('fileautoexec ({})'.format (fileautoexec)) with open (fileautoexec, 'w') as fd: - fd.write (res['contents']) + fd.write (base64.b64decode (res['contents']).decode('utf-8')) self.ejecutaArchivo (fileautoexec);