mirror of
				https://github.com/altlinux/gpupdate.git
				synced 2025-11-04 08:24:14 +03:00 
			
		
		
		
	Compare commits
	
		
			134 Commits
		
	
	
		
			0.11.3-alt
			...
			freeipa_ba
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bbbc0b8289 | ||
| 
						 | 
					d975cd2f10 | ||
| 
						 | 
					cb9c70d6c1 | ||
| 
						 | 
					99feb569a2 | ||
| 
						 | 
					cd1a2fc042 | ||
| 
						 | 
					5e918900c6 | ||
| 
						 | 
					63e5ffc3f8 | ||
| 
						 | 
					01d219cb8e | ||
| 
						 | 
					6af54ff17d | ||
| 
						 | 
					238d1f4784 | ||
| 
						 | 
					b3253bd684 | ||
| 
						 | 
					66b17be85b | ||
| 
						 | 
					bea7fe9803 | ||
| 
						 | 
					f36b362523 | ||
| 
						 | 
					abfb756edb | ||
| 
						 | 
					0578e21521 | ||
| 
						 | 
					02bd6773aa | ||
| 
						 | 
					927c3ceb2f | ||
| 
						 | 
					a329f601f7 | ||
| 
						 | 
					d4f12dacfa | ||
| 
						 | 
					4d05358790 | ||
| 
						 | 
					bc4bb96b03 | ||
| 
						 | 
					5588be1daa | ||
| 
						 | 
					bde48cbedf | ||
| 
						 | 
					43d32c3882 | ||
| 
						 | 
					0932d1da26 | ||
| 
						 | 
					2a4375c6fb | ||
| 
						 | 
					ba11149983 | ||
| 
						 | 
					56008e7e1c | ||
| 
						 | 
					9325c241ef | ||
| 
						 | 
					3c0c722818 | ||
| 
						 | 
					9424c2c8e8 | ||
| 
						 | 
					9065352bb0 | ||
| 
						 | 
					9034d4ba4c | ||
| 
						 | 
					2e6c76337b | ||
| 
						 | 
					a3398e0307 | ||
| 
						 | 
					c7192773fd | ||
| 
						 | 
					93bcac5f19 | ||
| 
						 | 
					967687497c | ||
| 
						 | 
					3797993209 | ||
| 
						 | 
					04831c4dbd | ||
| 
						 | 
					316c0881a9 | ||
| 
						 | 
					22d0c87538 | ||
| 
						 | 
					2c66ad9bc1 | ||
| 
						 | 
					5fe0b6f418 | ||
| 
						 | 
					829825060b | ||
| 
						 | 
					463620ff25 | ||
| 
						 | 
					ab632a8177 | ||
| 
						 | 
					5c47ebb6c5 | ||
| 
						 | 
					6a840674ca | ||
| 
						 | 
					a6f6b021fa | ||
| 
						 | 
					0f4066e0f0 | ||
| 
						 | 
					030e69cb86 | ||
| 
						 | 
					5f94fad90b | ||
| 
						 | 
					156918ad3b | ||
| 
						 | 
					6df5a5754f | ||
| 
						 | 
					dda57ed179 | ||
| 
						 | 
					99595c85d3 | ||
| 
						 | 
					e25c5844a9 | ||
| 
						 | 
					8e1a76552f | ||
| 
						 | 
					1f6776912d | ||
| 
						 | 
					3e889622b1 | ||
| 
						 | 
					1c827d4533 | ||
| 
						 | 
					ce660afcbd | ||
| 
						 | 
					5b1a928291 | ||
| 
						 | 
					a77a6e3c6f | ||
| 
						 | 
					25a784fa2e | ||
| 
						 | 
					6378c8c78b | ||
| 
						 | 
					9ad7440c8b | ||
| 
						 | 
					2a5642a76d | ||
| dbff83050b | |||
| ed1b2aa39e | |||
| 
						 | 
					02701136c0 | ||
| 
						 | 
					408d221c3d | ||
| 
						 | 
					67a02a4623 | ||
| 7a0af6ab9b | |||
| 
						 | 
					ce6e49443f | ||
| 
						 | 
					433d312c0f | ||
| 
						 | 
					2ec68dd95a | ||
| 
						 | 
					3990f876a4 | ||
| 
						 | 
					1f541914cd | ||
| 
						 | 
					dc054008fd | ||
| 
						 | 
					aa4bf9a7c8 | ||
| 
						 | 
					99a6e85ccf | ||
| 
						 | 
					79ef884f7d | ||
| 
						 | 
					0abc5b0282 | ||
| dce52c4d9c | |||
| 
						 | 
					4d5969a5fa | ||
| 
						 | 
					3263a4cfd3 | ||
| 
						 | 
					0685b9e492 | ||
| 
						 | 
					7188c70a77 | ||
| 
						 | 
					2edc5c326c | ||
| 
						 | 
					39b92ce763 | ||
| 
						 | 
					620010e1ab | ||
| 
						 | 
					b87e8b218f | ||
| 
						 | 
					df0f806035 | ||
| 
						 | 
					7e8657939f | ||
| 
						 | 
					a879d5ad52 | ||
| 
						 | 
					c097769681 | ||
| 
						 | 
					a85158ce3c | ||
| 
						 | 
					f79b283574 | ||
| 
						 | 
					b791f3d5eb | ||
| 
						 | 
					b16460309a | ||
| 
						 | 
					40cf97989e | ||
| 
						 | 
					71eeb1d5a0 | ||
| 
						 | 
					f45fc7092d | ||
| 
						 | 
					e537b3846a | ||
| 
						 | 
					64581f60d2 | ||
| 
						 | 
					1436ee201e | ||
| 
						 | 
					0051e001a8 | ||
| 
						 | 
					d4eb4263fa | ||
| 
						 | 
					a99ed2db2a | ||
| 
						 | 
					8bc4375339 | ||
| 
						 | 
					f24038b288 | ||
| 
						 | 
					96ec5cc690 | ||
| 
						 | 
					e88278fb47 | ||
| 
						 | 
					4be89029aa | ||
| 
						 | 
					b981744d75 | ||
| 
						 | 
					760a1d8b90 | ||
| 
						 | 
					cb035fd56e | ||
| 
						 | 
					e56293e768 | ||
| 
						 | 
					0c0f7d223b | ||
| 
						 | 
					3c09737aa7 | ||
| 
						 | 
					0027b5aa96 | ||
| df8984dd65 | |||
| 
						 | 
					5f8c75e27c | ||
| 
						 | 
					03b031734a | ||
| 77c0d60b7d | |||
| 51b744f94b | |||
| cdd9d84037 | |||
| 
						 | 
					4de1946e32 | ||
| 
						 | 
					73759857b3 | ||
| 
						 | 
					b3e222ae55 | ||
| 
						 | 
					8a2c9554f7 | 
@@ -24,6 +24,8 @@ from util.logging import log
 | 
			
		||||
from util.config import GPConfig
 | 
			
		||||
from util.util import get_uid_by_username, touch_file
 | 
			
		||||
from util.paths import get_dconf_config_file
 | 
			
		||||
from util.ipacreds import ipacreds
 | 
			
		||||
from .freeipa_backend import freeipa_backend
 | 
			
		||||
from storage.dconf_registry import Dconf_registry, create_dconf_ini_file, add_preferences_to_global_registry_dict
 | 
			
		||||
 | 
			
		||||
def backend_factory(dc, username, is_machine, no_domain = False):
 | 
			
		||||
@@ -52,6 +54,20 @@ def backend_factory(dc, username, is_machine, no_domain = False):
 | 
			
		||||
            logdata = dict({'error': str(exc)})
 | 
			
		||||
            log('E7', logdata)
 | 
			
		||||
 | 
			
		||||
    if config.get_backend() == 'freeipa' and not no_domain:
 | 
			
		||||
        try:
 | 
			
		||||
            if not dc:
 | 
			
		||||
                dc = config.get_dc()
 | 
			
		||||
                if dc:
 | 
			
		||||
                    ld = {'dc': dc}
 | 
			
		||||
                    log('D52', ld)
 | 
			
		||||
            ipac = ipacreds()
 | 
			
		||||
            domain = ipac.get_domain()
 | 
			
		||||
            back = freeipa_backend(ipac, username, domain, is_machine)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'error': str(exc)}
 | 
			
		||||
            log('E77', logdata)
 | 
			
		||||
 | 
			
		||||
    if config.get_backend() == 'local' or no_domain:
 | 
			
		||||
        log('D8')
 | 
			
		||||
        try:
 | 
			
		||||
@@ -62,7 +78,7 @@ def backend_factory(dc, username, is_machine, no_domain = False):
 | 
			
		||||
 | 
			
		||||
    return back
 | 
			
		||||
 | 
			
		||||
def save_dconf(username, is_machine):
 | 
			
		||||
def save_dconf(username, is_machine, nodomain=None):
 | 
			
		||||
    if is_machine:
 | 
			
		||||
        uid = None
 | 
			
		||||
    else:
 | 
			
		||||
@@ -71,4 +87,5 @@ def save_dconf(username, is_machine):
 | 
			
		||||
    touch_file(target_file)
 | 
			
		||||
    Dconf_registry.apply_template(uid)
 | 
			
		||||
    add_preferences_to_global_registry_dict(username, is_machine)
 | 
			
		||||
    create_dconf_ini_file(target_file,Dconf_registry.global_registry_dict, uid)
 | 
			
		||||
    Dconf_registry.update_dict_to_previous()
 | 
			
		||||
    create_dconf_ini_file(target_file,Dconf_registry.global_registry_dict, uid, nodomain)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -16,10 +16,231 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import smbc
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from .applier_backend import applier_backend
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from gpt.gpt import gpt, get_local_gpt
 | 
			
		||||
from gpt.gpo_dconf_mapping import GpoInfoDconf
 | 
			
		||||
from storage import registry_factory
 | 
			
		||||
from storage.dconf_registry import Dconf_registry, extract_display_name_version
 | 
			
		||||
from storage.fs_file_cache import fs_file_cache
 | 
			
		||||
from util.logging import log
 | 
			
		||||
from util.util import get_uid_by_username
 | 
			
		||||
from util.kerberos import (
 | 
			
		||||
      machine_kinit
 | 
			
		||||
    , machine_kdestroy
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class freeipa_backend(applier_backend):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        pass
 | 
			
		||||
    def __init__(self, ipacreds, username, domain, is_machine):
 | 
			
		||||
        self.ipacreds = ipacreds
 | 
			
		||||
        self.cache_path = '/var/cache/gpupdate/creds/krb5cc_{}'.format(os.getpid())
 | 
			
		||||
        self.__kinit_successful = machine_kinit(self.cache_path, "freeipa")
 | 
			
		||||
        if not self.__kinit_successful:
 | 
			
		||||
            raise Exception('kinit is not successful')
 | 
			
		||||
 | 
			
		||||
        self.storage = registry_factory()
 | 
			
		||||
        self.storage.set_info('domain', domain)
 | 
			
		||||
 | 
			
		||||
        machine_name = self.ipacreds.get_machine_name()
 | 
			
		||||
        self.storage.set_info('machine_name', machine_name)
 | 
			
		||||
        self.username = machine_name if is_machine else username
 | 
			
		||||
        self._is_machine_username = is_machine
 | 
			
		||||
 | 
			
		||||
        self.cache_dir = self.ipacreds.get_cache_dir()
 | 
			
		||||
        self.gpo_cache_part = 'gpo_cache'
 | 
			
		||||
        self.gpo_cache_dir = os.path.join(self.cache_dir, self.gpo_cache_part)
 | 
			
		||||
        self.storage.set_info('cache_dir', self.gpo_cache_dir)
 | 
			
		||||
        self.file_cache = fs_file_cache("freeipa_gpo", username)
 | 
			
		||||
        logdata = {'cachedir': self.cache_dir}
 | 
			
		||||
        log('D7', logdata)
 | 
			
		||||
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
        if self.__kinit_successful:
 | 
			
		||||
            machine_kdestroy()
 | 
			
		||||
 | 
			
		||||
    def retrieve_and_store(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Retrieve settings and store it in a database - FreeIPA version
 | 
			
		||||
        '''
 | 
			
		||||
        try:
 | 
			
		||||
            if self._is_machine_username:
 | 
			
		||||
                dconf_dict = Dconf_registry.get_dictionary_from_dconf_file_db(save_dconf_db=True)
 | 
			
		||||
            else:
 | 
			
		||||
                uid = get_uid_by_username(self.username)
 | 
			
		||||
                dconf_dict = Dconf_registry.get_dictionary_from_dconf_file_db(uid, save_dconf_db=True)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logdata = {'msg': str(e)}
 | 
			
		||||
            log('E72', logdata)
 | 
			
		||||
 | 
			
		||||
        if self._is_machine_username:
 | 
			
		||||
            machine_gpts = []
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                machine_name = self.storage.get_info('machine_name')
 | 
			
		||||
                machine_gpts = self._get_gpts(machine_name)
 | 
			
		||||
                machine_gpts.reverse()
 | 
			
		||||
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = {'msg': str(exc)}
 | 
			
		||||
                log('E17', logdata)
 | 
			
		||||
 | 
			
		||||
            for i, gptobj in enumerate(machine_gpts):
 | 
			
		||||
                try:
 | 
			
		||||
                    gptobj.merge_machine()
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = {'msg': str(exc)}
 | 
			
		||||
                    log('E26', logdata)
 | 
			
		||||
        else:
 | 
			
		||||
            user_gpts = []
 | 
			
		||||
            try:
 | 
			
		||||
                user_gpts = self._get_gpts(self.username)
 | 
			
		||||
                user_gpts.reverse()
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = {'msg': str(exc)}
 | 
			
		||||
                log('E17', logdata)
 | 
			
		||||
            for i, gptobj in enumerate(user_gpts):
 | 
			
		||||
                try:
 | 
			
		||||
                    gptobj.merge_user()
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = {'msg': str(exc)}
 | 
			
		||||
                    log('E27', logdata)
 | 
			
		||||
 | 
			
		||||
    def _get_gpts(self, username):
 | 
			
		||||
        gpts = []
 | 
			
		||||
        gpos, server = self.ipacreds.update_gpos(username)
 | 
			
		||||
        if not gpos:
 | 
			
		||||
            return gpts
 | 
			
		||||
        if not server:
 | 
			
		||||
            return gpts
 | 
			
		||||
 | 
			
		||||
        cached_gpos = []
 | 
			
		||||
        download_gpos = []
 | 
			
		||||
 | 
			
		||||
        for i, gpo in enumerate(gpos):
 | 
			
		||||
            if gpo.file_sys_path.startswith('/'):
 | 
			
		||||
                if os.path.exists(gpo.file_sys_path):
 | 
			
		||||
                    logdata = {'gpo_name': gpo.display_name, 'path': gpo.file_sys_path}
 | 
			
		||||
                    log('D11', logdata)
 | 
			
		||||
                    cached_gpos.append(gpo)
 | 
			
		||||
                else:
 | 
			
		||||
                    download_gpos.append(gpo)
 | 
			
		||||
            else:
 | 
			
		||||
                if self._check_sysvol_present(gpo):
 | 
			
		||||
                    download_gpos.append(gpo)
 | 
			
		||||
                else:
 | 
			
		||||
                    logdata = {'gpo_name': gpo.display_name}
 | 
			
		||||
                    log('W4', logdata)
 | 
			
		||||
 | 
			
		||||
        if download_gpos:
 | 
			
		||||
            try:
 | 
			
		||||
                self._download_gpos(download_gpos, server)
 | 
			
		||||
                logdata = {'count': len(download_gpos)}
 | 
			
		||||
                log('D50', logdata)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                logdata = {'msg': str(e), 'count': len(download_gpos)}
 | 
			
		||||
                log('E35', logdata)
 | 
			
		||||
        else:
 | 
			
		||||
            log('D211', {})
 | 
			
		||||
 | 
			
		||||
        all_gpos = cached_gpos + download_gpos
 | 
			
		||||
        for gpo in all_gpos:
 | 
			
		||||
            gpt_abspath = gpo.file_sys_path
 | 
			
		||||
            if not os.path.exists(gpt_abspath):
 | 
			
		||||
                logdata = {'path': gpt_abspath, 'gpo_name': gpo.display_name}
 | 
			
		||||
                log('W12', logdata)
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if self._is_machine_username:
 | 
			
		||||
                obj = gpt(gpt_abspath, None, GpoInfoDconf(gpo))
 | 
			
		||||
            else:
 | 
			
		||||
                obj = gpt(gpt_abspath, self.username, GpoInfoDconf(gpo))
 | 
			
		||||
 | 
			
		||||
            obj.set_name(gpo.display_name)
 | 
			
		||||
            gpts.append(obj)
 | 
			
		||||
 | 
			
		||||
        local_gpt = get_local_gpt()
 | 
			
		||||
        gpts.append(local_gpt)
 | 
			
		||||
        logdata = {'total_count': len(gpts), 'downloaded_count': len(download_gpos)}
 | 
			
		||||
        log('I2', logdata)
 | 
			
		||||
        return gpts
 | 
			
		||||
 | 
			
		||||
    def _check_sysvol_present(self, gpo):
 | 
			
		||||
        if not gpo.file_sys_path:
 | 
			
		||||
            if getattr(gpo, 'name', '') != 'Local Policy':
 | 
			
		||||
                logdata = {'gponame': getattr(gpo, 'name', 'Unknown')}
 | 
			
		||||
                log('W4', logdata)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if gpo.file_sys_path.startswith('\\\\'):
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        elif gpo.file_sys_path.startswith('/'):
 | 
			
		||||
            if os.path.exists(gpo.file_sys_path):
 | 
			
		||||
                return True
 | 
			
		||||
            else:
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def _download_gpos(self, gpos, server):
 | 
			
		||||
        cache_dir = self.ipacreds.get_cache_dir()
 | 
			
		||||
        domain = self.ipacreds.get_domain().upper()
 | 
			
		||||
        gpo_cache_dir = os.path.join(cache_dir, domain, 'POLICIES')
 | 
			
		||||
        os.makedirs(gpo_cache_dir, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        for gpo in gpos:
 | 
			
		||||
            if not gpo.file_sys_path:
 | 
			
		||||
                continue
 | 
			
		||||
            smb_remote_path = None
 | 
			
		||||
            try:
 | 
			
		||||
                smb_remote_path = self._convert_to_smb_path(gpo.file_sys_path, server)
 | 
			
		||||
                local_gpo_path = os.path.join(gpo_cache_dir, gpo.name)
 | 
			
		||||
 | 
			
		||||
                self._download_gpo_directory(smb_remote_path, local_gpo_path)
 | 
			
		||||
                gpo.file_sys_path = local_gpo_path
 | 
			
		||||
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                logdata = {
 | 
			
		||||
                    'msg': str(e),
 | 
			
		||||
                    'gpo_name': gpo.display_name,
 | 
			
		||||
                    'smb_path': smb_remote_path,
 | 
			
		||||
                }
 | 
			
		||||
                log('E38', logdata)
 | 
			
		||||
 | 
			
		||||
    def _convert_to_smb_path(self, windows_path, server):
 | 
			
		||||
        match = re.search(r'\\\\[^\\]+\\(.+)', windows_path)
 | 
			
		||||
        if not match:
 | 
			
		||||
            raise Exception(f"Invalid Windows path format: {windows_path}")
 | 
			
		||||
        relative_path = match.group(1).replace('\\', '/').lower()
 | 
			
		||||
        smb_url = f"smb://{server}/{relative_path}"
 | 
			
		||||
 | 
			
		||||
        return smb_url
 | 
			
		||||
 | 
			
		||||
    def _download_gpo_directory(self, remote_smb_path, local_path):
 | 
			
		||||
        os.makedirs(local_path, exist_ok=True)
 | 
			
		||||
        try:
 | 
			
		||||
            entries = self.file_cache.samba_context.opendir(remote_smb_path).getdents()
 | 
			
		||||
            for entry in entries:
 | 
			
		||||
                if entry.name in [".", ".."]:
 | 
			
		||||
                    continue
 | 
			
		||||
                remote_entry_path = f"{remote_smb_path}/{entry.name}"
 | 
			
		||||
                local_entry_path = os.path.join(local_path, entry.name)
 | 
			
		||||
                if entry.smbc_type == smbc.DIR:
 | 
			
		||||
                    self._download_gpo_directory(remote_entry_path, local_entry_path)
 | 
			
		||||
                elif entry.smbc_type == smbc.FILE:
 | 
			
		||||
                    try:
 | 
			
		||||
                        os.makedirs(os.path.dirname(local_entry_path), exist_ok=True)
 | 
			
		||||
                        self.file_cache.store(remote_entry_path, Path(local_entry_path))
 | 
			
		||||
                    except Exception as e:
 | 
			
		||||
                        logdata = {'exception': str(e), 'file': entry.name}
 | 
			
		||||
                        log('W30', logdata)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logdata = {'exception': str(e), 'remote_folder_path': remote_smb_path}
 | 
			
		||||
            log('W31', logdata)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -16,33 +16,17 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from .applier_backend import applier_backend
 | 
			
		||||
from storage import registry_factory
 | 
			
		||||
from gpt.gpt import gpt, get_local_gpt
 | 
			
		||||
from util.util import (
 | 
			
		||||
    get_machine_name
 | 
			
		||||
)
 | 
			
		||||
from util.sid import get_sid
 | 
			
		||||
import util.preg
 | 
			
		||||
from util.logging import slogm
 | 
			
		||||
from gpt.gpt import get_local_gpt
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class nodomain_backend(applier_backend):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        domain = None
 | 
			
		||||
        machine_name = get_machine_name()
 | 
			
		||||
        machine_sid = get_sid(domain, machine_name, True)
 | 
			
		||||
        self.storage = registry_factory()
 | 
			
		||||
        self.storage.set_info('domain', domain)
 | 
			
		||||
        self.storage.set_info('machine_name', machine_name)
 | 
			
		||||
        self.storage.set_info('machine_sid', machine_sid)
 | 
			
		||||
 | 
			
		||||
        # User SID to work with HKCU hive
 | 
			
		||||
        self.username = machine_name
 | 
			
		||||
        self.sid = machine_sid
 | 
			
		||||
 | 
			
		||||
    def retrieve_and_store(self):
 | 
			
		||||
        '''
 | 
			
		||||
@@ -50,8 +34,7 @@ class nodomain_backend(applier_backend):
 | 
			
		||||
        '''
 | 
			
		||||
        # Get policies for machine at first.
 | 
			
		||||
        self.storage.wipe_hklm()
 | 
			
		||||
        self.storage.wipe_user(self.storage.get_info('machine_sid'))
 | 
			
		||||
        local_policy = get_local_gpt(self.sid)
 | 
			
		||||
        local_policy = get_local_gpt()
 | 
			
		||||
        local_policy.merge_machine()
 | 
			
		||||
        local_policy.merge_user()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -28,15 +28,13 @@ from storage import registry_factory
 | 
			
		||||
from gpt.gpt import gpt, get_local_gpt
 | 
			
		||||
from gpt.gpo_dconf_mapping import GpoInfoDconf
 | 
			
		||||
from util.util import (
 | 
			
		||||
    get_machine_name,
 | 
			
		||||
    is_machine_name
 | 
			
		||||
    get_machine_name
 | 
			
		||||
)
 | 
			
		||||
from util.kerberos import (
 | 
			
		||||
      machine_kinit
 | 
			
		||||
    , machine_kdestroy
 | 
			
		||||
)
 | 
			
		||||
from util.sid import get_sid
 | 
			
		||||
import util.preg
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
class samba_backend(applier_backend):
 | 
			
		||||
@@ -57,7 +55,7 @@ class samba_backend(applier_backend):
 | 
			
		||||
 | 
			
		||||
        # User SID to work with HKCU hive
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self._is_machine_username = is_machine
 | 
			
		||||
        self._is_machine = is_machine
 | 
			
		||||
        if is_machine:
 | 
			
		||||
            self.sid = machine_sid
 | 
			
		||||
        else:
 | 
			
		||||
@@ -70,7 +68,7 @@ class samba_backend(applier_backend):
 | 
			
		||||
        self.gpo_cache_part ='gpo_cache'
 | 
			
		||||
        self._cached = False
 | 
			
		||||
        self.storage.set_info('cache_dir', os.path.join(self.cache_dir, self.gpo_cache_part))
 | 
			
		||||
        logdata = dict({'cachedir': self.cache_dir})
 | 
			
		||||
        logdata = {'cachedir': self.cache_dir}
 | 
			
		||||
        log('D7', logdata)
 | 
			
		||||
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
@@ -100,28 +98,28 @@ class samba_backend(applier_backend):
 | 
			
		||||
        Retrieve settings and strore it in a database
 | 
			
		||||
        '''
 | 
			
		||||
        # Get policies for machine at first.
 | 
			
		||||
        machine_gpts = list()
 | 
			
		||||
        machine_gpts = []
 | 
			
		||||
        try:
 | 
			
		||||
            machine_gpts = self._get_gpts(get_machine_name(), self.storage.get_info('machine_sid'))
 | 
			
		||||
            machine_gpts = self._get_gpts()
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            log('F2')
 | 
			
		||||
            raise exc
 | 
			
		||||
 | 
			
		||||
        if self._is_machine_username:
 | 
			
		||||
        if self._is_machine:
 | 
			
		||||
            for gptobj in machine_gpts:
 | 
			
		||||
                try:
 | 
			
		||||
                    gptobj.merge_machine()
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = dict()
 | 
			
		||||
                    logdata = {}
 | 
			
		||||
                    logdata['msg'] = str(exc)
 | 
			
		||||
                    log('E26', logdata)
 | 
			
		||||
 | 
			
		||||
        # Load user GPT values in case user's name specified
 | 
			
		||||
        # This is a buggy implementation and should be tested more
 | 
			
		||||
        else:
 | 
			
		||||
            user_gpts = list()
 | 
			
		||||
            user_gpts = []
 | 
			
		||||
            try:
 | 
			
		||||
                user_gpts = self._get_gpts(self.username, self.sid)
 | 
			
		||||
                user_gpts = self._get_gpts(self.username)
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                log('F3')
 | 
			
		||||
                raise exc
 | 
			
		||||
@@ -129,7 +127,7 @@ class samba_backend(applier_backend):
 | 
			
		||||
            # Merge user settings if UserPolicyMode set accordingly
 | 
			
		||||
            # and user settings (for HKCU) are exist.
 | 
			
		||||
            policy_mode = self.get_policy_mode()
 | 
			
		||||
            logdata = dict({'mode': upm2str(policy_mode), 'sid': self.sid})
 | 
			
		||||
            logdata = {'mode': upm2str(policy_mode)}
 | 
			
		||||
            log('D152', logdata)
 | 
			
		||||
 | 
			
		||||
            if policy_mode < 2:
 | 
			
		||||
@@ -137,17 +135,16 @@ class samba_backend(applier_backend):
 | 
			
		||||
                    try:
 | 
			
		||||
                        gptobj.merge_user()
 | 
			
		||||
                    except Exception as exc:
 | 
			
		||||
                        logdata = dict()
 | 
			
		||||
                        logdata = {}
 | 
			
		||||
                        logdata['msg'] = str(exc)
 | 
			
		||||
                        log('E27', logdata)
 | 
			
		||||
 | 
			
		||||
            if policy_mode > 0:
 | 
			
		||||
                for gptobj in machine_gpts:
 | 
			
		||||
                    try:
 | 
			
		||||
                        gptobj.sid = self.sid
 | 
			
		||||
                        gptobj.merge_user()
 | 
			
		||||
                    except Exception as exc:
 | 
			
		||||
                        logdata = dict()
 | 
			
		||||
                        logdata = {}
 | 
			
		||||
                        logdata['msg'] = str(exc)
 | 
			
		||||
                        log('E63', logdata)
 | 
			
		||||
 | 
			
		||||
@@ -164,15 +161,16 @@ class samba_backend(applier_backend):
 | 
			
		||||
                self._cached = True
 | 
			
		||||
                return True
 | 
			
		||||
            elif 'Local Policy' != gpo.name:
 | 
			
		||||
                logdata = dict({'gponame': gpo.name})
 | 
			
		||||
                logdata = {'gponame': gpo.name}
 | 
			
		||||
                log('W4', logdata)
 | 
			
		||||
            return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _get_gpts(self, username, sid):
 | 
			
		||||
        gpts = list()
 | 
			
		||||
 | 
			
		||||
        log('D45', {'username': username, 'sid': sid})
 | 
			
		||||
    def _get_gpts(self, username=None):
 | 
			
		||||
        gpts = []
 | 
			
		||||
        if not username:
 | 
			
		||||
            username = get_machine_name()
 | 
			
		||||
        log('D45', {'username': username})
 | 
			
		||||
        # util.windows.smbcreds
 | 
			
		||||
        gpos = self.sambacreds.update_gpos(username)
 | 
			
		||||
        log('D46')
 | 
			
		||||
@@ -180,21 +178,21 @@ class samba_backend(applier_backend):
 | 
			
		||||
            if self._check_sysvol_present(gpo):
 | 
			
		||||
                if not self._cached:
 | 
			
		||||
                    path = check_safe_path(gpo.file_sys_path).upper()
 | 
			
		||||
                    slogdata = dict({'sysvol_path': gpo.file_sys_path, 'gpo_name': gpo.display_name, 'gpo_path': path})
 | 
			
		||||
                    slogdata = {'sysvol_path': gpo.file_sys_path, 'gpo_name': gpo.display_name, 'gpo_path': path}
 | 
			
		||||
                    log('D30', slogdata)
 | 
			
		||||
                    gpt_abspath = os.path.join(self.cache_dir, self.gpo_cache_part, path)
 | 
			
		||||
                else:
 | 
			
		||||
                    gpt_abspath = gpo.file_sys_path
 | 
			
		||||
                    log('D211', {'sysvol_path': gpo.file_sys_path, 'gpo_name': gpo.display_name})
 | 
			
		||||
                if self._is_machine_username:
 | 
			
		||||
                    obj = gpt(gpt_abspath, sid, None, GpoInfoDconf(gpo))
 | 
			
		||||
                if self._is_machine:
 | 
			
		||||
                    obj = gpt(gpt_abspath, None, GpoInfoDconf(gpo))
 | 
			
		||||
                else:
 | 
			
		||||
                    obj = gpt(gpt_abspath, sid, self.username, GpoInfoDconf(gpo))
 | 
			
		||||
                    obj = gpt(gpt_abspath, self.username, GpoInfoDconf(gpo))
 | 
			
		||||
                obj.set_name(gpo.display_name)
 | 
			
		||||
                gpts.append(obj)
 | 
			
		||||
            else:
 | 
			
		||||
                if 'Local Policy' == gpo.name:
 | 
			
		||||
                    gpts.append(get_local_gpt(sid))
 | 
			
		||||
                    gpts.append(get_local_gpt())
 | 
			
		||||
 | 
			
		||||
        return gpts
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,7 +18,6 @@
 | 
			
		||||
 | 
			
		||||
from abc import ABC
 | 
			
		||||
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
def check_experimental_enabled(storage):
 | 
			
		||||
    experimental_enable_flag = '/Software/BaseALT/Policies/GPUpdate/GlobalExperimental'
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ def control_subst(preg_name):
 | 
			
		||||
    This is a workaround for control names which can't be used in
 | 
			
		||||
    PReg/ADMX files.
 | 
			
		||||
    '''
 | 
			
		||||
    control_triggers = dict()
 | 
			
		||||
    control_triggers = {}
 | 
			
		||||
    control_triggers['dvd_rw-format'] = 'dvd+rw-format'
 | 
			
		||||
    control_triggers['dvd_rw-mediainfo'] = 'dvd+rw-mediainfo'
 | 
			
		||||
    control_triggers['dvd_rw-booktype'] = 'dvd+rw-booktype'
 | 
			
		||||
@@ -50,7 +50,7 @@ class control:
 | 
			
		||||
        Query possible values from control in order to perform check of
 | 
			
		||||
        parameter passed to constructor.
 | 
			
		||||
        '''
 | 
			
		||||
        values = list()
 | 
			
		||||
        values = []
 | 
			
		||||
 | 
			
		||||
        popen_call = ['/usr/sbin/control', self.control_name, 'list']
 | 
			
		||||
        with subprocess.Popen(popen_call, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
 | 
			
		||||
@@ -68,7 +68,7 @@ class control:
 | 
			
		||||
        try:
 | 
			
		||||
            str_status = self.possible_values[int_status]
 | 
			
		||||
        except IndexError as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['control'] = self.control_name
 | 
			
		||||
            logdata['value from'] = self.possible_values
 | 
			
		||||
            logdata['by index'] = int_status
 | 
			
		||||
@@ -97,20 +97,20 @@ class control:
 | 
			
		||||
        if type(self.control_value) == int:
 | 
			
		||||
            status = self._map_control_status(self.control_value)
 | 
			
		||||
            if status == None:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata = {}
 | 
			
		||||
                logdata['control'] = self.control_name
 | 
			
		||||
                logdata['inpossible values'] = self.control_value
 | 
			
		||||
                log('E42', logdata)
 | 
			
		||||
                return
 | 
			
		||||
        elif type(self.control_value) == str:
 | 
			
		||||
            if self.control_value not in self.possible_values:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata = {}
 | 
			
		||||
                logdata['control'] = self.control_name
 | 
			
		||||
                logdata['inpossible values'] = self.control_value
 | 
			
		||||
                log('E59', logdata)
 | 
			
		||||
                return
 | 
			
		||||
            status = self.control_value
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        logdata['control'] = self.control_name
 | 
			
		||||
        logdata['status'] = status
 | 
			
		||||
        log('D68', logdata)
 | 
			
		||||
@@ -120,7 +120,7 @@ class control:
 | 
			
		||||
            with subprocess.Popen(popen_call, stdout=subprocess.PIPE) as proc:
 | 
			
		||||
                proc.wait()
 | 
			
		||||
        except:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['control'] = self.control_name
 | 
			
		||||
            logdata['status'] = status
 | 
			
		||||
            log('E43', logdata)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -24,15 +24,33 @@ from util.arguments import (
 | 
			
		||||
)
 | 
			
		||||
from util.windows import expand_windows_var
 | 
			
		||||
from util.util import get_homedir
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
class Envvar:
 | 
			
		||||
    __envvar_file_path = '/etc/gpupdate/environment'
 | 
			
		||||
    __envvar_file_path_user = '/.gpupdate_environment'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, envvars, username=''):
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.envvars = envvars
 | 
			
		||||
        if self.username == 'root':
 | 
			
		||||
            self.envvar_file_path = '/etc/gpupdate/environment'
 | 
			
		||||
            self.envvar_file_path = Envvar.__envvar_file_path
 | 
			
		||||
        else:
 | 
			
		||||
            self.envvar_file_path = get_homedir(self.username) + '/.gpupdate_environment'
 | 
			
		||||
            self.envvar_file_path = get_homedir(self.username) + Envvar.__envvar_file_path_user
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def clear_envvar_file(username = False):
 | 
			
		||||
        if username:
 | 
			
		||||
            file_path = get_homedir(username) + Envvar.__envvar_file_path_user
 | 
			
		||||
        else:
 | 
			
		||||
            file_path = Envvar.__envvar_file_path
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            with open(file_path, 'w') as file:
 | 
			
		||||
                file.write('')
 | 
			
		||||
            log('D215', {'path':file_path})
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            log('D216', {'path': file_path, 'exc': exc})
 | 
			
		||||
 | 
			
		||||
    def _open_envvar_file(self):
 | 
			
		||||
        fd = None
 | 
			
		||||
@@ -46,7 +64,7 @@ class Envvar:
 | 
			
		||||
 | 
			
		||||
    def _create_action(self, create_dict, envvar_file):
 | 
			
		||||
        lines_old = envvar_file.readlines()
 | 
			
		||||
        lines_new = list()
 | 
			
		||||
        lines_new = []
 | 
			
		||||
        for name in create_dict:
 | 
			
		||||
            exist = False
 | 
			
		||||
            for line in lines_old:
 | 
			
		||||
@@ -75,7 +93,7 @@ class Envvar:
 | 
			
		||||
            with open(self.envvar_file_path, 'r') as f:
 | 
			
		||||
                lines = f.readlines()
 | 
			
		||||
        else:
 | 
			
		||||
            lines = list()
 | 
			
		||||
            lines = []
 | 
			
		||||
 | 
			
		||||
        file_changed = False
 | 
			
		||||
        for envvar_object in self.envvars:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -30,6 +30,8 @@ from util.util import get_homedir
 | 
			
		||||
from util.exceptions import NotUNCPathError
 | 
			
		||||
from util.paths import UNCPath
 | 
			
		||||
import fnmatch
 | 
			
		||||
import pwd
 | 
			
		||||
import grp
 | 
			
		||||
 | 
			
		||||
class Files_cp:
 | 
			
		||||
    def __init__(self, file_obj, file_cache, exe_check, username=None):
 | 
			
		||||
@@ -49,7 +51,8 @@ class Files_cp:
 | 
			
		||||
        self.suppress = str2bool(file_obj.suppress)
 | 
			
		||||
        self.executable = str2bool(file_obj.executable)
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.fromPathFiles = list()
 | 
			
		||||
        self.pw = pwd.getpwnam(username) if username else None
 | 
			
		||||
        self.fromPathFiles = []
 | 
			
		||||
        if self.fromPath:
 | 
			
		||||
            if targetPath[-1] == '/' or self.is_pattern(Path(self.fromPath).name):
 | 
			
		||||
                self.isTargetPathDirectory = True
 | 
			
		||||
@@ -77,7 +80,7 @@ class Files_cp:
 | 
			
		||||
                else:
 | 
			
		||||
                    return targetPath.parent.joinpath('.' + targetPath.name)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['targetPath'] = targetPath
 | 
			
		||||
            logdata['fromFile'] = fromFile
 | 
			
		||||
            logdata['exc'] = exc
 | 
			
		||||
@@ -94,7 +97,7 @@ class Files_cp:
 | 
			
		||||
            if fromFilePath.exists():
 | 
			
		||||
                targetFile.write_bytes(fromFilePath.read_bytes())
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['targetFile'] = targetFile
 | 
			
		||||
            logdata['fromFile'] = fromFile
 | 
			
		||||
            logdata['exc'] = exc
 | 
			
		||||
@@ -125,7 +128,7 @@ class Files_cp:
 | 
			
		||||
                shutil.os.chmod(targetFile, 0o644)
 | 
			
		||||
 | 
			
		||||
    def _create_action(self):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        for fromFile in self.fromPathFiles:
 | 
			
		||||
            targetFile = None
 | 
			
		||||
 | 
			
		||||
@@ -134,7 +137,8 @@ class Files_cp:
 | 
			
		||||
                if targetFile and not targetFile.exists():
 | 
			
		||||
                    self.copy_target_file(targetFile, fromFile)
 | 
			
		||||
                    if self.username:
 | 
			
		||||
                        shutil.chown(targetFile, self.username)
 | 
			
		||||
                        group_name = grp.getgrgid(self.pw.pw_gid).gr_name
 | 
			
		||||
                        chown_home_path(targetFile, username=self.username, group=group_name)
 | 
			
		||||
                    self.set_mod_file(targetFile, fromFile)
 | 
			
		||||
                    logdata['File'] = targetFile
 | 
			
		||||
                    log('D191', logdata)
 | 
			
		||||
@@ -149,7 +153,7 @@ class Files_cp:
 | 
			
		||||
        list_target = [self.targetPath.name]
 | 
			
		||||
        if self.is_pattern(self.targetPath.name) and self.targetPath.parent.exists() and self.targetPath.parent.is_dir():
 | 
			
		||||
            list_target = fnmatch.filter([str(x.name) for x in self.targetPath.parent.iterdir() if x.is_file()], self.targetPath.name)
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        for targetFile in list_target:
 | 
			
		||||
            targetFile = self.targetPath.parent.joinpath(targetFile)
 | 
			
		||||
            try:
 | 
			
		||||
@@ -165,13 +169,15 @@ class Files_cp:
 | 
			
		||||
                log('D165', logdata)
 | 
			
		||||
 | 
			
		||||
    def _update_action(self):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        for fromFile in self.fromPathFiles:
 | 
			
		||||
            targetFile = self.get_target_file(self.targetPath, fromFile)
 | 
			
		||||
            try:
 | 
			
		||||
                self.copy_target_file(targetFile, fromFile)
 | 
			
		||||
                if self.username:
 | 
			
		||||
                    shutil.chown(self.targetPath, self.username)
 | 
			
		||||
                    group_name = grp.getgrgid(self.pw.pw_gid).gr_name
 | 
			
		||||
                    chown_home_path(targetFile, username=self.username, group=group_name)
 | 
			
		||||
                self.set_mod_file(targetFile, fromFile)
 | 
			
		||||
                logdata['File'] = targetFile
 | 
			
		||||
                log('D192', logdata)
 | 
			
		||||
@@ -200,7 +206,7 @@ class Files_cp:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def get_list_files(self):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        logdata['targetPath'] = str(self.targetPath)
 | 
			
		||||
        fromFilePath = Path(self.fromPath)
 | 
			
		||||
        if not self.is_pattern(fromFilePath.name):
 | 
			
		||||
@@ -254,8 +260,8 @@ class Execution_check():
 | 
			
		||||
        marker_usage_path_branch = '{}\\{}%'.format(self.__hklm_branch, self.__marker_usage_path_key_name)
 | 
			
		||||
        self.etension_marker = storage.filter_hklm_entries(etension_marker_branch)
 | 
			
		||||
        self.marker_usage_path = storage.filter_hklm_entries(marker_usage_path_branch)
 | 
			
		||||
        self.list_paths = list()
 | 
			
		||||
        self.list_markers = list()
 | 
			
		||||
        self.list_paths = []
 | 
			
		||||
        self.list_markers = []
 | 
			
		||||
        for marker in self.etension_marker:
 | 
			
		||||
            self.list_markers.append(marker.data)
 | 
			
		||||
        for usage_path in self.marker_usage_path:
 | 
			
		||||
@@ -266,3 +272,32 @@ class Execution_check():
 | 
			
		||||
 | 
			
		||||
    def get_list_markers(self):
 | 
			
		||||
        return self.list_markers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def chown_home_path(path: Path, username: str, group: str) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    Change ownership (user and group) of the given path and all its parent
 | 
			
		||||
    directories up to (but NOT including) the user's home directory.
 | 
			
		||||
 | 
			
		||||
    If the path is not inside the user's home directory, do nothing.
 | 
			
		||||
 | 
			
		||||
    :param path: Path to a file or directory.
 | 
			
		||||
    :param user: Username to set as owner.
 | 
			
		||||
    :param group: Group name to set as group.
 | 
			
		||||
    """
 | 
			
		||||
    path = path.resolve()
 | 
			
		||||
    home_root = Path(get_homedir(username))
 | 
			
		||||
 | 
			
		||||
    # Check if the path is inside user's home directory
 | 
			
		||||
    if home_root not in path.parents:
 | 
			
		||||
        return  # Not inside user's home - do nothing
 | 
			
		||||
 | 
			
		||||
    # Walk upwards from the given path until just above home_root
 | 
			
		||||
    current = path
 | 
			
		||||
    while True:
 | 
			
		||||
        if current == home_root:
 | 
			
		||||
            break  # do not change ownership of the home directory itself
 | 
			
		||||
        shutil.chown(current, user=username, group=group)
 | 
			
		||||
        if current.parent == current:  # Safety check: reached root (/)
 | 
			
		||||
            break
 | 
			
		||||
        current = current.parent
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ from enum import Enum
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
def getprops(param_list):
 | 
			
		||||
    props = dict()
 | 
			
		||||
    props = {}
 | 
			
		||||
 | 
			
		||||
    for entry in param_list:
 | 
			
		||||
        lentry = entry.lower()
 | 
			
		||||
@@ -35,7 +35,7 @@ def getprops(param_list):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_ports(param_list):
 | 
			
		||||
    portlist = list()
 | 
			
		||||
    portlist = []
 | 
			
		||||
 | 
			
		||||
    for entry in param_list:
 | 
			
		||||
        lentry = entry.lower()
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ from util.windows import expand_windows_var
 | 
			
		||||
from util.util import get_homedir
 | 
			
		||||
 | 
			
		||||
def remove_dir_tree(path, delete_files=False, delete_folder=False, delete_sub_folders=False):
 | 
			
		||||
    content = list()
 | 
			
		||||
    content = []
 | 
			
		||||
    for entry in path.iterdir():
 | 
			
		||||
        content.append(entry)
 | 
			
		||||
        if entry.is_file() and delete_files:
 | 
			
		||||
@@ -77,9 +77,10 @@ class Folder:
 | 
			
		||||
                self.delete_folder,
 | 
			
		||||
                self.delete_sub_folders)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def act(self):
 | 
			
		||||
        if self.hidden_folder == True and str(self.folder_path.name)[0] != '.':
 | 
			
		||||
            path_components = list(self.folder_path.parts)
 | 
			
		||||
            path_components = [*self.folder_path.parts]
 | 
			
		||||
            path_components[-1] = '.' + path_components[-1]
 | 
			
		||||
            new_folder_path = Path(*path_components)
 | 
			
		||||
            self.folder_path = new_folder_path
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2021 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -53,15 +53,15 @@ class system_gsettings:
 | 
			
		||||
    __profile_data = 'user-db:user\nsystem-db:policy\nsystem-db:local\n'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, override_file_path):
 | 
			
		||||
        self.gsettings = list()
 | 
			
		||||
        self.locks = list()
 | 
			
		||||
        self.gsettings = []
 | 
			
		||||
        self.locks = []
 | 
			
		||||
        self.override_file_path = override_file_path
 | 
			
		||||
 | 
			
		||||
    def append(self, schema, path, data, lock, helper):
 | 
			
		||||
        if check_existing_gsettings(schema, path):
 | 
			
		||||
            self.gsettings.append(system_gsetting(schema, path, data, lock, helper))
 | 
			
		||||
        else:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['schema'] = schema
 | 
			
		||||
            logdata['path'] = path
 | 
			
		||||
            logdata['data'] = data
 | 
			
		||||
@@ -72,7 +72,7 @@ class system_gsettings:
 | 
			
		||||
        config = configparser.ConfigParser()
 | 
			
		||||
 | 
			
		||||
        for gsetting in self.gsettings:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['gsetting.schema'] = gsetting.schema
 | 
			
		||||
            logdata['gsetting.path'] = gsetting.path
 | 
			
		||||
            logdata['gsetting.value'] = gsetting.value
 | 
			
		||||
@@ -132,13 +132,13 @@ def check_existing_gsettings (schema, path):
 | 
			
		||||
 | 
			
		||||
class user_gsettings:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.gsettings = list()
 | 
			
		||||
        self.gsettings = []
 | 
			
		||||
 | 
			
		||||
    def append(self, schema, path, value, helper=None):
 | 
			
		||||
        if check_existing_gsettings(schema, path):
 | 
			
		||||
            self.gsettings.append(user_gsetting(schema, path, value, helper))
 | 
			
		||||
        else:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['schema'] = schema
 | 
			
		||||
            logdata['path'] = path
 | 
			
		||||
            logdata['data'] = value
 | 
			
		||||
@@ -146,7 +146,7 @@ class user_gsettings:
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
        for gsetting in self.gsettings:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['gsetting.schema'] = gsetting.schema
 | 
			
		||||
            logdata['gsetting.path'] = gsetting.path
 | 
			
		||||
            logdata['gsetting.value'] = gsetting.value
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ class Ini_file:
 | 
			
		||||
        if self.path.is_dir():
 | 
			
		||||
            return
 | 
			
		||||
        if self.section not in self.config:
 | 
			
		||||
            self.config[self.section] = dict()
 | 
			
		||||
            self.config[self.section] = {}
 | 
			
		||||
 | 
			
		||||
        self.config[self.section][self.key] = self.value
 | 
			
		||||
        self.config.write()
 | 
			
		||||
@@ -85,7 +85,7 @@ class Ini_file:
 | 
			
		||||
            if self.action == FileAction.REPLACE:
 | 
			
		||||
                self._create_action()
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['action'] = self.action
 | 
			
		||||
            logdata['exc'] = exc
 | 
			
		||||
            log('W23', logdata)
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ class Networkshare:
 | 
			
		||||
    def __init__(self, networkshare_obj, username = None):
 | 
			
		||||
        self.net_full_cmd = ['/usr/bin/net', 'usershare']
 | 
			
		||||
        self.net_cmd_check = ['/usr/bin/net', 'usershare', 'list']
 | 
			
		||||
        self.cmd = list()
 | 
			
		||||
        self.cmd = []
 | 
			
		||||
        self.name = networkshare_obj.name
 | 
			
		||||
        self.path = expand_windows_var(networkshare_obj.path, username).replace('\\', '/') if networkshare_obj.path else None
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +52,7 @@ class Networkshare:
 | 
			
		||||
            return exc
 | 
			
		||||
 | 
			
		||||
    def _run_net_full_cmd(self):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        try:
 | 
			
		||||
            res = subprocess.check_output(self.net_full_cmd, stderr=subprocess.DEVNULL, encoding='utf-8')
 | 
			
		||||
            if res:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -50,6 +50,7 @@ class polkit:
 | 
			
		||||
            if os.path.isfile(self.outfile):
 | 
			
		||||
                os.remove(self.outfile)
 | 
			
		||||
            return
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        try:
 | 
			
		||||
            template = self.__template_environment.get_template(self.infilename)
 | 
			
		||||
            text = template.render(**self.args)
 | 
			
		||||
@@ -57,12 +58,10 @@ class polkit:
 | 
			
		||||
            with open(self.outfile, 'w') as f:
 | 
			
		||||
                f.write(text)
 | 
			
		||||
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['file'] = self.outfile
 | 
			
		||||
            logdata['arguments'] = self.args
 | 
			
		||||
            log('D77', logdata)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['file'] = self.outfile
 | 
			
		||||
            logdata['arguments'] = self.args
 | 
			
		||||
            log('E44', logdata)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -17,9 +17,8 @@
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
import dbus
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from util.logging import slogm, log
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
class systemd_unit:
 | 
			
		||||
    def __init__(self, unit_name, state):
 | 
			
		||||
@@ -35,12 +34,14 @@ class systemd_unit:
 | 
			
		||||
        self.unit_properties = dbus.Interface(self.unit_proxy, dbus_interface='org.freedesktop.DBus.Properties')
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
        logdata = {'unit': self.unit_name}
 | 
			
		||||
        if self.desired_state == 1:
 | 
			
		||||
            self.manager.UnmaskUnitFiles([self.unit_name], dbus.Boolean(False))
 | 
			
		||||
            self.manager.EnableUnitFiles([self.unit_name], dbus.Boolean(False), dbus.Boolean(True))
 | 
			
		||||
            if self.unit_name == 'gpupdate.service':
 | 
			
		||||
                if self.manager.GetUnitFileState(dbus.String(self.unit_name)) == 'enabled':
 | 
			
		||||
                    return
 | 
			
		||||
            self.manager.StartUnit(self.unit_name, 'replace')
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['unit'] = self.unit_name
 | 
			
		||||
            log('I6', logdata)
 | 
			
		||||
 | 
			
		||||
            # In case the service has 'RestartSec' property set it
 | 
			
		||||
@@ -48,27 +49,21 @@ class systemd_unit:
 | 
			
		||||
            # 'active' so we consider 'activating' a valid state too.
 | 
			
		||||
            service_state = self._get_state()
 | 
			
		||||
 | 
			
		||||
            if not service_state in ['active', 'activating']:
 | 
			
		||||
            if service_state not in ('active', 'activating'):
 | 
			
		||||
                service_timer_name =  self.unit_name.replace(".service", ".timer")
 | 
			
		||||
                self.unit = self.manager.LoadUnit(dbus.String(service_timer_name))
 | 
			
		||||
                service_state = self._get_state()
 | 
			
		||||
                if not service_state in ['active', 'activating']:
 | 
			
		||||
                    logdata = dict()
 | 
			
		||||
                    logdata['unit'] = self.unit_name
 | 
			
		||||
                if service_state not in ('active', 'activating'):
 | 
			
		||||
                    log('E46', logdata)
 | 
			
		||||
        else:
 | 
			
		||||
            self.manager.StopUnit(self.unit_name, 'replace')
 | 
			
		||||
            self.manager.DisableUnitFiles([self.unit_name], dbus.Boolean(False))
 | 
			
		||||
            self.manager.MaskUnitFiles([self.unit_name], dbus.Boolean(False), dbus.Boolean(True))
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['unit'] = self.unit_name
 | 
			
		||||
            log('I6', logdata)
 | 
			
		||||
 | 
			
		||||
            service_state = self._get_state()
 | 
			
		||||
 | 
			
		||||
            if not service_state in ['stopped', 'deactivating', 'inactive']:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['unit'] = self.unit_name
 | 
			
		||||
            if service_state not in ('stopped', 'deactivating', 'inactive'):
 | 
			
		||||
                log('E46', logdata)
 | 
			
		||||
 | 
			
		||||
    def _get_state(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -34,15 +34,13 @@ class chromium_applier(applier_frontend):
 | 
			
		||||
    __managed_policies_path = '/etc/chromium/policies/managed'
 | 
			
		||||
    __recommended_policies_path = '/etc/chromium/policies/recommended'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self._is_machine_name = is_machine_name(self.username)
 | 
			
		||||
        chromium_filter = '{}%'.format(self.__registry_branch)
 | 
			
		||||
        self.chromium_keys = self.storage.filter_hklm_entries(chromium_filter)
 | 
			
		||||
        self.chromium_keys = self.storage.filter_hklm_entries(self.__registry_branch)
 | 
			
		||||
 | 
			
		||||
        self.policies_json = dict()
 | 
			
		||||
        self.policies_json = {}
 | 
			
		||||
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
@@ -70,7 +68,7 @@ class chromium_applier(applier_frontend):
 | 
			
		||||
        os.makedirs(self.__managed_policies_path, exist_ok=True)
 | 
			
		||||
        with open(destfile, 'w') as f:
 | 
			
		||||
            json.dump(dict_item_to_list(self.policies_json), f)
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['destfile'] = destfile
 | 
			
		||||
            log('D97', logdata)
 | 
			
		||||
 | 
			
		||||
@@ -78,7 +76,7 @@ class chromium_applier(applier_frontend):
 | 
			
		||||
        os.makedirs(self.__recommended_policies_path, exist_ok=True)
 | 
			
		||||
        with open(destfilerec, 'w') as f:
 | 
			
		||||
            json.dump(dict_item_to_list(recommended__json), f)
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['destfilerec'] = destfilerec
 | 
			
		||||
            log('D97', logdata)
 | 
			
		||||
 | 
			
		||||
@@ -185,7 +183,7 @@ class chromium_applier(applier_frontend):
 | 
			
		||||
        '''
 | 
			
		||||
        Collect dictionaries from registry keys into a general dictionary
 | 
			
		||||
        '''
 | 
			
		||||
        counts = dict()
 | 
			
		||||
        counts = {}
 | 
			
		||||
        #getting the list of keys to read as an integer
 | 
			
		||||
        valuename_typeint = self.get_valuename_typeint()
 | 
			
		||||
        for it_data in chromium_keys:
 | 
			
		||||
@@ -213,7 +211,7 @@ class chromium_applier(applier_frontend):
 | 
			
		||||
                        branch[parts[-1]] = str(it_data.data).replace('\\', '/')
 | 
			
		||||
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata = {}
 | 
			
		||||
                logdata['Exception'] = exc
 | 
			
		||||
                logdata['keyname'] = it_data.keyname
 | 
			
		||||
                log('D178', logdata)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,6 +18,7 @@
 | 
			
		||||
 | 
			
		||||
import jinja2
 | 
			
		||||
import os
 | 
			
		||||
import pwd
 | 
			
		||||
import subprocess
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import string
 | 
			
		||||
@@ -26,12 +27,12 @@ from .applier_frontend import (
 | 
			
		||||
      applier_frontend
 | 
			
		||||
    , check_enabled
 | 
			
		||||
)
 | 
			
		||||
from util.util import get_homedir
 | 
			
		||||
from util.util import get_homedir, get_uid_by_username, get_machine_name
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
def storage_get_drives(storage, sid):
 | 
			
		||||
    drives = storage.get_drives(sid)
 | 
			
		||||
    drive_list = list()
 | 
			
		||||
def storage_get_drives(storage):
 | 
			
		||||
    drives = storage.get_drives()
 | 
			
		||||
    drive_list = []
 | 
			
		||||
 | 
			
		||||
    for drv_obj in drives:
 | 
			
		||||
        drive_list.append(drv_obj)
 | 
			
		||||
@@ -64,7 +65,7 @@ def remove_escaped_quotes(input_string):
 | 
			
		||||
class Drive_list:
 | 
			
		||||
    __alphabet = string.ascii_uppercase
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.dict_drives = dict()
 | 
			
		||||
        self.dict_drives = {}
 | 
			
		||||
 | 
			
		||||
    def __get_letter(self, letter):
 | 
			
		||||
        slice_letters = set(self.__alphabet[self.__alphabet.find(letter) + 1:]) - set(self.dict_drives.keys())
 | 
			
		||||
@@ -124,14 +125,28 @@ class cifs_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'CIFSApplier'
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __dir4clean = '/etc/auto.master.gpupdate.d'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid):
 | 
			
		||||
        self.applier_cifs = cifs_applier_user(storage, sid, None)
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        self.clear_directory_auto_dir()
 | 
			
		||||
        self.applier_cifs = cifs_applier_user(storage, None)
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
            , self.__module_experimental
 | 
			
		||||
        )
 | 
			
		||||
    def clear_directory_auto_dir(self):
 | 
			
		||||
        path = Path(self.__dir4clean)
 | 
			
		||||
        if not path.exists():
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for item in path.iterdir():
 | 
			
		||||
            try:
 | 
			
		||||
                if item.is_file() or item.is_symlink():
 | 
			
		||||
                    item.unlink()
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                log('W37', {'exc': exc})
 | 
			
		||||
        log('D231')
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
        if self.__module_enabled:
 | 
			
		||||
@@ -152,32 +167,75 @@ class cifs_applier_user(applier_frontend):
 | 
			
		||||
    __template_auto = 'autofs_auto.j2'
 | 
			
		||||
    __template_mountpoints_hide = 'autofs_mountpoints_hide.j2'
 | 
			
		||||
    __template_auto_hide = 'autofs_auto_hide.j2'
 | 
			
		||||
    __enable_home_link = 'Software\\BaseALT\\Policies\\GPUpdate\\DriveMapsHome'
 | 
			
		||||
    __enable_home_link_user = 'Software\\BaseALT\\Policies\\GPUpdate\\DriveMapsHomeUser'
 | 
			
		||||
    __enable_home_link = '/Software/BaseALT/Policies/GPUpdate/DriveMapsHome'
 | 
			
		||||
    __enable_home_link_user = '/Software/BaseALT/Policies/GPUpdate/DriveMapsHomeUser'
 | 
			
		||||
    __name_dir = '/Software/BaseALT/Policies/GPUpdate'
 | 
			
		||||
    __name_link_prefix = '/Software/BaseALT/Policies/GPUpdate/DriveMapsHomeDisableNet'
 | 
			
		||||
    __name_link_prefix_user = '/Software/BaseALT/Policies/GPUpdate/DriveMapsHomeDisableNetUser'
 | 
			
		||||
    __key_link_prefix = 'DriveMapsHomeDisableNet'
 | 
			
		||||
    __key_link_prefix_user = 'DriveMapsHomeDisableNetUser'
 | 
			
		||||
    __timeout_user_key = '/Software/BaseALT/Policies/GPUpdate/TimeoutAutofsUser'
 | 
			
		||||
    __timeout_key = '/Software/BaseALT/Policies/GPUpdate/TimeoutAutofs'
 | 
			
		||||
    __cifsacl_key = '/Software/BaseALT/Policies/GPUpdate/CifsaclDisable'
 | 
			
		||||
    __target_mountpoint = '/media/gpupdate'
 | 
			
		||||
    __target_mountpoint_user = '/run/media'
 | 
			
		||||
    __mountpoint_dirname = 'drives.system'
 | 
			
		||||
    __mountpoint_dirname_user = 'drives'
 | 
			
		||||
    __key_cifs_previous_value = 'Previous/Software/BaseALT/Policies/GPUpdate'
 | 
			
		||||
    __key_preferences = 'Software/BaseALT/Policies/Preferences/'
 | 
			
		||||
    __key_preferences_previous = 'Previous/Software/BaseALT/Policies/Preferences/'
 | 
			
		||||
    __name_value = 'DriveMapsName'
 | 
			
		||||
    __name_value_user = 'DriveMapsNameUser'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.state_home_link = False
 | 
			
		||||
        self.state_home_link_user = False
 | 
			
		||||
        self.dict_registry_machine = self.storage.get_dictionary_from_dconf_file_db()
 | 
			
		||||
        self.homedir = ''
 | 
			
		||||
        name_dir = self.__name_dir[1:]
 | 
			
		||||
 | 
			
		||||
        if username:
 | 
			
		||||
            self.dict_registry_user = self.storage.get_dictionary_from_dconf_file_db(get_uid_by_username(username))
 | 
			
		||||
            self.home = self.__target_mountpoint_user + '/' + username
 | 
			
		||||
            self.state_home_link = self.check_enable_home_link(self.__enable_home_link)
 | 
			
		||||
            self.state_home_link_user = self.check_enable_home_link(self.__enable_home_link_user)
 | 
			
		||||
            self.state_home_link = self.storage.check_enable_key(self.__enable_home_link)
 | 
			
		||||
            self.state_home_link_disable_net = self.storage.check_enable_key(self.__name_link_prefix)
 | 
			
		||||
            self.state_home_link_disable_net_user = self.storage.check_enable_key(self.__name_link_prefix_user)
 | 
			
		||||
 | 
			
		||||
            self.state_home_link_user = self.storage.check_enable_key(self.__enable_home_link_user)
 | 
			
		||||
            self.timeout = self.storage.get_entry(self.__timeout_user_key)
 | 
			
		||||
            dirname = self.storage.get_entry(self.__name_dir + '/' + self.__name_value_user)
 | 
			
		||||
            dirname_system_from_machine = self.dict_registry_machine.get(name_dir, dict()).get(self.__name_value, None)
 | 
			
		||||
            self.__mountpoint_dirname_user = dirname.data if dirname and dirname.data else self.__mountpoint_dirname_user
 | 
			
		||||
            self.__mountpoint_dirname = dirname_system_from_machine if dirname_system_from_machine else self.__mountpoint_dirname
 | 
			
		||||
            mntTarget = self.__mountpoint_dirname_user
 | 
			
		||||
 | 
			
		||||
            self.keys_cifs_previous_values_user = self.dict_registry_user.get(self.__key_cifs_previous_value,{})
 | 
			
		||||
            self.keys_cifs_values_user = self.dict_registry_user.get(name_dir,{})
 | 
			
		||||
            self.keys_the_preferences_previous_values_user = self.dict_registry_user.get((self.__key_preferences_previous+self.username),{}).get('Drives', {})
 | 
			
		||||
            self.keys_the_preferences_values_user = self.dict_registry_user.get((self.__key_preferences+self.username),{}).get('Drives', {})
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            self.home = self.__target_mountpoint
 | 
			
		||||
            self.timeout = self.storage.get_entry(self.__timeout_key)
 | 
			
		||||
            dirname_system = self.storage.get_entry(self.__name_dir + '/' + self.__name_value)
 | 
			
		||||
            self.__mountpoint_dirname = dirname_system.data if dirname_system and dirname_system.data else self.__mountpoint_dirname
 | 
			
		||||
            mntTarget = self.__mountpoint_dirname
 | 
			
		||||
 | 
			
		||||
        conf_file = '{}.conf'.format(sid)
 | 
			
		||||
        conf_hide_file = '{}_hide.conf'.format(sid)
 | 
			
		||||
        autofs_file = '{}.autofs'.format(sid)
 | 
			
		||||
        autofs_hide_file = '{}_hide.autofs'.format(sid)
 | 
			
		||||
        cred_file = '{}.creds'.format(sid)
 | 
			
		||||
        self.keys_cifs_previous_values_machine = self.dict_registry_machine.get(self.__key_cifs_previous_value,{})
 | 
			
		||||
        self.keys_cifs_values_machine = self.dict_registry_machine.get(name_dir,{})
 | 
			
		||||
        self.keys_the_preferences_previous_values = self.dict_registry_machine.get((self.__key_preferences_previous+'Machine'),{}).get('Drives', {})
 | 
			
		||||
        self.keys_the_preferences_values = self.dict_registry_machine.get((self.__key_preferences+'Machine'),{}).get('Drives', {})
 | 
			
		||||
        self.cifsacl_disable = self.storage.get_entry(self.__cifsacl_key, preg=False)
 | 
			
		||||
 | 
			
		||||
        self.mntTarget = mntTarget.translate(str.maketrans({" ": r"\ "}))
 | 
			
		||||
        file_name = username if username else get_machine_name()
 | 
			
		||||
        conf_file = '{}.conf'.format(file_name)
 | 
			
		||||
        conf_hide_file = '{}_hide.conf'.format(file_name)
 | 
			
		||||
        autofs_file = '{}.autofs'.format(file_name)
 | 
			
		||||
        autofs_hide_file = '{}_hide.autofs'.format(file_name)
 | 
			
		||||
        cred_file = '{}.creds'.format(file_name)
 | 
			
		||||
 | 
			
		||||
        self.auto_master_d = Path(self.__auto_dir)
 | 
			
		||||
 | 
			
		||||
@@ -195,13 +253,9 @@ class cifs_applier_user(applier_frontend):
 | 
			
		||||
            self.user_autofs_hide.unlink()
 | 
			
		||||
        self.user_creds = self.auto_master_d / cred_file
 | 
			
		||||
 | 
			
		||||
        if username:
 | 
			
		||||
            self.mntTarget = self.__mountpoint_dirname_user
 | 
			
		||||
        else:
 | 
			
		||||
            self.mntTarget = self.__mountpoint_dirname
 | 
			
		||||
 | 
			
		||||
        self.mount_dir = Path(os.path.join(self.home))
 | 
			
		||||
        self.drives = storage_get_drives(self.storage, self.sid)
 | 
			
		||||
        self.drives = storage_get_drives(self.storage)
 | 
			
		||||
 | 
			
		||||
        self.template_loader = jinja2.FileSystemLoader(searchpath=self.__template_path)
 | 
			
		||||
        self.template_env = jinja2.Environment(loader=self.template_loader)
 | 
			
		||||
@@ -219,12 +273,20 @@ class cifs_applier_user(applier_frontend):
 | 
			
		||||
            , self.__module_experimental
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def check_enable_home_link(self, enable_home_link):
 | 
			
		||||
        if self.storage.get_hkcu_entry(self.sid, enable_home_link):
 | 
			
		||||
            data = self.storage.get_hkcu_entry(self.sid, enable_home_link).data
 | 
			
		||||
            return bool(int(data)) if data else None
 | 
			
		||||
 | 
			
		||||
    def is_mount_point_dirname(self):
 | 
			
		||||
        if self.username:
 | 
			
		||||
            return self.mount_dir.joinpath(self.__mountpoint_dirname_user).is_mount()
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
            return self.mount_dir.joinpath(self.__mountpoint_dirname).is_mount()
 | 
			
		||||
 | 
			
		||||
    def is_changed_keys(self):
 | 
			
		||||
        if self.username:
 | 
			
		||||
            return (self.keys_cifs_previous_values_user.get(self.__name_value_user) != self.keys_cifs_values_user.get(self.__name_value_user) or
 | 
			
		||||
                    self.keys_the_preferences_previous_values_user != self.keys_the_preferences_values_user)
 | 
			
		||||
        else:
 | 
			
		||||
            return (self.keys_cifs_previous_values_machine.get(self.__name_value) != self.keys_cifs_values_machine.get(self.__name_value) or
 | 
			
		||||
                    self.keys_the_preferences_previous_values != self.keys_the_preferences_values)
 | 
			
		||||
 | 
			
		||||
    def user_context_apply(self):
 | 
			
		||||
        '''
 | 
			
		||||
@@ -237,6 +299,10 @@ class cifs_applier_user(applier_frontend):
 | 
			
		||||
        self.auto_master_d.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        # Create user's destination mount directory
 | 
			
		||||
        self.mount_dir.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        uid = pwd.getpwnam(self.username).pw_uid if self.username else None
 | 
			
		||||
        if uid:
 | 
			
		||||
            os.chown(self.mount_dir, uid=uid, gid=-1)
 | 
			
		||||
            self.mount_dir.chmod(0o700)
 | 
			
		||||
 | 
			
		||||
        # Add pointer to /etc/auto.master.gpiupdate.d in /etc/auto.master
 | 
			
		||||
        auto_destdir = '+dir:{}'.format(self.__auto_dir)
 | 
			
		||||
@@ -245,7 +311,7 @@ class cifs_applier_user(applier_frontend):
 | 
			
		||||
        # Collect data for drive settings
 | 
			
		||||
        drive_list = Drive_list()
 | 
			
		||||
        for drv in self.drives:
 | 
			
		||||
            drive_settings = dict()
 | 
			
		||||
            drive_settings = {}
 | 
			
		||||
            drive_settings['dir'] = drv.dir
 | 
			
		||||
            drive_settings['login'] = drv.login
 | 
			
		||||
            drive_settings['password'] = drv.password
 | 
			
		||||
@@ -256,11 +322,13 @@ class cifs_applier_user(applier_frontend):
 | 
			
		||||
            drive_settings['label'] = remove_escaped_quotes(drv.label)
 | 
			
		||||
            drive_settings['persistent'] = drv.persistent
 | 
			
		||||
            drive_settings['useLetter'] = drv.useLetter
 | 
			
		||||
            drive_settings['username'] = self.username
 | 
			
		||||
            drive_settings['cifsacl'] = False if self.cifsacl_disable else True
 | 
			
		||||
 | 
			
		||||
            drive_list.append(drive_settings)
 | 
			
		||||
 | 
			
		||||
        if drive_list.len() > 0:
 | 
			
		||||
            mount_settings = dict()
 | 
			
		||||
            mount_settings = {}
 | 
			
		||||
            mount_settings['drives'] = drive_list()
 | 
			
		||||
            mount_text = self.template_mountpoints.render(**mount_settings)
 | 
			
		||||
 | 
			
		||||
@@ -276,10 +344,12 @@ class cifs_applier_user(applier_frontend):
 | 
			
		||||
                f.write(mount_text_hide)
 | 
			
		||||
                f.flush()
 | 
			
		||||
 | 
			
		||||
            autofs_settings = dict()
 | 
			
		||||
            autofs_settings = {}
 | 
			
		||||
            autofs_settings['home_dir'] = self.home
 | 
			
		||||
            autofs_settings['mntTarget'] = self.mntTarget
 | 
			
		||||
            autofs_settings['mount_file'] = self.user_config.resolve()
 | 
			
		||||
            autofs_settings['timeout'] = self.timeout.data if self.timeout and self.timeout.data else 120
 | 
			
		||||
 | 
			
		||||
            autofs_text = self.template_auto.render(**autofs_settings)
 | 
			
		||||
 | 
			
		||||
            with open(self.user_autofs.resolve(), 'w') as f:
 | 
			
		||||
@@ -294,59 +364,109 @@ class cifs_applier_user(applier_frontend):
 | 
			
		||||
                f.write(autofs_text)
 | 
			
		||||
                f.flush()
 | 
			
		||||
 | 
			
		||||
            if self.username:
 | 
			
		||||
                self.update_drivemaps_home_links()
 | 
			
		||||
        if self.is_changed_keys() or (self.drives and not self.is_mount_point_dirname()):
 | 
			
		||||
            self.restart_autofs()
 | 
			
		||||
 | 
			
		||||
        if self.username:
 | 
			
		||||
            self.update_drivemaps_home_links()
 | 
			
		||||
 | 
			
		||||
    def restart_autofs(self):
 | 
			
		||||
        try:
 | 
			
		||||
            subprocess.check_call(['/bin/systemctl', 'restart', 'autofs'])
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            log('E74', {'exc': exc})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def unlink_symlink(self, symlink:Path, previous=None):
 | 
			
		||||
        try:
 | 
			
		||||
            if symlink.exists() and symlink.is_symlink() and symlink.owner() == 'root':
 | 
			
		||||
                symlink.unlink()
 | 
			
		||||
            elif symlink.is_symlink() and not symlink.exists():
 | 
			
		||||
                symlink.unlink()
 | 
			
		||||
            elif previous:
 | 
			
		||||
                symlink.unlink()
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def del_previous_link(self, previous_value_link , mountpoint_dirname, prefix):
 | 
			
		||||
        d_previous = Path(self.homedir + ('/' if prefix else '/net.') + previous_value_link)
 | 
			
		||||
        if d_previous.name != mountpoint_dirname:
 | 
			
		||||
            dHide_previous = Path(self.homedir + ('/.' if prefix else '/.net.') + previous_value_link)
 | 
			
		||||
            self.unlink_symlink(d_previous, True)
 | 
			
		||||
            self.unlink_symlink(dHide_previous, True)
 | 
			
		||||
 | 
			
		||||
    def update_drivemaps_home_links(self):
 | 
			
		||||
        dUser = Path(get_homedir(self.username)+'/net.' + self.__mountpoint_dirname_user)
 | 
			
		||||
        dUserHide = Path(get_homedir(self.username)+'/.net.' + self.__mountpoint_dirname_user)
 | 
			
		||||
        if  self.state_home_link_disable_net:
 | 
			
		||||
            prefix = ''
 | 
			
		||||
        else:
 | 
			
		||||
            prefix = 'net.'
 | 
			
		||||
        if self.state_home_link_disable_net_user:
 | 
			
		||||
            prefix_user = ''
 | 
			
		||||
        else:
 | 
			
		||||
            prefix_user = 'net.'
 | 
			
		||||
 | 
			
		||||
        previous_value_link = self.keys_cifs_previous_values_machine.get(self.__name_value, self.__mountpoint_dirname)
 | 
			
		||||
        previous_state_home_link_disable_net_user = self.keys_cifs_previous_values_user.get(self.__key_link_prefix_user)
 | 
			
		||||
        previous_state_home_link_disable_net = self.keys_cifs_previous_values_user.get(self.__key_link_prefix)
 | 
			
		||||
        previous_value_link_user = self.keys_cifs_previous_values_user.get(self.__name_value_user, self.__mountpoint_dirname_user)
 | 
			
		||||
 | 
			
		||||
        self.homedir = get_homedir(self.username)
 | 
			
		||||
 | 
			
		||||
        dUser = Path(self.homedir + '/' + prefix_user + self.__mountpoint_dirname_user)
 | 
			
		||||
        dUserHide = Path(self.homedir + '/.' + prefix_user + self.__mountpoint_dirname_user)
 | 
			
		||||
        dMachine = Path(self.homedir+'/' + prefix + self.__mountpoint_dirname)
 | 
			
		||||
        dMachineHide = Path(self.homedir+'/.' + prefix + self.__mountpoint_dirname)
 | 
			
		||||
 | 
			
		||||
        if self.state_home_link_user:
 | 
			
		||||
            dUserMountpoint = Path(self.home).joinpath(self.__mountpoint_dirname_user)
 | 
			
		||||
            dUserMountpointHide = Path(self.home).joinpath('.' + self.__mountpoint_dirname_user)
 | 
			
		||||
 | 
			
		||||
            if not dUser.exists():
 | 
			
		||||
            self.del_previous_link(previous_value_link_user, dUser.name, previous_state_home_link_disable_net_user)
 | 
			
		||||
            if not dUser.exists() and dUserMountpoint.exists():
 | 
			
		||||
                try:
 | 
			
		||||
                    os.symlink(dUserMountpoint, dUser, True)
 | 
			
		||||
                except  Exception as exc:
 | 
			
		||||
                    log('D194', {'exc': exc})
 | 
			
		||||
            elif dUser.is_symlink() and not dUserMountpoint.exists():
 | 
			
		||||
                self.unlink_symlink(dUser)
 | 
			
		||||
 | 
			
		||||
            if not dUserHide.exists():
 | 
			
		||||
            if not dUserHide.exists() and dUserMountpointHide.exists():
 | 
			
		||||
                try:
 | 
			
		||||
                    os.symlink(dUserMountpointHide, dUserHide, True)
 | 
			
		||||
                except  Exception as exc:
 | 
			
		||||
                    log('D196', {'exc': exc})
 | 
			
		||||
            elif dUserHide.is_symlink() and not dUserMountpointHide.exists():
 | 
			
		||||
                self.unlink_symlink(dUserHide)
 | 
			
		||||
        else:
 | 
			
		||||
            if dUser.is_symlink() and dUser.owner() == 'root':
 | 
			
		||||
                dUser.unlink()
 | 
			
		||||
            if dUserHide.is_symlink() and dUserHide.owner() == 'root':
 | 
			
		||||
                dUserHide.unlink()
 | 
			
		||||
            self.del_previous_link(previous_value_link_user, dUser.name, previous_state_home_link_disable_net_user)
 | 
			
		||||
            self.unlink_symlink(dUser)
 | 
			
		||||
            self.unlink_symlink(dUserHide)
 | 
			
		||||
 | 
			
		||||
        dMachine = Path(get_homedir(self.username)+'/net.' + self.__mountpoint_dirname)
 | 
			
		||||
        dMachineHide = Path(get_homedir(self.username)+'/.net.' + self.__mountpoint_dirname)
 | 
			
		||||
 | 
			
		||||
        if self.state_home_link:
 | 
			
		||||
            dMachineMountpoint = Path(self.__target_mountpoint).joinpath(self.__mountpoint_dirname)
 | 
			
		||||
            dMachineMountpointHide = Path(self.__target_mountpoint).joinpath('.' + self.__mountpoint_dirname)
 | 
			
		||||
            self.del_previous_link(previous_value_link, dMachine.name, previous_state_home_link_disable_net)
 | 
			
		||||
 | 
			
		||||
            if not dMachine.exists():
 | 
			
		||||
            if not dMachine.exists() and dMachineMountpoint.exists():
 | 
			
		||||
                try:
 | 
			
		||||
                    os.symlink(dMachineMountpoint, dMachine, True)
 | 
			
		||||
                except  Exception as exc:
 | 
			
		||||
                    log('D195', {'exc': exc})
 | 
			
		||||
            elif dMachine.is_symlink() and not dMachineMountpoint.exists():
 | 
			
		||||
                self.unlink_symlink(dMachine)
 | 
			
		||||
 | 
			
		||||
            if not dMachineHide.exists():
 | 
			
		||||
            if not dMachineHide.exists() and dMachineMountpointHide.exists():
 | 
			
		||||
                try:
 | 
			
		||||
                    os.symlink(dMachineMountpointHide, dMachineHide, True)
 | 
			
		||||
                except  Exception as exc:
 | 
			
		||||
                    log('D197', {'exc': exc})
 | 
			
		||||
            elif dMachineHide.is_symlink() and not dMachineMountpointHide.exists():
 | 
			
		||||
                self.unlink_symlink(dMachineHide)
 | 
			
		||||
        else:
 | 
			
		||||
            if dMachine.is_symlink() and dMachine.owner() == 'root':
 | 
			
		||||
                dMachine.unlink()
 | 
			
		||||
            if dMachineHide.is_symlink() and dMachineHide.owner() == 'root':
 | 
			
		||||
                dMachineHide.unlink()
 | 
			
		||||
            self.del_previous_link(previous_value_link, dMachine.name, previous_state_home_link_disable_net)
 | 
			
		||||
            self.unlink_symlink(dMachine)
 | 
			
		||||
            self.unlink_symlink(dMachineHide)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def admin_context_apply(self):
 | 
			
		||||
        if self.__module_enabled:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,9 +21,8 @@ from .applier_frontend import (
 | 
			
		||||
    , check_enabled
 | 
			
		||||
)
 | 
			
		||||
from .appliers.control import control
 | 
			
		||||
from util.logging import slogm, log
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
class control_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'ControlApplier'
 | 
			
		||||
@@ -34,7 +33,7 @@ class control_applier(applier_frontend):
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.control_settings = self.storage.filter_hklm_entries(self._registry_branch)
 | 
			
		||||
        self.controls = list()
 | 
			
		||||
        self.controls = []
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
@@ -46,9 +45,7 @@ class control_applier(applier_frontend):
 | 
			
		||||
            valuename = setting.hive_key.rpartition('/')[2]
 | 
			
		||||
            try:
 | 
			
		||||
                self.controls.append(control(valuename, int(setting.data)))
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['control'] = valuename
 | 
			
		||||
                logdata['value'] = setting.data
 | 
			
		||||
                logdata = {'control': valuename, 'value': setting.data}
 | 
			
		||||
                log('I3', logdata)
 | 
			
		||||
            except ValueError as exc:
 | 
			
		||||
                try:
 | 
			
		||||
@@ -58,14 +55,10 @@ class control_applier(applier_frontend):
 | 
			
		||||
                    log('I3', logdata)
 | 
			
		||||
                    continue
 | 
			
		||||
                self.controls.append(ctl)
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['control'] = valuename
 | 
			
		||||
                logdata['with string value'] = setting.data
 | 
			
		||||
                logdata = {'control': valuename, 'with string value': setting.data}
 | 
			
		||||
                log('I3', logdata)
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['control'] = valuename
 | 
			
		||||
                logdata['exc'] = exc
 | 
			
		||||
                logdata = {'control': valuename, 'exc': exc}
 | 
			
		||||
                log('E39', logdata)
 | 
			
		||||
        #for e in polfile.pol_file.entries:
 | 
			
		||||
        #    print('{}:{}:{}:{}:{}'.format(e.type, e.data, e.valuename, e.keyname))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -16,26 +16,22 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
import cups
 | 
			
		||||
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
      applier_frontend
 | 
			
		||||
    , check_enabled
 | 
			
		||||
)
 | 
			
		||||
from gpt.printers import json2printer
 | 
			
		||||
from util.rpm import is_rpm_installed
 | 
			
		||||
from util.logging import slogm, log
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
def storage_get_printers(storage, sid):
 | 
			
		||||
def storage_get_printers(storage):
 | 
			
		||||
    '''
 | 
			
		||||
    Query printers configuration from storage
 | 
			
		||||
    '''
 | 
			
		||||
    printer_objs = storage.get_printers(sid)
 | 
			
		||||
    printers = list()
 | 
			
		||||
    printer_objs = storage.get_printers()
 | 
			
		||||
    printers = []
 | 
			
		||||
 | 
			
		||||
    for prnj in printer_objs:
 | 
			
		||||
        printers.append(prnj)
 | 
			
		||||
@@ -66,8 +62,8 @@ def connect_printer(connection, prn):
 | 
			
		||||
 | 
			
		||||
class cups_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'CUPSApplier'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
@@ -84,10 +80,9 @@ class cups_applier(applier_frontend):
 | 
			
		||||
        try:
 | 
			
		||||
            self.cups_connection = cups.Connection()
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['exc', exc]
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('W20', logdata)
 | 
			
		||||
        self.printers = storage_get_printers(self.storage, self.storage.get_info('machine_sid'))
 | 
			
		||||
        self.printers = storage_get_printers(self.storage)
 | 
			
		||||
 | 
			
		||||
        if self.printers:
 | 
			
		||||
            for prn in self.printers:
 | 
			
		||||
@@ -105,17 +100,16 @@ class cups_applier(applier_frontend):
 | 
			
		||||
 | 
			
		||||
class cups_applier_user(applier_frontend):
 | 
			
		||||
    __module_name = 'CUPSApplierUser'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
            , self.__module_enabled
 | 
			
		||||
            , self.__module_experimental
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def user_context_apply(self):
 | 
			
		||||
@@ -131,7 +125,7 @@ class cups_applier_user(applier_frontend):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self.cups_connection = cups.Connection()
 | 
			
		||||
        self.printers = storage_get_printers(self.storage, self.sid)
 | 
			
		||||
        self.printers = storage_get_printers(self.storage)
 | 
			
		||||
 | 
			
		||||
        if self.printers:
 | 
			
		||||
            for prn in self.printers:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,20 +21,19 @@ from .applier_frontend import (
 | 
			
		||||
    , check_enabled
 | 
			
		||||
)
 | 
			
		||||
from .appliers.envvar import Envvar
 | 
			
		||||
from util.logging import slogm, log
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
class envvar_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'EnvvarsApplier'
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid):
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.envvars = self.storage.get_envvars(self.sid)
 | 
			
		||||
        #self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_enabled)
 | 
			
		||||
        self.envvars = self.storage.get_envvars()
 | 
			
		||||
        Envvar.clear_envvar_file()
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
        if self.__module_enabled:
 | 
			
		||||
@@ -49,17 +48,14 @@ class envvar_applier_user(applier_frontend):
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.envvars = self.storage.get_envvars(self.sid)
 | 
			
		||||
        #self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
        self.envvars = self.storage.get_envvars()
 | 
			
		||||
        Envvar.clear_envvar_file(username)
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
 | 
			
		||||
    def admin_context_apply(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def user_context_apply(self):
 | 
			
		||||
        if self.__module_enabled:
 | 
			
		||||
            log('D136')
 | 
			
		||||
            ev = Envvar(self.envvars, self.username)
 | 
			
		||||
@@ -67,3 +63,6 @@ class envvar_applier_user(applier_frontend):
 | 
			
		||||
        else:
 | 
			
		||||
            log('D137')
 | 
			
		||||
 | 
			
		||||
    def user_context_apply(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -28,15 +28,14 @@ from util.logging import log
 | 
			
		||||
 | 
			
		||||
class file_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'FilesApplier'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, file_cache, sid):
 | 
			
		||||
    def __init__(self, storage, file_cache):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.exe_check = Execution_check(storage)
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.file_cache = file_cache
 | 
			
		||||
        self.files = self.storage.get_files(self.sid)
 | 
			
		||||
        self.files = self.storage.get_files()
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
@@ -52,16 +51,15 @@ class file_applier(applier_frontend):
 | 
			
		||||
 | 
			
		||||
class file_applier_user(applier_frontend):
 | 
			
		||||
    __module_name = 'FilesApplierUser'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, file_cache, sid, username):
 | 
			
		||||
    def __init__(self, storage, file_cache, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.file_cache = file_cache
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.exe_check = Execution_check(storage)
 | 
			
		||||
        self.files = self.storage.get_files(self.sid)
 | 
			
		||||
        self.files = self.storage.get_files()
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -40,114 +40,35 @@ class firefox_applier(applier_frontend):
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __registry_branch = 'Software/Policies/Mozilla/Firefox'
 | 
			
		||||
    __firefox_installdir1 = '/usr/lib64/firefox/distribution'
 | 
			
		||||
    __firefox_installdir2 = '/etc/firefox/policies'
 | 
			
		||||
    __firefox_policies = '/etc/firefox/policies'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self._is_machine_name = is_machine_name(self.username)
 | 
			
		||||
        self.policies = dict()
 | 
			
		||||
        self.policies_json = dict({ 'policies': self.policies })
 | 
			
		||||
        firefox_filter = '{}%'.format(self.__registry_branch)
 | 
			
		||||
        self.firefox_keys = self.storage.filter_hklm_entries(firefox_filter)
 | 
			
		||||
        self.policies_gen = dict()
 | 
			
		||||
        self.policies = {}
 | 
			
		||||
        self.policies_json = {'policies': self.policies}
 | 
			
		||||
        self.firefox_keys = self.storage.filter_hklm_entries(self.__registry_branch)
 | 
			
		||||
        self.policies_gen = {}
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
            , self.__module_experimental
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_boolean(self,data):
 | 
			
		||||
        if data in ['0', 'false', None, 'none', 0]:
 | 
			
		||||
            return False
 | 
			
		||||
        if data in ['1', 'true', 1]:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    def get_parts(self, hivekeyname):
 | 
			
		||||
        '''
 | 
			
		||||
        Parse registry path string and leave key parameters
 | 
			
		||||
        '''
 | 
			
		||||
        parts = hivekeyname.replace(self.__registry_branch, '').split('/')
 | 
			
		||||
        return parts
 | 
			
		||||
 | 
			
		||||
    def create_dict(self, firefox_keys):
 | 
			
		||||
        '''
 | 
			
		||||
        Collect dictionaries from registry keys into a general dictionary
 | 
			
		||||
        '''
 | 
			
		||||
        excp = ['SOCKSVersion']
 | 
			
		||||
        counts = dict()
 | 
			
		||||
        for it_data in firefox_keys:
 | 
			
		||||
            branch = counts
 | 
			
		||||
            try:
 | 
			
		||||
                if type(it_data.data) is bytes:
 | 
			
		||||
                    it_data.data = it_data.data.decode(encoding='utf-16').replace('\x00','')
 | 
			
		||||
                json_data = try_dict_to_literal_eval(it_data.data)
 | 
			
		||||
                if json_data:
 | 
			
		||||
                    it_data.data = json_data
 | 
			
		||||
                    it_data.type = 7
 | 
			
		||||
                else:
 | 
			
		||||
                    if it_data.type == 1:
 | 
			
		||||
                        it_data.data = clean_data_firefox(it_data.data)
 | 
			
		||||
                #Cases when it is necessary to create nested dictionaries
 | 
			
		||||
                if it_data.valuename != it_data.data:
 | 
			
		||||
                    parts = self.get_parts(it_data.hive_key)
 | 
			
		||||
                    #creating a nested dictionary from elements
 | 
			
		||||
                    for part in parts[:-1]:
 | 
			
		||||
                        branch = branch.setdefault(part, {})
 | 
			
		||||
                    #dictionary key value initialization
 | 
			
		||||
                    if it_data.type == 4:
 | 
			
		||||
                        if it_data.valuename in excp:
 | 
			
		||||
                            branch[parts[-1]] = int(it_data.data)
 | 
			
		||||
                        else:
 | 
			
		||||
                            branch[parts[-1]] = self.get_boolean(it_data.data)
 | 
			
		||||
                    elif it_data.type == 7:
 | 
			
		||||
                        branch[parts[-1]] = it_data.data
 | 
			
		||||
                    else:
 | 
			
		||||
                        branch[parts[-1]] = str(it_data.data).replace('\\', '/')
 | 
			
		||||
                #Cases when it is necessary to create lists in a dictionary
 | 
			
		||||
                else:
 | 
			
		||||
                    parts = self.get_parts(it_data.keyname)
 | 
			
		||||
                    for part in parts[:-1]:
 | 
			
		||||
                        branch = branch.setdefault(part, {})
 | 
			
		||||
                    if branch.get(parts[-1]) is None:
 | 
			
		||||
                        branch[parts[-1]] = list()
 | 
			
		||||
                    if it_data.type == 4:
 | 
			
		||||
                        branch[parts[-1]].append(self.get_boolean(it_data.data))
 | 
			
		||||
                    else:
 | 
			
		||||
                        if os.path.isdir(str(it_data.data).replace('\\', '/')):
 | 
			
		||||
                            branch[parts[-1]].append(str(it_data.data).replace('\\', '/'))
 | 
			
		||||
                        else:
 | 
			
		||||
                            branch[parts[-1]].append(str(it_data.data))
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['Exception'] = exc
 | 
			
		||||
                logdata['keyname'] = it_data.keyname
 | 
			
		||||
                log('W14', logdata)
 | 
			
		||||
 | 
			
		||||
        self.policies_json = {'policies': dict_item_to_list(counts)}
 | 
			
		||||
 | 
			
		||||
    def machine_apply(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Write policies.json to Firefox installdir.
 | 
			
		||||
        Write policies.json to Firefox.
 | 
			
		||||
        '''
 | 
			
		||||
        self.create_dict(self.firefox_keys)
 | 
			
		||||
        destfile = os.path.join(self.__firefox_installdir1, 'policies.json')
 | 
			
		||||
        excp = ['SOCKSVersion']
 | 
			
		||||
        self.policies_json = create_dict(self.firefox_keys, self.__registry_branch, excp)
 | 
			
		||||
 | 
			
		||||
        os.makedirs(self.__firefox_installdir1, exist_ok=True)
 | 
			
		||||
        destfile = os.path.join(self.__firefox_policies, 'policies.json')
 | 
			
		||||
        os.makedirs(self.__firefox_policies, exist_ok=True)
 | 
			
		||||
        with open(destfile, 'w') as f:
 | 
			
		||||
            json.dump(self.policies_json, f)
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['destfile'] = destfile
 | 
			
		||||
            log('D91', logdata)
 | 
			
		||||
 | 
			
		||||
        destfile = os.path.join(self.__firefox_installdir2, 'policies.json')
 | 
			
		||||
        os.makedirs(self.__firefox_installdir2, exist_ok=True)
 | 
			
		||||
        with open(destfile, 'w') as f:
 | 
			
		||||
            json.dump(self.policies_json, f)
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['destfile'] = destfile
 | 
			
		||||
            logdata = {'destfile': destfile}
 | 
			
		||||
            log('D91', logdata)
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
@@ -186,3 +107,60 @@ def dict_item_to_list(dictionary:dict) -> dict:
 | 
			
		||||
 | 
			
		||||
def clean_data_firefox(data):
 | 
			
		||||
    return data.replace("'", '\"')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_dict(firefox_keys, registry_branch, excp=[]):
 | 
			
		||||
    '''
 | 
			
		||||
    Collect dictionaries from registry keys into a general dictionary
 | 
			
		||||
    '''
 | 
			
		||||
    get_boolean = lambda data: data in ['1', 'true', 'True', True, 1] if isinstance(data, (str, int)) else False
 | 
			
		||||
    get_parts = lambda hivekey, registry: hivekey.replace(registry, '').split('/')
 | 
			
		||||
    counts = {}
 | 
			
		||||
    for it_data in firefox_keys:
 | 
			
		||||
        branch = counts
 | 
			
		||||
        try:
 | 
			
		||||
            if type(it_data.data) is bytes:
 | 
			
		||||
                it_data.data = it_data.data.decode(encoding='utf-16').replace('\x00','')
 | 
			
		||||
            json_data = try_dict_to_literal_eval(it_data.data)
 | 
			
		||||
            if json_data:
 | 
			
		||||
                it_data.data = json_data
 | 
			
		||||
                it_data.type = 7
 | 
			
		||||
            else:
 | 
			
		||||
                if it_data.type == 1:
 | 
			
		||||
                    it_data.data = clean_data_firefox(it_data.data)
 | 
			
		||||
            #Cases when it is necessary to create nested dictionaries
 | 
			
		||||
            if it_data.valuename != it_data.data:
 | 
			
		||||
                parts = get_parts(it_data.hive_key, registry_branch)
 | 
			
		||||
                #creating a nested dictionary from elements
 | 
			
		||||
                for part in parts[:-1]:
 | 
			
		||||
                    branch = branch.setdefault(part, {})
 | 
			
		||||
                #dictionary key value initialization
 | 
			
		||||
                if it_data.type == 4:
 | 
			
		||||
                    if it_data.valuename in excp:
 | 
			
		||||
                        branch[parts[-1]] = int(it_data.data)
 | 
			
		||||
                    else:
 | 
			
		||||
                        branch[parts[-1]] = get_boolean(it_data.data)
 | 
			
		||||
                elif it_data.type == 7:
 | 
			
		||||
                    branch[parts[-1]] = it_data.data
 | 
			
		||||
                else:
 | 
			
		||||
                    branch[parts[-1]] = str(it_data.data).replace('\\', '/')
 | 
			
		||||
            #Cases when it is necessary to create lists in a dictionary
 | 
			
		||||
            else:
 | 
			
		||||
                parts = get_parts(it_data.keyname, registry_branch)
 | 
			
		||||
                for part in parts[:-1]:
 | 
			
		||||
                    branch = branch.setdefault(part, {})
 | 
			
		||||
                if branch.get(parts[-1]) is None:
 | 
			
		||||
                    branch[parts[-1]] = []
 | 
			
		||||
                if it_data.type == 4:
 | 
			
		||||
                    branch[parts[-1]].append(get_boolean(it_data.data))
 | 
			
		||||
                else:
 | 
			
		||||
                    if os.path.isdir(str(it_data.data).replace('\\', '/')):
 | 
			
		||||
                        branch[parts[-1]].append(str(it_data.data).replace('\\', '/'))
 | 
			
		||||
                    else:
 | 
			
		||||
                        branch[parts[-1]].append(str(it_data.data))
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'Exception': exc, 'keyname': it_data.keyname}
 | 
			
		||||
            log('W14', logdata)
 | 
			
		||||
 | 
			
		||||
    return {'policies': dict_item_to_list(counts)}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -17,10 +17,9 @@
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
from util.logging import slogm, log
 | 
			
		||||
from util.logging import log
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
      applier_frontend
 | 
			
		||||
    , check_enabled
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -16,7 +16,6 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
      applier_frontend
 | 
			
		||||
@@ -32,10 +31,9 @@ class folder_applier(applier_frontend):
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid):
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.folders = self.storage.get_folders(self.sid)
 | 
			
		||||
        self.folders = self.storage.get_folders()
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
@@ -58,11 +56,10 @@ class folder_applier_user(applier_frontend):
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.folders = self.storage.get_folders(self.sid)
 | 
			
		||||
        self.folders = self.storage.get_folders()
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -26,6 +26,7 @@ from .polkit_applier import (
 | 
			
		||||
)
 | 
			
		||||
from .systemd_applier import systemd_applier
 | 
			
		||||
from .firefox_applier import firefox_applier
 | 
			
		||||
from .thunderbird_applier import thunderbird_applier
 | 
			
		||||
from .chromium_applier import chromium_applier
 | 
			
		||||
from .cups_applier import cups_applier
 | 
			
		||||
from .package_applier import (
 | 
			
		||||
@@ -72,11 +73,11 @@ from .kde_applier import (
 | 
			
		||||
      kde_applier
 | 
			
		||||
    , kde_applier_user
 | 
			
		||||
)
 | 
			
		||||
from .laps_applier import laps_applier
 | 
			
		||||
 | 
			
		||||
from .networkshare_applier import networkshare_applier
 | 
			
		||||
from .yandex_browser_applier import yandex_browser_applier
 | 
			
		||||
 | 
			
		||||
from util.sid import get_sid
 | 
			
		||||
from util.users import (
 | 
			
		||||
    is_root,
 | 
			
		||||
    get_process_user,
 | 
			
		||||
@@ -95,16 +96,15 @@ def determine_username(username=None):
 | 
			
		||||
 | 
			
		||||
    # If username is not set then it will be the name
 | 
			
		||||
    # of process owner.
 | 
			
		||||
    logdata = {'username': name}
 | 
			
		||||
    if not username:
 | 
			
		||||
        name = get_process_user()
 | 
			
		||||
        logdata = dict({'username': name})
 | 
			
		||||
        log('D2', logdata)
 | 
			
		||||
 | 
			
		||||
    if not username_match_uid(name):
 | 
			
		||||
        if not is_root():
 | 
			
		||||
            raise Exception('Current process UID does not match specified username')
 | 
			
		||||
 | 
			
		||||
    logdata = dict({'username': name})
 | 
			
		||||
    log('D15', logdata)
 | 
			
		||||
 | 
			
		||||
    return name
 | 
			
		||||
@@ -116,9 +116,7 @@ def apply_user_context(user_appliers):
 | 
			
		||||
        try:
 | 
			
		||||
            applier_object.user_context_apply()
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['applier'] = applier_name
 | 
			
		||||
            logdata['exception'] = str(exc)
 | 
			
		||||
            logdata = {'applier': applier_name, 'exception': str(exc)}
 | 
			
		||||
            log('E20', logdata)
 | 
			
		||||
 | 
			
		||||
class frontend_manager:
 | 
			
		||||
@@ -132,7 +130,6 @@ class frontend_manager:
 | 
			
		||||
        self.storage = registry_factory('dconf', username=self.username)
 | 
			
		||||
        self.is_machine = is_machine
 | 
			
		||||
        self.process_uname = get_process_user()
 | 
			
		||||
        self.sid = get_sid(self.storage.get_info('domain'), self.username, is_machine)
 | 
			
		||||
        self.file_cache = fs_file_cache('file_cache', self.username)
 | 
			
		||||
 | 
			
		||||
        self.machine_appliers = dict()
 | 
			
		||||
@@ -143,54 +140,52 @@ class frontend_manager:
 | 
			
		||||
            self._init_user_appliers()
 | 
			
		||||
 | 
			
		||||
    def _init_machine_appliers(self):
 | 
			
		||||
        self.machine_appliers['laps_applier'] = laps_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['control'] = control_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['polkit'] = polkit_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['systemd'] = systemd_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['firefox'] = firefox_applier(self.storage, self.sid, self.username)
 | 
			
		||||
        self.machine_appliers['chromium'] = chromium_applier(self.storage, self.sid, self.username)
 | 
			
		||||
        self.machine_appliers['yandex_browser'] = yandex_browser_applier(self.storage, self.sid, self.username)
 | 
			
		||||
        self.machine_appliers['firefox'] = firefox_applier(self.storage, self.username)
 | 
			
		||||
        self.machine_appliers['thunderbird'] = thunderbird_applier(self.storage, self.username)
 | 
			
		||||
        self.machine_appliers['chromium'] = chromium_applier(self.storage, self.username)
 | 
			
		||||
        self.machine_appliers['yandex_browser'] = yandex_browser_applier(self.storage, self.username)
 | 
			
		||||
        self.machine_appliers['shortcuts'] = shortcut_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['gsettings'] = gsettings_applier(self.storage, self.file_cache)
 | 
			
		||||
        try:
 | 
			
		||||
            self.machine_appliers['cifs'] = cifs_applier(self.storage, self.sid)
 | 
			
		||||
            self.machine_appliers['cifs'] = cifs_applier(self.storage)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['applier_name'] = 'cifs'
 | 
			
		||||
            logdata['msg'] = str(exc)
 | 
			
		||||
            logdata = {'applier_name': 'cifs', 'msg': str(exc)}
 | 
			
		||||
            log('E24', logdata)
 | 
			
		||||
        self.machine_appliers['cups'] = cups_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['firewall'] = firewall_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['folders'] = folder_applier(self.storage, self.sid)
 | 
			
		||||
        self.machine_appliers['package'] = package_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['folders'] = folder_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['ntp'] = ntp_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['envvar'] = envvar_applier(self.storage, self.sid)
 | 
			
		||||
        self.machine_appliers['networkshare'] = networkshare_applier(self.storage, self.sid)
 | 
			
		||||
        self.machine_appliers['scripts'] = scripts_applier(self.storage, self.sid)
 | 
			
		||||
        self.machine_appliers['files'] = file_applier(self.storage, self.file_cache, self.sid)
 | 
			
		||||
        self.machine_appliers['ini'] = ini_applier(self.storage, self.sid)
 | 
			
		||||
        self.machine_appliers['envvar'] = envvar_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['networkshare'] = networkshare_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['scripts'] = scripts_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['files'] = file_applier(self.storage, self.file_cache)
 | 
			
		||||
        self.machine_appliers['ini'] = ini_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['kde'] = kde_applier(self.storage)
 | 
			
		||||
        self.machine_appliers['package'] = package_applier(self.storage)
 | 
			
		||||
 | 
			
		||||
    def _init_user_appliers(self):
 | 
			
		||||
        # User appliers are expected to work with user-writable
 | 
			
		||||
        # files and settings, mostly in $HOME.
 | 
			
		||||
        self.user_appliers['shortcuts'] = shortcut_applier_user(self.storage, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['folders'] = folder_applier_user(self.storage, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['gsettings'] = gsettings_applier_user(self.storage, self.file_cache, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['shortcuts'] = shortcut_applier_user(self.storage, self.username)
 | 
			
		||||
        self.user_appliers['folders'] = folder_applier_user(self.storage, self.username)
 | 
			
		||||
        self.user_appliers['gsettings'] = gsettings_applier_user(self.storage, self.file_cache, self.username)
 | 
			
		||||
        try:
 | 
			
		||||
            self.user_appliers['cifs'] = cifs_applier_user(self.storage, self.sid, self.username)
 | 
			
		||||
            self.user_appliers['cifs'] = cifs_applier_user(self.storage, self.username)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['applier_name'] = 'cifs'
 | 
			
		||||
            logdata['msg'] = str(exc)
 | 
			
		||||
            logdata = {'applier_name': 'cifs', 'msg': str(exc)}
 | 
			
		||||
            log('E25', logdata)
 | 
			
		||||
        self.user_appliers['package'] = package_applier_user(self.storage, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['polkit'] = polkit_applier_user(self.storage, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['envvar'] = envvar_applier_user(self.storage, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['networkshare'] = networkshare_applier(self.storage, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['scripts'] = scripts_applier_user(self.storage, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['files'] = file_applier_user(self.storage, self.file_cache, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['ini'] = ini_applier_user(self.storage, self.sid, self.username)
 | 
			
		||||
        self.user_appliers['kde'] = kde_applier_user(self.storage, self.sid, self.username, self.file_cache)
 | 
			
		||||
        self.user_appliers['polkit'] = polkit_applier_user(self.storage, self.username)
 | 
			
		||||
        self.user_appliers['envvar'] = envvar_applier_user(self.storage, self.username)
 | 
			
		||||
        self.user_appliers['networkshare'] = networkshare_applier(self.storage, self.username)
 | 
			
		||||
        self.user_appliers['scripts'] = scripts_applier_user(self.storage, self.username)
 | 
			
		||||
        self.user_appliers['files'] = file_applier_user(self.storage, self.file_cache, self.username)
 | 
			
		||||
        self.user_appliers['ini'] = ini_applier_user(self.storage, self.username)
 | 
			
		||||
        self.user_appliers['kde'] = kde_applier_user(self.storage, self.username, self.file_cache)
 | 
			
		||||
        self.user_appliers['package'] = package_applier_user(self.storage, self.username)
 | 
			
		||||
 | 
			
		||||
    def machine_apply(self):
 | 
			
		||||
        '''
 | 
			
		||||
@@ -205,9 +200,7 @@ class frontend_manager:
 | 
			
		||||
            try:
 | 
			
		||||
                applier_object.apply()
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['applier_name'] = applier_name
 | 
			
		||||
                logdata['msg'] = str(exc)
 | 
			
		||||
                logdata = {'applier_name': applier_name, 'msg': str(exc)}
 | 
			
		||||
                log('E24', logdata)
 | 
			
		||||
 | 
			
		||||
    def user_apply(self):
 | 
			
		||||
@@ -219,24 +212,20 @@ class frontend_manager:
 | 
			
		||||
                try:
 | 
			
		||||
                    applier_object.admin_context_apply()
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = dict()
 | 
			
		||||
                    logdata['applier'] = applier_name
 | 
			
		||||
                    logdata['exception'] = str(exc)
 | 
			
		||||
                    logdata = {'applier': applier_name, 'exception': str(exc)}
 | 
			
		||||
                    log('E19', logdata)
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                with_privileges(self.username, lambda: apply_user_context(self.user_appliers))
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['username'] = self.username
 | 
			
		||||
                logdata['exception'] = str(exc)
 | 
			
		||||
                logdata = {'username': self.username, 'exception': str(exc)}
 | 
			
		||||
                log('E30', logdata)
 | 
			
		||||
        else:
 | 
			
		||||
            for applier_name, applier_object in self.user_appliers.items():
 | 
			
		||||
                try:
 | 
			
		||||
                    applier_object.user_context_apply()
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = dict({'applier_name': applier_name, 'message': str(exc)})
 | 
			
		||||
                    logdata = {'applier_name': applier_name, 'message': str(exc)}
 | 
			
		||||
                    log('E11', logdata)
 | 
			
		||||
 | 
			
		||||
    def apply_parameters(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2021 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,10 +21,7 @@ import os
 | 
			
		||||
import pwd
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
from gi.repository import (
 | 
			
		||||
      Gio
 | 
			
		||||
    , GLib
 | 
			
		||||
)
 | 
			
		||||
from gi.repository import Gio
 | 
			
		||||
from storage.dconf_registry import Dconf_registry
 | 
			
		||||
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
@@ -36,19 +33,18 @@ from .appliers.gsettings import (
 | 
			
		||||
    system_gsettings,
 | 
			
		||||
    user_gsettings
 | 
			
		||||
)
 | 
			
		||||
from util.logging import slogm ,log
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
def uri_fetch(schema, path, value, cache):
 | 
			
		||||
    '''
 | 
			
		||||
    Function to fetch and cache uri
 | 
			
		||||
    '''
 | 
			
		||||
    retval = value
 | 
			
		||||
    logdata = dict()
 | 
			
		||||
    logdata['schema'] = schema
 | 
			
		||||
    logdata['path'] = path
 | 
			
		||||
    logdata['src'] = value
 | 
			
		||||
    logdata = {'schema': schema, 'path': path, 'src': value}
 | 
			
		||||
    try:
 | 
			
		||||
        retval = cache.get(value)
 | 
			
		||||
        if not retval:
 | 
			
		||||
            retval = ''
 | 
			
		||||
        logdata['dst'] = retval
 | 
			
		||||
        log('D90', logdata)
 | 
			
		||||
    except Exception as exc:
 | 
			
		||||
@@ -79,7 +75,7 @@ class gsettings_applier(applier_frontend):
 | 
			
		||||
        self.override_file = os.path.join(self.__global_schema, self.__override_priority_file)
 | 
			
		||||
        self.override_old_file = os.path.join(self.__global_schema, self.__override_old_file)
 | 
			
		||||
        self.gsettings = system_gsettings(self.override_file)
 | 
			
		||||
        self.locks = dict()
 | 
			
		||||
        self.locks = {}
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
@@ -90,8 +86,7 @@ class gsettings_applier(applier_frontend):
 | 
			
		||||
        try:
 | 
			
		||||
            self.file_cache.store(data)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['exception'] = str(exc)
 | 
			
		||||
            logdata = {'exception': str(exc)}
 | 
			
		||||
            log('D145', logdata)
 | 
			
		||||
 | 
			
		||||
    def uri_fetch_helper(self, schema, path, value):
 | 
			
		||||
@@ -159,10 +154,9 @@ class GSettingsMapping:
 | 
			
		||||
            self.gsettings_schema_key = self.schema.get_key(self.gsettings_key)
 | 
			
		||||
            self.gsettings_type = self.gsettings_schema_key.get_value_type()
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['hive_key'] = self.hive_key
 | 
			
		||||
            logdata['gsettings_schema'] = self.gsettings_schema
 | 
			
		||||
            logdata['gsettings_key'] = self.gsettings_key
 | 
			
		||||
            logdata = {'hive_key': self.hive_key,
 | 
			
		||||
                       'gsettings_schema': self.gsettings_schema,
 | 
			
		||||
                       'gsettings_key': self.gsettings_key}
 | 
			
		||||
            log('W6', logdata)
 | 
			
		||||
 | 
			
		||||
    def preg2gsettings(self):
 | 
			
		||||
@@ -186,19 +180,18 @@ class gsettings_applier_user(applier_frontend):
 | 
			
		||||
    __wallpaper_entry = 'Software/BaseALT/Policies/gsettings/org.mate.background.picture-filename'
 | 
			
		||||
    __vino_authentication_methods_entry = 'Software/BaseALT/Policies/gsettings/org.gnome.Vino.authentication-methods'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, file_cache, sid, username):
 | 
			
		||||
    def __init__(self, storage, file_cache, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.file_cache = file_cache
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        gsettings_filter = '{}%'.format(self.__registry_branch)
 | 
			
		||||
        self.gsettings_keys = self.storage.filter_hkcu_entries(self.sid, gsettings_filter)
 | 
			
		||||
        self.gsettings_keys = self.storage.filter_hkcu_entries(gsettings_filter)
 | 
			
		||||
        self.gsettings = user_gsettings()
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
        self.__windows_mapping_enabled = check_windows_mapping_enabled(self.storage)
 | 
			
		||||
 | 
			
		||||
        self.__windows_settings = dict()
 | 
			
		||||
        self.windows_settings = list()
 | 
			
		||||
        self.__windows_settings = {}
 | 
			
		||||
        self.windows_settings = []
 | 
			
		||||
        mapping = [
 | 
			
		||||
              # Disable or enable screen saver
 | 
			
		||||
              GSettingsMapping(
 | 
			
		||||
@@ -233,11 +226,9 @@ class gsettings_applier_user(applier_frontend):
 | 
			
		||||
 | 
			
		||||
    def windows_mapping_append(self):
 | 
			
		||||
        for setting_key in self.__windows_settings.keys():
 | 
			
		||||
            value = self.storage.get_hkcu_entry(self.sid, setting_key)
 | 
			
		||||
            value = self.storage.get_hkcu_entry(setting_key)
 | 
			
		||||
            if value:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['setting_key'] = setting_key
 | 
			
		||||
                logdata['value.data'] = value.data
 | 
			
		||||
                logdata = {'setting_key': setting_key, 'value.data': value.data}
 | 
			
		||||
                log('D86', logdata)
 | 
			
		||||
                mapping = self.__windows_settings[setting_key]
 | 
			
		||||
                try:
 | 
			
		||||
@@ -281,14 +272,13 @@ class gsettings_applier_user(applier_frontend):
 | 
			
		||||
        # Cache files on remote locations
 | 
			
		||||
        try:
 | 
			
		||||
            entry = self.__wallpaper_entry
 | 
			
		||||
            filter_result = self.storage.get_hkcu_entry(self.sid, entry)
 | 
			
		||||
            filter_result = self.storage.get_hkcu_entry(entry)
 | 
			
		||||
            if filter_result and filter_result.data:
 | 
			
		||||
                self.file_cache.store(filter_result.data)
 | 
			
		||||
        except NotUNCPathError:
 | 
			
		||||
            ...
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['exception'] = str(exc)
 | 
			
		||||
            logdata = {'exception': str(exc)}
 | 
			
		||||
            log('E50', logdata)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -16,7 +16,6 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
from .appliers.ini_file import Ini_file
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
@@ -27,13 +26,12 @@ from util.logging import log
 | 
			
		||||
 | 
			
		||||
class ini_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'InifilesApplier'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid):
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.inifiles_info = self.storage.get_ini(self.sid)
 | 
			
		||||
        self.inifiles_info = self.storage.get_ini()
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
@@ -49,14 +47,13 @@ class ini_applier(applier_frontend):
 | 
			
		||||
 | 
			
		||||
class ini_applier_user(applier_frontend):
 | 
			
		||||
    __module_name = 'InifilesApplierUser'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.inifiles_info = self.storage.get_ini(self.sid)
 | 
			
		||||
        self.inifiles_info = self.storage.get_ini()
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -24,13 +24,14 @@ import os
 | 
			
		||||
import subprocess
 | 
			
		||||
import re
 | 
			
		||||
import dbus
 | 
			
		||||
import shutil
 | 
			
		||||
 | 
			
		||||
class kde_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'KdeApplier'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __hklm_branch = 'Software\\BaseALT\\Policies\\KDE\\'
 | 
			
		||||
    __hklm_lock_branch = 'Software\\BaseALT\\Policies\\KDELocks\\'
 | 
			
		||||
    __hklm_branch = 'Software/BaseALT/Policies/KDE/'
 | 
			
		||||
    __hklm_lock_branch = 'Software/BaseALT/Policies/KDELocks/'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
@@ -61,21 +62,25 @@ class kde_applier_user(applier_frontend):
 | 
			
		||||
    __module_name = 'KdeApplierUser'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __hkcu_branch = 'Software\\BaseALT\\Policies\\KDE\\'
 | 
			
		||||
    __hkcu_lock_branch = 'Software\\BaseALT\\Policies\\KDELocks\\'
 | 
			
		||||
    kde_version = None
 | 
			
		||||
    __hkcu_branch = 'Software/BaseALT/Policies/KDE'
 | 
			
		||||
    __hkcu_lock_branch = 'Software/BaseALT/Policies/KDELocks'
 | 
			
		||||
    __plasma_update_entry = 'Software/BaseALT/Policies/KDE/Plasma/Update'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid=None, username=None, file_cache = None):
 | 
			
		||||
    def __init__(self, storage, username=None, file_cache = None):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.file_cache = file_cache
 | 
			
		||||
        self.locks_dict = {}
 | 
			
		||||
        self.locks_data_dict = {}
 | 
			
		||||
        self.all_kde_settings = {}
 | 
			
		||||
        kde_applier_user.kde_version = get_kde_version()
 | 
			
		||||
        kde_filter = '{}%'.format(self.__hkcu_branch)
 | 
			
		||||
        locks_filter = '{}%'.format(self.__hkcu_lock_branch)
 | 
			
		||||
        self.locks_settings = self.storage.filter_hkcu_entries(self.sid, locks_filter)
 | 
			
		||||
        self.kde_settings = self.storage.filter_hkcu_entries(self.sid, kde_filter)
 | 
			
		||||
        self.locks_settings = self.storage.filter_hkcu_entries(locks_filter)
 | 
			
		||||
        self.plasma_update = self.storage.get_entry(self.__plasma_update_entry)
 | 
			
		||||
        self.plasma_update_flag = self.plasma_update.data if self.plasma_update is not None else 0
 | 
			
		||||
        self.kde_settings = self.storage.filter_hkcu_entries(kde_filter)
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
            self.storage,
 | 
			
		||||
            self.__module_name,
 | 
			
		||||
@@ -83,7 +88,15 @@ class kde_applier_user(applier_frontend):
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def admin_context_apply(self):
 | 
			
		||||
        pass
 | 
			
		||||
        try:
 | 
			
		||||
            for setting in self.kde_settings:
 | 
			
		||||
                file_name = setting.keyname.split("/")[-2]
 | 
			
		||||
                if file_name == 'wallpaper':
 | 
			
		||||
                    data = setting.data
 | 
			
		||||
                    break
 | 
			
		||||
            self.file_cache.store(data)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
 | 
			
		||||
    def user_context_apply(self):
 | 
			
		||||
        '''
 | 
			
		||||
@@ -91,12 +104,43 @@ class kde_applier_user(applier_frontend):
 | 
			
		||||
        '''
 | 
			
		||||
        if self.__module_enabled:
 | 
			
		||||
            log('D200')
 | 
			
		||||
            create_dict(self.kde_settings, self.all_kde_settings, self.locks_settings, self.locks_dict, self.file_cache, self.username)
 | 
			
		||||
            create_dict(self.kde_settings, self.all_kde_settings, self.locks_settings, self.locks_dict, self.file_cache, self.username, self.plasma_update_flag)
 | 
			
		||||
            apply(self.all_kde_settings, self.locks_dict, self.username)
 | 
			
		||||
        else:
 | 
			
		||||
            log('D201')
 | 
			
		||||
 | 
			
		||||
def create_dict(kde_settings, all_kde_settings, locks_settings, locks_dict, file_cache = None, username = None):
 | 
			
		||||
dbus_methods_mapping = {
 | 
			
		||||
    'kscreenlockerrc': {
 | 
			
		||||
        'dbus_service': 'org.kde.screensaver',
 | 
			
		||||
        'dbus_path': '/ScreenSaver',
 | 
			
		||||
        'dbus_interface': 'org.kde.screensaver',
 | 
			
		||||
        'dbus_method': 'configure'
 | 
			
		||||
    },
 | 
			
		||||
    'wallpaper': {
 | 
			
		||||
        'dbus_service': 'org.freedesktop.systemd1',
 | 
			
		||||
        'dbus_path': '/org/freedesktop/systemd1',
 | 
			
		||||
        'dbus_interface': 'org.freedesktop.systemd1.Manager',
 | 
			
		||||
        'dbus_method': 'RestartUnit',
 | 
			
		||||
        'dbus_args': ['plasma-plasmashell.service', 'replace']
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
def get_kde_version():
 | 
			
		||||
    try:
 | 
			
		||||
        kinfo_path = shutil.which("kinfo", path="/usr/lib/kf5/bin:/usr/bin")
 | 
			
		||||
        if not kinfo_path:
 | 
			
		||||
            raise FileNotFoundError("Unable to find kinfo")
 | 
			
		||||
        output = subprocess.check_output([kinfo_path], text=True, env={'LANG':'C'})
 | 
			
		||||
        for line in output.splitlines():
 | 
			
		||||
            if "KDE Frameworks Version" in line:
 | 
			
		||||
                frameworks_version = line.split(":", 1)[1].strip()
 | 
			
		||||
                major_frameworks_version = int(frameworks_version.split(".")[0])
 | 
			
		||||
                return major_frameworks_version
 | 
			
		||||
    except:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_dict(kde_settings, all_kde_settings, locks_settings, locks_dict, file_cache = None, username = None, plasmaupdate = False):
 | 
			
		||||
        for locks in locks_settings:
 | 
			
		||||
            locks_dict[locks.valuename] = locks.data
 | 
			
		||||
        for setting in kde_settings:
 | 
			
		||||
@@ -104,25 +148,20 @@ def create_dict(kde_settings, all_kde_settings, locks_settings, locks_dict, file
 | 
			
		||||
                file_name, section, value = setting.keyname.split("/")[-2], setting.keyname.split("/")[-1], setting.valuename
 | 
			
		||||
                data = setting.data
 | 
			
		||||
                if file_name == 'wallpaper':
 | 
			
		||||
                    apply_for_wallpaper(data, file_cache, username)
 | 
			
		||||
                    apply_for_wallpaper(data, file_cache, username, plasmaupdate)
 | 
			
		||||
                else:
 | 
			
		||||
                    if file_name not in all_kde_settings:
 | 
			
		||||
                        all_kde_settings[file_name] = {}
 | 
			
		||||
                    if section not in all_kde_settings[file_name]:
 | 
			
		||||
                        all_kde_settings[file_name][section] = {}
 | 
			
		||||
                    all_kde_settings[file_name][section][value] = data
 | 
			
		||||
 | 
			
		||||
                    all_kde_settings.setdefault(file_name, {}).setdefault(section, {})[value] = data
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['file_name'] = file_name
 | 
			
		||||
                logdata['section'] = section
 | 
			
		||||
                logdata['value'] = value
 | 
			
		||||
                logdata['data'] = data
 | 
			
		||||
                logdata['exc'] = exc
 | 
			
		||||
                logdata = {'file_name': file_name,
 | 
			
		||||
                           'section': section,
 | 
			
		||||
                           'value': value,
 | 
			
		||||
                           'data': data,
 | 
			
		||||
                           'exc': exc}
 | 
			
		||||
                log('W16', logdata)
 | 
			
		||||
 | 
			
		||||
def apply(all_kde_settings, locks_dict, username = None):
 | 
			
		||||
    logdata = dict()
 | 
			
		||||
    logdata = {}
 | 
			
		||||
    modified_files = set()
 | 
			
		||||
    if username is None:
 | 
			
		||||
        system_path_settings = '/etc/xdg/'
 | 
			
		||||
        system_files = [
 | 
			
		||||
@@ -148,11 +187,12 @@ def apply(all_kde_settings, locks_dict, username = None):
 | 
			
		||||
                    file.write(f'[{section}]\n')
 | 
			
		||||
                    for key, value in keys.items():
 | 
			
		||||
                        lock = f"{file_name}.{section}.{key}".replace('][', ')(')
 | 
			
		||||
                        if lock in locks_dict and locks_dict[lock] == 1:
 | 
			
		||||
                        if locks_dict.get(lock) == 1:
 | 
			
		||||
                            file.write(f'{key}[$i]={value}\n')
 | 
			
		||||
                        else:
 | 
			
		||||
                            file.write(f'{key}={value}\n')
 | 
			
		||||
                    file.write('\n')
 | 
			
		||||
            modified_files.add(file_name)
 | 
			
		||||
    else:
 | 
			
		||||
        for file_name, sections in all_kde_settings.items():
 | 
			
		||||
            path = f'{get_homedir(username)}/.config/{file_name}'
 | 
			
		||||
@@ -166,7 +206,7 @@ def apply(all_kde_settings, locks_dict, username = None):
 | 
			
		||||
                    lock = f"{file_name}.{section}.{key}"
 | 
			
		||||
                    if lock in locks_dict and locks_dict[lock] == 1:
 | 
			
		||||
                        command = [
 | 
			
		||||
                            'kwriteconfig5',
 | 
			
		||||
                            f'kwriteconfig{kde_applier_user.kde_version}',
 | 
			
		||||
                            '--file', file_name,
 | 
			
		||||
                            '--group', section,
 | 
			
		||||
                            '--key', key +'/$i/',
 | 
			
		||||
@@ -175,7 +215,7 @@ def apply(all_kde_settings, locks_dict, username = None):
 | 
			
		||||
                        ]
 | 
			
		||||
                    else:
 | 
			
		||||
                        command = [
 | 
			
		||||
                            'kwriteconfig5',
 | 
			
		||||
                            f'kwriteconfig{kde_applier_user.kde_version}',
 | 
			
		||||
                            '--file', file_name,
 | 
			
		||||
                            '--group', section,
 | 
			
		||||
                            '--key', key,
 | 
			
		||||
@@ -184,9 +224,11 @@ def apply(all_kde_settings, locks_dict, username = None):
 | 
			
		||||
                        ]
 | 
			
		||||
                    try:
 | 
			
		||||
                        clear_locks_settings(username, file_name, key)
 | 
			
		||||
                        subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | 
			
		||||
                        env_path = dict(os.environ)
 | 
			
		||||
                        env_path["PATH"] = "/usr/lib/kf5/bin:/usr/bin"
 | 
			
		||||
                        subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env_path)
 | 
			
		||||
                    except:
 | 
			
		||||
                            logdata['command'] = command
 | 
			
		||||
                            logdata = {'command': command}
 | 
			
		||||
                            log('W22', logdata)
 | 
			
		||||
            new_content = []
 | 
			
		||||
            file_path = f'{get_homedir(username)}/.config/{file_name}'
 | 
			
		||||
@@ -202,6 +244,9 @@ def apply(all_kde_settings, locks_dict, username = None):
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata['exc'] = exc
 | 
			
		||||
                log('W19', logdata)
 | 
			
		||||
            modified_files.add(file_name)
 | 
			
		||||
    for file_name in modified_files:
 | 
			
		||||
        call_dbus_method(file_name)
 | 
			
		||||
 | 
			
		||||
def clear_locks_settings(username, file_name, key):
 | 
			
		||||
    '''
 | 
			
		||||
@@ -216,64 +261,71 @@ def clear_locks_settings(username, file_name, key):
 | 
			
		||||
                file.write(line)
 | 
			
		||||
    for line in lines:
 | 
			
		||||
        if f'{key}[$i]=' in line:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['line'] = line.strip()
 | 
			
		||||
            logdata = {'line': line.strip()}
 | 
			
		||||
            log('I10', logdata)
 | 
			
		||||
 | 
			
		||||
def apply_for_wallpaper(data, file_cache, username):
 | 
			
		||||
def apply_for_wallpaper(data, file_cache, username, plasmaupdate):
 | 
			
		||||
    '''
 | 
			
		||||
    Method to change wallpaper
 | 
			
		||||
    '''
 | 
			
		||||
    logdata = dict()
 | 
			
		||||
    logdata = {}
 | 
			
		||||
    path_to_wallpaper = f'{get_homedir(username)}/.config/plasma-org.kde.plasma.desktop-appletsrc'
 | 
			
		||||
    id_desktop = get_id_desktop(path_to_wallpaper)
 | 
			
		||||
    try:
 | 
			
		||||
        try:
 | 
			
		||||
            file_cache.store(data)
 | 
			
		||||
            data = str(file_cache.get(data))
 | 
			
		||||
        except NotUNCPathError:
 | 
			
		||||
            data = str(data)
 | 
			
		||||
 | 
			
		||||
        with open(path_to_wallpaper, 'r') as file:
 | 
			
		||||
            current_wallpaper = file.read()
 | 
			
		||||
        match = re.search(rf'\[Containments\]\[{id_desktop}\]\[Wallpaper\]\[org\.kde\.image\]\[General\]\s+Image=(.*)', current_wallpaper)
 | 
			
		||||
        if match:
 | 
			
		||||
            current_wallpaper_path = match.group(1)
 | 
			
		||||
            flag = (current_wallpaper_path == data)
 | 
			
		||||
        else:
 | 
			
		||||
            flag = False
 | 
			
		||||
 | 
			
		||||
        os.environ["LANGUAGE"] = os.environ["LANG"].split(".")[0]
 | 
			
		||||
        os.environ["XDG_DATA_DIRS"] = "/usr/share/kf5:"
 | 
			
		||||
            #Variable for system detection of directories before files with .colors extension
 | 
			
		||||
        os.environ["DISPLAY"] = ":0"
 | 
			
		||||
            #Variable for command execution plasma-apply-colorscheme
 | 
			
		||||
        os.environ["XDG_RUNTIME_DIR"] = f"/run/user/{os.getuid()}"
 | 
			
		||||
        os.environ["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path=/run/user/{os.getuid()}/bus"#plasma-apply-wallpaperimage
 | 
			
		||||
        os.environ["PATH"] = "/usr/lib/kf5/bin:"
 | 
			
		||||
        os.environ["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path=/run/user/{os.getuid()}/bus"#plasma-apply-wallpaperimage
 | 
			
		||||
        env_path = dict(os.environ)
 | 
			
		||||
        env_path["PATH"] = "/usr/lib/kf5/bin:/usr/bin"
 | 
			
		||||
            #environment variable for accessing binary files without hard links
 | 
			
		||||
        if os.path.isfile(path_to_wallpaper):
 | 
			
		||||
            id_desktop = get_id_desktop(path_to_wallpaper)
 | 
			
		||||
            command = [
 | 
			
		||||
                'kwriteconfig5',
 | 
			
		||||
                '--file', 'plasma-org.kde.plasma.desktop-appletsrc',
 | 
			
		||||
                '--group', 'Containments',
 | 
			
		||||
                '--group', id_desktop,
 | 
			
		||||
                '--group', 'Wallpaper',
 | 
			
		||||
                '--group', 'org.kde.image',
 | 
			
		||||
                '--group', 'General',
 | 
			
		||||
                '--key', 'Image',
 | 
			
		||||
                 '--type', 'string',
 | 
			
		||||
                data
 | 
			
		||||
                ]
 | 
			
		||||
            try:
 | 
			
		||||
                subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | 
			
		||||
            except:
 | 
			
		||||
                    logdata['command'] = command
 | 
			
		||||
                    log('E68', logdata)
 | 
			
		||||
            try:
 | 
			
		||||
                session_bus = dbus.SessionBus()
 | 
			
		||||
                plasma_shell = session_bus.get_object('org.kde.plasmashell', '/PlasmaShell', introspect='org.kde.PlasmaShell')
 | 
			
		||||
                plasma_shell_iface = dbus.Interface(plasma_shell, 'org.kde.PlasmaShell')
 | 
			
		||||
                plasma_shell_iface.refreshCurrentShell()
 | 
			
		||||
            except:
 | 
			
		||||
                pass
 | 
			
		||||
        if not flag:
 | 
			
		||||
            if os.path.isfile(path_to_wallpaper):
 | 
			
		||||
                command = [
 | 
			
		||||
                    f'kwriteconfig{kde_applier_user.kde_version}',
 | 
			
		||||
                    '--file', 'plasma-org.kde.plasma.desktop-appletsrc',
 | 
			
		||||
                    '--group', 'Containments',
 | 
			
		||||
                    '--group', id_desktop,
 | 
			
		||||
                    '--group', 'Wallpaper',
 | 
			
		||||
                    '--group', 'org.kde.image',
 | 
			
		||||
                    '--group', 'General',
 | 
			
		||||
                    '--key', 'Image',
 | 
			
		||||
                    '--type', 'string',
 | 
			
		||||
                    data
 | 
			
		||||
                    ]
 | 
			
		||||
                try:
 | 
			
		||||
                    subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env_path)
 | 
			
		||||
                except:
 | 
			
		||||
                        logdata = {'command': command}
 | 
			
		||||
                        log('E68', logdata)
 | 
			
		||||
                if plasmaupdate == 1:
 | 
			
		||||
                    call_dbus_method("wallpaper")
 | 
			
		||||
        else:
 | 
			
		||||
            logdata['file'] = path_to_wallpaper
 | 
			
		||||
            logdata = {'file': path_to_wallpaper}
 | 
			
		||||
            log('W21', logdata)
 | 
			
		||||
    except OSError as exc:
 | 
			
		||||
        logdata['exc'] = exc
 | 
			
		||||
        logdata = {'exc': exc}
 | 
			
		||||
        log('W17', logdata)
 | 
			
		||||
    except Exception as exc:
 | 
			
		||||
        logdata['exc'] = exc
 | 
			
		||||
        logdata = {'exc': exc}
 | 
			
		||||
        log('E67', logdata)
 | 
			
		||||
 | 
			
		||||
def get_id_desktop(path_to_wallpaper):
 | 
			
		||||
@@ -285,9 +337,27 @@ def get_id_desktop(path_to_wallpaper):
 | 
			
		||||
        with open(path_to_wallpaper, 'r') as file:
 | 
			
		||||
            file_content = file.read()
 | 
			
		||||
        match = re.search(pattern, file_content)
 | 
			
		||||
        if match:
 | 
			
		||||
            return match.group(1)
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
        return match.group(1) if match else None
 | 
			
		||||
    except:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
def call_dbus_method(file_name):
 | 
			
		||||
    '''
 | 
			
		||||
    Method to call D-Bus method based on the file name
 | 
			
		||||
    '''
 | 
			
		||||
    os.environ["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path=/run/user/{os.getuid()}/bus"
 | 
			
		||||
    if file_name in dbus_methods_mapping:
 | 
			
		||||
        config = dbus_methods_mapping[file_name]
 | 
			
		||||
        try:
 | 
			
		||||
            session_bus = dbus.SessionBus()
 | 
			
		||||
            dbus_object = session_bus.get_object(config['dbus_service'], config['dbus_path'])
 | 
			
		||||
            dbus_iface = dbus.Interface(dbus_object, config['dbus_interface'])
 | 
			
		||||
            if 'dbus_args' in config:
 | 
			
		||||
                getattr(dbus_iface, config['dbus_method'])(*config['dbus_args'])
 | 
			
		||||
            else:
 | 
			
		||||
                getattr(dbus_iface, config['dbus_method'])()
 | 
			
		||||
        except dbus.exceptions.DBusException as exc:
 | 
			
		||||
            logdata = {'error': str(exc)}
 | 
			
		||||
            log('E31', logdata)
 | 
			
		||||
    else:
 | 
			
		||||
        pass
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										755
									
								
								gpoa/frontend/laps_applier.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										755
									
								
								gpoa/frontend/laps_applier.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,755 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
    applier_frontend,
 | 
			
		||||
    check_enabled
 | 
			
		||||
)
 | 
			
		||||
import struct
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
import dpapi_ng
 | 
			
		||||
from util.util import remove_prefix_from_keys, check_local_user_exists
 | 
			
		||||
from util.sid import WellKnown21RID
 | 
			
		||||
import subprocess
 | 
			
		||||
import ldb
 | 
			
		||||
import string
 | 
			
		||||
import secrets
 | 
			
		||||
import os
 | 
			
		||||
import psutil
 | 
			
		||||
from util.logging import log
 | 
			
		||||
import logging
 | 
			
		||||
import re
 | 
			
		||||
from datetime import timezone
 | 
			
		||||
from dateutil import tz
 | 
			
		||||
 | 
			
		||||
_DATEUTIL_AVAILABLE = False
 | 
			
		||||
try:
 | 
			
		||||
    from dateutil import tz
 | 
			
		||||
    _DATEUTIL_AVAILABLE = True
 | 
			
		||||
except ImportError:
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class laps_applier(applier_frontend):
 | 
			
		||||
    """
 | 
			
		||||
    LAPS (Local Administrator Password Solution) implementation for managing
 | 
			
		||||
    and automatically rotating administrator passwords.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Time calculation constants
 | 
			
		||||
 | 
			
		||||
    # Number of seconds between the Windows epoch (1601-01-01 00:00:00 UTC)
 | 
			
		||||
    # and the Unix epoch (1970-01-01 00:00:00 UTC).
 | 
			
		||||
    # Used to convert between Unix timestamps and Windows FileTime.
 | 
			
		||||
    _EPOCH_TIMESTAMP = 11644473600
 | 
			
		||||
    # Number of 100-nanosecond intervals per second.
 | 
			
		||||
    # Used to convert seconds to Windows FileTime format.
 | 
			
		||||
    _HUNDREDS_OF_NANOSECONDS = 10000000
 | 
			
		||||
    # Number of 100-nanosecond intervals in one day
 | 
			
		||||
    _DAY_FLOAT = 8.64e11
 | 
			
		||||
 | 
			
		||||
    # Module configuration
 | 
			
		||||
    __module_name = 'LapsApplier'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
 | 
			
		||||
    # Registry paths
 | 
			
		||||
    _WINDOWS_REGISTRY_PATH = 'SOFTWARE/Microsoft/Windows/CurrentVersion/Policies/LAPS/'
 | 
			
		||||
    _ALT_REGISTRY_PATH = 'Software/BaseALT/Policies/Laps/'
 | 
			
		||||
 | 
			
		||||
    # LDAP attributes
 | 
			
		||||
    _ATTR_ENCRYPTED_PASSWORD = 'msLAPS-EncryptedPassword'
 | 
			
		||||
    _ATTR_PASSWORD_EXPIRATION_TIME = 'msLAPS-PasswordExpirationTime'
 | 
			
		||||
 | 
			
		||||
    # dconf key for password modification time
 | 
			
		||||
    _KEY_PASSWORD_LAST_MODIFIED = '/Software/BaseALT/Policies/Laps/PasswordLastModified/'
 | 
			
		||||
 | 
			
		||||
    # Password complexity levels
 | 
			
		||||
    _PASSWORD_COMPLEXITY = {
 | 
			
		||||
        1: string.ascii_uppercase,
 | 
			
		||||
        2: string.ascii_letters,
 | 
			
		||||
        3: string.ascii_letters + string.digits,
 | 
			
		||||
        4: string.ascii_letters + string.digits + string.punctuation
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Post-authentication actions
 | 
			
		||||
    _ACTION_NONE = 0
 | 
			
		||||
    _ACTION_CHANGE_PASSWORD = 1
 | 
			
		||||
    _ACTION_TERMINATE_SESSIONS = 3
 | 
			
		||||
    _ACTION_REBOOT = 5
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        """
 | 
			
		||||
        Initialize the LAPS applier with configuration from registry.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            storage: Storage object containing registry entries and system information
 | 
			
		||||
        """
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
 | 
			
		||||
        # Load registry configuration
 | 
			
		||||
        if not self._load_configuration():
 | 
			
		||||
            self.__module_enabled = False
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if not self._check_requirements():
 | 
			
		||||
            log('W29')
 | 
			
		||||
            self.__module_enabled = False
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Initialize system connections and parameters
 | 
			
		||||
        self._initialize_system_parameters()
 | 
			
		||||
 | 
			
		||||
        # Check if module is enabled in configuration
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
            self.storage,
 | 
			
		||||
            self.__module_name,
 | 
			
		||||
            self.__module_experimental
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _load_configuration(self):
 | 
			
		||||
        """Load configuration settings from registry."""
 | 
			
		||||
        alt_keys = remove_prefix_from_keys(
 | 
			
		||||
            self.storage.filter_entries(self._ALT_REGISTRY_PATH),
 | 
			
		||||
            self._ALT_REGISTRY_PATH
 | 
			
		||||
        )
 | 
			
		||||
        windows_keys = remove_prefix_from_keys(
 | 
			
		||||
            self.storage.filter_entries(self._WINDOWS_REGISTRY_PATH),
 | 
			
		||||
            self._WINDOWS_REGISTRY_PATH
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Combine configurations with BaseALT taking precedence
 | 
			
		||||
        self.config = windows_keys
 | 
			
		||||
        self.config.update(alt_keys)
 | 
			
		||||
 | 
			
		||||
        # Extract commonly used configuration parameters
 | 
			
		||||
        self.backup_directory = self.config.get('BackupDirectory', None)
 | 
			
		||||
        self.encryption_enabled = self.config.get('ADPasswordEncryptionEnabled', 1)
 | 
			
		||||
        self.password_expiration_protection = self.config.get('PasswordExpirationProtectionEnabled', 1)
 | 
			
		||||
        self.password_age_days = self.config.get('PasswordAgeDays', 30)
 | 
			
		||||
        self.post_authentication_actions = self.config.get('PostAuthenticationActions', 3)
 | 
			
		||||
        self.post_authentication_reset_delay = self.config.get('PostAuthenticationResetDelay', 24)
 | 
			
		||||
        name = self.config.get('AdministratorAccountName', 'root')
 | 
			
		||||
        if name and check_local_user_exists(name):
 | 
			
		||||
            self.target_user = name
 | 
			
		||||
        else:
 | 
			
		||||
            log('W36')
 | 
			
		||||
            return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _check_requirements(self):
 | 
			
		||||
        """
 | 
			
		||||
        Check if the necessary requirements are met for the module to operate.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bool: True if requirements are met, False otherwise
 | 
			
		||||
        """
 | 
			
		||||
        if self.backup_directory != 2 or not self.encryption_enabled:
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['backup_directory'] = self.backup_directory
 | 
			
		||||
            logdata['encryption_enabled'] = self.encryption_enabled
 | 
			
		||||
            log('D223', logdata)
 | 
			
		||||
            return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _initialize_system_parameters(self):
 | 
			
		||||
        """Initialize system parameters and connections."""
 | 
			
		||||
        # Set up LDAP connections
 | 
			
		||||
        self.samdb = self.storage.get_info('samdb')
 | 
			
		||||
        self.domain_sid = self.samdb.get_domain_sid()
 | 
			
		||||
        self.domain_dn = self.samdb.domain_dn()
 | 
			
		||||
        self.computer_dn = self._get_computer_dn()
 | 
			
		||||
        self.admin_group_sid = f'{self.domain_sid}-{WellKnown21RID.DOMAIN_ADMINS.value}'
 | 
			
		||||
 | 
			
		||||
        # Set up time parameters
 | 
			
		||||
        self.expiration_date = self._get_expiration_date()
 | 
			
		||||
        self.expiration_date_int = self._convert_to_filetime(self.expiration_date)
 | 
			
		||||
        self.current_time_int = self._convert_to_filetime(datetime.now())
 | 
			
		||||
 | 
			
		||||
        # Get current system state
 | 
			
		||||
        self.expiration_time_attr = self._get_expiration_time_attr()
 | 
			
		||||
        self.pass_last_mod_int = self._read_dconf_pass_last_mod()
 | 
			
		||||
        self.encryption_principal = self._get_encryption_principal()
 | 
			
		||||
        self.last_login_hours_ago = self._get_admin_login_hours_ago_after_timestamp()
 | 
			
		||||
 | 
			
		||||
    def _get_computer_dn(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the Distinguished Name of the computer account.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            str: Computer's distinguished name in LDAP
 | 
			
		||||
        """
 | 
			
		||||
        machine_name = self.storage.get_info('machine_name')
 | 
			
		||||
        search_filter = f'(sAMAccountName={machine_name})'
 | 
			
		||||
        results = self.samdb.search(base=self.domain_dn, expression=search_filter, attrs=['dn'])
 | 
			
		||||
        return results[0]['dn']
 | 
			
		||||
 | 
			
		||||
    def _get_encryption_principal(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the encryption principal for password encryption.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            str: SID of the encryption principal
 | 
			
		||||
        """
 | 
			
		||||
        encryption_principal = self.config.get('ADPasswordEncryptionPrincipal', None)
 | 
			
		||||
        if not encryption_principal:
 | 
			
		||||
            return self.admin_group_sid
 | 
			
		||||
 | 
			
		||||
        return self._verify_encryption_principal(encryption_principal)
 | 
			
		||||
 | 
			
		||||
    def _verify_encryption_principal(self, principal_name):
 | 
			
		||||
        """
 | 
			
		||||
        Verify the encryption principal exists and get its SID.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            principal_name: Principal name to verify
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            str: SID of the encryption principal if found, or admin group SID as fallback
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            # Try to resolve as domain\\user format
 | 
			
		||||
            domain = self.storage.get_info('domain')
 | 
			
		||||
            username = f'{domain}\\{principal_name}'
 | 
			
		||||
            output = subprocess.check_output(['wbinfo', '-n', username])
 | 
			
		||||
            sid = output.split()[0].decode('utf-8')
 | 
			
		||||
            return sid
 | 
			
		||||
        except subprocess.CalledProcessError:
 | 
			
		||||
            # Try to resolve directly as SID
 | 
			
		||||
            try:
 | 
			
		||||
                output = subprocess.check_output(['wbinfo', '-s', principal_name])
 | 
			
		||||
                return principal_name
 | 
			
		||||
            except subprocess.CalledProcessError:
 | 
			
		||||
                # Fallback to admin group SID
 | 
			
		||||
                logdata = {}
 | 
			
		||||
                logdata['principal_name'] = principal_name
 | 
			
		||||
                log('W30', logdata)
 | 
			
		||||
                return self.admin_group_sid
 | 
			
		||||
 | 
			
		||||
    def _get_expiration_date(self, base_time=None):
 | 
			
		||||
        """
 | 
			
		||||
        Calculate the password expiration date.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            base_time: Optional datetime to base calculation on, defaults to now
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            datetime: Password expiration date
 | 
			
		||||
        """
 | 
			
		||||
        base = base_time or datetime.now()
 | 
			
		||||
        # Set to beginning of day and add password age
 | 
			
		||||
        return (base.replace(hour=0, minute=0, second=0, microsecond=0) +
 | 
			
		||||
                timedelta(days=int(self.password_age_days)))
 | 
			
		||||
 | 
			
		||||
    def _convert_to_filetime(self, dt):
 | 
			
		||||
        """
 | 
			
		||||
        Convert datetime to Windows filetime format (100ns intervals since 1601-01-01).
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            dt: Datetime to convert
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            int: Windows filetime integer
 | 
			
		||||
        """
 | 
			
		||||
        epoch_timedelta = timedelta(seconds=self._EPOCH_TIMESTAMP)
 | 
			
		||||
        new_dt = dt + epoch_timedelta
 | 
			
		||||
        return int(new_dt.timestamp() * self._HUNDREDS_OF_NANOSECONDS)
 | 
			
		||||
 | 
			
		||||
    def _get_expiration_time_attr(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the current password expiration time from LDAP.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            int: Password expiration time as integer, or 0 if not found
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            res = self.samdb.search(
 | 
			
		||||
                base=self.computer_dn,
 | 
			
		||||
                scope=ldb.SCOPE_BASE,
 | 
			
		||||
                expression="(objectClass=*)",
 | 
			
		||||
                attrs=[self._ATTR_PASSWORD_EXPIRATION_TIME]
 | 
			
		||||
            )
 | 
			
		||||
            return int(res[0].get(self._ATTR_PASSWORD_EXPIRATION_TIME, 0)[0])
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('W31', logdata)
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
    def _read_dconf_pass_last_mod(self):
 | 
			
		||||
        """
 | 
			
		||||
        Read the password last modified time from dconf.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            int: Timestamp of last password modification or current time if not found
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            key_path = self._KEY_PASSWORD_LAST_MODIFIED + self.target_user
 | 
			
		||||
            last_modified = subprocess.check_output(
 | 
			
		||||
                ['dconf', 'read', key_path],
 | 
			
		||||
                text=True
 | 
			
		||||
            ).strip().strip("'\"")
 | 
			
		||||
            return int(last_modified)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('W32', logdata)
 | 
			
		||||
            return self.current_time_int
 | 
			
		||||
 | 
			
		||||
    def _write_dconf_pass_last_mod(self):
 | 
			
		||||
        """
 | 
			
		||||
        Write the password last modified time to dconf.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            # Ensure dbus session is available
 | 
			
		||||
            self._ensure_dbus_session()
 | 
			
		||||
 | 
			
		||||
            # Write current time to dconf
 | 
			
		||||
            key_path = self._KEY_PASSWORD_LAST_MODIFIED + self.target_user
 | 
			
		||||
            last_modified = f'"{self.current_time_int}"'
 | 
			
		||||
            subprocess.check_output(['dconf', 'write', key_path, last_modified])
 | 
			
		||||
            log('D222')
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('W28', logdata)
 | 
			
		||||
 | 
			
		||||
    def _ensure_dbus_session(self):
 | 
			
		||||
        """Ensure a D-Bus session is available for dconf operations."""
 | 
			
		||||
        dbus_address = os.getenv("DBUS_SESSION_BUS_ADDRESS")
 | 
			
		||||
        if not dbus_address:
 | 
			
		||||
            result = subprocess.run(
 | 
			
		||||
                ["dbus-daemon", "--fork", "--session", "--print-address"],
 | 
			
		||||
                capture_output=True,
 | 
			
		||||
                text=True
 | 
			
		||||
            )
 | 
			
		||||
            dbus_address = result.stdout.strip()
 | 
			
		||||
            os.environ["DBUS_SESSION_BUS_ADDRESS"] = dbus_address
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _get_changed_password_hours_ago(self):
 | 
			
		||||
        """
 | 
			
		||||
        Calculate how many hours ago the password was last changed.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            int: Hours since password was last changed, or 0 if error
 | 
			
		||||
        """
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        logdata['target_user'] = self.target_user
 | 
			
		||||
        try:
 | 
			
		||||
            diff_time = self.current_time_int - self.pass_last_mod_int
 | 
			
		||||
            hours_difference = diff_time // 3.6e10
 | 
			
		||||
            hours_ago = int(hours_difference)
 | 
			
		||||
            logdata['hours_ago'] = hours_ago
 | 
			
		||||
            log('D225', logdata)
 | 
			
		||||
            return hours_ago
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('W34', logdata)
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
    def _generate_password(self):
 | 
			
		||||
        """
 | 
			
		||||
        Generate a secure password based on policy settings.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            str: Generated password meeting complexity requirements
 | 
			
		||||
        """
 | 
			
		||||
        # Get password length from config
 | 
			
		||||
        password_length = self.config.get('PasswordLength', 14)
 | 
			
		||||
        if not isinstance(password_length, int) or not (8 <= password_length <= 64):
 | 
			
		||||
            password_length = 14
 | 
			
		||||
 | 
			
		||||
        # Get password complexity from config
 | 
			
		||||
        password_complexity = self.config.get('PasswordComplexity', 4)
 | 
			
		||||
        if not isinstance(password_complexity, int) or not (1 <= password_complexity <= 4):
 | 
			
		||||
            password_complexity = 4
 | 
			
		||||
 | 
			
		||||
        # Get character set based on complexity
 | 
			
		||||
        char_set = self._PASSWORD_COMPLEXITY.get(password_complexity, self._PASSWORD_COMPLEXITY[4])
 | 
			
		||||
 | 
			
		||||
        # Generate initial password
 | 
			
		||||
        password = ''.join(secrets.choice(char_set) for _ in range(password_length))
 | 
			
		||||
 | 
			
		||||
        # Ensure password meets complexity requirements
 | 
			
		||||
        if password_complexity >= 3 and not any(c.isdigit() for c in password):
 | 
			
		||||
            # Add a digit if required but missing
 | 
			
		||||
            digit = secrets.choice(string.digits)
 | 
			
		||||
            position = secrets.randbelow(len(password))
 | 
			
		||||
            password = password[:position] + digit + password[position:]
 | 
			
		||||
 | 
			
		||||
        if password_complexity == 4 and not any(c in string.punctuation for c in password):
 | 
			
		||||
            # Add a special character if required but missing
 | 
			
		||||
            special_char = secrets.choice(string.punctuation)
 | 
			
		||||
            position = secrets.randbelow(len(password))
 | 
			
		||||
            password = password[:position] + special_char + password[position:]
 | 
			
		||||
 | 
			
		||||
        return password
 | 
			
		||||
 | 
			
		||||
    def _get_json_password_data(self, password):
 | 
			
		||||
        """
 | 
			
		||||
        Format password information as JSON.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            password: The password
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            str: JSON formatted password information
 | 
			
		||||
        """
 | 
			
		||||
        return f'{{"n":"{self.target_user}","t":"{self.expiration_date_int}","p":"{password}"}}'
 | 
			
		||||
 | 
			
		||||
    def _create_password_blob(self, password):
 | 
			
		||||
        """
 | 
			
		||||
        Create encrypted password blob for LDAP storage.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            password: Password to encrypt
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bytes: Encrypted password blob
 | 
			
		||||
        """
 | 
			
		||||
        # Create JSON data and encode as UTF-16LE with null terminator
 | 
			
		||||
        json_data = self._get_json_password_data(password)
 | 
			
		||||
        password_bytes = json_data.encode("utf-16-le") + b"\x00\x00"
 | 
			
		||||
        # Save and change loglevel
 | 
			
		||||
        logger = logging.getLogger()
 | 
			
		||||
        old_level = logger.level
 | 
			
		||||
        logger.setLevel(logging.ERROR)
 | 
			
		||||
        # Encrypt the password
 | 
			
		||||
        dpapi_blob = dpapi_ng.ncrypt_protect_secret(
 | 
			
		||||
            password_bytes,
 | 
			
		||||
            self.encryption_principal,
 | 
			
		||||
            auth_protocol='kerberos'
 | 
			
		||||
        )
 | 
			
		||||
        # Restoreloglevel
 | 
			
		||||
        logger.setLevel(old_level)
 | 
			
		||||
        # Create full blob with metadata
 | 
			
		||||
        return self._add_blob_metadata(dpapi_blob)
 | 
			
		||||
 | 
			
		||||
    def _add_blob_metadata(self, dpapi_blob):
 | 
			
		||||
        """
 | 
			
		||||
        Add metadata to the encrypted password blob.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            dpapi_blob: Encrypted password blob
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bytes: Complete blob with metadata
 | 
			
		||||
        """
 | 
			
		||||
        # Convert timestamp to correct format
 | 
			
		||||
        left, right = struct.unpack('<LL', struct.pack('Q', self.current_time_int))
 | 
			
		||||
        packed = struct.pack('<LL', right, left)
 | 
			
		||||
 | 
			
		||||
        # Add blob length and padding
 | 
			
		||||
        prefix = packed + struct.pack('<i', len(dpapi_blob)) + b'\x00\x00\x00\x00'
 | 
			
		||||
 | 
			
		||||
        # Combine metadata and encrypted blob
 | 
			
		||||
        return prefix + dpapi_blob
 | 
			
		||||
 | 
			
		||||
    def _change_user_password(self, new_password):
 | 
			
		||||
        """
 | 
			
		||||
        Change the password for the target user.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            new_password: New password to set
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bool: True if password was changed successfully, False otherwise
 | 
			
		||||
        """
 | 
			
		||||
        logdata = {'target_user': self.target_user}
 | 
			
		||||
        try:
 | 
			
		||||
            # Use chpasswd to change the password
 | 
			
		||||
            process = subprocess.Popen(
 | 
			
		||||
                ["chpasswd"],
 | 
			
		||||
                stdin=subprocess.PIPE,
 | 
			
		||||
                text=True
 | 
			
		||||
            )
 | 
			
		||||
            process.communicate(f"{self.target_user}:{new_password}")
 | 
			
		||||
 | 
			
		||||
            # Record the time of change
 | 
			
		||||
            self._write_dconf_pass_last_mod()
 | 
			
		||||
            log('D221', logdata)
 | 
			
		||||
            return True
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('W27', logdata)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def _update_ldap_password(self, encrypted_blob):
 | 
			
		||||
        """
 | 
			
		||||
        Update the encrypted password and expiration time in LDAP.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            encrypted_blob: Encrypted password blob
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bool: True if LDAP was updated successfully, False otherwise
 | 
			
		||||
        """
 | 
			
		||||
        logdata = {'computer_dn': self.computer_dn}
 | 
			
		||||
        try:
 | 
			
		||||
            # Create LDAP modification message
 | 
			
		||||
            mod_msg = ldb.Message()
 | 
			
		||||
            mod_msg.dn = self.computer_dn
 | 
			
		||||
 | 
			
		||||
            # Update password blob
 | 
			
		||||
            mod_msg[self._ATTR_ENCRYPTED_PASSWORD] = ldb.MessageElement(
 | 
			
		||||
                encrypted_blob,
 | 
			
		||||
                ldb.FLAG_MOD_REPLACE,
 | 
			
		||||
                self._ATTR_ENCRYPTED_PASSWORD
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Update expiration time
 | 
			
		||||
            mod_msg[self._ATTR_PASSWORD_EXPIRATION_TIME] = ldb.MessageElement(
 | 
			
		||||
                str(self.expiration_date_int),
 | 
			
		||||
                ldb.FLAG_MOD_REPLACE,
 | 
			
		||||
                self._ATTR_PASSWORD_EXPIRATION_TIME
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Perform the LDAP modification
 | 
			
		||||
            self.samdb.modify(mod_msg)
 | 
			
		||||
            log('D226', logdata)
 | 
			
		||||
            return True
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('E75', logdata)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def _should_update_password(self):
 | 
			
		||||
        """
 | 
			
		||||
        Determine if the password should be updated based on policy.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            tuple: (bool: update needed, bool: perform post-action)
 | 
			
		||||
        """
 | 
			
		||||
        # Check if password has expired
 | 
			
		||||
        if not self._is_password_expired():
 | 
			
		||||
            # Password not expired, check if post-login action needed
 | 
			
		||||
            return self._check_post_login_action()
 | 
			
		||||
 | 
			
		||||
        # Password has expired, update needed
 | 
			
		||||
        return True, False
 | 
			
		||||
 | 
			
		||||
    def _is_password_expired(self):
 | 
			
		||||
        """
 | 
			
		||||
        Check if the password has expired according to policy.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bool: True if password has expired, False otherwise
 | 
			
		||||
        """
 | 
			
		||||
        # Case 1: No expiration protection, check LDAP attribute
 | 
			
		||||
        if not self.password_expiration_protection:
 | 
			
		||||
            if self.expiration_time_attr > self.current_time_int:
 | 
			
		||||
                return False
 | 
			
		||||
        # Case 2: With expiration protection, check both policy and LDAP
 | 
			
		||||
        elif self.password_expiration_protection:
 | 
			
		||||
            policy_expiry = self.pass_last_mod_int + (self.password_age_days * int(self._DAY_FLOAT))
 | 
			
		||||
            if policy_expiry > self.current_time_int and self.expiration_time_attr > self.current_time_int:
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _check_post_login_action(self):
 | 
			
		||||
        """
 | 
			
		||||
        Check if a post-login password change action should be performed.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            tuple: (bool: update needed, bool: perform post-action)
 | 
			
		||||
        """
 | 
			
		||||
        # Check if password was changed after last login
 | 
			
		||||
        if self._get_changed_password_hours_ago() < self.last_login_hours_ago:
 | 
			
		||||
            return False, False
 | 
			
		||||
 | 
			
		||||
        # Check if enough time has passed since login
 | 
			
		||||
        if self.last_login_hours_ago < self.post_authentication_reset_delay:
 | 
			
		||||
            return False, False
 | 
			
		||||
 | 
			
		||||
        # Check if action is configured
 | 
			
		||||
        if self.post_authentication_actions == self._ACTION_NONE:
 | 
			
		||||
            return False, False
 | 
			
		||||
 | 
			
		||||
        # Update needed, determine if post-action required
 | 
			
		||||
        return True, self.post_authentication_actions > self._ACTION_CHANGE_PASSWORD
 | 
			
		||||
 | 
			
		||||
    def _perform_post_action(self):
 | 
			
		||||
        """
 | 
			
		||||
        Perform post-password-change action based on configuration.
 | 
			
		||||
        """
 | 
			
		||||
        if self.post_authentication_actions == self._ACTION_TERMINATE_SESSIONS:
 | 
			
		||||
            self._terminate_user_sessions()
 | 
			
		||||
        elif self.post_authentication_actions == self._ACTION_REBOOT:
 | 
			
		||||
            log('D220')
 | 
			
		||||
            subprocess.run(["reboot"])
 | 
			
		||||
 | 
			
		||||
    def _terminate_user_sessions(self):
 | 
			
		||||
        """
 | 
			
		||||
        Terminates all processes associated with the active sessions of the target user.
 | 
			
		||||
        """
 | 
			
		||||
        # Get active sessions for the target user
 | 
			
		||||
        user_sessions = [user for user in psutil.users() if user.name == self.target_user]
 | 
			
		||||
        logdata = {'target_user': self.target_user}
 | 
			
		||||
        if not user_sessions:
 | 
			
		||||
            log('D227', logdata)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Terminate each session
 | 
			
		||||
        for session in user_sessions:
 | 
			
		||||
            try:
 | 
			
		||||
                # Get the process and terminate it
 | 
			
		||||
                proc = psutil.Process(session.pid)
 | 
			
		||||
                proc.kill()  # Send SIGKILL
 | 
			
		||||
                logdata['pid'] = session.pid
 | 
			
		||||
                log('D228')
 | 
			
		||||
            except (psutil.NoSuchProcess, psutil.AccessDenied) as exc:
 | 
			
		||||
                logdata['pid'] = session.pid
 | 
			
		||||
                logdata['exc'] = exc
 | 
			
		||||
                log('W35', logdata)
 | 
			
		||||
 | 
			
		||||
    def update_laps_password(self):
 | 
			
		||||
        """
 | 
			
		||||
        Update the LAPS password if needed based on policy.
 | 
			
		||||
        Checks expiration and login times to determine if update is needed.
 | 
			
		||||
        """
 | 
			
		||||
        # Check if password update is needed
 | 
			
		||||
        update_needed, perform_post_action = self._should_update_password()
 | 
			
		||||
 | 
			
		||||
        if not update_needed:
 | 
			
		||||
            log('D229')
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Generate new password
 | 
			
		||||
        password = self._generate_password()
 | 
			
		||||
 | 
			
		||||
        # Create encrypted password blob
 | 
			
		||||
        encrypted_blob = self._create_password_blob(password)
 | 
			
		||||
 | 
			
		||||
        # Update password in LDAP
 | 
			
		||||
        ldap_success = self._update_ldap_password(encrypted_blob)
 | 
			
		||||
 | 
			
		||||
        if not ldap_success:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Change local user password
 | 
			
		||||
        local_success = self._change_user_password(password)
 | 
			
		||||
 | 
			
		||||
        if not local_success:
 | 
			
		||||
            log('E76')
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        log('D230')
 | 
			
		||||
 | 
			
		||||
        # Perform post-action if configured
 | 
			
		||||
        if perform_post_action:
 | 
			
		||||
            self._perform_post_action()
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
        """
 | 
			
		||||
        Main entry point for the LAPS applier.
 | 
			
		||||
        """
 | 
			
		||||
        if self.__module_enabled:
 | 
			
		||||
            log('D218')
 | 
			
		||||
            self.update_laps_password()
 | 
			
		||||
        else:
 | 
			
		||||
            log('D219')
 | 
			
		||||
 | 
			
		||||
    def _parse_login_time_from_last_line(self, line: str) -> datetime:
 | 
			
		||||
        match_login_dt = re.search(
 | 
			
		||||
            r"((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\d{4})",
 | 
			
		||||
            line
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if not match_login_dt:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        login_dt_str = match_login_dt.group(1)
 | 
			
		||||
        try:
 | 
			
		||||
            dt_naive = datetime.strptime(login_dt_str, "%a %b %d %H:%M:%S %Y")
 | 
			
		||||
            login_dt_utc: datetime
 | 
			
		||||
            if _DATEUTIL_AVAILABLE:
 | 
			
		||||
                local_tz = tz.tzlocal()
 | 
			
		||||
                dt_local = dt_naive.replace(tzinfo=local_tz)
 | 
			
		||||
                login_dt_utc = dt_local.astimezone(timezone.utc)
 | 
			
		||||
            else:
 | 
			
		||||
                system_local_tz = datetime.now().astimezone().tzinfo
 | 
			
		||||
                if system_local_tz:
 | 
			
		||||
                    dt_local = dt_naive.replace(tzinfo=system_local_tz)
 | 
			
		||||
                    login_dt_utc = dt_local.astimezone(timezone.utc)
 | 
			
		||||
                else:
 | 
			
		||||
                    login_dt_utc = dt_naive.replace(tzinfo=timezone.utc)
 | 
			
		||||
                    log('W38')
 | 
			
		||||
            return login_dt_utc
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def _get_user_login_datetimes_utc(self) -> list[datetime]:
 | 
			
		||||
        command = ["last", "-F", "-w", self.target_user]
 | 
			
		||||
        env = os.environ.copy()
 | 
			
		||||
        env["LC_TIME"] = "C"
 | 
			
		||||
        login_datetimes = []
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            process = subprocess.run(command, capture_output=True, text=True, check=False, env=env)
 | 
			
		||||
            if process.returncode != 0 and not ("no login record" in process.stderr.lower() or "no users logged in" in process.stdout.lower()):
 | 
			
		||||
                log('W39')
 | 
			
		||||
                return []
 | 
			
		||||
            output_lines = process.stdout.splitlines()
 | 
			
		||||
        except FileNotFoundError:
 | 
			
		||||
            log('W40')
 | 
			
		||||
            return []
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            log('W41')
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        for line in output_lines:
 | 
			
		||||
            if not line.strip() or "wtmp begins" in line or "btmp begins" in line:
 | 
			
		||||
                continue
 | 
			
		||||
            if not line.startswith(self.target_user):
 | 
			
		||||
                continue
 | 
			
		||||
            login_dt_utc = self._parse_login_time_from_last_line(line)
 | 
			
		||||
            if login_dt_utc:
 | 
			
		||||
                login_datetimes.append(login_dt_utc)
 | 
			
		||||
 | 
			
		||||
        return login_datetimes
 | 
			
		||||
 | 
			
		||||
    def _get_admin_login_hours_ago_after_timestamp(self) -> int:
 | 
			
		||||
        # Convert Windows FileTime to datetime
 | 
			
		||||
        reference_dt_utc = datetime.fromtimestamp(
 | 
			
		||||
            (self.pass_last_mod_int / self._HUNDREDS_OF_NANOSECONDS) - self._EPOCH_TIMESTAMP,
 | 
			
		||||
            tz=timezone.utc
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if not (reference_dt_utc.tzinfo is timezone.utc or
 | 
			
		||||
                (reference_dt_utc.tzinfo is not None and reference_dt_utc.tzinfo.utcoffset(reference_dt_utc) == timedelta(0))):
 | 
			
		||||
            log('W42')
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
        user_login_times_utc = self._get_user_login_datetimes_utc()
 | 
			
		||||
        if not user_login_times_utc:
 | 
			
		||||
            log('D232')
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
        most_recent_login_after_reference_utc = None
 | 
			
		||||
        for login_time_utc in user_login_times_utc[::-1]:
 | 
			
		||||
            if login_time_utc >= reference_dt_utc:
 | 
			
		||||
                most_recent_login_after_reference_utc = login_time_utc
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        if most_recent_login_after_reference_utc:
 | 
			
		||||
            now_utc = datetime.now(timezone.utc)
 | 
			
		||||
            time_delta_seconds = (now_utc - most_recent_login_after_reference_utc).total_seconds()
 | 
			
		||||
            hours_ago = int(time_delta_seconds / 3600.0)
 | 
			
		||||
            log('D233')
 | 
			
		||||
            return hours_ago
 | 
			
		||||
        else:
 | 
			
		||||
            log('D234')
 | 
			
		||||
            return 0
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -29,11 +29,10 @@ class networkshare_applier(applier_frontend):
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username = None):
 | 
			
		||||
    def __init__(self, storage, username = None):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.networkshare_info = self.storage.get_networkshare(self.sid)
 | 
			
		||||
        self.networkshare_info = self.storage.get_networkshare()
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
        self.__module_enabled_user = check_enabled(self.storage, self.__module_name_user, self.__module_experimental)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -77,8 +77,7 @@ class ntp_applier(applier_frontend):
 | 
			
		||||
        srv = None
 | 
			
		||||
        if server:
 | 
			
		||||
            srv = server.data.rpartition(',')[0]
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['srv'] = srv
 | 
			
		||||
            logdata = {'srv': srv}
 | 
			
		||||
            log('D122', logdata)
 | 
			
		||||
 | 
			
		||||
        start_command = ['/usr/bin/systemctl', 'start', 'chronyd']
 | 
			
		||||
@@ -92,8 +91,7 @@ class ntp_applier(applier_frontend):
 | 
			
		||||
        proc.wait()
 | 
			
		||||
 | 
			
		||||
        if srv:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['srv'] = srv
 | 
			
		||||
            logdata = {'srv': srv}
 | 
			
		||||
            log('D124', logdata)
 | 
			
		||||
 | 
			
		||||
            proc = subprocess.Popen(chrony_disconnect_all)
 | 
			
		||||
@@ -119,8 +117,7 @@ class ntp_applier(applier_frontend):
 | 
			
		||||
 | 
			
		||||
        if server_type and server_type.data:
 | 
			
		||||
            if NTPServerType.NTP.value != server_type.data:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['server_type'] = server_type
 | 
			
		||||
                logdata = {'server_type': server_type}
 | 
			
		||||
                log('W10', logdata)
 | 
			
		||||
            else:
 | 
			
		||||
                log('D126')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,12 +18,7 @@
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import subprocess
 | 
			
		||||
from util.logging import slogm, log
 | 
			
		||||
from util.rpm import (
 | 
			
		||||
      update
 | 
			
		||||
    , install_rpm
 | 
			
		||||
    , remove_rpm
 | 
			
		||||
)
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
      applier_frontend
 | 
			
		||||
@@ -32,8 +27,8 @@ from .applier_frontend import (
 | 
			
		||||
 | 
			
		||||
class package_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'PackagesApplier'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __install_key_name = 'Install'
 | 
			
		||||
    __remove_key_name = 'Remove'
 | 
			
		||||
    __sync_key_name = 'Sync'
 | 
			
		||||
@@ -45,7 +40,7 @@ class package_applier(applier_frontend):
 | 
			
		||||
        install_branch = '{}\\{}%'.format(self.__hklm_branch, self.__install_key_name)
 | 
			
		||||
        remove_branch = '{}\\{}%'.format(self.__hklm_branch, self.__remove_key_name)
 | 
			
		||||
        sync_branch = '{}\\{}%'.format(self.__hklm_branch, self.__sync_key_name)
 | 
			
		||||
        self.fulcmd = list()
 | 
			
		||||
        self.fulcmd = []
 | 
			
		||||
        self.fulcmd.append('/usr/libexec/gpupdate/pkcon_runner')
 | 
			
		||||
        self.fulcmd.append('--loglevel')
 | 
			
		||||
        logger = logging.getLogger()
 | 
			
		||||
@@ -69,15 +64,13 @@ class package_applier(applier_frontend):
 | 
			
		||||
                try:
 | 
			
		||||
                    subprocess.check_call(self.fulcmd)
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = dict()
 | 
			
		||||
                    logdata['msg'] = str(exc)
 | 
			
		||||
                    logdata = {'msg': str(exc)}
 | 
			
		||||
                    log('E55', logdata)
 | 
			
		||||
            else:
 | 
			
		||||
                try:
 | 
			
		||||
                    subprocess.Popen(self.fulcmd,close_fds=False)
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = dict()
 | 
			
		||||
                    logdata['msg'] = str(exc)
 | 
			
		||||
                    logdata = {'msg': str(exc)}
 | 
			
		||||
                    log('E61', logdata)
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
@@ -90,18 +83,17 @@ class package_applier(applier_frontend):
 | 
			
		||||
 | 
			
		||||
class package_applier_user(applier_frontend):
 | 
			
		||||
    __module_name = 'PackagesApplierUser'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __install_key_name = 'Install'
 | 
			
		||||
    __remove_key_name = 'Remove'
 | 
			
		||||
    __sync_key_name = 'Sync'
 | 
			
		||||
    __hkcu_branch = 'Software\\BaseALT\\Policies\\Packages'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.fulcmd = list()
 | 
			
		||||
        self.fulcmd = []
 | 
			
		||||
        self.fulcmd.append('/usr/libexec/gpupdate/pkcon_runner')
 | 
			
		||||
        self.fulcmd.append('--user')
 | 
			
		||||
        self.fulcmd.append(self.username)
 | 
			
		||||
@@ -113,9 +105,9 @@ class package_applier_user(applier_frontend):
 | 
			
		||||
        remove_branch = '{}\\{}%'.format(self.__hkcu_branch, self.__remove_key_name)
 | 
			
		||||
        sync_branch = '{}\\{}%'.format(self.__hkcu_branch, self.__sync_key_name)
 | 
			
		||||
 | 
			
		||||
        self.install_packages_setting = self.storage.filter_hkcu_entries(self.sid, install_branch)
 | 
			
		||||
        self.remove_packages_setting = self.storage.filter_hkcu_entries(self.sid, remove_branch)
 | 
			
		||||
        self.sync_packages_setting = self.storage.filter_hkcu_entries(self.sid, sync_branch)
 | 
			
		||||
        self.install_packages_setting = self.storage.filter_hkcu_entries(install_branch)
 | 
			
		||||
        self.remove_packages_setting = self.storage.filter_hkcu_entries(remove_branch)
 | 
			
		||||
        self.sync_packages_setting = self.storage.filter_hkcu_entries(sync_branch)
 | 
			
		||||
        self.flagSync = False
 | 
			
		||||
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
@@ -136,15 +128,13 @@ class package_applier_user(applier_frontend):
 | 
			
		||||
                try:
 | 
			
		||||
                    subprocess.check_call(self.fulcmd)
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = dict()
 | 
			
		||||
                    logdata['msg'] = str(exc)
 | 
			
		||||
                    logdata = {'msg': str(exc)}
 | 
			
		||||
                    log('E60', logdata)
 | 
			
		||||
            else:
 | 
			
		||||
                try:
 | 
			
		||||
                    subprocess.Popen(self.fulcmd,close_fds=False)
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = dict()
 | 
			
		||||
                    logdata['msg'] = str(exc)
 | 
			
		||||
                    logdata = {'msg': str(exc)}
 | 
			
		||||
                    log('E62', logdata)
 | 
			
		||||
 | 
			
		||||
    def admin_context_apply(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ class polkit_applier(applier_frontend):
 | 
			
		||||
        template_vars_all = self.__polkit_map[self.__registry_branch][1]
 | 
			
		||||
        template_file_all_lock = self.__polkit_map[self.__registry_locks_branch][0]
 | 
			
		||||
        template_vars_all_lock = self.__polkit_map[self.__registry_locks_branch][1]
 | 
			
		||||
        locks = list()
 | 
			
		||||
        locks = []
 | 
			
		||||
        for lock in self.polkit_locks:
 | 
			
		||||
            if bool(int(lock.data)):
 | 
			
		||||
                locks.append(lock.valuename)
 | 
			
		||||
@@ -77,7 +77,7 @@ class polkit_applier(applier_frontend):
 | 
			
		||||
            self.__polkit_map[self.__registry_locks_branch][1][key] = item[1]
 | 
			
		||||
 | 
			
		||||
        if deny_all_win:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['Deny_All_win'] = deny_all_win.data
 | 
			
		||||
            log('D69', logdata)
 | 
			
		||||
            self.__polkit_map[self.__deny_all_win][1]['Deny_All'] = deny_all_win.data
 | 
			
		||||
@@ -115,15 +115,14 @@ class polkit_applier_user(applier_frontend):
 | 
			
		||||
            __registry_branch : ['48-alt_group_policy_permissions_user', {'User': ''}]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        deny_all_win = None
 | 
			
		||||
        if check_windows_mapping_enabled(self.storage):
 | 
			
		||||
            deny_all_win = storage.filter_hkcu_entries(self.sid, self.__deny_all_win).first()
 | 
			
		||||
            deny_all_win = storage.filter_hkcu_entries(self.__deny_all_win).first()
 | 
			
		||||
        polkit_filter = '{}%'.format(self.__registry_branch)
 | 
			
		||||
        self.polkit_keys = self.storage.filter_hkcu_entries(self.sid, polkit_filter)
 | 
			
		||||
        self.polkit_keys = self.storage.filter_hkcu_entries(polkit_filter)
 | 
			
		||||
        # Deny_All hook: initialize defaults
 | 
			
		||||
        template_file = self.__polkit_map[self.__deny_all_win][0]
 | 
			
		||||
        template_vars = self.__polkit_map[self.__deny_all_win][1]
 | 
			
		||||
@@ -146,7 +145,7 @@ class polkit_applier_user(applier_frontend):
 | 
			
		||||
            self.__polkit_map[self.__registry_branch][1][key] = item
 | 
			
		||||
 | 
			
		||||
        if deny_all_win:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['user'] = self.username
 | 
			
		||||
            logdata['Deny_All_win'] = deny_all_win.data
 | 
			
		||||
            log('D70', logdata)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -29,15 +29,14 @@ from .applier_frontend import (
 | 
			
		||||
 | 
			
		||||
class scripts_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'ScriptsApplier'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __cache_scripts = '/var/cache/gpupdate_scripts_cache/machine/'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid):
 | 
			
		||||
    def __init__(self, storage):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.startup_scripts = self.storage.get_scripts(self.sid, 'STARTUP')
 | 
			
		||||
        self.shutdown_scripts = self.storage.get_scripts(self.sid, 'SHUTDOWN')
 | 
			
		||||
        self.startup_scripts = self.storage.get_scripts('STARTUP')
 | 
			
		||||
        self.shutdown_scripts = self.storage.get_scripts('SHUTDOWN')
 | 
			
		||||
        self.folder_path = Path(self.__cache_scripts)
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
@@ -51,8 +50,7 @@ class scripts_applier(applier_frontend):
 | 
			
		||||
        except FileNotFoundError as exc:
 | 
			
		||||
            log('D154')
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['exc'] = exc
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('E64', logdata)
 | 
			
		||||
 | 
			
		||||
    def filling_cache(self):
 | 
			
		||||
@@ -80,15 +78,14 @@ class scripts_applier(applier_frontend):
 | 
			
		||||
 | 
			
		||||
class scripts_applier_user(applier_frontend):
 | 
			
		||||
    __module_name = 'ScriptsApplierUser'
 | 
			
		||||
    __module_experimental = True
 | 
			
		||||
    __module_enabled = False
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __cache_scripts = '/var/cache/gpupdate_scripts_cache/users/'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.logon_scripts = self.storage.get_scripts(self.sid, 'LOGON')
 | 
			
		||||
        self.logoff_scripts = self.storage.get_scripts(self.sid, 'LOGOFF')
 | 
			
		||||
        self.logon_scripts = self.storage.get_scripts('LOGON')
 | 
			
		||||
        self.logoff_scripts = self.storage.get_scripts('LOGOFF')
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.folder_path = Path(self.__cache_scripts + self.username)
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage
 | 
			
		||||
@@ -103,8 +100,7 @@ class scripts_applier_user(applier_frontend):
 | 
			
		||||
        except FileNotFoundError as exc:
 | 
			
		||||
            log('D155')
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['exc'] = exc
 | 
			
		||||
            logdata = {'exc': exc}
 | 
			
		||||
            log('E65', logdata)
 | 
			
		||||
 | 
			
		||||
    def filling_cache(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -16,7 +16,6 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
@@ -27,15 +26,19 @@ from util.windows import expand_windows_var
 | 
			
		||||
from util.logging import log
 | 
			
		||||
from util.util import (
 | 
			
		||||
        get_homedir,
 | 
			
		||||
        homedir_exists
 | 
			
		||||
        homedir_exists,
 | 
			
		||||
        string_to_literal_eval
 | 
			
		||||
)
 | 
			
		||||
from gpt.shortcuts import shortcut, get_ttype
 | 
			
		||||
 | 
			
		||||
def storage_get_shortcuts(storage, sid, username=None):
 | 
			
		||||
def storage_get_shortcuts(storage, username=None, shortcuts_machine=None):
 | 
			
		||||
    '''
 | 
			
		||||
    Query storage for shortcuts' rows for specified SID.
 | 
			
		||||
    Query storage for shortcuts' rows for username.
 | 
			
		||||
    '''
 | 
			
		||||
    shortcut_objs = storage.get_shortcuts(sid)
 | 
			
		||||
    shortcuts = list()
 | 
			
		||||
    shortcut_objs = storage.get_shortcuts()
 | 
			
		||||
    shortcuts = []
 | 
			
		||||
    if username and shortcuts_machine:
 | 
			
		||||
        shortcut_objs += shortcuts_machine
 | 
			
		||||
 | 
			
		||||
    for sc in shortcut_objs:
 | 
			
		||||
        if username:
 | 
			
		||||
@@ -53,9 +56,7 @@ def apply_shortcut(shortcut, username=None):
 | 
			
		||||
    dest_abspath = shortcut.dest
 | 
			
		||||
    if not dest_abspath.startswith('/') and not dest_abspath.startswith('%'):
 | 
			
		||||
        dest_abspath = '%HOME%/' + dest_abspath
 | 
			
		||||
    logdata = dict()
 | 
			
		||||
    logdata['shortcut'] = dest_abspath
 | 
			
		||||
    logdata['for'] = username
 | 
			
		||||
    logdata = {'shortcut': dest_abspath, 'for': username}
 | 
			
		||||
    log('D105', logdata)
 | 
			
		||||
    dest_abspath = expand_windows_var(dest_abspath, username).replace('\\', '/') + '.desktop'
 | 
			
		||||
 | 
			
		||||
@@ -66,31 +67,24 @@ def apply_shortcut(shortcut, username=None):
 | 
			
		||||
        if dest_abspath.startswith(get_homedir(username)):
 | 
			
		||||
            # Don't try to operate on non-existent directory
 | 
			
		||||
            if not homedir_exists(username):
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['user'] = username
 | 
			
		||||
                logdata['dest_abspath'] = dest_abspath
 | 
			
		||||
                logdata = {'user': username, 'dest_abspath': dest_abspath}
 | 
			
		||||
                log('W7', logdata)
 | 
			
		||||
                return None
 | 
			
		||||
        else:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['user'] = username
 | 
			
		||||
            logdata['bad path'] = dest_abspath
 | 
			
		||||
            logdata = {'user': username, 'bad path': dest_abspath}
 | 
			
		||||
            log('W8', logdata)
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    if '%' in dest_abspath:
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata['dest_abspath'] = dest_abspath
 | 
			
		||||
        logdata = {'dest_abspath': dest_abspath}
 | 
			
		||||
        log('E53', logdata)
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    if not dest_abspath.startswith('/'):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata['dest_abspath'] = dest_abspath
 | 
			
		||||
        logdata = {'dest_abspath': dest_abspath}
 | 
			
		||||
        log('E54', logdata)
 | 
			
		||||
        return None
 | 
			
		||||
    logdata = dict()
 | 
			
		||||
    logdata['file'] = dest_abspath
 | 
			
		||||
    logdata = {'file': dest_abspath}
 | 
			
		||||
    logdata['with_action'] = shortcut.action
 | 
			
		||||
    log('D106', logdata)
 | 
			
		||||
    shortcut.apply_desktop(dest_abspath)
 | 
			
		||||
@@ -109,7 +103,7 @@ class shortcut_applier(applier_frontend):
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        shortcuts = storage_get_shortcuts(self.storage, self.storage.get_info('machine_sid'))
 | 
			
		||||
        shortcuts = storage_get_shortcuts(self.storage)
 | 
			
		||||
        if shortcuts:
 | 
			
		||||
            for sc in shortcuts:
 | 
			
		||||
                apply_shortcut(sc)
 | 
			
		||||
@@ -120,9 +114,7 @@ class shortcut_applier(applier_frontend):
 | 
			
		||||
                # /usr/local/share/applications
 | 
			
		||||
                subprocess.check_call(['/usr/bin/update-desktop-database'])
 | 
			
		||||
        else:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['machine_sid'] = self.storage.get_info('machine_sid')
 | 
			
		||||
            log('D100', logdata)
 | 
			
		||||
            log('D100')
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
        if self.__module_enabled:
 | 
			
		||||
@@ -135,14 +127,45 @@ class shortcut_applier_user(applier_frontend):
 | 
			
		||||
    __module_name = 'ShortcutsApplierUser'
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __REGISTRY_PATH_SHORTCATSMERGE= '/Software/BaseALT/Policies/GPUpdate/ShortcutsMerge'
 | 
			
		||||
    __DCONF_REGISTRY_PATH_PREFERENCES_MACHINE = 'Software/BaseALT/Policies/Preferences/Machine'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
 | 
			
		||||
 | 
			
		||||
    def get_machine_shortcuts(self):
 | 
			
		||||
        result = []
 | 
			
		||||
        try:
 | 
			
		||||
            storage_machine_dict =  self.storage.get_dictionary_from_dconf_file_db()
 | 
			
		||||
            machine_shortcuts = storage_machine_dict.get(
 | 
			
		||||
                self.__DCONF_REGISTRY_PATH_PREFERENCES_MACHINE, dict()).get('Shortcuts')
 | 
			
		||||
            shortcut_objs =  string_to_literal_eval(machine_shortcuts)
 | 
			
		||||
            for obj in shortcut_objs:
 | 
			
		||||
                shortcut_machine =shortcut(
 | 
			
		||||
                    obj.get('dest'),
 | 
			
		||||
                    obj.get('path'),
 | 
			
		||||
                    obj.get('arguments'),
 | 
			
		||||
                    obj.get('name'),
 | 
			
		||||
                    obj.get('action'),
 | 
			
		||||
                    get_ttype(obj.get('target_type')))
 | 
			
		||||
                shortcut_machine.set_usercontext(1)
 | 
			
		||||
                result.append(shortcut_machine)
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def check_enabled_shortcuts_merge(self):
 | 
			
		||||
        return self.storage.get_key_value(self.__REGISTRY_PATH_SHORTCATSMERGE)
 | 
			
		||||
 | 
			
		||||
    def run(self, in_usercontext):
 | 
			
		||||
        shortcuts = storage_get_shortcuts(self.storage, self.sid, self.username)
 | 
			
		||||
        shortcuts_machine = None
 | 
			
		||||
        if self.check_enabled_shortcuts_merge():
 | 
			
		||||
            shortcuts_machine = self.get_machine_shortcuts()
 | 
			
		||||
        shortcuts = storage_get_shortcuts(self.storage, self.username, shortcuts_machine)
 | 
			
		||||
 | 
			
		||||
        if shortcuts:
 | 
			
		||||
            for sc in shortcuts:
 | 
			
		||||
@@ -151,8 +174,7 @@ class shortcut_applier_user(applier_frontend):
 | 
			
		||||
                if not in_usercontext and not sc.is_usercontext():
 | 
			
		||||
                    apply_shortcut(sc, self.username)
 | 
			
		||||
        else:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['sid'] = self.sid
 | 
			
		||||
            logdata = {'username': self.username}
 | 
			
		||||
            log('D100', logdata)
 | 
			
		||||
 | 
			
		||||
    def user_context_apply(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,9 +21,8 @@ from .applier_frontend import (
 | 
			
		||||
    , check_enabled
 | 
			
		||||
)
 | 
			
		||||
from .appliers.systemd import systemd_unit
 | 
			
		||||
from util.logging import slogm, log
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
class systemd_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'SystemdApplier'
 | 
			
		||||
@@ -43,23 +42,18 @@ class systemd_applier(applier_frontend):
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        for setting in self.systemd_unit_settings:
 | 
			
		||||
            valuename = setting.hive_key.rpartition('/')[2]
 | 
			
		||||
            try:
 | 
			
		||||
                self.units.append(systemd_unit(valuename, int(setting.data)))
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['unit'] = format(valuename)
 | 
			
		||||
                self.units.append(systemd_unit(setting.valuename, int(setting.data)))
 | 
			
		||||
                logdata = {'unit': format(setting.valuename)}
 | 
			
		||||
                log('I4', logdata)
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['unit'] = format(valuename)
 | 
			
		||||
                logdata['exc'] = exc
 | 
			
		||||
                logdata = {'unit': format(setting.valuename), 'exc': exc}
 | 
			
		||||
                log('I5', logdata)
 | 
			
		||||
        for unit in self.units:
 | 
			
		||||
            try:
 | 
			
		||||
                unit.apply()
 | 
			
		||||
            except:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['unit'] = unit.unit_name
 | 
			
		||||
                logdata = {'unit': unit.unit_name}
 | 
			
		||||
                log('E45', logdata)
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
@@ -78,7 +72,7 @@ class systemd_applier_user(applier_frontend):
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __registry_branch = 'Software/BaseALT/Policies/SystemdUnits'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
 | 
			
		||||
    def user_context_apply(self):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										68
									
								
								gpoa/frontend/thunderbird_applier.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								gpoa/frontend/thunderbird_applier.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2024-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from .applier_frontend import (
 | 
			
		||||
      applier_frontend
 | 
			
		||||
    , check_enabled
 | 
			
		||||
)
 | 
			
		||||
from util.logging import log
 | 
			
		||||
from util.util import is_machine_name
 | 
			
		||||
from .firefox_applier import create_dict
 | 
			
		||||
 | 
			
		||||
class thunderbird_applier(applier_frontend):
 | 
			
		||||
    __module_name = 'ThunderbirdApplier'
 | 
			
		||||
    __module_experimental = False
 | 
			
		||||
    __module_enabled = True
 | 
			
		||||
    __registry_branch = 'Software/Policies/Mozilla/Thunderbird'
 | 
			
		||||
    __thunderbird_policies = '/etc/thunderbird/policies'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self._is_machine_name = is_machine_name(self.username)
 | 
			
		||||
        self.policies = {}
 | 
			
		||||
        self.policies_json = {'policies': self.policies}
 | 
			
		||||
        self.thunderbird_keys = self.storage.filter_hklm_entries(self.__registry_branch)
 | 
			
		||||
        self.policies_gen = {}
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
            , self.__module_name
 | 
			
		||||
            , self.__module_experimental
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def machine_apply(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Write policies.json to Thunderbird.
 | 
			
		||||
        '''
 | 
			
		||||
        self.policies_json = create_dict(self.thunderbird_keys, self.__registry_branch)
 | 
			
		||||
 | 
			
		||||
        destfile = os.path.join(self.__thunderbird_policies, 'policies.json')
 | 
			
		||||
        os.makedirs(self.__thunderbird_policies, exist_ok=True)
 | 
			
		||||
        with open(destfile, 'w') as f:
 | 
			
		||||
            json.dump(self.policies_json, f)
 | 
			
		||||
            logdata = {'destfile': destfile}
 | 
			
		||||
            log('D212', logdata)
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
        if self.__module_enabled:
 | 
			
		||||
            log('D213')
 | 
			
		||||
            self.machine_apply()
 | 
			
		||||
        else:
 | 
			
		||||
            log('D214')
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -34,15 +34,13 @@ class yandex_browser_applier(applier_frontend):
 | 
			
		||||
    __managed_policies_path = '/etc/opt/yandex/browser/policies/managed'
 | 
			
		||||
    __recommended_policies_path = '/etc/opt/yandex/browser/policies/recommended'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, storage, sid, username):
 | 
			
		||||
    def __init__(self, storage, username):
 | 
			
		||||
        self.storage = storage
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self._is_machine_name = is_machine_name(self.username)
 | 
			
		||||
        yandex_filter = '{}%'.format(self.__registry_branch)
 | 
			
		||||
        self.yandex_keys = self.storage.filter_hklm_entries(yandex_filter)
 | 
			
		||||
        self.yandex_keys = self.storage.filter_hklm_entries(self.__registry_branch)
 | 
			
		||||
 | 
			
		||||
        self.policies_json = dict()
 | 
			
		||||
        self.policies_json = {}
 | 
			
		||||
 | 
			
		||||
        self.__module_enabled = check_enabled(
 | 
			
		||||
              self.storage
 | 
			
		||||
@@ -70,16 +68,14 @@ class yandex_browser_applier(applier_frontend):
 | 
			
		||||
        os.makedirs(self.__managed_policies_path, exist_ok=True)
 | 
			
		||||
        with open(destfile, 'w') as f:
 | 
			
		||||
            json.dump(dict_item_to_list(self.policies_json), f)
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['destfile'] = destfile
 | 
			
		||||
            logdata = {'destfile': destfile}
 | 
			
		||||
            log('D185', logdata)
 | 
			
		||||
 | 
			
		||||
        destfilerec = os.path.join(self.__recommended_policies_path, 'policies.json')
 | 
			
		||||
        os.makedirs(self.__recommended_policies_path, exist_ok=True)
 | 
			
		||||
        with open(destfilerec, 'w') as f:
 | 
			
		||||
            json.dump(dict_item_to_list(recommended__json), f)
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['destfilerec'] = destfilerec
 | 
			
		||||
            logdata = {'destfilerec': destfilerec}
 | 
			
		||||
            log('D185', logdata)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -160,7 +156,7 @@ class yandex_browser_applier(applier_frontend):
 | 
			
		||||
        '''
 | 
			
		||||
        Collect dictionaries from registry keys into a general dictionary
 | 
			
		||||
        '''
 | 
			
		||||
        counts = dict()
 | 
			
		||||
        counts = {}
 | 
			
		||||
        #getting the list of keys to read as an integer
 | 
			
		||||
        valuename_typeint = self.get_valuename_typeint()
 | 
			
		||||
        for it_data in yandex_keys:
 | 
			
		||||
@@ -188,9 +184,7 @@ class yandex_browser_applier(applier_frontend):
 | 
			
		||||
                        branch[parts[-1]] = str(it_data.data).replace('\\', '/')
 | 
			
		||||
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['Exception'] = exc
 | 
			
		||||
                logdata['keyname'] = it_data.keyname
 | 
			
		||||
                logdata = {'Exception': exc, 'keyname': it_data.keyname}
 | 
			
		||||
                log('D178', logdata)
 | 
			
		||||
        try:
 | 
			
		||||
            self.policies_json = counts['']
 | 
			
		||||
 
 | 
			
		||||
@@ -153,6 +153,7 @@ class gpoa_controller:
 | 
			
		||||
                    try:
 | 
			
		||||
                        back.retrieve_and_store()
 | 
			
		||||
                        # Start frontend only on successful backend finish
 | 
			
		||||
                        save_dconf(self.username, self.is_machine, nodomain)
 | 
			
		||||
                        self.start_frontend()
 | 
			
		||||
                    except Exception as exc:
 | 
			
		||||
                        logdata = dict({'message': str(exc)})
 | 
			
		||||
@@ -164,7 +165,6 @@ class gpoa_controller:
 | 
			
		||||
                        einfo = geterr()
 | 
			
		||||
                        logdata.update(einfo)
 | 
			
		||||
                        log('E3', logdata)
 | 
			
		||||
        save_dconf(self.username, self.is_machine)
 | 
			
		||||
 | 
			
		||||
    def start_frontend(self):
 | 
			
		||||
        '''
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -47,7 +47,7 @@ def decrypt_pass(cpassword):
 | 
			
		||||
    # decrypt() returns byte array which is immutable and we need to
 | 
			
		||||
    # strip padding, then convert UTF-16LE to UTF-8
 | 
			
		||||
    binstr = decrypter.decrypt(password)
 | 
			
		||||
    by = list()
 | 
			
		||||
    by = []
 | 
			
		||||
    for item in binstr:
 | 
			
		||||
        if item != 16:
 | 
			
		||||
            by.append(item)
 | 
			
		||||
@@ -57,7 +57,7 @@ def decrypt_pass(cpassword):
 | 
			
		||||
    return utf8str.decode()
 | 
			
		||||
 | 
			
		||||
def read_drives(drives_file):
 | 
			
		||||
    drives = list()
 | 
			
		||||
    drives = []
 | 
			
		||||
 | 
			
		||||
    for drive in get_xml_root(drives_file):
 | 
			
		||||
        drive_obj = drivemap()
 | 
			
		||||
@@ -78,9 +78,9 @@ def read_drives(drives_file):
 | 
			
		||||
 | 
			
		||||
    return drives
 | 
			
		||||
 | 
			
		||||
def merge_drives(storage, sid, drive_objects, policy_name):
 | 
			
		||||
def merge_drives(storage, drive_objects, policy_name):
 | 
			
		||||
    for drive in drive_objects:
 | 
			
		||||
        storage.add_drive(sid, drive, policy_name)
 | 
			
		||||
        storage.add_drive(drive, policy_name)
 | 
			
		||||
 | 
			
		||||
def json2drive(json_str):
 | 
			
		||||
    json_obj = json.loads(json_str)
 | 
			
		||||
@@ -141,13 +141,13 @@ class drivemap(DynamicAttributes):
 | 
			
		||||
        self.useLetter = useLetter
 | 
			
		||||
 | 
			
		||||
    def to_json(self):
 | 
			
		||||
        drive = dict()
 | 
			
		||||
        drive = {}
 | 
			
		||||
        drive['login'] = self.login
 | 
			
		||||
        drive['password'] = self.password
 | 
			
		||||
        drive['dir'] = self.dir
 | 
			
		||||
        drive['path'] = self.path
 | 
			
		||||
 | 
			
		||||
        contents = dict()
 | 
			
		||||
        contents = {}
 | 
			
		||||
        contents['drive'] = drive
 | 
			
		||||
 | 
			
		||||
        return json.dumps(contents)
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,10 @@ class DynamicAttributes:
 | 
			
		||||
    def __setattr__(self, key, value):
 | 
			
		||||
        if isinstance(value, Enum):
 | 
			
		||||
            value = str(value)
 | 
			
		||||
        if isinstance(value, str):
 | 
			
		||||
            for q in ["'", "\""]:
 | 
			
		||||
                if any(q in ch for ch in value):
 | 
			
		||||
                    value = value.replace(q, "″")
 | 
			
		||||
        self.__dict__[key] = value
 | 
			
		||||
 | 
			
		||||
    def items(self):
 | 
			
		||||
@@ -34,12 +38,19 @@ class DynamicAttributes:
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return iter(self.__dict__.items())
 | 
			
		||||
 | 
			
		||||
    def get_original_value(self, key):
 | 
			
		||||
        value = self.__dict__.get(key)
 | 
			
		||||
        if isinstance(value, str):
 | 
			
		||||
            value = value.replace("″", "'")
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
class RegistryKeyMetadata(DynamicAttributes):
 | 
			
		||||
    def __init__(self, policy_name, type, is_list=None):
 | 
			
		||||
    def __init__(self, policy_name, type, is_list=None, mod_previous_value=None):
 | 
			
		||||
        self.policy_name = policy_name
 | 
			
		||||
        self.type = type
 | 
			
		||||
        self.reloaded_with_policy_key = None
 | 
			
		||||
        self.is_list = is_list
 | 
			
		||||
        self.mod_previous_value = mod_previous_value
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return str(dict(self))
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,7 +21,7 @@ from .dynamic_attributes import DynamicAttributes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read_envvars(envvars_file):
 | 
			
		||||
    variables = list()
 | 
			
		||||
    variables = []
 | 
			
		||||
 | 
			
		||||
    for var in get_xml_root(envvars_file):
 | 
			
		||||
        props = var.find('Properties')
 | 
			
		||||
@@ -34,9 +34,9 @@ def read_envvars(envvars_file):
 | 
			
		||||
 | 
			
		||||
    return variables
 | 
			
		||||
 | 
			
		||||
def merge_envvars(storage, sid, envvar_objects, policy_name):
 | 
			
		||||
def merge_envvars(storage, envvar_objects, policy_name):
 | 
			
		||||
    for envv in envvar_objects:
 | 
			
		||||
        storage.add_envvar(sid, envv, policy_name)
 | 
			
		||||
        storage.add_envvar(envv, policy_name)
 | 
			
		||||
 | 
			
		||||
class envvar(DynamicAttributes):
 | 
			
		||||
    def __init__(self, name, value, action):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,7 +20,7 @@ from util.xml import get_xml_root
 | 
			
		||||
from .dynamic_attributes import DynamicAttributes
 | 
			
		||||
 | 
			
		||||
def read_files(filesxml):
 | 
			
		||||
    files = list()
 | 
			
		||||
    files = []
 | 
			
		||||
 | 
			
		||||
    for fil in get_xml_root(filesxml):
 | 
			
		||||
        props = fil.find('Properties')
 | 
			
		||||
@@ -36,9 +36,9 @@ def read_files(filesxml):
 | 
			
		||||
 | 
			
		||||
    return files
 | 
			
		||||
 | 
			
		||||
def merge_files(storage, sid, file_objects, policy_name):
 | 
			
		||||
def merge_files(storage, file_objects, policy_name):
 | 
			
		||||
    for fileobj in file_objects:
 | 
			
		||||
        storage.add_file(sid, fileobj, policy_name)
 | 
			
		||||
        storage.add_file(fileobj, policy_name)
 | 
			
		||||
 | 
			
		||||
class fileentry(DynamicAttributes):
 | 
			
		||||
    def __init__(self, fromPath):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -17,7 +17,6 @@
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from enum import Enum
 | 
			
		||||
from .dynamic_attributes import DynamicAttributes
 | 
			
		||||
 | 
			
		||||
from util.xml import get_xml_root
 | 
			
		||||
@@ -41,7 +40,7 @@ def folder_int2bool(val):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read_folders(folders_file):
 | 
			
		||||
    folders = list()
 | 
			
		||||
    folders = []
 | 
			
		||||
 | 
			
		||||
    for fld in get_xml_root(folders_file):
 | 
			
		||||
        props = fld.find('Properties')
 | 
			
		||||
@@ -58,9 +57,9 @@ def read_folders(folders_file):
 | 
			
		||||
 | 
			
		||||
    return folders
 | 
			
		||||
 | 
			
		||||
def merge_folders(storage, sid, folder_objects, policy_name):
 | 
			
		||||
def merge_folders(storage, folder_objects, policy_name):
 | 
			
		||||
    for folder in folder_objects:
 | 
			
		||||
        storage.add_folder(sid, folder, policy_name)
 | 
			
		||||
        storage.add_folder(folder, policy_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class folderentry(DynamicAttributes):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -110,7 +110,7 @@ def get_preftype(path_to_file):
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
def pref_parsers():
 | 
			
		||||
    parsers = dict()
 | 
			
		||||
    parsers = {}
 | 
			
		||||
 | 
			
		||||
    parsers[FileType.PREG] = read_polfile
 | 
			
		||||
    parsers[FileType.SHORTCUTS] = read_shortcuts
 | 
			
		||||
@@ -132,7 +132,7 @@ def get_parser(preference_type):
 | 
			
		||||
    return parsers[preference_type]
 | 
			
		||||
 | 
			
		||||
def pref_mergers():
 | 
			
		||||
    mergers = dict()
 | 
			
		||||
    mergers = {}
 | 
			
		||||
 | 
			
		||||
    mergers[FileType.PREG] = merge_polfile
 | 
			
		||||
    mergers[FileType.SHORTCUTS] = merge_shortcuts
 | 
			
		||||
@@ -154,11 +154,10 @@ def get_merger(preference_type):
 | 
			
		||||
    return mergers[preference_type]
 | 
			
		||||
 | 
			
		||||
class gpt:
 | 
			
		||||
    def __init__(self, gpt_path, sid, username='Machine', gpo_info=None):
 | 
			
		||||
    def __init__(self, gpt_path, username='Machine', gpo_info=None):
 | 
			
		||||
        add_to_dict(gpt_path, username, gpo_info)
 | 
			
		||||
        self.path = gpt_path
 | 
			
		||||
        self.username = username
 | 
			
		||||
        self.sid = sid
 | 
			
		||||
        self.storage = registry_factory()
 | 
			
		||||
        self.storage._gpt_read_flag = True
 | 
			
		||||
        self.gpo_info = gpo_info
 | 
			
		||||
@@ -185,18 +184,18 @@ class gpt:
 | 
			
		||||
            , 'scripts'
 | 
			
		||||
            , 'networkshares'
 | 
			
		||||
        ]
 | 
			
		||||
        self.settings = dict()
 | 
			
		||||
        self.settings['machine'] = dict()
 | 
			
		||||
        self.settings['user'] = dict()
 | 
			
		||||
        self.settings = {}
 | 
			
		||||
        self.settings['machine'] = {}
 | 
			
		||||
        self.settings['user'] = {}
 | 
			
		||||
        self.settings['machine']['regpol'] = find_file(self._machine_path, 'registry.pol')
 | 
			
		||||
        self.settings['user']['regpol'] = find_file(self._user_path, 'registry.pol')
 | 
			
		||||
        for setting in self.settings_list:
 | 
			
		||||
            machine_preffile = find_preffile(self._machine_path, setting)
 | 
			
		||||
            user_preffile = find_preffile(self._user_path, setting)
 | 
			
		||||
            mlogdata = dict({'setting': setting, 'prefpath': machine_preffile})
 | 
			
		||||
            mlogdata = {'setting': setting, 'prefpath': machine_preffile}
 | 
			
		||||
            log('D24', mlogdata)
 | 
			
		||||
            self.settings['machine'][setting] = machine_preffile
 | 
			
		||||
            ulogdata = dict({'setting': setting, 'prefpath': user_preffile})
 | 
			
		||||
            ulogdata = {'setting': setting, 'prefpath': user_preffile}
 | 
			
		||||
            log('D23', ulogdata)
 | 
			
		||||
            self.settings['user'][setting] = user_preffile
 | 
			
		||||
 | 
			
		||||
@@ -217,21 +216,21 @@ class gpt:
 | 
			
		||||
        try:
 | 
			
		||||
            # Merge machine policies to registry if possible
 | 
			
		||||
            if self.settings['machine']['regpol']:
 | 
			
		||||
                mlogdata = dict({'polfile': self.settings['machine']['regpol']})
 | 
			
		||||
                mlogdata = {'polfile': self.settings['machine']['regpol']}
 | 
			
		||||
                log('D34', mlogdata)
 | 
			
		||||
                util.preg.merge_polfile(self.settings['machine']['regpol'], policy_name=self.name, gpo_info=self.gpo_info)
 | 
			
		||||
            # Merge machine preferences to registry if possible
 | 
			
		||||
            for preference_name, preference_path in self.settings['machine'].items():
 | 
			
		||||
                if preference_path:
 | 
			
		||||
                    preference_type = get_preftype(preference_path)
 | 
			
		||||
                    logdata = dict({'pref': preference_type.value, 'sid': self.sid})
 | 
			
		||||
                    logdata = {'pref': preference_type.value}
 | 
			
		||||
                    log('D28', logdata)
 | 
			
		||||
                    preference_parser = get_parser(preference_type)
 | 
			
		||||
                    preference_merger = get_merger(preference_type)
 | 
			
		||||
                    preference_objects = preference_parser(preference_path)
 | 
			
		||||
                    preference_merger(self.storage, self.sid, preference_objects, self.name)
 | 
			
		||||
                    preference_merger(self.storage, preference_objects, self.name)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['gpt'] = self.name
 | 
			
		||||
            logdata['msg'] = str(exc)
 | 
			
		||||
            log('E28', logdata)
 | 
			
		||||
@@ -243,10 +242,9 @@ class gpt:
 | 
			
		||||
        try:
 | 
			
		||||
            # Merge user policies to registry if possible
 | 
			
		||||
            if self.settings['user']['regpol']:
 | 
			
		||||
                mulogdata = dict({'polfile': self.settings['user']['regpol']})
 | 
			
		||||
                mulogdata = {'polfile': self.settings['user']['regpol']}
 | 
			
		||||
                log('D35', mulogdata)
 | 
			
		||||
                util.preg.merge_polfile(self.settings['user']['regpol'],
 | 
			
		||||
                                        sid=self.sid,
 | 
			
		||||
                                        policy_name=self.name,
 | 
			
		||||
                                        username=self.username,
 | 
			
		||||
                                        gpo_info=self.gpo_info)
 | 
			
		||||
@@ -254,14 +252,14 @@ class gpt:
 | 
			
		||||
            for preference_name, preference_path in self.settings['user'].items():
 | 
			
		||||
                if preference_path:
 | 
			
		||||
                    preference_type = get_preftype(preference_path)
 | 
			
		||||
                    logdata = dict({'pref': preference_type.value, 'sid': self.sid})
 | 
			
		||||
                    logdata = {'pref': preference_type.value}
 | 
			
		||||
                    log('D29', logdata)
 | 
			
		||||
                    preference_parser = get_parser(preference_type)
 | 
			
		||||
                    preference_merger = get_merger(preference_type)
 | 
			
		||||
                    preference_objects = preference_parser(preference_path)
 | 
			
		||||
                    preference_merger(self.storage, self.sid, preference_objects, self.name)
 | 
			
		||||
                    preference_merger(self.storage, preference_objects, self.name)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['gpt'] = self.name
 | 
			
		||||
            logdata['msg'] = str(exc)
 | 
			
		||||
            log('E29', logdata)
 | 
			
		||||
@@ -354,13 +352,13 @@ def lp2gpt():
 | 
			
		||||
    # Write PReg
 | 
			
		||||
    polparser.write_binary(os.path.join(destdir, 'Registry.pol'))
 | 
			
		||||
 | 
			
		||||
def get_local_gpt(sid):
 | 
			
		||||
def get_local_gpt():
 | 
			
		||||
    '''
 | 
			
		||||
    Convert default policy to GPT and create object out of it.
 | 
			
		||||
    '''
 | 
			
		||||
    log('D25')
 | 
			
		||||
    lp2gpt()
 | 
			
		||||
    local_policy = gpt(str(local_policy_cache()), sid)
 | 
			
		||||
    local_policy = gpt(str(local_policy_cache()))
 | 
			
		||||
    local_policy.set_name('Local Policy')
 | 
			
		||||
 | 
			
		||||
    return local_policy
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,7 +20,7 @@ from util.xml import get_xml_root
 | 
			
		||||
from .dynamic_attributes import DynamicAttributes
 | 
			
		||||
 | 
			
		||||
def read_inifiles(inifiles_file):
 | 
			
		||||
    inifiles = list()
 | 
			
		||||
    inifiles = []
 | 
			
		||||
 | 
			
		||||
    for ini in get_xml_root(inifiles_file):
 | 
			
		||||
        prors = ini.find('Properties')
 | 
			
		||||
@@ -34,9 +34,9 @@ def read_inifiles(inifiles_file):
 | 
			
		||||
 | 
			
		||||
    return inifiles
 | 
			
		||||
 | 
			
		||||
def merge_inifiles(storage, sid, inifile_objects, policy_name):
 | 
			
		||||
def merge_inifiles(storage, inifile_objects, policy_name):
 | 
			
		||||
    for iniobj in inifile_objects:
 | 
			
		||||
        storage.add_ini(sid, iniobj, policy_name)
 | 
			
		||||
        storage.add_ini(iniobj, policy_name)
 | 
			
		||||
 | 
			
		||||
class inifile(DynamicAttributes):
 | 
			
		||||
    def __init__(self, path):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2022 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,7 +20,7 @@ from util.xml import get_xml_root
 | 
			
		||||
from .dynamic_attributes import DynamicAttributes
 | 
			
		||||
 | 
			
		||||
def read_networkshares(networksharesxml):
 | 
			
		||||
    networkshares = list()
 | 
			
		||||
    networkshares = []
 | 
			
		||||
 | 
			
		||||
    for share in get_xml_root(networksharesxml):
 | 
			
		||||
        props = share.find('Properties')
 | 
			
		||||
@@ -35,9 +35,9 @@ def read_networkshares(networksharesxml):
 | 
			
		||||
 | 
			
		||||
    return networkshares
 | 
			
		||||
 | 
			
		||||
def merge_networkshares(storage, sid, networkshares_objects, policy_name):
 | 
			
		||||
def merge_networkshares(storage, networkshares_objects, policy_name):
 | 
			
		||||
    for networkshareobj in networkshares_objects:
 | 
			
		||||
        storage.add_networkshare(sid, networkshareobj, policy_name)
 | 
			
		||||
        storage.add_networkshare(networkshareobj, policy_name)
 | 
			
		||||
 | 
			
		||||
class networkshare(DynamicAttributes):
 | 
			
		||||
    def __init__(self, name):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -23,11 +23,7 @@ from util.preg import (
 | 
			
		||||
def read_polfile(filename):
 | 
			
		||||
    return load_preg(filename).entries
 | 
			
		||||
 | 
			
		||||
def merge_polfile(storage, sid, policy_objects, policy_name):
 | 
			
		||||
def merge_polfile(storage, policy_objects, policy_name):
 | 
			
		||||
    pass
 | 
			
		||||
    # for entry in policy_objects:
 | 
			
		||||
    #     if not sid:
 | 
			
		||||
    #         storage.add_hklm_entry(entry, policy_name)
 | 
			
		||||
    #     else:
 | 
			
		||||
    #         storage.add_hkcu_entry(entry, sid, policy_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -25,7 +25,7 @@ def read_printers(printers_file):
 | 
			
		||||
    '''
 | 
			
		||||
    Read printer configurations from Printer.xml
 | 
			
		||||
    '''
 | 
			
		||||
    printers = list()
 | 
			
		||||
    printers = []
 | 
			
		||||
 | 
			
		||||
    for prn in get_xml_root(printers_file):
 | 
			
		||||
        prn_obj = printer(prn.tag, prn.get('name'), prn.get('status'))
 | 
			
		||||
@@ -42,9 +42,9 @@ def read_printers(printers_file):
 | 
			
		||||
 | 
			
		||||
    return printers
 | 
			
		||||
 | 
			
		||||
def merge_printers(storage, sid, printer_objects, policy_name):
 | 
			
		||||
def merge_printers(storage, printer_objects, policy_name):
 | 
			
		||||
    for device in printer_objects:
 | 
			
		||||
        storage.add_printer(sid, device, policy_name)
 | 
			
		||||
        storage.add_printer(device, policy_name)
 | 
			
		||||
 | 
			
		||||
def json2printer(json_str):
 | 
			
		||||
    '''
 | 
			
		||||
@@ -101,7 +101,7 @@ class printer(DynamicAttributes):
 | 
			
		||||
        '''
 | 
			
		||||
        Return string-serialized JSON representation of the object.
 | 
			
		||||
        '''
 | 
			
		||||
        printer = dict()
 | 
			
		||||
        printer = {}
 | 
			
		||||
        printer['type'] = self.printer_type
 | 
			
		||||
        printer['name'] = self.name
 | 
			
		||||
        printer['status'] = self.status
 | 
			
		||||
@@ -113,7 +113,7 @@ class printer(DynamicAttributes):
 | 
			
		||||
 | 
			
		||||
        # Nesting JSON object into JSON object makes it easier to add
 | 
			
		||||
        # metadata if needed.
 | 
			
		||||
        config = dict()
 | 
			
		||||
        config = {}
 | 
			
		||||
        config['printer'] = printer
 | 
			
		||||
 | 
			
		||||
        return json.dumps(config)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -23,10 +23,10 @@ from .dynamic_attributes import DynamicAttributes
 | 
			
		||||
def read_scripts(scripts_file):
 | 
			
		||||
    scripts = Scripts_lists()
 | 
			
		||||
 | 
			
		||||
    logon_scripts = dict()
 | 
			
		||||
    logoff_scripts = dict()
 | 
			
		||||
    startup_scripts = dict()
 | 
			
		||||
    shutdown_scripts = dict()
 | 
			
		||||
    logon_scripts = {}
 | 
			
		||||
    logoff_scripts = {}
 | 
			
		||||
    startup_scripts = {}
 | 
			
		||||
    shutdown_scripts = {}
 | 
			
		||||
 | 
			
		||||
    config = configparser.ConfigParser()
 | 
			
		||||
    config.read(scripts_file, encoding = 'utf-16')
 | 
			
		||||
@@ -78,22 +78,22 @@ def read_scripts(scripts_file):
 | 
			
		||||
 | 
			
		||||
    return scripts
 | 
			
		||||
 | 
			
		||||
def merge_scripts(storage, sid, scripts_objects, policy_name):
 | 
			
		||||
def merge_scripts(storage, scripts_objects, policy_name):
 | 
			
		||||
    for script in scripts_objects.get_logon_scripts():
 | 
			
		||||
        storage.add_script(sid, script, policy_name)
 | 
			
		||||
        storage.add_script(script, policy_name)
 | 
			
		||||
    for script in scripts_objects.get_logoff_scripts():
 | 
			
		||||
        storage.add_script(sid, script, policy_name)
 | 
			
		||||
        storage.add_script(script, policy_name)
 | 
			
		||||
    for script in scripts_objects.get_startup_scripts():
 | 
			
		||||
        storage.add_script(sid, script, policy_name)
 | 
			
		||||
        storage.add_script(script, policy_name)
 | 
			
		||||
    for script in scripts_objects.get_shutdown_scripts():
 | 
			
		||||
        storage.add_script(sid, script, policy_name)
 | 
			
		||||
        storage.add_script(script, policy_name)
 | 
			
		||||
 | 
			
		||||
class Scripts_lists:
 | 
			
		||||
    def __init__ (self):
 | 
			
		||||
        self.__logon_scripts = list()
 | 
			
		||||
        self.__logoff_scripts = list()
 | 
			
		||||
        self.__startup_scripts = list()
 | 
			
		||||
        self.__shutdown_scripts = list()
 | 
			
		||||
        self.__logon_scripts = []
 | 
			
		||||
        self.__logoff_scripts = []
 | 
			
		||||
        self.__startup_scripts = []
 | 
			
		||||
        self.__shutdown_scripts = []
 | 
			
		||||
 | 
			
		||||
    def get_logon_scripts(self):
 | 
			
		||||
        return self.__logon_scripts
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -23,7 +23,7 @@ def read_services(service_file):
 | 
			
		||||
    '''
 | 
			
		||||
    Read Services.xml from GPT.
 | 
			
		||||
    '''
 | 
			
		||||
    services = list()
 | 
			
		||||
    services = []
 | 
			
		||||
 | 
			
		||||
    for srv in get_xml_root(service_file):
 | 
			
		||||
        srv_obj = service(srv.get('name'))
 | 
			
		||||
@@ -40,7 +40,7 @@ def read_services(service_file):
 | 
			
		||||
 | 
			
		||||
    return services
 | 
			
		||||
 | 
			
		||||
def merge_services(storage, sid, service_objects, policy_name):
 | 
			
		||||
def merge_services(storage, service_objects, policy_name):
 | 
			
		||||
    for srv in service_objects:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,7 +20,6 @@ from pathlib import Path
 | 
			
		||||
import stat
 | 
			
		||||
from enum import Enum
 | 
			
		||||
 | 
			
		||||
from xml.etree import ElementTree
 | 
			
		||||
from xdg.DesktopEntry import DesktopEntry
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
@@ -70,7 +69,7 @@ def read_shortcuts(shortcuts_file):
 | 
			
		||||
 | 
			
		||||
    :shortcuts_file: Location of Shortcuts.xml
 | 
			
		||||
    '''
 | 
			
		||||
    shortcuts = list()
 | 
			
		||||
    shortcuts = []
 | 
			
		||||
 | 
			
		||||
    for link in get_xml_root(shortcuts_file):
 | 
			
		||||
        props = link.find('Properties')
 | 
			
		||||
@@ -96,9 +95,9 @@ def read_shortcuts(shortcuts_file):
 | 
			
		||||
 | 
			
		||||
    return shortcuts
 | 
			
		||||
 | 
			
		||||
def merge_shortcuts(storage, sid, shortcut_objects, policy_name):
 | 
			
		||||
def merge_shortcuts(storage, shortcut_objects, policy_name):
 | 
			
		||||
    for shortcut in shortcut_objects:
 | 
			
		||||
        storage.add_shortcut(sid, shortcut, policy_name)
 | 
			
		||||
        storage.add_shortcut(shortcut, policy_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_desktop_entry(binary_path):
 | 
			
		||||
@@ -115,6 +114,8 @@ def find_desktop_entry(binary_path):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class shortcut(DynamicAttributes):
 | 
			
		||||
    _ignore_fields = {"desktop_file_template", "desktop_file"}
 | 
			
		||||
 | 
			
		||||
    def __init__(self, dest, path, arguments, name=None, action=None, ttype=TargetType.FILESYSTEM):
 | 
			
		||||
        '''
 | 
			
		||||
        :param dest: Path to resulting file on file system
 | 
			
		||||
@@ -136,6 +137,14 @@ class shortcut(DynamicAttributes):
 | 
			
		||||
        self.type = ttype
 | 
			
		||||
        self.desktop_file_template = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def items(self):
 | 
			
		||||
        return ((k, v) for k, v in super().items() if k not in self._ignore_fields)
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return iter(self.items())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def replace_slashes(self, input_path):
 | 
			
		||||
        if input_path.startswith('%'):
 | 
			
		||||
            index = input_path.find('%', 1)
 | 
			
		||||
@@ -239,7 +248,7 @@ class shortcut(DynamicAttributes):
 | 
			
		||||
            if self.desktop_file_template:
 | 
			
		||||
                terminal_state = str2bool_lambda(self.desktop_file_template.get('Terminal'))
 | 
			
		||||
                self.desktop_file.set('Terminal', 'true' if terminal_state else 'false')
 | 
			
		||||
            self.desktop_file.set('Exec', '{} {}'.format(desktop_path, self.arguments))
 | 
			
		||||
            self.desktop_file.set('Exec', '{} {}'.format(desktop_path, self.get_original_value('arguments')))
 | 
			
		||||
            self.desktop_file.set('Comment', self.comment)
 | 
			
		||||
 | 
			
		||||
        if self.icon:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
def read_tasks(filename):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
def merge_tasks(storage, sid, task_objects, policy_name):
 | 
			
		||||
def merge_tasks(storage, task_objects, policy_name):
 | 
			
		||||
    for task in task_objects:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -113,14 +113,14 @@ def runner_factory(args, target):
 | 
			
		||||
                target = 'COMPUTER'
 | 
			
		||||
        except:
 | 
			
		||||
            username = None
 | 
			
		||||
            logdata = dict({'username': args.user})
 | 
			
		||||
            logdata = {'username': args.user}
 | 
			
		||||
            log('W1', logdata)
 | 
			
		||||
    else:
 | 
			
		||||
        # User may only perform gpupdate for machine (None) or
 | 
			
		||||
        # itself (os.getusername()).
 | 
			
		||||
        username = pwd.getpwuid(os.getuid()).pw_name
 | 
			
		||||
        if args.user != username:
 | 
			
		||||
            logdata = dict({'username': username})
 | 
			
		||||
            logdata = {'username': username}
 | 
			
		||||
            log('W2', logdata)
 | 
			
		||||
 | 
			
		||||
    if args.system:
 | 
			
		||||
@@ -179,7 +179,7 @@ def main():
 | 
			
		||||
            try:
 | 
			
		||||
                gpo_appliers[0].run()
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict({'error': str(exc)})
 | 
			
		||||
                logdata = {'error': str(exc)}
 | 
			
		||||
                log('E5')
 | 
			
		||||
                return int(ExitCodeUpdater.FAIL_GPUPDATE_COMPUTER_NOREPLY)
 | 
			
		||||
 | 
			
		||||
@@ -187,7 +187,7 @@ def main():
 | 
			
		||||
            try:
 | 
			
		||||
                gpo_appliers[1].run()
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict({'error': str(exc)})
 | 
			
		||||
                logdata = {'error': str(exc)}
 | 
			
		||||
                log('E6', logdata)
 | 
			
		||||
                return int(ExitCodeUpdater.FAIL_GPUPDATE_USER_NOREPLY)
 | 
			
		||||
    else:
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -19,9 +19,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import argparse
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
from util.util import (
 | 
			
		||||
      runcmd
 | 
			
		||||
@@ -32,6 +30,7 @@ from util.util import (
 | 
			
		||||
)
 | 
			
		||||
from util.config import GPConfig
 | 
			
		||||
from util.paths import get_custom_policy_dir
 | 
			
		||||
from frontend.appliers.ini_file import Ini_file
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Runner:
 | 
			
		||||
@@ -79,7 +78,7 @@ def parse_arguments():
 | 
			
		||||
        type=str,
 | 
			
		||||
        nargs='?',
 | 
			
		||||
        const='backend',
 | 
			
		||||
        choices=['local', 'samba'],
 | 
			
		||||
        choices=['local', 'samba', 'freeipa'],
 | 
			
		||||
        help='Backend (source of settings) name')
 | 
			
		||||
 | 
			
		||||
    parser_write.add_argument('status',
 | 
			
		||||
@@ -94,7 +93,7 @@ def parse_arguments():
 | 
			
		||||
        type=str,
 | 
			
		||||
        nargs='?',
 | 
			
		||||
        const='backend',
 | 
			
		||||
        choices=['local', 'samba'],
 | 
			
		||||
        choices=['local', 'samba', 'freeipa'],
 | 
			
		||||
        help='Backend (source of settings) name')
 | 
			
		||||
 | 
			
		||||
    parser_enable.add_argument('--local-policy',
 | 
			
		||||
@@ -103,7 +102,7 @@ def parse_arguments():
 | 
			
		||||
    parser_enable.add_argument('--backend',
 | 
			
		||||
        default='samba',
 | 
			
		||||
        type=str,
 | 
			
		||||
        choices=['local', 'samba'],
 | 
			
		||||
        choices=['local', 'samba', 'freeipa'],
 | 
			
		||||
        help='Backend (source of settings) name')
 | 
			
		||||
 | 
			
		||||
    parser_update.add_argument('--local-policy',
 | 
			
		||||
@@ -112,7 +111,7 @@ def parse_arguments():
 | 
			
		||||
    parser_update.add_argument('--backend',
 | 
			
		||||
        default='samba',
 | 
			
		||||
        type=str,
 | 
			
		||||
        choices=['local', 'samba'],
 | 
			
		||||
        choices=['local', 'samba', 'freeipa'],
 | 
			
		||||
        help='Backend (source of settings) name')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -223,6 +222,8 @@ def enable_gp(policy_name, backend_type):
 | 
			
		||||
    cmd_enable_gpupdate_user_timer = ['/bin/systemctl', '--global', 'enable', 'gpupdate-user.timer']
 | 
			
		||||
    cmd_enable_gpupdate_scripts_service = ['/bin/systemctl', 'enable', 'gpupdate-scripts-run.service']
 | 
			
		||||
    cmd_enable_gpupdate_user_scripts_service = ['/bin/systemctl', '--global', 'enable', 'gpupdate-scripts-run-user.service']
 | 
			
		||||
    cmd_ipa_client_samba = ['/usr/sbin/ipa-client-samba', '--unattended']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    config = GPConfig()
 | 
			
		||||
 | 
			
		||||
@@ -273,6 +274,28 @@ def enable_gp(policy_name, backend_type):
 | 
			
		||||
    if not is_unit_enabled('gpupdate.timer'):
 | 
			
		||||
        disable_gp()
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if backend_type == 'freeipa':
 | 
			
		||||
        result = runcmd(cmd_ipa_client_samba)
 | 
			
		||||
        if result[0] != 0:
 | 
			
		||||
            if "already configured" in str(result[1]) or "already exists" in str(result[1]):
 | 
			
		||||
                print("FreeIPA is already configured")
 | 
			
		||||
            else:
 | 
			
		||||
                print(str(result))
 | 
			
		||||
                return
 | 
			
		||||
        else:
 | 
			
		||||
            print(str(result))
 | 
			
		||||
 | 
			
		||||
        ini_obj = type("ini", (), {})()
 | 
			
		||||
        ini_obj.path = "/etc/samba/smb.conf"
 | 
			
		||||
        ini_obj.section = "global"
 | 
			
		||||
        ini_obj.action = "UPDATE"
 | 
			
		||||
        ini_obj.property = "log level"
 | 
			
		||||
        ini_obj.value = "0"
 | 
			
		||||
 | 
			
		||||
        Ini_file(ini_obj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # Enable gpupdate-setup.timer for all users
 | 
			
		||||
    if not rollback_on_error(cmd_enable_gpupdate_user_timer):
 | 
			
		||||
        return
 | 
			
		||||
@@ -344,18 +367,19 @@ def act_default_policy():
 | 
			
		||||
def main():
 | 
			
		||||
    arguments = parse_arguments()
 | 
			
		||||
 | 
			
		||||
    action = dict()
 | 
			
		||||
    action['list'] = act_list
 | 
			
		||||
    action['list-backends'] = act_list_backends
 | 
			
		||||
    action['status'] = act_status
 | 
			
		||||
    action['set-backend'] = act_set_backend
 | 
			
		||||
    action['write'] = act_write
 | 
			
		||||
    action['enable'] = act_enable
 | 
			
		||||
    action['update'] = act_enable
 | 
			
		||||
    action['disable'] = disable_gp
 | 
			
		||||
    action['active-policy'] = act_active_policy
 | 
			
		||||
    action['active-backend'] = act_active_backend
 | 
			
		||||
    action['default-policy'] = act_default_policy
 | 
			
		||||
    action = {
 | 
			
		||||
        'list': act_list,
 | 
			
		||||
        'list-backends': act_list_backends,
 | 
			
		||||
        'status': act_status,
 | 
			
		||||
        'set-backend': act_set_backend,
 | 
			
		||||
        'write': act_write,
 | 
			
		||||
        'enable': act_enable,
 | 
			
		||||
        'update': act_enable,
 | 
			
		||||
        'disable': disable_gp,
 | 
			
		||||
        'active-policy': act_active_policy,
 | 
			
		||||
        'active-backend': act_active_backend,
 | 
			
		||||
        'default-policy': act_default_policy
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if arguments.action == None:
 | 
			
		||||
        action['status']()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -259,6 +259,21 @@ msgstr "Возникло исключение при обновлении баз
 | 
			
		||||
msgid "Failed to retrieve data from dconf database"
 | 
			
		||||
msgstr "Не удалось получить данные из базы dconf"
 | 
			
		||||
 | 
			
		||||
msgid "Autofs restart failed"
 | 
			
		||||
msgstr "Перезапуск Autofs не удался"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to update LDAP with new password data"
 | 
			
		||||
msgstr "Не удалось обновить LDAP новыми данными пароля"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to change local user password"
 | 
			
		||||
msgstr "Не удалось изменить пароль локального пользователя"
 | 
			
		||||
 | 
			
		||||
msgid "Unable to initialize Freeipa backend"
 | 
			
		||||
msgstr "Невозможно инициализировать бэкэнд Freeipa"
 | 
			
		||||
 | 
			
		||||
msgid "FreeIPA API Error"
 | 
			
		||||
msgstr "Ошибка API FreeIPA"
 | 
			
		||||
 | 
			
		||||
# Error_end
 | 
			
		||||
 | 
			
		||||
# Debug
 | 
			
		||||
@@ -634,11 +649,11 @@ msgstr "Запуск применение настроек Envvar для маш
 | 
			
		||||
msgid "Envvar applier for machine will not be started"
 | 
			
		||||
msgstr "Применение настроек Envvar для машины не запускается"
 | 
			
		||||
 | 
			
		||||
msgid "Running Envvar applier for user in user context"
 | 
			
		||||
msgstr "Запуск применение настроек Envvar для пользователя в контексте пользователя"
 | 
			
		||||
msgid "Running Envvar applier for user in admin context"
 | 
			
		||||
msgstr "Запуск применение настроек Envvar для пользователя в контексте администратора"
 | 
			
		||||
 | 
			
		||||
msgid "Envvar applier for user in user context will not be started"
 | 
			
		||||
msgstr "Применение настроек Envvar для пользователя в контексте пользователя не запускается"
 | 
			
		||||
msgid "Envvar applier for user in admin context will not be started"
 | 
			
		||||
msgstr "Применение настроек Envvar для пользователя в контексте администратора не запускается"
 | 
			
		||||
 | 
			
		||||
msgid "Running Package applier for machine"
 | 
			
		||||
msgstr "Запуск установки пакетов для машины"
 | 
			
		||||
@@ -880,6 +895,81 @@ msgstr "Версия GPO не найдена"
 | 
			
		||||
msgid "SYSVOL entry found in cache"
 | 
			
		||||
msgstr "Запись SYSVOL найдена в кеше"
 | 
			
		||||
 | 
			
		||||
msgid "Wrote Thunderbird preferences to"
 | 
			
		||||
msgstr "Настройки Thunderbird записаны в"
 | 
			
		||||
 | 
			
		||||
msgid "Running Thunderbird applier for machine"
 | 
			
		||||
msgstr "Запуск применение настроек Thunderbird для машины"
 | 
			
		||||
 | 
			
		||||
msgid "Thunderbird applier for machine will not be started"
 | 
			
		||||
msgstr "Применение настроек Thunderbird для компьютера не запускается"
 | 
			
		||||
 | 
			
		||||
msgid "The environment file has been cleaned"
 | 
			
		||||
msgstr "Файл environment очищен"
 | 
			
		||||
 | 
			
		||||
msgid  "Cleanup of file environment failed"
 | 
			
		||||
msgstr "Очистка файла environment не удалась"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to get dictionary"
 | 
			
		||||
msgstr "Не удалось получить словарь"
 | 
			
		||||
 | 
			
		||||
msgid "LAPS applier started"
 | 
			
		||||
msgstr "Запущен обработчик LAPS"
 | 
			
		||||
 | 
			
		||||
msgid "LAPS applier is disabled"
 | 
			
		||||
msgstr "Обработчик LAPS отключен"
 | 
			
		||||
 | 
			
		||||
msgid "Rebooting system after password change"
 | 
			
		||||
msgstr "Перезагрузка системы после смены пароля"
 | 
			
		||||
 | 
			
		||||
msgid "Password changed"
 | 
			
		||||
msgstr "Пароль изменён"
 | 
			
		||||
 | 
			
		||||
msgid "Writing password changes time"
 | 
			
		||||
msgstr "Запись времени изменения пароля"
 | 
			
		||||
 | 
			
		||||
msgid "Requirements not met"
 | 
			
		||||
msgstr "Требования не выполнены"
 | 
			
		||||
 | 
			
		||||
msgid "The number of hours from the moment of the last user entrance"
 | 
			
		||||
msgstr "Количество часов с момента последнего входа пользователя"
 | 
			
		||||
 | 
			
		||||
msgid "The number of hours since the password has last changed"
 | 
			
		||||
msgstr "Количество часов с момента последнего изменения пароля"
 | 
			
		||||
 | 
			
		||||
msgid "LDAP updated with new password data"
 | 
			
		||||
msgstr "LDAP обновлён новыми данными пароля"
 | 
			
		||||
 | 
			
		||||
msgid "No active sessions found"
 | 
			
		||||
msgstr "Активные сеансы не найдены"
 | 
			
		||||
 | 
			
		||||
msgid "Process terminated"
 | 
			
		||||
msgstr "Процесс завершён"
 | 
			
		||||
 | 
			
		||||
msgid "Password update not needed"
 | 
			
		||||
msgstr "Обновление пароля не требуется"
 | 
			
		||||
 | 
			
		||||
msgid "Password successfully updated"
 | 
			
		||||
msgstr "Пароль успешно обновлён"
 | 
			
		||||
 | 
			
		||||
msgid "Cleaning the autofs catalog"
 | 
			
		||||
msgstr "Очистка каталога autofs"
 | 
			
		||||
 | 
			
		||||
msgid "No user login records found"
 | 
			
		||||
msgstr "Не найдены записи о входе пользователя"
 | 
			
		||||
 | 
			
		||||
msgid "Calculating time since the first user login after their password change"
 | 
			
		||||
msgstr "Расчет времени с момента первого входа пользователя после изменения их пароля"
 | 
			
		||||
 | 
			
		||||
msgid "No logins found after password change"
 | 
			
		||||
msgstr "Не найдены входы после изменения пароля"
 | 
			
		||||
 | 
			
		||||
msgid "Unknown message type, no message assigned"
 | 
			
		||||
msgstr "Неизвестный тип сообщения"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to load cached versions"
 | 
			
		||||
msgstr "Не удалось загрузить кешированные версии"
 | 
			
		||||
 | 
			
		||||
# Debug_end
 | 
			
		||||
 | 
			
		||||
# Warning
 | 
			
		||||
@@ -956,6 +1046,64 @@ msgstr "Не удалось выполнить действие для INI-фа
 | 
			
		||||
msgid "Couldn't get the uid"
 | 
			
		||||
msgstr "Не удалось получить uid"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to load content from remote host"
 | 
			
		||||
msgstr "Не удалось загрузить контент с удаленного узла"
 | 
			
		||||
 | 
			
		||||
msgid "Force mode activated"
 | 
			
		||||
msgstr "Режим force задействован"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to change password"
 | 
			
		||||
msgstr "Не удалось изменить пароль"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to write password modification time"
 | 
			
		||||
msgstr "Не удалось записать время изменения пароля"
 | 
			
		||||
 | 
			
		||||
msgid "LAPS requirements not met, module disabled"
 | 
			
		||||
msgstr "Требования LAPS не выполнены, модуль отключён"
 | 
			
		||||
 | 
			
		||||
msgid "Could not resolve encryption principal name. Return admin group SID"
 | 
			
		||||
msgstr "Не удалось определить имя шифрования. Возвращён SID группы администраторов"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to get expiration time from LDAP"
 | 
			
		||||
msgstr "Не удалось получить время истечения срока действия из LDAP"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to read password modification time from dconf"
 | 
			
		||||
msgstr "Не удалось прочитать время изменения пароля из dconf"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to get last login time"
 | 
			
		||||
msgstr "Не удалось получить время последнего входа"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to calculate password age"
 | 
			
		||||
msgstr "Не удалось вычислить возраст пароля"
 | 
			
		||||
 | 
			
		||||
msgid "Failed to terminate process"
 | 
			
		||||
msgstr "Не удалось завершить процесс"
 | 
			
		||||
 | 
			
		||||
msgid "The user was not found to change the password"
 | 
			
		||||
msgstr "Пользователь для изменения пароля не был найден"
 | 
			
		||||
 | 
			
		||||
msgid "Error while cleaning the autofs catalog"
 | 
			
		||||
msgstr "Ошибка при очистке каталога autofs"
 | 
			
		||||
 | 
			
		||||
msgid "Problem with timezone detection"
 | 
			
		||||
msgstr "Проблема с определением часового пояса"
 | 
			
		||||
 | 
			
		||||
msgid "Error executing last command"
 | 
			
		||||
msgstr "Ошибка выполнения команды last"
 | 
			
		||||
 | 
			
		||||
msgid "Last command not found"
 | 
			
		||||
msgstr "Команда last не найдена"
 | 
			
		||||
 | 
			
		||||
msgid "Error getting user login times"
 | 
			
		||||
msgstr "Ошибка получения времени входа пользователя"
 | 
			
		||||
 | 
			
		||||
msgid "Invalid timezone in reference datetime"
 | 
			
		||||
msgstr "Некорректный часовой пояс в reference datetime"
 | 
			
		||||
 | 
			
		||||
msgid "wbinfo SID lookup failed; will try as trusted domain user"
 | 
			
		||||
msgstr "Ошибка получения SID через wbinfo; будет предпринята попытка как для пользователя доверенного домена"
 | 
			
		||||
# Warning_end
 | 
			
		||||
 | 
			
		||||
# Fatal
 | 
			
		||||
msgid "Unable to refresh GPO list"
 | 
			
		||||
msgstr "Невозможно обновить список объектов групповых политик"
 | 
			
		||||
@@ -969,7 +1117,5 @@ msgstr "Не удалось получить GPT для пользователя
 | 
			
		||||
msgid "Unknown fatal code"
 | 
			
		||||
msgstr "Неизвестный код фатальной ошибки"
 | 
			
		||||
 | 
			
		||||
# get_message
 | 
			
		||||
msgid "Unknown message type, no message assigned"
 | 
			
		||||
msgstr "Неизвестный тип сообщения"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
import gettext
 | 
			
		||||
 | 
			
		||||
def info_code(code):
 | 
			
		||||
    info_ids = dict()
 | 
			
		||||
    info_ids = {}
 | 
			
		||||
    info_ids[1] = 'Got GPO list for username'
 | 
			
		||||
    info_ids[2] = 'Got GPO'
 | 
			
		||||
    info_ids[3] = 'Working with control'
 | 
			
		||||
@@ -36,7 +36,7 @@ def info_code(code):
 | 
			
		||||
    return info_ids.get(code, 'Unknown info code')
 | 
			
		||||
 | 
			
		||||
def error_code(code):
 | 
			
		||||
    error_ids = dict()
 | 
			
		||||
    error_ids = {}
 | 
			
		||||
    error_ids[1] = 'Insufficient permissions to run gpupdate'
 | 
			
		||||
    error_ids[2] = 'gpupdate will not be started'
 | 
			
		||||
    error_ids[3] = 'Backend execution error'
 | 
			
		||||
@@ -109,10 +109,15 @@ def error_code(code):
 | 
			
		||||
    error_ids[71] = 'Failed to update dconf database'
 | 
			
		||||
    error_ids[72] = 'Exception occurred while updating dconf database'
 | 
			
		||||
    error_ids[73] = 'Failed to retrieve data from dconf database'
 | 
			
		||||
    error_ids[74] = 'Autofs restart failed'
 | 
			
		||||
    error_ids[75] = 'Failed to update LDAP with new password data'
 | 
			
		||||
    error_ids[76] = 'Failed to change local user password'
 | 
			
		||||
    error_ids[77] = 'Unable to initialize Freeipa backend'
 | 
			
		||||
    error_ids[78] = 'FreeIPA API error'
 | 
			
		||||
    return error_ids.get(code, 'Unknown error code')
 | 
			
		||||
 | 
			
		||||
def debug_code(code):
 | 
			
		||||
    debug_ids = dict()
 | 
			
		||||
    debug_ids = {}
 | 
			
		||||
    debug_ids[1] = 'The GPOA process was started for user'
 | 
			
		||||
    debug_ids[2] = 'Username is not specified - will use username of the current process'
 | 
			
		||||
    debug_ids[3] = 'Initializing plugin manager'
 | 
			
		||||
@@ -248,8 +253,8 @@ def debug_code(code):
 | 
			
		||||
    debug_ids[133] = 'NTP applier for machine will not be started'
 | 
			
		||||
    debug_ids[134] = 'Running Envvar applier for machine'
 | 
			
		||||
    debug_ids[135] = 'Envvar applier for machine will not be started'
 | 
			
		||||
    debug_ids[136] = 'Running Envvar applier for user in user context'
 | 
			
		||||
    debug_ids[137] = 'Envvar applier for user in user context will not be started'
 | 
			
		||||
    debug_ids[136] = 'Running Envvar applier for user in admin context'
 | 
			
		||||
    debug_ids[137] = 'Envvar applier for user in admin context will not be started'
 | 
			
		||||
    debug_ids[138] = 'Running Package applier for machine'
 | 
			
		||||
    debug_ids[139] = 'Package applier for machine will not be started'
 | 
			
		||||
    debug_ids[140] = 'Running Package applier for user in administrator context'
 | 
			
		||||
@@ -323,12 +328,35 @@ def debug_code(code):
 | 
			
		||||
    debug_ids[208] = 'No entry found for the specified path'
 | 
			
		||||
    debug_ids[209] = 'Creating an ini file with policies for dconf'
 | 
			
		||||
    debug_ids[211] = 'SYSVOL entry found in cache'
 | 
			
		||||
    #debug_ids[210] = 'GPO version was not found'
 | 
			
		||||
    debug_ids[212] = 'Wrote Thunderbird preferences to'
 | 
			
		||||
    debug_ids[213] = 'Running Thunderbird applier for machine'
 | 
			
		||||
    debug_ids[214] = 'Thunderbird applier for machine will not be started'
 | 
			
		||||
    debug_ids[215] = 'The environment file has been cleaned'
 | 
			
		||||
    debug_ids[216] = 'Cleanup of file environment failed'
 | 
			
		||||
    debug_ids[217] = 'Failed to get dictionary'
 | 
			
		||||
    debug_ids[218] = 'LAPS applier started'
 | 
			
		||||
    debug_ids[219] = 'LAPS applier is disabled'
 | 
			
		||||
    debug_ids[220] = 'Rebooting system after password change'
 | 
			
		||||
    debug_ids[221] = 'Password changed'
 | 
			
		||||
    debug_ids[222] = 'Writing password changes time'
 | 
			
		||||
    debug_ids[223] = 'Requirements not met'
 | 
			
		||||
    debug_ids[224] = 'The number of hours from the moment of the last user entrance'
 | 
			
		||||
    debug_ids[225] = 'The number of hours since the password has last changed'
 | 
			
		||||
    debug_ids[226] = 'LDAP updated with new password data'
 | 
			
		||||
    debug_ids[227] = 'No active sessions found'
 | 
			
		||||
    debug_ids[228] = 'Process terminated'
 | 
			
		||||
    debug_ids[229] = 'Password update not needed'
 | 
			
		||||
    debug_ids[230] = 'Password successfully updated'
 | 
			
		||||
    debug_ids[231] = 'Cleaning the autofs catalog'
 | 
			
		||||
    debug_ids[232] = 'No user login records found'
 | 
			
		||||
    debug_ids[233] = 'Calculating time since the first user login after their password change'
 | 
			
		||||
    debug_ids[234] = 'No logins found after password change'
 | 
			
		||||
    debug_ids[235] = 'Failed to load cached versions'
 | 
			
		||||
 | 
			
		||||
    return debug_ids.get(code, 'Unknown debug code')
 | 
			
		||||
 | 
			
		||||
def warning_code(code):
 | 
			
		||||
    warning_ids = dict()
 | 
			
		||||
    warning_ids = {}
 | 
			
		||||
    warning_ids[1] = (
 | 
			
		||||
        'Unable to perform gpupdate for non-existent user, '
 | 
			
		||||
        'will update machine settings'
 | 
			
		||||
@@ -359,12 +387,30 @@ def warning_code(code):
 | 
			
		||||
    warning_ids[22] = 'The user setting was not installed, conflict with computer setting'
 | 
			
		||||
    warning_ids[23] = 'Action for ini file failed'
 | 
			
		||||
    warning_ids[24] = 'Couldn\'t get the uid'
 | 
			
		||||
 | 
			
		||||
    warning_ids[25] = 'Failed to load content from remote host'
 | 
			
		||||
    warning_ids[26] = 'Force mode activated'
 | 
			
		||||
    warning_ids[27] = 'Failed to change password'
 | 
			
		||||
    warning_ids[28] = 'Failed to write password modification time'
 | 
			
		||||
    warning_ids[29] = 'LAPS requirements not met, module disabled'
 | 
			
		||||
    warning_ids[30] = 'Could not resolve encryption principal name. Return admin group SID'
 | 
			
		||||
    warning_ids[31] = 'Failed to get expiration time from LDAP'
 | 
			
		||||
    warning_ids[32] = 'Failed to read password modification time from dconf'
 | 
			
		||||
    warning_ids[33] = 'Failed to get last login time'
 | 
			
		||||
    warning_ids[34] = 'Failed to calculate password age'
 | 
			
		||||
    warning_ids[35] = 'Failed to terminate process'
 | 
			
		||||
    warning_ids[36] = 'The user was not found to change the password'
 | 
			
		||||
    warning_ids[37] = 'Error while cleaning the autofs catalog'
 | 
			
		||||
    warning_ids[38] = 'Problem with timezone detection'
 | 
			
		||||
    warning_ids[39] = 'Error executing last command'
 | 
			
		||||
    warning_ids[40] = 'Last command not found'
 | 
			
		||||
    warning_ids[41] = 'Error getting user login times'
 | 
			
		||||
    warning_ids[42] = 'Invalid timezone in reference datetime'
 | 
			
		||||
    warning_ids[43] = 'wbinfo SID lookup failed; will try as trusted domain user'
 | 
			
		||||
 | 
			
		||||
    return warning_ids.get(code, 'Unknown warning code')
 | 
			
		||||
 | 
			
		||||
def fatal_code(code):
 | 
			
		||||
    fatal_ids = dict()
 | 
			
		||||
    fatal_ids = {}
 | 
			
		||||
    fatal_ids[1] = 'Unable to refresh GPO list'
 | 
			
		||||
    fatal_ids[2] = 'Error getting GPTs for machine'
 | 
			
		||||
    fatal_ids[3] = 'Error getting GPTs for user'
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@
 | 
			
		||||
import rpm
 | 
			
		||||
import subprocess
 | 
			
		||||
from gpoa.storage import registry_factory
 | 
			
		||||
from util.gpoa_ini_parsing import GpoaConfigObj
 | 
			
		||||
from util.util import get_uid_by_username, string_to_literal_eval
 | 
			
		||||
import logging
 | 
			
		||||
from util.logging import log
 | 
			
		||||
@@ -62,9 +61,11 @@ class Pkcon_applier:
 | 
			
		||||
        self.remove_packages_setting = string_to_literal_eval(dict_packages.get(remove_key_name,[]))
 | 
			
		||||
 | 
			
		||||
        for package in self.install_packages_setting:
 | 
			
		||||
            package = package.strip()
 | 
			
		||||
            if not is_rpm_installed(package):
 | 
			
		||||
                self.install_packages.add(package)
 | 
			
		||||
        for package in self.remove_packages_setting:
 | 
			
		||||
            package = package.strip()
 | 
			
		||||
            if package in self.install_packages:
 | 
			
		||||
                self.install_packages.remove(package)
 | 
			
		||||
            if is_rpm_installed(package):
 | 
			
		||||
@@ -74,24 +75,20 @@ class Pkcon_applier:
 | 
			
		||||
        log('D142')
 | 
			
		||||
        self.update()
 | 
			
		||||
        for package in self.remove_packages:
 | 
			
		||||
            logdata = {'name': package}
 | 
			
		||||
            try:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['name'] = package
 | 
			
		||||
                log('D149', logdata)
 | 
			
		||||
                self.remove_pkg(package)
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['exc'] = exc
 | 
			
		||||
                log('E58', logdata)
 | 
			
		||||
 | 
			
		||||
        for package in self.install_packages:
 | 
			
		||||
           logdata = {'name': package}
 | 
			
		||||
           try:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['name'] = package
 | 
			
		||||
                log('D148', logdata)
 | 
			
		||||
                self.install_pkg(package)
 | 
			
		||||
           except Exception as exc:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata['exc'] = exc
 | 
			
		||||
                log('E57', logdata)
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +102,7 @@ class Pkcon_applier:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def remove_pkg(self, package_name):
 | 
			
		||||
        fullcmd = self.__remove_command
 | 
			
		||||
        fullcmd = list(self.__remove_command)
 | 
			
		||||
        fullcmd.append(package_name)
 | 
			
		||||
        return subprocess.check_output(fullcmd)
 | 
			
		||||
 | 
			
		||||
@@ -116,15 +113,14 @@ class Pkcon_applier:
 | 
			
		||||
        try:
 | 
			
		||||
            res =  subprocess.check_output(['/usr/bin/apt-get', 'update'], encoding='utf-8')
 | 
			
		||||
            msg =  str(res).split('\n')
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            for mslog in msg:
 | 
			
		||||
                ms =  str(mslog).split(' ')
 | 
			
		||||
                if ms:
 | 
			
		||||
                    logdata = {ms[0]: ms[1:-1]}
 | 
			
		||||
                log('D143', logdata)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata['msg'] = exc
 | 
			
		||||
            logdata = {'msg': exc}
 | 
			
		||||
            log('E56',logdata)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ from messages import message_with_code
 | 
			
		||||
 | 
			
		||||
class plugin_manager:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.plugins = dict()
 | 
			
		||||
        self.plugins = {}
 | 
			
		||||
        logging.debug(slogm(message_with_code('D3')))
 | 
			
		||||
        try:
 | 
			
		||||
            self.plugins['adp'] = adp()
 | 
			
		||||
@@ -35,6 +35,6 @@ class plugin_manager:
 | 
			
		||||
            logging.warning(slogm(str(exc)))
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        self.plugins.get('adp', plugin('adp')).run()
 | 
			
		||||
        #self.plugins.get('adp', plugin('adp')).run()
 | 
			
		||||
        self.plugins.get('roles', plugin('roles')).run()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ class Scripts_runner:
 | 
			
		||||
        self.dir_scripts_machine = '/var/cache/gpupdate_scripts_cache/machine/'
 | 
			
		||||
        self.dir_scripts_users = '/var/cache/gpupdate_scripts_cache/users/'
 | 
			
		||||
        self.user_name = user_name
 | 
			
		||||
        self.list_with_all_commands = list()
 | 
			
		||||
        self.list_with_all_commands = []
 | 
			
		||||
        stack_dir = None
 | 
			
		||||
        if work_mode and work_mode.upper() == 'MACHINE':
 | 
			
		||||
            stack_dir = self.machine_runner_fill()
 | 
			
		||||
@@ -59,7 +59,7 @@ class Scripts_runner:
 | 
			
		||||
        return self.get_stack_dir(self.dir_scripts_machine)
 | 
			
		||||
 | 
			
		||||
    def get_stack_dir(self, path_dir):
 | 
			
		||||
        stack_dir = list()
 | 
			
		||||
        stack_dir = []
 | 
			
		||||
        try:
 | 
			
		||||
            dir_script = Path(path_dir)
 | 
			
		||||
            for it_dir in dir_script.iterdir():
 | 
			
		||||
@@ -72,7 +72,7 @@ class Scripts_runner:
 | 
			
		||||
    def find_action(self, stack_dir):
 | 
			
		||||
        if not stack_dir:
 | 
			
		||||
            return
 | 
			
		||||
        list_tmp = list()
 | 
			
		||||
        list_tmp = []
 | 
			
		||||
        while stack_dir:
 | 
			
		||||
            path_turn = stack_dir.pop()
 | 
			
		||||
            basename = os.path.basename(path_turn)
 | 
			
		||||
@@ -94,7 +94,7 @@ class Scripts_runner:
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    print('Argument read for {}: {}'.format(self.list_with_all_commands.pop(), exc))
 | 
			
		||||
            else:
 | 
			
		||||
                cmd = list()
 | 
			
		||||
                cmd = []
 | 
			
		||||
                cmd.append(file_in_task_dir)
 | 
			
		||||
                self.list_with_all_commands.append(cmd)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2023 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,7 +18,12 @@
 | 
			
		||||
 | 
			
		||||
import subprocess
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from util.util import string_to_literal_eval, touch_file, get_uid_by_username
 | 
			
		||||
from util.util import (string_to_literal_eval,
 | 
			
		||||
                       try_dict_to_literal_eval,
 | 
			
		||||
                       touch_file, get_uid_by_username,
 | 
			
		||||
                       add_prefix_to_keys,
 | 
			
		||||
                       remove_keys_with_prefix,
 | 
			
		||||
                       clean_data)
 | 
			
		||||
from util.paths import get_dconf_config_path
 | 
			
		||||
from util.logging import log
 | 
			
		||||
import re
 | 
			
		||||
@@ -59,43 +64,50 @@ class Dconf_registry():
 | 
			
		||||
    '''
 | 
			
		||||
    _GpoPriority = 'Software/BaseALT/Policies/GpoPriority'
 | 
			
		||||
    _gpo_name = set()
 | 
			
		||||
    global_registry_dict = dict({_GpoPriority:{}})
 | 
			
		||||
    global_registry_dict = {_GpoPriority:{}}
 | 
			
		||||
    previous_global_registry_dict = {}
 | 
			
		||||
    __template_file = '/usr/share/dconf/user_mandatory.template'
 | 
			
		||||
    _policies_path = 'Software/'
 | 
			
		||||
    _policies_win_path = 'SOFTWARE/'
 | 
			
		||||
    _gpt_read_flag = False
 | 
			
		||||
    _force = False
 | 
			
		||||
    __dconf_dict_flag = False
 | 
			
		||||
    __dconf_dict = dict()
 | 
			
		||||
    _dict_gpo_name_version_cache = dict()
 | 
			
		||||
    __dconf_dict = {}
 | 
			
		||||
    _dconf_db = {}
 | 
			
		||||
    _dict_gpo_name_version_cache = {}
 | 
			
		||||
    _username = None
 | 
			
		||||
    _uid = None
 | 
			
		||||
    _envprofile = None
 | 
			
		||||
    _path_bin_system = "/etc/dconf/db/policy"
 | 
			
		||||
 | 
			
		||||
    list_keys = list()
 | 
			
		||||
    _info = dict()
 | 
			
		||||
    list_keys = []
 | 
			
		||||
    _info = {}
 | 
			
		||||
    _counter_gpt = itertools.count(0)
 | 
			
		||||
 | 
			
		||||
    shortcuts = list()
 | 
			
		||||
    folders = list()
 | 
			
		||||
    files = list()
 | 
			
		||||
    drives = list()
 | 
			
		||||
    scheduledtasks = list()
 | 
			
		||||
    environmentvariables = list()
 | 
			
		||||
    inifiles = list()
 | 
			
		||||
    services = list()
 | 
			
		||||
    printers = list()
 | 
			
		||||
    scripts = list()
 | 
			
		||||
    networkshares = list()
 | 
			
		||||
    trans_table = str.maketrans({
 | 
			
		||||
                        '\n': '',
 | 
			
		||||
                        '\r': '',
 | 
			
		||||
                        '"': "'",
 | 
			
		||||
                        '\\': '\\\\'
 | 
			
		||||
                    })
 | 
			
		||||
 | 
			
		||||
    shortcuts = []
 | 
			
		||||
    folders = []
 | 
			
		||||
    files = []
 | 
			
		||||
    drives = []
 | 
			
		||||
    scheduledtasks = []
 | 
			
		||||
    environmentvariables = []
 | 
			
		||||
    inifiles = []
 | 
			
		||||
    services = []
 | 
			
		||||
    printers = []
 | 
			
		||||
    scripts = []
 | 
			
		||||
    networkshares = []
 | 
			
		||||
 | 
			
		||||
    _true_strings = {
 | 
			
		||||
        "True",
 | 
			
		||||
        "true",
 | 
			
		||||
        "TRUE",
 | 
			
		||||
        "yes",
 | 
			
		||||
        "Yes",
 | 
			
		||||
        "enabled",
 | 
			
		||||
        "enable",
 | 
			
		||||
        "Enabled",
 | 
			
		||||
        "Enable",
 | 
			
		||||
        '1'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def set_info(cls, key , data):
 | 
			
		||||
@@ -114,7 +126,7 @@ class Dconf_registry():
 | 
			
		||||
    def get_matching_keys(path):
 | 
			
		||||
        if path[0] != '/':
 | 
			
		||||
            path = '/' + path
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        envprofile = get_dconf_envprofile()
 | 
			
		||||
        try:
 | 
			
		||||
            process = subprocess.Popen(['dconf', 'list', path],
 | 
			
		||||
@@ -145,7 +157,7 @@ class Dconf_registry():
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_key_value(key):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        envprofile = get_dconf_envprofile()
 | 
			
		||||
        try:
 | 
			
		||||
            process = subprocess.Popen(['dconf', 'read', key],
 | 
			
		||||
@@ -164,7 +176,7 @@ class Dconf_registry():
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def dconf_update(uid=None):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        path_dconf_config = get_dconf_config_path(uid)
 | 
			
		||||
        db_file = path_dconf_config[:-3]
 | 
			
		||||
        try:
 | 
			
		||||
@@ -189,9 +201,15 @@ class Dconf_registry():
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def update_dict_to_previous(cls):
 | 
			
		||||
        dict_clean_previous = remove_keys_with_prefix(cls._dconf_db)
 | 
			
		||||
        dict_with_previous = add_prefix_to_keys(dict_clean_previous)
 | 
			
		||||
        cls.global_registry_dict.update(dict_with_previous)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def apply_template(cls, uid):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        if uid and cls.check_profile_template():
 | 
			
		||||
            with open(cls.__template_file, "r") as f:
 | 
			
		||||
                template = f.read()
 | 
			
		||||
@@ -200,13 +218,15 @@ class Dconf_registry():
 | 
			
		||||
 | 
			
		||||
        elif uid:
 | 
			
		||||
            content = f"user-db:user\n" \
 | 
			
		||||
              f"system-db:distr\n" \
 | 
			
		||||
              f"system-db:policy\n" \
 | 
			
		||||
              f"system-db:policy{uid}\n" \
 | 
			
		||||
              f"system-db:local\n" \
 | 
			
		||||
              f"system-db:default\n" \
 | 
			
		||||
              f"system-db:local\n" \
 | 
			
		||||
              f"system-db:policy{uid}\n" \
 | 
			
		||||
              f"system-db:policy\n"
 | 
			
		||||
              f"system-db:policy\n" \
 | 
			
		||||
              f"system-db:distr\n"
 | 
			
		||||
        else:
 | 
			
		||||
            logdata['uid'] = uid
 | 
			
		||||
            log('W24', logdata)
 | 
			
		||||
@@ -238,9 +258,12 @@ class Dconf_registry():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_dictionary_from_dconf_file_db(self, uid=None):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        if not uid:
 | 
			
		||||
    def get_dictionary_from_dconf_file_db(self, uid=None, path_bin=None, save_dconf_db=False):
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        error_skip = None
 | 
			
		||||
        if path_bin:
 | 
			
		||||
            error_skip = True
 | 
			
		||||
        elif not uid:
 | 
			
		||||
            path_bin = self._path_bin_system
 | 
			
		||||
        else:
 | 
			
		||||
            path_bin = self._path_bin_system + str(uid)
 | 
			
		||||
@@ -253,7 +276,7 @@ class Dconf_registry():
 | 
			
		||||
                name_list = Gvdb.Table.get_names(table)
 | 
			
		||||
                for name in name_list:
 | 
			
		||||
                    value = Gvdb.Table.get_value(table, name)
 | 
			
		||||
                    if not value:
 | 
			
		||||
                    if value is None:
 | 
			
		||||
                        continue
 | 
			
		||||
                    list_path = name.split('/')
 | 
			
		||||
                    if value.is_of_type(GLib.VariantType('s')):
 | 
			
		||||
@@ -265,8 +288,12 @@ class Dconf_registry():
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata['exc'] = exc
 | 
			
		||||
            logdata['path_bin'] = path_bin
 | 
			
		||||
            log('E73', logdata)
 | 
			
		||||
 | 
			
		||||
            if not error_skip:
 | 
			
		||||
                log('E73', logdata)
 | 
			
		||||
            else:
 | 
			
		||||
                log('D217', logdata)
 | 
			
		||||
        if save_dconf_db:
 | 
			
		||||
            Dconf_registry._dconf_db = output_dict
 | 
			
		||||
        return output_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -304,7 +331,7 @@ class Dconf_registry():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def filter_hkcu_entries(cls, sid, startswith):
 | 
			
		||||
    def filter_hkcu_entries(cls, startswith):
 | 
			
		||||
        return cls.filter_hklm_entries(startswith)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -330,8 +357,8 @@ class Dconf_registry():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_entry(cls, path, dictionary = None):
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
    def get_entry(cls, path, dictionary = None, preg = True):
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        result = Dconf_registry.get_storage(dictionary)
 | 
			
		||||
 | 
			
		||||
        keys = path.split("\\") if "\\" in path else path.split("/")
 | 
			
		||||
@@ -340,15 +367,26 @@ class Dconf_registry():
 | 
			
		||||
        if isinstance(result, dict) and key in result.keys():
 | 
			
		||||
            data = result.get(key).get(keys[-1])
 | 
			
		||||
            return PregDconf(
 | 
			
		||||
                key, convert_string_dconf(keys[-1]), find_preg_type(data), data)
 | 
			
		||||
                key, convert_string_dconf(keys[-1]), find_preg_type(data), data) if preg else data
 | 
			
		||||
        else:
 | 
			
		||||
            logdata['path'] = path
 | 
			
		||||
            log('D208', logdata)
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def check_enable_key(cls ,key):
 | 
			
		||||
        data = cls.get_entry(key, preg = False)
 | 
			
		||||
        if data:
 | 
			
		||||
            if isinstance(data, str):
 | 
			
		||||
                return True if data in cls._true_strings else False
 | 
			
		||||
            elif isinstance(data, int):
 | 
			
		||||
                return bool(data)
 | 
			
		||||
            else:
 | 
			
		||||
                return False
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_hkcu_entry(cls, sid, hive_key, dictionary = None):
 | 
			
		||||
    def get_hkcu_entry(cls, hive_key, dictionary = None):
 | 
			
		||||
        return cls.get_hklm_entry(hive_key, dictionary)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -359,85 +397,85 @@ class Dconf_registry():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_shortcut(cls, sid, sc_obj, policy_name):
 | 
			
		||||
    def add_shortcut(cls, sc_obj, policy_name):
 | 
			
		||||
        sc_obj.policy_name = policy_name
 | 
			
		||||
        cls.shortcuts.append(sc_obj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_printer(cls, sid, pobj, policy_name):
 | 
			
		||||
    def add_printer(cls, pobj, policy_name):
 | 
			
		||||
        pobj.policy_name = policy_name
 | 
			
		||||
        cls.printers.append(pobj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_drive(cls, sid, dobj, policy_name):
 | 
			
		||||
    def add_drive(cls, dobj, policy_name):
 | 
			
		||||
        dobj.policy_name = policy_name
 | 
			
		||||
        cls.drives.append(dobj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_folder(cls, sid, fobj, policy_name):
 | 
			
		||||
    def add_folder(cls, fobj, policy_name):
 | 
			
		||||
        fobj.policy_name = policy_name
 | 
			
		||||
        cls.folders.append(fobj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_envvar(self, sid, evobj, policy_name):
 | 
			
		||||
    def add_envvar(self, evobj, policy_name):
 | 
			
		||||
        evobj.policy_name = policy_name
 | 
			
		||||
        self.environmentvariables.append(evobj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_script(cls, sid, scrobj, policy_name):
 | 
			
		||||
    def add_script(cls, scrobj, policy_name):
 | 
			
		||||
        scrobj.policy_name = policy_name
 | 
			
		||||
        cls.scripts.append(scrobj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_file(cls, sid, fileobj, policy_name):
 | 
			
		||||
    def add_file(cls, fileobj, policy_name):
 | 
			
		||||
        fileobj.policy_name = policy_name
 | 
			
		||||
        cls.files.append(fileobj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_ini(cls, sid, iniobj, policy_name):
 | 
			
		||||
    def add_ini(cls, iniobj, policy_name):
 | 
			
		||||
        iniobj.policy_name = policy_name
 | 
			
		||||
        cls.inifiles.append(iniobj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_networkshare(cls, sid, networkshareobj, policy_name):
 | 
			
		||||
    def add_networkshare(cls, networkshareobj, policy_name):
 | 
			
		||||
        networkshareobj.policy_name = policy_name
 | 
			
		||||
        cls.networkshares.append(networkshareobj)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_shortcuts(cls, sid):
 | 
			
		||||
    def get_shortcuts(cls):
 | 
			
		||||
        return cls.shortcuts
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_printers(cls, sid):
 | 
			
		||||
    def get_printers(cls):
 | 
			
		||||
        return cls.printers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_drives(cls, sid):
 | 
			
		||||
    def get_drives(cls):
 | 
			
		||||
        return cls.drives
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_folders(cls, sid):
 | 
			
		||||
    def get_folders(cls):
 | 
			
		||||
        return cls.folders
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_envvars(cls, sid):
 | 
			
		||||
    def get_envvars(cls):
 | 
			
		||||
        return cls.environmentvariables
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_scripts(cls, sid, action):
 | 
			
		||||
    def get_scripts(cls, action):
 | 
			
		||||
        action_scripts = list()
 | 
			
		||||
        for part in cls.scripts:
 | 
			
		||||
            if action == 'LOGON' and part.action == 'LOGON':
 | 
			
		||||
@@ -452,22 +490,22 @@ class Dconf_registry():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_files(cls, sid):
 | 
			
		||||
    def get_files(cls):
 | 
			
		||||
        return cls.files
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_networkshare(cls, sid):
 | 
			
		||||
    def get_networkshare(cls):
 | 
			
		||||
        return cls.networkshares
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_ini(cls, sid):
 | 
			
		||||
    def get_ini(cls):
 | 
			
		||||
        return cls.inifiles
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def wipe_user(cls, sid):
 | 
			
		||||
    def wipe_user(cls):
 | 
			
		||||
        cls.wipe_hklm()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -549,7 +587,17 @@ def add_to_dict(string, username, gpo_info):
 | 
			
		||||
    dictionary['version'] = str(version)
 | 
			
		||||
    dictionary['correct_path'] = string
 | 
			
		||||
 | 
			
		||||
def get_mod_previous_value(key_source, key_valuename):
 | 
			
		||||
    previous_sourc = try_dict_to_literal_eval(Dconf_registry._dconf_db
 | 
			
		||||
            .get(key_source, {})
 | 
			
		||||
            .get(key_valuename, {}))
 | 
			
		||||
    return previous_sourc.get('mod_previous_value') if previous_sourc else None
 | 
			
		||||
 | 
			
		||||
def get_previous_value(key_source, key_valuename):
 | 
			
		||||
    previous = key_source.replace('Source', 'Previous')
 | 
			
		||||
    return (Dconf_registry._dconf_db
 | 
			
		||||
            .get(previous, {})
 | 
			
		||||
            .get(key_valuename, None))
 | 
			
		||||
 | 
			
		||||
def load_preg_dconf(pregfile, pathfile, policy_name, username, gpo_info):
 | 
			
		||||
    '''
 | 
			
		||||
@@ -565,42 +613,71 @@ def load_preg_dconf(pregfile, pathfile, policy_name, username, gpo_info):
 | 
			
		||||
        valuename = convert_string_dconf(i.valuename)
 | 
			
		||||
        data = check_data(i.data, i.type)
 | 
			
		||||
        if i.valuename != i.data and i.valuename:
 | 
			
		||||
            key_registry_source = f"{source_pre}/{i.keyname}".replace('\\', '/')
 | 
			
		||||
            key_registry = f"{i.keyname}".replace('\\', '/')
 | 
			
		||||
            key_valuename = valuename.replace('\\', '/')
 | 
			
		||||
            if i.keyname.replace('\\', '/') in dd:
 | 
			
		||||
                # If the key exists in dd, update its value with the new key-value pair
 | 
			
		||||
                dd[i.keyname.replace('\\', '/')].update({valuename.replace('\\', '/'):data})
 | 
			
		||||
                dd[f"{source_pre}/{i.keyname}".replace('\\', '/')].update({valuename.replace('\\', '/'):RegistryKeyMetadata(policy_name, i.type)})
 | 
			
		||||
                dd[i.keyname.replace('\\', '/')].update({key_valuename:data})
 | 
			
		||||
                mod_previous_value = get_mod_previous_value(key_registry_source, key_valuename)
 | 
			
		||||
                previous_value = get_previous_value(key_registry, key_valuename)
 | 
			
		||||
                if previous_value != data:
 | 
			
		||||
                    (dd[key_registry_source]
 | 
			
		||||
                     .update({key_valuename:RegistryKeyMetadata(policy_name, i.type, mod_previous_value=previous_value)}))
 | 
			
		||||
                else:
 | 
			
		||||
                    (dd[key_registry_source]
 | 
			
		||||
                     .update({key_valuename:RegistryKeyMetadata(policy_name, i.type, mod_previous_value=mod_previous_value)}))
 | 
			
		||||
            else:
 | 
			
		||||
                # If the key does not exist in dd, create a new key-value pair
 | 
			
		||||
                dd[i.keyname.replace('\\', '/')] = {valuename.replace('\\', '/'):data}
 | 
			
		||||
                dd[f"{source_pre}/{i.keyname}".replace('\\', '/')] = {valuename.replace('\\', '/'):RegistryKeyMetadata(policy_name, i.type)}
 | 
			
		||||
                dd[i.keyname.replace('\\', '/')] = {key_valuename:data}
 | 
			
		||||
                mod_previous_value = get_mod_previous_value(key_registry_source, key_valuename)
 | 
			
		||||
                previous_value = get_previous_value(key_registry, key_valuename)
 | 
			
		||||
                if previous_value != data:
 | 
			
		||||
                    dd[key_registry_source] = {key_valuename:RegistryKeyMetadata(policy_name, i.type, mod_previous_value=previous_value)}
 | 
			
		||||
                else:
 | 
			
		||||
                    dd[key_registry_source] = {key_valuename:RegistryKeyMetadata(policy_name, i.type, mod_previous_value=mod_previous_value)}
 | 
			
		||||
 | 
			
		||||
        elif not i.valuename:
 | 
			
		||||
            keyname_tmp = i.keyname.replace('\\', '/').split('/')
 | 
			
		||||
            keyname = '/'.join(keyname_tmp[:-1])
 | 
			
		||||
            mod_previous_value = get_mod_previous_value(f"{source_pre}/{keyname}", keyname_tmp[-1])
 | 
			
		||||
            previous_value = get_previous_value(f"{keyname}", keyname_tmp[-1])
 | 
			
		||||
            if keyname in dd:
 | 
			
		||||
                # If the key exists in dd, update its value with the new key-value pair
 | 
			
		||||
                dd[keyname].update({keyname_tmp[-1]:data})
 | 
			
		||||
                dd[f"{source_pre}{keyname}"].update({keyname_tmp[-1]:RegistryKeyMetadata(policy_name, i.type)})
 | 
			
		||||
                if previous_value != data:
 | 
			
		||||
                    dd[f"{source_pre}/{keyname}"].update({keyname_tmp[-1]:RegistryKeyMetadata(policy_name, i.type, mod_previous_value=previous_value)})
 | 
			
		||||
                else:
 | 
			
		||||
                    dd[f"{source_pre}/{keyname}"].update({keyname_tmp[-1]:RegistryKeyMetadata(policy_name, i.type, mod_previous_value=mod_previous_value)})
 | 
			
		||||
            else:
 | 
			
		||||
                # If the key does not exist in dd, create a new key-value pair
 | 
			
		||||
                dd[keyname] = {keyname_tmp[-1]:data}
 | 
			
		||||
                dd[f"{source_pre}{keyname}"] = {keyname_tmp[-1]:RegistryKeyMetadata(policy_name, i.type)}
 | 
			
		||||
                if previous_value != data:
 | 
			
		||||
                    dd[f"{source_pre}/{keyname}"] = {keyname_tmp[-1]:RegistryKeyMetadata(policy_name, i.type, mod_previous_value=previous_value)}
 | 
			
		||||
                else:
 | 
			
		||||
                    dd[f"{source_pre}/{keyname}"] = {keyname_tmp[-1]:RegistryKeyMetadata(policy_name, i.type, mod_previous_value=mod_previous_value)}
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            # If the value name is the same as the data,
 | 
			
		||||
            # split the keyname and add the data to the appropriate location in dd.
 | 
			
		||||
            all_list_key = i.keyname.split('\\')
 | 
			
		||||
            dd_target = dd.setdefault('/'.join(all_list_key[:-1]),{})
 | 
			
		||||
            key_d ='/'.join(all_list_key[:-1])
 | 
			
		||||
            dd_target_source = dd.setdefault(f"Source/{key_d}",{})
 | 
			
		||||
            dd_target.setdefault(all_list_key[-1], []).append(data)
 | 
			
		||||
            dd_target_source.setdefault(all_list_key[-1], RegistryKeyMetadata(policy_name, i.type, True))
 | 
			
		||||
            dd_target = dd.setdefault(key_d,{})
 | 
			
		||||
            key_source = f"Source/{key_d}"
 | 
			
		||||
            dd_target_source = dd.setdefault(key_source, {})
 | 
			
		||||
            data_list = dd_target.setdefault(all_list_key[-1], []).append(data)
 | 
			
		||||
            mod_previous_value = get_mod_previous_value(key_source, all_list_key[-1])
 | 
			
		||||
            previous_value = get_previous_value(key_d, all_list_key[-1])
 | 
			
		||||
            if previous_value != str(data_list):
 | 
			
		||||
                dd_target_source[all_list_key[-1]] = RegistryKeyMetadata(policy_name, i.type, is_list=True, mod_previous_value=previous_value)
 | 
			
		||||
            else:
 | 
			
		||||
                dd_target_source[all_list_key[-1]] = RegistryKeyMetadata(policy_name, i.type, is_list=True, mod_previous_value=mod_previous_value)
 | 
			
		||||
 | 
			
		||||
    # Update the global registry dictionary with the contents of dd
 | 
			
		||||
    update_dict(Dconf_registry.global_registry_dict, dd)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_dconf_ini_file(filename, data, uid):
 | 
			
		||||
def create_dconf_ini_file(filename, data, uid=None, nodomain=None):
 | 
			
		||||
    '''
 | 
			
		||||
    Create an ini-file based on a dictionary of dictionaries.
 | 
			
		||||
    Args:
 | 
			
		||||
@@ -611,7 +688,7 @@ def create_dconf_ini_file(filename, data, uid):
 | 
			
		||||
    Raises:
 | 
			
		||||
        None
 | 
			
		||||
    '''
 | 
			
		||||
    with open(filename, 'w') as file:
 | 
			
		||||
    with open(filename, 'a' if nodomain else 'w') as file:
 | 
			
		||||
        for section, section_data in data.items():
 | 
			
		||||
            file.write(f'[{section}]\n')
 | 
			
		||||
            for key, value in section_data.items():
 | 
			
		||||
@@ -620,17 +697,58 @@ def create_dconf_ini_file(filename, data, uid):
 | 
			
		||||
                else:
 | 
			
		||||
                    file.write(f'{key} = "{value}"\n')
 | 
			
		||||
            file.write('\n')
 | 
			
		||||
    logdata = dict()
 | 
			
		||||
    logdata['path'] = filename
 | 
			
		||||
    logdata = {'path': filename}
 | 
			
		||||
    log('D209', logdata)
 | 
			
		||||
    create_dconf_file_locks(filename, data)
 | 
			
		||||
    Dconf_registry.dconf_update(uid)
 | 
			
		||||
 | 
			
		||||
def clean_data(data):
 | 
			
		||||
    try:
 | 
			
		||||
        cleaned_string = data.translate(Dconf_registry.trans_table)
 | 
			
		||||
        return cleaned_string
 | 
			
		||||
    except:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
def create_dconf_file_locks(filename_ini, data):
 | 
			
		||||
    """
 | 
			
		||||
    Creates a dconf lock file based on the provided filename and data.
 | 
			
		||||
 | 
			
		||||
    :param filename_ini: Path to the ini file (str)
 | 
			
		||||
    :param data: Dictionary containing configuration data
 | 
			
		||||
    """
 | 
			
		||||
    # Extract the path parts up to the directory of the ini file
 | 
			
		||||
    tmp_lock = filename_ini.split('/')[:-1]
 | 
			
		||||
 | 
			
		||||
    # Construct the path to the lock file
 | 
			
		||||
    file_lock = '/'.join(tmp_lock + ['locks', tmp_lock[-1][:-1] + 'pol'])
 | 
			
		||||
 | 
			
		||||
    # Create an empty lock file
 | 
			
		||||
    touch_file(file_lock)
 | 
			
		||||
 | 
			
		||||
    # Open the lock file for writing
 | 
			
		||||
    with open(file_lock, 'w') as file:
 | 
			
		||||
        # Iterate over all lock keys obtained from the data
 | 
			
		||||
        for key_lock in get_keys_dconf_locks(data):
 | 
			
		||||
            # Remove the "lock/" prefix from the key and split into parts
 | 
			
		||||
            key = key_lock.split('/')[1:]
 | 
			
		||||
            # Write the cleaned key to the lock file
 | 
			
		||||
            file.write(f'{key}\n')
 | 
			
		||||
 | 
			
		||||
def get_keys_dconf_locks(data):
 | 
			
		||||
    """
 | 
			
		||||
    Extracts keys from the provided data that start with "Locks/"
 | 
			
		||||
    and have a value of 1.
 | 
			
		||||
 | 
			
		||||
    :param data: Dictionary containing configuration data
 | 
			
		||||
    :return: List of lock keys (str) without the "Locks/" prefix
 | 
			
		||||
    """
 | 
			
		||||
    result = []
 | 
			
		||||
    # Flatten the nested dictionary into a single-level dictionary
 | 
			
		||||
    flatten_data = flatten_dictionary(data)
 | 
			
		||||
 | 
			
		||||
    # Iterate through all keys in the flattened dictionary
 | 
			
		||||
    for key in flatten_data:
 | 
			
		||||
        # Check if the key starts with "Locks/" and its value is 1
 | 
			
		||||
        if key.startswith('Locks/') and flatten_data[key] == 1:
 | 
			
		||||
            # Remove the "Locks" prefix and append to the result
 | 
			
		||||
            result.append(key.removeprefix('Locks'))
 | 
			
		||||
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_data(data, t_data):
 | 
			
		||||
    if isinstance(data, bytes):
 | 
			
		||||
@@ -723,9 +841,11 @@ def add_preferences_to_global_registry_dict(username, is_machine):
 | 
			
		||||
 | 
			
		||||
    update_dict(Dconf_registry.global_registry_dict, preferences_global_dict)
 | 
			
		||||
 | 
			
		||||
def extract_display_name_version(data):
 | 
			
		||||
def extract_display_name_version(data, username):
 | 
			
		||||
    policy_force = data.get('Software/BaseALT/Policies/GPUpdate', {}).get('Force', False)
 | 
			
		||||
    if Dconf_registry._force or policy_force:
 | 
			
		||||
        logdata = {'username': username}
 | 
			
		||||
        log('W26', logdata)
 | 
			
		||||
        return {}
 | 
			
		||||
    result = {}
 | 
			
		||||
    tmp = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2021 BaseALT Ltd. <org@basealt.ru>
 | 
			
		||||
# Copyright (C) 2021-2025 BaseALT Ltd. <org@basealt.ru>
 | 
			
		||||
# Copyright (C) 2021 Igor Chudov <nir@nir.org.ru>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
@@ -27,21 +27,22 @@ import smbc
 | 
			
		||||
from util.logging import log
 | 
			
		||||
from util.paths import file_cache_dir, file_cache_path_home, UNCPath
 | 
			
		||||
from util.exceptions import NotUNCPathError
 | 
			
		||||
 | 
			
		||||
from util.util import get_machine_name
 | 
			
		||||
 | 
			
		||||
class fs_file_cache:
 | 
			
		||||
    __read_blocksize = 4096
 | 
			
		||||
 | 
			
		||||
    def __init__(self, cache_name, username = None):
 | 
			
		||||
        self.cache_name = cache_name
 | 
			
		||||
        if username:
 | 
			
		||||
        self.username = username
 | 
			
		||||
        if username and username != get_machine_name():
 | 
			
		||||
            try:
 | 
			
		||||
                self.storage_uri = file_cache_path_home(username)
 | 
			
		||||
            except:
 | 
			
		||||
                self.storage_uri = file_cache_dir()
 | 
			
		||||
        else:
 | 
			
		||||
            self.storage_uri = file_cache_dir()
 | 
			
		||||
        logdata = dict({'cache_file': self.storage_uri})
 | 
			
		||||
        logdata = {'cache_file': self.storage_uri}
 | 
			
		||||
        log('D20', logdata)
 | 
			
		||||
        self.samba_context = smbc.Context(use_kerberos=1)
 | 
			
		||||
                #, debug=10)
 | 
			
		||||
@@ -61,7 +62,7 @@ class fs_file_cache:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict({'exception': str(exc)})
 | 
			
		||||
            logdata = {'exception': str(exc)}
 | 
			
		||||
            log('D144', logdata)
 | 
			
		||||
            raise exc
 | 
			
		||||
 | 
			
		||||
@@ -85,7 +86,9 @@ class fs_file_cache:
 | 
			
		||||
            df.close()
 | 
			
		||||
            os.rename(tmpfile, destfile)
 | 
			
		||||
            os.chmod(destfile, 0o644)
 | 
			
		||||
        except:
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exception': str(exc)}
 | 
			
		||||
            log('W25', logdata)
 | 
			
		||||
            tmppath = Path(tmpfile)
 | 
			
		||||
            if tmppath.exists():
 | 
			
		||||
                tmppath.unlink()
 | 
			
		||||
@@ -100,14 +103,16 @@ class fs_file_cache:
 | 
			
		||||
                uri_path.get_domain(),
 | 
			
		||||
                uri_path.get_path()))
 | 
			
		||||
        except NotUNCPathError as exc:
 | 
			
		||||
            logdata = dict({'path': str(exc)})
 | 
			
		||||
            logdata = {'path': str(exc)}
 | 
			
		||||
            log('D62', logdata)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict({'exception': str(exc)})
 | 
			
		||||
            logdata = {'exception': str(exc)}
 | 
			
		||||
            log('E36', logdata)
 | 
			
		||||
            raise exc
 | 
			
		||||
 | 
			
		||||
        return str(destfile)
 | 
			
		||||
        if Path(destfile).exists():
 | 
			
		||||
            return str(destfile)
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_ls_smbdir(self, uri):
 | 
			
		||||
        type_file_smb = 8
 | 
			
		||||
@@ -120,6 +125,6 @@ class fs_file_cache:
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            if Path(uri).exists():
 | 
			
		||||
                return None
 | 
			
		||||
            logdata = dict({'exception': str(exc)})
 | 
			
		||||
            logdata = {'exception': str(exc)}
 | 
			
		||||
            log('W12', logdata)
 | 
			
		||||
            return None
 | 
			
		||||
 
 | 
			
		||||
@@ -16,5 +16,5 @@
 | 
			
		||||
 # You should have received a copy of the GNU General Public License
 | 
			
		||||
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 #}
 | 
			
		||||
{{ home_dir }}/{{mntTarget}}	{{ mount_file }}	-t 120  --browse
 | 
			
		||||
{{ home_dir }}/{{mntTarget}}	{{ mount_file }}	-t {{timeout}}  --browse
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,5 +16,5 @@
 | 
			
		||||
 # You should have received a copy of the GNU General Public License
 | 
			
		||||
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 #}
 | 
			
		||||
{{ home_dir }}/.{{mntTarget}}	{{ mount_file }}	-t 120
 | 
			
		||||
{{ home_dir }}/.{{mntTarget}}	{{ mount_file }}	-t {{timeout}}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,9 @@
 | 
			
		||||
{%- for drv in drives %}
 | 
			
		||||
{% if (drv.thisDrive != 'HIDE') %}
 | 
			
		||||
{% if drv.label %}
 | 
			
		||||
"{{ drv.label }}"	-fstype=cifs,cruid=$USER,sec=krb5,noperm,cifsacl	:{{ drv.path }}
 | 
			
		||||
"{{ drv.label }}" -fstype=cifs,cruid=$USER,sec=krb5,noperm{% if drv.username %}{% else %},multiuser{% endif %}{% if drv.cifsacl %},cifsacl{% endif %} :{{ drv.path }}
 | 
			
		||||
{% else %}
 | 
			
		||||
"{{ drv.dir }}"	-fstype=cifs,cruid=$USER,sec=krb5,noperm,cifsacl	:{{ drv.path }}
 | 
			
		||||
"{{ drv.dir }}" -fstype=cifs,cruid=$USER,sec=krb5,noperm{% if drv.username %}{% else %},multiuser{% endif %}{% if drv.cifsacl %},cifsacl{% endif %} :{{ drv.path }}
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endfor %}
 | 
			
		||||
{% endfor %}
 | 
			
		||||
@@ -19,9 +19,9 @@
 | 
			
		||||
{%- for drv in drives %}
 | 
			
		||||
{% if (drv.thisDrive == 'HIDE') %}
 | 
			
		||||
{% if drv.label %}
 | 
			
		||||
"{{ drv.label }}"	-fstype=cifs,cruid=$USER,sec=krb5,noperm,cifsacl	:{{ drv.path }}
 | 
			
		||||
"{{ drv.label }}" -fstype=cifs,cruid=$USER,sec=krb5,noperm{% if drv.username %}{% else %},multiuser{% endif %}{% if drv.cifsacl %},cifsacl{% endif %} :{{ drv.path }}
 | 
			
		||||
{% else %}
 | 
			
		||||
"{{ drv.dir }}"	-fstype=cifs,cruid=$USER,sec=krb5,noperm,cifsacl	:{{ drv.path }}
 | 
			
		||||
"{{ drv.dir }}" -fstype=cifs,cruid=$USER,sec=krb5,noperm{% if drv.username %}{% else %},multiuser{% endif %}{% if drv.cifsacl %},cifsacl{% endif %} :{{ drv.path }}
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endfor %}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,8 +20,7 @@ import logging
 | 
			
		||||
import logging.handlers
 | 
			
		||||
from enum import IntEnum, Enum
 | 
			
		||||
 | 
			
		||||
from messages import message_with_code
 | 
			
		||||
from .logging import slogm
 | 
			
		||||
from .logging import log
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_loglevel(loglevel_num=None):
 | 
			
		||||
@@ -69,8 +68,8 @@ def process_target(target_name=None):
 | 
			
		||||
    if target_name:
 | 
			
		||||
        target = target_name
 | 
			
		||||
 | 
			
		||||
    logdata = dict({'target': target})
 | 
			
		||||
    logging.debug(slogm(message_with_code('D10'), logdata))
 | 
			
		||||
    logdata = {'target': target}
 | 
			
		||||
    log('D10', logdata)
 | 
			
		||||
 | 
			
		||||
    return target.upper()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -71,7 +71,7 @@ class dbus_runner:
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        if self.username:
 | 
			
		||||
            logdata = dict({'username': self.username})
 | 
			
		||||
            logdata = {'username': self.username}
 | 
			
		||||
            log('D6', logdata)
 | 
			
		||||
            gpupdate = 'gpupdate' if not Dconf_registry._force else 'gpupdate_force'
 | 
			
		||||
            if is_root():
 | 
			
		||||
@@ -88,8 +88,7 @@ class dbus_runner:
 | 
			
		||||
                        timeout=self._synchronous_timeout)
 | 
			
		||||
                    print_dbus_result(result)
 | 
			
		||||
                except dbus.exceptions.DBusException as exc:
 | 
			
		||||
                    logdata = dict()
 | 
			
		||||
                    logdata['username'] = self.username
 | 
			
		||||
                    logdata = {'username': self.username}
 | 
			
		||||
                    log('E23', logdata)
 | 
			
		||||
                    raise exc
 | 
			
		||||
            else:
 | 
			
		||||
@@ -103,7 +102,7 @@ class dbus_runner:
 | 
			
		||||
                        timeout=self._synchronous_timeout)
 | 
			
		||||
                    print_dbus_result(result)
 | 
			
		||||
                except dbus.exceptions.DBusException as exc:
 | 
			
		||||
                    logdata = dict({'error': str(exc)})
 | 
			
		||||
                    logdata = {'error': str(exc)}
 | 
			
		||||
                    log('E21', logdata)
 | 
			
		||||
                    raise exc
 | 
			
		||||
        else:
 | 
			
		||||
@@ -121,7 +120,7 @@ class dbus_runner:
 | 
			
		||||
                    timeout=self._synchronous_timeout)
 | 
			
		||||
                print_dbus_result(result)
 | 
			
		||||
            except dbus.exceptions.DBusException as exc:
 | 
			
		||||
                logdata = dict({'error': str(exc)})
 | 
			
		||||
                logdata = {'error': str(exc)}
 | 
			
		||||
                log('E22', logdata)
 | 
			
		||||
                raise exc
 | 
			
		||||
 | 
			
		||||
@@ -194,7 +193,7 @@ def print_dbus_result(result):
 | 
			
		||||
    '''
 | 
			
		||||
    exitcode = result[0]
 | 
			
		||||
    message = result[1:]
 | 
			
		||||
    logdata = dict({'retcode': exitcode})
 | 
			
		||||
    logdata = {'retcode': exitcode}
 | 
			
		||||
    log('D12', logdata)
 | 
			
		||||
 | 
			
		||||
    for line in message:
 | 
			
		||||
@@ -208,7 +207,7 @@ class dbus_session:
 | 
			
		||||
            self.session_dbus = self.session_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
 | 
			
		||||
            self.session_iface = dbus.Interface(self.session_dbus, 'org.freedesktop.DBus')
 | 
			
		||||
        except dbus.exceptions.DBusException as exc:
 | 
			
		||||
            logdata = dict({'error': str(exc)})
 | 
			
		||||
            logdata = {'error': str(exc)}
 | 
			
		||||
            log('E31', logdata)
 | 
			
		||||
            raise exc
 | 
			
		||||
 | 
			
		||||
@@ -219,7 +218,7 @@ class dbus_session:
 | 
			
		||||
            log('D57', {"pid": pid})
 | 
			
		||||
        except dbus.exceptions.DBusException as exc:
 | 
			
		||||
            if exc.get_dbus_name() != 'org.freedesktop.DBus.Error.NameHasNoOwner':
 | 
			
		||||
                logdata = dict({'error': str(exc)})
 | 
			
		||||
                logdata = {'error': str(exc)}
 | 
			
		||||
                log('E32', logdata)
 | 
			
		||||
                raise exc
 | 
			
		||||
            log('D58', {'connection': connection})
 | 
			
		||||
 
 | 
			
		||||
@@ -27,13 +27,13 @@ def geterr():
 | 
			
		||||
    '''
 | 
			
		||||
    etype, evalue, etrace = sys.exc_info()
 | 
			
		||||
 | 
			
		||||
    traceinfo = dict({
 | 
			
		||||
    traceinfo = {
 | 
			
		||||
          'file': etrace.tb_frame.f_code.co_filename
 | 
			
		||||
        , 'line': etrace.tb_lineno
 | 
			
		||||
        , 'name': etrace.tb_frame.f_code.co_name
 | 
			
		||||
        , 'type': etype.__name__
 | 
			
		||||
        , 'message': evalue
 | 
			
		||||
    })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    del(etype, evalue, etrace)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										68
									
								
								gpoa/util/ipa.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								gpoa/util/ipa.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
import configparser
 | 
			
		||||
import os
 | 
			
		||||
from ipalib import api
 | 
			
		||||
 | 
			
		||||
class ipaopts:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        """Initialize the class and load the FreeIPA config file."""
 | 
			
		||||
        self.config_file = "/etc/ipa/default.conf"
 | 
			
		||||
        self.config = configparser.ConfigParser()
 | 
			
		||||
 | 
			
		||||
        if not os.path.exists(self.config_file):
 | 
			
		||||
            raise FileNotFoundError(f"Config file for Freeipa{self.config_file} not found.")
 | 
			
		||||
 | 
			
		||||
        self.config.read(self.config_file)
 | 
			
		||||
 | 
			
		||||
    def get_realm(self):
 | 
			
		||||
        """Return the Kerberos realm from the config."""
 | 
			
		||||
        try:
 | 
			
		||||
            return self.config.get('global', 'realm')
 | 
			
		||||
        except (configparser.NoSectionError, configparser.NoOptionError):
 | 
			
		||||
            raise ValueError("Realm not found in config file.")
 | 
			
		||||
 | 
			
		||||
    def get_domain(self):
 | 
			
		||||
        """Return the domain from the config."""
 | 
			
		||||
        try:
 | 
			
		||||
            return self.config.get('global', 'domain')
 | 
			
		||||
        except (configparser.NoSectionError, configparser.NoOptionError):
 | 
			
		||||
            raise ValueError("Domain not found in config file.")
 | 
			
		||||
 | 
			
		||||
    def get_server(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return the FreeIPA PDC Emulator server from API.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            result = api.Command.gpmaster_show_pdc()
 | 
			
		||||
            pdc_server = result['result']['pdc_emulator']
 | 
			
		||||
            return pdc_server
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def get_machine_name(self):
 | 
			
		||||
        """Return the host from the config."""
 | 
			
		||||
        try:
 | 
			
		||||
            return self.config.get('global', 'host')
 | 
			
		||||
        except (configparser.NoSectionError, configparser.NoOptionError):
 | 
			
		||||
            raise ValueError("Host not found in config file.")
 | 
			
		||||
 | 
			
		||||
    def get_cache_dir(self):
 | 
			
		||||
        """Return the cache directory path."""
 | 
			
		||||
        return "/var/cache/freeipa/gpo_cache"
 | 
			
		||||
							
								
								
									
										102
									
								
								gpoa/util/ipacreds.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								gpoa/util/ipacreds.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>
 | 
			
		||||
 | 
			
		||||
import smbc
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
from ipalib import api
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from storage.dconf_registry import Dconf_registry, extract_display_name_version
 | 
			
		||||
from util.util import get_uid_by_username
 | 
			
		||||
from .ipa import ipaopts
 | 
			
		||||
from util.logging import log
 | 
			
		||||
 | 
			
		||||
class ipacreds(ipaopts):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.smb_context = smbc.Context(use_kerberos=True)
 | 
			
		||||
        self.gpo_list = []
 | 
			
		||||
 | 
			
		||||
    def update_gpos(self, username):
 | 
			
		||||
        gpos = []
 | 
			
		||||
        try:
 | 
			
		||||
            if not api.isdone('bootstrap'):
 | 
			
		||||
                api.bootstrap(context='cli')
 | 
			
		||||
            if not api.isdone('finalize'):
 | 
			
		||||
                api.finalize()
 | 
			
		||||
            api.Backend.rpcclient.connect()
 | 
			
		||||
            try:
 | 
			
		||||
                server = self.get_server()
 | 
			
		||||
                is_machine = (username == self.get_machine_name())
 | 
			
		||||
                if is_machine:
 | 
			
		||||
                    result = api.Command.chain_resolve_for_host(username)
 | 
			
		||||
                else:
 | 
			
		||||
                    result = api.Command.chain_resolve_for_user(username)
 | 
			
		||||
                policies_list = result["result"]
 | 
			
		||||
                try:
 | 
			
		||||
                    if is_machine:
 | 
			
		||||
                        dconf_dict = Dconf_registry.get_dictionary_from_dconf_file_db(save_dconf_db=True)
 | 
			
		||||
                    else:
 | 
			
		||||
                        uid = get_uid_by_username(username)
 | 
			
		||||
                        dconf_dict = Dconf_registry.get_dictionary_from_dconf_file_db(uid, save_dconf_db=True)
 | 
			
		||||
                    dict_gpo_name_version = extract_display_name_version(dconf_dict, username)
 | 
			
		||||
                except Exception as exc:
 | 
			
		||||
                    logdata = {'exc': str(exc)}
 | 
			
		||||
                    log('D235', logdata)
 | 
			
		||||
                    dict_gpo_name_version = {}
 | 
			
		||||
 | 
			
		||||
                for policy in policies_list:
 | 
			
		||||
                    class SimpleGPO:
 | 
			
		||||
                        def __init__(self, policy_data):
 | 
			
		||||
                            self.display_name = policy_data.get('name', 'Unknown')
 | 
			
		||||
                            self.file_sys_path = policy_data.get('file_system_path', '')
 | 
			
		||||
                            self.version = int(policy_data.get('version', 0))
 | 
			
		||||
                            self.flags = int(policy_data.get('flags', 0))
 | 
			
		||||
                            self.link = policy_data.get('link', 'Unknown')
 | 
			
		||||
                            guid_match = re.search(r'\{[^}]+\}', self.file_sys_path)
 | 
			
		||||
                            self.name = guid_match.group(0) if guid_match else f"policy_{id(self)}"
 | 
			
		||||
 | 
			
		||||
                    gpo = SimpleGPO(policy)
 | 
			
		||||
                    if (gpo.display_name in dict_gpo_name_version.keys() and
 | 
			
		||||
                        dict_gpo_name_version.get(gpo.display_name, {}).get('version') == str(gpo.version)):
 | 
			
		||||
 | 
			
		||||
                        cached_path = dict_gpo_name_version.get(gpo.display_name, {}).get('correct_path')
 | 
			
		||||
                        if cached_path and Path(cached_path).exists():
 | 
			
		||||
                            gpo.file_sys_path = cached_path
 | 
			
		||||
                            ldata = {'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name, 'file_sys_path_cache': True}
 | 
			
		||||
                        else:
 | 
			
		||||
                            ldata = {'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name, 'file_sys_path': gpo.file_sys_path}
 | 
			
		||||
                    else:
 | 
			
		||||
                        ldata = {'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name, 'file_sys_path': gpo.file_sys_path}
 | 
			
		||||
                    gpos.append(gpo)
 | 
			
		||||
            finally:
 | 
			
		||||
                api.Backend.rpcclient.disconnect()
 | 
			
		||||
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = {'exc': str(exc)}
 | 
			
		||||
            log('E78', logdata)
 | 
			
		||||
        return gpos, server
 | 
			
		||||
 | 
			
		||||
    def get_domain(self):
 | 
			
		||||
        return super().get_domain()
 | 
			
		||||
 | 
			
		||||
    def get_server(self):
 | 
			
		||||
        return super().get_server()
 | 
			
		||||
 | 
			
		||||
    def get_cache_dir(self):
 | 
			
		||||
        return super().get_cache_dir()
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -22,20 +22,31 @@ import subprocess
 | 
			
		||||
from .util import get_machine_name
 | 
			
		||||
from .logging import log
 | 
			
		||||
from .samba import smbopts
 | 
			
		||||
from .ipa import ipaopts
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def machine_kinit(cache_name=None):
 | 
			
		||||
def machine_kinit(cache_name=None, backend_type=None):
 | 
			
		||||
    '''
 | 
			
		||||
    Perform kinit with machine credentials
 | 
			
		||||
    '''
 | 
			
		||||
    opts = smbopts()
 | 
			
		||||
    host = get_machine_name()
 | 
			
		||||
    realm = opts.get_realm()
 | 
			
		||||
    with_realm = '{}@{}'.format(host, realm)
 | 
			
		||||
    os.environ['KRB5CCNAME'] = 'FILE:{}'.format(cache_name)
 | 
			
		||||
    kinit_cmd = ['kinit', '-k', with_realm]
 | 
			
		||||
    if backend_type == 'freeipa':
 | 
			
		||||
        keytab_path = '/etc/samba/samba.keytab'
 | 
			
		||||
        opts = ipaopts()
 | 
			
		||||
        host = "cifs/" + opts.get_machine_name()
 | 
			
		||||
        realm = opts.get_realm()
 | 
			
		||||
        with_realm = '{}@{}'.format(host, realm)
 | 
			
		||||
        kinit_cmd = ['kinit', '-kt', keytab_path, with_realm]
 | 
			
		||||
    else:
 | 
			
		||||
        opts = smbopts()
 | 
			
		||||
        host = get_machine_name()
 | 
			
		||||
        realm = opts.get_realm()
 | 
			
		||||
        with_realm = '{}@{}'.format(host, realm)
 | 
			
		||||
        kinit_cmd = ['kinit', '-k', with_realm]
 | 
			
		||||
 | 
			
		||||
    if cache_name:
 | 
			
		||||
        os.environ['KRB5CCNAME'] = 'FILE:{}'.format(cache_name)
 | 
			
		||||
        kinit_cmd.extend(['-c', cache_name])
 | 
			
		||||
 | 
			
		||||
    proc = subprocess.Popen(kinit_cmd)
 | 
			
		||||
    proc.wait()
 | 
			
		||||
 | 
			
		||||
@@ -80,12 +91,10 @@ def check_krb_ticket():
 | 
			
		||||
        subprocess.check_call(['klist', '-s'])
 | 
			
		||||
        output = subprocess.check_output('klist', stderr=subprocess.STDOUT).decode()
 | 
			
		||||
        result = True
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata['output'] = output
 | 
			
		||||
        logdata = {'output': output}
 | 
			
		||||
        log('D17', logdata)
 | 
			
		||||
    except Exception as exc:
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata['krb-exc'] = exc
 | 
			
		||||
        logdata = {'krb-exc': exc}
 | 
			
		||||
        log('E14', logdata)
 | 
			
		||||
 | 
			
		||||
    return result
 | 
			
		||||
 
 | 
			
		||||
@@ -40,15 +40,15 @@ class slogm(object):
 | 
			
		||||
    '''
 | 
			
		||||
    Structured log message class
 | 
			
		||||
    '''
 | 
			
		||||
    def __init__(self, message, kwargs=dict()):
 | 
			
		||||
    def __init__(self, message, kwargs={}):
 | 
			
		||||
        self.message = message
 | 
			
		||||
        self.kwargs = kwargs
 | 
			
		||||
        if not self.kwargs:
 | 
			
		||||
            self.kwargs = dict()
 | 
			
		||||
            self.kwargs = {}
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        now = str(datetime.datetime.now().isoformat(sep=' ', timespec='milliseconds'))
 | 
			
		||||
        args = dict()
 | 
			
		||||
        args = {}
 | 
			
		||||
        args.update(self.kwargs)
 | 
			
		||||
        result = '{}|{}|{}'.format(now, self.message, args)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2021 BaseALT Ltd. <org@basealt.ru>
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd. <org@basealt.ru>
 | 
			
		||||
# Copyright (C) 2019-2021 Igor Chudov <nir@nir.org.ru>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
@@ -93,6 +93,7 @@ def local_policy_cache():
 | 
			
		||||
 | 
			
		||||
    return lpcache
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_dconf_config_path(uid = None):
 | 
			
		||||
    if uid:
 | 
			
		||||
        return f'/etc/dconf/db/policy{uid}.d/'
 | 
			
		||||
@@ -122,7 +123,7 @@ class UNCPath:
 | 
			
		||||
    def get_uri(self):
 | 
			
		||||
        path = self.path
 | 
			
		||||
        if self.type == 'unc':
 | 
			
		||||
            path = self.path.replace('\\', '/')
 | 
			
		||||
            path = self.path.replace('\\\\', '/')
 | 
			
		||||
            path = path.replace('//', 'smb://')
 | 
			
		||||
        else:
 | 
			
		||||
            pass
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,7 +18,6 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from xml.etree import ElementTree
 | 
			
		||||
from storage import registry_factory
 | 
			
		||||
from storage.dconf_registry import load_preg_dconf
 | 
			
		||||
 | 
			
		||||
from samba.gp_parse.gp_pol import GPPolParser
 | 
			
		||||
@@ -40,7 +39,7 @@ def load_xml_preg(xml_path):
 | 
			
		||||
    '''
 | 
			
		||||
    Parse XML/PReg file and return its preg object
 | 
			
		||||
    '''
 | 
			
		||||
    logdata = dict({'polfile': xml_path})
 | 
			
		||||
    logdata = {'polfile': xml_path}
 | 
			
		||||
    log('D36', logdata)
 | 
			
		||||
    gpparser = GPPolParser()
 | 
			
		||||
    xml_root = ElementTree.parse(xml_path).getroot()
 | 
			
		||||
@@ -54,14 +53,14 @@ def load_pol_preg(polfile):
 | 
			
		||||
    '''
 | 
			
		||||
    Parse PReg file and return its preg object
 | 
			
		||||
    '''
 | 
			
		||||
    logdata = dict({'polfile': polfile})
 | 
			
		||||
    logdata = {'polfile': polfile}
 | 
			
		||||
    log('D31', logdata)
 | 
			
		||||
    gpparser = GPPolParser()
 | 
			
		||||
    data = None
 | 
			
		||||
 | 
			
		||||
    with open(polfile, 'rb') as f:
 | 
			
		||||
        data = f.read()
 | 
			
		||||
        logdata = dict({'polfile': polfile, 'length': len(data)})
 | 
			
		||||
        logdata = {'polfile': polfile, 'length': len(data)}
 | 
			
		||||
        log('D33', logdata)
 | 
			
		||||
        gpparser.parse(data)
 | 
			
		||||
 | 
			
		||||
@@ -72,7 +71,7 @@ def load_pol_preg(polfile):
 | 
			
		||||
 | 
			
		||||
def preg_keymap(preg):
 | 
			
		||||
    pregfile = load_preg(preg)
 | 
			
		||||
    keymap = dict()
 | 
			
		||||
    keymap = {}
 | 
			
		||||
 | 
			
		||||
    for entry in pregfile.entries:
 | 
			
		||||
        hive_key = '{}\\{}'.format(entry.keyname, entry.valuename)
 | 
			
		||||
@@ -87,16 +86,9 @@ def merge_polfile(preg, sid=None, reg_name='registry', reg_path=None, policy_nam
 | 
			
		||||
        load_preg_dconf(pregfile, preg, policy_name, None, gpo_info)
 | 
			
		||||
    else:
 | 
			
		||||
        load_preg_dconf(pregfile, preg, policy_name, username, gpo_info)
 | 
			
		||||
    logdata = dict({'pregfile': preg})
 | 
			
		||||
    logdata = {'pregfile': preg}
 | 
			
		||||
    log('D32', logdata)
 | 
			
		||||
    #log dconf
 | 
			
		||||
    return
 | 
			
		||||
    storage = registry_factory(reg_name, reg_path)
 | 
			
		||||
    for entry in pregfile.entries:
 | 
			
		||||
        if not sid:
 | 
			
		||||
            storage.add_hklm_entry(entry, policy_name)
 | 
			
		||||
        else:
 | 
			
		||||
            storage.add_hkcu_entry(entry, sid, policy_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class entry:
 | 
			
		||||
@@ -105,16 +97,12 @@ class entry:
 | 
			
		||||
        self.valuename = e_valuename
 | 
			
		||||
        self.type = e_type
 | 
			
		||||
        self.data = e_data
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata['keyname'] = self.keyname
 | 
			
		||||
        logdata['valuename'] = self.valuename
 | 
			
		||||
        logdata['type'] = self.type
 | 
			
		||||
        logdata['data'] = self.data
 | 
			
		||||
        logdata = {'keyname': self.keyname, 'valuename': self.valuename, 'type': self.type, 'data': self.data}
 | 
			
		||||
        log('D22', logdata)
 | 
			
		||||
 | 
			
		||||
class pentries:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.entries = list()
 | 
			
		||||
        self.entries = []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def preg2entries(preg_obj):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -28,7 +28,7 @@ def get_roles(role_dir):
 | 
			
		||||
    '''
 | 
			
		||||
    Return list of directories in /etc/role named after role plus '.d'
 | 
			
		||||
    '''
 | 
			
		||||
    directories = list()
 | 
			
		||||
    directories = []
 | 
			
		||||
    try:
 | 
			
		||||
        for item in role_dir.iterdir():
 | 
			
		||||
            if item.is_dir():
 | 
			
		||||
@@ -45,7 +45,7 @@ def read_groups(role_file_path):
 | 
			
		||||
    '''
 | 
			
		||||
    Read list of whitespace-separated groups from file
 | 
			
		||||
    '''
 | 
			
		||||
    groups = list()
 | 
			
		||||
    groups = []
 | 
			
		||||
 | 
			
		||||
    with open(role_file_path, 'r') as role_file:
 | 
			
		||||
        lines = role_file.readlines()
 | 
			
		||||
@@ -54,7 +54,7 @@ def read_groups(role_file_path):
 | 
			
		||||
            print(linegroups)
 | 
			
		||||
            groups.extend(linegroups)
 | 
			
		||||
 | 
			
		||||
    return set(groups)
 | 
			
		||||
    return {*groups}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_rolegroups(roledir):
 | 
			
		||||
@@ -63,16 +63,16 @@ def get_rolegroups(roledir):
 | 
			
		||||
    '''
 | 
			
		||||
    roledir_path = pathlib.Path(roledir)
 | 
			
		||||
 | 
			
		||||
    group_files = list()
 | 
			
		||||
    group_files = []
 | 
			
		||||
    for item in roledir_path.iterdir():
 | 
			
		||||
        if item.is_file():
 | 
			
		||||
            group_files.append(item)
 | 
			
		||||
 | 
			
		||||
    groups = list()
 | 
			
		||||
    groups = []
 | 
			
		||||
    for item in group_files:
 | 
			
		||||
        groups.extend(read_groups(item))
 | 
			
		||||
 | 
			
		||||
    return set(groups)
 | 
			
		||||
    return {*groups}
 | 
			
		||||
 | 
			
		||||
def create_role(role_name, privilege_list):
 | 
			
		||||
    '''
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2025 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -116,7 +116,7 @@ def install_rpms(rpm_names):
 | 
			
		||||
    '''
 | 
			
		||||
    Install set of RPMs sequentially
 | 
			
		||||
    '''
 | 
			
		||||
    result = list()
 | 
			
		||||
    result = []
 | 
			
		||||
 | 
			
		||||
    for package in rpm_names:
 | 
			
		||||
        result.append(install_rpm(package))
 | 
			
		||||
@@ -127,7 +127,7 @@ def remove_rpms(rpm_names):
 | 
			
		||||
    '''
 | 
			
		||||
    Remove set of RPMs requentially
 | 
			
		||||
    '''
 | 
			
		||||
    result = list()
 | 
			
		||||
    result = []
 | 
			
		||||
 | 
			
		||||
    for package in rpm_names:
 | 
			
		||||
        result.append(remove_rpm(package))
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2020 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,9 +20,9 @@
 | 
			
		||||
from enum import Enum
 | 
			
		||||
 | 
			
		||||
import pwd
 | 
			
		||||
import logging
 | 
			
		||||
import subprocess
 | 
			
		||||
import pysss_nss_idmap
 | 
			
		||||
from storage.dconf_registry import Dconf_registry
 | 
			
		||||
 | 
			
		||||
from .logging import log
 | 
			
		||||
 | 
			
		||||
@@ -39,10 +39,19 @@ def wbinfo_getsid(domain, user):
 | 
			
		||||
 | 
			
		||||
    # This part works only on DC
 | 
			
		||||
    wbinfo_cmd = ['wbinfo', '-n', username]
 | 
			
		||||
    output = subprocess.check_output(wbinfo_cmd)
 | 
			
		||||
    sid = output.split()[0].decode('utf-8')
 | 
			
		||||
 | 
			
		||||
    return sid
 | 
			
		||||
    try:
 | 
			
		||||
        output = subprocess.check_output(wbinfo_cmd, stderr=subprocess.STDOUT)
 | 
			
		||||
        Dconf_registry.set_info('trust', False)
 | 
			
		||||
        return output.split()[0].decode('utf-8')
 | 
			
		||||
    except:
 | 
			
		||||
        log('W43')
 | 
			
		||||
    try:
 | 
			
		||||
        wbinfo_cmd[-1] = user
 | 
			
		||||
        output = subprocess.check_output(wbinfo_cmd)
 | 
			
		||||
        Dconf_registry.set_info('trust', True)
 | 
			
		||||
    except Exception as exc:
 | 
			
		||||
        raise exc
 | 
			
		||||
    return output.split()[0].decode('utf-8')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_local_sid_prefix():
 | 
			
		||||
@@ -66,10 +75,10 @@ def get_sid(domain, username, is_machine = False):
 | 
			
		||||
    try:
 | 
			
		||||
        sid = wbinfo_getsid(domain, username)
 | 
			
		||||
    except:
 | 
			
		||||
        logdata = dict({'sid': sid})
 | 
			
		||||
        logdata = {'sid': sid}
 | 
			
		||||
        log('E16', logdata)
 | 
			
		||||
 | 
			
		||||
    logdata = dict({'sid': sid})
 | 
			
		||||
    logdata = {'sid': sid}
 | 
			
		||||
    log('D21', logdata)
 | 
			
		||||
 | 
			
		||||
    return sid
 | 
			
		||||
@@ -204,7 +213,7 @@ def is_sid(sid):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
def sid2descr(sid):
 | 
			
		||||
    sids = dict()
 | 
			
		||||
    sids = {}
 | 
			
		||||
    sids['S-1-0']    = 'Null Authority'
 | 
			
		||||
    sids['S-1-0-0']  = 'Nobody'
 | 
			
		||||
    sids['S-1-1']    = 'World Authority'
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ def set_privileges(username, uid, gid, groups, home):
 | 
			
		||||
 | 
			
		||||
    os.chdir(home)
 | 
			
		||||
 | 
			
		||||
    logdata = dict()
 | 
			
		||||
    logdata = {}
 | 
			
		||||
    logdata['uid'] = uid
 | 
			
		||||
    logdata['gid'] = gid
 | 
			
		||||
    logdata['username'] = username
 | 
			
		||||
@@ -123,12 +123,12 @@ def with_privileges(username, func):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    except Exception as exc:
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        logdata['msg'] = str(exc)
 | 
			
		||||
        log('E33', logdata)
 | 
			
		||||
        result = 1;
 | 
			
		||||
    finally:
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        logdata['dbus_pid'] = dbus_pid
 | 
			
		||||
        logdata['dconf_pid'] = dconf_pid
 | 
			
		||||
        log('D56', logdata)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#
 | 
			
		||||
# GPOA - GPO Applier for Linux
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2019-2021 BaseALT Ltd.
 | 
			
		||||
# Copyright (C) 2019-2024 BaseALT Ltd.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -19,8 +19,6 @@
 | 
			
		||||
import os
 | 
			
		||||
import pwd
 | 
			
		||||
 | 
			
		||||
from .logging import log
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_root():
 | 
			
		||||
    '''
 | 
			
		||||
 
 | 
			
		||||
@@ -106,7 +106,7 @@ def get_backends():
 | 
			
		||||
    '''
 | 
			
		||||
    Get the list of backends supported by GPOA
 | 
			
		||||
    '''
 | 
			
		||||
    return ['local', 'samba']
 | 
			
		||||
    return ['local', 'samba', 'freeipa']
 | 
			
		||||
 | 
			
		||||
def get_default_policy_name():
 | 
			
		||||
    '''
 | 
			
		||||
@@ -138,7 +138,7 @@ def get_policy_entries(directory):
 | 
			
		||||
    '''
 | 
			
		||||
    Get list of directories representing "Local Policy" templates.
 | 
			
		||||
    '''
 | 
			
		||||
    filtered_entries = list()
 | 
			
		||||
    filtered_entries = []
 | 
			
		||||
    if os.path.isdir(directory):
 | 
			
		||||
        entries = [os.path.join(directory, entry) for entry in os.listdir(directory)]
 | 
			
		||||
 | 
			
		||||
@@ -162,7 +162,7 @@ def get_policy_variants():
 | 
			
		||||
    system_policies = get_policy_entries(policy_dir)
 | 
			
		||||
    user_policies = get_policy_entries(etc_policy_dir)
 | 
			
		||||
 | 
			
		||||
    general_listing = list()
 | 
			
		||||
    general_listing = []
 | 
			
		||||
    general_listing.extend(system_policies)
 | 
			
		||||
    general_listing.extend(user_policies)
 | 
			
		||||
 | 
			
		||||
@@ -196,3 +196,61 @@ def get_uid_by_username(username):
 | 
			
		||||
        return user_info.pw_uid
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
def add_prefix_to_keys(dictionary: dict, prefix: str='Previous/') -> dict:
 | 
			
		||||
    """
 | 
			
		||||
    Adds a prefix to each key in the dictionary.
 | 
			
		||||
    Args: Input dictionary whose keys need to be modified
 | 
			
		||||
    prefix string to be added to each key. Defaults to 'Previous/'
 | 
			
		||||
    Returns: New dictionary with modified keys having the specified prefix
 | 
			
		||||
    """
 | 
			
		||||
    result = {}
 | 
			
		||||
    for key, value in dictionary.items():
 | 
			
		||||
        new_key = f'{prefix}{key}'
 | 
			
		||||
        if isinstance(value, dict):
 | 
			
		||||
            result[new_key] = {deep_key:clean_data(val) if isinstance(val, str) else val for deep_key, val in value.items()}
 | 
			
		||||
        else:
 | 
			
		||||
            result[new_key] = value
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def remove_keys_with_prefix(dictionary: dict, prefix: tuple=('Previous/', 'Source/')) -> dict:
 | 
			
		||||
    """
 | 
			
		||||
    Removes all keys that start with the specified prefix from the dictionary.
 | 
			
		||||
    By default, removes keys starting with 'Previous/' and 'Source/' prefix.
 | 
			
		||||
    """
 | 
			
		||||
    return {key: value for key, value in dictionary.items() if not key.startswith(prefix)}
 | 
			
		||||
 | 
			
		||||
def remove_prefix_from_keys(dictionary: dict, prefix: str) -> dict:
 | 
			
		||||
    """
 | 
			
		||||
    Removes the specified prefix from the keys of the dictionary.
 | 
			
		||||
    If a key starts with the prefix, it is removed.
 | 
			
		||||
    """
 | 
			
		||||
    return {key[len(prefix):] if key.startswith(prefix) else key: value for key, value in dictionary.items()}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_trans_table():
 | 
			
		||||
    return str.maketrans({
 | 
			
		||||
                    '\n': '',
 | 
			
		||||
                    '\r': '',
 | 
			
		||||
                    '"': "'",
 | 
			
		||||
                    '\\': '\\\\'
 | 
			
		||||
                    })
 | 
			
		||||
 | 
			
		||||
def clean_data(data):
 | 
			
		||||
    try:
 | 
			
		||||
        cleaned_string = data.translate(get_trans_table())
 | 
			
		||||
        return cleaned_string
 | 
			
		||||
    except:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
def check_local_user_exists(username):
 | 
			
		||||
    """
 | 
			
		||||
    Checks if a local user with the given username exists on a Linux system.
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        # Try to get user information from the password database
 | 
			
		||||
        pwd.getpwnam(username)
 | 
			
		||||
        return True
 | 
			
		||||
    except:
 | 
			
		||||
        return False
 | 
			
		||||
 
 | 
			
		||||
@@ -19,13 +19,13 @@
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from samba import getopt as options
 | 
			
		||||
from samba.credentials import Credentials
 | 
			
		||||
from samba import NTSTATUSError
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from samba.gpclass import get_dc_hostname, check_refresh_gpo_list
 | 
			
		||||
except ImportError:
 | 
			
		||||
    from samba.gp.gpclass import get_dc_hostname, check_refresh_gpo_list
 | 
			
		||||
    from samba.gp.gpclass import get_dc_hostname, check_refresh_gpo_list, get_gpo_list
 | 
			
		||||
 | 
			
		||||
from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
 | 
			
		||||
from storage.dconf_registry import Dconf_registry, extract_display_name_version
 | 
			
		||||
@@ -51,10 +51,13 @@ class smbcreds (smbopts):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, dc_fqdn=None):
 | 
			
		||||
        smbopts.__init__(self, 'GPO Applier')
 | 
			
		||||
        self.credopts = options.CredentialsOptions(self.parser)
 | 
			
		||||
        self.creds = self.credopts.get_credentials(self.lp, fallback_machine=True)
 | 
			
		||||
 | 
			
		||||
        self.creds = Credentials()
 | 
			
		||||
        self.creds.guess(self.lp)
 | 
			
		||||
        self.creds.set_machine_account()
 | 
			
		||||
 | 
			
		||||
        self.set_dc(dc_fqdn)
 | 
			
		||||
        self.sDomain =  SiteDomainScanner(self.creds, self.lp, self.selected_dc)
 | 
			
		||||
        self.sDomain = SiteDomainScanner(self.creds, self.lp, self.selected_dc)
 | 
			
		||||
        self.dc_site_servers = self.sDomain.select_site_servers()
 | 
			
		||||
        self.all_servers = self.sDomain.select_all_servers()
 | 
			
		||||
        [self.all_servers.remove(element)
 | 
			
		||||
@@ -73,7 +76,7 @@ class smbcreds (smbopts):
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if dc_fqdn is not None:
 | 
			
		||||
                logdata = dict()
 | 
			
		||||
                logdata = {}
 | 
			
		||||
                logdata['user_dc'] = dc_fqdn
 | 
			
		||||
                log('D38', logdata)
 | 
			
		||||
 | 
			
		||||
@@ -81,7 +84,7 @@ class smbcreds (smbopts):
 | 
			
		||||
            else:
 | 
			
		||||
                self.selected_dc = get_dc_hostname(self.creds, self.lp)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['msg'] = str(exc)
 | 
			
		||||
            log('E10', logdata)
 | 
			
		||||
            raise exc
 | 
			
		||||
@@ -96,7 +99,7 @@ class smbcreds (smbopts):
 | 
			
		||||
            # Look and python/samba/netcmd/domain.py for more examples
 | 
			
		||||
            res = netcmd_get_domain_infos_via_cldap(self.lp, None, self.selected_dc)
 | 
			
		||||
            dns_domainname = res.dns_domain
 | 
			
		||||
            logdata = dict({'domain': dns_domainname})
 | 
			
		||||
            logdata = {'domain': dns_domainname}
 | 
			
		||||
            log('D18', logdata)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            log('E15')
 | 
			
		||||
@@ -109,36 +112,43 @@ class smbcreds (smbopts):
 | 
			
		||||
        Get GPO list for the specified username for the specified DC
 | 
			
		||||
        hostname
 | 
			
		||||
        '''
 | 
			
		||||
        gpos = list()
 | 
			
		||||
        gpos = []
 | 
			
		||||
        if Dconf_registry.get_info('machine_name') == username:
 | 
			
		||||
            dconf_dict = Dconf_registry.get_dictionary_from_dconf_file_db()
 | 
			
		||||
            dconf_dict = Dconf_registry.get_dictionary_from_dconf_file_db(save_dconf_db=True)
 | 
			
		||||
            self.is_machine = True
 | 
			
		||||
        else:
 | 
			
		||||
            dconf_dict = Dconf_registry.get_dictionary_from_dconf_file_db(get_uid_by_username(username))
 | 
			
		||||
        dict_gpo_name_version = extract_display_name_version(dconf_dict)
 | 
			
		||||
            dconf_dict = Dconf_registry.get_dictionary_from_dconf_file_db(get_uid_by_username(username), save_dconf_db=True)
 | 
			
		||||
            self.is_machine = False
 | 
			
		||||
        if not self.is_machine and Dconf_registry.get_info('trust'):
 | 
			
		||||
            # TODO: Always returning an empty list here.
 | 
			
		||||
            # Need to implement fetching policies from the trusted domain.
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        dict_gpo_name_version = extract_display_name_version(dconf_dict, username)
 | 
			
		||||
        try:
 | 
			
		||||
            log('D48')
 | 
			
		||||
            ads = samba.gpo.ADS_STRUCT(self.selected_dc, self.lp, self.creds)
 | 
			
		||||
            if ads.connect():
 | 
			
		||||
                log('D47')
 | 
			
		||||
                gpos = ads.get_gpo_list(username)
 | 
			
		||||
                logdata = dict({'username': username})
 | 
			
		||||
                logdata = {'username': username}
 | 
			
		||||
                log('I1', logdata)
 | 
			
		||||
                for gpo in gpos:
 | 
			
		||||
                    # These setters are taken from libgpo/pygpo.c
 | 
			
		||||
                    # print(gpo.ds_path) # LDAP entry
 | 
			
		||||
                    if gpo.display_name in dict_gpo_name_version.keys() and dict_gpo_name_version.get(gpo.display_name, {}).get('version') == str(gpo.version):
 | 
			
		||||
                    if gpo.display_name in dict_gpo_name_version.keys() and dict_gpo_name_version.get(gpo.display_name, {}).get('version') == str(getattr(gpo, 'version', None)):
 | 
			
		||||
                        if Path(dict_gpo_name_version.get(gpo.display_name, {}).get('correct_path')).exists():
 | 
			
		||||
                            gpo.file_sys_path = ''
 | 
			
		||||
                            ldata = dict({'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name, 'file_sys_path_cache': True})
 | 
			
		||||
                            ldata = {'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name, 'file_sys_path_cache': True}
 | 
			
		||||
                            log('I11', ldata)
 | 
			
		||||
                            continue
 | 
			
		||||
                    ldata = dict({'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name, 'file_sys_path': gpo.file_sys_path})
 | 
			
		||||
                    ldata = {'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name, 'file_sys_path': gpo.file_sys_path}
 | 
			
		||||
                    log('I2', ldata)
 | 
			
		||||
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            if self.selected_dc != self.pdc_emulator_server:
 | 
			
		||||
                raise GetGPOListFail(exc)
 | 
			
		||||
            logdata = dict({'username': username, 'dc': self.selected_dc})
 | 
			
		||||
            logdata = {'username': username, 'dc': self.selected_dc, 'exc': exc}
 | 
			
		||||
            log('E17', logdata)
 | 
			
		||||
 | 
			
		||||
        return gpos
 | 
			
		||||
@@ -163,7 +173,7 @@ class smbcreds (smbopts):
 | 
			
		||||
            gpos = self.get_gpos(username)
 | 
			
		||||
 | 
			
		||||
        while list_selected_dc:
 | 
			
		||||
            logdata = dict()
 | 
			
		||||
            logdata = {}
 | 
			
		||||
            logdata['username'] = username
 | 
			
		||||
            logdata['dc'] = self.selected_dc
 | 
			
		||||
            try:
 | 
			
		||||
@@ -209,6 +219,7 @@ class smbcreds (smbopts):
 | 
			
		||||
class SiteDomainScanner:
 | 
			
		||||
    def __init__(self, smbcreds, lp, dc):
 | 
			
		||||
        self.samdb = SamDB(url='ldap://{}'.format(dc), session_info=system_session(), credentials=smbcreds, lp=lp)
 | 
			
		||||
        Dconf_registry.set_info('samdb', self.samdb)
 | 
			
		||||
        self.pdc_emulator = self._search_pdc_emulator()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
@@ -307,7 +318,7 @@ def expand_windows_var(text, username=None):
 | 
			
		||||
    '''
 | 
			
		||||
    Scan the line for percent-encoded variables and expand them.
 | 
			
		||||
    '''
 | 
			
		||||
    variables = dict()
 | 
			
		||||
    variables = {}
 | 
			
		||||
    variables['HOME'] = '/etc/skel'
 | 
			
		||||
    variables['HOMEPATH'] = '/etc/skel'
 | 
			
		||||
    variables['HOMEDRIVE'] = '/'
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,7 @@ def xdg_get_desktop(username, homedir = None):
 | 
			
		||||
        homedir = get_homedir(username)
 | 
			
		||||
    if not homedir:
 | 
			
		||||
        msgtext = message_with_code('E18')
 | 
			
		||||
        logdata = dict()
 | 
			
		||||
        logdata['username'] = username
 | 
			
		||||
        logdata = {}
 | 
			
		||||
        log('E18', logdata)
 | 
			
		||||
        raise Exception(msgtext)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@
 | 
			
		||||
%add_python3_req_skip gpt.gpo_dconf_mapping
 | 
			
		||||
%add_python3_req_skip gpt.dynamic_attributes
 | 
			
		||||
%add_python3_req_skip messages
 | 
			
		||||
%add_python3_req_skip plugin
 | 
			
		||||
%add_python3_req_skip storage
 | 
			
		||||
%add_python3_req_skip storage.fs_file_cache
 | 
			
		||||
%add_python3_req_skip storage.dconf_registry
 | 
			
		||||
@@ -33,9 +34,11 @@
 | 
			
		||||
%add_python3_req_skip util.windows
 | 
			
		||||
%add_python3_req_skip util.xml
 | 
			
		||||
%add_python3_req_skip util.gpoa_ini_parsing
 | 
			
		||||
%add_python3_req_skip util.ipacreds
 | 
			
		||||
%add_python3_req_skip frontend.appliers.ini_file
 | 
			
		||||
 | 
			
		||||
Name: gpupdate
 | 
			
		||||
Version: 0.11.3
 | 
			
		||||
Version: 0.13.4
 | 
			
		||||
Release: alt1
 | 
			
		||||
 | 
			
		||||
Summary: GPT applier
 | 
			
		||||
@@ -51,13 +54,18 @@ BuildRequires: gettext-tools
 | 
			
		||||
Requires: python3-module-rpm
 | 
			
		||||
Requires: python3-module-dbus
 | 
			
		||||
Requires: python3-module-configobj
 | 
			
		||||
Requires: python3-module-gssapi
 | 
			
		||||
Requires: python3-module-krb5
 | 
			
		||||
Requires: oddjob-%name >= 0.2.3
 | 
			
		||||
Requires: libnss-role >= 0.5.0
 | 
			
		||||
Requires: local-policy >= 0.4.9
 | 
			
		||||
Requires: pam-config >= 1.9.0
 | 
			
		||||
Requires: autofs
 | 
			
		||||
Requires: dconf-profile
 | 
			
		||||
Requires: packagekit
 | 
			
		||||
Requires: dconf
 | 
			
		||||
Requires: libgvdb-gir
 | 
			
		||||
Requires: freeipa-client-samba
 | 
			
		||||
# This is needed by shortcuts_applier
 | 
			
		||||
Requires: desktop-file-utils
 | 
			
		||||
# This is needed for smb file cache support
 | 
			
		||||
@@ -194,6 +202,71 @@ fi
 | 
			
		||||
%exclude %python3_sitelibdir/gpoa/test
 | 
			
		||||
 | 
			
		||||
%changelog
 | 
			
		||||
* Mon Aug 25 2025 Valery Sinelnikov <greh@altlinux.org> 0.13.4-alt1
 | 
			
		||||
- Added:
 | 
			
		||||
  Production-ready modules: CUPS, file management, INI config (default),
 | 
			
		||||
  package management, script modules
 | 
			
		||||
  Fallback SID lookup for trusted domain users
 | 
			
		||||
  Missing log translations (laps: timezone, login)
 | 
			
		||||
  Added ownership handling for files within user home directory
 | 
			
		||||
- Changed:
 | 
			
		||||
  Refactored to use literals ({}, []) instead of constructors
 | 
			
		||||
  Final optimization passes and minor cleanups
 | 
			
		||||
  Updated copyright year
 | 
			
		||||
  Adjusted login time search and messages
 | 
			
		||||
- Fixed:
 | 
			
		||||
  Skipped policy retrieval for trusted users to avoid GPO errors
 | 
			
		||||
  Corrected login time tracking in laps
 | 
			
		||||
  Fixed typos
 | 
			
		||||
  Prevented subprocess errors from printing to console
 | 
			
		||||
  Adjusted call order (save_dconf - start_frontend)
 | 
			
		||||
- Removed:
 | 
			
		||||
  Legacy sid variable propagation and related helpers
 | 
			
		||||
 | 
			
		||||
* Sat Jul 26 2025 Evgeny Sinelnikov <sin@altlinux.org> 0.13.3-alt1
 | 
			
		||||
- Fixed machine account credentials initialization (closes: 55324)
 | 
			
		||||
 | 
			
		||||
* Thu Apr 03 2025 Valery Sinelnikov <greh@altlinux.org> 0.13.2-alt1
 | 
			
		||||
- Fixed: Check directory existence before cleanup to avoid errors(closes:53703)
 | 
			
		||||
 | 
			
		||||
* Fri Mar 14 2025 Valery Sinelnikov <greh@altlinux.org> 0.13.1-alt1
 | 
			
		||||
- Refined registry key handling: LAPS enablement and user presence check
 | 
			
		||||
 | 
			
		||||
* Thu Mar 06 2025 Valery Sinelnikov <greh@altlinux.org> 0.13.0-alt1
 | 
			
		||||
- Implemented Local Administrator Password Solution (LAPS) functionality,
 | 
			
		||||
  including support for Group Policy Object (GPO) keys to
 | 
			
		||||
  configure LAPS settings
 | 
			
		||||
- Added support for disabling cifsacl in autofs mounts (closes:52333)
 | 
			
		||||
- Implemented the ability to merge computer and user GPO shortcuts
 | 
			
		||||
- Added access restrictions to network directories of other users
 | 
			
		||||
- Added cleaning functionality for the autofs configuration catalog
 | 
			
		||||
- Added ability to configure KDE 6 files
 | 
			
		||||
 | 
			
		||||
* Tue Jan 14 2025 Valery Sinelnikov <greh@altlinux.org> 0.12.2-alt1
 | 
			
		||||
- Fixed interpretation of boolean values (closes:52683)
 | 
			
		||||
 | 
			
		||||
* Fri Jan 10 2025 Valery Sinelnikov <greh@altlinux.org> 0.12.1-alt1
 | 
			
		||||
- Fixed checking the path for existence (closes:52597)
 | 
			
		||||
 | 
			
		||||
* Tue Dec 10 2024 Valery Sinelnikov <greh@altlinux.org> 0.12.0-alt1
 | 
			
		||||
- Special thanks to Andrey Belgorodtsev (andrey@net55.su)
 | 
			
		||||
  for valuable pre-release testing and feedback
 | 
			
		||||
- Added applier thunderbird
 | 
			
		||||
- Added environment file cleaning (closes: 51016)
 | 
			
		||||
- Added the ability to set the name of the directory to automount
 | 
			
		||||
- Added the ability to remove the prefix from a sylink
 | 
			
		||||
  to the catalog in automount
 | 
			
		||||
- Added the ability to set the timeout in automount
 | 
			
		||||
- Added messages using the force mode
 | 
			
		||||
- Improved KDE update logic
 | 
			
		||||
- Added preservation of previous keys
 | 
			
		||||
 | 
			
		||||
* Fri Oct 11 2024 Valery Sinelnikov <greh@altlinux.org> 0.11.4-alt1
 | 
			
		||||
- Added skip plugin (closes: 51631)
 | 
			
		||||
- Fixed getting the network path (closes:51606)
 | 
			
		||||
- The _appliers sequence has been changed,
 | 
			
		||||
  package_applier has been moved to the end
 | 
			
		||||
 | 
			
		||||
* Fri Sep 06 2024 Valery Sinelnikov <greh@altlinux.org> 0.11.3-alt1
 | 
			
		||||
- Optimized string cleaning using str.translate()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user