1 # GPO Parser for generic extensions
3 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
4 # Written by Garming Sam <garming@catalyst.net.nz>
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.
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.
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/>.
20 from xml
.dom
import minidom
21 from io
import BytesIO
22 from xml
.etree
.ElementTree
import ElementTree
, fromstring
, tostring
23 from hashlib
import md5
24 from samba
.common
import get_bytes
29 ENTITY_NETWORK_PATH
= 2
32 class GPNoParserException(Exception):
35 class GPGeneralizeException(Exception):
39 def entity_type_to_string(ent_type
):
42 if ent_type
== ENTITY_USER_ID
:
44 elif ent_type
== ENTITY_SDDL_ACL
:
46 elif ent_type
== ENTITY_NETWORK_PATH
:
47 type_str
= "NETWORK_PATH"
53 # [MS-GPDPC] Deployed Printer Connections (LDAP)
54 # [MS-GPPREF] Preferences Extension (XML)
55 # [MS-GPWL] Wireless/Wired Protocol Extension (LDAP)
56 class GPParser(object):
58 output_encoding
= 'utf-8'
60 def parse(self
, contents
):
63 def write_xml(self
, filename
):
64 with
open(filename
, 'w') as f
:
65 f
.write('<?xml version="1.0" encoding="utf-8"?><UnknownFile/>')
67 def load_xml(self
, filename
):
70 def write_binary(self
, filename
):
71 raise GPNoParserException("This file has no parser available.")
73 def write_pretty_xml(self
, xml_element
, handle
):
74 # Add the xml header as well as format it nicely.
75 # ElementTree doesn't have a pretty-print, so use minidom.
77 et
= ElementTree(xml_element
)
78 temporary_bytes
= BytesIO()
79 et
.write(temporary_bytes
, encoding
=self
.output_encoding
,
81 minidom_parsed
= minidom
.parseString(temporary_bytes
.getvalue())
82 handle
.write(minidom_parsed
.toprettyxml(encoding
=self
.output_encoding
))
84 def new_xml_entity(self
, name
, ent_type
):
85 identifier
= md5(get_bytes(name
)).hexdigest()
87 type_str
= entity_type_to_string(ent_type
)
90 raise GPGeneralizeException("No such entity type")
92 # For formatting reasons, align the length of the entities
93 longest
= entity_type_to_string(ENTITY_NETWORK_PATH
)
94 type_str
= type_str
.center(len(longest
), '_')
96 return "&SAMBA__{}__{}__;".format(type_str
, identifier
)
98 def generalize_xml(self
, root
, out_file
, global_entities
):
101 # Locate all user_id and all ACLs
102 user_ids
= root
.findall('.//*[@user_id="TRUE"]')
103 user_ids
.sort(key
= lambda x
: x
.tag
)
105 for elem
in user_ids
:
107 if old_text
is None or old_text
== '':
110 if old_text
in global_entities
:
111 elem
.text
= global_entities
[old_text
]
112 entities
.append((elem
.text
, old_text
))
114 elem
.text
= self
.new_xml_entity(old_text
,
117 entities
.append((elem
.text
, old_text
))
118 global_entities
.update([(old_text
, elem
.text
)])
120 acls
= root
.findall('.//*[@acl="TRUE"]')
121 acls
.sort(key
= lambda x
: x
.tag
)
126 if old_text
is None or old_text
== '':
129 if old_text
in global_entities
:
130 elem
.text
= global_entities
[old_text
]
131 entities
.append((elem
.text
, old_text
))
133 elem
.text
= self
.new_xml_entity(old_text
,
136 entities
.append((elem
.text
, old_text
))
137 global_entities
.update([(old_text
, elem
.text
)])
139 share_paths
= root
.findall('.//*[@network_path="TRUE"]')
140 share_paths
.sort(key
= lambda x
: x
.tag
)
142 for elem
in share_paths
:
145 if old_text
is None or old_text
== '':
148 stripped
= old_text
.lstrip('\\')
149 file_server
= stripped
.split('\\')[0]
151 server_index
= old_text
.find(file_server
)
153 remaining
= old_text
[server_index
+ len(file_server
):]
154 old_text
= old_text
[:server_index
] + file_server
156 if old_text
in global_entities
:
157 elem
.text
= global_entities
[old_text
] + remaining
158 to_put
= global_entities
[old_text
]
159 entities
.append((to_put
, old_text
))
161 to_put
= self
.new_xml_entity(old_text
,
163 elem
.text
= to_put
+ remaining
165 entities
.append((to_put
, old_text
))
166 global_entities
.update([(old_text
, to_put
)])
168 # Call any file specific customization of entities
169 # (which appear in any subclasses).
170 entities
.extend(self
.custom_entities(root
, global_entities
))
172 output_xml
= tostring(root
)
175 entb
= get_bytes(ent
[0])
176 output_xml
= output_xml
.replace(entb
.replace(b
'&', b
'&'), entb
)
178 with
open(out_file
, 'wb') as f
:
183 def custom_entities(self
, root
, global_entities
):
184 # Override this method to do special entity handling