]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
gpo: Read GPO versions locally, not from sysvol
authorDavid Mulder <dmulder@suse.com>
Mon, 8 Jan 2018 14:17:29 +0000 (07:17 -0700)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 12 Jul 2018 20:11:22 +0000 (22:11 +0200)
Non-kdc clients cannot read directly from the
sysvol, so we need to store the GPT.INI file
locally to read each gpo version.

Signed-off-by: David Mulder <dmulder@suse.com>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/gpclass.py
python/samba/tests/gpo.py

index 0966611b6866f6f563f70d89fea806d1060cb292..086656d87858c639c809f970fccf6a0865824514 100644 (file)
@@ -17,6 +17,7 @@
 
 import sys
 import os
+import errno
 import tdb
 sys.path.insert(0, "bin/python")
 from samba import NTSTATUSError
@@ -29,6 +30,7 @@ from samba.net import Net
 from samba.dcerpc import nbt
 from samba import smb
 import samba.gpo as gpo
+from tempfile import NamedTemporaryFile
 
 try:
     from enum import Enum
@@ -423,6 +425,49 @@ def get_gpo_list(dc_hostname, creds, lp):
         gpos = ads.get_gpo_list(creds.get_username())
     return gpos
 
+
+def cache_gpo_dir(conn, cache, sub_dir):
+    loc_sub_dir = sub_dir.upper()
+    local_dir = os.path.join(cache, loc_sub_dir)
+    try:
+        os.makedirs(local_dir, mode=0o755)
+    except OSError as e:
+        if e.errno != errno.EEXIST:
+            raise
+    for fdata in conn.list(sub_dir):
+        if fdata['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
+            cache_gpo_dir(conn, cache, os.path.join(sub_dir, fdata['name']))
+        else:
+            local_name = fdata['name'].upper()
+            f = NamedTemporaryFile(delete=False, dir=local_dir)
+            fname = os.path.join(sub_dir, fdata['name']).replace('/', '\\')
+            f.write(conn.loadfile(fname))
+            f.close()
+            os.rename(f.name, os.path.join(local_dir, local_name))
+
+
+def check_safe_path(path):
+    dirs = re.split('/|\\\\', path)
+    if 'sysvol' in path:
+        dirs = dirs[dirs.index('sysvol')+1:]
+    if not '..' in dirs:
+        return os.path.join(*dirs)
+    raise OSError(path)
+
+def check_refresh_gpo_list(dc_hostname, lp, creds, gpos):
+    conn = smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds, sign=True)
+    cache_path = lp.cache_path('gpo_cache')
+    for gpo in gpos:
+        if not gpo.file_sys_path:
+            continue
+        cache_gpo_dir(conn, cache_path, check_safe_path(gpo.file_sys_path))
+
+def gpo_version(lp, path):
+    # gpo.gpo_get_sysvol_gpt_version() reads the GPT.INI from a local file,
+    # read from the gpo client cache.
+    gpt_path = lp.cache_path(os.path.join('gpo_cache', path))
+    return int(gpo.gpo_get_sysvol_gpt_version(gpt_path)[1])
+
 def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
     gp_db = store.get_gplog(creds.get_username())
     dc_hostname = get_dc_hostname(creds, lp)
@@ -432,14 +477,19 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
         logger.error('Error connecting to \'%s\' using SMB' % dc_hostname)
         raise
     gpos = get_gpo_list(dc_hostname, creds, lp)
+    try:
+        check_refresh_gpo_list(dc_hostname, lp, creds, gpos)
+    except:
+        logger.error('Failed downloading gpt cache from \'%s\' using SMB' \
+            % dc_hostname)
+        return
 
     for gpo_obj in gpos:
         guid = gpo_obj.name
         if guid == 'Local Policy':
             continue
-        path = os.path.join(lp.get('realm').lower(), 'Policies', guid)
-        local_path = os.path.join(lp.get("path", "sysvol"), path)
-        version = int(gpo.gpo_get_sysvol_gpt_version(local_path)[1])
+        path = os.path.join(lp.get('realm'), 'Policies', guid).upper()
+        version = gpo_version(lp, path)
         if version != store.get_int(guid):
             logger.info('GPO %s has changed' % guid)
             gp_db.state(GPOSTATE.APPLY)
index 49fb59f32b1dd4d86780cdc370bf1f20e1b3d841..a25dbd1c3656213da5bf72830e591236c4be3215 100644 (file)
@@ -17,6 +17,7 @@
 import os
 from samba import gpo, tests
 from samba.param import LoadParm
+from samba.gpclass import check_refresh_gpo_list, check_safe_path
 
 poldir = r'\\addom.samba.example.com\sysvol\addom.samba.example.com\Policies'
 dspath = 'CN=Policies,CN=System,DC=addom,DC=samba,DC=example,DC=com'
@@ -59,8 +60,8 @@ class GPOTests(tests.TestCase):
 
     def test_gpt_version(self):
         global gpt_data
-        local_path = self.lp.get("path", "sysvol")
-        policies = 'addom.samba.example.com/Policies'
+        local_path = self.lp.cache_path('gpo_cache')
+        policies = 'ADDOM.SAMBA.EXAMPLE.COM/POLICIES'
         guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
         gpo_path = os.path.join(local_path, policies, guid)
         old_vers = gpo.gpo_get_sysvol_gpt_version(gpo_path)[1]
@@ -75,3 +76,36 @@ class GPOTests(tests.TestCase):
         self.assertEquals(gpo.gpo_get_sysvol_gpt_version(gpo_path)[1], old_vers,
           'gpo_get_sysvol_gpt_version() did not return the expected version')
 
+    def test_check_refresh_gpo_list(self):
+        cache = self.lp.cache_path('gpo_cache')
+        ads = gpo.ADS_STRUCT(self.server, self.lp, self.creds)
+        if ads.connect():
+            gpos = ads.get_gpo_list(self.creds.get_username())
+        check_refresh_gpo_list(self.server, self.lp, self.creds, gpos)
+
+        self.assertTrue(os.path.exists(cache),
+                        'GPO cache %s was not created' % cache)
+
+        guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
+        gpt_ini = os.path.join(cache, 'ADDOM.SAMBA.EXAMPLE.COM/POLICIES',
+                               guid, 'GPT.INI')
+        self.assertTrue(os.path.exists(gpt_ini),
+                        'GPT.INI was not cached for %s' % guid)
+
+    def test_check_refresh_gpo_list_malicious_paths(self):
+        # the path cannot contain ..
+        path = '/usr/local/samba/var/locks/sysvol/../../../../../../root/'
+        self.assertRaises(OSError, check_safe_path, path)
+
+        self.assertEqual(check_safe_path('/etc/passwd'), 'etc/passwd')
+        self.assertEqual(check_safe_path('\\\\etc/\\passwd'), 'etc/passwd')
+
+        # there should be no backslashes used to delineate paths
+        before = 'sysvol/addom.samba.example.com\\Policies/' \
+            '{31B2F340-016D-11D2-945F-00C04FB984F9}\\GPT.INI'
+        after = 'addom.samba.example.com/Policies/' \
+            '{31B2F340-016D-11D2-945F-00C04FB984F9}/GPT.INI'
+        result = check_safe_path(before)
+        self.assertEquals(result, after, 'check_safe_path() didn\'t' \
+            ' correctly convert \\ to /')
+