From decdb17ba9f6d41676a60eb1602fc5d5a2f777a4 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Mon, 8 Jan 2018 07:17:29 -0700 Subject: [PATCH 1/2] gpo: Read GPO versions locally, not from sysvol This patch does not change current functionality for the kdc. 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 --- python/samba/gpclass.py | 45 +++++++++++++++++++++++++++++++++++++++++++-- python/samba/tests/gpo.py | 15 +++++++++++++++ source4/selftest/tests.py | 2 +- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 0966611b686..4fffe392513 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -423,6 +423,41 @@ def get_gpo_list(dc_hostname, creds, lp): gpos = ads.get_gpo_list(creds.get_username()) return gpos +FILE_ATTRIBUTE_DIRECTORY = 0x10 +def cache_gpo_dir(conn, cache, sub_dir): + try: + os.makedirs(os.path.join(cache, sub_dir), mode=0o744) + except OSError: + pass # File Exists + for fdata in conn.list(sub_dir): + if fdata['attrib'] & FILE_ATTRIBUTE_DIRECTORY: + cache_gpo_dir(conn, cache, os.path.join(sub_dir, fdata['name'])) + else: + with open(os.path.join(cache, sub_dir, fdata['name']), 'w') as f: + fname = os.path.join(sub_dir, fdata['name']).replace('/', '\\') + f.write(conn.loadfile(fname)) + +def check_refresh_gpo_list(dc_hostname, lp, creds, gpos): + conn = smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds) + cache_path = lp.cache_path('gpo_cache') + for gpo in gpos: + if not gpo.file_sys_path: + continue + dirs = gpo.file_sys_path.split('\\') + dirs = dirs[dirs.index('sysvol')+1:] + cache_gpo_dir(conn, cache_path, os.path.join(*dirs)) + +def gpo_version(lp, path, sysvol): + # gpo.gpo_get_sysvol_gpt_version() reads the GPT.INI from a local file. + # If we don't have a sysvol path locally (if we're not a kdc), then + # read from the gpo client cache. + if sysvol: + local_path = os.path.join(sysvol, path, 'GPT.INI') + else: + gpt_path = lp.cache_path(os.path.join('gpo_cache', path)) + local_path = os.path.join(gpt_path, 'GPT.INI') + return int(gpo.gpo_get_sysvol_gpt_version(os.path.dirname(local_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 +467,20 @@ 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) + sysvol = lp.get("path", "sysvol") + if not sysvol: + try: + check_refresh_gpo_list(dc_hostname, lp, creds, gpos) + except: + logger.warn('Failed downloading gpt cache from \'%s\' using SMB' \ + % dc_hostname) 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]) + version = gpo_version(lp, path, sysvol) if version != store.get_int(guid): logger.info('GPO %s has changed' % guid) gp_db.state(GPOSTATE.APPLY) diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py index 796a5cb06cb..78b22e908a4 100644 --- a/python/samba/tests/gpo.py +++ b/python/samba/tests/gpo.py @@ -17,6 +17,7 @@ import os from samba import gpo, tests from samba.param import LoadParm +from samba.gpclass import check_refresh_gpo_list 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' @@ -75,3 +76,17 @@ def test_gpt_version(self): assert 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) + + assert 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') + assert os.path.exists(gpt_ini), 'GPT.INI was not cached for %s' % guid + diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 8b1fb7b280a..cc649a4501c 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -633,7 +633,7 @@ def planoldpythontestsuite(env, module, name=None, extra_path=[], environ={}, ex planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.dcerpc.rpcecho", py3_compatible=True) planoldpythontestsuite("nt4_dc", "samba.tests.netbios", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) -planoldpythontestsuite("ad_dc:local", "samba.tests.gpo", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) +planoldpythontestsuite("ad_dc:local", "samba.tests.gpo", extra_args=['-U"$USERNAME%$PASSWORD"']) planoldpythontestsuite("ad_dc:local", "samba.tests.dckeytab", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) planoldpythontestsuite("ad_dc:local", "samba.tests.smb", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) From 1d8c8a3481fdee7504e87821a85e7e8bbbae8935 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Wed, 16 May 2018 10:37:09 -0600 Subject: [PATCH 2/2] gpo: Offline policy application via cache Read policy files from the cache, rather than the sysvol (unless on a dc, then read from the local sysvol). This enables offline policy apply. Signed-off-by: David Mulder --- python/samba/gpclass.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 4fffe392513..062c168938a 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -305,12 +305,17 @@ def apply_map(self): def read(self, policy): pass - def parse(self, afile, ldb, conn, gp_db, lp): + def parse(self, afile, ldb, gp_db, lp): self.ldb = ldb self.gp_db = gp_db self.lp = lp # Fixing the bug where only some Linux Boxes capitalize MACHINE + sysvol = self.lp.get("path", "sysvol") + if sysvol: + local_path = sysvol + else: + local_path = self.lp.cache_path('gpo_cache') try: blist = afile.split('/') idx = afile.lower().split('/').index('machine') @@ -319,18 +324,18 @@ def parse(self, afile, ldb, conn, gp_db, lp): blist[idx].capitalize(), blist[idx].lower() ]: - bfile = '/'.join(blist[:idx]) + '/' + case + '/' + \ - '/'.join(blist[idx+1:]) - try: - return self.read(conn.loadfile(bfile.replace('/', '\\'))) - except NTSTATUSError: + bfile = ('/'.join(blist[:idx]) + '/' + case + '/' + \ + '/'.join(blist[idx+1:])).replace('\\', '/') + data_file = os.path.join(local_path, bfile) + if os.path.exists(data_file): + return self.read(open(data_file, 'r').read()) + else: continue except ValueError: - try: - return self.read(conn.loadfile(afile.replace('/', '\\'))) - except Exception as e: - self.logger.error(str(e)) - return None + data_file = os.path.join(local_path, afile).replace('\\', '/') + if os.path.exists(data_file): + return self.read(open(data_file, 'r').read()) + return None @abstractmethod def __str__(self): @@ -461,11 +466,6 @@ def gpo_version(lp, path, sysvol): 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) - try: - conn = smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds) - except: - logger.error('Error connecting to \'%s\' using SMB' % dc_hostname) - raise gpos = get_gpo_list(dc_hostname, creds, lp) sysvol = lp.get("path", "sysvol") if not sysvol: @@ -490,7 +490,7 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions): store.start() for ext in gp_extensions: try: - ext.parse(ext.list(path), test_ldb, conn, gp_db, lp) + ext.parse(ext.list(path), test_ldb, gp_db, lp) except Exception as e: logger.error('Failed to parse gpo %s for extension %s' % \ (guid, str(ext)))