3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Andrew Tridgell 2009
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from __future__ import print_function
21 """Tests the possibleInferiors generation in the schema_fsmo ldb module"""
27 # Find right directory when running from source tree
28 sys.path.insert(0, "bin/python")
30 from samba import getopt as options, Ldb
33 parser = optparse.OptionParser("possibleinferiors.py <URL> [<CLASS>]")
34 sambaopts = options.SambaOptions(parser)
35 parser.add_option_group(sambaopts)
36 credopts = options.CredentialsOptions(parser)
37 parser.add_option_group(credopts)
38 parser.add_option_group(options.VersionOptions(parser))
39 parser.add_option("--wspp", action="store_true")
41 opts, args = parser.parse_args()
55 """return a unique list"""
57 return [set.setdefault(e, e) for e in alist if e not in set]
60 lp_ctx = sambaopts.get_loadparm()
62 creds = credopts.get_credentials(lp_ctx)
65 # use 'paged_search' module when connecting remotely
66 if url.lower().startswith("ldap://"):
67 ldb_options = ["modules:paged_searches"]
69 db = Ldb(url, credentials=creds, lp=lp_ctx, options=ldb_options)
72 res = db.search(base="", expression="",
74 attrs=["schemaNamingContext"])
77 schema_base = rootDse["schemaNamingContext"][0]
80 def possible_inferiors_search(db, oc):
81 """return the possible inferiors via a search for the possibleInferiors attribute"""
82 res = db.search(base=schema_base,
83 expression=("ldapDisplayName=%s" % oc),
84 attrs=["possibleInferiors"])
87 if len(res) == 0 or res[0].get("possibleInferiors") is None:
89 for item in res[0]["possibleInferiors"]:
90 poss.append(str(item))
91 poss = uniq_list(poss)
96 # see [MS-ADTS] section 3.1.1.4.5.21
97 # and section 3.1.1.4.2 for this algorithm
100 # !objectClassCategory=2
101 # !objectClassCategory=3
103 def supclasses(classinfo, oc):
107 if classinfo[oc].get("SUPCLASSES") is not None:
108 return classinfo[oc]["SUPCLASSES"]
109 res = classinfo[oc]["subClassOf"]
112 list.extend(supclasses(classinfo, r))
113 classinfo[oc]["SUPCLASSES"] = list
117 def auxclasses(classinfo, oclist):
122 if classinfo[oc].get("AUXCLASSES") is not None:
123 list.extend(classinfo[oc]["AUXCLASSES"])
126 list2.extend(classinfo[oc]["systemAuxiliaryClass"])
127 list2.extend(auxclasses(classinfo, classinfo[oc]["systemAuxiliaryClass"]))
128 list2.extend(classinfo[oc]["auxiliaryClass"])
129 list2.extend(auxclasses(classinfo, classinfo[oc]["auxiliaryClass"]))
130 list2.extend(auxclasses(classinfo, supclasses(classinfo, oc)))
131 classinfo[oc]["AUXCLASSES"] = list2
136 def subclasses(classinfo, oclist):
139 list.extend(classinfo[oc]["SUBCLASSES"])
143 def posssuperiors(classinfo, oclist):
146 if classinfo[oc].get("POSSSUPERIORS") is not None:
147 list.extend(classinfo[oc]["POSSSUPERIORS"])
150 list2.extend(classinfo[oc]["systemPossSuperiors"])
151 list2.extend(classinfo[oc]["possSuperiors"])
152 list2.extend(posssuperiors(classinfo, supclasses(classinfo, oc)))
154 # the WSPP docs suggest we should do this:
155 list2.extend(posssuperiors(classinfo, auxclasses(classinfo, [oc])))
157 # but testing against w2k3 and w2k8 shows that we need to do this instead
158 list2.extend(subclasses(classinfo, list2))
159 classinfo[oc]["POSSSUPERIORS"] = list2
164 def pull_classinfo(db):
165 """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors"""
167 res = db.search(base=schema_base,
168 expression="objectclass=classSchema",
169 attrs=["ldapDisplayName", "systemOnly", "objectClassCategory",
170 "possSuperiors", "systemPossSuperiors",
171 "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"])
173 name = str(r["ldapDisplayName"][0])
175 if str(r["systemOnly"]) == "TRUE":
176 classinfo[name]["systemOnly"] = True
178 classinfo[name]["systemOnly"] = False
179 if r.get("objectClassCategory"):
180 classinfo[name]["objectClassCategory"] = int(r["objectClassCategory"][0])
182 classinfo[name]["objectClassCategory"] = 0
183 for a in ["possSuperiors", "systemPossSuperiors",
184 "auxiliaryClass", "systemAuxiliaryClass",
186 classinfo[name][a] = []
189 classinfo[name][a].append(str(i))
191 # build a list of subclasses for each class
192 def subclasses_recurse(subclasses, oc):
193 list = subclasses[oc]
195 list.extend(subclasses_recurse(subclasses, c))
202 for c in classinfo[oc]["subClassOf"]:
204 subclasses[c].append(oc)
206 classinfo[oc]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses, oc))
211 def is_in_list(list, c):
218 def possible_inferiors_constructed(db, classinfo, c):
221 superiors = posssuperiors(classinfo, [oc])
222 if (is_in_list(superiors, c) and
223 classinfo[oc]["systemOnly"] == False and
224 classinfo[oc]["objectClassCategory"] != 2 and
225 classinfo[oc]["objectClassCategory"] != 3):
227 list = uniq_list(list)
232 def test_class(db, classinfo, oc):
233 """test to see if one objectclass returns the correct possibleInferiors"""
234 print("test: objectClass.%s" % oc)
235 poss1 = possible_inferiors_search(db, oc)
236 poss2 = possible_inferiors_constructed(db, classinfo, oc)
238 print("failure: objectClass.%s [" % oc)
239 print("Returned incorrect list for objectclass %s" % oc)
240 print("search: %s" % poss1)
241 print("constructed: %s" % poss2)
242 for i in range(0, min(len(poss1), len(poss2))):
243 print("%30s %30s" % (poss1[i], poss2[i]))
247 print("success: objectClass.%s" % oc)
250 def get_object_classes(db):
251 """return a list of all object classes"""
253 for item in classinfo:
257 classinfo = pull_classinfo(db)
259 if objectclass is None:
260 for oc in get_object_classes(db):
261 test_class(db, classinfo, oc)
263 test_class(db, classinfo, objectclass)
265 print("Lists match OK")