3 # work out the minimal schema for a set of objectclasses
11 # Find right directory when running from source tree
12 sys.path.insert(0, "bin/python")
15 from samba import getopt as options, Ldb
16 from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError
19 parser = optparse.OptionParser("minschema <URL> <classfile>")
20 sambaopts = options.SambaOptions(parser)
21 parser.add_option_group(sambaopts)
22 credopts = options.CredentialsOptions(parser)
23 parser.add_option_group(credopts)
24 parser.add_option_group(options.VersionOptions(parser))
25 parser.add_option("--verbose", help="Be verbose", action="store_true")
26 parser.add_option("--dump-classes", action="store_true")
27 parser.add_option("--dump-attributes", action="store_true")
28 parser.add_option("--dump-subschema", action="store_true")
29 parser.add_option("--dump-subschema-auto", action="store_true")
31 opts, args = parser.parse_args()
36 if opts.dump_attributes:
38 if opts.dump_subschema:
40 if opts.dump_subschema_auto:
42 opts.dump_subschema = True
44 opts.dump_classes = True
45 opts.dump_attributes = True
46 opts.dump_subschema = True
47 opts.dump_subschema_auto = True
53 (url, classfile) = args
55 lp_ctx = sambaopts.get_loadparm()
57 creds = credopts.get_credentials(lp_ctx)
58 ldb = Ldb(url, credentials=creds, lp=lp_ctx)
63 objectclasses_expanded = set()
65 # the attributes we need for objectclasses
66 class_attrs = ["objectClass",
75 "showInAdvancedViewOnly",
78 "objectClassCategory",
82 "systemPossSuperiors",
85 "systemAuxiliaryClass",
86 "defaultSecurityDescriptor",
90 "defaultObjectCategory",
92 # this attributes are not used by w2k3
95 "msDs-Schema-Extensions",
99 attrib_attrs = ["objectClass",
107 "showInAdvancedViewOnly",
113 "extendedCharsAllowed",
116 "attributeSecurityGUID",
119 "isMemberOfPartialAttributeSet",
122 # this attributes are not used by w2k3
125 "msDs-Schema-Extensions",
133 # objectClassCategory
138 def get_object_cn(ldb, name):
140 res = ldb.search(expression="(ldapDisplayName=%s)" % name, base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE, attrs=attrs)
145 class Objectclass(dict):
147 def __init__(self, ldb, name):
148 """create an objectclass object"""
150 self["cn"] = get_object_cn(ldb, name)
153 class Attribute(dict):
155 def __init__(self, ldb, name):
156 """create an attribute object"""
158 self["cn"] = get_object_cn(ldb, name)
163 syntaxmap['2.5.5.1'] = '1.3.6.1.4.1.1466.115.121.1.12'
164 syntaxmap['2.5.5.2'] = '1.3.6.1.4.1.1466.115.121.1.38'
165 syntaxmap['2.5.5.3'] = '1.2.840.113556.1.4.1362'
166 syntaxmap['2.5.5.4'] = '1.2.840.113556.1.4.905'
167 syntaxmap['2.5.5.5'] = '1.3.6.1.4.1.1466.115.121.1.26'
168 syntaxmap['2.5.5.6'] = '1.3.6.1.4.1.1466.115.121.1.36'
169 syntaxmap['2.5.5.7'] = '1.2.840.113556.1.4.903'
170 syntaxmap['2.5.5.8'] = '1.3.6.1.4.1.1466.115.121.1.7'
171 syntaxmap['2.5.5.9'] = '1.3.6.1.4.1.1466.115.121.1.27'
172 syntaxmap['2.5.5.10'] = '1.3.6.1.4.1.1466.115.121.1.40'
173 syntaxmap['2.5.5.11'] = '1.3.6.1.4.1.1466.115.121.1.24'
174 syntaxmap['2.5.5.12'] = '1.3.6.1.4.1.1466.115.121.1.15'
175 syntaxmap['2.5.5.13'] = '1.3.6.1.4.1.1466.115.121.1.43'
176 syntaxmap['2.5.5.14'] = '1.2.840.113556.1.4.904'
177 syntaxmap['2.5.5.15'] = '1.2.840.113556.1.4.907'
178 syntaxmap['2.5.5.16'] = '1.2.840.113556.1.4.906'
179 syntaxmap['2.5.5.17'] = '1.3.6.1.4.1.1466.115.121.1.40'
182 def map_attribute_syntax(s):
183 """map some attribute syntaxes from some apparently MS specific
184 syntaxes to the standard syntaxes"""
185 if s in list(syntaxmap):
191 """fix a string DN to use ${SCHEMADN}"""
192 return dn.replace(rootDse["schemaNamingContext"][0], "${SCHEMADN}")
195 def write_ldif_one(o, attrs):
196 """dump an object as ldif"""
197 print "dn: CN=%s,${SCHEMADN}" % o["cn"]
201 # special case for oMObjectClass, which is a binary object
205 if a == "oMObjectClass":
206 print "%s:: %s" % (a, base64.b64encode(value))
207 elif a.endswith("GUID"):
208 print "%s: %s" % (a, ldb.schema_format_value(a, value))
210 print "%s: %s" % (a, value)
214 def write_ldif(o, attrs):
215 """dump an array of objects as ldif"""
216 for n, i in o.items():
217 write_ldif_one(i, attrs)
220 def create_testdn(exampleDN):
221 """create a testDN based an an example DN
222 the idea is to ensure we obey any structural rules"""
223 a = exampleDN.split(",")
228 def find_objectclass_properties(ldb, o):
229 """the properties of an objectclass"""
231 expression="(ldapDisplayName=%s)" % o.name,
232 base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE, attrs=class_attrs)
233 assert(len(res) == 1)
238 def find_attribute_properties(ldb, o):
239 """find the properties of an attribute"""
241 expression="(ldapDisplayName=%s)" % o.name,
242 base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE,
244 assert(len(res) == 1)
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'" % testdn
259 ldif = "dn: " + testdn
260 ldif += "\nobjectClass: " + o.name
264 print "error adding %s: %s" % (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"][0], scope=SCOPE_SUBTREE,
285 print >>sys.stderr, "Expanding class %s" % o.name
286 assert(len(res) == 1)
292 if isinstance(list, str):
295 if not objectclasses.has_key(name):
296 print >>sys.stderr, "Found new objectclass '%s'" % 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 >>sys.stderr, "Unable to fetch allowedAttributes for '%s' - %r" % (dn, e)
325 allattrs = res[0]["allowedAttributes"]
327 res = ldb.search("objectClass=*", dn, SCOPE_BASE, allattrs)
329 print >>sys.stderr, "Unable to fetch all attributes for '%s' - %s" % (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 >>sys.stderr, "Unable to fetch objectClasses for '%s' - %s" % (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"]
360 if objectclasses.has_key(x):
362 objectclass["possibleInferiors"] = newpossinf
364 # trim systemMayContain,
366 if objectclass.has_key("systemMayContain"):
367 sysmay = objectclass["systemMayContain"]
370 if not x in newsysmay:
372 objectclass["systemMayContain"] = newsysmay
376 if objectclass.has_key("mayContain"):
377 may = objectclass["mayContain"]
379 if isinstance(may, str):
384 objectclass["mayContain"] = newmay
387 def build_objectclass(ldb, name):
388 """load the basic attributes of an objectClass"""
391 expression="(&(objectClass=classSchema)(ldapDisplayName=%s))" % name,
392 base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE,
395 print >>sys.stderr, "unknown class '%s'" % name
397 return Objectclass(ldb, name)
400 def attribute_list(objectclass, attr1, attr2):
401 """form a coalesced attribute list"""
402 a1 = list(objectclass.get(attr1, []))
403 a2 = list(objectclass.get(attr2, []))
406 def aggregate_list(name, list):
407 """write out a list in aggregate form"""
410 print "%s ( %s )" % (name, "$ ".join(list))
412 def write_aggregate_objectclass(objectclass):
413 """write the aggregate record for an objectclass"""
414 print "objectClasses: ( %s NAME '%s' " % (objectclass["governsID"], objectclass.name),
415 if not objectclass.has_key('subClassOf'):
416 print "SUP %s " % objectclass['subClassOf'],
417 if objectclass["objectClassCategory"] == 1:
419 elif objectclass["objectClassCategory"] == 2:
421 elif objectclass["objectClassCategory"] == 3:
424 list = attribute_list(objectclass, "systemMustContain", "mustContain")
425 aggregate_list("MUST", list)
427 list = attribute_list(objectclass, "systemMayContain", "mayContain")
428 aggregate_list("MAY", list)
433 def write_aggregate_ditcontentrule(objectclass):
434 """write the aggregate record for an ditcontentrule"""
435 list = attribute_list(objectclass, "auxiliaryClass", "systemAuxiliaryClass")
439 print "dITContentRules: ( %s NAME '%s' " % (objectclass["governsID"], objectclass.name)
441 aggregate_list("AUX", list)
447 list2 = attribute_list(objectclasses[c],
448 "mayContain", "systemMayContain")
449 may_list = may_list + list2
450 list2 = attribute_list(objectclasses[c],
451 "mustContain", "systemMustContain")
452 must_list = must_list + list2
454 aggregate_list("MUST", must_list)
455 aggregate_list("MAY", may_list)
459 def write_aggregate_attribute(attrib):
460 """write the aggregate record for an attribute"""
461 print "attributeTypes: ( %s NAME '%s' SYNTAX '%s' " % (
462 attrib["attributeID"], attrib.name,
463 map_attribute_syntax(attrib["attributeSyntax"]))
464 if attrib.get('isSingleValued') == "TRUE":
465 print "SINGLE-VALUE "
466 if attrib.get('systemOnly') == "TRUE":
467 print "NO-USER-MODIFICATION "
472 def write_aggregate():
473 """write the aggregate record"""
474 print "dn: CN=Aggregate,${SCHEMADN}\n"
475 print """objectClass: top
476 objectClass: subSchema
477 objectCategory: CN=SubSchema,${SCHEMADN}
479 if not opts.dump_subschema_auto:
482 for objectclass in objectclasses.values():
483 write_aggregate_objectclass(objectclass)
484 for attr in attributes.values():
485 write_aggregate_attribute(attr)
486 for objectclass in objectclasses.values():
487 write_aggregate_ditcontentrule(objectclass)
490 """load a list from a file"""
491 return [l.strip("\n") for l in open(file, 'r').readlines()]
494 res = ldb.search(base="", expression="", scope=SCOPE_BASE, attrs=["schemaNamingContext"])
497 # load the list of classes we are interested in
498 classes = load_list(classfile)
499 for classname in classes:
500 objectclass = build_objectclass(ldb, classname)
501 if objectclass is not None:
502 objectclasses[classname] = objectclass
506 # expand the objectclass list as needed
510 # so EJS do not have while nor the break statement
511 # cannot find any other way than doing more loops
512 # than necessary to recursively expand all classes
514 for inf in range(500):
515 for n, o in objectclasses.items():
516 if not n in objectclasses_expanded:
517 expand_objectclass(ldb, o)
518 objectclasses_expanded.add(n)
521 # find objectclass properties
523 for name, objectclass in objectclasses.items():
524 find_objectclass_properties(ldb, objectclass)
528 # form the full list of attributes
530 for name, objectclass in objectclasses.items():
531 add_objectclass_attributes(ldb, objectclass)
533 # and attribute properties
534 for name, attr in attributes.items():
535 find_attribute_properties(ldb, attr)
538 # trim the 'may' attribute lists to those really needed
540 for name, objectclass in objectclasses.items():
541 trim_objectclass_attributes(ldb, objectclass)
544 # dump an ldif form of the attributes and objectclasses
546 if opts.dump_attributes:
547 write_ldif(attributes, attrib_attrs)
548 if opts.dump_classes:
549 write_ldif(objectclasses, class_attrs)
550 if opts.dump_subschema:
557 # dump list of objectclasses
559 print "objectClasses:\n"
560 for objectclass in objectclasses:
561 print "\t%s\n" % objectclass
563 print "attributes:\n"
564 for attr in attributes:
565 print "\t%s\n" % attr
567 print "autocreated attributes:\n"
568 for attr in attributes: