1
0
mirror of https://github.com/samba-team/samba.git synced 2025-02-03 13:47:25 +03:00

gpoupdate: Rewrite samba_gpoupdate

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>
This commit is contained in:
David Mulder 2017-05-25 07:27:27 -06:00 committed by Garming Sam
parent 8eba3b5d38
commit de9cee2262
2 changed files with 111 additions and 357 deletions

View File

@ -17,6 +17,7 @@
import sys
import os
import tdb
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
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
@ -39,7 +64,7 @@ class gp_ext(object):
pass
@abstractmethod
def parse(self, afile, ldb, conn, attr_log, lp):
def parse(self, afile, ldb, conn, lp):
pass
@abstractmethod
@ -50,10 +75,9 @@ class gp_ext(object):
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.dn = dn
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):
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):
path = "%s%s" % (rootpath, "Machine/Registry.pol")
return path
return os.path.join(rootpath, "Machine/Registry.pol")
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),
@ -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
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.
@ -173,13 +192,12 @@ class gp_sec_ext(gp_ext):
(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
def parse(self, afile, ldb, conn, attr_log, lp):
def parse(self, afile, ldb, conn, 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'):
@ -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:
return self.read_inf(bfile, conn, attr_log)
return self.read_inf(bfile, conn)
except NTSTATUSError:
continue
except ValueError:
try:
return self.read_inf(afile, conn, attr_log)
return self.read_inf(afile, conn)
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

View File

@ -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
# 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
@ -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
import fcntl
import sys
import tempfile
import subprocess
import tdb
sys.path.insert(0, "bin/python")
import samba
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
''' 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
# Finds all GPO Files ending in inf
def gp_path_list(path):
''' 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
GPO_LIST = []
for ext in gp_extensions:
GPO_LIST.append((ext, ext.list(path)))
return GPO_LIST
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)
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
'''
# Set the options and the arguments
(opts, args) = parser.parse_args()
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
# 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()
class GPOServiceSetup:
def __init__(self):
"""Initialize all components necessary to return instances of
a Samba lp context (smb.conf) and Samba LDB context
"""
# 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)
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
'''Return a live instance of Samba'''
test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
# 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)
# 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')
def init_argsopts(self):
'''Set the options and the arguments'''
(opts, args) = self.parser.parse_args()
backlog = Backlog(sysvol_log)
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])
dc_hostname = get_dc_hostname()
try:
old_version = int(BackLoggedGPO.get(guid))
conn = smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
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()