diff --git a/CHANGELOG.md b/CHANGELOG.md index 68230df286..9c8bbb5d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ## [5.0.1](https://github.com/medulla-tech/medulla/releases/tag/5.0.1) (unreleased) - [FEATURE] Add auto-refresh on the deployment page +- [FEATURE] Make the imaging inventory use xmpp to send inventory. - [BUGFIX] Fix Translations - [BUGFIX] Fix display of the inventory of a machine when it is offline - [BUGFIX] Fix creating group from a boolean diff --git a/debian/pulse2-package-server.install b/debian/pulse2-package-server.install index 7b97cb8df0..8749744a5d 100644 --- a/debian/pulse2-package-server.install +++ b/debian/pulse2-package-server.install @@ -1,5 +1,6 @@ usr/sbin/pulse2-package-server usr/sbin/pulse2-package-server-register-imaging +usr/sbin/message-sender.py usr/lib/python3/*-packages/pulse2/package_server etc/mmc/pulse2/package-server etc/mmc/pulse2/atftpd/pcre.conf diff --git a/services/bin/Makefile.am b/services/bin/Makefile.am index 6edd62df9d..28e63a5494 100644 --- a/services/bin/Makefile.am +++ b/services/bin/Makefile.am @@ -23,7 +23,7 @@ dist_sbin_SCRIPTS = pulse2-package-server pulse2-package-server-register-imaging pulse2-packageparser.py pulse2-setup pulse2-load-defaults \ pulse2-extract-glpi-search-options \ pulse2-backup-servers pulse2-debug pulse2-dbupdate pulse2-migration_old_package.py\ - pulse2-msc-clean-database pulse2-inventory-clean-database \ + pulse2-msc-clean-database pulse2-inventory-clean-database message-sender.py\ pulse2-collect-info pulse2-create-group pulse2-register-pxe.py \ pulse2-connect-machine-backuppc pulse2-disconnect-machine-backuppc \ restart-pulse-services pulse2-inscription_packages_in_base.py pulse2-generation_package.py \ diff --git a/services/bin/message-sender.py b/services/bin/message-sender.py new file mode 100644 index 0000000000..47ba96254f --- /dev/null +++ b/services/bin/message-sender.py @@ -0,0 +1,227 @@ +#!/usr/bin/python3 +# -*- coding: utf-8; -*- +# SPDX-FileCopyrightText: 2016-2023 Siveo +# SPDX-License-Identifier: GPL-3.0-or-later + +import sys + +from slixmpp import ClientXMPP +from slixmpp import jid +from slixmpp.xmlstream import handler, matcher +from slixmpp.xmlstream.stanzabase import ET +from slixmpp.exceptions import * +import slixmpp +import asyncio +import os +import logging +from argparse import ArgumentParser + +logger = logging.getLogger("message-sender") +# Additionnal path + + +class MyXMPPClient(ClientXMPP): + def __init__( + self, + jid, + password, + message, + timeout=20, + toagent="master@pulse", + ipv4="127.0.0.1", + port=5222, + ): + super().__init__(jid, password) + self.stanzamessage = message + self.timeout = timeout + self.toagent = toagent + self.ipv4 = ipv4 + self.port = port + + self.add_event_handler("session_start", self.start) + self.add_event_handler("message", self.message) + self.add_event_handler("register", self.register) + self.add_event_handler("connecting", self.handle_connecting) + self.add_event_handler("connection_failed", self.connection_failed) + self.add_event_handler("disconnected", self.handle_disconnected) + self.add_event_handler("connected", self.handle_connected) + + async def start(self, event): + self.send_presence() + await self.get_roster() + # We're only concerned about registering, so nothing more to do here. + self.send_message(mto=self.toagent, mbody=self.stanzamessage, mtype="chat") + self.disconnect() + + async def message(self, msg): + # msg["body"] + # msg["type"] + # msg["from"] + logging.debug("Message : %s" % msg["body"]) + + async def stop(self): + logger.debug("Stopping XMPP client...") + self.disconnect() + + def handle_connecting(self, data): + """ + success connecting agent + """ + logger.debug("Connected") + + def connection_failed(self, data): + """ + on connection failed on libere la connection + """ + logger.debug("Failed to connect to %s : %s" % (self.boundjid, data)) + self.disconnect() + + def handle_disconnected(self, data): + logger.debug("Disconnected") + + def handle_connected(self, data): + """ + success connecting agentconnect( + """ + logger.debug("connected to %s" % (self.boundjid)) + + async def register(self, iq): + """ + Fill out and submit a registration form. + + The form may be composed of basic registration fields, a data form, + an out-of-band link, or any combination thereof. Data forms and OOB + links can be checked for as so: + + if iq.match('iq/register/form'): + # do stuff with data form + # iq['register']['form']['fields'] + if iq.match('iq/register/oob'): + # do stuff with OOB URL + # iq['register']['oob']['url'] + + To get the list of basic registration fields, you can use: + iq['register']['fields'] + """ + resp = self.Iq() + resp["type"] = "set" + resp["register"]["username"] = self.boundjid.user + resp["register"]["password"] = self.password + try: + await resp.send() + logger.info("Account created for %s!" % self.boundjid) + except IqError as e: + logger.error("Could not register account: %s" % e.iq["error"]["text"]) + self.disconnect() + except IqTimeout: + logger.error("No response from server.") + self.disconnect() + + +async def main(): + parser = ArgumentParser() + parser.add_argument( + "-P", + "--port", + dest="port", + type=int, + default=5222, + help="port utilisé (par défaut: 5222)", + ) + parser.add_argument( + "-I", + "--ipv4", + dest="ipv4", + default="127.0.0.1", + help="adresse IPv4 de destination", + ) + parser.add_argument("-j", "--jid", dest="jid", help="JID à utiliser") + parser.add_argument( + "-p", "--password", dest="password", help="mot de passe à utiliser" + ) + parser.add_argument("-m", "--message", dest="message", help="message envoyé") + parser.add_argument( + "-T", + "--timeout", + dest="timeout", + type=int, + default=5, + help="timeout en secondes (par défaut: 5)", + ) + parser.add_argument( + "-t", + "--toagent", + dest="toagent", + default="master@pulse", + help="agent destinataire du message)", + ) + parser.add_argument( + "-L", + "--levellog", + dest="levellog", + default="DEBUG", + help="niveau de journalisation (par défaut: DEBUG)", + ) + parser.add_argument( + "-F", + "--filelog", + dest="filelog", + default="/var/log/pulse2/message-sender.log", + help="fichier de journal (par défaut: /var/log/pulse2/message-sender.log)", + ) + args = parser.parse_args() + + if len(sys.argv) == 1: + parser.print_help() + sys.exit(0) + log_dir = os.path.dirname(args.filelog) + print(log_dir) + if not os.path.exists(log_dir): + os.makedirs(log_dir) + + logging.basicConfig( + level=args.levellog.upper(), + format="%(levelname)-8s %(message)s", + filename=args.filelog, + ) + # self.logger = logging.getLogger('message-sender') + if sys.platform.startswith("linux") and os.getuid() != 0: + logging.error("Agent must be running as root") + sys.exit(0) + elif sys.platform.startswith("win") and isWinUserAdmin() == 0: + logging.error("Medulla agent must be running as Administrator") + sys.exit(0) + elif sys.platform.startswith("darwin") and not isMacOsUserAdmin(): + logging.error("Medulla agent must be running as root") + sys.exit(0) + # xmpp = MyXMPPClient(jid, password, message, timeout, toagent, ipv4, port) + xmpp = MyXMPPClient( + args.jid, + args.password, + args.message, + args.timeout, + args.toagent, + args.ipv4, + args.port, + ) + xmpp.register_plugin("xep_0030") # Service Discovery + xmpp.register_plugin("xep_0045") # Multi-User Chat + xmpp.register_plugin("xep_0004") # Data Forms + xmpp.register_plugin("xep_0050") # Adhoc Commands + xmpp.register_plugin( + "xep_0199", + {"keepalive": True, "frequency": 600, "interval": 600, "timeout": 500}, + ) + xmpp.register_plugin("xep_0077") # In-band Registration + xmpp["xep_0077"].force_registration = True + # Démarre le client XMPP dans une boucle asyncio + + xmpp.connect() + # Temporisateur pour déclencher l'événement après 10 secondes + await asyncio.sleep(10) + # Déclenche l'événement d'arrêt pour déconnecter proprement le client XMPP + await xmpp.stop() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/services/bin/pulse2-register-pxe.py b/services/bin/pulse2-register-pxe.py index af4b518bef..c104be3dc1 100644 --- a/services/bin/pulse2-register-pxe.py +++ b/services/bin/pulse2-register-pxe.py @@ -417,12 +417,13 @@ def parsejsoninventory(file, file_content): def senddata(query, ip="127.0.0.1", port=1001): adresse = (ip, port) monSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - logging.getLogger().debug("Send PXE xml for registration :%s" % query) - monSocket.sendto("\xBB%s" % query, adresse) + logging.getLogger().error( + "Send PXE xml for registration :%s" % query.encode("utf-8") + ) + monSocket.sendto(bytes("\xBB%s" % query, "utf-8"), adresse) time.sleep(conf["pxe_timesenddata"]) - monSocket.sendto("\xBA%s" % query, adresse) + monSocket.sendto(bytes("\xBA%s" % query, "utf-8"), adresse) time.sleep(conf["pxe_timesenddata"]) - monSocket.sendto("\xBA%s" % query, adresse) monSocket.close() @@ -462,11 +463,12 @@ def macadressclear(file_content, interface_mac_clear): logging.getLogger().debug( "Clear interface with macadress %s " % interface_mac_clear ) - xml_str = ET.tostring(root).encode("ASCII", "ignore") - logging.getLogger().debug("New xml netwrok : %s" % xml_str) + xml_str = ET.tostring(root) + logging.getLogger().debug("New xml network : %s" % xml_str) + xml_str = xml_str.decode("utf-8") xml_str = xml_str.replace("\n", "") + return xml_str - pass return file_content @@ -524,7 +526,6 @@ def process_IN_DELETE(self, event): def process_IN_MODIFY(self, event): logging.getLogger().debug("MODIFY event: %s" % event.pathname) - time.sleep(0.025) self.traitement(event.pathname) def process_IN_OPEN(self, event): @@ -555,6 +556,7 @@ def traitement(self, name): header = '' file_content = file_content[:-10] xmldata = "%s%sMc:%s" % (header, file_content, mac) + logging.getLogger().debug( "XML recv from pxe client %s" % xmldata ) diff --git a/services/conf/pulse2/package-server/package-server.ini b/services/conf/pulse2/package-server/package-server.ini index dc9b6dc508..15fc3ac68b 100644 --- a/services/conf/pulse2/package-server/package-server.ini +++ b/services/conf/pulse2/package-server/package-server.ini @@ -7,6 +7,16 @@ host = 0.0.0.0 # The public IP send when a client agent is wondering how to reach this package server, set to the "host" parameter value if not defined public_ip = public_mask = 255.255.255.0 + +[connection] +jid = +password = +recipient = master_inv@pulse +server=127.0.0.1 +port=5222 +timeout=40 +domain = + # # -------- # PACKAGES diff --git a/services/mmc/plugins/imaging/functions.py b/services/mmc/plugins/imaging/functions.py index ed9dd931e8..6644fdc956 100644 --- a/services/mmc/plugins/imaging/functions.py +++ b/services/mmc/plugins/imaging/functions.py @@ -3935,6 +3935,9 @@ def injectInventory(self, imaging_server_uuid, computer_uuid, inventory): except Exception as e: logging.getLogger().exception(e) ret = [False, str(e)] + + self.synchroComputer(computer_uuid) + return ret def getPartitionsToBackupRestore(self, computer_uuid): @@ -4286,7 +4289,6 @@ def treatRegister( logger.error("couldn't initialize the ImagingApi to %s" % (url)) distinct_loc = xmlrpcCleanup(distinct_loc) - # if defer_list == False: if len(defer_list) == 0: distinct_locs = distinct_loc keyvaleur = list(distinct_loc.keys()) diff --git a/services/pulse2/database/imaging/__init__.py b/services/pulse2/database/imaging/__init__.py index 552cf13539..e01f5f15a8 100644 --- a/services/pulse2/database/imaging/__init__.py +++ b/services/pulse2/database/imaging/__init__.py @@ -1535,7 +1535,7 @@ def __fillMenuItem(self, session, mi, menu_id, params): def __addMenuDefaults(self, session, menu, mi, params): is_menu_modified = False # if 'default' in params and params['default']: - if "default" in params and "default" in params and params["default"]: + if "default" in params and params["default"]: is_menu_modified = True menu.fk_default_item = mi.id # if 'default_WOL' in params and params['default_WOL']: @@ -5896,17 +5896,33 @@ def injectInventory(self, imaging_server_uuid, computer_uuid, inventory): Inject a computer inventory into the dabatase. For now only the ComputerDisk and ComputerPartition tables are used. """ + if not isUUID(imaging_server_uuid): raise TypeError("Bad imaging server UUID: %s" % imaging_server_uuid) if not isUUID(computer_uuid): raise TypeError("Bad computer UUID: %s" % computer_uuid) session = create_session() session.begin() + + locationServerImaging = self.getLocationImagingServerByServerUUID( + imaging_server_uuid + ) + target = None + session.query(Target).filter_by(uuid=computer_uuid).delete() + menu = self.getEntityDefaultMenu("UUID%s" % locationServerImaging) + new_menu = self.__duplicateMenu( + session, menu, "UUID%s" % locationServerImaging, None, False + ) + target = Target() + target.fk_menu = new_menu.id + target.is_registered_in_package_server = 1 + new_menu.fk_synchrostate = 1 + target.name = inventory["shortname"] + target.uuid = computer_uuid + target.type = 1 + target.fk_entity = locationServerImaging + try: - # First remove old computer inventory - target = session.query(Target).filter_by(uuid=computer_uuid).one() - for current_disk in target.disks: - session.delete(current_disk) # Then push a new inventory if "disk" in inventory: for disknum in inventory["disk"]: @@ -5926,44 +5942,25 @@ def injectInventory(self, imaging_server_uuid, computer_uuid, inventory): cp.start = int(part["start"]) cd.partitions.append(cp) target.disks.append(cd) - locationServerImaging = self.getLocationImagingServerByServerUUID( - imaging_server_uuid - ) - target.fk_entity = locationServerImaging + self.logger.debug( "Attribution location %s for computer %s" % (target.fk_entity, target.name) ) session.add(target) session.commit() - except InvalidRequestError as e: - session.rollback() - if hasattr(e, "message"): - if e.message == "No rows returned for one()": - self.logger.warn( - "Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging." - % (computer_uuid) - ) - return [ - False, - "Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging." - % (computer_uuid), - ] - elif e == "No rows returned for one()": - self.logger.warn( - "Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging." - % (computer_uuid) - ) - return [ - False, - "Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging." - % (computer_uuid), - ] - else: - raise - except BaseException: + # except InvalidRequestError as e: + except Exception as e: session.rollback() - raise + self.logger.warn( + "Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging." + % (computer_uuid) + ) + return [ + False, + "Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging." + % (computer_uuid), + ] session.close() return [True, True] diff --git a/services/pulse2/package_server/config.py b/services/pulse2/package_server/config.py index e8dd8810f6..a4bdfea410 100644 --- a/services/pulse2/package_server/config.py +++ b/services/pulse2/package_server/config.py @@ -660,6 +660,42 @@ def setup(self, config_file): if self.cp.has_option("main", "up_assign_algo"): self.up_assign_algo = self.cp.get("main", "up_assign_algo") + self.connection_jid = "" + self.connection_password = "" + self.connection_recipient = "" + self.connection_server = "" # ip + self.connection_port = 0 + self.connection_timeout = 40 + if self.cp.has_section("connection") and self.cp.has_option( + "connection", "jid" + ): + self.connection_jid = self.cp.get("connection", "jid") + + if self.cp.has_section("connection") and self.cp.has_option( + "connection", "password" + ): + self.connection_password = self.cp.get("connection", "password") + + if self.cp.has_section("connection") and self.cp.has_option( + "connection", "server" + ): + self.connection_server = self.cp.get("connection", "server") + + if self.cp.has_section("connection") and self.cp.has_option( + "connection", "port" + ): + self.connection_port = self.cp.getint("connection", "port") + + if self.cp.has_section("connection") and self.cp.has_option( + "connection", "recipient" + ): + self.connection_recipient = self.cp.get("connection", "recipient") + + if self.cp.has_section("connection") and self.cp.has_option( + "connection", "timeout" + ): + self.connection_timeout = self.cp.getint("connection", "timeout") + def config_addons(conf): if len(conf.mirrors) > 0: diff --git a/services/pulse2/package_server/imaging/pxe/api.py b/services/pulse2/package_server/imaging/pxe/api.py index 1f7a8bdca4..d89b81ebf8 100644 --- a/services/pulse2/package_server/imaging/pxe/api.py +++ b/services/pulse2/package_server/imaging/pxe/api.py @@ -25,10 +25,70 @@ from pulse2.package_server.imaging.pxe.tracking import EntryTracking from pulse2.package_server.config import P2PServerCP from pulse2.imaging.bootinventory import BootInventory +import subprocess import re import xml.etree.ElementTree as ET # form XML Building import time +import asyncio +from asyncio.exceptions import TimeoutError +from slixmpp import ClientXMPP +import zlib +import base64 +import os +import random +import time +import sys +import json +import platform +from subprocess import Popen, DEVNULL + +logger = logging.getLogger() + + +class DetachedProcess: + def __init__(self, command=[]): + self.command = command + self.system = platform.system() + self.creationflags = self._get_creationflags() + + def _get_creationflags(self): + if self.system == "Windows": + from subprocess import ( + DETACHED_PROCESS, + CREATE_NEW_PROCESS_GROUP, + CREATE_BREAKAWAY_FROM_JOB, + ) + + return ( + DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB + ) + else: + return 0 + + def add_option(self, option): + self.command.append(option) + + def run(self): + self.command = [ + str(element) + if (isinstance(element, int) or isinstance(element, float)) + else element + for element in self.command + ] + if self.system == "Windows": + Popen( + self.command, + creationflags=self.creationflags, + stdin=DEVNULL, + stdout=DEVNULL, + stderr=DEVNULL, + ) + else: + # Pour Linux et macOS, la gestion de processus détachés est différente + # Vous pouvez adapter cela selon les besoins spécifiques du système d'exploitation + Popen(self.command, stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL) + class PXEImagingApi(PXEMethodParser): """ @@ -112,13 +172,14 @@ def computerRegisterSyslinux(self, mac, inventory, ip_address): """ logging.getLogger().debug("FIRST REGISTRATION TO ALLOW INVENTORY") m = re.search(".*<\\/REQUEST>", inventory) - file_content = str(m.group(0)) + file_content = m.group(0) ipadress = self.ip_adressexml(file_content) mac1 = self.mac_adressexml(file_content) hostnamexml = self.hostname_xml(file_content) inventory = '\n%s' % (file_content) ip_address = ipadress mac = mac1 + return self.send_inventory(inventory, hostnamexml) def _computerRegister(self, result, hostname, mac, delay=0): @@ -283,8 +344,7 @@ def InventorySysLinux(self, mac, inventory, ip_address): """ logging.getLogger().debug("INJECT INVENTORY NEXT HOSTNAME AND ENTITY") m = re.search(".*<\\/REQUEST>", inventory) - file_content = str(m.group(0)) - + file_content = m.group(0) ipadress = self.ip_adressexml(file_content) mac1 = self.mac_adressexml(file_content) hostnamexml = self.hostname_xml(file_content) @@ -303,15 +363,18 @@ def InventorySysLinux(self, mac, inventory, ip_address): parsed_inventory1 = BootInventory() parsed_inventory1.initialise(file_content) parsed_inventory = parsed_inventory1.dump() + logging.getLogger().error( + "parsed inventory : %s %s" % (type(parsed_inventory), parsed_inventory) + ) self.api.logClientAction( mac, LOG_LEVEL.DEBUG, LOG_STATE.MENU, "boot menu shown" ) - d = self.api.injectInventory(mac, parsed_inventory) # 2nd step - send inventory by HTTP POST to inventory server + d = self.api.injectInventory(mac, parsed_inventory) + d.addCallback(self._injectedInventoryOk, mac, inventory) d.addErrback(self._injectedInventoryError) - # self.send_inventory(parsed_inventory1., hostname) return d # ------------------------ process inventory --------------------------- @@ -466,30 +529,30 @@ def _injectedInventorySend(self, computer, mac, inventory): "PXE Proxy: Unknown client, ignore received inventory" ) return + hostname = computer["shortname"] entity = computer["entity"] m = re.search(".*<\\/REQUEST>", inventory) - file_content = str(m.group(0)) - # inventory = parsed_inventory1.changEntityAndHostName(file_content,entity,hostname) + file_content = m.group(0) file_content = self.changEntityAndHostName(file_content, entity, hostname) inventory = self.changdeviceid(file_content, hostname) + if isinstance(inventory, bytes): + inventory = inventory.decode("utf-8") + inventory = '' + inventory logging.getLogger().debug("send invotory depuis _injectedInventorySend") - d = self.send_inventory(inventory, hostname) - @d.addCallback - def _cb(result): + try: + self.send_inventory(inventory, hostname) self.api.logClientAction( mac, LOG_LEVEL.INFO, LOG_STATE.INVENTORY, "hardware inventory updated" ) - - @d.addErrback - def _eb(failure): + except Exception as e: self.api.logClientAction( mac, LOG_LEVEL.WARNING, LOG_STATE.INVENTORY, - "hardware inventory not updated", + "hardware inventory not updated: " + str(e), ) self.api.logClientAction( @@ -497,22 +560,68 @@ def _eb(failure): ) m = re.search(".*<\\/REQUEST>", inventory) - file_content = str(m.group(0)) + file_content = m.group(0) ipadress = self.ip_adressexml(file_content) ip_address = ipadress + if self.config.imaging_api["glpi_mode"]: - d = task.deferLater( - reactor, 0, self.glpi_register, mac, hostname, ip_address - ) - d.addCallback(self._computerRegister, hostname, mac, 2) - d.addErrback(self._ebRegisterError, mac) - return d + try: + self.glpi_register(mac, hostname, ip_address) + self._computerRegister(None, hostname, mac) + except Exception as e: + self._ebRegisterError(e, mac) else: - return self._computerRegister(None, hostname, mac) + self._computerRegister(None, hostname, mac) + + def file_get_binarycontents(self, filename, offset=-1, maxlen=-1): + fp = open(filename, "rb") + try: + if offset > 0: + fp.seek(offset) + return fp.read(maxlen) + finally: + fp.close() + + def file_put_contents(self, filename, data): + if not os.path.exists(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + with open(filename, "w") as f: + f.write(data) + + def file_put_contents_w_a(self, filename, data, option="w"): + if not os.path.exists(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + if option in ["a", "w"]: + with open(filename, option) as f: + f.write(data) + + def getRandomName(self, nb, pref=""): + a = "abcdefghijklnmopqrstuvwxyz0123456789" + d = pref + for _ in range(nb): + d = d + a[random.randint(0, 35)] + return d + + def convert_to_bytes(self, input_data): + if isinstance(input_data, bytes): + return input_data + elif isinstance(input_data, str): + return input_data.encode("utf-8") + else: + raise TypeError("L'entrée doit être de type bytes ou string.") + + def compress_and_encode(self, string): + # Convert string to bytes + data = self.convert_to_bytes(string) + # Compress the data using zlib + compressed_data = zlib.compress(data, 9) + # Encode the compressed data in base64 + encoded_data = base64.b64encode(compressed_data) + return encoded_data.decode("utf-8") def send_inventory(self, inventory, hostname): """ - Sending the inventory on FusionInventory (XML) format. + Sending the inventory on substitut inventory (XML) format. @param inventory: inventory to send @type inventory: str @@ -521,56 +630,88 @@ def send_inventory(self, inventory, hostname): @type hostname: str """ - url = "http://" - try: - if self.config.imaging_api["inventory_enablessl"]: - protocol = "https" - else: - protocol = "http" - - url = "%s://%s:%d/" % ( - protocol, - self.config.imaging_api["inventory_host"], - self.config.imaging_api["inventory_port"], - ) - - # POST the inventory to the inventory server - logging.getLogger().debug( - "PXE Proxy: PXE inventory forwarded to inventory server at %s" % url - ) - logging.getLogger().debug( - "POST the inventory to the inventory server \nINVENTORY\n %s" - % inventory - ) - d = Agent( - url, - method="POST", - postdata=inventory, - headers={ - "Content-Type": "application/x-www-form-urlencoded", - "Content-Length": str(len(inventory)), - "User-Agent": "Pulse2 Imaging server inventory hook", - }, + retour = False + + domain = self.config.connection_domain + password = self.getRandomName(6, "messagesenderpxe") + jid = f"{password}@{domain}" + recipient = self.config.connection_recipient + server = self.config.connection_server + port = self.config.connection_port + timeout = self.config.connection_timeout + result = {} + result["action"] = "resultinventory" + result["ret"] = 0 + result["sessionid"] = self.getRandomName(6, "inventory") + result["base64"] = False + result["data"] = {} + if not inventory.startswith("' + inventory + else: + strinventorysave = inventory + + # cette fonction compress_and_encode + # prend une chaîne de caractères, + # la compresse, encode le résultat compressé en base64, + # et retourne le résultat encodé sous forme de chaîne de caractères. + datainventory = self.compress_and_encode(strinventorysave) + result["data"]["inventory"] = datainventory + + if sys.platform.startswith("win"): + detached_process = DetachedProcess( + [ + "python3", + "/usr/sbin/message-sender.py", + "-j", + jid, + "-P", + port, + "-p", + password, + "-I", + server, + "-m", + json.dumps(result), + "-t", + recipient, + ] + ).run() + else: + python3_path = ( + subprocess.check_output(["which", "python3"]).decode("utf-8").strip() ) + detached_process = DetachedProcess( + [ + python3_path, + "/usr/sbin/message-sender.py", + "-j", + jid, + "-P", + port, + "-p", + password, + "-I", + server, + "-m", + json.dumps(result), + "-t", + recipient, + ] + ).run() + time.sleep(12) + command = ["ejabberdctl", "unregister", password, domain] + try: + subprocess.run(command, check=True) + logging.getLogger().debug("Command executed successfully.") + retour = True + except subprocess.CalledProcessError as e: + logging.getLogger().debug("Error: %s", e) + + logger.debug( + "PXE Proxy: PXE inventory from client %s successfully injected" % hostname + ) - @d.addCallback - def _cb(result): - if result: - logging.getLogger().debug( - "PXE Proxy: PXE inventory from client %s successfully injected" - % hostname - ) - return result - - return d - - except Exception as e: - logging.getLogger().error( - "PXE Proxy: Unable to forward PXE inventory to inventory server at %s: %s" - % (url, str(e)) - ) - # This method must return a Deferred - return Deferred() + return retour # ----------------------- backup ------------------------------- diff --git a/services/pulse2/package_server/imaging/pxe/parser.py b/services/pulse2/package_server/imaging/pxe/parser.py index 982a693ce4..6b9d8142f1 100644 --- a/services/pulse2/package_server/imaging/pxe/parser.py +++ b/services/pulse2/package_server/imaging/pxe/parser.py @@ -115,6 +115,9 @@ def __init__(self, packet): @property def mac(self): """Common argument for all PXE methods""" + if isinstance(self.MAC_FLAG, str): + self.MAC_FLAG = self.MAC_FLAG.encode("utf-8") + if self.MAC_FLAG in self.packet: start = self.packet.index(self.MAC_FLAG) + len(self.MAC_FLAG) @@ -169,7 +172,7 @@ def message(self): complement = None if self.level == 1: - complement = ord(self.packet[2]) + complement = self.packet[2] if self.level in [2, 3, 4, 5]: if self.packet[2] == "-": complement = self.packet[3:] @@ -225,7 +228,7 @@ def password(self): def num(self): """Menu item number""" if len(self.packet) > 1: - return ord(self.packet[1]) + return self.packet[1] else: return 0 diff --git a/services/pulse2/package_server/imaging/pxe/server.py b/services/pulse2/package_server/imaging/pxe/server.py index 5f0c99fb65..a566ac7dad 100644 --- a/services/pulse2/package_server/imaging/pxe/server.py +++ b/services/pulse2/package_server/imaging/pxe/server.py @@ -57,6 +57,37 @@ def method_exec(self, imaging, method, args): """ return method(imaging, *args) + def xmpp_process(self, data, client=None): + """ + Processus XMPP. + + Cette fonction traite les données reçues via XMPP. + + @param data: Les données reçues. + @type data: str + + @param client: Tuple représentant l'adresse du client (hôte, port). + @type client: tuple + + @return: False si la chaîne est vide ou si le premier octet est 0xBB ou 0xBA, True sinon. + @rtype: bool + """ + logging.getLogger().debug("PXE Proxy: self.config: %s " % self.config) + logging.getLogger().debug("PXE Proxy: method: %s " % data) + if not data: + return False + first_byte = ord(data[0]) + hex_value = hex(first_byte) + if hex_value == "0xBB": # or hex_value == '0xBA' + imaging = PXEImagingApi(self.config) + imaging.set_api(self.api) + fnc, args = imaging.get_method(data) + result = self.method_exec(imaging, fnc, args) + # pas de reponse + self.send_response(None, fnc, client) + return False + return True + def process_data(self, data, client=None): """ Called when a packet received. @@ -67,8 +98,6 @@ def process_data(self, data, client=None): @param client: client (host, port) tuple @type client: tuple """ - # For each session a new instance of PXEImagingApi created - imaging = PXEImagingApi(self.config) imaging.set_api(self.api) diff --git a/web/modules/imaging/imaging/configure.php b/web/modules/imaging/imaging/configure.php index 44acbbf62b..f3cb40a9a4 100644 --- a/web/modules/imaging/imaging/configure.php +++ b/web/modules/imaging/imaging/configure.php @@ -39,7 +39,6 @@ function expertModeDisplay($f, $has_profile, $type, $menu, $opts, $target, $real if (!$has_profile) { $f->add(new TitleElement(sprintf(_T("%s menu parameters", "imaging"), ($type == '' ? _T('Computer', 'imaging') : _T('Profile', 'imaging'))))); $f->push(new Table()); - $f->add( new TrFormElement( _T('Default menu label', 'imaging'), @@ -47,6 +46,7 @@ function expertModeDisplay($f, $has_profile, $type, $menu, $opts, $target, $real ), array("value" => $menu['default_name']) ); + $f->add( new TrFormElement( _T('Menu timeout', 'imaging'), @@ -84,12 +84,7 @@ function expertModeDisplay($f, $has_profile, $type, $menu, $opts, $target, $real ), array("value" => $menu['message']) //"Warning ! Your PC is being backed up or restored. Do not reboot !") ); - /* $f->add( - new TrFormElement(_T("Keyboard mapping (empty/fr)", "imaging"), - new InputTpl("boot_keyboard")), array("value" => "") - ); */ $f->pop(); - } else { $f->add(new HiddenTpl("default_m_label"), array("value" => $menu['default_name'], "hide" => true)); $f->add(new HiddenTpl("default_m_timeout"), array("value" => $menu['timeout'], "hide" => true)); @@ -471,10 +466,12 @@ function expertModeDisplay($f, $has_profile, $type, $menu, $opts, $target, $real } if (!$ret[0] && $ret[1] == 'ERROR') { $err_msg = getPulse2ErrorString($ret[2], $ret[3]); + $errno = $ret[1]; + $errmsg = $ret[2]; + } else { list($whose, $menu) = $ret; } - /* * whose is a list who come from python imaging database code: [uuid, type, target[0].toH] * menu is the target boot menu diff --git a/web/modules/imaging/imaging/register_target.php b/web/modules/imaging/imaging/register_target.php index 04e839c908..ef9d1528c8 100644 --- a/web/modules/imaging/imaging/register_target.php +++ b/web/modules/imaging/imaging/register_target.php @@ -25,18 +25,15 @@ if ($_GET['module'] == 'base' && $_GET['submod'] == 'computers') { - require("modules/base/computers/localSidebar.php"); + require_once("modules/base/computers/localSidebar.php"); +} else { + require_once("modules/imaging/manage/localSidebar.php"); } -else { - require("modules/imaging/manage/localSidebar.php"); -} -require("graph/navbar.inc.php"); +require_once("graph/navbar.inc.php"); $p = new PageGenerator(); $p->setSideMenu($sidemenu); $p->display(); -$is_registering = True; -include("configure.php"); - -?> +$is_registering = true; +require_once("configure.php"); diff --git a/web/modules/imaging/imaging/tabs.php b/web/modules/imaging/imaging/tabs.php index b956359b03..aa134661d3 100644 --- a/web/modules/imaging/imaging/tabs.php +++ b/web/modules/imaging/imaging/tabs.php @@ -72,14 +72,6 @@ function resetDefaultMenus($uuids) } } -function resetDefaultMenus($uuids) -{ - $ret = xmlrpc_resetComputerBootMenus($uuids); - if (!isXMLRPCError() && $ret) { - new NotifyWidgetSuccess(sprintf(_T("Default menu has been successfully restored.", "imaging"))); - } -} - if (isset($_GET['reset_defaultMenu']) && $_GET['reset_defaultMenu'] == 1) { if(isset($_GET['target']) && $_GET['target'] == "all") { $location = htmlentities($_GET['location']);