diff --git a/salt/modules/win_lgpo.py b/salt/modules/win_lgpo.py index 2d3fda61296a..1bcda44f6bb2 100644 --- a/salt/modules/win_lgpo.py +++ b/salt/modules/win_lgpo.py @@ -40,6 +40,8 @@ # Import Python libs from __future__ import absolute_import, unicode_literals, print_function import csv +import glob +import zlib import io import os import logging @@ -71,7 +73,6 @@ UUID = uuid.uuid4().hex adm_policy_name_map = {True: {}, False: {}} -adm_policy_key_map = {} HAS_WINDOWS_MODULES = False # define some global XPATH variables that we'll set assuming all our imports are # good @@ -86,7 +87,6 @@ TRUE_LIST_XPATH = None FALSE_LIST_XPATH = None REGKEY_XPATH = None -REGKEY_XPATH_MAPPED = None POLICY_ANCESTOR_XPATH = None ALL_CLASS_POLICY_XPATH = None ADML_DISPLAY_NAME_XPATH = None @@ -116,8 +116,8 @@ VALUE_XPATH = etree.XPath('.//*[local-name() = "value"]') TRUE_LIST_XPATH = etree.XPath('.//*[local-name() = "trueList"]') FALSE_LIST_XPATH = etree.XPath('.//*[local-name() = "falseList"]') - REGKEY_XPATH = etree.XPath('//*[translate(@*[local-name() = "key"], "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz") = $keyvalue]') - REGKEY_XPATH_MAPPED = etree.XPath('//*[@key = $keyvalue]') + # REGKEY_XPATH = etree.XPath('//*[translate(@*[local-name() = "key"], "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz") = $keyvalue]') + REGKEY_XPATH = etree.XPath('//*[@key = $keyvalue]') POLICY_ANCESTOR_XPATH = etree.XPath('ancestor::*[local-name() = "policy"]') ALL_CLASS_POLICY_XPATH = etree.XPath('//*[local-name() = "policy" and (@*[local-name() = "class"] = "Both" or @*[local-name() = "class"] = $registry_class)]') ADML_DISPLAY_NAME_XPATH = etree.XPath('//*[local-name() = $displayNameType and @*[local-name() = "id"] = $displayNameId]') @@ -493,7 +493,7 @@ def __init__(self): self.smb_server_name_hardening_levels = { 0: 'Off', 1: 'Accept if provided by client', - 2: 'Require from client', + 2: 'Required from client', None: 'Not Defined', '(value not set)': 'Not Defined', } @@ -552,7 +552,7 @@ def __init__(self): 0: 'No minimum', 1: 'DES_CBC_CRC', 2: 'DES_CBD_MD5', - 4: 'RC4_MHAC_MD5', + 4: 'RC4_HMAC_MD5', 8: 'AES128_HMAC_SHA1', 16: 'AES256_HMAC_SHA1', 2147483616: 'Future Encryption Types', @@ -842,7 +842,8 @@ def __init__(self): 'Type': 'REG_MULTI_SZ' }, 'Transform': { - 'Put': '_multi_string_put_transform' + 'Put': '_multi_string_put_transform', + 'Get': '_multi_string_get_transform' } }, 'RemoteRegistryExactPaths': { @@ -858,7 +859,8 @@ def __init__(self): 'Type': 'REG_MULTI_SZ' }, 'Transform': { - 'Put': '_multi_string_put_transform' + 'Put': '_multi_string_put_transform', + 'Get': '_multi_string_get_transform' } }, 'RemoteRegistryPaths': { @@ -873,7 +875,8 @@ def __init__(self): 'Type': 'REG_MULTI_SZ' }, 'Transform': { - 'Put': '_multi_string_put_transform' + 'Put': '_multi_string_put_transform', + 'Get': '_multi_string_get_transform' } }, 'RestrictNullSessAccess': { @@ -902,7 +905,8 @@ def __init__(self): 'Type': 'REG_MULTI_SZ' }, 'Transform': { - 'Put': '_multi_string_put_transform' + 'Put': '_multi_string_put_transform', + 'Get': '_multi_string_get_transform' } }, 'ForceGuest': { @@ -1886,7 +1890,7 @@ def __init__(self): 'Path': 'Software\\Microsoft\\Windows NT\\' 'CurrentVersion\\Winlogon', 'Value': 'ScRemoveOption', - 'Type': 'REG_DWORD', + 'Type': 'REG_SZ', }, 'Transform': { 'Get': '_dict_lookup', @@ -3298,6 +3302,7 @@ def __init__(self): 'Policy': 'Access Credential Manager as a trusted ' 'caller', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeTrustedCredManAccessPrivilege' @@ -3310,6 +3315,7 @@ def __init__(self): 'SeNetworkLogonRight': { 'Policy': 'Access this computer from the network', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeNetworkLogonRight' @@ -3322,6 +3328,7 @@ def __init__(self): 'SeTcbPrivilege': { 'Policy': 'Act as part of the operating system', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeTcbPrivilege' @@ -3334,6 +3341,7 @@ def __init__(self): 'SeMachineAccountPrivilege': { 'Policy': 'Add workstations to domain', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeMachineAccountPrivilege' @@ -3346,6 +3354,7 @@ def __init__(self): 'SeIncreaseQuotaPrivilege': { 'Policy': 'Adjust memory quotas for a process', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeIncreaseQuotaPrivilege' @@ -3358,6 +3367,7 @@ def __init__(self): 'SeInteractiveLogonRight': { 'Policy': 'Allow log on locally', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeInteractiveLogonRight' @@ -3370,6 +3380,7 @@ def __init__(self): 'SeRemoteInteractiveLogonRight': { 'Policy': 'Allow log on through Remote Desktop Services', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeRemoteInteractiveLogonRight' @@ -3382,6 +3393,7 @@ def __init__(self): 'SeBackupPrivilege': { 'Policy': 'Backup files and directories', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeBackupPrivilege' @@ -3394,6 +3406,7 @@ def __init__(self): 'SeChangeNotifyPrivilege': { 'Policy': 'Bypass traverse checking', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeChangeNotifyPrivilege' @@ -3406,6 +3419,7 @@ def __init__(self): 'SeSystemtimePrivilege': { 'Policy': 'Change the system time', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeSystemtimePrivilege' @@ -3418,6 +3432,7 @@ def __init__(self): 'SeTimeZonePrivilege': { 'Policy': 'Change the time zone', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeTimeZonePrivilege' @@ -3430,6 +3445,7 @@ def __init__(self): 'SeCreatePagefilePrivilege': { 'Policy': 'Create a pagefile', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeCreatePagefilePrivilege' @@ -3442,6 +3458,7 @@ def __init__(self): 'SeCreateTokenPrivilege': { 'Policy': 'Create a token object', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeCreateTokenPrivilege' @@ -3454,6 +3471,7 @@ def __init__(self): 'SeCreateGlobalPrivilege': { 'Policy': 'Create global objects', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeCreateGlobalPrivilege' @@ -3466,6 +3484,7 @@ def __init__(self): 'SeCreatePermanentPrivilege': { 'Policy': 'Create permanent shared objects', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeCreatePermanentPrivilege' @@ -3478,6 +3497,7 @@ def __init__(self): 'SeCreateSymbolicLinkPrivilege': { 'Policy': 'Create symbolic links', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeCreateSymbolicLinkPrivilege' @@ -3490,6 +3510,7 @@ def __init__(self): 'SeDebugPrivilege': { 'Policy': 'Debug programs', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeDebugPrivilege' @@ -3503,6 +3524,7 @@ def __init__(self): 'Policy': 'Deny access to this computer from the ' 'network', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeDenyNetworkLogonRight' @@ -3515,6 +3537,7 @@ def __init__(self): 'SeDenyBatchLogonRight': { 'Policy': 'Deny log on as a batch job', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeDenyBatchLogonRight' @@ -3527,6 +3550,7 @@ def __init__(self): 'SeDenyServiceLogonRight': { 'Policy': 'Deny log on as a service', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeDenyServiceLogonRight' @@ -3539,6 +3563,7 @@ def __init__(self): 'SeDenyInteractiveLogonRight': { 'Policy': 'Deny log on locally', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeDenyInteractiveLogonRight' @@ -3551,6 +3576,7 @@ def __init__(self): 'SeDenyRemoteInteractiveLogonRight': { 'Policy': 'Deny log on through Remote Desktop Services', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeDenyRemoteInteractiveLogonRight' @@ -3564,6 +3590,7 @@ def __init__(self): 'Policy': 'Enable computer and user accounts to be ' 'trusted for delegation', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeEnableDelegationPrivilege' @@ -3576,6 +3603,7 @@ def __init__(self): 'SeRemoteShutdownPrivilege': { 'Policy': 'Force shutdown from a remote system', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeRemoteShutdownPrivilege' @@ -3588,6 +3616,7 @@ def __init__(self): 'SeAuditPrivilege': { 'Policy': 'Generate security audits', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeAuditPrivilege' @@ -3600,6 +3629,7 @@ def __init__(self): 'SeImpersonatePrivilege': { 'Policy': 'Impersonate a client after authentication', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeImpersonatePrivilege' @@ -3612,6 +3642,7 @@ def __init__(self): 'SeIncreaseWorkingSetPrivilege': { 'Policy': 'Increase a process working set', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeIncreaseWorkingSetPrivilege' @@ -3623,6 +3654,7 @@ def __init__(self): }, 'SeIncreaseBasePriorityPrivilege': { 'Policy': 'Increase scheduling priority', + 'rights_assignment': True, 'lgpo_section': self.user_rights_assignment_gpedit_path, 'Settings': None, 'LsaRights': { @@ -3636,6 +3668,7 @@ def __init__(self): 'SeLoadDriverPrivilege': { 'Policy': 'Load and unload device drivers', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeLoadDriverPrivilege' @@ -3648,6 +3681,7 @@ def __init__(self): 'SeLockMemoryPrivilege': { 'Policy': 'Lock pages in memory', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeLockMemoryPrivilege' @@ -3660,6 +3694,7 @@ def __init__(self): 'SeBatchLogonRight': { 'Policy': 'Log on as a batch job', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeBatchLogonRight' @@ -3672,6 +3707,7 @@ def __init__(self): 'SeServiceLogonRight': { 'Policy': 'Log on as a service', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeServiceLogonRight' @@ -3684,6 +3720,7 @@ def __init__(self): 'SeSecurityPrivilege': { 'Policy': 'Manage auditing and security log', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeSecurityPrivilege' @@ -3696,6 +3733,7 @@ def __init__(self): 'SeRelabelPrivilege': { 'Policy': 'Modify an object label', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeRelabelPrivilege' @@ -3708,6 +3746,7 @@ def __init__(self): 'SeSystemEnvironmentPrivilege': { 'Policy': 'Modify firmware environment values', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeSystemEnvironmentPrivilege' @@ -3720,6 +3759,7 @@ def __init__(self): 'SeManageVolumePrivilege': { 'Policy': 'Perform volume maintenance tasks', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeManageVolumePrivilege' @@ -3732,6 +3772,7 @@ def __init__(self): 'SeProfileSingleProcessPrivilege': { 'Policy': 'Profile single process', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeProfileSingleProcessPrivilege' @@ -3744,6 +3785,7 @@ def __init__(self): 'SeSystemProfilePrivilege': { 'Policy': 'Profile system performance', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeSystemProfilePrivilege' @@ -3756,6 +3798,7 @@ def __init__(self): 'SeUndockPrivilege': { 'Policy': 'Remove computer from docking station', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeUndockPrivilege' @@ -3768,6 +3811,7 @@ def __init__(self): 'SeAssignPrimaryTokenPrivilege': { 'Policy': 'Replace a process level token', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeAssignPrimaryTokenPrivilege' @@ -3780,6 +3824,7 @@ def __init__(self): 'SeRestorePrivilege': { 'Policy': 'Restore files and directories', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeRestorePrivilege' @@ -3792,6 +3837,7 @@ def __init__(self): 'SeShutdownPrivilege': { 'Policy': 'Shut down the system', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeShutdownPrivilege' @@ -3804,6 +3850,7 @@ def __init__(self): 'SeSyncAgentPrivilege': { 'Policy': 'Synchronize directory service data', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeSyncAgentPrivilege' @@ -3816,6 +3863,7 @@ def __init__(self): 'SeTakeOwnershipPrivilege': { 'Policy': 'Take ownership of files or other objects', 'lgpo_section': self.user_rights_assignment_gpedit_path, + 'rights_assignment': True, 'Settings': None, 'LsaRights': { 'Option': 'SeTakeOwnershipPrivilege' @@ -4111,7 +4159,8 @@ def __init__(self): 'Type': 'REG_MULTI_SZ' }, 'Transform': { - 'Put': '_multi_string_put_transform' + 'Put': '_multi_string_put_transform', + 'Get': '_multi_string_get_transform' } }, 'DCAllowedNTLMServers': { @@ -4125,7 +4174,8 @@ def __init__(self): 'Type': 'REG_MULTI_SZ' }, 'Transform': { - 'Put': '_multi_string_put_transform' + 'Put': '_multi_string_put_transform', + 'Get': '_multi_string_get_transform' } }, 'AuditReceivingNTLMTraffic': { @@ -4312,7 +4362,8 @@ def __init__(self): 'Type': 'REG_MULTI_SZ' }, 'Transform': { - 'Put': '_multi_string_put_transform' + 'Put': '_multi_string_put_transform', + 'Get': '_multi_string_get_transform' } }, 'AuthenticodeEnabled': { @@ -4533,7 +4584,7 @@ def _driver_signing_reg_conversion(cls, val, **kwargs): converts the binary value in the registry for driver signing into the correct string representation ''' - log.debug('we have %s for the driver signing value', val) + log.trace('we have %s for the driver signing value', val) if val is not None: # since this is from secedit, it should be 3, _val = val.split(',') @@ -4622,7 +4673,7 @@ def _powershell_script_order_conversion(cls, val, **kwargs): converts true/false/None to the GUI representation of the powershell startup/shutdown script order ''' - log.debug('script order value = %s', val) + log.trace('script order value = %s', val) if val is None or val == 'None': return 'Not Configured' elif val == 'true': @@ -4655,17 +4706,17 @@ def _dict_lookup(cls, item, **kwargs): kwarg value_lookup bool to determine if item should be compared to keys or values ''' - log.debug('item == %s', item) + log.trace('item == %s', item) value_lookup = kwargs.get('value_lookup', False) if 'lookup' in kwargs: for k, v in six.iteritems(kwargs['lookup']): if value_lookup: if six.text_type(v).lower() == six.text_type(item).lower(): - log.debug('returning key %s', k) + log.trace('returning key %s', k) return k else: if six.text_type(k).lower() == six.text_type(item).lower(): - log.debug('returning value %s', v) + log.trace('returning value %s', v) return v return 'Invalid Value' @@ -4716,7 +4767,7 @@ def _dict_lookup_bitwise_add(cls, item, **kwargs): @classmethod def _multi_string_put_transform(cls, item, **kwargs): ''' - transform for a REG_MULTI_SZ to properly handle "Not Defined" + transform for setting REG_MULTI_SZ to properly handle "Not Defined" ''' if isinstance(item, list): return item @@ -4728,10 +4779,22 @@ def _multi_string_put_transform(cls, item, **kwargs): else: return 'Invalid Value' + @classmethod + def _multi_string_get_transform(cls, item, **kwargs): + ''' + transform for getting REG_MULTI_SZ to properly handle `None` + ''' + if isinstance(item, list): + return item + elif item is None: + return 'Not Defined' + else: + return 'Invalid Value' + @classmethod def _string_put_transform(cls, item, **kwargs): ''' - transfrom for a REG_SZ to properly handle "Not Defined" + transform for a REG_SZ to properly handle "Not Defined" ''' if isinstance(item, six.string_types): if item.lower() == 'not defined': @@ -4791,13 +4854,105 @@ def _remove_unicode_encoding(xml_file): ''' attempts to remove the "encoding='unicode'" from an xml file as lxml does not support that on a windows node currently - see issue #38100 + + see issue #38100 (Search.adml) + + For some reason this file is encoded 'utf-16' + ''' + with salt.utils.files.fopen(xml_file, 'rb') as f: + xml_content = f.read() + modified_xml = re.sub(r' encoding=[\'"]+unicode[\'"]+', '', + xml_content.decode('utf-16'), count=1) + xml_tree = lxml.etree.parse(six.StringIO(modified_xml)) + return xml_tree + + +def _remove_invalid_xmlns(xml_file): + ''' + Attempts to remove an invalid xmlns entry in newer versions of + WindowsDefender.adml + + xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions" + + For some reason this file is encoded 'utf-8' ''' with salt.utils.files.fopen(xml_file, 'rb') as f: xml_content = f.read() - modified_xml = re.sub(r' encoding=[\'"]+unicode[\'"]+', '', xml_content.decode('utf-16'), count=1) - xmltree = lxml.etree.parse(six.StringIO(modified_xml)) - return xmltree + modified_xml = re.sub(r' xmlns=[\'"]+.*[\'"]+', '', + xml_content.decode('utf-8'), count=1) + xml_tree = lxml.etree.parse(six.StringIO(modified_xml)) + return xml_tree + + +def _parse_xml(adm_file): + ''' + Parse the admx/adml file. There are 3 scenarios (so far) that we'll likely + encounter: + + 1. Valid File + 2. invalid encoding (encoding="unicode") which the lxml library doesn't + recognize + 3. invalid xmlns entry in the xml header, which the lxml library doesn't + recognize + ''' + parser = lxml.etree.XMLParser(remove_comments=True) + + modified_xml = '' + with salt.utils.files.fopen(adm_file, 'rb') as rfh: + file_hash = '%08X' % (zlib.crc32(rfh.read()) & 0xffffffff) + + name, ext = os.path.splitext(os.path.basename(adm_file)) + hashed_filename = '%s-%s%s' % (name, file_hash, ext) + + cache_dir = os.path.join(__opts__['cachedir'], 'lgpo', 'policy_defs') + if not os.path.exists(cache_dir): + os.makedirs(cache_dir) + + out_file = os.path.join(cache_dir, hashed_filename) + + if not os.path.isfile(out_file): + log.debug('LGPO: Generating policy template cache for %s%s' % (name, ext)) + + # Remove old files, keep the cache clean + file_list = glob.glob(os.path.join(cache_dir, '%s*%s' % (name, ext))) + for file_path in file_list: + os.remove(file_path) + + # Load the file + with salt.utils.files.fopen(adm_file, 'rb') as rfh: + + encoding = 'utf-8' + raw = rfh.read() + try: + raw = raw.decode(encoding) + except UnicodeDecodeError: + log.trace('LGPO: Detecting encoding') + encoding = 'utf-16' + raw = raw.decode(encoding) + for line in raw.split('\r\n'): + if 'key="' in line: + start = line.index('key="') + q1 = line[start:].index('"') + start + q2 = line[q1 + 1:].index('"') + q1 + 1 + line = line.replace(line[start:q2], line[start:q2].lower()) + found_key = True + modified_xml += line + '\r\n' + + with salt.utils.files.fopen(out_file, 'wb') as wfh: + wfh.write(modified_xml.encode(encoding)) + + try: + # First we'll try to parse valid xml + xml_tree = lxml.etree.parse(out_file, parser=parser) + except lxml.etree.XMLSyntaxError: + try: + # Next we'll try invalid encoding (see issue #38100) + xml_tree = _remove_unicode_encoding(out_file) + except lxml.etree.XMLSyntaxError: + # Finally we'll try invalid xmlns entry, if this fails, we just want + # to raise the error + xml_tree = _remove_invalid_xmlns(out_file) + return xml_tree def _load_policy_definitions(path='c:\\Windows\\PolicyDefinitions', @@ -4823,51 +4978,45 @@ def _load_policy_definitions(path='c:\\Windows\\PolicyDefinitions', policydef_resources_xpath = etree.XPath('/policyDefinitionResources') for root, dirs, files in salt.utils.path.os_walk(path): if root == path: - for t_admfile in files: - admfile = os.path.join(root, t_admfile) - parser = lxml.etree.XMLParser(remove_comments=True) - # see issue #38100 + for t_admx_file in files: + admx_file = os.path.join(root, t_admx_file) + # Parse xml for the ADMX file try: - xmltree = lxml.etree.parse(admfile, parser=parser) + xml_tree = _parse_xml(admx_file) except lxml.etree.XMLSyntaxError: - try: - xmltree = _remove_unicode_encoding(admfile) - # TODO: This needs to be more specific - except Exception: - log.exception('Handle this explicitly') - log.error('A error was found while processing admx ' - 'file %s, all policies from this file will ' - 'be unavailable via this module', admfile) - continue - namespaces = xmltree.getroot().nsmap + log.error('An error was found while processing admx ' + 'file %s, all policies from this file will ' + 'be unavailable via this module', admx_file) + continue + namespaces = xml_tree.getroot().nsmap namespace_string = '' if None in namespaces: namespaces['None'] = namespaces[None] namespaces.pop(None) namespace_string = 'None:' - this_prefix = xmltree.xpath( + this_prefix = xml_tree.xpath( '/{0}policyDefinitions/{0}policyNamespaces/{0}target/@prefix'.format(namespace_string), namespaces=namespaces)[0] - this_namespace = xmltree.xpath( + this_namespace = xml_tree.xpath( '/{0}policyDefinitions/{0}policyNamespaces/{0}target/@namespace'.format(namespace_string), namespaces=namespaces)[0] - categories = xmltree.xpath( + categories = xml_tree.xpath( '/{0}policyDefinitions/{0}categories/{0}category'.format(namespace_string), namespaces=namespaces) for category in categories: temp_cat = category temp_cat = _updateNamespace(temp_cat, this_namespace) policydefs_categories_xpath(t_policy_definitions)[0].append(temp_cat) - policies = xmltree.xpath('/{0}policyDefinitions/{0}policies/{0}policy'.format(namespace_string), + policies = xml_tree.xpath('/{0}policyDefinitions/{0}policies/{0}policy'.format(namespace_string), namespaces=namespaces) for policy in policies: temp_pol = policy temp_pol = _updateNamespace(temp_pol, this_namespace) if 'key' in temp_pol.attrib: temp_pol = _updatePolicyElements(temp_pol, temp_pol.attrib['key']) - adm_policy_key_map[temp_pol.attrib['key'].lower()] = temp_pol.attrib['key'] + policydefs_policies_xpath(t_policy_definitions)[0].append(temp_pol) - policy_namespaces = xmltree.xpath( + policy_namespaces = xml_tree.xpath( '/{0}policyDefinitions/{0}policyNamespaces/{0}*'.format(namespace_string), namespaces=namespaces) for policy_ns in policy_namespaces: @@ -4882,65 +5031,58 @@ def _load_policy_definitions(path='c:\\Windows\\PolicyDefinitions', adml_file = os.path.join( root, language, - os.path.splitext(t_admfile)[0] + '.adml') + os.path.splitext(t_admx_file)[0] + '.adml') if not __salt__['file.file_exists'](adml_file): log.info('An ADML file in the specified ADML language ' '"%s" does not exist for the ADMX "%s", the ' 'the abbreviated language code will be tried.', - language, t_admfile) + language, t_admx_file) adml_file = os.path.join( root, language.split('-')[0], - os.path.splitext(t_admfile)[0] + '.adml') + os.path.splitext(t_admx_file)[0] + '.adml') if not __salt__['file.file_exists'](adml_file): log.info('An ADML file in the specified ADML language ' 'code %s does not exist for the ADMX "%s", ' 'the fallback language will be tried.', - language[:2], t_admfile) + language[:2], t_admx_file) adml_file = os.path.join( root, display_language_fallback, - os.path.splitext(t_admfile)[0] + '.adml') + os.path.splitext(t_admx_file)[0] + '.adml') if not __salt__['file.file_exists'](adml_file): log.info('An ADML file in the specified ADML ' 'fallback language "%s" ' 'does not exist for the ADMX "%s" ' 'the abbreviated fallback language code ' 'will be tried.', - display_language_fallback, t_admfile) + display_language_fallback, t_admx_file) adml_file = os.path.join( root, display_language_fallback.split('-')[0], - os.path.splitext(t_admfile)[0] + '.adml') + os.path.splitext(t_admx_file)[0] + '.adml') if not __salt__['file.file_exists'](adml_file): msg = ('An ADML file in the specified ADML language ' '"{0}" and the fallback language "{1}" do not ' 'exist for the ADMX "{2}".') raise SaltInvocationError(msg.format(language, display_language_fallback, - t_admfile)) + t_admx_file)) + # Parse xml for the ADML file try: - xmltree = lxml.etree.parse(adml_file) + xml_tree = _parse_xml(adml_file) except lxml.etree.XMLSyntaxError: - # see issue #38100 - try: - xmltree = _remove_unicode_encoding(adml_file) - # TODO: This needs to be more specific - except Exception: - log.exception('Handle this explicitly') - log.error('An error was found while processing ' - 'adml file %s, all policy ' - 'language data from this file will be ' - 'unavailable via this module', - adml_file) - continue + log.error('An error was found while processing adml ' + 'file %s, all policies from this file will ' + 'be unavailable via this module', adml_file) + continue if None in namespaces: namespaces['None'] = namespaces[None] namespaces.pop(None) - policydefs_resources = policydefs_resources_localname_xpath(xmltree) + policydefs_resources = policydefs_resources_localname_xpath(xml_tree) for policydefs_resource in policydefs_resources: t_poldef = policydefs_resource t_poldef = _updateNamespace(t_poldef, this_namespace) @@ -5157,13 +5299,13 @@ def _set_audit_file_data(option, value): # The value is not None, make the change row['Inclusion Setting'] = auditpol_values[value] row['Setting Value'] = value - log.debug('LGPO: Setting {0} to {1}' + log.trace('LGPO: Setting {0} to {1}' ''.format(option, value)) writer.writerow(row) else: # value is None, remove it by not writing it to the # temp file - log.debug('LGPO: Removing {0}'.format(option)) + log.trace('LGPO: Removing {0}'.format(option)) value_written = True # If it's not the value we're setting, just write it else: @@ -5175,7 +5317,7 @@ def _set_audit_file_data(option, value): if not value_written: if not value == 'None': # value is not None, write the new value - log.debug('LGPO: Setting {0} to {1}' + log.trace('LGPO: Setting {0} to {1}' ''.format(option, value)) defaults = _get_audit_defaults(option) writer.writerow({ @@ -5252,7 +5394,8 @@ def _setOptionValueAdvAudit(option, value): if not _set_auditpol_data(option=option, value=value): # Only log this error, it will be in effect the next time the machine # updates its policy - log.debug('Failed to apply audit setting: {0}'.format(option)) + log.error('Failed to apply audit setting: {0}\n' + 'Policy will take effect on next GPO update'.format(option)) # Update __context__ if value is None: @@ -5275,7 +5418,7 @@ def _findOptionValueNetSH(profile, option): settings = salt.utils.win_lgpo_netsh.get_all_settings(profile=profile, store='lgpo') __context__['lgpo.netsh_data'].update({profile: settings}) - log.debug('LGPO: netsh returning value: {0}' + log.trace('LGPO: netsh returning value: {0}' ''.format(__context__['lgpo.netsh_data'][profile][option])) return __context__['lgpo.netsh_data'][profile][option] @@ -5283,7 +5426,7 @@ def _findOptionValueNetSH(profile, option): def _setOptionValueNetSH(profile, section, option, value): if section not in ('firewallpolicy', 'settings', 'logging', 'state'): raise ValueError('LGPO: Invalid section: {0}'.format(section)) - log.debug('LGPO: Setting the following\n' + log.trace('LGPO: Setting the following\n' 'Profile: {0}\n' 'Section: {1}\n' 'Option: {2}\n' @@ -5309,7 +5452,7 @@ def _setOptionValueNetSH(profile, section, option, value): option = option[3:] salt.utils.win_lgpo_netsh.set_logging_settings( profile=profile, setting=option, value=value, store='lgpo') - log.debug('LGPO: Clearing netsh data for {0} profile'.format(profile)) + log.trace('LGPO: Clearing netsh data for {0} profile'.format(profile)) __context__['lgpo.netsh_data'].pop(profile) return True @@ -5628,10 +5771,10 @@ def _getDataFromRegPolData(search_string, policy_data, return_value_name=False): else: value = value.decode('utf-16-le').rstrip(chr(0)) if return_value_name: - log.debug('we want value names and the value') + log.trace('we want value names and the value') values[valueName] = value elif len(matches) > 1: - log.debug('we have multiple matches, we will return a list') + log.trace('we have multiple matches, we will return a list') values.append(value) if values: value = values @@ -5683,7 +5826,7 @@ def _checkListItem(policy_element, policy_name, policy_key, xpath_object, policy if test_items: if _regexSearchRegPolData(re.escape(search_string), policy_file_data): configured_items = configured_items + 1 - log.debug('found the search string in the pol file,' + log.trace('found the search string in the pol file,' '%s of %s items for policy %s are ' 'configured in registry.pol', configured_items, required_items, @@ -5692,7 +5835,7 @@ def _checkListItem(policy_element, policy_name, policy_key, xpath_object, policy expected_strings.append(search_string) if test_items: if required_items > 0 and required_items == configured_items: - log.debug('%s all items are set', policy_name) + log.trace('%s all items are set', policy_name) return True if test_items: return False @@ -5727,7 +5870,7 @@ def _checkValueItemParent(policy_element, policy_name, policy_key, if not test_item: return search_string if _regexSearchRegPolData(re.escape(search_string), policy_file_data): - log.debug('found the search string in the pol file, ' + log.trace('found the search string in the pol file, ' '%s is configured', policy_name) return True return False @@ -5737,12 +5880,11 @@ def _encode_string(value): encoded_null = chr(0).encode('utf-16-le') if value is None: return encoded_null - else: + elif not isinstance(value, six.string_types): # Should we raise an error here, or attempt to cast to a string - if not isinstance(value, six.string_types): - raise TypeError('Value {0} is not a string type\n' - 'Type: {1}'.format(repr(value), type(value))) - return b''.join([value.encode('utf-16-le'), encoded_null]) + raise TypeError('Value {0} is not a string type\n' + 'Type: {1}'.format(repr(value), type(value))) + return b''.join([value.encode('utf-16-le'), encoded_null]) def _buildKnownDataSearchString(reg_key, reg_valueName, reg_vtype, reg_data, @@ -5904,7 +6046,8 @@ def _processValueItem(element, reg_key, reg_valuename, policy, parent_element, if 'expandable' in element.attrib: if element.attrib['expandable'].lower() == 'true': this_vtype = 'REG_EXPAND_SZ' - this_element_value = _encode_string(this_element_value) + if this_element_value is not None: + this_element_value = _encode_string(this_element_value) elif etree.QName(element).localname == 'multiText': this_vtype = 'REG_MULTI_SZ' if not check_deleted else 'REG_SZ' if this_element_value is not None: @@ -5954,10 +6097,10 @@ def _processValueItem(element, reg_key, reg_valuename, policy, parent_element, element_valuenames = [str(z) for z in element_values] if not check_deleted: if this_element_value is not None: - log.debug('_processValueItem has an explicit ' + log.trace('_processValueItem has an explicit ' 'element_value of %s', this_element_value) expected_string = del_keys - log.debug('element_valuenames == %s and element_values ' + log.trace('element_valuenames == %s and element_values ' '== %s', element_valuenames, element_values) for i, item in enumerate(element_valuenames): expected_string = expected_string + b''.join(['['.encode('utf-16-le'), @@ -6070,7 +6213,7 @@ def _checkAllAdmxPolicies(policy_class, policy and look in the registry.pol file to determine if it is enabled/disabled/not configured ''' - log.debug('POLICY CLASS == %s', policy_class) + log.trace('POLICY CLASS == %s', policy_class) module_policy_data = _policy_info() policy_file_data = _read_regpol_file(module_policy_data.admx_registry_classes[policy_class]['policy_path']) admx_policies = [] @@ -6080,7 +6223,7 @@ def _checkAllAdmxPolicies(policy_class, admx_policy_definitions = _get_policy_definitions(language=adml_language) adml_policy_resources = _get_policy_resources(language=adml_language) if policy_file_data: - log.debug('POLICY CLASS {0} has file data'.format(policy_class)) + log.trace('POLICY CLASS {0} has file data'.format(policy_class)) policy_filedata_split = re.sub( salt.utils.stringutils.to_bytes(r'\]{0}$'.format(chr(0))), b'', @@ -6089,26 +6232,16 @@ def _checkAllAdmxPolicies(policy_class, re.sub(re.escape(module_policy_data.reg_pol_header.encode('utf-16-le')), b'', policy_file_data))).split(']['.encode('utf-16-le')) - log.debug('Parsing %s policies...', len(policy_filedata_split)) + log.trace('Searching %s policies...', len(policy_filedata_split)) start_time = time.time() # Get the policy for each item defined in Registry.pol for policy_item in policy_filedata_split: policy_item_key = policy_item.split('{0};'.format(chr(0)).encode('utf-16-le'))[0].decode('utf-16-le').lower() if policy_item_key: # Find the policy definitions with this key - if policy_item_key in adm_policy_key_map: - # Use the faster method if possible - admx_items = REGKEY_XPATH_MAPPED(admx_policy_definitions, - keyvalue=adm_policy_key_map[policy_item_key]) - log.debug('Found %s policies using the mapped method', - len(admx_items)) - else: - # Fall back to this when the faster method is not feasible - admx_items = REGKEY_XPATH(admx_policy_definitions, - keyvalue=policy_item_key) - log.warning('%s not mapped', policy_item_key) - log.warning('Found %s policies using the original method', - len(admx_items)) + admx_items = REGKEY_XPATH(admx_policy_definitions, + keyvalue=policy_item_key) + log.trace('Found %s policies for %s', len(admx_items), policy_item_key) for admx_item in admx_items: # If this is a policy, append it to admx_policies if etree.QName(admx_item).localname == 'policy': @@ -6119,10 +6252,10 @@ def _checkAllAdmxPolicies(policy_class, for policy_item in POLICY_ANCESTOR_XPATH(admx_item): if policy_item not in admx_policies: admx_policies.append(policy_item) - log.debug('Parsing complete: %s seconds', time.time() - start_time) + log.trace('Search complete: %s seconds', time.time() - start_time) if return_not_configured: - log.debug('Gathering non configured policies') + log.trace('Gathering non configured policies') start_time = time.time() not_configured_policies = ALL_CLASS_POLICY_XPATH(admx_policy_definitions, registry_class=policy_class) for policy_item in admx_policies: @@ -6142,7 +6275,7 @@ def _checkAllAdmxPolicies(policy_class, policy_name=not_configured_policy.attrib['name'], return_full_policy_names=return_full_policy_names, adml_language=adml_language) - log.debug('building hierarchy for non-configured item %s', + log.trace('building hierarchy for non-configured item %s', not_configured_policy.attrib['name']) if not_configured_policy_namespace not in hierarchy: hierarchy[not_configured_policy_namespace] = {} @@ -6150,9 +6283,9 @@ def _checkAllAdmxPolicies(policy_class, policy_definition=not_configured_policy, return_full_policy_names=return_full_policy_names, adml_language=adml_language) - log.debug('Gathering complete: %s seconds', time.time() - start_time) + log.trace('Gathering complete: %s seconds', time.time() - start_time) - log.debug('Examining %s policies...', len(admx_policies)) + log.trace('Examining %s policies...', len(admx_policies)) start_time = time.time() for admx_policy in admx_policies: this_valuename = None @@ -6188,7 +6321,7 @@ def _checkAllAdmxPolicies(policy_class, ENABLED_VALUE_XPATH, policy_file_data): this_policy_setting = 'Enabled' - log.debug('%s is enabled by detected ENABLED_VALUE_XPATH', this_policyname) + log.trace('%s is enabled by detected ENABLED_VALUE_XPATH', this_policyname) if this_policynamespace not in policy_vals: policy_vals[this_policynamespace] = {} policy_vals[this_policynamespace][this_policyname] = this_policy_setting @@ -6205,7 +6338,7 @@ def _checkAllAdmxPolicies(policy_class, DISABLED_VALUE_XPATH, policy_file_data): this_policy_setting = 'Disabled' - log.debug('%s is disabled by detected DISABLED_VALUE_XPATH', this_policyname) + log.trace('%s is disabled by detected DISABLED_VALUE_XPATH', this_policyname) if this_policynamespace not in policy_vals: policy_vals[this_policynamespace] = {} policy_vals[this_policynamespace][this_policyname] = this_policy_setting @@ -6215,7 +6348,7 @@ def _checkAllAdmxPolicies(policy_class, explicit_enable_disable_value_setting = True if _checkListItem(admx_policy, this_policyname, this_key, ENABLED_LIST_XPATH, policy_file_data): this_policy_setting = 'Enabled' - log.debug('%s is enabled by detected ENABLED_LIST_XPATH', this_policyname) + log.trace('%s is enabled by detected ENABLED_LIST_XPATH', this_policyname) if this_policynamespace not in policy_vals: policy_vals[this_policynamespace] = {} policy_vals[this_policynamespace][this_policyname] = this_policy_setting @@ -6225,7 +6358,7 @@ def _checkAllAdmxPolicies(policy_class, explicit_enable_disable_value_setting = True if _checkListItem(admx_policy, this_policyname, this_key, DISABLED_LIST_XPATH, policy_file_data): this_policy_setting = 'Disabled' - log.debug('%s is disabled by detected DISABLED_LIST_XPATH', this_policyname) + log.trace('%s is disabled by detected DISABLED_LIST_XPATH', this_policyname) if this_policynamespace not in policy_vals: policy_vals[this_policynamespace] = {} policy_vals[this_policynamespace][this_policyname] = this_policy_setting @@ -6240,7 +6373,7 @@ def _checkAllAdmxPolicies(policy_class, '1')), policy_file_data): this_policy_setting = 'Enabled' - log.debug('%s is enabled by no explicit enable/disable list or value', this_policyname) + log.trace('%s is enabled by no explicit enable/disable list or value', this_policyname) if this_policynamespace not in policy_vals: policy_vals[this_policynamespace] = {} policy_vals[this_policynamespace][this_policyname] = this_policy_setting @@ -6251,7 +6384,7 @@ def _checkAllAdmxPolicies(policy_class, check_deleted=True)), policy_file_data): this_policy_setting = 'Disabled' - log.debug('%s is disabled by no explicit enable/disable list or value', this_policyname) + log.trace('%s is disabled by no explicit enable/disable list or value', this_policyname) if this_policynamespace not in policy_vals: policy_vals[this_policynamespace] = {} policy_vals[this_policynamespace][this_policyname] = this_policy_setting @@ -6270,12 +6403,9 @@ def _checkAllAdmxPolicies(policy_class, return_full_policy_names=return_full_policy_names, adml_language=adml_language) required_elements[this_element_name] = None - child_key = this_key - child_valuename = this_valuename - if 'key' in child_item.attrib: - child_key = child_item.attrib['key'] - if 'valueName' in child_item.attrib: - child_valuename = child_item.attrib['valueName'] + child_key = child_item.attrib.get('key', this_key) + child_valuename = child_item.attrib.get( + 'valueName', this_valuename) if etree.QName(child_item).localname == 'boolean': # https://msdn.microsoft.com/en-us/library/dn605978(v=vs.85).aspx @@ -6288,7 +6418,7 @@ def _checkAllAdmxPolicies(policy_class, TRUE_VALUE_XPATH, policy_file_data): configured_elements[this_element_name] = True - log.debug('element %s is configured true', + log.trace('element %s is configured true', child_item.attrib['id']) if FALSE_VALUE_XPATH(child_item) and this_element_name not in configured_elements: if _checkValueItemParent(child_item, @@ -6299,22 +6429,22 @@ def _checkAllAdmxPolicies(policy_class, policy_file_data): configured_elements[this_element_name] = False policy_disabled_elements = policy_disabled_elements + 1 - log.debug('element %s is configured false', + log.trace('element %s is configured false', child_item.attrib['id']) # WARNING - no standard ADMX files use true/falseList # so this hasn't actually been tested if TRUE_LIST_XPATH(child_item) and this_element_name not in configured_elements: - log.debug('checking trueList') + log.trace('checking trueList') if _checkListItem(child_item, this_policyname, this_key, TRUE_LIST_XPATH, policy_file_data): configured_elements[this_element_name] = True - log.debug('element %s is configured true', + log.trace('element %s is configured true', child_item.attrib['id']) if FALSE_LIST_XPATH(child_item) and this_element_name not in configured_elements: - log.debug('checking falseList') + log.trace('checking falseList') if _checkListItem(child_item, this_policyname, this_key, @@ -6322,7 +6452,7 @@ def _checkAllAdmxPolicies(policy_class, policy_file_data): configured_elements[this_element_name] = False policy_disabled_elements = policy_disabled_elements + 1 - log.debug('element %s is configured false', + log.trace('element %s is configured false', child_item.attrib['id']) else: if _regexSearchRegPolData(re.escape(_processValueItem(child_item, @@ -6334,7 +6464,7 @@ def _checkAllAdmxPolicies(policy_class, policy_file_data): configured_elements[this_element_name] = False policy_disabled_elements = policy_disabled_elements + 1 - log.debug('element %s is configured false', child_item.attrib['id']) + log.trace('element %s is configured false', child_item.attrib['id']) elif _regexSearchRegPolData(re.escape(_processValueItem(child_item, child_key, child_valuename, @@ -6343,7 +6473,7 @@ def _checkAllAdmxPolicies(policy_class, check_deleted=False)), policy_file_data): configured_elements[this_element_name] = True - log.debug('element %s is configured true', + log.trace('element %s is configured true', child_item.attrib['id']) elif etree.QName(child_item).localname == 'decimal' \ or etree.QName(child_item).localname == 'text' \ @@ -6359,7 +6489,7 @@ def _checkAllAdmxPolicies(policy_class, policy_file_data): configured_elements[this_element_name] = 'Disabled' policy_disabled_elements = policy_disabled_elements + 1 - log.debug('element %s is disabled', + log.trace('element %s is disabled', child_item.attrib['id']) elif _regexSearchRegPolData(re.escape(_processValueItem(child_item, child_key, @@ -6376,7 +6506,7 @@ def _checkAllAdmxPolicies(policy_class, check_deleted=False), policy_file_data) configured_elements[this_element_name] = configured_value - log.debug('element %s is enabled, value == %s', + log.trace('element %s is enabled, value == %s', child_item.attrib['id'], configured_value) elif etree.QName(child_item).localname == 'enum': @@ -6387,7 +6517,7 @@ def _checkAllAdmxPolicies(policy_class, elements_item, check_deleted=True)), policy_file_data): - log.debug('enum element %s is disabled', + log.trace('enum element %s is disabled', child_item.attrib['id']) configured_elements[this_element_name] = 'Disabled' policy_disabled_elements = policy_disabled_elements + 1 @@ -6400,13 +6530,13 @@ def _checkAllAdmxPolicies(policy_class, VALUE_XPATH, policy_file_data): if VALUE_LIST_XPATH(enum_item): - log.debug('enum item has a valueList') + log.trace('enum item has a valueList') if _checkListItem(enum_item, this_policyname, child_key, VALUE_LIST_XPATH, policy_file_data): - log.debug('all valueList items exist in file') + log.trace('all valueList items exist in file') configured_elements[this_element_name] = _getAdmlDisplayName( adml_policy_resources, enum_item.attrib['displayName']) @@ -6420,7 +6550,7 @@ def _checkAllAdmxPolicies(policy_class, return_value_name = False if 'explicitValue' in child_item.attrib \ and child_item.attrib['explicitValue'].lower() == 'true': - log.debug('explicitValue list, we will return value names') + log.trace('explicitValue list, we will return value names') return_value_name = True if _regexSearchRegPolData(re.escape(_processValueItem(child_item, child_key, @@ -6439,7 +6569,7 @@ def _checkAllAdmxPolicies(policy_class, policy_file_data, return_value_name=return_value_name) configured_elements[this_element_name] = configured_value - log.debug('element %s is enabled values: %s', + log.trace('element %s is enabled values: %s', child_item.attrib['id'], configured_value) elif _regexSearchRegPolData(re.escape(_processValueItem(child_item, @@ -6451,12 +6581,12 @@ def _checkAllAdmxPolicies(policy_class, policy_file_data): configured_elements[this_element_name] = "Disabled" policy_disabled_elements = policy_disabled_elements + 1 - log.debug('element {0} is disabled'.format(child_item.attrib['id'])) + log.trace('element {0} is disabled'.format(child_item.attrib['id'])) if element_only_enabled_disabled: if len(required_elements.keys()) > 0 \ and len(configured_elements.keys()) == len(required_elements.keys()): if policy_disabled_elements == len(required_elements.keys()): - log.debug('{0} is disabled by all enum elements'.format(this_policyname)) + log.trace('{0} is disabled by all enum elements'.format(this_policyname)) if this_policynamespace not in policy_vals: policy_vals[this_policynamespace] = {} policy_vals[this_policynamespace][this_policyname] = 'Disabled' @@ -6464,7 +6594,7 @@ def _checkAllAdmxPolicies(policy_class, if this_policynamespace not in policy_vals: policy_vals[this_policynamespace] = {} policy_vals[this_policynamespace][this_policyname] = configured_elements - log.debug('{0} is enabled by enum elements'.format(this_policyname)) + log.trace('{0} is enabled by enum elements'.format(this_policyname)) else: if this_policy_setting == 'Enabled': if this_policynamespace not in policy_vals: @@ -6484,9 +6614,9 @@ def _checkAllAdmxPolicies(policy_class, # When the state does it's first `lgpo.get` it would return `AllowTelemetry` # On the second run, it would return `Allow Telemetry` # This makes sure we're always returning the full_name when required - if admx_policy.attrib['name'] in policy_vals[this_policynamespace][this_policyname]: + if this_policyname in policy_vals[this_policynamespace][this_policyname]: full_name = full_names[this_policynamespace][this_policyname] - setting = policy_vals[this_policynamespace][this_policyname].pop(admx_policy.attrib['name']) + setting = policy_vals[this_policynamespace][this_policyname].pop(this_policyname) policy_vals[this_policynamespace][this_policyname][full_name] = setting if this_policynamespace in policy_vals and this_policyname in policy_vals[this_policynamespace]: if this_policynamespace not in hierarchy: @@ -6495,7 +6625,7 @@ def _checkAllAdmxPolicies(policy_class, policy_definition=admx_policy, return_full_policy_names=return_full_policy_names, adml_language=adml_language) - log.debug('Examination complete: %s seconds', time.time() - start_time) + log.trace('Examination complete: %s seconds', time.time() - start_time) if policy_vals and return_full_policy_names and not hierarchical_return: log.debug('Compiling non hierarchical return...') start_time = time.time() @@ -6521,9 +6651,9 @@ def _checkAllAdmxPolicies(policy_class, full_path_list = hierarchy[policy_namespace][unpathed_dict[policy_namespace][path_needed]] full_path_list.reverse() full_path_list.append(path_needed) - log.debug('full_path_list == %s', full_path_list) + log.trace('full_path_list == %s', full_path_list) policy_vals['\\'.join(full_path_list)] = policy_vals[policy_namespace].pop(path_needed) - log.debug('Compilation complete: %s seconds', time.time() - start_time) + log.trace('Compilation complete: %s seconds', time.time() - start_time) for policy_namespace in list(policy_vals): if policy_vals[policy_namespace] == {}: policy_vals.pop(policy_namespace) @@ -6551,7 +6681,7 @@ def _checkAllAdmxPolicies(policy_class, policy_vals = dictupdate.update(policy_vals, tdict) if policy_namespace in policy_vals and policy_vals[policy_namespace] == {}: policy_vals.pop(policy_namespace) - log.debug('Compilation complete: %s seconds', time.time() - start_time) + log.trace('Compilation complete: %s seconds', time.time() - start_time) policy_vals = { module_policy_data.admx_registry_classes[policy_class]['lgpo_section']: { 'Administrative Templates': policy_vals}} @@ -6785,15 +6915,15 @@ def _policyFileReplaceOrAppendList(string_list, policy_data): b'', this_string.split(b'\00;')[1], flags=re.IGNORECASE) - log.debug('item value name is %s', list_item_value_name) + log.trace('item value name is %s', list_item_value_name) data_to_replace = _regexSearchKeyValueCombo(policy_data, list_item_key, list_item_value_name) if data_to_replace: - log.debug('replacing %s with %s', data_to_replace, this_string) + log.trace('replacing %s with %s', data_to_replace, this_string) policy_data = policy_data.replace(data_to_replace, this_string) else: - log.debug('appending %s', this_string) + log.trace('appending %s', this_string) policy_data = b''.join([policy_data, this_string]) return policy_data @@ -6816,13 +6946,13 @@ def _policyFileReplaceOrAppend(this_string, policy_data, append_only=False): b'', this_string.split(b'\00;')[1], flags=re.IGNORECASE) - log.debug('item value name is %s', item_value_name) + log.trace('item value name is %s', item_value_name) data_to_replace = _regexSearchKeyValueCombo(policy_data, item_key, item_value_name) if data_to_replace: - log.debug('replacing %s with %s', data_to_replace, this_string) + log.trace('replacing %s with %s', data_to_replace, this_string) policy_data = policy_data.replace(data_to_replace, this_string) else: - log.debug('appending %s', this_string) + log.trace('appending %s', this_string) policy_data = b''.join([policy_data, this_string]) return policy_data @@ -6857,20 +6987,20 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, for adm_policy in admtemplate_data[adm_namespace]: if six.text_type(admtemplate_data[adm_namespace][adm_policy]).lower() == 'not configured': if base_policy_settings.get(adm_namespace, {}).pop(adm_policy, None) is not None: - log.debug('Policy "%s" removed', adm_policy) + log.trace('Policy "%s" removed', adm_policy) else: - log.debug('adding %s to base_policy_settings', adm_policy) + log.trace('adding %s to base_policy_settings', adm_policy) if adm_namespace not in base_policy_settings: base_policy_settings[adm_namespace] = {} base_policy_settings[adm_namespace][adm_policy] = admtemplate_data[adm_namespace][adm_policy] for adm_namespace in base_policy_settings: for admPolicy in base_policy_settings[adm_namespace]: - log.debug('working on admPolicy %s', admPolicy) + log.trace('working on admPolicy %s', admPolicy) explicit_enable_disable_value_setting = False this_key = None this_valuename = None if six.text_type(base_policy_settings[adm_namespace][admPolicy]).lower() == 'disabled': - log.debug('time to disable %s', admPolicy) + log.trace('time to disable %s', admPolicy) this_policy = admx_policy_definitions.xpath(policySearchXpath.format(admPolicy), namespaces={'ns1': adm_namespace}) if this_policy: this_policy = this_policy[0] @@ -6906,7 +7036,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, DISABLED_LIST_XPATH, None, test_items=False) - log.debug('working with disabledList ' + log.trace('working with disabledList ' 'portion of %s', admPolicy) existing_data = _policyFileReplaceOrAppendList(disabled_list_strings, existing_data) @@ -6919,7 +7049,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, existing_data = _policyFileReplaceOrAppend(disabled_value_string, existing_data) if ELEMENTS_XPATH(this_policy): - log.debug('checking elements of %s', + log.trace('checking elements of %s', admPolicy) for elements_item in ELEMENTS_XPATH(this_policy): for child_item in elements_item.getchildren(): @@ -6942,7 +7072,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, temp_dict[this_list], None, test_items=False) - log.debug('working with %s portion of %s', + log.trace('working with %s portion of %s', admPolicy, this_list) existing_data = _policyFileReplaceOrAppendList( @@ -6960,7 +7090,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, this_policy, elements_item, check_deleted=True) - log.debug('I have disabled value string of %s', + log.trace('I have disabled value string of %s', disabled_value_string) existing_data = _policyFileReplaceOrAppend( disabled_value_string, @@ -6972,7 +7102,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, this_policy, elements_item, check_deleted=True) - log.debug('I have disabled value string of %s', + log.trace('I have disabled value string of %s', disabled_value_string) existing_data = _policyFileReplaceOrAppend( disabled_value_string, @@ -6984,10 +7114,10 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, log.error('policy item %s does not have the requried "class" attribute', this_policy.attrib) else: - log.debug('time to enable and set the policy "%s"', + log.trace('time to enable and set the policy "%s"', admPolicy) this_policy = admx_policy_definitions.xpath(policySearchXpath.format(admPolicy), namespaces={'ns1': adm_namespace}) - log.debug('found this_policy == %s', this_policy) + log.trace('found this_policy == %s', this_policy) if this_policy: this_policy = this_policy[0] if 'class' in this_policy.attrib: @@ -7022,7 +7152,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, ENABLED_LIST_XPATH, None, test_items=False) - log.debug('working with enabledList portion of %s', admPolicy) + log.trace('working with enabledList portion of %s', admPolicy) existing_data = _policyFileReplaceOrAppendList( enabled_list_strings, existing_data) @@ -7055,7 +7185,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, TRUE_LIST_XPATH, None, test_items=False) - log.debug('working with trueList portion of {0}'.format(admPolicy)) + log.trace('working with trueList portion of {0}'.format(admPolicy)) else: list_strings = _checkListItem(child_item, admPolicy, @@ -7103,7 +7233,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, elements_item, check_deleted=False, this_element_value=base_policy_settings[adm_namespace][admPolicy][child_item.attrib['id']]) - log.debug('I have enabled value string of %s', enabled_value_string) + log.trace('I have enabled value string of %s', enabled_value_string) existing_data = _policyFileReplaceOrAppend( enabled_value_string, existing_data) @@ -7132,7 +7262,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, VALUE_LIST_XPATH, None, test_items=False) - log.debug('working with valueList portion of %s', + log.trace('working with valueList portion of %s', child_item.attrib['id']) existing_data = _policyFileReplaceOrAppendList( enabled_list_strings, @@ -7147,7 +7277,7 @@ def _writeAdminTemplateRegPolFile(admtemplate_data, elements_item, check_deleted=False, this_element_value=base_policy_settings[adm_namespace][admPolicy][child_item.attrib['id']]) - log.debug('I have enabled value string of %s', + log.trace('I have enabled value string of %s', enabled_value_string) existing_data = _policyFileReplaceOrAppend( enabled_value_string, @@ -7180,13 +7310,13 @@ def _getScriptSettingsFromIniFile(policy_info): if _existingData: try: _existingData = deserialize(_existingData.decode('utf-16-le').lstrip('\ufeff')) - log.debug('Have deserialized data %s', _existingData) + log.trace('Have deserialized data %s', _existingData) except Exception as error: log.exception('An error occurred attempting to deserialize data for %s', policy_info['Policy']) raise CommandExecutionError(error) if 'Section' in policy_info['ScriptIni'] and policy_info['ScriptIni']['Section'].lower() in [z.lower() for z in _existingData.keys()]: if 'SettingName' in policy_info['ScriptIni']: - log.debug('Need to look for %s', policy_info['ScriptIni']['SettingName']) + log.trace('Need to look for %s', policy_info['ScriptIni']['SettingName']) if policy_info['ScriptIni']['SettingName'].lower() in [z.lower() for z in _existingData[policy_info['ScriptIni']['Section']].keys()]: return _existingData[policy_info['ScriptIni']['Section']][policy_info['ScriptIni']['SettingName'].lower()] else: @@ -7292,6 +7422,7 @@ def _lookup_admin_template(policy_name, hierarchy = [] hierarchy_policy_name = policy_name if not adml_search_results: + log.warning('Trying another: %s' % policy_name) if '\\' in policy_name: hierarchy = policy_name.split('\\') policy_name = hierarchy.pop() @@ -7302,14 +7433,14 @@ def _lookup_admin_template(policy_name, suggested_policies = '' adml_to_remove = [] if len(adml_search_results) > 1: - log.debug('multiple ADML entries found matching the policy name %s', policy_name) + log.trace('multiple ADML entries found matching the policy name %s', policy_name) multiple_adml_entries = True for adml_search_result in adml_search_results: if not getattr(adml_search_result, 'text', '').strip() == policy_name: adml_to_remove.append(adml_search_result) else: if hierarchy: - log.debug('we have hierarchy of %s', hierarchy) + log.trace('we have hierarchy of %s', hierarchy) display_name_searchval = '$({0}.{1})'.format( adml_search_result.tag.split('}')[1], adml_search_result.attrib['id']) @@ -7321,10 +7452,10 @@ def _lookup_admin_template(policy_name, admx_results = [] these_admx_search_results = admx_policy_definitions.xpath(policy_search_string, namespaces=adml_search_result.nsmap) if not these_admx_search_results: - log.debug('No admx was found for the adml entry %s, it will be removed', display_name_searchval) + log.trace('No admx was found for the adml entry %s, it will be removed', display_name_searchval) adml_to_remove.append(adml_search_result) for search_result in these_admx_search_results: - log.debug('policy_name == %s', policy_name) + log.trace('policy_name == %s', policy_name) this_hierarchy = _build_parent_list( policy_definition=search_result, return_full_policy_names=True, @@ -7332,13 +7463,13 @@ def _lookup_admin_template(policy_name, this_hierarchy.reverse() if hierarchy != this_hierarchy: msg = 'hierarchy %s does not match this item\'s hierarchy of %s' - log.debug(msg, hierarchy, this_hierarchy) + log.trace(msg, hierarchy, this_hierarchy) if len(these_admx_search_results) == 1: - log.debug('only 1 admx was found and it does not match this adml, it is safe to remove from the list') + log.trace('only 1 admx was found and it does not match this adml, it is safe to remove from the list') adml_to_remove.append(adml_search_result) else: - log.debug('hierarchy %s matches item\'s hierarchy of %s', hierarchy, this_hierarchy) - log.debug('search_result %s added to results', search_result) + log.trace('hierarchy %s matches item\'s hierarchy of %s', hierarchy, this_hierarchy) + log.trace('search_result %s added to results', search_result) admx_results.append(search_result) if len(admx_results) == 1: admx_search_results.append(admx_results[0]) @@ -7360,22 +7491,22 @@ def _lookup_admin_template(policy_name, if len(adml_search_results) == 1 and multiple_adml_entries: multiple_adml_entries = False for adml_search_result in adml_search_results: - log.debug('found an ADML entry matching the string! %s -- %s', + log.trace('found an ADML entry matching the string! %s -- %s', adml_search_result.tag, adml_search_result.attrib) display_name_searchval = '$({0}.{1})'.format( adml_search_result.tag.split('}')[1], adml_search_result.attrib['id']) - log.debug('searching for displayName == %s', display_name_searchval) + log.trace('searching for displayName == %s', display_name_searchval) if not admx_search_results: - log.debug('search for an admx entry matching display_name %s and registry_class %s', display_name_searchval, policy_class) + log.trace('search for an admx entry matching display_name %s and registry_class %s', display_name_searchval, policy_class) admx_search_results = ADMX_DISPLAYNAME_SEARCH_XPATH( admx_policy_definitions, display_name=display_name_searchval, registry_class=policy_class) if admx_search_results: - log.debug('processing admx_search_results of {0}'.format(admx_search_results)) - log.debug('multiple_adml_entries is {0}'.format(multiple_adml_entries)) + log.trace('processing admx_search_results of {0}'.format(admx_search_results)) + log.trace('multiple_adml_entries is {0}'.format(multiple_adml_entries)) if (len(admx_search_results) == 1 or hierarchy) and not multiple_adml_entries: found = False for search_result in admx_search_results: @@ -7386,13 +7517,13 @@ def _lookup_admin_template(policy_name, return_full_policy_names=True, adml_language=adml_language) this_hierarchy.reverse() - log.debug('testing %s == %s', hierarchy, this_hierarchy) + log.trace('testing %s == %s', hierarchy, this_hierarchy) if hierarchy == this_hierarchy: found = True else: found = True if found: - log.debug('found the ADMX policy matching ' + log.trace('found the ADMX policy matching ' 'the display name %s -- %s', search_result, policy_name) if 'name' in search_result.attrib: @@ -7413,7 +7544,7 @@ def _lookup_admin_template(policy_name, return True, search_result, policy_aliases, None else: msg = ('ADMX policy with the display name {0} does not' - 'have the required name attribtue') + 'have the required name attribute') msg = msg.format(policy_name) return False, None, [], msg if not found: @@ -7686,7 +7817,6 @@ def get(policy_class=None, return_full_policy_names=True, ''' vals = {} - modal_returns = {} _policydata = _policy_info() if policy_class is None or policy_class.lower() == 'both': @@ -7714,46 +7844,7 @@ def get(policy_class=None, return_full_policy_names=True, policy_name = policy if _pol: vals_key_name = policy_name - if 'Registry' in _pol: - # get value from registry - class_vals[policy_name] = __salt__['reg.read_value']( - _pol['Registry']['Hive'], - _pol['Registry']['Path'], - _pol['Registry']['Value'])['vdata'] - log.debug( - 'Value %r found for reg policy %s', - class_vals[policy_name], policy_name - ) - elif 'Secedit' in _pol: - # get value from secedit - _val = _get_secedit_value(option=_pol['Secedit']['Option']) - class_vals[policy_name] = _val - elif 'NetSH' in _pol: - # get value from netsh - class_vals[policy_name] = _findOptionValueNetSH( - profile=_pol['NetSH']['Profile'], - option=_pol['NetSH']['Option']) - elif 'AdvAudit' in _pol: - # get value from auditpol - class_vals[policy_name] = _findOptionValueAdvAudit( - option=_pol['AdvAudit']['Option']) - elif 'NetUserModal' in _pol: - # get value from UserNetMod - if _pol['NetUserModal']['Modal'] not in modal_returns: - modal_returns[_pol['NetUserModal']['Modal']] = win32net.NetUserModalsGet( - None, - _pol['NetUserModal']['Modal']) - class_vals[policy_name] = modal_returns[_pol['NetUserModal']['Modal']][_pol['NetUserModal']['Option']] - elif 'LsaRights' in _pol: - class_vals[policy_name] = _getRightsAssignments(_pol['LsaRights']['Option']) - elif 'ScriptIni' in _pol: - log.debug('Working with ScriptIni setting %s', policy_name) - class_vals[policy_name] = _getScriptSettingsFromIniFile(_pol) - if policy_name in class_vals: - class_vals[policy_name] = _transform_value( - value=class_vals[policy_name], - policy=_policydata.policies[p_class]['policies'][policy_name], - transform_type='Get') + class_vals[policy_name] = _get_policy_info_setting(_pol) if return_full_policy_names: class_vals[_pol['Policy']] = class_vals.pop(policy_name) vals_key_name = _pol['Policy'] @@ -7774,7 +7865,7 @@ def get(policy_class=None, return_full_policy_names=True, else: msg = 'The specified policy {0} is not currently available ' \ 'to be configured via this module' - raise SaltInvocationError(msg.format(policy_name)) + raise CommandExecutionError(msg.format(policy_name)) class_vals = dictupdate.update( class_vals, _checkAllAdmxPolicies(policy_class=p_class, @@ -7791,6 +7882,676 @@ def get(policy_class=None, return_full_policy_names=True, return vals +def _get_policy_info_setting(policy_definition): + ''' + Some policies are defined in this module and others by the ADMX/ADML files + on the machine. This function loads the current values for policies defined + in this module. + + Args: + policy_definition (dict): + A sub-dict of Policies property of the _policy_info() class. + Basically a dictionary that defines the policy + + Returns: + The transformed value. The transform is defined in the policy + definition. It can be a list, a string, a dictionary, depending on how + it's defined + + Usage: + policy_data = _policy_info() + policy_name = 'RemoteRegistryExactPaths' + policy_definition = policy_data.policies['Machine']['policies'][policy_name] + policy_value = _get_policy_info_setting(policy_definition) + ''' + if 'Registry' in policy_definition: + # Get value using the Registry mechanism + value = __salt__['reg.read_value']( + policy_definition['Registry']['Hive'], + policy_definition['Registry']['Path'], + policy_definition['Registry']['Value'])['vdata'] + log.trace('Value %r found for Regisry policy %s', + value, policy_definition['Policy']) + elif 'Secedit' in policy_definition: + # Get value using the Secedit mechanism + value = _get_secedit_value( + option=policy_definition['Secedit']['Option']) + log.trace('Value %r found for Secedit policy %s', + value, policy_definition['Policy']) + elif 'NetSH' in policy_definition: + # Get value using the NetSH mechanism + value = _findOptionValueNetSH( + profile=policy_definition['NetSH']['Profile'], + option=policy_definition['NetSH']['Option']) + log.trace('Value %r found for NetSH policy %s', + value, policy_definition['Policy']) + elif 'AdvAudit' in policy_definition: + # Get value using the AuditPol mechanism + value = _findOptionValueAdvAudit( + option=policy_definition['AdvAudit']['Option']) + log.trace('Value %r found for AuditPol policy %s', + value, policy_definition['Policy']) + elif 'NetUserModal' in policy_definition: + # Get value using the NetUserModal mechanism + modal_return = win32net.NetUserModalsGet( + None, policy_definition['NetUserModal']['Modal']) + value = modal_return[ + policy_definition['NetUserModal']['Option']] + log.trace('Value %r found for NetUserModal policy %s', + value, policy_definition['Policy']) + elif 'LsaRights' in policy_definition: + # Get value using the LSARights mechanism + value = _getRightsAssignments(policy_definition['LsaRights']['Option']) + log.trace('Value %r found for LSARights policy %s', + value, policy_definition['Policy']) + elif 'ScriptIni' in policy_definition: + value = _getScriptSettingsFromIniFile(policy_definition) + log.trace('Value %r found for ScriptIni policy %s', + value, policy_definition['Policy']) + else: + message = 'Unknown or missing mechanism in policy_definition\n' \ + 'r%'.format(policy_definition) + raise CommandExecutionError(message) + value = _transform_value(value=value, + policy=policy_definition, + transform_type='Get') + return value + + +def _get_policy_adm_setting(admx_policy, + policy_class, + adml_language='en-US', + return_full_policy_names=False, + hierarchical_return=False): + ''' + Get the current setting for polices set via the policy templates (ADMX/ADML) + files + + Args: + admx_policy (obj): + The XPath object as returned by the ``_lookup_admin_template`` + function + + policy_class (str): + The policy class. Must be one of ``machine`` or ``user`` + + adml_language (str): + The language code for the adml file to use for localization. The + default is ``en-US`` + + return_full_policy_names (bool): + Returns the full policy name regardless of what was passed in + ``policy_name`` + + hierarchical_return (bool): + Returns a hierarchical view of the policy showing its parents + + Returns: + dict: A dictionary containing the policy settings + + Usage: + policy_name = 'AutoUpdateCfg' + policy_class = 'machine' + adml_language = 'en-US' + success, policy_obj, _, _ = _lookup_admin_template( + policy_name=policy_name, + policy_class=policy_class, + adml_language=adml_language) + if success: + setting = _get_policy_adm_setting( + admx_policy=policy_obj, + policy_class=policy_class, + adml_language=adml_language, + return_full_policy_names=return_full_policy_names, + hierarchical_return=hierarchical_return + ) + ''' + # TODO: Need to figure out how to get the lgpo.get function to use this code + # TODO: as it is very similar + # Validate policy Key and Name attributes + this_key = admx_policy.attrib.get('key', None) + this_policy_name = admx_policy.attrib.get('name', None) + if this_key is None or this_policy_name is None: + msg = 'Policy is missing the required "key" or "name" attribute:\n' \ + '%s' % admx_policy.attrib + raise CommandExecutionError(msg) + + # Get additional settings + this_value_name = admx_policy.attrib.get('valueName', None) + this_policy_setting = 'Not Configured' + this_policy_namespace = admx_policy.nsmap[admx_policy.prefix] + + # Set some default values, these will get flipped below + element_only_enabled_disabled = True + explicit_enable_disable_value_setting = False + + # Load additional data + policy_data = _policy_info() + policy_file_data = _read_regpol_file( + policy_data.admx_registry_classes[policy_class]['policy_path']) + adml_policy_resources = _get_policy_resources(language=adml_language) + + policy_vals = {} + + if ENABLED_VALUE_XPATH(admx_policy) and this_policy_setting == 'Not Configured': + # some policies have a disabled list but not an enabled list + # added this to address those issues + if DISABLED_LIST_XPATH(admx_policy) or DISABLED_VALUE_XPATH(admx_policy): + element_only_enabled_disabled = False + explicit_enable_disable_value_setting = True + if _checkValueItemParent(policy_element=admx_policy, + policy_name=this_policy_name, + policy_key=this_key, + policy_valueName=this_value_name, + xpath_object=ENABLED_VALUE_XPATH, + policy_file_data=policy_file_data): + log.trace('%s is enabled by detected ENABLED_VALUE_XPATH', + this_policy_name) + this_policy_setting = 'Enabled' + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + if DISABLED_VALUE_XPATH(admx_policy) and this_policy_setting == 'Not Configured': + # some policies have a disabled list but not an enabled list + # added this to address those issues + if ENABLED_LIST_XPATH(admx_policy) or ENABLED_VALUE_XPATH(admx_policy): + element_only_enabled_disabled = False + explicit_enable_disable_value_setting = True + if _checkValueItemParent(policy_element=admx_policy, + policy_name=this_policy_name, + policy_key=this_key, + policy_valueName=this_value_name, + xpath_object=DISABLED_VALUE_XPATH, + policy_file_data=policy_file_data): + log.trace('%s is disabled by detected DISABLED_VALUE_XPATH', + this_policy_name) + this_policy_setting = 'Disabled' + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + if ENABLED_LIST_XPATH(admx_policy): + if DISABLED_LIST_XPATH(admx_policy) or DISABLED_VALUE_XPATH(admx_policy): + element_only_enabled_disabled = False + explicit_enable_disable_value_setting = True + if _checkListItem(policy_element=admx_policy, + policy_name=this_policy_name, + policy_key=this_key, + xpath_object=ENABLED_LIST_XPATH, + policy_file_data=policy_file_data): + log.trace('%s is enabled by detected ENABLED_LIST_XPATH', + this_policy_name) + this_policy_setting = 'Enabled' + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + if DISABLED_LIST_XPATH(admx_policy): + if ENABLED_LIST_XPATH(admx_policy) or ENABLED_VALUE_XPATH(admx_policy): + element_only_enabled_disabled = False + explicit_enable_disable_value_setting = True + if _checkListItem(policy_element=admx_policy, + policy_name=this_policy_name, + policy_key=this_key, + xpath_object=DISABLED_LIST_XPATH, + policy_file_data=policy_file_data): + log.trace('%s is disabled by detected DISABLED_LIST_XPATH', + this_policy_name) + this_policy_setting = 'Disabled' + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + + if not explicit_enable_disable_value_setting and this_value_name: + # the policy has a key/valuename but no explicit Enabled/Disabled + # Value or List + # these seem to default to a REG_DWORD 1 = "Enabled" **del. = "Disabled" + if _regexSearchRegPolData(re.escape(_buildKnownDataSearchString( + reg_key=this_key, + reg_valueName=this_value_name, + reg_vtype='REG_DWORD', + reg_data='1')), + policy_file_data): + log.trace('%s is enabled by no explicit enable/disable list or ' + 'value', this_policy_name) + this_policy_setting = 'Enabled' + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + elif _regexSearchRegPolData(re.escape(_buildKnownDataSearchString( + reg_key=this_key, + reg_valueName=this_value_name, + reg_vtype='REG_DWORD', + reg_data=None, + check_deleted=True)), + policy_file_data): + log.trace('%s is disabled by no explicit enable/disable list or ' + 'value', this_policy_name) + this_policy_setting = 'Disabled' + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + + full_names = {} + hierarchy = {} + if ELEMENTS_XPATH(admx_policy): + if element_only_enabled_disabled or this_policy_setting == 'Enabled': + # TODO does this need to be modified based on the 'required' attribute? + required_elements = {} + configured_elements = {} + policy_disabled_elements = 0 + for elements_item in ELEMENTS_XPATH(admx_policy): + for child_item in elements_item.getchildren(): + this_element_name = _getFullPolicyName( + policy_item=child_item, + policy_name=child_item.attrib['id'], + return_full_policy_names=return_full_policy_names, + adml_language=adml_language) + required_elements[this_element_name] = None + child_key = child_item.attrib.get('key', this_key) + child_value_name = child_item.attrib.get('valueName', + this_value_name) + if etree.QName(child_item).localname == 'boolean': + # https://msdn.microsoft.com/en-us/library/dn605978(v=vs.85).aspx + if child_item.getchildren(): + if TRUE_VALUE_XPATH(child_item) and this_element_name not in configured_elements: + if _checkValueItemParent( + policy_element=child_item, + policy_name=this_policy_name, + policy_key=child_key, + policy_valueName=child_value_name, + xpath_object=TRUE_VALUE_XPATH, + policy_file_data=policy_file_data): + configured_elements[this_element_name] = True + log.trace('element %s is configured true', + child_item.attrib['id']) + if FALSE_VALUE_XPATH(child_item) and this_element_name not in configured_elements: + if _checkValueItemParent( + policy_element=child_item, + policy_name=this_policy_name, + policy_key=child_key, + policy_valueName=child_value_name, + xpath_object=FALSE_VALUE_XPATH, + policy_file_data=policy_file_data): + configured_elements[this_element_name] = False + policy_disabled_elements = policy_disabled_elements + 1 + log.trace('element %s is configured false', + child_item.attrib['id']) + # WARNING - no standard ADMX files use true/falseList + # so this hasn't actually been tested + if TRUE_LIST_XPATH(child_item) and this_element_name not in configured_elements: + log.trace('checking trueList') + if _checkListItem( + policy_element=child_item, + policy_name=this_policy_name, + policy_key=this_key, + xpath_object=TRUE_LIST_XPATH, + policy_file_data=policy_file_data): + configured_elements[this_element_name] = True + log.trace('element %s is configured true', + child_item.attrib['id']) + if FALSE_LIST_XPATH(child_item) and this_element_name not in configured_elements: + log.trace('checking falseList') + if _checkListItem( + policy_element=child_item, + policy_name=this_policy_name, + policy_key=this_key, + xpath_object=FALSE_LIST_XPATH, + policy_file_data=policy_file_data): + configured_elements[this_element_name] = False + policy_disabled_elements = policy_disabled_elements + 1 + log.trace('element %s is configured false', + child_item.attrib['id']) + else: + if _regexSearchRegPolData( + re.escape(_processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=True)), + policy_file_data): + configured_elements[this_element_name] = False + policy_disabled_elements = policy_disabled_elements + 1 + log.trace('element %s is configured false', child_item.attrib['id']) + elif _regexSearchRegPolData( + re.escape(_processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=False)), + policy_file_data): + configured_elements[this_element_name] = True + log.trace('element %s is configured true', + child_item.attrib['id']) + elif etree.QName(child_item).localname == 'decimal' \ + or etree.QName(child_item).localname == 'text' \ + or etree.QName(child_item).localname == 'longDecimal' \ + or etree.QName(child_item).localname == 'multiText': + # https://msdn.microsoft.com/en-us/library/dn605987(v=vs.85).aspx + if _regexSearchRegPolData( + re.escape(_processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=True)), + policy_file_data): + configured_elements[this_element_name] = 'Disabled' + policy_disabled_elements = policy_disabled_elements + 1 + log.trace('element %s is disabled', + child_item.attrib['id']) + elif _regexSearchRegPolData( + re.escape(_processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=False)), + policy_data=policy_file_data): + configured_value = _getDataFromRegPolData( + _processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=False), + policy_data=policy_file_data) + configured_elements[this_element_name] = configured_value + log.trace('element %s is enabled, value == %s', + child_item.attrib['id'], + configured_value) + elif etree.QName(child_item).localname == 'enum': + if _regexSearchRegPolData( + re.escape(_processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=True)), + policy_file_data): + log.trace('enum element %s is disabled', + child_item.attrib['id']) + configured_elements[this_element_name] = 'Disabled' + policy_disabled_elements = policy_disabled_elements + 1 + else: + for enum_item in child_item.getchildren(): + if _checkValueItemParent( + policy_element=enum_item, + policy_name=child_item.attrib['id'], + policy_key=child_key, + policy_valueName=child_value_name, + xpath_object=VALUE_XPATH, + policy_file_data=policy_file_data): + if VALUE_LIST_XPATH(enum_item): + log.trace('enum item has a valueList') + if _checkListItem( + policy_element=enum_item, + policy_name=this_policy_name, + policy_key=child_key, + xpath_object=VALUE_LIST_XPATH, + policy_file_data=policy_file_data): + log.trace('all valueList items exist in file') + configured_elements[this_element_name] = _getAdmlDisplayName( + adml_xml_data=adml_policy_resources, + display_name=enum_item.attrib['displayName']) + else: + configured_elements[this_element_name] = _getAdmlDisplayName( + adml_xml_data=adml_policy_resources, + display_name=enum_item.attrib['displayName']) + elif etree.QName(child_item).localname == 'list': + return_value_name = False + if 'explicitValue' in child_item.attrib \ + and child_item.attrib['explicitValue'].lower() == 'true': + log.trace('explicitValue list, we will return value names') + return_value_name = True + if _regexSearchRegPolData( + re.escape(_processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=False)) + salt.utils.stringutils.to_bytes(r'(?!\*\*delvals\.)'), + policy_data=policy_file_data): + configured_value = _getDataFromRegPolData( + _processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=False), + policy_data=policy_file_data, + return_value_name=return_value_name) + configured_elements[this_element_name] = configured_value + log.trace('element %s is enabled values: %s', + child_item.attrib['id'], + configured_value) + elif _regexSearchRegPolData( + re.escape(_processValueItem( + element=child_item, + reg_key=child_key, + reg_valuename=child_value_name, + policy=admx_policy, + parent_element=elements_item, + check_deleted=True)), + policy_file_data): + configured_elements[this_element_name] = "Disabled" + policy_disabled_elements = policy_disabled_elements + 1 + log.trace('element {0} is disabled'.format(child_item.attrib['id'])) + if element_only_enabled_disabled: + if len(required_elements.keys()) > 0 and len(configured_elements.keys()) == len(required_elements.keys()): + if policy_disabled_elements == len(required_elements.keys()): + log.trace('{0} is disabled by all enum elements'.format(this_policy_name)) + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = 'Disabled' + else: + log.trace('{0} is enabled by enum elements'.format(this_policy_name)) + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = configured_elements + else: + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + else: + if this_policy_setting == 'Enabled': + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = configured_elements + else: + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + else: + policy_vals.setdefault(this_policy_namespace, {})[this_policy_name] = this_policy_setting + + if return_full_policy_names and \ + this_policy_namespace in policy_vals and \ + this_policy_name in policy_vals[this_policy_namespace]: + full_names.setdefault(this_policy_namespace, {}) + full_names[this_policy_namespace][this_policy_name] = _getFullPolicyName( + policy_item=admx_policy, + policy_name=admx_policy.attrib['name'], + return_full_policy_names=return_full_policy_names, + adml_language=adml_language) + # Make sure the we're passing the full policy name + # This issue was found when setting the `Allow Telemetry` setting + # All following states would show a change in this setting + # When the state does it's first `lgpo.get` it would return `AllowTelemetry` + # On the second run, it would return `Allow Telemetry` + # This makes sure we're always returning the full_name when required + if this_policy_name in policy_vals[this_policy_namespace][this_policy_name]: + full_name = full_names[this_policy_namespace][this_policy_name] + setting = policy_vals[this_policy_namespace][this_policy_name].pop(this_policy_name) + policy_vals[this_policy_namespace][this_policy_name][full_name] = setting + if this_policy_namespace in policy_vals and \ + this_policy_name in policy_vals[this_policy_namespace]: + hierarchy.setdefault(this_policy_namespace, {})[this_policy_name] = _build_parent_list( + policy_definition=admx_policy, + return_full_policy_names=return_full_policy_names, + adml_language=adml_language) + + if policy_vals and return_full_policy_names and not hierarchical_return: + log.debug('Compiling non hierarchical return...') + unpathed_dict = {} + pathed_dict = {} + for policy_namespace in list(policy_vals): + for policy_item in list(policy_vals[policy_namespace]): + full_name = full_names[policy_namespace][policy_item] + if full_name in policy_vals[policy_namespace]: + # add this item with the path'd full name + full_path_list = hierarchy[policy_namespace][policy_item] + full_path_list.reverse() + full_path_list.append(full_names[policy_namespace][policy_item]) + policy_vals['\\'.join(full_path_list)] = policy_vals[policy_namespace].pop(policy_item) + pathed_dict[full_name] = True + else: + policy_vals[policy_namespace][full_name] = policy_vals[policy_namespace].pop(policy_item) + unpathed_dict.setdefault(policy_namespace, {})[full_name] = policy_item + # go back and remove any "unpathed" policies that need a full path + for path_needed in unpathed_dict[policy_namespace]: + # remove the item with the same full name and re-add it w/a path'd version + full_path_list = hierarchy[policy_namespace][unpathed_dict[policy_namespace][path_needed]] + full_path_list.reverse() + full_path_list.append(path_needed) + log.trace('full_path_list == %s', full_path_list) + policy_vals['\\'.join(full_path_list)] = policy_vals[policy_namespace].pop(path_needed) + for policy_namespace in list(policy_vals): + if policy_vals[policy_namespace] == {}: + policy_vals.pop(policy_namespace) + if policy_vals and hierarchical_return: + if hierarchy: + log.debug('Compiling hierarchical return...') + start_time = time.time() + for policy_namespace in hierarchy: + for hierarchy_item in hierarchy[policy_namespace]: + if hierarchy_item in policy_vals[policy_namespace]: + t_dict = {} + first_item = True + for item in hierarchy[policy_namespace][hierarchy_item]: + new_dict = {} + if first_item: + h_policy_name = hierarchy_item + if return_full_policy_names: + h_policy_name = full_names[policy_namespace][hierarchy_item] + new_dict[item] = {h_policy_name: policy_vals[policy_namespace].pop(hierarchy_item)} + first_item = False + else: + new_dict[item] = t_dict + t_dict = new_dict + if t_dict: + policy_vals = dictupdate.update(policy_vals, t_dict) + if policy_namespace in policy_vals and policy_vals[policy_namespace] == {}: + policy_vals.pop(policy_namespace) + policy_vals = { + policy_data.admx_registry_classes[policy_class]['lgpo_section']: { + 'Administrative Templates': policy_vals}} + + return policy_vals + + +def get_policy(policy_name, + policy_class, + adml_language='en-US', + return_value_only=True, + return_full_policy_names=True, + hierarchical_return=False): + r''' + Get the current settings for a single policy on the machine + + Args: + policy_name (str): + The name of the policy to retrieve. Can be the any of the names + or alieses returned by ``lgpo.get_policy_info`` + + policy_class (str): + The policy class. Must be one of ``machine`` or ``user`` + + adml_language (str): + The language code for the adml file to use for localization. The + default is ``en-US`` + + return_value_only (bool): + ``True`` will return only the value for the policy, without the + name of the policy. ``return_full_policy_names`` and + ``hierarchical_return`` will be ignored. Default is ``True`` + + return_full_policy_names (bool): + Returns the full policy name regardless of what was passed in + ``policy_name`` + + hierarchical_return (bool): + Returns a hierarchical view of the policy showing its parents + + Returns: + dict: A dictionary containing the policy settings + + CLI Example: + + .. code-block:: bash + + # Using the policy id + salt * lgpo.get_policy LockoutDuration machine + salt * lgpo.get_policy AutoUpdateCfg machine + + # Using the full name + salt * lgpo.get_policy "Account lockout duration" machine + salt * lgpo.get_policy "Configure Automatic Updates" machine + + # Using full path and name + salt * lgpo.get_policy "Windows Components\Windows Update\Configure Automatic Updates" machine + ''' + if not policy_name: + raise SaltInvocationError('policy_name must be defined') + if not policy_class: + raise SaltInvocationError('policy_class must be defined') + policy_class = policy_class.title() + policy_data = _policy_info() + if policy_class not in policy_data.policies.keys(): + policy_classes = ', '.join(policy_data.policies.keys()) + message = 'The requested policy class "{0}" is invalid, policy_class ' \ + 'should be one of: {1}'.format(policy_class, policy_classes) + raise CommandExecutionError(message) + + # Look in the _policy_data object first + policy_definition = None + if policy_name in policy_data.policies[policy_class]['policies']: + policy_definition = policy_data.policies[policy_class]['policies'][policy_name] + else: + for pol in policy_data.policies[policy_class]['policies']: + if policy_data.policies[policy_class]['policies'][pol]['Policy'].lower() == policy_name.lower(): + policy_definition = policy_data.policies[policy_class]['policies'][pol] + break + if policy_definition: + if return_value_only: + return _get_policy_info_setting(policy_definition) + if return_full_policy_names: + key_name = policy_definition['Policy'] + else: + key_name = policy_name + setting = {key_name: _get_policy_info_setting(policy_definition)} + if hierarchical_return: + if 'lgpo_section' in policy_definition: + first_item = True + t_dict = {} + for level in reversed(policy_definition['lgpo_section']): + new_dict = {} + if first_item: + new_dict[level] = { + key_name: setting.pop(key_name)} + first_item = False + else: + new_dict[level] = t_dict + t_dict = new_dict + if t_dict: + setting = t_dict + + return setting + + success, policy_obj, _, _ = _lookup_admin_template( + policy_name=policy_name, + policy_class=policy_class, + adml_language=adml_language) + if success: + setting = _get_policy_adm_setting( + admx_policy=policy_obj, + policy_class=policy_class, + adml_language=adml_language, + return_full_policy_names=return_full_policy_names, + hierarchical_return=hierarchical_return + ) + if return_value_only: + for key in setting: + return setting[key] + return setting + + def set_computer_policy(name, setting, cumulative_rights_assignments=True, @@ -7994,11 +8755,11 @@ class in this module. raise SaltInvocationError(msg.format(policies[p_class][policy_name], policy_name)) if 'Registry' in _pol: # set value in registry - log.debug('%s is a registry policy', policy_name) + log.trace('%s is a registry policy', policy_name) _regedits[policy_name] = {'policy': _pol, 'value': _value} elif 'Secedit' in _pol: # set value with secedit - log.debug('%s is a Secedit policy', policy_name) + log.trace('%s is a Secedit policy', policy_name) if _pol['Secedit']['Section'] not in _secedits: _secedits[_pol['Secedit']['Section']] = [] _secedits[_pol['Secedit']['Section']].append( @@ -8006,7 +8767,7 @@ class in this module. '=', six.text_type(_value)])) elif 'NetSH' in _pol: # set value with netsh - log.debug('%s is a NetSH policy', policy_name) + log.trace('%s is a NetSH policy', policy_name) _netshs.setdefault(policy_name, { 'profile': _pol['NetSH']['Profile'], 'section': _pol['NetSH']['Section'], @@ -8021,16 +8782,16 @@ class in this module. }) elif 'NetUserModal' in _pol: # set value via NetUserModal - log.debug('%s is a NetUserModal policy', policy_name) + log.trace('%s is a NetUserModal policy', policy_name) if _pol['NetUserModal']['Modal'] not in _modal_sets: _modal_sets[_pol['NetUserModal']['Modal']] = {} _modal_sets[_pol['NetUserModal']['Modal']][_pol['NetUserModal']['Option']] = _value elif 'LsaRights' in _pol: - log.debug('%s is a LsaRights policy', policy_name) + log.trace('%s is a LsaRights policy', policy_name) _lsarights[policy_name] = {'policy': _pol, 'value': _value} else: _value = policies[p_class][policy_name] - log.debug('searching for "%s" in admx data', policy_name) + log.trace('searching for "%s" in admx data', policy_name) success, the_policy, policy_name_list, msg = _lookup_admin_template( policy_name=policy_name, policy_class=p_class, @@ -8044,8 +8805,8 @@ class in this module. else: raise SaltInvocationError(msg) if policy_namespace and policy_name in _admTemplateData[policy_namespace] and the_policy is not None: - log.debug('setting == %s', six.text_type(_admTemplateData[policy_namespace][policy_name]).lower()) - log.debug(six.text_type(_admTemplateData[policy_namespace][policy_name]).lower()) + log.trace('setting == %s', six.text_type(_admTemplateData[policy_namespace][policy_name]).lower()) + log.trace(six.text_type(_admTemplateData[policy_namespace][policy_name]).lower()) if six.text_type(_admTemplateData[policy_namespace][policy_name]).lower() != 'disabled' \ and six.text_type(_admTemplateData[policy_namespace][policy_name]).lower() != 'not configured': if ELEMENTS_XPATH(the_policy): @@ -8053,14 +8814,14 @@ class in this module. for elements_item in ELEMENTS_XPATH(the_policy): for child_item in elements_item.getchildren(): # check each element - log.debug('checking element %s', child_item.attrib['id']) + log.trace('checking element %s', child_item.attrib['id']) temp_element_name = None this_element_name = _getFullPolicyName( policy_item=child_item, policy_name=child_item.attrib['id'], return_full_policy_names=True, adml_language=adml_language) - log.debug('id attribute == "%s" this_element_name == "%s"', child_item.attrib['id'], this_element_name) + log.trace('id attribute == "%s" this_element_name == "%s"', child_item.attrib['id'], this_element_name) if this_element_name in _admTemplateData[policy_namespace][policy_name]: temp_element_name = this_element_name elif child_item.attrib['id'] in _admTemplateData[policy_namespace][policy_name]: @@ -8147,7 +8908,7 @@ class in this module. raise SaltInvocationError(msg) if _regedits: for regedit in _regedits: - log.debug('%s is a Registry policy', regedit) + log.trace('%s is a Registry policy', regedit) # if the value setting is None or "(value not set)", we will delete the value from the registry if _regedits[regedit]['value'] is not None and _regedits[regedit]['value'] != '(value not set)': _ret = __salt__['reg.set_value']( @@ -8193,7 +8954,7 @@ class in this module. raise SaltInvocationError(msg.format(lsaright)) if _secedits: # we've got secedits to make - log.debug(_secedits) + log.trace(_secedits) ini_data = '\r\n'.join(['[Unicode]', 'Unicode=yes']) _seceditSections = ['System Access', 'Event Audit', 'Registry Values', 'Privilege Rights'] for _seceditSection in _seceditSections: @@ -8203,7 +8964,7 @@ class in this module. ini_data = '\r\n'.join([ini_data, '[Version]', 'signature="$CHICAGO$"', 'Revision=1']) - log.debug('ini_data == %s', ini_data) + log.trace('ini_data == %s', ini_data) if not _write_secedit_data(ini_data): msg = ('Error while attempting to set policies via ' 'secedit. Some changes may not be applied as ' @@ -8212,34 +8973,36 @@ class in this module. if _netshs: # we've got netsh settings to make for setting in _netshs: - log.debug('Setting firewall policy: {0}'.format(setting)) - log.debug(_netshs[setting]) + log.trace('Setting firewall policy: {0}'.format(setting)) + log.trace(_netshs[setting]) _setOptionValueNetSH(**_netshs[setting]) if _advaudits: # We've got AdvAudit settings to make for setting in _advaudits: - log.debug('Setting Advanced Audit policy: {0}'.format(setting)) - log.debug(_advaudits[setting]) + log.trace('Setting Advanced Audit policy: {0}'.format(setting)) + log.trace(_advaudits[setting]) _setOptionValueAdvAudit(**_advaudits[setting]) if _modal_sets: # we've got modalsets to make - log.debug(_modal_sets) + log.trace(_modal_sets) for _modal_set in _modal_sets: try: _existingModalData = win32net.NetUserModalsGet(None, _modal_set) _newModalSetData = dictupdate.update(_existingModalData, _modal_sets[_modal_set]) - log.debug('NEW MODAL SET = %s', _newModalSetData) + log.trace('NEW MODAL SET = %s', _newModalSetData) _ret = win32net.NetUserModalsSet(None, _modal_set, _newModalSetData) # TODO: This needs to be more specific - except Exception: - msg = 'An unhandled exception occurred while attempting to set policy via NetUserModalSet' + except Exception as exc: + msg = 'An unhandled exception occurred while ' \ + 'attempting to set policy via ' \ + 'NetUserModalSet\n%s' % exc log.exception(msg) raise CommandExecutionError(msg) if _admTemplateData: _ret = False - log.debug('going to write some adm template data :: %s', _admTemplateData) + log.trace('going to write some adm template data :: %s', _admTemplateData) _ret = _writeAdminTemplateRegPolFile(_admTemplateData, adml_language=adml_language, registry_class=p_class) diff --git a/salt/states/win_lgpo.py b/salt/states/win_lgpo.py index 8645f4420dcf..3182cdbbd82f 100644 --- a/salt/states/win_lgpo.py +++ b/salt/states/win_lgpo.py @@ -7,9 +7,11 @@ This state allows configuring local Windows Group Policy -The state can be used to ensure the setting of a single policy or multiple policies in one pass. +The state can be used to ensure the setting of a single policy or multiple +policies in one pass. -Single policies must specify the policy name, the setting, and the policy class (Machine/User/Both) +Single policies must specify the policy name, the setting, and the policy class +(Machine/User/Both) Example single policy configuration @@ -24,7 +26,7 @@ .. code-block:: yaml Account lockout duration: - gpo.set: + lgpo.set: - setting: 120 - policy_class: Machine @@ -35,10 +37,11 @@ Company Local Group Policy: lgpo.set: - computer_policy: - Deny logon locally: Guest + Deny log on locally: + - Guest Account lockout duration: 120 Account lockout threshold: 10 - Reset account lockout counter after: 1440 + Reset account lockout counter after: 120 Enforce password history: 24 Maximum password age: 60 Minimum password age: 1 @@ -63,9 +66,9 @@ Maximum password age: 60 Minimum password age: 1 Minimum password length: 14 - Account lockout duration: 1440 + Account lockout duration: 120 Account lockout threshold: 10 - Reset account lockout counter after: 1440 + Reset account lockout counter after: 120 Manage auditing and security log: - "BUILTIN\\Administrators" Replace a process level token: @@ -100,9 +103,7 @@ "Set the intranet update service for detecting updates": http://mywsus "Set the intranet statistics server": http://mywsus - cumulative_rights_assignments: True - ''' - # Import python libs from __future__ import absolute_import, unicode_literals, print_function import logging @@ -111,6 +112,7 @@ import salt.utils.data import salt.utils.dictdiffer import salt.utils.json +import salt.utils.win_functions # Import 3rd party libs from salt.ext import six @@ -133,7 +135,7 @@ def _compare_policies(new_policy, current_policy): otherwise ``False`` ''' # Compared dicts, lists, and strings - if isinstance(new_policy, six.string_types): + if isinstance(new_policy, (six.string_types, six.integer_types)): return new_policy == current_policy elif isinstance(new_policy, list): if isinstance(current_policy, list): @@ -157,33 +159,42 @@ def set_(name, cumulative_rights_assignments=True, adml_language='en-US'): ''' - Ensure the specified policy is set - - name - the name of a single policy to configure - - setting - the configuration setting for the single named policy - if this argument is used the computer_policy/user_policy arguments will be ignored - - policy_class - the policy class of the single named policy to configure - this can "machine", "user", or "both" - - computer_policy - a dict of policyname: value pairs of a set of computer policies to configure - if this argument is used, the name/setting/policy_class arguments will be ignored - - user_policy - a dict of policyname: value pairs of a set of user policies to configure - if this argument is used, the name/setting/policy_class arguments will be ignored - - cumulative_rights_assignments - determine if any user right assignment policies specified will be cumulative - or explicit - - adml_language - the adml language to use for AMDX policy data/display conversions + Ensure the specified policy is set. + + .. warning:: + The ``setting`` argument cannot be used in conjunction with the + ``computer_policy`` or ``user_policy`` arguments + + Args: + name (str): The name of a single policy to configure + + setting (str, dict, list): + The configuration setting for the single named policy. If this + argument is used the ``computer_policy`` / ``user_policy`` arguments + will be ignored + + policy_class (str): + The policy class of the single named policy to configure. This can + ``machine``, ``user``, or ``both`` + + computer_policy (dict): + A dictionary of containing the policy name and key/value pairs of a + set of computer policies to configure. If this argument is used, the + ``name`` / ``policy_class`` arguments will be ignored + + user_policy (dict): + A dictionary of containing the policy name and key/value pairs of a + set of user policies to configure. If this argument is used, the + ``name`` / ``policy_class`` arguments will be ignored + + cumulative_rights_assignments (bool): + If user rights assignments are being configured, determines if any + user right assignment policies specified will be cumulative or + explicit + + adml_language (str): + The adml language to use for AMDX policy data/display conversions. + Default is ``en-US`` ''' ret = {'name': name, 'result': True, @@ -191,20 +202,21 @@ def set_(name, 'comment': ''} policy_classes = ['machine', 'computer', 'user', 'both'] if not setting and not computer_policy and not user_policy: - msg = 'At least one of the parameters setting, computer_policy, or user_policy' - msg = msg + ' must be specified.' + msg = 'At least one of the parameters setting, computer_policy, or ' \ + 'user_policy must be specified.' ret['result'] = False ret['comment'] = msg return ret if setting and not policy_class: - msg = 'A single policy setting was specified but the policy_class was not specified.' + msg = 'A single policy setting was specified but the policy_class ' \ + 'was not specified.' ret['result'] = False ret['comment'] = msg return ret if setting and (computer_policy or user_policy): - msg = 'The setting and computer_policy/user_policy parameters are mutually exclusive. Please' - msg = msg + ' specify either a policy name and setting or a computer_policy and/or user_policy' - msg = msg + ' dict' + msg = 'The setting and computer_policy/user_policy parameters are ' \ + 'mutually exclusive. Please specify either a policy name and ' \ + 'setting or a computer_policy and/or user_policy dict' ret['result'] = False ret['comment'] = msg return ret @@ -238,68 +250,62 @@ def set_(name, computer_policy[name] = setting elif policy_class.lower() == 'user': user_policy[name] = setting - elif policy_class.lower() == 'machine' or policy_class.lower() == 'computer': + elif policy_class.lower() in ['machine', 'computer']: computer_policy[name] = setting - pol_data = {} - pol_data['user'] = {'output_section': 'User Configuration', - 'requested_policy': user_policy, - 'policy_lookup': {}} - pol_data['machine'] = {'output_section': 'Computer Configuration', - 'requested_policy': computer_policy, - 'policy_lookup': {}} - + pol_data = { + 'user': { + 'requested_policy': user_policy, + 'policy_lookup': {}}, + 'machine': { + 'requested_policy': computer_policy, + 'policy_lookup': {}}} + + current_policy = {} for p_class, p_data in six.iteritems(pol_data): if p_data['requested_policy']: - for policy_name, policy_setting in six.iteritems(p_data['requested_policy']): - lookup = __salt__['lgpo.get_policy_info'](policy_name, - p_class, - adml_language=adml_language) + for p_name, _ in six.iteritems(p_data['requested_policy']): + lookup = __salt__['lgpo.get_policy_info']( + policy_name=p_name, + policy_class=p_class, + adml_language=adml_language) if lookup['policy_found']: - pol_data[p_class]['policy_lookup'][policy_name] = lookup + pol_data[p_class]['policy_lookup'][p_name] = lookup + # Since we found the policy, let's get the current setting + # as well + current_policy.setdefault(p_class, {}) + current_policy[p_class][p_name] = __salt__['lgpo.get_policy']( + policy_name=p_name, + policy_class=p_class, + adml_language=adml_language, + return_value_only=True) else: ret['comment'] = ' '.join([ret['comment'], lookup['message']]) ret['result'] = False if not ret['result']: return ret - current_policy = __salt__['lgpo.get'](policy_class=policy_class, - adml_language=adml_language, - hierarchical_return=False) + log.debug('pol_data == %s', pol_data) log.debug('current policy == %s', current_policy) # compare policies policy_changes = [] - for policy_section, policy_data in six.iteritems(pol_data): - pol_id = None - if policy_data and policy_data['output_section'] in current_policy: - for policy_name, policy_setting in six.iteritems(policy_data['requested_policy']): - currently_set = False - # Check Case sensitive first (faster) - if policy_name in current_policy[policy_data['output_section']]: + for p_class, p_data in six.iteritems(pol_data): + requested_policy = p_data.get('requested_policy') + if requested_policy: + for p_name, p_setting in six.iteritems(requested_policy): + if p_name in current_policy[p_class]: currently_set = True - pol_id = policy_name - # Check case insensitive - elif policy_name.lower() in (k.lower() for k in current_policy[policy_data['output_section']]): - for p_name in current_policy[policy_data['output_section']]: - if policy_name.lower() == p_name.lower(): - currently_set = True - pol_id = p_name - break - # Check aliases - else: - for alias in policy_data['policy_lookup'][policy_name]['policy_aliases']: - log.debug('checking alias %s', alias) - if alias in current_policy[policy_data['output_section']]: - currently_set = True - pol_id = alias - break if currently_set: # compare - log.debug('need to compare %s from ' - 'current/requested policy', policy_name) + log.debug('need to compare %s from current/requested ' + 'policy', p_name) changes = False - requested_policy_json = salt.utils.json.dumps(policy_data['requested_policy'][policy_name], sort_keys=True).lower() - current_policy_json = salt.utils.json.dumps(current_policy[policy_data['output_section']][pol_id], sort_keys=True).lower() + requested_policy_json = salt.utils.json.dumps( + p_data['requested_policy'][p_name], + sort_keys=True).lower() + current_policy_json = salt.utils.json.dumps( + current_policy[p_class][p_name], + sort_keys=True).lower() requested_policy_check = salt.utils.json.loads(requested_policy_json) current_policy_check = salt.utils.json.loads(current_policy_json) @@ -310,35 +316,39 @@ def set_(name, if not policies_are_equal: additional_policy_comments = [] - if policy_data['policy_lookup'][policy_name]['rights_assignment'] and cumulative_rights_assignments: - for user in policy_data['requested_policy'][policy_name]: - if user not in current_policy[policy_data['output_section']][pol_id]: - changes = True + if p_data['policy_lookup'][p_name]['rights_assignment'] and cumulative_rights_assignments: + for user in p_data['requested_policy'][p_name]: + if user not in current_policy[p_class][p_name]: + user = salt.utils.win_functions.get_sam_name(user) + if user not in current_policy[p_class][p_name]: + changes = True + else: + additional_policy_comments.append('"{0}" is already granted the right'.format(user)) else: additional_policy_comments.append('"{0}" is already granted the right'.format(user)) else: changes = True if changes: log.debug('%s current policy != requested policy', - policy_name) + p_name) log.debug( 'we compared %s to %s', requested_policy_json, current_policy_json ) - policy_changes.append(policy_name) + policy_changes.append(p_name) else: if additional_policy_comments: - ret['comment'] = '"{0}" is already set ({1})\n'.format(policy_name, ', '.join(additional_policy_comments)) + ret['comment'] = '"{0}" is already set ({1})\n'.format(p_name, ', '.join(additional_policy_comments)) else: - ret['comment'] = '"{0}" is already set\n'.format(policy_name) + ret['comment'] + ret['comment'] = '"{0}" is already set\n'.format(p_name) + ret['comment'] else: log.debug('%s current setting matches ' - 'the requested setting', policy_name) - ret['comment'] = '"{0}" is already set\n'.format(policy_name) + ret['comment'] + 'the requested setting', p_name) + ret['comment'] = '"{0}" is already set\n'.format(p_name) + ret['comment'] else: - policy_changes.append(policy_name) + policy_changes.append(p_name) log.debug('policy %s is not set, we will configure it', - policy_name) + p_name) if __opts__['test']: if policy_changes: ret['result'] = None @@ -354,11 +364,20 @@ def set_(name, adml_language=adml_language) if _ret: ret['result'] = _ret + new_policy = {} + for p_class, p_data in six.iteritems(pol_data): + if p_data['requested_policy']: + for p_name, p_setting in six.iteritems( + p_data['requested_policy']): + new_policy.setdefault(p_class, {}) + new_policy[p_class][p_name] = __salt__[ + 'lgpo.get_policy']( + policy_name=p_name, + policy_class=p_class, + adml_language=adml_language, + return_value_only=True) ret['changes'] = salt.utils.dictdiffer.deep_diff( - current_policy, - __salt__['lgpo.get'](policy_class=policy_class, - adml_language=adml_language, - hierarchical_return=False)) + old=current_policy, new=new_policy) if ret['changes']: ret['comment'] = 'The following policies changed:\n{0}' \ ''.format('\n'.join(policy_changes)) diff --git a/salt/utils/win_reg.py b/salt/utils/win_reg.py index ab5de3bf9b47..f9dad8600cfc 100644 --- a/salt/utils/win_reg.py +++ b/salt/utils/win_reg.py @@ -89,6 +89,8 @@ def _to_unicode(vdata): # None does not convert to Unicode if vdata is None: return None + if isinstance(vdata, int): + vdata = str(vdata) return salt.utils.stringutils.to_unicode(vdata, 'utf-8') @@ -522,7 +524,7 @@ def read_value(hive, key, vname=None, use_32bit_registry=False): try: # RegQueryValueEx returns and accepts unicode data vdata, vtype = win32api.RegQueryValueEx(handle, local_vname) - if vdata or vdata in [0, '']: + if vdata or vdata in [0, '', []]: # Only convert text types to unicode ret['vtype'] = registry.vtype_reverse[vtype] if vtype == win32con.REG_MULTI_SZ: diff --git a/tests/unit/modules/test_win_lgpo.py b/tests/unit/modules/test_win_lgpo.py index b666c99e04b9..723760164f9f 100644 --- a/tests/unit/modules/test_win_lgpo.py +++ b/tests/unit/modules/test_win_lgpo.py @@ -53,3 +53,66 @@ def test__encode_string_none(self): ''' value = win_lgpo._encode_string(None) self.assertEqual(value, self.encoded_null) + + def test__multi_string_get_transform_list(self): + ''' + ``_multi_string_get_transform`` should return the list when a list is + passed + ''' + test_value = ['Spongebob', 'Squarepants'] + value = win_lgpo._policy_info._multi_string_get_transform(item=test_value) + self.assertEqual(value, test_value) + + def test__multi_string_get_transform_none(self): + ''' + ``_multi_string_get_transform`` should return "Not Defined" when + ``None`` is passed + ''' + test_value = None + value = win_lgpo._policy_info._multi_string_get_transform(item=test_value) + self.assertEqual(value, 'Not Defined') + + def test__multi_string_get_transform_invalid(self): + ''' + ``_multi_string_get_transform`` should return "Not Defined" when + ``None`` is passed + ''' + test_value = 'Some String' + value = win_lgpo._policy_info._multi_string_get_transform(item=test_value) + self.assertEqual(value, 'Invalid Value') + + def test__multi_string_put_transform_list(self): + ''' + ``_multi_string_put_transform`` should return the list when a list is + passed + ''' + test_value = ['Spongebob', 'Squarepants'] + value = win_lgpo._policy_info._multi_string_put_transform(item=test_value) + self.assertEqual(value, test_value) + + def test__multi_string_put_transform_none(self): + ''' + ``_multi_string_put_transform`` should return ``None`` when + "Not Defined" is passed + ''' + test_value = "Not Defined" + value = win_lgpo._policy_info._multi_string_put_transform(item=test_value) + self.assertEqual(value, None) + + def test__multi_string_put_transform_list_from_string(self): + ''' + ``_multi_string_put_transform`` should return a list when a comma + delimited string is passed + ''' + test_value = "Spongebob,Squarepants" + value = win_lgpo._policy_info._multi_string_put_transform(item=test_value) + self.assertEqual(value, ['Spongebob', 'Squarepants']) + + def test__multi_string_put_transform_invalid(self): + ''' + ``_multi_string_put_transform`` should return "Invalid" value if neither + string nor list is passed + ''' + test_value = None + value = win_lgpo._policy_info._multi_string_put_transform(item=test_value) + self.assertEqual(value, "Invalid Value") diff --git a/tests/unit/states/test_win_lgpo.py b/tests/unit/states/test_win_lgpo.py index 3b184401e3e0..ef3f833be341 100644 --- a/tests/unit/states/test_win_lgpo.py +++ b/tests/unit/states/test_win_lgpo.py @@ -98,3 +98,30 @@ def test__compare_policies_dict(self): self.assertFalse( win_lgpo._compare_policies(compare_dict, None) ) + + def test__compare_policies_integer(self): + ''' + ``_compare_policies`` should only return ``True`` when the integer + values are the same. All other scenarios should return ``False`` + ''' + compare_integer = 1 + # Same + self.assertTrue( + win_lgpo._compare_policies(compare_integer, compare_integer) + ) + # Different + self.assertFalse( + win_lgpo._compare_policies(compare_integer, 0) + ) + # List + self.assertFalse( + win_lgpo._compare_policies(compare_integer, ['item1', 'item2']) + ) + # Dict + self.assertFalse( + win_lgpo._compare_policies(compare_integer, {'key': 'value'}) + ) + # None + self.assertFalse( + win_lgpo._compare_policies(compare_integer, None) + ) diff --git a/tests/unit/utils/test_win_reg.py b/tests/unit/utils/test_win_reg.py index 2d3961c54d23..cceca9e1f228 100644 --- a/tests/unit/utils/test_win_reg.py +++ b/tests/unit/utils/test_win_reg.py @@ -2,6 +2,7 @@ # Import Python Libs from __future__ import absolute_import, unicode_literals, print_function +from six import PY3 # Import Salt Testing Libs from tests.support.helpers import destructiveTest, generate_random_name @@ -177,6 +178,40 @@ def test_read_value_non_existing_key(self): expected ) + @destructiveTest + def test_read_value_multi_sz_empty_list(self): + ''' + An empty REG_MULTI_SZ value should return an empty list, not None + ''' + try: + self.assertTrue( + win_reg.set_value( + hive='HKLM', + key=FAKE_KEY, + vname='empty_list', + vdata=[], + vtype='REG_MULTI_SZ' + ) + ) + expected = { + 'hive': 'HKLM', + 'key': FAKE_KEY, + 'success': True, + 'vdata': [], + 'vname': 'empty_list', + 'vtype': 'REG_MULTI_SZ' + } + self.assertEqual( + win_reg.read_value( + hive='HKLM', + key=FAKE_KEY, + vname='empty_list', + ), + expected + ) + finally: + win_reg.delete_key_recursive(hive='HKLM', key=FAKE_KEY) + @destructiveTest def test_set_value(self): ''' @@ -463,3 +498,13 @@ def test_delete_key_unicode(self): ) finally: win_reg.delete_key_recursive(hive='HKLM', key=FAKE_KEY) + + def test__to_unicode_int(self): + ''' + Test the ``_to_unicode`` function when it receives an integer value. + Should return a unicode value, which is unicode in PY2 and str in PY3. + ''' + if PY3: + self.assertTrue(isinstance(win_reg._to_unicode(1), str)) + else: + self.assertTrue(isinstance(win_reg._to_unicode(1), unicode))