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 """Generate LDIF from WSPP documentation."""
27 # bit positions as labeled in the docs
28 bitFields
["searchflags"] = {
30 'fPDNTATTINDEX': 30, # PI
32 'fPRESERVEONDELETE': 28, # PR
34 'fTUPLEINDEX': 26, # TP
35 'fSUBTREEATTINDEX': 25, # ST
36 'fCONFIDENTIAL': 24, # CF
37 'fCONFIDENTAIL': 24, # typo
38 'fNEVERVALUEAUDIT': 23, # NV
39 'fRODCAttribute': 22, # RO
42 # missing in ADTS but required by LDIF
43 'fRODCFilteredAttribute': 22, # RO
44 'fRODCFILTEREDATTRIBUTE': 22, # case
45 'fEXTENDEDLINKTRACKING': 21, # XL
47 'fPARTITIONSECRET': 19, # SE
51 bitFields
["systemflags"] = {
52 'FLAG_ATTR_NOT_REPLICATED': 31, 'FLAG_CR_NTDS_NC': 31, # NR
53 'FLAG_ATTR_REQ_PARTIAL_SET_MEMBER': 30, 'FLAG_CR_NTDS_DOMAIN': 30, # PS
54 'FLAG_ATTR_IS_CONSTRUCTED': 29, 'FLAG_CR_NTDS_NOT_GC_REPLICATED': 29, # CS
55 'FLAG_ATTR_IS_OPERATIONAL': 28, # OP
56 'FLAG_SCHEMA_BASE_OBJECT': 27, # BS
57 'FLAG_ATTR_IS_RDN': 26, # RD
58 'FLAG_DISALLOW_MOVE_ON_DELETE': 6, # DE
59 'FLAG_DOMAIN_DISALLOW_MOVE': 5, # DM
60 'FLAG_DOMAIN_DISALLOW_RENAME': 4, # DR
61 'FLAG_CONFIG_ALLOW_LIMITED_MOVE': 3, # AL
62 'FLAG_CONFIG_ALLOW_MOVE': 2, # AM
63 'FLAG_CONFIG_ALLOW_RENAME': 1, # AR
64 'FLAG_DISALLOW_DELETE': 0 # DD
68 bitFields
["schemaflagsex"] = {
69 'FLAG_ATTR_IS_CRITICAL': 31
74 '1.3.12.2.1011.28.0.702': base64
.b64encode(b
'\x2B\x0C\x02\x87\x73\x1C\x00\x85\x3E').decode('utf8'),
75 '1.2.840.113556.1.1.1.12': base64
.b64encode(b
'\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x0C').decode('utf8'),
76 '2.6.6.1.2.5.11.29': base64
.b64encode(b
'\x56\x06\x01\x02\x05\x0B\x1D').decode('utf8'),
77 '1.2.840.113556.1.1.1.11': base64
.b64encode(b
'\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x0B').decode('utf8'),
78 '1.3.12.2.1011.28.0.714': base64
.b64encode(b
'\x2B\x0C\x02\x87\x73\x1C\x00\x85\x4A').decode('utf8'),
79 '1.3.12.2.1011.28.0.732': base64
.b64encode(b
'\x2B\x0C\x02\x87\x73\x1C\x00\x85\x5C').decode('utf8'),
80 '1.2.840.113556.1.1.1.6': base64
.b64encode(b
'\x2A\x86\x48\x86\xF7\x14\x01\x01\x01\x06').decode('utf8')
83 # separated by commas in docs, and must be broken up
84 multivalued_attrs
= set(["auxiliaryclass", "maycontain", "mustcontain", "posssuperiors",
85 "systemauxiliaryclass", "systemmaycontain", "systemmustcontain",
86 "systemposssuperiors"])
89 def __read_folded_line(f
, buffer):
90 """ reads a line from an LDIF file, unfolding it"""
99 # cannot fold an empty line
100 assert(line
!= "" and line
!= "\n")
110 # eof, definitely won't be folded
113 # marks end of a folded line
114 # line contains the now unfolded line
115 # buffer contains the start of the next possibly folded line
119 return (line
, buffer)
122 def __read_raw_entries(f
):
123 """reads an LDIF entry, only unfolding lines"""
126 # will not match options after the attribute type
127 attr_type_re
= re
.compile("^([A-Za-z]+[A-Za-z0-9-]*):")
135 (l
, buffer) = __read_folded_line(f
, buffer)
140 if l
== "\n" or l
== "":
143 m
= attr_type_re
.match(l
)
151 print("Invalid line: %s" % l
, end
=' ', file=sys
.stderr
)
162 """fix a string DN to use ${SCHEMADN}"""
165 if dn
.find("<RootDomainDN>") != -1:
166 dn
= dn
.replace("\n ", "")
167 dn
= dn
.replace(" ", "")
168 return dn
.replace("CN=Schema,CN=Configuration,<RootDomainDN>", "${SCHEMADN}")
169 elif dn
.endswith("DC=X"):
170 return dn
.replace("CN=Schema,CN=Configuration,DC=X", "${SCHEMADN}")
171 elif dn
.endswith("CN=X"):
172 return dn
.replace("CN=Schema,CN=Configuration,CN=X", "${SCHEMADN}")
177 def __convert_bitfield(key
, value
):
178 """Evaluate the OR expression in 'value'"""
179 assert(isinstance(value
, str))
181 value
= value
.replace("\n ", "")
182 value
= value
.replace(" ", "")
185 # some attributes already have numeric values
189 flags
= value
.split("|")
191 bitpos
= bitFields
[key
][f
]
192 o
= o |
(1 << (31 - bitpos
))
197 def __write_ldif_one(entry
):
198 """Write out entry as LDIF"""
202 if isinstance(l
[1], str):
208 out
.append("%s:: %s" % (l
[0], l
[1]))
212 out
.append("%s: %s" % (l
[0], v
))
214 return "\n".join(out
)
217 def __transform_entry(entry
, objectClass
):
218 """Perform transformations required to convert the LDIF-like schema
219 file entries to LDIF, including Samba-specific stuff."""
221 entry
= [l
.split(":", 1) for l
in entry
]
224 skip_dn
= skip_objectclass
= skip_admin_description
= skip_admin_display_name
= False
227 if l
[1].startswith(': '):
237 if not cn
and key
== "cn":
240 if key
in multivalued_attrs
:
241 # unlike LDIF, these are comma-separated
242 l
[1] = l
[1].replace("\n ", "")
243 l
[1] = l
[1].replace(" ", "")
245 l
[1] = l
[1].split(",")
248 l
[1] = __convert_bitfield(key
, l
[1])
250 if key
== "omobjectclass":
252 l
[1] = oMObjectClassBER
[l
[1].strip()]
255 if isinstance(l
[1], str):
262 if key
== 'objectclass':
263 skip_objectclass
= True
264 elif key
== 'admindisplayname':
265 skip_admin_display_name
= True
266 elif key
== 'admindescription':
267 skip_admin_description
= True
273 header
.append(["dn", "CN=%s,${SCHEMADN}" % cn
, False])
275 header
.append(["dn", dn
, False])
277 if not skip_objectclass
:
278 header
.append(["objectClass", ["top", objectClass
], False])
279 if not skip_admin_description
:
280 header
.append(["adminDescription", cn
, False])
281 if not skip_admin_display_name
:
282 header
.append(["adminDisplayName", cn
, False])
284 header
.append(["objectGUID", str(uuid
.uuid4()), False])
286 entry
= header
+ [x
for x
in entry
if x
[0].lower() not in set(['dn', 'changetype', 'objectcategory'])]
291 def __parse_schema_file(filename
, objectClass
):
292 """Load and transform a schema file."""
297 with
open(filename
, "r", encoding
='latin-1') as f
:
298 for entry
in __read_raw_entries(f
):
299 out
.append(__write_ldif_one(__transform_entry(entry
, objectClass
)))
301 return "\n\n".join(out
)
304 def read_ms_schema(attr_file
, classes_file
, dump_attributes
=True, dump_classes
=True, debug
=False):
305 """Read WSPP documentation-derived schema files."""
311 attr_ldif
= __parse_schema_file(attr_file
, "attributeSchema")
313 classes_ldif
= __parse_schema_file(classes_file
, "classSchema")
315 return attr_ldif
+ "\n\n" + classes_ldif
+ "\n\n"
318 if __name__
== '__main__':
322 attr_file
= sys
.argv
[1]
323 classes_file
= sys
.argv
[2]
325 print("Usage: %s attr-file.txt classes-file.txt" % (sys
.argv
[0]), file=sys
.stderr
)
328 print(read_ms_schema(attr_file
, classes_file
))