1
0
mirror of https://github.com/altlinux/gpupdate.git synced 2025-08-26 13:50:22 +03:00

Compare commits

..

260 Commits

Author SHA1 Message Date
2f20262927 Install hooks improved 2020-08-13 15:48:04 +04:00
NIR
a1bd67f7d2 Merge pull request #105 from altlinux/gsettings_fixes
Improved logging in GSettings applier
2020-08-13 08:31:47 +04:00
NIR
30b942d32d Merge pull request #104 from altlinux/testing_data
Testing data
2020-08-13 08:31:11 +04:00
NIR
84b7977351 Merge pull request #102 from altlinux/extra_error_handling
Extra error handling
2020-08-13 08:30:44 +04:00
4c37b15dde Improved logging in GSettings applier 2020-08-13 08:28:42 +04:00
3dfb37313d GPT test data 2020-08-13 08:22:58 +04:00
81b9e88d91 test_xdg.py 2020-08-13 08:22:34 +04:00
dff80ba14d Extra information for E10 added 2020-08-11 15:26:48 +04:00
6c5c22a932 Two more error handlers for GPT merging so bugs in mergers won't block backend 2020-08-10 15:56:56 +04:00
c67487b56c GPT merging error handlers added 2020-08-05 13:16:55 +04:00
abe943f399 New error messages translated 2020-08-05 13:16:29 +04:00
353082a6bb Two more error codes added 2020-08-05 13:16:04 +04:00
NIR
e37796e5b4 Merge pull request #28 from altlinux/l10n
Initial translation
2020-08-04 16:14:31 +04:00
3afe965b75 Translation leftovers removed 2020-08-03 21:38:43 +04:00
aa93d95012 Fix for translation install 2020-08-03 21:29:02 +04:00
0bc7144da9 gpupdate localized 2020-08-03 21:28:30 +04:00
ffbef2d18f Install language packages 2020-08-03 21:04:04 +04:00
6429b3c290 Russian translations added 2020-08-03 20:58:26 +04:00
05320f86b3 Messages internationalized 2020-08-03 20:57:56 +04:00
b10b965287 GPOA: Initialize gettext 2020-08-03 20:56:35 +04:00
716862201e Initial translation 2020-07-30 17:15:44 +04:00
NIR
c701565325 Merge pull request #95 from altlinux/gsettings_expansion
Appliers for GSettings expanded with Windows key support
2020-07-30 17:13:08 +04:00
NIR
09553a1c9e Merge pull request #99 from altlinux/logging_codes
Logging codes
2020-07-30 17:12:22 +04:00
674f07569e util.logging: Show milliseconds in logs and handle parameter errors 2020-07-30 15:36:08 +04:00
3d39e1f010 backend.samba_backend: Logging improved 2020-07-29 18:07:18 +04:00
c45fa3e552 util.windows: Logging improved 2020-07-29 18:07:01 +04:00
8135b21cd3 messages: Message list expanded 2020-07-29 18:06:44 +04:00
758421b611 util.logging.log(): Introduced function to wrap log data 2020-07-29 17:49:23 +04:00
25b1774d49 gpupdate: Logging improved 2020-07-29 17:48:44 +04:00
70ef7ef384 gpoa: Logging improved 2020-07-29 17:48:26 +04:00
3517f3a67b util.sid: Leftovers eliminated 2020-07-29 17:47:50 +04:00
2f6a287727 gpt.gpt: Logging improved 2020-07-29 17:47:28 +04:00
586528f8e9 frontend.frontend_manager: Logging improved 2020-07-29 17:47:03 +04:00
68910679e6 backend.samba_backend: Logging improved 2020-07-29 17:46:27 +04:00
8cb1278f04 backend: Logging improved 2020-07-29 17:46:03 +04:00
e12042fb2c storage.sqlite_cache: Logging improved 2020-07-29 17:45:37 +04:00
8d351dde63 storage.sqlite_cache: Logging improved 2020-07-29 17:44:53 +04:00
ae3672dbdc util.dbus: Logging improved 2020-07-29 17:35:01 +04:00
bc8b6369c2 util.kerberos: Logging improved 2020-07-29 17:34:16 +04:00
99c7a305de util.preg: Logging improved 2020-07-29 17:33:50 +04:00
4f6f17024e util.users: Logging improved 2020-07-29 17:33:09 +04:00
0d7a1e9740 util.windows: Logging improved 2020-07-29 17:30:22 +04:00
f203a48bee util.xdg: Logging improved 2020-07-29 17:29:49 +04:00
df22fe21f5 Message code lists expanded 2020-07-29 17:27:45 +04:00
c4d89921aa util.exceptions: Error metadata adjusted 2020-07-28 12:14:55 +04:00
0851f96c6f gpoa: Logging improved 2020-07-28 12:10:55 +04:00
23de5b63f6 gpupdate: Logging improved 2020-07-28 12:10:25 +04:00
6f6612862d plugin.adp: Logging improved 2020-07-28 12:10:01 +04:00
70886bd605 plugin.plugin_manager: Logging improved 2020-07-28 12:09:40 +04:00
d3caf73dac frontend.frontend_manager: Logging improved 2020-07-28 12:09:09 +04:00
551020f074 backend.samba_backend: Logging improved 2020-07-28 12:08:41 +04:00
1bf4687d41 gpt.gpt: Logging improved 2020-07-28 12:06:43 +04:00
d9afe438bc storage.sqlite_cache: Logging improved 2020-07-28 12:05:19 +04:00
067f1831ac util.arguments: Logging improved 2020-07-28 12:04:54 +04:00
fcdf3af1de util.dbus: Logging improved 2020-07-28 12:02:10 +04:00
d776cbbc7a util.kerberos: Logging improved 2020-07-28 12:01:31 +04:00
58a609f0bf util.preg: Logging improved 2020-07-28 11:59:53 +04:00
ec0d6fc81a util.windows: Logging improved 2020-07-28 11:58:23 +04:00
5d51ea63ed Storage logging improved 2020-07-27 20:11:21 +04:00
67e3a18547 Logging format changed (separator is now '|') 2020-07-27 20:07:51 +04:00
93017b2766 Structured logging improved (message list expanded) 2020-07-27 20:06:43 +04:00
18aae2995b Improved logging of plugins 2020-07-24 15:48:17 +04:00
3e2e90be5b Backend improved with numbered log messages 2020-07-24 14:02:16 +04:00
955d15622f Log message list expanded 2020-07-24 14:02:16 +04:00
4bf59442ac Log message fixes 2020-07-24 14:02:16 +04:00
749ce49bf5 Improved logging in gpoa and gpupdate utilities 2020-07-24 14:02:16 +04:00
bbb46f941f Switch to new log message format 2020-07-24 14:02:16 +04:00
d608864f8a Initial debugging improved 2020-07-24 14:02:16 +04:00
24bce0f38b Expand the list of log messages 2020-07-24 14:02:15 +04:00
NIR
efa5573d6c Merge pull request #100 from altlinux/exception_handling
util.exceptions.geterr() - function to fetch erxception information
2020-07-24 14:01:32 +04:00
118a0ad398 util.exceptions.geterr() - function to fetch erxception information 2020-07-24 14:00:42 +04:00
NIR
70af7e9504 Merge pull request #96 from altlinux/ntp_applier
NTP applier
2020-07-24 13:43:52 +04:00
NIR
7b3e2b7968 Merge pull request #94 from altlinux/firewall_applier_switch_support
Support for Windows firewall switch added
2020-07-23 17:29:13 +04:00
f22de34634 NTP applier logic fix 2020-07-23 17:22:36 +04:00
149f929616 NTP applier expanded with simple start mechanism 2020-07-21 18:54:40 +04:00
211568e345 NTP applier enabled 2020-07-20 16:13:37 +04:00
3b70363fbe NTP applier stub added 2020-07-20 16:13:28 +04:00
0cb273a7d0 Utility module for wallpapers added 2020-07-13 16:10:53 +04:00
26772794a5 GSettings applier: functionality to apply PReg settings to GSettings added 2020-07-13 16:03:39 +04:00
8219013b8a GSettingsMapping - class to map Windows registry keys to GSettings 2020-07-13 16:02:45 +04:00
eeb8c3ce52 GSettings property application mechanism fixes and expansion 2020-07-13 16:00:22 +04:00
ace949d4ec glib_map function to convert Python data to GLib (GSettings) type system 2020-07-13 15:59:10 +04:00
NIR
a37d87ae98 Merge pull request #47 from altlinux/sid_module
Module to operate on SIDs
2020-07-06 17:24:55 +04:00
63e7dab1ee Appliers for GSettings expanded with Windows key support 2020-07-03 23:41:42 +04:00
6f68917355 Apply firewall rules only if firewall is explicitly enabled 2020-07-03 18:23:42 +04:00
a6defe8c41 Support for Windows firewall switch added 2020-07-03 17:09:15 +04:00
48532716ac 0.7.0-alt1
- Add multiple appliers, part of which marks as experimental yet
2020-07-01 01:40:53 +04:00
e8f72eeab4 Merge pull request #93 from altlinux/kinit_check_retcode
Check kinit and kdestroy for errors
2020-07-01 01:17:16 +04:00
5525cf014e Avoid printing about setting log level 2020-07-01 01:00:07 +04:00
18422ff986 Set kdestroy more silent
Avoid kdestroy output like:
Other credential caches present, use -A to destroy all
2020-07-01 01:00:06 +04:00
3c061169bc Replace KRB5CCNAME to machine_kinit() 2020-07-01 00:59:57 +04:00
883ee62017 Check kinit and kdestroy for errors 2020-06-30 21:16:29 +04:00
adf4ca4614 Merge pull request #92 from altlinux/switch_fixes
Switch fixes
2020-06-30 20:21:53 +04:00
a36fe32e05 Fixed polkit_applier_user module experimental check 2020-06-30 20:17:31 +04:00
3b258a5b71 Module enablement flag fix 2020-06-30 20:16:02 +04:00
NIR
7a90f3c0e6 Merge pull request #79 from altlinux/firewall_applier
Firewall applier
2020-06-30 17:56:49 +04:00
272785a780 Firewall applier logging improved 2020-06-30 17:55:56 +04:00
95e2f5dbb1 Feature switch for firewall applier 2020-06-30 17:47:15 +04:00
6df2f45b89 Fastfix for port opening 2020-06-30 17:42:52 +04:00
beb3ae9359 Firewall applier working with host mode 2020-06-30 17:42:52 +04:00
0f8db2fdcb frontend.appliers.firewall_rule: fix comment length 2020-06-30 17:42:52 +04:00
12516b2a4b Firewall rule wrapper initial implementation 2020-06-30 17:42:52 +04:00
a92d6c25b9 Enabled firewall applier 2020-06-30 17:42:52 +04:00
283825ecc2 Initial firewall applier implementation 2020-06-30 17:42:51 +04:00
2369384a2a Firewall applier stub 2020-06-30 17:42:48 +04:00
NIR
d1e9c31bef Merge pull request #88 from altlinux/experimental_feature
Experimental frontend modules' switch
2020-06-30 17:40:33 +04:00
NIR
63e4dd0767 Merge pull request #91 from altlinux/fix_ownership_problem
Fix TALLOC ownership problem in PReg parser
2020-06-30 17:38:56 +04:00
d0c13547a4 Loglevel changed 2020-06-30 16:07:05 +04:00
5880ac963e Fix TALLOC ownership problem in PReg parser
This commit fixes the following error:

```
2020-06-30 00:18:25:add_hkcu_entry: S-1-5-21-1609667327-4120075585-2415302043-1109 Software\BaseALT\Policies\GPUpdate 0 4
Traceback (most recent call last):
  File "/usr/sbin/gpoa", line 145, in <module>
    main()
  File "/usr/sbin/gpoa", line 140, in main
    controller.run()
  File "/usr/sbin/gpoa", line 97, in run
    self.start_backend()
  File "/usr/sbin/gpoa", line 115, in start_backend
    back.retrieve_and_store()
  File "/usr/lib/python3/site-packages/gpoa/backend/samba_backend.py", line 71, in retrieve_and_store
    gptobj.merge()
  File "/usr/lib/python3/site-packages/gpoa/gpt/gpt.py", line 219, in merge
    preference_merger(self.storage, self.sid, preference_objects, self.name)
  File "/usr/lib/python3/site-packages/gpoa/gpt/polfile.py", line 31, in merge_polfile
    storage.add_hkcu_entry(entry, sid, policy_name)
  File "/usr/lib/python3/site-packages/gpoa/storage/sqlite_registry.py", line 242, in add_hkcu_entry
    valname = preg_entry.valuename
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd2 in position 0: invalid continuation byte
```

This is the critical bug related to ownership problem in objects
managed by LIBTALLOC which leads to object destruction and freeing the
memory used by the object (and also corruption of this memory).

It is needed to transfer memory ownership from TALLOC to Python ASAP so
we're creating Python-managed object and copy the data into it.
2020-06-30 00:51:05 +04:00
d4119b47d4 Fixes for module checks 2020-06-29 21:18:29 +04:00
daf074d8dd frontend_manager logging removed due to redundancy 2020-06-29 20:07:22 +04:00
e1df2dde54 chromium applier logging improved 2020-06-29 20:06:55 +04:00
e8c71aa913 cifs applier logging improved 2020-06-29 20:06:39 +04:00
3a16e6eb7e control applier logging improved 2020-06-29 20:06:23 +04:00
44ec5ecc93 cups applier logging improved 2020-06-29 20:06:05 +04:00
0352da6d58 firefox applier logging improved 2020-06-29 20:05:44 +04:00
ac2ec348f2 folder applier fixes and logging improvements 2020-06-29 20:05:28 +04:00
fadd7de447 gsettings applier fixes and logging improvement 2020-06-29 20:05:03 +04:00
7c753098cd package applier fix and logging improved 2020-06-29 20:04:30 +04:00
28637d099b polkit applier logging improved 2020-06-29 20:04:06 +04:00
e4f17f2704 shortcut applier logging improved 2020-06-29 20:03:45 +04:00
1be13ea8ed systemd applier logging improved 2020-06-29 20:03:23 +04:00
59743d7957 Return result! 2020-06-29 18:55:51 +04:00
2d27c5580a Merge pull request #90 from altlinux/hkcu_fix
sqlite_registry: Fixed samba_hkcu_preg mapping in query
2020-06-29 17:06:21 +04:00
23983f7316 sqlite_storage.get_hkcu_entry() 2020-06-29 16:59:30 +04:00
b616221562 sqlite_registry: Fixed samba_hkcu_preg mapping in query 2020-06-29 16:22:53 +04:00
292fd9238c frontend: Bunch of fixes to make it work 2020-06-29 16:21:17 +04:00
36d0a92247 frontend: All-in-one fixes for module switches 2020-06-29 12:59:22 +04:00
f680a3025e cups_applier: Run only if it is enabled 2020-06-26 20:18:23 +04:00
41f49e5bab firefox_applier: Run only if it is enabled 2020-06-26 20:17:45 +04:00
f5b94ed02d folder_applier: Run only if it is enabled 2020-06-26 20:17:09 +04:00
98b7346bf1 gsettings_applier: Run only if it is enabled 2020-06-26 20:16:44 +04:00
f275583874 shortcut_applier: Run only if it is enabled 2020-06-26 20:16:13 +04:00
7f8d495b1b systemd_applier: Run only in case it is enabled 2020-06-26 20:07:28 +04:00
00c1aaeb55 control_applier: Run module only in case it is enabled 2020-06-26 20:06:56 +04:00
08a09eca81 .gitignore updated 2020-06-26 18:30:06 +04:00
02aa2cdbc6 firefox_applier: Check if module is enabled via group policy 2020-06-26 18:29:23 +04:00
f460ba3ae6 cups_applier: Check if module is enabled via group policy 2020-06-26 18:28:58 +04:00
917f1b27fb control_applier: Check if module is enabled via group policy 2020-06-26 18:28:34 +04:00
9121534de3 folder_applier: Check if module is enabled via group policy 2020-06-26 18:27:47 +04:00
8230781e3d cifs_applier: Check if module is enabled via group policy 2020-06-26 18:27:03 +04:00
db200e2668 package_applier: Check if module is enabled via policy 2020-06-26 18:08:11 +04:00
a74e29d335 polkit_applier: Check if module is enabled via policy 2020-06-26 18:01:04 +04:00
023739444b shortcut_applier: Check if module is enabled via policy 2020-06-26 17:59:22 +04:00
eb8cf25690 systemd_applier: Check if module is enabled via policy 2020-06-26 17:58:16 +04:00
c0a2b13da5 cifs_applier: Check if module is enabled via policy 2020-06-26 17:56:59 +04:00
1f09cb2c2d chromium_applier: Check if module is enabled via policy 2020-06-26 17:55:58 +04:00
08abbc57a9 Frontend functions to check for experimental modules added 2020-06-26 17:52:43 +04:00
NIR
8c87a22ac0 Merge pull request #77 from altlinux/error_codes
Error codes
2020-06-26 14:34:23 +04:00
343ef0d9ff Merge pull request #85 from altlinux/krb5_fix
gpoa: Fixed KRB5CCNAME usage
2020-06-25 21:03:23 +04:00
7f88857227 gpoa: Fixed KRB5CCNAME usage 2020-06-25 20:36:11 +04:00
d155769cda Merge pull request #83 from altlinux/polkit_user
Add user policy implementation for deny all removable storages
2020-06-25 19:25:48 +04:00
NIR
3d1d21ffa1 Merge pull request #84 from altlinux/krb5cache_destroy
KRB5CCACHE destroy
2020-06-25 17:00:25 +04:00
21f4d9514e Improved SIGINT handling mechanism 2020-06-25 16:06:55 +04:00
49f900b2a9 gpoa: Use KRB5CCNAME implicitly 2020-06-25 14:57:42 +04:00
2803d2be72 Remove Kerberos 5 credential cache on interrupt 2020-06-25 14:45:47 +04:00
9bc426fa0c util.kerberos: Update for old Python3 compatibility 2020-06-25 14:39:01 +04:00
6f5db26688 Install cache directory for credentials 2020-06-25 14:33:15 +04:00
fa8c50f665 gpoa: Perform kdestroy on finish 2020-06-25 13:39:29 +04:00
02e4da1758 util.kerberos: machine_kdestroy() introduced, used specific krb5 cache path 2020-06-25 13:38:30 +04:00
NIR
004fc38962 Merge pull request #81 from altlinux/printer_applier
Enable Printers.xml parser and merger
2020-06-25 13:24:18 +04:00
1d3f4feec9 Add user policy implementation for deny all removable storages 2020-06-25 11:39:31 +04:00
20bafa3e58 Multiple fixes for CUPS applier 2020-06-23 16:29:26 +04:00
c0d82c6d22 gpt.gpt: Enable printer settings search 2020-06-23 16:29:13 +04:00
17e65a680d Merge pull request #82 from altlinux/autofs_use_correct_variable
Use USER variable instead of unstable AUTOFS_UID
2020-06-08 16:49:21 +04:00
76308961bd autofs: Ignore local permissions 2020-06-08 16:48:23 +04:00
1a1442386d Use USER variable instead of unstable AUTOFS_UID 2020-06-08 16:42:48 +04:00
158240614f cups_applier: Use pycups to manage printer connection 2020-06-04 19:20:48 +04:00
923bf039bf Enable Printers.xml parser and merger 2020-06-04 19:20:48 +04:00
NIR
bfd383659f Merge pull request #78 from altlinux/packages_nir
Another package install implementation
2020-06-04 19:16:03 +04:00
94e2c5fb59 Fixed package abstration object 2020-06-04 19:14:41 +04:00
433f4c063e Printer search disabled due to missing implementation 2020-06-04 19:13:14 +04:00
455bcf727b Package applier for user imported to frontend manager 2020-06-04 19:11:16 +04:00
7713e3c460 Package applier adjusted to work with multiline package entries 2020-06-04 19:10:41 +04:00
1cc6dbb784 Multiple fixes for package applier 2020-05-29 18:40:46 +04:00
319e0fcf1d Wiki index updated 2020-05-28 14:28:19 +04:00
b5b6fe7478 User part of package applier implemented 2020-05-25 18:28:54 +04:00
8132622866 frontend.appliers.rpm removed 2020-05-25 18:28:31 +04:00
3af33dca4b util.rpm.install_rpm(): Don't update package database implicitly 2020-05-25 18:20:28 +04:00
6c06638138 Use apt-shell for install/remove packages
Add small unit test
2020-05-25 18:19:05 +04:00
d9cad51f52 Add packages installation/removing 2020-05-25 18:19:02 +04:00
NIR
400ea0aced Merge pull request #75 from altlinux/folder_applier
Applier for Folders.xml
2020-05-25 16:10:25 +04:00
4fb6f83eb1 folder_applier enabled 2020-05-22 22:06:45 +04:00
0f6851601a folder_applier: Basic functionality implemented 2020-05-22 22:06:44 +04:00
89412cc8e4 frontend.appliers.folder - Folder object added for frontend 2020-05-22 22:06:44 +04:00
580bc0c6b2 gpt.folders - FileAction expanded to mention all CRUD operations 2020-05-22 22:06:36 +04:00
1332409dc2 Merge pull request #74 from altlinux/backend_refactoring
Backend refactoring
2020-05-22 20:28:04 +04:00
b6fb9abfa1 gpupdate: Use new messaging mechanism
This will allow user to see coded messages like:

```
[E00001]: Insufficient permissions to run gpupdate
[E00002]: gpupdate will not be started
```

