gpo: Make the gpoupdate script much more reliable
authorDavid Mulder <dmulder@suse.com>
Sat, 11 Feb 2017 14:53:07 +0000 (07:53 -0700)
committerGarming Sam <garming@samba.org>
Mon, 20 Nov 2017 20:41:14 +0000 (21:41 +0100)
Using a static file blanks the file when samba_gpoupdate crashes. Transformed
to a tdb file and added transactions. Add info logging to monitor gpo changes,
etc. Also handle parse errors and log an error message, then recover. Modified
the parsing code to use ConfigParser. Also, use the backslash in path names
when opening smb files, otherwise it fails against a windows server.

Signed-off-by: David Mulder <dmulder@suse.com>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/gpclass.py
source4/scripting/bin/samba_gpoupdate

index 304d670e432d3e2a4aeeb61e55d7525becc2e3f2..0e8d74ad1c699a2a4f44bf2d677849da8decb902 100644 (file)
@@ -27,6 +27,8 @@ from samba.samdb import SamDB
 from samba.netcmd import gpo as gpo_user
 import codecs
 from samba import NTSTATUSError
+from ConfigParser import ConfigParser
+from StringIO import StringIO
 
 class gp_ext(object):
     def list(self, rootpath):
@@ -42,22 +44,27 @@ class inf_to_ldb(object):
     parameter to Samba4. Not registry oriented whatsoever.
     '''
 
-    def __init__(self, ldb, dn, attribute, val):
+    def __init__(self, logger, ldb, dn, attribute, val):
+        self.logger = logger
         self.ldb = ldb
         self.dn = dn
         self.attribute = attribute
         self.val = val
 
     def ch_minPwdAge(self, val):
+        self.logger.info('KDC Minimum Password age was changed from %s to %s' % (self.ldb.get_minPwdAge(), val))
         self.ldb.set_minPwdAge(val)
 
     def ch_maxPwdAge(self, val):
+        self.logger.info('KDC Maximum Password age was changed from %s to %s' % (self.ldb.get_maxPwdAge(), val))
         self.ldb.set_maxPwdAge(val)
 
     def ch_minPwdLength(self, val):
+        self.logger.info('KDC Minimum Password length was changed from %s to %s' % (self.ldb.get_minPwdLength(), val))
         self.ldb.set_minPwdLength(val)
 
     def ch_pwdProperties(self, val):
+        self.logger.info('KDC Password Properties were changed from %s to %s' % (self.ldb.get_pwdProperties(), val))
         self.ldb.set_pwdProperties(val)
 
     def explicit(self):
@@ -95,6 +102,9 @@ class gp_sec_ext(gp_ext):
 
     count = 0
 
+    def __init__(self, logger):
+        self.logger = logger
+
     def __str__(self):
         return "Security GPO extension"
 
@@ -122,7 +132,7 @@ class gp_sec_ext(gp_ext):
         ret = False
         inftable = self.populate_inf()
 
-        policy = conn.loadfile(path).decode('utf-16')
+        policy = conn.loadfile(path.replace('/', '\\')).decode('utf-16')
         current_section = None
         LOG = open(attr_log, "a")
         LOG.write(str(path.split('/')[2]) + '\n')
@@ -133,23 +143,20 @@ class gp_sec_ext(gp_ext):
         # If at any point in time a GPO was applied,
         # then we return that boolean at the end.
 
-        for line in policy.splitlines():
-            line = line.strip()
-            if line[0] == '[':
-                section = line[1: -1]
-                current_section = inftable.get(section.encode('ascii', 'ignore'))
-
-            else:
-                # We must be in a section
-                if not current_section:
-                    continue
-                (key, value) = line.split("=")
-                key = key.strip()
+        inf_conf = ConfigParser()
+        inf_conf.optionxform=str
+        inf_conf.readfp(StringIO(policy))
+
+        for section in inf_conf.sections():
+            current_section = inftable.get(section)
+            if not current_section:
+                continue
+            for key, value in inf_conf.items(section):
                 if current_section.get(key):
                     (att, setter) = current_section.get(key)
                     value = value.encode('ascii', 'ignore')
                     ret = True
-                    setter(self.ldb, self.dn, att, value).update_samba()
+                    setter(self.logger, self.ldb, self.dn, att, value).update_samba()
         return ret
 
     def parse(self, afile, ldb, conn, attr_log):
@@ -174,13 +181,10 @@ class gp_sec_ext(gp_ext):
                     return None
 
 
-def scan_log(sysvol_path):
-    a = open(sysvol_path, "r")
+def scan_log(sysvol_tdb):
     data = {}
-    for line in a.readlines():
-        line = line.strip()
-        (guid, version) = line.split(" ")
-        data[guid] = int(version)
+    for key in sysvol_tdb.iterkeys():
+        data[key] = sysvol_tdb.get(key)
     return data
 
 
index ba83dcf7e91ab608ec6d4dbf0692d3e4eaec35e2..a5573cece265185627ecbd1ee34c0bef9c003b43 100755 (executable)
@@ -26,6 +26,7 @@ import fcntl
 import sys
 import tempfile
 import subprocess
+import tdb
 
 sys.path.insert(0, "bin/python")
 
@@ -36,6 +37,7 @@ from samba.gpclass import *
 from samba.net import Net
 from samba.dcerpc import nbt
 from samba import smb
+import logging
 
 
 # Finds all GPO Files ending in inf
@@ -140,19 +142,6 @@ class GPOServiceSetup:
         return self.creds
 
 
-def GetBackLog(sys_log):
-    """Reads BackLog and makes thread aware of which GPO are unchanged or empty
-    :param String sys_log: path to backLog
-    :return Dictionary previous_scanned_version: {Unedited GPO: Version Number}
-    *NOTE on Version below
-    """
-    previous_scanned_version = {}
-    if os.path.isfile(sys_log):
-        previous_scanned_version = scan_log(sys_log)
-        return previous_scanned_version
-    else:
-        return None
-
 # Set up the GPO service
 GPOService = GPOServiceSetup()
 GPOService.InitializeService()
@@ -163,18 +152,35 @@ test_ldb = GPOService.Get_LDB()
 # Get The lp context
 lp = GPOService.Get_lp_Content()
 
+# Set up logging
+logger = logging.getLogger('samba_gpoupdate')
+logger.addHandler(logging.StreamHandler(sys.stdout))
+logger.setLevel(logging.CRITICAL)
+log_level = lp.log_level()
+if log_level == 1:
+    logger.setLevel(logging.ERROR)
+elif log_level == 2:
+    logger.setLevel(logging.WARNING)
+elif log_level == 3:
+    logger.setLevel(logging.INFO)
+elif log_level >= 4:
+    logger.setLevel(logging.DEBUG)
+
 # Get the CREDS
 creds = GPOService.Get_Creds()
 
 # Read the readable backLog into a hashmap
 # then open writable backLog in same location
 BackLoggedGPO = None
-sys_log = '%s/%s' % (lp.get("path", "sysvol"), 'syslog.txt')
+sys_log = '%s/%s' % (lp.get("path", "sysvol"), 'gpo.tdb')
 attr_log = '%s/%s' % (lp.get("path", "sysvol"), 'attrlog.txt')
-BackLoggedGPO = GetBackLog(sys_log)
 
 
-BackLog = open(sys_log, "w")
+if os.path.isfile(sys_log):
+    BackLog = tdb.open(sys_log)
+else:
+    BackLog = tdb.Tdb(sys_log, 0, tdb.DEFAULT, os.O_CREAT|os.O_RDWR)
+BackLoggedGPO = scan_log(BackLog)
 
 
 # We need to know writable DC to setup SMB connection
@@ -215,13 +221,18 @@ if (GPO_Deleted):
     Reset_Defaults(test_ldb)
     GPO_Changed = False
 
+BackLog.transaction_start()
 for guid_eval in hierarchy_gpos:
     guid = guid_eval[0]
-    gp_extensions = [gp_sec_ext()]
+    gp_extensions = [gp_sec_ext(logger)]
     local_path = '%s/Policies' % lp.get("realm").lower() + '/' + guid + '/'
-    version = gpo.gpo_get_sysvol_gpt_version(lp.get("path", "sysvol") + '/' + local_path)[1]
+    version = int(gpo.gpo_get_sysvol_gpt_version(lp.get("path", "sysvol") + '/' + local_path)[1])
+    try:
+        old_version = int(BackLoggedGPO.get(guid))
+    except:
+        old_version = -1
     gpolist = gp_path_list(local_path)
-    if(version != BackLoggedGPO.get(guid)):
+    if version != old_version:
         GPO_Changed = True
     # If the GPO has a dn that is applicable to Samba
     if guid_eval[1]:
@@ -230,6 +241,13 @@ for guid_eval in hierarchy_gpos:
             # If it we have not read it before and is not empty
             # Rewrite entire logfile here
             if  (version != 0) and GPO_Changed == True:
-                change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log)
+                logger.info('GPO %s has changed' % guid)
+                try:
+                    change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log)
+                except:
+                    logger.error('Failed to parse gpo %s' % guid)
+                    continue
+    BackLog.store(guid, '%i' % version)
+BackLog.transaction_commit()
+BackLog.close()
 
-    BackLog.write('%s %i\n' % (guid, version))