gpoupdate: Rewrite samba_gpoupdate
authorDavid Mulder <dmulder@suse.com>
Thu, 25 May 2017 13:27:27 +0000 (07:27 -0600)
committerGarming Sam <garming@samba.org>
Mon, 20 Nov 2017 20:41:15 +0000 (21:41 +0100)
Use new python bindings and remove obsoleted code

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 3b8738e33062957c2a170a59c8778049630b4341..c3f7512a00d07e220cebb8609a4ec34ab888fe75 100644 (file)
@@ -17,6 +17,7 @@
 
 import sys
 import os
 
 import sys
 import os
+import tdb
 sys.path.insert(0, "bin/python")
 import samba.gpo as gpo
 import optparse
 sys.path.insert(0, "bin/python")
 import samba.gpo as gpo
 import optparse
@@ -31,6 +32,30 @@ from ConfigParser import ConfigParser
 from StringIO import StringIO
 from abc import ABCMeta, abstractmethod
 
 from StringIO import StringIO
 from abc import ABCMeta, abstractmethod
 
+class Backlog:
+    def __init__(self, sysvol_log):
+        if os.path.isfile(sysvol_log):
+            self.backlog = tdb.open(sysvol_log)
+        else:
+            self.backlog = tdb.Tdb(sysvol_log, 0, tdb.DEFAULT, os.O_CREAT|os.O_RDWR)
+        self.backlog.transaction_start()
+
+    def version(self, guid):
+        try:
+            old_version = int(self.backlog.get(guid))
+        except TypeError:
+            old_version = -1
+        return old_version
+
+    def store(self, guid, version):
+        self.backlog.store(guid, '%i' % version)
+
+    def commit(self):
+        self.backlog.transaction_commit()
+
+    def __del__(self):
+        self.backlog.close()
+
 class gp_ext(object):
     __metaclass__ = ABCMeta
 
 class gp_ext(object):
     __metaclass__ = ABCMeta
 
@@ -39,7 +64,7 @@ class gp_ext(object):
         pass
 
     @abstractmethod
         pass
 
     @abstractmethod
-    def parse(self, afile, ldb, conn, attr_log, lp):
+    def parse(self, afile, ldb, conn, lp):
         pass
 
     @abstractmethod
         pass
 
     @abstractmethod
@@ -50,10 +75,9 @@ class gp_ext(object):
 class inf_to():
     __metaclass__ = ABCMeta
 
 class inf_to():
     __metaclass__ = ABCMeta
 
-    def __init__(self, logger, ldb, dn, lp, attribute, val):
+    def __init__(self, logger, ldb, lp, attribute, val):
         self.logger = logger
         self.ldb = ldb
         self.logger = logger
         self.ldb = ldb
-        self.dn = dn
         self.attribute = attribute
         self.val = val
         self.lp = lp
         self.attribute = attribute
         self.val = val
         self.lp = lp
@@ -126,16 +150,13 @@ class gp_sec_ext(gp_ext):
         return "Security GPO extension"
 
     def list(self, rootpath):
         return "Security GPO extension"
 
     def list(self, rootpath):
-        path = "%s%s" % (rootpath, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
-        return path
+        return os.path.join(rootpath, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
 
     def listmachpol(self, rootpath):
 
     def listmachpol(self, rootpath):
-        path = "%s%s" % (rootpath, "Machine/Registry.pol")
-        return path
+        return os.path.join(rootpath, "Machine/Registry.pol")
 
     def listuserpol(self, rootpath):
 
     def listuserpol(self, rootpath):
-        path = "%s%s" % (rootpath, "User/Registry.pol")
-        return path
+        return os.path.join(rootpath, "User/Registry.pol")
 
     def populate_inf(self):
         return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb),
 
     def populate_inf(self):
         return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb),
@@ -145,14 +166,12 @@ class gp_sec_ext(gp_ext):
                                  }
                }
 
                                  }
                }
 
-    def read_inf(self, path, conn, attr_log):
+    def read_inf(self, path, conn):
         ret = False
         inftable = self.populate_inf()
 
         policy = conn.loadfile(path.replace('/', '\\')).decode('utf-16')
         current_section = None
         ret = False
         inftable = self.populate_inf()
 
         policy = conn.loadfile(path.replace('/', '\\')).decode('utf-16')
         current_section = None
