Esempio n. 1
0
    def create(self, fileList, baseDir='.', dereference=False):
        try:
            fileList = forceUnicodeList(fileList)
            baseDir = os.path.abspath(forceFilename(baseDir))
            dereference = forceBool(dereference)

            if not os.path.isdir(baseDir):
                raise IOError(u"Base dir '%s' not found" % baseDir)

            command = u'%s --create --quiet --verbose --format crc' % System.which(
                'cpio')
            if dereference:
                command += ' --dereference'
            if self._compression == 'gzip':
                if self.pigz_detected:
                    command += ' | %s --rsyncable' % System.which('pigz')
                else:
                    command += ' | %s --rsyncable' % System.which('gzip')
            elif self._compression == 'bzip2':
                command += ' | %s' % System.which('bzip2')
            command += ' > "%s"' % self._filename

            self._create(fileList, baseDir, command)
        except Exception as e:
            raise RuntimeError(u"Failed to create archive '%s': %s" %
                               (self._filename, e))
Esempio n. 2
0
    def rebootMachine(self, waitSeconds=3):
        self._isRebootTriggered = True
        System.setRegistryValue(System.HKEY_LOCAL_MACHINE,
                                "SOFTWARE\\opsi.org\\winst", "RebootRequested",
                                0)

        # Running in thread to avoid failure of reboot (device not ready)
        RebootThread().start()
Esempio n. 3
0
 def rebootMachine(self, waitSeconds=3):
     self._isRebootTriggered = True
     if self._controlPipe:
         try:
             self._controlPipe.executeRpc("rebootTriggered", True)
         except Exception as err:  # pylint: disable=broad-except
             logger.debug(err)
     self.clearRebootRequest()
     System.reboot(wait=waitSeconds)
Esempio n. 4
0
 def shutdownMachine(self, waitSeconds=3):
     self._isShutdownTriggered = True
     if self._controlPipe:
         try:
             self._controlPipe.executeRpc("shutdownTriggered", True)
         except Exception as err:  # pylint: disable=broad-except
             logger.debug(err)
     self.clearShutdownRequest()
     System.shutdown(wait=waitSeconds)
Esempio n. 5
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)
Esempio n. 6
0
 def run(self):
     while True:
         try:
             System.reboot(0)
             logger.notice("Reboot initiated")
             break
         except Exception as err:  # pylint: disable=broad-except
             # Device not ready?
             logger.info("Failed to initiate reboot: %s", err)
             time.sleep(1)
Esempio n. 7
0
    def extract(self, targetPath='.', patterns=[]):
        try:
            targetPath = os.path.abspath(forceFilename(targetPath))
            patterns = forceUnicodeList(patterns)
            if not os.path.isdir(targetPath):
                try:
                    os.mkdir(targetPath)
                except Exception as e:
                    raise IOError(u"Failed to create target dir '%s': %s" %
                                  (targetPath, e))

            cat = System.which('cat')
            if self._compression == 'gzip':
                if self.pigz_detected:
                    cat = u'%s --stdout --decompress' % (
                        System.which('pigz'), )
                else:
                    cat = System.which('zcat')
            elif self._compression == 'bzip2':
                cat = System.which('bzcat')

            fileCount = 0
            for filename in self.content():
                match = False
                if not patterns:
                    match = True
                else:
                    for pattern in patterns:
                        try:
                            pattern = pattern.replace('*', '.*')
                            if re.search(pattern, filename):
                                match = True
                                break
                            fileCount += 1
                        except Exception as e:
                            raise ValueError(u"Bad pattern '%s': %s" %
                                             (pattern, e))
                if match:
                    fileCount += 1

            include = ' '.join('"%s"' % pattern for pattern in patterns)

            curDir = os.path.abspath(os.getcwd())
            os.chdir(targetPath)
            try:
                command = u'%s "%s" | %s --quiet --extract --make-directories --unconditional --preserve-modification-time --verbose --no-preserve-owner %s' % (
                    cat, self._filename, System.which('cpio'), include)
                self._extract(command, fileCount)
            finally:
                os.chdir(curDir)
        except Exception as e:
            raise RuntimeError(u"Failed to extract archive '%s': %s" %
                               (self._filename, e))
Esempio n. 8
0
 def suspendBitlocker(self):  # pylint: disable=no-self-use
     logger.notice("Suspending bitlocker for one reboot if active")
     try:
         System.execute(
             "powershell.exe -ExecutionPolicy Bypass -Command \""
             "foreach($v in Get-BitLockerVolume)"
             "{if ($v.EncryptionPercentage -gt 0)"
             "{$v | Suspend-BitLocker -RebootCount 1}}\"",
             captureStderr=True,
             waitForEnding=True,
             timeout=20)
     except Exception as err:  # pylint: disable=broad-except
         logger.error("Failed to suspend bitlocker: %s", err, exc_info=True)
Esempio n. 9
0
    def deleteActionProcessorUser(self):
        if not config.get('action_processor', 'delete_user'):
            return

        if not self._actionProcessorUserName:
            return

        if not System.existsUser(username=self._actionProcessorUserName):
            return

        logger.notice("Deleting local user '%s'",
                      self._actionProcessorUserName)
        System.deleteUser(username=self._actionProcessorUserName)
        self._actionProcessorUserName = ''
        self._actionProcessorUserPassword = ''
Esempio n. 10
0
        def is_correct_pigz_version():
            ver = System.execute('pigz --version')[0][5:]

            logger.debug('Detected pigz version: %s' % (ver, ))
            versionMatches = compareVersions(ver, '>=', '2.2.3')
            logger.debug('pigz version is compatible? %s' % (versionMatches))
            return versionMatches
Esempio n. 11
0
    def _applySystemSpecificConfiguration(self):
        defaultToApply = self.WINDOWS_DEFAULT_PATHS.copy()
        if RUNNING_ON_LINUX:
            defaultToApply = self.LINUX_DEFAULT_PATHS.copy()
        elif RUNNING_ON_DARWIN:
            defaultToApply = self.MACOS_DEFAULT_PATHS.copy()

        baseDir = self._config["global"]["base_dir"]

        for key in list(self._config):
            if key in defaultToApply:
                self._config[key].update(defaultToApply[key])

        self._config['cache_service']['extension_config_dir'] = os.path.join(
            baseDir, 'opsiclientd', 'extend.d')

        if RUNNING_ON_WINDOWS:
            systemDrive = System.getSystemDrive()
            logger.debug(
                "Running on windows: adapting paths to use system drive (%s)",
                systemDrive)
            systemDrive += "\\"
            self._config['cache_service']['storage_dir'] = os.path.join(
                systemDrive, 'opsi.org', 'cache')
            self._config['global']['config_file'] = os.path.join(
                baseDir, 'opsiclientd', 'opsiclientd.conf')
            self._config['global']['log_dir'] = os.path.join(
                systemDrive, 'opsi.org', 'log')
            self._config['global']['state_file'] = os.path.join(
                systemDrive, 'opsi.org', 'opsiclientd', 'state.json')
            self._config['global']['server_cert_dir'] = os.path.join(
                systemDrive, 'opsi.org', 'tls')
            self._config['global']['timeline_db'] = os.path.join(
                systemDrive, 'opsi.org', 'opsiclientd', 'timeline.sqlite')
            self._config['system'][
                'program_files_dir'] = System.getProgramFilesDir()

            if sys.getwindowsversion()[0] == 5:  # pylint: disable=no-member
                self._config['action_processor']['run_as_user'] = '******'
        else:
            sslCertDir = os.path.join('/etc', 'opsi-client-agent')

            for certPath in ('ssl_server_key_file', 'ssl_server_cert_file'):
                if sslCertDir not in self._config['control_server'][certPath]:
                    self._config['control_server'][certPath] = os.path.join(
                        sslCertDir, self._config['control_server'][certPath])
Esempio n. 12
0
    def is_pigz_available():
        def is_correct_pigz_version():
            ver = System.execute('pigz --version')[0][5:]

            logger.debug('Detected pigz version: %s' % (ver, ))
            versionMatches = compareVersions(ver, '>=', '2.2.3')
            logger.debug('pigz version is compatible? %s' % (versionMatches))
            return versionMatches

        if not PIGZ_ENABLED:
            return False

        try:
            System.which('pigz')
            logger.debug(u'Detected "pigz".')

            return is_correct_pigz_version()
        except Exception:
            logger.debug(u'Did not detect "pigz".')
            return False
