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
17 parser = optparse.OptionParser("minschema <URL> <classfile>")
18 sambaopts = options.SambaOptions(parser)
19 parser.add_option_group(sambaopts)
20 credopts = options.CredentialsOptions(parser)
21 parser.add_option_group(credopts)
22 parser.add_option_group(options.VersionOptions(parser))
23 parser.add_option("--verbose", help="Be verbose", action="store_true")
24 parser.add_option("--dump-classes", action="store_true")
25 parser.add_option("--dump-attributes", action="store_true")
26 parser.add_option("--dump-subschema", action="store_true")
27 parser.add_option("--dump-subschema-auto", action="store_true")
29 opts, args = parser.parse_args()
34 if opts.dump_attributes:
36 if opts.dump_subschema:
38 if opts.dump_subschema_auto:
40 opts.dump_subschema = True
42 opts.dump_classes = True
43 opts.dump_attributes = True
44 opts.dump_subschema = True
45 opts.dump_subschema_auto = True
51 (url, classfile) = args
53 creds = credopts.get_credentials()
54 ldb = Ldb(url, credentials=creds)
59 objectclasses_expanded = set()
61 # the attributes we need for objectclasses
62 class_attrs = ["objectClass",
71 "showInAdvancedViewOnly",
74 "objectClassCategory",
78 "systemPossSuperiors",
81 "systemAuxiliaryClass",
82 "defaultSecurityDescriptor",
86 "defaultObjectCategory",
88 # this attributes are not used by w2k3
91 "msDs-Schema-Extensions",
95 attrib_attrs = ["objectClass",
103 "showInAdvancedViewOnly",
109 "extendedCharsAllowed",
112 "attributeSecurityGUID",
115 "isMemberOfPartialAttributeSet",
118 # this attributes are not used by w2k3
121 "msDs-Schema-Extensions",
129 # objectClassCategory
135 # print only if verbose is set
138 if verbose is not None:
141 def get_object_cn(ldb, name):
144 res = ldb.search("(ldapDisplayName=%s)" % name, rootDse["schemaNamingContext"], ldb.SCOPE_SUBTREE, attrs)
150 def __init__(self, ldb, name):
151 """create an objectclass object"""
153 self.cn = get_object_cn(ldb, name)
157 def __init__(self, ldb, name):
158 """create an attribute object"""
160 self.cn = get_object_cn(ldb, name)
165 syntaxmap['2.5.5.1'] = '1.3.6.1.4.1.1466.115.121.1.12'
166 syntaxmap['2.5.5.2'] = '1.3.6.1.4.1.1466.115.121.1.38'
167 syntaxmap['2.5.5.3'] = '1.2.840.113556.1.4.1362'
168 syntaxmap['2.5.5.4'] = '1.2.840.113556.1.4.905'
169 syntaxmap['2.5.5.5'] = '1.3.6.1.4.1.1466.115.121.1.26'
170 syntaxmap['2.5.5.6'] = '1.3.6.1.4.1.1466.115.121.1.36'
171 syntaxmap['2.5.5.7'] = '1.2.840.113556.1.4.903'
172 syntaxmap['2.5.5.8'] = '1.3.6.1.4.1.1466.115.121.1.7'
173 syntaxmap['2.5.5.9'] = '1.3.6.1.4.1.1466.115.121.1.27'
174 syntaxmap['2.5.5.10'] = '1.3.6.1.4.1.1466.115.121.1.40'
175 syntaxmap['2.5.5.11'] = '1.3.6.1.4.1.1466.115.121.1.24'
176 syntaxmap['2.5.5.12'] = '1.3.6.1.4.1.1466.115.121.1.15'
177 syntaxmap['2.5.5.13'] = '1.3.6.1.4.1.1466.115.121.1.43'
178 syntaxmap['2.5.5.14'] = '1.2.840.113556.1.4.904'
179 syntaxmap['2.5.5.15'] = '1.2.840.113556.1.4.907'
180 syntaxmap['2.5.5.16'] = '1.2.840.113556.1.4.906'
181 syntaxmap['2.5.5.17'] = '1.3.6.1.4.1.1466.115.121.1.40'
184 def map_attribute_syntax(s):
185 """map some attribute syntaxes from some apparently MS specific
186 syntaxes to the standard syntaxes"""
187 if syntaxmap.has_key(s):
193 """fix a string DN to use ${SCHEMADN}"""
194 return dn.replace(rootDse["schemaNamingContext"], "${SCHEMADN}")
197 def write_ldif_one(o, attrs):
198 """dump an object as ldif"""
199 print "dn: CN=%s,${SCHEMADN}\n" % o["cn"]
203 # special case for oMObjectClass, which is a binary object
204 if a == "oMObjectClass":
205 print "%s:: %s\n" % (a, o[a])
208 if isinstance(v, str):
211 print "%s: %s\n" % (a, fix_dn(j))
214 def write_ldif(o, attrs):
215 """dump an array of objects as ldif"""
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 basedn=rootDse["schemaNamingContext"], scope=ldb.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 basedn=rootDse["schemaNamingContext"], scope=ldb.SCOPE_SUBTREE,
244 assert(len(res) == 1)
247 # special case for oMObjectClass, which is a binary object
248 if a == "oMObjectClass":
249 o[a] = ldb.encode(msg[a])
254 def find_objectclass_auto(ldb, o):
255 """find the auto-created properties of an objectclass. Only works for
256 classes that can be created using just a DN and the objectclass"""
257 if not o.has_key("exampleDN"):
259 testdn = create_testdn(o.exampleDN)
261 print "testdn is '%s'\n" % testdn
263 ldif = "dn: " + testdn
264 ldif += "\nobjectClass: " + o.name
268 print "error adding %s: %s\n" % (o.name, e)
272 res = ldb.search("", testdn, ldb.SCOPE_BASE)
275 for a in res.msgs[0]:
276 attributes[a].autocreate = True
279 def expand_objectclass(ldb, o):
280 """look at auxiliary information from a class to intuit the existance of
281 more classes needed for a minimal schema"""
282 attrs = ["auxiliaryClass", "systemAuxiliaryClass",
283 "possSuperiors", "systemPossSuperiors",
286 expression="(&(objectClass=classSchema)(ldapDisplayName=%s))" % o.name,
287 basedn=rootDse["schemaNamingContext"], scope=ldb.SCOPE_SUBTREE,
289 print "Expanding class %s\n" % o.name
290 assert(len(res) == 1)
293 if not msg.has_key(aname):
296 if isinstance(list, str):
299 if not objectclasses.has_key(name):
300 print "Found new objectclass '%s'\n" % name
301 objectclasses[name] = Objectclass(ldb, name)
304 def add_objectclass_attributes(ldb, objectclass):
305 """add the must and may attributes from an objectclass to the full list
307 attrs = ["mustContain", "systemMustContain",
308 "mayContain", "systemMayContain"]
310 if not objectclass.has_key(aname):
312 alist = objectclass[aname]
313 if isinstance(alist, str):
316 if not attributes.has_key(a):
317 attributes[a] = Attribute(ldb, a)
320 def walk_dn(ldb, dn):
321 """process an individual record, working out what attributes it has"""
322 # get a list of all possible attributes for this object
323 attrs = ["allowedAttributes"]
325 res = ldb.search("objectClass=*", dn, ldb.SCOPE_BASE, attrs)
327 print "Unable to fetch allowedAttributes for '%s' - %r\n" % (dn, e)
329 allattrs = res[0]["allowedAttributes"]
331 res = ldb.search("objectClass=*", dn, ldb.SCOPE_BASE, allattrs)
333 print "Unable to fetch all attributes for '%s' - %s\n" % (dn, e)
337 if not attributes.has_key(a):
338 attributes[a] = Attribute(ldb, a)
340 def walk_naming_context(ldb, namingContext):
341 """walk a naming context, looking for all records"""
343 res = ldb.search("objectClass=*", namingContext, ldb.SCOPE_DEFAULT,
346 print "Unable to fetch objectClasses for '%s' - %s\n" % (namingContext, e)
349 msg = res.msgs[r]["objectClass"]
350 for objectClass in msg:
351 if not objectclasses.has_key(objectClass):
352 objectclasses[objectClass] = Objectclass(ldb, objectClass)
353 objectclasses[objectClass].exampleDN = res.msgs[r]["dn"]
354 walk_dn(ldb, res.msgs[r].dn)
356 def trim_objectclass_attributes(ldb, objectclass):
357 """trim the may attributes for an objectClass"""
358 # trim possibleInferiors,
359 # include only the classes we extracted
360 if objectclass.has_key("possibleInferiors"):
361 possinf = objectclass["possibleInferiors"]
363 if isinstance(possinf, str):
366 if objectclasses.has_key(x):
369 objectclass["possibleInferiors"] = newpossinf
371 # trim systemMayContain,
373 if objectclass.has_key("systemMayContain"):
374 sysmay = objectclass["systemMayContain"]
376 if isinstance(sysmay, str):
379 if not x in newsysmay:
381 objectclass["systemMayContain"] = newsysmay
385 if not objectclass.has_key("mayContain"):
386 may = objectclass["mayContain"]
388 if isinstance(may, str):
393 objectclass["mayContain"] = newmay
395 def build_objectclass(ldb, name):
396 """load the basic attributes of an objectClass"""
400 expression="(&(objectClass=classSchema)(ldapDisplayName=%s))" % name,
401 basedn=rootDse["schemaNamingContext"], scope=ldb.SCOPE_SUBTREE,
404 print "unknown class '%s'\n" % name
407 print "unknown class '%s'\n" % name
409 return Objectclass(ldb, name)
411 def attribute_list(objectclass, attr1, attr2):
412 """form a coalesced attribute list"""
413 a1 = objectclass[attr1]
414 a2 = objectclass[attr2]
415 if isinstance(a1, str):
417 if isinstance(a2, str):
421 def aggregate_list(name, list):
422 """write out a list in aggregate form"""
425 print "%s ( %s )" % (name, "$ ".join(list))
427 def write_aggregate_objectclass(objectclass):
428 """write the aggregate record for an objectclass"""
429 print "objectClasses: ( %s NAME '%s' " % (objectclass.governsID, objectclass.name)
430 if not objectclass.has_key('subClassOf'):
431 print "SUP %s " % objectclass['subClassOf']
432 if objectclass.objectClassCategory == 1:
434 elif objectclass.objectClassCategory == 2:
436 elif objectclass.objectClassCategory == 3:
439 list = attribute_list(objectclass, "systemMustContain", "mustContain")
440 aggregate_list("MUST", list)
442 list = attribute_list(objectclass, "systemMayContain", "mayContain")
443 aggregate_list("MAY", list)
448 def write_aggregate_ditcontentrule(objectclass):
449 """write the aggregate record for an ditcontentrule"""
450 list = attribute_list(objectclass, "auxiliaryClass", "systemAuxiliaryClass")
454 print "dITContentRules: ( %s NAME '%s' " % (objectclass.governsID, objectclass.name)
456 aggregate_list("AUX", list)
462 list2 = attribute_list(objectclasses[c],
463 "mayContain", "systemMayContain")
464 may_list = may_list + list2
465 list2 = attribute_list(objectclasses[c],
466 "mustContain", "systemMustContain")
467 must_list = must_list + list2
469 aggregate_list("MUST", must_list)
470 aggregate_list("MAY", may_list)
474 def write_aggregate_attribute(attrib):
475 """write the aggregate record for an attribute"""
476 print "attributeTypes: ( %s NAME '%s' SYNTAX '%s' " % (
477 attrib.attributeID, attrib.name,
478 map_attribute_syntax(attrib.attributeSyntax))
479 if attrib['isSingleValued'] == "TRUE":
480 print "SINGLE-VALUE "
481 if attrib['systemOnly'] == "TRUE":
482 print "NO-USER-MODIFICATION "
487 def write_aggregate():
488 """write the aggregate record"""
489 print "dn: CN=Aggregate,${SCHEMADN}\n"
490 print """objectClass: top
491 objectClass: subSchema
492 objectCategory: CN=SubSchema,${SCHEMADN}
494 if not opts.dump_subschema_auto:
497 for objectclass in objectclasses:
498 write_aggregate_objectclass(objectclass)
499 for attr in attributes:
500 write_aggregate_attribute(attr)
501 for objectclass in objectclasses:
502 write_aggregate_ditcontentrule(objectclass)
505 """load a list from a file"""
506 return open(file, 'r').splitlines()
509 res = ldb.search("", "", ldb.SCOPE_BASE)
512 # load the list of classes we are interested in
513 classes = load_list(classfile)
514 for classname in classes:
515 objectclass = build_objectclass(ldb, classname)
516 if objectclass is not None:
517 objectclasses[classname] = objectclass
521 # expand the objectclass list as needed
525 # so EJS do not have while nor the break statement
526 # cannot find any other way than doing more loops
527 # than necessary to recursively expand all classes
529 for inf in range(500):
530 for n in objectclasses:
531 if not n in objectclasses_expanded:
532 expand_objectclass(ldb, objectclasses[i])
533 objectclasses_expanded.add(n)
536 # find objectclass properties
538 for objectclass in objectclasses:
539 find_objectclass_properties(ldb, objectclass)
543 # form the full list of attributes
545 for objectclass in objectclasses:
546 add_objectclass_attributes(ldb, objectclass)
548 # and attribute properties
549 for attr in attributes:
550 find_attribute_properties(ldb, attr)
553 # trim the 'may' attribute lists to those really needed
555 for objectclass in objectclasses:
556 trim_objectclass_attributes(ldb, objectclass)
559 # dump an ldif form of the attributes and objectclasses
561 if opts.dump_attributes:
562 write_ldif(attributes, attrib_attrs)
563 if opts.dump_classes:
564 write_ldif(objectclasses, class_attrs)
565 if opts.dump_subschema:
572 # dump list of objectclasses
574 print "objectClasses:\n"
575 for objectclass in objectclasses:
576 print "\t%s\n" % objectclass
578 print "attributes:\n"
579 for attr in attributes:
580 print "\t%s\n" % attr
582 print "autocreated attributes:\n"
583 for attr in attributes: