forked from shaba/openuds
Started MacOS Unmanaged Support
This commit is contained in:
parent
552ba3796b
commit
ff25b4945a
33
actor/macos/net.virtualcable.udsactor.server.plist
Normal file
33
actor/macos/net.virtualcable.udsactor.server.plist
Normal 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
1
actor/macos/notes.txt
Normal file
@ -0,0 +1 @@
|
||||
service file (net.virtualcable.udsactor.server.plist) goes in /Library/LaunchDaemons
|
@ -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))
|
||||
|
@ -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... :)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -29,9 +29,6 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import os
|
||||
import sys
|
||||
import pkgutil
|
||||
import typing
|
||||
|
||||
from .. import operations
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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 ''
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
'''
|
||||
|
31
actor/src/udsactor/macos/__init__.py
Normal file
31
actor/src/udsactor/macos/__init__.py
Normal 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
|
||||
'''
|
76
actor/src/udsactor/macos/log.py
Normal file
76
actor/src/udsactor/macos/log.py
Normal 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)
|
146
actor/src/udsactor/macos/operations.py
Normal file
146
actor/src/udsactor/macos/operations.py
Normal 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
|
70
actor/src/udsactor/macos/runner.py
Normal file
70
actor/src/udsactor/macos/runner.py
Normal 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()
|
109
actor/src/udsactor/macos/service.py
Normal file
109
actor/src/udsactor/macos/service.py
Normal 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()
|
106
actor/src/udsactor/macos/store.py
Normal file
106
actor/src/udsactor/macos/store.py
Normal 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 ''
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
#
|
||||
|
@ -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.
|
||||
#
|
||||
|
@ -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.
|
||||
#
|
||||
|
@ -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.
|
||||
#
|
||||
|
Loading…
Reference in New Issue
Block a user