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

Compare commits

..

172 Commits

Author SHA1 Message Date
Evgeny Sinelnikov
a8bc2dbcb5 Fix gsettings_applier arrays processing for machine 2021-10-25 12:48:13 +04:00
Valery Sinelnikov
585811e282 Added work with arrays in gsettings 2021-10-22 20:23:05 +04:00
Valery Sinelnikov
0b497bcc02 Fixed bug for authentication-methods in org.gnome.Vino 2021-10-21 19:17:02 +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
Evgeny Sinelnikov
b426ab5b36 0.8.1-alt2
- Fixed compatibility upgrade trigger from 0.7 releases for update
  active local-policy in new gpupdate.ini configuartion file
2020-10-07 05:47:22 +04:00
Evgeny Sinelnikov
bb54d3e01e 0.8.1-alt1
- Workaround for control names with special symbols
- Improved logging on Kerberos errors
2020-09-11 22:05:50 +04:00
NIR
e2c386b6d0 Merge pull request #119 from altlinux/fix_kerberos_logging
Improved logging on Kerberos errors
2020-09-11 19:08:37 +04:00
66c2303069 Improved logging on Kerberos errors 2020-09-11 19:06:02 +04:00
NIR
e9cf33855c Merge pull request #117 from altlinux/control_spec_workaround
Workaround for control names with special symbols
2020-09-11 18:56:07 +04:00
3eae206e6f Workaround for control names with special symbols 2020-09-11 18:39:36 +04:00
536d989497 Automatically detect local policy name 2020-09-11 14:26:25 +04:00
Evgeny Sinelnikov
6cf0a7b136 0.8.0-alt1
- Improve gpo applier logging
- Add new configuration file /etc/gpupdate/gpupdate.ini
- Fix folders applier regression
- kinit move to samba backend
- Replace gpupdate-setup utility with new implementation
2020-09-10 00:40:48 +04:00
Evgeny Sinelnikov
48e484937e Set gpupdate-setup enable options not positional 2020-09-10 00:40:13 +04:00
Evgeny Sinelnikov
150ed3d29f Fix write action arguments 2020-09-09 23:11:23 +04:00
NIR
cff5ed1932 control 'system-policy global' changed to 'system-policy remote' 2020-09-09 23:02:29 +04:00
Evgeny Sinelnikov
b6a41c3843 Merge pull request #114 from altlinux/gpsetup_active_backend
Active backend selection fixes in gpupdate-setup
2020-09-09 20:53:10 +04:00
564d324b53 gpupdate-setup: Added set-backend and default-backend commands 2020-09-09 19:58:20 +04:00
41de03e6e8 Fix DC selection logging 2020-09-09 19:57:46 +04:00
828f6099da Fix logging for Kerberos 2020-09-09 19:57:27 +04:00
0a5af77655 Eliminate unused variable in util.windows 2020-09-09 19:57:08 +04:00
4abea3cc32 Make samba default backend in code 2020-09-09 19:56:38 +04:00
2597ae46cd Handle GPT retrieval error on incorrect DC specification 2020-09-09 19:56:10 +04:00
fdec0dc765 Two new FATAL-level messages added 2020-09-09 19:55:25 +04:00
8f96b1be85 Don't do anything if log metadata is not JSON-encodable 2020-09-09 19:54:43 +04:00
1f6b7d4cb7 Changed default backend to samba in configuration file 2020-09-09 19:52:21 +04:00
NIR
adea356f1b Merge pull request #113 from altlinux/dc_fix_log
Omit missing variable from log
2020-09-08 15:18:05 +04:00
75bb669ce7 Omit missing variable from log 2020-09-08 15:15:46 +04:00
NIR
e7967e5cc4 Merge pull request #112 from altlinux/avoid_dc_search
Avoid DC search on DC specification (either as CLI or config option)
2020-09-07 17:41:16 +04:00
d99ec2d890 Avoid DC search on DC specification (either as CLI or config option) 2020-09-07 17:11:07 +04:00
NIR
91b5c7f858 Merge pull request #111 from altlinux/dc_in_config
Added functionality to tune Samba backend via INI file
2020-09-07 16:53:56 +04:00
cb5101fc48 Added functionality to tune Samba backend via INI file
It is now possible to write in `/etc/gpupdate/gpupdate.ini`:

```
[samba]
dc = your.buggy.dc
```

To avoid auto-selection of Domain Controller for policy replication.
2020-09-07 16:51:38 +04:00
Evgeny Sinelnikov
a9f9689032 Merge pull request #108 from altlinux/freeipa_backend_stub
gpupdate-setup and other improvements
2020-09-02 20:31:40 +04:00
508fbe4dd5 Kinit functionality moved to Samba backend 2020-08-28 22:10:09 +04:00
ea379c3181 Correctly handle backend initialization 2020-08-28 21:52:40 +04:00
Evgeny Sinelnikov
ccfba4b592 Merge pull request #109 from altlinux/update-desktop-database
Shortcuts applier improvement
2020-08-26 17:57:30 +04:00
Evgeny Sinelnikov
3dcebe25e2 Merge pull request #110 from altlinux/storage_trigger
Postinst trigger introduced
2020-08-26 17:56:33 +04:00
37416ab77d Postinst trigger introduced
There are situations when we might lose storage compatibility between
versions due to SQLite3 table structure changes. This commit introduces
trigger which removes old storage in case of specific version change.

