s3-rpcclient: Fix a compile warning.
[Samba/gebeck_regimport.git] / source4 / scripting / bin / minschema
blobe7d7ed497913e752ac267e482d4f0e0506fbef3d
1 #!/usr/bin/python
2 #
3 # work out the minimal schema for a set of objectclasses
6 import optparse
8 import os, sys
10 # Find right directory when running from source tree
11 sys.path.insert(0, "bin/python")
13 import samba
14 from samba import getopt as options, Ldb
15 from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError
16 import sys
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()
31 opts.dump_all = True
33 if opts.dump_classes:
34 opts.dump_all = False
35 if opts.dump_attributes:
36 opts.dump_all = False
37 if opts.dump_subschema:
38 opts.dump_all = False
39 if opts.dump_subschema_auto:
40 opts.dump_all = False
41 opts.dump_subschema = True
42 if opts.dump_all:
43 opts.dump_classes = True
44 opts.dump_attributes = True
45 opts.dump_subschema = True
46 opts.dump_subschema_auto = True
48 if len(args) != 2:
49 parser.print_usage()
50 sys.exit(1)
52 (url, classfile) = args
54 lp_ctx = sambaopts.get_loadparm()
56 creds = credopts.get_credentials(lp_ctx)
57 ldb = Ldb(url, credentials=creds)
59 objectclasses = []
60 attributes = []
62 objectclasses_expanded = set()
64 # the attributes we need for objectclasses
65 class_attrs = ["objectClass",
66 "subClassOf",
67 "governsID",
68 "possSuperiors",
69 "possibleInferiors",
70 "mayContain",
71 "mustContain",
72 "auxiliaryClass",
73 "rDNAttID",
74 "showInAdvancedViewOnly",
75 "adminDisplayName",
76 "adminDescription",
77 "objectClassCategory",
78 "lDAPDisplayName",
79 "schemaIDGUID",
80 "systemOnly",
81 "systemPossSuperiors",
82 "systemMayContain",
83 "systemMustContain",
84 "systemAuxiliaryClass",
85 "defaultSecurityDescriptor",
86 "systemFlags",
87 "defaultHidingValue",
88 "objectCategory",
89 "defaultObjectCategory",
91 # this attributes are not used by w2k3
92 "schemaFlagsEx",
93 "msDs-IntId",
94 "msDs-Schema-Extensions",
95 "classDisplayName",
96 "isDefunct"]
98 attrib_attrs = ["objectClass",
99 "attributeID",
100 "attributeSyntax",
101 "isSingleValued",
102 "rangeLower",
103 "rangeUpper",
104 "mAPIID",
105 "linkID",
106 "showInAdvancedViewOnly",
107 "adminDisplayName",
108 "oMObjectClass",
109 "adminDescription",
110 "oMSyntax",
111 "searchFlags",
112 "extendedCharsAllowed",
113 "lDAPDisplayName",
114 "schemaIDGUID",
115 "attributeSecurityGUID",
116 "systemOnly",
117 "systemFlags",
118 "isMemberOfPartialAttributeSet",
119 "objectCategory",
121 # this attributes are not used by w2k3
122 "schemaFlagsEx",
123 "msDs-IntId",
124 "msDs-Schema-Extensions",
125 "classDisplayName",
126 "isEphemeral",
127 "isDefunct"]
130 # notes:
132 # objectClassCategory
133 # 1: structural
134 # 2: abstract
135 # 3: auxiliary
137 def get_object_cn(ldb, name):
138 attrs = ["cn"]
140 res = ldb.search("(ldapDisplayName=%s)" % name, rootDse["schemaNamingContext"], SCOPE_SUBTREE, attrs)
141 assert len(res) == 1
143 return res[0]["cn"]
145 class Objectclass:
146 def __init__(self, ldb, name):
147 """create an objectclass object"""
148 self.name = name
149 self.cn = get_object_cn(ldb, name)
152 class Attribute:
153 def __init__(self, ldb, name):
154 """create an attribute object"""
155 self.name = name
156 self.cn = get_object_cn(ldb, name)
159 syntaxmap = dict()
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):
184 return syntaxmap[s]
185 return s
188 def fix_dn(dn):
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"]
196 for a in attrs:
197 if not o.has_key(a):
198 continue
199 # special case for oMObjectClass, which is a binary object
200 if a == "oMObjectClass":
201 print "%s:: %s\n" % (a, o[a])
202 continue
203 v = o[a]
204 if isinstance(v, str):
205 v = [v]
206 for j in v:
207 print "%s: %s\n" % (a, fix_dn(j))
208 print "\n"
210 def write_ldif(o, attrs):
211 """dump an array of objects as ldif"""
212 for i in o:
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(",")
220 a[0] = "CN=TestDN"
221 return ",".join(a)
224 def find_objectclass_properties(ldb, o):
225 """the properties of an objectclass"""
226 res = ldb.search(
227 expression="(ldapDisplayName=%s)" % o.name,
228 base=rootDse["schemaNamingContext"], scope=SCOPE_SUBTREE, attrs=class_attrs)
229 assert(len(res) == 1)
230 msg = res[0]
231 for a in msg:
232 o[a] = msg[a]
234 def find_attribute_properties(ldb, o):
235 """find the properties of an attribute"""
236 res = ldb.search(
237 expression="(ldapDisplayName=%s)" % o.name,
238 base=rootDse["schemaNamingContext"], scope=SCOPE_SUBTREE,
239 attrs=attrib_attrs)
240 assert(len(res) == 1)
241 msg = res[0]
242 for a in msg:
243 # special case for oMObjectClass, which is a binary object
244 if a == "oMObjectClass":
245 o[a] = ldb.encode(msg[a])
246 continue
247 o[a] = 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"):
254 return
255 testdn = create_testdn(o.exampleDN)
257 print "testdn is '%s'\n" % testdn
259 ldif = "dn: " + testdn
260 ldif += "\nobjectClass: " + o.name
261 try:
262 ldb.add(ldif)
263 except LdbError, e:
264 print "error adding %s: %s\n" % (o.name, e)
265 print "%s\n" % ldif
266 return
268 res = ldb.search(base=testdn, scope=ldb.SCOPE_BASE)
269 ldb.delete(testdn)
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",
280 "subClassOf"]
281 res = ldb.search(
282 expression="(&(objectClass=classSchema)(ldapDisplayName=%s))" % o.name,
283 base=rootDse["schemaNamingContext"], scope=SCOPE_SUBTREE,
284 attrs=attrs)
285 print "Expanding class %s\n" % o.name
286 assert(len(res) == 1)
287 msg = res[0]
288 for a in attrs:
289 if not msg.has_key(aname):
290 continue
291 list = msg[aname]
292 if isinstance(list, str):
293 list = [msg[aname]]
294 for name in list:
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
302 of attributes"""
303 attrs = ["mustContain", "systemMustContain",
304 "mayContain", "systemMayContain"]
305 for aname in attrs:
306 if not objectclass.has_key(aname):
307 continue
308 alist = objectclass[aname]
309 if isinstance(alist, str):
310 alist = [alist]
311 for a in alist:
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"]
320 try:
321 res = ldb.search("objectClass=*", dn, SCOPE_BASE, attrs)
322 except LdbError, e:
323 print "Unable to fetch allowedAttributes for '%s' - %r\n" % (dn, e)
324 return
325 allattrs = res[0]["allowedAttributes"]
326 try:
327 res = ldb.search("objectClass=*", dn, SCOPE_BASE, allattrs)
328 except LdbError, e:
329 print "Unable to fetch all attributes for '%s' - %s\n" % (dn, e)
330 return
331 msg = res[0]
332 for a in msg:
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"""
338 try:
339 res = ldb.search("objectClass=*", namingContext, SCOPE_DEFAULT,
340 ["objectClass"])
341 except LdbError, e:
342 print "Unable to fetch objectClasses for '%s' - %s\n" % (namingContext, e)
343 return
344 for msg in res:
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"]
358 newpossinf = []
359 if isinstance(possinf, str):
360 possinf = [possinf]
361 for x in possinf:
362 if objectclasses.has_key(x):
363 newpossinf[n] = x
364 n+=1
365 objectclass["possibleInferiors"] = newpossinf
367 # trim systemMayContain,
368 # remove duplicates
369 if objectclass.has_key("systemMayContain"):
370 sysmay = objectclass["systemMayContain"]
371 newsysmay = []
372 if isinstance(sysmay, str):
373 sysmay = [sysmay]
374 for x in sysmay:
375 if not x in newsysmay:
376 newsysmay.append(x)
377 objectclass["systemMayContain"] = newsysmay
379 # trim mayContain,
380 # remove duplicates
381 if not objectclass.has_key("mayContain"):
382 may = objectclass["mayContain"]
383 newmay = []
384 if isinstance(may, str):
385 may = [may]
386 for x in may:
387 if not x in newmay:
388 newmay.append(x)
389 objectclass["mayContain"] = newmay
391 def build_objectclass(ldb, name):
392 """load the basic attributes of an objectClass"""
393 attrs = ["name"]
394 try:
395 res = ldb.search(
396 expression="(&(objectClass=classSchema)(ldapDisplayName=%s))" % name,
397 base=rootDse["schemaNamingContext"], scope=SCOPE_SUBTREE,
398 attrs=attrs)
399 except LdbError, e:
400 print "unknown class '%s'\n" % name
401 return None
402 if len(res) == 0:
403 print "unknown class '%s'\n" % name
404 return None
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):
412 a1 = [a1]
413 if isinstance(a2, str):
414 a2 = [a2]
415 return a1 + a2
417 def aggregate_list(name, list):
418 """write out a list in aggregate form"""
419 if list is None:
420 return
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:
429 print "STRUCTURAL "
430 elif objectclass.objectClassCategory == 2:
431 print "ABSTRACT "
432 elif objectclass.objectClassCategory == 3:
433 print "AUXILIARY "
435 list = attribute_list(objectclass, "systemMustContain", "mustContain")
436 aggregate_list("MUST", list)
438 list = attribute_list(objectclass, "systemMayContain", "mayContain")
439 aggregate_list("MAY", list)
441 print ")\n"
444 def write_aggregate_ditcontentrule(objectclass):
445 """write the aggregate record for an ditcontentrule"""
446 list = attribute_list(objectclass, "auxiliaryClass", "systemAuxiliaryClass")
447 if list is None:
448 return
450 print "dITContentRules: ( %s NAME '%s' " % (objectclass.governsID, objectclass.name)
452 aggregate_list("AUX", list)
454 may_list = None
455 must_list = None
457 for c in 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)
468 print ")\n"
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 "
480 print ")\n"
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:
491 return
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)
500 def load_list(file):
501 """load a list from a file"""
502 return open(file, 'r').readlines()
504 # get the rootDSE
505 res = ldb.search(base="", expression="", scope=SCOPE_BASE, attrs=["schemaNamingContext"])
506 rootDse = res[0]
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
519 expanded = 0
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:
562 write_aggregate()
564 if not opts.verbose:
565 sys.exit(0)
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:
580 if attr.autocreate:
581 print "\t%s\n" % i