Now you may easily refer to the specific error code/message when asking
for technical support.
2020-05-22 03:42:41 +04:00
7941157235 Introduce new messaging mechanism
The logging mechanism of gpupdate is in poor state at the moment.
This commit introduces Oracle-style messages with codes. The messages
are not format strings and may be easily localized and it is also
easy to refer to specific problem.

The original idea is to permanently assign error codes only expanding
code lists during time in order to deliver reliable product.
2020-05-22 03:38:49 +04:00
cb38da0e09 Frontend manager: Running machine appliers made a little bit more flexible
At the start of the project the demands were pretty simple and the
functionality required was rather restricted. Now we have some
architecture and demands are growing accordingly so this commit
eliminated hardcode in favor of iteration over flexible lists.
2020-05-21 16:48:08 +04:00
2b86ab87eb gpt.gpt: More modular approach to scanning GPT
At start I thought I will need to implement only one PReg
parser/interpreter and that's it. At the moment it is demanded all
parser to be implemented, all GPT settings covered and ADMX files to
be extended to work with Linux (ALT) specific cases.

Now I have more expertise in the domain so this PR doint simple thing
despite lots of code:

* There is "parser" for each file type in corresponding module.
* There is also "merger" for each object parsed from file in
  corresponding module.

We're doing this:

* Determine file type by its name (stupid and working!)
* Call the corresponding parser to get objects
* Call the corresponding merger for objects
2020-05-21 16:48:08 +04:00
6325382155 gpt.gpt: Eliminated unused functions
This commit fixes the problem of multiple unused functions in GPT
lurker. The most strange function here is obviously __str__ which was
used for testing purposes in early days but made obsolete because of
introduction of unit testing.
2020-05-21 16:48:08 +04:00
1665ab92f3 gpt.gpt: Use simplified settings search function 2020-05-21 16:48:08 +04:00
773b8d032f Implemented parser and merger for Folders.xml 2020-05-21 16:48:07 +04:00
6b8a1a4d0b gpt.drives: Drives merger implemented 2020-05-21 16:48:07 +04:00
59f2b3703f gpt.shortcuts: Shortcuts merger implemented 2020-05-21 16:48:07 +04:00
4892843c52 gpt.*: merger stubs implemented 2020-05-21 16:48:07 +04:00
61eec97b68 gpt.tasks: ScheduledTasks.xml parser and merger stub implemented 2020-05-21 16:48:07 +04:00
0696b68085 gpt.polfile: Polfile parser and merger implemented as part of GPT 2020-05-21 16:48:07 +04:00
c67e3b7674 gpt.gpt: Introduced functions to find file in Preferences directory
During research done about Active Directory and GPTs I found out that
There is no need for specialized search functions for each type of
Preference and GPTs has pretty simple structure so I implemented the
generalized way to find yet another XML file in "Preferences" directory.
2020-05-21 16:48:07 +04:00
9592100218 util.preg.merge_polfile(): Pass policy_name
The renewed storage requires Policy Name to be passed as well. This is
needed to simplify debugging of GPT merging problems.
2020-05-21 16:48:06 +04:00
1fab02a274 sqlite_registry: General refactoring
There were tons of duplicated code in storage related to object wipe
and upsert functionality. In general, all this commit does - duplicate
code elimination.

It is also `add_folder()` function added in order to store Folder.xml
object in the database.
2020-05-21 16:45:55 +04:00
c765077b2f storage.record_types: folder_entry added and general refactoring
The new architecture demands "Policy Name" to be passed along with
GPT object in order to ease debugging of GPT merge problems so
this commit introduces additional parameter to constructor of each
record type.

It is also `folder_entry` record type added.

During rework of large part of code I found out that lots of upsert
code were duplicated and contained update row constructors for
SQLAlchemy. I decided that record objects may know which fields it
is needed to update in case of UPDATE calls so I introduced
`update_fields()` function which returns update dictionary thus making
hardcoded row constructors in `sqlite_storage` obsolete.
2020-05-21 16:41:10 +04:00
baafb18971 Merge pull request #68 from altlinux/wipe_logic_fix
Wipe logic fix
2020-05-21 15:54:30 +04:00
8b7e8547e6 Catch exceptions from backend 2020-05-21 15:46:04 +04:00
19cb5c072a util.windows: Re-raise exceptions when working with domain
This is needed to prevent wipe of settings done by backend in case of
successful GPO retrieval by Samba. This commit re-raises exceptions
which must be caught by backend runner in `gpoa`.
2020-05-21 15:44:46 +04:00
a4607c75eb Remove duplicated requires to local-policy from spec 2020-05-20 20:51:19 +04:00
c6f0397f51 0.6.0-alt2
- Update system-policy PAM-rules (clean system-auth-common, add pam_env support)
- Add dependency to pam-config later than 1.9.0 release
2020-05-20 01:06:28 +04:00
01c7e29a87 0.6.0-alt1
- Add drives policy for shared folders with cifs applier using autofs
- Update shortcuts policy with xdg-users-dir support for DESKTOP
2020-05-15 23:29:18 +04:00
NIR
d36b92e49f Merge pull request #64 from altlinux/get_localized_desktop
Get localized path to Desktop directory
2020-05-15 23:06:03 +04:00
62f3c5dd96 Fix xdg-users-dirs getting for shortcuts 2020-05-15 22:57:51 +04:00
2f3fb8810e gpt/shortcuts: fix getting iconPath from properties 2020-05-15 19:33:58 +04:00
9ac94df0b5 shortcut_applier: skip shortcut with no substituted variables 2020-05-15 18:47:38 +04:00
43c1728a43 shortcut_applier: append HOME substitution to path if it not absolute 2020-05-15 18:46:44 +04:00
5de5ad9801 Merge pull request #67 from altlinux/wipe_drives
Wipe drives along with other tables
2020-05-15 15:51:07 +04:00
05f13610bf Wipe drives along with other tables 2020-05-15 00:37:32 +04:00
8fce5e7dd6 Strange fixes for shortcut address resolution 2020-05-15 00:13:27 +04:00
ee81010f82 Added icon name support for .desktop files 2020-05-14 23:37:32 +04:00
9d40910890 Merge pull request #59 from altlinux/share_automount
Autofs/CIFS applier for mounting Samba shares
2020-05-14 23:27:02 +04:00
8b322748a7 CIFS applier file cleanup 2020-05-14 23:20:12 +04:00
338cc5d80f Don't generate autofs configuration if no drive mappings present 2020-05-14 22:57:17 +04:00
8c88f825dc cifs_applier: restart autofs daemon on configuration change 2020-05-14 22:19:53 +04:00
047d72dbd1 Added autofs dependency for CIFS applier 2020-05-14 22:18:31 +04:00
70c233a0df Add newline when writing to /etc/auto.master 2020-05-14 22:12:02 +04:00
e864235761 autofs templates' fixes 2020-05-14 21:53:39 +04:00
7d01e331fe cifs_applier typo fixes 2020-05-14 21:53:12 +04:00
ccd429a632 CIFS applier imporved with map support 2020-05-14 21:30:24 +04:00
e6d9867443 Fixed storage upsert for drives 2020-05-14 21:29:56 +04:00
87f21867f6 Drives.xml parser improved 2020-05-14 21:29:18 +04:00
f3b1b68f87 Drives.xml finding mechanism improvement 2020-05-14 21:28:49 +04:00
5012917412 autofs templates expanded 2020-05-14 21:28:08 +04:00
816c40ce40 cifs_applier: Fixed template name 2020-05-14 19:46:06 +04:00
6843690340 gpupdate_mount template 2020-05-14 19:39:53 +04:00
b3fa7f2868 cifs_applier: get_homedir imported 2020-05-14 19:33:21 +04:00
6d45289e1a gpt.gpt: Don't check if path exists 2020-05-14 19:24:12 +04:00
fd7fc8cb1f cifs_applier: Expand variables for templating 2020-05-14 19:03:22 +04:00
5f516d2726 cifs_applier: Imported missing 'os' module 2020-05-14 18:52:58 +04:00
6d3904ea93 Password decryption for Drives.xml fixed 2020-05-14 18:52:16 +04:00
fd42505f33 CIFS applier refactored 2020-05-14 18:17:08 +04:00
1762a23a22 Merge pull request #62 from altlinux/exclude_tests
Excludes for unit tests added
2020-05-14 17:02:43 +04:00
6170a0dd85 util.xdg.xdg_get_desktop_user(): Function to resolve localized path to Desktop directory 2020-05-14 16:11:08 +04:00
087248d172 util.windows.expand_windows_var(): The default(machine) HOME is /etc/skel 2020-05-14 16:02:48 +04:00
2bd533acb8 util.users.with_privileges(): return function result 2020-05-14 16:02:01 +04:00
d96b28025d Fix typo in Drives.xml parser 2020-05-12 22:48:17 +04:00
d240ff2542 CIFS applier implemented 2020-05-12 22:43:13 +04:00
a12d771efd Excludes for unit tests added 2020-05-12 01:15:49 +04:00
bf22d58139 Unit test for Drives.xml parser 2020-05-12 01:08:11 +04:00
963fd22f83 CIFS applier enabled in frontend manager 2020-05-12 00:26:43 +04:00
2053e26c53 CIFS applier stub implemented 2020-05-12 00:26:26 +04:00
4d28a42120 Storage expanded to work with drives 2020-05-12 00:26:04 +04:00
bf9eeb22eb Drive.xml parser expanded 2020-05-12 00:25:33 +04:00
ad3624d73e Merge pull request #53 from altlinux/active_policy
gpupdate-setup: add default-policy and update active-policy commands
2020-05-06 23:21:15 +04:00
957823b264 gpupdate-setup: fix active directory domain controller detection 2020-05-06 23:06:23 +04:00
4f1c45970c storage.record_types: Stub for drive record added 2020-05-06 13:43:52 +04:00
bfb68aa483 storage.sqlite_registry: Table for Drive mappings added 2020-05-06 13:43:27 +04:00
fc7cc603cf CIFS applier slightly expanded 2020-05-06 13:42:42 +04:00
4e57823983 gpt.gpt: Functionality to find Drives.xml added 2020-05-06 13:40:54 +04:00
faf9e9a8cf Stub for autofs/CIFS applier 2020-04-28 16:47:06 +04:00
9bcff54817 gpupdate-setup: add default-policy and update active-policy commands
Command active-policy revert only name now and "unknown" if policy is not setted.
2020-04-23 01:19:20 +04:00
8c7d106191 Module to operate on SIDs 2020-04-21 22:07:36 +04:00
72 changed files with 3315 additions and 604 deletions

1
.gitignore vendored
View File

@ -2,4 +2,5 @@ __pycache__
*~
_opam
_build
*.pyc

23
dist/gpupdate-setup vendored
View File

@ -50,7 +50,7 @@ def get_default_policy_name():
dcpolicy = 'ad-domain-controller'
try:
if smbopt.get_server_role() == 'active directory domain controller':
if smbopts().get_server_role() == 'active directory domain controller':
return dcpolicy
except:
pass
@ -86,6 +86,8 @@ def parse_arguments():
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')
@ -144,17 +146,17 @@ def get_status():
return os.path.islink(systemd_unit_link)
def get_active_policy():
policy_dir = '/usr/share/local-policy'
def get_active_policy_name():
etc_policy_dir = '/etc/local-policy'
default_policy_name = os.path.join(policy_dir, get_default_policy_name())
actual_policy_name = 'unknown'
active_policy_name = os.path.join(etc_policy_dir, 'active')
active_policy_path = os.path.join(etc_policy_dir, 'active')
actual_policy_name = os.path.realpath(default_policy_name)
if os.path.islink(active_policy_path):
active_policy_path = os.path.realpath(active_policy_path)
if os.path.isdir(active_policy_name):
actual_policy_name = os.path.realpath(active_policy_name)
if os.path.isdir(active_policy_path):
actual_policy_name = os.path.basename(active_policy_path)
return actual_policy_name
@ -221,7 +223,10 @@ def main():
disable_gp()
if arguments.action == 'active-policy':
print(get_active_policy())
print(get_active_policy_name())
if arguments.action == 'default-policy':
print(get_default_policy_name())
if __name__ == '__main__':
main()

View File

@ -1,5 +1,4 @@
#%PAM-1.0
session required pam_mktemp.so
session required pam_mkhomedir.so silent
session required pam_limits.so
-session required pam_oddjob_gpupdate.so
session optional pam_env.so user_readenv=1 conffile=/etc/gpupdate/environment user_envfile=.gpupdate_environment

View File

