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
32 from abc
import ABCMeta
, abstractmethod
35 __metaclass__
= ABCMeta
38 def list(self
, rootpath
):
42 def parse(self
, afile
, ldb
, conn
, attr_log
, lp
):
51 __metaclass__
= ABCMeta
53 def __init__(self
, logger
, ldb
, dn
, lp
, attribute
, val
):
57 self
.attribute
= attribute
64 def update_samba(self
):
65 (upd_sam
, value
) = self
.mapper().get(self
.attribute
)
72 class inf_to_ldb(inf_to
):
73 '''This class takes the .inf file parameter (essentially a GPO file mapped to a GUID),
74 hashmaps it to the Samba parameter, which then uses an ldb object to update the
75 parameter to Samba4. Not registry oriented whatsoever.
78 def ch_minPwdAge(self
, val
):
79 self
.logger
.info('KDC Minimum Password age was changed from %s to %s' % (self
.ldb
.get_minPwdAge(), val
))
80 self
.ldb
.set_minPwdAge(val
)
82 def ch_maxPwdAge(self
, val
):
83 self
.logger
.info('KDC Maximum Password age was changed from %s to %s' % (self
.ldb
.get_maxPwdAge(), val
))
84 self
.ldb
.set_maxPwdAge(val
)
86 def ch_minPwdLength(self
, val
):
87 self
.logger
.info('KDC Minimum Password length was changed from %s to %s' % (self
.ldb
.get_minPwdLength(), val
))
88 self
.ldb
.set_minPwdLength(val
)
90 def ch_pwdProperties(self
, val
):
91 self
.logger
.info('KDC Password Properties were changed from %s to %s' % (self
.ldb
.get_pwdProperties(), val
))
92 self
.ldb
.set_pwdProperties(val
)
94 def nttime2unix(self
):
101 return str(-(val
* seconds
* minutes
* hours
* sam_add
))
104 '''ldap value : samba setter'''
105 return { "minPwdAge" : (self
.ch_minPwdAge
, self
.nttime2unix
),
106 "maxPwdAge" : (self
.ch_maxPwdAge
, self
.nttime2unix
),
107 # Could be none, but I like the method assignment in update_samba
108 "minPwdLength" : (self
.ch_minPwdLength
, self
.explicit
),
109 "pwdProperties" : (self
.ch_pwdProperties
, self
.explicit
),
114 class gp_sec_ext(gp_ext
):
115 '''This class does the following two things:
116 1) Identifies the GPO if it has a certain kind of filepath,
117 2) Finally parses it.
122 def __init__(self
, logger
):
126 return "Security GPO extension"
128 def list(self
, rootpath
):
129 path
= "%s%s" % (rootpath
, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
132 def listmachpol(self
, rootpath
):
133 path
= "%s%s" % (rootpath
, "Machine/Registry.pol")
136 def listuserpol(self
, rootpath
):
137 path
= "%s%s" % (rootpath
, "User/Registry.pol")
140 def populate_inf(self
):
141 return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb
),
142 "MaximumPasswordAge": ("maxPwdAge", inf_to_ldb
),
143 "MinimumPasswordLength": ("minPwdLength", inf_to_ldb
),
144 "PasswordComplexity": ("pwdProperties", inf_to_ldb
),
148 def read_inf(self
, path
, conn
, attr_log
):
150 inftable
= self
.populate_inf()
152 policy
= conn
.loadfile(path
.replace('/', '\\')).decode('utf-16')
153 current_section
= None
154 LOG
= open(attr_log
, "a")
155 LOG
.write(str(path
.split('/')[2]) + '\n')
157 # So here we would declare a boolean,
158 # that would get changed to TRUE.
160 # If at any point in time a GPO was applied,
161 # then we return that boolean at the end.
163 inf_conf
= ConfigParser()
164 inf_conf
.optionxform
=str
165 inf_conf
.readfp(StringIO(policy
))
167 for section
in inf_conf
.sections():
168 current_section
= inftable
.get(section
)
169 if not current_section
:
171 for key
, value
in inf_conf
.items(section
):
172 if current_section
.get(key
):
173 (att
, setter
) = current_section
.get(key
)
174 value
= value
.encode('ascii', 'ignore')
176 setter(self
.logger
, self
.ldb
, self
.dn
, self
.lp
, att
, value
).update_samba()
179 def parse(self
, afile
, ldb
, conn
, attr_log
, lp
):
182 self
.dn
= ldb
.get_default_basedn()
184 # Fixing the bug where only some Linux Boxes capitalize MACHINE
185 if afile
.endswith('inf'):
187 blist
= afile
.split('/')
188 idx
= afile
.lower().split('/').index('machine')
189 for case
in [blist
[idx
].upper(), blist
[idx
].capitalize(), blist
[idx
].lower()]:
190 bfile
= '/'.join(blist
[:idx
]) + '/' + case
+ '/' + '/'.join(blist
[idx
+1:])
192 return self
.read_inf(bfile
, conn
, attr_log
)
193 except NTSTATUSError
:
197 return self
.read_inf(afile
, conn
, attr_log
)
202 def scan_log(sysvol_tdb
):
204 for key
in sysvol_tdb
.iterkeys():
205 data
[key
] = sysvol_tdb
.get(key
)
209 def Reset_Defaults(test_ldb
):
210 test_ldb
.set_minPwdAge(str(-25920000000000))
211 test_ldb
.set_maxPwdAge(str(-38016000000000))
212 test_ldb
.set_minPwdLength(str(7))
213 test_ldb
.set_pwdProperties(str(1))
216 def check_deleted(guid_list
, backloggpo
):
217 if backloggpo
is None:
219 for guid
in backloggpo
:
220 if guid
not in guid_list
:
225 # The hierarchy is as per MS http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
227 # It does not care about local GPO, because GPO and snap-ins are not made in Linux yet.
228 # It follows the linking order and children GPO are last written format.
230 # Also, couple further testing with call scripts entitled informant and informant2.
231 # They explicitly show the returned hierarchically sorted list.
234 def container_indexes(GUID_LIST
):
235 '''So the original list will need to be seperated into containers.
236 Returns indexed list of when the container changes after hierarchy
239 container_indexes
= []
240 while count
< (len(GUID_LIST
)-1):
241 if GUID_LIST
[count
][2] != GUID_LIST
[count
+1][2]:
242 container_indexes
.append(count
+1)
244 container_indexes
.append(len(GUID_LIST
))
245 return container_indexes
248 def sort_linked(SAMDB
, guid_list
, start
, end
):
249 '''So GPO in same level need to have link level.
250 This takes a container and sorts it.
252 TODO: Small small problem, it is backwards
254 containers
= gpo_user
.get_gpo_containers(SAMDB
, guid_list
[start
][0])
255 for right_container
in containers
:
256 if right_container
.get('dn') == guid_list
[start
][2]:
258 gplink
= str(right_container
.get('gPLink'))
259 gplink_split
= gplink
.split('[')
262 for ldap_guid
in gplink_split
:
263 linked_order
.append(str(ldap_guid
[10:48]))
264 count
= len(linked_order
) - 1
266 ret_list
.append([linked_order
[count
], guid_list
[start
][1], guid_list
[start
][2]])
271 def establish_hierarchy(SamDB
, GUID_LIST
, DC_OU
, global_dn
):
272 '''Takes a list of GUID from gpo, and sorts them based on OU, and realm.
273 See http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
276 count_unapplied_GPO
= 0
277 for GUID
in GUID_LIST
:
279 container_iteration
= 0
280 # Assume first it is not applied
282 # Realm only written on last call, if the GPO is linked to multiple places
285 # A very important call. This gets all of the linked information.
286 GPO_CONTAINERS
= gpo_user
.get_gpo_containers(SamDB
, GUID
)
287 for GPO_CONTAINER
in GPO_CONTAINERS
:
289 container_iteration
+= 1
291 if DC_OU
== str(GPO_CONTAINER
.get('dn')):
293 insert_gpo
= [GUID
, applied
, str(GPO_CONTAINER
.get('dn'))]
294 final_list
.append(insert_gpo
)
297 if global_dn
== str(GPO_CONTAINER
.get('dn')) and (len(GPO_CONTAINERS
) == 1):
302 if global_dn
== str(GPO_CONTAINER
.get('dn')) and (len(GPO_CONTAINERS
) > 1):
307 if container_iteration
== len(GPO_CONTAINERS
):
308 if gpo_realm
== False:
309 insert_dud
= [GUID
, applied
, str(GPO_CONTAINER
.get('dn'))]
310 final_list
.insert(0, insert_dud
)
311 count_unapplied_GPO
+= 1
313 REALM_GPO
= [GUID
, applied
, str(GPO_CONTAINER
.get('dn'))]
314 final_list
.insert(count_unapplied_GPO
, REALM_GPO
)
316 # After GPO are sorted into containers, let's sort the containers themselves.
317 # But first we can get the GPO that we don't care about, out of the way.
318 indexed_places
= container_indexes(final_list
)
321 # Sorted by container
324 # Unapplied GPO live at start of list, append them to final list
325 while final_list
[0][1] == False:
326 unapplied_gpo
.append(final_list
[count
])
329 sorted_gpo_list
+= unapplied_gpo
331 # A single container call gets the linked order for all GPO in container.
332 # So we need one call per container - > index of the Original list
333 indexed_places
.insert(0, 0)
334 while count
< (len(indexed_places
)-1):
335 sorted_gpo_list
+= (sort_linked(SamDB
, final_list
, indexed_places
[count
], indexed_places
[count
+1]))
337 return sorted_gpo_list