[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