gpo: Always enforce policy, even if unchanged
[mdw/samba.git] / source4 / scripting / bin / samba_gpoupdate
1 #!/usr/bin/env python
2 # Copyright Luke Morrison <luc785@.hotmail.com> July 2013
3 # Co-Edited by Matthieu Pattou July 2013 from original August 2013
4 # Edited by Garming Sam Feb. 2014
5 # Edited by Luke Morrison April 2014
6 # Edited by David Mulder May 2017
7
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21 '''This script reads a log file of previous GPO, gets all GPO from sysvol
22 and sorts them by container. Then, it applies the ones that haven't been
23 applied, have changed, or is in the right container'''
24
25 import os
26 import sys
27
28 sys.path.insert(0, "bin/python")
29
30 import optparse
31 from samba import getopt as options
32 from samba.gpclass import *
33 from samba.net import Net
34 from samba.dcerpc import nbt
35 from samba import smb
36 import logging
37
38 ''' Fetch the hostname of a writable DC '''
39 def get_dc_hostname():
40     net = Net(creds=creds, lp=lp)
41     cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP |
42         nbt.NBT_SERVER_DS))
43     return cldap_ret.pdc_dns_name
44
45 ''' Fetch a list of GUIDs for applicable GPOs '''
46 def get_gpo_list(dc_hostname, creds, lp):
47     gpos = []
48     ads = gpo.ADS_STRUCT(dc_hostname, lp, creds)
49     if ads.connect():
50         gpos = ads.get_gpo_list(creds.get_username())
51     return gpos
52
53 def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
54     gp_db = store.get_gplog(creds.get_username())
55     dc_hostname = get_dc_hostname()
56     try:
57         conn =  smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
58     except:
59         logger.error('Error connecting to \'%s\' using SMB' % dc_hostname)
60         raise
61     gpos = get_gpo_list(dc_hostname, creds, lp)
62
63     for gpo_obj in gpos:
64         guid = gpo_obj.name
65         if guid == 'Local Policy':
66             continue
67         local_path = os.path.join(lp.get('realm').lower(), 'Policies', guid)
68         version = int(gpo.gpo_get_sysvol_gpt_version(os.path.join(lp.get("path", "sysvol"), local_path))[1])
69         if version != store.get_int(guid):
70             logger.info('GPO %s has changed' % guid)
71             gp_db.state(GPOSTATE.APPLY)
72         else:
73             gp_db.state(GPOSTATE.ENFORCE)
74         gp_db.set_guid(guid)
75         store.start()
76         try:
77             for ext in gp_extensions:
78                 ext.parse(ext.list(local_path), test_ldb, conn, gp_db, lp)
79         except:
80             logger.error('Failed to parse gpo %s' % guid)
81             store.cancel()
82             continue
83         store.store(guid, '%i' % version)
84         store.commit()
85
86 def unapply_log(gp_db):
87     while True:
88         item = gp_db.apply_log_pop()
89         if item:
90             yield item
91         else:
92             break
93
94 def unapply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
95     gp_db = store.get_gplog(creds.get_username())
96     gp_db.state(GPOSTATE.UNAPPLY)
97     for gpo_guid in unapply_log(gp_db):
98         gp_db.set_guid(gpo_guid)
99         unapply_attributes = gp_db.list(gp_extensions)
100         for attr in unapply_attributes:
101             attr_obj = attr[-1](logger, test_ldb, gp_db, lp, attr[0], attr[1])
102             attr_obj.mapper()[attr[0]][0](attr[1]) # Set the old value
103             gp_db.delete(str(attr_obj), attr[0])
104         gp_db.commit()
105
106 if __name__ == "__main__":
107     parser = optparse.OptionParser('samba_gpoupdate [options]')
108     sambaopts = options.SambaOptions(parser)
109
110     # Get the command line options
111     parser.add_option_group(sambaopts)
112     parser.add_option_group(options.VersionOptions(parser))
113     credopts = options.CredentialsOptions(parser)
114     parser.add_option('-H', '--url', dest='url', help='URL for the samdb')
115     parser.add_option('-X', '--unapply', help='Unapply Group Policy', action='store_true')
116     parser.add_option_group(credopts)
117
118     # Set the options and the arguments
119     (opts, args) = parser.parse_args()
120
121     # Set the loadparm context
122     lp = sambaopts.get_loadparm()
123     if not opts.url:
124         url = lp.samdb_url()
125     else:
126         url = opts.url
127
128     # Initialize the session
129     creds = credopts.get_credentials(lp, fallback_machine=True)
130     session = system_session()
131
132     # Set up logging
133     logger = logging.getLogger('samba_gpoupdate')
134     logger.addHandler(logging.StreamHandler(sys.stdout))
135     logger.setLevel(logging.CRITICAL)
136     log_level = lp.log_level()
137     if log_level == 1:
138         logger.setLevel(logging.ERROR)
139     elif log_level == 2:
140         logger.setLevel(logging.WARNING)
141     elif log_level == 3:
142         logger.setLevel(logging.INFO)
143     elif log_level >= 4:
144         logger.setLevel(logging.DEBUG)
145
146     cache_dir = lp.get('cache directory')
147     store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
148
149     gp_extensions = [gp_sec_ext(logger)]
150
151     # Get a live instance of Samba
152     test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
153
154     if not opts.unapply:
155         apply_gp(lp, creds, test_ldb, logger, store, gp_extensions)
156     else:
157         unapply_gp(lp, creds, test_ldb, logger, store, gp_extensions)
158