Started MacOS Unmanaged Support

This commit is contained in:
Adolfo Gómez García 2022-08-04 15:55:47 +02:00
parent 552ba3796b
commit ff25b4945a
30 changed files with 756 additions and 105 deletions

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.virtualcable.udsactor.server</string>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>ProgramArguments</key>
<array>
<string>/Applications/UDSActor.app/Contents/MacOS/udsactor</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/var/log/udsactor.log</string>
<key>StandardOutPath</key>
<string>/var/log/nxserver.log</string>
<key>WorkingDirectory</key>
<string>/Applications/UDSActor.app/Contents/Resources/</string>
</dict>
</plist>

1
actor/macos/notes.txt Normal file
View File

@ -0,0 +1 @@
service file (net.virtualcable.udsactor.server.plist) goes in /Library/LaunchDaemons

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2020 Virtual Cable S.L. # Copyright (c) 2020-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #
@ -32,7 +32,7 @@
# pylint: disable=invalid-name # pylint: disable=invalid-name
import sys import sys
import os import os
import pickle import pickle # nosec: B403
import logging import logging
import typing import typing
@ -102,7 +102,7 @@ class UDSConfigDialog(QDialog):
self, self,
'UDS Test', 'UDS Test',
'Service token seems to be invalid . Please, check token validity.', 'Service token seems to be invalid . Please, check token validity.',
QMessageBox.Ok, QMessageBox.Ok, # type: ignore
) )
else: else:
QMessageBox.information( QMessageBox.information(
@ -111,14 +111,14 @@ class UDSConfigDialog(QDialog):
'Configuration for {} seems to be correct.'.format( 'Configuration for {} seems to be correct.'.format(
self._config.host self._config.host
), ),
QMessageBox.Ok, QMessageBox.Ok, # type: ignore
) )
except Exception: except Exception:
QMessageBox.information( QMessageBox.information(
self, self,
'UDS Test', 'UDS Test',
'Configured host {} seems to be inaccesible.'.format(self._config.host), 'Configured host {} seems to be inaccesible.'.format(self._config.host),
QMessageBox.Ok, QMessageBox.Ok, # type: ignore
) )
def saveConfig(self) -> None: def saveConfig(self) -> None:
@ -134,7 +134,7 @@ class UDSConfigDialog(QDialog):
self, self,
'Invalid subnet', 'Invalid subnet',
'Invalid subnet {}. Please, check it.'.format(restrictNet), 'Invalid subnet {}. Please, check it.'.format(restrictNet),
QMessageBox.Ok, QMessageBox.Ok, # type: ignore
) )
return return
@ -153,12 +153,15 @@ class UDSConfigDialog(QDialog):
self.ui.testButton.setEnabled(True) self.ui.testButton.setEnabled(True)
# Informs the user # Informs the user
QMessageBox.information( QMessageBox.information(
self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok self,
'UDS Configuration',
'Configuration saved.',
QMessageBox.Ok, # type: ignore
) )
if __name__ == "__main__": 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: if 'linux' in sys.platform:
os.environ['QT_X11_NO_MITSHM'] = '1' os.environ['QT_X11_NO_MITSHM'] = '1'
@ -171,16 +174,18 @@ if __name__ == "__main__":
if len(sys.argv) > 2: if len(sys.argv) > 2:
if sys.argv[1] == 'export': if sys.argv[1] == 'export':
try: try:
with open(sys.argv[2], 'wb') as f: with open(sys.argv[2], 'wb') as export_:
pickle.dump(udsactor.platform.store.readConfig(), f, protocol=3) pickle.dump(
udsactor.platform.store.readConfig(), export_, protocol=3
)
except Exception as e: except Exception as e:
print('Error exporting configuration file: {}'.format(e)) print('Error exporting configuration file: {}'.format(e))
sys.exit(1) sys.exit(1)
sys.exit(0) sys.exit(0)
if sys.argv[1] == 'import': elif sys.argv[1] == 'import':
try: try:
with open(sys.argv[2], 'rb') as f: with open(sys.argv[2], 'rb') as import_:
config = pickle.load(f) config = pickle.load(import_) # nosec: B301: the file is provided by user, so it's not a security issue
udsactor.platform.store.writeConfig(config) udsactor.platform.store.writeConfig(config)
except Exception as e: except Exception as e:
print('Error importing configuration file: {}'.format(e)) print('Error importing configuration file: {}'.format(e))

