1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-03 01:17:56 +03:00

Moved "custom" parameters for osManager inside the "custom" dict

Also, kept "old" parematers for a while (a couple of versions), so old clients are compatible with UDS actor 4.0 at least (so we can upgrade server, keep running and eventually upgrade actors).
Compatibility mast be kept for at lest a couple of minor releases,
Al least, until UDS 4.5 will be kept this way
This commit is contained in:
Adolfo Gómez García 2023-05-18 15:04:47 +02:00
parent 880aa24dbb
commit c264ea9c13
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
14 changed files with 179 additions and 218 deletions

View File

@ -187,13 +187,16 @@ def renameComputer(newName: str) -> bool:
rename(newName) rename(newName)
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, custom: typing.Optional[typing.Mapping[str, typing.Any]] = None): def joinDomain(name: str, custom: typing.Optional[typing.Mapping[str, typing.Any]] = None):
if not custom: if not custom:
logger.error('Error joining domain: no custom data provided') logger.error('Error joining domain: no custom data provided')
return return
# Read parameters from custom data # Read parameters from custom data
name: str = custom.get('name', '') domain: str = custom.get('domain', '')
ou: str = custom.get('ou', '')
account: str = custom.get('account', '')
password: str = custom.get('password', '')
client_software: str = custom.get('client_software', '') client_software: str = custom.get('client_software', '')
server_software: str = custom.get('server_software', '') server_software: str = custom.get('server_software', '')
membership_software: str = custom.get('membership_software', '') membership_software: str = custom.get('membership_software', '')

View File

@ -64,20 +64,15 @@ class UDSActorSvc(daemon.Daemon, CommonService):
return self._sensibleDataCleanable return self._sensibleDataCleanable
def joinDomain( # pylint: disable=unused-argument, too-many-arguments def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self, name: str, domain: str, ou: str, account: str, password: str, custom: typing.Optional[typing.Mapping[str, typing.Any]] = None self, name: str, custom: typing.Mapping[str, typing.Any]
) -> None: ) -> None:
# Add name to custom data, needed by joinDomain on Linux
# First, Copy mapping to mutable dict so we can add name to it self._sensibleDataCleanable = custom.get('isPersistent', False)
localCustom = {k: v for k, v in (custom.items() if custom is not None else [])}
localCustom['name'] = name
# If isPersistent is False, we need to keep sensible data, so it's not cleaned
self._sensibleDataCleanable = localCustom.get('isPersistent', False)
self.rename(name) self.rename(name)
logger.debug(f'Starting joining domain {domain} with name {name}') logger.debug('Starting joining domain %s with name %s', custom.get('domain', ''), name)
operations.joinDomain(domain, ou, account, password, custom=localCustom) operations.joinDomain(name, custom)
def finish(self) -> None: def finish(self) -> None:
try: try:
@ -86,11 +81,11 @@ class UDSActorSvc(daemon.Daemon, CommonService):
custom = self._cfg.config.os.custom custom = self._cfg.config.os.custom
if osData.action == 'rename_ad' and custom.get('isPersistent', False): if osData.action == 'rename_ad' and custom.get('isPersistent', False):
operations.leaveDomain( operations.leaveDomain(
osData.ad or '', custom.get('ad', ''),
osData.username or '', custom.get('username', ''),
osData.password or '', custom.get('password', ''),
custom['clientSoftware'] or '', custom.get('clientSoftware', ''),
custom['serverSoftware'] or '', custom.get('serverSoftware', ''),
) )
except Exception as e: except Exception as e:
logger.error(f'Got exception operating machine: {e}') logger.error(f'Got exception operating machine: {e}')

View File

@ -289,11 +289,6 @@ class UDSServerApi(UDSApi):
os=types.ActorOsConfigurationType( os=types.ActorOsConfigurationType(
action=os['action'], action=os['action'],
name=os['name'], name=os['name'],
username=os.get('username'),
password=os.get('password'),
new_password=os.get('new_password'),
ad=os.get('ad'),
ou=os.get('ou'),
custom=os.get('custom'), custom=os.get('custom'),
) )
if r['os'] if r['os']

View File

