%PDF- %PDF-
| Direktori : /proc/self/root/lib/python3/dist-packages/ldap3/core/ |
| Current File : //proc/self/root/lib/python3/dist-packages/ldap3/core/server.py |
"""
"""
# Created on 2014.05.31
#
# Author: Giovanni Cannata
#
# Copyright 2014 - 2020 Giovanni Cannata
#
# This file is part of ldap3.
#
# ldap3 is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ldap3 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ldap3 in the COPYING and COPYING.LESSER files.
# If not, see <http://www.gnu.org/licenses/>.
import socket
from threading import Lock
from datetime import datetime, MINYEAR
from .. import DSA, SCHEMA, ALL, BASE, get_config_parameter, OFFLINE_EDIR_8_8_8, OFFLINE_EDIR_9_1_4, OFFLINE_AD_2012_R2, OFFLINE_SLAPD_2_4, OFFLINE_DS389_1_3_3, SEQUENCE_TYPES, IP_SYSTEM_DEFAULT, IP_V4_ONLY, IP_V6_ONLY, IP_V4_PREFERRED, IP_V6_PREFERRED, STRING_TYPES
from .exceptions import LDAPInvalidServerError, LDAPDefinitionError, LDAPInvalidPortError, LDAPInvalidTlsSpecificationError, LDAPSocketOpenError, LDAPInfoError
from ..protocol.formatters.standard import format_attribute_values
from ..protocol.rfc4511 import LDAP_MAX_INT
from ..protocol.rfc4512 import SchemaInfo, DsaInfo
from .tls import Tls
from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, NETWORK
from ..utils.conv import to_unicode
from ..utils.port_validators import check_port, check_port_and_port_list
try:
from urllib.parse import unquote # Python 3
except ImportError:
from urllib import unquote # Python 2
try: # try to discover if unix sockets are available for LDAP over IPC (ldapi:// scheme)
# noinspection PyUnresolvedReferences
from socket import AF_UNIX
unix_socket_available = True
except ImportError:
unix_socket_available = False
class Server(object):
"""
LDAP Server definition class
Allowed_referral_hosts can be None (default), or a list of tuples of
allowed servers ip address or names to contact while redirecting
search to referrals.
The second element of the tuple is a boolean to indicate if
authentication to that server is allowed; if False only anonymous
bind will be used.
Per RFC 4516. Use [('*', False)] to allow any host with anonymous
bind, use [('*', True)] to allow any host with same authentication of
Server.
"""
_message_counter = 0
_message_id_lock = Lock() # global lock for message_id shared by all Server objects
def __init__(self,
host,
port=None,
use_ssl=False,
allowed_referral_hosts=None,
get_info=SCHEMA,
tls=None,
formatter=None,
connect_timeout=None,
mode=IP_V6_PREFERRED,
validator=None):
self.ipc = False
url_given = False
host = host.strip()
if host.lower().startswith('ldap://'):
self.host = host[7:]
use_ssl = False
url_given = True
elif host.lower().startswith('ldaps://'):
self.host = host[8:]
use_ssl = True
url_given = True
elif host.lower().startswith('ldapi://') and unix_socket_available:
self.ipc = True
use_ssl = False
url_given = True
elif host.lower().startswith('ldapi://') and not unix_socket_available:
raise LDAPSocketOpenError('LDAP over IPC not available - UNIX sockets non present')
else:
self.host = host
if self.ipc:
if str is bytes: # Python 2
self.host = unquote(host[7:]).decode('utf-8')
else: # Python 3
self.host = unquote(host[7:]) # encoding defaults to utf-8 in python3
self.port = None
elif ':' in self.host and self.host.count(':') == 1:
hostname, _, hostport = self.host.partition(':')
try:
port = int(hostport) or port
except ValueError:
if log_enabled(ERROR):
log(ERROR, 'port <%s> must be an integer', port)
raise LDAPInvalidPortError('port must be an integer')
self.host = hostname
elif url_given and self.host.startswith('['):
hostname, sep, hostport = self.host[1:].partition(']')
if sep != ']' or not self._is_ipv6(hostname):
if log_enabled(ERROR):
log(ERROR, 'invalid IPv6 server address for <%s>', self.host)
raise LDAPInvalidServerError()
if len(hostport):
if not hostport.startswith(':'):
if log_enabled(ERROR):
log(ERROR, 'invalid URL in server name for <%s>', self.host)
raise LDAPInvalidServerError('invalid URL in server name')
if not hostport[1:].isdecimal():
if log_enabled(ERROR):
log(ERROR, 'port must be an integer for <%s>', self.host)
raise LDAPInvalidPortError('port must be an integer')
port = int(hostport[1:])
self.host = hostname
elif not url_given and self._is_ipv6(self.host):
pass
elif self.host.count(':') > 1:
if log_enabled(ERROR):
log(ERROR, 'invalid server address for <%s>', self.host)
raise LDAPInvalidServerError()
if not self.ipc:
self.host.rstrip('/')
if not use_ssl and not port:
port = 389
elif use_ssl and not port:
port = 636
port_err = check_port(port)
if port_err:
if log_enabled(ERROR):
log(ERROR, port_err)
raise LDAPInvalidPortError(port_err)
self.port = port
if allowed_referral_hosts is None: # defaults to any server with authentication
allowed_referral_hosts = [('*', True)]
if isinstance(allowed_referral_hosts, SEQUENCE_TYPES):
self.allowed_referral_hosts = []
for referral_host in allowed_referral_hosts:
if isinstance(referral_host, tuple):
if isinstance(referral_host[1], bool):
self.allowed_referral_hosts.append(referral_host)
elif isinstance(allowed_referral_hosts, tuple):
if isinstance(allowed_referral_hosts[1], bool):
self.allowed_referral_hosts = [allowed_referral_hosts]
else:
self.allowed_referral_hosts = []
self.ssl = True if use_ssl else False
if tls and not isinstance(tls, Tls):
if log_enabled(ERROR):
log(ERROR, 'invalid tls specification: <%s>', tls)
raise LDAPInvalidTlsSpecificationError('invalid Tls object')
self.tls = Tls() if self.ssl and not tls else tls
if not self.ipc:
if self._is_ipv6(self.host):
self.name = ('ldaps' if self.ssl else 'ldap') + '://[' + self.host + ']:' + str(self.port)
else:
self.name = ('ldaps' if self.ssl else 'ldap') + '://' + self.host + ':' + str(self.port)
else:
self.name = host
self.get_info = get_info
self._dsa_info = None
self._schema_info = None
self.dit_lock = Lock()
self.custom_formatter = formatter
self.custom_validator = validator
self._address_info = [] # property self.address_info resolved at open time (or when check_availability is called)
self._address_info_resolved_time = datetime(MINYEAR, 1, 1) # smallest date ever
self.current_address = None
self.connect_timeout = connect_timeout
self.mode = mode
self.get_info_from_server(None) # load offline schema if needed
if log_enabled(BASIC):
log(BASIC, 'instantiated Server: <%r>', self)
@staticmethod
def _is_ipv6(host):
try:
socket.inet_pton(socket.AF_INET6, host)
except (socket.error, AttributeError, ValueError):
return False
return True
def __str__(self):
if self.host:
s = self.name + (' - ssl' if self.ssl else ' - cleartext') + (' - unix socket' if self.ipc else '')
else:
s = object.__str__(self)
return s
def __repr__(self):
r = 'Server(host={0.host!r}, port={0.port!r}, use_ssl={0.ssl!r}'.format(self)
r += '' if not self.allowed_referral_hosts else ', allowed_referral_hosts={0.allowed_referral_hosts!r}'.format(self)
r += '' if self.tls is None else ', tls={0.tls!r}'.format(self)
r += '' if not self.get_info else ', get_info={0.get_info!r}'.format(self)
r += '' if not self.connect_timeout else ', connect_timeout={0.connect_timeout!r}'.format(self)
r += '' if not self.mode else ', mode={0.mode!r}'.format(self)
r += ')'
return r
@property
def address_info(self):
conf_refresh_interval = get_config_parameter('ADDRESS_INFO_REFRESH_TIME')
if not self._address_info or (datetime.now() - self._address_info_resolved_time).seconds > conf_refresh_interval:
# converts addresses tuple to list and adds a 6th parameter for availability (None = not checked, True = available, False=not available) and a 7th parameter for the checking time
addresses = None
try:
if self.ipc:
addresses = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, None, self.host, None)]
else:
if self.mode == IP_V4_ONLY:
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
elif self.mode == IP_V6_ONLY:
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
else:
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
except (socket.gaierror, AttributeError):
pass
if not addresses: # if addresses not found or raised an exception (for example for bad flags) tries again without flags
try:
if self.mode == IP_V4_ONLY:
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
elif self.mode == IP_V6_ONLY:
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)
else:
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP)
except socket.gaierror:
pass
if addresses:
self._address_info = [list(address) + [None, None] for address in addresses]
self._address_info_resolved_time = datetime.now()
else:
self._address_info = []
self._address_info_resolved_time = datetime(MINYEAR, 1, 1) # smallest date
if log_enabled(BASIC):
for address in self._address_info:
log(BASIC, 'address for <%s> resolved as <%r>', self, address[:-2])
return self._address_info
def update_availability(self, address, available):
cont = 0
while cont < len(self._address_info):
if self.address_info[cont] == address:
self._address_info[cont][5] = True if available else False
self._address_info[cont][6] = datetime.now()
break
cont += 1
def reset_availability(self):
for address in self._address_info:
address[5] = None
address[6] = None
def check_availability(self, source_address=None, source_port=None, source_port_list=None):
"""
Tries to open, connect and close a socket to specified address and port to check availability.
Timeout in seconds is specified in CHECK_AVAILABITY_TIMEOUT if not specified in
the Server object.
If specified, use a specific address, port, or list of possible ports, when attempting to check availability.
NOTE: This will only consider multiple ports from the source port list if the first ones we try to bind to are
already in use. This will not attempt using different ports in the list if the server is unavailable,
as that could result in the runtime of check_availability significantly exceeding the connection timeout.
"""
source_port_err = check_port_and_port_list(source_port, source_port_list)
if source_port_err:
if log_enabled(ERROR):
log(ERROR, source_port_err)
raise LDAPInvalidPortError(source_port_err)
# using an empty string to bind a socket means "use the default as if this wasn't provided" because socket
# binding requires that you pass something for the ip if you want to pass a specific port
bind_address = source_address if source_address is not None else ''
# using 0 as the source port to bind a socket means "use the default behavior of picking a random port from
# all ports as if this wasn't provided" because socket binding requires that you pass something for the port
# if you want to pass a specific ip
candidate_bind_ports = [0]
# if we have either a source port or source port list, convert that into our candidate list
if source_port is not None:
candidate_bind_ports = [source_port]
elif source_port_list is not None:
candidate_bind_ports = source_port_list[:]
conf_availability_timeout = get_config_parameter('CHECK_AVAILABILITY_TIMEOUT')
available = False
self.reset_availability()
for address in self.candidate_addresses():
available = True
try:
temp_socket = socket.socket(*address[:3])
# Go through our candidate bind ports and try to bind our socket to our source address with them.
# if no source address or ports were specified, this will have the same success/fail result as if we
# tried to connect to the remote server without binding locally first.
# This is actually a little bit better, as it lets us distinguish the case of "issue binding the socket
# locally" from "remote server is unavailable" with more clarity, though this will only really be an
# issue when no source address/port is specified if the system checking server availability is running
# as a very unprivileged user.
last_bind_exc = None
socket_bind_succeeded = False
for bind_port in candidate_bind_ports:
try:
temp_socket.bind((bind_address, bind_port))
socket_bind_succeeded = True
break
except Exception as bind_ex:
last_bind_exc = bind_ex
if log_enabled(NETWORK):
log(NETWORK, 'Unable to bind to local address <%s> with source port <%s> due to <%s>',
bind_address, bind_port, bind_ex)
if not socket_bind_succeeded:
if log_enabled(ERROR):
log(ERROR, 'Unable to locally bind to local address <%s> with any of the source ports <%s> due to <%s>',
bind_address, candidate_bind_ports, last_bind_exc)
raise LDAPSocketOpenError('Unable to bind socket locally to address {} with any of the source ports {} due to {}'
.format(bind_address, candidate_bind_ports, last_bind_exc))
if self.connect_timeout:
temp_socket.settimeout(self.connect_timeout)
else:
temp_socket.settimeout(conf_availability_timeout) # set timeout for checking availability to default
try:
temp_socket.connect(address[4])
except socket.error:
available = False
finally:
try:
temp_socket.shutdown(socket.SHUT_RDWR)
except socket.error:
available = False
finally:
temp_socket.close()
except socket.gaierror:
available = False
if available:
if log_enabled(BASIC):
log(BASIC, 'server <%s> available at <%r>', self, address)
self.update_availability(address, True)
break # if an available address is found exits immediately
else:
self.update_availability(address, False)
if log_enabled(ERROR):
log(ERROR, 'server <%s> not available at <%r>', self, address)
return available
@staticmethod
def next_message_id():
"""
LDAP messageId is unique for all connections to same server
"""
with Server._message_id_lock:
Server._message_counter += 1
if Server._message_counter >= LDAP_MAX_INT:
Server._message_counter = 1
if log_enabled(PROTOCOL):
log(PROTOCOL, 'new message id <%d> generated', Server._message_counter)
return Server._message_counter
def _get_dsa_info(self, connection):
"""
Retrieve DSE operational attribute as per RFC4512 (5.1).
"""
if connection.strategy.no_real_dsa: # do not try for mock strategies
return
if not connection.strategy.pooled: # in pooled strategies get_dsa_info is performed by the worker threads
result = connection.search(search_base='',
search_filter='(objectClass=*)',
search_scope=BASE,
attributes=['altServer', # requests specific dsa info attributes
'namingContexts',
'supportedControl',
'supportedExtension',
'supportedFeatures',
'supportedCapabilities',
'supportedLdapVersion',
'supportedSASLMechanisms',
'vendorName',
'vendorVersion',
'subschemaSubentry',
'*',
'+'], # requests all remaining attributes (other),
get_operational_attributes=True)
if connection.strategy.thread_safe:
status, result, response, _ = result
else:
status = result
result = connection.result
response = connection.response
with self.dit_lock:
if connection.strategy.sync: # sync request
self._dsa_info = DsaInfo(response[0]['attributes'], response[0]['raw_attributes']) if status else self._dsa_info
elif status: # asynchronous request, must check if attributes in response
results, _ = connection.get_response(status)
if len(results) == 1 and 'attributes' in results[0] and 'raw_attributes' in results[0]:
self._dsa_info = DsaInfo(results[0]['attributes'], results[0]['raw_attributes'])
if log_enabled(BASIC):
log(BASIC, 'DSA info read for <%s> via <%s>', self, connection)
def _get_schema_info(self, connection, entry=''):
"""
Retrieve schema from subschemaSubentry DSE attribute, per RFC
4512 (4.4 and 5.1); entry = '' means DSE.
"""
if connection.strategy.no_real_dsa: # do not try for mock strategies
return
schema_entry = None
if self._dsa_info and entry == '': # subschemaSubentry already present in dsaInfo
if isinstance(self._dsa_info.schema_entry, SEQUENCE_TYPES):
schema_entry = self._dsa_info.schema_entry[0] if self._dsa_info.schema_entry else None
else:
schema_entry = self._dsa_info.schema_entry if self._dsa_info.schema_entry else None
else:
result = connection.search(entry, '(objectClass=*)', BASE, attributes=['subschemaSubentry'], get_operational_attributes=True)
if connection.strategy.thread_safe:
status, result, response, _ = result
else:
status = result
result = connection.result
response = connection.response
if connection.strategy.sync: # sync request
if status and 'subschemaSubentry' in response[0]['raw_attributes']:
if len(response[0]['raw_attributes']['subschemaSubentry']) > 0:
schema_entry = response[0]['raw_attributes']['subschemaSubentry'][0]
else: # asynchronous request, must check if subschemaSubentry in attributes
results, _ = connection.get_response(status)
if len(results) == 1 and 'raw_attributes' in results[0] and 'subschemaSubentry' in results[0]['attributes']:
if len(results[0]['raw_attributes']['subschemaSubentry']) > 0:
schema_entry = results[0]['raw_attributes']['subschemaSubentry'][0]
if schema_entry and not connection.strategy.pooled: # in pooled strategies get_schema_info is performed by the worker threads
if isinstance(schema_entry, bytes) and str is not bytes: # Python 3
schema_entry = to_unicode(schema_entry, from_server=True)
result = connection.search(schema_entry,
search_filter='(objectClass=subschema)',
search_scope=BASE,
attributes=['objectClasses', # requests specific subschema attributes
'attributeTypes',
'ldapSyntaxes',
'matchingRules',
'matchingRuleUse',
'dITContentRules',
'dITStructureRules',
'nameForms',
'createTimestamp',
'modifyTimestamp',
'*'], # requests all remaining attributes (other)
get_operational_attributes=True
)
if connection.strategy.thread_safe:
status, result, response, _ = result
else:
status = result
result = connection.result
response = connection.response
with self.dit_lock:
self._schema_info = None
if status:
if connection.strategy.sync: # sync request
self._schema_info = SchemaInfo(schema_entry, response[0]['attributes'], response[0]['raw_attributes'])
else: # asynchronous request, must check if attributes in response
results, result = connection.get_response(status)
if len(results) == 1 and 'attributes' in results[0] and 'raw_attributes' in results[0]:
self._schema_info = SchemaInfo(schema_entry, results[0]['attributes'], results[0]['raw_attributes'])
if self._schema_info and not self._schema_info.is_valid(): # flaky servers can return an empty schema, checks if it is so and set schema to None
self._schema_info = None
if self._schema_info: # if schema is valid tries to apply formatter to the "other" dict with raw values for schema and info
for attribute in self._schema_info.other:
self._schema_info.other[attribute] = format_attribute_values(self._schema_info, attribute, self._schema_info.raw[attribute], self.custom_formatter)
if self._dsa_info: # try to apply formatter to the "other" dict with dsa info raw values
for attribute in self._dsa_info.other:
self._dsa_info.other[attribute] = format_attribute_values(self._schema_info, attribute, self._dsa_info.raw[attribute], self.custom_formatter)
if log_enabled(BASIC):
log(BASIC, 'schema read for <%s> via <%s>', self, connection)
def get_info_from_server(self, connection):
"""
reads info from DSE and from subschema
"""
if connection and not connection.closed:
if self.get_info in [DSA, ALL]:
self._get_dsa_info(connection)
if self.get_info in [SCHEMA, ALL]:
self._get_schema_info(connection)
elif self.get_info == OFFLINE_EDIR_8_8_8:
from ..protocol.schemas.edir888 import edir_8_8_8_schema, edir_8_8_8_dsa_info
self.attach_schema_info(SchemaInfo.from_json(edir_8_8_8_schema))
self.attach_dsa_info(DsaInfo.from_json(edir_8_8_8_dsa_info))
elif self.get_info == OFFLINE_EDIR_9_1_4:
from ..protocol.schemas.edir914 import edir_9_1_4_schema, edir_9_1_4_dsa_info
self.attach_schema_info(SchemaInfo.from_json(edir_9_1_4_schema))
self.attach_dsa_info(DsaInfo.from_json(edir_9_1_4_dsa_info))
elif self.get_info == OFFLINE_AD_2012_R2:
from ..protocol.schemas.ad2012R2 import ad_2012_r2_schema, ad_2012_r2_dsa_info
self.attach_schema_info(SchemaInfo.from_json(ad_2012_r2_schema))
self.attach_dsa_info(DsaInfo.from_json(ad_2012_r2_dsa_info))
elif self.get_info == OFFLINE_SLAPD_2_4:
from ..protocol.schemas.slapd24 import slapd_2_4_schema, slapd_2_4_dsa_info
self.attach_schema_info(SchemaInfo.from_json(slapd_2_4_schema))
self.attach_dsa_info(DsaInfo.from_json(slapd_2_4_dsa_info))
elif self.get_info == OFFLINE_DS389_1_3_3:
from ..protocol.schemas.ds389 import ds389_1_3_3_schema, ds389_1_3_3_dsa_info
self.attach_schema_info(SchemaInfo.from_json(ds389_1_3_3_schema))
self.attach_dsa_info(DsaInfo.from_json(ds389_1_3_3_dsa_info))
def attach_dsa_info(self, dsa_info=None):
if isinstance(dsa_info, DsaInfo):
self._dsa_info = dsa_info
if log_enabled(BASIC):
log(BASIC, 'attached DSA info to Server <%s>', self)
def attach_schema_info(self, dsa_schema=None):
if isinstance(dsa_schema, SchemaInfo):
self._schema_info = dsa_schema
if log_enabled(BASIC):
log(BASIC, 'attached schema info to Server <%s>', self)
@property
def info(self):
return self._dsa_info
@property
def schema(self):
return self._schema_info
@staticmethod
def from_definition(host, dsa_info, dsa_schema, port=None, use_ssl=False, formatter=None, validator=None):
"""
Define a dummy server with preloaded schema and info
:param host: host name
:param dsa_info: DsaInfo preloaded object or a json formatted string or a file name
:param dsa_schema: SchemaInfo preloaded object or a json formatted string or a file name
:param port: fake port
:param use_ssl: use_ssl
:param formatter: custom formatters
:return: Server object
"""
if isinstance(host, SEQUENCE_TYPES):
dummy = Server(host=host[0], port=port, use_ssl=use_ssl, formatter=formatter, validator=validator, get_info=ALL) # for ServerPool object
else:
dummy = Server(host=host, port=port, use_ssl=use_ssl, formatter=formatter, validator=validator, get_info=ALL)
if isinstance(dsa_info, DsaInfo):
dummy._dsa_info = dsa_info
elif isinstance(dsa_info, STRING_TYPES):
try:
dummy._dsa_info = DsaInfo.from_json(dsa_info) # tries to use dsa_info as a json configuration string
except Exception:
dummy._dsa_info = DsaInfo.from_file(dsa_info) # tries to use dsa_info as a file name
if not dummy.info:
if log_enabled(ERROR):
log(ERROR, 'invalid DSA info for %s', host)
raise LDAPDefinitionError('invalid dsa info')
if isinstance(dsa_schema, SchemaInfo):
dummy._schema_info = dsa_schema
elif isinstance(dsa_schema, STRING_TYPES):
try:
dummy._schema_info = SchemaInfo.from_json(dsa_schema)
except Exception:
dummy._schema_info = SchemaInfo.from_file(dsa_schema)
if not dummy.schema:
if log_enabled(ERROR):
log(ERROR, 'invalid schema info for %s', host)
raise LDAPDefinitionError('invalid schema info')
if log_enabled(BASIC):
log(BASIC, 'created server <%s> from definition', dummy)
return dummy
def candidate_addresses(self):
conf_reset_availability_timeout = get_config_parameter('RESET_AVAILABILITY_TIMEOUT')
if self.ipc:
candidates = self.address_info
if log_enabled(BASIC):
log(BASIC, 'candidate address for <%s>: <%s> with mode UNIX_SOCKET', self, self.name)
else:
# checks reset availability timeout
for address in self.address_info:
if address[6] and ((datetime.now() - address[6]).seconds > conf_reset_availability_timeout):
address[5] = None
address[6] = None
# selects server address based on server mode and availability (in address[5])
addresses = self.address_info[:] # copy to avoid refreshing while searching candidates
candidates = []
if addresses:
if self.mode == IP_SYSTEM_DEFAULT:
candidates.append(addresses[0])
elif self.mode == IP_V4_ONLY:
candidates = [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
elif self.mode == IP_V6_ONLY:
candidates = [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
elif self.mode == IP_V4_PREFERRED:
candidates = [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
candidates += [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
elif self.mode == IP_V6_PREFERRED:
candidates = [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
candidates += [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
else:
if log_enabled(ERROR):
log(ERROR, 'invalid server mode for <%s>', self)
raise LDAPInvalidServerError('invalid server mode')
if log_enabled(BASIC):
for candidate in candidates:
log(BASIC, 'obtained candidate address for <%s>: <%r> with mode %s', self, candidate[:-2], self.mode)
return candidates
def _check_info_property(self, kind, name):
if not self._dsa_info:
raise LDAPInfoError('server info not loaded')
if kind == 'control':
properties = self.info.supported_controls
elif kind == 'extension':
properties = self.info.supported_extensions
elif kind == 'feature':
properties = self.info.supported_features
else:
raise LDAPInfoError('invalid info category')
for prop in properties:
if name == prop[0] or (prop[2] and name.lower() == prop[2].lower()): # checks oid and description
return True
return False
def has_control(self, control):
return self._check_info_property('control', control)
def has_extension(self, extension):
return self._check_info_property('extension', extension)
def has_feature(self, feature):
return self._check_info_property('feature', feature)