Example #1
0
def start_service():
    with log_context({'instance', 'opsiclientd'}):
        logger.essential("opsiclientd service start")
        #logger.debug(os.environ)
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(OpsiclientdService)
        servicemanager.StartServiceCtrlDispatcher()
Example #2
0
    def run(self):
        with log_context({'instance': 'control pipe'}):
            try:
                while not self._stopEvent.is_set():
                    if self.clientInfo:
                        self.checkConnection()
                    else:
                        # Old protocol
                        with self.comLock:
                            request = self.read()
                            if request:
                                logger.info("Received request '%s' from %s",
                                            request, self)
                                response = self.processIncomingRpc(request)
                                logger.info("Sending response '%s' to %s",
                                            response, self)
                                self.write(response)

                                if self.clientInfo:
                                    # Switch to new protocol
                                    self.executeRpc('blockLogin', [
                                        self._controller._opsiclientd.
                                        _blockLogin
                                    ],
                                                    with_lock=False)  # pylint: disable=protected-access
                    time.sleep(0.5)
            except Exception as err:  # pylint: disable=broad-except
                logger.error(err, exc_info=True)
            finally:
                self.clientDisconnected()
Example #3
0
    def executeRpc(self, method, *params):
        with log_context({'instance': 'control pipe'}):
            if not self._clients:
                raise RuntimeError("Cannot execute rpc, no client connected")

            if method == "loginUser" and not self.credentialProviderConnected(
                    login_capable=True):
                raise RuntimeError(
                    "Cannot execute rpc, no login capable opsi credential provider connected"
                )

            responses = []
            errors = []
            for client in self._clients:
                if method == "loginUser" and not client.login_capable:
                    continue
                response = client.executeRpc(method, params)
                responses.append(response)
                if response.get("error"):
                    errors.append(response["error"])

            if len(errors) == len(responses):
                raise RuntimeError(", ".join(errors))

            return responses
    def __init__(self, eventId, **kwargs):
        if not eventId:
            raise TypeError("Event id not given")
        self._id = str(eventId)  # pylint: disable=invalid-name

        # Setting context here only succeeds if id is set
        with log_context({'instance', f'event config {self._id}'}):
            self.setConfig(kwargs)
Example #5
0
 def __init__(self, service, request, resource):
     with log_context({'instance': 'software on demand'}):
         WorkerOpsiJsonRpc.__init__(self, service, request, resource)
         self._auth_module = None
         if os.name == 'posix':
             import OPSI.Backend.Manager.Authentication.PAM  # pylint: disable=import-outside-toplevel
             self._auth_module = OPSI.Backend.Manager.Authentication.PAM.PAMAuthentication(
             )
         elif os.name == 'nt':
             import OPSI.Backend.Manager.Authentication.NT  # pylint: disable=import-outside-toplevel
             self._auth_module = OPSI.Backend.Manager.Authentication.NT.NTAuthentication(
                 "S-1-5-32-544")
Example #6
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}
Example #7
0
 def run(self):
     with log_context({'instance': 'control pipe'}):
         self._running = True
         self.setup()
         try:
             while not self._stopEvent.is_set():
                 try:
                     client, client_id = self.waitForClient()
                     if self._stopEvent.is_set():
                         break
                     with self._clientLock:
                         connection = self.connection_class(
                             self, client, client_id)
                         self._clients.append(connection)
                         connection.daemon = True
                         connection.start()
                 except Exception as err1:  # pylint: disable=broad-except
                     logger.error(err1, exc_info=True)
                     self.setup()
         except Exception as err2:  # pylint: disable=broad-except
             logger.error(err2, exc_info=True)
         self._running = False
         self.teardown()
