3 # Works 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",
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",
111 "extendedCharsAllowed",
114 "attributeSecurityGUID",
117 "isMemberOfPartialAttributeSet",
120 # this attributes are not used by w2k3
123 "msDs-Schema-Extensions",
131 # objectClassCategory
136 def get_object_cn(ldb
, name
):
138 res
= ldb
.search(expression
="(ldapDisplayName=%s)" % name
, base
=rootDse
["schemaNamingContext"][0], scope
=SCOPE_SUBTREE
, attrs
=attrs
)
143 class Objectclass(dict):
145 def __init__(self
, ldb
, name
):
146 """create an objectclass object"""
148 self
["cn"] = get_object_cn(ldb
, name
)
151 class Attribute(dict):
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 s
in list(syntaxmap
):
189 """fix a string DN to use ${SCHEMADN}"""
190 return dn
.replace(rootDse
["schemaNamingContext"][0], "${SCHEMADN}")
193 def write_ldif_one(o
, attrs
):
194 """dump an object as ldif"""
195 print "dn: CN=%s,${SCHEMADN}" % o
["cn"]
199 # special case for oMObjectClass, which is a binary object
203 if a
== "oMObjectClass":
204 print "%s:: %s" % (a
, base64
.b64encode(value
))
205 elif a
.endswith("GUID"):
206 print "%s: %s" % (a
, ldb
.schema_format_value(a
, value
))
208 print "%s: %s" % (a
, value
)
212 def write_ldif(o
, attrs
):
213 """dump an array of objects as ldif"""
214 for n
, i
in o
.items():
215 write_ldif_one(i
, attrs
)
218 def create_testdn(exampleDN
):
219 """create a testDN based an an example DN
220 the idea is to ensure we obey any structural rules"""
221 a
= exampleDN
.split(",")
226 def find_objectclass_properties(ldb
, o
):
227 """the properties of an objectclass"""
229 expression
="(ldapDisplayName=%s)" % o
.name
,
230 base
=rootDse
["schemaNamingContext"][0], scope
=SCOPE_SUBTREE
, attrs
=class_attrs
)
231 assert(len(res
) == 1)
236 def find_attribute_properties(ldb
, o
):
237 """find the properties of an attribute"""
239 expression
="(ldapDisplayName=%s)" % o
.name
,
240 base
=rootDse
["schemaNamingContext"][0], scope
=SCOPE_SUBTREE
,
242 assert(len(res
) == 1)
248 def find_objectclass_auto(ldb
, o
):
249 """find the auto-created properties of an objectclass. Only works for
250 classes that can be created using just a DN and the objectclass"""
251 if not o
.has_key("exampleDN"):
253 testdn
= create_testdn(o
.exampleDN
)
255 print "testdn is '%s'" % testdn
257 ldif
= "dn: " + testdn
258 ldif
+= "\nobjectClass: " + o
.name
262 print "error adding %s: %s" % (o
.name
, e
)
266 res
= ldb
.search(base
=testdn
, scope
=ldb
.SCOPE_BASE
)
269 for a
in res
.msgs
[0]:
270 attributes
[a
].autocreate
= True
273 def expand_objectclass(ldb
, o
):
274 """look at auxiliary information from a class to intuit the existance of
275 more classes needed for a minimal schema"""
276 attrs
= ["auxiliaryClass", "systemAuxiliaryClass",
277 "possSuperiors", "systemPossSuperiors",
280 expression
="(&(objectClass=classSchema)(ldapDisplayName=%s))" % o
.name
,
281 base
=rootDse
["schemaNamingContext"][0], scope
=SCOPE_SUBTREE
,
283 print >>sys
.stderr
, "Expanding class %s" % o
.name
284 assert(len(res
) == 1)
290 if isinstance(list, str):
293 if not objectclasses
.has_key(name
):
294 print >>sys
.stderr
, "Found new objectclass '%s'" % name
295 objectclasses
[name
] = Objectclass(ldb
, name
)
298 def add_objectclass_attributes(ldb
, objectclass
):
299 """add the must and may attributes from an objectclass to the full list
301 attrs
= ["mustContain", "systemMustContain",
302 "mayContain", "systemMayContain"]
304 if not objectclass
.has_key(aname
):
306 alist
= objectclass
[aname
]
307 if isinstance(alist
, str):
310 if not attributes
.has_key(a
):
311 attributes
[a
] = Attribute(ldb
, a
)
314 def walk_dn(ldb
, dn
):
315 """process an individual record, working out what attributes it has"""
316 # get a list of all possible attributes for this object
317 attrs
= ["allowedAttributes"]
319 res
= ldb
.search("objectClass=*", dn
, SCOPE_BASE
, attrs
)
321 print >>sys
.stderr
, "Unable to fetch allowedAttributes for '%s' - %r" % (dn
, e
)
323 allattrs
= res
[0]["allowedAttributes"]
325 res
= ldb
.search("objectClass=*", dn
, SCOPE_BASE
, allattrs
)
327 print >>sys
.stderr
, "Unable to fetch all attributes for '%s' - %s" % (dn
, e
)
331 if not attributes
.has_key(a
):
332 attributes
[a
] = Attribute(ldb
, a
)
334 def walk_naming_context(ldb
, namingContext
):
335 """walk a naming context, looking for all records"""
337 res
= ldb
.search("objectClass=*", namingContext
, SCOPE_DEFAULT
,
340 print >>sys
.stderr
, "Unable to fetch objectClasses for '%s' - %s" % (namingContext
, e
)
343 msg
= res
.msgs
[r
]["objectClass"]
344 for objectClass
in msg
:
345 if not objectclasses
.has_key(objectClass
):
346 objectclasses
[objectClass
] = Objectclass(ldb
, objectClass
)
347 objectclasses
[objectClass
].exampleDN
= res
.msgs
[r
]["dn"]
348 walk_dn(ldb
, res
.msgs
[r
].dn
)
350 def trim_objectclass_attributes(ldb
, objectclass
):
351 """trim the may attributes for an objectClass"""
352 # trim possibleInferiors,
353 # include only the classes we extracted
354 if objectclass
.has_key("possibleInferiors"):
355 possinf
= objectclass
["possibleInferiors"]
358 if objectclasses
.has_key(x
):
360 objectclass
["possibleInferiors"] = newpossinf
362 # trim systemMayContain,
364 if objectclass
.has_key("systemMayContain"):
365 sysmay
= objectclass
["systemMayContain"]
368 if not x
in newsysmay
:
370 objectclass
["systemMayContain"] = newsysmay
374 if objectclass
.has_key("mayContain"):
375 may
= objectclass
["mayContain"]
377 if isinstance(may
, str):
382 objectclass
["mayContain"] = newmay
385 def build_objectclass(ldb
, name
):
386 """load the basic attributes of an objectClass"""
389 expression
="(&(objectClass=classSchema)(ldapDisplayName=%s))" % name
,
390 base
=rootDse
["schemaNamingContext"][0], scope
=SCOPE_SUBTREE
,
393 print >>sys
.stderr
, "unknown class '%s'" % name
395 return Objectclass(ldb
, name
)
398 def attribute_list(objectclass
, attr1
, attr2
):
399 """form a coalesced attribute list"""
400 a1
= list(objectclass
.get(attr1
, []))
401 a2
= list(objectclass
.get(attr2
, []))
404 def aggregate_list(name
, list):
405 """write out a list in aggregate form"""
408 return " %s ( %s )" % (name
, " $ ".join(list))
410 def write_aggregate_objectclass(objectclass
):
411 """write the aggregate record for an objectclass"""
412 line
= "objectClasses: ( %s NAME '%s' " % (objectclass
["governsID"], objectclass
.name
)
413 if not objectclass
.has_key('subClassOf'):
414 line
+= "SUP %s" % objectclass
['subClassOf']
415 if objectclass
["objectClassCategory"] == 1:
417 elif objectclass
["objectClassCategory"] == 2:
419 elif objectclass
["objectClassCategory"] == 3:
422 list = attribute_list(objectclass
, "systemMustContain", "mustContain")
423 line
+= aggregate_list("MUST", list)
425 list = attribute_list(objectclass
, "systemMayContain", "mayContain")
426 line
+= aggregate_list("MAY", list)
431 def write_aggregate_ditcontentrule(objectclass
):
432 """write the aggregate record for an ditcontentrule"""
433 list = attribute_list(objectclass
, "auxiliaryClass", "systemAuxiliaryClass")
437 line
= "dITContentRules: ( %s NAME '%s'" % (objectclass
["governsID"], objectclass
.name
)
439 line
+= aggregate_list("AUX", list)
445 list2
= attribute_list(objectclasses
[c
],
446 "mayContain", "systemMayContain")
447 may_list
= may_list
+ list2
448 list2
= attribute_list(objectclasses
[c
],
449 "mustContain", "systemMustContain")
450 must_list
= must_list
+ list2
452 line
+= aggregate_list("MUST", must_list
)
453 line
+= aggregate_list("MAY", may_list
)
457 def write_aggregate_attribute(attrib
):
458 """write the aggregate record for an attribute"""
459 line
= "attributeTypes: ( %s NAME '%s' SYNTAX '%s' " % (
460 attrib
["attributeID"], attrib
.name
,
461 map_attribute_syntax(attrib
["attributeSyntax"]))
462 if attrib
.get('isSingleValued') == "TRUE":
463 line
+= "SINGLE-VALUE "
464 if attrib
.get('systemOnly') == "TRUE":
465 line
+= "NO-USER-MODIFICATION "
470 def write_aggregate():
471 """write the aggregate record"""
472 print "dn: CN=Aggregate,${SCHEMADN}"
473 print """objectClass: top
474 objectClass: subSchema
475 objectCategory: CN=SubSchema,${SCHEMADN}"""
476 if not opts
.dump_subschema_auto
:
479 for objectclass
in objectclasses
.values():
480 write_aggregate_objectclass(objectclass
)
481 for attr
in attributes
.values():
482 write_aggregate_attribute(attr
)
483 for objectclass
in objectclasses
.values():
484 write_aggregate_ditcontentrule(objectclass
)
487 """load a list from a file"""
488 return [l
.strip("\n") for l
in open(file, 'r').readlines()]
491 res
= ldb
.search(base
="", expression
="", scope
=SCOPE_BASE
, attrs
=["schemaNamingContext"])
494 # load the list of classes we are interested in
495 classes
= load_list(classfile
)
496 for classname
in classes
:
497 objectclass
= build_objectclass(ldb
, classname
)
498 if objectclass
is not None:
499 objectclasses
[classname
] = objectclass
503 # expand the objectclass list as needed
507 # so EJS do not have while nor the break statement
508 # cannot find any other way than doing more loops
509 # than necessary to recursively expand all classes
511 for inf
in range(500):
512 for n
, o
in objectclasses
.items():
513 if not n
in objectclasses_expanded
:
514 expand_objectclass(ldb
, o
)
515 objectclasses_expanded
.add(n
)
518 # find objectclass properties
520 for name
, objectclass
in objectclasses
.items():
521 find_objectclass_properties(ldb
, objectclass
)
525 # form the full list of attributes
527 for name
, objectclass
in objectclasses
.items():
528 add_objectclass_attributes(ldb
, objectclass
)
530 # and attribute properties
531 for name
, attr
in attributes
.items():
532 find_attribute_properties(ldb
, attr
)
535 # trim the 'may' attribute lists to those really needed
537 for name
, objectclass
in objectclasses
.items():
538 trim_objectclass_attributes(ldb
, objectclass
)
541 # dump an ldif form of the attributes and objectclasses
543 if opts
.dump_attributes
:
544 write_ldif(attributes
, attrib_attrs
)
545 if opts
.dump_classes
:
546 write_ldif(objectclasses
, class_attrs
)
547 if opts
.dump_subschema
:
554 # dump list of objectclasses
556 print "objectClasses:\n"
557 for objectclass
in objectclasses
:
558 print "\t%s\n" % objectclass
560 print "attributes:\n"
561 for attr
in attributes
:
562 print "\t%s\n" % attr
564 print "autocreated attributes:\n"
565 for attr
in attributes
: