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