#!/usr/bin/env python3 # 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") 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)) parser.add_option("--wspp", action="store_true") 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) ldb_options = [] # use 'paged_search' module when connecting remotely if url.lower().startswith("ldap://"): ldb_options = ["modules:paged_searches"] db = Ldb(url, credentials=creds, lp=lp_ctx, options=ldb_options) # 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 # and section 3.1.1.4.2 for this algorithm # !systemOnly=TRUE # !objectClassCategory=2 # !objectClassCategory=3 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: list.append(r) list.extend(supclasses(classinfo, r)) classinfo[oc]["SUPCLASSES"] = list return list 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 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))) 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 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: 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("test: objectClass.%s" % oc) poss1 = possible_inferiors_search(db, oc) poss2 = possible_inferiors_constructed(db, classinfo, oc) if poss1 != poss2: print("failure: objectClass.%s [" % oc) print("Returned incorrect list for objectclass %s" % oc) print("search: %s" % poss1) print("constructed: %s" % poss2) for i in range(0, min(len(poss1), len(poss2))): print("%30s %30s" % (poss1[i], poss2[i])) print("]") sys.exit(1) else: print("success: objectClass.%s" % oc) def get_object_classes(db): """return a list of all object classes""" list = [] for item in classinfo: list.append(item) return list classinfo = pull_classinfo(db) if objectclass is None: for oc in get_object_classes(db): test_class(db, classinfo, oc) else: test_class(db, classinfo, objectclass) print("Lists match OK")