View File

@ -235,7 +235,7 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
pixmap: 'QPixmap' = self._qApp.primaryScreen().grabWindow(0) # type: ignore pixmap: 'QPixmap' = self._qApp.primaryScreen().grabWindow(0) # type: ignore
ba = QByteArray() ba = QByteArray()
buffer = QBuffer(ba) buffer = QBuffer(ba)
buffer.open(QIODevice.WriteOnly) buffer.open(QIODevice.WriteOnly) # type: ignore
pixmap.save(buffer, 'PNG') pixmap.save(buffer, 'PNG')
buffer.close() buffer.close()
scrBase64 = bytes(ba.toBase64()).decode() # type: ignore # there are problems with Pylance and connects on PyQt5... :) scrBase64 = bytes(ba.toBase64()).decode() # type: ignore # there are problems with Pylance and connects on PyQt5... :)

View File

@ -101,7 +101,7 @@ class Daemon:
def removePidFile(self) -> None: def removePidFile(self) -> None:
try: try:
os.remove(self.pidfile) os.remove(self.pidfile)
except Exception: except Exception: # nosec: Not interested in exception
# Not found/not permissions or whatever, ignore it # Not found/not permissions or whatever, ignore it
pass pass

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2014-2019 Virtual Cable S.L. # Copyright (c) 2014-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #
@ -35,8 +35,8 @@ import logging
import typing import typing
class LocalLogger: # pylint: disable=too-few-public-methods class LocalLogger: # pylint: disable=too-few-public-methods
linux = False linux = True
windows = True windows = False
serviceLogger = False serviceLogger = False
logger: typing.Optional[logging.Logger] logger: typing.Optional[logging.Logger]
@ -59,7 +59,8 @@ class LocalLogger: # pylint: disable=too-few-public-methods
self.logger = logging.getLogger('udsactor') self.logger = logging.getLogger('udsactor')
os.chmod(fname, 0o0600) os.chmod(fname, 0o0600)
return return
except Exception: except Exception: # nosec: B110: we don't care about exceptions here
# Ignore and try next
pass pass
# Logger can't be set # Logger can't be set

View File

