Exemplo n.º 1
0
 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)
Exemplo n.º 2
0
    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")
Exemplo n.º 3
0
    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()])
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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)
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
    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)
Exemplo n.º 9
0
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
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
    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}
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
    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)
Exemplo n.º 15
0
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))
Exemplo n.º 16
0
    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()
Exemplo n.º 17
0
    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
Exemplo n.º 18
0
 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")
Exemplo n.º 19
0
    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
Exemplo n.º 20
0
    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])
Exemplo n.º 21
0
    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)
Exemplo n.º 22
0
 def processEvent(self, event):  # pylint: disable=unused-argument
     logger.warning("%s: processEvent() not implemented", self)