3 # work out the minimal schema for a set of objectclasses
10 # Find right directory when running from source tree
11 sys
.path
.insert(0, "bin/python")
14 from samba
import getopt
as options
, Ldb
15 from ldb
import SCOPE_SUBTREE
, SCOPE_BASE
, LdbError
18 parser
= optparse
.OptionParser("minschema <URL> <classfile>")
19 sambaopts
= options
.SambaOptions(parser
)
20 parser
.add_option_group(sambaopts
)
21 credopts
= options
.CredentialsOptions(parser
)
22 parser
.add_option_group(credopts
)
23 parser
.add_option_group(options
.VersionOptions(parser
))
24 parser
.add_option("--verbose", help="Be verbose", action
="store_true")
25 parser
.add_option("--dump-classes", action
="store_true")
26 parser
.add_option("--dump-attributes", action
="store_true")
27 parser
.add_option("--dump-subschema", action
="store_true")
28 parser
.add_option("--dump-subschema-auto", action
="store_true")
30 opts
, args
= parser
.parse_args()
35 if opts
.dump_attributes
:
37 if opts
.dump_subschema
:
39 if opts
.dump_subschema_auto
:
41 opts
.dump_subschema
= True
43 opts
.dump_classes
= True
44 opts
.dump_attributes
= True
45 opts
.dump_subschema
= True
46 opts
.dump_subschema_auto
= True
52 (url
, classfile
) = args
54 lp_ctx
= sambaopts
.get_loadparm()
56 creds
= credopts
.get_credentials(lp_ctx
)
57 ldb
= Ldb(url
, credentials
=creds
)
62 objectclasses_expanded
= set()
64 # the attributes we need for objectclasses
65 class_attrs
= ["objectClass",
74 "showInAdvancedViewOnly",
77 "objectClassCategory",
81 "systemPossSuperiors",
84 "systemAuxiliaryClass",
85 "defaultSecurityDescriptor",
89 "defaultObjectCategory",
91 # this attributes are not used by w2k3
94 "msDs-Schema-Extensions",
98 attrib_attrs
= ["objectClass",
106 "showInAdvancedViewOnly",
112 "extendedCharsAllowed",
115 "attributeSecurityGUID",
118 "isMemberOfPartialAttributeSet",
121 # this attributes are not used by w2k3
124 "msDs-Schema-Extensions",
132 # objectClassCategory
137 def get_object_cn(ldb
, name
):
140 res
= ldb
.search("(ldapDisplayName=%s)" % name
, rootDse
["schemaNamingContext"], SCOPE_SUBTREE
, attrs
)
146 def __init__(self
, ldb
, name
):
147 """create an objectclass object"""
149 self
.cn
= get_object_cn(ldb
, name
)
153 def __init__(self
, ldb
, name
):
154 """create an attribute object"""
156 self
.cn
= get_object_cn(ldb
, name
)
161 syntaxmap
['2.5.5.1'] = '1.3.6.1.4.1.1466.115.121.1.12'
162 syntaxmap
['2.5.5.2'] = '1.3.6.1.4.1.1466.115.121.1.38'
163 syntaxmap
['2.5.5.3'] = '1.2.840.113556.1.4.1362'
164 syntaxmap
['2.5.5.4'] = '1.2.840.113556.1.4.905'
165 syntaxmap
['2.5.5.5'] = '1.3.6.1.4.1.1466.115.121.1.26'
166 syntaxmap
['2.5.5.6'] = '1.3.6.1.4.1.1466.115.121.1.36'
167 syntaxmap
['2.5.5.7'] = '1.2.840.113556.1.4.903'
168 syntaxmap
['2.5.5.8'] = '1.3.6.1.4.1.1466.115.121.1.7'
169 syntaxmap
['2.5.5.9'] = '1.3.6.1.4.1.1466.115.121.1.27'
170 syntaxmap
['2.5.5.10'] = '1.3.6.1.4.1.1466.115.121.1.40'
171 syntaxmap
['2.5.5.11'] = '1.3.6.1.4.1.1466.115.121.1.24'
172 syntaxmap
['2.5.5.12'] = '1.3.6.1.4.1.1466.115.121.1.15'
173 syntaxmap
['2.5.5.13'] = '1.3.6.1.4.1.1466.115.121.1.43'
174 syntaxmap
['2.5.5.14'] = '1.2.840.113556.1.4.904'
175 syntaxmap
['2.5.5.15'] = '1.2.840.113556.1.4.907'
176 syntaxmap
['2.5.5.16'] = '1.2.840.113556.1.4.906'
177 syntaxmap
['2.5.5.17'] = '1.3.6.1.4.1.1466.115.121.1.40'
180 def map_attribute_syntax(s
):
181 """map some attribute syntaxes from some apparently MS specific
182 syntaxes to the standard syntaxes"""
183 if syntaxmap
.has_key(s
):
189 """fix a string DN to use ${SCHEMADN}"""
190 return dn
.replace(rootDse
["schemaNamingContext"], "${SCHEMADN}")
193 def write_ldif_one(o
, attrs
):
194 """dump an object as ldif"""
195 print "dn: CN=%s,${SCHEMADN}\n" % o
["cn"]
199 # special case for oMObjectClass, which is a binary object
200 if a
== "oMObjectClass":
201 print "%s:: %s\n" % (a
, o
[a
])
204 if isinstance(v
, str):
207 print "%s: %s\n" % (a
, fix_dn(j
))
210 def write_ldif(o
, attrs
):
211 """dump an array of objects as ldif"""
213 write_ldif_one(i
, attrs
)
216 def create_testdn(exampleDN
):
217 """create a testDN based an an example DN
218 the idea is to ensure we obey any structural rules"""
219 a
= exampleDN
.split(",")
224 def find_objectclass_properties(ldb
, o
):
225 """the properties of an objectclass"""
227 expression
="(ldapDisplayName=%s)" % o
.name
,
228 base
=rootDse
["schemaNamingContext"], scope
=SCOPE_SUBTREE
, attrs
=class_attrs
)
229 assert(len(res
) == 1)
234 def find_attribute_properties(ldb
, o
):
235 """find the properties of an attribute"""
237 expression
="(ldapDisplayName=%s)" % o
.name
,
238 base
=rootDse
["schemaNamingContext"], scope
=SCOPE_SUBTREE
,
240 assert(len(res
) == 1)
243 # special case for oMObjectClass, which is a binary object
244 if a
== "oMObjectClass":
245 o
[a
] = ldb
.encode(msg
[a
])
250 def find_objectclass_auto(ldb
, o
):
251 """find the auto-created properties of an objectclass. Only works for
252 classes that can be created using just a DN and the objectclass"""
253 if not o
.has_key("exampleDN"):
255 testdn
= create_testdn(o
.exampleDN
)
257 print "testdn is '%s'\n" % testdn
259 ldif
= "dn: " + testdn
260 ldif
+= "\nobjectClass: " + o
.name
264 print "error adding %s: %s\n" % (o
.name
, e
)
268 res
= ldb
.search(base
=testdn
, scope
=ldb
.SCOPE_BASE
)
271 for a
in res
.msgs
[0]:
272 attributes
[a
].autocreate
= True
275 def expand_objectclass(ldb
, o
):
276 """look at auxiliary information from a class to intuit the existance of
277 more classes needed for a minimal schema"""
278 attrs
= ["auxiliaryClass", "systemAuxiliaryClass",
279 "possSuperiors", "systemPossSuperiors",
282 expression
="(&(objectClass=classSchema)(ldapDisplayName=%s))" % o
.name
,
283 base
=rootDse
["schemaNamingContext"], scope
=SCOPE_SUBTREE
,
285 print "Expanding class %s\n" % o
.name
286 assert(len(res
) == 1)
289 if not msg
.has_key(aname
):
292 if isinstance(list, str):
295 if not objectclasses
.has_key(name
):
296 print "Found new objectclass '%s'\n" % name
297 objectclasses
[name
] = Objectclass(ldb
, name
)
300 def add_objectclass_attributes(ldb
, objectclass
):
301 """add the must and may attributes from an objectclass to the full list
303 attrs
= ["mustContain", "systemMustContain",
304 "mayContain", "systemMayContain"]
306 if not objectclass
.has_key(aname
):
308 alist
= objectclass
[aname
]
309 if isinstance(alist
, str):
312 if not attributes
.has_key(a
):
313 attributes
[a
] = Attribute(ldb
, a
)
316 def walk_dn(ldb
, dn
):
317 """process an individual record, working out what attributes it has"""
318 # get a list of all possible attributes for this object
319 attrs
= ["allowedAttributes"]
321 res
= ldb
.search("objectClass=*", dn
, SCOPE_BASE
, attrs
)
323 print "Unable to fetch allowedAttributes for '%s' - %r\n" % (dn
, e
)
325 allattrs
= res
[0]["allowedAttributes"]
327 res
= ldb
.search("objectClass=*", dn
, SCOPE_BASE
, allattrs
)
329 print "Unable to fetch all attributes for '%s' - %s\n" % (dn
, e
)
333 if not attributes
.has_key(a
):
334 attributes
[a
] = Attribute(ldb
, a
)
336 def walk_naming_context(ldb
, namingContext
):
337 """walk a naming context, looking for all records"""
339 res
= ldb
.search("objectClass=*", namingContext
, SCOPE_DEFAULT
,
342 print "Unable to fetch objectClasses for '%s' - %s\n" % (namingContext
, e
)
345 msg
= res
.msgs
[r
]["objectClass"]
346 for objectClass
in msg
:
347 if not objectclasses
.has_key(objectClass
):
348 objectclasses
[objectClass
] = Objectclass(ldb
, objectClass
)
349 objectclasses
[objectClass
].exampleDN
= res
.msgs
[r
]["dn"]
350 walk_dn(ldb
, res
.msgs
[r
].dn
)
352 def trim_objectclass_attributes(ldb
, objectclass
):
353 """trim the may attributes for an objectClass"""
354 # trim possibleInferiors,
355 # include only the classes we extracted
356 if objectclass
.has_key("possibleInferiors"):
357 possinf
= objectclass
["possibleInferiors"]
359 if isinstance(possinf
, str):
362 if objectclasses
.has_key(x
):
365 objectclass
["possibleInferiors"] = newpossinf
367 # trim systemMayContain,
369 if objectclass
.has_key("systemMayContain"):
370 sysmay
= objectclass
["systemMayContain"]
372 if isinstance(sysmay
, str):
375 if not x
in newsysmay
:
377 objectclass
["systemMayContain"] = newsysmay
381 if not objectclass
.has_key("mayContain"):
382 may
= objectclass
["mayContain"]
384 if isinstance(may
, str):
389 objectclass
["mayContain"] = newmay
391 def build_objectclass(ldb
, name
):
392 """load the basic attributes of an objectClass"""
396 expression
="(&(objectClass=classSchema)(ldapDisplayName=%s))" % name
,
397 base
=rootDse
["schemaNamingContext"], scope
=SCOPE_SUBTREE
,
400 print "unknown class '%s'\n" % name
403 print "unknown class '%s'\n" % name
405 return Objectclass(ldb
, name
)
407 def attribute_list(objectclass
, attr1
, attr2
):
408 """form a coalesced attribute list"""
409 a1
= objectclass
[attr1
]
410 a2
= objectclass
[attr2
]
411 if isinstance(a1
, str):
413 if isinstance(a2
, str):
417 def aggregate_list(name
, list):
418 """write out a list in aggregate form"""
421 print "%s ( %s )" % (name
, "$ ".join(list))
423 def write_aggregate_objectclass(objectclass
):
424 """write the aggregate record for an objectclass"""
425 print "objectClasses: ( %s NAME '%s' " % (objectclass
.governsID
, objectclass
.name
)
426 if not objectclass
.has_key('subClassOf'):
427 print "SUP %s " % objectclass
['subClassOf']
428 if objectclass
.objectClassCategory
== 1:
430 elif objectclass
.objectClassCategory
== 2:
432 elif objectclass
.objectClassCategory
== 3:
435 list = attribute_list(objectclass
, "systemMustContain", "mustContain")
436 aggregate_list("MUST", list)
438 list = attribute_list(objectclass
, "systemMayContain", "mayContain")
439 aggregate_list("MAY", list)
444 def write_aggregate_ditcontentrule(objectclass
):
445 """write the aggregate record for an ditcontentrule"""
446 list = attribute_list(objectclass
, "auxiliaryClass", "systemAuxiliaryClass")
450 print "dITContentRules: ( %s NAME '%s' " % (objectclass
.governsID
, objectclass
.name
)
452 aggregate_list("AUX", list)
458 list2
= attribute_list(objectclasses
[c
],
459 "mayContain", "systemMayContain")
460 may_list
= may_list
+ list2
461 list2
= attribute_list(objectclasses
[c
],
462 "mustContain", "systemMustContain")
463 must_list
= must_list
+ list2
465 aggregate_list("MUST", must_list
)
466 aggregate_list("MAY", may_list
)
470 def write_aggregate_attribute(attrib
):
471 """write the aggregate record for an attribute"""
472 print "attributeTypes: ( %s NAME '%s' SYNTAX '%s' " % (
473 attrib
.attributeID
, attrib
.name
,
474 map_attribute_syntax(attrib
.attributeSyntax
))
475 if attrib
['isSingleValued'] == "TRUE":
476 print "SINGLE-VALUE "
477 if attrib
['systemOnly'] == "TRUE":
478 print "NO-USER-MODIFICATION "
483 def write_aggregate():
484 """write the aggregate record"""
485 print "dn: CN=Aggregate,${SCHEMADN}\n"
486 print """objectClass: top
487 objectClass: subSchema
488 objectCategory: CN=SubSchema,${SCHEMADN}
490 if not opts
.dump_subschema_auto
:
493 for objectclass
in objectclasses
:
494 write_aggregate_objectclass(objectclass
)
495 for attr
in attributes
:
496 write_aggregate_attribute(attr
)
497 for objectclass
in objectclasses
:
498 write_aggregate_ditcontentrule(objectclass
)
501 """load a list from a file"""
502 return open(file, 'r').readlines()
505 res
= ldb
.search(base
="", expression
="", scope
=SCOPE_BASE
, attrs
=["schemaNamingContext"])
508 # load the list of classes we are interested in
509 classes
= load_list(classfile
)
510 for classname
in classes
:
511 objectclass
= build_objectclass(ldb
, classname
)
512 if objectclass
is not None:
513 objectclasses
[classname
] = objectclass
517 # expand the objectclass list as needed
521 # so EJS do not have while nor the break statement
522 # cannot find any other way than doing more loops
523 # than necessary to recursively expand all classes
525 for inf
in range(500):
526 for n
in objectclasses
:
527 if not n
in objectclasses_expanded
:
528 expand_objectclass(ldb
, objectclasses
[i
])
529 objectclasses_expanded
.add(n
)
532 # find objectclass properties
534 for objectclass
in objectclasses
:
535 find_objectclass_properties(ldb
, objectclass
)
539 # form the full list of attributes
541 for objectclass
in objectclasses
:
542 add_objectclass_attributes(ldb
, objectclass
)
544 # and attribute properties
545 for attr
in attributes
:
546 find_attribute_properties(ldb
, attr
)
549 # trim the 'may' attribute lists to those really needed
551 for objectclass
in objectclasses
:
552 trim_objectclass_attributes(ldb
, objectclass
)
555 # dump an ldif form of the attributes and objectclasses
557 if opts
.dump_attributes
:
558 write_ldif(attributes
, attrib_attrs
)
559 if opts
.dump_classes
:
560 write_ldif(objectclasses
, class_attrs
)
561 if opts
.dump_subschema
:
568 # dump list of objectclasses
570 print "objectClasses:\n"
571 for objectclass
in objectclasses
:
572 print "\t%s\n" % objectclass
574 print "attributes:\n"
575 for attr
in attributes
:
576 print "\t%s\n" % attr
578 print "autocreated attributes:\n"
579 for attr
in attributes
: