import os
import tdb
sys.path.insert(0, "bin/python")
-import samba.gpo as gpo
-import optparse
-import ldb
-from samba.auth import system_session
-import samba.getopt as options
-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
from abc import ABCMeta, abstractmethod
import xml.etree.ElementTree as etree
+import re
+
+try:
+ from enum import Enum
+ GPOSTATE = Enum('GPOSTATE', 'APPLY ENFORCE UNAPPLY')
+except ImportError:
+ class GPOSTATE:
+ APPLY = 1
+ ENFORCE = 2
+ UNAPPLY = 3
class gp_log:
''' Log settings overwritten by gpo apply
- The gp_log is an xml file that stores a history of gpo changes (and the original setting value).
+ The gp_log is an xml file that stores a history of gpo changes (and the
+ original setting value).
The log is organized like so:
</user>
</gp>
- Each guid value contains a list of extensions, which contain a list of attributes. The guid value
- represents a GPO. The attributes are the values of those settings prior to the application of
- the GPO.
- The list of guids is enclosed within a user name, which represents the user the settings were
- applied to. This user may be the samaccountname of the local computer, which implies that these
- are machine policies.
- The applylog keeps track of the order in which the GPOs were applied, so that they can be rolled
- back in reverse, returning the machine to the state prior to policy application.
+ Each guid value contains a list of extensions, which contain a list of
+ attributes. The guid value represents a GPO. The attributes are the values
+ of those settings prior to the application of the GPO.
+ The list of guids is enclosed within a user name, which represents the user
+ the settings were applied to. This user may be the samaccountname of the
+ local computer, which implies that these are machine policies.
+ The applylog keeps track of the order in which the GPOs were applied, so
+ that they can be rolled back in reverse, returning the machine to the state
+ prior to policy application.
'''
def __init__(self, user, gpostore, db_log=None):
''' Initialize the gp_log
- param user - the username (or machine name) that policies are being applied to
- param gpostore - the GPOStorage obj which references the tdb which contains gp_logs
+ param user - the username (or machine name) that policies are
+ being applied to
+ param gpostore - the GPOStorage obj which references the tdb which
+ contains gp_logs
param db_log - (optional) a string to initialize the gp_log
'''
+ self._state = GPOSTATE.APPLY
self.gpostore = gpostore
self.username = user
if db_log:
self.gpdb = etree.fromstring(db_log)
else:
self.gpdb = etree.Element('gp')
- self.user = self.gpdb.find('user[@name="%s"]' % user)
- if self.user is None:
- self.user = etree.SubElement(self.gpdb, 'user')
- self.user.attrib['name'] = user
+ self.user = user
+ user_obj = self.gpdb.find('user[@name="%s"]' % user)
+ if user_obj is None:
+ user_obj = etree.SubElement(self.gpdb, 'user')
+ user_obj.attrib['name'] = user
+
+ def state(self, value):
+ ''' Policy application state
+ param value - APPLY, ENFORCE, or UNAPPLY
+
+ The behavior of the gp_log depends on whether we are applying policy,
+ enforcing policy, or unapplying policy. During an apply, old settings
+ are recorded in the log. During an enforce, settings are being applied
+ but the gp_log does not change. During an unapply, additions to the log
+ should be ignored (since function calls to apply settings are actually
+ reverting policy), but removals from the log are allowed.
+ '''
+ # If we're enforcing, but we've unapplied, apply instead
+ if value == GPOSTATE.ENFORCE:
+ user_obj = self.gpdb.find('user[@name="%s"]' % self.user)
+ apply_log = user_obj.find('applylog')
+ if apply_log is None or len(apply_log) == 0:
+ self._state = GPOSTATE.APPLY
+ else:
+ self._state = value
+ else:
+ self._state = value
def set_guid(self, guid):
''' Log to a different GPO guid
- param guid - guid value of the GPO from which we're applying policy
+ param guid - guid value of the GPO from which we're applying
+ policy
'''
- self.guid = self.user.find('guid[@value="%s"]' % guid)
- if self.guid is None:
- self.guid = etree.SubElement(self.user, 'guid')
- self.guid.attrib['value'] = guid
- apply_log = self.user.find('applylog')
- if apply_log is None:
- apply_log = etree.SubElement(self.user, 'applylog')
- item = etree.SubElement(apply_log, 'guid')
- item.attrib['count'] = '%d' % (len(apply_log)-1)
- item.attrib['value'] = guid
+ self.guid = guid
+ user_obj = self.gpdb.find('user[@name="%s"]' % self.user)
+ obj = user_obj.find('guid[@value="%s"]' % guid)
+ if obj is None:
+ obj = etree.SubElement(user_obj, 'guid')
+ obj.attrib['value'] = guid
+ if self._state == GPOSTATE.APPLY:
+ apply_log = user_obj.find('applylog')
+ if apply_log is None:
+ apply_log = etree.SubElement(user_obj, 'applylog')
+ item = etree.SubElement(apply_log, 'guid')
+ item.attrib['count'] = '%d' % (len(apply_log)-1)
+ item.attrib['value'] = guid
def apply_log_pop(self):
''' Pop a GPO guid from the applylog
return - last applied GPO guid
- Removes the GPO guid last added to the list, which is the most recently applied GPO.
+ Removes the GPO guid last added to the list, which is the most recently
+ applied GPO.
'''
- apply_log = self.user.find('applylog')
+ user_obj = self.gpdb.find('user[@name="%s"]' % self.user)
+ apply_log = user_obj.find('applylog')
if apply_log is not None:
ret = apply_log.find('guid[@count="%d"]' % (len(apply_log)-1))
if ret is not None:
apply_log.remove(ret)
return ret.attrib['value']
- if len(apply_log) == 0 and apply_log in self.user:
- self.user.remove(apply_log)
+ if len(apply_log) == 0 and apply_log in user_obj:
+ user_obj.remove(apply_log)
return None
def store(self, gp_ext_name, attribute, old_val):
''' Store an attribute in the gp_log
param gp_ext_name - Name of the extension applying policy
param attribute - The attribute being modified
- param old_val - The value of the attribute prior to policy application
+ param old_val - The value of the attribute prior to policy
+ application
'''
- assert self.guid is not None, "gpo guid was not set"
- ext = self.guid.find('gp_ext[@name="%s"]' % gp_ext_name)
+ if self._state == GPOSTATE.UNAPPLY or self._state == GPOSTATE.ENFORCE:
+ return None
+ user_obj = self.gpdb.find('user[@name="%s"]' % self.user)
+ guid_obj = user_obj.find('guid[@value="%s"]' % self.guid)
+ assert guid_obj is not None, "gpo guid was not set"
+ ext = guid_obj.find('gp_ext[@name="%s"]' % gp_ext_name)
if ext is None:
- ext = etree.SubElement(self.guid, 'gp_ext')
+ ext = etree.SubElement(guid_obj, 'gp_ext')
ext.attrib['name'] = gp_ext_name
attr = ext.find('attribute[@name="%s"]' % attribute)
if attr is None:
attr = etree.SubElement(ext, 'attribute')
attr.attrib['name'] = attribute
- attr.text = old_val
+ attr.text = old_val
def retrieve(self, gp_ext_name, attribute):
''' Retrieve a stored attribute from the gp_log
param gp_ext_name - Name of the extension which applied policy
param attribute - The attribute being retrieved
- return - The value of the attribute prior to policy application
+ return - The value of the attribute prior to policy
+ application
'''
- assert self.guid is not None, "gpo guid was not set"
- ext = self.guid.find('gp_ext[@name="%s"]' % gp_ext_name)
+ user_obj = self.gpdb.find('user[@name="%s"]' % self.user)
+ guid_obj = user_obj.find('guid[@value="%s"]' % self.guid)
+ assert guid_obj is not None, "gpo guid was not set"
+ ext = guid_obj.find('gp_ext[@name="%s"]' % gp_ext_name)
if ext is not None:
attr = ext.find('attribute[@name="%s"]' % attribute)
if attr is not None:
return None
def list(self, gp_extensions):
- ''' Return a list of attributes, their previous values, and functions to set them
- param gp_extensions - list of extension objects, for retrieving attr to func mappings
- return - list of (attr, value, apply_func) tuples for unapplying policy
+ ''' Return a list of attributes, their previous values, and functions
+ to set them
+ param gp_extensions - list of extension objects, for retrieving attr to
+ func mappings
+ return - list of (attr, value, apply_func) tuples for
+ unapplying policy
'''
- assert self.guid is not None, "gpo guid was not set"
+ user_obj = self.gpdb.find('user[@name="%s"]' % self.user)
+ guid_obj = user_obj.find('guid[@value="%s"]' % self.guid)
+ assert guid_obj is not None, "gpo guid was not set"
ret = []
data_maps = {}
for gp_ext in gp_extensions:
data_maps.update(gp_ext.apply_map())
- exts = self.guid.findall('gp_ext')
+ exts = guid_obj.findall('gp_ext')
if exts is not None:
for ext in exts:
- ext_map = {val[0]: val[1] for (key, val) in data_maps[ext.attrib['name']].items()}
attrs = ext.findall('attribute')
for attr in attrs:
- ret.append((attr.attrib['name'], attr.text, ext_map[attr.attrib['name']]))
+ func = None
+ if attr.attrib['name'] in data_maps[ext.attrib['name']]:
+ func = data_maps[ext.attrib['name']]\
+ [attr.attrib['name']][-1]
+ else:
+ for dmap in data_maps[ext.attrib['name']].keys():
+ if data_maps[ext.attrib['name']][dmap][0] == \
+ attr.attrib['name']:
+ func = data_maps[ext.attrib['name']][dmap][-1]
+ break
+ ret.append((attr.attrib['name'], attr.text, func))
return ret
def delete(self, gp_ext_name, attribute):
''' Remove an attribute from the gp_log
- param gp_ext_name - name of extension from which to remove the attribute
+ param gp_ext_name - name of extension from which to remove the
+ attribute
param attribute - attribute to remove
'''
- assert self.guid is not None, "gpo guid was not set"
- ext = self.guid.find('gp_ext[@name="%s"]' % gp_ext_name)
+ user_obj = self.gpdb.find('user[@name="%s"]' % self.user)
+ guid_obj = user_obj.find('guid[@value="%s"]' % self.guid)
+ assert guid_obj is not None, "gpo guid was not set"
+ ext = guid_obj.find('gp_ext[@name="%s"]' % gp_ext_name)
if ext is not None:
attr = ext.find('attribute[@name="%s"]' % attribute)
if attr is not None:
ext.remove(attr)
if len(ext) == 0:
- self.guid.remove(ext)
+ guid_obj.remove(ext)
def commit(self):
''' Write gp_log changes to disk '''
- if len(self.guid) == 0 and self.guid in self.user:
- self.user.remove(self.guid)
- if len(self.user) == 0 and self.user in self.gpdb:
- self.gpdb.remove(self.user)
self.gpostore.store(self.username, etree.tostring(self.gpdb, 'utf-8'))
class GPOStorage:
class gp_ext(object):
__metaclass__ = ABCMeta
+ def __init__(self, logger):
+ self.logger = logger
+
@abstractmethod
def list(self, rootpath):
pass
pass
@abstractmethod
- def parse(self, afile, ldb, conn, gp_db, lp):
+ def read(self, policy):
pass
+ def parse(self, afile, ldb, conn, gp_db, lp):
+ self.ldb = ldb
+ self.gp_db = gp_db
+ self.lp = lp
+
+ # Fixing the bug where only some Linux Boxes capitalize MACHINE
+ try:
+ blist = afile.split('/')
+ idx = afile.lower().split('/').index('machine')
+ 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(conn.loadfile(bfile.replace('/', '\\')))
+ except NTSTATUSError:
+ continue
+ except ValueError:
+ try:
+ return self.read(conn.loadfile(afile.replace('/', '\\')))
+ except Exception as e:
+ self.logger.error(str(e))
+ return None
+
@abstractmethod
def __str__(self):
pass
-class inf_to():
+class gp_ext_setter():
__metaclass__ = ABCMeta
def __init__(self, logger, ldb, gp_db, lp, attribute, val):
def __str__(self):
pass
-class inf_to_ldb(inf_to):
- '''This class takes the .inf file parameter (essentially a GPO file mapped to a GUID),
- hashmaps it to the Samba parameter, which then uses an ldb object to update the
- parameter to Samba4. Not registry oriented whatsoever.
+class inf_to_kdc_tdb(gp_ext_setter):
+ def mins_to_hours(self):
+ return '%d' % (int(self.val)/60)
+
+ def days_to_hours(self):
+ return '%d' % (int(self.val)*24)
+
+ def set_kdc_tdb(self, val):
+ old_val = self.gp_db.gpostore.get(self.attribute)
+ self.logger.info('%s was changed from %s to %s' % (self.attribute,
+ old_val, val))
+ if val is not None:
+ self.gp_db.gpostore.store(self.attribute, val)
+ self.gp_db.store(str(self), self.attribute, old_val)
+ else:
+ self.gp_db.gpostore.delete(self.attribute)
+ self.gp_db.delete(str(self), self.attribute)
+
+ def mapper(self):
+ return { 'kdc:user_ticket_lifetime': (self.set_kdc_tdb, self.explicit),
+ 'kdc:service_ticket_lifetime': (self.set_kdc_tdb,
+ self.mins_to_hours),
+ 'kdc:renewal_lifetime': (self.set_kdc_tdb,
+ self.days_to_hours),
+ }
+
+ def __str__(self):
+ return 'Kerberos Policy'
+
+class inf_to_ldb(gp_ext_setter):
+ '''This class takes the .inf file parameter (essentially a GPO file mapped
+ to a GUID), hashmaps it to the Samba parameter, which then uses an ldb
+ object to update the parameter to Samba4. Not registry oriented whatsoever.
'''
def ch_minPwdAge(self, val):
old_val = self.ldb.get_minPwdAge()
- self.logger.info('KDC Minimum Password age was changed from %s to %s' % (old_val, val))
+ self.logger.info('KDC Minimum Password age was changed from %s to %s' \
+ % (old_val, val))
self.gp_db.store(str(self), self.attribute, old_val)
self.ldb.set_minPwdAge(val)
def ch_maxPwdAge(self, val):
old_val = self.ldb.get_maxPwdAge()
- self.logger.info('KDC Maximum Password age was changed from %s to %s' % (old_val, val))
+ self.logger.info('KDC Maximum Password age was changed from %s to %s' \
+ % (old_val, val))
self.gp_db.store(str(self), self.attribute, old_val)
self.ldb.set_maxPwdAge(val)
def ch_minPwdLength(self, val):
old_val = self.ldb.get_minPwdLength()
- self.logger.info('KDC Minimum Password length was changed from %s to %s' % (old_val, val))
+ self.logger.info(
+ 'KDC Minimum Password length was changed from %s to %s' \
+ % (old_val, val))
self.gp_db.store(str(self), self.attribute, old_val)
self.ldb.set_minPwdLength(val)
def ch_pwdProperties(self, val):
old_val = self.ldb.get_pwdProperties()
- self.logger.info('KDC Password Properties were changed from %s to %s' % (old_val, val))
+ self.logger.info('KDC Password Properties were changed from %s to %s' \
+ % (old_val, val))
self.gp_db.store(str(self), self.attribute, old_val)
self.ldb.set_pwdProperties(val)
- def nttime2unix(self):
+ def days2rel_nttime(self):
seconds = 60
minutes = 60
hours = 24
def mapper(self):
'''ldap value : samba setter'''
- return { "minPwdAge" : (self.ch_minPwdAge, self.nttime2unix),
- "maxPwdAge" : (self.ch_maxPwdAge, self.nttime2unix),
- # Could be none, but I like the method assignment in update_samba
+ return { "minPwdAge" : (self.ch_minPwdAge, self.days2rel_nttime),
+ "maxPwdAge" : (self.ch_maxPwdAge, self.days2rel_nttime),
+ # Could be none, but I like the method assignment in
+ # update_samba
"minPwdLength" : (self.ch_minPwdLength, self.explicit),
"pwdProperties" : (self.ch_pwdProperties, self.explicit),
count = 0
- def __init__(self, logger):
- self.logger = logger
-
def __str__(self):
return "Security GPO extension"
def list(self, rootpath):
- return os.path.join(rootpath, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
+ return os.path.join(rootpath,
+ "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
def listmachpol(self, rootpath):
return os.path.join(rootpath, "Machine/Registry.pol")
return os.path.join(rootpath, "User/Registry.pol")
def apply_map(self):
- return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb),
- "MaximumPasswordAge": ("maxPwdAge", inf_to_ldb),
- "MinimumPasswordLength": ("minPwdLength", inf_to_ldb),
- "PasswordComplexity": ("pwdProperties", inf_to_ldb),
- }
+ return {"System Access": {"MinimumPasswordAge": ("minPwdAge",
+ inf_to_ldb),
+ "MaximumPasswordAge": ("maxPwdAge",
+ inf_to_ldb),
+ "MinimumPasswordLength": ("minPwdLength",
+ inf_to_ldb),
+ "PasswordComplexity": ("pwdProperties",
+ inf_to_ldb),
+ },
+ "Kerberos Policy": {"MaxTicketAge": (
+ "kdc:user_ticket_lifetime",
+ inf_to_kdc_tdb
+ ),
+ "MaxServiceAge": (
+ "kdc:service_ticket_lifetime",
+ inf_to_kdc_tdb
+ ),
+ "MaxRenewAge": (
+ "kdc:renewal_lifetime",
+ inf_to_kdc_tdb
+ ),
+ }
}
- def read_inf(self, path, conn):
+ def read(self, policy):
ret = False
inftable = self.apply_map()
- policy = conn.loadfile(path.replace('/', '\\'))
current_section = None
# So here we would declare a boolean,
(att, setter) = current_section.get(key)
value = value.encode('ascii', 'ignore')
ret = True
- setter(self.logger, self.ldb, self.gp_db, self.lp, att, value).update_samba()
+ setter(self.logger, self.ldb, self.gp_db, self.lp, att,
+ value).update_samba()
self.gp_db.commit()
return ret
- def parse(self, afile, ldb, conn, gp_db, lp):
- self.ldb = ldb
- self.gp_db = gp_db
- self.lp = lp
-
- # Fixing the bug where only some Linux Boxes capitalize MACHINE
- if afile.endswith('inf'):
- try:
- blist = afile.split('/')
- idx = afile.lower().split('/').index('machine')
- 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)
- except NTSTATUSError:
- continue
- except ValueError:
- try:
- return self.read_inf(afile, conn)
- except:
- return None
-