From 051bcc014fa9a99f3fb5371f5a632eb86a7b7abb Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 08:00:15 -0600 Subject: [PATCH 01/11] 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 Reviewed-by: Douglas Bagnall --- 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 c5bed6dc800fe60f5de1141f3d874943893f13d9 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 09:07:53 -0600 Subject: [PATCH 02/11] 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 Reviewed-by: Douglas Bagnall --- 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 213cc6e93c3e98a314ab75ac3f12b5e5faca9a23 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 08:05:21 -0600 Subject: [PATCH 03/11] 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 Reviewed-by: Douglas Bagnall --- 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 c2ab675c36639da92b374fb6eab895f66c4d8309 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 08:25:05 -0600 Subject: [PATCH 04/11] 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 Reviewed-by: Douglas Bagnall --- 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 3622663b99e18fb74e7a7b6583a3cd138f9abee8 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Thu, 29 Mar 2018 08:32:02 -0600 Subject: [PATCH 05/11] 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 Reviewed-by: Douglas Bagnall --- 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 6a4190bbe782ae5eb73f0ec401b7b7c78d5d45b5 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Mon, 12 Mar 2018 09:44:38 -0600 Subject: [PATCH 06/11] 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 Reviewed-by: Douglas Bagnall --- 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 5fc6676a3e57181e58a981085d224172a8026e9d Mon Sep 17 00:00:00 2001 From: David Mulder Date: Tue, 10 Apr 2018 15:07:34 -0600 Subject: [PATCH 07/11] param: Add python binding for lpcfg_cache_path Signed-off-by: David Mulder --- source4/param/pyparam.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/source4/param/pyparam.c b/source4/param/pyparam.c index f16c2c0b227..3790a04fd7b 100644 --- a/source4/param/pyparam.c +++ b/source4/param/pyparam.c @@ -358,6 +358,27 @@ 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) { + PyErr_Format(PyExc_RuntimeError, + "Unable to access cache %s", name); + 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 +415,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 } }; From 7ed5e92f26c4343ebbe03492e0f959557197696c Mon Sep 17 00:00:00 2001 From: David Mulder Date: Wed, 11 Apr 2018 12:40:18 -0600 Subject: [PATCH 08/11] libgpo: gpo_copy_file() shouldn't explicitly call smb1 Don't call cli_openx directly to open a file this calls smb1 code explicitly, which fails if we did a multi-protocol negotiate and negotiated smb2+. Use the higher level cli_open() instead. Signed-off-by: David Mulder --- libgpo/gpo_filesync.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libgpo/gpo_filesync.c b/libgpo/gpo_filesync.c index 6e3efdaf6c1..bf0bb5381fc 100644 --- a/libgpo/gpo_filesync.c +++ b/libgpo/gpo_filesync.c @@ -50,7 +50,7 @@ NTSTATUS gpo_copy_file(TALLOC_CTX *mem_ctx, int read_size = io_bufsize; off_t nread = 0; - result = cli_openx(cli, nt_path, O_RDONLY, DENY_NONE, &fnum); + result = cli_open(cli, nt_path, O_RDONLY, DENY_NONE, &fnum); if (!NT_STATUS_IS_OK(result)) { goto out; } From 27bebbd0e2f2061a11f9a707a0b679431e093585 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Tue, 10 Apr 2018 15:04:53 -0600 Subject: [PATCH 09/11] libgpo: Add python bindings for check_refresh_gpo_list Signed-off-by: David Mulder --- libgpo/pygpo.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c index ac6e3237a82..e5f63e1f440 100644 --- a/libgpo/pygpo.c +++ b/libgpo/pygpo.c @@ -290,6 +290,46 @@ static PyObject* py_ads_connect(ADS *self) /* Parameter mapping and functions for the GP_EXT struct */ void initgpo(void); +static PyObject *py_check_refresh_gpo_list(PyObject * self, + PyObject * args) +{ + TALLOC_CTX *frame = talloc_stackframe(); + ADS *ads; + const char *cache_dir; + struct GROUP_POLICY_OBJECT *gpo_ptr = NULL; + PyObject *gpo_list; + PyObject *gpo_obj; + NTSTATUS status; + PyObject *ret = NULL; + + if (!PyArg_ParseTuple(args, "OO|s", &ads, &gpo_list, &cache_dir)) { + goto out; + } + gpo_obj = PyList_GetItem(gpo_list, 0); + gpo_ptr = (struct GROUP_POLICY_OBJECT *)pytalloc_get_ptr(gpo_obj); + + if (!cache_dir) { + cache_dir = cache_path(GPO_CACHE_DIR); + if (!cache_dir) { + PyErr_SetString(PyExc_MemoryError, + "Failed to determine gpo cache dir"); + goto out; + } + } + + status = check_refresh_gpo_list(ads->ads_ptr, frame, cache_dir, 0, + gpo_ptr); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + goto out; + } + + ret = Py_True; +out: + TALLOC_FREE(frame); + return ret; +} + /* Global methods aka do not need a special pyobject type */ static PyObject *py_gpo_get_sysvol_gpt_version(PyObject * self, PyObject * args) @@ -497,6 +537,9 @@ static PyMethodDef py_gpo_methods[] = { {"gpo_get_sysvol_gpt_version", (PyCFunction)py_gpo_get_sysvol_gpt_version, METH_VARARGS, NULL}, + {"check_refresh_gpo_list", + (PyCFunction)py_check_refresh_gpo_list, + METH_VARARGS, NULL}, {NULL} }; From 6ffe43e3286a5f938df86b3bbfafbb4c5d9a4efa Mon Sep 17 00:00:00 2001 From: David Mulder Date: Wed, 11 Apr 2018 12:45:40 -0600 Subject: [PATCH 10/11] gpo: python chardet is not a dep of samba Signed-off-by: David Mulder --- python/samba/gpclass.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 0876e54cefb..0966611b686 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -29,7 +29,6 @@ from samba.dcerpc import nbt from samba import smb import samba.gpo as gpo -import chardet try: from enum import Enum From dfdb5aedbfff632ec9c33763c0d8bda11a4bc654 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Mon, 8 Jan 2018 07:17:29 -0700 Subject: [PATCH 11/11] 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 | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 0966611b686..b9c376bf75a 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -421,7 +421,18 @@ def get_gpo_list(dc_hostname, creds, lp): ads = gpo.ADS_STRUCT(dc_hostname, lp, creds) if ads.connect(): gpos = ads.get_gpo_list(creds.get_username()) - return gpos + return (ads, gpos) + +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()) @@ -431,15 +442,17 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions): except: logger.error('Error connecting to \'%s\' using SMB' % dc_hostname) raise - gpos = get_gpo_list(dc_hostname, creds, lp) + ads, gpos = get_gpo_list(dc_hostname, creds, lp) + sysvol = lp.get("path", "sysvol") + if not sysvol: + gpo.check_refresh_gpo_list(ads, gpos) 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)