gpo: Extract Access policy from Security extension
[samba.git] / python / samba / gp_sec_ext.py
1 # gp_sec_ext kdc gpo policy
2 # Copyright (C) Luke Morrison <luc785@.hotmail.com> 2013
3 # Copyright (C) David Mulder <dmulder@suse.com> 2018
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 import os.path
19 from samba.gpclass import gp_ext_setter, gp_inf_ext
20 from samba.auth import system_session
21 from samba.compat import get_string
22 try:
23     from ldb import LdbError
24     from samba.samdb import SamDB
25 except ImportError:
26     pass
27
28
29 class gp_krb_ext(gp_inf_ext):
30     apply_map = { 'MaxTicketAge':  'kdc:user_ticket_lifetime',
31                   'MaxServiceAge': 'kdc:service_ticket_lifetime',
32                   'MaxRenewAge':   'kdc:renewal_lifetime' }
33     def process_group_policy(self, deleted_gpo_list, changed_gpo_list):
34         if self.lp.get('server role') != 'active directory domain controller':
35             return
36         inf_file = 'MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf'
37         for gpo in deleted_gpo_list:
38             self.gp_db.set_guid(gpo[0])
39             for section in gpo[1].keys():
40                 if section == str(self):
41                     for att, value in gpo[1][section].items():
42                         update_samba, _ = self.mapper().get(att)
43                         update_samba(att, value)
44                         self.gp_db.delete(section, att)
45                         self.gp_db.commit()
46
47         for gpo in changed_gpo_list:
48             if gpo.file_sys_path:
49                 self.gp_db.set_guid(gpo.name)
50                 path = os.path.join(gpo.file_sys_path, inf_file)
51                 inf_conf = self.parse(path)
52                 if not inf_conf:
53                     continue
54                 for section in inf_conf.sections():
55                     if section == str(self):
56                         for key, value in inf_conf.items(section):
57                             att = gp_krb_ext.apply_map[key]
58                             (update_samba, value_func) = self.mapper().get(att)
59                             update_samba(att, value_func(value))
60                             self.gp_db.commit()
61
62     def mins_to_hours(self, val):
63         return '%d' % (int(val) / 60)
64
65     def days_to_hours(self, val):
66         return '%d' % (int(val) * 24)
67
68     def set_kdc_tdb(self, attribute, val):
69         old_val = self.gp_db.gpostore.get(attribute)
70         self.logger.info('%s was changed from %s to %s' % (attribute,
71                                                            old_val, val))
72         if val is not None:
73             self.gp_db.gpostore.store(attribute, get_string(val))
74             self.gp_db.store(str(self), attribute, get_string(old_val) \
75                     if old_val else None)
76         else:
77             self.gp_db.gpostore.delete(attribute)
78             self.gp_db.delete(str(self), attribute)
79
80     def mapper(self):
81         return {'kdc:user_ticket_lifetime': (self.set_kdc_tdb,
82                                              lambda val: val),
83                 'kdc:service_ticket_lifetime': (self.set_kdc_tdb,
84                                                 self.mins_to_hours),
85                 'kdc:renewal_lifetime': (self.set_kdc_tdb,
86                                          self.days_to_hours),
87                 }
88
89     def __str__(self):
90         return 'Kerberos Policy'
91
92     def rsop(self, gpo):
93         output = {}
94         inf_file = 'MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf'
95         if gpo.file_sys_path:
96             path = os.path.join(gpo.file_sys_path, inf_file)
97             inf_conf = self.parse(path)
98             if not inf_conf:
99                 return output
100             for section in inf_conf.sections():
101                 output[section] = {k: v for k, v in inf_conf.items(section) \
102                                       if gp_krb_ext.apply_map.get(k)}
103         return output
104
105
106 class gp_access_ext(gp_inf_ext):
107     '''This class takes the .inf file parameter (essentially a GPO file mapped
108     to a GUID), hashmaps it to the Samba parameter, which then uses an ldb
109     object to update the parameter to Samba4. Not registry oriented whatsoever.
110     '''
111
112     def __init__(self, *args):
113         super().__init__(*args)
114         try:
115             self.ldb = SamDB(self.lp.samdb_url(),
116                              session_info=system_session(),
117                              credentials=self.creds,
118                              lp=self.lp)
119         except (NameError, LdbError):
120             raise Exception('Failed to load SamDB for assigning Group Policy')
121
122     apply_map = { 'MinimumPasswordAge':     'minPwdAge',
123                   'MaximumPasswordAge':     'maxPwdAge',
124                   'MinimumPasswordLength':  'minPwdLength',
125                   'PasswordComplexity':     'pwdProperties' }
126     def process_group_policy(self, deleted_gpo_list, changed_gpo_list):
127         if self.lp.get('server role') != 'active directory domain controller':
128             return
129         inf_file = 'MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf'
130         for gpo in deleted_gpo_list:
131             self.gp_db.set_guid(gpo[0])
132             for section in gpo[1].keys():
133                 if section == str(self):
134                     for att, value in gpo[1][section].items():
135                         update_samba, _ = self.mapper().get(att)
136                         update_samba(att, value)
137                         self.gp_db.delete(section, att)
138                         self.gp_db.commit()
139
140         for gpo in changed_gpo_list:
141             if gpo.file_sys_path:
142                 self.gp_db.set_guid(gpo.name)
143                 path = os.path.join(gpo.file_sys_path, inf_file)
144                 inf_conf = self.parse(path)
145                 if not inf_conf:
146                     continue
147                 for section in inf_conf.sections():
148                     if section == str(self):
149                         for key, value in inf_conf.items(section):
150                             att = gp_access_ext.apply_map[key]
151                             (update_samba, value_func) = self.mapper().get(att)
152                             update_samba(att, value_func(value))
153                             self.gp_db.commit()
154
155     def ch_minPwdAge(self, attribute, val):
156         old_val = self.ldb.get_minPwdAge()
157         self.logger.info('KDC Minimum Password age was changed from %s to %s'
158                          % (old_val, val))
159         self.gp_db.store(str(self), attribute, str(old_val))
160         self.ldb.set_minPwdAge(val)
161
162     def ch_maxPwdAge(self, attribute, val):
163         old_val = self.ldb.get_maxPwdAge()
164         self.logger.info('KDC Maximum Password age was changed from %s to %s'
165                          % (old_val, val))
166         self.gp_db.store(str(self), attribute, str(old_val))
167         self.ldb.set_maxPwdAge(val)
168
169     def ch_minPwdLength(self, attribute, val):
170         old_val = self.ldb.get_minPwdLength()
171         self.logger.info(
172             'KDC Minimum Password length was changed from %s to %s'
173             % (old_val, val))
174         self.gp_db.store(str(self), attribute, str(old_val))
175         self.ldb.set_minPwdLength(val)
176
177     def ch_pwdProperties(self, attribute, val):
178         old_val = self.ldb.get_pwdProperties()
179         self.logger.info('KDC Password Properties were changed from %s to %s'
180                          % (old_val, val))
181         self.gp_db.store(str(self), attribute, str(old_val))
182         self.ldb.set_pwdProperties(val)
183
184     def days2rel_nttime(self, val):
185         seconds = 60
186         minutes = 60
187         hours = 24
188         sam_add = 10000000
189         val = int(val)
190         return str(-(val * seconds * minutes * hours * sam_add))
191
192     def mapper(self):
193         '''ldap value : samba setter'''
194         return {"minPwdAge": (self.ch_minPwdAge, self.days2rel_nttime),
195                 "maxPwdAge": (self.ch_maxPwdAge, self.days2rel_nttime),
196                 # Could be none, but I like the method assignment in
197                 # update_samba
198                 "minPwdLength": (self.ch_minPwdLength, lambda val: val),
199                 "pwdProperties": (self.ch_pwdProperties, lambda val: val),
200
201                 }
202
203     def __str__(self):
204         return 'System Access'
205
206     def rsop(self, gpo):
207         output = {}
208         inf_file = 'MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf'
209         if gpo.file_sys_path:
210             path = os.path.join(gpo.file_sys_path, inf_file)
211             inf_conf = self.parse(path)
212             if not inf_conf:
213                 return output
214             for section in inf_conf.sections():
215                 output[section] = {k: v for k, v in inf_conf.items(section) \
216                                       if gp_access_ext.apply_map.get(k)}
217         return output