1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-05 09:17:54 +03:00
This commit is contained in:
Adolfo Gómez García 2019-11-29 02:23:56 +01:00
parent 6f3f573f61
commit e5b4fb393f
5 changed files with 143 additions and 234 deletions

View File

@ -149,8 +149,13 @@ def loggoff() -> None:
# subprocess.call(['/usr/bin/systemctl', 'reboot', '-i'])
def renameComputer(newName: str) -> None:
def renameComputer(newName: str) -> bool:
'''
Changes the computer name
Returns True if reboot needed
'''
rename(newName)
return False
def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False):

View File

@ -33,7 +33,6 @@ import signal
import typing
from . import operations
from . import renamer
from . import daemon
from ..log import logger
@ -59,37 +58,6 @@ class UDSActorSvc(daemon.Daemon, CommonService):
def markForExit(self, signum, frame) -> None:
self._isAlive = False
def rename( # pylint: disable=unused-argument
self,
name: str,
userName: typing.Optional[str] = None,
oldPassword: typing.Optional[str] = None,
newPassword: typing.Optional[str] = None
) -> None:
'''
Renames the computer, and optionally sets a password for an user
before this
'''
hostName = operations.getComputerName()
if hostName.lower() == name.lower():
logger.info('Computer name is already {}'.format(hostName))
return
logger.debug('Data: {}'.format((name, userName, oldPassword, newPassword)))
# Check for password change request for an user
if userName and oldPassword and newPassword:
logger.info('Setting password for user {}'.format(userName))
try:
operations.changeUserPassword(userName, oldPassword, newPassword)
except Exception as e:
# We stop here without even renaming computer, because the
# process has failed
raise Exception('Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(userName, e))
renamer.rename(name)
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self,
name: str,
@ -106,6 +74,7 @@ class UDSActorSvc(daemon.Daemon, CommonService):
# Linux daemon will continue running unless something is requested to
if not self.initialize():
self.notifyStop()
return # Stop daemon if initializes told to do so
logger.debug('Initialized, setting ready')

View File

@ -202,9 +202,6 @@ class CommonService:
# No ip changed, log exception for info
logger.warn('Checking ips faield: {}'.format(e))
# ***************************************************
# Methods that ARE overriden by linux & windows Actor
# ***************************************************
def rename( # pylint: disable=unused-argument
self,
name: str,
@ -216,8 +213,28 @@ class CommonService:
Invoked when broker requests a rename action
default does nothing
'''
logger.info('Base renamed invoked: {}, {}'.format(name, userName))
hostName = platform.operations.getComputerName()
if hostName.lower() == name.lower():
logger.info('Computer name is already {}'.format(hostName))
return
# Check for password change request for an user
if userName and newPassword:
logger.info('Setting password for user {}'.format(userName))
try:
platform.operations.changeUserPassword(userName, oldPassword or '', newPassword)
except Exception as e:
raise Exception('Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(userName, str(e)))
if platform.operations.renameComputer(name):
# Reboot just after renaming
logger.info('Rebooting computer to activate new name {}'.format(name))
self.reboot()
# ******************************************************
# Methods that can be overriden by linux & windows Actor
# ******************************************************
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self,
name: str,
@ -248,7 +265,7 @@ class CommonService:
'''
logger.info('Service is being stopped')
def preConnect(self, user: str, protocol: str) -> str: # pylint: disable=unused-argument
def preConnect(self, userName: str, protocol: str) -> str: # pylint: disable=unused-argument
'''
Invoked when received a PRE Connection request via REST
Base preconnect executes the preconnect command
@ -258,5 +275,5 @@ class CommonService:
return 'ok'
def onLogout(self, user: str) -> None:
logger.debug('On logout invoked for {}'.format(user))
def onLogout(self, userName: str) -> None:
logger.debug('On logout invoked for {}'.format(userName))

View File

@ -86,7 +86,7 @@ def getDomainName() -> str:
return domain
def getWindowsVersion() -> str:
def getWindowsVersion() -> typing.Tuple[int, int, int, int, str]:
return win32api.GetVersionEx()
EWX_LOGOFF = 0x00000000
@ -106,7 +106,11 @@ def reboot(flags: int = EWX_FORCEIFHUNG | EWX_REBOOT) -> None:
def loggoff() -> None:
win32api.ExitWindowsEx(EWX_LOGOFF)
def renameComputer(newName: str) -> None:
def renameComputer(newName: str) -> bool:
'''
Changes the computer name
Returns True if reboot needed
'''
# Needs admin privileges to work
if ctypes.windll.kernel32.SetComputerNameExW(DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)) == 0: # @UndefinedVariable
# win32api.FormatMessage -> returns error string
@ -115,6 +119,7 @@ def renameComputer(newName: str) -> None:
error = getErrorMessage()
computerName = win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
raise Exception('Error renaming computer from {} to {}: {}'.format(computerName, newName, error))
return True
NETSETUP_JOIN_DOMAIN = 0x00000001
NETSETUP_ACCT_CREATE = 0x00000002
@ -171,16 +176,18 @@ def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneSt
raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain, account, ', under OU {}'.format(ou) if ou is not None else '', res, error))
def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
lpUser = LPCWSTR(user)
lpOldPassword = LPCWSTR(oldPassword)
lpNewPassword = LPCWSTR(newPassword)
# lpUser = LPCWSTR(user)
# lpOldPassword = LPCWSTR(oldPassword)
# lpNewPassword = LPCWSTR(newPassword)
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
res = win32net.NetUserSetInfo(None, user, 1003, {'password': newPassword})
if res != 0:
# Log the error, and raise exception to parent
error = getErrorMessage(res)
raise Exception('Error changing password for user {}: {} {}'.format(lpUser.value, res, error))
raise Exception('Error changing password for user {}: {} {}'.format(user, res, error))
class LASTINPUTINFO(ctypes.Structure): # pylint: disable=too-few-public-methods
_fields_ = [

View File

@ -33,6 +33,7 @@ import struct
import subprocess
import os
import stat
import typing
import win32serviceutil
import win32service
@ -43,12 +44,11 @@ import win32com.client
import pythoncom
import servicemanager
from udsactor import operations
from udsactor import store
from udsactor.service import CommonService
from udsactor.service import initCfg
from . import operations
from . import store
from ..service import CommonService
from udsactor.log import logger
from ..log import logger
from .SENS import SensLogon
from .SENS import logevent
@ -57,70 +57,44 @@ from .SENS import SENSGUID_PUBLISHER
from .SENS import PROGID_EventSubscription
from .SENS import PROGID_EventSystem
POST_CMD = 'c:\\windows\\post-uds.bat'
REMOTE_USERS_SID = 'S-1-5-32-555' # Well nown sid for remote desktop users
class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
'''
This class represents a Windows Service for managing actor interactions
with UDS Broker and Machine
'''
# ServiceeFramework related
_svc_name_ = "UDSActorNG"
_svc_display_name_ = "UDS Actor Service"
_svc_description_ = "UDS Actor Management Service"
# 'System Event Notification' is the SENS service
_svc_deps_ = ['EventLog', 'SENS']
_user: typing.Optional[str]
_hWaitStop: typing.Any
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
CommonService.__init__(self)
self.hWaitStop = win32event.CreateEvent(None, 1, 0, None)
self._hWaitStop = win32event.CreateEvent(None, 1, 0, None)
self._user = None
def SvcStop(self):
def SvcStop(self) -> None:
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
self.isAlive = False
self._isAlive = False
win32event.SetEvent(self.hWaitStop)
SvcShutdown = SvcStop
def notifyStop(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STOPPED,
(self._svc_name_, ''))
def notifyStop(self) -> None:
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STOPPED, (self._svc_name_, ''))
def doWait(self, miliseconds):
def doWait(self, miliseconds: int) -> None:
win32event.WaitForSingleObject(self.hWaitStop, miliseconds)
def rename(self, name, user=None, oldPassword=None, newPassword=None):
'''
Renames the computer, and optionally sets a password for an user
before this
'''
hostName = operations.getComputerName()
if hostName.lower() == name.lower():
logger.info('Computer name is now {}'.format(hostName))
self.setReady()
return
# Check for password change request for an user
if user is not None:
logger.info('Setting password for user {}'.format(user))
try:
operations.changeUserPassword(user, oldPassword, newPassword)
except Exception as e:
# We stop here without even renaming computer, because the
# process has failed
raise Exception(
'Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(user, str(e)))
operations.renameComputer(name)
# Reboot just after renaming
logger.info('Rebooting computer to activate new name {}'.format(name))
self.reboot()
def oneStepJoin(self, name, domain, ou, account, password):
def oneStepJoin(self, name: str, domain: str, ou: str, account: str, password: str) -> None:
'''
Ejecutes the join domain in exactly one step
'''
@ -129,176 +103,126 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
# name will not change
if currName.lower() == name.lower():
self.multiStepJoin(name, domain, ou, account, password)
else:
operations.renameComputer(name)
logger.debug('Computer renamed to {} without reboot'.format(name))
operations.joinDomain(
domain, ou, account, password, executeInOneStep=True)
logger.debug(
'Requested join domain {} without errors'.format(domain))
self.reboot()
return
def multiStepJoin(self, name, domain, ou, account, password):
operations.renameComputer(name)
logger.debug('Computer renamed to {} without reboot'.format(name))
operations.joinDomain(domain, ou, account, password, executeInOneStep=True)
logger.debug('Requested join domain {} without errors'.format(domain))
self.reboot()
def multiStepJoin(self, name: str, domain: str, ou: str, account: str, password: str) -> None:
currName = operations.getComputerName()
if currName.lower() == name.lower():
currDomain = operations.getDomainName()
if currDomain is not None:
if currDomain:
# logger.debug('Name: "{}" vs "{}", Domain: "{}" vs "{}"'.format(currName.lower(), name.lower(), currDomain.lower(), domain.lower()))
logger.info('Machine {} is part of domain {}'.format(name, domain))
self.setReady()
else:
operations.joinDomain(
domain, ou, account, password, executeInOneStep=False)
operations.joinDomain(domain, ou, account, password, executeInOneStep=False)
self.reboot()
else:
operations.renameComputer(name)
logger.info(
'Rebooting computer got activate new name {}'.format(name))
logger.info('Rebooting computer got activate new name {}'.format(name))
self.reboot()
def joinDomain(self, name, domain, ou, account, password):
ver = operations.getWindowsVersion()
ver = ver[0] * 10 + ver[1]
logger.debug('Starting joining domain {} with name {} (detected operating version: {})'.format(
domain, name, ver))
# If file c:\compat.bin exists, joind domain in two steps instead one
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self,
name: str,
domain: str,
ou: str,
account: str,
password: str
) -> None:
versionData = operations.getWindowsVersion()
versionInt = versionData[0] * 10 + versionData[1]
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
# microsoft, but this also must works with it because will do a "multi
# step" join
if ver >= 60 and store.useOldJoinSystem() is False:
if versionInt >= 60 and not store.useOldJoinSystem():
self.oneStepJoin(name, domain, ou, account, password)
else:
logger.info('Using multiple step join because configuration requests to do so')
self.multiStepJoin(name, domain, ou, account, password)
def preConnect(self, user, protocol):
def preConnect(self, userName: str, protocol: str) -> str:
logger.debug('Pre connect invoked')
if protocol != 'rdp': # If connection is not using rdp, skip adding user
return 'ok'
# Well known SSID for Remote Desktop Users
REMOTE_USERS_SID = 'S-1-5-32-555'
p = win32security.GetBinarySid(REMOTE_USERS_SID)
groupName = win32security.LookupAccountSid(None, p)[0]
if protocol == 'rdp': # If connection is not using rdp, skip adding user
# Well known SSID for Remote Desktop Users
groupName = win32security.LookupAccountSid(None, win32security.GetBinarySid(REMOTE_USERS_SID))[0]
useraAlreadyInGroup = False
resumeHandle = 0
while True:
users, _, resumeHandle = win32net.NetLocalGroupGetMembers(None, groupName, 1, resumeHandle, 32768)
if user.lower() in [u['name'].lower() for u in users]:
useraAlreadyInGroup = True
break
if resumeHandle == 0:
break
useraAlreadyInGroup = False
resumeHandle = 0
while True:
users, _, resumeHandle = win32net.NetLocalGroupGetMembers(None, groupName, 1, resumeHandle, 32768)
if userName.lower() in [u['name'].lower() for u in users]:
useraAlreadyInGroup = True
break
if resumeHandle == 0:
break
if useraAlreadyInGroup is False:
logger.debug('User not in group, adding it')
self._user = user
try:
userSSID = win32security.LookupAccountName(None, user)[0]
win32net.NetLocalGroupAddMembers(None, groupName, 0, [{'sid': userSSID}])
except Exception as e:
logger.error('Exception adding user to Remote Desktop Users: {}'.format(e))
else:
self._user = None
logger.debug('User {} already in group'.format(user))
# Now try to run pre connect command
try:
pre_cmd = store.preApplication()
if os.path.isfile(pre_cmd):
if (os.stat(pre_cmd).st_mode & stat.S_IXUSR) != 0:
subprocess.call([pre_cmd, user, protocol])
else:
logger.info('PRECONNECT file exists but it it is not executable (needs execution permission by root)')
if not useraAlreadyInGroup:
logger.debug('User not in group, adding it')
self._user = userName
try:
userSSID = win32security.LookupAccountName(None, userName)[0]
win32net.NetLocalGroupAddMembers(None, groupName, 0, [{'sid': userSSID}])
except Exception as e:
logger.error('Exception adding user to Remote Desktop Users: {}'.format(e))
else:
logger.info('PRECONNECT file not found & not executed')
except Exception as e:
# Ignore output of execution command
logger.error('Executing preconnect command give')
self._user = None
logger.debug('User {} already in group'.format(userName))
return 'ok'
return super().preConnect(userName, protocol)
def ovLogon(self, username, password):
def ovLogon(self, username: str, password: str) -> str:
"""
Logon on oVirt agent
currently not used.
"""
# Compose packet for ov
ub = username.encode('utf8')
up = username.encode('utf8')
packet = struct.pack('!I', len(ub)) + ub + struct.pack('!I', len(up)) + up
usernameBytes = username.encode()
passwordBytes = username.encode()
packet = struct.pack('!I', len(usernameBytes)) + usernameBytes + struct.pack('!I', len(passwordBytes)) + passwordBytes
# Send packet with username/password to ov pipe
operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
return 'done'
def onLogout(self, user):
def onLogout(self, userName) -> None:
logger.debug('Windows onLogout invoked: {}, {}'.format(user, self._user))
try:
REMOTE_USERS_SID = 'S-1-5-32-555'
p = win32security.GetBinarySid(REMOTE_USERS_SID)
groupName = win32security.LookupAccountSid(None, p)[0]
except Exception:
logger.error('Exception getting Windows Group')
return
if self._user is not None:
if self._user:
try:
win32net.NetLocalGroupDelMembers(None, groupName, [self._user])
except Exception as e:
logger.error('Exception removing user from Remote Desktop Users: {}'.format(e))
def SvcDoRun(self): # pylint: disable=too-many-statements, too-many-branches
def SvcDoRun(self) -> None: # pylint: disable=too-many-statements, too-many-branches
'''
Main service loop
'''
try:
initCfg()
logger.debug('running SvcDoRun')
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))
logger.debug('running SvcDoRun')
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
# thread
logger.debug('Initializing com...')
pythoncom.CoInitialize()
# call the CoInitialize to allow the registration to run in an other
# thread
logger.debug('Initializing com...')
pythoncom.CoInitialize()
# ********************************************************
# * Ask brokers what to do before proceding to main loop *
# ********************************************************
while True:
brokerConnected = self.interactWithBroker()
if brokerConnected is False:
logger.debug('Interact with broker returned false, stopping service after a while')
self.notifyStop()
win32event.WaitForSingleObject(self.hWaitStop, 5000)
return
elif brokerConnected is True:
break
# If brokerConnected returns None, repeat the cycle
self.doWait(16000) # Wait for a looong while
if self.interactWithBroker() is False:
logger.debug('Interact with broker returned false, stopping service after a while')
self.notifyStop()
win32event.WaitForSingleObject(self.hWaitStop, 5000)
return
if self.isAlive is False:
logger.debug('The service is not alive after broker interaction, stopping it')
self.notifyStop()
return
if self.rebootRequested is True:
logger.debug('Reboot has been requested, stopping service')
self.notifyStop()
return
self.initIPC()
except Exception: # Any init exception wil be caught, service must be then restarted
logger.exception()
logger.debug('Exiting service with failure status')
os._exit(-1) # pylint: disable=protected-access
if not self.initialize():
self.notifyStop()
win32event.WaitForSingleObject(self.hWaitStop, 5000)
return # Stop daemon if initializes told to do so
# ********************************
# * Registers SENS subscriptions *
@ -319,18 +243,11 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
event_system.Store(PROGID_EventSubscription, event_subscription)
logger.debug('Registered SENS, running main loop')
logger.debug('Registered SENS')
logger.debug('Initialized, setting ready')
# Execute script in c:\\windows\\post-uds.bat after interacting with broker, if no reboot is requested ofc
# This will be executed only when machine gets "ready"
try:
if os.path.isfile(POST_CMD):
subprocess.call([POST_CMD, ])
else:
logger.info('POST file not found & not executed')
except Exception as e:
# Ignore output of execution command
logger.error('Executing post command give')
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady()
# *********************
# * Main Service loop *
@ -338,17 +255,15 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
# Counter used to check ip changes only once every 10 seconds, for
# example
counter = 0
while self.isAlive:
while self._isAlive:
counter += 1
# Process SENS messages, This will be a bit asyncronous (1 second
# delay)
# Process SENS messages, This will be a bit asyncronous (1 second delay)
pythoncom.PumpWaitingMessages()
if counter >= 15: # Once every 15 seconds
if counter >= 10: # Once every 15 seconds
counter = 0
try:
self.checkIpsChanged()
except Exception as e:
logger.error('Error checking ip change: {}'.format(e))
self.checkIpsChanged()
# In milliseconds, will break
win32event.WaitForSingleObject(self.hWaitStop, 1000)
@ -357,10 +272,6 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
# *******************************************
# * Remove SENS subscription before exiting *
# *******************************************
event_system.Remove(
PROGID_EventSubscription, "SubscriptionID == " + subscription_guid)
self.endIPC() # Ends IPC servers
self.endAPI() # And deinitializes REST api if needed
event_system.Remove(PROGID_EventSubscription, "SubscriptionID == " + subscription_guid)
self.notifyStop()