@ -75,9 +75,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
logger.debug('Executing command on {}: {}'.format(section, cmdLine)) logger.debug('Executing command on {}: {}'.format(section, cmdLine))
res = subprocess.check_call(cmdLine, shell=True) res = subprocess.check_call(cmdLine, shell=True)
except Exception as e: except Exception as e:
logger.error( logger.error('Got exception executing: {} - {} - {}'.format(section, cmdLine, e))
'Got exception executing: {} - {} - {}'.format(section, cmdLine, e)
)
return False return False
logger.debug('Result of executing cmd for {} was {}'.format(section, res)) logger.debug('Result of executing cmd for {} was {}'.format(section, res))
return True return True
@ -131,9 +129,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
) # Emty interfaces is like "no ip change" because cannot be notified ) # Emty interfaces is like "no ip change" because cannot be notified
if self._cfg.config and interfaces: if self._cfg.config and interfaces:
try: try:
return next( return next(x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id)
x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id
)
except StopIteration: except StopIteration:
return interfaces[0] return interfaces[0]
@ -188,9 +184,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while. # Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
break break
else: else:
logger.error( logger.error('Could not locate IP address!!!. (Not registered with UDS)')
'Could not locate IP address!!!. (Not registered with UDS)'
)
# Do not continue if not alive... # Do not continue if not alive...
if not self._isAlive: if not self._isAlive:
@ -199,9 +193,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# Cleans sensible data # Cleans sensible data
if self._cfg.config: if self._cfg.config:
if self.canCleanSensibleData(): if self.canCleanSensibleData():
self._cfg = self._cfg._replace( self._cfg = self._cfg._replace(config=self._cfg.config._replace(os=None), data=None)
config=self._cfg.config._replace(os=None), data=None
)
platform.store.writeConfig(self._cfg) platform.store.writeConfig(self._cfg)
logger.info('Service ready') logger.info('Service ready')
@ -232,22 +224,17 @@ class CommonService: # pylint: disable=too-many-instance-attributes
try: try:
if self._cfg.config and self._cfg.config.os: if self._cfg.config and self._cfg.config.os:
osData = self._cfg.config.os osData = self._cfg.config.os
custom: typing.Mapping[str, typing.Any] = osData.custom or {}
# Needs UDS Server >= 4.0 to work
if osData.action == 'rename': if osData.action == 'rename':
self.rename( self.rename(
osData.name, osData.name,
osData.username, custom.get('username'),
osData.password, custom.get('password'),
osData.new_password, custom.get('new_password'),
) )
elif osData.action == 'rename_ad': elif osData.action == 'rename_ad':
self.joinDomain( self.joinDomain(osData.name, custom)
osData.name,
osData.ad or '',
osData.ou or '',
osData.username or '',
osData.password or '',
osData.custom
)
if self._rebootRequested: if self._rebootRequested:
try: try:
@ -295,9 +282,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self.doWait(5000) self.doWait(5000)
def initialize(self) -> bool: def initialize(self) -> bool:
if ( if self._initialized or not self._cfg.host or not self._isAlive: # Not configured or not running
self._initialized or not self._cfg.host or not self._isAlive
): # Not configured or not running
return False return False
self._initialized = True self._initialized = True
@ -319,18 +304,14 @@ class CommonService: # pylint: disable=too-many-instance-attributes
) )
if not initResult.own_token: # Not managed if not initResult.own_token: # Not managed
logger.debug( logger.debug(
'This host is not managed by UDS Broker (ids: {})'.format( 'This host is not managed by UDS Broker (ids: {})'.format(self._interfaces)
self._interfaces
)
) )
return False return False
# Only removes master token for managed machines (will need it on next client execution) # Only removes master token for managed machines (will need it on next client execution)
# For unmanaged, if alias is present, replace master token with it # For unmanaged, if alias is present, replace master token with it
master_token = ( master_token = (
None None if self.isManaged() else (initResult.alias_token or self._cfg.master_token)
if self.isManaged()
else (initResult.alias_token or self._cfg.master_token)
) )
# Replace master token with alias token if present # Replace master token with alias token if present
self._cfg = self._cfg._replace( self._cfg = self._cfg._replace(
@ -352,16 +333,10 @@ class CommonService: # pylint: disable=too-many-instance-attributes
break # Initial configuration done.. break # Initial configuration done..
except rest.RESTConnectionError as e: except rest.RESTConnectionError as e:
logger.info( logger.info('Trying to inititialize connection with broker (last error: {})'.format(e))
'Trying to inititialize connection with broker (last error: {})'.format(
e
)
)
self.doWait(5000) # Wait a bit and retry self.doWait(5000) # Wait a bit and retry
except rest.RESTError as e: # Invalid key? except rest.RESTError as e: # Invalid key?
logger.error( logger.error('Error validating with broker. (Invalid token?): {}'.format(e))
'Error validating with broker. (Invalid token?): {}'.format(e)
)
return False return False
except Exception: except Exception:
logger.exception() logger.exception()
@ -371,9 +346,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
def uninitialize(self): def uninitialize(self):
self._initialized = False self._initialized = False
self._cfg = self._cfg._replace( self._cfg = self._cfg._replace(own_token=None) # Ensures assigned token is cleared
own_token=None
) # Ensures assigned token is cleared
def finish(self) -> None: def finish(self) -> None:
if self._http: if self._http:
@ -389,8 +362,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self._cfg.actorType, self._cfg.actorType,
self._cfg.own_token, self._cfg.own_token,
'', '',
client.session_id client.session_id or 'stop', # If no session id, pass "stop"
or 'stop', # If no session id, pass "stop"
'', '',
self._interfaces, self._interfaces,
self._secret, self._secret,
@ -405,11 +377,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
return # Unamanaged hosts does not changes ips. (The full initialize-login-logout process is done in a row, so at login the IP is correct) return # Unamanaged hosts does not changes ips. (The full initialize-login-logout process is done in a row, so at login the IP is correct)
try: try:
if ( if not self._cfg.own_token or not self._cfg.config or not self._cfg.config.unique_id:
not self._cfg.own_token
or not self._cfg.config
or not self._cfg.config.unique_id
):
# Not enouth data do check # Not enouth data do check
return return
currentInterfaces = tools.validNetworkCards( currentInterfaces = tools.validNetworkCards(
@ -418,59 +386,20 @@ class CommonService: # pylint: disable=too-many-instance-attributes
old = self.serviceInterfaceInfo() old = self.serviceInterfaceInfo()
new = self.serviceInterfaceInfo(currentInterfaces) new = self.serviceInterfaceInfo(currentInterfaces)
if not new or not old: if not new or not old:
raise Exception( raise Exception('No ip currently available for {}'.format(self._cfg.config.unique_id))
'No ip currently available for {}'.format(
self._cfg.config.unique_id
)
)
if old.ip != new.ip: if old.ip != new.ip:
self._certificate = self._api.notifyIpChange( self._certificate = self._api.notifyIpChange(
self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT
) )
# Now store new addresses & interfaces... # Now store new addresses & interfaces...
self._interfaces = currentInterfaces self._interfaces = currentInterfaces
logger.info( logger.info('Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip))
'Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip)
)
# Stop the running HTTP Thread and start a new one, with new generated cert # Stop the running HTTP Thread and start a new one, with new generated cert
self.startHttpServer() self.startHttpServer()
except Exception as e: except Exception as e:
# No ip changed, log exception for info # No ip changed, log exception for info
logger.warn('Checking ips failed: {}'.format(e)) logger.warn('Checking ips failed: {}'.format(e))
def rename(
self,
name: str,
userName: typing.Optional[str] = None,
oldPassword: typing.Optional[str] = None,
newPassword: typing.Optional[str] = None,
) -> None:
'''
Invoked when broker requests a rename action
default does nothing
'''
hostName = platform.operations.getComputerName()
# Check for password change request for an user
if userName and newPassword:
logger.info('Setting password for configured user')
try:
platform.operations.changeUserPassword(
userName, oldPassword or '', newPassword
)
except Exception as e:
# Logs error, but continue renaming computer
logger.error(
'Could not change password for user {}: {}'.format(userName, e)
)
if hostName.lower() == name.lower():
logger.info('Computer name is already {}'.format(hostName))
return
if platform.operations.renameComputer(name):
self.reboot()
def loop(self): def loop(self):
# Main common loop # Main common loop
try: try:
@ -487,22 +416,44 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# ****************************************************** # ******************************************************
# Methods that can be overriden by linux & windows Actor # Methods that can be overriden by linux & windows Actor
# ****************************************************** # ******************************************************
def joinDomain( # pylint: disable=unused-argument, too-many-arguments def rename(
self, name: str, domain: str, ou: str, account: str, password: str, custom: typing.Optional[typing.Mapping[str, typing.Any]] = None self,
name: str,
userName: typing.Optional[str] = None,
oldPassword: typing.Optional[str] = None,
newPassword: typing.Optional[str] = None,
) -> None: ) -> None:
'''
Invoked when broker requests a rename action
'''
hostName = platform.operations.getComputerName()
# Check for password change request for an user
if userName and newPassword:
logger.info('Setting password for configured user')
try:
platform.operations.changeUserPassword(userName, oldPassword or '', newPassword)
except Exception as e:
# Logs error, but continue renaming computer
logger.error('Could not change password for user {}: {}'.format(userName, e))
if hostName.lower() == name.lower():
logger.info('Computer name is already {}'.format(hostName))
return
if platform.operations.renameComputer(name):
self.reboot()
def joinDomain(self, name: str, custom: typing.Mapping[str, typing.Any]) -> None:
''' '''
Invoked when broker requests a "domain" action Invoked when broker requests a "domain" action
default does nothing default does nothing
''' '''
logger.debug('Base join invoked: {} on {}, {}'.format(name, domain, ou)) logger.debug('Base join invoked: %s on %s, %s', name, custom)
# Client notifications # Client notifications
def login( def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
self, username: str, sessionType: typing.Optional[str] = None result = types.LoginResultInfoType(ip='', hostname='', dead_line=None, max_idle=None, session_id=None)
) -> types.LoginResultInfoType:
result = types.LoginResultInfoType(
ip='', hostname='', dead_line=None, max_idle=None, session_id=None
)
master_token = None master_token = None
secret = None secret = None
# If unmanaged, do initialization now, because we don't know before this # If unmanaged, do initialization now, because we don't know before this
@ -564,9 +515,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
) )
!= 'ok' # Can return also "notified", that means the logout has not been processed by UDS != 'ok' # Can return also "notified", that means the logout has not been processed by UDS
): ):
logger.info( logger.info('Logout from %s ignored as required by uds broker', username)
'Logout from %s ignored as required by uds broker', username
)
return return
self.onLogout(username, session_id or '') self.onLogout(username, session_id or '')
@ -595,9 +544,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
''' '''
logger.info('Service stopped') logger.info('Service stopped')
def preConnect( def preConnect(self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str) -> str:
self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str
) -> str:
''' '''
Invoked when received a PRE Connection request via REST Invoked when received a PRE Connection request via REST
Base preconnect executes the preconnect command Base preconnect executes the preconnect command

View File

@ -19,11 +19,6 @@ class AuthenticatorType(typing.NamedTuple):
class ActorOsConfigurationType(typing.NamedTuple): class ActorOsConfigurationType(typing.NamedTuple):
action: str action: str
name: str name: str
username: typing.Optional[str] = None
password: typing.Optional[str] = None
new_password: typing.Optional[str] = None
ad: typing.Optional[str] = None
ou: typing.Optional[str] = None
custom: typing.Optional[typing.Mapping[str, typing.Any]] = None custom: typing.Optional[typing.Mapping[str, typing.Any]] = None
class ActorDataConfigurationType(typing.NamedTuple): class ActorDataConfigurationType(typing.NamedTuple):

View File

@ -63,24 +63,17 @@ def getComputerName() -> str:
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]: def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
obj = win32com.client.Dispatch("WbemScripting.SWbemLocator") obj = win32com.client.Dispatch("WbemScripting.SWbemLocator")
wmobj = obj.ConnectServer("localhost", "root\\cimv2") wmobj = obj.ConnectServer("localhost", "root\\cimv2")
adapters = wmobj.ExecQuery( adapters = wmobj.ExecQuery("Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True")
"Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True"
)
try: try:
for obj in adapters: for obj in adapters:
for ip in obj.IPAddress: for ip in obj.IPAddress:
if ':' in ip: # Is IPV6, skip this if ':' in ip: # Is IPV6, skip this
continue continue
if ( if (
ip is None ip is None or ip == '' or ip.startswith('169.254') or ip.startswith('0.')
or ip == ''
or ip.startswith('169.254')
or ip.startswith('0.')
): # If single link ip, or no ip ): # If single link ip, or no ip
continue continue
yield types.InterfaceInfoType( yield types.InterfaceInfoType(name=obj.Caption, mac=obj.MACAddress, ip=ip)
name=obj.Caption, mac=obj.MACAddress, ip=ip
)
except Exception: except Exception:
return return
@ -97,7 +90,7 @@ def getDomainName() -> str:
# 3 = Domain # 3 = Domain
domain, status = win32net.NetGetJoinInformation() domain, status = win32net.NetGetJoinInformation()
if status != 3: if status != 3:
domain = None domain = ''
return domain return domain
@ -109,9 +102,7 @@ def getWindowsVersion() -> typing.Tuple[int, int, int, int, str]:
def getVersion() -> str: def getVersion() -> str:
verinfo = getWindowsVersion() verinfo = getWindowsVersion()
# Remove platform id i # Remove platform id i
return 'Windows-{}.{} Build {} ({})'.format( return 'Windows-{}.{} Build {} ({})'.format(verinfo[0], verinfo[1], verinfo[2], verinfo[4])
verinfo[0], verinfo[1], verinfo[2], verinfo[4]
)
EWX_LOGOFF = 0x00000000 EWX_LOGOFF = 0x00000000
@ -129,11 +120,11 @@ def reboot(flags: int = EWX_FORCEIFHUNG | EWX_REBOOT) -> None:
) )
privs = ( privs = (
( (
win32security.LookupPrivilegeValue(None, win32security.SE_SHUTDOWN_NAME), win32security.LookupPrivilegeValue(None, win32security.SE_SHUTDOWN_NAME), # type: ignore
win32security.SE_PRIVILEGE_ENABLED, win32security.SE_PRIVILEGE_ENABLED,
), ),
) )
win32security.AdjustTokenPrivileges(htok, 0, privs) win32security.AdjustTokenPrivileges(htok, 0, privs) # type: ignore
win32api.ExitWindowsEx(flags, 0) win32api.ExitWindowsEx(flags, 0)
@ -148,7 +139,7 @@ def renameComputer(newName: str) -> bool:
''' '''
# Needs admin privileges to work # Needs admin privileges to work
if ( if (
ctypes.windll.kernel32.SetComputerNameExW( ctypes.windll.kernel32.SetComputerNameExW( # type: ignore
DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName) DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)
) )
== 0 == 0
@ -157,14 +148,8 @@ def renameComputer(newName: str) -> bool:
# win32api.GetLastError -> returns error code # win32api.GetLastError -> returns error code
# (just put this comment here to remember to log this when logger is available) # (just put this comment here to remember to log this when logger is available)
error = getErrorMessage() error = getErrorMessage()
computerName = win32api.GetComputerNameEx( computerName = win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
win32con.ComputerNamePhysicalDnsHostname raise Exception('Error renaming computer from {} to {}: {}'.format(computerName, newName, error))
)
raise Exception(
'Error renaming computer from {} to {}: {}'.format(
computerName, newName, error
)
)
return True return True
@ -179,9 +164,7 @@ NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400
NETSETUP_DEFER_SPN_SET = 0x1000000 NETSETUP_DEFER_SPN_SET = 0x1000000
def joinDomain( def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False) -> None:
domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False
) -> None:
''' '''
Joins machine to a windows domain Joins machine to a windows domain
:param domain: Domain to join to :param domain: Domain to join to
@ -198,9 +181,7 @@ def joinDomain(
account = domain + '\\' + account account = domain + '\\' + account
# Do log # Do log
flags: typing.Any = ( flags: typing.Any = NETSETUP_ACCT_CREATE | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN
NETSETUP_ACCT_CREATE | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN
)
if executeInOneStep: if executeInOneStep:
flags |= NETSETUP_JOIN_WITH_NEW_NAME flags |= NETSETUP_JOIN_WITH_NEW_NAME
@ -214,13 +195,13 @@ def joinDomain(
lpAccount = LPCWSTR(account) lpAccount = LPCWSTR(account)
lpPassword = LPCWSTR(password) lpPassword = LPCWSTR(password)
res = ctypes.windll.netapi32.NetJoinDomain( res = ctypes.windll.netapi32.NetJoinDomain( # type: ignore
None, lpDomain, lpOu, lpAccount, lpPassword, flags None, lpDomain, lpOu, lpAccount, lpPassword, flags
) )
# Machine found in another ou, use it and warn this on log # Machine found in another ou, use it and warn this on log
if res == 2224: if res == 2224:
flags = DWORD(NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN) flags = DWORD(NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN)
res = ctypes.windll.netapi32.NetJoinDomain( res = ctypes.windll.netapi32.NetJoinDomain( # type: ignore
None, lpDomain, None, lpAccount, lpPassword, flags None, lpDomain, None, lpAccount, lpPassword, flags
) )
if res: if res:
@ -247,14 +228,12 @@ def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
# res = ctypes.windll.netapi32.NetUserChangePassword(None, lpUser, lpOldPassword, lpNewPassword) # res = ctypes.windll.netapi32.NetUserChangePassword(None, lpUser, lpOldPassword, lpNewPassword)
# Try to set new password "a las bravas", ignoring old one. This will not work with domain users # Try to set new password "a las bravas", ignoring old one. This will not work with domain users
res = win32net.NetUserSetInfo(None, user, 1003, {'password': newPassword}) res = win32net.NetUserSetInfo(None, user, 1003, {'password': newPassword}) # type: ignore
if res: if res:
# Log the error, and raise exception to parent # Log the error, and raise exception to parent
error = getErrorMessage(res) error = getErrorMessage(res)
raise Exception( raise Exception('Error changing password for user {}: {} {}'.format(user, res, error))
'Error changing password for user {}: {} {}'.format(user, res, error)
)
class LASTINPUTINFO(ctypes.Structure): # pylint: disable=too-few-public-methods class LASTINPUTINFO(ctypes.Structure): # pylint: disable=too-few-public-methods
@ -274,14 +253,14 @@ def initIdleDuration(atLeastSeconds: int): # pylint: disable=unused-argument
def getIdleDuration() -> float: def getIdleDuration() -> float:
try: try:
lastInputInfo = LASTINPUTINFO() lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = ctypes.sizeof( lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) # pylint: disable=attribute-defined-outside-init
lastInputInfo if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) == 0: # type: ignore
) # pylint: disable=attribute-defined-outside-init
if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) == 0:
return 0 return 0
current = ctypes.c_uint(ctypes.windll.kernel32.GetTickCount()).value current = ctypes.c_uint(ctypes.windll.kernel32.GetTickCount()).value # type: ignore
if current < lastInputInfo.dwTime: if current < lastInputInfo.dwTime:
current += 4294967296 # If current has "rolled" to zero, adjust it so it is greater than lastInputInfo current += (
4294967296 # If current has "rolled" to zero, adjust it so it is greater than lastInputInfo
)
millis = current - lastInputInfo.dwTime # @UndefinedVariable millis = current - lastInputInfo.dwTime # @UndefinedVariable
return millis / 1000.0 return millis / 1000.0
except Exception as e: except Exception as e:
@ -306,9 +285,7 @@ def getSessionType() -> str:
return os.environ.get('SESSIONNAME', 'unknown') return os.environ.get('SESSIONNAME', 'unknown')
def writeToPipe( def writeToPipe(pipeName: str, bytesPayload: bytes, waitForResponse: bool) -> typing.Optional[bytes]:
pipeName: str, bytesPayload: bytes, waitForResponse: bool
) -> typing.Optional[bytes]:
# (str, bytes, bool) -> Optional[bytes] # (str, bytes, bool) -> Optional[bytes]
try: try:
with open(pipeName, 'r+b', 0) as f: with open(pipeName, 'r+b', 0) as f:
@ -323,8 +300,6 @@ def writeToPipe(
def forceTimeSync() -> None: def forceTimeSync() -> None:
try: try:
subprocess.call( subprocess.call([r'c:\WINDOWS\System32\w32tm.exe', ' /resync']) # , '/rediscover'])
[r'c:\WINDOWS\System32\w32tm.exe', ' /resync']
) # , '/rediscover'])
except Exception as e: except Exception as e:
logger.error('Error invoking time sync command: %s', e) logger.error('Error invoking time sync command: %s', e)

View File

@ -49,11 +49,13 @@ from ..log import logger
REMOTE_USERS_SID = 'S-1-5-32-555' # Well nown sid for remote desktop users REMOTE_USERS_SID = 'S-1-5-32-555' # Well nown sid for remote desktop users
class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService): class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
''' '''
This class represents a Windows Service for managing actor interactions This class represents a Windows Service for managing actor interactions
with UDS Broker and Machine with UDS Broker and Machine
''' '''
# ServiceeFramework related # ServiceeFramework related
_svc_name_ = "UDSActorNG" _svc_name_ = "UDSActorNG"
_svc_display_name_ = "UDS Actor Service" _svc_display_name_ = "UDS Actor Service"
@ -78,7 +80,9 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
SvcShutdown = SvcStop SvcShutdown = SvcStop
def notifyStop(self) -> None: def notifyStop(self) -> None:
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STOPPED, (self._svc_name_, '')) servicemanager.LogMsg(
servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STOPPED, (self._svc_name_, '')
)
super().notifyStop() super().notifyStop()
def doWait(self, miliseconds: int) -> None: def doWait(self, miliseconds: int) -> None:
@ -86,7 +90,9 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
# On windows, and while on tasks, ensure that our app processes waiting messages on "wait times" # On windows, and while on tasks, ensure that our app processes waiting messages on "wait times"
pythoncom.PumpWaitingMessages() # pylint: disable=no-member pythoncom.PumpWaitingMessages() # pylint: disable=no-member
def oneStepJoin(self, name: str, domain: str, ou: str, account: str, password: str) -> None: # pylint: disable=too-many-arguments def oneStepJoin(
self, name: str, domain: str, ou: str, account: str, password: str
) -> None: # pylint: disable=too-many-arguments
''' '''
Ejecutes the join domain in exactly one step Ejecutes the join domain in exactly one step
''' '''
@ -103,7 +109,9 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
logger.debug('Requested join domain {} without errors'.format(domain)) logger.debug('Requested join domain {} without errors'.format(domain))
self.reboot() self.reboot()
def multiStepJoin(self, name: str, domain: str, ou: str, account: str, password: str) -> None: # pylint: disable=too-many-arguments def multiStepJoin(
self, name: str, domain: str, ou: str, account: str, password: str
) -> None: # pylint: disable=too-many-arguments
currName = operations.getComputerName() currName = operations.getComputerName()
if currName.lower() == name.lower(): if currName.lower() == name.lower():
currDomain = operations.getDomainName() currDomain = operations.getDomainName()
@ -119,17 +127,21 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
logger.info('Rebooting computer for activating new name {}'.format(name)) logger.info('Rebooting computer for activating new name {}'.format(name))
self.reboot() self.reboot()
def joinDomain( # pylint: disable=unused-argument, too-many-arguments def joinDomain(self, name: str, custom: typing.Mapping[str, typing.Any]) -> None:
self,
name: str,
domain: str,
ou: str,
account: str,
password: str
) -> None:
versionData = operations.getWindowsVersion() versionData = operations.getWindowsVersion()
versionInt = versionData[0] * 10 + versionData[1] versionInt = versionData[0] * 10 + versionData[1]
logger.debug('Starting joining domain {} with name {} (detected operating version: {})'.format(domain, name, versionData))
# Extract custom data
domain = custom.get('domain', '')
ou = custom.get('ou', '')
account = custom.get('account', '')
password = custom.get('password', '')
logger.debug(
'Starting joining domain {} with name {} (detected operating version: {})'.format(
domain, name, versionData
)
)
# Accepts one step joinDomain, also remember XP is no more supported by # Accepts one step joinDomain, also remember XP is no more supported by
# microsoft, but this also must works with it because will do a "multi # microsoft, but this also must works with it because will do a "multi
# step" join # step" join
@ -139,17 +151,19 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
logger.info('Using multiple step join because configuration requests to do so') logger.info('Using multiple step join because configuration requests to do so')
self.multiStepJoin(name, domain, ou, account, password) self.multiStepJoin(name, domain, ou, account, password)
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str) -> str: def preConnect(self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str) -> str:
logger.debug('Pre connect invoked') logger.debug('Pre connect invoked')
if protocol == 'rdp': # If connection is not using rdp, skip adding user if protocol == 'rdp': # If connection is not using rdp, skip adding user
# Well known SSID for Remote Desktop Users # Well known SSID for Remote Desktop Users
groupName = win32security.LookupAccountSid(None, win32security.GetBinarySid(REMOTE_USERS_SID))[0] groupName = win32security.LookupAccountSid(None, win32security.GetBinarySid(REMOTE_USERS_SID))[0] # type: ignore
useraAlreadyInGroup = False useraAlreadyInGroup = False
resumeHandle = 0 resumeHandle = 0
while True: while True:
users, _, resumeHandle = win32net.NetLocalGroupGetMembers(None, groupName, 1, resumeHandle, 32768) users, _, resumeHandle = win32net.NetLocalGroupGetMembers(
None, groupName, 1, resumeHandle, 32768 # type: ignore
)[:3]
if userName.lower() in [u['name'].lower() for u in users]: if userName.lower() in [u['name'].lower() for u in users]:
useraAlreadyInGroup = True useraAlreadyInGroup = True
break break
@ -161,7 +175,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
self._user = userName self._user = userName
try: try:
userSSID = win32security.LookupAccountName(None, userName)[0] userSSID = win32security.LookupAccountName(None, userName)[0]
win32net.NetLocalGroupAddMembers(None, groupName, 0, [{'sid': userSSID}]) win32net.NetLocalGroupAddMembers(None, groupName, 0, [{'sid': userSSID}]) # type: ignore
except Exception as e: except Exception as e:
logger.error('Exception adding user to Remote Desktop Users: {}'.format(e)) logger.error('Exception adding user to Remote Desktop Users: {}'.format(e))
else: else:
@ -178,7 +192,12 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
# Compose packet for ov # Compose packet for ov
usernameBytes = username.encode() usernameBytes = username.encode()
passwordBytes = password.encode() passwordBytes = password.encode()
packet = struct.pack('!I', len(usernameBytes)) + usernameBytes + struct.pack('!I', len(passwordBytes)) + passwordBytes packet = (
struct.pack('!I', len(usernameBytes))
+ usernameBytes
+ struct.pack('!I', len(passwordBytes))
+ passwordBytes
)
# Send packet with username/password to ov pipe # Send packet with username/password to ov pipe
operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True) operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
return 'done' return 'done'
@ -187,14 +206,14 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
logger.debug('Windows onLogout invoked: {}, {}'.format(userName, self._user)) logger.debug('Windows onLogout invoked: {}, {}'.format(userName, self._user))
try: try:
p = win32security.GetBinarySid(REMOTE_USERS_SID) p = win32security.GetBinarySid(REMOTE_USERS_SID)
groupName = win32security.LookupAccountSid(None, p)[0] groupName = win32security.LookupAccountSid(None, p)[0] # type: ignore
except Exception: except Exception:
logger.error('Exception getting Windows Group') logger.error('Exception getting Windows Group')
return return
if self._user: if self._user:
try: try:
win32net.NetLocalGroupDelMembers(None, groupName, [self._user]) win32net.NetLocalGroupDelMembers(None, groupName, [self._user]) # type: ignore
except Exception as e: except Exception as e:
logger.error('Exception removing user from Remote Desktop Users: {}'.format(e)) logger.error('Exception removing user from Remote Desktop Users: {}'.format(e))
@ -203,18 +222,22 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
Detect if windows is installing anything, so we can delay the execution of Service Detect if windows is installing anything, so we can delay the execution of Service
''' '''
try: try:
key = wreg.OpenKey(wreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State') key = wreg.OpenKey(wreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State') # type: ignore
data, _ = wreg.QueryValueEx(key, 'ImageState') data, _ = wreg.QueryValueEx(key, 'ImageState') # type: ignore
logger.debug('State: %s', data) logger.debug('State: %s', data)
return data != 'IMAGE_STATE_COMPLETE' # If ImageState is different of ImageStateComplete, there is something running on installation return (
except Exception: # If not found, means that no installation is running data != 'IMAGE_STATE_COMPLETE'
) # If ImageState is different of ImageStateComplete, there is something running on installation
except Exception: # If not found, means that no installation is running
return False return False
def SvcDoRun(self) -> None: # pylint: disable=too-many-statements, too-many-branches def SvcDoRun(self) -> None: # pylint: disable=too-many-statements, too-many-branches
''' '''
Main service loop Main service loop
''' '''
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, '')) servicemanager.LogMsg(
servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, '')
)
# call the CoInitialize to allow the registration to run in an other # call the CoInitialize to allow the registration to run in an other
# thread # thread
@ -239,7 +262,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
logger.info('Service stopped due to init') logger.info('Service stopped due to init')
self.finish() self.finish()
win32event.WaitForSingleObject(self._hWaitStop, 5000) win32event.WaitForSingleObject(self._hWaitStop, 5000)
return # Stop daemon if initializes told to do so return # Stop daemon if initializes told to do so
# Initialization is done, set machine to ready for UDS, communicate urls, etc... # Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady() self.setReady()

