From e1b49382c3e48b6e07264b8a023e085a8e0628fd Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 08:00:15 -0600 Subject: [PATCH 1/7] gpo: Rename the inf_to class to gp_ext_setter This class will be subclassed and used for more than just inf settings application. Signed-off-by: David Mulder --- python/samba/gpclass.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 33c9001cb6d..efca807fa64 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -302,7 +302,7 @@ def parse(self, afile, ldb, conn, gp_db, lp): def __str__(self): pass -class inf_to(): +class gp_ext_setter(): __metaclass__ = ABCMeta def __init__(self, logger, ldb, gp_db, lp, attribute, val): @@ -328,7 +328,7 @@ def mapper(self): def __str__(self): pass -class inf_to_kdc_tdb(inf_to): +class inf_to_kdc_tdb(gp_ext_setter): def mins_to_hours(self): return '%d' % (int(self.val)/60) @@ -357,7 +357,7 @@ def mapper(self): def __str__(self): return 'Kerberos Policy' -class inf_to_ldb(inf_to): +class inf_to_ldb(gp_ext_setter): '''This class takes the .inf file parameter (essentially a GPO file mapped to a GUID), hashmaps it to the Samba parameter, which then uses an ldb object to update the parameter to Samba4. Not registry oriented whatsoever. From 841fbb3fd9f2b6b74e8519507de1780bbc821165 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 09:07:53 -0600 Subject: [PATCH 2/7] gpo: Move gp_sec_ext __init__ to base class For this class to be extensible, the constructor should be available to subclasses. Signed-off-by: David Mulder --- python/samba/gpclass.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index efca807fa64..8405c8fe595 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -286,6 +286,9 @@ def __del__(self): class gp_ext(object): __metaclass__ = ABCMeta + def __init__(self, logger): + self.logger = logger + @abstractmethod def list(self, rootpath): pass @@ -424,9 +427,6 @@ class gp_sec_ext(gp_ext): count = 0 - def __init__(self, logger): - self.logger = logger - def __str__(self): return "Security GPO extension" From 2ccba1797823e69b398c83d1120844feec44262d Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 08:05:21 -0600 Subject: [PATCH 3/7] gpo: Move the file parse function to gp_ext A file will always be read from the sysvol the same way, but the data will be read differently. This patch moves the parse function to gp_ext, and requires subclasses to implement the read() function to interpret the data. Signed-off-by: David Mulder --- python/samba/gpclass.py | 56 +++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 8405c8fe595..7f7392d8092 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -298,9 +298,36 @@ def apply_map(self): pass @abstractmethod - def parse(self, afile, ldb, conn, gp_db, lp): + def read(self, policy): pass + def parse(self, afile, ldb, conn, gp_db, lp): + self.ldb = ldb + self.gp_db = gp_db + self.lp = lp + + # Fixing the bug where only some Linux Boxes capitalize MACHINE + try: + blist = afile.split('/') + idx = afile.lower().split('/').index('machine') + for case in [ + blist[idx].upper(), + 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: + continue + except ValueError: + try: + return self.read(conn.loadfile(afile.replace('/', '\\'))) + except Exception as e: + self.logger.error(str(e)) + return None + @abstractmethod def __str__(self): pass @@ -465,11 +492,10 @@ def apply_map(self): } } - def read_inf(self, path, conn): + def read(self, policy): ret = False inftable = self.apply_map() - policy = conn.loadfile(path.replace('/', '\\')) current_section = None # So here we would declare a boolean, @@ -499,27 +525,3 @@ def read_inf(self, path, conn): self.gp_db.commit() return ret - def parse(self, afile, ldb, conn, gp_db, lp): - self.ldb = ldb - self.gp_db = gp_db - self.lp = lp - - # Fixing the bug where only some Linux Boxes capitalize MACHINE - if afile.endswith('inf'): - try: - blist = afile.split('/') - idx = afile.lower().split('/').index('machine') - for case in [blist[idx].upper(), blist[idx].capitalize(), - blist[idx].lower()]: - bfile = '/'.join(blist[:idx]) + '/' + case + '/' + \ - '/'.join(blist[idx+1:]) - try: - return self.read_inf(bfile, conn) - except NTSTATUSError: - continue - except ValueError: - try: - return self.read_inf(afile, conn) - except: - return None - From 142df23261fd5ad7e29b4f16c528fc6b1e46b7cd Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 08:25:05 -0600 Subject: [PATCH 4/7] gpo: Create base class gp_inf_ext Abstract the process of reading from an ini file, since other extensions will be reading gpos this way. Signed-off-by: David Mulder --- python/samba/gpclass.py | 81 ++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 7f7392d8092..a4ff22b5e13 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -446,7 +446,53 @@ def __str__(self): return 'System Access' -class gp_sec_ext(gp_ext): +class gp_inf_ext(gp_ext): + @abstractmethod + def list(self, rootpath): + pass + + @abstractmethod + def apply_map(self): + pass + + def read(self, policy): + ret = False + inftable = self.apply_map() + + current_section = None + + # So here we would declare a boolean, + # that would get changed to TRUE. + # + # If at any point in time a GPO was applied, + # then we return that boolean at the end. + + inf_conf = ConfigParser() + inf_conf.optionxform=str + try: + inf_conf.readfp(StringIO(policy)) + except: + inf_conf.readfp(StringIO(policy.decode('utf-16'))) + + for section in inf_conf.sections(): + current_section = inftable.get(section) + if not current_section: + continue + for key, value in inf_conf.items(section): + if current_section.get(key): + (att, setter) = current_section.get(key) + value = value.encode('ascii', 'ignore') + ret = True + setter(self.logger, self.ldb, self.gp_db, self.lp, att, + value).update_samba() + self.gp_db.commit() + return ret + + @abstractmethod + def __str__(self): + pass + +class gp_sec_ext(gp_inf_ext): '''This class does the following two things: 1) Identifies the GPO if it has a certain kind of filepath, 2) Finally parses it. @@ -492,36 +538,3 @@ def apply_map(self): } } - def read(self, policy): - ret = False - inftable = self.apply_map() - - current_section = None - - # So here we would declare a boolean, - # that would get changed to TRUE. - # - # If at any point in time a GPO was applied, - # then we return that boolean at the end. - - inf_conf = ConfigParser() - inf_conf.optionxform=str - try: - inf_conf.readfp(StringIO(policy)) - except: - inf_conf.readfp(StringIO(policy.decode('utf-16'))) - - for section in inf_conf.sections(): - current_section = inftable.get(section) - if not current_section: - continue - for key, value in inf_conf.items(section): - if current_section.get(key): - (att, setter) = current_section.get(key) - value = value.encode('ascii', 'ignore') - ret = True - setter(self.logger, self.ldb, self.gp_db, self.lp, att, - value).update_samba() - self.gp_db.commit() - return ret - From 3467f35748328b304267ff0dda316682dca57667 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 08:32:02 -0600 Subject: [PATCH 5/7] gpo: Move implementation from samba_gpoupdate The implementation of group policy apply should not be in the application script. One reason is to implement user apply, we can call these functions via the python c-api, (passing creds via the command line will expose them via ps). Another reason for this is if some overrides the smb.conf "gpo update command" option, it would be useful to have these functions. Signed-off-by: David Mulder --- python/samba/gpclass.py | 76 +++++++++++++++++++++++++++++++++ source4/scripting/bin/samba_gpoupdate | 79 +---------------------------------- 2 files changed, 78 insertions(+), 77 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index a4ff22b5e13..3be70498117 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -25,6 +25,11 @@ from abc import ABCMeta, abstractmethod import xml.etree.ElementTree as etree import re +from samba.net import Net +from samba.dcerpc import nbt +from samba import smb +import samba.gpo as gpo +import chardet try: from enum import Enum @@ -538,3 +543,74 @@ def apply_map(self): } } +''' Fetch the hostname of a writable DC ''' +def get_dc_hostname(creds, lp): + net = Net(creds=creds, lp=lp) + cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP | + nbt.NBT_SERVER_DS)) + return cldap_ret.pdc_dns_name + +''' Fetch a list of GUIDs for applicable GPOs ''' +def get_gpo_list(dc_hostname, creds, lp): + gpos = [] + ads = gpo.ADS_STRUCT(dc_hostname, lp, creds) + if ads.connect(): + gpos = ads.get_gpo_list(creds.get_username()) + return gpos + +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) + + 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]) + if version != store.get_int(guid): + logger.info('GPO %s has changed' % guid) + gp_db.state(GPOSTATE.APPLY) + else: + gp_db.state(GPOSTATE.ENFORCE) + gp_db.set_guid(guid) + store.start() + for ext in gp_extensions: + try: + ext.parse(ext.list(path), test_ldb, conn, gp_db, lp) + except Exception as e: + logger.error('Failed to parse gpo %s for extension %s' % \ + (guid, str(ext))) + logger.error('Message was: ' + str(e)) + store.cancel() + continue + store.store(guid, '%i' % version) + store.commit() + +def unapply_log(gp_db): + while True: + item = gp_db.apply_log_pop() + if item: + yield item + else: + break + +def unapply_gp(lp, creds, test_ldb, logger, store, gp_extensions): + gp_db = store.get_gplog(creds.get_username()) + gp_db.state(GPOSTATE.UNAPPLY) + for gpo_guid in unapply_log(gp_db): + gp_db.set_guid(gpo_guid) + unapply_attributes = gp_db.list(gp_extensions) + for attr in unapply_attributes: + attr_obj = attr[-1](logger, test_ldb, gp_db, lp, attr[0], attr[1]) + attr_obj.mapper()[attr[0]][0](attr[1]) # Set the old value + gp_db.delete(str(attr_obj), attr[0]) + gp_db.commit() + diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate index 26e0984413e..89b3ed77616 100755 --- a/source4/scripting/bin/samba_gpoupdate +++ b/source4/scripting/bin/samba_gpoupdate @@ -34,84 +34,9 @@ try: from samba.samdb import SamDB except: SamDB = None -from samba.gpclass import * -from samba.net import Net -from samba.dcerpc import nbt -from samba import smb -import samba.gpo as gpo +from samba.gpclass import apply_gp, unapply_gp, GPOStorage +from samba.gp_sec_ext import gp_sec_ext import logging -import chardet - -''' Fetch the hostname of a writable DC ''' -def get_dc_hostname(creds, lp): - net = Net(creds=creds, lp=lp) - cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP | - nbt.NBT_SERVER_DS)) - return cldap_ret.pdc_dns_name - -''' Fetch a list of GUIDs for applicable GPOs ''' -def get_gpo_list(dc_hostname, creds, lp): - gpos = [] - ads = gpo.ADS_STRUCT(dc_hostname, lp, creds) - if ads.connect(): - gpos = ads.get_gpo_list(creds.get_username()) - return gpos - -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) - - 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]) - if version != store.get_int(guid): - logger.info('GPO %s has changed' % guid) - gp_db.state(GPOSTATE.APPLY) - else: - gp_db.state(GPOSTATE.ENFORCE) - gp_db.set_guid(guid) - store.start() - for ext in gp_extensions: - try: - ext.parse(ext.list(path), test_ldb, conn, gp_db, lp) - except Exception as e: - logger.error('Failed to parse gpo %s for extension %s' % \ - (guid, str(ext))) - logger.error('Message was: ' + str(e)) - store.cancel() - continue - store.store(guid, '%i' % version) - store.commit() - -def unapply_log(gp_db): - while True: - item = gp_db.apply_log_pop() - if item: - yield item - else: - break - -def unapply_gp(lp, creds, test_ldb, logger, store, gp_extensions): - gp_db = store.get_gplog(creds.get_username()) - gp_db.state(GPOSTATE.UNAPPLY) - for gpo_guid in unapply_log(gp_db): - gp_db.set_guid(gpo_guid) - unapply_attributes = gp_db.list(gp_extensions) - for attr in unapply_attributes: - attr_obj = attr[-1](logger, test_ldb, gp_db, lp, attr[0], attr[1]) - attr_obj.mapper()[attr[0]][0](attr[1]) # Set the old value - gp_db.delete(str(attr_obj), attr[0]) - gp_db.commit() if __name__ == "__main__": parser = optparse.OptionParser('samba_gpoupdate [options]') From 17c1b24ed52cc542950da686d6bc56f06db902a8 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Mon, 12 Mar 2018 09:44:38 -0600 Subject: [PATCH 6/7] gpo: Create a gp_sec_ext module Move the gp_sec_ext into it's own module, which is how new gp_ext's will be created. Signed-off-by: David Mulder --- python/samba/gp_sec_ext.py | 153 +++++++++++++++++++++++++++++++++++++++++++++ python/samba/gpclass.py | 134 --------------------------------------- 2 files changed, 153 insertions(+), 134 deletions(-) create mode 100644 python/samba/gp_sec_ext.py diff --git a/python/samba/gp_sec_ext.py b/python/samba/gp_sec_ext.py new file mode 100644 index 00000000000..bbd385f73c6 --- /dev/null +++ b/python/samba/gp_sec_ext.py @@ -0,0 +1,153 @@ +# gp_sec_ext kdc gpo policy +# Copyright (C) Luke Morrison 2013 +# Copyright (C) David Mulder 2018 +# +# 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 . + +import os.path +from gpclass import gp_ext_setter, gp_inf_ext + +class inf_to_kdc_tdb(gp_ext_setter): + def mins_to_hours(self): + return '%d' % (int(self.val)/60) + + def days_to_hours(self): + return '%d' % (int(self.val)*24) + + def set_kdc_tdb(self, val): + old_val = self.gp_db.gpostore.get(self.attribute) + self.logger.info('%s was changed from %s to %s' % (self.attribute, + old_val, val)) + if val is not None: + self.gp_db.gpostore.store(self.attribute, val) + self.gp_db.store(str(self), self.attribute, old_val) + else: + self.gp_db.gpostore.delete(self.attribute) + self.gp_db.delete(str(self), self.attribute) + + def mapper(self): + return { 'kdc:user_ticket_lifetime': (self.set_kdc_tdb, self.explicit), + 'kdc:service_ticket_lifetime': (self.set_kdc_tdb, + self.mins_to_hours), + 'kdc:renewal_lifetime': (self.set_kdc_tdb, + self.days_to_hours), + } + + def __str__(self): + return 'Kerberos Policy' + +class inf_to_ldb(gp_ext_setter): + '''This class takes the .inf file parameter (essentially a GPO file mapped + to a GUID), hashmaps it to the Samba parameter, which then uses an ldb + object to update the parameter to Samba4. Not registry oriented whatsoever. + ''' + + def ch_minPwdAge(self, val): + old_val = self.ldb.get_minPwdAge() + self.logger.info('KDC Minimum Password age was changed from %s to %s' \ + % (old_val, val)) + self.gp_db.store(str(self), self.attribute, old_val) + self.ldb.set_minPwdAge(val) + + def ch_maxPwdAge(self, val): + old_val = self.ldb.get_maxPwdAge() + self.logger.info('KDC Maximum Password age was changed from %s to %s' \ + % (old_val, val)) + self.gp_db.store(str(self), self.attribute, old_val) + self.ldb.set_maxPwdAge(val) + + def ch_minPwdLength(self, val): + old_val = self.ldb.get_minPwdLength() + self.logger.info( + 'KDC Minimum Password length was changed from %s to %s' \ + % (old_val, val)) + self.gp_db.store(str(self), self.attribute, old_val) + self.ldb.set_minPwdLength(val) + + def ch_pwdProperties(self, val): + old_val = self.ldb.get_pwdProperties() + self.logger.info('KDC Password Properties were changed from %s to %s' \ + % (old_val, val)) + self.gp_db.store(str(self), self.attribute, old_val) + self.ldb.set_pwdProperties(val) + + def days2rel_nttime(self): + seconds = 60 + minutes = 60 + hours = 24 + sam_add = 10000000 + val = (self.val) + val = int(val) + return str(-(val * seconds * minutes * hours * sam_add)) + + def mapper(self): + '''ldap value : samba setter''' + return { "minPwdAge" : (self.ch_minPwdAge, self.days2rel_nttime), + "maxPwdAge" : (self.ch_maxPwdAge, self.days2rel_nttime), + # Could be none, but I like the method assignment in + # update_samba + "minPwdLength" : (self.ch_minPwdLength, self.explicit), + "pwdProperties" : (self.ch_pwdProperties, self.explicit), + + } + + def __str__(self): + return 'System Access' + +class gp_sec_ext(gp_inf_ext): + '''This class does the following two things: + 1) Identifies the GPO if it has a certain kind of filepath, + 2) Finally parses it. + ''' + + count = 0 + + def __str__(self): + return "Security GPO extension" + + def list(self, rootpath): + return os.path.join(rootpath, + "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf") + + def listmachpol(self, rootpath): + return os.path.join(rootpath, "Machine/Registry.pol") + + def listuserpol(self, rootpath): + return os.path.join(rootpath, "User/Registry.pol") + + def apply_map(self): + return {"System Access": {"MinimumPasswordAge": ("minPwdAge", + inf_to_ldb), + "MaximumPasswordAge": ("maxPwdAge", + inf_to_ldb), + "MinimumPasswordLength": ("minPwdLength", + inf_to_ldb), + "PasswordComplexity": ("pwdProperties", + inf_to_ldb), + }, + "Kerberos Policy": {"MaxTicketAge": ( + "kdc:user_ticket_lifetime", + inf_to_kdc_tdb + ), + "MaxServiceAge": ( + "kdc:service_ticket_lifetime", + inf_to_kdc_tdb + ), + "MaxRenewAge": ( + "kdc:renewal_lifetime", + inf_to_kdc_tdb + ), + } + } + diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 3be70498117..0876e54cefb 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -363,94 +363,6 @@ def mapper(self): def __str__(self): pass -class inf_to_kdc_tdb(gp_ext_setter): - def mins_to_hours(self): - return '%d' % (int(self.val)/60) - - def days_to_hours(self): - return '%d' % (int(self.val)*24) - - def set_kdc_tdb(self, val): - old_val = self.gp_db.gpostore.get(self.attribute) - self.logger.info('%s was changed from %s to %s' % (self.attribute, - old_val, val)) - if val is not None: - self.gp_db.gpostore.store(self.attribute, val) - self.gp_db.store(str(self), self.attribute, old_val) - else: - self.gp_db.gpostore.delete(self.attribute) - self.gp_db.delete(str(self), self.attribute) - - def mapper(self): - return { 'kdc:user_ticket_lifetime': (self.set_kdc_tdb, self.explicit), - 'kdc:service_ticket_lifetime': (self.set_kdc_tdb, - self.mins_to_hours), - 'kdc:renewal_lifetime': (self.set_kdc_tdb, - self.days_to_hours), - } - - def __str__(self): - return 'Kerberos Policy' - -class inf_to_ldb(gp_ext_setter): - '''This class takes the .inf file parameter (essentially a GPO file mapped - to a GUID), hashmaps it to the Samba parameter, which then uses an ldb - object to update the parameter to Samba4. Not registry oriented whatsoever. - ''' - - def ch_minPwdAge(self, val): - old_val = self.ldb.get_minPwdAge() - self.logger.info('KDC Minimum Password age was changed from %s to %s' \ - % (old_val, val)) - self.gp_db.store(str(self), self.attribute, old_val) - self.ldb.set_minPwdAge(val) - - def ch_maxPwdAge(self, val): - old_val = self.ldb.get_maxPwdAge() - self.logger.info('KDC Maximum Password age was changed from %s to %s' \ - % (old_val, val)) - self.gp_db.store(str(self), self.attribute, old_val) - self.ldb.set_maxPwdAge(val) - - def ch_minPwdLength(self, val): - old_val = self.ldb.get_minPwdLength() - self.logger.info( - 'KDC Minimum Password length was changed from %s to %s' \ - % (old_val, val)) - self.gp_db.store(str(self), self.attribute, old_val) - self.ldb.set_minPwdLength(val) - - def ch_pwdProperties(self, val): - old_val = self.ldb.get_pwdProperties() - self.logger.info('KDC Password Properties were changed from %s to %s' \ - % (old_val, val)) - self.gp_db.store(str(self), self.attribute, old_val) - self.ldb.set_pwdProperties(val) - - def days2rel_nttime(self): - seconds = 60 - minutes = 60 - hours = 24 - sam_add = 10000000 - val = (self.val) - val = int(val) - return str(-(val * seconds * minutes * hours * sam_add)) - - def mapper(self): - '''ldap value : samba setter''' - return { "minPwdAge" : (self.ch_minPwdAge, self.days2rel_nttime), - "maxPwdAge" : (self.ch_maxPwdAge, self.days2rel_nttime), - # Could be none, but I like the method assignment in - # update_samba - "minPwdLength" : (self.ch_minPwdLength, self.explicit), - "pwdProperties" : (self.ch_pwdProperties, self.explicit), - - } - - def __str__(self): - return 'System Access' - - class gp_inf_ext(gp_ext): @abstractmethod def list(self, rootpath): @@ -497,52 +409,6 @@ def read(self, policy): def __str__(self): pass -class gp_sec_ext(gp_inf_ext): - '''This class does the following two things: - 1) Identifies the GPO if it has a certain kind of filepath, - 2) Finally parses it. - ''' - - count = 0 - - def __str__(self): - return "Security GPO extension" - - def list(self, rootpath): - return os.path.join(rootpath, - "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf") - - def listmachpol(self, rootpath): - return os.path.join(rootpath, "Machine/Registry.pol") - - def listuserpol(self, rootpath): - return os.path.join(rootpath, "User/Registry.pol") - - def apply_map(self): - return {"System Access": {"MinimumPasswordAge": ("minPwdAge", - inf_to_ldb), - "MaximumPasswordAge": ("maxPwdAge", - inf_to_ldb), - "MinimumPasswordLength": ("minPwdLength", - inf_to_ldb), - "PasswordComplexity": ("pwdProperties", - inf_to_ldb), - }, - "Kerberos Policy": {"MaxTicketAge": ( - "kdc:user_ticket_lifetime", - inf_to_kdc_tdb - ), - "MaxServiceAge": ( - "kdc:service_ticket_lifetime", - inf_to_kdc_tdb - ), - "MaxRenewAge": ( - "kdc:renewal_lifetime", - inf_to_kdc_tdb - ), - } - } - ''' Fetch the hostname of a writable DC ''' def get_dc_hostname(creds, lp): net = Net(creds=creds, lp=lp) From decadeb6c6f979847c8c21e316069f02e4500af1 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Mon, 8 Jan 2018 07:17:29 -0700 Subject: [PATCH 7/7] 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 | 20 ++++++++++++++++++-- source4/param/pyparam.c | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 0876e54cefb..d53fc8e5e79 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -424,6 +424,23 @@ def get_gpo_list(dc_hostname, creds, lp): gpos = ads.get_gpo_list(creds.get_username()) return gpos +def gpo_version(lp, conn, path): + # 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 + # store the file locally here so it can be read on a client. + sysvol = lp.get("path", "sysvol") + if sysvol: + local_path = os.path.join(sysvol, path, 'GPT.INI') + else: + gpt_path = lp.cache_path(os.path.join('gpt', path)) + local_path = os.path.join(gpt_path, 'GPT.INI') + if not os.path.exists(os.path.dirname(local_path)): + os.makedirs(os.path.dirname(local_path), 0o700) + data = conn.loadfile(os.path.join(path, 'GPT.INI').replace('/', '\\')) + encoding = chardet.detect(data) + open(local_path, 'w').write(data.decode(encoding['encoding'])) + 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) @@ -439,8 +456,7 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions): 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, conn, path) if version != store.get_int(guid): logger.info('GPO %s has changed' % guid) gp_db.state(GPOSTATE.APPLY) diff --git a/source4/param/pyparam.c b/source4/param/pyparam.c index f16c2c0b227..08659792a4a 100644 --- a/source4/param/pyparam.c +++ b/source4/param/pyparam.c @@ -358,6 +358,25 @@ static PyObject *py_samdb_url(PyObject *self, PyObject *unused) return PyStr_FromFormat("tdb://%s/sam.ldb", lpcfg_private_dir(lp_ctx)); } +static PyObject *py_cache_path(PyObject *self, PyObject *args) +{ + struct loadparm_context *lp_ctx = PyLoadparmContext_AsLoadparmContext(self); + char *name, *path; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "s", &name)) { + return NULL; + } + + path = lpcfg_cache_path(NULL, lp_ctx, name); + if (!path) { + return NULL; + } + ret = PyStr_FromString(path); + talloc_free(path); + + return ret; +} static PyMethodDef py_lp_ctx_methods[] = { { "load", py_lp_ctx_load, METH_VARARGS, @@ -394,6 +413,9 @@ static PyMethodDef py_lp_ctx_methods[] = { { "samdb_url", py_samdb_url, METH_NOARGS, "S.samdb_url() -> string\n" "Returns the current URL for sam.ldb." }, + { "cache_path", py_cache_path, METH_VARARGS, + "S.cache_path(name) -> string\n" + "Returns a path in the Samba cache directory." }, { NULL } };