-        LOG = open(attr_log, "a")
-        LOG.write(str(path.split('/')[2]) + '\n')
 
         # So here we would declare a boolean,
         # that would get changed to TRUE.
 
         # So here we would declare a boolean,
         # that would get changed to TRUE.
@@ -173,13 +192,12 @@ class gp_sec_ext(gp_ext):
                     (att, setter) = current_section.get(key)
                     value = value.encode('ascii', 'ignore')
                     ret = True
                     (att, setter) = current_section.get(key)
                     value = value.encode('ascii', 'ignore')
                     ret = True
-                    setter(self.logger, self.ldb, self.dn, self.lp, att, value).update_samba()
+                    setter(self.logger, self.ldb, self.lp, att, value).update_samba()
         return ret
 
         return ret
 
-    def parse(self, afile, ldb, conn, attr_log, lp):
+    def parse(self, afile, ldb, conn, lp):
         self.ldb = ldb
         self.lp = lp
         self.ldb = ldb
         self.lp = lp
-        self.dn = ldb.get_default_basedn()
 
         # Fixing the bug where only some Linux Boxes capitalize MACHINE
         if afile.endswith('inf'):
 
         # Fixing the bug where only some Linux Boxes capitalize MACHINE
         if afile.endswith('inf'):
@@ -189,149 +207,12 @@ class gp_sec_ext(gp_ext):
                 for case in [blist[idx].upper(), blist[idx].capitalize(), blist[idx].lower()]:
                     bfile = '/'.join(blist[:idx]) + '/' + case + '/' + '/'.join(blist[idx+1:])
                     try:
                 for case in [blist[idx].upper(), blist[idx].capitalize(), blist[idx].lower()]:
                     bfile = '/'.join(blist[:idx]) + '/' + case + '/' + '/'.join(blist[idx+1:])
                     try:
-                        return self.read_inf(bfile, conn, attr_log)
+                        return self.read_inf(bfile, conn)
                     except NTSTATUSError:
                         continue
             except ValueError:
                 try:
                     except NTSTATUSError:
                         continue
             except ValueError:
                 try:
-                    return self.read_inf(afile, conn, attr_log)
+                    return self.read_inf(afile, conn)
                 except:
                     return None
 
                 except:
                     return None
 
-
-def scan_log(sysvol_tdb):
-    data = {}
-    for key in sysvol_tdb.iterkeys():
-        data[key] = sysvol_tdb.get(key)
-    return data
-
-
-def Reset_Defaults(test_ldb):
-    test_ldb.set_minPwdAge(str(-25920000000000))
-    test_ldb.set_maxPwdAge(str(-38016000000000))
-    test_ldb.set_minPwdLength(str(7))
-    test_ldb.set_pwdProperties(str(1))
-
-
-def check_deleted(guid_list, backloggpo):
-    if backloggpo is None:
-        return False
-    for guid in backloggpo:
-        if guid not in guid_list:
-            return True
-    return False
-
-
-# The hierarchy is as per MS http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
-#
-# It does not care about local GPO, because GPO and snap-ins are not made in Linux yet.
-# It follows the linking order and children GPO are last written format.
-#
-# Also, couple further testing with call scripts entitled informant and informant2.
-# They explicitly show the returned hierarchically sorted list.
-
-
-def container_indexes(GUID_LIST):
-    '''So the original list will need to be seperated into containers.
-    Returns indexed list of when the container changes after hierarchy
-    '''
-    count = 0
-    container_indexes = []
-    while count < (len(GUID_LIST)-1):
-        if GUID_LIST[count][2] != GUID_LIST[count+1][2]:
-            container_indexes.append(count+1)
-        count += 1
-    container_indexes.append(len(GUID_LIST))
-    return container_indexes
-
-
-def sort_linked(SAMDB, guid_list, start, end):
-    '''So GPO in same level need to have link level.
-    This takes a container and sorts it.
-
-    TODO:  Small small problem, it is backwards
-    '''
-    containers = gpo_user.get_gpo_containers(SAMDB, guid_list[start][0])
-    for right_container in containers:
-        if right_container.get('dn') == guid_list[start][2]:
-            break
-    gplink = str(right_container.get('gPLink'))
-    gplink_split = gplink.split('[')
-    linked_order = []
-    ret_list = []
-    for ldap_guid in gplink_split:
-        linked_order.append(str(ldap_guid[10:48]))
-    count = len(linked_order) - 1
-    while count > 0:
-        ret_list.append([linked_order[count], guid_list[start][1], guid_list[start][2]])
-        count -= 1
-    return ret_list
-
-
-def establish_hierarchy(SamDB, GUID_LIST, DC_OU, global_dn):
-    '''Takes a list of GUID from gpo, and sorts them based on OU, and realm.
-    See http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
-    '''
-    final_list = []
-    count_unapplied_GPO = 0
-    for GUID in GUID_LIST:
-
-        container_iteration = 0
-        # Assume first it is not applied
-        applied = False
-        # Realm only written on last call, if the GPO is linked to multiple places
-        gpo_realm = False
-
-        # A very important call. This gets all of the linked information.
-        GPO_CONTAINERS = gpo_user.get_gpo_containers(SamDB, GUID)
-        for GPO_CONTAINER in GPO_CONTAINERS:
-
-            container_iteration += 1
-
-            if DC_OU == str(GPO_CONTAINER.get('dn')):
-                applied = True
-                insert_gpo = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
-                final_list.append(insert_gpo)
-                break
-
-            if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) == 1):
-                gpo_realm = True
-                applied = True
-
-
-            if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) > 1):
-                gpo_realm = True
-                applied = True
-
-
-            if container_iteration == len(GPO_CONTAINERS):
-                if gpo_realm == False:
-                    insert_dud = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
-                    final_list.insert(0, insert_dud)
-                    count_unapplied_GPO += 1
-                else:
-                    REALM_GPO = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
-                    final_list.insert(count_unapplied_GPO, REALM_GPO)
-
-    # After GPO are sorted into containers, let's sort the containers themselves.
-    # But first we can get the GPO that we don't care about, out of the way.
-    indexed_places = container_indexes(final_list)
-    count = 0
-    unapplied_gpo = []
-    # Sorted by container
-    sorted_gpo_list = []
-
-    # Unapplied GPO live at start of list, append them to final list
-    while final_list[0][1] == False:
-        unapplied_gpo.append(final_list[count])
-        count += 1
-    count = 0
-    sorted_gpo_list += unapplied_gpo
-
-    # A single container call gets the linked order for all GPO in container.
-    # So we need one call per container - > index of the Original list
-    indexed_places.insert(0, 0)
-    while count < (len(indexed_places)-1):
-        sorted_gpo_list += (sort_linked(SamDB, final_list, indexed_places[count], indexed_places[count+1]))
-        count += 1
-    return sorted_gpo_list
index 3a6ed99d7b8125a243edaf1eb01edd03797e8eab..bba5398571a9b335fca21a17ada489141515129c 100755 (executable)
@@ -3,6 +3,7 @@
 # Co-Edited by Matthieu Pattou July 2013 from original August 2013
 # Edited by Garming Sam Feb. 2014
 # Edited by Luke Morrison April 2014
 # Co-Edited by Matthieu Pattou July 2013 from original August 2013
 # Edited by Garming Sam Feb. 2014
 # Edited by Luke Morrison April 2014
+# Edited by David Mulder May 2017
 
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -22,15 +23,10 @@ and sorts them by container. Then, it applies the ones that haven't been
 applied, have changed, or is in the right container'''
 
 import os
 applied, have changed, or is in the right container'''
 
 import os
-import fcntl
 import sys
 import sys
-import tempfile
-import subprocess
-import tdb
 
 sys.path.insert(0, "bin/python")
 
 
 sys.path.insert(0, "bin/python")
 
-import samba
 import optparse
 from samba import getopt as options
 from samba.gpclass import *
 import optparse
 from samba import getopt as options
 from samba.gpclass import *
@@ -39,215 +35,92 @@ from samba.dcerpc import nbt
 from samba import smb
 import logging
 
 from samba import smb
 import logging
 
-
-# Finds all GPO Files ending in inf
-def gp_path_list(path):
-
-    GPO_LIST = []
-    for ext in gp_extensions:
-        GPO_LIST.append((ext, ext.list(path)))
-    return GPO_LIST
-
-
-def gpo_parser(GPO_LIST, ldb, conn, attr_log, lp):
-    '''The API method to parse the GPO
-    :param GPO_LIST:
-    :param ldb: Live instance of an LDB object AKA Samba
-    :param conn: Live instance of a CIFS connection
-    :param attr_log: backlog path for GPO and attribute to be written
-    no return except a newly updated Samba
-    '''
-
-    ret = False
-    for entry in GPO_LIST:
-        (ext, thefile) = entry
-        if ret == False:
-            ret = ext.parse(thefile, ldb, conn, attr_log, lp)
-        else:
-            temp = ext.parse(thefile, ldb, conn, attr_log, lp)
-    return ret
-
-
-class GPOServiceSetup:
-    def __init__(self):
-        """Initialize all components necessary to return instances of
-        a Samba lp context (smb.conf) and Samba LDB context
-        """
-
-        self.parser = optparse.OptionParser("samba_gpoupdate [options]")
-        self.sambaopts = options.SambaOptions(self.parser)
-        self.credopts = None
-        self.opts = None
-        self.args = None
-        self.lp = None
-        self.smbconf = None
-        self.creds = None
-        self.url = None
-
-    # Setters or Initializers
-    def init_parser(self):
-        '''Get the command line options'''
-        self.parser.add_option_group(self.sambaopts)
-        self.parser.add_option_group(options.VersionOptions(self.parser))
-        self.init_credopts()
-        self.parser.add_option("-H", dest="url", help="URL for the samdb")
-        self.parser.add_option_group(self.credopts)
-
-    def init_argsopts(self):
-        '''Set the options and the arguments'''
-        (opts, args) = self.parser.parse_args()
-
-        self.opts = opts
-        self.args = args
-
-    def init_credopts(self):
-        '''Set Credential operations'''
-        self.credopts = options.CredentialsOptions(self.parser)
-
-    def init_lp(self):
-        '''Set the loadparm context'''
-        self.lp = self.sambaopts.get_loadparm()
-        self.smbconf = self.lp.configfile
-        if (not self.opts.url):
-            self.url = self.lp.samdb_url()
-        else:
-            self.url = self.opts.url
-
-    def init_session(self):
-        '''Initialize the session'''
-        self.creds = self.credopts.get_credentials(self.lp,
-            fallback_machine=True)
-        self.session = system_session()
-
-    def InitializeService(self):
-        '''Inializer for the thread'''
-        self.init_parser()
-        self.init_argsopts()
-        self.init_lp()
-        self.init_session()
-
-    # Getters
-    def Get_LDB(self):
-        '''Return a live instance of Samba'''
-        SambaDB = SamDB(self.url, session_info=self.session,
-            credentials=self.creds, lp=self.lp)
-        return SambaDB
-
-    def Get_lp_Content(self):
-        '''Return an instance of a local lp context'''
-        return self.lp
-
-    def Get_Creds(self):
-        '''Return an instance of a local creds'''
-        return self.creds
-
-
-# Set up the GPO service
-GPOService = GPOServiceSetup()
-GPOService.InitializeService()
-
-# Get the Samba Instance
-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"), 'gpo.tdb')
-attr_log = '%s/%s' % (lp.get("path", "sysvol"), 'attrlog.txt')
-
-
-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
-net = Net(creds=creds, lp=lp)
-cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP |
-    nbt.NBT_SERVER_DS))
-dc_hostname = cldap_ret.pdc_dns_name
-
-try:
-    conn = smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
-except Exception, e:
-    raise Exception("Error connecting to '%s' using SMB" % dc_hostname, e)
-
-# Get the dn of the domain, and the dn of readable/writable DC
-global_dn = test_ldb.domain_dn()
-DC_OU = "OU=Domain Controllers" + ',' + global_dn
-
-# Set up a List of the GUID for all GPO's
-guid_list = [x['name'] for x in conn.list('%s/Policies' % lp.get("realm").lower())]
-SYSV_PATH = '%s/%s/%s' % (lp.get("path", "sysvol"), lp.get("realm"), 'Policies')
-
-hierarchy_gpos = establish_hierarchy(test_ldb, guid_list, DC_OU, global_dn)
-change_backlog = False
-
-# Take a local list of all current GPO list and run it against previous GPO's
-# to see if something has changed. If so reset default and re-apply GPO.
-Applicable_GPO = []
-for i in hierarchy_gpos:
-    Applicable_GPO += i
-
-# Flag gets set when
-GPO_Changed = False
-GPO_Deleted = check_deleted(Applicable_GPO, BackLoggedGPO)
-if (GPO_Deleted):
-    # Null the backlog
-    BackLoggedGPO = {}
-    # Reset defaults then overwrite them
-    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(logger)]
-    local_path = '%s/Policies' % lp.get("realm").lower() + '/' + guid + '/'
-    version = int(gpo.gpo_get_sysvol_gpt_version(lp.get("path", "sysvol") + '/' + local_path)[1])
+''' Fetch the hostname of a writable DC '''
+def get_dc_hostname():
+    net = Net(creds=creds, lp=lp)
+    cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP |
+        nbt.NBT_SERVER_DS))
+    return cldap_ret.pdc_dns_name
+
+''' Fetch a list of GUIDs for applicable GPOs '''
+def get_gpo_list(dc_hostname, creds, lp):
+    gpos = []
+    ads = gpo.ADS_STRUCT(dc_hostname, lp, creds)
+    if ads.connect():
+        gpos = ads.get_gpo_list(creds.get_username())
+    return gpos
+
+if __name__ == "__main__":
+    parser = optparse.OptionParser('samba_gpoupdate [options]')
+    sambaopts = options.SambaOptions(parser)
+
+    # Get the command line options
+    parser.add_option_group(sambaopts)
+    parser.add_option_group(options.VersionOptions(parser))
+    credopts = options.CredentialsOptions(parser)
+    parser.add_option('-H', '--url', dest='url', help='URL for the samdb')
+    parser.add_option_group(credopts)
+
+    # Set the options and the arguments
+    (opts, args) = parser.parse_args()
+
+    # Set the loadparm context
+    lp = sambaopts.get_loadparm()
+    if not opts.url:
+        url = lp.samdb_url()
+    else:
+        url = opts.url
+
+    # Initialize the session
+    creds = credopts.get_credentials(lp, fallback_machine=True)
+    session = system_session()
+
+    # 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)
+
+    '''Return a live instance of Samba'''
+    test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
+
+    # Read the readable backLog into a hashmap
+    # then open writable backLog in same location
+    sysvol_log = os.path.join(lp.get('cache directory'), 'gpo.tdb')
+
+    backlog = Backlog(sysvol_log)
+
+    dc_hostname = get_dc_hostname()
     try:
     try:
-        old_version = int(BackLoggedGPO.get(guid))
+        conn =  smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
     except:
     except:
-        old_version = -1
-    gpolist = gp_path_list(local_path)
-    if version != old_version:
-        GPO_Changed = True
-    # If the GPO has a dn that is applicable to Samba
-    if guid_eval[1]:
-        # If it has a GPO file that could apply to Samba
-        if gpolist[0][1]:
-            # If it we have not read it before and is not empty
-            # Rewrite entire logfile here
-            if  (version != 0) and GPO_Changed == True:
-                logger.info('GPO %s has changed' % guid)
-                try:
-                    change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log, lp)
-                except:
-                    logger.error('Failed to parse gpo %s' % guid)
-                    continue
-    BackLog.store(guid, '%i' % version)
-BackLog.transaction_commit()
-BackLog.close()
+        logger.error('Error connecting to \'%s\' using SMB' % dc_hostname)
+        raise
+    gpos = get_gpo_list(dc_hostname, creds, lp)
+
+    for gpo_obj in gpos:
+        guid = gpo_obj.name
+        if guid == 'Local Policy':
+            continue
+        gp_extensions = [gp_sec_ext(logger)]
+        local_path = os.path.join(lp.get('realm').lower(), 'Policies', guid)
+        version = int(gpo.gpo_get_sysvol_gpt_version(os.path.join(lp.get("path", "sysvol"), local_path))[1])
+        if version != backlog.version(guid):
+            logger.info('GPO %s has changed' % guid)
+            try:
+                for ext in gp_extensions:
+                    ext.parse(ext.list(local_path), test_ldb, conn, lp)
+            except:
+                logger.error('Failed to parse gpo %s' % guid)
+                continue
+        backlog.store(guid, version)
+    backlog.commit()