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()
54 """return a unique list"""
56 return [set.setdefault(e,e) for e in alist if e not in set]
59 lp_ctx = sambaopts.get_loadparm()
61 creds = credopts.get_credentials(lp_ctx)
64 # use 'paged_search' module when connecting remotely
65 if url.lower().startswith("ldap://"):
66 ldb_options = ["modules:paged_searches"]
68 db = Ldb(url, credentials=creds, lp=lp_ctx, options=ldb_options)
71 res = db.search(base="", expression="",
73 attrs=["schemaNamingContext"])
76 schema_base = rootDse["schemaNamingContext"][0]
78 def possible_inferiors_search(db, oc):
79 """return the possible inferiors via a search for the possibleInferiors attribute"""
80 res = db.search(base=schema_base,
81 expression=("ldapDisplayName=%s" % oc),
82 attrs=["possibleInferiors"])
85 if len(res) == 0 or res[0].get("possibleInferiors") is None:
87 for item in res[0]["possibleInferiors"]:
88 poss.append(str(item))
89 poss = uniq_list(poss)
95 # see [MS-ADTS] section 3.1.1.4.5.21
96 # and section 3.1.1.4.2 for this algorithm
99 # !objectClassCategory=2
100 # !objectClassCategory=3
102 def supclasses(classinfo, oc):
106 if classinfo[oc].get("SUPCLASSES") is not None:
107 return classinfo[oc]["SUPCLASSES"]
108 res = classinfo[oc]["subClassOf"]
111 list.extend(supclasses(classinfo,r))
112 classinfo[oc]["SUPCLASSES"] = list
115 def auxclasses(classinfo, oclist):
120 if classinfo[oc].get("AUXCLASSES") is not None:
121 list.extend(classinfo[oc]["AUXCLASSES"])
124 list2.extend(classinfo[oc]["systemAuxiliaryClass"])
125 list2.extend(auxclasses(classinfo, classinfo[oc]["systemAuxiliaryClass"]))
126 list2.extend(classinfo[oc]["auxiliaryClass"])
127 list2.extend(auxclasses(classinfo, classinfo[oc]["auxiliaryClass"]))
128 list2.extend(auxclasses(classinfo, supclasses(classinfo, oc)))
129 classinfo[oc]["AUXCLASSES"] = list2
133 def subclasses(classinfo, oclist):
136 list.extend(classinfo[oc]["SUBCLASSES"])
139 def posssuperiors(classinfo, oclist):
142 if classinfo[oc].get("POSSSUPERIORS") is not None:
143 list.extend(classinfo[oc]["POSSSUPERIORS"])
146 list2.extend(classinfo[oc]["systemPossSuperiors"])
147 list2.extend(classinfo[oc]["possSuperiors"])
148 list2.extend(posssuperiors(classinfo, supclasses(classinfo, oc)))
150 # the WSPP docs suggest we should do this:
151 list2.extend(posssuperiors(classinfo, auxclasses(classinfo, [oc])))
153 # but testing against w2k3 and w2k8 shows that we need to do this instead
154 list2.extend(subclasses(classinfo, list2))
155 classinfo[oc]["POSSSUPERIORS"] = list2
159 def pull_classinfo(db):
160 """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors"""
162 res = db.search(base=schema_base,
163 expression="objectclass=classSchema",
164 attrs=["ldapDisplayName", "systemOnly", "objectClassCategory",
165 "possSuperiors", "systemPossSuperiors",
166 "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"])
168 name = str(r["ldapDisplayName"][0])
170 if str(r["systemOnly"]) == "TRUE":
171 classinfo[name]["systemOnly"] = True
173 classinfo[name]["systemOnly"] = False
174 if r.get("objectClassCategory"):
175 classinfo[name]["objectClassCategory"] = int(r["objectClassCategory"][0])
177 classinfo[name]["objectClassCategory"] = 0
178 for a in ["possSuperiors", "systemPossSuperiors",
179 "auxiliaryClass", "systemAuxiliaryClass",
181 classinfo[name][a] = []
184 classinfo[name][a].append(str(i))
186 # build a list of subclasses for each class
187 def subclasses_recurse(subclasses, oc):
188 list = subclasses[oc]
190 list.extend(subclasses_recurse(subclasses, c))
197 for c in classinfo[oc]["subClassOf"]:
199 subclasses[c].append(oc)
201 classinfo[oc]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses, oc))
205 def is_in_list(list, c):
211 def possible_inferiors_constructed(db, classinfo, c):
214 superiors = posssuperiors(classinfo, [oc])
215 if (is_in_list(superiors, c) and
216 classinfo[oc]["systemOnly"] == False and
217 classinfo[oc]["objectClassCategory"] != 2 and
218 classinfo[oc]["objectClassCategory"] != 3):
220 list = uniq_list(list)
224 def test_class(db, classinfo, oc):
225 """test to see if one objectclass returns the correct possibleInferiors"""
226 print("test: objectClass.%s" % oc)
227 poss1 = possible_inferiors_search(db, oc)
228 poss2 = possible_inferiors_constructed(db, classinfo, oc)
230 print("failure: objectClass.%s [" % oc)
231 print("Returned incorrect list for objectclass %s" % oc)
232 print("search: %s" % poss1)
233 print("constructed: %s" % poss2)
234 for i in range(0,min(len(poss1),len(poss2))):
235 print("%30s %30s" % (poss1[i], poss2[i]))
239 print("success: objectClass.%s" % oc)
241 def get_object_classes(db):
242 """return a list of all object classes"""
244 for item in classinfo:
248 classinfo = pull_classinfo(db)
250 if objectclass is None:
251 for oc in get_object_classes(db):
252 test_class(db,classinfo,oc)
254 test_class(db,classinfo,objectclass)
256 print("Lists match OK")