diff --git a/python/samba/gp/gp_drive_maps_ext.py b/python/samba/gp/gp_drive_maps_ext.py index c4ca6ba0703..85aaa56b439 100644 --- a/python/samba/gp/gp_drive_maps_ext.py +++ b/python/samba/gp/gp_drive_maps_ext.py @@ -14,15 +14,156 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from samba.gp.gpclass import gp_xml_ext, gp_misc_applier, drop_privileges +import os +import json +from samba.gp.gpclass import gp_xml_ext, gp_misc_applier, drop_privileges, \ + expand_pref_variables +from subprocess import Popen, PIPE +from samba.gp.gp_scripts_ext import fetch_crontab, install_crontab, \ + install_user_crontab +from samba.gp.util.logging import log +from samba.gp import gp_scripts_ext +gp_scripts_ext.intro = ''' +### autogenerated by samba +# +# This file is generated by the gp_drive_maps_user_ext Group Policy +# Client Side Extension. To modify the contents of this file, +# modify the appropriate Group Policy objects which apply +# to this machine. DO NOT MODIFY THIS FILE DIRECTLY. +# + +''' + +def mount_drive(uri): + log.debug('Mounting drive', uri) + out, err = Popen(['gio', 'mount', uri], + stdout=PIPE, stderr=PIPE).communicate() + if err: + if b'Location is already mounted' not in err: + raise SystemError(err) + +def unmount_drive(uri): + log.debug('Unmounting drive', uri) + return Popen(['gio', 'mount', uri, '--unmount']).wait() class gp_drive_maps_user_ext(gp_xml_ext, gp_misc_applier): + def parse_value(self, val): + vals = super().parse_value(val) + if 'props' in vals.keys(): + vals['props'] = json.loads(vals['props']) + if 'run_once' in vals.keys(): + vals['run_once'] = json.loads(vals['run_once']) + return vals + + def unapply(self, guid, uri, val): + vals = self.parse_value(val) + if 'props' in vals.keys() and \ + vals['props']['action'] in ['C', 'R', 'U']: + unmount_drive(uri) + others, entries = fetch_crontab(self.username) + if 'crontab' in vals.keys() and vals['crontab'] in entries: + entries.remove(vals['crontab']) + install_user_crontab(self.username, others, entries) + self.cache_remove_attribute(guid, uri) + + def apply(self, guid, uri, props, run_once, entry): + old_val = self.cache_get_attribute_value(guid, uri) + val = self.generate_value(props=json.dumps(props), + run_once=json.dumps(run_once), + crontab=entry) + + # The policy has changed, unapply it first + if old_val: + self.unapply(guid, uri, old_val) + + if props['action'] in ['C', 'R', 'U']: + mount_drive(uri) + elif props['action'] == 'D': + unmount_drive(uri) + if not run_once: + others, entries = fetch_crontab(self.username) + if entry not in entries: + entries.append(entry) + install_user_crontab(self.username, others, entries) + self.cache_add_attribute(guid, uri, val) + def __str__(self): return 'Preferences/Drives' def process_group_policy(self, deleted_gpo_list, changed_gpo_list): - pass + for guid, settings in deleted_gpo_list: + if str(self) in settings: + for uri, val in settings[str(self)].items(): + self.unapply(guid, uri, val) + + for gpo in changed_gpo_list: + if gpo.file_sys_path: + xml = 'USER/Preferences/Drives/Drives.xml' + path = os.path.join(gpo.file_sys_path, xml) + xml_conf = drop_privileges('root', self.parse, path) + if not xml_conf: + continue + drives = xml_conf.findall('Drive') + attrs = [] + for drive in drives: + prop = drive.find('Properties') + if prop is None: + log.warning('Drive is missing Properties', drive.attrib) + continue + if prop.attrib['thisDrive'] == 'HIDE': + log.warning('Drive is hidden', prop.attrib) + continue # Don't mount a hidden drive + run_once = False + filters = drive.find('Filters') + if filters: + run_once_filter = filters.find('FilterRunOnce') + if run_once_filter is not None: + run_once = True + uri = 'smb:{}'.format(prop.attrib['path'].replace('\\', '/')) + # Ensure we expand the preference variables, or fail if we + # are unable to (the uri is invalid if we fail). + gptpath = os.path.join(gpo.file_sys_path, 'USER') + try: + uri = expand_pref_variables(uri, gptpath, self.lp, + username=self.username) + except NameError as e: + # If we fail expanding variables, then the URI is + # invalid and we can't continue processing this drive + # map. We can continue processing other drives, as they + # may succeed. This is not a critical error, since some + # Windows specific policies won't apply here. + log.warn('Failed to expand drive map variables: %s' % e, + prop.attrib) + continue + attrs.append(uri) + entry = '' + if not run_once: + if prop.attrib['action'] in ['C', 'R', 'U']: + entry = '@hourly gio mount {}'.format(uri) + elif prop.attrib['action'] == 'D': + entry = '@hourly gio mount {} --unmount'.format(uri) + self.apply(gpo.name, uri, prop.attrib, run_once, entry) + self.clean(gpo.name, keep=attrs) def rsop(self, gpo): output = {} + if gpo.file_sys_path: + xml = 'USER/Preferences/Drives/Drives.xml' + path = os.path.join(gpo.file_sys_path, xml) + xml_conf = self.parse(path) + if not xml_conf: + return output + drives = xml_conf.findall('Drive') + for drive in drives: + prop = drive.find('Properties') + if prop is None: + continue + if prop.attrib['thisDrive'] == 'HIDE': + continue + uri = 'smb:{}'.format(prop.attrib['path'].replace('\\', '/')) + if prop.attrib['action'] in ['C', 'R', 'U']: + output[prop.attrib['label']] = 'gio mount {}'.format(uri) + elif prop.attrib['action'] == 'D': + output[prop.attrib['label']] = \ + 'gio mount {} --unmount'.format(uri) return output diff --git a/python/samba/gp/gpclass.py b/python/samba/gp/gpclass.py index a01a74a356d..f7228107082 100644 --- a/python/samba/gp/gpclass.py +++ b/python/samba/gp/gpclass.py @@ -50,6 +50,7 @@ from samba.auth import AUTH_SESSION_INFO_DEFAULT_GROUPS, AUTH_SESSION_INFO_AUTHE from samba.dcerpc import security import samba.security from samba.dcerpc import netlogon +from datetime import datetime try: @@ -1215,3 +1216,69 @@ def drop_privileges(username, func, *args): raise exc return out + +def expand_pref_variables(text, gpt_path, lp, username=None): + utc_dt = datetime.utcnow() + dt = datetime.now() + cache_path = lp.cache_path(os.path.join('gpo_cache')) + # These are all the possible preference variables that MS supports. The + # variables set to 'None' here are currently unsupported by Samba, and will + # prevent the individual policy from applying. + variables = { 'AppDataDir': os.path.expanduser('~/.config'), + 'BinaryComputerSid': None, + 'BinaryUserSid': None, + 'CommonAppdataDir': None, + 'CommonDesktopDir': None, + 'CommonFavoritesDir': None, + 'CommonProgramsDir': None, + 'CommonStartUpDir': None, + 'ComputerName': lp.get('netbios name'), + 'CurrentProccessId': None, + 'CurrentThreadId': None, + 'DateTime': utc_dt.strftime('%Y-%m-%d %H:%M:%S UTC'), + 'DateTimeEx': str(utc_dt), + 'DesktopDir': os.path.expanduser('~/Desktop'), + 'DomainName': lp.get('realm'), + 'FavoritesDir': None, + 'GphPath': None, + 'GptPath': os.path.join(cache_path, + check_safe_path(gpt_path).upper()), + 'GroupPolicyVersion': None, + 'LastDriveMapped': None, + 'LastError': None, + 'LastErrorText': None, + 'LdapComputerSid': None, + 'LdapUserSid': None, + 'LocalTime': dt.strftime('%H:%M:%S'), + 'LocalTimeEx': dt.strftime('%H:%M:%S.%f'), + 'LogonDomain': lp.get('realm'), + 'LogonServer': None, + 'LogonUser': username, + 'LogonUserSid': None, + 'MacAddress': None, + 'NetPlacesDir': None, + 'OsVersion': None, + 'ProgramFilesDir': None, + 'ProgramsDir': None, + 'RecentDocumentsDir': None, + 'ResultCode': None, + 'ResultText': None, + 'ReversedComputerSid': None, + 'ReversedUserSid': None, + 'SendToDir': None, + 'StartMenuDir': None, + 'StartUpDir': None, + 'SystemDir': None, + 'SystemDrive': '/', + 'TempDir': '/tmp', + 'TimeStamp': str(datetime.timestamp(dt)), + 'TraceFile': None, + 'WindowsDir': None + } + for exp_var, val in variables.items(): + exp_var_fmt = '%%%s%%' % exp_var + if exp_var_fmt in text: + if val is None: + raise NameError('Expansion variable %s is undefined' % exp_var) + text = text.replace(exp_var_fmt, val) + return text diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo deleted file mode 100644 index ae63567f433..00000000000 --- a/selftest/knownfail.d/gpo +++ /dev/null @@ -1,2 +0,0 @@ -samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_drive_maps_user_ext -samba.tests.gpo.samba.tests.gpo.GPOTests.test_expand_pref_variables diff --git a/source4/scripting/bin/samba-gpupdate b/source4/scripting/bin/samba-gpupdate index 4b3f057f534..0f1c9a11aaa 100755 --- a/source4/scripting/bin/samba-gpupdate +++ b/source4/scripting/bin/samba-gpupdate @@ -52,6 +52,7 @@ from samba.gp.gp_firewalld_ext import gp_firewalld_ext from samba.gp.gp_centrify_sudoers_ext import gp_centrify_sudoers_ext from samba.gp.gp_centrify_crontab_ext import gp_centrify_crontab_ext, \ gp_user_centrify_crontab_ext +from samba.gp.gp_drive_maps_ext import gp_drive_maps_user_ext from samba.credentials import Credentials from samba.gp.util.logging import logger_init @@ -125,6 +126,7 @@ if __name__ == "__main__": elif opts.target == 'User': gp_extensions.append(gp_user_scripts_ext) gp_extensions.append(gp_user_centrify_crontab_ext) + gp_extensions.append(gp_drive_maps_user_ext) gp_extensions.extend(user_exts) if opts.rsop: