1
0
mirror of https://github.com/altlinux/gpupdate.git synced 2025-10-11 07:33:16 +03:00

Compare commits

..

105 Commits

Author SHA1 Message Date
Valery Sinelnikov
285e646986 Fixed a bug due to which gsettings could not cache the specified URI 2021-10-27 11:46:05 +04:00
Valery Sinelnikov
94d039653a Added exception for org.gnome.Vino authentication-methods 2021-10-25 20:03:23 +04:00
Valery Sinelnikov
e6f19a2116 Fixed bug for alternative-port in org.gnome.Vino 2021-10-21 19:16:43 +04:00
Evgeny Sinelnikov
86c240b9df VERSION: Bump version up to 0.9.8... 2021-09-29 08:43:27 +04:00
Evgeny Sinelnikov
dae3cf2c6c 0.9.7-alt1
- Fix regression with kestroy for user credential cache
- Update system-policy-gpupdate PAM-rules to ignore applying group policies
  for local users and system users with uid less than 500
- Add control facilities to rule system-policy-gpupdate rules:
  + gpupdate-group-users
  + gpupdate-localusers
  + gpupdate-system-uids
2021-09-29 08:42:25 +04:00
Evgeny Sinelnikov
4fe7d0a73e system-policy-gpupdate: fix with tested rules and add two special
controls facilities gpupdate-group-users and gpupdate-system-uids.
2021-09-29 08:35:04 +04:00
Evgeny Sinelnikov
54d0c7c2cb util/kerberos.py: fix regression with kestroy for user credential cache
in forked process with droped privileges.
2021-09-29 02:45:03 +04:00
Evgeny Sinelnikov
954a5598fb system-policy-gpupdate: apply group policy to users in users group only 2021-09-27 01:15:00 +04:00
Evgeny Sinelnikov
ba4eb4bf28 Add control gpupdate-localusers facility for group policy applying to local users 2021-09-26 20:01:41 +04:00
Evgeny Sinelnikov
aa10d5bbf9 system-policy-gpupdate: add PAM-rules for ignore applying group policies
for local users and system users with uid less than 500
2021-09-26 19:56:13 +04:00
Evgeny Sinelnikov
f3062668fa VERSION: Bump version up to 0.9.7... 2021-09-20 08:08:20 +04:00
Evgeny Sinelnikov
046079d4c9 0.9.6-alt1
- Add support changed GPO List Processing for '**DelVals.' value name
2021-09-20 07:57:50 +04:00
Evgeny Sinelnikov
414a827eb8 Add support changed GPO List Processing for '**DelVals.' value name 2021-09-20 07:56:04 +04:00
Evgeny Sinelnikov
8ce322d552 VERSION: Bump version up to 0.9.6... 2021-09-20 07:23:13 +04:00
Evgeny Sinelnikov
84d5122319 0.9.5-alt1
- Refix local policy path detection
- gpupdate-setup: revert settings to default when disabled
2021-09-20 03:31:30 +04:00
Evgeny Sinelnikov
436eeb3760 gpupdate-setup: revert settings to default when disabled 2021-09-14 06:56:27 +04:00
Evgeny Sinelnikov
4b9ef4335a Refix local policy path detection 2021-09-14 06:39:13 +04:00
Evgeny Sinelnikov
929f9678ad 0.9.4-alt1
- Add improvement with new local-policy system-policy control
- Fix gpupdate-setup and user service installation regressions
- Set empty local policy and local backend by default
- Fix local policy path detection
2021-09-14 04:00:27 +04:00
Evgeny Sinelnikov
03cada30cf Add gpo file_sys_path to common logs 2021-09-14 03:57:16 +04:00
Evgeny Sinelnikov
8199cac510 Fix local policy path detection 2021-09-14 02:22:34 +04:00
Evgeny Sinelnikov
e050889a07 Set empty local policy and local backend by default 2021-09-14 01:45:15 +04:00
Evgeny Sinelnikov
1bf2bd053d Fix gupdate-user.service installation 2021-09-14 00:58:33 +04:00
Evgeny Sinelnikov
950e132e2a gpupdate-setup: fix local policy setup regression after code moving 2021-09-14 00:46:26 +04:00
Evgeny Sinelnikov
82e255efc9 0.9.3-alt1
- Use NetBIOS name for Kerberos authentification
- Add support actions (create, update, delete, replace) for Shortcuts
- Add support GSettings with locks feature
- Add support file cache for special GSettings policy:
  Software\BaseALT\Policies\GSettings\org.mate.background.picture-filename
  (requires python smbc module with use_kerberos option support)
2021-09-09 18:10:18 +04:00
Evgeny Sinelnikov
011a3fbed3 gpupdate.service: add starting After syslog and network-online targets 2021-09-09 18:10:18 +04:00
Valery Sinelnikov
8eda2fbedb Fixed GSettings blocking errors 2021-09-09 17:50:08 +04:00
Evgeny Sinelnikov
3f3fa5f7d9 Merge pull request #140 from altlinux/file_cache
File cache
2021-09-06 15:18:00 +04:00
Evgeny Sinelnikov
0210f97e0d Fix GSettings locks profile db generating 2021-09-06 15:12:09 +04:00
Evgeny Sinelnikov
7e6dec6b3d Update file cache functionality for GSettings 2021-09-06 11:37:45 +04:00
Evgeny Sinelnikov
5e4ed2f655 Fix pathlib PosixPath type conversion, clean code from unused json import 2021-09-06 06:38:23 +04:00
Evgeny Sinelnikov
721ba96559 Add exception handling for not UNC path case in picture_fetch() 2021-09-06 05:29:24 +04:00
Evgeny Sinelnikov
c83568cc70 Fix bad raise Exception during UNCPath construction 2021-09-06 00:44:05 +04:00
Evgeny Sinelnikov
15f99e0171 Fix not unusefull logging 2021-09-03 05:12:54 +04:00
Evgeny Sinelnikov
f84af7e0e8 Fix gpupdate_file_cache directory name in gpupdate.spec 2021-09-03 03:47:34 +04:00
7bd1131d5d fs_file_cache() object moved to gsettings_applier.py 2021-09-02 18:12:09 +04:00
5fb8e6ff74 Create file cache directory on install 2021-09-02 17:49:21 +04:00
ce2797e5f1 File cache introduced for frontend
Introduced file cache for GSettings, specifically for wallpapers.
It is needed to work with wallpapers on remote resources.
2021-09-02 17:49:20 +04:00
6d9417fb94 storage.fs_file_cache.fs_file_cache
File caching mechanism utilizing /var/cache/gpupdate_file_cache
directory to cache files from remote locations, specifically, to
cache files specified as UNC paths.
2021-09-02 17:49:20 +04:00
301a77e90a util.paths: UNCPath primitive introduced
It is needed to allow parsing and conversion of UNC paths
2021-09-02 17:47:15 +04:00
Evgeny Sinelnikov
274d9d8555 Merge pull request #146 from altlinux/gsettings_locks
Add support GSettings locks with local profile
2021-09-02 17:43:55 +04:00
Evgeny Sinelnikov
04f5f98681 Merge pull request #145 from altlinux/realm_from_smb_conf
Use default realm taken from smb.conf, not krb5.conf
2021-09-02 17:43:32 +04:00
Evgeny Sinelnikov
9638e5fabb Merge pull request #141 from altlinux/netbios_name
Use NetBIOS name for Kerberos authentification
2021-09-02 17:43:12 +04:00
Evgeny Sinelnikov
393fd25cdb Merge pull request #139 from altlinux/gpo_initial
Fix startup user and computer gpoa initialization
2021-09-02 17:42:59 +04:00
Valery Sinelnikov
23f862f9a5 Add support GSettings locks with local profile 2021-08-20 16:48:43 +04:00
a85fed7cff Use default realm taken from smb.conf, not krb5.conf 2021-08-18 15:52:32 +04:00
bbcb98bb94 Use NetBIOS name for Kerberos authentification 2021-08-16 14:49:52 +04:00
Evgeny Sinelnikov
57f4f0678a Fix startup user and computer gpoa initialization 2021-08-06 09:21:38 +04:00
Evgeny Sinelnikov
306b8db34a Merge pull request #138 from altlinux/shortcuts_actions
Shortcuts actions
2021-08-02 12:22:47 +04:00
Evgeny Sinelnikov
7c8f9892b5 Fix Shortcuts update logic with reading firstly 2021-07-29 00:02:04 +04:00
Evgeny Sinelnikov
4c6a099529 Add support actions (create, update, delete, replace) for Shortcuts 2021-07-28 23:50:36 +04:00
Evgeny Sinelnikov
e6c563e540 0.9.2-alt1
- Fix Shortcuts applier double running in user context
- Add LogonUser variable to expand_windows_var() function
- Add support of path variable expansion to Shortcuts applier
2021-07-28 21:47:38 +04:00
Evgeny Sinelnikov
d67d472b1c Merge pull request #137 from altlinux/user_shortcuts
User shortcuts (fix usercontext and support of path expansion)
2021-07-27 20:54:41 +04:00
Evgeny Sinelnikov
ac8aba2212 Add support of path variable expansion to Shortcuts applier 2021-07-27 07:44:39 +04:00
Evgeny Sinelnikov
39241bc625 util.windows: add LogonUser variable to expand_windows_var() function. 2021-07-27 07:42:28 +04:00
Evgeny Sinelnikov
9206a0b732 Fix Shortcuts applier double running in user context. 2021-07-27 06:49:57 +04:00
Evgeny Sinelnikov
cdb7306d65 0.9.1-alt1
- Fix GSettings applier user part support
- Add support additional firefox appliers
- Add new windows policies mapping capability feature ruled by:
  Software\BaseALT\Policies\GPUpdate\WindowsPoliciesMapping
- Improve drop privileges mechanism with fork and dbus session
2021-07-18 20:33:36 +04:00
Evgeny Sinelnikov
e0ac5f98ac Revert "Added keys sleep-computer-ac and sleep-display-ac"
This reverts commit 389bad4382.

Revert untested Windows mapping.
2021-07-18 20:31:55 +04:00
Evgeny Sinelnikov
96db0a2200 Merge pull request #136 from altlinux/windows_mapping
Windows mapping feature and dbus fixes
2021-07-18 01:05:28 +04:00
Evgeny Sinelnikov
f3e4d463b9 Add new Software\BaseALT\Policies\GPUpdate\WindowsPoliciesMapping feature 2021-07-18 00:59:41 +04:00
Evgeny Sinelnikov
0fded79484 Fix debug info for GSetting applying 2021-07-18 00:58:16 +04:00
Evgeny Sinelnikov
a6e8f0b352 Avoid error in forked process with dconf-service killing 2021-07-18 00:56:13 +04:00
Evgeny Sinelnikov
c8542fa477 Avoid multiple debug info from appliers/gsettings.py 2021-07-18 00:53:10 +04:00
Evgeny Sinelnikov
31183afa60 Merge pull request #135 from altlinux/dbus_session_kill
Dbus session kill
2021-07-17 22:17:53 +04:00
Evgeny Sinelnikov
17cd27b73e Fix dbus session daemon and dconf service killing in user context 2021-07-17 03:17:37 +04:00
Evgeny Sinelnikov
cf82fae5ec Replace with_privileges function to separate system module 2021-07-17 02:31:30 +04:00
Evgeny Sinelnikov
00abee6f7c Fix Gio.Settings synchronization step due dbus races 2021-07-17 02:25:15 +04:00
Evgeny Sinelnikov
760585f3fb Merge pull request #134 from altlinux/drop_privs_for_gsettings
Run user-mode functions in correct environment
2021-07-16 22:47:58 +04:00
Evgeny Sinelnikov
28b4cd7d11 Merge pull request #133 from alenka26/mapping
Added keys sleep-computer-ac and sleep-display-ac
2021-07-16 22:47:29 +04:00
Evgeny Sinelnikov
14153c6272 Remove uneeded hacks with DBUS_SESSION_BUS_ADDRESS 2021-07-16 21:45:42 +04:00
Evgeny Sinelnikov
b2801eec07 Improve drop privileges mechanism 2021-07-16 21:45:41 +04:00
Evgeny Sinelnikov
ae414993e7 Fix GSettings applier user part initialization 2021-07-16 21:45:40 +04:00
Evgeny Sinelnikov
26e5126312 Replace user context applying to single cycle 2021-07-16 21:45:19 +04:00
Evgeny Sinelnikov
45a5df32c3 Merge pull request #132 from altlinux/firefox_applier_fix
Add support additional firefox appliers
2021-07-16 21:39:52 +04:00
c842ce0e07 Run D-Bus session daemon 2021-07-12 20:25:22 +04:00
Evgeny Sinelnikov
2327952896 Add fork for drop privileges 2021-07-12 20:20:14 +04:00
Evgeny Sinelnikov
ee656f52f6 Fix user part of GSettings applier 2021-07-12 20:16:18 +04:00
Alenka Glukhovskaya
389bad4382 Added keys sleep-computer-ac and sleep-display-ac 2021-07-10 10:30:13 +04:00
Evgeny Sinelnikov
eca3fb43c6 Add support additional firefox appliers 2021-07-06 07:03:27 +04:00
Evgeny Sinelnikov
8367fcba99 0.9.0-alt1
- Change policies.json location for Firefox
- Set GSettings, Chromium and Firefox appliers
  not experimental and enabled by default
2021-07-05 22:35:32 +04:00
Evgeny Sinelnikov
0480e88e69 Set GSettingsUser, Chromium and Firefox appliers not experimental and enabled by default 2021-07-04 05:32:13 +04:00
110aee3970 Change policies.json location for Firefox
Mozilla Firefox version 78 changed its default `policies.json` file
location to `/etc/firefox/policies/policies.json`. Previous location
does not work despite changelog ensuring it will continue working.
2021-06-28 15:40:07 +04:00
Evgeny Sinelnikov
f0f3152d86 GSettings applier: final and enable by default system part applied to machine 2021-06-28 15:39:42 +04:00
NIR
4bd03585db Merge pull request #122 from altlinux/Envvars_applier
environment variables applier
2021-03-18 13:53:44 +04:00
NIR
e1a30a1436 Merge branch 'master' into Envvars_applier 2021-03-18 13:53:16 +04:00
NIR
4dbb290f73 Merge pull request #115 from altlinux/default_policy_detector
Automatically detect local policy name
2021-03-18 13:50:38 +04:00
NIR
7a588e9b68 Merge pull request #127 from altlinux/case_insensitive_keys
Make registry keys case-insensitive like in Windows registry
2021-03-18 13:49:54 +04:00
Evgeny Sinelnikov
c367b04f55 Merge pull request #123 from altlinux/Handle_kinit_error
Catch exception in case kinit is not successful
2021-02-24 20:16:58 +04:00
eaee242639 Make registry keys case-insensitive like in Windows registry 2021-02-20 11:41:56 +04:00
8009467a87 0.8.2-alt1
- Increased D-Bus timeouts on calls
- Minor logging fixes
2020-12-22 19:01:11 +04:00
NIR
22c3a9f06e Merge pull request #125 from altlinux/logging_bugfixes
Logging bugfixes
2020-12-22 18:20:47 +04:00
NIR
b213c83854 Merge pull request #124 from altlinux/dbus_runner_timeouts
Increase D-Bus timeout
2020-12-22 18:19:34 +04:00
9480f41469 Fix for gsettings_applier logging 2020-12-22 18:18:32 +04:00
a984e896a5 Re-raise exceptions from applier 2020-12-22 18:14:08 +04:00
702aead8b5 Fix for envvars parser 2020-12-22 18:13:12 +04:00
01de884037 Missing comma added 2020-12-21 20:05:48 +04:00
fba30c9b0e Increase D-Bus timeout
`python-dbus` `Interface` and `Object` primitives are unable to provide
methods allowing to set synchronous blocking call timeout. The problem
is that default D-Bus timeout is only 25 seconds while application of
Group Policy Objects takes 2-5 minutes. It was decided to set blocking
call timeout to 10 minutes using `call_blocking` method of `Connection`
primitive to solve the problem.
2020-12-07 20:15:20 +04:00
Rustem Bapin
2d92b5cb6e Environment variables: optimization for actions update and replace 2020-10-23 19:14:50 +04:00
Rustem Bapin
02632b1c88 replacing slashes in value only if value contains variables
using split instead startswith
2020-10-21 22:13:18 +04:00
Rustem Bapin
d781a257e9 added translation for debug message about environment variables 2020-10-21 21:54:14 +04:00
Rustem Bapin
4b80dc13cf fix debug code for adding environment variables into storage 2020-10-21 21:43:48 +04:00
Rustem Bapin
e45cd1fd18 refactor, add update and replace actions 2020-10-21 20:36:32 +04:00
Rustem Bapin
ca01b20464 Catch exception in case kinit is not successful 2020-10-16 22:16:02 +04:00
Rustem Bapin
590fd8c464 environment variables applier 2020-10-16 22:08:46 +04:00
Evgeny Sinelnikov
d967c0786d 0.8.1-alt3
- Fixed compatibility upgrade trigger condition
2020-10-07 06:33:05 +04:00
536d989497 Automatically detect local policy name 2020-09-11 14:26:25 +04:00
38 changed files with 1365 additions and 251 deletions

19
dist/gpupdate-group-users vendored Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/sh
. /etc/control.d/functions
CONFIG=/etc/pam.d/system-policy-gpupdate
new_subst disabled \
'^[[:space:]]*session[[:space:]]+\[.*default=1.*\][[:space:]]+pam_succeed_if.so user ingroup users.*' \
's,^\([[:space:]]*session[[:space:]]\+\[.*\)default=[[:alnum:]]\+\(.*pam_succeed_if.so user ingroup users.*\)$,\1default=1\2,'
new_subst enabled \
'^[[:space:]]*session[[:space:]]+\[.*default=ignore.*\][[:space:]]+pam_succeed_if.so user ingroup users.*' \
's,^\([[:space:]]*session[[:space:]]\+\[.*\)default=[[:alnum:]]\+\(.*pam_succeed_if.so user ingroup users.*\)$,\1default=ignore\2,'
new_help disabled "Disable group policy applying for users in 'users' group only"
new_help enabled "Enable group policy applying for users in 'users' group only"
new_summary "Group policy applying for users in 'users' group only"
control_subst "$CONFIG" "$*"

19
dist/gpupdate-localusers vendored Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/sh
. /etc/control.d/functions
CONFIG=/etc/pam.d/system-policy-gpupdate
new_subst disabled \
'^[[:space:]]*session[[:space:]]+\[.*success=2.*\][[:space:]]+pam_localuser.so' \
's,^\([[:space:]]*session[[:space:]]\+\[.*\)success=[[:alnum:]]\+\(.*pam_localuser.so.*\)$,\1success=2\2,'
new_subst enabled \
'^[[:space:]]*session[[:space:]]+\[.*success=1.*\][[:space:]]+pam_localuser.so' \
's,^\([[:space:]]*session[[:space:]]\+\[.*\)success=[[:alnum:]]\+\(.*pam_localuser.so.*\)$,\1success=1\2,'
new_help disabled 'Disable group policy applying for local users'
new_help enabled 'Enable group policy applying for local users'
new_summary 'Group policy applying for local users'
control_subst "$CONFIG" "$*"

19
dist/gpupdate-system-uids vendored Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/sh
. /etc/control.d/functions
CONFIG=/etc/pam.d/system-policy-gpupdate
new_subst disabled \
'^[[:space:]]*session[[:space:]]+\[.*default=1.*\][[:space:]]+pam_succeed_if.so uid >= 500.*' \
's,^\([[:space:]]*session[[:space:]]\+\[.*\)default=[[:alnum:]]\+\(.*pam_succeed_if.so uid >= 500.*\)$,\1default=1\2,'
new_subst enabled \
'^[[:space:]]*session[[:space:]]+\[.*default=ignore.*\][[:space:]]+pam_succeed_if.so uid >= 500.*' \
's,^\([[:space:]]*session[[:space:]]\+\[.*\)default=[[:alnum:]]\+\(.*pam_succeed_if.so uid >= 500.*\)$,\1default=ignore\2,'
new_help disabled "Disable group policy applying for users with not system uids only"
new_help enabled "Enable group policy applying for users with not system uids only"
new_summary "Group policy applying for users with not system uids (greater or equal 500) only"
control_subst "$CONFIG" "$*"

4
dist/gpupdate.ini vendored
View File

@@ -1,4 +1,4 @@
[gpoa]
backend = samba
local-policy = auto
backend = local
local-policy = default

View File

@@ -1,6 +1,6 @@
[Unit]
Description=Group policy update for machine
After=sssd.service
After=syslog.target network-online.target sssd.service
[Service]
Environment="PATH=/bin:/sbin:/usr/bin:/usr/sbin"

View File

@@ -1,4 +1,12 @@
#%PAM-1.0
session [success=2 perm_denied=ignore default=die] pam_localuser.so
session required pam_mkhomedir.so silent
session [default=1] pam_permit.so
session [default=6] pam_permit.so
session [success=1 default=ignore] pam_succeed_if.so user ingroup users quiet
session [default=4] pam_permit.so
session [success=1 default=ignore] pam_succeed_if.so uid >= 500 quiet
session [default=2] pam_permit.so
-session required pam_oddjob_gpupdate.so
session optional pam_env.so user_readenv=1 conffile=/etc/gpupdate/environment user_envfile=.gpupdate_environment
session required pam_permit.so

View File

@@ -41,7 +41,7 @@ def backend_factory(dc, username, is_machine, no_domain = False):
log('D52', ld)
sc = smbcreds(dc)
domain = sc.get_domain()
ldata = dict({'domain': domain})
ldata = dict({'domain': domain, "username": username, 'is_machine': is_machine})
log('D9', ldata)
try:
back = samba_backend(sc, username, domain, is_machine)

View File

@@ -40,6 +40,8 @@ class samba_backend(applier_backend):
def __init__(self, sambacreds, username, domain, is_machine):
self.cache_path = '/var/cache/gpupdate/creds/krb5cc_{}'.format(os.getpid())
self.__kinit_successful = machine_kinit(self.cache_path)
if not self.__kinit_successful:
raise Exception('kinit is not successful')
self.storage = registry_factory('registry')
self.storage.set_info('domain', domain)
machine_name = get_machine_name()
@@ -124,7 +126,7 @@ class samba_backend(applier_backend):
def _get_gpts(self, username, sid):
gpts = list()
log('D45')
log('D45', {'username': username, 'sid': sid})
# util.windows.smbcreds
gpos = self.sambacreds.update_gpos(username)
log('D46')

View File

@@ -32,6 +32,17 @@ def check_experimental_enabled(storage):
return result
def check_windows_mapping_enabled(storage):
windows_mapping_enable_flag = 'Software\\BaseALT\\Policies\\GPUpdate\\WindowsPoliciesMapping'
flag = storage.get_hklm_entry(windows_mapping_enable_flag)
result = True
if flag and '0' == flag.data:
result = False
return result
def check_module_enabled(storage, module_name):
gpupdate_module_enable_branch = 'Software\\BaseALT\\Policies\\GPUpdate'
gpupdate_module_flag = '{}\\{}'.format(gpupdate_module_enable_branch, module_name)

View File

@@ -0,0 +1,118 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from os.path import isfile
from util.logging import slogm
import logging
from gpt.envvars import (
FileAction
, action_letter2enum
)
from util.windows import expand_windows_var
from util.util import (
get_homedir,
homedir_exists
)
class Envvar:
def __init__(self, envvars, username=''):
self.username = username
self.envvars = envvars
if self.username == 'root':
self.envvar_file_path = '/etc/gpupdate/environment'
else:
self.envvar_file_path = get_homedir(self.username) + '/.gpupdate_environment'
def _open_envvar_file(self):
fd = None
if isfile(self.envvar_file_path):
fd = open(self.envvar_file_path, 'r+')
else:
fd = open(self.envvar_file_path, 'w')
fd.close()
fd = open(self.envvar_file_path, 'r+')
return fd
def _create_action(self, create_dict, envvar_file):
lines_old = envvar_file.readlines()
lines_new = list()
for name in create_dict:
exist = False
for line in lines_old:
if line.startswith(name + '='):
exist = True
break
if not exist:
lines_new.append(name + '=' + create_dict[name] + '\n')
if len(lines_new) > 0:
envvar_file.writelines(lines_new)
def _delete_action(self, delete_dict, envvar_file):
lines = envvar_file.readlines()
deleted = False
for name in delete_dict:
for line in lines:
if line.startswith(name + '='):
lines.remove(line)
deleted = True
break
if deleted:
envvar_file.writelines(lines)
def act(self):
if isfile(self.envvar_file_path):
with open(self.envvar_file_path, 'r') as f:
lines = f.readlines()
else:
lines = list()
file_changed = False
for envvar_object in self.envvars:
action = action_letter2enum(envvar_object.action)
name = envvar_object.name
value = expand_windows_var(envvar_object.value, self.username)
if value != envvar_object.value:
#slashes are replaced only if the change of variables was performed and we consider the variable as a path to a file or directory
value = value.replace('\\', '/')
exist_line = None
for line in lines:
if line.split()[0] == name:
exist_line = line
break
if exist_line != None:
if action == FileAction.CREATE:
pass
if action == FileAction.DELETE:
lines.remove(exist_line)
file_changed = True
if action == FileAction.UPDATE or action == FileAction.REPLACE:
if exist_line.split()[1].split('=')[1].replace('"', '') != value: #from 'NAME DEFAULT=value' cut value and compare, don`t change if it matches
lines.remove(exist_line)
lines.append(name + ' ' + 'DEFAULT=\"' + value + '\"\n')
file_changed = True
else:
if action == FileAction.CREATE or action == FileAction.UPDATE or action == FileAction.REPLACE:
lines.append(name + ' ' + 'DEFAULT=\"' + value + '\"\n')
file_changed = True
if action == FileAction.DELETE:
pass
if file_changed:
with open(self.envvar_file_path, 'w') as f:
f.writelines(lines)

View File

@@ -1,7 +1,7 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
# Copyright (C) 2019-2021 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,64 +24,112 @@ from gi.repository import Gio, GLib
from util.logging import slogm
class system_gsetting:
__global_schema = '/usr/share/glib-2.0/schemas'
def __init__(self, schema, path, value, override_priority='0'):
def __init__(self, schema, path, value, lock, helper_function=None):
self.schema = schema
self.path = path
self.value = value
self.override_priority = override_priority
self.filename = '{}_policy.gschema.override'.format(self.override_priority)
self.file_path = os.path.join(self.__global_schema, self.filename)
self.lock = lock
self.helper_function = helper_function
def apply(self, settings, config, locks):
try:
config.add_section(self.schema)
except configparser.DuplicateSectionError:
pass
value = self.value
if self.helper_function:
value = self.helper_function(self.schema, self.path, value)
result = glib_value(self.schema, self.path, value, settings)
config.set(self.schema, self.path, str(result))
if self.lock:
lock_path = dconf_path(settings, self.path)
locks.append(lock_path)
class system_gsettings:
__path_local_dir = '/etc/dconf/db/local.d'
__path_locks = '/etc/dconf/db/policy.d/locks/policy'
__path_profile = '/etc/dconf/profile/user'
__profile_data = 'user-db:user\nsystem-db:policy\nsystem-db:local\n'
def __init__(self, override_file_path):
self.gsettings = list()
self.locks = list()
self.override_file_path = override_file_path
def append(self, schema, path, data, lock, helper):
self.gsettings.append(system_gsetting(schema, path, data, lock, helper))
def apply(self):
config = configparser.ConfigParser()
try:
config.read(self.file_path)
except Exception as exc:
logging.error(slogm(exc))
config.add_section(self.schema)
config.set(self.schema, self.path, self.value)
with open(self.file_path, 'w') as f:
for gsetting in self.gsettings:
settings = Gio.Settings(schema=gsetting.schema)
logging.debug(slogm('Applying machine setting {}.{} to {} {}'.format(gsetting.schema,
gsetting.path,
gsetting.value,
gsetting.value,
'locked' if gsetting.lock else 'unlocked')))
gsetting.apply(settings, config, self.locks)
with open(self.override_file_path, 'w') as f:
config.write(f)
os.makedirs(self.__path_local_dir, mode=0o755, exist_ok=True)
os.makedirs(os.path.dirname(self.__path_locks), mode=0o755, exist_ok=True)
os.makedirs(os.path.dirname(self.__path_profile), mode=0o755, exist_ok=True)
try:
os.remove(self.__path_locks)
except OSError as error:
pass
file_locks = open(self.__path_locks,'w')
for lock in self.locks:
file_locks.write(lock +'\n')
file_locks.close()
profile = open(self.__path_profile ,'w')
profile.write(self.__profile_data)
profile.close()
def glib_map(value, glib_type):
result_value = value
if glib_type == 'i':
if glib_type == 'i' or glib_type == 'b' or glib_type == 'q':
result_value = GLib.Variant(glib_type, int(value))
else:
result_value = GLib.Variant(glib_type, value)
return result_value
def dconf_path(settings, path):
return settings.get_property("path") + path
def glib_value(schema, path, value, settings):
# Get the key to modify
key = settings.get_value(path)
# Query the data type for the key
glib_value_type = key.get_type_string()
# Build the new value with the determined type
return glib_map(value, glib_value_type)
class user_gsetting:
def __init__(self, schema, path, value, helper_function=None):
logging.debug('Creating GSettings element {} (in {}) with value {}'.format(path, schema, value))
self.schema = schema
self.path = path
self.value = value
self.helper_function = helper_function
def apply(self):
logging.debug('Setting GSettings key {} (in {}) to {}'.format(self.path, self.schema, self.value))
if self.helper_function:
self.helper_function(self.schema, self.path, self.value)
# Access the current schema
settings = Gio.Settings(self.schema)
# Get the key to modify
key = settings.get_value(self.path)
# Query the data type for the key
glib_value_type = key.get_type_string()
# Build the new value with the determined type
val = glib_map(self.value, glib_value_type)
settings = Gio.Settings(schema=self.schema)
# Update result with helper function
value = self.value
if self.helper_function:
value = self.helper_function(self.schema, self.path, value)
# Get typed value by schema
result = glib_value(self.schema, self.path, value, settings)
# Set the value
settings.set_value(self.path, val)
#gso = Gio.Settings.new(self.schema)
#variants = gso.get_property(self.path)
#if (variants.has_key(self.path)):
# key = variants.get_key(self.path)
# print(key.get_range())
settings.set_value(self.path, result)
settings.sync()

View File

@@ -30,8 +30,8 @@ from util.util import is_machine_name
class chromium_applier(applier_frontend):
__module_name = 'ChromiumApplier'
__module_enabled = False
__module_experimental = True
__module_enabled = True
__module_experimental = False
__registry_branch = 'Software\\Policies\\Google\\Chrome'
__managed_policies_path = '/etc/chromium/policies/managed'
__recommended_policies_path = '/etc/chromium/policies/recommended'

View File

@@ -0,0 +1,69 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.envvar import Envvar
from util.logging import slogm
import logging
class envvar_applier(applier_frontend):
__module_name = 'EnvvarsApplier'
__module_experimental = False
__module_enabled = True
def __init__(self, storage, sid):
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)
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Envvar applier for machine'))
ev = Envvar(self.envvars, 'root')
ev.act()
else:
logging.debug(slogm('Envvar applier for machine will not be started'))
class envvar_applier_user(applier_frontend):
__module_name = 'EnvvarsApplierUser'
__module_experimental = False
__module_enabled = True
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
self.envvars = self.storage.get_envvars(self.sid)
#self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_experimental)
def admin_context_apply(self):
pass
def user_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Envvar applier for user in user context'))
ev = Envvar(self.envvars, self.username)
ev.act()
else:
logging.debug(slogm('Envvar applier for user in user context will not be started'))

View File

@@ -39,10 +39,11 @@ from util.util import is_machine_name
class firefox_applier(applier_frontend):
__module_name = 'FirefoxApplier'
__module_experimental = True
__module_enabled = False
__module_experimental = False
__module_enabled = True
__registry_branch = 'Software\\Policies\\Mozilla\\Firefox'
__firefox_installdir = '/usr/lib64/firefox/distribution'
__firefox_installdir1 = '/usr/lib64/firefox/distribution'
__firefox_installdir2 = '/etc/firefox/policies'
__user_settings_dir = '.mozilla/firefox'
def __init__(self, storage, sid, username):
@@ -114,28 +115,78 @@ class firefox_applier(applier_frontend):
return homepage
return None
def get_block_about_config(self):
def get_boolean_config(self, name):
'''
Query BlockAboutConfig boolean property from the storage.
Query boolean property from the storage.
'''
response = self.get_hklm_string_entry('BlockAboutConfig')
response = self.get_hklm_string_entry(name)
if response:
if response.data.lower() in ['0', 'false', False, None, 'None']:
data = response.data if isinstance(response.data, int) else str(response.data).lower()
if data in ['0', 'false', None, 'none', 0]:
return False
return True
if data in ['1', 'true', 1]:
return True
return None
def set_boolean_policy(self, name):
'''
Add boolean entry to policy set.
'''
obj = self.get_boolean_config(name)
if obj is not None:
self.policies[name] = obj
logging.info(slogm('Firefox policy \'{}\' set to {}'.format(name, obj)))
def machine_apply(self):
'''
Write policies.json to Firefox installdir.
'''
self.set_policy('Homepage', self.get_home_page())
self.set_policy('BlockAboutConfig', self.get_block_about_config())
self.set_boolean_policy('BlockAboutConfig')
self.set_boolean_policy('BlockAboutProfiles')
self.set_boolean_policy('BlockAboutSupport')
self.set_boolean_policy('CaptivePortal')
self.set_boolean_policy('DisableSetDesktopBackground')
self.set_boolean_policy('DisableMasterPasswordCreation')
self.set_boolean_policy('DisableBuiltinPDFViewer')
self.set_boolean_policy('DisableDeveloperTools')
self.set_boolean_policy('DisableFeedbackCommands')
self.set_boolean_policy('DisableFirefoxScreenshots')
self.set_boolean_policy('DisableFirefoxAccounts')
self.set_boolean_policy('DisableFirefoxStudies')
self.set_boolean_policy('DisableForgetButton')
self.set_boolean_policy('DisableFormHistory')
self.set_boolean_policy('DisablePasswordReveal')
self.set_boolean_policy('DisablePocket')
self.set_boolean_policy('DisablePrivateBrowsing')
self.set_boolean_policy('DisableProfileImport')
self.set_boolean_policy('DisableProfileRefresh')
self.set_boolean_policy('DisableSafeMode')
self.set_boolean_policy('DisableSystemAddonUpdate')
self.set_boolean_policy('DisableTelemetry')
self.set_boolean_policy('DontCheckDefaultBrowser')
self.set_boolean_policy('ExtensionUpdate')
self.set_boolean_policy('HardwareAcceleration')
self.set_boolean_policy('PrimaryPassword')
self.set_boolean_policy('NetworkPrediction')
self.set_boolean_policy('NewTabPage')
self.set_boolean_policy('NoDefaultBookmarks')
self.set_boolean_policy('OfferToSaveLogins')
self.set_boolean_policy('PasswordManagerEnabled')
self.set_boolean_policy('PromptForDownloadLocation')
self.set_boolean_policy('SanitizeOnShutdown')
self.set_boolean_policy('SearchSuggestEnabled')
destfile = os.path.join(self.__firefox_installdir, 'policies.json')
destfile = os.path.join(self.__firefox_installdir1, 'policies.json')
os.makedirs(self.__firefox_installdir, exist_ok=True)
os.makedirs(self.__firefox_installdir1, exist_ok=True)
with open(destfile, 'w') as f:
json.dump(self.policies_json, f)
logging.debug(slogm('Wrote Firefox preferences to {}'.format(destfile)))
destfile = os.path.join(self.__firefox_installdir2, 'policies.json')
os.makedirs(self.__firefox_installdir2, exist_ok=True)
with open(destfile, 'w') as f:
json.dump(self.policies_json, f)
logging.debug(slogm('Wrote Firefox preferences to {}'.format(destfile)))