The storage will be regenerated on next GPOA run.
2020-08-21 16:34:21 +04:00
22cd8844ba Logic fix for shortcuts applier 2020-08-21 16:03:34 +04:00
c8cfd51915 Shortcuts applier improvement
Run update-desktop-database utility only in case there are
shortcuts for processing present in storage.
2020-08-21 16:00:39 +04:00
1a4ae69cdf Install gpupdate.ini 2020-08-20 18:33:33 +04:00
e1170d2096 FreeIPA backend stub added 2020-08-20 18:30:24 +04:00
e44b3c96ec gpupdate-setup: Fixed GPOA call 2020-08-20 18:29:41 +04:00
7e7450d52a gpupdate-setup: Retain behavior of active-policy option 2020-08-20 18:29:29 +04:00
e4113e971f gpupdate-setup: Check for gpupdate-user.service correctly 2020-08-20 18:29:01 +04:00
57607d1311 gpupdate-setup.is_unit_enabled(): Fixed stdout check 2020-08-20 18:28:30 +04:00
103110bdef gpupdate-setup.is_unit_enabled(): Work with global units too 2020-08-20 18:28:05 +04:00
126f2ffd7d backend: Fixed incorrect variable reference error 2020-08-20 18:27:15 +04:00
914a20b244 Operate on configuration file instead of symlinks 2020-08-20 15:09:40 +04:00
55dbdfc246 Numerous config parser fixes 2020-08-20 15:08:37 +04:00
b5059620a7 gpupdate-setup functions for local policy setup moved to util.util 2020-08-20 15:01:23 +04:00
af1e756037 gpupdate-setup: Docstrings added for functions 2020-08-20 15:00:39 +04:00
e1bc549a51 Fixed CLI definitions in gpupdate-setup 2020-08-19 19:38:46 +04:00
NIR
43b1ea392f Merge pull request #103 from altlinux/folders_regression_fix
Folders regression fix
2020-08-19 18:15:29 +04:00
NIR
b29c13b8dd Merge pull request #107 from altlinux/fix_set_dc
Fixed set_dc()
2020-08-19 18:14:42 +04:00
035950f234 Configuration file wrapper added 2020-08-19 17:50:03 +04:00
44545943ac gpupdate.ini: gpupdate configuration file template added 2020-08-19 16:49:40 +04:00
14bc3f3f96 gpupdate-setup: Status checks improved 2020-08-18 16:12:45 +04:00
e01e6f511a gpupdate-setup: Switch implementation for CLI options 2020-08-17 19:35:45 +04:00
79927743ca Use rollback_on_error() function in gpupdate-setup 2020-08-17 19:34:44 +04:00
f9cef07151 gpupdate-setup/rollback_on_error()
This is helper function for consistent Group Policy disabling in case
of any errors while enabling Group Policies.
2020-08-17 19:19:22 +04:00
53b94246a8 gpupdate-setup: Check return codes 2020-08-17 18:47:25 +04:00
3f1edd2791 runcmd(): Run program and check return code 2020-08-17 18:40:45 +04:00
165f0defa7 Install symlink to gpupdate-setup 2020-08-13 16:38:59 +04:00
0808128b5f gpupdate-setup became necessary utility 2020-08-13 16:37:10 +04:00
c40ae95a08 Fixed set_dc() 2020-08-13 15:59:10 +04:00
2bcfd75a5b Folders applier: Fixed regression and added path expansion 2020-08-13 08:16:48 +04:00
53ad06b787 Folders applier: update action support added 2020-08-13 08:15:09 +04:00
505f0152f7 Fixed typo in Folders storage 2020-08-13 08:12:32 +04:00
0ba273ee0e Fixed typo in Folders.xml parser 2020-08-13 08:11:28 +04:00
ef55a63c7e Set default values for missing XML parameters 2020-08-13 08:10:38 +04:00
6ebbf5f59d Stub 2020-08-13 08:05:46 +04:00
45 changed files with 2048 additions and 524 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" "$*"

233
dist/gpupdate-setup vendored
View File

