The branch, master has been updated via 09c2121 gpo: python chardet is not a dep of samba via 8e25e2f libgpo: gpo_copy_file() shouldn't explicitly call smb1 via 6747553 param: Add python binding for lpcfg_cache_path via 477fd77 gpo: Create a gp_sec_ext module via 76cfbb6 gpo: Move implementation from samba_gpoupdate via 10b43aa gpo: Create base class gp_inf_ext via dc41514 gpo: Move the file parse function to gp_ext via 1d47ab7 gpo: Move gp_sec_ext __init__ to base class via cfd2d70 gpo: Rename the inf_to class to gp_ext_setter from 490756a Check "auth event notification" param in log_json
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 09c21214798ad435db488d206050d842b117166e Author: David Mulder <dmul...@suse.com> Date: Wed Apr 11 12:45:40 2018 -0600 gpo: python chardet is not a dep of samba Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> Autobuild-User(master): Douglas Bagnall <dbagn...@samba.org> Autobuild-Date(master): Thu Apr 12 11:27:03 CEST 2018 on sn-devel-144 commit 8e25e2f3408bda87e2acffa3fc464c32173bb814 Author: David Mulder <dmul...@suse.com> Date: Wed Apr 11 12:40:18 2018 -0600 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 <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 6747553d1b40dc82b6aae6173dff7d2f89deb90d Author: David Mulder <dmul...@suse.com> Date: Tue Apr 10 15:07:34 2018 -0600 param: Add python binding for lpcfg_cache_path Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 477fd77c4d200f2c987caaf0c1740273c7a4e4a7 Author: David Mulder <dmul...@suse.com> Date: Mon Mar 12 09:44:38 2018 -0600 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 <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 76cfbb6e8cec87b7f1b069dfed06a572359eab0f Author: David Mulder <dmul...@suse.com> Date: Thu Mar 29 08:32:02 2018 -0600 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 <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 10b43aa1c9c1772af1485d93a8bdf53c31de211c Author: David Mulder <dmul...@suse.com> Date: Thu Mar 29 08:25:05 2018 -0600 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 <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit dc4151476923067b7659f42ca1f2c5f809cfef1e Author: David Mulder <dmul...@suse.com> Date: Thu Mar 29 08:05:21 2018 -0600 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 <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 1d47ab7e2a49048f425d544b38bc980abb4f53ef Author: David Mulder <dmul...@suse.com> Date: Thu Mar 29 09:07:53 2018 -0600 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 <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit cfd2d70889fdb07bba4d0bb05e59abe2128c84ee Author: David Mulder <dmul...@suse.com> Date: Thu Mar 29 08:00:15 2018 -0600 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 <dmul...@suse.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> ----------------------------------------------------------------------- Summary of changes: libgpo/gpo_filesync.c | 2 +- python/samba/gp_sec_ext.py | 153 +++++++++++++++++++ python/samba/gpclass.py | 274 ++++++++++++++-------------------- source4/param/pyparam.c | 25 ++++ source4/scripting/bin/samba_gpoupdate | 79 +--------- 5 files changed, 296 insertions(+), 237 deletions(-) create mode 100644 python/samba/gp_sec_ext.py Changeset truncated at 500 lines: diff --git a/libgpo/gpo_filesync.c b/libgpo/gpo_filesync.c index 6e3efda..bf0bb53 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; } diff --git a/python/samba/gp_sec_ext.py b/python/samba/gp_sec_ext.py new file mode 100644 index 0000000..bbd385f --- /dev/null +++ b/python/samba/gp_sec_ext.py @@ -0,0 +1,153 @@ +# gp_sec_ext kdc gpo policy +# Copyright (C) Luke Morrison <luc...@.hotmail.com> 2013 +# Copyright (C) David Mulder <dmul...@suse.com> 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 <http://www.gnu.org/licenses/>. + +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 33c9001..0966611 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -25,6 +25,10 @@ from StringIO import StringIO 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 try: from enum import Enum @@ -286,6 +290,9 @@ class GPOStorage: class gp_ext(object): __metaclass__ = ABCMeta + def __init__(self, logger): + self.logger = logger + @abstractmethod def list(self, rootpath): pass @@ -295,14 +302,41 @@ class gp_ext(object): 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 -class inf_to(): +class gp_ext_setter(): __metaclass__ = ABCMeta def __init__(self, logger, ldb, gp_db, lp, attribute, val): @@ -328,148 +362,19 @@ class inf_to(): def __str__(self): pass -class inf_to_kdc_tdb(inf_to): - 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(inf_to): - '''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_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 __init__(self, logger): - self.logger = logger - - def __str__(self): - return "Security GPO extension" - +class gp_inf_ext(gp_ext): + @abstractmethod 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") + pass + @abstractmethod 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 - ), - } - } - - def read_inf(self, path, conn): + pass + + 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 +404,78 @@ class gp_sec_ext(gp_ext): 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 + @abstractmethod + def __str__(self): + pass - # Fixing the bug where only some Linux Boxes capitalize MACHINE - if afile.endswith('inf'): +''' 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: - 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 + 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/param/pyparam.c b/source4/param/pyparam.c index f16c2c0..18d7017 100644 --- a/source4/param/pyparam.c -- Samba Shared Repository