source: vendor/3.6.0/source4/scripting/bin/minschema

Last change on this file was 740, checked in by Silvan Scherrer, 13 years ago

Samba Server: update vendor to 3.6.0

File size: 17.5 KB
Line 
1#!/usr/bin/env python
2#
3# Works out the minimal schema for a set of objectclasses
4#
5
6import base64
7import optparse
8import os
9import sys
10
11# Find right directory when running from source tree
12sys.path.insert(0, "bin/python")
13
14import samba
15from samba import getopt as options, Ldb
16from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError
17import sys
18
19parser = optparse.OptionParser("minschema <URL> <classfile>")
20sambaopts = options.SambaOptions(parser)
21parser.add_option_group(sambaopts)
22credopts = options.CredentialsOptions(parser)
23parser.add_option_group(credopts)
24parser.add_option_group(options.VersionOptions(parser))
25parser.add_option("--verbose", help="Be verbose", action="store_true")
26parser.add_option("--dump-classes", action="store_true")
27parser.add_option("--dump-attributes", action="store_true")
28parser.add_option("--dump-subschema", action="store_true")
29parser.add_option("--dump-subschema-auto", action="store_true")
30
31opts, args = parser.parse_args()
32opts.dump_all = True
33
34if opts.dump_classes:
35 opts.dump_all = False
36if opts.dump_attributes:
37 opts.dump_all = False
38if opts.dump_subschema:
39 opts.dump_all = False
40if opts.dump_subschema_auto:
41 opts.dump_all = False
42 opts.dump_subschema = True
43if opts.dump_all:
44 opts.dump_classes = True
45 opts.dump_attributes = True
46 opts.dump_subschema = True
47 opts.dump_subschema_auto = True
48
49if len(args) != 2:
50 parser.print_usage()
51 sys.exit(1)
52
53(url, classfile) = args
54
55lp_ctx = sambaopts.get_loadparm()
56
57creds = credopts.get_credentials(lp_ctx)
58ldb = Ldb(url, credentials=creds, lp=lp_ctx)
59
60objectclasses = {}
61attributes = {}
62
63objectclasses_expanded = set()
64
65# the attributes we need for objectclasses
66class_attrs = ["objectClass",
67 "subClassOf",
68 "governsID",
69 "possSuperiors",
70 "possibleInferiors",
71 "mayContain",
72 "mustContain",
73 "auxiliaryClass",
74 "rDNAttID",
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",
90
91 # this attributes are not used by w2k3
92 "schemaFlagsEx",
93 "msDs-IntId",
94 "msDs-Schema-Extensions",
95 "classDisplayName",
96 "isDefunct"]
97
98attrib_attrs = ["objectClass",
99 "attributeID",
100 "attributeSyntax",
101 "isSingleValued",
102 "rangeLower",
103 "rangeUpper",
104 "mAPIID",
105 "linkID",
106 "adminDisplayName",
107 "oMObjectClass",
108 "adminDescription",
109 "oMSyntax",
110 "searchFlags",
111 "extendedCharsAllowed",
112 "lDAPDisplayName",
113 "schemaIDGUID",
114 "attributeSecurityGUID",
115 "systemOnly",
116 "systemFlags",
117 "isMemberOfPartialAttributeSet",
118 "objectCategory",
119
120 # this attributes are not used by w2k3
121 "schemaFlagsEx",
122 "msDs-IntId",
123 "msDs-Schema-Extensions",
124 "classDisplayName",
125 "isEphemeral",
126 "isDefunct"]
127
128#
129# notes:
130#
131# objectClassCategory
132# 1: structural
133# 2: abstract
134# 3: auxiliary
135
136def get_object_cn(ldb, name):
137 attrs = ["cn"]
138 res = ldb.search(expression="(ldapDisplayName=%s)" % name, base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE, attrs=attrs)
139 assert len(res) == 1
140 return res[0]["cn"]
141
142
143class Objectclass(dict):
144
145 def __init__(self, ldb, name):
146 """create an objectclass object"""
147 self.name = name
148 self["cn"] = get_object_cn(ldb, name)
149
150
151class Attribute(dict):
152
153 def __init__(self, ldb, name):
154 """create an attribute object"""
155 self.name = name
156 self["cn"] = get_object_cn(ldb, name)
157
158
159syntaxmap = dict()
160
161syntaxmap['2.5.5.1'] = '1.3.6.1.4.1.1466.115.121.1.12'
162syntaxmap['2.5.5.2'] = '1.3.6.1.4.1.1466.115.121.1.38'
163syntaxmap['2.5.5.3'] = '1.2.840.113556.1.4.1362'
164syntaxmap['2.5.5.4'] = '1.2.840.113556.1.4.905'
165syntaxmap['2.5.5.5'] = '1.3.6.1.4.1.1466.115.121.1.26'
166syntaxmap['2.5.5.6'] = '1.3.6.1.4.1.1466.115.121.1.36'
167syntaxmap['2.5.5.7'] = '1.2.840.113556.1.4.903'
168syntaxmap['2.5.5.8'] = '1.3.6.1.4.1.1466.115.121.1.7'
169syntaxmap['2.5.5.9'] = '1.3.6.1.4.1.1466.115.121.1.27'
170syntaxmap['2.5.5.10'] = '1.3.6.1.4.1.1466.115.121.1.40'
171syntaxmap['2.5.5.11'] = '1.3.6.1.4.1.1466.115.121.1.24'
172syntaxmap['2.5.5.12'] = '1.3.6.1.4.1.1466.115.121.1.15'
173syntaxmap['2.5.5.13'] = '1.3.6.1.4.1.1466.115.121.1.43'
174syntaxmap['2.5.5.14'] = '1.2.840.113556.1.4.904'
175syntaxmap['2.5.5.15'] = '1.2.840.113556.1.4.907'
176syntaxmap['2.5.5.16'] = '1.2.840.113556.1.4.906'
177syntaxmap['2.5.5.17'] = '1.3.6.1.4.1.1466.115.121.1.40'
178
179
180def 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):
184 return syntaxmap[s]
185 return s
186
187
188def fix_dn(dn):
189 """fix a string DN to use ${SCHEMADN}"""
190 return dn.replace(rootDse["schemaNamingContext"][0], "${SCHEMADN}")
191
192
193def write_ldif_one(o, attrs):
194 """dump an object as ldif"""
195 print "dn: CN=%s,${SCHEMADN}" % 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 v = o[a]
201 for j in v:
202 value = fix_dn(j)
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))
207 else:
208 print "%s: %s" % (a, value)
209 print ""
210
211
212def 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)
216
217
218def 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(",")
222 a[0] = "CN=TestDN"
223 return ",".join(a)
224
225
226def find_objectclass_properties(ldb, o):
227 """the properties of an objectclass"""
228 res = ldb.search(
229 expression="(ldapDisplayName=%s)" % o.name,
230 base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE, attrs=class_attrs)
231 assert(len(res) == 1)
232 msg = res[0]
233 for a in msg:
234 o[a] = msg[a]
235
236def find_attribute_properties(ldb, o):
237 """find the properties of an attribute"""
238 res = ldb.search(
239 expression="(ldapDisplayName=%s)" % o.name,
240 base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE,
241 attrs=attrib_attrs)
242 assert(len(res) == 1)
243 msg = res[0]
244 for a in msg:
245 o[a] = msg[a]
246
247
248def 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"):
252 return
253 testdn = create_testdn(o.exampleDN)
254
255 print "testdn is '%s'" % testdn
256
257 ldif = "dn: " + testdn
258 ldif += "\nobjectClass: " + o.name
259 try:
260 ldb.add(ldif)
261 except LdbError, e:
262 print "error adding %s: %s" % (o.name, e)
263 print "%s" % ldif
264 return
265
266 res = ldb.search(base=testdn, scope=ldb.SCOPE_BASE)
267 ldb.delete(testdn)
268
269 for a in res.msgs[0]:
270 attributes[a].autocreate = True
271
272
273def 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",
278 "subClassOf"]
279 res = ldb.search(
280 expression="(&(objectClass=classSchema)(ldapDisplayName=%s))" % o.name,
281 base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE,
282 attrs=attrs)
283 print >>sys.stderr, "Expanding class %s" % o.name
284 assert(len(res) == 1)
285 msg = res[0]
286 for aname in attrs:
287 if not aname in msg:
288 continue
289 list = msg[aname]
290 if isinstance(list, str):
291 list = [msg[aname]]
292 for name in list:
293 if not objectclasses.has_key(name):
294 print >>sys.stderr, "Found new objectclass '%s'" % name
295 objectclasses[name] = Objectclass(ldb, name)
296
297
298def add_objectclass_attributes(ldb, objectclass):
299 """add the must and may attributes from an objectclass to the full list
300 of attributes"""
301 attrs = ["mustContain", "systemMustContain",
302 "mayContain", "systemMayContain"]
303 for aname in attrs:
304 if not objectclass.has_key(aname):
305 continue
306 alist = objectclass[aname]
307 if isinstance(alist, str):
308 alist = [alist]
309 for a in alist:
310 if not attributes.has_key(a):
311 attributes[a] = Attribute(ldb, a)
312
313
314def 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"]
318 try:
319 res = ldb.search("objectClass=*", dn, SCOPE_BASE, attrs)
320 except LdbError, e:
321 print >>sys.stderr, "Unable to fetch allowedAttributes for '%s' - %r" % (dn, e)
322 return
323 allattrs = res[0]["allowedAttributes"]
324 try:
325 res = ldb.search("objectClass=*", dn, SCOPE_BASE, allattrs)
326 except LdbError, e:
327 print >>sys.stderr, "Unable to fetch all attributes for '%s' - %s" % (dn, e)
328 return
329 msg = res[0]
330 for a in msg:
331 if not attributes.has_key(a):
332 attributes[a] = Attribute(ldb, a)
333
334def walk_naming_context(ldb, namingContext):
335 """walk a naming context, looking for all records"""
336 try:
337 res = ldb.search("objectClass=*", namingContext, SCOPE_DEFAULT,
338 ["objectClass"])
339 except LdbError, e:
340 print >>sys.stderr, "Unable to fetch objectClasses for '%s' - %s" % (namingContext, e)
341 return
342 for msg in res:
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)
349
350def 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"]
356 newpossinf = []
357 for x in possinf:
358 if objectclasses.has_key(x):
359 newpossinf.append(x)
360 objectclass["possibleInferiors"] = newpossinf
361
362 # trim systemMayContain,
363 # remove duplicates
364 if objectclass.has_key("systemMayContain"):
365 sysmay = objectclass["systemMayContain"]
366 newsysmay = []
367 for x in sysmay:
368 if not x in newsysmay:
369 newsysmay.append(x)
370 objectclass["systemMayContain"] = newsysmay
371
372 # trim mayContain,
373 # remove duplicates
374 if objectclass.has_key("mayContain"):
375 may = objectclass["mayContain"]
376 newmay = []
377 if isinstance(may, str):
378 may = [may]
379 for x in may:
380 if not x in newmay:
381 newmay.append(x)
382 objectclass["mayContain"] = newmay
383
384
385def build_objectclass(ldb, name):
386 """load the basic attributes of an objectClass"""
387 attrs = ["name"]
388 res = ldb.search(
389 expression="(&(objectClass=classSchema)(ldapDisplayName=%s))" % name,
390 base=rootDse["schemaNamingContext"][0], scope=SCOPE_SUBTREE,
391 attrs=attrs)
392 if len(res) == 0:
393 print >>sys.stderr, "unknown class '%s'" % name
394 return None
395 return Objectclass(ldb, name)
396
397
398def attribute_list(objectclass, attr1, attr2):
399 """form a coalesced attribute list"""
400 a1 = list(objectclass.get(attr1, []))
401 a2 = list(objectclass.get(attr2, []))
402 return a1 + a2
403
404def aggregate_list(name, list):
405 """write out a list in aggregate form"""
406 if list == []:
407 return ""
408 return " %s ( %s )" % (name, " $ ".join(list))
409
410def 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:
416 line += "STRUCTURAL"
417 elif objectclass["objectClassCategory"] == 2:
418 line += "ABSTRACT"
419 elif objectclass["objectClassCategory"] == 3:
420 line += "AUXILIARY"
421
422 list = attribute_list(objectclass, "systemMustContain", "mustContain")
423 line += aggregate_list("MUST", list)
424
425 list = attribute_list(objectclass, "systemMayContain", "mayContain")
426 line += aggregate_list("MAY", list)
427
428 print line + " )"
429
430
431def write_aggregate_ditcontentrule(objectclass):
432 """write the aggregate record for an ditcontentrule"""
433 list = attribute_list(objectclass, "auxiliaryClass", "systemAuxiliaryClass")
434 if list == []:
435 return
436
437 line = "dITContentRules: ( %s NAME '%s'" % (objectclass["governsID"], objectclass.name)
438
439 line += aggregate_list("AUX", list)
440
441 may_list = []
442 must_list = []
443
444 for c in 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
451
452 line += aggregate_list("MUST", must_list)
453 line += aggregate_list("MAY", may_list)
454
455 print line + " )"
456
457def 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 "
466
467 print line + ")"
468
469
470def write_aggregate():
471 """write the aggregate record"""
472 print "dn: CN=Aggregate,${SCHEMADN}"
473 print """objectClass: top
474objectClass: subSchema
475objectCategory: CN=SubSchema,${SCHEMADN}"""
476 if not opts.dump_subschema_auto:
477 return
478
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)
485
486def load_list(file):
487 """load a list from a file"""
488 return [l.strip("\n") for l in open(file, 'r').readlines()]
489
490# get the rootDSE
491res = ldb.search(base="", expression="", scope=SCOPE_BASE, attrs=["schemaNamingContext"])
492rootDse = res[0]
493
494# load the list of classes we are interested in
495classes = load_list(classfile)
496for classname in classes:
497 objectclass = build_objectclass(ldb, classname)
498 if objectclass is not None:
499 objectclasses[classname] = objectclass
500
501
502#
503# expand the objectclass list as needed
504#
505expanded = 0
506
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
510#
511for 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)
516
517#
518# find objectclass properties
519#
520for name, objectclass in objectclasses.items():
521 find_objectclass_properties(ldb, objectclass)
522
523
524#
525# form the full list of attributes
526#
527for name, objectclass in objectclasses.items():
528 add_objectclass_attributes(ldb, objectclass)
529
530# and attribute properties
531for name, attr in attributes.items():
532 find_attribute_properties(ldb, attr)
533
534#
535# trim the 'may' attribute lists to those really needed
536#
537for name, objectclass in objectclasses.items():
538 trim_objectclass_attributes(ldb, objectclass)
539
540#
541# dump an ldif form of the attributes and objectclasses
542#
543if opts.dump_attributes:
544 write_ldif(attributes, attrib_attrs)
545if opts.dump_classes:
546 write_ldif(objectclasses, class_attrs)
547if opts.dump_subschema:
548 write_aggregate()
549
550if not opts.verbose:
551 sys.exit(0)
552
553#
554# dump list of objectclasses
555#
556print "objectClasses:\n"
557for objectclass in objectclasses:
558 print "\t%s\n" % objectclass
559
560print "attributes:\n"
561for attr in attributes:
562 print "\t%s\n" % attr
563
564print "autocreated attributes:\n"
565for attr in attributes:
566 if attr.autocreate:
567 print "\t%s\n" % i
Note: See TracBrowser for help on using the repository browser.