[SCM] Samba Shared Repository - branch master updated
Andrew Bartlett
abartlet at samba.org
Mon Oct 16 02:02:02 UTC 2023
The branch, master has been updated
via 6e862bd3690 s4/torture: fix exit status of raw.bench-lookup
via b76e184c073 gpdupate: Implement Drive Maps Client Side Extension
via 42d03da3063 gpupdate: Test Drive Maps Client Side Extension
from acd9248b13c tevent: version 0.16.0
https://git.samba.org/?p=samba.git;a=shortlog;h=master
- Log -----------------------------------------------------------------
commit 6e862bd3690c041aa061ed8f7ee1d9207381674f
Author: Oleg Kravtsov <oleg at tuxera.com>
Date: Fri Oct 6 12:20:05 2023 +0300
s4/torture: fix exit status of raw.bench-lookup
Use correct value of 'result' when the test passes.
Signed-off-by: Oleg Kravtsov <oleg at tuxera.com>
Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
Autobuild-User(master): Andrew Bartlett <abartlet at samba.org>
Autobuild-Date(master): Mon Oct 16 02:01:17 UTC 2023 on atb-devel-224
commit b76e184c07333b00daab5969ba4687b8844c1ce3
Author: David Mulder <dmulder at samba.org>
Date: Fri Mar 10 14:30:17 2023 -0700
gpdupate: Implement Drive Maps Client Side Extension
Signed-off-by: David Mulder <dmulder at samba.org>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
commit 42d03da3063a1ac7c20674312a3d730ac143874b
Author: David Mulder <dmulder at samba.org>
Date: Fri Mar 10 14:29:24 2023 -0700
gpupdate: Test Drive Maps Client Side Extension
Signed-off-by: David Mulder <dmulder at samba.org>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
-----------------------------------------------------------------------
Summary of changes:
python/samba/gp/gp_drive_maps_ext.py | 169 +++++++++++++++++++++++++++++++
python/samba/gp/gpclass.py | 67 +++++++++++++
python/samba/tests/bin/gio | 11 ++
python/samba/tests/gpo.py | 188 ++++++++++++++++++++++++++++++++++-
source4/scripting/bin/samba-gpupdate | 2 +
source4/torture/raw/lookuprate.c | 1 +
6 files changed, 437 insertions(+), 1 deletion(-)
create mode 100644 python/samba/gp/gp_drive_maps_ext.py
create mode 100755 python/samba/tests/bin/gio
Changeset truncated at 500 lines:
diff --git a/python/samba/gp/gp_drive_maps_ext.py b/python/samba/gp/gp_drive_maps_ext.py
new file mode 100644
index 00000000000..85aaa56b439
--- /dev/null
+++ b/python/samba/gp/gp_drive_maps_ext.py
@@ -0,0 +1,169 @@
+# gp_drive_maps_user_ext samba gpo policy
+# Copyright (C) David Mulder <dmulder at 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
+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):
+ 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/python/samba/tests/bin/gio b/python/samba/tests/bin/gio
new file mode 100755
index 00000000000..30e31acb6d3
--- /dev/null
+++ b/python/samba/tests/bin/gio
@@ -0,0 +1,11 @@
+#!/usr/bin/python3
+import optparse
+
+if __name__ == "__main__":
+ parser = optparse.OptionParser('gio <cmd> <url> [options]')
+ parser.add_option('--unmount')
+
+ (opts, args) = parser.parse_args()
+
+ assert args[0] == 'mount', 'Unrecognized command `gio %s`' % args[0]
+ assert len(args) == 2, 'Missing url parameter'
diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
index c317bd8d15e..d68f11233a6 100644
--- a/python/samba/tests/gpo.py
+++ b/python/samba/tests/gpo.py
@@ -50,6 +50,7 @@ from samba.gp.gp_msgs_ext import gp_msgs_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.common import get_bytes
from samba.dcerpc import preg
from samba.ndr import ndr_pack
@@ -60,7 +61,7 @@ import hashlib
from samba.gp_parse.gp_pol import GPPolParser
from glob import glob
from configparser import ConfigParser
-from samba.gp.gpclass import get_dc_hostname
+from samba.gp.gpclass import get_dc_hostname, expand_pref_variables
from samba import Ldb
import ldb as _ldb
from samba.auth import system_session
@@ -5009,6 +5010,11 @@ br"""
</PolFile>
"""
+drive_maps_xml = b"""<?xml version="1.0" encoding="utf-8"?>
+<Drives clsid="{8FDDCC1A-0C3C-43cd-A6B4-71A6DF20DA8C}"><Drive clsid="{935D1B74-9CB8-4e3c-9914-7DD559B7A417}" name="A:" status="A:" image="2" changed="2023-03-08 19:23:02" uid="{1641E121-DEF3-418D-A428-2D8DF4749504}" bypassErrors="1"><Properties action="U" thisDrive="NOCHANGE" allDrives="NOCHANGE" userName="" path="\\\\example.com\\test" label="TEST" persistent="1" useLetter="0" letter="A"/></Drive>
+</Drives>
+"""
+
def days2rel_nttime(val):
seconds = 60
minutes = 60
@@ -7829,3 +7835,183 @@ class GPOTests(tests.TestCase):
# Unstage the Registry.pol files
unstage_file(reg_pol)
unstage_file(reg_pol2)
+
+ def test_gp_drive_maps_user_ext(self):
+ local_path = self.lp.cache_path('gpo_cache')
+ guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
+ xml_path = os.path.join(local_path, policies, guid,
+ 'USER/PREFERENCES/DRIVES/DRIVES.XML')
+ cache_dir = self.lp.get('cache directory')
+ store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
+
+ machine_creds = Credentials()
+ machine_creds.guess(self.lp)
+ machine_creds.set_machine_account()
+
+ # Initialize the group policy extension
+ ext = gp_drive_maps_user_ext(self.lp, machine_creds,
+ os.environ.get('DC_USERNAME'), store)
+
+ ads = gpo.ADS_STRUCT(self.server, self.lp, machine_creds)
+ if ads.connect():
+ gpos = ads.get_gpo_list(machine_creds.get_username())
+
+ # Stage the Drives.xml file with test data
+ ret = stage_file(xml_path, drive_maps_xml)
+ self.assertTrue(ret, 'Could not create the target %s' % xml_path)
+
+ # Process all gpos, intentionally skipping the privilege drop
+ ext.process_group_policy([], gpos)
+ # Dump the fake crontab setup for testing
+ p = Popen(['crontab', '-l'], stdout=PIPE)
+ crontab, _ = p.communicate()
+ entry = b'@hourly gio mount smb://example.com/test'
+ self.assertIn(entry, crontab,
+ 'The crontab entry was not installed')
+
+ # Check that a call to gpupdate --rsop also succeeds
+ ret = rsop(self.lp)
+ self.assertEquals(ret, 0, 'gpupdate --rsop failed!')
+
+ # Unstage the Drives.xml
+ unstage_file(xml_path)
+
+ # Modify the policy and ensure it is updated
+ xml_conf = etree.fromstring(drive_maps_xml.strip())
+ drives = xml_conf.findall('Drive')
+ props = drives[0].find('Properties')
+ props.attrib['action'] = 'D'
+ ret = stage_file(xml_path,
+ etree.tostring(xml_conf, encoding='unicode'))
+ self.assertTrue(ret, 'Could not create the target %s' % xml_path)
+
+ # Process all gpos, intentionally skipping the privilege drop
+ ext.process_group_policy([], gpos)
+ # Dump the fake crontab setup for testing
+ p = Popen(['crontab', '-l'], stdout=PIPE)
+ crontab, _ = p.communicate()
+ self.assertNotIn(entry+b'\n', crontab,
+ 'The old crontab entry was not removed')
+ entry = entry + b' --unmount'
+ self.assertIn(entry, crontab,
+ 'The crontab entry was not installed')
+
+ # Remove policy
+ gp_db = store.get_gplog(os.environ.get('DC_USERNAME'))
+ del_gpos = get_deleted_gpos_list(gp_db, [])
+ ext.process_group_policy(del_gpos, [])
+ # Dump the fake crontab setup for testing
+ p = Popen(['crontab', '-l'], stdout=PIPE)
+ crontab, _ = p.communicate()
+ self.assertNotIn(entry, crontab,
+ 'Unapply failed to cleanup crontab entry')
+
+ # Unstage the Drives.xml
+ unstage_file(xml_path)
+
+ # Modify the policy to set 'run once', ensure there is no cron entry
+ xml_conf = etree.fromstring(drive_maps_xml.strip())
+ drives = xml_conf.findall('Drive')
+ filters = etree.SubElement(drives[0], 'Filters')
+ etree.SubElement(filters, 'FilterRunOnce')
+ ret = stage_file(xml_path,
+ etree.tostring(xml_conf, encoding='unicode'))
+ self.assertTrue(ret, 'Could not create the target %s' % xml_path)
+
+ # Process all gpos, intentionally skipping the privilege drop
+ ext.process_group_policy([], gpos)
+ # Dump the fake crontab setup for testing
+ p = Popen(['crontab', '-l'], stdout=PIPE)
+ crontab, _ = p.communicate()
+ entry = b'@hourly gio mount smb://example.com/test'
+ self.assertNotIn(entry, crontab,
+ 'The crontab entry was added despite run-once request')
+
+ # Remove policy
+ gp_db = store.get_gplog(os.environ.get('DC_USERNAME'))
+ del_gpos = get_deleted_gpos_list(gp_db, [])
+ ext.process_group_policy(del_gpos, [])
+
+ # Unstage the Drives.xml
+ unstage_file(xml_path)
+
+ def test_expand_pref_variables(self):
+ cache_path = self.lp.cache_path(os.path.join('gpo_cache'))
+ gpt_path = 'TEST'
+ username = 'test_uname'
+ test_vars = { 'AppDataDir': os.path.expanduser('~/.config'),
+ 'ComputerName': self.lp.get('netbios name'),
+ 'DesktopDir': os.path.expanduser('~/Desktop'),
+ 'DomainName': self.lp.get('realm'),
+ 'GptPath': os.path.join(cache_path,
+ check_safe_path(gpt_path).upper()),
+ 'LogonDomain': self.lp.get('realm'),
+ 'LogonUser': username,
+ 'SystemDrive': '/',
+ 'TempDir': '/tmp'
+ }
+ for exp_var, val in test_vars.items():
+ self.assertEqual(expand_pref_variables('%%%s%%' % exp_var,
+ gpt_path,
+ self.lp,
+ username),
+ val, 'Failed to expand variable %s' % exp_var)
+ # With the time variables, we can't test for an exact time, so let's do
+ # simple checks instead.
+ time_vars = ['DateTime', 'DateTimeEx', 'LocalTime',
+ 'LocalTimeEx', 'TimeStamp']
+ for time_var in time_vars:
+ self.assertNotEqual(expand_pref_variables('%%%s%%' % time_var,
+ gpt_path,
+ self.lp,
+ username),
+ None, 'Failed to expand variable %s' % time_var)
+
+ # Here we test to ensure undefined preference variables cause an error.
+ # The reason for testing these is to ensure we don't apply nonsense
+ # policies when they can't be defined. Also, these tests will fail if
+ # one of these is implemented in the future (forcing us to write a test
+ # anytime these are implemented).
+ undef_vars = ['BinaryComputerSid',
+ 'BinaryUserSid',
+ 'CommonAppdataDir',
+ 'CommonDesktopDir',
+ 'CommonFavoritesDir',
+ 'CommonProgramsDir',
+ 'CommonStartUpDir',
+ 'CurrentProccessId',
+ 'CurrentThreadId',
+ 'FavoritesDir',
+ 'GphPath',
+ 'GroupPolicyVersion',
+ 'LastDriveMapped',
+ 'LastError',
+ 'LastErrorText',
+ 'LdapComputerSid',
+ 'LdapUserSid',
+ 'LogonServer',
+ 'LogonUserSid',
+ 'MacAddress',
+ 'NetPlacesDir',
+ 'OsVersion',
+ 'ProgramFilesDir',
+ 'ProgramsDir',
+ 'RecentDocumentsDir',
+ 'ResultCode',
+ 'ResultText',
+ 'ReversedComputerSid',
+ 'ReversedUserSid',
+ 'SendToDir',
+ 'StartMenuDir',
+ 'StartUpDir',
+ 'SystemDir',
+ 'TraceFile',
+ 'WindowsDir'
+ ]
+ for undef_var in undef_vars:
+ try:
+ expand_pref_variables('%%%s%%' % undef_var, gpt_path, self.lp)
+ except NameError:
+ pass
+ else:
+ self.fail('Undefined variable %s caused no error' % undef_var)
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
--
Samba Shared Repository
More information about the samba-cvs
mailing list