@ -34,7 +34,7 @@ import platform
import socket import socket
import fcntl # Only available on Linux. Expect complains if edited from windows import fcntl # Only available on Linux. Expect complains if edited from windows
import os import os
import subprocess import subprocess # nosec
import struct import struct
import array import array
import typing import typing
@ -53,7 +53,9 @@ def _getMacAddr(ifname: str) -> typing.Optional[str]:
ifnameBytes = ifname.encode('utf-8') ifnameBytes = ifname.encode('utf-8')
try: try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 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() return str(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
except Exception: except Exception:
return None return None
@ -67,11 +69,15 @@ def _getIpAddr(ifname: str) -> typing.Optional[str]:
ifnameBytes = ifname.encode('utf-8') ifnameBytes = ifname.encode('utf-8')
try: try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return str(socket.inet_ntoa(fcntl.ioctl( return str(
socket.inet_ntoa(
fcntl.ioctl(
s.fileno(), s.fileno(),
0x8915, # SIOCGIFADDR 0x8915, # SIOCGIFADDR
struct.pack(str('256s'), ifnameBytes[:15]) struct.pack(str('256s'), ifnameBytes[:15]),
)[20:24])) )[20:24]
)
)
except Exception: except Exception:
return None return None
@ -91,22 +97,32 @@ def _getInterfaces() -> typing.List[str]:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array.array(str('B'), b'\0' * space) names = array.array(str('B'), b'\0' * space)
outbytes = struct.unpack('iL', fcntl.ioctl( outbytes = struct.unpack(
'iL',
fcntl.ioctl(
s.fileno(), s.fileno(),
0x8912, # SIOCGIFCONF 0x8912, # SIOCGIFCONF
struct.pack('iL', space, names.buffer_info()[0]) struct.pack('iL', space, names.buffer_info()[0]),
))[0] ),
)[0]
namestr = names.tobytes() namestr = names.tobytes()
# return namestr, outbytes # 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) ip, mac = _getIpAddr(ifname), _getMacAddr(ifname)
return (ip, mac) return (ip, mac)
def checkPermissions() -> bool: 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: def getComputerName() -> str:
''' '''
@ -114,15 +130,23 @@ def getComputerName() -> str:
''' '''
return socket.gethostname().split('.')[0] return socket.gethostname().split('.')[0]
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]: def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
for ifname in _getInterfaces(): for ifname in _getInterfaces():
ip, mac = _getIpAndMac(ifname) 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) yield types.InterfaceInfoType(name=ifname, mac=mac, ip=ip)
def getDomainName() -> str: def getDomainName() -> str:
return '' return ''
def getLinuxOs() -> str: def getLinuxOs() -> str:
try: try:
with open('/etc/os-release', 'r') as f: with open('/etc/os-release', 'r') as f:
@ -133,18 +157,19 @@ def getLinuxOs() -> str:
except Exception: except Exception:
return 'unknown' return 'unknown'
def reboot(flags: int = 0): def reboot(flags: int = 0):
''' '''
Simple reboot using os command 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: def loggoff() -> None:
''' '''
Right now restarts the machine... 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(['/sbin/shutdown', 'now', '-r'])
# subprocess.call(['/usr/bin/systemctl', 'reboot', '-i']) # 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 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 pass
@ -166,7 +193,11 @@ def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
''' '''
Simple password change for user using command line 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: def initIdleDuration(atLeastSeconds: int) -> None:
@ -183,6 +214,7 @@ def getCurrentUser() -> str:
''' '''
return os.environ['USER'] return os.environ['USER']
def getSessionType() -> str: def getSessionType() -> str:
''' '''
Known values: Known values:
@ -190,7 +222,12 @@ def getSessionType() -> str:
* xrdp --> xrdp session * xrdp --> xrdp session
* other types * 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: def forceTimeSync() -> None:
return return

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2014-2019 Virtual Cable S.L. # Copyright (c) 2014-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #
@ -28,7 +28,7 @@
''' '''
@author: Alexey Shabalin, shaba at altlinux dot org @author: Alexey Shabalin, shaba at altlinux dot org
''' '''
import os import subprocess # nosec
from .common import renamers from .common import renamers
from ...log import logger from ...log import logger
@ -46,8 +46,8 @@ def rename(newName: str) -> bool:
hostname.write(newName) hostname.write(newName)
# Force system new name # Force system new name
os.system('/bin/hostname {}'.format(newName)) subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: subprocess
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName)) subprocess.run(['/bin/hostname', newName]) # nosec: subprocess
# add name to "hosts" # add name to "hosts"
with open('/etc/hosts', 'r') as hosts: with open('/etc/hosts', 'r') as hosts:

View File

@ -29,9 +29,6 @@
''' '''
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
''' '''
import os
import sys
import pkgutil
import typing import typing
from .. import operations from .. import operations

View File

@ -29,7 +29,7 @@
''' '''
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
''' '''
import os import subprocess # nosec
from .common import renamers from .common import renamers
from ...log import logger from ...log import logger
@ -45,8 +45,8 @@ def rename(newName: str) -> bool:
hostname.write(newName) hostname.write(newName)
# Force system new name # Force system new name
os.system('/bin/hostname {}'.format(newName)) subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName)) subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root
# add name to "hosts" # add name to "hosts"
with open('/etc/hosts', 'r') as hosts: with open('/etc/hosts', 'r') as hosts:

View File

@ -28,7 +28,7 @@
''' '''
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
''' '''
import os import subprocess # nosec
from .common import renamers from .common import renamers
from ...log import logger from ...log import logger
@ -46,8 +46,8 @@ def rename(newName: str) -> bool:
hostname.write(newName) hostname.write(newName)
# Force system new name # Force system new name
os.system('/bin/hostname {}'.format(newName)) subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName)) subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root
# add name to "hosts" # add name to "hosts"
with open('/etc/hosts', 'r') as hosts: with open('/etc/hosts', 'r') as hosts:

View File

@ -28,7 +28,7 @@
''' '''
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
''' '''
import os import subprocess # nosec
from .common import renamers from .common import renamers
from ...log import logger from ...log import logger
@ -46,8 +46,8 @@ def rename(newName: str) -> bool:
hostname.write(newName) hostname.write(newName)
# Force system new name # Force system new name
os.system('/bin/hostname {}'.format(newName)) subprocess.run(['hostnamectl', 'set-hostname', newName]) # nosec: ok, we are root
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName)) subprocess.run(['/bin/hostname', newName]) # nosec: ok, we are root
# add name to "hosts" # add name to "hosts"
with open('/etc/hosts', 'r') as hosts: with open('/etc/hosts', 'r') as hosts:

View File

@ -37,7 +37,7 @@ from ..log import logger
from ..service import CommonService from ..service import CommonService
try: 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 except ImportError: # Platform may not include prctl, so in case it's not available, we let the "name" as is
def set_proctitle(_): def set_proctitle(_):
pass pass

View File

@ -32,12 +32,13 @@
import os import os
import configparser import configparser
import base64 import base64
import pickle import pickle # nosec
from .. import types from .. import types
CONFIGFILE = '/etc/udsactor/udsactor.cfg' CONFIGFILE = '/etc/udsactor/udsactor.cfg'
def readConfig() -> types.ActorConfigurationType: def readConfig() -> types.ActorConfigurationType:
try: try:
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
@ -45,10 +46,22 @@ def readConfig() -> types.ActorConfigurationType:
uds: configparser.SectionProxy = cfg['uds'] uds: configparser.SectionProxy = cfg['uds']
# Extract data: # Extract data:
base64Config = uds.get('config', None) 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) 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( return types.ActorConfigurationType(
actorType=uds.get('type', types.MANAGED), actorType=uds.get('type', types.MANAGED),
@ -62,20 +75,23 @@ def readConfig() -> types.ActorConfigurationType:
post_command=uds.get('post_command', None), post_command=uds.get('post_command', None),
log_level=int(uds.get('log_level', '2')), log_level=int(uds.get('log_level', '2')),
config=config, config=config,
data=data data=data,
) )
except Exception: except Exception:
return types.ActorConfigurationType('', False) return types.ActorConfigurationType('', False)
def writeConfig(config: types.ActorConfigurationType) -> None: def writeConfig(config: types.ActorConfigurationType) -> None:
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
cfg.add_section('uds') cfg.add_section('uds')
uds: configparser.SectionProxy = cfg['uds'] uds: configparser.SectionProxy = cfg['uds']
uds['host'] = config.host uds['host'] = config.host
uds['validate'] = 'yes' if config.validateCertificate else 'no' uds['validate'] = 'yes' if config.validateCertificate else 'no'
def writeIfValue(val, name): def writeIfValue(val, name):
if val: if val:
uds[name] = val uds[name] = val
writeIfValue(config.actorType, 'type') writeIfValue(config.actorType, 'type')
writeIfValue(config.master_token, 'master_token') writeIfValue(config.master_token, 'master_token')
writeIfValue(config.own_token, 'own_token') writeIfValue(config.own_token, 'own_token')
@ -93,15 +109,19 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
# Ensures exists destination folder # Ensures exists destination folder
dirname = os.path.dirname(CONFIGFILE) dirname = os.path.dirname(CONFIGFILE)
if not os.path.exists(dirname): 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: with open(CONFIGFILE, 'w') as f:
cfg.write(f) cfg.write(f)
os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root
def useOldJoinSystem() -> bool: def useOldJoinSystem() -> bool:
return False return False
def invokeScriptOnLogin() -> str: def invokeScriptOnLogin() -> str:
return '' return ''

View File

@ -31,7 +31,7 @@
# pylint: disable=invalid-name # pylint: disable=invalid-name
import ctypes import ctypes
import ctypes.util import ctypes.util
import subprocess import subprocess # nosec
xlib = None xlib = None
xss = None xss = None
@ -39,17 +39,22 @@ display = None
xssInfo = None xssInfo = None
initialized = False initialized = False
class XScreenSaverInfo(ctypes.Structure): # pylint: disable=too-few-public-methods class XScreenSaverInfo(ctypes.Structure): # pylint: disable=too-few-public-methods
_fields_ = [('window', ctypes.c_long), _fields_ = [
('window', ctypes.c_long),
('state', ctypes.c_int), ('state', ctypes.c_int),
('kind', ctypes.c_int), ('kind', ctypes.c_int),
('til_or_since', ctypes.c_ulong), ('til_or_since', ctypes.c_ulong),
('idle', ctypes.c_ulong), ('idle', ctypes.c_ulong),
('eventMask', ctypes.c_ulong)] ('eventMask', ctypes.c_ulong),
]
class c_ptr(ctypes.c_void_p): class c_ptr(ctypes.c_void_p):
pass pass
def _ensureInitialized(): def _ensureInitialized():
global xlib, xss, xssInfo, display, initialized # pylint: disable=global-statement global xlib, xss, xssInfo, display, initialized # pylint: disable=global-statement
@ -73,13 +78,15 @@ def _ensureInitialized():
xss.XScreenSaverQueryExtension.argtypes = [ xss.XScreenSaverQueryExtension.argtypes = [
ctypes.c_void_p, ctypes.c_void_p,
ctypes.POINTER(ctypes.c_int), 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 = [ xss.XScreenSaverQueryInfo.argtypes = [
ctypes.c_void_p, ctypes.c_void_p,
ctypes.c_void_p, ctypes.c_void_p,
ctypes.POINTER(XScreenSaverInfo) ctypes.POINTER(XScreenSaverInfo),
] ]
xlib.XOpenDisplay.argtypes = [ctypes.c_char_p] xlib.XOpenDisplay.argtypes = [ctypes.c_char_p]
xlib.XOpenDisplay.restype = c_ptr xlib.XOpenDisplay.restype = c_ptr
@ -95,7 +102,9 @@ def _ensureInitialized():
event_base = ctypes.c_int() event_base = ctypes.c_int()
error_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: if available != 1:
raise Exception('ScreenSaver not available') raise Exception('ScreenSaver not available')
@ -107,9 +116,11 @@ def _ensureInitialized():
def initIdleDuration(atLeastSeconds: int) -> None: def initIdleDuration(atLeastSeconds: int) -> None:
_ensureInitialized() _ensureInitialized()
if atLeastSeconds: 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 # And now reset it
subprocess.call(['/usr/bin/xset', 's', 'reset']) subprocess.call(['/usr/bin/xset', 's', 'reset']) # nosec: fixed command
def getIdleDuration() -> float: def getIdleDuration() -> float:
@ -122,7 +133,11 @@ def getIdleDuration() -> float:
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo) xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo)
# States: 0 = off, 1 = On, 2 = Cycle, 3 = Disabled, ...? # 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 if (
return 3600 * 100 * 1000 # If screen saver is active, return a high enough value 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 return xssInfo.contents.idle / 1000.0

View File

