gpo: Create base class gp_inf_ext
[nivanova/samba-autobuild/.git] / python / samba / ms_schema.py
1 # create schema.ldif (as a string) from WSPP documentation
2 #
3 # based on minschema.py and minschema_wspp
4 #
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.
9 #
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.
14 #
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/>.
17
18 from __future__ import print_function
19 """Generate LDIF from WSPP documentation."""
20
21 import re
22 import base64
23 import uuid
24
25 bitFields = {}
26
27 # ADTS: 2.2.9
28 # bit positions as labeled in the docs
29 bitFields["searchflags"] = {
30     'fATTINDEX': 31,         # IX
31     'fPDNTATTINDEX': 30,     # PI
32     'fANR': 29,  # AR
33     'fPRESERVEONDELETE': 28,         # PR
34     'fCOPY': 27,     # CP
35     'fTUPLEINDEX': 26,       # TP
36     'fSUBTREEATTINDEX': 25,  # ST
37     'fCONFIDENTIAL': 24,     # CF
38     'fNEVERVALUEAUDIT': 23,  # NV
39     'fRODCAttribute': 22,    # RO
40
41
42     # missing in ADTS but required by LDIF
43     'fRODCFilteredAttribute': 22,    # RO ?
44     'fCONFIDENTAIL': 24, # typo
45     'fRODCFILTEREDATTRIBUTE': 22 # case
46     }
47
48 # ADTS: 2.2.10
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
63     }
64
65 # ADTS: 2.2.11
66 bitFields["schemaflagsex"] = {
67     'FLAG_ATTR_IS_CRITICAL': 31
68     }
69
70 # ADTS: 3.1.1.2.2.2
71 oMObjectClassBER = {
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')
79 }
80
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"])
85
86 def __read_folded_line(f, buffer):
87     """ reads a line from an LDIF file, unfolding it"""
88     line = buffer
89
90     while True:
91         l = f.readline()
92
93         if l[:1] == " ":
94             # continued line
95
96             # cannot fold an empty line
97             assert(line != "" and line != "\n")
98
99             # preserves '\n '
100             line = line + l
101         else:
102             # non-continued line
103             if line == "":
104                 line = l
105
106                 if l == "":
107                     # eof, definitely won't be folded
108                     break
109             else:
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
113                 buffer = l
114                 break
115
116     return (line, buffer)
117
118
119 def __read_raw_entries(f):
120     """reads an LDIF entry, only unfolding lines"""
121     import sys
122
123     # will not match options after the attribute type
124     attr_type_re = re.compile("^([A-Za-z]+[A-Za-z0-9-]*):")
125
126     buffer = ""
127
128     while True:
129         entry = []
130
131         while True:
132             (l, buffer) = __read_folded_line(f, buffer)
133
134             if l[:1] == "#":
135                 continue
136
137             if l == "\n" or l == "":
138                 break
139
140             m = attr_type_re.match(l)
141
142             if m:
143                 if l[-1:] == "\n":
144                     l = l[:-1]
145
146                 entry.append(l)
147             else:
148                 print("Invalid line: %s" % l, end=' ', file=sys.stderr)
149                 sys.exit(1)
150
151         if len(entry):
152             yield entry
153
154         if l == "":
155             break
156
157
158 def fix_dn(dn):
159     """fix a string DN to use ${SCHEMADN}"""
160
161     # folding?
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}")
170     else:
171         return dn
172
173 def __convert_bitfield(key, value):
174     """Evaluate the OR expression in 'value'"""
175     assert(isinstance(value, str))
176
177     value = value.replace("\n ", "")
178     value = value.replace(" ", "")
179
180     try:
181         # some attributes already have numeric values
182         o = int(value)
183     except ValueError:
184         o = 0
185         flags = value.split("|")
186         for f in flags:
187             bitpos = bitFields[key][f]
188             o = o | (1 << (31 - bitpos))
189
190     return str(o)
191
192 def __write_ldif_one(entry):
193     """Write out entry as LDIF"""
194     out = []
195
196     for l in entry:
197         if isinstance(l[1], str):
198             vl = [l[1]]
199         else:
200             vl = l[1]
201
202         if l[2]:
203             out.append("%s:: %s" % (l[0], l[1]))
204             continue
205
206         for v in vl:
207             out.append("%s: %s" % (l[0], v))
208
209
210     return "\n".join(out)
211
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."""
215
216     entry = [l.split(":", 1) for l in entry]
217
218     cn = ""
219     skip_dn = skip_objectclass = skip_admin_description = skip_admin_display_name = False
220
221     for l in entry:
222         if l[1].startswith(': '):
223             l.append(True)
224             l[1] = l[1][2:]
225         else:
226             l.append(False)
227
228         key = l[0].lower()
229         l[1] = l[1].lstrip()
230         l[1] = l[1].rstrip()
231
232         if not cn and key == "cn":
233             cn = l[1]
234
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(" ", "")
239
240             l[1] = l[1].split(",")
241
242         if key in bitFields:
243             l[1] = __convert_bitfield(key, l[1])
244
245         if key == "omobjectclass":
246             if not l[2]:
247                 l[1] = oMObjectClassBER[l[1].strip()]
248                 l[2] = True
249
250         if isinstance(l[1], str):
251             l[1] = fix_dn(l[1])
252
253         if key == 'dn':
254             skip_dn = True
255             dn = l[1]
256
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
263
264     assert(cn)
265
266     header = []
267     if not skip_dn:
268         header.append(["dn", "CN=%s,${SCHEMADN}" % cn, False])
269     else:
270         header.append(["dn", dn, False])
271
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])
278
279     header.append(["objectGUID", str(uuid.uuid4()), False])
280
281     entry = header + [x for x in entry if x[0].lower() not in set(['dn', 'changetype', 'objectcategory'])]
282
283     return entry
284
285 def __parse_schema_file(filename, objectClass):
286     """Load and transform a schema file."""
287
288     out = []
289
290     f = open(filename, "rU")
291     for entry in __read_raw_entries(f):
292         out.append(__write_ldif_one(__transform_entry(entry, objectClass)))
293
294     return "\n\n".join(out)
295
296
297 def read_ms_schema(attr_file, classes_file, dump_attributes = True, dump_classes = True, debug = False):
298     """Read WSPP documentation-derived schema files."""
299
300     attr_ldif = ""
301     classes_ldif = ""
302
303     if dump_attributes:
304         attr_ldif =  __parse_schema_file(attr_file, "attributeSchema")
305     if dump_classes:
306         classes_ldif = __parse_schema_file(classes_file, "classSchema")
307
308     return attr_ldif + "\n\n" + classes_ldif + "\n\n"
309
310 if __name__ == '__main__':
311     import sys
312
313     try:
314         attr_file = sys.argv[1]
315         classes_file = sys.argv[2]
316     except IndexError:
317         print("Usage: %s attr-file.txt classes-file.txt" % (sys.argv[0]), file=sys.stderr)
318         sys.exit(1)
319
320     print(read_ms_schema(attr_file, classes_file))