#!/usr/bin/python # # work out the minimal schema for a set of objectclasses # import getopt import optparse import samba parser = optparse.OptionParser("minschema ") sambaopts = options.SambaOptions(parser) parser.add_option_group(sambaopts) credopts = options.CredentialsOptions(parser) parser.add_option_group(credopts) parser.add_option_group(options.VersionOptions(parser)) parser.add_option("--verbose", help="Be verbose", action="store_true") parser.add_option("--dump-classes", action="store_true") parser.add_option("--dump-attributes", action="store_true") parser.add_option("--dump-subschema", action="store_true") parser.add_option("--dump-subschema-auto", action="store_true") opts, args = parser.parse_args() opts.dump_all = True if opts.dump_classes: opts.dump_all = False if opts.dump_attributes: opts.dump_all = False if opts.dump_subschema: opts.dump_all = False if dump_subschema_auto: opts.dump_all = False opts.dump_subschema = True if opts.dump_all: opts.dump_classes = True opts.dump_attributes = True opts.dump_subschema = True opts.dump_subschema_auto = True if len(args) != 2: parser.print_usage() sys.exit(1) (url, classfile) = args creds = credopts.get_credentials() ldb = Ldb(url, credentials=creds) objectclasses = [] attributes = [] rootDse = {} objectclasses_expanded = [] # the attributes we need for objectclasses class_attrs = ["objectClass", "subClassOf", "governsID", "possSuperiors", "possibleInferiors", "mayContain", "mustContain", "auxiliaryClass", "rDNAttID", "showInAdvancedViewOnly", "adminDisplayName", "adminDescription", "objectClassCategory", "lDAPDisplayName", "schemaIDGUID", "systemOnly", "systemPossSuperiors", "systemMayContain", "systemMustContain", "systemAuxiliaryClass", "defaultSecurityDescriptor", "systemFlags", "defaultHidingValue", "objectCategory", "defaultObjectCategory", # this attributes are not used by w2k3 "schemaFlagsEx", "msDs-IntId", "msDs-Schema-Extensions", "classDisplayName", "isDefunct"] attrib_attrs = ["objectClass", "attributeID", "attributeSyntax", "isSingleValued", "rangeLower", "rangeUpper", "mAPIID", "linkID", "showInAdvancedViewOnly", "adminDisplayName", "oMObjectClass", "adminDescription", "oMSyntax", "searchFlags", "extendedCharsAllowed", "lDAPDisplayName", "schemaIDGUID", "attributeSecurityGUID", "systemOnly", "systemFlags", "isMemberOfPartialAttributeSet", "objectCategory", # this attributes are not used by w2k3 "schemaFlagsEx", "msDs-IntId", "msDs-Schema-Extensions", "classDisplayName", "isEphemeral", "isDefunct"] # # notes: # # objectClassCategory # 1: structural # 2: abstract # 3: auxiliary # # print only if verbose is set # def dprintf(text): if verbose is not None: print text def get_object_cn(ldb, name): attrs = ["cn"] res = ldb.search("(ldapDisplayName=%s)" % name, rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, attrs) assert len(res) == 1 cn = res[0]["cn"] # # create an objectclass object # def obj_objectClass(ldb, name): var o = new Object() o.name = name o.cn = get_object_cn(ldb, name) return o # # create an attribute object # def obj_attribute(ldb, name): var o = new Object() o.name = name o.cn = get_object_cn(ldb, name) return o syntaxmap = dict() syntaxmap['2.5.5.1'] = '1.3.6.1.4.1.1466.115.121.1.12' syntaxmap['2.5.5.2'] = '1.3.6.1.4.1.1466.115.121.1.38' syntaxmap['2.5.5.3'] = '1.2.840.113556.1.4.1362' syntaxmap['2.5.5.4'] = '1.2.840.113556.1.4.905' syntaxmap['2.5.5.5'] = '1.3.6.1.4.1.1466.115.121.1.26' syntaxmap['2.5.5.6'] = '1.3.6.1.4.1.1466.115.121.1.36' syntaxmap['2.5.5.7'] = '1.2.840.113556.1.4.903' syntaxmap['2.5.5.8'] = '1.3.6.1.4.1.1466.115.121.1.7' syntaxmap['2.5.5.9'] = '1.3.6.1.4.1.1466.115.121.1.27' syntaxmap['2.5.5.10'] = '1.3.6.1.4.1.1466.115.121.1.40' syntaxmap['2.5.5.11'] = '1.3.6.1.4.1.1466.115.121.1.24' syntaxmap['2.5.5.12'] = '1.3.6.1.4.1.1466.115.121.1.15' syntaxmap['2.5.5.13'] = '1.3.6.1.4.1.1466.115.121.1.43' syntaxmap['2.5.5.14'] = '1.2.840.113556.1.4.904' syntaxmap['2.5.5.15'] = '1.2.840.113556.1.4.907' syntaxmap['2.5.5.16'] = '1.2.840.113556.1.4.906' syntaxmap['2.5.5.17'] = '1.3.6.1.4.1.1466.115.121.1.40' # # map some attribute syntaxes from some apparently MS specific # syntaxes to the standard syntaxes # def map_attribute_syntax(s): if syntaxmap.has_key(s): return syntaxmap[s] return s # # fix a string DN to use ${SCHEMADN} # def fix_dn(dn): s = strstr(dn, rootDse.schemaNamingContext) if (s == NULL) { return dn } return substr(dn, 0, strlen(dn) - strlen(s)) + "${SCHEMADN}" # # dump an object as ldif # def write_ldif_one(o, attrs): print "dn: CN=%s,${SCHEMADN}\n" % o["cn"] for a in attrs: if not o.has_key(a): continue # special case for oMObjectClass, which is a binary object if a == "oMObjectClass": print "%s:: %s\n" % (a, o[a]) continue v = o[a] if isinstance(v, str): v = [v] for j in v: print "%s: %s\n" % (a, fix_dn(j)) print "\n" # # dump an array of objects as ldif # def write_ldif(o, attrs): for i in o: write_ldif_one(i, attrs) # # create a testDN based an an example DN # the idea is to ensure we obey any structural rules # def create_testdn(exampleDN): a = split(",", exampleDN) a[0] = "CN=TestDN" return ",".join(a) # # find the properties of an objectclass # def find_objectclass_properties(ldb, o): res = ldb.search( expression="(ldapDisplayName=%s)" % o.name, rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, class_attrs) assert(len(res) == 1) msg = res[0] for a in msg: o[a] = msg[a] # # find the properties of an attribute # def find_attribute_properties(ldb, o): res = ldb.search( expression="(ldapDisplayName=%s)" % o.name, rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, attrib_attrs) assert(len(res) == 1) msg = res[0] for a in msg: # special case for oMObjectClass, which is a binary object if a == "oMObjectClass": o[a] = ldb.encode(msg[a]) continue o[a] = msg[a] # # find the auto-created properties of an objectclass. Only works for classes # that can be created using just a DN and the objectclass # def find_objectclass_auto(ldb, o): if not o.has_key("exampleDN"): return testdn = create_testdn(o.exampleDN) print "testdn is '%s'\n" % testdn ldif = "dn: " + testdn ldif += "\nobjectClass: " + o.name try: ldb.add(ldif) except LdbError, e: print "error adding %s: %s\n" % (o.name, e) print "%s\n" % ldif return res = ldb.search("", testdn, ldb.SCOPE_BASE) ldb.delete(testdn) for a in res.msgs[0]: attributes[a].autocreate = True # # look at auxiliary information from a class to intuit the existance of more # classes needed for a minimal schema # def expand_objectclass(ldb, o): attrs = ["auxiliaryClass", "systemAuxiliaryClass", "possSuperiors", "systemPossSuperiors", "subClassOf"] res = ldb.search( "(&(objectClass=classSchema)(ldapDisplayName=%s))" % o.name, rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, attrs) print "Expanding class %s\n" % o.name assert(len(res) == 1) msg = res[0] for a in attrs: if not msg.has_key(aname): continue list = msg[aname] if isinstance(list, str): list = [msg[aname]] for name in list: if not objectclasses.has_key(name): print "Found new objectclass '%s'\n" % name objectclasses[name] = obj_objectClass(ldb, name) # # add the must and may attributes from an objectclass to the full list # of attributes # def add_objectclass_attributes(ldb, class): attrs = ["mustContain", "systemMustContain", "mayContain", "systemMayContain"] for aname in attrs: if not class.has_key(aname): continue alist = class[aname] if isinstance(alist, str): alist = [alist] for a in alist: if not attributes.has_key(a): attributes[a] = obj_attribute(ldb, a) # # process an individual record, working out what attributes it has # def walk_dn(ldb, dn): # get a list of all possible attributes for this object attrs = ["allowedAttributes"] try: res = ldb.search("objectClass=*", dn, ldb.SCOPE_BASE, attrs) except LdbError, e: print "Unable to fetch allowedAttributes for '%s' - %r\n" % (dn, e) return allattrs = res[0]["allowedAttributes"] try: res = ldb.search("objectClass=*", dn, ldb.SCOPE_BASE, allattrs) except LdbError, e: print "Unable to fetch all attributes for '%s' - %s\n" % (dn, e) return msg = res[0] for a in msg: if not attributes.has_key(a): attributes[a] = obj_attribute(ldb, a) # # walk a naming context, looking for all records # def walk_naming_context(ldb, namingContext): try: res = ldb.search("objectClass=*", namingContext, ldb.SCOPE_DEFAULT, ["objectClass"]) except LdbError, e: print "Unable to fetch objectClasses for '%s' - %s\n" % (namingContext, e) return for msg in res: msg = res.msgs[r]["objectClass"] for objectClass in msg: if not objectclasses.has_key(objectClass): objectclasses[objectClass] = obj_objectClass(ldb, objectClass) objectclasses[objectClass].exampleDN = res.msgs[r]["dn"] walk_dn(ldb, res.msgs[r].dn) # # trim the may attributes for an objectClass # def trim_objectclass_attributes(ldb, class): # trim possibleInferiors, # include only the classes we extracted if class.has_key("possibleInferiors"): possinf = class["possibleInferiors"] newpossinf = [] if isinstance(possinf, str): possinf = [possinf] for x in possinf: if objectclasses.has_key(x): newpossinf[n] = x n++ class["possibleInferiors"] = newpossinf # trim systemMayContain, # remove duplicates if class.has_key("systemMayContain"): sysmay = class["systemMayContain"] newsysmay = [] if isinstance(sysmay, str): sysmay = [sysmay] for x in sysmay: dup = False if newsysmay[0] == undefined) { newsysmay[0] = x else: for (n = 0; n < newsysmay.length; n++) { if (newsysmay[n] == x) { dup = True if not dup: newsysmay[n] = x class["systemMayContain"] = newsysmay # trim mayContain, # remove duplicates if not class.has_key("mayContain"): may = class["mayContain"] newmay = [] if isinstance(may, str): may = [may] for x in may: dup = False if (newmay[0] == undefined) { newmay[0] = x } else { for (n = 0; n < newmay.length; n++) { if (newmay[n] == x) { dup = True if not dup: newmay[n] = x class["mayContain"] = newmay # # load the basic attributes of an objectClass # def build_objectclass(ldb, name): attrs = ["name"] try: res = ldb.search( expression="(&(objectClass=classSchema)(ldapDisplayName=%s))" % name, rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, attrs) except LdbError, e: print "unknown class '%s'\n" % name return None if len(res) == 0: print "unknown class '%s'\n" % name return None return obj_objectClass(ldb, name) # # append 2 lists # def list_append(a1, a2): if (a1 == undefined) { return a2 if (a2 == undefined) return a1 for (i=0;i