diff options
Diffstat (limited to 'python/samba/gp/gp_gnome_settings_ext.py')
-rw-r--r-- | python/samba/gp/gp_gnome_settings_ext.py | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/python/samba/gp/gp_gnome_settings_ext.py b/python/samba/gp/gp_gnome_settings_ext.py new file mode 100644 index 0000000..567ab94 --- /dev/null +++ b/python/samba/gp/gp_gnome_settings_ext.py @@ -0,0 +1,418 @@ +# gp_gnome_settings_ext samba gpo policy +# Copyright (C) David Mulder <dmulder@suse.com> 2020 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os, re +from samba.gp.gpclass import gp_pol_ext, gp_file_applier +from tempfile import NamedTemporaryFile +import shutil +from configparser import ConfigParser +from subprocess import Popen, PIPE +from samba.common import get_string +from glob import glob +import xml.etree.ElementTree as etree +from samba.gp.util.logging import log + +def dconf_update(test_dir): + if test_dir is not None: + return + dconf = shutil.which('dconf') + if dconf is None: + log.error('Failed to update dconf. Command not found') + return + p = Popen([dconf, 'update'], stdout=PIPE, stderr=PIPE) + out, err = p.communicate() + if p.returncode != 0: + log.error('Failed to update dconf', get_string(err)) + +def create_locks_dir(test_dir): + locks_dir = '/etc/dconf/db/local.d/locks' + if test_dir is not None: + locks_dir = os.path.join(test_dir, locks_dir[1:]) + os.makedirs(locks_dir, exist_ok=True) + return locks_dir + +def create_user_profile(test_dir): + user_profile = '/etc/dconf/profile/user' + if test_dir is not None: + user_profile = os.path.join(test_dir, user_profile[1:]) + if os.path.exists(user_profile): + return + os.makedirs(os.path.dirname(user_profile), exist_ok=True) + with NamedTemporaryFile('w', dir=os.path.dirname(user_profile), + delete=False) as w: + w.write('user-db:user\nsystem-db:local') + os.chmod(w.name, 0o644) + fname = w.name + shutil.move(fname, user_profile) + +def create_local_db(test_dir): + local_db = '/etc/dconf/db/local.d' + if test_dir is not None: + local_db = os.path.join(test_dir, local_db[1:]) + os.makedirs(local_db, exist_ok=True) + return local_db + +def select_next_conf(directory, fname=''): + configs = [re.match(r'(\d+)%s' % fname, f) for f in os.listdir(directory)] + return max([int(m.group(1)) for m in configs if m]+[0])+1 + +class gp_gnome_settings_ext(gp_pol_ext, gp_file_applier): + def __init__(self, *args): + super().__init__(*args) + self.keys = ['Compose Key', + 'Dim Screen when User is Idle', + 'Lock Down Specific Settings', + 'Whitelisted Online Accounts', + 'Enabled Extensions'] + self.lock_down_settings = {} + self.test_dir = None + + def __str__(self): + return 'GNOME Settings/Lock Down Settings' + + def __add_lockdown_data(self, k, e): + if k not in self.lock_down_settings: + self.lock_down_settings[k] = {} + self.lock_down_settings[k][e.valuename] = e.data + + def __enable_lockdown_data(self, e): + if e.valuename not in self.lock_down_settings: + self.lock_down_settings[e.valuename] = {} + self.lock_down_settings[e.valuename]['Enabled'] = e.data == 1 + + def __apply_compose_key(self, data): + create_user_profile(self.test_dir) + local_db_dir = create_local_db(self.test_dir) + + conf_id = select_next_conf(local_db_dir, '-input-sources') + local_db = os.path.join(local_db_dir, + '%010d-input-sources' % conf_id) + data_map = { 'Right Alt': 'compose:ralt', + 'Left Win': 'compose:lwin', + '3rd level of Left Win': 'compose:lwin-altgr', + 'Right Win': 'compose:rwin', + '3rd level of Right Win': 'compose:rwin-altgr', + 'Menu': 'compose:menu', + '3rd level of Menu': 'compose:menu-altgr', + 'Left Ctrl': 'compose:lctrl', + '3rd level of Left Ctrl': 'compose:lctrl-altgr', + 'Right Ctrl': 'compose:rctrl', + '3rd level of Right Ctrl': 'compose:rctrl-altgr', + 'Caps Lock': 'compose:caps', + '3rd level of Caps Lock': 'compose:caps-altgr', + 'The "< >" key': 'compose:102', + '3rd level of the "< >" key': 'compose:102-altgr', + 'Pause': 'compose:paus', + 'PrtSc': 'compose:prsc', + 'Scroll Lock': 'compose:sclk' + } + if data['Key Name'] not in data_map.keys(): + log.error('Compose Key not recognized', data) + return + parser = ConfigParser() + section = 'org/gnome/desktop/input-sources' + parser.add_section(section) + parser.set(section, 'xkb-options', + "['%s']" % data_map[data['Key Name']]) + with open(local_db, 'w') as w: + parser.write(w) + + # Lock xkb-options + locks_dir = create_locks_dir(self.test_dir) + conf_id = select_next_conf(locks_dir) + lock = os.path.join(locks_dir, '%010d-input-sources' % conf_id) + with open(lock, 'w') as w: + w.write('/org/gnome/desktop/input-sources/xkb-options') + + dconf_update(self.test_dir) + return [local_db, lock] + + def __apply_dim_idle(self, data): + create_user_profile(self.test_dir) + local_db_dir = create_local_db(self.test_dir) + conf_id = select_next_conf(local_db_dir, '-power') + local_power_db = os.path.join(local_db_dir, '%010d-power' % conf_id) + parser = ConfigParser() + section = 'org/gnome/settings-daemon/plugins/power' + parser.add_section(section) + parser.set(section, 'idle-dim', 'true') + parser.set(section, 'idle-brightness', str(data['Dim Idle Brightness'])) + with open(local_power_db, 'w') as w: + parser.write(w) + conf_id = select_next_conf(local_db_dir, '-session') + local_session_db = os.path.join(local_db_dir, '%010d-session' % conf_id) + parser = ConfigParser() + section = 'org/gnome/desktop/session' + parser.add_section(section) + parser.set(section, 'idle-delay', 'uint32 %d' % data['Delay']) + with open(local_session_db, 'w') as w: + parser.write(w) + + # Lock power-saving + locks_dir = create_locks_dir(self.test_dir) + conf_id = select_next_conf(locks_dir) + lock = os.path.join(locks_dir, '%010d-power-saving' % conf_id) + with open(lock, 'w') as w: + w.write('/org/gnome/settings-daemon/plugins/power/idle-dim\n') + w.write('/org/gnome/settings-daemon/plugins/power/idle-brightness\n') + w.write('/org/gnome/desktop/session/idle-delay') + + dconf_update(self.test_dir) + return [local_power_db, local_session_db, lock] + + def __apply_specific_settings(self, data): + create_user_profile(self.test_dir) + locks_dir = create_locks_dir(self.test_dir) + conf_id = select_next_conf(locks_dir, '-group-policy') + policy_file = os.path.join(locks_dir, '%010d-group-policy' % conf_id) + with open(policy_file, 'w') as w: + for key in data.keys(): + w.write('%s\n' % key) + dconf_update(self.test_dir) + return [policy_file] + + def __apply_whitelisted_account(self, data): + create_user_profile(self.test_dir) + local_db_dir = create_local_db(self.test_dir) + locks_dir = create_locks_dir(self.test_dir) + val = "['%s']" % "', '".join(data.keys()) + policy_files = self.__lockdown(local_db_dir, locks_dir, 'goa', + 'whitelisted-providers', val, + 'org/gnome/online-accounts') + dconf_update(self.test_dir) + return policy_files + + def __apply_enabled_extensions(self, data): + create_user_profile(self.test_dir) + local_db_dir = create_local_db(self.test_dir) + conf_id = select_next_conf(local_db_dir) + policy_file = os.path.join(local_db_dir, '%010d-extensions' % conf_id) + parser = ConfigParser() + section = 'org/gnome/shell' + parser.add_section(section) + exts = data.keys() + parser.set(section, 'enabled-extensions', "['%s']" % "', '".join(exts)) + parser.set(section, 'development-tools', 'false') + with open(policy_file, 'w') as w: + parser.write(w) + dconf_update(self.test_dir) + return [policy_file] + + def __lockdown(self, local_db_dir, locks_dir, name, key, val, + section='org/gnome/desktop/lockdown'): + policy_files = [] + conf_id = select_next_conf(local_db_dir) + policy_file = os.path.join(local_db_dir, + '%010d-%s' % (conf_id, name)) + policy_files.append(policy_file) + conf_id = select_next_conf(locks_dir) + lock = os.path.join(locks_dir, '%010d-%s' % (conf_id, name)) + policy_files.append(lock) + parser = ConfigParser() + parser.add_section(section) + parser.set(section, key, val) + with open(policy_file, 'w') as w: + parser.write(w) + with open(lock, 'w') as w: + w.write('/%s/%s' % (section, key)) + return policy_files + + def __apply_enabled(self, k): + policy_files = [] + + create_user_profile(self.test_dir) + local_db_dir = create_local_db(self.test_dir) + locks_dir = create_locks_dir(self.test_dir) + + if k == 'Lock Down Enabled Extensions': + conf_id = select_next_conf(locks_dir) + policy_file = os.path.join(locks_dir, '%010d-extensions' % conf_id) + policy_files.append(policy_file) + with open(policy_file, 'w') as w: + w.write('/org/gnome/shell/enabled-extensions\n') + w.write('/org/gnome/shell/development-tools') + elif k == 'Disable Printing': + policy_files = self.__lockdown(local_db_dir, locks_dir, 'printing', + 'disable-printing', 'true') + elif k == 'Disable File Saving': + policy_files = self.__lockdown(local_db_dir, locks_dir, + 'filesaving', + 'disable-save-to-disk', 'true') + elif k == 'Disable Command-Line Access': + policy_files = self.__lockdown(local_db_dir, locks_dir, 'cmdline', + 'disable-command-line', 'true') + elif k == 'Disallow Login Using a Fingerprint': + policy_files = self.__lockdown(local_db_dir, locks_dir, + 'fingerprintreader', + 'enable-fingerprint-authentication', + 'false', + section='org/gnome/login-screen') + elif k == 'Disable User Logout': + policy_files = self.__lockdown(local_db_dir, locks_dir, 'logout', + 'disable-log-out', 'true') + elif k == 'Disable User Switching': + policy_files = self.__lockdown(local_db_dir, locks_dir, 'logout', + 'disable-user-switching', 'true') + elif k == 'Disable Repartitioning': + actions = '/usr/share/polkit-1/actions' + udisk2 = glob(os.path.join(actions, + 'org.freedesktop.[u|U][d|D]isks2.policy')) + if len(udisk2) == 1: + udisk2 = udisk2[0] + else: + udisk2 = os.path.join(actions, + 'org.freedesktop.UDisks2.policy') + udisk2_etc = os.path.join('/etc/share/polkit-1/actions', + os.path.basename(udisk2)) + if self.test_dir is not None: + udisk2_etc = os.path.join(self.test_dir, udisk2_etc[1:]) + os.makedirs(os.path.dirname(udisk2_etc), exist_ok=True) + xml_data = etree.ElementTree(etree.Element('policyconfig')) + if os.path.exists(udisk2): + with open(udisk2, 'rb') as f: + data = f.read() + existing_xml = etree.ElementTree(etree.fromstring(data)) + root = xml_data.getroot() + root.append(existing_xml.find('vendor')) + root.append(existing_xml.find('vendor_url')) + root.append(existing_xml.find('icon_name')) + else: + vendor = etree.SubElement(xml_data.getroot(), 'vendor') + vendor.text = 'The Udisks Project' + vendor_url = etree.SubElement(xml_data.getroot(), 'vendor_url') + vendor_url.text = 'https://github.com/storaged-project/udisks' + icon_name = etree.SubElement(xml_data.getroot(), 'icon_name') + icon_name.text = 'drive-removable-media' + action = etree.SubElement(xml_data.getroot(), 'action') + action.attrib['id'] = 'org.freedesktop.udisks2.modify-device' + description = etree.SubElement(action, 'description') + description.text = 'Modify the drive settings' + message = etree.SubElement(action, 'message') + message.text = 'Authentication is required to modify drive settings' + defaults = etree.SubElement(action, 'defaults') + allow_any = etree.SubElement(defaults, 'allow_any') + allow_any.text = 'no' + allow_inactive = etree.SubElement(defaults, 'allow_inactive') + allow_inactive.text = 'no' + allow_active = etree.SubElement(defaults, 'allow_active') + allow_active.text = 'yes' + with open(udisk2_etc, 'wb') as w: + xml_data.write(w, encoding='UTF-8', xml_declaration=True) + policy_files.append(udisk2_etc) + else: + log.error('Unable to apply', k) + return + dconf_update(self.test_dir) + return policy_files + + def __clean_data(self, k): + data = self.lock_down_settings[k] + return {i: data[i] for i in data.keys() if i != 'Enabled'} + + def process_group_policy(self, deleted_gpo_list, changed_gpo_list, + test_dir=None): + if test_dir is not None: + self.test_dir = test_dir + for guid, settings in deleted_gpo_list: + if str(self) in settings: + for attribute, value in settings[str(self)].items(): + self.unapply(guid, attribute, value, sep=';') + dconf_update(test_dir) + + for gpo in changed_gpo_list: + if gpo.file_sys_path: + section_name = 'GNOME Settings\\Lock Down Settings' + pol_file = 'MACHINE/Registry.pol' + path = os.path.join(gpo.file_sys_path, pol_file) + pol_conf = self.parse(path) + if not pol_conf: + continue + for e in pol_conf.entries: + if e.keyname.startswith(section_name) and e.data and \ + '**delvals.' not in e.valuename: + for k in self.keys: + if e.keyname.endswith(k): + self.__add_lockdown_data(k, e) + break + else: + self.__enable_lockdown_data(e) + for k in self.lock_down_settings.keys(): + # Ignore disabled preferences + if not self.lock_down_settings[k]['Enabled']: + # Unapply the disabled preference if previously applied + self.clean(gpo.name, remove=k) + continue + + # Apply using the appropriate applier + data = str(self.lock_down_settings[k]) + value_hash = self.generate_value_hash(data) + if k == self.keys[0]: + self.apply(gpo.name, k, value_hash, + self.__apply_compose_key, + self.__clean_data(k), sep=';') + elif k == self.keys[1]: + self.apply(gpo.name, k, value_hash, + self.__apply_dim_idle, + self.__clean_data(k), sep=';') + elif k == self.keys[2]: + self.apply(gpo.name, k, value_hash, + self.__apply_specific_settings, + self.__clean_data(k), sep=';') + elif k == self.keys[3]: + self.apply(gpo.name, k, value_hash, + self.__apply_whitelisted_account, + self.__clean_data(k), sep=';') + elif k == self.keys[4]: + self.apply(gpo.name, k, value_hash, + self.__apply_enabled_extensions, + self.__clean_data(k), sep=';') + else: + self.apply(gpo.name, k, value_hash, + self.__apply_enabled, + k, sep=';') + + # Unapply any policy that has been removed + self.clean(gpo.name, keep=self.lock_down_settings.keys()) + + def rsop(self, gpo): + output = {} + if gpo.file_sys_path: + section_name = 'GNOME Settings\\Lock Down Settings' + pol_file = 'MACHINE/Registry.pol' + path = os.path.join(gpo.file_sys_path, pol_file) + pol_conf = self.parse(path) + if not pol_conf: + return output + for e in pol_conf.entries: + if e.keyname.startswith(section_name) and e.data and \ + '**delvals.' not in e.valuename: + for k in self.keys: + if e.keyname.endswith(k): + self.__add_lockdown_data(k, e) + break + else: + self.__enable_lockdown_data(e) + for k in self.lock_down_settings.keys(): + if self.lock_down_settings[k]['Enabled']: + if len(self.lock_down_settings[k]) > 1: + data = self.__clean_data(k) + if all([i == data[i] for i in data.keys()]): + output[k] = list(data.keys()) + else: + output[k] = data + else: + output[k] = self.lock_down_settings[k] + return output |