You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
537 lines
26 KiB
537 lines
26 KiB
# coding=utf-8
|
|
#
|
|
# Copyright (C) 2009-2021 Splunk Inc. All Rights Reserved.
|
|
|
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
from collections import namedtuple, OrderedDict
|
|
from base64 import b64decode
|
|
import os
|
|
import ssl
|
|
import sys
|
|
from functools import reduce as reduce
|
|
|
|
from ldap3 import Server, Tls, core, ALL
|
|
from splunklib.searchcommands.validators import Boolean, Integer, List, Map
|
|
from splunklib.binding import HTTPError
|
|
from splunklib import data
|
|
import app
|
|
from .six import itervalues
|
|
|
|
|
|
try:
|
|
from ssl import create_default_context # New in version 2.7.9
|
|
use_ssl_context = True
|
|
except ImportError:
|
|
use_ssl_context = False
|
|
|
|
class Configuration(object):
|
|
Credentials = namedtuple('Credentials', ['realm', 'username', 'password', 'authorization_id'])
|
|
_tls = None
|
|
|
|
def __init__(self, command, is_expanded=False):
|
|
|
|
if command.debug: # debug option overrides logging_level option and logging.conf level setting
|
|
command.logging_level = 'DEBUG'
|
|
|
|
self._buffered_configurations = None
|
|
self.command = command
|
|
self.domain = None
|
|
self.settings = None
|
|
|
|
# All settings are ultimately reduced to these in Configuration._reset_fields
|
|
|
|
self.alternatedomain = None
|
|
self.basedn = None
|
|
self.server = None
|
|
self.credentials = None
|
|
self.decode = None
|
|
self.paged_size = None
|
|
|
|
command.logger.debug('Command = %s', command)
|
|
|
|
if is_expanded:
|
|
self._read_all_configurations()
|
|
else:
|
|
self._read_configuration()
|
|
|
|
return
|
|
|
|
def __str__(self):
|
|
username = self.credentials.username
|
|
text = '{0}(server={1}, credentials={2}, alternatedomain={3}, basedn={4}, decode={5}, paged_size={6})'.format(
|
|
self.command.name, self.server, username, self.alternatedomain, self.basedn, self.decode, self.paged_size)
|
|
return text
|
|
|
|
def open_connection_pool(self, attributes):
|
|
return app.ConnectionPool(self, attributes)
|
|
|
|
def select(self, domain):
|
|
settings = self._buffered_configurations[domain]
|
|
self._reset_fields(settings[0][0], settings[1])
|
|
|
|
# region Privates
|
|
|
|
def _add_buffered_configuration(self, domain, settings):
|
|
|
|
alternatedomain = settings.get('alternatedomain')
|
|
|
|
self._ensure_unique_configuration_names(domain, alternatedomain)
|
|
self._buffered_configurations[domain] = ((domain, alternatedomain), settings)
|
|
|
|
if alternatedomain:
|
|
self._buffered_configurations[alternatedomain] = ((domain, alternatedomain), settings)
|
|
|
|
return
|
|
|
|
def _ensure_unique_configuration_names(self, domain, alternatedomain):
|
|
|
|
existing_configuration = self._buffered_configurations.get(domain)
|
|
|
|
if existing_configuration:
|
|
existing_domain, existing_alternatedomain = existing_configuration[0]
|
|
assert domain != existing_domain # Configuration system guarantees stanza names are unique within a file
|
|
assert domain == existing_alternatedomain
|
|
message = 'Domain {0} clashes with alternatedomain = {1} in [{2}].'.format(
|
|
domain, existing_alternatedomain, existing_domain)
|
|
self.command.error_exit(ValueError(message), message)
|
|
|
|
if alternatedomain:
|
|
|
|
existing_configuration = self._buffered_configurations.get(alternatedomain)
|
|
|
|
if existing_configuration:
|
|
|
|
existing_domain, existing_alternatedomain = existing_configuration[0]
|
|
|
|
if alternatedomain == domain or alternatedomain == existing_domain:
|
|
message = 'Alternate domain {0} in [{1}] clashes with domain = [{2}].'.format(
|
|
alternatedomain, domain, existing_domain)
|
|
self.command.error_exit(ValueError(message), message)
|
|
|
|
if alternatedomain == existing_alternatedomain:
|
|
message = 'Alternate domain {0} in [{1}] clashes with alternatedomain = {2} in [{3}]'.format(
|
|
alternatedomain, domain, existing_alternatedomain, existing_domain)
|
|
self.command.error_exit(ValueError(message), message)
|
|
|
|
return
|
|
|
|
def _get_tls(self):
|
|
"""
|
|
Gets the TLS configuration of the application.
|
|
|
|
The default TLS configuration is obtained from the sslConfig stanza in server.conf. The application may
|
|
selectively override the defaults by specifying sslConfig settings in the custom sslConfig stanza in
|
|
local/ssl.conf. Settings are retrieved from configuration just once, the first time this method is called.
|
|
|
|
Differences between SA-ldapsearch and Splunk's treatment of `sslConfig` settings
|
|
--------------------------------------------------------------------------------
|
|
SA-ldapsearch is a pure Python application that executes in the runtime that ships with Splunk 6.0+:
|
|
|
|
+------------------+----------------+
|
|
| Splunk version | Python version |
|
|
+==================+================+
|
|
| Splunk 6.0 | Python 2.7.5 |
|
|
+------------------+----------------+
|
|
| Splunk 6.1 | Python 2.7.5 |
|
|
+------------------+----------------+
|
|
| Splunk 6.2 | Python 2.7.8 |
|
|
+------------------+----------------+
|
|
| Splunk 6.3 | Python 2.7.9 |
|
|
+------------------+----------------+
|
|
|
|
SA-ldapsearch depends on the Python :module:`ssl` module for SSL support and some features that Splunk lets
|
|
you configure are missing from this module in versions of Python prior to 2.7.9
|
|
|
|
1. Specific support for TLS 1.1 and TLS 1.2
|
|
Prior to Python 2.7.9:
|
|
You can specify an `sslVersions` value of `tls` to negotiate for TLS 1.0, TLS 1.1, or TLS 1.2. You can
|
|
specifically request TLS 1.0 by specifying an `sslVersions value of `tls1.0`. However, you cannot specify
|
|
any combination of `sslVersions` that includes `tls1.1` or `tls1.2`.
|
|
under Python 2.7.9:
|
|
You can specifically request TLS 1.0, 1.1, or 1.2 by specifying an sslVersions value of 'tls1.0', 'tls1.1',
|
|
or 'tls1.2'
|
|
|
|
2. Password protected private key files used in mutual authentication
|
|
If you want to configure SA-ldapsearch for mutual authentication, you cannot password protect the file
|
|
identified by `sslKeysfile` and the value of `sslKeysfilePassword` must be left blank.
|
|
|
|
Splunk `sslConfig` to ldap3.Tls settings map
|
|
--------------------------------------------
|
|
|
|
Here is the mapping between sslConfig settings and the arguments to :meth:`Tls.__init__`.
|
|
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| Supp. | Tls.__init__ argument | sslConfig setting | Description |
|
|
+=======+================================+=========================+===========================================+
|
|
| no | ca_certs_data[1]_ | #N/A | String containing the PEM- or DER- |
|
|
| | | | formatted certificates of the |
|
|
| | | | certification authorities. |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| yes | ca_certs_file | os.path.join( | File containing the certificates of the |
|
|
| | | caPath, caCertFile) | certification authorities. |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| yes | #N/A | caPath | Path to directory containing caCertFile |
|
|
| | | | and sslKeysfile. |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| no | ca_certs_path[1]_ | #N/A | Path to directory containing the |
|
|
| | | | certification certificates authorities. |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| yes | local_certificate_file | os.path.join( | File with the certificate of the client |
|
|
| | | caPath, sslKeysfile) | |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| yes | local_private_key_file | os.path.join( | File with the private key of the client |
|
|
| | | caPath, sslKeysfile) | |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| no | local_private_key_password[1]_ | sslKeysfilePassword[2]_ | Password required to access |
|
|
| | | | local_certificate_file. |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| yes | validate | sslVerifyServerCert | Specifies if the server certificate must |
|
|
| | | | be validated. |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| yes | valid_names | sslCommonNameToCheck, | List of valid host names. The matching |
|
|
| | | sslCommonNameList, | algorithm used is as outlined in |
|
|
| | | sslAltNameToCheck | `RFC-2818 <http://goo.gl/9nVMfp>`_ and |
|
|
| | | | `RFC-6125 <http://goo.gl/QfWKVn>`_ except |
|
|
| | | | that IP addresses are not currently |
|
|
| | | | supported. See `ssl.match_hostname |
|
|
| | | | <http://goo.gl/IWgnEK>`_ for additional |
|
|
| | | | information. |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
| yes | version | sslVersions | SSL or TLS version to use. |
|
|
+-------+--------------------------------+-------------------------+-------------------------------------------+
|
|
.. [1] Requires `ssl.SSLContext <http://goo.gl/c0aI0s>`_ which is new in Python 2.7.9/3.2.
|
|
.. [2] Password protected SSL Keys Files are unsupported because we do not have access to `ssl.SSLContext
|
|
<http://goo.gl/c0aI0s>`_.
|
|
|
|
:return: TLS configuration of the app.
|
|
:rtype: Tls
|
|
|
|
References
|
|
----------
|
|
1. `Securing Splunk Enterprise: About securing Splunk with SSL <http://goo.gl/7IUFs9>`_
|
|
2. `Splunk Admin Manual: server.conf <http://goo.gl/HpkMYA>`_
|
|
3. `Microsoft Active Directory: Using SSL/TLS <http://goo.gl/3wE2Fa>`_
|
|
4. `Microsoft TechNet: LDAP over SSL (LDAPS) Certificate <http://goo.gl/qnBg41>`_
|
|
5. `RFC-2818: HTTP Over TLS <http://goo.gl/9nVMfp>`_
|
|
6. `RFC-6125: Representation and Verification of Domain-Based Application Service Identity within Internet
|
|
Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS)
|
|
<http://goo.gl/QfWKVn>`_
|
|
7. `ldap3: SSL & TLS <http://goo.gl/3P1vjG>`_
|
|
8. `ssl — TLS/SSL wrapper for socket objects: ssl.match_hostname(cert, hostname) <http://goo.gl/IWgnEK>`_
|
|
|
|
"""
|
|
if Configuration._tls:
|
|
return Configuration._tls
|
|
|
|
command = self.command
|
|
|
|
configuration_file = command.service.confs[str('ssl')]
|
|
|
|
try:
|
|
app_settings = configuration_file['sslConfig']
|
|
except KeyError as error:
|
|
message = 'Cannot use SSL because the sslConfig stanza is missing from ssl.conf.'
|
|
command.error_exit(error, message)
|
|
return
|
|
|
|
configuration_file = command.service.confs[str('server')]
|
|
|
|
try:
|
|
server_settings = configuration_file['sslConfig']
|
|
except KeyError as error:
|
|
message = 'Cannot use SSL because the sslConfig stanza is missing from server.conf.'
|
|
command.error_exit(error, message)
|
|
return
|
|
|
|
def get_ssl_configuration_setting(setting_name, default=None, require=None, validate=None):
|
|
default = self._get_value(server_settings, setting_name, default=default, validate=validate)
|
|
value = self._get_value(app_settings, setting_name, default=default, require=require, validate=validate)
|
|
return value
|
|
|
|
ca_path = os.path.expandvars(get_ssl_configuration_setting('caPath', default=''))
|
|
ca_cert_file = get_ssl_configuration_setting('caCertFile', default='')
|
|
|
|
if ca_path:
|
|
ca_cert_file = os.path.join(ca_path, ca_cert_file) if ca_cert_file else None
|
|
|
|
ssl_verify_server_cert = get_ssl_configuration_setting('sslVerifyServerCert', default=False, validate=Boolean())
|
|
|
|
ssl_versions = get_ssl_configuration_setting('sslVersions', default='', validate=List())
|
|
protocol_set = set()
|
|
|
|
for ssl_version in ssl_versions:
|
|
|
|
if ssl_version[0] == '-':
|
|
operation = protocol_set.difference_update
|
|
member = ssl_version[1:]
|
|
else:
|
|
operation = protocol_set.update
|
|
member = ssl_version
|
|
|
|
if member not in ('*', 'ssl2', 'ssl3', 'tls', 'tls1.0', 'tls1.1', 'tls1.2'):
|
|
message = 'SSL configuration issue: sslVersions="{0}" is unrecognized.'.format(ssl_versions)
|
|
command.error_exit(ValueError(message), message)
|
|
return
|
|
|
|
if member == '*':
|
|
member = ('ssl2', 'ssl3', 'tls1.0', 'tls1.1', 'tls1.2')
|
|
elif member == 'tls':
|
|
member = ('tls1.0', 'tls1.1', 'tls1.2')
|
|
else:
|
|
member = (member,)
|
|
|
|
operation(member)
|
|
|
|
if use_ssl_context: # python 2.7.9
|
|
tls = self.get_tls_python_2_7_9_or_later(command, protocol_set, ssl_versions, ca_cert_file, ssl_verify_server_cert)
|
|
else: #prior to python 2.7.9
|
|
tls = self.get_tls_python_2_7_8_or_earlier(command, protocol_set, ssl_versions, ca_cert_file, ssl_verify_server_cert)
|
|
|
|
Configuration._tls = tls
|
|
return tls
|
|
|
|
def get_tls_python_2_7_9_or_later(self, command, protocol_set, ssl_versions, ca_cert_file, ssl_verify_server_cert):
|
|
|
|
ssl_no_v2 = False
|
|
ssl_no_v3 = False
|
|
|
|
if not protocol_set.symmetric_difference(('ssl3', 'tls1.0', 'tls1.1', 'tls1.2')):
|
|
version = ssl.PROTOCOL_SSLv23
|
|
ssl_no_v2 = True
|
|
elif not protocol_set.symmetric_difference(('tls1.0', 'tls1.1', 'tls1.2')):
|
|
ssl_no_v2 = True
|
|
ssl_no_v3 = True
|
|
version = ssl.PROTOCOL_SSLv23
|
|
elif not protocol_set.symmetric_difference(('ssl3',)):
|
|
version = ssl.PROTOCOL_SSLv3
|
|
elif not protocol_set.symmetric_difference(('tls1.0',)):
|
|
version = ssl.PROTOCOL_TLSv1
|
|
elif not protocol_set.symmetric_difference(('tls1.1',)):
|
|
version = ssl.PROTOCOL_TLSv1_1
|
|
elif not protocol_set.symmetric_difference(('tls1.2',)):
|
|
version = ssl.PROTOCOL_TLSv1_2
|
|
else:
|
|
message = 'SSL configuration issue: sslVersions="{0}" is an invalid combination.'.format(ssl_versions)
|
|
command.error_exit(ValueError(message), message)
|
|
return
|
|
|
|
try:
|
|
tls = Tls(
|
|
ca_certs_file=ca_cert_file if ca_cert_file else None,
|
|
validate=ssl.CERT_REQUIRED if ssl_verify_server_cert else ssl.CERT_NONE,
|
|
version=version)
|
|
except core.exceptions.LDAPSSLConfigurationError as error:
|
|
message = 'SSL configuration issue: {0}'.format(error)
|
|
command.error_exit(error, message)
|
|
return
|
|
|
|
if ssl_no_v2==False or ssl_no_v3==False:
|
|
command.logger.warning(
|
|
'POODLE Vulnerable: "sslVersions = %s". Upgrade Splunk and disable ssl2 and ssl3 to mitigate this '
|
|
'issue. Consider using "sslVersions = tls". See "Splunk response to SSLv3 POODLE vulnerability '
|
|
'(CVE-2014-3566)" at http://www.splunk.com/view/SP-CAAANKE for additional information.', ssl_versions)
|
|
pass
|
|
|
|
return tls
|
|
|
|
def get_tls_python_2_7_8_or_earlier(self, command, protocol_set, ssl_versions, ca_cert_file, ssl_verify_server_cert):
|
|
|
|
if hasattr(ssl, 'PROTOCOL_SSLv23_NO23'):
|
|
is_poodle_vulnerable_splunk = False
|
|
else:
|
|
is_poodle_vulnerable_splunk = True
|
|
ssl.PROTOCOL_SSLv23_NO23 = 5 # intentionally provokes an error that's treated as a configuration error later
|
|
|
|
if not protocol_set.symmetric_difference(('ssl2', 'ssl3', 'tls1.0', 'tls1.1', 'tls1.2')):
|
|
version = ssl.PROTOCOL_SSLv23
|
|
elif not protocol_set.symmetric_difference(('ssl3', 'tls1.0', 'tls1.1', 'tls1.2')):
|
|
version = ssl.PROTOCOL_SSLv23_NO2
|
|
elif not protocol_set.symmetric_difference(('tls1.0', 'tls1.1', 'tls1.2')):
|
|
version = ssl.PROTOCOL_SSLv23_NO23
|
|
elif not protocol_set.symmetric_difference(('ssl2',)):
|
|
version = ssl.PROTOCOL_SSLv2
|
|
elif not protocol_set.symmetric_difference(('ssl3',)):
|
|
version = ssl.PROTOCOL_SSLv3
|
|
elif not protocol_set.symmetric_difference(('tls1.0',)):
|
|
version = ssl.PROTOCOL_TLSv1
|
|
else:
|
|
message = 'SSL configuration issue: sslVersions="{0}" is an invalid combination.'.format(ssl_versions)
|
|
command.error_exit(ValueError(message), message)
|
|
return
|
|
|
|
try:
|
|
tls = Tls(
|
|
ca_certs_file=ca_cert_file if ca_cert_file else None,
|
|
validate=ssl.CERT_REQUIRED if ssl_verify_server_cert else ssl.CERT_NONE,
|
|
version=version)
|
|
except core.exceptions.LDAPSSLConfigurationError as error:
|
|
message = 'SSL configuration issue: {0}'.format(error)
|
|
command.error_exit(error, message)
|
|
return
|
|
|
|
if is_poodle_vulnerable_splunk:
|
|
command.logger.warning(
|
|
'POODLE Vulnerable: "sslVersions = %s". Upgrade Splunk and disable ssl2 and ssl3 to mitigate this '
|
|
'issue. Consider using "sslVersions = tls". See "Splunk response to SSLv3 POODLE vulnerability '
|
|
'(CVE-2014-3566)" at http://www.splunk.com/view/SP-CAAANKE for additional information.', ssl_versions)
|
|
pass
|
|
elif version not in (ssl.PROTOCOL_SSLv23_NO23, ssl.PROTOCOL_TLSv1):
|
|
command.logger.warning(
|
|
'POODLE Vulnerable: "sslVersions = %s". Disable ssl2 and ssl3 to mitigate this issue. Consider using '
|
|
'"sslVersions = tls". See "Splunk response to SSLv3 POODLE vulnerability (CVE-2014-3566)" at '
|
|
'http://www.splunk.com/view/SP-CAAANKE for additional information.', ssl_versions)
|
|
pass
|
|
|
|
return tls
|
|
|
|
def _get_value(self, settings, setting_name, require=None, default=None, validate=None):
|
|
# Because the Splunk Python SDK does not respect the semantics of dict type that it inherits from we cannot
|
|
# use settings.get(setting_name, default_value). We must resort to our own helper instead.
|
|
command = self.command
|
|
try:
|
|
value = settings[setting_name]
|
|
if value is None or len(value) == 0:
|
|
raise KeyError
|
|
if validate is not None:
|
|
try:
|
|
value = validate(value)
|
|
except ValueError as e:
|
|
message = 'Illegal value for %s in ldap/%s: %s' % (setting_name, command.domain, e)
|
|
self.command.logger.error(message)
|
|
self.command.write_error(message)
|
|
sys.exit(1)
|
|
except (AttributeError, KeyError):
|
|
if require:
|
|
message = 'Missing required value for %s in ldap/%s.' % (setting_name, command.domain)
|
|
self.command.logger.error(message)
|
|
self.command.write_error(message)
|
|
sys.exit(1)
|
|
value = default
|
|
|
|
return value
|
|
|
|
def _read_all_configurations(self):
|
|
|
|
self._buffered_configurations = OrderedDict()
|
|
settings = self._read_default_configuration()
|
|
self._add_buffered_configuration('default', settings)
|
|
|
|
for configuration_stanza in self.command.service.confs[str('ldap')].iter():
|
|
domain = configuration_stanza.name
|
|
settings = configuration_stanza.content
|
|
self._add_buffered_configuration(domain, settings)
|
|
|
|
def _read_configuration(self):
|
|
|
|
command = self.command
|
|
|
|
if command.domain == 'default':
|
|
settings = self._read_default_configuration()
|
|
else:
|
|
configuration_file = command.service.confs[str('ldap')]
|
|
|
|
try:
|
|
stanza = configuration_file[command.domain]
|
|
except KeyError as error:
|
|
self._read_all_configurations()
|
|
try:
|
|
self.select(command.domain)
|
|
return
|
|
except Exception as error:
|
|
message = 'Cannot find the configuration stanza for domain={0} in ldap.conf.'.format(command.domain)
|
|
command.error_exit(error, message)
|
|
return
|
|
settings = stanza.content
|
|
self._reset_fields(command.domain, settings)
|
|
return
|
|
|
|
def _read_default_configuration(self):
|
|
|
|
command = self.command
|
|
service = command.service
|
|
namespace = service.namespace
|
|
|
|
try:
|
|
response = service.get('properties/ldap/default', namespace.owner, namespace.app, namespace.sharing)
|
|
except HTTPError as error:
|
|
command.error_exit(error, 'The default configuration stanza for ldap.conf is missing: ' + str(error))
|
|
return
|
|
|
|
body = response.body.read()
|
|
feed = data.load(body)
|
|
entries = feed['feed'].get('entry', ())
|
|
|
|
if isinstance(entries, data.Record):
|
|
entries = entries,
|
|
|
|
settings = {entry['title']: entry['content'].get('$text', '') for entry in entries}
|
|
return settings
|
|
|
|
def _reset_fields(self, domain, settings):
|
|
|
|
self.settings = settings
|
|
self.domain = domain
|
|
command = self.command
|
|
|
|
self.alternatedomain = self._get_value(settings, 'alternatedomain', require=True)
|
|
self.basedn = self._get_value(settings, 'basedn', require=True)
|
|
host = self._get_value(settings, 'server', require=True, validate=List())
|
|
use_ssl = self._get_value(settings, 'ssl', default=False, validate=Boolean())
|
|
port = self._get_value(settings, 'port', default=636 if use_ssl else 389, validate=Integer(0, 65535))
|
|
|
|
binddn = self._get_value(settings, 'binddn')
|
|
|
|
storage_passwords = command.service.storage_passwords
|
|
|
|
if domain == 'default':
|
|
storage_password_names = 'SA-ldapsearch:default:',
|
|
else:
|
|
domain = reduce(lambda v, c: v + ('\\' + c if c in '\\:' else c), self.domain, 'SA-ldapsearch:') + ':'
|
|
storage_password_names = domain, 'SA-ldapsearch:default:'
|
|
|
|
password = None
|
|
|
|
for ssl_version in storage_password_names:
|
|
try:
|
|
storage_password = storage_passwords[ssl_version]
|
|
password = storage_password.clear_password
|
|
break
|
|
except HTTPError as e:
|
|
if e.status != 403:
|
|
raise
|
|
command.logger.debug('Storage password "%s" access denied: %s', ssl_version, e)
|
|
break
|
|
except KeyError as e:
|
|
command.logger.debug('Storage password "%s" not found', ssl_version)
|
|
|
|
if password is None:
|
|
password = self._get_value(settings, 'password', default='')
|
|
if password.startswith('{64}'):
|
|
password = b64decode(password[4:])
|
|
pass
|
|
|
|
self.decode = self._get_value(settings, 'decode', default=True, validate=Boolean())
|
|
self.paged_size = int(self._get_value(settings, 'paged_size', default=1000, validate=Integer(1, 65535)))
|
|
|
|
for option in itervalues(command.options): # override settings with command option values, if they're present
|
|
if not option.is_set:
|
|
if not option.name == 'domain':
|
|
if hasattr(self, option.name):
|
|
option.value = getattr(self, option.name)
|
|
|
|
decode = command.decode if hasattr(command, 'decode') else self.decode
|
|
formatter = app.formatting_extensions if decode else None
|
|
tls = self._get_tls() if use_ssl else None
|
|
|
|
def create_server(hostname):
|
|
return Server(
|
|
hostname, int(port), use_ssl, formatter=formatter, get_info=ALL,
|
|
allowed_referral_hosts=[('*', True)], tls=tls)
|
|
|
|
self.server = create_server(host[0]) if len(host) == 1 else [create_server(h) for h in host]
|
|
self.credentials = Configuration.Credentials(None, binddn, password, None)
|
|
|
|
command.logger.debug('Configuration = %s', self)
|
|
|
|
# endregion
|