@ -16,11 +16,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from util.windows import smbcreds
from .samba_backend import samba_backend
from .nodomain_backend import nodomain_backend
from util.logging import log
def backend_factory(dc, username, is_machine, no_domain = False):
'''
@ -36,17 +36,20 @@ def backend_factory(dc, username, is_machine, no_domain = False):
domain = sc.get_domain()
if domain:
logging.debug('Initialize Samba backend for domain: {}'.format(domain))
ldata = dict({'domain': domain})
log('D9', ldata)
try:
back = samba_backend(sc, username, domain, is_machine)
except Exception as exc:
logging.error('Unable to initialize Samba backend: {}'.format(exc))
logdata = dict({'error': str(exc)})
log('E7', logdata)
else:
logging.debug('Initialize local backend with no domain')
log('D8')
try:
back = nodomain_backend()
except Exception as exc:
logging.error('Unable to initialize no-domain backend: {}'.format(exc))
logdata = dict({'error': str(exc)})
log('E8', logdata)
return back

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
# Facility to determine GPTs for user
from samba.gpclass import check_safe_path, check_refresh_gpo_list
@ -30,7 +29,7 @@ from util.util import (
)
from util.windows import get_sid
import util.preg
from util.logging import slogm
from util.logging import log
class samba_backend(applier_backend):
@ -57,7 +56,8 @@ class samba_backend(applier_backend):
self.sambacreds = sambacreds
self.cache_dir = self.sambacreds.get_cache_dir()
logging.debug(slogm('Cache directory is: {}'.format(self.cache_dir)))
logdata = dict({'cachedir': self.cache_dir})
log('D7', logdata)
def retrieve_and_store(self):
'''
@ -68,7 +68,12 @@ class samba_backend(applier_backend):
self.storage.wipe_hklm()
self.storage.wipe_user(self.storage.get_info('machine_sid'))
for gptobj in machine_gpts:
gptobj.merge()
try:
gptobj.merge()
except Exception as exc:
logdata = dict()
logdata['msg'] = str(exc)
log('E26', logdata)
# Load user GPT values in case user's name specified
# This is a buggy implementation and should be tested more
@ -76,7 +81,12 @@ class samba_backend(applier_backend):
user_gpts = self._get_gpts(self.username, self.sid)
self.storage.wipe_user(self.sid)
for gptobj in user_gpts:
gptobj.merge()
try:
gptobj.merge()
except Exception as exc:
logdata = dict()
logdata['msg'] = str(exc)
log('E27', logdata)
def _check_sysvol_present(self, gpo):
'''
@ -86,19 +96,23 @@ class samba_backend(applier_backend):
# GPO named "Local Policy" has no entry by its nature so
# no reason to print warning.
if 'Local Policy' != gpo.name:
logging.warning(slogm('No SYSVOL entry assigned to GPO {}'.format(gpo.name)))
logdata = dict({'gponame': gpo.name})
log('W4', logdata)
return False
return True
def _get_gpts(self, username, sid):
gpts = list()
log('D45')
# util.windows.smbcreds
gpos = self.sambacreds.update_gpos(username)
log('D46')
for gpo in gpos:
if self._check_sysvol_present(gpo):
logging.debug(slogm('Found SYSVOL entry "{}" for GPO "{}"'.format(gpo.file_sys_path, gpo.display_name)))
path = check_safe_path(gpo.file_sys_path).upper()
logging.debug(slogm('Path: {}'.format(path)))
slogdata = dict({'sysvol_path': gpo.file_sys_path, 'gpo_name': gpo.display_name, 'gpo_path': path})
log('D30', slogdata)
gpt_abspath = os.path.join(self.cache_dir, 'gpo_cache', path)
obj = gpt(gpt_abspath, sid)
obj.set_name(gpo.display_name)

View File

@ -18,6 +18,51 @@
from abc import ABC
import logging
from util.logging import slogm
def check_experimental_enabled(storage):
experimental_enable_flag = 'Software\\BaseALT\\Policies\\GPUpdate\\GlobalExperimental'
flag = storage.get_hklm_entry(experimental_enable_flag)
result = False
if flag and '1' == flag.data:
result = True
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)
flag = storage.get_hklm_entry(gpupdate_module_flag)
result = None
if flag:
if '1' == flag.data:
result = True
if '0' == flag.data:
result = False
return result
def check_enabled(storage, module_name, is_experimental):
module_enabled = check_module_enabled(storage, module_name)
exp_enabled = check_experimental_enabled(storage)
result = False
if None == module_enabled:
if is_experimental and exp_enabled:
result = True
if not is_experimental:
result = True
else:
result = module_enabled
return result
class applier_frontend(ABC):
@classmethod
def __init__(self, regobj):

View File

@ -0,0 +1,97 @@
#
# 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 enum import Enum
import subprocess
def getprops(param_list):
props = dict()
for entry in param_list:
lentry = entry.lower()
if lentry.startswith('action'):
props['action'] = lentry.rpartition('=')[2]
if lentry.startswith('protocol'):
props['protocol'] = lentry.rpartition('=')[2]
if lentry.startswith('dir'):
props['dir'] = lentry.rpartition('=')[2]
return props
def get_ports(param_list):
portlist = list()
for entry in param_list:
lentry = entry.lower()
if lentry.startswith('lport'):
port = lentry.rpartition('=')[2]
portlist.append(port)
return portlist
class PortState(Enum):
OPEN = 'Allow'
CLOSE = 'Deny'
class Protocol(Enum):
TCP = 'tcp'
UDP = 'udp'
class FirewallMode(Enum):
ROUTER = 'router'
GATEWAY = 'gateway'
HOST = 'host'
# This shi^Wthing named alterator-net-iptables is unable to work in
# multi-threaded environment
class FirewallRule:
__alterator_command = '/usr/bin/alterator-net-iptables'
def __init__(self, data):
data_array = data.split('|')
self.version = data_array[0]
self.ports = get_ports(data_array[1:])
self.properties = getprops(data_array[1:])
def apply(self):
tcp_command = []
udp_command = []
for port in self.ports:
tcp_port = '{}'.format(port)
udp_port = '{}'.format(port)
if PortState.OPEN.value == self.properties['action']:
tcp_port = '+' + tcp_port
udp_port = '+' + udp_port
if PortState.CLOSE.value == self.properties['action']:
tcp_port = '-' + tcp_port
udp_port = '-' + udp_port
portcmd = [
self.__alterator_command
, 'write'
, '-m', FirewallMode.HOST.value
, '-t', tcp_port
, '-u', udp_port
]
proc = subprocess.Popen(portcmd)
proc.wait()

View File

@ -0,0 +1,72 @@
#
# 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 pathlib import Path
from gpt.folders import (
FileAction
, action_letter2enum
)
def remove_dir_tree(path, delete_files=False, delete_folder=False, delete_sub_folders=False):
for entry in path.iterdir():
if entry.is_file():
entry.unlink()
if entry.is_dir():
if delete_sub_folders:
remove_dir_tree(entry,
delete_files,
delete_folder,
delete_sub_folders)
if delete_folder:
path.rmdir()
def str2bool(boolstr):
if boolstr.lower in ['true', 'yes', '1']:
return True
return False
class Folder:
def __init__(self, folder_object):
self.folder_path = Path(folder_object.path)
self.action = action_letter2enum(folder_object.action)
self.delete_files = str2bool(folder_object.delete_files)
self.delete_folder = str2bool(folder_object.delete_folder)
self.delete_sub_folders = str2bool(folder_object.delete_sub_folders)
def _create_action(self):
self.folder_path.mkdir(parents=True, exist_ok=True)
def _delete_action(self):
remove_dir_tree(self.folder_path,
self.delete_files,
self.delete_folders,
self.delete_sub_folders)
def action(self):
if self.action == FileAction.CREATE:
self._create_action()
if self.action == FileAction.DELETE:
self._delete_action()
if self.action == FileAction.REPLACE:
self._delete_action()
self._create_action()

View File

@ -46,19 +46,39 @@ class system_gsetting:
with open(self.file_path, 'w') as f:
config.write(f)
def glib_map(value, glib_type):
result_value = value
if glib_type == 'i':
result_value = GLib.Variant(glib_type, int(value))
else:
result_value = GLib.Variant(glib_type, value)
return result_value
class user_gsetting:
def __init__(self, schema, path, value):
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):
source = Gio.SettingsSchemaSource.get_default()
schema = source.lookup(self.schema, True)
key = schema.get_key(self.path)
gvformat = key.get_value_type()
val = GLib.Variant(gvformat.dup_string(), self.value)
schema.set_value(self.path, val)
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)
# 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)):

View File

@ -28,11 +28,15 @@ class polkit:
__template_loader = jinja2.FileSystemLoader(searchpath=__template_path)
__template_environment = jinja2.Environment(loader=__template_loader)
def __init__(self, template_name, arglist):
def __init__(self, template_name, arglist, username=None):
self.template_name = template_name
self.args = arglist
self.username = username
self.infilename = '{}.rules.j2'.format(self.template_name)
self.outfile = os.path.join(self.__policy_dir, '{}.rules'.format(self.template_name))
if self.username:
self.outfile = os.path.join(self.__policy_dir, '{}.{}.rules'.format(self.template_name, self.username))
else:
self.outfile = os.path.join(self.__policy_dir, '{}.rules'.format(self.template_name))
def generate(self):
try:

View File

@ -0,0 +1,23 @@
#
# 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 enum import Enum
class WallpaperStretchMode(Enum):
STRETCH = 2

View File

@ -16,7 +16,10 @@
# 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
from .applier_frontend import (
applier_frontend
, check_enabled
)
import logging
import json
@ -26,6 +29,9 @@ from util.logging import slogm
from util.util import is_machine_name
class chromium_applier(applier_frontend):
__module_name = 'ChromiumApplier'
__module_enabled = False
__module_experimental = True
__registry_branch = 'Software\\Policies\\Google\\Chrome'
__managed_policies_path = '/etc/chromium/policies/managed'
__recommended_policies_path = '/etc/chromium/policies/recommended'
@ -39,6 +45,11 @@ class chromium_applier(applier_frontend):
self.username = username
self._is_machine_name = is_machine_name(self.username)
self.policies = dict()
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def get_hklm_string_entry(self, hive_subkey):
query_str = '{}\\{}'.format(self.__registry_branch, hive_subkey)
@ -131,7 +142,12 @@ class chromium_applier(applier_frontend):
'''
All actual job done here.
'''
self.machine_apply()
if self.__module_enabled:
logging.debug(slogm('Running Chromium applier for machine'))
self.machine_apply()
else:
logging.debug(slogm('Chromium applier for machine will not be started'))
#if not self._is_machine_name:
# logging.debug('Running user applier for Chromium')
# self.user_apply()

View File

@ -0,0 +1,164 @@
#
# 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 fileinput
import jinja2
import os
import subprocess
import logging
from pathlib import Path
from .applier_frontend import (
applier_frontend
, check_enabled
)
from gpt.drives import json2drive
from util.util import get_homedir
from util.logging import slogm
def storage_get_drives(storage, sid):
drives = storage.get_drives(sid)
drive_list = list()
for drv_obj in drives:
drive_list.append(drv_obj)
return drive_list
def add_line_if_missing(filename, ins_line):
with open(filename, 'r+') as f:
for line in f:
if ins_line == line.strip():
break
else:
f.write(ins_line + '\n')
f.flush()
class cifs_applier(applier_frontend):
def __init__(self, storage):
pass
def apply(self):
pass
class cifs_applier_user(applier_frontend):
__module_name = 'CIFSApplierUser'
__module_enabled = False
__module_experimental = True
__auto_file = '/etc/auto.master'
__auto_dir = '/etc/auto.master.gpupdate.d'
__template_path = '/usr/share/gpupdate/templates'
__template_mountpoints = 'autofs_mountpoints.j2'
__template_identity = 'autofs_identity.j2'
__template_auto = 'autofs_auto.j2'
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
self.home = get_homedir(username)
conf_file = '{}.conf'.format(sid)
autofs_file = '{}.autofs'.format(sid)
cred_file = '{}.creds'.format(sid)
self.auto_master_d = Path(self.__auto_dir)
self.user_config = self.auto_master_d / conf_file
if os.path.exists(self.user_config.resolve()):
self.user_config.unlink()
self.user_autofs = self.auto_master_d / autofs_file
if os.path.exists(self.user_autofs.resolve()):
self.user_autofs.unlink()
self.user_creds = self.auto_master_d / cred_file
self.mount_dir = Path(os.path.join(self.home, 'net'))
self.drives = storage_get_drives(self.storage, self.sid)
self.template_loader = jinja2.FileSystemLoader(searchpath=self.__template_path)
self.template_env = jinja2.Environment(loader=self.template_loader)
self.template_mountpoints = self.template_env.get_template(self.__template_mountpoints)
self.template_indentity = self.template_env.get_template(self.__template_identity)
self.template_auto = self.template_env.get_template(self.__template_auto)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def user_context_apply(self):
'''
Nothing to implement.
'''
pass
def __admin_context_apply(self):
# Create /etc/auto.master.gpupdate.d directory
self.auto_master_d.mkdir(parents=True, exist_ok=True)
# Create user's destination mount directory
self.mount_dir.mkdir(parents=True, exist_ok=True)
# Add pointer to /etc/auto.master.gpiupdate.d in /etc/auto.master
auto_destdir = '+dir:{}'.format(self.__auto_dir)
add_line_if_missing(self.__auto_file, auto_destdir)
# Collect data for drive settings
drive_list = list()
for drv in self.drives:
drive_settings = dict()
drive_settings['dir'] = drv.dir
drive_settings['login'] = drv.login
drive_settings['password'] = drv.password
drive_settings['path'] = drv.path.replace('\\', '/')
drive_list.append(drive_settings)
if len(drive_list) > 0:
mount_settings = dict()
mount_settings['drives'] = drive_list
mount_text = self.template_mountpoints.render(**mount_settings)
with open(self.user_config.resolve(), 'w') as f:
f.truncate()
f.write(mount_text)
f.flush()
autofs_settings = dict()
autofs_settings['home_dir'] = self.home
autofs_settings['mount_file'] = self.user_config.resolve()
autofs_text = self.template_auto.render(**autofs_settings)
with open(self.user_autofs.resolve(), 'w') as f:
f.truncate()
f.write(autofs_text)
f.flush()
subprocess.check_call(['/bin/systemctl', 'restart', 'autofs'])
def admin_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running CIFS applier for user in administrator context'))
self.__admin_context_apply()
else:
logging.debug(slogm('CIFS applier for user in administrator context will not be started'))

View File

@ -16,24 +16,32 @@
# 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
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.control import control
from util.logging import slogm
import logging
class control_applier(applier_frontend):
__module_name = 'ControlApplier'
__module_experimental = False
__module_enabled = True
_registry_branch = 'Software\\BaseALT\\Policies\\Control'
def __init__(self, storage):
self.storage = storage
self.control_settings = self.storage.filter_hklm_entries('Software\\BaseALT\\Policies\\Control%')
self.controls = list()
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def apply(self):
'''
Trigger control facility invocation.
'''
def run(self):
for setting in self.control_settings:
valuename = setting.hive_key.rpartition('\\')[2]
try:
@ -49,3 +57,13 @@ class control_applier(applier_frontend):
for cont in self.controls:
cont.set_control_status()
def apply(self):
'''
Trigger control facility invocation.
'''
if self.__module_enabled:
logging.debug(slogm('Running Control applier for machine'))
self.run()
else:
logging.debug(slogm('Control applier for machine will not be started'))

View File

@ -18,8 +18,14 @@
import logging
import os
import json
from .applier_frontend import applier_frontend
import cups
from .applier_frontend import (
applier_frontend
, check_enabled
)
from gpt.printers import json2printer
from util.rpm import is_rpm_installed
from util.logging import slogm
@ -32,42 +38,81 @@ def storage_get_printers(storage, sid):
printers = list()
for prnj in printer_objs:
prn_obj = json2printer(prnj)
printers.append(prn_obj)
printers.append(prnj)
return printers
def write_printer(prn):
def connect_printer(connection, prn):
'''
Dump printer cinfiguration to disk as CUPS config
'''
printer_config_path = os.path.join('/etc/cups', prn.name)
with open(printer_config_path, 'r') as f:
print(prn.cups_config(), file=f)
# PPD file location
printer_driver = 'generic'
pjson = json.loads(prn.printer)
printer_parts = pjson['printer']['path'].partition(' ')
# Printer queue name in CUPS
printer_name = printer_parts[2].replace('(', '').replace(')', '')
# Printer description in CUPS
printer_info = printer_name
printer_uri = printer_parts[0].replace('\\', '/')
printer_uri = 'smb:' + printer_uri
connection.addPrinter(
name=printer_name
, info=printer_info
, device=printer_uri
#filename=printer_driver
)
class cups_applier(applier_frontend):
__module_name = 'CUPSApplier'
__module_experimental = True
__module_enabled = False
def __init__(self, storage):
self.storage = storage
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
if not is_rpm_installed('cups'):
logging.warning(slogm('CUPS is not installed: no printer settings will be deployed'))
return
self.cups_connection = cups.Connection()
self.printers = storage_get_printers(self.storage, self.storage.get_info('machine_sid'))
if self.printers:
for prn in self.printers:
connect_printer(self.cups_connection, prn)
def apply(self):
'''
Perform configuration of printer which is assigned to computer.
'''
if not is_rpm_installed('cups'):
logging.warning(slogm('CUPS is not installed: no printer settings will be deployed'))
return
printers = storage_get_printers(self.storage, self.storage.get_info('machine_sid'))
if printers:
for prn in printers:
write_printer(prn)
if self.__module_enabled:
logging.debug(slogm('Running CUPS applier for machine'))
self.run()
else:
logging.debug(slogm('CUPS applier for machine will not be started'))
class cups_applier_user(applier_frontend):
__module_name = 'CUPSApplierUser'
__module_experimental = True
__module_enabled = False
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_enabled
)
def user_context_apply(self):
'''
@ -76,17 +121,25 @@ class cups_applier_user(applier_frontend):
'''
pass
def admin_context_apply(self):
'''
Perform printer configuration assigned for user.
'''
def run(self):
if not is_rpm_installed('cups'):
logging.warning(slogm('CUPS is not installed: no printer settings will be deployed'))
return
printers = storage_get_printers(self.storage, self.sid)
self.cups_connection = cups.Connection()
self.printers = storage_get_printers(self.storage, self.sid)
if printers:
for prn in printers:
write_printer(prn)
if self.printers:
for prn in self.printers:
connect_printer(self.cups_connection, prn)
def admin_context_apply(self):
'''
Perform printer configuration assigned for user.
'''
if self.__module_enabled:
logging.debug(slogm('Running CUPS applier for user in administrator context'))
self.run()
else:
logging.debug(slogm('CUPS applier for user in administrator context will not be started'))

View File

@ -30,11 +30,17 @@ import json
import os
import configparser
from .applier_frontend import applier_frontend
from .applier_frontend import (
applier_frontend
, check_enabled
)
from util.logging import slogm
from util.util import is_machine_name
class firefox_applier(applier_frontend):
__module_name = 'FirefoxApplier'
__module_experimental = True
__module_enabled = False
__registry_branch = 'Software\\Policies\\Mozilla\\Firefox'
__firefox_installdir = '/usr/lib64/firefox/distribution'
__user_settings_dir = '.mozilla/firefox'
@ -46,6 +52,11 @@ class firefox_applier(applier_frontend):
self._is_machine_name = is_machine_name(self.username)
self.policies = dict()
self.policies_json = dict({ 'policies': self.policies })
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def get_profiles(self):
'''
@ -137,7 +148,11 @@ class firefox_applier(applier_frontend):
logging.debug(slogm('Found Firefox profile in {}/{}'.format(profiledir, profile)))
def apply(self):
self.machine_apply()
if self.__module_enabled:
logging.debug(slogm('Running Firefox applier for machine'))
self.machine_apply()
else:
logging.debug(slogm('Firefox applier for machine will not be started'))
#if not self._is_machine_name:
# logging.debug('Running user applier for Firefox')
# self.user_apply()

View File

@ -0,0 +1,65 @@
#
# 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 logging
import subprocess
from util.logging import slogm
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.firewall_rule import FirewallRule
class firewall_applier(applier_frontend):
__module_name = 'FirewallApplier'
__module_experimental = True
__module_enabled = False
__firewall_branch = 'SOFTWARE\\Policies\\Microsoft\\WindowsFirewall\\FirewallRules'
__firewall_switch = 'SOFTWARE\\Policies\\Microsoft\\WindowsFirewall\\DomainProfile\\EnableFirewall'
__firewall_reset_cmd = ['/usr/bin/alterator-net-iptables', 'reset']
def __init__(self, storage):
self.storage = storage
self.firewall_settings = self.storage.filter_hklm_entries('{}%'.format(self.__firewall_branch))
self.firewall_enabled = self.storage.get_hklm_entry(self.__firewall_switch)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
for setting in self.firewall_settings:
rule = FirewallRule(setting.data)
rule.apply()
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Firewall applier for machine'))
if '1' == self.firewall_enabled:
logging.debug(slogm('Firewall is enabled'))
self.run()
else:
logging.debug(slogm('Firewall is disabled, settings will be reset'))
proc = subprocess.Popen(self.__firewall_reset_cmd)
proc.wait()
else:
logging.debug(slogm('Firewall applier will not be started'))

View File

