gpo: Initial commit for GPO work
[nivanova/samba-autobuild/.git] / python / samba / gpclass.py
1 # Reads important GPO parameters and updates Samba
2 # Copyright (C) Luke Morrison <luc785@.hotmail.com> 2013
3 #
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.
8 #
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.
13 #
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/>.
16
17
18 import sys
19 import os
20 sys.path.insert(0, "bin/python")
21 import samba.gpo as gpo
22 import optparse
23 import ldb
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
28 import codecs
29 from samba import NTSTATUSError
30
31 class gp_ext(object):
32     def list(self, rootpath):
33         return None
34
35     def __str__(self):
36         return "default_gp_ext"
37
38
39 class inf_to_ldb(object):
40     '''This class takes the .inf file parameter (essentially a GPO file mapped to a GUID),
41     hashmaps it to the Samba parameter, which then uses an ldb object to update the
42     parameter to Samba4. Not registry oriented whatsoever.
43     '''
44
45     def __init__(self, ldb, dn, attribute, val):
46         self.ldb = ldb
47         self.dn = dn
48         self.attribute = attribute
49         self.val = val
50
51     def ch_minPwdAge(self, val):
52         self.ldb.set_minPwdAge(val)
53
54     def ch_maxPwdAge(self, val):
55         self.ldb.set_maxPwdAge(val)
56
57     def ch_minPwdLength(self, val):
58         self.ldb.set_minPwdLength(val)
59
60     def ch_pwdProperties(self, val):
61         self.ldb.set_pwdProperties(val)
62
63     def explicit(self):
64         return self.val
65
66     def nttime2unix(self):
67         seconds = 60
68         minutes = 60
69         hours = 24
70         sam_add = 10000000
71         val = (self.val)
72         val = int(val)
73         return  str(-(val * seconds * minutes * hours * sam_add))
74
75     def mapper(self):
76         '''ldap value : samba setter'''
77         return { "minPwdAge" : (self.ch_minPwdAge, self.nttime2unix),
78                  "maxPwdAge" : (self.ch_maxPwdAge, self.nttime2unix),
79                  # Could be none, but I like the method assignment in update_samba
80                  "minPwdLength" : (self.ch_minPwdLength, self.explicit),
81                  "pwdProperties" : (self.ch_pwdProperties, self.explicit),
82
83                }
84
85     def update_samba(self):
86         (upd_sam, value) = self.mapper().get(self.attribute)
87         upd_sam(value())     # or val = value() then update(val)
88
89
90 class gp_sec_ext(gp_ext):
91     '''This class does the following two things:
92         1) Identifies the GPO if it has a certain kind of filepath,
93         2) Finally parses it.
94     '''
95
96     count = 0
97
98     def __str__(self):
99         return "Security GPO extension"
100
101     def list(self, rootpath):
102         path = "%s%s" % (rootpath, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
103         return path
104
105     def listmachpol(self, rootpath):
106         path = "%s%s" % (rootpath, "Machine/Registry.pol")
107         return path
108
109     def listuserpol(self, rootpath):
110         path = "%s%s" % (rootpath, "User/Registry.pol")
111         return path
112
113     def populate_inf(self):
114         return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb),
115                                   "MaximumPasswordAge": ("maxPwdAge", inf_to_ldb),
116                                   "MinimumPasswordLength": ("minPwdLength", inf_to_ldb),
117                                   "PasswordComplexity": ("pwdProperties", inf_to_ldb),
118                                  }
119                }
120
121     def read_inf(self, path, conn, attr_log):
122         ret = False
123         inftable = self.populate_inf()
124
125         policy = conn.loadfile(path).decode('utf-16')
126         current_section = None
127         LOG = open(attr_log, "a")
128         LOG.write(str(path.split('/')[2]) + '\n')
129
130         # So here we would declare a boolean,
131         # that would get changed to TRUE.
132         #
133         # If at any point in time a GPO was applied,
134         # then we return that boolean at the end.
135
136         for line in policy.splitlines():
137             line = line.strip()
138             if line[0] == '[':
139                 section = line[1: -1]
140                 current_section = inftable.get(section.encode('ascii', 'ignore'))
141
142             else:
143                 # We must be in a section
144                 if not current_section:
145                     continue
146                 (key, value) = line.split("=")
147                 key = key.strip()
148                 if current_section.get(key):
149                     (att, setter) = current_section.get(key)
150                     value = value.encode('ascii', 'ignore')
151                     ret = True
152                     setter(self.ldb, self.dn, att, value).update_samba()
153         return ret
154
155     def parse(self, afile, ldb, conn, attr_log):
156         self.ldb = ldb
157         self.dn = ldb.get_default_basedn()
158
159         # Fixing the bug where only some Linux Boxes capitalize MACHINE
160         if afile.endswith('inf'):
161             try:
162                 blist = afile.split('/')
163                 idx = afile.lower().split('/').index('machine')
164                 for case in [blist[idx].upper(), blist[idx].capitalize(), blist[idx].lower()]:
165                     bfile = '/'.join(blist[:idx]) + '/' + case + '/' + '/'.join(blist[idx+1:])
166                     try:
167                         return self.read_inf(bfile, conn, attr_log)
168                     except NTSTATUSError:
169                         continue
170             except ValueError:
171                 try:
172                     return self.read_inf(afile, conn, attr_log)
173                 except:
174                     return None
175
176
177 def scan_log(sysvol_path):
178     a = open(sysvol_path, "r")
179     data = {}
180     for line in a.readlines():
181         line = line.strip()
182         (guid, version) = line.split(" ")
183         data[guid] = int(version)
184     return data
185
186
187 def Reset_Defaults(test_ldb):
188     test_ldb.set_minPwdAge(str(-25920000000000))
189     test_ldb.set_maxPwdAge(str(-38016000000000))
190     test_ldb.set_minPwdLength(str(7))
191     test_ldb.set_pwdProperties(str(1))
192
193
194 def check_deleted(guid_list, backloggpo):
195     if backloggpo is None:
196         return False
197     for guid in backloggpo:
198         if guid not in guid_list:
199             return True
200     return False
201
202
203 # The hierarchy is as per MS http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
204 #
205 # It does not care about local GPO, because GPO and snap-ins are not made in Linux yet.
206 # It follows the linking order and children GPO are last written format.
207 #
208 # Also, couple further testing with call scripts entitled informant and informant2.
209 # They explicitly show the returned hierarchically sorted list.
210
211
212 def container_indexes(GUID_LIST):
213     '''So the original list will need to be seperated into containers.
214     Returns indexed list of when the container changes after hierarchy
215     '''
216     count = 0
217     container_indexes = []
218     while count < (len(GUID_LIST)-1):
219         if GUID_LIST[count][2] != GUID_LIST[count+1][2]:
220             container_indexes.append(count+1)
221         count += 1
222     container_indexes.append(len(GUID_LIST))
223     return container_indexes
224
225
226 def sort_linked(SAMDB, guid_list, start, end):
227     '''So GPO in same level need to have link level.
228     This takes a container and sorts it.
229
230     TODO:  Small small problem, it is backwards
231     '''
232     containers = gpo_user.get_gpo_containers(SAMDB, guid_list[start][0])
233     for right_container in containers:
234         if right_container.get('dn') == guid_list[start][2]:
235             break
236     gplink = str(right_container.get('gPLink'))
237     gplink_split = gplink.split('[')
238     linked_order = []
239     ret_list = []
240     for ldap_guid in gplink_split:
241         linked_order.append(str(ldap_guid[10:48]))
242     count = len(linked_order) - 1
243     while count > 0:
244         ret_list.append([linked_order[count], guid_list[start][1], guid_list[start][2]])
245         count -= 1
246     return ret_list
247
248
249 def establish_hierarchy(SamDB, GUID_LIST, DC_OU, global_dn):
250     '''Takes a list of GUID from gpo, and sorts them based on OU, and realm.
251     See http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
252     '''
253     final_list = []
254     count_unapplied_GPO = 0
255     for GUID in GUID_LIST:
256
257         container_iteration = 0
258         # Assume first it is not applied
259         applied = False
260         # Realm only written on last call, if the GPO is linked to multiple places
261         gpo_realm = False
262
263         # A very important call. This gets all of the linked information.
264         GPO_CONTAINERS = gpo_user.get_gpo_containers(SamDB, GUID)
265         for GPO_CONTAINER in GPO_CONTAINERS:
266
267             container_iteration += 1
268
269             if DC_OU == str(GPO_CONTAINER.get('dn')):
270                 applied = True
271                 insert_gpo = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
272                 final_list.append(insert_gpo)
273                 break
274
275             if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) == 1):
276                 gpo_realm = True
277                 applied = True
278
279
280             if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) > 1):
281                 gpo_realm = True
282                 applied = True
283
284
285             if container_iteration == len(GPO_CONTAINERS):
286                 if gpo_realm == False:
287                     insert_dud = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
288                     final_list.insert(0, insert_dud)
289                     count_unapplied_GPO += 1
290                 else:
291                     REALM_GPO = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
292                     final_list.insert(count_unapplied_GPO, REALM_GPO)
293
294     # After GPO are sorted into containers, let's sort the containers themselves.
295     # But first we can get the GPO that we don't care about, out of the way.
296     indexed_places = container_indexes(final_list)
297     count = 0
298     unapplied_gpo = []
299     # Sorted by container
300     sorted_gpo_list = []
301
302     # Unapplied GPO live at start of list, append them to final list
303     while final_list[0][1] == False:
304         unapplied_gpo.append(final_list[count])
305         count += 1
306     count = 0
307     sorted_gpo_list += unapplied_gpo
308
309     # A single container call gets the linked order for all GPO in container.
310     # So we need one call per container - > index of the Original list
311     indexed_places.insert(0, 0)
312     while count < (len(indexed_places)-1):
313         sorted_gpo_list += (sort_linked(SamDB, final_list, indexed_places[count], indexed_places[count+1]))
314         count += 1
315     return sorted_gpo_list