diff --git a/data/etc/revpipyload/revpipyload.conf b/data/etc/revpipyload/revpipyload.conf index 52f0d8f..37e1eaa 100644 --- a/data/etc/revpipyload/revpipyload.conf +++ b/data/etc/revpipyload/revpipyload.conf @@ -27,7 +27,7 @@ watchdog = 1 [XMLRPC] xmlrpc = 1 aclfile = /etc/revpipyload/aclxmlrpc.conf -bindip = 127.0.0.1 +bindip = socket [MQTT] mqtt = 0 diff --git a/src/revpipyload/revpipyload.py b/src/revpipyload/revpipyload.py index bb663bd..ff762de 100644 --- a/src/revpipyload/revpipyload.py +++ b/src/revpipyload/revpipyload.py @@ -31,6 +31,7 @@ __license__ = "GPLv2" import gzip import os import signal +import socket import tarfile import zipfile from configparser import ConfigParser @@ -51,7 +52,7 @@ from . import proginit from .helper import get_revpiled_address, pi_control_reset, refullmatch from .shared.ipaclmanager import IpAclManager from .watchdogs import ResetDriverWatchdog -from .xrpcserver import SaveXMLRPCServer +from .xrpcserver import SaveXMLRPCServer, UnixStreamXMLRPCServer min_revpimodio = "2.5.0" @@ -314,10 +315,20 @@ class RevPiPyLoad: # Bind IP lesen und anpassen self.xmlrpcbindip = \ self.globalconfig.get("XMLRPC", "bindip", fallback="127.0.0.1") - if self.xmlrpcbindip == "*": - self.xmlrpcbindip = "" - elif self.xmlrpcbindip == "": - self.xmlrpcbindip = "127.0.0.1" + + if self.xmlrpcbindip.lower() == "socket": + # Unix Domain Socket mit festem Pfad + self.xmlrpcbindip = "/run/revpipyload/xmlrpc.socket" + self.xmlrpcisunix = True + elif self.xmlrpcbindip.startswith("/"): + # Unix Domain Socket + self.xmlrpcisunix = True + else: + self.xmlrpcisunix = False + if self.xmlrpcbindip == "*": + self.xmlrpcbindip = "" + elif self.xmlrpcbindip == "": + self.xmlrpcbindip = "127.0.0.1" self.xmlrpcport = self.globalconfig.getint("XMLRPC", "port", fallback=55123) @@ -388,12 +399,35 @@ class RevPiPyLoad: self.xsrv = None else: proginit.logger.debug("create xmlrpc server") - self.xsrv = SaveXMLRPCServer( - (self.xmlrpcbindip, self.xmlrpcport), - logRequests=False, - allow_none=True, - ipacl=self.xmlrpcacl - ) + + if self.xmlrpcisunix: + # Unix Domain Socket Server + proginit.logger.info( + "starting xmlrpc unix server on {0}".format(self.xmlrpcbindip) + ) + + # Vorherige Socket-Datei löschen + try: + os.unlink(self.xmlrpcbindip) + except FileNotFoundError: + pass + + self.xsrv = UnixStreamXMLRPCServer( + self.xmlrpcbindip, + requestHandler=UnixStreamXMLRPCRequestHandler, + logRequests=False, + allow_none=True, + ) + + else: + # Standard IP Server + self.xsrv = SaveXMLRPCServer( + (self.xmlrpcbindip, self.xmlrpcport), + logRequests=False, + allow_none=True, + ipacl=self.xmlrpcacl + ) + self.xsrv.register_introspection_functions() self.xsrv.register_multicall_functions() @@ -936,8 +970,17 @@ class RevPiPyLoad: if self.xsrv is not None: proginit.logger.info("close xmlrpc-server") + self.xsrv.shutdown() self.xsrv.server_close() + # Unix Socket aufräumen + if hasattr(self.xsrv, "address_family") and \ + self.xsrv.address_family == socket.AF_UNIX: + try: + os.unlink(self.xsrv.server_address) + except (FileNotFoundError, AttributeError): + pass + proginit.logger.debug("leave RevPiPyLoad.stop_xmlrpcserver()") def xml_getconfig(self): diff --git a/src/revpipyload/xrpcserver.py b/src/revpipyload/xrpcserver.py index 9f9a616..583ad64 100644 --- a/src/revpipyload/xrpcserver.py +++ b/src/revpipyload/xrpcserver.py @@ -4,6 +4,7 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager" __license__ = "GPLv2" +import socket from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer from . import proginit @@ -102,3 +103,44 @@ class SaveXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): ) return False + + +class UnixStreamXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): + """XML-RPC Request-Handler fuer Unix Domain Sockets.""" + + timeout = 2.0 + + def setup(self): + """Initialisiert die Verbindung ohne TCP-spezifische Socket-Optionen.""" + self.connection = self.request + if self.timeout is not None: + self.connection.settimeout(self.timeout) + self.rfile = self.connection.makefile("rb", self.rbufsize) + self.wfile = self.connection.makefile("wb", self.wbufsize) + + def address_string(self): + """Liefert einen Namen fuer Logging bei Unix Domain Sockets.""" + return "localhost" + + +class UnixStreamXMLRPCServer(SimpleXMLRPCServer): + """XML-RPC Server fuer Unix Domain Sockets.""" + address_family = socket.AF_UNIX + + def _dispatch(self, method, params): + """Prueft ACL Level fuer angeforderte Methode. + + @param method Angeforderte Methode + @param params Argumente fuer Methode + @return Dispatched data + + """ + # ACL on a socket is always max level + if method == "xmlmodus": + params = (4,) + + return super()._dispatch(method, params) + + def register_function(self, acl_level, function, name=None): + """Override register_function to ignore acl_level for unix sockets.""" + return super().register_function(function, name)