1 # Reads important GPO parameters and updates Samba
2 # Copyright (C) Luke Morrison <luc785@.hotmail.com> 2013
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 sys.path.insert(0, "bin/python")
21 import samba.gpo as gpo
24 from samba.auth import system_session
25 import samba.getopt as options
26 from samba.samdb import SamDB
27 from samba.netcmd import gpo as gpo_user
29 from samba import NTSTATUSError
30 from ConfigParser import ConfigParser
31 from StringIO import StringIO
34 def list(self, rootpath):
38 return "default_gp_ext"
41 class inf_to_ldb(object):
42 '''This class takes the .inf file parameter (essentially a GPO file mapped to a GUID),
43 hashmaps it to the Samba parameter, which then uses an ldb object to update the
44 parameter to Samba4. Not registry oriented whatsoever.
47 def __init__(self, logger, ldb, dn, attribute, val):
51 self.attribute = attribute
54 def ch_minPwdAge(self, val):
55 self.logger.info('KDC Minimum Password age was changed from %s to %s' % (self.ldb.get_minPwdAge(), val))
56 self.ldb.set_minPwdAge(val)
58 def ch_maxPwdAge(self, val):
59 self.logger.info('KDC Maximum Password age was changed from %s to %s' % (self.ldb.get_maxPwdAge(), val))
60 self.ldb.set_maxPwdAge(val)
62 def ch_minPwdLength(self, val):
63 self.logger.info('KDC Minimum Password length was changed from %s to %s' % (self.ldb.get_minPwdLength(), val))
64 self.ldb.set_minPwdLength(val)
66 def ch_pwdProperties(self, val):
67 self.logger.info('KDC Password Properties were changed from %s to %s' % (self.ldb.get_pwdProperties(), val))
68 self.ldb.set_pwdProperties(val)
73 def nttime2unix(self):
80 return str(-(val * seconds * minutes * hours * sam_add))
83 '''ldap value : samba setter'''
84 return { "minPwdAge" : (self.ch_minPwdAge, self.nttime2unix),
85 "maxPwdAge" : (self.ch_maxPwdAge, self.nttime2unix),
86 # Could be none, but I like the method assignment in update_samba
87 "minPwdLength" : (self.ch_minPwdLength, self.explicit),
88 "pwdProperties" : (self.ch_pwdProperties, self.explicit),
92 def update_samba(self):
93 (upd_sam, value) = self.mapper().get(self.attribute)
94 upd_sam(value()) # or val = value() then update(val)
97 class gp_sec_ext(gp_ext):
98 '''This class does the following two things:
99 1) Identifies the GPO if it has a certain kind of filepath,
100 2) Finally parses it.
105 def __init__(self, logger):
109 return "Security GPO extension"
111 def list(self, rootpath):
112 path = "%s%s" % (rootpath, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
115 def listmachpol(self, rootpath):
116 path = "%s%s" % (rootpath, "Machine/Registry.pol")
119 def listuserpol(self, rootpath):
120 path = "%s%s" % (rootpath, "User/Registry.pol")
123 def populate_inf(self):
124 return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb),
125 "MaximumPasswordAge": ("maxPwdAge", inf_to_ldb),
126 "MinimumPasswordLength": ("minPwdLength", inf_to_ldb),
127 "PasswordComplexity": ("pwdProperties", inf_to_ldb),
131 def read_inf(self, path, conn, attr_log):
133 inftable = self.populate_inf()
135 policy = conn.loadfile(path.replace('/', '\\')).decode('utf-16')
136 current_section = None
137 LOG = open(attr_log, "a")
138 LOG.write(str(path.split('/')[2]) + '\n')
140 # So here we would declare a boolean,
141 # that would get changed to TRUE.
143 # If at any point in time a GPO was applied,
144 # then we return that boolean at the end.
146 inf_conf = ConfigParser()
147 inf_conf.optionxform=str
148 inf_conf.readfp(StringIO(policy))
150 for section in inf_conf.sections():
151 current_section = inftable.get(section)
152 if not current_section:
154 for key, value in inf_conf.items(section):
155 if current_section.get(key):
156 (att, setter) = current_section.get(key)
157 value = value.encode('ascii', 'ignore')
159 setter(self.logger, self.ldb, self.dn, att, value).update_samba()
162 def parse(self, afile, ldb, conn, attr_log):
164 self.dn = ldb.get_default_basedn()
166 # Fixing the bug where only some Linux Boxes capitalize MACHINE
167 if afile.endswith('inf'):
169 blist = afile.split('/')
170 idx = afile.lower().split('/').index('machine')
171 for case in [blist[idx].upper(), blist[idx].capitalize(), blist[idx].lower()]:
172 bfile = '/'.join(blist[:idx]) + '/' + case + '/' + '/'.join(blist[idx+1:])
174 return self.read_inf(bfile, conn, attr_log)
175 except NTSTATUSError:
179 return self.read_inf(afile, conn, attr_log)
184 def scan_log(sysvol_tdb):
186 for key in sysvol_tdb.iterkeys():
187 data[key] = sysvol_tdb.get(key)
191 def Reset_Defaults(test_ldb):
192 test_ldb.set_minPwdAge(str(-25920000000000))
193 test_ldb.set_maxPwdAge(str(-38016000000000))
194 test_ldb.set_minPwdLength(str(7))
195 test_ldb.set_pwdProperties(str(1))
198 def check_deleted(guid_list, backloggpo):
199 if backloggpo is None:
201 for guid in backloggpo:
202 if guid not in guid_list:
207 # The hierarchy is as per MS http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
209 # It does not care about local GPO, because GPO and snap-ins are not made in Linux yet.
210 # It follows the linking order and children GPO are last written format.
212 # Also, couple further testing with call scripts entitled informant and informant2.
213 # They explicitly show the returned hierarchically sorted list.
216 def container_indexes(GUID_LIST):
217 '''So the original list will need to be seperated into containers.
218 Returns indexed list of when the container changes after hierarchy
221 container_indexes = []
222 while count < (len(GUID_LIST)-1):
223 if GUID_LIST[count][2] != GUID_LIST[count+1][2]:
224 container_indexes.append(count+1)
226 container_indexes.append(len(GUID_LIST))
227 return container_indexes
230 def sort_linked(SAMDB, guid_list, start, end):
231 '''So GPO in same level need to have link level.
232 This takes a container and sorts it.
234 TODO: Small small problem, it is backwards
236 containers = gpo_user.get_gpo_containers(SAMDB, guid_list[start][0])
237 for right_container in containers:
238 if right_container.get('dn') == guid_list[start][2]:
240 gplink = str(right_container.get('gPLink'))
241 gplink_split = gplink.split('[')
244 for ldap_guid in gplink_split:
245 linked_order.append(str(ldap_guid[10:48]))
246 count = len(linked_order) - 1
248 ret_list.append([linked_order[count], guid_list[start][1], guid_list[start][2]])
253 def establish_hierarchy(SamDB, GUID_LIST, DC_OU, global_dn):
254 '''Takes a list of GUID from gpo, and sorts them based on OU, and realm.
255 See http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
258 count_unapplied_GPO = 0
259 for GUID in GUID_LIST:
261 container_iteration = 0
262 # Assume first it is not applied
264 # Realm only written on last call, if the GPO is linked to multiple places
267 # A very important call. This gets all of the linked information.
268 GPO_CONTAINERS = gpo_user.get_gpo_containers(SamDB, GUID)
269 for GPO_CONTAINER in GPO_CONTAINERS:
271 container_iteration += 1
273 if DC_OU == str(GPO_CONTAINER.get('dn')):
275 insert_gpo = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
276 final_list.append(insert_gpo)
279 if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) == 1):
284 if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) > 1):
289 if container_iteration == len(GPO_CONTAINERS):
290 if gpo_realm == False:
291 insert_dud = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
292 final_list.insert(0, insert_dud)
293 count_unapplied_GPO += 1
295 REALM_GPO = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
296 final_list.insert(count_unapplied_GPO, REALM_GPO)
298 # After GPO are sorted into containers, let's sort the containers themselves.
299 # But first we can get the GPO that we don't care about, out of the way.
300 indexed_places = container_indexes(final_list)
303 # Sorted by container
306 # Unapplied GPO live at start of list, append them to final list
307 while final_list[0][1] == False:
308 unapplied_gpo.append(final_list[count])
311 sorted_gpo_list += unapplied_gpo
313 # A single container call gets the linked order for all GPO in container.
314 # So we need one call per container - > index of the Original list
315 indexed_places.insert(0, 0)
316 while count < (len(indexed_places)-1):
317 sorted_gpo_list += (sort_linked(SamDB, final_list, indexed_places[count], indexed_places[count+1]))
319 return sorted_gpo_list