@ -35,6 +35,8 @@ import typing
if sys.platform == 'win32': if sys.platform == 'win32':
from .windows.log import LocalLogger from .windows.log import LocalLogger
elif sys.platform == 'darwin':
from .macos.log import LocalLogger
else: else:
from .linux.log import LocalLogger from .linux.log import LocalLogger
@ -55,7 +57,7 @@ class Logger:
self.logLevel = INFO self.logLevel = INFO
self.localLogger = LocalLogger() self.localLogger = LocalLogger()
self.remoteLogger = None 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: def setLevel(self, level: typing.Union[str, int]) -> None:
''' '''

View File

@ -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
'''

View File

@ -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)

View File

@ -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"<key>ProductVersion</key>\s*<string>(.*)</string>", 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

View File

@ -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()

View File

@ -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()

View File

@ -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 ''

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2014 Virtual Cable S.L. # Copyright (c) 2014-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #
@ -32,6 +32,8 @@ import sys
name = sys.platform name = sys.platform
if sys.platform == 'win32': 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: else:
from .linux import operations, store # pylint: disable=unused-import from .linux import operations, store

View File

@ -103,7 +103,7 @@ class UDSApi: # pylint: disable=too-few-public-methods
logging.getLogger('urllib3').setLevel(logging.ERROR) logging.getLogger('urllib3').setLevel(logging.ERROR)
try: try:
warnings.simplefilter('ignore') # Disables all warnings warnings.simplefilter('ignore') # Disables all warnings
except Exception: except Exception: # nosec: not interested in exceptions
pass pass
@property @property
@ -178,7 +178,7 @@ class UDSServerApi(UDSApi):
priority=v['priority'], priority=v['priority'],
isCustom=v['isCustom'], isCustom=v['isCustom'],
) )
except Exception: except Exception: # nosec: not interested in exceptions
pass pass
def register( # pylint: disable=too-many-arguments, too-many-locals def register( # pylint: disable=too-many-arguments, too-many-locals
@ -329,7 +329,7 @@ class UDSServerApi(UDSApi):
) -> types.LoginResultInfoType: ) -> types.LoginResultInfoType:
if not token: if not token:
return types.LoginResultInfoType( 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 = { payload = {
'type': actor_type or types.MANAGED, 'type': actor_type or types.MANAGED,

View File

@ -101,7 +101,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# 0 = OTHER, 10000 = DEBUG, 20000 = WARN, 30000 = INFO, 40000 = ERROR, 50000 = FATAL # 0 = OTHER, 10000 = DEBUG, 20000 = WARN, 30000 = INFO, 40000 = ERROR, 50000 = FATAL
# So this comes: # So this comes:
logger.setLevel([DEBUG, INFO, ERROR, FATAL][self._cfg.log_level]) 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() logger.enableServiceLogger()
socket.setdefaulttimeout(20) socket.setdefaulttimeout(20)
@ -538,9 +538,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
if not self.isManaged(): if not self.isManaged():
self.uninitialize() self.uninitialize()
# **************************************** # ******************************************************
# Methods that CAN BE overriden by actors # Methods that CAN BE overriden by specific OS Actor
# **************************************** # ******************************************************
def doWait(self, miliseconds: int) -> None: def doWait(self, miliseconds: int) -> None:
''' '''
Invoked to wait a bit Invoked to wait a bit

View File

@ -47,7 +47,7 @@ class ScriptExecutorThread(threading.Thread):
try: try:
logger.debug('Executing script: {}'.format(self.script)) 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: except Exception as e:
logger.error('Error executing script: {}'.format(e)) logger.error('Error executing script: {}'.format(e))
logger.exception() logger.exception()

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2014-2019 Virtual Cable S.L. # Copyright (c) 2014-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2014 Virtual Cable S.L. # Copyright (c) 2014-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2014-2019 Virtual Cable S.L. # Copyright (c) 2014-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2019 Virtual Cable S.L. # Copyright (c) 2019-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #