def wait(self, timeout=None): self._guiStarted.wait(timeout) if self._should_stop: return if not self._guiStarted.isSet(): logger.warning("Timed out after %d seconds while waiting for GUI", timeout)
def initialize(self): if self._opsiclientd.is_stopping(): return if not self._wql: return from opsiclientd.windows import importWmiAndPythoncom # pylint: disable=import-outside-toplevel (wmi, pythoncom) = importWmiAndPythoncom() pythoncom.CoInitialize() max_attempts = 10 for attempt in range(1, 100): try: logger.debug("Creating wmi object") con = wmi.WMI(privileges=["Security"]) logger.info("Watching for wql: %s", self._wql) self._watcher = con.watch_for(raw_wql=self._wql, wmi_class='') break except Exception as err: # pylint: disable=broad-except if self._stopped: return logger.warning("Failed to create wmi watcher (wql=%s): %s", self._wql, err, exc_info=True) if attempt >= max_attempts: raise for i in range(3): # pylint: disable=unused-variable if self._stopped: return time.sleep(1) logger.debug("Initialized")
def createActionProcessorUser(self, recreate=True): if not config.get('action_processor', 'create_user'): return run_as_user = config.get('action_processor', 'run_as_user') if run_as_user.lower() == 'system': self._actionProcessorUserName = '' self._actionProcessorUserPassword = '' return if '\\' in run_as_user: logger.warning( "Ignoring domain part of user to run action processor '%s'", run_as_user) run_as_user = run_as_user.split('\\', -1) if not recreate and self._actionProcessorUserName and self._actionProcessorUserPassword and System.existsUser( username=run_as_user): return self._actionProcessorUserName = run_as_user logger.notice(f"Creating local user '{run_as_user}'") self._actionProcessorUserPassword = '******' + str( randomString(16)) + '!/%' secret_filter.add_secrets(self._actionProcessorUserPassword) if System.existsUser(username=run_as_user): System.deleteUser(username=run_as_user) System.createUser(username=run_as_user, password=self._actionProcessorUserPassword, groups=[System.getAdminGroupName()])
def setup_firewall_linux(): logger.notice("Configure iptables") port = config.get('control_server', 'port') cmds = [] if os.path.exists("/usr/bin/firewall-cmd"): # openSUSE Leap cmds.append([ "/usr/bin/firewall-cmd", f"--add-port={port}/tcp", "--zone", "public" ]) elif os.path.exists("/sbin/SuSEfirewall2"): # other SUSE cmds.append(["/sbin/SuSEfirewall2", "open", "EXT", "TCP" f"{port}"]) elif os.path.exists("/usr/sbin/ucr"): # UCS cmds.append([ "/usr/sbin/ucr", "set", f"security/packetfilter/package/opsiclientd/tcp/{port}/all=ACCEPT" ]) cmds.append(["/usr/sbin/service", "univention-firewall", "restart"]) elif os.path.exists("/sbin/iptables"): for iptables in ("iptables", "ip6tables"): cmds.append([ iptables, "-A", "INPUT", "-p", "tcp", "--dport", str(port), "-j", "ACCEPT" ]) else: logger.warning( "Could not configure firewall - no suitable executable found.") for cmd in cmds: logger.info("Running command: %s", str(cmd)) subprocess.call(cmd)
def importWmiAndPythoncom(importWmi=True, importPythoncom=True): global wmi # pylint: disable=global-statement,invalid-name global pythoncom # pylint: disable=global-statement,invalid-name if importWmi and not pythoncom: importPythoncom = True if not ((wmi or not importWmi) and (pythoncom or not importPythoncom)): logger.info("Importing wmi / pythoncom") with importWmiAndPythoncomLock: while not ((wmi or not importWmi) and (pythoncom or not importPythoncom)): try: if not pythoncom and importPythoncom: logger.debug("Importing pythoncom") import pythoncom # pylint: disable=import-error,import-outside-toplevel,redefined-outer-name if not wmi and importWmi: logger.debug("Importing wmi") pythoncom.CoInitialize() try: import wmi # pylint: disable=import-error,import-outside-toplevel,redefined-outer-name finally: pythoncom.CoUninitialize() except Exception as import_error: # pylint: disable=broad-except logger.warning( "Failed to import: %s, retrying in 2 seconds", import_error) time.sleep(2) return (wmi, pythoncom)
def set_status_message(backend, session_id, message): if session_id == "-1": logger.debug("Not setting status message") return try: backend.setStatusMessage(session_id, message) # pylint: disable=no-member except Exception as err: # pylint: disable=broad-except logger.warning("Failed to set status message: %s", err)
def setBlockLogin(self, blockLogin, handleNotifier=True): # pylint: disable=too-many-branches blockLogin = forceBool(blockLogin) changed = self._blockLogin != blockLogin self._blockLogin = blockLogin logger.notice("Block login now set to '%s'", self._blockLogin) if self._blockLogin: if not self._blockLoginEventId: self._blockLoginEventId = timeline.addEvent( title="Blocking login", description="User login blocked", category="block_login", durationEvent=True) if not self._blockLoginNotifierPid and config.get( 'global', 'block_login_notifier'): if handleNotifier and RUNNING_ON_WINDOWS: logger.info("Starting block login notifier app") # Start block login notifier on physical console sessionId = System.getActiveConsoleSessionId() while True: try: self._blockLoginNotifierPid = System.runCommandInSession( command=config.get('global', 'block_login_notifier'), sessionId=sessionId, desktop='winlogon', waitForProcessEnding=False)[2] break except Exception as err: # pylint: disable=broad-except logger.error( "Failed to start block login notifier app: %s", err) break else: if self._blockLoginEventId: timeline.setEventEnd(eventId=self._blockLoginEventId) self._blockLoginEventId = None if handleNotifier and self._blockLoginNotifierPid: try: logger.info( "Terminating block login notifier app (pid %s)", self._blockLoginNotifierPid) System.terminateProcess( processId=self._blockLoginNotifierPid) except Exception as err: # pylint: disable=broad-except logger.warning( "Failed to terminate block login notifier app: %s", err) self._blockLoginNotifierPid = None if changed and self._controlPipe: try: self._controlPipe.executeRpc("blockLogin", self._blockLogin) except Exception as rpc_error: # pylint: disable=broad-except logger.debug(rpc_error)
def processEvent(self, event): logger.notice("Processing event %s", event) description = f"Event {event.eventConfig.getId()} occurred\n" description += "Config:\n" _config = event.eventConfig.getConfig() configKeys = list(_config.keys()) configKeys.sort() for configKey in configKeys: description += f"{configKey}: {_config[configKey]}\n" logger.trace("check lock (ocd), currently %s -> locking if not True", self.eventLock.locked()) # if triggered by Basic.py fire_event, lock is already acquired if not self.eventLock.locked(): self.eventLock.acquire() # pylint: disable=consider-using-with try: timeline.addEvent(title=f"Event {event.eventConfig.getName()}", description=description, category="event_occurrence") # if processEvent is called through Event.fireEvent(), this check is already done #self.canProcessEvent(event) # A user login event should not cancel running non-login Event if event.eventConfig.actionType != 'login': self.cancelOthersAndWaitUntilReady() except (ValueError, RuntimeError) as err: # skipping execution if event cannot be created logger.warning("Could not start event: %s", err, exc_info=True) logger.trace("release lock (ocd cannot process event)") self.eventLock.release() return try: logger.debug("Creating new ept (ocd)") eventProcessingThread = EventProcessingThread(self, event) self.createActionProcessorUser(recreate=False) with self._eptListLock: self._eventProcessingThreads.append(eventProcessingThread) finally: logger.trace("release lock (ocd)") self.eventLock.release() try: eventProcessingThread.start() eventProcessingThread.join() logger.notice("Done processing event %s", event) finally: with self._eptListLock: self._eventProcessingThreads.remove(eventProcessingThread) if not self._eventProcessingThreads: try: self.deleteActionProcessorUser() except Exception as err: # pylint: disable=broad-except logger.warning(err)
def get_ips(): ips = {"127.0.0.1", "::1"} for addr in get_ip_addresses(): if addr["family"] in ("ipv4", "ipv6") and addr["address"] not in ips: if addr["address"].startswith("fe80"): continue try: ips.add(ipaddress.ip_address(addr["address"]).compressed) except ValueError as err: logger.warning(err) return ips
def isRebootRequested(self): try: rebootRequested = System.getRegistryValue( System.HKEY_LOCAL_MACHINE, "SOFTWARE\\opsi.org\\winst", "RebootRequested") except Exception as error: # pylint: disable=broad-except logger.warning("Failed to get RebootRequested from registry: %s", error) rebootRequested = 0 logger.notice("Reboot request in Registry: %s", rebootRequested) if rebootRequested == 2: # Logout logger.info("Logout requested") self.clearRebootRequest() return False return forceBool(rebootRequested)
def executeRpc(self, method, params=None, with_lock=True): params = params or [] with log_context({'instance': 'control pipe'}): rpc_id = 1 if not self.clientInfo: return { "id": rpc_id, "error": f"Cannot execute rpc, not supported by client {self}", "result": None } request = {"id": rpc_id, "method": method, "params": params} try: if with_lock: self.comLock.acquire() # pylint: disable=consider-using-with try: request_json = toJson(request) logger.info("Sending request '%s' to client %s", request_json, self) self.write(request_json) response_json = self.read() if not response_json: logger.warning( "No response for method '%s' received from client %s", request["method"], self) return {"id": rpc_id, "error": None, "result": None} logger.info("Received response '%s' from client %s", response_json, self) response = fromJson(response_json) if method == "loginUser" and response.get("result"): # Credential provider can only handle one successful login. # Ensure, that the credential provider is not used for a # second login if it keeps the pipe connection open. self.login_capable = False return response finally: if with_lock: self.comLock.release() except Exception as client_err: # pylint: disable=broad-except logger.error(client_err, exc_info=True) return {"id": rpc_id, "error": str(client_err), "result": None}
def get(self, name, default=None): # pylint: disable=too-many-return-statements,too-many-branches name = forceUnicode(name) if name == 'user_logged_in': if RUNNING_ON_WINDOWS: for session in System.getActiveSessionInformation(): if session["UserName"] != OPSI_SETUP_USER_NAME: return True return False if RUNNING_ON_LINUX: for proc in psutil.process_iter(): try: env = proc.environ() if env.get("DISPLAY") and proc.uids()[0] >= 1000: return True except psutil.AccessDenied: pass return False if RUNNING_ON_DARWIN: # TODO return True return False if name == 'products_cached': return self._state.get('product_cache_service', {}).get('products_cached', default) if name == 'config_cached': return self._state.get('config_cache_service', {}).get('config_cached', default) if "cancel_counter" in name: return self._state.get(name, 0) if name == 'installation_pending': return forceBool(self._state.get('installation_pending', False)) try: return self._state[name] except KeyError: logger.warning("Unknown state name '%s', returning default '%s'", name, default) return default
def getCurrentActiveDesktopName(self, sessionId=None): if not RUNNING_ON_WINDOWS: return None if not ('opsiclientd_rpc' in config.getDict() and 'command' in config.getDict()['opsiclientd_rpc']): raise Exception("opsiclientd_rpc command not defined") if sessionId is None: sessionId = System.getActiveSessionId() if sessionId is None: sessionId = System.getActiveConsoleSessionId() rpc = f"setCurrentActiveDesktopName(\"{sessionId}\", System.getActiveDesktopName())" cmd = config.get('opsiclientd_rpc', 'command') + ' "' + rpc.replace( '"', '\\"') + '"' try: System.runCommandInSession(command=cmd, sessionId=sessionId, desktop="winlogon", waitForProcessEnding=True, timeoutSeconds=60, noWindow=True) except Exception as err: # pylint: disable=broad-except logger.error(err) desktop = self._currentActiveDesktopName.get(sessionId) if not desktop: logger.warning( "Failed to get current active desktop name for session %s, using 'default'", sessionId) desktop = "default" self._currentActiveDesktopName[sessionId] = desktop logger.debug( "Returning current active dektop name '%s' for session %s", desktop, sessionId) return desktop
def cleanup_opsi_setup_user(self, keep_sid: str = None): # pylint: disable=no-self-use,too-many-locals keep_profile = None with winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" ) as key: for idx in range(1024): try: keyname = winreg.EnumKey(key, idx) with winreg.OpenKey(key, keyname) as subkey: profile_path = winreg.QueryValueEx( subkey, "ProfileImagePath")[0] if keep_sid and keyname == keep_sid: keep_profile = profile_path continue username = profile_path.split("\\")[-1].split(".")[0] if not username.startswith(OPSI_SETUP_USER_NAME): continue sid = win32security.ConvertStringSidToSid(keyname) try: #win32profile.DeleteProfile(sid) username, _domain, _type = win32security.LookupAccountSid( None, sid) logger.info("Deleting user '%s'", username) win32net.NetUserDel(None, username) except Exception: # pylint: disable=broad-except pass logger.info("Deleting '%s' from ProfileList", keyname) except WindowsError as err: if err.errno == 22: # pylint: disable=no-member # No more subkeys break logger.debug(err) # takeown parameter /d is localized 😠res = subprocess.run("choice <nul 2>nul", capture_output=True, check=False, shell=True) yes = res.stdout.decode().split(",")[0].lstrip("[").strip() for pdir in glob.glob(f"c:\\users\\{OPSI_SETUP_USER_NAME}*"): if keep_profile and keep_profile.lower() == pdir.lower(): continue logger.info("Deleting user dir '%s'", pdir) for cmd, shell, exit_codes_success in (([ 'takeown', '/a', '/d', yes, '/r', '/f', pdir ], False, [0, 1]), (['del', pdir, "/f", "/s", "/q"], True, [0]), (['rd', pdir, "/s", "/q"], True, [0])): logger.info("Executing: %s", cmd) res = subprocess.run(cmd, shell=shell, capture_output=True, check=False) out = res.stdout.decode(errors="replace") + res.stderr.decode( errors="replace") if res.returncode not in exit_codes_success: logger.warning("Command %s failed with exit code %s: %s", cmd, res.returncode, out) else: logger.info("Command %s successful: %s", cmd, out)
def setup_ssl(full: bool = False): # pylint: disable=too-many-branches,too-many-statements,too-many-locals logger.info("Checking server cert") key_file = config.get('control_server', 'ssl_server_key_file') cert_file = config.get('control_server', 'ssl_server_cert_file') server_cn = config.get('global', 'host_id') if not server_cn: server_cn = get_fqdn() create = False exists_self_signed = False if not os.path.exists(key_file) or not os.path.exists(cert_file): create = True else: try: with open(cert_file, "r", encoding="utf-8") as file: srv_crt = load_certificate(FILETYPE_PEM, file.read()) enddate = datetime.datetime.strptime( srv_crt.get_notAfter().decode("utf-8"), "%Y%m%d%H%M%SZ") diff = (enddate - datetime.datetime.now()).days logger.info("Server cert '%s' will expire in %d days", srv_crt.get_subject().CN, diff) if diff <= CERT_RENEW_DAYS: logger.notice( "Server cert '%s' will expire in %d days, needing new cert", srv_crt.get_subject().CN, diff) create = True elif server_cn != srv_crt.get_subject().CN: logger.notice( "Server CN has changed from '%s' to '%s', needing new cert", srv_crt.get_subject().CN, server_cn) create = True elif full and srv_crt.get_issuer().CN == srv_crt.get_subject( ).CN: logger.notice( "Self signed certificate found, needing new cert") create = True exists_self_signed = True if not create: with open(key_file, "r", encoding="utf-8") as file: srv_key = load_privatekey(FILETYPE_PEM, file.read()) except CryptoError as err: logger.error(err) create = True if not create: logger.info("Server cert is up to date") return (srv_crt, srv_key) = (None, None) try: logger.notice("Fetching tls server certificate from config service") config.readConfigFile() jsonrpc_client = JSONRPCClient( address=config.get('config_service', 'url')[0], username=config.get('global', 'host_id'), password=config.get('global', 'opsi_host_key'), proxy_url=config.get('global', 'proxy_url')) try: pem = jsonrpc_client.host_getTLSCertificate(server_cn) # pylint: disable=no-member srv_crt = load_certificate(FILETYPE_PEM, pem) srv_key = load_privatekey(FILETYPE_PEM, pem) finally: jsonrpc_client.disconnect() except Exception as err: # pylint: disable=broad-except logger.warning("Failed to fetch tls certificate from server: %s", err) if exists_self_signed: return if not srv_crt or not srv_key: logger.notice("Creating self-signed tls server certificate") (ca_cert, ca_key) = create_ca(subject={"commonName": server_cn}, valid_days=10000) (srv_crt, srv_key) = create_server_cert(subject={"commonName": server_cn}, valid_days=10000, ip_addresses=get_ips(), hostnames=get_hostnames(), ca_key=ca_key, ca_cert=ca_cert) # key_file and cert_file can be the same file if os.path.exists(key_file): os.unlink(key_file) if os.path.exists(cert_file): os.unlink(cert_file) if not os.path.exists(os.path.dirname(key_file)): os.makedirs(os.path.dirname(key_file)) with open(key_file, "a", encoding="utf-8") as out: out.write(as_pem(srv_key)) if not os.path.exists(os.path.dirname(cert_file)): os.makedirs(os.path.dirname(cert_file)) with open(cert_file, "a", encoding="utf-8") as out: out.write(as_pem(srv_crt))
def showPopup(self, message, mode='prepend', addTimestamp=True, displaySeconds=0): # pylint: disable=too-many-branches,too-many-statements, too-many-locals if mode not in ('prepend', 'append', 'replace'): mode = 'prepend' port = config.get('notification_server', 'popup_port') if not port: raise Exception('notification_server.popup_port not defined') notifierCommand = config.get('opsiclientd_notifier', 'command') if not notifierCommand: raise Exception('opsiclientd_notifier.command not defined') notifierCommand = f'{notifierCommand} -s {os.path.join("notifier", "popup.ini")}' if addTimestamp: message = "=== " + time.strftime( "%Y-%m-%d %H:%M:%S") + " ===\n" + message with self._popupNotificationLock: # pylint: disable=too-many-nested-blocks if (mode in ('prepend', 'append') and self._popupNotificationServer and self._popupNotificationServer.isListening()): # Already runnning try: for subject in self._popupNotificationServer.getSubjects(): if subject.getId() == 'message': if mode == 'prepend': message = message + "\n\n" + subject.getMessage( ) else: message = subject.getMessage( ) + "\n\n" + message break except Exception as err: # pylint: disable=broad-except logger.warning(err, exc_info=True) self.hidePopup() popupSubject = MessageSubject(id='message') choiceSubject = ChoiceSubject(id='choice') popupSubject.setMessage(message) logger.notice( "Starting popup message notification server on port %d", port) try: self._popupNotificationServer = NotificationServer( address="127.0.0.1", start_port=port, subjects=[popupSubject, choiceSubject]) self._popupNotificationServer.daemon = True with log_context({'instance': 'popup notification server'}): if not self._popupNotificationServer.start_and_wait( timeout=30): raise Exception( "Timed out while waiting for notification server") except Exception as err: # pylint: disable=broad-except logger.error("Failed to start notification server: %s", err) raise notifierCommand = notifierCommand.replace( '%port%', str(self._popupNotificationServer.port)).replace( '%id%', "popup") choiceSubject.setChoices([_('Close')]) choiceSubject.setCallbacks([self.popupCloseCallback]) sessionIds = System.getActiveSessionIds() if not sessionIds: sessionIds = [System.getActiveConsoleSessionId()] for sessionId in sessionIds: desktops = [None] if RUNNING_ON_WINDOWS: desktops = ["default", "winlogon"] for desktop in desktops: try: System.runCommandInSession(command=notifierCommand, sessionId=sessionId, desktop=desktop, waitForProcessEnding=False) except Exception as err: # pylint: disable=broad-except logger.error( "Failed to start popup message notifier app in session %s on desktop %s: %s", sessionId, desktop, err) class PopupClosingThread(threading.Thread): def __init__(self, opsiclientd, seconds): super().__init__() self.opsiclientd = opsiclientd self.seconds = seconds self.stopped = False def stop(self): self.stopped = True def run(self): while not self.stopped: time.sleep(1) if time.time() > self.seconds: break if not self.stopped: logger.debug("hiding popup window") self.opsiclientd.hidePopup() # last popup decides end time (even if unlimited) if self._popupClosingThread and self._popupClosingThread.is_alive( ): self._popupClosingThread.stop() if displaySeconds > 0: logger.debug("displaying popup for %s seconds", displaySeconds) self._popupClosingThread = PopupClosingThread( self, time.time() + displaySeconds) self._popupClosingThread.start()
def run(self): # pylint: disable=too-many-locals,too-many-branches,too-many-statements with log_context({'instance': 'service connection'}): logger.debug("ServiceConnectionThread started...") self.running = True self.connected = False self.cancelled = False try: # pylint: disable=too-many-nested-blocks verify_server_cert = ( config.get('global', 'verify_server_cert') or config.get('global', 'verify_server_cert_by_ca')) ca_cert_file = config.ca_cert_file self.prepare_ca_cert_file() compression = config.get('config_service', 'compression') if "localhost" in self._configServiceUrl or "127.0.0.1" in self._configServiceUrl: compression = False verify_server_cert = False if verify_server_cert: if os.path.exists(ca_cert_file): logger.info( "Server verification enabled, using CA cert file '%s'", ca_cert_file) else: logger.error( "Server verification enabled, but CA cert file '%s' not found, skipping verification", ca_cert_file) ca_cert_file = None verify_server_cert = False tryNum = 0 while not self.cancelled and not self.connected: tryNum += 1 try: logger.notice("Connecting to config server '%s' #%d", self._configServiceUrl, tryNum) self.setStatusMessage( _("Connecting to config server '%s' #%d") % (self._configServiceUrl, tryNum)) if len(self._username.split('.')) < 3: raise Exception( f"Domain missing in username '{self._username}'" ) logger.debug( "JSONRPCBackend address=%s, verify_server_cert=%s, ca_cert_file=%s, proxy_url=%s, application=%s", self._configServiceUrl, verify_server_cert, ca_cert_file, config.get('global', 'proxy_url'), f"opsiclientd/{__version__}") self.configService = JSONRPCBackend( address=self._configServiceUrl, username=self._username, password=self._password, verify_server_cert=verify_server_cert, ca_cert_file=ca_cert_file, proxy_url=config.get('global', 'proxy_url'), application=f"opsiclientd/{__version__}", compression=compression, ip_version=config.get('global', 'ip_version')) self.configService.accessControl_authenticated() # pylint: disable=no-member self.connected = True self.connectionError = None serverVersion = self.configService.serverVersion self.setStatusMessage( _("Connected to config server '%s'") % self._configServiceUrl) logger.notice( "Connected to config server '%s' (name=%s, version=%s)", self._configServiceUrl, self.configService.serverName, serverVersion) if serverVersion and (serverVersion[0] > 4 or (serverVersion[0] == 4 and serverVersion[1] > 1)): if not os.path.exists( config.ca_cert_file ) or verify_server_cert or config.get( 'global', 'install_opsi_ca_into_os_store'): # Renew CA if not exists or connection is verified try: update_ca_cert(self.configService, allow_remove=True) except Exception as err: # pylint: disable=broad-except logger.error(err, exc_info=True) except OpsiServiceVerificationError as verificationError: self.connectionError = forceUnicode(verificationError) self.setStatusMessage( _("Failed to connect to config server '%s': Service verification failure" ) % self._configServiceUrl) logger.error( "Failed to connect to config server '%s': %s", self._configServiceUrl, verificationError) break except Exception as error: # pylint: disable=broad-except self.connectionError = forceUnicode(error) self.setStatusMessage( _("Failed to connect to config server '%s': %s") % (self._configServiceUrl, forceUnicode(error))) logger.info( "Failed to connect to config server '%s': %s", self._configServiceUrl, error) logger.debug(error, exc_info=True) if isinstance(error, OpsiAuthenticationError): fqdn = System.getFQDN() try: fqdn = forceFqdn(fqdn) except Exception as fqdnError: # pylint: disable=broad-except logger.warning( "Failed to get fqdn from os, got '%s': %s", fqdn, fqdnError) break if self._username != fqdn: logger.notice( "Connect failed with username '%s', got fqdn '%s' from os, trying fqdn", self._username, fqdn) self._username = fqdn else: break if 'is not supported by the backend' in self.connectionError.lower( ): try: from cryptography.hazmat.backends import default_backend # pylint: disable=import-outside-toplevel logger.debug( "Got the following crypto backends: %s", default_backend()._backends) # pylint: disable=no-member,protected-access except Exception as cryptoCheckError: # pylint: disable=broad-except logger.debug( "Failed to get info about installed crypto modules: %s", cryptoCheckError) for _unused in range( 3): # Sleeping before the next retry time.sleep(1) except Exception as err: # pylint: disable=broad-except logger.error(err, exc_info=True) finally: self.running = False
def canProcessEvent(self, event, can_cancel=False): # pylint: disable=unused-argument logger.warning("%s: canProcessEvent() not implemented", self) raise NotImplementedError(f"{self}: canProcessEvent() not implemented")
def connectConfigService(self, allowTemporaryConfigServiceUrls=True): # pylint: disable=too-many-locals,too-many-branches,too-many-statements try: # pylint: disable=too-many-nested-blocks configServiceUrls = config.getConfigServiceUrls( allowTemporaryConfigServiceUrls=allowTemporaryConfigServiceUrls ) if not configServiceUrls: raise Exception("No service url defined") if self._loadBalance and (len(configServiceUrls) > 1): random.shuffle(configServiceUrls) for urlIndex, configServiceURL in enumerate(configServiceUrls): self._configServiceUrl = configServiceURL kwargs = self.connectionThreadOptions() logger.debug("Creating ServiceConnectionThread (url: %s)", self._configServiceUrl) serviceConnectionThread = ServiceConnectionThread( configServiceUrl=self._configServiceUrl, username=config.get('global', 'host_id'), password=config.get('global', 'opsi_host_key'), **kwargs) serviceConnectionThread.daemon = True self.connectionStart(self._configServiceUrl) cancellableAfter = forceInt( config.get('config_service', 'user_cancelable_after')) timeout = forceInt( config.get('config_service', 'connection_timeout')) logger.info( "Starting ServiceConnectionThread, timeout is %d seconds", timeout) serviceConnectionThread.start() for _unused in range(5): if serviceConnectionThread.running: break time.sleep(1) logger.debug("ServiceConnectionThread started") while serviceConnectionThread.running and timeout > 0: if self._should_stop: return logger.debug( "Waiting for ServiceConnectionThread (timeout: %d, alive: %s, cancellable in: %d)", timeout, serviceConnectionThread.is_alive(), cancellableAfter) self.connectionTimeoutChanged(timeout) if cancellableAfter > 0: cancellableAfter -= 1 if cancellableAfter == 0: self.connectionCancelable( serviceConnectionThread.stopConnectionCallback) time.sleep(1) timeout -= 1 if serviceConnectionThread.cancelled: self.connectionCanceled() elif serviceConnectionThread.running: serviceConnectionThread.stop() if urlIndex + 1 < len(configServiceUrls): # Try next url continue self.connectionTimedOut() if not serviceConnectionThread.connected: self.connectionFailed( serviceConnectionThread.connectionError) if serviceConnectionThread.connected and ( serviceConnectionThread.getUsername() != config.get( 'global', 'host_id')): config.set('global', 'host_id', serviceConnectionThread.getUsername().lower()) logger.info("Updated host_id to '%s'", config.get('global', 'host_id')) config.updateConfigFile() if serviceConnectionThread.connected and forceBool( config.get('config_service', 'sync_time_from_service')): logger.info("Syncing local system time from service") try: System.setLocalSystemTime( serviceConnectionThread.configService. getServiceTime(utctime=True)) # pylint: disable=no-member except Exception as err: # pylint: disable=broad-except logger.error("Failed to sync time: '%s'", err) if ("localhost" not in configServiceURL and "127.0.0.1" not in configServiceURL): try: config.set( 'depot_server', 'master_depot_id', serviceConnectionThread.configService.getDepotId( config.get('global', 'host_id')) # pylint: disable=no-member ) config.updateConfigFile() except Exception as err: # pylint: disable=broad-except logger.warning(err) self._configService = serviceConnectionThread.configService self.connectionEstablished() except Exception: self.disconnectConfigService() raise
def set(self, section, option, value): # pylint: disable=too-many-branches,too-many-statements if not section: section = 'global' section = str(section).strip().lower() if section == 'system': return option = str(option).strip().lower() if isinstance(value, str): value = value.strip() # Rename legacy options if option == 'warning_time': option = 'action_warning_time' elif option == 'user_cancelable': option = 'action_user_cancelable' elif option == 'w10bitlockersuspendonreboot': option = 'suspend_bitlocker_on_reboot' # Check if empty value is allowed if ( # pylint: disable=too-many-boolean-expressions value == '' and 'command' not in option and 'productids' not in option and 'exclude_product_group_ids' not in option and 'include_product_group_ids' not in option and 'proxy_url' not in option and 'working_window' not in option): if section == 'action_processor' and option == 'remote_common_dir': return logger.warning("Refusing to set empty value config %s.%s", section, option) return if section == 'depot_server' and option == 'drive': if (RUNNING_ON_LINUX or RUNNING_ON_DARWIN) and not value.startswith("/"): logger.warning("Refusing to set %s.%s to '%s' on posix", section, option, value) return # Preprocess values, convert to correct type if option in ('exclude_product_group_ids', 'include_product_group_ids'): if not isinstance(value, list): value = [x.strip() for x in value.split(",") if x.strip()] value = forceList(value) if RUNNING_ON_WINDOWS and (option.endswith("_dir") or option.endswith("_file")): if ":" in value and ":\\" not in value: logger.warning("Correcting path '%s' to '%s'", value, value.replace(":", ":\\")) value = value.replace(":", ":\\") if option.endswith("_dir") or option.endswith("_file"): arch = '64' if '64' in platform.architecture()[0] else '32' value = value.replace('%arch%', arch) if section.startswith("event_") or section.startswith("precondition_"): if option.endswith('_warning_time') or option.endswith( '_user_cancelable'): try: value = int(value) except ValueError: value = 0 elif option in ('active', ): value = forceBool(value) elif section in self._config and option in self._config[section]: if section == 'config_service' and option == 'url': urls = value if not isinstance(urls, list): urls = str(urls).split(',') value = [] for url in urls: url = url.strip() if not re.search('https?://[^/]+', url): logger.error("Bad config service url '%s'", url) if not url in value: value.append(url) else: try: if isinstance(self._config[section][option], bool): value = forceBool(value) elif self._config[section][option] is not None: _type = type(self._config[section][option]) value = _type(value) except ValueError as err: logger.error( "Failed to set value '%s' for config %s.%s: %s", value, section, option, err) return # Check / correct value if option in ('connection_timeout', 'user_cancelable_after') and value < 0: value = 0 elif option == 'opsi_host_key': if len(value) != 32: raise ValueError("Bad opsi host key, length != 32") secret_filter.add_secrets(value) elif option in ('depot_id', 'host_id'): value = forceHostId(value.replace('_', '-')) else: logger.warning( "Refusing to set value '%s' for invalid config %s.%s", value, section, option) return logger.info("Setting config %s.%s to %r", section, option, value) if section not in self._config: self._config[section] = {} self._config[section][option] = value if section == 'global' and option == 'log_level': logging_config(file_level=self._config[section][option]) elif section == 'global' and option == 'server_cert_dir': if not os.path.exists(self._config[section][option]): os.makedirs(self._config[section][option])
def updateConfigFile(self, force=False): # pylint: disable=too-many-branches logger.info("Updating config file: '%s'", self.get('global', 'config_file')) if self._config_file_mtime and os.path.getmtime( self.get('global', 'config_file')) > self._config_file_mtime: msg = "overwriting changes is forced" if force else "keeping file as is" logger.warning( "The config file '%s' has been changed by another program, %s", self.get('global', 'config_file'), msg) if not force: return try: configFile = IniFile(filename=self.get('global', 'config_file'), raw=True) configFile.setKeepOrdering(True) (config, comments) = configFile.parse(returnComments=True) changed = False for (section, values) in self._config.items(): if not isinstance(values, dict): continue if section == 'system': continue if not config.has_section(section): logger.debug("Config changed - new section: %s", section) config.add_section(section) changed = True for (option, value) in values.items(): if (section == 'global') and (option == 'config_file'): # Do not store these option continue if isinstance(value, list): value = ', '.join(forceUnicodeList(value)) elif isinstance(value, bool): value = str(value).lower() else: value = forceUnicode(value) if value.lower() in ("true", "false"): value = value.lower() if not config.has_option(section, option): logger.debug("Config changed - new option: %s.%s = %s", section, option, value) config.set(section, option, value) changed = True elif config.get(section, option) != value: logger.debug( "Config changed - changed value: %s.%s = %s => %s", section, option, config.get(section, option), value) config.set(section, option, value) changed = True for option in config.options(section): if option not in values: logger.info("Removing obsolete config option: %s.%s", section, option) config.remove_option(section, option) changed = True if changed: # Write back config file if changed configFile.generate(config, comments=comments) logger.notice("Config file '%s' written", self.get('global', 'config_file')) self._config_file_mtime = os.path.getmtime( self.get('global', 'config_file')) else: logger.info( "No need to write config file '%s', config file is up to date", self.get('global', 'config_file')) except Exception as err: # pylint: disable=broad-except # An error occured while trying to write the config file logger.error(err, exc_info=True) logger.error("Failed to write config file '%s': %s", self.get('global', 'config_file'), err)
def processEvent(self, event): # pylint: disable=unused-argument logger.warning("%s: processEvent() not implemented", self)