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

Last change on this file was 988, checked in by Silvan Scherrer, 9 years ago

Samba Server: update vendor to version 4.4.3

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