Esempio n. 13
0
    def isShutdownRequested(self):
        try:
            shutdownRequested = System.getRegistryValue(
                System.HKEY_LOCAL_MACHINE, "SOFTWARE\\opsi.org\\winst",
                "ShutdownRequested")
        except Exception as err:  # pylint: disable=broad-except
            logger.info("Failed to get shutdownRequested from registry: %s",
                        err)
            shutdownRequested = 0

        logger.notice("Shutdown request in Registry: %s", shutdownRequested)
        return forceBool(shutdownRequested)
Esempio n. 14
0
    def content(self):
        try:
            if not os.path.exists(self._filename):
                raise IOError(u"Archive file not found: '%s'" % self._filename)

            cat = System.which('cat')
            if self._compression == 'gzip':
                if self.pigz_detected:
                    cat = u'{pigz} --stdout --decompress'.format(
                        pigz=System.which('pigz'))
                else:
                    cat = System.which('zcat')
            elif self._compression == 'bzip2':
                cat = System.which('bzcat')

            return [
                unicode(line) for line in System.execute(
                    u'{cat} "{filename}" | {cpio} --quiet --extract --list'.
                    format(cat=cat,
                           filename=self._filename,
                           cpio=System.which('cpio'))) if line
            ]
        except Exception as e:
            raise RuntimeError(u"Failed to get archive content '%s': %s" %
                               (self._filename, e))
Esempio n. 15
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()])
Esempio n. 16
0
 def loginUser(self, username, password):
     for session_id in System.getActiveSessionIds(protocol="console"):
         System.lockSession(session_id)
     for _unused in range(20):
         if self._controlPipe.credentialProviderConnected(
                 login_capable=True):
             break
         time.sleep(0.5)
     if not self._controlPipe.credentialProviderConnected(
             login_capable=True):
         raise RuntimeError(
             "No login capable opsi credential provider connected")
     logger.info(
         "Login capable opsi credential provider connected, calling loginUser"
     )
     for response in self._controlPipe.executeRpc("loginUser", username,
                                                  password):
         if not response.get("error") and response.get("result"):
             return True
         raise RuntimeError(
             f"opsi credential provider failed to login user '{username}': {response.get('error')}"
         )
Esempio n. 17
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
Esempio n. 18
0
    def content(self):
        try:
            if not os.path.exists(self._filename):
                raise IOError(u"Archive file not found: '%s'" % self._filename)
            names = []
            options = u''
            if self._compression == 'gzip':
                if self.pigz_detected:
                    options += u'--use-compress-program=pigz'
                else:
                    options += u'--gunzip'
            elif self._compression == 'bzip2':
                options += u'--bzip2'

            for line in System.execute(
                    u'%s %s --list --file "%s"' %
                (System.which('tar'), options, self._filename)):
                if line:
                    names.append(unicode(line))

            return names
        except Exception as e:
            raise RuntimeError(u"Failed to get archive content '%s': %s" %
                               (self._filename, e))
Esempio n. 19
0
    def switchDesktop(self, desktop, sessionId=None):  # pylint: disable=no-self-use
        if not ('opsiclientd_rpc' in config.getDict()
                and 'command' in config.getDict()['opsiclientd_rpc']):
            raise Exception("opsiclientd_rpc command not defined")

        desktop = forceUnicode(desktop)
        if sessionId is None:
            sessionId = System.getActiveSessionId()
            if sessionId is None:
                sessionId = System.getActiveConsoleSessionId()
        sessionId = forceInt(sessionId)

        rpc = f"noop(System.switchDesktop('{desktop}'))"
        cmd = f'{config.get("opsiclientd_rpc", "command")} "{rpc}"'

        try:
            System.runCommandInSession(command=cmd,
                                       sessionId=sessionId,
                                       desktop=desktop,
                                       waitForProcessEnding=True,
                                       timeoutSeconds=60,
                                       noWindow=True)
        except Exception as err:  # pylint: disable=broad-except
            logger.error(err)
Esempio n. 20
0
            def run(self):
                self._reloadEvent.clear()
                self._reloadEvent.wait(2)

                with self._reloadLock:
                    try:
                        result = System.execute(self._reloadConfigCommand)
                        for line in result:
                            if 'error' in line:
                                raise RuntimeError(u'\n'.join(result))
                    except Exception as error:
                        logger.critical(
                            u"Failed to restart dhcpd: {0}".format(error))

                self._reloadEvent.set()
Esempio n. 21
0
    def extract(self, targetPath='.', patterns=[]):
        try:
            targetPath = os.path.abspath(forceFilename(targetPath))
            patterns = forceUnicodeList(patterns)
            if not os.path.isdir(targetPath):
                try:
                    os.mkdir(targetPath)
                except Exception as e:
                    raise IOError(u"Failed to create target dir '%s': %s" %
                                  (targetPath, e))

            options = u''
            if self._compression == 'gzip':
                if self.pigz_detected:
                    options += u'--use-compress-program=pigz'
                else:
                    options += u'--gunzip'
            elif self._compression == 'bzip2':
                options += u'--bzip2'

            fileCount = 0
            for filename in self.content():
                match = False
                if not patterns:
                    match = True
                else:
                    for pattern in patterns:
                        try:
                            pattern = pattern.replace('*', '.*')
                            if re.search(pattern, filename):
                                match = True
                                break
                            fileCount += 1
                        except Exception as e:
                            raise ValueError(u"Bad pattern '%s': %s" %
                                             (pattern, e))

                if match:
                    fileCount += 1
                else:
                    options += u' --exclude="%s"' % filename

            command = u'%s %s --directory "%s" --extract --verbose --file "%s"' % (
                System.which('tar'), options, targetPath, self._filename)
            self._extract(command, fileCount)
        except Exception as e:
            raise RuntimeError(u"Failed to extract archive '%s': %s" %
                               (self._filename, e))
Esempio n. 22
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)
Esempio n. 23
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
Esempio n. 24
0
def main(): # pylint: disable=too-many-statements,too-many-branches
	startup_log("windows.main")
	log_dir = os.path.join(System.getSystemDrive() + "\\opsi.org\\log")
	parent = psutil.Process(os.getpid()).parent()
	parent_name = parent.name() if parent else None
	# https://stackoverflow.com/questions/25770873/python-windows-service-pyinstaller-executables-error-1053

	startup_log(f"argv={sys.argv} parent_name={parent_name}")

	if len(sys.argv) == 1 and parent_name == "services.exe":
		startup_log("import start service")
		from opsiclientd.windows.service import start_service # pylint: disable=import-outside-toplevel
		startup_log("init logging")
		init_logging(stderr_level=LOG_NONE, log_dir=log_dir)
		startup_log("start service")
		start_service()
		return

	if any(arg in sys.argv[1:] for arg in ("install", "update", "remove", "start", "stop", "restart")):
		from opsiclientd.windows.service import handle_commandline # pylint: disable=import-outside-toplevel
		handle_commandline()
		return

	if any(arg in sys.argv[1:] for arg in ("setup", "--version", "--help")):
		options = parser.parse_args()
		if options.config_file:
			Config().set("global", "config_file", options.config_file)
		if options.action == "setup":
			oc_init_logging(stderr_level=options.logLevel, stderr_format=DEFAULT_STDERR_LOG_FORMAT)
			setup(full=True, options=options)
		return

	try:
		if "--elevated" not in sys.argv and parent_name != "python.exe":
			executable = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + ".exe"
			args = " ".join(sys.argv[1:])
			command = executable + " " + args + " --elevated"
			try:
				run_as_system(command)
			except Exception as err: # pylint: disable=broad-except
				print(f"Failed to run {command} as system: {err}", file=sys.stderr)
				raise
			return

		integrity_level = get_integrity_level()
		if int(integrity_level.split("-")[-1]) < 12288:
			raise RuntimeError(
				f"opsiclientd.exe must be run as service or from an elevated cmd.exe (integrity_level={integrity_level})"
			)

		if "--elevated" in sys.argv:
			sys.argv.remove("--elevated")
		options = parser.parse_args()
		if options.config_file:
			Config().set("global", "config_file", options.config_file)

		init_logging(log_dir=log_dir, stderr_level=options.logLevel, log_filter=options.logFilter)

		with opsicommon.logging.log_context({'instance', 'opsiclientd'}):
			logger.notice("Running as user: %s", win32api.GetUserName())
			if parent:
				logger.notice("Parent process: %s (%s)", parent.name(), parent.pid)
			logger.debug(os.environ)
			from .opsiclientd import opsiclientd_factory # pylint: disable=import-outside-toplevel
			opsiclientd = opsiclientd_factory()
			try:
				opsiclientd.start()
				while True:
					time.sleep(1)
			except KeyboardInterrupt:
				logger.essential("KeyboardInterrupt #1 -> stop")
				opsiclientd.stop()
				try:
					opsiclientd.join(15)
				except KeyboardInterrupt:
					logger.essential("KeyboardInterrupt #2 -> kill process")
					psutil.Process(os.getpid()).kill()
	except Exception as err:  # pylint: disable=broad-except
		logger.error(err, exc_info=True)
		time.sleep(3)
		sys.exit(1)
