From ff25b4945acce9591dd5e2ee999c3ec940ba4528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Thu, 4 Aug 2022 15:55:47 +0200 Subject: [PATCH] Started MacOS Unmanaged Support --- .../net.virtualcable.udsactor.server.plist | 33 ++++ actor/macos/notes.txt | 1 + actor/src/actor_config_unmanaged.py | 33 ++-- actor/src/udsactor/client.py | 2 +- actor/src/udsactor/linux/daemon.py | 2 +- actor/src/udsactor/linux/log.py | 11 +- actor/src/udsactor/linux/operations.py | 87 ++++++++--- actor/src/udsactor/linux/renamer/alt.py | 10 +- actor/src/udsactor/linux/renamer/common.py | 3 - actor/src/udsactor/linux/renamer/debian.py | 6 +- actor/src/udsactor/linux/renamer/opensuse.py | 6 +- actor/src/udsactor/linux/renamer/redhat.py | 6 +- actor/src/udsactor/linux/service.py | 2 +- actor/src/udsactor/linux/store.py | 30 +++- actor/src/udsactor/linux/xss.py | 45 ++++-- actor/src/udsactor/log.py | 4 +- actor/src/udsactor/macos/__init__.py | 31 ++++ actor/src/udsactor/macos/log.py | 76 +++++++++ actor/src/udsactor/macos/operations.py | 146 ++++++++++++++++++ actor/src/udsactor/macos/runner.py | 70 +++++++++ actor/src/udsactor/macos/service.py | 109 +++++++++++++ actor/src/udsactor/macos/store.py | 106 +++++++++++++ actor/src/udsactor/platform.py | 10 +- actor/src/udsactor/rest.py | 6 +- actor/src/udsactor/service.py | 8 +- actor/src/udsactor/tools.py | 2 +- actor/src/udsactor/windows/__init__.py | 4 +- actor/src/udsactor/windows/log.py | 4 +- actor/src/udsactor/windows/operations.py | 4 +- actor/src/udsactor/windows/runner.py | 4 +- 30 files changed, 756 insertions(+), 105 deletions(-) create mode 100644 actor/macos/net.virtualcable.udsactor.server.plist create mode 100644 actor/macos/notes.txt create mode 100644 actor/src/udsactor/macos/__init__.py create mode 100644 actor/src/udsactor/macos/log.py create mode 100644 actor/src/udsactor/macos/operations.py create mode 100644 actor/src/udsactor/macos/runner.py create mode 100644 actor/src/udsactor/macos/service.py create mode 100644 actor/src/udsactor/macos/store.py diff --git a/actor/macos/net.virtualcable.udsactor.server.plist b/actor/macos/net.virtualcable.udsactor.server.plist new file mode 100644 index 00000000..e4c6c240 --- /dev/null +++ b/actor/macos/net.virtualcable.udsactor.server.plist @@ -0,0 +1,33 @@ + + + + + Label + net.virtualcable.udsactor.server + + KeepAlive + + SuccessfulExit + + + + ProgramArguments + + /Applications/UDSActor.app/Contents/MacOS/udsactor + start + + + RunAtLoad + + + StandardErrorPath + /var/log/udsactor.log + + StandardOutPath + /var/log/nxserver.log + + WorkingDirectory + /Applications/UDSActor.app/Contents/Resources/ + + + diff --git a/actor/macos/notes.txt b/actor/macos/notes.txt new file mode 100644 index 00000000..88a19be4 --- /dev/null +++ b/actor/macos/notes.txt @@ -0,0 +1 @@ +service file (net.virtualcable.udsactor.server.plist) goes in /Library/LaunchDaemons diff --git a/actor/src/actor_config_unmanaged.py b/actor/src/actor_config_unmanaged.py index 0f986689..211ce674 100755 --- a/actor/src/actor_config_unmanaged.py +++ b/actor/src/actor_config_unmanaged.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # -# Copyright (c) 2020 Virtual Cable S.L. +# Copyright (c) 2020-2022 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -12,7 +12,7 @@ # * 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 +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # @@ -32,7 +32,7 @@ # pylint: disable=invalid-name import sys import os -import pickle +import pickle # nosec: B403 import logging import typing @@ -102,7 +102,7 @@ class UDSConfigDialog(QDialog): self, 'UDS Test', 'Service token seems to be invalid . Please, check token validity.', - QMessageBox.Ok, + QMessageBox.Ok, # type: ignore ) else: QMessageBox.information( @@ -111,14 +111,14 @@ class UDSConfigDialog(QDialog): 'Configuration for {} seems to be correct.'.format( self._config.host ), - QMessageBox.Ok, + QMessageBox.Ok, # type: ignore ) except Exception: QMessageBox.information( self, 'UDS Test', 'Configured host {} seems to be inaccesible.'.format(self._config.host), - QMessageBox.Ok, + QMessageBox.Ok, # type: ignore ) def saveConfig(self) -> None: @@ -134,7 +134,7 @@ class UDSConfigDialog(QDialog): self, 'Invalid subnet', 'Invalid subnet {}. Please, check it.'.format(restrictNet), - QMessageBox.Ok, + QMessageBox.Ok, # type: ignore ) return @@ -153,12 +153,15 @@ class UDSConfigDialog(QDialog): self.ui.testButton.setEnabled(True) # Informs the user QMessageBox.information( - self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok + self, + 'UDS Configuration', + 'Configuration saved.', + QMessageBox.Ok, # type: ignore ) if __name__ == "__main__": - # If to be run as "sudo" on linux, we will need this to avoid problems + # If run as "sudo" on linux, we will need this to avoid problems if 'linux' in sys.platform: os.environ['QT_X11_NO_MITSHM'] = '1' @@ -171,16 +174,18 @@ if __name__ == "__main__": if len(sys.argv) > 2: if sys.argv[1] == 'export': try: - with open(sys.argv[2], 'wb') as f: - pickle.dump(udsactor.platform.store.readConfig(), f, protocol=3) + with open(sys.argv[2], 'wb') as export_: + pickle.dump( + udsactor.platform.store.readConfig(), export_, protocol=3 + ) except Exception as e: print('Error exporting configuration file: {}'.format(e)) sys.exit(1) sys.exit(0) - if sys.argv[1] == 'import': + elif sys.argv[1] == 'import': try: - with open(sys.argv[2], 'rb') as f: - config = pickle.load(f) + with open(sys.argv[2], 'rb') as import_: + config = pickle.load(import_) # nosec: B301: the file is provided by user, so it's not a security issue udsactor.platform.store.writeConfig(config) except Exception as e: print('Error importing configuration file: {}'.format(e)) diff --git a/actor/src/udsactor/client.py b/actor/src/udsactor/client.py index 2678a57b..700bb99f 100644 --- a/actor/src/udsactor/client.py +++ b/actor/src/udsactor/client.py @@ -235,7 +235,7 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att pixmap: 'QPixmap' = self._qApp.primaryScreen().grabWindow(0) # type: ignore ba = QByteArray() buffer = QBuffer(ba) - buffer.open(QIODevice.WriteOnly) + buffer.open(QIODevice.WriteOnly) # type: ignore pixmap.save(buffer, 'PNG') buffer.close() scrBase64 = bytes(ba.toBase64()).decode() # type: ignore # there are problems with Pylance and connects on PyQt5... :) diff --git a/actor/src/udsactor/linux/daemon.py b/actor/src/udsactor/linux/daemon.py index 2af43080..99569fa8 100644 --- a/actor/src/udsactor/linux/daemon.py +++ b/actor/src/udsactor/linux/daemon.py @@ -101,7 +101,7 @@ class Daemon: def removePidFile(self) -> None: try: os.remove(self.pidfile) - except Exception: + except Exception: # nosec: Not interested in exception # Not found/not permissions or whatever, ignore it pass diff --git a/actor/src/udsactor/linux/log.py b/actor/src/udsactor/linux/log.py index e70d3688..e458b81c 100644 --- a/actor/src/udsactor/linux/log.py +++ b/actor/src/udsactor/linux/log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2014-2019 Virtual Cable S.L. +# Copyright (c) 2014-2022 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ # * 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 +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # @@ -35,8 +35,8 @@ import logging import typing class LocalLogger: # pylint: disable=too-few-public-methods - linux = False - windows = True + linux = True + windows = False serviceLogger = False logger: typing.Optional[logging.Logger] @@ -59,7 +59,8 @@ class LocalLogger: # pylint: disable=too-few-public-methods self.logger = logging.getLogger('udsactor') os.chmod(fname, 0o0600) return - except Exception: + except Exception: # nosec: B110: we don't care about exceptions here + # Ignore and try next pass # Logger can't be set diff --git a/actor/src/udsactor/linux/operations.py b/actor/src/udsactor/linux/operations.py index c39d2b08..a26d036f 100644 --- a/actor/src/udsactor/linux/operations.py +++ b/actor/src/udsactor/linux/operations.py @@ -34,7 +34,7 @@ import platform import socket import fcntl # Only available on Linux. Expect complains if edited from windows import os -import subprocess +import subprocess # nosec import struct import array import typing @@ -53,7 +53,9 @@ def _getMacAddr(ifname: str) -> typing.Optional[str]: ifnameBytes = ifname.encode('utf-8') try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifnameBytes[:15]))) + info = bytearray( + fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifnameBytes[:15])) + ) return str(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper() except Exception: return None @@ -67,11 +69,15 @@ def _getIpAddr(ifname: str) -> typing.Optional[str]: ifnameBytes = ifname.encode('utf-8') try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - return str(socket.inet_ntoa(fcntl.ioctl( - s.fileno(), - 0x8915, # SIOCGIFADDR - struct.pack(str('256s'), ifnameBytes[:15]) - )[20:24])) + return str( + socket.inet_ntoa( + fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack(str('256s'), ifnameBytes[:15]), + )[20:24] + ) + ) except Exception: return None @@ -91,22 +97,32 @@ def _getInterfaces() -> typing.List[str]: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) names = array.array(str('B'), b'\0' * space) - outbytes = struct.unpack('iL', fcntl.ioctl( - s.fileno(), - 0x8912, # SIOCGIFCONF - struct.pack('iL', space, names.buffer_info()[0]) - ))[0] + outbytes = struct.unpack( + 'iL', + fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack('iL', space, names.buffer_info()[0]), + ), + )[0] namestr = names.tobytes() # return namestr, outbytes - return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)] + return [ + namestr[i : i + offset].split(b'\0', 1)[0].decode('utf-8') + for i in range(0, outbytes, length) + ] -def _getIpAndMac(ifname: str) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]: +def _getIpAndMac( + ifname: str, +) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]: ip, mac = _getIpAddr(ifname), _getMacAddr(ifname) return (ip, mac) + def checkPermissions() -> bool: - return os.getuid() == 0 # getuid only available on linux. Expect "complaioins" if edited from Windows + return os.getuid() == 0 + def getComputerName() -> str: ''' @@ -114,15 +130,23 @@ def getComputerName() -> str: ''' return socket.gethostname().split('.')[0] + def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]: for ifname in _getInterfaces(): ip, mac = _getIpAndMac(ifname) - if mac != '00:00:00:00:00:00' and mac and ip and ip.startswith('169.254') is False: # Skips local interfaces & interfaces with no dhcp IPs + if ( + mac != '00:00:00:00:00:00' + and mac + and ip + and ip.startswith('169.254') is False + ): # Skips local interfaces & interfaces with no dhcp IPs yield types.InterfaceInfoType(name=ifname, mac=mac, ip=ip) + def getDomainName() -> str: return '' + def getLinuxOs() -> str: try: with open('/etc/os-release', 'r') as f: @@ -133,18 +157,19 @@ def getLinuxOs() -> str: except Exception: return 'unknown' + def reboot(flags: int = 0): ''' Simple reboot using os command ''' - subprocess.call(['/sbin/shutdown', 'now', '-r']) + subprocess.call(['/sbin/shutdown', 'now', '-r']) # nosec: Fine, all under control def loggoff() -> None: ''' Right now restarts the machine... ''' - subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']]) + subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']]) # nosec: Fine, all under control # subprocess.call(['/sbin/shutdown', 'now', '-r']) # subprocess.call(['/usr/bin/systemctl', 'reboot', '-i']) @@ -158,7 +183,9 @@ def renameComputer(newName: str) -> bool: return True # Always reboot right now. Not much slower but much more convenient -def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False): +def joinDomain( + domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False +): pass @@ -166,7 +193,11 @@ def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None: ''' Simple password change for user using command line ''' - os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword)) + + subprocess.run( # nosec: Fine, all under control + 'echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword), + shell=True, + ) def initIdleDuration(atLeastSeconds: int) -> None: @@ -183,14 +214,20 @@ def getCurrentUser() -> str: ''' return os.environ['USER'] + def getSessionType() -> str: ''' - Known values: - * Unknown -> No XDG_SESSION_TYPE environment variable - * xrdp --> xrdp session - * other types + Known values: + * Unknown -> No XDG_SESSION_TYPE environment variable + * xrdp --> xrdp session + * other types ''' - return 'xrdp' if 'XRDP_SESSION' in os.environ else os.environ.get('XDG_SESSION_TYPE', 'unknown') + return ( + 'xrdp' + if 'XRDP_SESSION' in os.environ + else os.environ.get('XDG_SESSION_TYPE', 'unknown') + ) + def forceTimeSync() -> None: return diff --git a/actor/src/udsactor/linux/renamer/alt.py b/actor/src/udsactor/linux/renamer/alt.py index 25f02757..fcd93a38 100644 --- a/actor/src/udsactor/linux/renamer/alt.py +++ b/actor/src/udsactor/linux/renamer/alt.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2014-2019 Virtual Cable S.L. +# Copyright (c) 2014-2022 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ # * 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 +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # @@ -28,7 +28,7 @@ ''' @author: Alexey Shabalin, shaba at altlinux dot org ''' -import os +import subprocess # nosec from .common import renamers from ...log import logger @@ -46,8 +46,8 @@ def rename(newName: str) -> bool: hostname.write(newName) # Force system new name - os.system('/bin/hostname {}'.format(newName)) - os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName)) + subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: subprocess + subprocess.run(['/bin/hostname', newName]) # nosec: subprocess # add name to "hosts" with open('/etc/hosts', 'r') as hosts: diff --git a/actor/src/udsactor/linux/renamer/common.py b/actor/src/udsactor/linux/renamer/common.py index cf8d4419..7d67beaa 100644 --- a/actor/src/udsactor/linux/renamer/common.py +++ b/actor/src/udsactor/linux/renamer/common.py @@ -29,9 +29,6 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -import os -import sys -import pkgutil import typing from .. import operations diff --git a/actor/src/udsactor/linux/renamer/debian.py b/actor/src/udsactor/linux/renamer/debian.py index 7c198d03..c88d54d1 100644 --- a/actor/src/udsactor/linux/renamer/debian.py +++ b/actor/src/udsactor/linux/renamer/debian.py @@ -29,7 +29,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -import os +import subprocess # nosec from .common import renamers from ...log import logger @@ -45,8 +45,8 @@ def rename(newName: str) -> bool: hostname.write(newName) # Force system new name - os.system('/bin/hostname {}'.format(newName)) - os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName)) + subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root + subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root # add name to "hosts" with open('/etc/hosts', 'r') as hosts: diff --git a/actor/src/udsactor/linux/renamer/opensuse.py b/actor/src/udsactor/linux/renamer/opensuse.py index 63efd82d..9574abc9 100644 --- a/actor/src/udsactor/linux/renamer/opensuse.py +++ b/actor/src/udsactor/linux/renamer/opensuse.py @@ -28,7 +28,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -import os +import subprocess # nosec from .common import renamers from ...log import logger @@ -46,8 +46,8 @@ def rename(newName: str) -> bool: hostname.write(newName) # Force system new name - os.system('/bin/hostname {}'.format(newName)) - os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName)) + subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root + subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root # add name to "hosts" with open('/etc/hosts', 'r') as hosts: diff --git a/actor/src/udsactor/linux/renamer/redhat.py b/actor/src/udsactor/linux/renamer/redhat.py index 3ad1ac81..73263aab 100644 --- a/actor/src/udsactor/linux/renamer/redhat.py +++ b/actor/src/udsactor/linux/renamer/redhat.py @@ -28,7 +28,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -import os +import subprocess # nosec from .common import renamers from ...log import logger @@ -46,8 +46,8 @@ def rename(newName: str) -> bool: hostname.write(newName) # Force system new name - os.system('/bin/hostname {}'.format(newName)) - os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName)) + subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root + subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root # add name to "hosts" with open('/etc/hosts', 'r') as hosts: diff --git a/actor/src/udsactor/linux/service.py b/actor/src/udsactor/linux/service.py index 835bfc94..2d345bba 100644 --- a/actor/src/udsactor/linux/service.py +++ b/actor/src/udsactor/linux/service.py @@ -37,7 +37,7 @@ from ..log import logger from ..service import CommonService try: - from prctl import set_proctitle # @UnresolvedImport + from prctl import set_proctitle # type: ignore except ImportError: # Platform may not include prctl, so in case it's not available, we let the "name" as is def set_proctitle(_): pass diff --git a/actor/src/udsactor/linux/store.py b/actor/src/udsactor/linux/store.py index 869475e3..43a6d89f 100644 --- a/actor/src/udsactor/linux/store.py +++ b/actor/src/udsactor/linux/store.py @@ -32,12 +32,13 @@ import os import configparser import base64 -import pickle +import pickle # nosec from .. import types CONFIGFILE = '/etc/udsactor/udsactor.cfg' + def readConfig() -> types.ActorConfigurationType: try: cfg = configparser.ConfigParser() @@ -45,10 +46,22 @@ def readConfig() -> types.ActorConfigurationType: uds: configparser.SectionProxy = cfg['uds'] # Extract data: base64Config = uds.get('config', None) - config = pickle.loads(base64.b64decode(base64Config.encode())) if base64Config else None + config = ( + pickle.loads( # nosec: file is restricted + base64.b64decode(base64Config.encode()) + ) + if base64Config + else None + ) base64Data = uds.get('data', None) - data = pickle.loads(base64.b64decode(base64Data.encode())) if base64Data else None + data = ( + pickle.loads( # nosec: file is restricted + base64.b64decode(base64Data.encode()) + ) + if base64Data + else None + ) return types.ActorConfigurationType( actorType=uds.get('type', types.MANAGED), @@ -62,20 +75,23 @@ def readConfig() -> types.ActorConfigurationType: post_command=uds.get('post_command', None), log_level=int(uds.get('log_level', '2')), config=config, - data=data + data=data, ) except Exception: return types.ActorConfigurationType('', False) + def writeConfig(config: types.ActorConfigurationType) -> None: cfg = configparser.ConfigParser() cfg.add_section('uds') uds: configparser.SectionProxy = cfg['uds'] uds['host'] = config.host uds['validate'] = 'yes' if config.validateCertificate else 'no' + def writeIfValue(val, name): if val: uds[name] = val + writeIfValue(config.actorType, 'type') writeIfValue(config.master_token, 'master_token') writeIfValue(config.own_token, 'own_token') @@ -93,15 +109,19 @@ def writeConfig(config: types.ActorConfigurationType) -> None: # Ensures exists destination folder dirname = os.path.dirname(CONFIGFILE) if not os.path.exists(dirname): - os.mkdir(dirname, mode=0o700) # Will create only if route to path already exists, for example, /etc (that must... :-)) + os.mkdir( + dirname, mode=0o700 + ) # Will create only if route to path already exists, for example, /etc (that must... :-)) with open(CONFIGFILE, 'w') as f: cfg.write(f) os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root + def useOldJoinSystem() -> bool: return False + def invokeScriptOnLogin() -> str: return '' diff --git a/actor/src/udsactor/linux/xss.py b/actor/src/udsactor/linux/xss.py index 9b97d8d2..aba0f08c 100644 --- a/actor/src/udsactor/linux/xss.py +++ b/actor/src/udsactor/linux/xss.py @@ -31,7 +31,7 @@ # pylint: disable=invalid-name import ctypes import ctypes.util -import subprocess +import subprocess # nosec xlib = None xss = None @@ -39,17 +39,22 @@ display = None xssInfo = None initialized = False + class XScreenSaverInfo(ctypes.Structure): # pylint: disable=too-few-public-methods - _fields_ = [('window', ctypes.c_long), - ('state', ctypes.c_int), - ('kind', ctypes.c_int), - ('til_or_since', ctypes.c_ulong), - ('idle', ctypes.c_ulong), - ('eventMask', ctypes.c_ulong)] + _fields_ = [ + ('window', ctypes.c_long), + ('state', ctypes.c_int), + ('kind', ctypes.c_int), + ('til_or_since', ctypes.c_ulong), + ('idle', ctypes.c_ulong), + ('eventMask', ctypes.c_ulong), + ] + class c_ptr(ctypes.c_void_p): pass + def _ensureInitialized(): global xlib, xss, xssInfo, display, initialized # pylint: disable=global-statement @@ -73,13 +78,15 @@ def _ensureInitialized(): xss.XScreenSaverQueryExtension.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_int), - ctypes.POINTER(ctypes.c_int) + ctypes.POINTER(ctypes.c_int), ] - xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) # Result in a XScreenSaverInfo structure + xss.XScreenSaverAllocInfo.restype = ctypes.POINTER( + XScreenSaverInfo + ) # Result in a XScreenSaverInfo structure xss.XScreenSaverQueryInfo.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, - ctypes.POINTER(XScreenSaverInfo) + ctypes.POINTER(XScreenSaverInfo), ] xlib.XOpenDisplay.argtypes = [ctypes.c_char_p] xlib.XOpenDisplay.restype = c_ptr @@ -95,7 +102,9 @@ def _ensureInitialized(): event_base = ctypes.c_int() error_base = ctypes.c_int() - available = xss.XScreenSaverQueryExtension(display, ctypes.byref(event_base), ctypes.byref(error_base)) + available = xss.XScreenSaverQueryExtension( + display, ctypes.byref(event_base), ctypes.byref(error_base) + ) if available != 1: raise Exception('ScreenSaver not available') @@ -107,9 +116,11 @@ def _ensureInitialized(): def initIdleDuration(atLeastSeconds: int) -> None: _ensureInitialized() if atLeastSeconds: - subprocess.call(['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)]) + subprocess.call( # nosec, controlled params + ['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)] + ) # And now reset it - subprocess.call(['/usr/bin/xset', 's', 'reset']) + subprocess.call(['/usr/bin/xset', 's', 'reset']) # nosec: fixed command def getIdleDuration() -> float: @@ -122,7 +133,11 @@ def getIdleDuration() -> float: xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo) # States: 0 = off, 1 = On, 2 = Cycle, 3 = Disabled, ...? - if xssInfo.contents.state == 1: # state = 1 means "active", so idle is not a valid state - return 3600 * 100 * 1000 # If screen saver is active, return a high enough value + if ( + xssInfo.contents.state == 1 + ): # state = 1 means "active", so idle is not a valid state + return ( + 3600 * 100 * 1000 + ) # If screen saver is active, return a high enough value return xssInfo.contents.idle / 1000.0 diff --git a/actor/src/udsactor/log.py b/actor/src/udsactor/log.py index 8073b2b8..4edc5fd4 100644 --- a/actor/src/udsactor/log.py +++ b/actor/src/udsactor/log.py @@ -35,6 +35,8 @@ import typing if sys.platform == 'win32': from .windows.log import LocalLogger +elif sys.platform == 'darwin': + from .macos.log import LocalLogger else: from .linux.log import LocalLogger @@ -55,7 +57,7 @@ class Logger: self.logLevel = INFO self.localLogger = LocalLogger() self.remoteLogger = None - self.own_token = '' + self.own_token = '' # nosec: This is no password at all def setLevel(self, level: typing.Union[str, int]) -> None: ''' diff --git a/actor/src/udsactor/macos/__init__.py b/actor/src/udsactor/macos/__init__.py new file mode 100644 index 00000000..f29bc997 --- /dev/null +++ b/actor/src/udsactor/macos/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022 Virtual Cable S.L.U. +# 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.U. 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 +''' diff --git a/actor/src/udsactor/macos/log.py b/actor/src/udsactor/macos/log.py new file mode 100644 index 00000000..2a7ecaed --- /dev/null +++ b/actor/src/udsactor/macos/log.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014-2022 Virtual Cable S.L.U. +# 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.U. 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 +''' +# pylint: disable=invalid-name +import os +import tempfile +import logging +import typing + +# Basically, same logger as in linux, +class LocalLogger: + linux = False + windows = False + serviceLogger = False + + logger: typing.Optional[logging.Logger] + + def __init__(self) -> None: + # tempdir is different for "user application" and "service" + # service wil get c:\windows\temp, while user will get c:\users\XXX\temp + # Try to open logger at /var/log path + # If it fails (access denied normally), will try to open one at user's home folder, and if + # agaim it fails, open it at the tmpPath + for logDir in ('/var/log', os.path.expanduser('~'), tempfile.gettempdir()): + try: + fname = os.path.join(logDir, 'udsactor.log') + logging.basicConfig( + filename=fname, + filemode='a', + format='%(levelname)s %(asctime)s %(message)s', + level=logging.DEBUG + ) + self.logger = logging.getLogger('udsactor') + os.chmod(fname, 0o0600) + return + except Exception: # nosec: B110: we don't care about exceptions here + # ignore and try next one + pass + + # Logger can't be set + self.logger = None + + def log(self, level: int, message: str) -> None: + # Debug messages are logged to a file + # our loglevels are 0 (other), 10000 (debug), .... + # logging levels are 10 (debug), 20 (info) + # OTHER = logging.NOTSET + if self.logger: + self.logger.log(int(level / 1000), message) diff --git a/actor/src/udsactor/macos/operations.py b/actor/src/udsactor/macos/operations.py new file mode 100644 index 00000000..a6fc568b --- /dev/null +++ b/actor/src/udsactor/macos/operations.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014-2019 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' +@author: Adolfo Gómez, dkmaster at dkmon dot com +''' + +# Note. most methods are not implemented, as they are not needed for this platform (macos) +# that only supports unmanaged machines + +import socket +import os +import re +import subprocess # nosec +import typing + +import psutil + +from .. import types + +MACVER_RE = re.compile(r"ProductVersion\s*(.*)", re.MULTILINE) +MACVER_FILE = '/System/Library/CoreServices/SystemVersion.plist' + +def checkPermissions() -> bool: + return os.getuid() == 0 + +def getComputerName() -> str: + ''' + Returns computer name, with no domain + ''' + return socket.gethostname().split('.')[0] + +def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]: + ifdata: typing.List['psutil._common.snicaddr'] + for ifname, ifdata in psutil.net_if_addrs().items(): + name, ip, mac = '', '', '' + # Get IP address, interface name and MAC address whenever possible + for row in ifdata: + if row.family == socket.AF_INET: + ip = row.address + name = ifname + elif row.family == socket.AF_LINK: + mac = row.address + + # if all data is available, stop iterating + if ip and name and mac: + if mac != '00:00:00:00:00:00' and mac and ip and ip.startswith('169.254') is False: # Skips local interfaces & interfaces with no dhcp IPs + yield types.InterfaceInfoType(name=name, ip=ip, mac=mac) + break + + +def getDomainName() -> str: + return '' + +def getMacOs() -> str: + try: + with open(MACVER_FILE, 'r') as f: + data = f.read() + m = MACVER_RE.search(data) + if m: + return m.group(1) + except Exception: # nosec: B110: ignore exception because we are not interested in it + pass + + return 'unknown' + +def reboot(flags: int = 0): + ''' + Simple reboot using os command + ''' + subprocess.call(['/sbin/shutdown', '-r', 'now']) # nosec: Command line is fixed + + +def loggoff() -> None: + ''' + Right now restarts the machine... + ''' + subprocess.run("/bin/launchctl bootout gui/$(id -u $USER)", shell=True) # nosec: Command line is fixed + # Ignores output, as it may fail if user is not logged in + + +def renameComputer(newName: str) -> bool: + ''' + Changes the computer name + Returns True if reboot needed + ''' + return False + + +def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False): + pass + + +def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None: + pass + + +def initIdleDuration(atLeastSeconds: int) -> None: + pass + + +def getIdleDuration() -> float: + return 0 + + +def getCurrentUser() -> str: + ''' + Returns current logged in user + ''' + return os.environ['USER'] + +def getSessionType() -> str: + ''' + Known values: + * Unknown -> No XDG_SESSION_TYPE environment variable + * xrdp --> xrdp session + * other types + ''' + return 'macos' + +def forceTimeSync() -> None: + return diff --git a/actor/src/udsactor/macos/runner.py b/actor/src/udsactor/macos/runner.py new file mode 100644 index 00000000..e201f479 --- /dev/null +++ b/actor/src/udsactor/macos/runner.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014-2020 Virtual Cable S.L.U. +# 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 +''' +import sys + +from .. import rest +from .. import platform +from ..log import logger +from .service import UDSActorSvc + +def usage(): + sys.stderr.write('usage: udsactor start|login "username"|logout "username"\n') + sys.exit(2) + +def run() -> None: + logger.setLevel(20000) + + if len(sys.argv) == 3 and sys.argv[1] in ('login', 'logout'): + logger.debug('Running client udsactor') + try: + client: rest.UDSClientApi = rest.UDSClientApi() + if sys.argv[1] == 'login': + r = client.login(sys.argv[2], platform.operations.getSessionType()) + print('{},{},{},{}\n'.format(r.ip, r.hostname, r.max_idle, r.dead_line or '')) + elif sys.argv[1] == 'logout': + client.logout(sys.argv[2]) + except Exception as e: + logger.exception() + logger.error('Got exception while processing command: %s', e) + sys.exit(0) + elif len(sys.argv) != 2: + usage() + + daemonSvr = UDSActorSvc() + if len(sys.argv) == 2: + # Daemon mode... + if sys.argv[1] in ('start', 'start-foreground'): + daemonSvr.run() # execute in foreground + else: + usage() + sys.exit(0) + else: + usage() diff --git a/actor/src/udsactor/macos/service.py b/actor/src/udsactor/macos/service.py new file mode 100644 index 00000000..a8a816e1 --- /dev/null +++ b/actor/src/udsactor/macos/service.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022 Virtual Cable S.L.U. +# 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.U. 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 +''' +import typing +import signal + +from ..log import logger +from ..service import CommonService + + +class UDSActorSvc(CommonService): + def __init__(self) -> None: + CommonService.__init__(self) + + # Captures signals so we can stop gracefully + signal.signal(signal.SIGINT, self.markForExit) + signal.signal(signal.SIGTERM, self.markForExit) + + def markForExit(self, signum, frame) -> None: # pylint: disable=unused-argument + self._isAlive = False + + def joinDomain( # pylint: disable=unused-argument, too-many-arguments + self, + name: str, + domain: str, + ou: str, + account: str, + password: str + ) -> None: + pass # Not implemented for unmanaged machines + + def rename( + self, + name: str, + userName: typing.Optional[str] = None, + oldPassword: typing.Optional[str] = None, + newPassword: typing.Optional[str] = None, + ) -> None: + pass # Not implemented for unmanaged machines + + def run(self) -> None: + logger.debug('Running Daemon: {}'.format(self._isAlive)) + + # Linux daemon will continue running unless something is requested to + # Unmanaged services does not initializes "on start", but rather when user logs in (because userservice does not exists "as such" before that) + if self.isManaged(): # Currently, managed is not implemented for UDS on M + logger.error('Managed machines not supported on MacOS') + # Wait a bit, this is mac os and will be run by launchd + # If the daemon shuts down too quickly, launchd may think it is a crash. + self.doWait(10000) + + self.finish() + return # Stop daemon if initializes told to do so + else: + if not self.initializeUnmanaged(): + # Wait a bit, this is mac os and will be run by launchd + # If the daemon shuts down too quickly, launchd may think it is a crash. + self.doWait(10000) + self.finish() + return + + # Start listening for petitions + self.startHttpServer() + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + counter = 0 + while self._isAlive: + counter += 1 + try: + if counter % 5 == 0: + self.loop() + except Exception as e: + logger.error('Got exception on main loop: %s', e) + # In milliseconds, will break + self.doWait(1000) + + self.finish() diff --git a/actor/src/udsactor/macos/store.py b/actor/src/udsactor/macos/store.py new file mode 100644 index 00000000..e8290d6b --- /dev/null +++ b/actor/src/udsactor/macos/store.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014-2019 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' +@author: Adolfo Gómez, dkmaster at dkmon dot com +''' +import os +import configparser +import base64 +import pickle # nosec + +from .. import types + +CONFIGFILE = '/etc/udsactor/udsactor.cfg' + +def readConfig() -> types.ActorConfigurationType: + try: + cfg = configparser.ConfigParser() + cfg.read(CONFIGFILE) + uds: configparser.SectionProxy = cfg['uds'] + # Extract data: + base64Config = uds.get('config', None) + config = pickle.loads(base64.b64decode(base64Config.encode())) if base64Config else None # nosec: Read from root controled file, secure + + base64Data = uds.get('data', None) + data = pickle.loads(base64.b64decode(base64Data.encode())) if base64Data else None # nosec: Read from root controled file, secure + + return types.ActorConfigurationType( + actorType=uds.get('type', types.MANAGED), + host=uds.get('host', ''), + validateCertificate=uds.getboolean('validate', fallback=False), + master_token=uds.get('master_token', None), + own_token=uds.get('own_token', None), + restrict_net=uds.get('restrict_net', None), + pre_command=uds.get('pre_command', None), + runonce_command=uds.get('runonce_command', None), + post_command=uds.get('post_command', None), + log_level=int(uds.get('log_level', '2')), + config=config, + data=data + ) + except Exception: + return types.ActorConfigurationType('', False) + +def writeConfig(config: types.ActorConfigurationType) -> None: + cfg = configparser.ConfigParser() + cfg.add_section('uds') + uds: configparser.SectionProxy = cfg['uds'] + uds['host'] = config.host + uds['validate'] = 'yes' if config.validateCertificate else 'no' + def writeIfValue(val, name): + if val: + uds[name] = val + writeIfValue(config.actorType, 'type') + writeIfValue(config.master_token, 'master_token') + writeIfValue(config.own_token, 'own_token') + writeIfValue(config.restrict_net, 'restrict_net') + writeIfValue(config.pre_command, 'pre_command') + writeIfValue(config.post_command, 'post_command') + writeIfValue(config.runonce_command, 'runonce_command') + uds['log_level'] = str(config.log_level) + if config.config: # Special case, encoded & dumped + uds['config'] = base64.b64encode(pickle.dumps(config.config)).decode() + + if config.data: # Special case, encoded & dumped + uds['data'] = base64.b64encode(pickle.dumps(config.data)).decode() + + # Ensures exists destination folder + dirname = os.path.dirname(CONFIGFILE) + if not os.path.exists(dirname): + os.mkdir(dirname, mode=0o700) # Will create only if route to path already exists, for example, /etc (that must... :-)) + + with open(CONFIGFILE, 'w') as f: + cfg.write(f) + + os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root + +def useOldJoinSystem() -> bool: + return False + +def invokeScriptOnLogin() -> str: + return '' diff --git a/actor/src/udsactor/platform.py b/actor/src/udsactor/platform.py index ebd3f5a7..2c8591bf 100644 --- a/actor/src/udsactor/platform.py +++ b/actor/src/udsactor/platform.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2014 Virtual Cable S.L. +# Copyright (c) 2014-2022 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ # * 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 +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # @@ -32,6 +32,8 @@ import sys name = sys.platform if sys.platform == 'win32': - from .windows import operations, store # pylint: disable=unused-import + from .windows import operations, store +elif sys.platform == 'darwin': + from .macos import operations, store else: - from .linux import operations, store # pylint: disable=unused-import + from .linux import operations, store diff --git a/actor/src/udsactor/rest.py b/actor/src/udsactor/rest.py index 04abdb5f..37e50076 100644 --- a/actor/src/udsactor/rest.py +++ b/actor/src/udsactor/rest.py @@ -103,7 +103,7 @@ class UDSApi: # pylint: disable=too-few-public-methods logging.getLogger('urllib3').setLevel(logging.ERROR) try: warnings.simplefilter('ignore') # Disables all warnings - except Exception: + except Exception: # nosec: not interested in exceptions pass @property @@ -178,7 +178,7 @@ class UDSServerApi(UDSApi): priority=v['priority'], isCustom=v['isCustom'], ) - except Exception: + except Exception: # nosec: not interested in exceptions pass def register( # pylint: disable=too-many-arguments, too-many-locals @@ -329,7 +329,7 @@ class UDSServerApi(UDSApi): ) -> types.LoginResultInfoType: if not token: return types.LoginResultInfoType( - ip='0.0.0.0', hostname=UNKNOWN, dead_line=None, max_idle=None + ip='0.0.0.0', hostname=UNKNOWN, dead_line=None, max_idle=None # nosec: this is not a binding ) payload = { 'type': actor_type or types.MANAGED, diff --git a/actor/src/udsactor/service.py b/actor/src/udsactor/service.py index 71f884e2..11e93276 100644 --- a/actor/src/udsactor/service.py +++ b/actor/src/udsactor/service.py @@ -101,7 +101,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes # 0 = OTHER, 10000 = DEBUG, 20000 = WARN, 30000 = INFO, 40000 = ERROR, 50000 = FATAL # So this comes: logger.setLevel([DEBUG, INFO, ERROR, FATAL][self._cfg.log_level]) - # If windows, enable service logger + # If windows, enable service logger FOR SERVICE only logger.enableServiceLogger() socket.setdefaulttimeout(20) @@ -538,9 +538,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes if not self.isManaged(): self.uninitialize() - # **************************************** - # Methods that CAN BE overriden by actors - # **************************************** + # ****************************************************** + # Methods that CAN BE overriden by specific OS Actor + # ****************************************************** def doWait(self, miliseconds: int) -> None: ''' Invoked to wait a bit diff --git a/actor/src/udsactor/tools.py b/actor/src/udsactor/tools.py index a39a39e7..18502d45 100644 --- a/actor/src/udsactor/tools.py +++ b/actor/src/udsactor/tools.py @@ -47,7 +47,7 @@ class ScriptExecutorThread(threading.Thread): try: logger.debug('Executing script: {}'.format(self.script)) - exec(self.script, globals(), None) # pylint: disable=exec-used + exec(self.script, globals(), None) # nosec: exec is fine, it's a "trusted" script except Exception as e: logger.error('Error executing script: {}'.format(e)) logger.exception() diff --git a/actor/src/udsactor/windows/__init__.py b/actor/src/udsactor/windows/__init__.py index 4db3bd57..2a7d5e6c 100644 --- a/actor/src/udsactor/windows/__init__.py +++ b/actor/src/udsactor/windows/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2014-2019 Virtual Cable S.L. +# Copyright (c) 2014-2022 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ # * 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 +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # diff --git a/actor/src/udsactor/windows/log.py b/actor/src/udsactor/windows/log.py index 263fb985..eb4db669 100644 --- a/actor/src/udsactor/windows/log.py +++ b/actor/src/udsactor/windows/log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2014 Virtual Cable S.L. +# Copyright (c) 2014-2022 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ # * 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 +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # diff --git a/actor/src/udsactor/windows/operations.py b/actor/src/udsactor/windows/operations.py index 3b685d25..9067ee0a 100644 --- a/actor/src/udsactor/windows/operations.py +++ b/actor/src/udsactor/windows/operations.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2014-2019 Virtual Cable S.L. +# Copyright (c) 2014-2022 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ # * 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 +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # diff --git a/actor/src/udsactor/windows/runner.py b/actor/src/udsactor/windows/runner.py index 6973b784..a2aa8512 100644 --- a/actor/src/udsactor/windows/runner.py +++ b/actor/src/udsactor/windows/runner.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2019 Virtual Cable S.L. +# Copyright (c) 2019-2022 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ # * 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 +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. #