@@ -1,233 +0,0 @@
#! /usr/bin/env python3
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import argparse
import subprocess
import re
from gpoa.util.samba import smbopts
def command(args):
try:
subprocess.check_call(args.split())
except:
print ('command: \'%s\' error' % args)
def from_command(args):
try:
with subprocess.Popen(args.split(), stdout=subprocess.PIPE) as proc:
value = proc.stdout.readline().decode('utf-8')
proc.wait()
except:
print ('from_command: \'%s\' error' % args)
return 'local'
return value.strip()
def get_default_policy_name():
localpolicy = 'workstation'
dcpolicy = 'ad-domain-controller'
try:
if smbopts().get_server_role() == 'active directory domain controller':
return dcpolicy
except:
pass
try:
release = '/etc/altlinux-release'
if os.path.isfile(release):
f = open(release)
s = f.readline()
if re.search('server', s, re.I):
localpolicy = 'server'
except:
pass
return localpolicy
def parse_arguments():
'''
Parse CLI arguments.
'''
parser = argparse.ArgumentParser(prog='gpupdate-setup')
subparsers = parser.add_subparsers(dest='action',
metavar='action',
help='Group Policy management actions (default action is status)')
parser_list = subparsers.add_parser('list',
help='List avalable types of local policy')
parser_status = subparsers.add_parser('status',
help='Show current Group Policy status')
parser_enable = subparsers.add_parser('enable',
help='Enable Group Policy subsystem')
parser_disable = subparsers.add_parser('disable',
help='Disable Group Policy subsystem')
parser_write = subparsers.add_parser('write',
help='Operate on Group Policies (enable or disable)')
parser_default = subparsers.add_parser('default-policy',
help='Show name of default policy')
parser_active = subparsers.add_parser('active-policy',
help='Show name of policy enabled')
parser_write.add_argument('status',
choices=['enable', 'disable'],
help='Enable or disable Group Policies')
parser_write.add_argument('localpolicy',
default=None,
nargs='?',
help='Name of local policy to enable')
parser_enable.add_argument('localpolicy',
default=None,
nargs='?',
help='Name of local policy to enable')
return parser.parse_args()
def get_policy_entries(directory):
filtered_entries = list()
if os.path.isdir(directory):
entries = [os.path.join(directory, entry) for entry in os.listdir(directory)]
for entry in entries:
if os.path.isdir(os.path.join(entry)):
if not os.path.islink(os.path.join(entry)):
if not entry.rpartition('/')[2] == 'default':
filtered_entries.append(entry)
return filtered_entries
def get_policy_variants():
'''
Get the list of local policy variants deployed on this system.
Please note that is case overlapping names the names in
/etc/local-policy must override names in /usr/share/local-policy
'''
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
system_policies = get_policy_entries(policy_dir)
user_policies = get_policy_entries(etc_policy_dir)
general_listing = list()
general_listing.extend(system_policies)
general_listing.extend(user_policies)
return general_listing
def validate_policy_name(policy_name):
return policy_name in [os.path.basename(d) for d in get_policy_variants()]
def get_status():
systemd_unit_link = '/etc/systemd/system/multi-user.target.wants/gpupdate.service'
return os.path.islink(systemd_unit_link)
def get_active_policy_name():
etc_policy_dir = '/etc/local-policy'
actual_policy_name = 'unknown'
active_policy_path = os.path.join(etc_policy_dir, 'active')
if os.path.islink(active_policy_path):
active_policy_path = os.path.realpath(active_policy_path)
if os.path.isdir(active_policy_path):
actual_policy_name = os.path.basename(active_policy_path)
return actual_policy_name
def disable_gp():
if from_command('/usr/sbin/control system-auth') != 'local':
command('/usr/sbin/control system-policy global')
else:
command('/usr/sbin/control system-policy local')
command('systemctl disable gpupdate.service')
command('systemctl --global disable gpupdate-user.service')
def enable_gp(policy_name):
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
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)
active_policy_name = os.path.join(etc_policy_dir, 'active')
if not os.path.isdir(etc_policy_dir):
os.makedirs(etc_policy_dir)
if not os.path.islink(active_policy_name):
os.symlink(default_policy_name, active_policy_name)
else:
os.unlink(active_policy_name)
os.symlink(default_policy_name, active_policy_name)
# Enable oddjobd_gpupdate in PAM config
command('/usr/sbin/control system-policy gpupdate')
# Bootstrap the Group Policy engine
command('/usr/sbin/gpoa --nodomain --loglevel 5')
# Enable gpupdate-setup.service for all users
command('systemctl --global enable gpupdate-user.service')
def main():
arguments = parse_arguments()
if arguments.action == 'list':
for entry in get_policy_variants():
print(entry.rpartition('/')[2])
if arguments.action == 'status' or arguments.action == None:
if get_status():
print('enabled')
else:
print('disabled')
if arguments.action == 'write':
if arguments.status == 'enable' or arguments.status == '#t':
enable_gp(arguments.localpolicy)
if arguments.status == 'disable' or arguments.status == '#f':
disable_gp()
if arguments.action == "enable":
enable_gp(arguments.localpolicy)
if arguments.action == "disable":
disable_gp()
if arguments.action == 'active-policy':
print(get_active_policy_name())
if arguments.action == 'default-policy':
print(get_default_policy_name())
if __name__ == '__main__':
main()

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 Normal file
View File

@@ -0,0 +1,4 @@
[gpoa]
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

@@ -21,6 +21,7 @@ from util.windows import smbcreds
from .samba_backend import samba_backend
from .nodomain_backend import nodomain_backend
from util.logging import log
from util.config import GPConfig
def backend_factory(dc, username, is_machine, no_domain = False):
'''
@@ -30,20 +31,25 @@ def backend_factory(dc, username, is_machine, no_domain = False):
policies enforced by domain administrators.
'''
back = None
domain = None
if not no_domain:
config = GPConfig()
if config.get_backend() == 'samba' and not no_domain:
if not dc:
dc = config.get_dc()
if dc:
ld = dict({'dc': dc})
log('D52', ld)
sc = smbcreds(dc)
domain = sc.get_domain()
if 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)
except Exception as exc:
logdata = dict({'error': str(exc)})
log('E7', logdata)
else:
if config.get_backend() == 'local' or no_domain:
log('D8')
try:
back = nodomain_backend()

View File

@@ -0,0 +1,25 @@
#
# 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_backend import applier_backend
class freeipa_backend(applier_backend):
def __init__(self):
pass

View File

