1 # create schema.ldif (as a string) from WSPP documentation
3 # based on minschema.py and minschema_wspp
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 from __future__ import print_function
19 """Generate LDIF from WSPP documentation."""
28 # bit positions as labeled in the docs
29 bitFields["searchflags"] = {
31 'fPDNTATTINDEX': 30, # PI
33 'fPRESERVEONDELETE': 28, # PR
35 'fTUPLEINDEX': 26, # TP
36 'fSUBTREEATTINDEX': 25, # ST
37 'fCONFIDENTIAL': 24, # CF
38 'fNEVERVALUEAUDIT': 23, # NV
39 'fRODCAttribute': 22, # RO
42 # missing in ADTS but required by LDIF
43 'fRODCFilteredAttribute': 22, # RO ?
44 'fCONFIDENTAIL': 24, # typo
45 'fRODCFILTEREDATTRIBUTE': 22 # case
49 bitFields["systemflags"] = {
50 'FLAG_ATTR_NOT_REPLICATED': 31, 'FLAG_CR_NTDS_NC': 31, # NR
51 'FLAG_ATTR_REQ_PARTIAL_SET_MEMBER': 30, 'FLAG_CR_NTDS_DOMAIN': 30, # PS
52 'FLAG_ATTR_IS_CONSTRUCTED': 29, 'FLAG_CR_NTDS_NOT_GC_REPLICATED': 29, # CS
53 'FLAG_ATTR_IS_OPERATIONAL': 28, # OP
54 'FLAG_SCHEMA_BASE_OBJECT': 27, # BS
55 'FLAG_ATTR_IS_RDN': 26, # RD
56 'FLAG_DISALLOW_MOVE_ON_DELETE': 6, # DE
57 'FLAG_DOMAIN_DISALLOW_MOVE': 5, # DM
58 'FLAG_DOMAIN_DISALLOW_RENAME': 4, # DR
59 'FLAG_CONFIG_ALLOW_LIMITED_MOVE': 3, # AL
60 'FLAG_CONFIG_ALLOW_MOVE': 2, # AM
61 'FLAG_CONFIG_ALLOW_RENAME': 1, # AR
62 'FLAG_DISALLOW_DELETE': 0 # DD
66 bitFields["schemaflagsex"] = {
67 'FLAG_ATTR_IS_CRITICAL': 31
72 '1.3.12.2.1011.28.0.702' : base64.b64encode(b'\x2B\x0C\x02\x87\x73\x1C\x00\x85\x3E').decode('utf8'),
73 '1.2.840.113556.1.1.1.12': base64.b64encode(b'\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x0C').decode('utf8'),
74 '2.6.6.1.2.5.11.29' : base64.b64encode(b'\x56\x06\x01\x02\x05\x0B\x1D').decode('utf8'),
75 '1.2.840.113556.1.1.1.11': base64.b64encode(b'\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x0B').decode('utf8'),
76 '1.3.12.2.1011.28.0.714' : base64.b64encode(b'\x2B\x0C\x02\x87\x73\x1C\x00\x85\x4A').decode('utf8'),
77 '1.3.12.2.1011.28.0.732' : base64.b64encode(b'\x2B\x0C\x02\x87\x73\x1C\x00\x85\x5C').decode('utf8'),
78 '1.2.840.113556.1.1.1.6' : base64.b64encode(b'\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x06').decode('utf8')
81 # separated by commas in docs, and must be broken up
82 multivalued_attrs = set(["auxiliaryclass","maycontain","mustcontain","posssuperiors",
83 "systemauxiliaryclass","systemmaycontain","systemmustcontain",
84 "systemposssuperiors"])
86 def __read_folded_line(f, buffer):
87 """ reads a line from an LDIF file, unfolding it"""
96 # cannot fold an empty line
97 assert(line != "" and line != "\n")
107 # eof, definitely won't be folded
110 # marks end of a folded line
111 # line contains the now unfolded line
112 # buffer contains the start of the next possibly folded line
116 return (line, buffer)
119 def __read_raw_entries(f):
120 """reads an LDIF entry, only unfolding lines"""
123 # will not match options after the attribute type
124 attr_type_re = re.compile("^([A-Za-z]+[A-Za-z0-9-]*):")
132 (l, buffer) = __read_folded_line(f, buffer)
137 if l == "\n" or l == "":
140 m = attr_type_re.match(l)
148 print("Invalid line: %s" % l, end=' ', file=sys.stderr)
159 """fix a string DN to use ${SCHEMADN}"""
162 if dn.find("<RootDomainDN>") != -1:
163 dn = dn.replace("\n ", "")
164 dn = dn.replace(" ", "")
165 return dn.replace("CN=Schema,CN=Configuration,<RootDomainDN>", "${SCHEMADN}")
166 elif dn.endswith("DC=X"):
167 return dn.replace("CN=Schema,CN=Configuration,DC=X", "${SCHEMADN}")
168 elif dn.endswith("CN=X"):
169 return dn.replace("CN=Schema,CN=Configuration,CN=X", "${SCHEMADN}")
173 def __convert_bitfield(key, value):
174 """Evaluate the OR expression in 'value'"""
175 assert(isinstance(value, str))
177 value = value.replace("\n ", "")
178 value = value.replace(" ", "")
181 # some attributes already have numeric values
185 flags = value.split("|")
187 bitpos = bitFields[key][f]
188 o = o | (1 << (31 - bitpos))
192 def __write_ldif_one(entry):
193 """Write out entry as LDIF"""
197 if isinstance(l[1], str):
203 out.append("%s:: %s" % (l[0], l[1]))
207 out.append("%s: %s" % (l[0], v))
210 return "\n".join(out)
212 def __transform_entry(entry, objectClass):
213 """Perform transformations required to convert the LDIF-like schema
214 file entries to LDIF, including Samba-specific stuff."""
216 entry = [l.split(":", 1) for l in entry]
219 skip_dn = skip_objectclass = skip_admin_description = skip_admin_display_name = False
222 if l[1].startswith(': '):
232 if not cn and key == "cn":
235 if key in multivalued_attrs:
236 # unlike LDIF, these are comma-separated
237 l[1] = l[1].replace("\n ", "")
238 l[1] = l[1].replace(" ", "")
240 l[1] = l[1].split(",")
243 l[1] = __convert_bitfield(key, l[1])
245 if key == "omobjectclass":
247 l[1] = oMObjectClassBER[l[1].strip()]
250 if isinstance(l[1], str):
257 if key == 'objectclass':
258 skip_objectclass = True
259 elif key == 'admindisplayname':
260 skip_admin_display_name = True
261 elif key == 'admindescription':
262 skip_admin_description = True
268 header.append(["dn", "CN=%s,${SCHEMADN}" % cn, False])
270 header.append(["dn", dn, False])
272 if not skip_objectclass:
273 header.append(["objectClass", ["top", objectClass], False])
274 if not skip_admin_description:
275 header.append(["adminDescription", cn, False])
276 if not skip_admin_display_name:
277 header.append(["adminDisplayName", cn, False])
279 header.append(["objectGUID", str(uuid.uuid4()), False])
281 entry = header + [x for x in entry if x[0].lower() not in set(['dn', 'changetype', 'objectcategory'])]
285 def __parse_schema_file(filename, objectClass):
286 """Load and transform a schema file."""
290 f = open(filename, "rU")
291 for entry in __read_raw_entries(f):
292 out.append(__write_ldif_one(__transform_entry(entry, objectClass)))
294 return "\n\n".join(out)
297 def read_ms_schema(attr_file, classes_file, dump_attributes = True, dump_classes = True, debug = False):
298 """Read WSPP documentation-derived schema files."""
304 attr_ldif = __parse_schema_file(attr_file, "attributeSchema")
306 classes_ldif = __parse_schema_file(classes_file, "classSchema")
308 return attr_ldif + "\n\n" + classes_ldif + "\n\n"
310 if __name__ == '__main__':
314 attr_file = sys.argv[1]
315 classes_file = sys.argv[2]
317 print("Usage: %s attr-file.txt classes-file.txt" % (sys.argv[0]), file=sys.stderr)
320 print(read_ms_schema(attr_file, classes_file))