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 """Tests the possibleInferiors generation in the schema_fsmo ldb module"""
26 # Find right directory when running from source tree
27 sys
.path
.insert(0, "bin/python")
29 from samba
import getopt
as options
, Ldb
32 parser
= optparse
.OptionParser("possibleinferiors.py <URL> [<CLASS>]")
33 sambaopts
= options
.SambaOptions(parser
)
34 parser
.add_option_group(sambaopts
)
35 credopts
= options
.CredentialsOptions(parser
)
36 parser
.add_option_group(credopts
)
37 parser
.add_option_group(options
.VersionOptions(parser
))
38 parser
.add_option("--wspp", action
="store_true")
40 opts
, args
= parser
.parse_args()
53 """return a unique list"""
55 return [set.setdefault(e
,e
) for e
in alist
if e
not in set]
58 lp_ctx
= sambaopts
.get_loadparm()
60 creds
= credopts
.get_credentials(lp_ctx
)
63 # use 'paged_search' module when connecting remotely
64 if url
.lower().startswith("ldap://"):
65 ldb_options
= ["modules:paged_searches"]
67 db
= Ldb(url
, credentials
=creds
, lp
=lp_ctx
, options
=ldb_options
)
70 res
= db
.search(base
="", expression
="",
72 attrs
=["schemaNamingContext"])
75 schema_base
= rootDse
["schemaNamingContext"][0]
77 def possible_inferiors_search(db
, oc
):
78 """return the possible inferiors via a search for the possibleInferiors attribute"""
79 res
= db
.search(base
=schema_base
,
80 expression
=("ldapDisplayName=%s" % oc
),
81 attrs
=["possibleInferiors"])
84 if len(res
) == 0 or res
[0].get("possibleInferiors") is None:
86 for item
in res
[0]["possibleInferiors"]:
87 poss
.append(str(item
))
88 poss
= uniq_list(poss
)
94 # see [MS-ADTS] section 3.1.1.4.5.21
95 # and section 3.1.1.4.2 for this algorithm
98 # !objectClassCategory=2
99 # !objectClassCategory=3
101 def supclasses(classinfo
, oc
):
105 if classinfo
[oc
].get("SUPCLASSES") is not None:
106 return classinfo
[oc
]["SUPCLASSES"]
107 res
= classinfo
[oc
]["subClassOf"]
110 list.extend(supclasses(classinfo
,r
))
111 classinfo
[oc
]["SUPCLASSES"] = list
114 def auxclasses(classinfo
, oclist
):
119 if classinfo
[oc
].get("AUXCLASSES") is not None:
120 list.extend(classinfo
[oc
]["AUXCLASSES"])
123 list2
.extend(classinfo
[oc
]["systemAuxiliaryClass"])
124 list2
.extend(auxclasses(classinfo
, classinfo
[oc
]["systemAuxiliaryClass"]))
125 list2
.extend(classinfo
[oc
]["auxiliaryClass"])
126 list2
.extend(auxclasses(classinfo
, classinfo
[oc
]["auxiliaryClass"]))
127 list2
.extend(auxclasses(classinfo
, supclasses(classinfo
, oc
)))
128 classinfo
[oc
]["AUXCLASSES"] = list2
132 def subclasses(classinfo
, oclist
):
135 list.extend(classinfo
[oc
]["SUBCLASSES"])
138 def posssuperiors(classinfo
, oclist
):
141 if classinfo
[oc
].get("POSSSUPERIORS") is not None:
142 list.extend(classinfo
[oc
]["POSSSUPERIORS"])
145 list2
.extend(classinfo
[oc
]["systemPossSuperiors"])
146 list2
.extend(classinfo
[oc
]["possSuperiors"])
147 list2
.extend(posssuperiors(classinfo
, supclasses(classinfo
, oc
)))
149 # the WSPP docs suggest we should do this:
150 list2
.extend(posssuperiors(classinfo
, auxclasses(classinfo
, [oc
])))
152 # but testing against w2k3 and w2k8 shows that we need to do this instead
153 list2
.extend(subclasses(classinfo
, list2
))
154 classinfo
[oc
]["POSSSUPERIORS"] = list2
158 def pull_classinfo(db
):
159 """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors"""
161 res
= db
.search(base
=schema_base
,
162 expression
="objectclass=classSchema",
163 attrs
=["ldapDisplayName", "systemOnly", "objectClassCategory",
164 "possSuperiors", "systemPossSuperiors",
165 "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"])
167 name
= str(r
["ldapDisplayName"][0])
169 if str(r
["systemOnly"]) == "TRUE":
170 classinfo
[name
]["systemOnly"] = True
172 classinfo
[name
]["systemOnly"] = False
173 if r
.get("objectClassCategory"):
174 classinfo
[name
]["objectClassCategory"] = int(r
["objectClassCategory"][0])
176 classinfo
[name
]["objectClassCategory"] = 0
177 for a
in [ "possSuperiors", "systemPossSuperiors",
178 "auxiliaryClass", "systemAuxiliaryClass",
180 classinfo
[name
][a
] = []
183 classinfo
[name
][a
].append(str(i
))
185 # build a list of subclasses for each class
186 def subclasses_recurse(subclasses
, oc
):
187 list = subclasses
[oc
]
189 list.extend(subclasses_recurse(subclasses
, c
))
196 for c
in classinfo
[oc
]["subClassOf"]:
198 subclasses
[c
].append(oc
)
200 classinfo
[oc
]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses
, oc
))
204 def is_in_list(list, c
):
210 def possible_inferiors_constructed(db
, classinfo
, c
):
213 superiors
= posssuperiors(classinfo
, [oc
])
214 if (is_in_list(superiors
, c
) and
215 classinfo
[oc
]["systemOnly"] == False and
216 classinfo
[oc
]["objectClassCategory"] != 2 and
217 classinfo
[oc
]["objectClassCategory"] != 3):
219 list = uniq_list(list)
223 def test_class(db
, classinfo
, oc
):
224 """test to see if one objectclass returns the correct possibleInferiors"""
225 print "test: objectClass.%s" % oc
226 poss1
= possible_inferiors_search(db
, oc
)
227 poss2
= possible_inferiors_constructed(db
, classinfo
, oc
)
229 print "failure: objectClass.%s [" % oc
230 print "Returned incorrect list for objectclass %s" % oc
231 print "search: %s" % poss1
232 print "constructed: %s" % poss2
233 for i
in range(0,min(len(poss1
),len(poss2
))):
234 print "%30s %30s" % (poss1
[i
], poss2
[i
])
238 print "success: objectClass.%s" % oc
240 def get_object_classes(db
):
241 """return a list of all object classes"""
243 for item
in classinfo
:
247 classinfo
= pull_classinfo(db
)
249 if objectclass
is None:
250 for oc
in get_object_classes(db
):
251 test_class(db
,classinfo
,oc
)
253 test_class(db
,classinfo
,objectclass
)
255 print "Lists match OK"