View File

@@ -17,6 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from storage import registry_factory
from storage.fs_file_cache import fs_file_cache
from .control_applier import control_applier
from .polkit_applier import (
@@ -46,14 +47,18 @@ from .folder_applier import (
)
from .cifs_applier import cifs_applier_user
from .ntp_applier import ntp_applier
from .envvar_applier import (
envvar_applier
, envvar_applier_user
)
from util.windows import get_sid
from util.users import (
is_root,
get_process_user,
username_match_uid,
with_privileges
)
from util.logging import log
from util.system import with_privileges
def determine_username(username=None):
@@ -79,6 +84,18 @@ def determine_username(username=None):
return name
def apply_user_context(user_appliers):
for applier_name, applier_object in user_appliers.items():
log('D55', {'name': applier_name})
try:
applier_object.user_context_apply()
except Exception as exc:
logdata = dict()
logdata['applier'] = applier_name
logdata['exception'] = str(exc)
log('E20', logdata)
class frontend_manager:
'''
The frontend_manager class decides when and how to run appliers
@@ -91,6 +108,7 @@ class frontend_manager:
self.is_machine = is_machine
self.process_uname = get_process_user()
self.sid = get_sid(self.storage.get_info('domain'), self.username, is_machine)
self.file_cache = fs_file_cache('file_cache')
self.machine_appliers = dict()
self.machine_appliers['control'] = control_applier(self.storage)
@@ -99,19 +117,20 @@ class frontend_manager:
self.machine_appliers['firefox'] = firefox_applier(self.storage, self.sid, self.username)
self.machine_appliers['chromium'] = chromium_applier(self.storage, self.sid, self.username)
self.machine_appliers['shortcuts'] = shortcut_applier(self.storage)
self.machine_appliers['gsettings'] = gsettings_applier(self.storage)
self.machine_appliers['gsettings'] = gsettings_applier(self.storage, self.file_cache)
self.machine_appliers['cups'] = cups_applier(self.storage)
self.machine_appliers['firewall'] = firewall_applier(self.storage)
self.machine_appliers['folders'] = folder_applier(self.storage, self.sid)
self.machine_appliers['package'] = package_applier(self.storage)
self.machine_appliers['ntp'] = ntp_applier(self.storage)
self.machine_appliers['envvar'] = envvar_applier(self.storage, self.sid)
# User appliers are expected to work with user-writable
# files and settings, mostly in $HOME.
self.user_appliers = dict()
self.user_appliers['shortcuts'] = shortcut_applier_user(self.storage, self.sid, self.username)
self.user_appliers['folders'] = folder_applier_user(self.storage, self.sid, self.username)
self.user_appliers['gsettings'] = gsettings_applier_user(self.storage, self.sid, self.username)
self.user_appliers['gsettings'] = gsettings_applier_user(self.storage, self.file_cache, self.sid, self.username)
try:
self.user_appliers['cifs'] = cifs_applier_user(self.storage, self.sid, self.username)
except Exception as exc:
@@ -121,6 +140,7 @@ class frontend_manager:
log('E25', logdata)
self.user_appliers['package'] = package_applier_user(self.storage, self.sid, self.username)
self.user_appliers['polkit'] = polkit_applier_user(self.storage, self.sid, self.username)
self.user_appliers['envvar'] = envvar_applier_user(self.storage, self.sid, self.username)
def machine_apply(self):
'''
@@ -130,6 +150,7 @@ class frontend_manager:
log('E13')
return
log('D16')
for applier_name, applier_object in self.machine_appliers.items():
try:
applier_object.apply()
@@ -153,13 +174,13 @@ class frontend_manager:
logdata['exception'] = str(exc)
log('E19', logdata)
try:
with_privileges(self.username, applier_object.user_context_apply)
except Exception as exc:
logdata = dict()
logdata['applier'] = applier_name
logdata['exception'] = str(exc)
log('E20', logdata)
try:
with_privileges(self.username, lambda: apply_user_context(self.user_appliers))
except Exception as exc:
logdata = dict()
logdata['username'] = self.username
logdata['exception'] = str(exc)
log('E30', logdata)
else:
for applier_name, applier_object in self.user_appliers.items():
try:

View File

@@ -1,7 +1,7 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
# Copyright (C) 2019-2021 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,6 +20,7 @@ import logging
import os
import pwd
import subprocess
import urllib.parse
from gi.repository import (
Gio
@@ -29,50 +30,108 @@ from gi.repository import (
from .applier_frontend import (
applier_frontend
, check_enabled
, check_windows_mapping_enabled
)
from .appliers.gsettings import (
system_gsetting,
system_gsettings,
user_gsetting
)
from util.logging import slogm
def uri_fetch(schema, path, value, cache):
'''
Function to fetch and cache uri
'''
retval = value
logdata = dict()
logdata['schema'] = schema
logdata['path'] = path
logdata['src'] = value
try:
retval = cache.get(value)
logdata['dst'] = retval
logging.debug(slogm('Getting cached file for URI: {}'.format(logdata)))
except Exception as exc:
pass
return retval
class gsettings_applier(applier_frontend):
__module_name = 'GSettingsApplier'
__module_experimental = True
__module_enabled = False
__registry_branch = 'Software\\BaseALT\\Policies\\gsettings'
__module_experimental = False
__module_enabled = True
__registry_branch = 'Software\\BaseALT\\Policies\\GSettings\\'
__registry_locks_branch = 'Software\\BaseALT\\Policies\\GSettingsLocks\\'
__wallpaper_entry = 'Software\\BaseALT\\Policies\\GSettings\\org.mate.background.picture-filename'
__vino_authentication_methods_entry = 'Software\\BaseALT\\Policies\\GSettings\\org.gnome.Vino.authentication-methods'
__global_schema = '/usr/share/glib-2.0/schemas'
__override_priority_file = 'zzz_policy.gschema.override'
__override_old_file = '0_policy.gschema.override'
__windows_settings = dict()
def __init__(self, storage):
def __init__(self, storage, file_cache):
self.storage = storage
self.file_cache = file_cache
gsettings_filter = '{}%'.format(self.__registry_branch)
gsettings_locks_filter = '{}%'.format(self.__registry_locks_branch)
self.gsettings_keys = self.storage.filter_hklm_entries(gsettings_filter)
self.gsettings = list()
self.override_file = os.path.join(self.__global_schema, '0_policy.gschema.override')
self.gsettings_locks = self.storage.filter_hklm_entries(gsettings_locks_filter)
self.override_file = os.path.join(self.__global_schema, self.__override_priority_file)
self.override_old_file = os.path.join(self.__global_schema, self.__override_old_file)
self.gsettings = system_gsettings(self.override_file)
self.locks = dict()
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def update_file_cache(self, data):
try:
self.file_cache.store(data)
except Exception as exc:
logdata = dict()
logdata['exception'] = str(exc)
logging.debug(slogm('Unable to cache specified URI for machine: {}'.format(logdata)))
def uri_fetch_helper(self, schema, path, value):
return uri_fetch(schema, path, value, self.file_cache)
def run(self):
# Compatility cleanup of old settings
if os.path.exists(self.override_old_file):
os.remove(self.override_old_file)
# Cleanup settings from previous run
if os.path.exists(self.override_file):
logging.debug(slogm('Removing GSettings policy file from previous run'))
os.remove(self.override_file)
# Get all configured gsettings locks
for lock in self.gsettings_locks:
valuename = lock.hive_key.rpartition('\\')[2]
self.locks[valuename] = int(lock.data)
# Calculate all configured gsettings
for setting in self.gsettings_keys:
helper = None
valuename = setting.hive_key.rpartition('\\')[2]
rp = valuename.rpartition('.')
schema = rp[0]
path = rp[2]
self.gsettings.append(system_gsetting(schema, path, setting.data))
data = setting.data
lock = bool(self.locks[valuename]) if valuename in self.locks else None
if setting.hive_key.lower() == self.__wallpaper_entry.lower():
check = urllib.parse.urlparse(setting.data)
if check.scheme:
self.update_file_cache(setting.data)
helper = self.uri_fetch_helper
elif setting.hive_key.lower() == self.__vino_authentication_methods_entry.lower():
data = [setting.data]
self.gsettings.append(schema, path, data, lock, helper)
# Create GSettings policy with highest available priority
for gsetting in self.gsettings:
gsetting.apply()
self.gsettings.apply()
# Recompile GSettings schemas with overrides
try:
@@ -80,9 +139,16 @@ class gsettings_applier(applier_frontend):
except Exception as exc:
logging.debug(slogm('Error recompiling global GSettings schemas'))
# Update desktop configuration system backend
try:
proc = subprocess.run(args=['/usr/bin/dconf', "update"], capture_output=True, check=True)
except Exception as exc:
logging.debug(slogm('Error update desktop configuration system backend'))
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running GSettings applier for machine'))
self.run()
else:
logging.debug(slogm('GSettings applier for machine will not be started'))
@@ -119,18 +185,22 @@ class GSettingsMapping:
class gsettings_applier_user(applier_frontend):
__module_name = 'GSettingsApplierUser'
__module_experimental = True
__module_enabled = False
__registry_branch = 'Software\\BaseALT\\Policies\\gsettings'
__module_experimental = False
__module_enabled = True
__registry_branch = 'Software\\BaseALT\\Policies\\GSettings\\'
__wallpaper_entry = 'Software\\BaseALT\\Policies\\GSettings\\org.mate.background.picture-filename'
__vino_authentication_methods_entry = 'Software\\BaseALT\\Policies\\GSettings\\org.gnome.Vino.authentication-methods'
def __init__(self, storage, sid, username):
def __init__(self, storage, file_cache, sid, username):
self.storage = storage
self.file_cache = file_cache
self.sid = sid
self.username = username
gsettings_filter = '{}%'.format(self.__registry_branch)
self.gsettings_keys = self.storage.filter_hkcu_entries(self.sid, gsettings_filter)
self.gsettings = list()
self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_enabled)
self.__windows_mapping_enabled = check_windows_mapping_enabled(self.storage)
self.__windows_settings = dict()
self.windows_settings = list()
@@ -166,6 +236,20 @@ class gsettings_applier_user(applier_frontend):
self.__windows_settings[element.hive_key] = element
def windows_mapping_append(self):
for setting_key in self.__windows_settings.keys():
value = self.storage.get_hkcu_entry(self.sid, setting_key)
if value:
logging.debug(slogm('Found GSettings windows mapping {} to {}'.format(setting_key, value.data)))
mapping = self.__windows_settings[setting_key]
try:
self.gsettings.append(user_gsetting(mapping.gsettings_schema, mapping.gsettings_key, value.data))
except Exception as exc:
print(exc)
def uri_fetch_helper(self, schema, path, value):
return uri_fetch(schema, path, value, self.file_cache)
def run(self):
#for setting in self.gsettings_keys:
# valuename = setting.hive_key.rpartition('\\')[2]
@@ -174,24 +258,33 @@ class gsettings_applier_user(applier_frontend):
# path = rp[2]
# self.gsettings.append(user_gsetting(schema, path, setting.data))
os.environ['DBUS_SESSION_BUS_ADDRESS'] = 'unix:path=/run/user/{}/bus'.format(pwd.getpwnam(self.username).pw_uid)
for setting_key in self.__windows_settings.keys():
logging.debug('Checking for GSettings mapping {}'.format(setting_key))
value = self.storage.get_hkcu_entry(self.sid, setting_key)
if value:
logging.debug('Found GSettings mapping {} to {}'.format(setting_key, value.data))
mapping = self.__windows_settings[setting_key]
self.gsettings.append(user_gsetting(mapping.gsettings_schema, mapping.gsettings_key, value.data))
else:
logging.debug('GSettings mapping of {} to {} not found'.format(setting_key, value.data))
# Calculate all mapped gsettings if mapping enabled
if self.__windows_mapping_enabled:
logging.debug(slogm('Mapping Windows policies to GSettings policies'))
self.windows_mapping_append()
else:
logging.debug(slogm('GSettings windows policies mapping not enabled'))
# Calculate all configured gsettings
for setting in self.gsettings_keys:
valuename = setting.hive_key.rpartition('\\')[2]
rp = valuename.rpartition('.')
schema = rp[0]
path = rp[2]
data = setting.data
helper = self.uri_fetch_helper if setting.hive_key.lower() == self.__wallpaper_entry.lower() else None
if setting.hive_key.lower() == self.__vino_authentication_methods_entry.lower():
data = [setting.data]
self.gsettings.append(user_gsetting(schema, path, data, helper))
# Create GSettings policy with highest available priority
for gsetting in self.gsettings:
logging.debug('Applying setting {}/{}'.format(gsetting.schema, gsetting.path))
logging.debug(slogm('Applying user setting {}.{} to {}'.format(gsetting.schema,
gsetting.path,
gsetting.value)))
gsetting.apply()
del os.environ['DBUS_SESSION_BUS_ADDRESS']
def user_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running GSettings applier for user in user context'))
@@ -200,8 +293,14 @@ class gsettings_applier_user(applier_frontend):
logging.debug(slogm('GSettings applier for user in user context will not be started'))
def admin_context_apply(self):
'''
Not implemented because there is no point of doing so.
'''
pass
# Cache files on remote locations
try:
entry = self.__wallpaper_entry
filter_result = self.storage.get_hkcu_entry(self.sid, entry)
if filter_result:
self.file_cache.store(filter_result.data)
except Exception as exc:
logdata = dict()
logdata['exception'] = str(exc)
logging.debug(slogm('Unable to cache specified URI for user: {}'.format(logdata)))

View File

@@ -31,7 +31,7 @@ from util.util import (
homedir_exists
)
def storage_get_shortcuts(storage, sid):
def storage_get_shortcuts(storage, sid, username=None):
'''
Query storage for shortcuts' rows for specified SID.
'''
@@ -40,13 +40,15 @@ def storage_get_shortcuts(storage, sid):
for sc_obj in shortcut_objs:
sc = json2sc(sc_obj.shortcut)
if username:
sc.set_expanded_path(expand_windows_var(sc.path, username))
shortcuts.append(sc)
return shortcuts
def write_shortcut(shortcut, username=None):
def apply_shortcut(shortcut, username=None):
'''
Write the single shortcut file to the disk.
Apply the single shortcut file to the disk.
:username: None means working with machine variables and paths
'''
@@ -64,22 +66,22 @@ def write_shortcut(shortcut, username=None):
if dest_abspath.startswith(get_homedir(username)):
# Don't try to operate on non-existent directory
if not homedir_exists(username):
logging.warning(slogm('No home directory exists for user {}: will not create link {}'.format(username, dest_abspath)))
logging.warning(slogm('No home directory exists for user {}: will not apply link {}'.format(username, dest_abspath)))
return None
else:
logging.warning(slogm('User\'s shortcut not placed to home directory for {}: bad path {}'.format(username, dest_abspath)))
return None
if '%' in dest_abspath:
logging.debug(slogm('Fail for writing shortcut to file with \'%\': {}'.format(dest_abspath)))
logging.debug(slogm('Fail for applying shortcut to file with \'%\': {}'.format(dest_abspath)))
return None
if not dest_abspath.startswith('/'):
logging.debug(slogm('Fail for writing shortcut to not absolute path \'%\': {}'.format(dest_abspath)))
logging.debug(slogm('Fail for applying shortcut to not absolute path \'%\': {}'.format(dest_abspath)))
return None
logging.debug(slogm('Writing shortcut file to {}'.format(dest_abspath)))
shortcut.write_desktop(dest_abspath)
logging.debug(slogm('Applying shortcut file to {} with action {}'.format(dest_abspath, shortcut.action)))
shortcut.apply_desktop(dest_abspath)
class shortcut_applier(applier_frontend):
__module_name = 'ShortcutsApplier'
@@ -98,7 +100,7 @@ class shortcut_applier(applier_frontend):
shortcuts = storage_get_shortcuts(self.storage, self.storage.get_info('machine_sid'))
if shortcuts:
for sc in shortcuts:
write_shortcut(sc)
apply_shortcut(sc)
if len(shortcuts) > 0:
# According to ArchWiki - this thing is needed to rebuild MIME
# type cache in order file bindings to work. This rebuilds
@@ -125,27 +127,29 @@ class shortcut_applier_user(applier_frontend):
self.sid = sid
self.username = username
def run(self):
shortcuts = storage_get_shortcuts(self.storage, self.sid)
def run(self, in_usercontext):
shortcuts = storage_get_shortcuts(self.storage, self.sid, self.username)
if shortcuts:
for sc in shortcuts:
if sc.is_usercontext():
write_shortcut(sc, self.username)
if in_usercontext and sc.is_usercontext():
apply_shortcut(sc, self.username)
if not in_usercontext and not sc.is_usercontext():
apply_shortcut(sc, self.username)
else:
logging.debug(slogm('No shortcuts to process for {}'.format(self.sid)))
def user_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Shortcut applier for user in user context'))
self.run()
self.run(True)
else:
logging.debug(slogm('Shortcut applier for user in user context will not be started'))
def admin_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Shortcut applier for user in administrator context'))
self.run()
self.run(False)
else:
logging.debug(slogm('Shortcut applier for user in administrator context will not be started'))

View File

@@ -73,25 +73,44 @@ class gpoa_controller:
def __init__(self):
self.__args = parse_arguments()
self.is_machine = False
if not self.__args.user:
user = get_machine_name()
self.is_machine = True
self.noupdate = self.__args.noupdate
set_loglevel(self.__args.loglevel)
locale.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale')
gettext.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale')
gettext.textdomain('gpoa')
if not self.__args.user:
self.username = get_machine_name()
self.is_machine = True
else:
self.username = self.__args.user
uname = get_process_user()
uid = os.getuid()
logdata = dict()
logdata['username'] = uname
logdata['uid'] = uid
log('D1', logdata)
logdata['username'] = self.username
logdata['is_machine'] = self.is_machine
logdata['process_username'] = uname
logdata['process_uid'] = uid
if self.is_machine:
log('D61', logdata)
else:
log('D1', logdata)
self.username = determine_username(self.username)
if not is_root():
self.username = uname
self.noupdate = True
if self.is_machine:
msgtext = message_with_code('E34')
log('E34', {'username': self.username})
raise Exception(msgtext)
log('D59', {'username': self.username})
else:
self.username = determine_username(self.__args.user)
log('D60', {'username': self.username})
def run(self):
'''
@@ -113,7 +132,7 @@ class gpoa_controller:
if self.__args.nodomain:
nodomain = True
if not self.__args.noupdate:
if not self.noupdate:
if is_root():
back = None
try:

View File

@@ -18,21 +18,48 @@
from util.xml import get_xml_root
from enum import Enum
class FileAction(Enum):
CREATE = 'C'
REPLACE = 'R'
UPDATE = 'U'
DELETE = 'D'
def action_letter2enum(letter):
if letter in ['C', 'R', 'U', 'D']:
if letter == 'C': return FileAction.CREATE
if letter == 'R': return FileAction.REPLACE
if letter == 'U': return FileAction.UPDATE
if letter == 'D': return FileAction.DELETE
return FileAction.CREATE
def read_envvars(envvars_file):
variables = list()
for var in get_xml_root(envvars_file):
var_obj = envvar()
props = var.find('Properties')
name = props.get('name')
value = props.get('value')
var_obj = envvar(name, value)
var_obj.set_action(action_letter2enum(props.get('action', default='C')))
variables.append(var_obj)
return variables
def merge_envvars(storage, sid, envvars_objects, policy_name):
for envvar in envvar_objects:
pass
def merge_envvars(storage, sid, envvar_objects, policy_name):
for envv in envvar_objects:
storage.add_envvar(sid, envv, policy_name)
class envvar:
def __init__(self):
pass
def __init__(self, name, value):
self.name = name
self.value = value
self.action = FileAction.CREATE
def set_action(self, action):
self.action = action

View File

@@ -68,7 +68,7 @@ from .tasks import (
import util
import util.preg
from util.paths import (
default_policy_path,
local_policy_path,
cache_dir,
local_policy_cache
)
@@ -326,7 +326,7 @@ def lp2gpt():
'''
Convert local-policy to full-featured GPT.
'''
lppath = os.path.join(default_policy_path(), 'Machine/Registry.pol.xml')
lppath = os.path.join(local_policy_path(), 'Machine/Registry.pol.xml')
# Load settings from XML PolFile
polparser = GPPolParser()

View File

@@ -79,7 +79,7 @@ def read_shortcuts(shortcuts_file):
# URL or FILESYSTEM
target_type = get_ttype(props.get('targetType'))
sc = shortcut(dest, path, arguments, link.get('name'), target_type)
sc = shortcut(dest, path, arguments, link.get('name'), props.get('action'), target_type)
sc.set_changed(link.get('changed'))
sc.set_clsid(link.get('clsid'))
sc.set_guid(link.get('uid'))
@@ -100,7 +100,7 @@ def json2sc(json_str):
json_obj = json.loads(json_str)
link_type = get_ttype(json_obj['type'])
sc = shortcut(json_obj['dest'], json_obj['path'], json_obj['arguments'], json_obj['name'], link_type)
sc = shortcut(json_obj['dest'], json_obj['path'], json_obj['arguments'], json_obj['name'], json_obj['action'], link_type)
sc.set_changed(json_obj['changed'])
sc.set_clsid(json_obj['clsid'])
sc.set_guid(json_obj['guid'])
@@ -111,7 +111,7 @@ def json2sc(json_str):
return sc
class shortcut:
def __init__(self, dest, path, arguments, name=None, ttype=TargetType.FILESYSTEM):
def __init__(self, dest, path, arguments, name=None, action=None, ttype=TargetType.FILESYSTEM):
'''
:param dest: Path to resulting file on file system
:param path: Path where the link should point to
@@ -121,8 +121,10 @@ class shortcut:
'''
self.dest = dest
self.path = path
self.expanded_path = None
self.arguments = arguments
self.name = name
self.action = action
self.changed = ''
self.icon = None
self.is_in_user_context = self.set_usercontext()
@@ -166,6 +168,12 @@ class shortcut:
self.is_in_user_context = ctx
def set_expanded_path(self, path):
'''
Adjust shortcut path with expanding windows variables
'''
self.expanded_path = path
def is_usercontext(self):
return self.is_in_user_context
@@ -181,6 +189,7 @@ class shortcut:
content['clsid'] = self.clsid
content['guid'] = self.guid
content['changed'] = self.changed
content['action'] = self.action
content['is_in_user_context'] = self.is_in_user_context
content['type'] = ttype2str(self.type)
if self.icon:
@@ -190,39 +199,78 @@ class shortcut:
return json.dumps(result.content)
def desktop(self):
def desktop(self, dest=None):
'''
Returns desktop file object which may be written to disk.
'''
self.desktop_file = DesktopEntry()
self.desktop_file.addGroup('Desktop Entry')
if dest:
self.desktop_file = DesktopEntry(dest)
else:
self.desktop_file = DesktopEntry()
self.desktop_file.addGroup('Desktop Entry')
self.desktop_file.set('Version', '1.0')
self._update_desktop()
return self.desktop_file
def _update_desktop(self):
'''
Update desktop file object from internal data.
'''
if self.type == TargetType.URL:
self.desktop_file.set('Type', 'Link')
else:
self.desktop_file.set('Type', 'Application')
self.desktop_file.set('Version', '1.0')
self.desktop_file.set('Name', self.name)
desktop_path = self.path
if self.expanded_path:
desktop_path = self.expanded_path
if self.type == TargetType.URL:
self.desktop_file.set('URL', self.path)
self.desktop_file.set('URL', desktop_path)
else:
self.desktop_file.set('Terminal', 'false')
self.desktop_file.set('Exec', '{} {}'.format(self.path, self.arguments))
self.desktop_file.set('Exec', '{} {}'.format(desktop_path, self.arguments))
if self.icon:
self.desktop_file.set('Icon', self.icon)
return self.desktop_file
def write_desktop(self, dest):
def _write_desktop(self, dest, create_only=False, read_firstly=False):
'''
Write .desktop file to disk using path 'dest'. Please note that
.desktop files must have executable bit set in order to work in
GUI.
'''
self.desktop().write(dest)
sc = Path(dest)
if sc.exists() and create_only:
return
if sc.exists() and read_firstly:
self.desktop(dest).write(dest)
else:
self.desktop().write(dest)
sc.chmod(sc.stat().st_mode | stat.S_IEXEC)
def _remove_desktop(self, dest):
'''
Remove .desktop file fromo disk using path 'dest'.
'''
sc = Path(dest)
if sc.exists():
sc.unlink()
def apply_desktop(self, dest):
'''
Apply .desktop file by action.
'''
if self.action == 'U':
self._write_desktop(dest, read_firstly=True)
elif self.action == 'D':
self._remove_desktop(dest)
elif self.action == 'R':
self._remove_desktop(dest)
self._write_desktop(dest)
elif self.action == 'C':
self._write_desktop(dest, create_only=True)

View File

@@ -23,8 +23,6 @@ import sys
import argparse
import subprocess
import re
from util.util import (
runcmd
, get_backends
@@ -33,17 +31,14 @@ from util.util import (
, get_policy_variants
)
from util.config import GPConfig
from util.paths import get_custom_policy_dir
class Runner:
__control_path = '/usr/sbin/control'
__systemctl_path = '/bin/systemctl'
__etc_policy_dir = '/etc/local-policy'
__usr_policy_dir = '/usr/share/local-policy'
def __init__(self):
self.etc_policies = get_policy_entries(self.__etc_policy_dir)
self.usr_policies = get_policy_entries(self.__usr_policy_dir)
self.arguments = parse_arguments()
def parse_arguments():
@@ -194,13 +189,13 @@ def disable_gp():
runcmd(cmd_set_local_policy)
runcmd(cmd_disable_gpupdate_service)
runcmd(cmd_disable_gpupdate_user_service)
config.set_local_policy_template()
config.set_backend()
def enable_gp(policy_name, backend_type):
'''
Consistently enable group policy services
'''
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
cmd_set_gpupdate_policy = ['/usr/sbin/control', 'system-policy', 'gpupdate']
cmd_gpoa_nodomain = ['/usr/sbin/gpoa', '--nodomain', '--loglevel', '5']
cmd_enable_gpupdate_service = ['/bin/systemctl', 'enable', 'gpupdate.service']
@@ -208,18 +203,17 @@ def enable_gp(policy_name, backend_type):
config = GPConfig()
custom_policy_dir = get_custom_policy_dir()
if not os.path.isdir(custom_policy_dir):
os.makedirs(custom_policy_dir)
target_policy_name = get_default_policy_name()
if policy_name:
if validate_policy_name(policy_name):
target_policy_name = policy_name
print (target_policy_name)
default_policy_name = os.path.join(policy_dir, target_policy_name)
if not os.path.isdir(etc_policy_dir):
os.makedirs(etc_policy_dir)
config.set_local_policy_template(default_policy_name)
config.set_local_policy_template(target_policy_name)
config.set_backend(backend_type)
# Enable oddjobd_gpupdate in PAM config

View File

@@ -289,6 +289,9 @@ msgstr "Пропускаем специальный ключ удаления в
msgid "Read domain name from configuration file"
msgstr "Имя контроллера домена для репликации прочитано из файла конфигурации"
msgid "Saving information about environment variables"
msgstr "Сохранение информации о переменных окружения"
msgid "Unknown debug code"
msgstr "Неизвестный отладочный код"

View File

@@ -57,6 +57,15 @@ def error_code(code):
error_ids[27] = 'Error merging user GPT'
error_ids[28] = 'Error merging machine part of GPT'
error_ids[29] = 'Error merging user part of GPT'
error_ids[30] = 'Error occured while running dropped privileges process for user context appliers'
error_ids[31] = 'Error connecting to DBus Session daemon'
error_ids[32] = 'No reply from DBus Session'
error_ids[33] = 'Error occured while running forked process with dropped privileges'
error_ids[34] = 'Error running GPOA directly for computer'
error_ids[35] = 'Error caching URI to file'
error_ids[36] = 'Error getting cached file for URI'
error_ids[37] = 'Error caching file URIs'
error_ids[38] = 'Unable to cache specified URI'
return error_ids.get(code, 'Unknown error code')
@@ -114,6 +123,20 @@ def debug_code(code):
debug_ids[50] = 'Finished GPO replication from AD DC'
debug_ids[51] = 'Skipping HKCU branch deletion key'
debug_ids[52] = 'Read domain name from configuration file'
debug_ids[53] = 'Saving information about environment variables'
debug_ids[54] = 'Run forked process with droped privileges'
debug_ids[55] = 'Run user context applier with dropped privileges'
debug_ids[56] = 'Kill dbus-daemon and dconf-service in user context'
debug_ids[57] = 'Found connection by org.freedesktop.DBus.GetConnectionUnixProcessID'
debug_ids[58] = 'Connection search return org.freedesktop.DBus.Error.NameHasNoOwner'
debug_ids[59] = 'Running GPOA without GPT update directly for user'
debug_ids[60] = 'Running GPOA by root for user'
debug_ids[61] = 'The GPOA process was started for computer'
debug_ids[62] = 'Path not resolved as UNC URI'
debug_ids[63] = 'Delete HKLM branch key'
debug_ids[64] = 'Delete HKCU branch key'
debug_ids[65] = 'Delete HKLM branch key error'
debug_ids[66] = 'Delete HKCU branch key error'
return debug_ids.get(code, 'Unknown debug code')

View File

@@ -0,0 +1,97 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2021 BaseALT Ltd. <org@basealt.ru>
# Copyright (C) 2021 Igor Chudov <nir@nir.org.ru>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import os.path
from pathlib import Path
import smbc
from util.logging import log
from util.paths import file_cache_dir, UNCPath
from util.exceptions import NotUNCPathError
class fs_file_cache:
__read_blocksize = 4096
def __init__(self, cache_name):
self.cache_name = cache_name
self.storage_uri = file_cache_dir()
logdata = dict({'cache_file': self.storage_uri})
log('D20', logdata)
self.samba_context = smbc.Context(use_kerberos=1)
#, debug=10)
def store(self, uri):
destdir = uri
try:
uri_path = UNCPath(uri)
file_name = os.path.basename(uri_path.get_path())
file_path = os.path.dirname(uri_path.get_path())
destdir = Path('{}/{}/{}'.format(self.storage_uri,
uri_path.get_domain(),
file_path))
except Exception as exc:
logdata = dict({'exception': str(exc)})
log('E38', logdata)
raise exc
if not destdir.exists():
destdir.mkdir(parents=True, exist_ok=True)
destfile = Path('{}/{}/{}'.format(self.storage_uri,
uri_path.get_domain(),
uri_path.get_path()))
with open(destfile, 'wb') as df:
df.truncate()
df.flush()
try:
file_handler = self.samba_context.open(str(uri_path), os.O_RDONLY)
while True:
data = file_handler.read(self.__read_blocksize)
if not data:
break
df.write(data)
df.flush()
except Exception as exc:
logdata = dict({'exception': str(exc)})
log('E35', logdata)
raise exc
def get(self, uri):
destfile = uri
try:
uri_path = UNCPath(uri)
file_name = os.path.basename(uri_path.get_path())
file_path = os.path.dirname(uri_path.get_path())
destfile = Path('{}/{}/{}'.format(self.storage_uri,
uri_path.get_domain(),
uri_path.get_path()))
except NotUNCPathError as exc:
logdata = dict({'path': str(exc)})
log('D62', logdata)
except Exception as exc:
logdata = dict({'exception': str(exc)})
log('E36', logdata)
raise exc
return str(destfile)

View File

@@ -22,7 +22,9 @@ class samba_preg(object):
'''
def __init__(self, preg_obj, policy_name):
self.policy_name = policy_name
self.hive_key = '{}\\{}'.format(preg_obj.keyname, preg_obj.valuename)
self.keyname = preg_obj.keyname
self.valuename = preg_obj.valuename
self.hive_key = '{}\\{}'.format(self.keyname, self.valuename)
self.type = preg_obj.type
self.data = preg_obj.data
@@ -41,7 +43,9 @@ class samba_hkcu_preg(object):
def __init__(self, sid, preg_obj, policy_name):
self.sid = sid
self.policy_name = policy_name
self.hive_key = '{}\\{}'.format(preg_obj.keyname, preg_obj.valuename)
self.keyname = preg_obj.keyname
self.valuename = preg_obj.valuename
self.hive_key = '{}\\{}'.format(self.keyname, self.valuename)
self.type = preg_obj.type
self.data = preg_obj.data
@@ -148,3 +152,25 @@ class folder_entry(object):
return fields
class envvar_entry(object):
'''
Object mapping representing environment variables
'''
def __init__(self, sid, evobj, policy_name):
self.sid = sid
self.policy_name = policy_name
self.name = evobj.name
self.value = evobj.value
self.action = evobj.action.value
def update_fields(self):
'''
Return list of fields to update
'''
fields = dict()
fields['policy_name'] = self.policy_name
fields['action'] = self.action
fields['value'] = self.value
return fields

View File

@@ -43,6 +43,7 @@ from .record_types import (
, printer_entry
, drive_entry
, folder_entry
, envvar_entry
)
class sqlite_registry(registry):
@@ -65,7 +66,10 @@ class sqlite_registry(registry):
'HKLM'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('hive_key', String(65536), unique=True)
, Column('hive_key', String(65536, collation='NOCASE'),
unique=True)
, Column('keyname', String(collation='NOCASE'))
, Column('valuename', String(collation='NOCASE'))
, Column('policy_name', String)
, Column('type', Integer)
, Column('data', String)
@@ -75,7 +79,9 @@ class sqlite_registry(registry):
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('hive_key', String(65536))
, Column('hive_key', String(65536, collation='NOCASE'))
, Column('keyname', String(collation='NOCASE'))
, Column('valuename', String(collation='NOCASE'))
, Column('policy_name', String)
, Column('type', Integer)
, Column('data', String)
@@ -126,6 +132,17 @@ class sqlite_registry(registry):
, Column('delete_files', String)
, UniqueConstraint('sid', 'path')
)
self.__envvars = Table(
'Envvars'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('name', String)
, Column('policy_name', String)
, Column('action', String)
, Column('value', String)
, UniqueConstraint('sid', 'name')
)
self.__metadata.create_all(self.db_cnt)
Session = sessionmaker(bind=self.db_cnt)
self.db_session = Session()
@@ -137,6 +154,7 @@ class sqlite_registry(registry):
mapper(printer_entry, self.__printers)
mapper(drive_entry, self.__drives)
mapper(folder_entry, self.__folders)
mapper(envvar_entry, self.__envvars)
except:
pass
#logging.error('Error creating mapper')
@@ -226,16 +244,52 @@ class sqlite_registry(registry):
log('D19', logdata)
self._info_upsert(ientry)
def _delete_hklm_keyname(self, keyname):
'''
Delete PReg hive_key from HKEY_LOCAL_MACHINE
'''
logdata = dict({'keyname': keyname})
try:
(self
.db_session
.query(samba_preg)
.filter(samba_preg.keyname == keyname)
.delete(synchronize_session=False))
self.db_session.commit()
log('D65', logdata)
except Exception as exc:
log('D63', logdata)
def add_hklm_entry(self, preg_entry, policy_name):
'''
Write PReg entry to HKEY_LOCAL_MACHINE
'''
pentry = samba_preg(preg_entry, policy_name)
if not pentry.hive_key.rpartition('\\')[2].startswith('**'):
if not pentry.valuename.startswith('**'):
self._hklm_upsert(pentry)
else:
logdata = dict({'key': pentry.hive_key})
log('D27', logdata)
if pentry.valuename.lower() == '**delvals.':
self._delete_hklm_keyname(pentry.keyname)
else:
log('D27', logdata)
def _delete_hkcu_keyname(self, keyname, sid):
'''
Delete PReg hive_key from HKEY_CURRENT_USER
'''
logdata = dict({'sid': sid, 'keyname': keyname})
try:
(self
.db_session
.query(samba_hkcu_preg)
.filter(samba_hkcu_preg.sid == sid)
.filter(samba_hkcu_preg.keyname == keyname)
.delete(synchronize_session=False))
self.db_session.commit()
log('D66', logdata)
except:
log('D64', logdata)
def add_hkcu_entry(self, preg_entry, sid, policy_name):
'''
@@ -243,11 +297,14 @@ class sqlite_registry(registry):
'''
hkcu_pentry = samba_hkcu_preg(sid, preg_entry, policy_name)
logdata = dict({'sid': sid, 'policy': policy_name, 'key': hkcu_pentry.hive_key})
if not hkcu_pentry.hive_key.rpartition('\\')[2].startswith('**'):
if not hkcu_pentry.valuename.startswith('**'):
log('D26', logdata)
self._hkcu_upsert(hkcu_pentry)
else:
log('D51', logdata)
if hkcu_pentry.valuename.lower() == '**delvals.':
self._delete_hkcu_keyname(hkcu_pentry.keyname, sid)
else:
log('D51', logdata)
def add_shortcut(self, sid, sc_obj, policy_name):
'''
@@ -294,6 +351,21 @@ class sqlite_registry(registry):
.update(fld_entry.update_fields()))
self.db_session.commit()
def add_envvar(self, sid, evobj, policy_name):
ev_entry = envvar_entry(sid, evobj, policy_name)
logdata = dict()
logdata['envvar'] = ev_entry.name
logdata['sid'] = sid
log('D53', logdata)
try:
self._add(ev_entry)
except Exception as exc:
(self
._filter_sid_obj(envvar_entry, sid)
.filter(envvar_entry.name == ev_entry.name)
.update(ev_entry.update_fields()))
self.db_session.commit()
def _filter_sid_obj(self, row_object, sid):
res = (self
.db_session
@@ -306,6 +378,7 @@ class sqlite_registry(registry):
.db_session
.query(row_object)
.filter(row_object.sid == sid)
.order_by(row_object.id)
.all())
return res
@@ -321,6 +394,9 @@ class sqlite_registry(registry):
def get_folders(self, sid):
return self._filter_sid_list(folder_entry, sid)
def get_envvars(self, sid):
return self._filter_sid_list(envvar_entry, sid)
def get_hkcu_entry(self, sid, hive_key):
res = (self
.db_session

View File

@@ -45,7 +45,7 @@ class GPConfig:
return 'samba'
def set_backend(self, backend_name):
def set_backend(self, backend_name='local'):
self.full_config['gpoa']['backend'] = backend_name
self.write_config()
@@ -71,7 +71,7 @@ class GPConfig:
return get_default_policy_name()
def set_local_policy_template(self, template_name):
def set_local_policy_template(self, template_name='default'):
self.full_config['gpoa']['local-policy'] = template_name
self.write_config()

View File

@@ -29,22 +29,38 @@ class dbus_runner:
'''
_bus_name = 'com.redhat.oddjob_gpupdate'
# Interface name is equal to bus name.
_interface_name = 'com.redhat.oddjob_gpupdate'
_object_path = '/'
# The timeout is in milliseconds. The default is -1 which is
# DBUS_TIMEOUT_USE_DEFAULT which is 25 seconds. There is also
# DBUS_TIMEOUT_INFINITE constant which is equal to INT32_MAX or
# 0x7ffffff (largest 32-bit integer).
#
# It was decided to set the timeout to 10 minutes which must be
# sufficient to replicate and apply all recognizable GPOs.
_synchronous_timeout = 600000
def __init__(self, username=None):
self.username = username
system_bus = dbus.SystemBus()
obj = system_bus.get_object(self._bus_name, self._object_path)
self.interface = dbus.Interface(obj, self._bus_name)
self.system_bus = dbus.SystemBus()
def run(self):
#print(obj.Introspect()[0])
if self.username:
logdata = dict({'username': self.username})
log('D6', logdata)
if is_root():
# oddjobd-gpupdate's ACL allows access to this method
# only for superuser. This method is called via PAM
# when user logs in.
try:
result = self.interface.gpupdatefor(dbus.String(self.username))
result = self.system_bus.call_blocking(self._bus_name,
self._object_path,
self._interface_name,
'gpupdatefor',
(username),
(dbus.String(self.username)),
timeout=self._synchronous_timeout)
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
logdata = dict()
@@ -53,20 +69,36 @@ class dbus_runner:
raise exc
else:
try:
result = self.interface.gpupdate()
result = self.system_bus.call_blocking(self._bus_name,
self._object_path,
self._interface_name,
'gpupdate',
None,
(),
timeout=self._synchronous_timeout)
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
log('E21')
logdata = dict({'error': str(exc)})
log('E21', logdata)
raise exc
else:
log('D11')
try:
result = self.interface.gpupdate_computer()
result = self.system_bus.call_blocking(self._bus_name,
self._object_path,
self._interface_name,
'gpupdate_computer',
None,
# The following positional parameter is called "args".
# There is no official documentation for it.
(),
timeout=self._synchronous_timeout)
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
log('E22')
print(exc)
logdata = dict({'error': str(exc)})
log('E22', logdata)
raise exc
#self.interface.Quit()
def start_gpupdate_user():
@@ -138,3 +170,27 @@ def print_dbus_result(result):
for line in message:
print(str(line))
class dbus_session:
def __init__(self):
try:
self.session_bus = dbus.SessionBus()
self.session_dbus = self.session_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
self.session_iface = dbus.Interface(self.session_dbus, 'org.freedesktop.DBus')
except dbus.exceptions.DBusException as exc:
logdata = dict({'error': str(exc)})
log('E31', logdata)
raise exc
def get_connection_pid(self, connection):
pid = -1
try:
pid = self.session_iface.GetConnectionUnixProcessID(connection)
log('D57', {"pid": pid})
except dbus.exceptions.DBusException as exc:
if exc.get_dbus_name() != 'org.freedesktop.DBus.Error.NameHasNoOwner':
logdata = dict({'error': str(exc)})
log('E32', logdata)
raise exc
log('D58', {'connection': connection})
return int(pid)

View File

@@ -39,3 +39,10 @@ def geterr():
return traceinfo
class NotUNCPathError(Exception):
def __init__(self, path):
self.path = path
def __str__(self):
return self.path

View File

@@ -21,15 +21,19 @@ import subprocess
from .util import get_machine_name
from .logging import log
from .samba import smbopts
def machine_kinit(cache_name=None):
'''
Perform kinit with machine credentials
'''
opts = smbopts()
host = get_machine_name()
realm = opts.get_realm()
with_realm = '{}@{}'.format(host, realm)
os.environ['KRB5CCNAME'] = 'FILE:{}'.format(cache_name)
kinit_cmd = ['kinit', '-k', host]
kinit_cmd = ['kinit', '-k', with_realm]
if cache_name:
kinit_cmd.extend(['-c', cache_name])
proc = subprocess.Popen(kinit_cmd)
@@ -55,8 +59,9 @@ def machine_kdestroy(cache_name=None):
if cache_name:
kdestroy_cmd.extend(['-c', cache_name])
proc = subprocess.Popen(kdestroy_cmd, stderr=subprocess.DEVNULL)
proc.wait()
if cache_name or 'KRB5CCNAME' in os.environ:
proc = subprocess.Popen(kdestroy_cmd, stderr=subprocess.DEVNULL)
proc.wait()
if cache_name and os.path.exists(cache_name):
os.unlink(cache_name)

View File

@@ -1,7 +1,8 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
# Copyright (C) 2019-2021 BaseALT Ltd. <org@basealt.ru>
# Copyright (C) 2019-2021 Igor Chudov <nir@nir.org.ru>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,25 +19,38 @@
import pathlib
import os
from pathlib import Path
from urllib.parse import urlparse
from .config import GPConfig
from .exceptions import NotUNCPathError
def default_policy_path():
def get_custom_policy_dir():
'''
Returns path pointing to Default Policy directory.
Returns path pointing to Custom Policy directory.
'''
local_policy_default = '/usr/share/local-policy/default'
return '/etc/local-policy'
def local_policy_path(default_template_name="default"):
'''
Returns path pointing to Local Policy template directory.
'''
local_policy_dir = '/usr/share/local-policy'
config = GPConfig()
local_policy_template = config.get_local_policy_template()
local_policy_template_path = os.path.join(local_policy_dir, local_policy_template)
local_policy_default = os.path.join(local_policy_dir, default_template_name)
result_path = pathlib.Path(local_policy_default)
if os.path.exists(config.get_local_policy_template()):
result_path = pathlib.Path(config.get_local_policy_template())
if os.path.exists(local_policy_template):
result_path = pathlib.Path(local_policy_template)
elif os.path.exists(local_policy_template_path):
result_path = pathlib.Path(local_policy_template_path)
return pathlib.Path(result_path)
def cache_dir():
'''
Returns path pointing to gpupdate's cache directory
@@ -48,6 +62,16 @@ def cache_dir():
return cachedir
def file_cache_dir():
'''
Returns path pointing to gpupdate's cache directory
'''
cachedir = pathlib.Path('/var/cache/gpupdate_file_cache')
if not cachedir.exists():
cachedir.mkdir(parents=True, exist_ok=True)
return cachedir
def local_policy_cache():
'''
@@ -61,3 +85,46 @@ def local_policy_cache():
return lpcache
class UNCPath:
def __init__(self, path):
self.path = path
self.type = None
if self.path.startswith(r'smb://'):
self.type = 'uri'
if self.path.startswith(r'\\'):
self.type = 'unc'
if not self.type:
raise NotUNCPathError(path)
def get_uri(self):
path = self.path
if self.type == 'unc':
path = self.path.replace('\\', '/')
path = path.replace('//', 'smb://')
else:
pass
return path
def get_unc(self):
path = self.path
if self.type == 'uri':
path = self.path.replace('//', '\\\\')
path = path.replace('smb:\\\\', '\\\\')
path = path.replace('/', '\\')
else:
pass
return path
def get_domain(self):
schema_struct = urlparse(self.get_uri())
return schema_struct.netloc
def get_path(self):
schema_struct = urlparse(self.get_uri())
return schema_struct.path
def __str__(self):
return self.get_uri()

View File

@@ -18,6 +18,7 @@
import optparse
import socket
from samba import getopt as options
@@ -28,11 +29,32 @@ class smbopts:
self.sambaopts = options.SambaOptions(self.parser)
self.lp = self.sambaopts.get_loadparm()
def get_realm(self):
'''
Get the default realm specified in smb.conf file.
'''
return self._get_prop('realm')
def get_cache_dir(self):
return self._get_prop('cache directory')
def get_server_role(self):
return self._get_prop('server role')
def get_machine_name(self):
'''
Get localhost name looking like DC0$
'''
nb_name = self.get_netbios_name()
result = nb_name + "$"
if result == '':
result = socket.gethostname().split('.', 1)[0].upper() + "$"
return result
def get_netbios_name(self):
return self._get_prop('netbios name')
def _get_prop(self, property_name):
return self.lp.get(property_name)

141
gpoa/util/system.py Normal file
View File

@@ -0,0 +1,141 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2021 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import pwd
import signal
import subprocess
from .logging import log
from .dbus import dbus_session
def set_privileges(username, uid, gid, groups, home):
'''
Set current process privileges
'''
os.environ.clear()
os.environ['HOME'] = home
os.environ['USER'] = username
os.environ['USERNAME'] = username
try:
os.setgid(gid)
except Exception as exc:
raise Exception('Error setgid() for drop privileges: {}'.format(str(exc)))
try:
os.setgroups(groups)
except Exception as exc:
raise Exception('Error setgroups() for drop privileges: {}'.format(str(exc)))
try:
os.setuid(uid)
except Exception as exc:
raise Exception('Error setuid() for drop privileges: {}'.format(str(exc)))
os.chdir(home)
logdata = dict()
logdata['uid'] = uid
logdata['gid'] = gid
logdata['username'] = username
log('D37', logdata)
def with_privileges(username, func):
'''
Run supplied function with privileges for specified username.
'''
if not os.getuid() == 0:
raise Exception('Not enough permissions to drop privileges')
user_pw = pwd.getpwnam(username)
user_uid = user_pw.pw_uid
user_gid = user_pw.pw_gid
user_groups = os.getgrouplist(username, user_gid)
user_home = user_pw.pw_dir
if not os.path.isdir(user_home):
raise Exception('User home directory not exists')
pid = os.fork()
if pid > 0:
log('D54', {'pid': pid})
waitpid, status = os.waitpid(pid, 0)
code = os.WEXITSTATUS(status)
if code != 0:
raise Exception('Error in forked process ({})'.format(status))
return
# We need to return child error code to parent
result = 0
dbus_pid = -1
dconf_pid = -1
try:
# Drop privileges
set_privileges(username, user_uid, user_gid, user_groups, user_home)
# Run the D-Bus session daemon in order D-Bus calls to work
proc = subprocess.Popen(
'dbus-launch',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
for var in proc.stdout:
sp = var.decode('utf-8').split('=', 1)
os.environ[sp[0]] = sp[1][:-1]
# Save pid of dbus-daemon
dbus_pid = int(os.environ['DBUS_SESSION_BUS_PID'])
# Run user appliers
func()
# Save pid of dconf-service
dconf_connection = "ca.desrt.dconf"
try:
session = dbus_session()
dconf_pid = session.get_connection_pid(dconf_connection)
except Exception:
pass
except Exception as exc:
logdata = dict()
logdata['msg'] = str(exc)
log('E33', logdata)
result = 1;
finally:
logdata = dict()
logdata['dbus_pid'] = dbus_pid
logdata['dconf_pid'] = dconf_pid
log('D56', logdata)
if dbus_pid > 0:
os.kill(dbus_pid, signal.SIGHUP)
if dconf_pid > 0:
os.kill(dconf_pid, signal.SIGTERM)
if dbus_pid > 0:
os.kill(dbus_pid, signal.SIGTERM)
sys.exit(result)

View File

@@ -1,7 +1,7 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
# Copyright (C) 2019-2021 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
@@ -55,59 +55,3 @@ def username_match_uid(username):
return False
def set_privileges(username, uid, gid, groups=list()):
'''
Set current process privileges
'''
try:
os.setegid(gid)
except Exception as exc:
print('setegid')
try:
os.seteuid(uid)
except Exception as exc:
print('seteuid')
#try:
# os.setgroups(groups)
#except Exception as exc:
# print('setgroups')
logdata = dict()
logdata['uid'] = uid
logdata['gid'] = gid
logdata['username'] = username
log('D37', logdata)
def with_privileges(username, func):
'''
Run supplied function with privileges for specified username.
'''
current_uid = os.getuid()
current_groups = os.getgrouplist('root', 0)
if not current_uid == 0:
raise Exception('Not enough permissions to drop privileges')
user_uid = pwd.getpwnam(username).pw_uid
user_gid = pwd.getpwnam(username).pw_gid
user_groups = os.getgrouplist(username, user_gid)
# Drop privileges
set_privileges(username, user_uid, user_gid, user_groups)
# We need to catch exception in order to be able to restore
# privileges later in this function
out = None
try:
out = func()
except Exception as exc:
log(str(exc))
# Restore privileges
set_privileges('root', current_uid, 0, current_groups)
return out

View File

@@ -17,10 +17,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import socket
import os
import pwd
import subprocess
import re
from pathlib import Path
from .samba import smbopts
@@ -29,7 +29,10 @@ def get_machine_name():
'''
Get localhost name looking like DC0$
'''
return socket.gethostname().split('.', 1)[0].upper() + "$"
loadparm = smbopts()
result = loadparm.get_machine_name()
return result
def is_machine_name(name):

View File

@@ -105,7 +105,7 @@ class smbcreds (smbopts):
for gpo in gpos:
# These setters are taken from libgpo/pygpo.c
# print(gpo.ds_path) # LDAP entry
ldata = dict({'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name})
ldata = dict({'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name, 'file_sys_path': gpo.file_sys_path})
log('I2', ldata)
except Exception as exc:
@@ -192,6 +192,7 @@ def expand_windows_var(text, username=None):
variables['DesktopDir'] = xdg_get_desktop(username, variables['HOME'])
if username:
variables['LogonUser'] = username
variables['HOME'] = get_homedir(username)
variables['StartMenuDir'] = os.path.join(

View File

@@ -1,8 +1,8 @@
%define _unpackaged_files_terminate_build 1
Name: gpupdate
Version: 0.8.1
Release: alt2
Version: 0.9.8
Release: alt0.dev1
Summary: GPT applier
License: GPLv3+
@@ -13,16 +13,18 @@ BuildArch: noarch
Requires: control
BuildRequires: rpm-build-python3
BuildRequires: python-tools-i18n
BuildRequires: gettext-tools
Requires: python3-module-rpm
Requires: python3-module-dbus
Requires: oddjob-%name >= 0.2.0
Requires: libnss-role >= 0.5.0
Requires: local-policy >= 0.4.0
Requires: local-policy >= 0.4.9
Requires: pam-config >= 1.9.0
Requires: autofs
# This is needed by shortcuts_applier
Requires: desktop-file-utils
# This is needed for smb file cache support
Requires: python3-module-smbc >= 1.0.23-alt3
Source0: %name-%version.tar
@@ -40,7 +42,7 @@ cp -r gpoa \
%buildroot%python3_sitelibdir/
# Generate translations
pymsgfmt \
msgfmt \
-o %buildroot%python3_sitelibdir/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.mo \
%buildroot%python3_sitelibdir/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po
@@ -48,6 +50,7 @@ mkdir -p \
%buildroot%_bindir/ \
%buildroot%_sbindir/ \
%buildroot%_cachedir/%name/ \
%buildroot%_cachedir/%{name}_file_cache/ \
%buildroot%_cachedir/%name/creds
ln -s %python3_sitelibdir/gpoa/gpoa \
@@ -65,12 +68,20 @@ mkdir -p %buildroot%_sysconfdir/%name
touch %buildroot%_sysconfdir/%name/environment
install -Dm0644 dist/%name.service %buildroot%_unitdir/%name.service
install -Dm0644 dist/%name.service %buildroot/usr/lib/systemd/user/%name-user.service
install -Dm0644 dist/%name-user.service %buildroot/usr/lib/systemd/user/%name-user.service
install -Dm0644 dist/system-policy-%name %buildroot%_sysconfdir/pam.d/system-policy-%name
install -Dm0644 dist/%name.ini %buildroot%_sysconfdir/%name/%name.ini
install -Dm0644 doc/gpoa.1 %buildroot/%_man1dir/gpoa.1
install -Dm0644 doc/gpupdate.1 %buildroot/%_man1dir/gpupdate.1
for i in gpupdate-localusers \
gpupdate-group-users \
gpupdate-system-uids
do
install -pD -m755 "dist/$i" \
"%buildroot%_sysconfdir/control.d/facilities/$i"
done
%preun
%preun_service gpupdate
@@ -80,7 +91,7 @@ install -Dm0644 doc/gpupdate.1 %buildroot/%_man1dir/gpupdate.1
# Remove storage in case we've lost compatibility between versions.
# The storage will be regenerated on GPOA start.
%define active_policy %_sysconfdir/local-policy/active
%triggerpostun -- %name > 0.7
%triggerpostun -- %name < 0.9.6
rm -f %_cachedir/%name/registry.sqlite
if test -L %active_policy; then
sed -i "s|^\s*local-policy\s*=.*|local-policy = $(readlink -f %active_policy)|" \
@@ -101,10 +112,12 @@ fi
%_man1dir/gpupdate.1.*
/usr/lib/systemd/user/%name-user.service
%dir %_sysconfdir/%name
%_sysconfdir/control.d/facilities/*
%config(noreplace) %_sysconfdir/%name/environment
%config(noreplace) %_sysconfdir/%name/%name.ini
%config(noreplace) %_sysconfdir/pam.d/system-policy-%name
%dir %attr(0700, root, root) %_cachedir/%name
%dir %attr(0755, root, root) %_cachedir/%{name}_file_cache
%dir %attr(0700, root, root) %_cachedir/%name/creds
%exclude %python3_sitelibdir/gpoa/.pylintrc
%exclude %python3_sitelibdir/gpoa/.prospector.yaml
@@ -112,6 +125,60 @@ fi
%exclude %python3_sitelibdir/gpoa/test
%changelog
* Wed Sep 29 2021 Evgeny Sinelnikov <sin@altlinux.org> 0.9.7-alt1
- Fix regression with kestroy for user credential cache
- Update system-policy-gpupdate PAM-rules to ignore applying group policies
for local users and system users with uid less than 500
- Add control facilities to rule system-policy-gpupdate rules:
+ gpupdate-group-users
+ gpupdate-localusers
+ gpupdate-system-uids
* Mon Sep 20 2021 Evgeny Sinelnikov <sin@altlinux.org> 0.9.6-alt1
- Add support changed GPO List Processing for '**DelVals.' value name
* Tue Sep 14 2021 Evgeny Sinelnikov <sin@altlinux.org> 0.9.5-alt1
- Refix local policy path detection
- gpupdate-setup: revert settings to default when disabled
* Tue Sep 14 2021 Evgeny Sinelnikov <sin@altlinux.org> 0.9.4-alt1
- Add improvement with new local-policy system-policy control
- Fix gpupdate-setup and user service installation regressions
- Set empty local policy and local backend by default
- Fix local policy path detection
* Mon Sep 06 2021 Evgeny Sinelnikov <sin@altlinux.org> 0.9.3-alt1
- Use NetBIOS name for Kerberos authentification
- Add support actions (create, update, delete, replace) for Shortcuts
- Add support GSettings with locks feature
- Add support file cache for special GSettings policy:
Software\BaseALT\Policies\GSettings\org.mate.background.picture-filename
(requires python smbc module with use_kerberos option support)
* Wed Jul 28 2021 Evgeny Sinelnikov <sin@altlinux.org> 0.9.2-alt1
- Fix Shortcuts applier double running in user context
- Add LogonUser variable to expand_windows_var() function
- Add support of path variable expansion to Shortcuts applier
* Sun Jul 18 2021 Evgeny Sinelnikov <sin@altlinux.org> 0.9.1-alt1
- Fix GSettings applier user part support
- Add support additional firefox appliers
- Add new windows policies mapping capability feature ruled by:
Software\BaseALT\Policies\GPUpdate\WindowsPoliciesMapping
- Improve drop privileges mechanism with fork and dbus session
* Fri Jun 25 2021 Evgeny Sinelnikov <sin@altlinux.org> 0.9.0-alt1
- Change policies.json location for Firefox
- Set GSettings, Chromium and Firefox appliers
not experimental and enabled by default
* Tue Dec 22 2020 Igor Chudov <nir@altlinux.org> 0.8.2-alt1
- Increased D-Bus timeouts on calls
- Minor logging fixes
* Wed Oct 07 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.8.1-alt3
- Fixed compatibility upgrade trigger condition
* Wed Oct 07 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.8.1-alt2
- Fixed compatibility upgrade trigger from 0.7 releases for update
active local-policy in new gpupdate.ini configuartion file