@@ -27,6 +27,10 @@ from util.util import (
get_machine_name,
is_machine_name
)
from util.kerberos import (
machine_kinit
, machine_kdestroy
)
from util.windows import get_sid
import util.preg
from util.logging import log
@@ -34,6 +38,10 @@ from util.logging import log
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()
@@ -59,12 +67,21 @@ class samba_backend(applier_backend):
logdata = dict({'cachedir': self.cache_dir})
log('D7', logdata)
def __del__(self):
if self.__kinit_successful:
machine_kdestroy()
def retrieve_and_store(self):
'''
Retrieve settings and strore it in a database
'''
# Get policies for machine at first.
machine_gpts = self._get_gpts(get_machine_name(), self.storage.get_info('machine_sid'))
machine_gpts = list()
try:
machine_gpts = self._get_gpts(get_machine_name(), self.storage.get_info('machine_sid'))
except Exception as exc:
log('F2')
raise exc
self.storage.wipe_hklm()
self.storage.wipe_user(self.storage.get_info('machine_sid'))
for gptobj in machine_gpts:
@@ -78,7 +95,12 @@ class samba_backend(applier_backend):
# Load user GPT values in case user's name specified
# This is a buggy implementation and should be tested more
if not self._is_machine_username:
user_gpts = self._get_gpts(self.username, self.sid)
user_gpts = list()
try:
user_gpts = self._get_gpts(self.username, self.sid)
except Exception as exc:
log('F3')
raise exc
self.storage.wipe_user(self.sid)
for gptobj in user_gpts:
try:
@@ -104,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

@@ -21,11 +21,27 @@ import threading
import logging
from util.logging import slogm
def control_subst(preg_name):
'''
This is a workaround for control names which can't be used in
PReg/ADMX files.
'''
control_triggers = dict()
control_triggers['dvd_rw-format'] = 'dvd+rw-format'
control_triggers['dvd_rw-mediainfo'] = 'dvd+rw-mediainfo'
control_triggers['dvd_rw-booktype'] = 'dvd+rw-booktype'
result = preg_name
if preg_name in control_triggers:
result = control_triggers[preg_name]
return result
class control:
def __init__(self, name, value):
if type(value) != int and type(value) != str:
raise Exception('Unknown type of value for control')
self.control_name = name
self.control_name = control_subst(name)
self.control_value = value
self.possible_values = self._query_control_values()
if self.possible_values == None:

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

@@ -24,6 +24,7 @@ from gpt.folders import (
FileAction
, action_letter2enum
)
from util.windows import expand_windows_var
def remove_dir_tree(path, delete_files=False, delete_folder=False, delete_sub_folders=False):
for entry in path.iterdir():
@@ -45,8 +46,8 @@ def str2bool(boolstr):
return False
class Folder:
def __init__(self, folder_object):
self.folder_path = Path(folder_object.path)
def __init__(self, folder_object, username):
self.folder_path = Path(expand_windows_var(folder_object.path, username).replace('\\', '/'))
self.action = action_letter2enum(folder_object.action)
self.delete_files = str2bool(folder_object.delete_files)
self.delete_folder = str2bool(folder_object.delete_folder)
@@ -61,9 +62,11 @@ class Folder:
self.delete_folders,
self.delete_sub_folders)
def action(self):
def act(self):
if self.action == FileAction.CREATE:
self._create_action()
if self.action == FileAction.UPDATE:
self._create_action()
if self.action == FileAction.DELETE:
self._delete_action()
if self.action == FileAction.REPLACE:

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,115 @@ 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 pop(self):
self.gsettings.pop()
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

