From d7760d5ddab248765b1344e56a56d975f41d8254 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 May 2019 10:49:11 +0200 Subject: [PATCH 1/9] docs-xml: add "netlogon refuse password change" option Signed-off-by: Stefan Metzmacher --- .../smbdotconf/logon/refusepasswordchange.xml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs-xml/smbdotconf/logon/refusepasswordchange.xml diff --git a/docs-xml/smbdotconf/logon/refusepasswordchange.xml b/docs-xml/smbdotconf/logon/refusepasswordchange.xml new file mode 100644 index 000000000000..8ddd966875fe --- /dev/null +++ b/docs-xml/smbdotconf/logon/refusepasswordchange.xml @@ -0,0 +1,23 @@ + + + This option controls whether the netlogon server (currently + only in 'active directory domain controller' mode), will + refuse workstation password changes via NetrServerPasswordSet[2]. + + You can set this during a migration in order to avoid + touching all domain members in order to disable machine account + password changes. + + This mirrors the effects of the + + HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\RefusePasswordChange + + registry key on Windows DCs. + + + +no + -- 2.17.1 From f20a82f8ced82dc1fba47bcd402ec4399d5799d3 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 May 2019 11:03:25 +0200 Subject: [PATCH 2/9] s4:rpc_server/netlogon: implement logic behind "netlogon refuse password change" Signed-off-by: Stefan Metzmacher --- source4/rpc_server/netlogon/dcerpc_netlogon.c | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index 13887e0c4d2d..d84c88e96d77 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -672,6 +672,19 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet(struct dcesrv_call_state *dce_call &creds); NT_STATUS_NOT_OK_RETURN(nt_status); + if (creds->secure_channel_type == SEC_CHAN_WKSTA) { + bool refuse_password_change; + + refuse_password_change = lpcfg_netlogon_refuse_password_change( + dce_call->conn->dce_ctx->lp_ctx); + + if (refuse_password_change) { + DBG_NOTICE("RefusePasswordChange for Workstation[%s]\n", + creds->account_name); + return NT_STATUS_WRONG_PASSWORD; + } + } + sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, @@ -737,6 +750,19 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal &creds); NT_STATUS_NOT_OK_RETURN(nt_status); + if (creds->secure_channel_type == SEC_CHAN_WKSTA) { + bool refuse_password_change; + + refuse_password_change = lpcfg_netlogon_refuse_password_change( + dce_call->conn->dce_ctx->lp_ctx); + + if (refuse_password_change) { + DBG_NOTICE("RefusePasswordChange for Workstation[%s]\n", + creds->account_name); + return NT_STATUS_WRONG_PASSWORD; + } + } + sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, -- 2.17.1 From 78f6cddc03762e7d554604b7d7fb261ca63b6d55 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 May 2019 11:04:41 +0200 Subject: [PATCH 3/9] TODO: python/samba/gp_parse/gp_inf.py RegParam --- python/samba/gp_parse/gp_inf.py | 54 ++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/python/samba/gp_parse/gp_inf.py b/python/samba/gp_parse/gp_inf.py index 79e28159f1f0..e83a13f59d67 100644 --- a/python/samba/gp_parse/gp_inf.py +++ b/python/samba/gp_parse/gp_inf.py @@ -91,32 +91,64 @@ class GptTmplInfParser(GPParser): self.param_list.append((key, value)) class RegParam(AbstractParam): - # param_list = [Value, Value, ...] + # param_list = [(Name, Type, Value)] def parse(self, line): - # = can occur in a registry key, so don't parse these - self.param_list.append(line) - # print line + # Read quoted string + if line[:1] == '"': + line = line[1:] + findex = line.find('"') + name = line[:findex] + line = line[findex + 1:] + findex = line.find('=') + if name is None: + name = line[:findex] + line = line[findex + 1:] + findex = line.find(',') + vtype = line[:findex] + line = line[findex + 1:] + # Read quoted string + if line[:1] == '"': + line = line[1:] + findex = line.find('"') + value = line[:findex] + line = line[findex + 1:] + else: + value = line + + self.param_list.append((name, vtype, value)) + + def may_quote(self, value): + findex = value.find(' ') + if findex == -1: + return u'%s' % value + return u'"%s"' % value + + def prepare_line(self, name, vtype, value): + line = u'%s=%s,%s' % (may_quote(name), vtype, may_quote(value)) + return line def write_section(self, header, fp): if len(self.param_list) == 0: return fp.write(u'[%s]\r\n' % header) - for param in self.param_list: - fp.write(u'%s\r\n' % param) + for name, vtype, value in self.param_list: + line = self.prepare_line(name, vtype, value) + fp.write(u'%s\r\n' % line) def build_xml(self, xml_parent): - for val_ini in self.param_list: + for name, vtype, value in self.param_list: + line = self.prepare_line(name, vtype, value) child = SubElement(xml_parent, 'Parameter') value = SubElement(child, 'Value') - value.text = val_ini + value.text = line def from_xml(self, section): for param in section.findall('Parameter'): - value = param.find('Value').text + line = param.find('Value').text if value is None: - value = '' + continue - self.param_list.append(value) + self.parse(line) class PrivSIDListParam(AbstractParam): # param_list = [(Key, [SID, SID,..]), -- 2.17.1 From 50e99ebd3c314a3bf851aa47143ce7b81947fcac Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 22 May 2019 15:11:13 +0200 Subject: [PATCH 4/9] check_safe_path SysVol ... --- python/samba/gpclass.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index 0040f235e6e3..c212cbd0ad4f 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -400,8 +400,15 @@ def cache_gpo_dir(conn, cache, sub_dir): def check_safe_path(path): dirs = re.split('/|\\\\', path) - if 'sysvol' in path: - dirs = dirs[dirs.index('sysvol') + 1:] + sysvol_idx = -1 + idx = 0 + for comp in dirs: + if comp.lower() == 'sysvol': + sysvol_idx = idx + break + idx += 1 + if sysvol_idx != -1: + dirs = dirs[sysvol_idx + 1:] if '..' not in dirs: return os.path.join(*dirs) raise OSError(path) -- 2.17.1 From 336af31fd70cdc956b25255e29e6f87cf8198dd5 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 22 May 2019 15:12:12 +0200 Subject: [PATCH 5/9] debug gpo --- libgpo/pygpo.c | 2 ++ source3/auth/token_util.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c index b1f788d3a00e..2adfa8cf432b 100644 --- a/libgpo/pygpo.c +++ b/libgpo/pygpo.c @@ -418,6 +418,8 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds) goto out; } + DBG_ERR("uac[0x%x] samaccountname[%s]\n" , uac, samaccountname); + if (uac & UF_WORKSTATION_TRUST_ACCOUNT || uac & UF_SERVER_TRUST_ACCOUNT) { flags |= GPO_LIST_FLAG_MACHINE; diff --git a/source3/auth/token_util.c b/source3/auth/token_util.c index 3a3b5d9936bc..4dc9ec257fb4 100644 --- a/source3/auth/token_util.c +++ b/source3/auth/token_util.c @@ -538,6 +538,8 @@ static NTSTATUS add_local_groups(struct security_token *result, TALLOC_CTX *tmp_ctx = talloc_stackframe(); int i; + TALLOC_FREE(tmp_ctx); + return NT_STATUS_OK; if (is_guest) { /* * Guest is a special case. It's always -- 2.17.1 From 9f39133706fe865c266a2eace2b69e4e91bb11b9 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 22 May 2019 16:46:10 +0200 Subject: [PATCH 6/9] split out _inf_to_gpo_tdb --- python/samba/gp_sec_ext.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/python/samba/gp_sec_ext.py b/python/samba/gp_sec_ext.py index 6eab975e6fee..c938eb943eca 100644 --- a/python/samba/gp_sec_ext.py +++ b/python/samba/gp_sec_ext.py @@ -26,14 +26,8 @@ except ImportError: 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): +class _inf_to_gpo_tdb(gp_ext_setter): + def set_gpo_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)) @@ -44,11 +38,21 @@ class inf_to_kdc_tdb(gp_ext_setter): self.gp_db.gpostore.delete(self.attribute) self.gp_db.delete(str(self), self.attribute) + def __str__(self): + return 'Generic _inf_to_gpo_tdb()' + +class inf_to_kdc_tdb(_inf_to_gpo_tdb): + def mins_to_hours(self): + return '%d' % (int(self.val) / 60) + + def days_to_hours(self): + return '%d' % (int(self.val) * 24) + def mapper(self): - return {'kdc:user_ticket_lifetime': (self.set_kdc_tdb, self.explicit), - 'kdc:service_ticket_lifetime': (self.set_kdc_tdb, + return {'kdc:user_ticket_lifetime': (self.set_gpo_tdb, self.explicit), + 'kdc:service_ticket_lifetime': (self.set_gpo_tdb, self.mins_to_hours), - 'kdc:renewal_lifetime': (self.set_kdc_tdb, + 'kdc:renewal_lifetime': (self.set_gpo_tdb, self.days_to_hours), } -- 2.17.1 From ee1f6741dbd323e0d5726bb49778870ea81221a2 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 22 May 2019 16:48:43 +0200 Subject: [PATCH 7/9] MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RefusePasswordChange GPO --- python/samba/gp_sec_ext.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/python/samba/gp_sec_ext.py b/python/samba/gp_sec_ext.py index c938eb943eca..68712d4c7ff6 100644 --- a/python/samba/gp_sec_ext.py +++ b/python/samba/gp_sec_ext.py @@ -60,6 +60,34 @@ class inf_to_kdc_tdb(_inf_to_gpo_tdb): return 'Kerberos Policy' +class inf_to_registry_tdb(_inf_to_gpo_tdb): + def dword_to_bool(self): + print("dword_to_bool(attr=%s, val=%s)" % ( + self.attribute, self.val)) + if not self.val.startswith("4,"): + raise ValueError(self.val) + val = self.val[2:] + val = int(val) + if val not in [0,1]: + raise ValueError(self.val) + if val == 0: + ret = "no" + else: + ret = "yes" + print("dword_to_bool(attr=%s, val=%s) => %s" % ( + self.attribute, self.val, ret)) + return ret + + def mapper(self): + return { + 'netlogon:refuse_password_change': + (self.set_gpo_tdb, self.dword_to_bool), + } + + def __str__(self): + return 'Registry Values' + + 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 @@ -162,7 +190,13 @@ class gp_sec_ext(gp_inf_ext): "kdc:renewal_lifetime", inf_to_kdc_tdb ), - } + }, + "Registry Values": { + "MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RefusePasswordChange": ( + "netlogon:refuse_password_change", + inf_to_registry_tdb + ), + }, } def process_group_policy(self, deleted_gpo_list, changed_gpo_list): -- 2.17.1 From 7c389df935ef89d113f49acc91cf8867fc487ded Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 22 May 2019 16:52:45 +0200 Subject: [PATCH 8/9] DEBUG samba-gpupdate --- python/samba/gp_parse/gp_inf.py | 6 +++++- python/samba/gp_sec_ext.py | 14 ++++++++++++++ python/samba/gpclass.py | 4 ++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/python/samba/gp_parse/gp_inf.py b/python/samba/gp_parse/gp_inf.py index e83a13f59d67..1b2ea1cf0058 100644 --- a/python/samba/gp_parse/gp_inf.py +++ b/python/samba/gp_parse/gp_inf.py @@ -59,7 +59,8 @@ class GptTmplInfParser(GPParser): def parse(self, line): key, val = line.split('=') - + return + assert False self.param_list.append((key.strip(), val.strip())) @@ -93,6 +94,7 @@ class GptTmplInfParser(GPParser): class RegParam(AbstractParam): # param_list = [(Name, Type, Value)] def parse(self, line): + print line # Read quoted string if line[:1] == '"': line = line[1:] @@ -327,6 +329,7 @@ class GptTmplInfParser(GPParser): pass def parse(self, contents): + print "INF parse()" inf_file = contents.decode(self.encoding) self.sections = collections.OrderedDict([ @@ -385,6 +388,7 @@ class GptTmplInfParser(GPParser): # self.load_xml(fromstring(contents)) def load_xml(self, root): + print "INF load_xml()" self.sections = collections.OrderedDict([ (u'Unicode', self.UnicodeParam()), (u'Version', self.VersionParam()), diff --git a/python/samba/gp_sec_ext.py b/python/samba/gp_sec_ext.py index 68712d4c7ff6..f13dec744f85 100644 --- a/python/samba/gp_sec_ext.py +++ b/python/samba/gp_sec_ext.py @@ -197,6 +197,12 @@ class gp_sec_ext(gp_inf_ext): inf_to_registry_tdb ), }, + "Privilege Rights": { + "None": ( + "none", + None + ), + }, } def process_group_policy(self, deleted_gpo_list, changed_gpo_list): @@ -205,6 +211,7 @@ class gp_sec_ext(gp_inf_ext): inf_file = 'MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf' apply_map = self.apply_map() for gpo in deleted_gpo_list: + print "DEL: %s" % gpo.name self.gp_db.set_guid(gpo[0]) for section in gpo[1].keys(): current_section = apply_map.get(section) @@ -224,17 +231,24 @@ class gp_sec_ext(gp_inf_ext): self.gp_db.commit() for gpo in changed_gpo_list: + print "CHANGED: %s" % gpo.name if gpo.file_sys_path: self.gp_db.set_guid(gpo.name) path = os.path.join(gpo.file_sys_path, inf_file) + print "INF_PATH: %s" % path + print "SELF.parse: %s" % self.parse inf_conf = self.parse(path) if not inf_conf: continue + print "INF_CONF: %s" % inf_conf for section in inf_conf.sections(): + print "section: [%s]" % section current_section = apply_map.get(section) if not current_section: continue + print "current_section: [%s]" % current_section for key, value in inf_conf.items(section): + print "key: [%s]" % key if current_section.get(key): (att, setter) = current_section.get(key) value = value.encode('ascii', 'ignore') diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py index c212cbd0ad4f..a539c98ccbba 100644 --- a/python/samba/gpclass.py +++ b/python/samba/gpclass.py @@ -304,8 +304,10 @@ class gp_ext(object): pass def parse(self, afile): + print "parse afile: %s" % afile local_path = self.lp.cache_path('gpo_cache') data_file = os.path.join(local_path, check_safe_path(afile).upper()) + print "parse data_file: %s" % data_file if os.path.exists(data_file): return self.read(open(data_file, 'r').read()) return None @@ -348,12 +350,14 @@ class gp_ext_setter(object): class gp_inf_ext(gp_ext): def read(self, policy): + #print "read policy: %s" % policy inf_conf = ConfigParser() inf_conf.optionxform = str try: inf_conf.readfp(StringIO(policy)) except: inf_conf.readfp(StringIO(policy.decode('utf-16'))) + #print "read policy: %s" % policy return inf_conf -- 2.17.1 From 55a0a7716f05d7a539308443fc8658f66b300072 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 22 May 2019 17:08:43 +0200 Subject: [PATCH 9/9] lib/param: cache the gpo.tdb context on loadparm_context This is better than leaking TDB_CONTEXT context handles each time we call lpcfg_default_kdc_policy(). In future we'll use the gpo.tdb in more places, that's why it's moved to it's own function. Signed-off-by: Stefan Metzmacher --- lib/param/loadparm.h | 2 ++ lib/param/util.c | 38 +++++++++++++++++++++++++++++++++----- lib/param/wscript_build | 2 +- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/param/loadparm.h b/lib/param/loadparm.h index 0b2e302d2a90..4c516f03b63b 100644 --- a/lib/param/loadparm.h +++ b/lib/param/loadparm.h @@ -60,6 +60,7 @@ struct enum_list { }; struct loadparm_service; +struct tdb_wrap; struct loadparm_context { const char *szConfigFile; struct loadparm_global *globals; @@ -76,6 +77,7 @@ struct loadparm_context { bool global; /* Is this the global context, which may set * global variables such as debug level etc? */ const struct loadparm_s3_helpers *s3_fns; + struct tdb_wrap *gpo_tdb; }; struct parm_struct { diff --git a/lib/param/util.c b/lib/param/util.c index cd8e74b9d8f3..e5442f5e7a43 100644 --- a/lib/param/util.c +++ b/lib/param/util.c @@ -28,8 +28,9 @@ #include "system/filesys.h" #include "system/dir.h" #include "param/param.h" +#include "param/loadparm.h" #include "libds/common/roles.h" -#include "tdb.h" +#include "lib/tdb_wrap/tdb_wrap.h" /** * @file @@ -298,6 +299,36 @@ static long tdb_fetch_lifetime(TALLOC_CTX *mem_ctx, struct tdb_context *tdb, con return result; } +static TDB_CONTEXT *lpcfg_get_gpo_tdb_context(struct loadparm_context *lp_ctx) +{ + char *gpo_tdb_path = NULL; + int saved_errno; + + if (lp_ctx == NULL) { + return NULL; + } + + if (lp_ctx->gpo_tdb != NULL) { + return lp_ctx->gpo_tdb->tdb; + } + + gpo_tdb_path = lpcfg_cache_path(lp_ctx, lp_ctx, "gpo.tdb"); + if (gpo_tdb_path == NULL) { + return NULL; + } + + lp_ctx->gpo_tdb = tdb_wrap_open(lp_ctx, gpo_tdb_path, + 0, TDB_DEFAULT, O_RDWR, 0600); + saved_errno = errno; + TALLOC_FREE(gpo_tdb_path); + errno = saved_errno; + if (lp_ctx->gpo_tdb == NULL) { + return NULL; + } + + return lp_ctx->gpo_tdb->tdb; +} + void lpcfg_default_kdc_policy(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx, time_t *svc_tkt_lifetime, @@ -306,11 +337,8 @@ void lpcfg_default_kdc_policy(TALLOC_CTX *mem_ctx, { long val; TDB_CONTEXT *ctx = NULL; - const char *kdc_tdb = NULL; - kdc_tdb = lpcfg_cache_path(mem_ctx, lp_ctx, "gpo.tdb"); - if (kdc_tdb) - ctx = tdb_open(kdc_tdb, 0, TDB_DEFAULT, O_RDWR, 0600); + ctx = lpcfg_get_gpo_tdb_context(lp_ctx); if (!ctx || ( val = tdb_fetch_lifetime(mem_ctx, ctx, "kdc:service_ticket_lifetime") ) == -1 ) val = lpcfg_parm_long(lp_ctx, NULL, "kdc", "service ticket lifetime", 10); diff --git a/lib/param/wscript_build b/lib/param/wscript_build index 20c8bcab22a5..b657506c2c95 100644 --- a/lib/param/wscript_build +++ b/lib/param/wscript_build @@ -39,7 +39,7 @@ bld.SAMBA_LIBRARY('samba-hostconfig', source='loadparm.c util.c param_table.c', pc_files='samba-hostconfig.pc', vnum='0.0.1', - deps='DYNCONFIG server-role tdb', + deps='DYNCONFIG server-role tdb-wrap', public_deps='samba-util param_local.h', public_headers='param.h', autoproto='param_proto.h' -- 2.17.1