1#!/usr/bin/env python 2# 3# create schema.ldif (as a string) from WSPP documentation 4# 5# based on minschema.py and minschema_wspp 6# 7 8import re 9import base64 10 11bitFields = {} 12 13# ADTS: 2.2.9 14# bit positions as labeled in the docs 15bitFields["searchflags"] = { 16 'fATTINDEX': 31, # IX 17 'fPDNTATTINDEX': 30, # PI 18 'fANR': 29, #AR 19 'fPRESERVEONDELETE': 28, # PR 20 'fCOPY': 27, # CP 21 'fTUPLEINDEX': 26, # TP 22 'fSUBTREEATTINDEX': 25, # ST 23 'fCONFIDENTIAL': 24, # CF 24 'fNEVERVALUEAUDIT': 23, # NV 25 'fRODCAttribute': 22, # RO 26 27 28 # missing in ADTS but required by LDIF 29 'fRODCFilteredAttribute': 22, # RO ? 30 'fCONFIDENTAIL': 24, # typo 31 'fRODCFILTEREDATTRIBUTE': 22 # case 32 } 33 34# ADTS: 2.2.10 35bitFields["systemflags"] = { 36 'FLAG_ATTR_NOT_REPLICATED': 31, 'FLAG_CR_NTDS_NC': 31, # NR 37 'FLAG_ATTR_REQ_PARTIAL_SET_MEMBER': 30, 'FLAG_CR_NTDS_DOMAIN': 30, # PS 38 'FLAG_ATTR_IS_CONSTRUCTED': 29, 'FLAG_CR_NTDS_NOT_GC_REPLICATED': 29, # CS 39 'FLAG_ATTR_IS_OPERATIONAL': 28, # OP 40 'FLAG_SCHEMA_BASE_OBJECT': 27, # BS 41 'FLAG_ATTR_IS_RDN': 26, # RD 42 'FLAG_DISALLOW_MOVE_ON_DELETE': 6, # DE 43 'FLAG_DOMAIN_DISALLOW_MOVE': 5, # DM 44 'FLAG_DOMAIN_DISALLOW_RENAME': 4, # DR 45 'FLAG_CONFIG_ALLOW_LIMITED_MOVE': 3, # AL 46 'FLAG_CONFIG_ALLOW_MOVE': 2, # AM 47 'FLAG_CONFIG_ALLOW_RENAME': 1, # AR 48 'FLAG_DISALLOW_DELETE': 0 # DD 49 } 50 51# ADTS: 2.2.11 52bitFields["schemaflagsex"] = { 53 'FLAG_ATTR_IS_CRITICAL': 31 54 } 55 56# ADTS: 3.1.1.2.2.2 57oMObjectClassBER = { 58 '1.3.12.2.1011.28.0.702' : base64.b64encode('\x2B\x0C\x02\x87\x73\x1C\x00\x85\x3E'), 59 '1.2.840.113556.1.1.1.12': base64.b64encode('\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x0C'), 60 '2.6.6.1.2.5.11.29' : base64.b64encode('\x56\x06\x01\x02\x05\x0B\x1D'), 61 '1.2.840.113556.1.1.1.11': base64.b64encode('\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x0B'), 62 '1.3.12.2.1011.28.0.714' : base64.b64encode('\x2B\x0C\x02\x87\x73\x1C\x00\x85\x4A'), 63 '1.3.12.2.1011.28.0.732' : base64.b64encode('\x2B\x0C\x02\x87\x73\x1C\x00\x85\x5C'), 64 '1.2.840.113556.1.1.1.6' : base64.b64encode('\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x06') 65} 66 67# separated by commas in docs, and must be broken up 68multivalued_attrs = set(["auxiliaryclass","maycontain","mustcontain","posssuperiors", 69 "systemauxiliaryclass","systemmaycontain","systemmustcontain", 70 "systemposssuperiors"]) 71 72def __read_folded_line(f, buffer): 73 """ reads a line from an LDIF file, unfolding it""" 74 line = buffer 75 76 while True: 77 l = f.readline() 78 79 if l[:1] == " ": 80 # continued line 81 82 # cannot fold an empty line 83 assert(line != "" and line != "\n") 84 85 # preserves '\n ' 86 line = line + l 87 else: 88 # non-continued line 89 if line == "": 90 line = l 91 92 if l == "": 93 # eof, definitely won't be folded 94 break 95 else: 96 # marks end of a folded line 97 # line contains the now unfolded line 98 # buffer contains the start of the next possibly folded line 99 buffer = l 100 break 101 102 return (line, buffer) 103 104 105def __read_raw_entries(f): 106 """reads an LDIF entry, only unfolding lines""" 107 108 # will not match options after the attribute type 109 attr_type_re = re.compile("^([A-Za-z]+[A-Za-z0-9-]*):") 110 111 buffer = "" 112 113 while True: 114 entry = [] 115 116 while True: 117 (l, buffer) = __read_folded_line(f, buffer) 118 119 if l[:1] == "#": 120 continue 121 122 if l == "\n" or l == "": 123 break 124 125 m = attr_type_re.match(l) 126 127 if m: 128 if l[-1:] == "\n": 129 l = l[:-1] 130 131 entry.append(l) 132 else: 133 print >>sys.stderr, "Invalid line: %s" % l, 134 sys.exit(1) 135 136 if len(entry): 137 yield entry 138 139 if l == "": 140 break 141 142 143def fix_dn(dn): 144 """fix a string DN to use ${SCHEMADN}""" 145 146 # folding? 147 if dn.find("<RootDomainDN>") != -1: 148 dn = dn.replace("\n ", "") 149 dn = dn.replace(" ", "") 150 return dn.replace("CN=Schema,CN=Configuration,<RootDomainDN>", "${SCHEMADN}") 151 else: 152 return dn 153 154def __convert_bitfield(key, value): 155 """Evaluate the OR expression in 'value'""" 156 assert(isinstance(value, str)) 157 158 value = value.replace("\n ", "") 159 value = value.replace(" ", "") 160 161 try: 162 # some attributes already have numeric values 163 o = int(value) 164 except ValueError: 165 o = 0 166 flags = value.split("|") 167 for f in flags: 168 bitpos = bitFields[key][f] 169 o = o | (1 << (31 - bitpos)) 170 171 return str(o) 172 173def __write_ldif_one(entry): 174 """Write out entry as LDIF""" 175 out = [] 176 177 for l in entry: 178 if isinstance(l[1], str): 179 vl = [l[1]] 180 else: 181 vl = l[1] 182 183 if l[0].lower() == 'omobjectclass': 184 out.append("%s:: %s" % (l[0], l[1])) 185 continue 186 187 for v in vl: 188 out.append("%s: %s" % (l[0], v)) 189 190 191 return "\n".join(out) 192 193def __transform_entry(entry, objectClass): 194 """Perform transformations required to convert the LDIF-like schema 195 file entries to LDIF, including Samba-specific stuff.""" 196 197 entry = [l.split(":", 1) for l in entry] 198 199 cn = "" 200 201 for l in entry: 202 key = l[0].lower() 203 l[1] = l[1].lstrip() 204 l[1] = l[1].rstrip() 205 206 if not cn and key == "cn": 207 cn = l[1] 208 209 if key in multivalued_attrs: 210 # unlike LDIF, these are comma-separated 211 l[1] = l[1].replace("\n ", "") 212 l[1] = l[1].replace(" ", "") 213 214 l[1] = l[1].split(",") 215 216 if key in bitFields: 217 l[1] = __convert_bitfield(key, l[1]) 218 219 if key == "omobjectclass": 220 l[1] = oMObjectClassBER[l[1].strip()] 221 222 if isinstance(l[1], str): 223 l[1] = fix_dn(l[1]) 224 225 226 assert(cn) 227 entry.insert(0, ["dn", "CN=%s,${SCHEMADN}" % cn]) 228 entry.insert(1, ["objectClass", ["top", objectClass]]) 229 entry.insert(2, ["cn", cn]) 230 231 for l in entry: 232 key = l[0].lower() 233 234 if key == "cn": 235 entry.remove(l) 236 237 return entry 238 239def __parse_schema_file(filename, objectClass): 240 """Load and transform a schema file.""" 241 242 out = [] 243 244 f = open(filename, "rU") 245 for entry in __read_raw_entries(f): 246 out.append(__write_ldif_one(__transform_entry(entry, objectClass))) 247 248 return "\n\n".join(out) 249 250 251def read_ms_schema(attr_file, classes_file, dump_attributes = True, dump_classes = True, debug = False): 252 """Read WSPP documentation-derived schema files.""" 253 254 attr_ldif = "" 255 classes_ldif = "" 256 257 if dump_attributes: 258 attr_ldif = __parse_schema_file(attr_file, "attributeSchema") 259 if dump_classes: 260 classes_ldif = __parse_schema_file(classes_file, "classSchema") 261 262 return attr_ldif + "\n\n" + classes_ldif + "\n\n" 263 264if __name__ == '__main__': 265 import sys 266 267 try: 268 attr_file = sys.argv[1] 269 classes_file = sys.argv[2] 270 except IndexError: 271 print >>sys.stderr, "Usage: %s attr-file.txt classes-file.txt" % (sys.argv[0]) 272 sys.exit(1) 273 274 print read_ms_schema(attr_file, classes_file) 275 276 277