@@ -65,8 +65,8 @@ class folder_applier_user(applier_frontend):
def run(self):
for directory_obj in self.folders:
fld = Folder(directory_obj)
fld.action()
fld = Folder(directory_obj, self.username)
fld.act()
def admin_context_apply(self):
if self.__module_enabled:

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
@@ -29,50 +29,120 @@ 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'
__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.dictArr = 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:
valuename = setting.hive_key.rpartition('\\')[2]
helper = None
valuename = setting.valuename
rp = valuename.rpartition('.')
schema = rp[0]
path = rp[2]
self.gsettings.append(system_gsetting(schema, path, setting.data))
data = setting.data
if setting.hive_key.lower() == self.__wallpaper_entry.lower():
self.update_file_cache(setting.data)
helper = self.uri_fetch_helper
# Registry branch ends with back slash.
# If keyname starts with same prefix, it would be array
elif setting.keyname.lower().startswith(self.__registry_branch.lower()):
valuenameArr = setting.keyname.rpartition('\\')[2]
if valuenameArr:
valuename = valuenameArr
rpArr = valuename.rpartition('.')
schema = rpArr[0]
path = rpArr[2]
if self.dictArr and path in self.dictArr.keys():
self.dictArr[path].append(setting.data)
self.gsettings.pop()
else:
self.dictArr[path] = [setting.data,]
data = self.dictArr[path]
lock = bool(self.locks[valuename]) if valuename in self.locks else None
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 +150,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,19 +196,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'
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.dictArr = dict()
self.__windows_settings = dict()
self.windows_settings = list()
mapping = [
@@ -166,6 +246,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 +268,42 @@ 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]
helper = self.uri_fetch_helper if setting.hive_key.lower() == self.__wallpaper_entry.lower() else None
if valuename == setting.data:
valuenameArr = setting.keyname.rpartition('\\')[2]
rpArr = valuenameArr.rpartition('.')
schema = rpArr[0]
path = rpArr[2]
if self.dictArr and path in self.dictArr.keys():
self.dictArr[path].append(setting.data)
self.gsettings.pop()
else:
self.dictArr[path] = [setting.data,]
self.gsettings.append(user_gsetting(schema, path, self.dictArr[path], helper))
continue
self.gsettings.append(user_gsetting(schema, path, setting.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 +312,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,14 +100,15 @@ 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
# databases located in /usr/share/applications and
# /usr/local/share/applications
subprocess.check_call(['/usr/bin/update-desktop-database'])
else:
logging.debug(slogm('No shortcuts to process for {}'.format(self.storage.get_info('machine_sid'))))
# According to ArchWiki - this thing is needed to rebuild MIME
# type cache in order file bindings to work. This rebuilds
# databases located in /usr/share/applications and
# /usr/local/share/applications
subprocess.check_call(['/usr/bin/update-desktop-database'])
def apply(self):
if self.__module_enabled:
@@ -124,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

@@ -29,10 +29,6 @@ from plugin import plugin_manager
from messages import message_with_code
from util.util import get_machine_name
from util.kerberos import (
machine_kinit
, machine_kdestroy
)
from util.users import (
is_root,
get_process_user
@@ -62,6 +58,9 @@ def parse_arguments():
arguments.add_argument('--noplugins',
action='store_true',
help='Don\'t start plugins')
arguments.add_argument('--list-backends',
action='store_true',
help='Show list of available backends')
arguments.add_argument('--loglevel',
type=int,
default=4,
@@ -69,42 +68,60 @@ def parse_arguments():
return arguments.parse_args()
class gpoa_controller:
__kinit_successful = False
__args = None
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)
self.cache_path = '/var/cache/gpupdate/creds/krb5cc_{}'.format(os.getpid())
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):
'''
GPOA controller entry point
'''
self.__kinit_successful = machine_kinit(self.cache_path)
if self.__args.list_backends:
print('local')
print('samba')
return
self.start_plugins()
self.start_backend()
if self.__kinit_successful:
machine_kdestroy()
def start_backend(self):
'''
@@ -115,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

@@ -62,10 +62,10 @@ def read_folders(folders_file):
for fld in get_xml_root(folders_file):
props = fld.find('Properties')
fld_obj = folderentry(props.get('path'))
fld_obj.set_action(action_letter2enum(props.get('action')))
fld_obj.set_delete_folder(folder_int2bool(props.get('deleteFolder')))
fld_obj.set_delete_sub_folder(folder_int2bool(props.get('deleteSubFolders')))
fld_obj.set_delete_files(folder_int2bool(props.get('deleteFiles')))
fld_obj.set_action(action_letter2enum(props.get('action', default='C')))
fld_obj.set_delete_folder(folder_int2bool(props.get('deleteFolder', default=1)))
fld_obj.set_delete_sub_folders(folder_int2bool(props.get('deleteSubFolders', default=1)))
fld_obj.set_delete_files(folder_int2bool(props.get('deleteFiles', default=1)))
folders.append(fld_obj)
@@ -81,7 +81,7 @@ class folderentry:
self.path = path
self.action = FileAction.CREATE
self.delete_folder = False
self.delete_sub_folder = False
self.delete_sub_folders = False
self.delete_files = False
def set_action(self, action):
@@ -90,8 +90,8 @@ class folderentry:
def set_delete_folder(self, del_bool):
self.delete_folder = del_bool
def set_delete_sub_folder(self, del_bool):
self.delete_sub_folder = del_bool
def set_delete_sub_folders(self, del_bool):
self.delete_sub_folders = del_bool
def set_delete_files(self, del_bool):
self.delete_files = del_bool

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)

327
gpoa/gpupdate-setup Executable file
View File

@@ -0,0 +1,327 @@
#! /usr/bin/env python3
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import argparse
import subprocess
from util.util import (
runcmd
, get_backends
, get_default_policy_name
, get_policy_entries
, 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'
def __init__(self):
self.arguments = parse_arguments()
def parse_arguments():
'''
Parse CLI arguments.
'''
parser = argparse.ArgumentParser(prog='gpupdate-setup')
subparsers = parser.add_subparsers(dest='action',
metavar='action',
help='Group Policy management actions (default action is status)')
parser_list = subparsers.add_parser('list',
help='List avalable types of local policy')
parser_list = subparsers.add_parser('list-backends',
help='Show list of available backends')
parser_status = subparsers.add_parser('status',
help='Show current Group Policy status')
parser_enable = subparsers.add_parser('enable',
help='Enable Group Policy subsystem')
parser_disable = subparsers.add_parser('disable',
help='Disable Group Policy subsystem')
parser_write = subparsers.add_parser('write',
help='Operate on Group Policies (enable or disable)')
parser_set_backend = subparsers.add_parser('set-backend',
help='Set or change currently active backend')
parser_default = subparsers.add_parser('default-policy',
help='Show name of default policy')
parser_active = subparsers.add_parser('active-policy',
help='Show name of policy enabled')
parser_active_backend = subparsers.add_parser('active-backend',
help='Show currently configured backend')
parser_set_backend.add_argument('backend',
default='samba',
type=str,
nargs='?',
const='backend',
choices=['local', 'samba'],
help='Backend (source of settings) name')
parser_write.add_argument('status',
choices=['enable', 'disable'],
help='Enable or disable Group Policies')
parser_write.add_argument('localpolicy',
default=None,
nargs='?',
help='Name of local policy to enable')
parser_write.add_argument('backend',
default='samba',
type=str,
nargs='?',
const='backend',
choices=['local', 'samba'],
help='Backend (source of settings) name')
parser_enable.add_argument('--local-policy',
default=None,
help='Name of local policy to enable')
parser_enable.add_argument('--backend',
default='samba',
type=str,
choices=['local', 'samba'],
help='Backend (source of settings) name')
return parser.parse_args()
def validate_policy_name(policy_name):
return policy_name in [os.path.basename(d) for d in get_policy_variants()]
def is_unit_enabled(unit_name, unit_global=False):
'''
Check that designated systemd unit is enabled
'''
command = ['/bin/systemctl', 'is-enabled', unit_name]
if unit_global:
command = ['/bin/systemctl', '--global', 'is-enabled', unit_name]
value = runcmd(command)
# If first line of stdout is equal to "enabled" and return code
# is zero then unit is considered enabled.
rc = value[0]
result = []
try:
result = value[1].replace('\n', '')
except IndexError as exc:
return False
if result == 'enabled' and rc == 0:
return True
return False
def get_status():
'''
Check that gpupdate.service and gpupdate-user.service are enabled.
'''
is_gpupdate = is_unit_enabled('gpupdate.service')
is_gpupdate_user = is_unit_enabled('gpupdate-user.service', unit_global=True)
if is_gpupdate and is_gpupdate_user:
return True
return False
def get_active_policy_name():
'''
Show the name of an active Local Policy template
'''
config = GPConfig()
return os.path.basename(config.get_local_policy_template())
def get_active_backend():
config = GPConfig()
return config.get_backend()
def rollback_on_error(command_name):
'''
Disable group policy services in case command returns error code
'''
if 0 != runcmd(command_name)[0]:
disable_gp()
return False
return True
def disable_gp():
'''
Consistently disable group policy services
'''
cmd_set_global_policy = ['/usr/sbin/control', 'system-policy', 'remote']
cmd_set_local_policy = ['/usr/sbin/control', 'system-policy', 'local']
cmd_disable_gpupdate_service = ['/bin/systemctl', 'disable', 'gpupdate.service']
cmd_disable_gpupdate_user_service = ['/bin/systemctl', '--global', 'disable', 'gpupdate-user.service']
cmd_control_system_auth = ['/usr/sbin/control', 'system-auth']
config = GPConfig()
auth_result = 'local'
try:
auth_result = runcmd(cmd_control_system_auth)[1][0]
except Exception as exc:
print(str(exc))
if auth_result != 'local':
runcmd(cmd_set_global_policy)
else:
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
'''
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']
cmd_enable_gpupdate_user_service = ['/bin/systemctl', '--global', 'enable', 'gpupdate-user.service']
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)
config.set_local_policy_template(target_policy_name)
config.set_backend(backend_type)
# Enable oddjobd_gpupdate in PAM config
if not rollback_on_error(cmd_set_gpupdate_policy):
return
# Bootstrap the Group Policy engine
if not rollback_on_error(cmd_gpoa_nodomain):
return
# Enable gpupdate.service
if not rollback_on_error(cmd_enable_gpupdate_service):
return
if not is_unit_enabled('gpupdate.service'):
disable_gp()
return
# Enable gpupdate-setup.service for all users
if not rollback_on_error(cmd_enable_gpupdate_user_service):
return
if not is_unit_enabled('gpupdate-user.service', unit_global=True):
disable_gp()
return
def act_list():
'''
Show list of available templates of Local Policy
'''
for entry in get_policy_variants():
print(entry.rpartition('/')[2])
def act_list_backends():
'''
List backends supported by GPOA
'''
backends = get_backends()
for backend in backends:
print(backend)
def act_status():
'''
Check that group policy services are enabled
'''
if get_status():
print('enabled')
else:
print('disabled')
def act_set_backend(backend_name):
config = GPConfig()
config.set_backend(backend_name)
def act_write(status, localpolicy, backend):
'''
Enable or disable group policy services
'''
if status == 'enable' or status == '#t':
enable_gp(localpolicy, backend)
if status == 'disable' or status == '#f':
disable_gp()
def act_enable(localpolicy, backend):
'''
Enable group policy services
'''
enable_gp(localpolicy, backend)
def act_active_policy():
'''
Print active Local Policy template name to stdout
'''
print(get_active_policy_name())
def act_active_backend():
'''
Print currently configured backend.
'''
print(get_active_backend())
def act_default_policy():
'''
Print default Local Policy template name to stdout
'''
print(get_default_policy_name())
def main():
arguments = parse_arguments()
action = dict()
action['list'] = act_list
action['list-backends'] = act_list_backends
action['status'] = act_status
action['set-backend'] = act_set_backend
action['write'] = act_write
action['enable'] = act_enable
action['disable'] = disable_gp
action['active-policy'] = act_active_policy
action['active-backend'] = act_active_backend
action['default-policy'] = act_default_policy
if arguments.action == None:
action['status']()
elif arguments.action == 'enable':
action[arguments.action](arguments.local_policy, arguments.backend)
elif arguments.action == 'write':
action[arguments.action](arguments.status, arguments.localpolicy, arguments.backend)
elif arguments.action == 'set-backend':
action[arguments.action](arguments.backend)
else:
action[arguments.action]()
if __name__ == '__main__':
main()

View File

@@ -286,6 +286,12 @@ msgstr "Завершена репликация GPO от контроллера
msgid "Skipping HKCU branch deletion key"
msgstr "Пропускаем специальный ключ удаления ветви реестра HKCU"
msgid "Read domain name from configuration file"
msgstr "Имя контроллера домена для репликации прочитано из файла конфигурации"
msgid "Saving information about environment variables"
msgstr "Сохранение информации о переменных окружения"
msgid "Unknown debug code"
msgstr "Неизвестный отладочный код"
@@ -313,6 +319,12 @@ msgstr "Неизвестный код предупреждения"
msgid "Unable to refresh GPO list"
msgstr "Невозможно обновить список объектов групповых политик"
msgid "Error getting GPTs for machine"
msgstr "Не удалось получить GPT для машины"
msgid "Error getting GPTs for user"
msgstr "Не удалось получить GPT для пользователя"
msgid "Unknown fatal 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')
@@ -113,6 +122,21 @@ def debug_code(code):
debug_ids[49] = 'Started GPO replication from AD DC'
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')
@@ -135,6 +159,8 @@ def warning_code(code):
def fatal_code(code):
fatal_ids = dict()
fatal_ids[1] = 'Unable to refresh GPO list'
fatal_ids[2] = 'Error getting GPTs for machine'
fatal_ids[3] = 'Error getting GPTs for user'
return fatal_ids.get(code, 'Unknown fatal 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
@@ -132,7 +136,7 @@ class folder_entry(object):
self.path = fobj.path
self.action = fobj.action.value
self.delete_folder = str(fobj.delete_folder)
self.delete_sub_folder = str(fobj.delete_sub_folder)
self.delete_sub_folders = str(fobj.delete_sub_folders)
self.delete_files = str(fobj.delete_files)
def update_fields(self):
@@ -143,8 +147,30 @@ class folder_entry(object):
fields['policy_name'] = self.policy_name
fields['action'] = self.action
fields['delete_folder'] = self.delete_folder
fields['delete_sub_folder'] = self.delete_sub_folder
fields['delete_sub_folders'] = self.delete_sub_folders
fields['delete_files'] = self.delete_files
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)
@@ -122,10 +128,21 @@ class sqlite_registry(registry):
, Column('policy_name', String)
, Column('action', String)
, Column('delete_folder', String)
, Column('delete_sub_folder', String)
, Column('delete_sub_folders', String)
, 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

81
gpoa/util/config.py Normal file
View File

@@ -0,0 +1,81 @@
#
# 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 configparser import ConfigParser
from .util import (
get_backends
, get_default_policy_name
)
class GPConfig:
__config_path = '/etc/gpupdate/gpupdate.ini'
def __init__(self, config_path=None):
if config_path:
self.__config_path = config_path
self.full_config = ConfigParser()
self.full_config.read(self.__config_path)
def get_backend(self):
'''
Fetch the name of the backend from configuration file.
'''
if 'gpoa' in self.full_config:
if 'backend' in self.full_config['gpoa']:
if self.full_config['gpoa']['backend'] in get_backends():
return self.full_config['gpoa']['backend']
return 'samba'
def set_backend(self, backend_name='local'):
self.full_config['gpoa']['backend'] = backend_name
self.write_config()
# This function is not expected corresponding "set_dc()" function
# because we have no way to automatically determine such kind
# of setting.
def get_dc(self):
'''
Fetch Domain Controller from configuration file.
'''
if 'samba' in self.full_config:
if 'dc' in self.full_config['samba']:
return self.full_config['samba']['dc']
def get_local_policy_template(self):
'''
Fetch the name of chosen Local Policy template from
configuration file.
'''
if 'gpoa' in self.full_config:
if 'local-policy' in self.full_config['gpoa']:
return self.full_config['gpoa']['local-policy']
return get_default_policy_name()
def set_local_policy_template(self, template_name='default'):
self.full_config['gpoa']['local-policy'] = template_name
self.write_config()
def write_config(self):
with open(self.__config_path, 'w') as config_file:
self.full_config.write(config_file)

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)
@@ -74,11 +79,13 @@ def check_krb_ticket():
try:
subprocess.check_call(['klist', '-s'])
output = subprocess.check_output('klist', stderr=subprocess.STDOUT).decode()
logging.info(output)
result = True
except:
log('E14')
log('D17')
logdata = dict()
logdata['output'] = output
log('D17', logdata)
except Exception as exc:
logdata = dict()
logdata['krb-exc'] = exc
log('E14', logdata)
return result