Esempio n. 25
0
 def clearShutdownRequest(self):
     System.setRegistryValue(System.HKEY_LOCAL_MACHINE,
                             "SOFTWARE\\opsi.org\\winst",
                             "ShutdownRequested", 0)
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
Esempio n. 27
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()
Esempio n. 28
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
Esempio n. 29
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
Esempio n. 30
0
    def __init__(self):
        baseDir = self._getBaseDirectory()

        self._temporaryConfigServiceUrls = []
        self._temporaryDepotDrive = []
        self._temporary_depot_path = None
        self._config_file_mtime = 0
        self.disabledEventTypes = []

        self._config = {
            "system": {
                "program_files_dir": "",
            },
            "global": {
                "base_dir":
                baseDir,
                "config_file":
                os.path.join(baseDir, "opsiclientd", "opsiclientd.conf"),
                "log_file":
                "opsiclientd.log",
                "log_level":
                LOG_NOTICE,
                "keep_rotated_logs":
                10,
                "max_log_size":
                5,  # In MB
                "max_log_transfer_size":
                5,  # In MB
                "host_id":
                System.getFQDN().lower(),
                "opsi_host_key":
                "",
                "wait_for_gui_timeout":
                120,
                "block_login_notifier":
                "",
                "verify_server_cert":
                False,
                "verify_server_cert_by_ca":
                False,
                "trust_uib_opsi_ca":
                True,
                "install_opsi_ca_into_os_store":
                False,
                "proxy_url":
                "system",
                "suspend_bitlocker_on_reboot":
                False,
                "ip_version":
                "auto"
            },
            "config_service": {
                "url": [],
                "compression": True,
                "connection_timeout": 10,
                "user_cancelable_after": 0,
                "sync_time_from_service": False
            },
            "depot_server": {
                # The id of the depot the client is assigned to
                "master_depot_id": "",
                # The id of the depot currently set as (dynamic) depot
                "depot_id": "",
                "url": "",
                "drive": "",
                "username": "******",
            },
            "cache_service": {
                "product_cache_max_size": 6000000000,
                "extension_config_dir": "",
                "include_product_group_ids": [],
                "exclude_product_group_ids": []
            },
            "control_server": {
                "interface":
                "0.0.0.0",
                "port":
                4441,
                "ssl_server_key_file":
                os.path.join(baseDir, "opsiclientd", "opsiclientd.pem"),
                "ssl_server_cert_file":
                os.path.join(baseDir, "opsiclientd", "opsiclientd.pem"),
                "static_dir":
                os.path.join(baseDir, "opsiclientd", "static_html"),
                "max_authentication_failures":
                5,
                "kiosk_api_active":
                True,
                "process_actions_event":
                "auto"
            },
            "notification_server": {
                "interface": "127.0.0.1",
                "start_port": 44000,
                "popup_port": 45000,
            },
            "opsiclientd_rpc": {
                "command": "",
            },
            "opsiclientd_notifier": {
                "command": "",
            },
            "action_processor": {
                "local_dir": "",
                "remote_dir": "",
                "remote_common_dir": "",
                "filename": "",
                "command": "",
                "run_as_user": "******",
                "create_user": True,
                "delete_user": True,
                "create_environment": False,
            }
        }

        self._applySystemSpecificConfiguration()