From 2a8f367b0ff567223144bd41e97684301d540f57 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Tue, 24 Mar 2009 16:28:39 +1100 Subject: the start of a possibleInferiors test suite we haven't implemented possibleInferiors yet. This test is meant to help us understand how it works. It tries to construct possibleInferiors via searches on other attributes, and compares it to the servers constructed possibleInferiors attribute for each class in the servers schema. see [MS-ADTS] section 3.1.1.4.5.21 --- .../samdb/ldb_modules/tests/possibleinferiors.py | 155 +++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100755 source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py (limited to 'source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py') diff --git a/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py new file mode 100755 index 0000000000..0e74456dac --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py @@ -0,0 +1,155 @@ +#!/usr/bin/python + +# Unix SMB/CIFS implementation. +# Copyright (C) Andrew Tridgell 2009 +# +# 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 . +# + +"""Tests the possibleInferiors generation in the schema_fsmo ldb module""" + +import optparse +import sys + + +# Find right directory when running from source tree +sys.path.insert(0, "bin/python") + +import samba +from samba import getopt as options, Ldb +import ldb + +parser = optparse.OptionParser("possibleinferiors.py []") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +parser.add_option_group(options.VersionOptions(parser)) + +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +url = args[0] +if (len(args) > 1): + objectclass = args[1] +else: + objectclass = None + +def uniq_list(alist): + """return a unique list""" + set = {} + return [set.setdefault(e,e) for e in alist if e not in set] + + +lp_ctx = sambaopts.get_loadparm() + +creds = credopts.get_credentials(lp_ctx) +db = Ldb(url, credentials=creds, lp=lp_ctx, options=["modules:paged_searches"]) + +# get the rootDSE +res = db.search(base="", expression="", + scope=ldb.SCOPE_BASE, + attrs=["schemaNamingContext"]) +rootDse = res[0] + +schema_base = rootDse["schemaNamingContext"][0] + +def possible_inferiors_search(db, oc): + """return the possible inferiors via a search for the possibleInferiors attribute""" + res = db.search(base=schema_base, + expression=("ldapdisplayname=%s" % oc), + attrs=["possibleInferiors"]) + + poss=[] + if len(res) == 0 or res[0].get("possibleInferiors") is None: + return poss + for item in res[0]["possibleInferiors"]: + poss.append(str(item)) + poss = uniq_list(poss) + poss.sort() + return poss; + + + +# see [MS-ADTS] section 3.1.1.4.5.21 +# for this algorithm + +# !systemOnly=TRUE +# !objectClassCategory=2 +# !objectClassCategory=3 + +def POSSINFERIORS(db, oc): + """returns a list of possible inferiors to a class. Returned list has the ldapdisplayname, systemOnly and objectClassCategory for each element""" + expanded = [oc] + res = db.search(base=schema_base, + expression=("subclassof=%s" % str(oc["ldapdisplayname"][0])), + attrs=["ldapdisplayname", "systemOnly", "objectClassCategory"]) + for r in res: + expanded.extend(POSSINFERIORS(db,r)) + return expanded + +def possible_inferiors_constructed(db, oc): + """return the possbible inferiors via a recursive search and match""" + res = db.search(base=schema_base, + expression=("(&(objectclass=classSchema)(|(posssuperiors=%s)(systemposssuperiors=%s)))" % (oc,oc)), + attrs=["ldapdisplayname", "systemOnly", "objectClassCategory"]) + + poss = [] + for r in res: + poss.extend(POSSINFERIORS(db,r)) + + poss2 = [] + for p in poss: + if (not (p["systemOnly"][0] == "TRUE" or + int(p["objectClassCategory"][0]) == 2 or + int(p["objectClassCategory"][0]) == 3)): + poss2.append(p["ldapdisplayname"][0]) + + poss2 = uniq_list(poss2) + poss2.sort() + return poss2 + +def test_class(db, oc): + """test to see if one objectclass returns the correct possibleInferiors""" + poss1 = possible_inferiors_search(db, oc) + poss2 = possible_inferiors_constructed(db, oc) + if poss1 != poss2: + print "Returned incorrect list for objectclass %s" % oc + print poss1 + print poss2 + for i in range(0,min(len(poss1),len(poss2))): + print "%30s %30s" % (poss1[i], poss2[i]) + exit(1) + +def get_object_classes(db): + """return a list of all object classes""" + res = db.search(base=schema_base, + expression="objectClass=classSchema", + attrs=["ldapdisplayname"]) + list=[] + for item in res: + list.append(item["ldapdisplayname"][0]) + return list + +if objectclass is None: + for oc in get_object_classes(db): + print "testing objectClass %s" % oc + test_class(db,oc) +else: + test_class(db,objectclass) + +print "Lists match OK" -- cgit From 3f37342bc0e7c26b6a61490706fe8cec3e6d491e Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Thu, 26 Mar 2009 22:10:02 +1100 Subject: fixed possibleinferiors.py so it matches windows behaviour This test code builds the possibleInferiors for every class in the schema on a target machine, and compares it to the servers possibleInferiors attribute. The MS-ADTS spec describes how to calculate possibleInferiors for a object, but it seems to have some bugs. The spec says that we need to use AUXCLASSES, and it does not mention the use of the SUBCLASS tree. In trying to match windows behaviour, I found that I needed to ignore the AUXCLASSES and build a SUBCLASSES tree. --- .../samdb/ldb_modules/tests/possibleinferiors.py | 166 ++++++++++++++++----- 1 file changed, 127 insertions(+), 39 deletions(-) (limited to 'source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py') diff --git a/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py index 0e74456dac..ec426c099b 100755 --- a/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py +++ b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py @@ -71,7 +71,7 @@ schema_base = rootDse["schemaNamingContext"][0] def possible_inferiors_search(db, oc): """return the possible inferiors via a search for the possibleInferiors attribute""" res = db.search(base=schema_base, - expression=("ldapdisplayname=%s" % oc), + expression=("ldapDisplayName=%s" % oc), attrs=["possibleInferiors"]) poss=[] @@ -86,47 +86,137 @@ def possible_inferiors_search(db, oc): # see [MS-ADTS] section 3.1.1.4.5.21 -# for this algorithm +# and section 3.1.1.4.2 for this algorithm # !systemOnly=TRUE # !objectClassCategory=2 # !objectClassCategory=3 -def POSSINFERIORS(db, oc): - """returns a list of possible inferiors to a class. Returned list has the ldapdisplayname, systemOnly and objectClassCategory for each element""" - expanded = [oc] - res = db.search(base=schema_base, - expression=("subclassof=%s" % str(oc["ldapdisplayname"][0])), - attrs=["ldapdisplayname", "systemOnly", "objectClassCategory"]) +def SUPCLASSES(classinfo, oc): + list = [] + if oc == "top": + return list + if classinfo[oc].get("SUPCLASSES") is not None: + return classinfo[oc]["SUPCLASSES"] + res = classinfo[oc]["subClassOf"]; for r in res: - expanded.extend(POSSINFERIORS(db,r)) - return expanded + list.append(r) + list.extend(SUPCLASSES(classinfo,r)) + classinfo[oc]["SUPCLASSES"] = list + return list -def possible_inferiors_constructed(db, oc): - """return the possbible inferiors via a recursive search and match""" - res = db.search(base=schema_base, - expression=("(&(objectclass=classSchema)(|(posssuperiors=%s)(systemposssuperiors=%s)))" % (oc,oc)), - attrs=["ldapdisplayname", "systemOnly", "objectClassCategory"]) +def AUXCLASSES(classinfo, oclist): + list = [] + if oclist == []: + return list + for oc in oclist: + if classinfo[oc].get("AUXCLASSES") is not None: + list.extend(classinfo[oc]["AUXCLASSES"]) + else: + list2 = [] + list2.extend(classinfo[oc]["systemAuxiliaryClass"]) + list2.extend(AUXCLASSES(classinfo, classinfo[oc]["systemAuxiliaryClass"])) + list2.extend(classinfo[oc]["auxiliaryClass"]) + list2.extend(AUXCLASSES(classinfo, classinfo[oc]["auxiliaryClass"])) + list2.extend(AUXCLASSES(classinfo, SUPCLASSES(classinfo, oc))) + classinfo[oc]["AUXCLASSES"] = list2 + list.extend(list2) + return list - poss = [] +def SUBCLASSES(classinfo, oclist): + list = [] + for oc in oclist: + list.extend(classinfo[oc]["SUBCLASSES"]) + return list + +def POSSSUPERIORS(classinfo, oclist): + list = [] + for oc in oclist: + if classinfo[oc].get("POSSSUPERIORS") is not None: + list.extend(classinfo[oc]["POSSSUPERIORS"]) + else: + list2 = [] + list2.extend(classinfo[oc]["systemPossSuperiors"]) + list2.extend(classinfo[oc]["possSuperiors"]) + list2.extend(POSSSUPERIORS(classinfo, SUPCLASSES(classinfo, oc))) + # the WSPP docs suggest we should do this: + # list2.extend(POSSSUPERIORS(classinfo, AUXCLASSES(classinfo, [oc]))) + # but testing against w2k3 and w2k8 shows that we need to do this instead + list2.extend(SUBCLASSES(classinfo, list2)) + classinfo[oc]["POSSSUPERIORS"] = list2 + list.extend(list2) + return list + +def pull_classinfo(db): + """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors""" + classinfo = {} + res = db.search(base=schema_base, + expression="objectclass=classSchema", + attrs=["ldapDisplayName", "systemOnly", "objectClassCategory", + "possSuperiors", "systemPossSuperiors", + "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"]) for r in res: - poss.extend(POSSINFERIORS(db,r)) - - poss2 = [] - for p in poss: - if (not (p["systemOnly"][0] == "TRUE" or - int(p["objectClassCategory"][0]) == 2 or - int(p["objectClassCategory"][0]) == 3)): - poss2.append(p["ldapdisplayname"][0]) - - poss2 = uniq_list(poss2) - poss2.sort() - return poss2 - -def test_class(db, oc): + name = str(r["ldapDisplayName"][0]) + classinfo[name] = {} + if str(r["systemOnly"]) == "TRUE": + classinfo[name]["systemOnly"] = True + else: + classinfo[name]["systemOnly"] = False + if r.get("objectClassCategory"): + classinfo[name]["objectClassCategory"] = int(r["objectClassCategory"][0]) + else: + classinfo[name]["objectClassCategory"] = 0 + for a in [ "possSuperiors", "systemPossSuperiors", + "auxiliaryClass", "systemAuxiliaryClass", + "subClassOf" ]: + classinfo[name][a] = [] + if r.get(a): + for i in r[a]: + classinfo[name][a].append(str(i)) + + # build a list of subclasses for each class + def subclasses_recurse(subclasses, oc): + list = subclasses[oc] + for c in list: + list.extend(subclasses_recurse(subclasses, c)) + return list + + subclasses = {} + for oc in classinfo: + subclasses[oc] = [] + for oc in classinfo: + for c in classinfo[oc]["subClassOf"]: + if not c == oc: + subclasses[c].append(oc) + for oc in classinfo: + classinfo[oc]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses, oc)) + + return classinfo + +def is_in_list(list, c): + for a in list: + if c == a: + return True + return False + +def possible_inferiors_constructed(db, classinfo, c): + list = [] + for oc in classinfo: + superiors = POSSSUPERIORS(classinfo, [oc]) + if (is_in_list(superiors, c) and + classinfo[oc]["systemOnly"] == False and + classinfo[oc]["objectClassCategory"] != 2 and + classinfo[oc]["objectClassCategory"] != 3): + list.append(oc) + list = uniq_list(list) + list.sort() + return list + +def test_class(db, classinfo, oc): """test to see if one objectclass returns the correct possibleInferiors""" + print "testing objectClass %s" % oc poss1 = possible_inferiors_search(db, oc) - poss2 = possible_inferiors_constructed(db, oc) + poss2 = possible_inferiors_constructed(db, classinfo, oc) if poss1 != poss2: print "Returned incorrect list for objectclass %s" % oc print poss1 @@ -137,19 +227,17 @@ def test_class(db, oc): def get_object_classes(db): """return a list of all object classes""" - res = db.search(base=schema_base, - expression="objectClass=classSchema", - attrs=["ldapdisplayname"]) list=[] - for item in res: - list.append(item["ldapdisplayname"][0]) + for item in classinfo: + list.append(item) return list +classinfo = pull_classinfo(db) + if objectclass is None: for oc in get_object_classes(db): - print "testing objectClass %s" % oc - test_class(db,oc) + test_class(db,classinfo,oc) else: - test_class(db,objectclass) + test_class(db,classinfo,objectclass) print "Lists match OK" -- cgit From d88ced1dccd8f6eb6a1931d3ccae2c7cb0667020 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 27 Mar 2009 09:31:51 +1100 Subject: added a --wspp option Adding --wspp to possibleInferiors.py forces it to use the WSPP documented algorithm, which doesn't match windows behaviour --- source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py') diff --git a/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py index ec426c099b..aab93e59a3 100755 --- a/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py +++ b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py @@ -36,6 +36,7 @@ parser.add_option_group(sambaopts) credopts = options.CredentialsOptions(parser) parser.add_option_group(credopts) parser.add_option_group(options.VersionOptions(parser)) +parser.add_option("--wspp", action="store_true") opts, args = parser.parse_args() @@ -139,10 +140,12 @@ def POSSSUPERIORS(classinfo, oclist): list2.extend(classinfo[oc]["systemPossSuperiors"]) list2.extend(classinfo[oc]["possSuperiors"]) list2.extend(POSSSUPERIORS(classinfo, SUPCLASSES(classinfo, oc))) - # the WSPP docs suggest we should do this: - # list2.extend(POSSSUPERIORS(classinfo, AUXCLASSES(classinfo, [oc]))) - # but testing against w2k3 and w2k8 shows that we need to do this instead - list2.extend(SUBCLASSES(classinfo, list2)) + if opts.wspp: + # the WSPP docs suggest we should do this: + list2.extend(POSSSUPERIORS(classinfo, AUXCLASSES(classinfo, [oc]))) + else: + # but testing against w2k3 and w2k8 shows that we need to do this instead + list2.extend(SUBCLASSES(classinfo, list2)) classinfo[oc]["POSSSUPERIORS"] = list2 list.extend(list2) return list -- cgit