mirror of
https://github.com/altlinux/gpupdate.git
synced 2025-10-19 03:33:38 +03:00
Compare commits
95 Commits
fix_kde
...
0.13.3-alt
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
@@ -62,7 +62,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 +71,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-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
|
||||
@@ -16,18 +16,14 @@
|
||||
# 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 gpt.gpt import get_local_gpt
|
||||
from util.util import (
|
||||
get_machine_name
|
||||
)
|
||||
from util.sid import get_sid
|
||||
import util.preg
|
||||
from util.logging import slogm
|
||||
|
||||
class nodomain_backend(applier_backend):
|
||||
|
||||
|
@@ -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
|
||||
@@ -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):
|
||||
|
@@ -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'
|
||||
|
@@ -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
|
||||
|
@@ -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):
|
||||
@@ -38,6 +37,9 @@ class systemd_unit:
|
||||
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
|
||||
|
@@ -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
|
||||
@@ -39,8 +39,7 @@ class chromium_applier(applier_frontend):
|
||||
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()
|
||||
|
||||
|
@@ -18,6 +18,7 @@
|
||||
|
||||
import jinja2
|
||||
import os
|
||||
import pwd
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import string
|
||||
@@ -26,7 +27,7 @@ from .applier_frontend import (
|
||||
applier_frontend
|
||||
, check_enabled
|
||||
)
|
||||
from util.util import get_homedir
|
||||
from util.util import get_homedir, get_uid_by_username
|
||||
from util.logging import log
|
||||
|
||||
def storage_get_drives(storage, sid):
|
||||
@@ -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.clear_directory_auto_dir()
|
||||
self.applier_cifs = cifs_applier_user(storage, sid, 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,12 +167,25 @@ 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):
|
||||
self.storage = storage
|
||||
@@ -165,14 +193,44 @@ class cifs_applier_user(applier_frontend):
|
||||
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,dict())
|
||||
self.keys_cifs_values_user = self.dict_registry_user.get(name_dir,dict())
|
||||
self.keys_the_preferences_previous_values_user = self.dict_registry_user.get((self.__key_preferences_previous+self.username),dict()).get('Drives', dict())
|
||||
self.keys_the_preferences_values_user = self.dict_registry_user.get((self.__key_preferences+self.username),dict()).get('Drives', dict())
|
||||
|
||||
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
|
||||
|
||||
self.keys_cifs_previous_values_machine = self.dict_registry_machine.get(self.__key_cifs_previous_value,dict())
|
||||
self.keys_cifs_values_machine = self.dict_registry_machine.get(name_dir,dict())
|
||||
self.keys_the_preferences_previous_values = self.dict_registry_machine.get((self.__key_preferences_previous+'Machine'),dict()).get('Drives', dict())
|
||||
self.keys_the_preferences_values = self.dict_registry_machine.get((self.__key_preferences+'Machine'),dict()).get('Drives', dict())
|
||||
self.cifsacl_disable = self.storage.get_entry(self.__cifsacl_key, preg=False)
|
||||
|
||||
self.mntTarget = mntTarget.translate(str.maketrans({" ": r"\ "}))
|
||||
conf_file = '{}.conf'.format(sid)
|
||||
conf_hide_file = '{}_hide.conf'.format(sid)
|
||||
autofs_file = '{}.autofs'.format(sid)
|
||||
@@ -195,10 +253,6 @@ 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)
|
||||
@@ -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)
|
||||
@@ -256,6 +322,8 @@ 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)
|
||||
|
||||
@@ -280,6 +348,8 @@ class cifs_applier_user(applier_frontend):
|
||||
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,111 @@ 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-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
|
||||
@@ -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'
|
||||
|
@@ -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
|
||||
@@ -16,19 +16,15 @@
|
||||
# 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):
|
||||
'''
|
||||
@@ -115,7 +111,7 @@ class cups_applier_user(applier_frontend):
|
||||
self.__module_enabled = check_enabled(
|
||||
self.storage
|
||||
, self.__module_name
|
||||
, self.__module_enabled
|
||||
, self.__module_experimental
|
||||
)
|
||||
|
||||
def user_context_apply(self):
|
||||
|
@@ -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
|
||||
@@ -21,9 +21,8 @@ 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'
|
||||
@@ -34,7 +33,8 @@ class envvar_applier(applier_frontend):
|
||||
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)
|
||||
Envvar.clear_envvar_file()
|
||||
self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
|
||||
|
||||
def apply(self):
|
||||
if self.__module_enabled:
|
||||
@@ -54,12 +54,10 @@ class envvar_applier_user(applier_frontend):
|
||||
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)
|
||||
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 +65,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-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
|
||||
@@ -40,8 +40,7 @@ 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):
|
||||
self.storage = storage
|
||||
@@ -50,8 +49,7 @@ class firefox_applier(applier_frontend):
|
||||
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.firefox_keys = self.storage.filter_hklm_entries(self.__registry_branch)
|
||||
self.policies_gen = dict()
|
||||
self.__module_enabled = check_enabled(
|
||||
self.storage
|
||||
@@ -59,91 +57,16 @@ class firefox_applier(applier_frontend):
|
||||
, 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)
|
||||
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)
|
||||
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()
|
||||
@@ -186,3 +109,62 @@ def dict_item_to_list(dictionary:dict) -> dict:
|
||||
|
||||
def clean_data_firefox(data):
|
||||
return data.replace("'", '\"')
|
||||
|
||||
|
||||
|
||||
def create_dict(firefox_keys, registry_branch, excp=list()):
|
||||
'''
|
||||
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 = 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 = 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]] = list()
|
||||
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 = dict()
|
||||
logdata['Exception'] = exc
|
||||
logdata['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-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
|
||||
@@ -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
|
||||
|
@@ -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
|
||||
@@ -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,6 +73,7 @@ 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
|
||||
@@ -143,10 +145,12 @@ 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['thunderbird'] = thunderbird_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['shortcuts'] = shortcut_applier(self.storage)
|
||||
|
@@ -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,7 +33,7 @@ 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):
|
||||
'''
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# GPOA - GPO Applier for Linux
|
||||
#
|
||||
# Copyright (C) 2019-2022 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
|
||||
@@ -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 (
|
||||
|
@@ -24,6 +24,7 @@ import os
|
||||
import subprocess
|
||||
import re
|
||||
import dbus
|
||||
import shutil
|
||||
|
||||
class kde_applier(applier_frontend):
|
||||
__module_name = 'KdeApplier'
|
||||
@@ -61,6 +62,7 @@ class kde_applier_user(applier_frontend):
|
||||
__module_name = 'KdeApplierUser'
|
||||
__module_experimental = True
|
||||
__module_enabled = False
|
||||
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'
|
||||
@@ -73,10 +75,12 @@ class kde_applier_user(applier_frontend):
|
||||
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.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(self.sid, kde_filter)
|
||||
self.__module_enabled = check_enabled(
|
||||
self.storage,
|
||||
@@ -102,11 +106,42 @@ 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, self.plasma_update.data)
|
||||
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')
|
||||
|
||||
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
|
||||
@@ -117,12 +152,7 @@ def create_dict(kde_settings, all_kde_settings, locks_settings, locks_dict, file
|
||||
if file_name == 'wallpaper':
|
||||
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
|
||||
@@ -134,6 +164,7 @@ def create_dict(kde_settings, all_kde_settings, locks_settings, locks_dict, file
|
||||
|
||||
def apply(all_kde_settings, locks_dict, username = None):
|
||||
logdata = dict()
|
||||
modified_files = set()
|
||||
if username is None:
|
||||
system_path_settings = '/etc/xdg/'
|
||||
system_files = [
|
||||
@@ -159,11 +190,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}'
|
||||
@@ -177,7 +209,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/',
|
||||
@@ -186,7 +218,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,
|
||||
@@ -195,7 +227,9 @@ 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
|
||||
log('W22', logdata)
|
||||
@@ -213,6 +247,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):
|
||||
'''
|
||||
@@ -259,13 +296,15 @@ def apply_for_wallpaper(data, file_cache, username, plasmaupdate):
|
||||
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 not flag:
|
||||
if os.path.isfile(path_to_wallpaper):
|
||||
command = [
|
||||
'kwriteconfig5',
|
||||
f'kwriteconfig{kde_applier_user.kde_version}',
|
||||
'--file', 'plasma-org.kde.plasma.desktop-appletsrc',
|
||||
'--group', 'Containments',
|
||||
'--group', id_desktop,
|
||||
@@ -277,18 +316,12 @@ def apply_for_wallpaper(data, file_cache, username, plasmaupdate):
|
||||
data
|
||||
]
|
||||
try:
|
||||
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env_path)
|
||||
except:
|
||||
logdata['command'] = command
|
||||
log('E68', logdata)
|
||||
if plasmaupdate == 1:
|
||||
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
|
||||
call_dbus_method("wallpaper")
|
||||
else:
|
||||
logdata['file'] = path_to_wallpaper
|
||||
log('W21', logdata)
|
||||
@@ -308,9 +341,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 e:
|
||||
logdata = dict({'error': str(exc)})
|
||||
log('E31', logdata)
|
||||
else:
|
||||
pass
|
||||
|
695
gpoa/frontend/laps_applier.py
Normal file
695
gpoa/frontend/laps_applier.py
Normal file
@@ -0,0 +1,695 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
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 = dict()
|
||||
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_last_login_hours_ago()
|
||||
|
||||
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 = dict()
|
||||
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 = dict()
|
||||
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 = dict()
|
||||
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 = dict()
|
||||
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_last_login_hours_ago(self):
|
||||
"""
|
||||
Get the number of hours since the user's last login.
|
||||
|
||||
Returns:
|
||||
int: Hours since last login, or 0 if error or no login found
|
||||
"""
|
||||
logdata = dict()
|
||||
logdata['target_user'] = self.target_user
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
["last", "-n", "1", self.target_user],
|
||||
env={'LANG':'C'},
|
||||
text=True
|
||||
).split("\n")[0]
|
||||
|
||||
parts = output.split()
|
||||
if len(parts) < 7:
|
||||
return 0
|
||||
|
||||
# Parse login time
|
||||
login_str = f"{parts[4]} {parts[5]} {parts[6]}"
|
||||
last_login_time = datetime.strptime(login_str, "%b %d %H:%M")
|
||||
last_login_time = last_login_time.replace(year=datetime.now().year)
|
||||
|
||||
# Calculate hours difference
|
||||
time_diff = datetime.now() - last_login_time
|
||||
hours_ago = int(time_diff.total_seconds() // 3600)
|
||||
logdata['hours_ago'] = hours_ago
|
||||
log('D224', logdata)
|
||||
return hours_ago
|
||||
except Exception as exc:
|
||||
logdata['exc'] = exc
|
||||
log('W33', logdata)
|
||||
return 0
|
||||
|
||||
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 = dict()
|
||||
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 = dict()
|
||||
logdata['target_use'] = 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 = dict()
|
||||
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 = dict()
|
||||
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')
|
@@ -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,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
|
||||
|
@@ -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
|
||||
@@ -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, sid, username=None, shortcuts_machine=None):
|
||||
'''
|
||||
Query storage for shortcuts' rows for specified SID.
|
||||
'''
|
||||
shortcut_objs = storage.get_shortcuts(sid)
|
||||
shortcuts = list()
|
||||
if username and shortcuts_machine:
|
||||
shortcut_objs += shortcuts_machine
|
||||
|
||||
for sc in shortcut_objs:
|
||||
if username:
|
||||
@@ -135,14 +138,46 @@ 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):
|
||||
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 = list()
|
||||
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.sid, self.username, shortcuts_machine)
|
||||
|
||||
if shortcuts:
|
||||
for sc in shortcuts:
|
||||
|
@@ -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
|
||||
@@ -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,15 +42,14 @@ 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)))
|
||||
self.units.append(systemd_unit(setting.valuename, int(setting.data)))
|
||||
logdata = dict()
|
||||
logdata['unit'] = format(valuename)
|
||||
logdata['unit'] = format(setting.valuename)
|
||||
log('I4', logdata)
|
||||
except Exception as exc:
|
||||
logdata = dict()
|
||||
logdata['unit'] = format(valuename)
|
||||
logdata['unit'] = format(setting.valuename)
|
||||
logdata['exc'] = exc
|
||||
log('I5', logdata)
|
||||
for unit in self.units:
|
||||
|
70
gpoa/frontend/thunderbird_applier.py
Normal file
70
gpoa/frontend/thunderbird_applier.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#
|
||||
# GPOA - GPO Applier for Linux
|
||||
#
|
||||
# Copyright (C) 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
|
||||
# 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, sid, 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 })
|
||||
self.thunderbird_keys = self.storage.filter_hklm_entries(self.__registry_branch)
|
||||
self.policies_gen = dict()
|
||||
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 = dict()
|
||||
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-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
|
||||
@@ -39,8 +39,7 @@ class yandex_browser_applier(applier_frontend):
|
||||
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()
|
||||
|
||||
|
@@ -154,6 +154,7 @@ class gpoa_controller:
|
||||
back.retrieve_and_store()
|
||||
# Start frontend only on successful backend finish
|
||||
self.start_frontend()
|
||||
save_dconf(self.username, self.is_machine, nodomain)
|
||||
except Exception as exc:
|
||||
logdata = dict({'message': str(exc)})
|
||||
# In case we're handling "E3" - it means that
|
||||
@@ -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):
|
||||
'''
|
||||
|
@@ -38,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-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,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
|
||||
|
@@ -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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# GPOA - GPO Applier for Linux
|
||||
#
|
||||
# Copyright (C) 2019-2022 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# GPOA - GPO Applier for Linux
|
||||
#
|
||||
# Copyright (C) 2019-2022 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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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,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
|
||||
|
||||
@@ -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:
|
||||
|
@@ -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
|
||||
@@ -19,9 +19,7 @@
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import subprocess
|
||||
|
||||
from util.util import (
|
||||
runcmd
|
||||
|
@@ -259,6 +259,15 @@ 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 "Не удалось изменить пароль локального пользователя"
|
||||
|
||||
# Error_end
|
||||
|
||||
# Debug
|
||||
@@ -634,11 +643,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 +889,66 @@ 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"
|
||||
|
||||
# Debug_end
|
||||
|
||||
# Warning
|
||||
@@ -959,6 +1028,42 @@ 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"
|
||||
|
||||
# Fatal
|
||||
msgid "Unable to refresh GPO list"
|
||||
msgstr "Невозможно обновить список объектов групповых политик"
|
||||
|
@@ -109,6 +109,9 @@ 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'
|
||||
return error_ids.get(code, 'Unknown error code')
|
||||
|
||||
def debug_code(code):
|
||||
@@ -248,8 +251,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,7 +326,26 @@ 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'
|
||||
|
||||
return debug_ids.get(code, 'Unknown debug code')
|
||||
|
||||
@@ -360,6 +382,18 @@ def warning_code(code):
|
||||
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'
|
||||
|
||||
return warning_ids.get(code, 'Unknown warning code')
|
||||
|
||||
|
@@ -62,9 +62,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):
|
||||
|
@@ -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()
|
||||
|
||||
|
@@ -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
|
||||
@@ -60,6 +65,7 @@ class Dconf_registry():
|
||||
_GpoPriority = 'Software/BaseALT/Policies/GpoPriority'
|
||||
_gpo_name = set()
|
||||
global_registry_dict = dict({_GpoPriority:{}})
|
||||
previous_global_registry_dict = dict()
|
||||
__template_file = '/usr/share/dconf/user_mandatory.template'
|
||||
_policies_path = 'Software/'
|
||||
_policies_win_path = 'SOFTWARE/'
|
||||
@@ -67,6 +73,7 @@ class Dconf_registry():
|
||||
_force = False
|
||||
__dconf_dict_flag = False
|
||||
__dconf_dict = dict()
|
||||
_dconf_db = dict()
|
||||
_dict_gpo_name_version_cache = dict()
|
||||
_username = None
|
||||
_uid = None
|
||||
@@ -88,14 +95,19 @@ class Dconf_registry():
|
||||
printers = list()
|
||||
scripts = list()
|
||||
networkshares = list()
|
||||
trans_table = str.maketrans({
|
||||
'\n': '',
|
||||
'\r': '',
|
||||
'"': "'",
|
||||
'\\': '\\\\'
|
||||
})
|
||||
|
||||
|
||||
_true_strings = {
|
||||
"True",
|
||||
"true",
|
||||
"TRUE",
|
||||
"yes",
|
||||
"Yes",
|
||||
"enabled",
|
||||
"enable",
|
||||
"Enabled",
|
||||
"Enable",
|
||||
'1'
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def set_info(cls, key , data):
|
||||
@@ -189,6 +201,12 @@ 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()
|
||||
@@ -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):
|
||||
def get_dictionary_from_dconf_file_db(self, uid=None, path_bin=None, save_dconf_db=False):
|
||||
logdata = dict()
|
||||
if not uid:
|
||||
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
|
||||
|
||||
|
||||
@@ -330,7 +357,7 @@ class Dconf_registry():
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_entry(cls, path, dictionary = None):
|
||||
def get_entry(cls, path, dictionary = None, preg = True):
|
||||
logdata = dict()
|
||||
result = Dconf_registry.get_storage(dictionary)
|
||||
|
||||
@@ -340,12 +367,23 @@ 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):
|
||||
@@ -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():
|
||||
@@ -623,14 +700,56 @@ def create_dconf_ini_file(filename, data, uid):
|
||||
logdata = dict()
|
||||
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 +842,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 = dict({'username': username})
|
||||
log('W26', logdata)
|
||||
return {}
|
||||
result = {}
|
||||
tmp = {}
|
||||
|
@@ -109,7 +109,7 @@ class fs_file_cache:
|
||||
logdata = dict({'exception': str(exc)})
|
||||
log('E36', logdata)
|
||||
raise exc
|
||||
if destfile.exists():
|
||||
if Path(destfile).exists():
|
||||
return str(destfile)
|
||||
else:
|
||||
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):
|
||||
@@ -70,7 +69,7 @@ def process_target(target_name=None):
|
||||
target = target_name
|
||||
|
||||
logdata = dict({'target': target})
|
||||
logging.debug(slogm(message_with_code('D10'), logdata))
|
||||
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
|
||||
|
@@ -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/'
|
||||
|
@@ -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
|
||||
@@ -89,14 +88,7 @@ def merge_polfile(preg, sid=None, reg_name='registry', reg_path=None, policy_nam
|
||||
load_preg_dconf(pregfile, preg, policy_name, username, gpo_info)
|
||||
logdata = dict({'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:
|
||||
|
@@ -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,7 +20,6 @@
|
||||
from enum import Enum
|
||||
|
||||
import pwd
|
||||
import logging
|
||||
import subprocess
|
||||
import pysss_nss_idmap
|
||||
|
||||
|
@@ -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():
|
||||
'''
|
||||
|
@@ -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,7 +19,7 @@
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from samba import getopt as options
|
||||
from samba.credentials import Credentials
|
||||
from samba import NTSTATUSError
|
||||
|
||||
try:
|
||||
@@ -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)
|
||||
@@ -111,10 +114,10 @@ class smbcreds (smbopts):
|
||||
'''
|
||||
gpos = list()
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
@@ -126,7 +129,7 @@ class smbcreds (smbopts):
|
||||
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})
|
||||
@@ -138,7 +141,7 @@ class smbcreds (smbopts):
|
||||
except Exception as exc:
|
||||
if self.selected_dc != self.pdc_emulator_server:
|
||||
raise GetGPOListFail(exc)
|
||||
logdata = dict({'username': username, 'dc': self.selected_dc})
|
||||
logdata = dict({'username': username, 'dc': self.selected_dc, 'exc': exc})
|
||||
log('E17', logdata)
|
||||
|
||||
return gpos
|
||||
@@ -209,6 +212,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
|
||||
|
@@ -36,7 +36,7 @@
|
||||
%add_python3_req_skip util.gpoa_ini_parsing
|
||||
|
||||
Name: gpupdate
|
||||
Version: 0.11.4
|
||||
Version: 0.13.3
|
||||
Release: alt1
|
||||
|
||||
Summary: GPT applier
|
||||
@@ -52,12 +52,16 @@ 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
|
||||
# This is needed by shortcuts_applier
|
||||
Requires: desktop-file-utils
|
||||
@@ -195,6 +199,44 @@ fi
|
||||
%exclude %python3_sitelibdir/gpoa/test
|
||||
|
||||
%changelog
|
||||
* 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)
|
||||
|
Reference in New Issue
Block a user