@ -0,0 +1,84 @@
#
# 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 pathlib import Path
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.folder import Folder
from util.logging import slogm
import logging
class folder_applier(applier_frontend):
__module_name = 'FoldersApplier'
__module_experimental = False
__module_enabled = True
def __init__(self, storage, sid):
self.storage = storage
self.sid = sid
self.folders = self.storage.get_folders(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 Folder applier for machine'))
for directory_obj in self.folders:
fld = Folder(directory_obj)
fld.action()
else:
logging.debug(slogm('Folder applier for machine will not be started'))
class folder_applier_user(applier_frontend):
__module_name = 'FoldersApplierUser'
__module_experimental = False
__module_enabled = True
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
self.folders = self.storage.get_folders(self.sid)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
for directory_obj in self.folders:
fld = Folder(directory_obj)
fld.action()
def admin_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Folder applier for user in administrator context'))
self.run()
else:
logging.debug(slogm('Folder applier for user in administrator context will not be started'))
def user_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Folder applier for user in user context'))
self.run()
else:
logging.debug(slogm('Folder applier for user administrator context will not be started'))

View File

@ -19,12 +19,18 @@
from storage import registry_factory
from .control_applier import control_applier
from .polkit_applier import polkit_applier
from .polkit_applier import (
polkit_applier
, polkit_applier_user
)
from .systemd_applier import systemd_applier
from .firefox_applier import firefox_applier
from .chromium_applier import chromium_applier
from .cups_applier import cups_applier
from .package_applier import package_applier
from .package_applier import (
package_applier
, package_applier_user
)
from .shortcut_applier import (
shortcut_applier,
shortcut_applier_user
@ -33,6 +39,13 @@ from .gsettings_applier import (
gsettings_applier,
gsettings_applier_user
)
from .firewall_applier import firewall_applier
from .folder_applier import (
folder_applier
, folder_applier_user
)
from .cifs_applier import cifs_applier_user
from .ntp_applier import ntp_applier
from util.windows import get_sid
from util.users import (
is_root,
@ -40,14 +53,8 @@ from util.users import (
username_match_uid,
with_privileges
)
from util.logging import slogm
from util.paths import (
frontend_module_dir
)
from util.logging import log
import logging
import os
import subprocess
def determine_username(username=None):
'''
@ -60,13 +67,15 @@ def determine_username(username=None):
# of process owner.
if not username:
name = get_process_user()
logging.debug(slogm('Username is not specified - will use username of current process'))
logdata = dict({'username': name})
log('D2', logdata)
if not username_match_uid(name):
if not is_root():
raise Exception('Current process UID does not match specified username')
logging.debug(slogm('Username for frontend is set to {}'.format(name)))
logdata = dict({'username': name})
log('D15', logdata)
return name
@ -77,75 +86,87 @@ class frontend_manager:
'''
def __init__(self, username, is_machine):
frontend_module_files = frontend_module_dir().glob('**/*')
self.frontend_module_binaries = list()
for exe in frontend_module_files:
if (exe.is_file() and os.access(exe.resolve(), os.X_OK)):
self.frontend_module_binaries.append(exe)
self.storage = registry_factory('registry')
self.username = determine_username(username)
self.is_machine = is_machine
self.process_uname = get_process_user()
self.sid = get_sid(self.storage.get_info('domain'), self.username, is_machine)
self.machine_appliers = dict({
'control': control_applier(self.storage),
'polkit': polkit_applier(self.storage),
'systemd': systemd_applier(self.storage),
'firefox': firefox_applier(self.storage, self.sid, self.username),
'chromium': chromium_applier(self.storage, self.sid, self.username),
'shortcuts': shortcut_applier(self.storage),
'gsettings': gsettings_applier(self.storage),
'cups': cups_applier(self.storage),
'package': package_applier(self.storage)
})
self.machine_appliers = dict()
self.machine_appliers['control'] = control_applier(self.storage)
self.machine_appliers['polkit'] = polkit_applier(self.storage)
self.machine_appliers['systemd'] = systemd_applier(self.storage)
self.machine_appliers['firefox'] = firefox_applier(self.storage, self.sid, self.username)
self.machine_appliers['chromium'] = chromium_applier(self.storage, self.sid, self.username)
self.machine_appliers['shortcuts'] = shortcut_applier(self.storage)
self.machine_appliers['gsettings'] = gsettings_applier(self.storage)
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)
# User appliers are expected to work with user-writable
# files and settings, mostly in $HOME.
self.user_appliers = dict({
'shortcuts': shortcut_applier_user(self.storage, self.sid, self.username),
'gsettings': gsettings_applier_user(self.storage, self.sid, self.username)
})
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)
try:
self.user_appliers['cifs'] = cifs_applier_user(self.storage, self.sid, self.username)
except Exception as exc:
logdata = dict()
logdata['applier_name'] = 'cifs'
logdata['msg'] = str(exc)
log('E25', logdata)
self.user_appliers['package'] = package_applier_user(self.storage, self.sid, self.username)
self.user_appliers['polkit'] = polkit_applier_user(self.storage, self.sid, self.username)
def machine_apply(self):
'''
Run global appliers with administrator privileges.
'''
if not is_root():
logging.error('Not sufficient privileges to run machine appliers')
log('E13')
return
logging.debug(slogm('Applying computer part of settings'))
for exe in self.frontend_module_binaries:
subprocess.check_call([exe.resolve()])
self.machine_appliers['systemd'].apply()
self.machine_appliers['control'].apply()
self.machine_appliers['polkit'].apply()
self.machine_appliers['firefox'].apply()
self.machine_appliers['chromium'].apply()
self.machine_appliers['shortcuts'].apply()
self.machine_appliers['gsettings'].apply()
self.machine_appliers['cups'].apply()
#self.machine_appliers['package'].apply()
log('D16')
for applier_name, applier_object in self.machine_appliers.items():
try:
applier_object.apply()
except Exception as exc:
logdata = dict()
logdata['applier_name'] = applier_name
logdata['msg'] = str(exc)
log('E24', logdata)
def user_apply(self):
'''
Run appliers for users.
'''
if is_root():
logging.debug(slogm('Running user appliers from administrator context'))
self.user_appliers['shortcuts'].admin_context_apply()
self.user_appliers['gsettings'].admin_context_apply()
for applier_name, applier_object in self.user_appliers.items():
try:
applier_object.admin_context_apply()
except Exception as exc:
logdata = dict()
logdata['applier'] = applier_name
logdata['exception'] = str(exc)
log('E19', logdata)
logging.debug(slogm('Running user appliers for user context'))
with_privileges(self.username, self.user_appliers['shortcuts'].user_context_apply)
with_privileges(self.username, self.user_appliers['gsettings'].user_context_apply)
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)
else:
logging.debug(slogm('Running user appliers from user context'))
self.user_appliers['shortcuts'].user_context_apply()
self.user_appliers['gsettings'].user_context_apply()
for applier_name, applier_object in self.user_appliers.items():
try:
applier_object.user_context_apply()
except Exception as exc:
logdata = dict({'applier_name': applier_name, 'message': str(exc)})
log('E11', logdata)
def apply_parameters(self):
'''

View File

@ -18,9 +18,18 @@
import logging
import os
import pwd
import subprocess
from .applier_frontend import applier_frontend
from gi.repository import (
Gio
, GLib
)
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.gsettings import (
system_gsetting,
user_gsetting
@ -28,8 +37,12 @@ from .appliers.gsettings import (
from util.logging import slogm
class gsettings_applier(applier_frontend):
__module_name = 'GSettingsApplier'
__module_experimental = True
__module_enabled = False
__registry_branch = 'Software\\BaseALT\\Policies\\gsettings'
__global_schema = '/usr/share/glib-2.0/schemas'
__windows_settings = dict()
def __init__(self, storage):
self.storage = storage
@ -37,8 +50,13 @@ class gsettings_applier(applier_frontend):
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.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def apply(self):
def run(self):
# Cleanup settings from previous run
if os.path.exists(self.override_file):
logging.debug(slogm('Removing GSettings policy file from previous run'))
@ -62,7 +80,47 @@ class gsettings_applier(applier_frontend):
except Exception as exc:
logging.debug(slogm('Error recompiling global GSettings schemas'))
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running GSettings applier for machine'))
else:
logging.debug(slogm('GSettings applier for machine will not be started'))
class GSettingsMapping:
def __init__(self, hive_key, gsettings_schema, gsettings_key):
self.hive_key = hive_key
self.gsettings_schema = gsettings_schema
self.gsettings_key = gsettings_key
try:
self.schema_source = Gio.SettingsSchemaSource.get_default()
self.schema = self.schema_source.lookup(self.gsettings_schema, True)
self.gsettings_schema_key = self.schema.get_key(self.gsettings_key)
self.gsettings_type = self.gsettings_schema_key.get_value_type()
except Exception as exc:
logdata = dict()
logdata['hive_key'] = self.hive_key
logdata['gsettings_schema'] = self.gsettings_schema
logdata['gsettings_key'] = self.gsettings_key
logging.warning(slogm('Unable to resolve GSettings parameter {}.{}'.format(self.gsettings_schema, self.gsettings_key)))
def preg2gsettings(self):
'''
Transform PReg key variant into GLib.Variant. This function
performs mapping of PReg type system into GLib type system.
'''
pass
def gsettings2preg(self):
'''
Transform GLib.Variant key type into PReg key type.
'''
pass
class gsettings_applier_user(applier_frontend):
__module_name = 'GSettingsApplierUser'
__module_experimental = True
__module_enabled = False
__registry_branch = 'Software\\BaseALT\\Policies\\gsettings'
def __init__(self, storage, sid, username):
@ -72,18 +130,75 @@ class gsettings_applier_user(applier_frontend):
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)
def user_context_apply(self):
for setting in self.gsettings_keys:
valuename = setting.hive_key.rpartition('\\')[2]
rp = valuename.rpartition('.')
schema = rp[0]
path = rp[2]
self.gsettings.append(user_gsetting(schema, path, setting.data))
self.__windows_settings = dict()
self.windows_settings = list()
mapping = [
# Disable or enable screen saver
GSettingsMapping(
'Software\\Policies\\Microsoft\\Windows\\Control Panel\\Desktop\\ScreenSaveActive'
, 'org.mate.screensaver'
, 'idle-activation-enabled'
)
# Timeout in seconds for screen saver activation. The value of zero effectively disables screensaver start
, GSettingsMapping(
'Software\\Policies\\Microsoft\\Windows\\Control Panel\\Desktop\\ScreenSaveTimeOut'
, 'org.mate.session'
, 'idle-delay'
)
# Enable or disable password protection for screen saver
, GSettingsMapping(
'Software\\Policies\\Microsoft\\Windows\\Control Panel\\Desktop\\ScreenSaverIsSecure'
, 'org.mate.screensaver'
, 'lock-enabled'
)
# Specify image which will be used as a wallpaper
, GSettingsMapping(
'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Wallpaper'
, 'org.mate.background'
, 'picture-filename'
)
]
self.windows_settings.extend(mapping)
for element in self.windows_settings:
self.__windows_settings[element.hive_key] = element
def run(self):
#for setting in self.gsettings_keys:
# valuename = setting.hive_key.rpartition('\\')[2]
# rp = valuename.rpartition('.')
# schema = rp[0]
# 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))
for gsetting in self.gsettings:
logging.debug('Applying setting {}/{}'.format(gsetting.schema, gsetting.path))
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'))
self.run()
else:
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.

View File

@ -0,0 +1,147 @@
#
# 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 logging
import subprocess
from enum import Enum
from .applier_frontend import (
applier_frontend
, check_enabled
)
from util.logging import slogm
class NTPServerType(Enum):
NTP = 'NTP'
class ntp_applier(applier_frontend):
__module_name = 'NTPApplier'
__module_experimental = True
__module_enabled = False
__ntp_branch = 'Software\\Policies\\Microsoft\\W32time\\Parameters'
__ntp_client_branch = 'Software\\Policies\\Microsoft\\W32time\\TimeProviders\\NtpClient'
__ntp_server_branch = 'Software\\Policies\\Microsoft\\W32time\\TimeProviders\\NtpServer'
__ntp_key_address = 'NtpServer'
__ntp_key_type = 'Type'
__ntp_key_client_enabled = 'Enabled'
__ntp_key_server_enabled = 'Enabled'
__chrony_config = '/etc/chrony.conf'
def __init__(self, storage):
self.storage = storage
self.ntp_server_address_key = '{}\\{}'.format(self.__ntp_branch, self.__ntp_key_address)
self.ntp_server_type = '{}\\{}'.format(self.__ntp_branch, self.__ntp_key_type)
self.ntp_client_enabled = '{}\\{}'.format(self.__ntp_client_branch, self.__ntp_key_client_enabled)
self.ntp_server_enabled = '{}\\{}'.format(self.__ntp_server_branch, self.__ntp_key_server_enabled)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def _chrony_as_client(self):
command = ['/usr/sbin/control', 'chrony', 'client']
proc = subprocess.Popen(command)
proc.wait()
def _chrony_as_server(self):
command = ['/usr/sbin/control', 'chrony', 'server']
proc = subprocess.Popen(command)
proc.wait()
def _start_chrony_client(self, server=None):
srv = None
if server:
srv = server.data.rpartition(',')[0]
logging.debug(slogm('NTP server is configured to {}'.format(srv)))
start_command = ['/usr/bin/systemctl', 'start', 'chronyd']
chrony_set_server = ['/usr/bin/chronyc', 'add', 'server', srv]
chrony_disconnect_all = ['/usr/bin/chronyc', 'offline']
chrony_connect = ['/usr/bin/chronyc', 'online', srv]
logging.debug(slogm('Starting Chrony daemon'))
proc = subprocess.Popen(start_command)
proc.wait()
if srv:
logging.debug(slogm('Setting reference NTP server to {}'.format(srv)))
proc = subprocess.Popen(chrony_disconnect_all)
proc.wait()
proc = subprocess.Popen(chrony_set_server)
proc.wait()
proc = subprocess.Popen(chrony_connect)
proc.wait()
def _stop_chrony_client(self):
stop_command = ['/usr/bin/systemctl', 'stop', 'chronyd']
logging.debug(slogm('Stopping Chrony daemon'))
proc = subprocess.Popen(stop_command)
proc.wait()
def run(self):
server_type = self.storage.get_hklm_entry(self.ntp_server_type)
server_address = self.storage.get_hklm_entry(self.ntp_server_address_key)
ntp_server_enabled = self.storage.get_hklm_entry(self.ntp_server_enabled)
ntp_client_enabled = self.storage.get_hklm_entry(self.ntp_client_enabled)
if NTPServerType.NTP.value != server_type.data:
logging.warning(slogm('Unsupported NTP server type: {}'.format(server_type)))
else:
logging.debug(slogm('Configuring NTP server...'))
if '1' == ntp_server_enabled.data:
logging.debug(slogm('NTP server is enabled'))
self._start_chrony_client(server_address)
self._chrony_as_server()
elif '0' == ntp_server_enabled.data:
logging.debug(slogm('NTP server is disabled'))
self._chrony_as_client()
else:
logging.debug(slogm('NTP server is not configured'))
if '1' == ntp_client_enabled.data:
logging.debug(slogm('NTP client is enabled'))
self._start_chrony_client()
elif '0' == ntp_client_enabled.data:
logging.debug(slogm('NTP client is disabled'))
self._stop_chrony_client()
else:
logging.debug(slogm('NTP client is not configured'))
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running NTP applier for machine'))
self.run()
else:
logging.debug(slogm('NTP applier for machine will not be started'))

View File

@ -17,20 +17,84 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from util.logging import slogm
from util.rpm import (
update
, install_rpm
, remove_rpm
)
from .applier_frontend import applier_frontend
from .appliers.rpm import rpm
from .applier_frontend import (
applier_frontend
, check_enabled
)
class package_applier(applier_frontend):
__module_name = 'PackagesApplier'
__module_experimental = True
__module_enabled = False
__install_key_name = 'Install'
__remove_key_name = 'Remove'
__hklm_branch = 'Software\\BaseALT\\Policies\\Packages'
def __init__(self, storage):
self.storage = storage
install_branch = '{}\\{}%'.format(self.__hklm_branch, self.__install_key_name)
remove_branch = '{}\\{}%'.format(self.__hklm_branch, self.__remove_key_name)
self.install_packages_setting = self.storage.filter_hklm_entries(install_branch)
self.remove_packages_setting = self.storage.filter_hklm_entries(remove_branch)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
if 0 < self.install_packages_setting.count() or 0 < self.remove_packages_setting.count():
update()
for package in self.install_packages_setting:
try:
install_rpm(package.data)
except Exception as exc:
logging.error(exc)
for package in self.remove_packages_setting:
try:
remove_rpm(package.data)
except Exception as exc:
logging.error(exc)
def apply(self):
pass
if self.__module_enabled:
logging.debug(slogm('Running Package applier for machine'))
self.run()
else:
logging.debug(slogm('Package applier for machine will not be started'))
class package_applier_user(applier_frontend):
def __init__(self):
pass
__module_name = 'PackagesApplierUser'
__module_experimental = True
__module_enabled = False
__install_key_name = 'Install'
__remove_key_name = 'Remove'
__hkcu_branch = 'Software\\BaseALT\\Policies\\Packages'
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
install_branch = '{}\\{}%'.format(self.__hkcu_branch, self.__install_key_name)
remove_branch = '{}\\{}%'.format(self.__hkcu_branch, self.__remove_key_name)
self.install_packages_setting = self.storage.filter_hkcu_entries(self.sid, install_branch)
self.remove_packages_setting = self.storage.filter_hkcu_entries(self.sid, remove_branch)
self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_enabled)
def user_context_apply(self):
'''
@ -38,10 +102,29 @@ class package_applier_user(applier_frontend):
'''
pass
def run(self):
if 0 < self.install_packages_setting.count() or 0 < self.remove_packages_setting.count():
update()
for package in self.install_packages_setting:
try:
install_rpm(package.data)
except Exception as exc:
logging.debug(exc)
for package in self.remove_packages_setting:
try:
remove_rpm(package.data)
except Exception as exc:
logging.debug(exc)
def admin_context_apply(self):
'''
Install software assigned to specified username regardless
which computer he uses to log into system.
'''
pass
if self.__module_enabled:
logging.debug(slogm('Running Package applier for user in administrator context'))
self.run()
else:
logging.debug(slogm('Package applier for user in administrator context will not be started'))

View File

@ -16,16 +16,22 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .applier_frontend import applier_frontend
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.polkit import polkit
from util.logging import slogm
import logging
class polkit_applier(applier_frontend):
__module_name = 'PolkitApplier'
__module_experimental = False
__module_enabled = True
__deny_all = 'Software\\Policies\\Microsoft\\Windows\\RemovableStorageDevices\\Deny_All'
__polkit_map = {
__deny_all: ['99-gpoa_disk_permissions', { 'Deny_All': 0 }]
__deny_all: ['49-gpoa_disk_permissions', { 'Deny_All': 0 }]
}
def __init__(self, storage):
@ -41,11 +47,66 @@ class polkit_applier(applier_frontend):
logging.debug(slogm('Deny_All setting not found'))
self.policies = []
self.policies.append(polkit(template_file, template_vars))
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def apply(self):
'''
Trigger control facility invocation.
'''
for policy in self.policies:
policy.generate()
if self.__module_enabled:
logging.debug(slogm('Running Polkit applier for machine'))
for policy in self.policies:
policy.generate()
else:
logging.debug(slogm('Polkit applier for machine will not be started'))
class polkit_applier_user(applier_frontend):
__module_name = 'PolkitApplierUser'
__module_experimental = False
__module_enabled = True
__deny_all = 'Software\\Policies\\Microsoft\\Windows\\RemovableStorageDevices\\Deny_All'
__polkit_map = {
__deny_all: ['48-gpoa_disk_permissions_user', { 'Deny_All': 0, 'User': '' }]
}
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
deny_all = storage.filter_hkcu_entries(self.sid, self.__deny_all).first()
# Deny_All hook: initialize defaults
template_file = self.__polkit_map[self.__deny_all][0]
template_vars = self.__polkit_map[self.__deny_all][1]
if deny_all:
logging.debug(slogm('Deny_All setting for user {} found: {}'.format(self.username, deny_all.data)))
self.__polkit_map[self.__deny_all][1]['Deny_All'] = deny_all.data
self.__polkit_map[self.__deny_all][1]['User'] = self.username
else:
logging.debug(slogm('Deny_All setting not found'))
self.policies = []
self.policies.append(polkit(template_file, template_vars, self.username))
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def user_context_apply(self):
pass
def admin_context_apply(self):
'''
Trigger control facility invocation.
'''
if self.__module_enabled:
logging.debug(slogm('Running Polkit applier for user in administrator context'))
for policy in self.policies:
policy.generate()
else:
logging.debug(slogm('Polkit applier for user in administrator context will not be started'))

View File

@ -19,7 +19,10 @@
import logging
import subprocess
from .applier_frontend import applier_frontend
from .applier_frontend import (
applier_frontend
, check_enabled
)
from gpt.shortcuts import json2sc
from util.windows import expand_windows_var
from util.logging import slogm
@ -47,7 +50,12 @@ def write_shortcut(shortcut, username=None):
:username: None means working with machine variables and paths
'''
dest_abspath = expand_windows_var(shortcut.dest, username).replace('\\', '/') + '.desktop'
dest_abspath = shortcut.dest
if not dest_abspath.startswith('/') and not dest_abspath.startswith('%'):
dest_abspath = '%HOME%/' + dest_abspath
logging.debug(slogm('Try to expand path for shortcut: {} for {}'.format(dest_abspath, username)))
dest_abspath = expand_windows_var(dest_abspath, username).replace('\\', '/') + '.desktop'
# Check that we're working for user, not on global system level
if username:
@ -58,15 +66,35 @@ def write_shortcut(shortcut, username=None):
if not homedir_exists(username):
logging.warning(slogm('No home directory exists for user {}: will not create 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)))
return None
if not dest_abspath.startswith('/'):
logging.debug(slogm('Fail for writing 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)
class shortcut_applier(applier_frontend):
__module_name = 'ShortcutsApplier'
__module_experimental = False
__module_enabled = True
def __init__(self, storage):
self.storage = storage
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def apply(self):
def run(self):
shortcuts = storage_get_shortcuts(self.storage, self.storage.get_info('machine_sid'))
if shortcuts:
for sc in shortcuts:
@ -79,13 +107,24 @@ class shortcut_applier(applier_frontend):
# /usr/local/share/applications
subprocess.check_call(['/usr/bin/update-desktop-database'])
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Shortcut applier for machine'))
self.run()
else:
logging.debug(slogm('Shortcut applier for machine will not be started'))
class shortcut_applier_user(applier_frontend):
__module_name = 'ShortcutsApplierUser'
__module_experimental = False
__module_enabled = True
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
def user_context_apply(self):
def run(self):
shortcuts = storage_get_shortcuts(self.storage, self.sid)
if shortcuts:
@ -95,13 +134,17 @@ class shortcut_applier_user(applier_frontend):
else:
logging.debug(slogm('No shortcuts to process for {}'.format(self.sid)))
def admin_context_apply(self):
shortcuts = storage_get_shortcuts(self.storage, self.sid)
if shortcuts:
for sc in shortcuts:
if not sc.is_usercontext():
write_shortcut(sc, self.username)
def user_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Shortcut applier for user in user context'))
self.run()
else:
logging.debug(slogm('No shortcuts to process for {}'.format(self.sid)))
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()
else:
logging.debug(slogm('Shortcut applier for user in administrator context will not be started'))

View File

@ -16,24 +16,32 @@
# 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
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.systemd import systemd_unit
from util.logging import slogm
import logging
class systemd_applier(applier_frontend):
__module_name = 'SystemdApplier'
__module_experimental = False
__module_enabled = True
__registry_branch = 'Software\\BaseALT\\Policies\\SystemdUnits'
def __init__(self, storage):
self.storage = storage
self.systemd_unit_settings = self.storage.filter_hklm_entries('Software\\BaseALT\\Policies\\SystemdUnits%')
self.units = []
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def apply(self):
'''
Trigger control facility invocation.
'''
def run(self):
for setting in self.systemd_unit_settings:
valuename = setting.hive_key.rpartition('\\')[2]
try:
@ -47,7 +55,20 @@ class systemd_applier(applier_frontend):
except:
logging.error(slogm('Failed applying unit {}'.format(unit.unit_name)))
def apply(self):
'''
Trigger control facility invocation.
'''
if self.__module_enabled:
logging.debug(slogm('Running systemd applier for machine'))
self.run()
else:
logging.debug(slogm('systemd applier for machine will not be started'))
class systemd_applier_user(applier_frontend):
__module_name = 'SystemdApplierUser'
__module_experimental = False
__module_enabled = True
__registry_branch = 'Software\\BaseALT\\Policies\\SystemdUnits'
def __init__(self, storage, sid, username):

View File

@ -18,16 +18,21 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
import logging
import os
import signal
import gettext
import locale
from backend import backend_factory
from frontend.frontend_manager import frontend_manager, determine_username
from plugin import plugin_manager
from messages import message_with_code
from util.util import get_machine_name
from util.kerberos import machine_kinit
from util.kerberos import (
machine_kinit
, machine_kdestroy
)
from util.users import (
is_root,
get_process_user
@ -35,7 +40,8 @@ from util.users import (
from util.arguments import (
set_loglevel
)
from util.logging import slogm
from util.logging import log
from util.exceptions import geterr
from util.signals import signal_handler
def parse_arguments():
@ -73,11 +79,17 @@ class gpoa_controller:
user = get_machine_name()
self.is_machine = True
set_loglevel(self.__args.loglevel)
self.__kinit_successful = machine_kinit()
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')
uname = get_process_user()
uid = os.getuid()
logging.debug(slogm('The process was started for user {} with UID {}'.format(uname, uid), uid=uid))
logdata = dict()
logdata['username'] = uname
logdata['uid'] = uid
log('D1', logdata)
if not is_root():
self.username = uname
@ -88,9 +100,11 @@ class gpoa_controller:
'''
GPOA controller entry point
'''
self.__kinit_successful = machine_kinit(self.cache_path)
self.start_plugins()
self.start_backend()
self.start_frontend()
if self.__kinit_successful:
machine_kdestroy()
def start_backend(self):
'''
@ -103,9 +117,31 @@ class gpoa_controller:
if not self.__args.noupdate:
if is_root():
back = backend_factory(dc, self.username, self.is_machine, nodomain)
back = None
try:
back = backend_factory(dc, self.username, self.is_machine, nodomain)
except Exception as exc:
logdata = dict({'msg': str(exc)})
einfo = geterr()
print(einfo)
print(type(einfo))
#logdata.update(einfo)
log('E12', logdata)
if back:
back.retrieve_and_store()
try:
back.retrieve_and_store()
# Start frontend only on successful backend finish
self.start_frontend()
except Exception as exc:
logdata = dict({'message': str(exc)})
# In case we're handling "E3" - it means that
# this is a very specific exception that was
# not handled properly on lower levels of
# code so we're also printing file name and
# other information.
einfo = geterr()
logdata.update(einfo)
log('E3', logdata)
def start_frontend(self):
'''
@ -115,7 +151,11 @@ class gpoa_controller:
appl = frontend_manager(self.username, self.is_machine)
appl.apply_parameters()
except Exception as exc:
logging.error(slogm('Error occured while running applier: {}'.format(exc)))
logdata = dict({'message': str(exc)})
einfo = geterr()
#print(einfo)
logdata.update(einfo)
log('E4', logdata)
def start_plugins(self):
'''
@ -130,6 +170,7 @@ def main():
controller.run()
if __name__ == "__main__":
default_handler = signal.getsignal(signal.SIGINT)
signal.signal(signal.SIGINT, signal_handler)
main()

View File

@ -22,24 +22,13 @@ from Crypto.Cipher import AES
from util.xml import get_xml_root
def read_drives(drives_file):
drives = list()
for drive in get_xml_root(drives_file):
drive_obj = drivemap()
props = drive.find('Properties')
drive_obj.set_login(props.get('username'))
drive_obj.set_pass(props.get('cpassword'))
drives.append(drive_obj)
return drives
def decrypt_pass(cpassword):
'''
AES key for cpassword decryption: http://msdn.microsoft.com/en-us/library/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be%28v=PROT.13%29#endNote2
'''
if not cpassword:
return cpassword
key = (
b'\x4e\x99\x06\xe8'
b'\xfc\xb6\x6c\xc9'
@ -53,23 +42,80 @@ def decrypt_pass(cpassword):
cpass_len = len(cpassword)
padded_pass = (cpassword + "=" * ((4 - cpass_len % 4) % 4))
password = b64decode(padded_pass)
decrypter = AES(key, AES.MODE_CBC, '\x00' * 16)
decrypter = AES.new(key, AES.MODE_CBC, '\x00' * 16)
return decrypter.decrypt(password)
# decrypt() returns byte array which is immutable and we need to
# strip padding, then convert UTF-16LE to UTF-8
binstr = decrypter.decrypt(password)
by = list()
for item in binstr:
if item != 16:
by.append(item)
utf16str = bytes(by).decode('utf-16', 'ignore')
utf8str = utf16str.encode('utf8')
return utf8str.decode()
def read_drives(drives_file):
drives = list()
for drive in get_xml_root(drives_file):
drive_obj = drivemap()
props = drive.find('Properties')
drive_obj.set_login(props.get('username'))
drive_obj.set_pass(decrypt_pass(props.get('cpassword')))
drive_obj.set_dir(props.get('letter'))
drive_obj.set_path(props.get('path'))
drives.append(drive_obj)
return drives
def merge_drives(storage, sid, drive_objects, policy_name):
for drive in drive_objects:
storage.add_drive(sid, drive, policy_name)
def json2drive(json_str):
json_obj = json.loads(json_str)
drive_obj = drivemap()
drive_obj.set_login(json_obj['login'])
drive_obj.set_pass(json_obj['password'])
drive_obj.set_dir(json_obj['dir'])
drive_obj.set_path(json_obj['path'])
return drive_obj
class drivemap:
def __init__(self):
self.login = None
self.password = None
self.dir = None
self.path = None
def set_login(self, username):
self.login = username
if not username:
self.login = ''
def set_pass(self, password):
self.password = password
if not password:
self.password = ''
def set_dir(self, path):
self.dir = path
def set_path(self, path):
self.path = path
def to_json(self):
drive = dict()
drive['login'] = self.login
drive['password'] = self.password
drive['dir'] = self.dir
drive['path'] = self.path
contents = dict()
contents['drive'] = drive

View File

@ -28,6 +28,10 @@ def read_envvars(envvars_file):
return variables
def merge_envvars(storage, sid, envvars_objects, policy_name):
for envvar in envvar_objects:
pass
class envvar:
def __init__(self):
pass

View File

@ -28,6 +28,10 @@ def read_files(filesxml):
return files
def merge_files(storage, sid, file_objects, policy_name):
for fileobj in file_objects:
pass
class fileentry:
def __init__(self):
pass

View File

@ -16,19 +16,83 @@
# 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 enum import Enum
from util.xml import get_xml_root
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 action_enum2letter(enumitem):
return enumitem.value
def folder_int2bool(val):
value = val
if type(value) == str:
value = int(value)
if value == 0:
return True
return False
def read_folders(folders_file):
folders = list()
for fld in get_xml_root(folders_file):
fld_obj = folderentry()
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')))
folders.append(fld_obj)
return folders
class folderentry:
def __init__(self):
pass
def merge_folders(storage, sid, folder_objects, policy_name):
for folder in folder_objects:
storage.add_folder(sid, folder, policy_name)
class folderentry:
def __init__(self, path):
self.path = path
self.action = FileAction.CREATE
self.delete_folder = False
self.delete_sub_folder = False
self.delete_files = False
def set_action(self, action):
self.action = action
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_files(self, del_bool):
self.delete_files = del_bool

View File

@ -16,20 +16,55 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
from pathlib import Path
from enum import Enum, unique
from samba.gp_parse.gp_pol import GPPolParser
from storage import registry_factory
from .shortcuts import read_shortcuts
from .services import read_services
from .printers import read_printers
from .inifiles import read_inifiles
from .folders import read_folders
from .files import read_files
from .envvars import read_envvars
from .drives import read_drives
from .polfile import (
read_polfile
, merge_polfile
)
from .shortcuts import (
read_shortcuts
, merge_shortcuts
)
from .services import (
read_services
, merge_services
)
from .printers import (
read_printers
, merge_printers
)
from .inifiles import (
read_inifiles
, merge_inifiles
)
from .folders import (
read_folders
, merge_folders
)
from .files import (
read_files
, merge_files
)
from .envvars import (
read_envvars
, merge_envvars
)
from .drives import (
read_drives
, merge_drives
)
from .tasks import (
read_tasks
, merge_tasks
)
import util
import util.preg
from util.paths import (
@ -37,7 +72,72 @@ from util.paths import (
cache_dir,
local_policy_cache
)
from util.logging import slogm
from util.logging import log
@unique
class FileType(Enum):
PREG = 'registry.pol'
SHORTCUTS = 'shortcuts.xml'
FOLDERS = 'folders.xml'
FILES = 'files.xml'
DRIVES = 'drives.xml'
SCHEDULEDTASKS = 'scheduledtasks.xml'
ENVIRONMENTVARIABLES = 'environmentvariables.xml'
INIFILES = 'inifiles.xml'
SERVICES = 'services.xml'
PRINTERS = 'printers.xml'
def get_preftype(path_to_file):
fpath = Path(path_to_file)
if fpath.exists():
file_name = fpath.name.lower()
for item in FileType:
if file_name == item.value:
return item
return None
def pref_parsers():
parsers = dict()
parsers[FileType.PREG] = read_polfile
parsers[FileType.SHORTCUTS] = read_shortcuts
parsers[FileType.FOLDERS] = read_folders
parsers[FileType.FILES] = read_files
parsers[FileType.DRIVES] = read_drives
parsers[FileType.SCHEDULEDTASKS] = read_tasks
parsers[FileType.ENVIRONMENTVARIABLES] = read_envvars
parsers[FileType.INIFILES] = read_inifiles
parsers[FileType.SERVICES] = read_services
parsers[FileType.PRINTERS] = read_printers
return parsers
def get_parser(preference_type):
parsers = pref_parsers()
return parsers[preference_type]
def pref_mergers():
mergers = dict()
mergers[FileType.PREG] = merge_polfile
mergers[FileType.SHORTCUTS] = merge_shortcuts
mergers[FileType.FOLDERS] = merge_folders
mergers[FileType.FILES] = merge_files
mergers[FileType.DRIVES] = merge_drives
mergers[FileType.SCHEDULEDTASKS] = merge_tasks
mergers[FileType.ENVIRONMENTVARIABLES] = merge_envvars
mergers[FileType.INIFILES] = merge_inifiles
mergers[FileType.SERVICES] = merge_services
mergers[FileType.PRINTERS] = merge_printers
return mergers
def get_merger(preference_type):
mergers = pref_mergers()
return mergers[preference_type]
class gpt:
__user_policy_mode_key = 'Software\\Policies\\Microsoft\\Windows\\System\\UserPolicyMode'
@ -46,27 +146,40 @@ class gpt:
self.path = gpt_path
self.sid = sid
self.storage = registry_factory('registry')
self._scan_gpt()
def _scan_gpt(self):
'''
Collect the data from the specified GPT on file system (cached
by Samba).
'''
self.guid = self.path.rpartition('/')[2]
self.name = ''
self.guid = self.path.rpartition('/')[2]
if 'default' == self.guid:
self.guid = 'Local Policy'
self._machine_path = find_dir(self.path, 'Machine')
self._user_path = find_dir(self.path, 'User')
self._machine_prefs = find_dir(self._machine_path, 'Preferences')
self._user_prefs = find_dir(self._user_path, 'Preferences')
logging.debug(slogm('Looking for machine part of GPT {}'.format(self.guid)))
self._find_machine()
logging.debug(slogm('Looking for user part of GPT {}'.format(self.guid)))
self._find_user()
self.settings_list = [
'shortcuts'
, 'drives'
, 'environmentvariables'
, 'printers'
, 'folders'
, 'files'
, 'inifiles'
, 'services'
, 'scheduledtasks'
]
self.settings = dict()
self.settings['machine'] = dict()
self.settings['user'] = dict()
self.settings['machine']['regpol'] = find_file(self._machine_path, 'registry.pol')
self.settings['user']['regpol'] = find_file(self._user_path, 'registry.pol')
for setting in self.settings_list:
machine_preffile = find_preffile(self._machine_path, setting)
user_preffile = find_preffile(self._user_path, setting)
mlogdata = dict({'setting': setting, 'prefpath': machine_preffile})
log('D24', mlogdata)
self.settings['machine'][setting] = machine_preffile
ulogdata = dict({'setting': setting, 'prefpath': user_preffile})
log('D23', ulogdata)
self.settings['user'][setting] = user_preffile
def set_name(self, name):
'''
@ -89,142 +202,55 @@ class gpt:
return upm
def _find_user(self):
self._user_regpol = self._find_regpol('user')
self._user_shortcuts = self._find_shortcuts('user')
def _find_machine(self):
self._machine_regpol = self._find_regpol('machine')
self._machine_shortcuts = self._find_shortcuts('machine')
def _find_regpol(self, part):
'''
Find Registry.pol files.
'''
search_path = self._machine_path
if 'user' == part:
search_path = self._user_path
if not search_path:
return None
return find_file(search_path, 'registry.pol')
def _find_shortcuts(self, part):
'''
Find Shortcuts.xml files.
'''
shortcuts_dir = find_dir(self._machine_prefs, 'Shortcuts')
shortcuts_file = find_file(shortcuts_dir, 'shortcuts.xml')
if 'user' == part:
shortcuts_dir = find_dir(self._user_prefs, 'Shortcuts')
shortcuts_file = find_file(shortcuts_dir, 'shortcuts.xml')
return shortcuts_file
def _find_envvars(self, part):
'''
Find EnvironmentVariables.xml files.
'''
search_path = os.path.join(self._machine_path, 'Preferences', 'EnvironmentVariables')
if 'user' == part:
search_path = os.path.join(self._user_path, 'Preferences', 'EnvironmentVariables')
if not search_path:
return None
return find_file(search_path, 'environmentvariables.xml')
def _find_drives(self, part):
'''
Find Drives.xml files.
'''
search_path = os.path.join(self._machine_path, 'Preferences', 'Drives')
if 'user' == part:
search_path = os.path.join(self._user_path, 'Preferences', 'Drives')
if not search_path:
return None
return find_file(search_path, 'drives.xml')
def _find_printers(self, part):
'''
Find Printers.xml files.
'''
search_path = os.path.join(self._machine_path, 'Preferences', 'Printers')
if 'user' == part:
search_path = os.path.join(self._user_path, 'Preferences', 'Printers')
if not search_path:
return None
return find_file(search_path, 'printers.xml')
def _merge_shortcuts(self):
shortcuts = list()
if self.sid == self.storage.get_info('machine_sid'):
shortcuts = read_shortcuts(self._machine_shortcuts)
else:
shortcuts = read_shortcuts(self._user_shortcuts)
for sc in shortcuts:
self.storage.add_shortcut(self.sid, sc)
def merge(self):
'''
Merge machine and user (if sid provided) settings to storage.
'''
if self.sid == self.storage.get_info('machine_sid'):
# Merge machine settings to registry if possible
if self._machine_regpol:
logging.debug(slogm('Merging machine settings from {}'.format(self._machine_regpol)))
util.preg.merge_polfile(self._machine_regpol)
if self._user_regpol:
logging.debug(slogm('Merging machine(user) settings from {}'.format(self._machine_regpol)))
util.preg.merge_polfile(self._user_regpol, self.sid)
if self._machine_shortcuts:
logging.debug(slogm('Merging machine shortcuts from {}'.format(self._machine_shortcuts)))
self._merge_shortcuts()
try:
# Merge machine settings to registry if possible
for preference_name, preference_path in self.settings['machine'].items():
if preference_path:
preference_type = get_preftype(preference_path)
logdata = dict({'pref': preference_type.value, 'sid': self.sid})
log('D28', logdata)
preference_parser = get_parser(preference_type)
preference_merger = get_merger(preference_type)
preference_objects = preference_parser(preference_path)
preference_merger(self.storage, self.sid, preference_objects, self.name)
if self.settings['user']['regpol']:
mulogdata = dict({'polfile': self.settings['machine']['regpol']})
log('D35', mulogdata)
util.preg.merge_polfile(self.settings['user']['regpol'], sid=self.sid, policy_name=self.name)
if self.settings['machine']['regpol']:
mlogdata = dict({'polfile': self.settings['machine']['regpol']})
log('D34', mlogdata)
util.preg.merge_polfile(self.settings['machine']['regpol'], policy_name=self.name)
except Exception as exc:
logdata = dict()
logdata['gpt'] = self.name
logdata['msg'] = str(exc)
log('E28', logdata)
else:
# Merge user settings if UserPolicyMode set accordingly
# and user settings (for HKCU) are exist.
policy_mode = upm2str(self.get_policy_mode())
if 'Merge' == policy_mode or 'Not configured' == policy_mode:
if self._user_regpol:
logging.debug(slogm('Merging user settings from {} for {}'.format(self._user_regpol, self.sid)))
util.preg.merge_polfile(self._user_regpol, self.sid)
if self._user_shortcuts:
logging.debug(slogm('Merging user shortcuts from {} for {}'.format(self._user_shortcuts, self.sid)))
self._merge_shortcuts()
def __str__(self):
template = '''
GUID: {}
Name: {}
For SID: {}
Machine part: {}
Machine Registry.pol: {}
Machine Shortcuts.xml: {}
User part: {}
User Registry.pol: {}
User Shortcuts.xml: {}
'''
result = template.format(
self.guid,
self.name,
self.sid,
self._machine_path,
self._machine_regpol,
self._machine_shortcuts,
self._user_path,
self._user_regpol,
self._user_shortcuts,
)
return result
try:
for preference_name, preference_path in self.settings['user'].items():
if preference_path:
preference_type = get_preftype(preference_path)
logdata = dict({'pref': preference_type.value, 'sid': self.sid})
log('D29', logdata)
preference_parser = get_parser(preference_type)
preference_merger = get_merger(preference_type)
preference_objects = preference_parser(preference_path)
preference_merger(self.storage, self.sid, preference_objects, self.name)
except Exception as exc:
logdata = dict()
logdata['gpt'] = self.name
logdata['msg'] = str(exc)
log('E29', logdata)
def find_dir(search_path, name):
'''
@ -269,6 +295,33 @@ def find_file(search_path, name):
return None
def find_preferences(search_path):
'''
Find 'Preferences' directory
'''
if not search_path:
return None
return find_dir(search_path, 'Preferences')
def find_preffile(search_path, prefname):
'''
Find file with path like Preferences/prefname/prefname.xml
'''
# Look for 'Preferences' directory
prefdir = find_preferences(search_path)
if not prefdir:
return None
# Then search for preference directory
pref_dir = find_dir(prefdir, prefname)
file_name = '{}.xml'.format(prefname)
# And then try to find the corresponding file.
pref_file = find_file(pref_dir, file_name)
return pref_file
def lp2gpt():
'''
Convert local-policy to full-featured GPT.
@ -291,7 +344,7 @@ def get_local_gpt(sid):
'''
Convert default policy to GPT and create object out of it.
'''
logging.debug(slogm('Re-caching Local Policy'))
log('D25')
lp2gpt()
local_policy = gpt(str(local_policy_cache()), sid)
local_policy.set_name('Local Policy')

View File

@ -28,6 +28,10 @@ def read_inifiles(inifiles_file):
return inifiles
def merge_inifiles(storage, sid, inifile_objects, policy_name):
for inifile in inifile_objects:
pass
def inifile():
def __init__(self):
pass

32
gpoa/gpt/polfile.py Normal file
View File

@ -0,0 +1,32 @@
#
# 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 util.preg import (
load_preg
)
def read_polfile(filename):
return load_preg(filename).entries
def merge_polfile(storage, sid, policy_objects, policy_name):
for entry in policy_objects:
if not sid:
storage.add_hklm_entry(entry, policy_name)
else:
storage.add_hkcu_entry(entry, sid, policy_name)

View File

@ -41,6 +41,10 @@ def read_printers(printers_file):
return printers
def merge_printers(storage, sid, printer_objects, policy_name):
for device in printer_objects:
storage.add_printer(sid, device, policy_name)
def json2printer(json_str):
'''
Build printer object out of string-serialized JSON.

View File

@ -39,6 +39,10 @@ def read_services(service_file):
return services
def merge_services(storage, sid, service_objects, policy_name):
for srv in service_objects:
pass
class service:
def __init__(self, name):
self.unit = name

View File

@ -84,10 +84,15 @@ def read_shortcuts(shortcuts_file):
sc.set_clsid(link.get('clsid'))
sc.set_guid(link.get('uid'))
sc.set_usercontext(link.get('userContext', False))
sc.set_icon(props.get('iconPath'))
shortcuts.append(sc)
return shortcuts
def merge_shortcuts(storage, sid, shortcut_objects, policy_name):
for shortcut in shortcut_objects:
storage.add_shortcut(sid, shortcut, policy_name)
def json2sc(json_str):
'''
Build shortcut out of string-serialized JSON
@ -100,6 +105,8 @@ def json2sc(json_str):
sc.set_clsid(json_obj['clsid'])
sc.set_guid(json_obj['guid'])
sc.set_usercontext(json_obj['is_in_user_context'])
if 'icon' in json_obj:
sc.set_icon(json_obj['icon'])
return sc
@ -117,6 +124,7 @@ class shortcut:
self.arguments = arguments
self.name = name
self.changed = ''
self.icon = None
self.is_in_user_context = self.set_usercontext()
self.type = ttype
@ -136,6 +144,9 @@ class shortcut:
def set_guid(self, uid):
self.guid = uid
def set_icon(self, icon_name):
self.icon = icon_name
def set_type(self, ttype):
'''
Set type of the hyperlink - FILESYSTEM or URL
@ -172,7 +183,8 @@ class shortcut:
content['changed'] = self.changed
content['is_in_user_context'] = self.is_in_user_context
content['type'] = ttype2str(self.type)
if self.icon:
content['icon'] = self.icon
result = self.desktop()
result.content.update(content)
@ -199,6 +211,9 @@ class shortcut:
self.desktop_file.set('Terminal', 'false')
self.desktop_file.set('Exec', '{} {}'.format(self.path, self.arguments))
if self.icon:
self.desktop_file.set('Icon', self.icon)
return self.desktop_file
def write_desktop(self, dest):

View File

@ -16,16 +16,10 @@
# 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 util.rpm import (
install_rpm,
remove_rpm
)
def read_tasks(filename):
pass
class rpm:
def __init__(self, name, action):
self.name = name
self.action = action
def apply(self):
def merge_tasks(storage, sid, task_objects, policy_name):
for task in task_objects:
pass

View File

@ -18,11 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
import locale
import gettext
import subprocess
import os
import sys
import logging
import pwd
import signal
@ -31,6 +31,7 @@ from util.users import (
)
from util.arguments import (
process_target,
set_loglevel,
ExitCodeUpdater
)
from util.dbus import (
@ -39,7 +40,9 @@ from util.dbus import (
)
from util.signals import signal_handler
logging.basicConfig(level=logging.DEBUG)
from util.logging import log
#logging.basicConfig(level=logging.DEBUG)
class file_runner:
_gpoa_exe = '/usr/sbin/gpoa'
@ -89,24 +92,18 @@ def runner_factory(args, target):
target = 'Computer'
except:
username = None
logstring = (
'Unable to perform gpupdate for non-existent user {},'
' will update machine settings'
)
logging.error(logstring.format(args.user))
logdata = dict({'username': args.user})
log('W1', logdata)
else:
# User may only perform gpupdate for machine (None) or
# itself (os.getusername()).
username = pwd.getpwuid(os.getuid()).pw_name
if args.user != username:
logstring = (
'Unable to perform gpupdate for {} with current'
' permissions, will update current user settings'
)
logging.error(logstring.format(args.user))
logdata = dict({'username': args.user})
log('W2', logdata)
if is_oddjobd_gpupdate_accessible():
logging.debug('Starting gpupdate via D-Bus')
log('D13')
computer_runner = None
user_runner = None
if target == 'All' or target == 'Computer':
@ -116,10 +113,10 @@ def runner_factory(args, target):
user_runner = dbus_runner(username)
return (computer_runner, user_runner)
else:
logging.warning('oddjobd is inaccessible')
log('W3')
if is_root():
logging.debug('Starting gpupdate by command invocation')
log('D14')
computer_runner = None
user_runner = None
if target == 'All' or target == 'Computer':
@ -128,12 +125,16 @@ def runner_factory(args, target):
user_runner = file_runner(username)
return (computer_runner, user_runner)
else:
logging.error('Insufficient permissions to run gpupdate')
log('E1')
return None
def main():
args = parse_cli_arguments()
locale.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale')
gettext.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale')
gettext.textdomain('gpoa')
set_loglevel(0)
gpo_appliers = runner_factory(args, process_target(args.target))
if gpo_appliers:
@ -141,17 +142,19 @@ def main():
try:
gpo_appliers[0].run()
except Exception as exc:
logging.error('Error running GPOA for computer: {}'.format(exc))
logdata = dict({'error': str(exc)})
log('E5')
return int(ExitCodeUpdater.FAIL_GPUPDATE_COMPUTER_NOREPLY)
if gpo_appliers[1]:
try:
gpo_appliers[1].run()
except Exception as exc:
logging.error('Error running GPOA for user: {}'.format(exc))
logdata = dict({'error': str(exc)})
log('E6', logdata)
return int(ExitCodeUpdater.FAIL_GPUPDATE_USER_NOREPLY)
else:
logging.error('gpupdate will not be started')
log('E2')
return int(ExitCodeUpdater.FAIL_NO_RUNNER)
return int(ExitCodeUpdater.EXIT_SUCCESS)

View File

@ -0,0 +1,322 @@
#
# 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/>.
#domain "gpoa"
msgid ""
msgstr ""
"Project-Id-Version: 0.8.0\n"
"Report-Msgid-Bugs-To: samba@lists.altlinux.org\n"
"PO-Revision-Date: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain;charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
msgid "Don't start plugins"
msgstr "Не запускать модули"
# Info
msgid "Got GPO list for username"
msgstr "Получен список GPO для пользователя"
msgid "Got GPO"
msgstr "Получен объект групповой политики"
msgid "Unknown info code"
msgstr "Неизвестный код информационного сообщения"
# Error
msgid "Insufficient permissions to run gpupdate"
msgstr "Недостаточно прав для запуска gpupdate"
msgid "gpupdate will not be started"
msgstr "gpupdate не будет запущен"
msgid "Backend execution error"
msgstr "Ошибка бэкэнда"
msgid "Error occurred while running frontend manager"
msgstr "Ошибка фронтенда"
msgid "Error running GPOA for computer"
msgstr "Ошибка запуска GPOA для машины"
msgid "Error running GPOA for user"
msgstr "Ошибка запуска GPOA для пользователя"
msgid "Unable to initialize Samba backend"
msgstr "Невозможно инициализировать бэкэнд Samba"
msgid "Unable to initialize no-domain backend"
msgstr "Невозможно инициализировать бэкэнд-заглушку"
msgid "Error running ADP"
msgstr "Ошибка во время работы ADP"
msgid "Unable to determine DC hostname"
msgstr "Невозможно определить имя контроллера домена"
msgid "Error occured while running applier with user privileges"
msgstr "Ошибка во время работы applier в контексте пользователя"
msgid "Unable to initialize backend"
msgstr "Невозможно инициализировать бэкэнд"
msgid "Not sufficient privileges to run machine appliers"
msgstr "Недостаточно прав для запуска appliers для машины"
msgid "Kerberos ticket check failed"
msgstr "Проверка билета Kerberos закончилась неудачно"
msgid "Unable to retrieve domain name via CLDAP query"
msgstr "Не удалось определить имя домена AD через запрос к LDAP"
msgid "Error getting SID using wbinfo, will use SID from cache"
msgstr "Не удалось определить SID с использованием утилиты wbinfo, будет использоваться фиктивный/кэшированный SID"
msgid "Unable to get GPO list for user from AD DC"
msgstr "Не удалось получить список групповых политик для пользователя от контроллера домена AD"
msgid "Error getting XDG_DESKTOP_DIR"
msgstr "Не удалось получить значение XDG_DESKTOP_DIR"
msgid "Error occured while running user applier in administrator context"
msgstr "Ошибка выполнения applier в контексте администратора"
msgid "Error occured while running user applier in user context (with dropped privileges)"
msgstr "Ошибка работы пользовательского applier в пользовательском контексте (со сбросом привилегий процесса)"
msgid "No reply from oddjobd GPOA runner via D-Bus for current user"
msgstr "Не получен ответ от oddjobd для текущего пользователя"
msgid "No reply from oddjobd GPOA runner via D-Bus for computer"
msgstr "Не получен ответ от oddjobd для компьютера"
msgid "No reply from oddjobd GPOA runner via D-Bus for user"
msgstr "Не получен ответ от oddjobd для пользователя"
msgid "Error occured while running machine applier"
msgstr "Ошибка во время работы applier для машины"
msgid "Error occured while initializing user applier"
msgstr "Ошибка инициализации пользовательского applier"
msgid "Error merging machine GPT"
msgstr "Ошибка слияния машинной групповой политики"
msgid "Error merging user GPT"
msgstr "Ошибка слияния пользовательской групповой политики"
msgid "Error merging machine part of GPT"
msgstr "Ошибка слияния машинной части групповой политики"
msgid "Error merging user part of GPT"
msgstr "Ошибка слияния пользовательской части групповой политики"
msgid "Unknown error code"
msgstr "Неизвестный код ошибки"
# Debug
msgid "The GPOA process was started for user"
msgstr "Произведён запуск GPOA для обновления политик пользователя"
msgid "Username is not specified - will use username of the current process"
msgstr "Имя пользователя не указано - будет использовано имя владельца процесса"
msgid "Initializing plugin manager"
msgstr "Инициализация плагинов"
msgid "ADP plugin initialized"
msgstr "Инициализирован плагин ADP"
msgid "Running ADP plugin"
msgstr "Запущен плагин ADP"
msgid "Starting GPOA for user via D-Bus"
msgstr "Запускается GPOA для пользователя обращением к oddjobd через D-Bus"
msgid "Cache directory determined"
msgstr "Определена директория кэша Samba"
msgid "Initializing local backend without domain"
msgstr "Инициализация бэкэнда-заглушки"
msgid "Initializing Samba backend for domain"
msgstr "Инициализация бэкэнда Samba"
msgid "Group Policy target set for update"
msgstr "Групповые политики будут обновлены для указанной цели"
msgid "Starting GPOA for computer via D-Bus"
msgstr "Запускается GPOA для компьютера обращением к oddjobd через D-Bus"
msgid "Got exit code"
msgstr "Получен код возврата из утилиты"
msgid "Starting GPOA via D-Bus"
msgstr "Запускается GPOA обращением к oddjobd через D-Bus"
msgid "Starting GPOA via command invocation"
msgstr "GPOA запускается с помощью прямого вызова приложения"
msgid "Username for frontend is determined"
msgstr "Определено имя пользователя для фронтенда"
msgid "Applying computer part of settings"
msgstr "Применение настроек для машины"
msgid "Kerberos ticket check succeed"
msgstr "Проверка билета Kerberos прошла успешно"
msgid "Found AD domain via CLDAP query"
msgstr "Имя домена Active Directory успешно определено при запросе к LDAP"
msgid "Setting info"
msgstr "Установка вспомогательной переменной"
msgid "Initializing cache"
msgstr "Инициализация кэша"
msgid "Set operational SID"
msgstr "Установка рабочего SID"
msgid "Got PReg entry"
msgstr "Получен ключ реестра"
msgid "Looking for preference in user part of GPT"
msgstr "Поиск настроек в пользовательской части GPT"
msgid "Looking for preference in machine part of GPT"
msgstr "Поиск настроек в машинной части GPT"
msgid "Re-caching Local Policy"
msgstr "Обновление кэша локальной политики"
msgid "Adding HKCU entry"
msgstr "Слияние ключа в пользовательскую (HKCU) часть реестра"
msgid "Skipping HKLM branch deletion key"
msgstr "Пропускаем специальный ключ удаления ветви реестра HKLM"
msgid "Reading and merging machine preference"
msgstr "Вычитывание и слияние машинных настроек"
msgid "Reading and merging user preference"
msgstr "Вычитывание и слияние пользовательских настроек"
msgid "Found SYSVOL entry"
msgstr "Найден путь SYSVOL"
msgid "Trying to load PReg from .pol file"
msgstr "Пробуем загрузить ключи реестра из .pol файла"
msgid "Finished reading PReg from .pol file"
msgstr "Вычитаны ключи реестра из .pol файла"
msgid "Determined length of PReg file"
msgstr "Определена длина .pol файла"
msgid "Merging machine settings from PReg file"
msgstr "Слияние машинных настроек из .pol файла"
msgid "Merging machine (user part) settings from PReg file"
msgstr "Слияние пользовательской части машинных настроек из .pol файла"
msgid "Loading PReg from XML"
msgstr "Загружаем ключи реестра из XML"
msgid "Setting process permissions"
msgstr "Установка прав процесса"
msgid "Samba DC setting is overriden by user setting"
msgstr "Используется указанный пользователем контроллер домена AD"
msgid "Saving information about drive mapping"
msgstr "Сохранение информации о привязках дисков"
msgid "Saving information about printer"
msgstr "Сохранение информации о принтерах"
msgid "Saving information about link"
msgstr "Сохранение информации о ярлычках"
msgid "Saving information about folder"
msgstr "Сохранение информации о папках"
msgid "No value cached for object"
msgstr "Отсутствует кэшированное значение для объекта"
msgid "Key is already present in cache, will update the value"
msgstr "Ключ уже существует, его значение будет обновлено"
msgid "GPO update started"
msgstr "Начато обновление GPO"
msgid "GPO update finished"
msgstr "Завершено обновление GPO"
msgid "Retrieving list of GPOs to replicate from AD DC"
msgstr "Получение списка GPO для репликации с контроллера домена AD"
msgid "Establishing connection with AD DC"
msgstr "Установка соединения с контроллером домена AD"
msgid "Started GPO replication from AD DC"
msgstr "Начата репликация GPO от контроллера домена AD"
msgid "Finished GPO replication from AD DC"
msgstr "Завершена репликация GPO от контроллера домена AD"
msgid "Skipping HKCU branch deletion key"
msgstr "Пропускаем специальный ключ удаления ветви реестра HKCU"
msgid "Unknown debug code"
msgstr "Неизвестный отладочный код"
# Warning
msgid "Unable to perform gpupdate for non-existent user, will update machine settings"
msgstr "Невозможно запустить gpupdate для несуществующего пользователя, будут обновлены настройки машины"
msgid "Current permissions does not allow to perform gpupdate for designated user. Will update current user settings"
msgstr "Текущий уровень привилегий не позволяет выполнить gpupdate для указанного пользователя. Будут обновлены настройки текущего пользователя."
msgid "oddjobd is inaccessible"
msgstr "oddjobd недоступен"
msgid "No SYSVOL entry assigned to GPO"
msgstr "Объект групповой политики не имеет привязанного пути на SYSVOL"
msgid "ADP package is not installed - plugin will not be initialized"
msgstr "Пакет ADP не установлен, плагин не будет инициализирован"
msgid "Unknown warning code"
msgstr "Неизвестный код предупреждения"
# Fatal
msgid "Unable to refresh GPO list"
msgstr "Невозможно обновить список объектов групповых политик"
msgid "Unknown fatal code"
msgstr "Неизвестный код фатальной ошибки"
# get_message
msgid "Unknown message type, no message assigned"
msgstr "Неизвестный тип сообщения"

161
gpoa/messages/__init__.py Normal file
View File

@ -0,0 +1,161 @@
#
# 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 gettext
def info_code(code):
info_ids = dict()
info_ids[1] = 'Got GPO list for username'
info_ids[2] = 'Got GPO'
return info_ids.get(code, 'Unknown info code')
def error_code(code):
error_ids = dict()
error_ids[1] = 'Insufficient permissions to run gpupdate'
error_ids[2] = 'gpupdate will not be started'
error_ids[3] = 'Backend execution error'
error_ids[4] = 'Error occurred while running frontend manager'
error_ids[5] = 'Error running GPOA for computer'
error_ids[6] = 'Error running GPOA for user'
error_ids[7] = 'Unable to initialize Samba backend'
error_ids[8] = 'Unable to initialize no-domain backend'
error_ids[9] = 'Error running ADP'
error_ids[10] = 'Unable to determine DC hostname'
error_ids[11] = 'Error occured while running applier with user privileges'
error_ids[12] = 'Unable to initialize backend'
error_ids[13] = 'Not sufficient privileges to run machine appliers'
error_ids[14] = 'Kerberos ticket check failed'
error_ids[15] = 'Unable to retrieve domain name via CLDAP query'
error_ids[16] = 'Error getting SID using wbinfo, will use SID from cache'
error_ids[17] = 'Unable to get GPO list for user from AD DC'
error_ids[18] = 'Error getting XDG_DESKTOP_DIR'
error_ids[19] = 'Error occured while running user applier in administrator context'
error_ids[20] = 'Error occured while running user applier in user context (with dropped privileges)'
error_ids[21] = 'No reply from oddjobd GPOA runner via D-Bus for current user'
error_ids[22] = 'No reply from oddjobd GPOA runner via D-Bus for computer'
error_ids[23] = 'No reply from oddjobd GPOA runner via D-Bus for user'
error_ids[24] = 'Error occured while running machine applier'
error_ids[25] = 'Error occured while initializing user applier'
error_ids[26] = 'Error merging machine GPT'
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'
return error_ids.get(code, 'Unknown error code')
def debug_code(code):
debug_ids = dict()
debug_ids[1] = 'The GPOA process was started for user'
debug_ids[2] = 'Username is not specified - will use username of the current process'
debug_ids[3] = 'Initializing plugin manager'
debug_ids[4] = 'ADP plugin initialized'
debug_ids[5] = 'Running ADP plugin'
debug_ids[6] = 'Starting GPOA for user via D-Bus'
debug_ids[7] = 'Cache directory determined'
debug_ids[8] = 'Initializing local backend without domain'
debug_ids[9] = 'Initializing Samba backend for domain'
debug_ids[10] = 'Group Policy target set for update'
debug_ids[11] = 'Starting GPOA for computer via D-Bus'
debug_ids[12] = 'Got exit code'
debug_ids[13] = 'Starting GPOA via D-Bus'
debug_ids[14] = 'Starting GPOA via command invocation'
debug_ids[15] = 'Username for frontend is determined'
debug_ids[16] = 'Applying computer part of settings'
debug_ids[17] = 'Kerberos ticket check succeed'
debug_ids[18] = 'Found AD domain via CLDAP query'
debug_ids[19] = 'Setting info'
debug_ids[20] = 'Initializing cache'
debug_ids[21] = 'Set operational SID'
debug_ids[22] = 'Got PReg entry'
debug_ids[23] = 'Looking for preference in user part of GPT'
debug_ids[24] = 'Looking for preference in machine part of GPT'
debug_ids[25] = 'Re-caching Local Policy'
debug_ids[26] = 'Adding HKCU entry'
debug_ids[27] = 'Skipping HKLM branch deletion key'
debug_ids[28] = 'Reading and merging machine preference'
debug_ids[29] = 'Reading and merging user preference'
debug_ids[30] = 'Found SYSVOL entry'
debug_ids[31] = 'Trying to load PReg from .pol file'
debug_ids[32] = 'Finished reading PReg from .pol file'
debug_ids[33] = 'Determined length of PReg file'
debug_ids[34] = 'Merging machine settings from PReg file'
debug_ids[35] = 'Merging machine (user part) settings from PReg file'
debug_ids[36] = 'Loading PReg from XML'
debug_ids[37] = 'Setting process permissions'
debug_ids[38] = 'Samba DC setting is overriden by user setting'
debug_ids[39] = 'Saving information about drive mapping'
debug_ids[40] = 'Saving information about printer'
debug_ids[41] = 'Saving information about link'
debug_ids[42] = 'Saving information about folder'
debug_ids[43] = 'No value cached for object'
debug_ids[44] = 'Key is already present in cache, will update the value'
debug_ids[45] = 'GPO update started'
debug_ids[46] = 'GPO update finished'
debug_ids[47] = 'Retrieving list of GPOs to replicate from AD DC'
debug_ids[48] = 'Establishing connection with AD DC'
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'
return debug_ids.get(code, 'Unknown debug code')
def warning_code(code):
warning_ids = dict()
warning_ids[1] = (
'Unable to perform gpupdate for non-existent user, '
'will update machine settings'
)
warning_ids[2] = (
'Current permissions does not allow to perform gpupdate for '
'designated user. Will update current user settings'
)
warning_ids[3] = 'oddjobd is inaccessible'
warning_ids[4] = 'No SYSVOL entry assigned to GPO'
warning_ids[5] = 'ADP package is not installed - plugin will not be initialized'
return warning_ids.get(code, 'Unknown warning code')
def fatal_code(code):
fatal_ids = dict()
fatal_ids[1] = 'Unable to refresh GPO list'
return fatal_ids.get(code, 'Unknown fatal code')
def get_message(code):
retstr = 'Unknown message type, no message assigned'
if code.startswith('E'):
retstr = error_code(int(code[1:]))
if code.startswith('I'):
retstr = info_code(int(code[1:]))
if code.startswith('D'):
retstr = debug_code(int(code[1:]))
if code.startswith('W'):
retstr = warning_code(int(code[1:]))
if code.startswith('F'):
retstr = fatal_code(int(code[1:]))
return retstr
def message_with_code(code):
retstr = '[' + code[0:1] + code[1:].rjust(5, '0') + ']| ' + gettext.gettext(get_message(code))
return retstr

View File

@ -22,19 +22,20 @@ import subprocess
from util.rpm import is_rpm_installed
from .exceptions import PluginInitError
from util.logging import slogm
from messages import message_with_code
class adp:
def __init__(self):
if not is_rpm_installed('adp'):
raise PluginInitError('adp is not installed - plugin cannot be initialized')
logging.info(slogm('ADP plugin initialized'))
raise PluginInitError(message_with_code('W5'))
logging.info(slogm(message_with_code('D4')))
def run(self):
try:
logging.info('Running ADP plugin')
logging.info(slogm(message_with_code('D5')))
subprocess.call(['/usr/bin/adp', 'fetch'])
subprocess.call(['/usr/bin/adp', 'apply'])
except Exception as exc:
logging.error(slogm('Error running ADP'))
logging.error(slogm(message_with_code('E9')))
raise exc

View File

@ -23,15 +23,16 @@ from .roles import roles
from .exceptions import PluginInitError
from .plugin import plugin
from util.logging import slogm
from messages import message_with_code
class plugin_manager:
def __init__(self):
self.plugins = dict()
logging.info(slogm('Starting plugin manager'))
logging.debug(slogm(message_with_code('D3')))
try:
self.plugins['adp'] = adp()
except PluginInitError as exc:
logging.error(slogm(exc))
logging.warning(slogm(str(exc)))
def run(self):
self.plugins.get('adp', plugin('adp')).run()

View File

@ -20,40 +20,131 @@ class samba_preg(object):
'''
Object mapping representing HKLM entry (registry key without SID)
'''
def __init__(self, preg_obj):
def __init__(self, preg_obj, policy_name):
self.policy_name = policy_name
self.hive_key = '{}\\{}'.format(preg_obj.keyname, preg_obj.valuename)
self.type = preg_obj.type
self.data = preg_obj.data
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['type'] = self.type
fields['data'] = self.data
return fields
class samba_hkcu_preg(object):
'''
Object mapping representing HKCU entry (registry key with SID)
'''
def __init__(self, sid, preg_obj):
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.type = preg_obj.type
self.data = preg_obj.data
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['type'] = self.type
fields['data'] = self.data
return fields
class ad_shortcut(object):
'''
Object mapping representing Windows shortcut.
'''
def __init__(self, sid, sc):
def __init__(self, sid, sc, policy_name):
self.sid = sid
self.policy_name = policy_name
self.path = sc.dest
self.shortcut = sc.to_json()
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['path'] = self.path
fields['shortcut'] = self.shortcut
return fields
class info_entry(object):
def __init__(self, name, value):
self.name = name
self.value = value
def update_fields(self):
fields = dict()
fields['value'] = self.value
return fields
class printer_entry(object):
'''
Object mapping representing Windows printer of some type.
'''
def __init__(self, sid, pobj):
def __init__(self, sid, pobj, policy_name):
self.sid = sid
self.policy_name = policy_name
self.name = pobj.name
self.printer = pobj.to_json()
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['name'] = self.name
fields['printer'] = self.printer.to_json()
return fields
class drive_entry(object):
'''
Object mapping representing Samba share bound to drive letter
'''
def __init__(self, sid, dobj, policy_name):
self.sid = sid
self.policy_name = policy_name
self.login = dobj.login
self.password = dobj.password
self.dir = dobj.dir
self.path = dobj.path
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['login'] = self.login
fields['password'] = self.password
fields['dir'] = self.dir
fields['path'] = self.path
return fields
class folder_entry(object):
'''
Object mapping representing file system directory
'''
def __init__(self, sid, fobj, policy_name):
self.sid = sid
self.policy_name = policy_name
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_files = str(fobj.delete_files)
def update_fields(self):
'''
Return list of fields to update
'''
fields = dict()
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_files'] = self.delete_files
return fields

View File

@ -18,7 +18,6 @@
from .cache import cache
import logging
import os
from sqlalchemy import (
@ -34,7 +33,7 @@ from sqlalchemy.orm import (
sessionmaker
)
from util.logging import slogm
from util.logging import log
from util.paths import cache_dir
def mapping_factory(mapper_suffix):
@ -53,7 +52,8 @@ class sqlite_cache(cache):
self.cache_name = cache_name
self.mapper_obj = mapping_factory(self.cache_name)
self.storage_uri = os.path.join('sqlite:///{}/{}.sqlite'.format(cache_dir(), self.cache_name))
logging.debug(slogm('Initializing cache {}'.format(self.storage_uri)))
logdata = dict({'cache_file': self.storage_uri})
log('D20', logdata)
self.db_cnt = create_engine(self.storage_uri, echo=False)
self.__metadata = MetaData(self.db_cnt)
self.cache_table = Table(
@ -80,7 +80,9 @@ class sqlite_cache(cache):
def get_default(self, obj_id, default_value):
result = self.get(obj_id)
if result == None:
logging.debug(slogm('No value cached for {}'.format(obj_id)))
logdata = dict()
logdata['object'] = obj_id
log('D43', logdata)
self.store(obj_id, default_value)
return str(default_value)
return result.value
@ -89,9 +91,11 @@ class sqlite_cache(cache):
try:
self.db_session.add(obj)
self.db_session.commit()
except:
except Exception as exc:
self.db_session.rollback()
logging.error(slogm('Error inserting value into cache, will update the value'))
logdata = dict()
logdata['msg'] = str(exc)
log('D44', logdata)
self.db_session.query(self.mapper_obj).filter(self.mapper_obj.str_id == obj.str_id).update({ 'value': obj.value })
self.db_session.commit()

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
from sqlalchemy import (
@ -33,7 +32,7 @@ from sqlalchemy.orm import (
sessionmaker
)
from util.logging import slogm
from util.logging import log
from util.paths import cache_dir
from .registry import registry
from .record_types import (
@ -42,6 +41,8 @@ from .record_types import (
, ad_shortcut
, info_entry
, printer_entry
, drive_entry
, folder_entry
)
class sqlite_registry(registry):
@ -61,40 +62,69 @@ class sqlite_registry(registry):
Column('value', String(65536))
)
self.__hklm = Table(
'HKLM',
self.__metadata,
Column('id', Integer, primary_key=True),
Column('hive_key', String(65536), unique=True),
Column('type', Integer),
Column('data', String)
'HKLM'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('hive_key', String(65536), unique=True)
, Column('policy_name', String)
, Column('type', Integer)
, Column('data', String)
)
self.__hkcu = Table(
'HKCU',
self.__metadata,
Column('id', Integer, primary_key=True),
Column('sid', String),
Column('hive_key', String(65536)),
Column('type', Integer),
Column('data', String),
UniqueConstraint('sid', 'hive_key')
'HKCU'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('hive_key', String(65536))
, Column('policy_name', String)
, Column('type', Integer)
, Column('data', String)
, UniqueConstraint('sid', 'hive_key')
)
self.__shortcuts = Table(
'Shortcuts',
self.__metadata,
Column('id', Integer, primary_key=True),
Column('sid', String),
Column('path', String),
Column('shortcut', String),
UniqueConstraint('sid', 'path')
'Shortcuts'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('path', String)
, Column('policy_name', String)
, Column('shortcut', String)
, UniqueConstraint('sid', 'path')
)
self.__printers = Table(
'Printers',
self.__metadata,
Column('id', Integer, primary_key=True),
Column('sid', String),
Column('name', String),
Column('printer', String),
UniqueConstraint('sid', 'name')
'Printers'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('name', String)
, Column('policy_name', String)
, Column('printer', String)
, UniqueConstraint('sid', 'name')
)
self.__drives = Table(
'Drives'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('login', String)
, Column('password', String)
, Column('dir', String)
, Column('policy_name', String)
, Column('path', String)
, UniqueConstraint('sid', 'dir')
)
self.__folders = Table(
'Folders'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('path', String)
, Column('policy_name', String)
, Column('action', String)
, Column('delete_folder', String)
, Column('delete_sub_folder', String)
, Column('delete_files', String)
, UniqueConstraint('sid', 'path')
)
self.__metadata.create_all(self.db_cnt)
Session = sessionmaker(bind=self.db_cnt)
@ -105,6 +135,8 @@ class sqlite_registry(registry):
mapper(samba_hkcu_preg, self.__hkcu)
mapper(ad_shortcut, self.__shortcuts)
mapper(printer_entry, self.__printers)
mapper(drive_entry, self.__drives)
mapper(folder_entry, self.__folders)
except:
pass
#logging.error('Error creating mapper')
@ -121,133 +153,185 @@ class sqlite_registry(registry):
try:
self._add(row)
except:
update_obj = dict({ 'value': row.value })
(self
.db_session.query(info_entry)
.filter(info_entry.name == row.name)
.update(update_obj))
.update(row.update_fields()))
self.db_session.commit()
def _hklm_upsert(self, row):
try:
self._add(row)
except:
update_obj = dict({'type': row.type, 'data': row.data })
(self
.db_session
.query(samba_preg)
.filter(samba_preg.hive_key == row.hive_key)
.update(update_obj))
.update(row.update_fields()))
self.db_session.commit()
def _hkcu_upsert(self, row):
try:
self._add(row)
except:
update_obj = dict({'type': row.type, 'data': row.data })
except Exception as exc:
(self
.db_session
.query(samba_preg)
.query(samba_hkcu_preg)
.filter(samba_hkcu_preg.sid == row.sid)
.filter(samba_hkcu_preg.hive_key == row.hive_key)
.update(update_obj))
.update(row.update_fields()))
self.db_session.commit()
def _shortcut_upsert(self, row):
try:
self._add(row)
except:
update_obj = dict({ 'shortcut': row.shortcut })
(self
.db_session
.query(ad_shortcut)
.filter(ad_shortcut.sid == row.sid)
.filter(ad_shortcut.path == row.path)
.update(update_obj))
.update(row.update_fields()))
self.db_session.commit()
def _printer_upsert(self, row):
try:
self._add(row)
except:
update_obj = dict({ 'printer': row.printer })
(self
.db_session
.query(printer_entry)
.filter(printer_entry.sid == row.sid)
.filter(printer_entry.name == row.name)
.update(update_obj))
.update(row.update_fields()))
self.db_session.commit()
def _drive_upsert(self, row):
try:
self._add(row)
except:
(self
.db_session
.query(drive_entry)
.filter(drive_entry.sid == row.sid)
.filter(drive_entry.dir == row.dir)
.update(row.update_fields()))
self.db_session.commit()
def set_info(self, name, value):
ientry = info_entry(name, value)
logging.debug(slogm('Setting info {}:{}'.format(name, value)))
logdata = dict()
logdata['varname'] = name
logdata['value'] = value
log('D19', logdata)
self._info_upsert(ientry)
def add_hklm_entry(self, preg_entry):
def add_hklm_entry(self, preg_entry, policy_name):
'''
Write PReg entry to HKEY_LOCAL_MACHINE
'''
pentry = samba_preg(preg_entry)
pentry = samba_preg(preg_entry, policy_name)
if not pentry.hive_key.rpartition('\\')[2].startswith('**'):
self._hklm_upsert(pentry)
else:
logging.warning(slogm('Skipping branch deletion key: {}'.format(pentry.hive_key)))
logdata = dict({'key': pentry.hive_key})
log('D27', logdata)
def add_hkcu_entry(self, preg_entry, sid):
def add_hkcu_entry(self, preg_entry, sid, policy_name):
'''
Write PReg entry to HKEY_CURRENT_USER
'''
hkcu_pentry = samba_hkcu_preg(sid, preg_entry)
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('**'):
logging.debug(slogm('Adding HKCU entry for {}'.format(sid)))
log('D26', logdata)
self._hkcu_upsert(hkcu_pentry)
else:
logging.warning(slogm('Skipping branch deletion key: {}'.format(hkcu_pentry.hive_key)))
log('D51', logdata)
def add_shortcut(self, sid, sc_obj):
def add_shortcut(self, sid, sc_obj, policy_name):
'''
Store shortcut information in the database
'''
sc_entry = ad_shortcut(sid, sc_obj)
logging.debug(slogm('Saving info about {} link for {}'.format(sc_entry.path, sid)))
sc_entry = ad_shortcut(sid, sc_obj, policy_name)
logdata = dict()
logdata['link'] = sc_entry.path
logdata['sid'] = sid
log('D41', logdata)
self._shortcut_upsert(sc_entry)
def add_printer(self, sid, pobj):
def add_printer(self, sid, pobj, policy_name):
'''
Store printer configuration in the database
'''
prn_entry = printer_entry(sid, pobj)
logging.debug(slogm('Saving info about printer {} for {}'.format(prn_entry.name, sid)))
prn_entry = printer_entry(sid, pobj, policy_name)
logdata = dict()
logdata['printer'] = prn_entry.name
logdata['sid'] = sid
log('D40', logdata)
self._printer_upsert(prn_entry)
def get_shortcuts(self, sid):
def add_drive(self, sid, dobj, policy_name):
drv_entry = drive_entry(sid, dobj, policy_name)
logdata = dict()
logdata['uri'] = drv_entry.path
logdata['sid'] = sid
log('D39', logdata)
self._drive_upsert(drv_entry)
def add_folder(self, sid, fobj, policy_name):
fld_entry = folder_entry(sid, fobj, policy_name)
logdata = dict()
logdata['folder'] = fld_entry.path
logdata['sid'] = sid
log('D42', logdata)
try:
self._add(fld_entry)
except Exception as exc:
(self
._filter_sid_obj(folder_entry, sid)
.filter(folder_entry.path == fld_entry.path)
.update(fld_entry.update_fields()))
self.db_session.commit()
def _filter_sid_obj(self, row_object, sid):
res = (self
.db_session
.query(ad_shortcut)
.filter(ad_shortcut.sid == sid)
.query(row_object)
.filter(row_object.sid == sid))
return res
def _filter_sid_list(self, row_object, sid):
res = (self
.db_session
.query(row_object)
.filter(row_object.sid == sid)
.all())
return res
def get_shortcuts(self, sid):
return self._filter_sid_list(ad_shortcut, sid)
def get_printers(self, sid):
res = (self
.db_session
.query(printer_entry)
.filter(printer_entry.sid == sid)
.all())
return res
return self._filter_sid_list(printer_entry, sid)
def get_drives(self, sid):
return self._filter_sid_list(drive_entry, sid)
def get_folders(self, sid):
return self._filter_sid_list(folder_entry, sid)
def get_hkcu_entry(self, sid, hive_key):
res = (self
.db_session
.query(samba_preg)
.query(samba_hkcu_preg)
.filter(samba_hkcu_preg.sid == sid)
.filter(samba_hkcu_preg.hive_key == hive_key)
.first())
# Try to get the value from machine SID as a default if no option is set.
if not res:
machine_sid = self.get_info('machine_sid')
res = self.db_session.query(samba_preg).filter(samba_hkcu_preg.sid == machine_sid).filter(samba_hkcu_preg.hive_key == hive_key).first()
res = self.db_session.query(samba_hkcu_preg).filter(samba_hkcu_preg.sid == machine_sid).filter(samba_hkcu_preg.hive_key == hive_key).first()
return res
def filter_hkcu_entries(self, sid, startswith):
@ -282,31 +366,16 @@ class sqlite_registry(registry):
return res
def wipe_user(self, sid):
self.wipe_hkcu(sid)
self.wipe_shortcuts(sid)
self.wipe_printers(sid)
self._wipe_sid(samba_hkcu_preg, sid)
self._wipe_sid(ad_shortcut, sid)
self._wipe_sid(printer_entry, sid)
self._wipe_sid(drive_entry, sid)
def wipe_shortcuts(self, sid):
def _wipe_sid(self, row_object, sid):
(self
.db_session
.query(ad_shortcut)
.filter(ad_shortcut.sid == sid)
.delete())
self.db_session.commit()
def wipe_printers(self, sid):
(self
.db_session
.query(printer_entry)
.filter(printer_entry.sid == sid)
.delete())
self.db_session.commit()
def wipe_hkcu(self, sid):
(self
.db_session
.query(samba_hkcu_preg)
.filter(samba_hkcu_preg.sid == sid)
.query(row_object)
.filter(row_object.sid == sid)
.delete())
self.db_session.commit()

View File

@ -0,0 +1,29 @@
{#
# 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/>.
#}
{% if Deny_All == '1' %}
polkit.addRule(function (action, subject) {
if ((action.id == "org.freedesktop.udisks2.filesystem-mount" ||
action.id == "org.freedesktop.udisks2.filesystem-mount-system" ||
action.id == "org.freedesktop.udisks2.filesystem-mount-other-seat") &&
subject.user == "{{User}}" ) {
return polkit.Result.NO;
}
});
{% endif %}

View File

@ -0,0 +1,20 @@
{#
# 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/>.
#}
{{ home_dir }}/net {{ mount_file }} -t 120

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/>.
#}
{% if login %}
username={{ login }}
{% endif %}
{% if password %}
password={{ password }}
{% endif %}

View File

@ -0,0 +1,21 @@
{#
# 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/>.
#}
{%- for drv in drives %}
{{ drv.dir }} -fstype=cifs,cruid=$USER,sec=krb5,noperm :{{ drv.path }}
{% endfor %}

View File

@ -0,0 +1,39 @@
#
# 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 unittest
from frontend.appliers.rpm import rpm
class PackageTestCase(unittest.TestCase):
'''
Semi-integrational tests for packages installation/removing
'''
def test_package_not_exist(self):
packages_for_install = 'dummy1 dummy2'
packages_for_remove = 'dummy3'
test_rpm = rpm(packages_for_install, packages_for_remove)
test_rpm.apply()
def test_install_remove_same_package(self):
packages_for_install = 'gotop'
packages_for_remove = 'gotop'
test_rpm = rpm(packages_for_install, packages_for_remove)
test_rpm.apply()

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<Printers clsid="{1F577D12-3D1B-471e-A1B7-060317597B9C}"><PortPrinter clsid="{C3A739D2-4A44-401e-9F9D-88E5E77DFB3E}" name="10.64.128.250" status="10.64.128.250" image="0" changed="2020-01-23 11:48:07" uid="{88D998C2-9875-4278-A607-EC828839EFCE}" userContext="1" bypassErrors="1"><Properties lprQueue="" snmpCommunity="public" protocol="PROTOCOL_RAWTCP_TYPE" portNumber="9100" doubleSpool="0" snmpEnabled="0" snmpDevIndex="1" ipAddress="10.64.128.250" action="C" location="" localName="printer" comment="" default="1" skipLocal="0" useDNS="0" path="\\prnt" deleteAll="0"/><Filters><FilterGroup bool="AND" not="0" name="DOMAIN\Domain Users" sid="S-1-5-21-3359553909-270469630-9462315-513" userContext="1" primaryGroup="0" localGroup="0"/></Filters></PortPrinter>
</Printers>

Binary file not shown.

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><TaskV2 clsid="{D8896631-B747-47a7-84A6-C155337F3BC8}" name="mytask" image="2" changed="2020-01-24 13:06:25" uid="{0DBF3CAA-3DCF-4AAA-A52F-82B010B35380}"><Properties action="U" name="mytask" runAs="%LogonDomain%\%LogonUser%" logonType="InteractiveToken"><Task version="1.3"><RegistrationInfo><Author>DOMAIN\samba</Author><Description></Description></RegistrationInfo><Principals><Principal id="Author"><UserId>%LogonDomain%\%LogonUser%</UserId><LogonType>InteractiveToken</LogonType><RunLevel>HighestAvailable</RunLevel></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>true</AllowHardTerminate><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><ExecutionTimeLimit>P3D</ExecutionTimeLimit><Priority>7</Priority></Settings><Triggers><CalendarTrigger><StartBoundary>2020-01-24T14:59:48</StartBoundary><Enabled>true</Enabled><ScheduleByDay><DaysInterval>1</DaysInterval></ScheduleByDay></CalendarTrigger></Triggers><Actions><Exec><Command>C:\Program Files (x86)\Google\Chrome\Application\chrome.exe</Command></Exec></Actions></Task></Properties></TaskV2>
</ScheduledTasks>

View File

@ -0,0 +1,42 @@
#
# 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 unittest
import unittest.mock
import os
import util.paths
import json
class GptDrivesTestCase(unittest.TestCase):
@unittest.mock.patch('util.paths.cache_dir')
def test_drive_reader(self, cdir_mock):
'''
Test functionality to read objects from Shortcuts.xml
'''
cdir_mock.return_value = '/var/cache/gpupdate'
import gpt.drives
testdata_path = '{}/test/gpt/data/Drives.xml'.format(os.getcwd())
drvs = gpt.drives.read_drives(testdata_path)
json_obj = json.loads(drvs[0].to_json())
self.assertIsNotNone(json_obj['drive'])

View File

@ -0,0 +1,32 @@
#
# 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 unittest
from util.xdg import (
xdg_get_desktop_user
)
class XDGTestCase(unittest.TestCase):
def test_get_desktop_dir(self):
print('Machine desktop:')
print(xdg_get_desktop_user(None))
print('Users desktop:')
print(xdg_get_desktop_user('nir'))

View File

@ -20,6 +20,7 @@ import logging
import logging.handlers
from enum import IntEnum
from messages import message_with_code
from .logging import slogm
@ -43,7 +44,6 @@ def set_loglevel(loglevel_num=None):
log_level = 10 * log_num
print('Setting log level to {}'.format(loglevels[log_num]))
logging.basicConfig(format=format_message)
logger = logging.getLogger()
logger.setLevel(log_level)
@ -72,7 +72,8 @@ def process_target(target_name=None):
if target_name == 'User':
target = 'User'
logging.debug(slogm('Target is: {}'.format(target)))
logdata = dict({'target': target})
logging.debug(slogm(message_with_code('D10'), logdata))
return target

View File

@ -16,10 +16,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import dbus
from .logging import slogm
from .logging import log
from .users import is_root
@ -41,28 +40,31 @@ class dbus_runner:
def run(self):
#print(obj.Introspect()[0])
if self.username:
logging.info(slogm('Starting GPO applier for user {} via D-Bus'.format(self.username)))
logdata = dict({'username': self.username})
log('D6', logdata)
if is_root():
try:
result = self.interface.gpupdatefor(dbus.String(self.username))
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
logging.error(slogm('No reply from oddjobd gpoa runner for {}'.format(self.username)))
logdata = dict()
logdata['username'] = self.username
log('E23', logdata)
raise exc
else:
try:
result = self.interface.gpupdate()
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
logging.error(slogm('No reply from oddjobd gpoa runner for current user'))
log('E21')
raise exc
else:
logging.info(slogm('Starting GPO applier for computer via D-Bus'))
log('D11')
try:
result = self.interface.gpupdate_computer()
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
logging.error(slogm('No reply from oddjobd gpoa runner for computer'))
log('E22')
raise exc
#self.interface.Quit()
@ -130,7 +132,8 @@ def print_dbus_result(result):
'''
exitcode = result[0]
message = result[1:]
logging.debug(slogm('Exit code is {}'.format(exitcode)))
logdata = dict({'retcode': exitcode})
log('D12', logdata)
for line in message:
print(str(line))

41
gpoa/util/exceptions.py Normal file
View File

@ -0,0 +1,41 @@
#
# 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 sys
def geterr():
'''
Fetches information about recent exception so we will be able
to print tracebacks and other information in a uniform way.
'''
etype, evalue, etrace = sys.exc_info()
traceinfo = dict({
'file': etrace.tb_frame.f_code.co_filename
, 'line': etrace.tb_lineno
, 'name': etrace.tb_frame.f_code.co_name
, 'type': etype.__name__
, 'message': evalue
})
del(etype, evalue, etrace)
return traceinfo

View File

@ -16,20 +16,54 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
import subprocess
from .util import get_machine_name
from .logging import slogm
from .logging import log
def machine_kinit():
def machine_kinit(cache_name=None):
'''
Perform kinit with machine credentials
'''
host = get_machine_name()
subprocess.call(['kinit', '-k', host])
return check_krb_ticket()
os.environ['KRB5CCNAME'] = 'FILE:{}'.format(cache_name)
kinit_cmd = ['kinit', '-k', host]
if cache_name:
kinit_cmd.extend(['-c', cache_name])
proc = subprocess.Popen(kinit_cmd)
proc.wait()
result = False
if 0 == proc.returncode:
result = True
if result:
result = check_krb_ticket()
return result
def machine_kdestroy(cache_name=None):
'''
Perform kdestroy for machine credentials
'''
host = get_machine_name()
kdestroy_cmd = ['kdestroy']
if cache_name:
kdestroy_cmd.extend(['-c', cache_name])
proc = subprocess.Popen(kdestroy_cmd, stderr=subprocess.DEVNULL)
proc.wait()
if cache_name and os.path.exists(cache_name):
os.unlink(cache_name)
elif 'KRB5CCNAME' in os.environ:
path = os.environ['KRB5CCNAME'][5:]
if os.path.exists(path):
os.unlink(path)
def check_krb_ticket():
@ -43,8 +77,8 @@ def check_krb_ticket():
logging.info(output)
result = True
except:
logging.error(slogm('Kerberos ticket check unsuccessful'))
log('E14')
logging.debug(slogm('Ticket check succeed'))
log('D17')
return result

View File

@ -18,11 +18,15 @@
import json
import datetime
import logging
from messages import message_with_code
class encoder(json.JSONEncoder):
def default(self, obj):
result = super(encoder, self).default(obj)
result = super(encoder, self)
result = result.default(obj)
if isinstance(obj, set):
result = tuple(obj)
@ -36,19 +40,42 @@ class slogm(object):
'''
Structured log message class
'''
def __init__(self, message, **kwargs):
def __init__(self, message, kwargs=dict()):
self.message = message
self.kwargs = kwargs
if not self.kwargs:
self.kwargs = dict()
def __str__(self):
now = str(datetime.datetime.now())
now = str(datetime.datetime.now().isoformat(sep=' ', timespec='milliseconds'))
args = dict()
args.update(dict({'timestamp': now, 'message': str(self.message)}))
#args.update(dict({'timestamp': now, 'message': str(self.message)}))
args.update(self.kwargs)
kwa = encoder().encode(args)
result = '{}:{}'.format(now.rpartition('.')[0], self.message)
result = '{}|{}|{}'.format(now, self.message, kwa)
return result
def log(message_code, data=None):
mtype = message_code[0]
if 'I' == mtype:
logging.info(slogm(message_with_code(message_code), data))
return
if 'W' == mtype:
logging.warning(slogm(message_with_code(message_code), data))
return
if 'E' == mtype:
logging.error(slogm(message_with_code(message_code), data))
return
if 'F' == mtype:
logging.fatal(slogm(message_with_code(message_code), data))
return
if 'D' == mtype:
logging.debug(slogm(message_with_code(message_code), data))
return
logging.error(slogm(message_with_code(message_code), data))

View File

@ -59,31 +59,3 @@ def local_policy_cache():
return lpcache
def backend_module_dir():
backend_dir = '/usr/lib/gpoa/backend'
return pathlib.Path(backend_dir)
def frontend_module_dir():
frontend_dir = '/usr/lib/gpoa/frontend'
return pathlib.Path(frontend_dir)
def storage_module_dir():
storage_dir = '/usr/lib/gpoa/storage'
return pathlib.Path(storage_dir)
def pre_backend_plugin_dir():
pre_backend_dir = '/usr/lib/gpoa/backend_pre'
return pathlib.Path(pre_backend_dir)
def post_backend_plugin_dir():
post_backend_dir = '/usr/lib/gpoa/backend_post'
return pathlib.Path(post_backend_dir)
def pre_frontend_plugin_dir():
pre_forntend_dir = '/usr/lib/gpoa/frontend_pre'
return pathlib.Path(pre_frontend_dir)
def post_frontend_plugin_dir():
post_frontend_dir = '/usr/lib/gpoa/frontend_post'
return pathlib.Path(post_frontend_dir)

View File

@ -16,14 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from xml.etree import ElementTree
from storage import registry_factory
from samba.gp_parse.gp_pol import GPPolParser
from .logging import slogm
from .logging import log
def load_preg(file_path):
@ -40,7 +39,8 @@ def load_xml_preg(xml_path):
'''
Parse XML/PReg file and return its preg object
'''
logging.debug('Loading PReg from XML: {}'.format(xml_path))
logdata = dict({'polfile': xml_path})
log('D36', logdata)
gpparser = GPPolParser()
xml_root = ElementTree.parse(xml_path).getroot()
gpparser.load_xml(xml_root)
@ -53,16 +53,20 @@ def load_pol_preg(polfile):
'''
Parse PReg file and return its preg object
'''
logging.debug(slogm('Loading PReg from .pol file: {}'.format(polfile)))
logdata = dict({'polfile': polfile})
log('D31', logdata)
gpparser = GPPolParser()
data = None
with open(polfile, 'rb') as f:
data = f.read()
logdata = dict({'polfile': polfile, 'length': len(data)})
log('D33', logdata)
gpparser.parse(data)
#print(gpparser.pol_file.__ndr_print__())
return gpparser.pol_file
pentries = preg2entries(gpparser.pol_file)
return pentries
def preg_keymap(preg):
@ -76,15 +80,16 @@ def preg_keymap(preg):
return keymap
def merge_polfile(preg, sid=None, reg_name='registry', reg_path=None):
def merge_polfile(preg, sid=None, reg_name='registry', reg_path=None, policy_name='Unknown'):
pregfile = load_preg(preg)
logging.info(slogm('Loaded PReg {}'.format(preg)))
logdata = dict({'pregfile': preg})
log('D32', logdata)
storage = registry_factory(reg_name, reg_path)
for entry in pregfile.entries:
if not sid:
storage.add_hklm_entry(entry)
storage.add_hklm_entry(entry, policy_name)
else:
storage.add_hkcu_entry(entry, sid)
storage.add_hkcu_entry(entry, sid, policy_name)
class entry:
@ -93,12 +98,22 @@ class entry:
self.valuename = e_valuename
self.type = e_type
self.data = e_data
logdata = dict()
logdata['keyname'] = self.keyname
logdata['valuename'] = self.valuename
logdata['type'] = self.type
logdata['data'] = self.data
log('D22', logdata)
class pentries:
def __init__(self):
self.entries = list()
def preg2entries(preg_obj):
entries = []
for elem in prej_obj.entries:
entries = pentries()
for elem in preg_obj.entries:
entry_obj = entry(elem.keyname, elem.valuename, elem.type, elem.data)
entries.append(entry_obj)
entries.entries.append(entry_obj)
return entries

View File

@ -34,11 +34,11 @@ def is_rpm_installed(rpm_name):
return False
class Package:
__install_command = ['/usr/bin/apt-get', '-y', 'install']
__remove_command = ['/usr/bin/apt-get', '-y', 'remove']
__reinstall_command = ['/usr/bin/apt-get', '-y', 'reinstall']
def __init__(self, package_name):
self.__install_command = ['/usr/bin/apt-get', '-y', 'install']
self.__remove_command = ['/usr/bin/apt-get', '-y', 'remove']
self.__reinstall_command = ['/usr/bin/apt-get', '-y', 'reinstall']
self.package_name = package_name
self.for_install = True
@ -102,7 +102,6 @@ def install_rpm(rpm_name):
'''
Install single RPM
'''
update()
rpm = Package(rpm_name)
return rpm.install()

245
gpoa/util/sid.py Normal file
View File

@ -0,0 +1,245 @@
#! /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/>.
from enum import Enum
def wbinfo_getsid(domain, user):
'''
Get SID using wbinfo
'''
# This part works only on client
username = '{}\\{}'.format(domain.upper(), user)
sid = pysss_nss_idmap.getsidbyname(username)
if username in sid:
return sid[username]['sid']
# This part works only on DC
wbinfo_cmd = ['wbinfo', '-n', username]
output = subprocess.check_output(wbinfo_cmd)
sid = output.split()[0].decode('utf-8')
return sid
def get_sid(domain, username):
'''
Lookup SID not only using wbinfo or sssd but also using own cache
'''
domain_username = '{}\\{}'.format(domain, username)
sid = 'local-{}'.format(username)
try:
sid = wbinfo_getsid(domain, username)
except:
sid = 'local-{}'.format(username)
logging.warning(
slogm('Error getting SID using wbinfo, will use cached SID: {}'.format(sid)))
logging.debug(slogm('Working with SID: {}'.format(sid)))
return sid
class IssuingAuthority(Enum):
SECURITY_NULL_SID_AUTHORITY = 0
SECURITY_WORLD_SID_AUTHORITY = 1
SECURITY_LOCAL_SID_AUTHORITY = 2
SECURITY_CREATOR_SID_AUTHORITY = 3
SECURITY_NON_UNIQUE_AUTHORITY = 4
SECURITY_NT_AUTHORITY = 5
SECURITY_RESOURCE_MANAGER_AUTHORITY = 9
class SidRevision(Enum):
FIRST = 1
# This thing exists only after "S-1-5-21-"
# Last part of full SID
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab
class WellKnown21RID(Enum):
ENTERPRISE_READONLY_DOMAIN_CONTROLLERS = 498
ADMINISTRATOR = 500 # For machine
GUEST = 501 # For machine
KRBTGT = 502
DOMAIN_ADMINS = 512
DOMAIN_USERS = 513
DOMAIN_GUESTS = 514
DOMAIN_COMPUTERS = 515
DOMAIN_CONTROLLERS = 516
CERT_PUBLISHERS = 517
SCHEMA_ADMINISTRATORS = 518 # For root domain
ENTERPRISE_ADMINS = 519 # For root domain
GROUP_POLICY_CREATOR_OWNERS = 520
READONLY_DOMAIN_CONTROLLERS = 521
CLONEABLE_CONTROLLERS = 522
PROTECTED_USERS = 525
KEY_ADMINS = 526
ENTERPRISE_KEY_ADMINS = 527
RAS_SERVERS = 553
ALLOWED_RODC_PASSWORD_REPLICATION_GROUP = 571
DENIED_RODC_PASSWORD_REPLICATION_GROUP = 572
# This thing exists only after "S-1-5-32-"
# Last part of full SID
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab
class WellKnown32RID(Enum):
BUILTIN_ADMINISTRATORS = 544
BUILTIN_USERS = 545
BUILTIN_GUESTS = 546
POWER_USERS = 547
ACCOUNT_OPERATORS = 548
SERVER_OPERATORS = 549
PRINTER_OPERATORS = 550
BACKUP_OPERATORS = 551
REPLICATOR = 552
ALIAS_PREW2KCOMPACC = 554
REMOTE_DESKTOP = 555
NETWORK_CONFIGURATION_OPS = 556
INCOMING_FOREST_TRUST_BUILDERS = 557
PERFMON_USERS = 558
PERFLOG_USERS = 559
WINDOWS_AUTHORIZATION_ACCESS_GROUP = 560
TERMINAL_SERVER_LICENSE_SERVERS = 561
DISTRIBUTED_COM_USERS = 562
IIS_IUSRS = 568
CRYPTOGRAPHIC_OPERATORS = 569
EVENT_LOG_READERS = 573
CERTIFICATE_SERVICE_DCOM_ACCESS = 574
RDS_REMOTE_ACCESS_SERVERS = 575
RDS_ENDPOINT_SERVERS = 576
RDS_MANAGEMENT_SERVERS = 577
HYPER_V_ADMINS = 578
ACCESS_CONTROL_ASSISTANCE_OPS = 579
REMOTE_MANAGEMENT_USERS = 580
# This thing exists only after "S-1-5-"
class FirstSubAuthority(Enum):
SECURITY_DIALUP_RID = 1
SECURITY_NETWORK_RID = 2
SECURITY_BATCH_RID = 3
SECURITY_INTERACTIVE_RID = 4
SECURITY_LOGON_IDS_RID = 5
SECURITY_SERVICE_RID = 6
SECURITY_ANONYMOUS_LOGON_RID = 7
SECURITY_PROXY_RID = 8
SECURITY_ENTERPRISE_CONTROLLERS_RID = 9
SECURITY_PRINCIPAL_SELF_RID = 10
SECURITY_AUTHENTICATED_USER_RID = 11
SECURITY_RESTRICTED_CODE_RID = 12
SECURITY_TERMINAL_SERVER_RID = 13
SECURITY_LOCAL_SYSTEM_RID = 18
SECURITY_NT_NON_UNIQUE = 21
SECURITY_BUILTIN_DOMAIN_RID = 32
SECURITY_WRITE_RESTRICTED_CODE_RID = 33
class SecondSubAuthority(Enum):
DOMAIN_ALIAS_RID_ADMINS = 544
def validate_issuing_authority(ia_num):
ia_value = None
ia_value = int(IssuingAuthority(ia_num))
return ia_value
def validate_sid_revision(revnum):
rev_value = None
rev_value = int(SidRevision(revnum))
return rev_value
def is_sid(sid):
# Check that SID is SID (S)
if not sid[0] == 'S':
return False
# Check revision version (1 for Windows-generated SID) (R)
if not validate_sid_revision(int(sid[2])):
return False
# Check issuing authority (IA)
issuing_authority = validate_issuing_authority(int(sid[4]))
if not issuing_authority:
return False
if issuing_authority == 21:
pass
elif issuing_authority == 32:
pass
else:
pass
def sid2descr(sid):
sids = dict()
sids['S-1-0'] = 'Null Authority'
sids['S-1-0-0'] = 'Nobody'
sids['S-1-1'] = 'World Authority'
sids['S-1-1-0'] = 'Everyone'
sids['S-1-2'] = 'Local Authority'
sids['S-1-2-0'] = 'Local'
sids['S-1-3'] = 'Creator Authority'
sids['S-1-3-0'] = 'Creator Owner'
sids['S-1-3-1'] = 'Creator Group'
sids['S-1-3-2'] = 'Creator Owner Server' # Since Windows 2003
sids['S-1-3-3'] = 'Creator Group Server' # Since Windows 2003
sids['S-1-3-4'] = 'Owner Rights'
sids['S-1-4'] = 'Non-unique Authority'
sids['S-1-5'] = 'NT Authority'
sids['S-1-5-1'] = 'Dialup'
sids['S-1-5-2'] = 'Network'
sids['S-1-5-3'] = 'Batch'
sids['S-1-5-4'] = 'Interactive'
sids['S-1-5-6'] = 'Service'
sids['S-1-5-7'] = 'Anonymous'
sids['S-1-5-8'] = 'Proxy' # Since Windows 2003
sids['S-1-5-9'] = 'Enterprise Domain Controllers'
sids['S-1-5-10'] = 'Principal Self'
sids['S-1-5-11'] = 'Authenticated Users'
sids['S-1-5-12'] = 'Restricted Code'
sids['S-1-5-13'] = 'Terminal Server Users'
sids['S-1-5-14'] = 'Remote Interactive Logon'
sids['S-1-5-15'] = 'This Organization' # Since Windows 2003
sids['S-1-5-17'] = 'This Organization'
sids['S-1-5-18'] = 'Local System'
sids['S-1-5-19'] = 'NT Authority' # Local Service
sids['S-1-5-20'] = 'NT Authority' # Network Service
sids['S-1-5-32-544'] = 'Administrators'
sids['S-1-5-32-545'] = 'Users'
sids['S-1-5-32-546'] = 'Guests'
sids['S-1-5-32-547'] = 'Power Users'
sids['S-1-5-32-548'] = 'Account Operators'
sids['S-1-5-32-549'] = 'Server Operators'
sids['S-1-5-32-550'] = 'Print Operators'
sids['S-1-5-32-551'] = 'Backup Operators'
sids['S-1-5-32-552'] = 'Replicators'
sids['S-1-5-32-554'] = 'Builtin\\Pre-Windows 2000 Compatible Access' # Since Windows 2003
sids['S-1-5-32-555'] = 'Builtin\\Remote Desktop Users' # Since Windows 2003
sids['S-1-5-32-556'] = 'Builtin\\Network Configuration Operators' # Since Windows 2003
sids['S-1-5-32-557'] = 'Builtin\\Incoming Forest Trust Builders' # Since Windows 2003
sids['S-1-5-32-558'] = 'Builtin\\Performance Monitor Users' # Since Windows 2003
sids['S-1-5-32-582'] = 'Storage Replica Administrators'
sids['S-1-5-64-10'] = 'NTLM Authentication'
sids['S-1-5-64-14'] = 'SChannel Authentication'
sids['S-1-5-64-21'] = 'Digest Authentication'
sids['S-1-5-80'] = 'NT Service'
return sids.get(sid, None)

View File

@ -16,16 +16,20 @@
# 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 signal
from sys import exit
from .arguments import ExitCodeUpdater
default_handler = signal.getsignal(signal.SIGINT)
from .kerberos import machine_kdestroy
def signal_handler(sig_number, frame):
print('Received signal, exiting gracefully')
# Ignore extra signals
signal.signal(sig_number, signal.SIG_IGN)
print('Received signal, exiting gracefully')
exit(ExitCodeUpdater.EXIT_SIGINT)
# Kerberos cache cleanup on interrupt
machine_kdestroy()
os._exit(ExitCodeUpdater.EXIT_SIGINT)

View File

@ -18,9 +18,8 @@
import os
import pwd
import logging
from .logging import slogm
from .logging import log
def is_root():
@ -75,9 +74,11 @@ def set_privileges(username, uid, gid, groups=list()):
#except Exception as exc:
# print('setgroups')
logging.debug(
slogm('Set process permissions to UID {} and GID {} for user {}'.format(
uid, gid, username)))
logdata = dict()
logdata['uid'] = uid
logdata['gid'] = gid
logdata['username'] = username
log('D37', logdata)
def with_privileges(username, func):
@ -99,11 +100,14 @@ def with_privileges(username, func):
# We need to catch exception in order to be able to restore
# privileges later in this function
out = None
try:
func()
out = func()
except Exception as exc:
logging.debug(slogm(exc))
log(str(exc))
# Restore privileges
set_privileges('root', current_uid, 0, current_groups)
return out

View File

@ -17,7 +17,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
import pwd
@ -29,9 +28,12 @@ import samba.gpo
import pysss_nss_idmap
from storage import cache_factory
from .xdg import get_user_dir
from messages import message_with_code
from .xdg import (
xdg_get_desktop
)
from .util import get_homedir
from .logging import slogm
from .logging import log
from .samba import smbopts
@ -56,16 +58,19 @@ class smbcreds (smbopts):
samba_dc = get_dc_hostname(self.creds, self.lp)
if samba_dc != dc_fqdn and dc_fqdn is not None:
logging.debug(
slogm('Samba DC setting is {} and is overwritten by user setting {}'.format(
samba_dc, dc)))
logdata = dict()
logdata['dc'] = samba_dc
logdata['user_dc'] = dc
log('D38', logdata)
self.selected_dc = dc_fqdn
else:
self.selected_dc = samba_dc
except:
logging.error(slogm('Unable to determine DC hostname'))
except Exception as exc:
logdata = dict()
logdata['msg'] = str(exc)
log('E10', logdata)
raise exc
return self.selected_dc
@ -79,9 +84,11 @@ class smbcreds (smbopts):
# Look and python/samba/netcmd/domain.py for more examples
res = netcmd_get_domain_infos_via_cldap(self.lp, None, self.selected_dc)
dns_domainname = res.dns_domain
logging.info(slogm('Found domain via CLDAP: {}'.format(dns_domainname)))
except:
logging.error(slogm('Unable to retrieve domain name via CLDAP query'))
logdata = dict({'domain': dns_domainname})
log('D18', logdata)
except Exception as exc:
log('E15')
raise exc
return dns_domainname
@ -93,19 +100,22 @@ class smbcreds (smbopts):
gpos = list()
try:
log('D48')
ads = samba.gpo.ADS_STRUCT(self.selected_dc, self.lp, self.creds)
if ads.connect():
log('D47')
gpos = ads.get_gpo_list(username)
logging.info(slogm('Got GPO list for {}:'.format(username)))
logdata = dict({'username': username})
log('I1', logdata)
for gpo in gpos:
# These setters are taken from libgpo/pygpo.c
# print(gpo.ds_path) # LDAP entry
logging.info(slogm('GPO: {} ({})'.format(gpo.display_name, gpo.name)))
ldata = dict({'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name})
log('I2', ldata)
except Exception as exc:
logging.error(
slogm('Unable to get GPO list for {} from {}'.format(
username, self.selected_dc)))
logdata = dict({'username': username, 'dc': self.selected_dc})
log('E17', logdata)
return gpos
@ -113,11 +123,16 @@ class smbcreds (smbopts):
gpos = self.get_gpos(username)
try:
log('D49')
check_refresh_gpo_list(self.selected_dc, self.lp, self.creds, gpos)
log('D50')
except Exception as exc:
logging.error(
slogm('Unable to refresh GPO list for {} from {}'.format(
username, self.selected_dc)))
logdata = dict()
logdata['username'] = username
logdata['dc'] = self.selected_dc
logdata['err'] = str(exc)
log('F1')
raise exc
return gpos
@ -161,10 +176,11 @@ def get_sid(domain, username, is_machine = False):
try:
sid = wbinfo_getsid(domain, username)
except:
logging.warning(
slogm('Error getting SID using wbinfo, will use cached SID: {}'.format(sid)))
logdata = dict({'sid': sid})
log('E16', logdata)
logging.debug(slogm('Working with SID: {}'.format(sid)))
logdata = dict({'sid': sid})
log('D21', logdata)
return sid
@ -174,17 +190,15 @@ def expand_windows_var(text, username=None):
Scan the line for percent-encoded variables and expand them.
'''
variables = dict()
variables['HOME'] = '/'
variables['HOME'] = '/etc/skel'
variables['SystemRoot'] = '/'
variables['StartMenuDir'] = '/usr/share/applications'
variables['SystemDrive'] = '/'
variables['DesktopDir'] = xdg_get_desktop(username, variables['HOME'])
if username:
variables['HOME'] = get_homedir(username)
variables['DesktopDir'] = get_user_dir(
'DESKTOP', os.path.join(variables['HOME'], 'Desktop'))
variables['StartMenuDir'] = os.path.join(
variables['HOME'], '.local', 'share', 'applications')

View File

@ -17,21 +17,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from configparser import RawConfigParser, DEFAULTSECT
import os
from xdg.BaseDirectory import xdg_config_home
from .util import get_homedir
from .logging import log
def get_user_dir(dir_name, default=None):
'''
Get path to XDG's user directory
'''
config = RawConfigParser(allow_no_value=True)
userdirs_path = os.path.join(xdg_config_home, 'user-dirs.dirs')
try:
with open(userdirs_path, 'r') as f:
config.read_string('[DEFAULT]\n' + f.read())
return config.get(DEFAULTSECT, 'XDG_DESKTOP_DIR')
except Exception as exc:
return default
def xdg_get_desktop(username, homedir = None):
if username:
homedir = get_homedir(username)
if not homedir:
msgtext = message_with_code('E18')
logdata = dict()
logdata['username'] = username
log('E18', logdata)
raise Exception(msgtext)
stream = os.popen('export HOME={}; xdg-user-dir DESKTOP'.format(homedir))
output = stream.read()[:-1]
return output

View File

@ -1,7 +1,7 @@
%define _unpackaged_files_terminate_build 1
Name: gpupdate
Version: 0.5.0
Version: 0.7.0
Release: alt1
Summary: GPT applier
@ -11,15 +11,16 @@ Url: https://github.com/altlinux/gpupdate
BuildArch: noarch
Requires: control
Requires: local-policy >= 0.1.0
BuildRequires: rpm-build-python3
BuildRequires: python-tools-i18n
Requires: python3-module-rpm
Requires: python3-module-dbus
Requires: oddjob-%name >= 0.2.0
Requires: libnss-role >= 0.5.0
Requires: local-policy >= 0.3.0
Requires: pam-config >= 1.8
Requires: local-policy >= 0.4.0
Requires: pam-config >= 1.9.0
Requires: autofs
# This is needed by shortcuts_applier
Requires: desktop-file-utils
@ -38,10 +39,16 @@ mkdir -p \
cp -r gpoa \
%buildroot%python3_sitelibdir/
# Generate translations
pymsgfmt \
-o %buildroot%python3_sitelibdir/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.mo \
%buildroot%python3_sitelibdir/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po
mkdir -p \
%buildroot%_bindir/ \
%buildroot%_sbindir/ \
%buildroot%_cachedir/%name/
%buildroot%_cachedir/%name/ \
%buildroot%_cachedir/%name/creds
ln -s %python3_sitelibdir/gpoa/gpoa \
%buildroot%_sbindir/gpoa
@ -54,17 +61,23 @@ mkdir -p %buildroot%_datadir/%name
mv %buildroot%python3_sitelibdir/gpoa/templates \
%buildroot%_datadir/%name/
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.service %buildroot/usr/lib/systemd/user/%name-user.service
install -Dm0644 dist/system-policy-%name %buildroot%_sysconfdir/pam.d/system-policy-%name
install -Dm0644 doc/gpoa.1 %buildroot/%{_man1dir}/gpoa.1
install -Dm0644 doc/gpupdate.1 %buildroot/%{_man1dir}/gpupdate.1
install -Dm0644 doc/gpoa.1 %buildroot/%_man1dir/gpoa.1
install -Dm0644 doc/gpupdate.1 %buildroot/%_man1dir/gpupdate.1
%preun
%preun_service gpupdate
%preun_service gpupdate-user
%post
%post_service gpupdate
%post_service gpupdate-user
rm -f /var/cache/gpupdate/registry.sqlite
%files
%_sbindir/gpoa
@ -78,10 +91,28 @@ install -Dm0644 doc/gpupdate.1 %buildroot/%{_man1dir}/gpupdate.1
%_man1dir/gpoa.1.*
%_man1dir/gpupdate.1.*
/usr/lib/systemd/user/%name-user.service
%_sysconfdir/pam.d/system-policy-%name
%dir %_cachedir/%name
%dir %_sysconfdir/%name
%config(noreplace) %_sysconfdir/%name/environment
%config(noreplace) %_sysconfdir/pam.d/system-policy-%name
%dir %attr(0700, root, root) %_cachedir/%name
%dir %attr(0700, root, root) %_cachedir/%name/creds
%exclude %python3_sitelibdir/gpoa/.pylintrc
%exclude %python3_sitelibdir/gpoa/.prospector.yaml
%exclude %python3_sitelibdir/gpoa/Makefile
%exclude %python3_sitelibdir/gpoa/test
%changelog
* Wed Jul 01 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.7.0-alt1
- Add multiple appliers, part of which marks as experimental yet
* Wed May 20 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.6.0-alt2
- Update system-policy PAM-rules (clean system-auth-common, add pam_env support)
- Add dependency to pam-config later than 1.9.0 release
* Fri May 15 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.6.0-alt1
- Add drives policy for shared folders with cifs applier using autofs
- Update shortcuts policy with xdg-users-dir support for DESKTOP
* Wed Apr 22 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.5.0-alt1
- Update samba format: local.xml -> Machine/Registry.pol.xml
- Add support of ad-domain-controller local policy profile

2
wiki

Submodule wiki updated: 8cb284e0f7...79e4ba7f58