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