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
20import codecs
21import collections
22import re
23
24from abc import ABCMeta, abstractmethod
25from xml.etree.ElementTree import Element, SubElement
26
27from samba.gp_parse import GPParser
28
29# [MS-GPSB] Security Protocol Extension
30class 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)
379