File: //usr/lib/python2.7/site-packages/salt/modules/win_snmp.py
# -*- coding: utf-8 -*-
'''
Module for managing SNMP service settings on Windows servers.
The Windows feature 'SNMP-Service' must be installed.
'''
# Import Python libs
from __future__ import absolute_import, unicode_literals, print_function
import logging
# Import Salt libs
import salt.utils.platform
from salt.exceptions import SaltInvocationError, CommandExecutionError
# Import 3rd party libs
from salt.ext import six
_HKEY = 'HKLM'
_SNMP_KEY = r'SYSTEM\CurrentControlSet\Services\SNMP\Parameters'
_AGENT_KEY = r'{0}\RFC1156Agent'.format(_SNMP_KEY)
_COMMUNITIES_KEY = r'{0}\ValidCommunities'.format(_SNMP_KEY)
_SNMP_GPO_KEY = r'SOFTWARE\Policies\SNMP\Parameters'
_COMMUNITIES_GPO_KEY = r'{0}\ValidCommunities'.format(_SNMP_GPO_KEY)
_PERMISSION_TYPES = {'None': 1,
'Notify': 2,
'Read Only': 4,
'Read Write': 8,
'Read Create': 16}
_SERVICE_TYPES = {'None': 0,
'Physical': 1,
'Datalink and subnetwork': 2,
'Internet': 4,
'End-to-end': 8,
'Applications': 64}
_LOG = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'win_snmp'
def __virtual__():
'''
Only works on Windows systems.
'''
if not salt.utils.platform.is_windows():
return False, 'Module win_snmp: Requires Windows'
if not __salt__['reg.key_exists'](_HKEY, _SNMP_KEY):
return False, 'Module win_snmp: SNMP not installed'
return __virtualname__
def _to_unicode(instr):
'''
Converts from current users character encoding to unicode.
When instr has a value of None, the return value of the function
will also be None.
'''
if instr is None or isinstance(instr, six.text_type):
return instr
else:
return six.text_type(instr, 'utf8')
def get_agent_service_types():
'''
Get the sysServices types that can be configured.
Returns:
list: A list of service types.
CLI Example:
.. code-block:: bash
salt '*' win_snmp.get_agent_service_types
'''
return list(_SERVICE_TYPES)
def get_permission_types():
'''
Get the permission types that can be configured for communities.
Returns:
list: A list of permission types.
CLI Example:
.. code-block:: bash
salt '*' win_snmp.get_permission_types
'''
return list(_PERMISSION_TYPES)
def get_agent_settings():
'''
Determine the value of the SNMP sysContact, sysLocation, and sysServices
settings.
Returns:
dict: A dictionary of the agent settings.
CLI Example:
.. code-block:: bash
salt '*' win_snmp.get_agent_settings
'''
ret = dict()
sorted_types = sorted(_SERVICE_TYPES.items(), key=lambda x: (-x[1], x[0]))
ret['services'] = list()
ret['contact'] = (__salt__['reg.read_value'](
_HKEY, _AGENT_KEY, 'sysContact'))['vdata']
ret['location'] = (__salt__['reg.read_value'](
_HKEY, _AGENT_KEY, 'sysLocation'))['vdata']
current_bitmask = (__salt__['reg.read_value'](
_HKEY, _AGENT_KEY, 'sysServices'))['vdata']
if current_bitmask == 0:
ret['services'].append(sorted_types[-1][0])
else:
# sorted_types is sorted from greatest to least bitmask.
for service, bitmask in sorted_types:
if current_bitmask is not None and current_bitmask > 0:
remaining_bitmask = current_bitmask - bitmask
if remaining_bitmask >= 0:
current_bitmask = remaining_bitmask
ret['services'].append(service)
else:
break
ret['services'] = sorted(ret['services'])
return ret
def set_agent_settings(contact=None, location=None, services=None):
'''
Manage the SNMP sysContact, sysLocation, and sysServices settings.
Args:
contact (str, optional): The SNMP contact.
location (str, optional): The SNMP location.
services (list, optional): A list of selected services. The possible
service names can be found via ``win_snmp.get_agent_service_types``.
To disable all services pass a list of None, ie: ['None']
Returns:
bool: True if successful, otherwise False
CLI Example:
.. code-block:: bash
salt '*' win_snmp.set_agent_settings contact='Contact Name' location='Place' services="['Physical']"
'''
if services is not None:
# Filter services for unique items, and sort them for comparison
# purposes.
services = sorted(set(services))
# Validate the services.
for service in services:
if service not in _SERVICE_TYPES:
message = ("Invalid service '{0}' specified. Valid services:"
' {1}').format(service, get_agent_service_types())
raise SaltInvocationError(message)
if six.PY2:
contact = _to_unicode(contact)
location = _to_unicode(location)
settings = {'contact': contact, 'location': location, 'services': services}
current_settings = get_agent_settings()
if settings == current_settings:
_LOG.debug('Agent settings already contain the provided values.')
return True
if contact is not None:
if contact != current_settings['contact']:
__salt__['reg.set_value'](
_HKEY, _AGENT_KEY, 'sysContact', contact, 'REG_SZ')
if location is not None:
if location != current_settings['location']:
__salt__['reg.set_value'](
_HKEY, _AGENT_KEY, 'sysLocation', location, 'REG_SZ')
if services is not None:
if set(services) != set(current_settings['services']):
# Calculate the total value. Produces 0 if an empty list was provided,
# corresponding to the None _SERVICE_TYPES value.
vdata = sum(_SERVICE_TYPES[service] for service in services)
_LOG.debug('Setting sysServices vdata to: %s', vdata)
__salt__['reg.set_value'](
_HKEY, _AGENT_KEY, 'sysServices', vdata, 'REG_DWORD')
# Get the fields post-change so that we can verify tht all values
# were modified successfully. Track the ones that weren't.
new_settings = get_agent_settings()
failed_settings = dict()
for setting in settings:
if settings[setting] is not None and \
settings[setting] != new_settings[setting]:
failed_settings[setting] = settings[setting]
if failed_settings:
_LOG.error('Unable to configure agent settings: %s', failed_settings)
return False
_LOG.debug('Agent settings configured successfully: %s', settings.keys())
return True
def get_auth_traps_enabled():
'''
Determine whether the host is configured to send authentication traps.
Returns:
bool: True if traps are enabled, otherwise False
CLI Example:
.. code-block:: bash
salt '*' win_snmp.get_auth_traps_enabled
'''
reg_ret = __salt__['reg.read_value'](
_HKEY, _SNMP_KEY, 'EnableAuthenticationTraps')
if reg_ret['vdata'] == '(value not set)':
return False
return bool(reg_ret['vdata'] or 0)
def set_auth_traps_enabled(status=True):
'''
Manage the sending of authentication traps.
Args:
status (bool): True to enable traps. False to disable.
Returns:
bool: True if successful, otherwise False
CLI Example:
.. code-block:: bash
salt '*' win_snmp.set_auth_traps_enabled status='True'
'''
vname = 'EnableAuthenticationTraps'
current_status = get_auth_traps_enabled()
if bool(status) == current_status:
_LOG.debug('%s already contains the provided value.', vname)
return True
vdata = int(status)
__salt__['reg.set_value'](_HKEY, _SNMP_KEY, vname, vdata, 'REG_DWORD')
new_status = get_auth_traps_enabled()
if status == new_status:
_LOG.debug('Setting %s configured successfully: %s', vname, vdata)
return True
_LOG.error('Unable to configure %s with value: %s', vname, vdata)
return False
def get_community_names():
'''
Get the current accepted SNMP community names and their permissions.
If community names are being managed by Group Policy, those values will be
returned instead like this:
.. code-block:: bash
TestCommunity:
Managed by GPO
Community names managed normally will denote the permission instead:
.. code-block:: bash
TestCommunity:
Read Only
Returns:
dict: A dictionary of community names and permissions.
CLI Example:
.. code-block:: bash
salt '*' win_snmp.get_community_names
'''
ret = dict()
# Look in GPO settings first
if __salt__['reg.key_exists'](_HKEY, _COMMUNITIES_GPO_KEY):
_LOG.debug('Loading communities from Group Policy settings')
current_values = __salt__['reg.list_values'](
_HKEY, _COMMUNITIES_GPO_KEY)
# GPO settings are different in that they do not designate permissions
# They are a numbered list of communities like so:
#
# {1: "community 1",
# 2: "community 2"}
#
# Denote that it is being managed by Group Policy.
#
# community 1:
# Managed by GPO
# community 2:
# Managed by GPO
if isinstance(current_values, list):
for current_value in current_values:
# Ignore error values
if not isinstance(current_value, dict):
continue
ret[current_value['vdata']] = 'Managed by GPO'
if not ret:
_LOG.debug('Loading communities from SNMP settings')
current_values = __salt__['reg.list_values'](
_HKEY, _COMMUNITIES_KEY)
# The communities are stored as the community name with a numeric
# permission value. Like this (4 = Read Only):
#
# {"community 1": 4,
# "community 2": 4}
#
# Convert the numeric value to the text equivalent, as present in the
# Windows SNMP service GUI.
#
# community 1:
# Read Only
# community 2:
# Read Only
if isinstance(current_values, list):
for current_value in current_values:
# Ignore error values
if not isinstance(current_value, dict):
continue
permissions = six.text_type()
for permission_name in _PERMISSION_TYPES:
if current_value['vdata'] == _PERMISSION_TYPES[permission_name]:
permissions = permission_name
break
ret[current_value['vname']] = permissions
if not ret:
_LOG.debug('Unable to find existing communities.')
return ret
def set_community_names(communities):
'''
Manage the SNMP accepted community names and their permissions.
.. note::
Settings managed by Group Policy will always take precedence over those
set using the SNMP interface. Therefore if this function finds Group
Policy settings it will raise a CommandExecutionError
Args:
communities (dict): A dictionary of SNMP community names and
permissions. The possible permissions can be found via
``win_snmp.get_permission_types``.
Returns:
bool: True if successful, otherwise False
Raises:
CommandExecutionError:
If SNMP settings are being managed by Group Policy
CLI Example:
.. code-block:: bash
salt '*' win_snmp.set_community_names communities="{'TestCommunity': 'Read Only'}'
'''
values = dict()
if __salt__['reg.key_exists'](_HKEY, _COMMUNITIES_GPO_KEY):
_LOG.debug('Communities on this system are managed by Group Policy')
raise CommandExecutionError(
'Communities on this system are managed by Group Policy')
current_communities = get_community_names()
if communities == current_communities:
_LOG.debug('Communities already contain the provided values.')
return True
for vname in communities:
if not communities[vname]:
communities[vname] = 'None'
try:
vdata = _PERMISSION_TYPES[communities[vname]]
except KeyError:
message = (
"Invalid permission '{0}' specified. Valid permissions: "
"{1}").format(communities[vname], _PERMISSION_TYPES.keys())
raise SaltInvocationError(message)
values[vname] = vdata
# Check current communities.
for current_vname in current_communities:
if current_vname in values:
# Modify existing communities that have a different permission value.
if current_communities[current_vname] != values[current_vname]:
__salt__['reg.set_value'](
_HKEY, _COMMUNITIES_KEY, current_vname,
values[current_vname], 'REG_DWORD')
else:
# Remove current communities that weren't provided.
__salt__['reg.delete_value'](
_HKEY, _COMMUNITIES_KEY, current_vname)
# Create any new communities.
for vname in values:
if vname not in current_communities:
__salt__['reg.set_value'](
_HKEY, _COMMUNITIES_KEY, vname, values[vname], 'REG_DWORD')
# Get the fields post-change so that we can verify tht all values
# were modified successfully. Track the ones that weren't.
new_communities = get_community_names()
failed_communities = dict()
for new_vname in new_communities:
if new_vname not in communities:
failed_communities[new_vname] = None
for vname in communities:
if communities[vname] != new_communities[vname]:
failed_communities[vname] = communities[vname]
if failed_communities:
_LOG.error('Unable to configure communities: %s', failed_communities)
return False
_LOG.debug('Communities configured successfully: %s', communities.keys())
return True