View File

@@ -52,7 +52,11 @@ class slogm(object):
#args.update(dict({'timestamp': now, 'message': str(self.message)}))
args.update(self.kwargs)
kwa = encoder().encode(args)
kwa = dict()
try:
kwa = encoder().encode(args)
except Exception as exc:
pass
result = '{}|{}|{}'.format(now, self.message, kwa)

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,23 +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'
etc_local_policy_default = '/etc/local-policy/active'
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(etc_local_policy_default):
result_path = pathlib.Path(etc_local_policy_default)
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
@@ -46,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():
'''
@@ -59,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,17 +17,22 @@
# 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
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):
@@ -83,3 +88,95 @@ def mk_homedir_path(username, homedir_path):
os.chown(homedir, uid=uid, gid=gid)
longer_path = os.path.join(longer_path, elem)
def runcmd(command_name):
'''
Run application.
'''
try:
with subprocess.Popen(command_name, stdout=subprocess.PIPE) as proc:
value = proc.stdout.read().decode('utf-8')
proc.wait()
rc = proc.returncode
return (rc, value)
except Exception as exc:
print(str(exc))
def get_backends():
'''
Get the list of backends supported by GPOA
'''
command = ['/usr/sbin/gpoa', '--list-backends']
backends = list()
out = list()
with subprocess.Popen(command, stdout=subprocess.PIPE) as proc:
out = proc.stdout.read().decode('utf-8')
proc.wait()
out = out.split('\n')
for line in out:
tmpline = line.replace('\n', '')
if tmpline != '':
backends.append(tmpline)
return backends
def get_default_policy_name():
'''
Determine the preferred Local Policy template name according to
ALT distribution type
'''
localpolicy = 'workstation'
dcpolicy = 'ad-domain-controller'
try:
if smbopts().get_server_role() == 'active directory domain controller':
return dcpolicy
except:
pass
try:
release = '/etc/altlinux-release'
if os.path.isfile(release):
f = open(release)
s = f.readline()
if re.search('server', s, re.I):
localpolicy = 'server'
except:
pass
return localpolicy
def get_policy_entries(directory):
'''
Get list of directories representing "Local Policy" templates.
'''
filtered_entries = list()
if os.path.isdir(directory):
entries = [os.path.join(directory, entry) for entry in os.listdir(directory)]
for entry in entries:
if os.path.isdir(os.path.join(entry)):
if not os.path.islink(os.path.join(entry)):
if not entry.rpartition('/')[2] == 'default':
filtered_entries.append(entry)
return filtered_entries
def get_policy_variants():
'''
Get the list of local policy variants deployed on this system.
Please note that is case overlapping names the names in
/etc/local-policy must override names in /usr/share/local-policy
'''
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
system_policies = get_policy_entries(policy_dir)
user_policies = get_policy_entries(etc_policy_dir)
general_listing = list()
general_listing.extend(system_policies)
general_listing.extend(user_policies)
return general_listing

View File

@@ -43,7 +43,7 @@ class smbcreds (smbopts):
smbopts.__init__(self, 'GPO Applier')
self.credopts = options.CredentialsOptions(self.parser)
self.creds = self.credopts.get_credentials(self.lp, fallback_machine=True)
self.selected_dc = self.set_dc(dc_fqdn)
self.set_dc(dc_fqdn)
def get_dc(self):
return self.selected_dc
@@ -55,25 +55,20 @@ class smbcreds (smbopts):
self.selected_dc = None
try:
samba_dc = get_dc_hostname(self.creds, self.lp)
if samba_dc != dc_fqdn and dc_fqdn is not None:
if dc_fqdn is not None:
logdata = dict()
logdata['dc'] = samba_dc
logdata['user_dc'] = dc
logdata['user_dc'] = dc_fqdn
log('D38', logdata)
self.selected_dc = dc_fqdn
else:
self.selected_dc = samba_dc
self.selected_dc = get_dc_hostname(self.creds, self.lp)
except Exception as exc:
logdata = dict()
logdata['msg'] = str(exc)
log('E10', logdata)
raise exc
return self.selected_dc
def get_domain(self):
'''
Get current Active Directory domain name
@@ -110,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:
@@ -197,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.7.0
Release: alt1
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,13 +50,14 @@ mkdir -p \
%buildroot%_bindir/ \
%buildroot%_sbindir/ \
%buildroot%_cachedir/%name/ \
%buildroot%_cachedir/%{name}_file_cache/ \
%buildroot%_cachedir/%name/creds
ln -s %python3_sitelibdir/gpoa/gpoa \
%buildroot%_sbindir/gpoa
ln -s %python3_sitelibdir/gpoa/gpupdate \
%buildroot%_bindir/gpupdate
cp dist/gpupdate-setup \
ln -s %python3_sitelibdir/gpoa/gpupdate-setup \
%buildroot%_sbindir/gpupdate-setup
mkdir -p %buildroot%_datadir/%name
@@ -65,19 +68,35 @@ 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
%preun_service gpupdate-user
%post
%post_service gpupdate
%post_service gpupdate-user
rm -f /var/cache/gpupdate/registry.sqlite
# 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.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)|" \
%_sysconfdir/%name/%name.ini
fi
%files
%_sbindir/gpoa
@@ -85,6 +104,7 @@ rm -f /var/cache/gpupdate/registry.sqlite
%_bindir/gpupdate
%attr(755,root,root) %python3_sitelibdir/gpoa/gpoa
%attr(755,root,root) %python3_sitelibdir/gpoa/gpupdate
%attr(755,root,root) %python3_sitelibdir/gpoa/gpupdate-setup
%python3_sitelibdir/gpoa
%_datadir/%name
%_unitdir/%name.service
@@ -92,9 +112,12 @@ rm -f /var/cache/gpupdate/registry.sqlite
%_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
@@ -102,6 +125,75 @@ rm -f /var/cache/gpupdate/registry.sqlite
%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
* Fri Sep 11 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.8.1-alt1
- Workaround for control names with special symbols
- Improved logging on Kerberos errors
* Fri Sep 04 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.8.0-alt1
- Improve gpo applier logging
- Add new configuration file /etc/gpupdate/gpupdate.ini
- Fix folders applier regression
- kinit move to samba backend
- Replace gpupdate-setup utility with new implementation
* Wed Jul 01 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.7.0-alt1
- Add multiple appliers, part of which marks as experimental yet