s3: smbd: Ensure srvstr_pull_req_talloc() always NULLs out *dest.
[Samba.git] / python / samba / ms_schema.py
blob986ae3d6fa7453f8960efbed6e664b7ff8371af5
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."""
20 import re
21 import base64
22 import uuid
24 bitFields = {}
26 # ADTS: 2.2.9
27 # bit positions as labeled in the docs
28 bitFields["searchflags"] = {
29 'fATTINDEX': 31, # IX
30 'fPDNTATTINDEX': 30, # PI
31 'fANR': 29, # AR
32 'fPRESERVEONDELETE': 28, # PR
33 'fCOPY': 27, # CP
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
46 'fBASEONLY': 20, # BO
47 'fPARTITIONSECRET': 19, # SE
50 # ADTS: 2.2.10
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
67 # ADTS: 2.2.11
68 bitFields["schemaflagsex"] = {
69 'FLAG_ATTR_IS_CRITICAL': 31
72 # ADTS: 3.1.1.2.2.2
73 oMObjectClassBER = {
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"""
91 line = buffer
93 attr_type_re = re.compile("^([A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])::?")
95 while True:
96 l = f.readline()
98 if l[:1] == " ":
99 # continued line
101 # cannot fold an empty line
102 assert(line != "" and line != "\n")
104 # preserves '\n '
105 line = line + l
106 else:
107 # non-continued line
108 if line == "":
109 line = l
111 if l == "":
112 # eof, definitely won't be folded
113 break
114 else:
115 if l[:1] != "#" and l != "\n" and l != "":
116 m = attr_type_re.match(l)
117 if not m:
118 line = line + " " + l
119 continue
121 # marks end of a folded line
122 # line contains the now unfolded line
123 # buffer contains the start of the next possibly folded line
124 buffer = l
125 break
127 return (line, buffer)
130 def __read_raw_entries(f):
131 """reads an LDIF entry, only unfolding lines"""
132 import sys
134 # will not match options after the attribute type
135 # attributes in the schema definition have at least two chars
136 attr_type_re = re.compile("^([A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])::?")
138 buffer = ""
140 while True:
141 entry = []
143 while True:
144 (l, buffer) = __read_folded_line(f, buffer)
146 if l[:1] == "#":
147 continue
149 if l == "\n" or l == "":
150 break
152 m = attr_type_re.match(l)
154 if m:
155 if l[-1:] == "\n":
156 l = l[:-1]
158 entry.append(l)
159 else:
160 print("Invalid line: %s" % l, end=' ', file=sys.stderr)
161 sys.exit(1)
163 if len(entry):
164 yield entry
166 if l == "":
167 break
170 def fix_dn(dn):
171 """fix a string DN to use ${SCHEMADN}"""
173 # folding?
174 if dn.find("<RootDomainDN>") != -1:
175 dn = dn.replace("\n ", "")
176 dn = dn.replace(" ", "")
177 return dn.replace("CN=Schema,CN=Configuration,<RootDomainDN>", "${SCHEMADN}")
178 elif dn.endswith("DC=X"):
179 return dn.replace("CN=Schema,CN=Configuration,DC=X", "${SCHEMADN}")
180 elif dn.endswith("CN=X"):
181 return dn.replace("CN=Schema,CN=Configuration,CN=X", "${SCHEMADN}")
182 else:
183 return dn
186 def __convert_bitfield(key, value):
187 """Evaluate the OR expression in 'value'"""
188 assert(isinstance(value, str))
190 value = value.replace("\n ", "")
191 value = value.replace(" ", "")
193 try:
194 # some attributes already have numeric values
195 o = int(value)
196 except ValueError:
197 o = 0
198 flags = value.split("|")
199 for f in flags:
200 bitpos = bitFields[key][f]
201 o = o | (1 << (31 - bitpos))
203 return str(o)
206 def __write_ldif_one(entry):
207 """Write out entry as LDIF"""
208 out = []
210 for l in entry:
211 if isinstance(l[1], str):
212 vl = [l[1]]
213 else:
214 vl = l[1]
216 if l[2]:
217 out.append("%s:: %s" % (l[0], l[1]))
218 continue
220 for v in vl:
221 out.append("%s: %s" % (l[0], v))
223 return "\n".join(out)
226 def __transform_entry(entry, objectClass):
227 """Perform transformations required to convert the LDIF-like schema
228 file entries to LDIF, including Samba-specific stuff."""
230 entry = [l.split(":", 1) for l in entry]
232 cn = ""
233 skip_dn = skip_objectclass = skip_admin_description = skip_admin_display_name = False
235 for l in entry:
236 if l[1].startswith(': '):
237 l.append(True)
238 l[1] = l[1][2:]
239 else:
240 l.append(False)
242 key = l[0].lower()
243 l[1] = l[1].lstrip()
244 l[1] = l[1].rstrip()
246 if not cn and key == "cn":
247 cn = l[1]
249 if key in multivalued_attrs:
250 # unlike LDIF, these are comma-separated
251 l[1] = l[1].replace("\n ", "")
252 l[1] = l[1].replace(" ", "")
254 l[1] = l[1].split(",")
256 if key in bitFields:
257 l[1] = __convert_bitfield(key, l[1])
259 if key == "omobjectclass":
260 if not l[2]:
261 l[1] = oMObjectClassBER[l[1].strip()]
262 l[2] = True
264 if isinstance(l[1], str):
265 l[1] = fix_dn(l[1])
267 if key == 'dn':
268 skip_dn = True
269 dn = l[1]
271 if key == 'objectclass':
272 skip_objectclass = True
273 elif key == 'admindisplayname':
274 skip_admin_display_name = True
275 elif key == 'admindescription':
276 skip_admin_description = True
278 assert(cn)
280 header = []
281 if not skip_dn:
282 header.append(["dn", "CN=%s,${SCHEMADN}" % cn, False])
283 else:
284 header.append(["dn", dn, False])
286 if not skip_objectclass:
287 header.append(["objectClass", ["top", objectClass], False])
288 if not skip_admin_description:
289 header.append(["adminDescription", cn, False])
290 if not skip_admin_display_name:
291 header.append(["adminDisplayName", cn, False])
293 header.append(["objectGUID", str(uuid.uuid4()), False])
295 entry = header + [x for x in entry if x[0].lower() not in set(['dn', 'changetype', 'objectcategory'])]
297 return entry
300 def __parse_schema_file(filename, objectClass):
301 """Load and transform a schema file."""
303 out = []
305 from io import open
306 with open(filename, "r", encoding='latin-1') as f:
307 for entry in __read_raw_entries(f):
308 out.append(__write_ldif_one(__transform_entry(entry, objectClass)))
310 return "\n\n".join(out)
313 def read_ms_schema(attr_file, classes_file, dump_attributes=True, dump_classes=True, debug=False):
314 """Read WSPP documentation-derived schema files."""
316 attr_ldif = ""
317 classes_ldif = ""
319 if dump_attributes:
320 attr_ldif = __parse_schema_file(attr_file, "attributeSchema")
321 if dump_classes:
322 classes_ldif = __parse_schema_file(classes_file, "classSchema")
324 return attr_ldif + "\n\n" + classes_ldif + "\n\n"
327 if __name__ == '__main__':
328 import sys
330 try:
331 attr_file = sys.argv[1]
332 classes_file = sys.argv[2]
333 except IndexError:
334 print("Usage: %s attr-file.txt classes-file.txt" % (sys.argv[0]), file=sys.stderr)
335 sys.exit(1)
337 print(read_ms_schema(attr_file, classes_file))