View File

@ -185,12 +185,12 @@ class LinuxOsADManager(LinuxOsManager):
return { return {
'action': 'rename_ad', 'action': 'rename_ad',
'name': userService.getName(), 'name': userService.getName(),
'ad': self._domain,
'username': self._account,
'password': self._password,
'ou': self._ou,
'isPersistent': self.isPersistent(),
'custom': { 'custom': {
'domain': self._domain,
'username': self._account,
'password': self._password,
'ou': self._ou,
'isPersistent': self.isPersistent(),
'clientSoftware': self._clientSoftware, 'clientSoftware': self._clientSoftware,
'serverSoftware': self._serverSoftware, 'serverSoftware': self._serverSoftware,
'membershipSoftware': self._membershipSoftware, 'membershipSoftware': self._membershipSoftware,

View File

@ -174,11 +174,11 @@ class LinuxOsFreeIPAManager(LinuxOsManager):
return { return {
'action': 'rename_ad', 'action': 'rename_ad',
'name': userService.getName(), 'name': userService.getName(),
'ad': self._domain,
'username': self._account,
'password': self._password,
'isPersistent': self.isPersistent(),
'custom': { 'custom': {
'domain': self._domain,
'username': self._account,
'password': self._password,
'isPersistent': self.isPersistent(),
'clientSoftware': self._clientSoftware, 'clientSoftware': self._clientSoftware,
'serverSoftware': self._serverSoftware, 'serverSoftware': self._serverSoftware,
'membershipSoftware': self._membershipSoftware, 'membershipSoftware': self._membershipSoftware,

View File

@ -151,7 +151,7 @@ class LinuxOsManager(osmanagers.OSManager):
def actorData( def actorData(
self, userService: 'UserService' self, userService: 'UserService'
) -> typing.MutableMapping[str, typing.Any]: ) -> typing.MutableMapping[str, typing.Any]:
return {'action': 'rename', 'name': userService.getName()} return {'action': 'rename', 'name': userService.getName()} # No custom data
def processUnused(self, userService: 'UserService') -> None: def processUnused(self, userService: 'UserService') -> None:
""" """

View File

@ -114,9 +114,18 @@ class LinuxRandomPassManager(LinuxOsManager):
return { return {
'action': 'rename', 'action': 'rename',
'name': userService.getName(), 'name': userService.getName(),
# Repeat data, to keep compat with old versions of Actor
# Will be removed in a couple of versions
'username': self._userAccount, 'username': self._userAccount,
'password': '', # On linux, user password is not needed so we provide an empty one 'password': '', # On linux, user password is not needed so we provide an empty one
'new_password': self.genPassword(userService), 'new_password': self.genPassword(userService),
'custom': {
'username': self._userAccount,
'password': '', # On linux, user password is not needed so we provide an empty one
'new_password': self.genPassword(userService),
},
} }
def marshal(self) -> bytes: def marshal(self) -> bytes:

View File

@ -160,7 +160,7 @@ class WindowsOsManager(osmanagers.OSManager):
def actorData( def actorData(
self, userService: 'UserService' self, userService: 'UserService'
) -> typing.MutableMapping[str, typing.Any]: ) -> typing.MutableMapping[str, typing.Any]:
return {'action': 'rename', 'name': userService.getName()} return {'action': 'rename', 'name': userService.getName()} # No custom data
def processUserPassword( def processUserPassword(
self, userService: 'UserService', username: str, password: str self, userService: 'UserService', username: str, password: str

View File

@ -440,10 +440,20 @@ class WinDomainOsManager(WindowsOsManager):
return { return {
'action': 'rename_ad', 'action': 'rename_ad',
'name': userService.getName(), 'name': userService.getName(),
# Repeat data, to keep compat with old versions of Actor
# Will be removed in a couple of versions
'ad': self._domain, 'ad': self._domain,
'ou': self._ou, 'ou': self._ou,
'username': self._account, 'username': self._account,
'password': self._password, 'password': self._password,
'custom': {
'domain': self._domain,
'ou': self._ou,
'username': self._account,
'password': self._password,
},
} }
def marshal(self) -> bytes: def marshal(self) -> bytes:

View File

@ -127,9 +127,18 @@ class WinRandomPassManager(WindowsOsManager):
return { return {
'action': 'rename', 'action': 'rename',
'name': userService.getName(), 'name': userService.getName(),
# Repeat data, to keep compat with old versions of Actor
# Will be removed in a couple of versions
'username': self._userAccount, 'username': self._userAccount,
'password': self._password, 'password': self._password,
'new_password': self.genPassword(userService), 'new_password': self.genPassword(userService),
'custom': {
'username': self._userAccount,
'password': self._password,
'new_password': self.genPassword(userService),
},
} }
def marshal(self) -> bytes: def marshal(self) -> bytes: