gp_inf: Read/write files with a UTF-16LE BOM in GptTmpl.inf
[npower/samba.git] / python / samba / gp_parse / gp_inf.py
1 # GPO Parser for security extensions
2 #
3 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
4 # Written by Garming Sam <garming@catalyst.net.nz>
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import codecs
21 import collections
22 import re
23
24 from abc import ABCMeta, abstractmethod
25 from xml.etree.ElementTree import Element, SubElement
26
27 from samba.gp_parse import GPParser
28
29 # [MS-GPSB] Security Protocol Extension
30 class GptTmplInfParser(GPParser):
31     sections = None
32     encoding = 'utf-16'
33     output_encoding = 'utf-16le'
34
35     class AbstractParam:
36         __metaclass__ = ABCMeta
37
38         def __init__(self):
39             self.param_list = []
40
41         @abstractmethod
42         def parse(self, line):
43             pass
44
45         @abstractmethod
46         def write_section(self, header, fp):
47             pass
48
49         @abstractmethod
50         def build_xml(self, xml_parent):
51             pass
52
53         @abstractmethod
54         def from_xml(self, section):
55             pass
56
57     class IniParam(AbstractParam):
58         # param_list = [(Key, Value),]
59
60         def parse(self, line):
61             key, val = line.split('=')
62
63             self.param_list.append((key.strip(),
64                                     val.strip()))
65
66             # print key.strip(), val.strip()
67
68         def write_section(self, header, fp):
69             if len(self.param_list) ==  0:
70                 return
71             fp.write(u'[%s]\r\n' % header)
72             for key_out, val_out in self.param_list:
73                 fp.write(u'%s = %s\r\n' % (key_out,
74                                            val_out))
75
76         def build_xml(self, xml_parent):
77             for key_ini, val_ini in self.param_list:
78                 child = SubElement(xml_parent, 'Parameter')
79                 key = SubElement(child, 'Key')
80                 value = SubElement(child, 'Value')
81                 key.text = key_ini
82                 value.text = val_ini
83
84         def from_xml(self, section):
85             for param in section.findall('Parameter'):
86                 key = param.find('Key').text
87                 value = param.find('Value').text
88                 if value is None:
89                     value = ''
90
91                 self.param_list.append((key, value))
92
93     class RegParam(AbstractParam):
94         # param_list = [Value, Value, ...]
95         def parse(self, line):
96             # = can occur in a registry key, so don't parse these
97             self.param_list.append(line)
98             # print line
99
100         def write_section(self, header, fp):
101             if len(self.param_list) ==  0:
102                 return
103             fp.write(u'[%s]\r\n' % header)
104             for param in self.param_list:
105                 fp.write(u'%s\r\n' % param)
106
107         def build_xml(self, xml_parent):
108             for val_ini in self.param_list:
109                 child = SubElement(xml_parent, 'Parameter')
110                 value = SubElement(child, 'Value')
111                 value.text = val_ini
112
113         def from_xml(self, section):
114             for param in section.findall('Parameter'):
115                 value = param.find('Value').text
116                 if value is None:
117                     value = ''
118
119                 self.param_list.append(value)
120
121     class PrivSIDListParam(AbstractParam):
122         # param_list = [(Key, [SID, SID,..]),
123         def parse(self, line):
124             key, val = line.split('=')
125
126             self.param_list.append((key.strip(),
127                                     [x.strip() for x in val.split(',')]))
128             # print line
129
130         def write_section(self, header, fp):
131             if len(self.param_list) ==  0:
132                 return
133             fp.write(u'[%s]\r\n' % header)
134             for key_out, val in self.param_list:
135                 val_out = u','.join(val)
136                 fp.write(u'%s = %s\r\n' % (key_out, val_out))
137
138         def build_xml(self, xml_parent):
139             for key_ini, sid_list in self.param_list:
140                 child = SubElement(xml_parent, 'Parameter')
141                 key = SubElement(child, 'Key')
142                 key.text = key_ini
143                 for val_ini in sid_list:
144                     value = SubElement(child, 'Value')
145                     value.attrib['user_id'] = 'TRUE'
146                     value.text = val_ini
147
148         def from_xml(self, section):
149             for param in section.findall('Parameter'):
150                 key = param.find('Key').text
151
152                 sid_list = []
153                 for val in param.findall('Value'):
154                     value = val.text
155                     if value is None:
156                         value = ''
157
158                     sid_list.append(value)
159
160                 self.param_list.append((key, sid_list))
161
162     class NameModeACLParam(AbstractParam):
163         # param_list = [[Name, Mode, ACL],]
164         def parse(self, line):
165             parameters = [None, None, None]
166             current_arg = 0
167
168             while line != '':
169                 # Read quoted string
170                 if line[:1] == '"':
171                     line = line[1:]
172                     findex = line.find('"')
173                     parameters[current_arg] = line[:findex]
174                     line = line[findex + 1:]
175                 # Skip past delimeter
176                 elif line[:1] == ',':
177                     line = line[1:]
178                     current_arg += 1
179                 # Read unquoted string
180                 else:
181                     findex = line.find(',')
182                     parameters[current_arg] = line[:findex]
183                     line = line[findex:]
184
185             # print parameters
186             # print line
187             self.param_list.append(parameters)
188
189         def write_section(self, header, fp):
190             if len(self.param_list) ==  0:
191                 return
192             fp.write(u'[%s]\r\n' % header)
193             for param in self.param_list:
194                 fp.write(u'"%s",%s,"%s"\r\n' % tuple(param))
195
196         def build_xml(self, xml_parent):
197             for name_mode_acl in self.param_list:
198                 child = SubElement(xml_parent, 'Parameter')
199
200                 value = SubElement(child, 'Value')
201                 value.text = name_mode_acl[0]
202
203                 value = SubElement(child, 'Value')
204                 value.text = name_mode_acl[1]
205
206                 value = SubElement(child, 'Value')
207                 value.attrib['acl'] = 'TRUE'
208                 value.text = name_mode_acl[2]
209
210         def from_xml(self, section):
211             for param in section.findall('Parameter'):
212                 name_mode_acl = [x.text if x.text else '' for x in param.findall('Value')]
213                 self.param_list.append(name_mode_acl)
214
215     class MemberSIDListParam(AbstractParam):
216         # param_list = [([XXXX, Memberof|Members], [SID, SID...]),...]
217         def parse(self, line):
218             key, val = line.split('=')
219
220             key = key.strip()
221
222             self.param_list.append((key.split('__'),
223                                     [x.strip() for x in val.split(',')]))
224             # print line
225
226         def write_section(self, header, fp):
227             if len(self.param_list) ==  0:
228                 return
229             fp.write(u'[%s]\r\n' % header)
230
231             for key, val in self.param_list:
232                 key_out = u'__'.join(key)
233                 val_out = u','.join(val)
234                 fp.write(u'%s = %s\r\n' % (key_out, val_out))
235
236         def build_xml(self, xml_parent):
237             for key_ini, sid_list in self.param_list:
238                 child = SubElement(xml_parent, 'Parameter')
239                 key = SubElement(child, 'Key')
240                 key.text = key_ini[0]
241                 key.attrib['member_type'] = key_ini[1]
242                 key.attrib['user_id'] = 'TRUE'
243
244                 for val_ini in sid_list:
245                     value = SubElement(child, 'Value')
246                     value.attrib['user_id'] = 'TRUE'
247                     value.text = val_ini
248
249         def from_xml(self, section):
250             for param in section.findall('Parameter'):
251                 key = param.find('Key')
252                 member_type = key.attrib['member_type']
253
254                 sid_list = []
255                 for val in param.findall('Value'):
256                     value = val.text
257                     if value is None:
258                         value = ''
259
260                     sid_list.append(value)
261
262                 self.param_list.append(([key.text, member_type], sid_list))
263
264     class UnicodeParam(AbstractParam):
265         def parse(self, line):
266             # print line
267             pass
268
269         def write_section(self, header, fp):
270             fp.write(u'[Unicode]\r\nUnicode=yes\r\n')
271
272         def build_xml(self, xml_parent):
273             # We do not bother storing this field
274             pass
275
276         def from_xml(self, section):
277             # We do not bother storing this field
278             pass
279
280     class VersionParam(AbstractParam):
281         def parse(self, line):
282             # print line
283             pass
284
285         def write_section(self, header, fp):
286             out = u'[Version]\r\nsignature="$CHICAGO$"\r\nRevision=1\r\n'
287             fp.write(out)
288
289         def build_xml(self, xml_parent):
290             # We do not bother storing this field
291             pass
292
293         def from_xml(self, section):
294             # We do not bother storing this field
295             pass
296
297     def parse(self, contents):
298         inf_file = contents.decode(self.encoding)
299
300         self.sections = collections.OrderedDict([
301             (u'Unicode', self.UnicodeParam()),
302             (u'Version', self.VersionParam()),
303
304             (u'System Access', self.IniParam()),
305             (u'Kerberos Policy', self.IniParam()),
306             (u'System Log', self.IniParam()),
307             (u'Security Log', self.IniParam()),
308             (u'Application Log', self.IniParam()),
309             (u'Event Audit', self.IniParam()),
310             (u'Registry Values', self.RegParam()),
311             (u'Privilege Rights', self.PrivSIDListParam()),
312             (u'Service General Setting', self.NameModeACLParam()),
313             (u'Registry Keys', self.NameModeACLParam()),
314             (u'File Security', self.NameModeACLParam()),
315             (u'Group Membership', self.MemberSIDListParam()),
316         ])
317
318         current_param_parser = None
319         current_header_name = None
320
321         for line in inf_file.splitlines():
322             match = re.match('\[(.*)\]', line)
323             if match:
324                 header_name = match.group(1)
325                 if header_name in self.sections:
326                     current_param_parser = self.sections[header_name]
327                     # print current_param_parser
328                     continue
329
330             # print 'using', current_param_parser
331             current_param_parser.parse(line)
332
333
334     def write_binary(self, filename):
335         with codecs.open(filename, 'wb+',
336                          self.output_encoding) as f:
337             # Write the byte-order mark
338             f.write(u'\ufeff')
339
340             for s in self.sections:
341                 self.sections[s].write_section(s, f)
342
343     def write_xml(self, filename):
344         with open(filename, 'wb') as f:
345             root = Element('GptTmplInfFile')
346
347             for sec_inf in self.sections:
348                 section = SubElement(root, 'Section')
349                 section.attrib['name'] = sec_inf
350
351                 self.sections[sec_inf].build_xml(section)
352
353             self.write_pretty_xml(root, f)
354
355         # contents = codecs.open(filename, encoding='utf-8').read()
356         # self.load_xml(fromstring(contents))
357
358     def load_xml(self, root):
359         self.sections = collections.OrderedDict([
360             (u'Unicode', self.UnicodeParam()),
361             (u'Version', self.VersionParam()),
362
363             (u'System Access', self.IniParam()),
364             (u'Kerberos Policy', self.IniParam()),
365             (u'System Log', self.IniParam()),
366             (u'Security Log', self.IniParam()),
367             (u'Application Log', self.IniParam()),
368             (u'Event Audit', self.IniParam()),
369             (u'Registry Values', self.RegParam()),
370             (u'Privilege Rights', self.PrivSIDListParam()),
371             (u'Service General Setting', self.NameModeACLParam()),
372             (u'Registry Keys', self.NameModeACLParam()),
373             (u'File Security', self.NameModeACLParam()),
374             (u'Group Membership', self.MemberSIDListParam()),
375         ])
376
377         for s in root.findall('Section'):
378             self.sections[s.attrib['name']].from_xml(s)