Example #8
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
def main():  # pylint: disable=too-many-locals,too-many-branches,too-many-statements
    if len(sys.argv) != 17:
        print(
            f"Usage: {os.path.basename(sys.argv[0])} <hostId> <hostKey> <controlServerPort>"
            " <logFile> <logLevel> <depotRemoteUrl> <depotDrive> <depotServerUsername> <depotServerPassword>"
            " <sessionId> <actionProcessorDesktop> <actionProcessorCommand> <actionProcessorTimeout>"
            " <runAsUser> <runAsPassword> <createEnvironment>")
        sys.exit(1)

    (  # pylint: disable=unbalanced-tuple-unpacking
        hostId, hostKey, controlServerPort, logFile, logLevel, depotRemoteUrl,
        depotDrive, depotServerUsername, depotServerPassword, sessionId,
        actionProcessorDesktop, actionProcessorCommand, actionProcessorTimeout,
        runAsUser, runAsPassword, createEnvironment) = sys.argv[1:]

    if hostKey:
        secret_filter.add_secrets(hostKey)
    if depotServerPassword:
        secret_filter.add_secrets(depotServerPassword)
    if runAsPassword:
        secret_filter.add_secrets(runAsPassword)

    init_logging(stderr_level=LOG_NONE,
                 stderr_format=DEFAULT_STDERR_LOG_FORMAT,
                 log_file=logFile,
                 file_level=int(logLevel),
                 file_format=DEFAULT_FILE_LOG_FORMAT)

    log_instance = f'{os.path.basename(sys.argv[0]).rsplit(".", 1)[0]}_s{sessionId}'
    with log_context({'instance': log_instance}):
        logger.debug(
            "Called with arguments: %s", ', '.join(
                (hostId, hostKey, controlServerPort, logFile, logLevel,
                 depotRemoteUrl, depotDrive, depotServerUsername,
                 depotServerPassword, sessionId, actionProcessorDesktop,
                 actionProcessorCommand, actionProcessorTimeout, runAsUser,
                 runAsPassword, createEnvironment)))

        language = "en"
        try:
            language = locale.getdefaultlocale()[0].split('_')[0]
        except Exception as err:  # pylint: disable=broad-except
            logger.debug("Failed to find default language: %s", err)

        def _(string):
            """ Fallback function """
            return string

        sp = None
        try:
            logger.debug("Loading translation for language '%s'", language)
            sp = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            if os.path.exists(os.path.join(sp, "site-packages")):
                sp = os.path.join(sp, "site-packages")
            sp = os.path.join(sp, 'opsiclientd_data', 'locale')
            translation = gettext.translation('opsiclientd', sp, [language])
            _ = translation.gettext
        except Exception as err:  # pylint: disable=broad-except
            logger.debug("Failed to load locale for %s from %s: %s", language,
                         sp, err)

        createEnvironment = bool(
            runAsUser and createEnvironment.lower() in ('yes', 'true', '1'))
        actionProcessorTimeout = int(actionProcessorTimeout)
        imp = None
        depotShareMounted = False
        be = None
        depot_url = urlparse(depotRemoteUrl)

        try:
            be = JSONRPCBackend(
                username=hostId,
                password=hostKey,
                address=f"https://127.0.0.1:{controlServerPort}/opsiclientd")

            if runAsUser:
                if getpass.getuser().lower() != runAsUser.lower():
                    logger.info("Impersonating user '%s'", runAsUser)
                    imp = System.Impersonate(username=runAsUser,
                                             password=runAsPassword,
                                             desktop=actionProcessorDesktop)
                    imp.start(logonType="INTERACTIVE",
                              newDesktop=False,
                              createEnvironment=createEnvironment)
            elif depot_url.scheme in ("smb", "cifs"):
                logger.info("Impersonating network account '%s'",
                            depotServerUsername)
                imp = System.Impersonate(username=depotServerUsername,
                                         password=depotServerPassword,
                                         desktop=actionProcessorDesktop)
                imp.start(logonType="NEW_CREDENTIALS")

            if depot_url.hostname.lower() not in ("127.0.0.1", "localhost",
                                                  "::1"):
                logger.notice("Mounting depot share %s", depotRemoteUrl)
                set_status_message(be, sessionId,
                                   _("Mounting depot share %s") %
                                   depotRemoteUrl)  # pylint: disable=no-member

                if runAsUser or depot_url.scheme not in ("smb", "cifs"):
                    System.mount(depotRemoteUrl,
                                 depotDrive,
                                 username=depotServerUsername,
                                 password=depotServerPassword)
                else:
                    System.mount(depotRemoteUrl, depotDrive)
                depotShareMounted = True

            logger.notice("Starting action processor")
            set_status_message(be, sessionId, _("Action processor is running"))  # pylint: disable=no-member

            if imp:
                imp.runCommand(actionProcessorCommand,
                               timeoutSeconds=actionProcessorTimeout)
            else:
                System.execute(actionProcessorCommand,
                               waitForEnding=True,
                               timeout=actionProcessorTimeout)

            logger.notice("Action processor ended")
            set_status_message(be, sessionId, _("Action processor ended"))  # pylint: disable=no-member
        except Exception as err:  # pylint: disable=broad-except
            logger.error(err, exc_info=True)
            error = f"Failed to process action requests: {err}"
            logger.error(error)
            if be:
                set_status_message(be, sessionId, error)

        if depotShareMounted:
            try:
                logger.notice("Unmounting depot share")
                System.umount(depotDrive)
            except Exception:  # pylint: disable=broad-except
                pass
        if imp:
            try:
                imp.end()
            except Exception:  # pylint: disable=broad-except
                pass

        if be:
            try:
                be.backend_exit()
            except Exception:  # pylint: disable=broad-except
                pass
Example #10
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()
Example #11
0
 def run(self):
     with log_context({'instance': 'opsiclientd'}):
         try:
             self._run()
         except Exception as err:  # pylint: disable=broad-except
             logger.error(err, exc_info=True)
Example #12
0
def main():  # pylint: disable=too-many-statements
    with log_context({'instance': os.path.basename(sys.argv[0])}):
        parser = ArgumentParser()
        parser.add_argument(
            '--version',
            action='version',
            version=f"{__version__} [python-opsi={python_opsi_version}]")
        parser.add_argument(
            '--log-level',
            default=LOG_NONE,
            type=int,
            choices=range(0, 10),
            help=
            ("Set the log level. "
             "0: nothing, 1: essential, 2: critical, 3: errors, 4: warnings, 5: notices "
             "6: infos, 7: debug messages, 8: trace messages, 9: secrets"))
        parser.add_argument('--log-file', help="Set log file")
        parser.add_argument('--address',
                            default="https://127.0.0.1:4441/opsiclientd",
                            help="Set service address")
        parser.add_argument('--username',
                            help="Username to use for service connection.")
        parser.add_argument(
            '--password',
            help=
            "Password to use for service connection (default: opsi host key).")
        parser.add_argument(
            '--timeout',
            type=int,
            default=30,
            help="Read timeout for the rpc in seconds (default: 30).")
        parser.add_argument('rpc',
                            help="The remote procedure call to execute.")

        log_file = None
        log_level = None
        address = None
        username = None
        password = None
        rpc = None
        timeout = None
        try:
            args = parser.parse_args()
            log_file = args.log_file
            log_level = args.log_level
            address = args.address
            username = args.username
            password = args.password
            rpc = args.rpc
            timeout = max(0, args.timeout)
            if not username and not password:
                try:
                    password = get_opsi_host_key()
                    secret_filter.add_secrets(password)
                except Exception as err:  # pylint: disable=broad-except
                    raise RuntimeError(
                        f"Failed to read opsi host key from config file: {err}"
                    ) from err

        except DeprecationWarning:
            # Fallback to legacy comandline arguments
            # <username> <password> <port> [debug-log-file] <rpc>
            (username, password, port, rpc) = sys.argv[1:5]  # pylint: disable=unbalanced-tuple-unpacking
            secret_filter.add_secrets(password)
            address = f"https://127.0.0.1:{port}/opsiclientd"
            if len(sys.argv) > 5:
                log_level = LOG_DEBUG
                log_file = sys.argv[4]
                rpc = sys.argv[5]

        init_logging(log_file=log_file,
                     file_level=log_level,
                     stderr_level=LOG_NONE,
                     file_format=DEFAULT_FILE_LOG_FORMAT)

        logger.debug(
            "log_file=%s, log_level=%s, address=%s, username=%s, password=%s, rpc=%s",
            log_file, log_level, address, username, password, rpc)

        try:
            jsonrpc = JSONRPCClient(  # pylint: disable=unused-variable
                address=address,
                username=username,
                password=password,
                readtimeout=timeout,
                compression=False)
            logger.notice(f"Executing: {rpc}")
            result = eval(f"jsonrpc.{rpc}")  # pylint: disable=eval-used
            print(result)
        except Exception as err:  # pylint: disable=broad-except
            logger.error(err, exc_info=True)
            print(f"Error: {err}", file=sys.stderr)
            sys.exit(1)