codekingpro/portable-devtools
114k
1"""2"""3 4# Created on 2014.05.315#6# Author: Giovanni Cannata7#8# Copyright 2014 - 2020 Giovanni Cannata9#10# This file is part of ldap3.11#12# ldap3 is free software: you can redistribute it and/or modify13# it under the terms of the GNU Lesser General Public License as published14# by the Free Software Foundation, either version 3 of the License, or15# (at your option) any later version.16#17# ldap3 is distributed in the hope that it will be useful,18# but WITHOUT ANY WARRANTY; without even the implied warranty of19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the20# GNU Lesser General Public License for more details.21#22# You should have received a copy of the GNU Lesser General Public License23# along with ldap3 in the COPYING and COPYING.LESSER files.24# If not, see <http://www.gnu.org/licenses/>.25 26import socket27from threading import Lock28from datetime import datetime, MINYEAR29 30from .. 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_TYPES31from .exceptions import LDAPInvalidServerError, LDAPDefinitionError, LDAPInvalidPortError, LDAPInvalidTlsSpecificationError, LDAPSocketOpenError, LDAPInfoError32from ..protocol.formatters.standard import format_attribute_values33from ..protocol.rfc4511 import LDAP_MAX_INT34from ..protocol.rfc4512 import SchemaInfo, DsaInfo35from .tls import Tls36from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, NETWORK37from ..utils.conv import to_unicode38from ..utils.port_validators import check_port, check_port_and_port_list39 40try:41 from urllib.parse import unquote # Python 342except ImportError:43 from urllib import unquote # Python 244 45try: # try to discover if unix sockets are available for LDAP over IPC (ldapi:// scheme)46 # noinspection PyUnresolvedReferences47 from socket import AF_UNIX48 unix_socket_available = True49except ImportError:50 unix_socket_available = False51 52 53class Server(object):54 """55 LDAP Server definition class56 57 Allowed_referral_hosts can be None (default), or a list of tuples of58 allowed servers ip address or names to contact while redirecting59 search to referrals.60 61 The second element of the tuple is a boolean to indicate if62 authentication to that server is allowed; if False only anonymous63 bind will be used.64 65 Per RFC 4516. Use [('*', False)] to allow any host with anonymous66 bind, use [('*', True)] to allow any host with same authentication of67 Server.68 """69 70 _message_counter = 071 _message_id_lock = Lock() # global lock for message_id shared by all Server objects72 73 def __init__(self,74 host,75 port=None,76 use_ssl=False,77 allowed_referral_hosts=None,78 get_info=SCHEMA,79 tls=None,80 formatter=None,81 connect_timeout=None,82 mode=IP_V6_PREFERRED,83 validator=None):84 85 self.ipc = False86 url_given = False87 host = host.strip()88 if host.lower().startswith('ldap://'):89 self.host = host[7:]90 use_ssl = False91 url_given = True92 elif host.lower().startswith('ldaps://'):93 self.host = host[8:]94 use_ssl = True95 url_given = True96 elif host.lower().startswith('ldapi://') and unix_socket_available:97 self.ipc = True98 use_ssl = False99 url_given = True100 elif host.lower().startswith('ldapi://') and not unix_socket_available:101 raise LDAPSocketOpenError('LDAP over IPC not available - UNIX sockets non present')102 else:103 self.host = host104 105 if self.ipc:106 if str is bytes: # Python 2107 self.host = unquote(host[7:]).decode('utf-8')108 else: # Python 3109 self.host = unquote(host[7:]) # encoding defaults to utf-8 in python3110 self.port = None111 elif ':' in self.host and self.host.count(':') == 1:112 hostname, _, hostport = self.host.partition(':')113 try:114 port = int(hostport) or port115 except ValueError:116 if log_enabled(ERROR):117 log(ERROR, 'port <%s> must be an integer', port)118 raise LDAPInvalidPortError('port must be an integer')119 self.host = hostname120 elif url_given and self.host.startswith('['):121 hostname, sep, hostport = self.host[1:].partition(']')122 if sep != ']' or not self._is_ipv6(hostname):123 if log_enabled(ERROR):124 log(ERROR, 'invalid IPv6 server address for <%s>', self.host)125 raise LDAPInvalidServerError()126 if len(hostport):127 if not hostport.startswith(':'):128 if log_enabled(ERROR):129 log(ERROR, 'invalid URL in server name for <%s>', self.host)130 raise LDAPInvalidServerError('invalid URL in server name')131 if not hostport[1:].isdecimal():132 if log_enabled(ERROR):133 log(ERROR, 'port must be an integer for <%s>', self.host)134 raise LDAPInvalidPortError('port must be an integer')135 port = int(hostport[1:])136 self.host = hostname137 elif not url_given and self._is_ipv6(self.host):138 pass139 elif self.host.count(':') > 1:140 if log_enabled(ERROR):141 log(ERROR, 'invalid server address for <%s>', self.host)142 raise LDAPInvalidServerError()143 144 if not self.ipc:145 self.host.rstrip('/')146 if not use_ssl and not port:147 port = 389148 elif use_ssl and not port:149 port = 636150 151 port_err = check_port(port)152 if port_err:153 if log_enabled(ERROR):154 log(ERROR, port_err)155 raise LDAPInvalidPortError(port_err)156 self.port = port157 158 if allowed_referral_hosts is None: # defaults to any server with authentication159 allowed_referral_hosts = [('*', True)]160 161 if isinstance(allowed_referral_hosts, SEQUENCE_TYPES):162 self.allowed_referral_hosts = []163 for referral_host in allowed_referral_hosts:164 if isinstance(referral_host, tuple):165 if isinstance(referral_host[1], bool):166 self.allowed_referral_hosts.append(referral_host)167 elif isinstance(allowed_referral_hosts, tuple):168 if isinstance(allowed_referral_hosts[1], bool):169 self.allowed_referral_hosts = [allowed_referral_hosts]170 else:171 self.allowed_referral_hosts = []172 173 self.ssl = True if use_ssl else False174 if tls and not isinstance(tls, Tls):175 if log_enabled(ERROR):176 log(ERROR, 'invalid tls specification: <%s>', tls)177 raise LDAPInvalidTlsSpecificationError('invalid Tls object')178 179 self.tls = Tls() if self.ssl and not tls else tls180 181 if not self.ipc:182 if self._is_ipv6(self.host):183 self.name = ('ldaps' if self.ssl else 'ldap') + '://[' + self.host + ']:' + str(self.port)184 else:185 self.name = ('ldaps' if self.ssl else 'ldap') + '://' + self.host + ':' + str(self.port)186 else:187 self.name = host188 189 self.get_info = get_info190 self._dsa_info = None191 self._schema_info = None192 self.dit_lock = Lock()193 self.custom_formatter = formatter194 self.custom_validator = validator195 self._address_info = [] # property self.address_info resolved at open time (or when check_availability is called)196 self._address_info_resolved_time = datetime(MINYEAR, 1, 1) # smallest date ever197 self.current_address = None198 self.connect_timeout = connect_timeout199 self.mode = mode200 201 self.get_info_from_server(None) # load offline schema if needed202 203 if log_enabled(BASIC):204 log(BASIC, 'instantiated Server: <%r>', self)205 206 @staticmethod207 def _is_ipv6(host):208 try:209 socket.inet_pton(socket.AF_INET6, host)210 except (socket.error, AttributeError, ValueError):211 return False212 return True213 214 def __str__(self):215 if self.host:216 s = self.name + (' - ssl' if self.ssl else ' - cleartext') + (' - unix socket' if self.ipc else '')217 else:218 s = object.__str__(self)219 return s220 221 def __repr__(self):222 r = 'Server(host={0.host!r}, port={0.port!r}, use_ssl={0.ssl!r}'.format(self)223 r += '' if not self.allowed_referral_hosts else ', allowed_referral_hosts={0.allowed_referral_hosts!r}'.format(self)224 r += '' if self.tls is None else ', tls={0.tls!r}'.format(self)225 r += '' if not self.get_info else ', get_info={0.get_info!r}'.format(self)226 r += '' if not self.connect_timeout else ', connect_timeout={0.connect_timeout!r}'.format(self)227 r += '' if not self.mode else ', mode={0.mode!r}'.format(self)228 r += ')'229 230 return r231 232 @property233 def address_info(self):234 conf_refresh_interval = get_config_parameter('ADDRESS_INFO_REFRESH_TIME')235 if not self._address_info or (datetime.now() - self._address_info_resolved_time).seconds > conf_refresh_interval:236 # 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 time237 addresses = None238 try:239 if self.ipc:240 addresses = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, None, self.host, None)]241 else:242 if self.mode == IP_V4_ONLY:243 addresses = socket.getaddrinfo(self.host, self.port, socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)244 elif self.mode == IP_V6_ONLY:245 addresses = socket.getaddrinfo(self.host, self.port, socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)246 else:247 addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)248 except (socket.gaierror, AttributeError):249 pass250 251 if not addresses: # if addresses not found or raised an exception (for example for bad flags) tries again without flags252 try:253 if self.mode == IP_V4_ONLY:254 addresses = socket.getaddrinfo(self.host, self.port, socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)255 elif self.mode == IP_V6_ONLY:256 addresses = socket.getaddrinfo(self.host, self.port, socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)257 else:258 addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP)259 except socket.gaierror:260 pass261 262 if addresses:263 self._address_info = [list(address) + [None, None] for address in addresses]264 self._address_info_resolved_time = datetime.now()265 else:266 self._address_info = []267 self._address_info_resolved_time = datetime(MINYEAR, 1, 1) # smallest date268 269 if log_enabled(BASIC):270 for address in self._address_info:271 log(BASIC, 'address for <%s> resolved as <%r>', self, address[:-2])272 return self._address_info273 274 def update_availability(self, address, available):275 cont = 0276 while cont < len(self._address_info):277 if self.address_info[cont] == address:278 self._address_info[cont][5] = True if available else False279 self._address_info[cont][6] = datetime.now()280 break281 cont += 1282 283 def reset_availability(self):284 for address in self._address_info:285 address[5] = None286 address[6] = None287 288 def check_availability(self, source_address=None, source_port=None, source_port_list=None):289 """290 Tries to open, connect and close a socket to specified address and port to check availability.291 Timeout in seconds is specified in CHECK_AVAILABITY_TIMEOUT if not specified in292 the Server object.293 If specified, use a specific address, port, or list of possible ports, when attempting to check availability.294 NOTE: This will only consider multiple ports from the source port list if the first ones we try to bind to are295 already in use. This will not attempt using different ports in the list if the server is unavailable,296 as that could result in the runtime of check_availability significantly exceeding the connection timeout.297 """298 source_port_err = check_port_and_port_list(source_port, source_port_list)299 if source_port_err:300 if log_enabled(ERROR):301 log(ERROR, source_port_err)302 raise LDAPInvalidPortError(source_port_err)303 304 # using an empty string to bind a socket means "use the default as if this wasn't provided" because socket305 # binding requires that you pass something for the ip if you want to pass a specific port306 bind_address = source_address if source_address is not None else ''307 # using 0 as the source port to bind a socket means "use the default behavior of picking a random port from308 # all ports as if this wasn't provided" because socket binding requires that you pass something for the port309 # if you want to pass a specific ip310 candidate_bind_ports = [0]311 312 # if we have either a source port or source port list, convert that into our candidate list313 if source_port is not None:314 candidate_bind_ports = [source_port]315 elif source_port_list is not None:316 candidate_bind_ports = source_port_list[:]317 318 conf_availability_timeout = get_config_parameter('CHECK_AVAILABILITY_TIMEOUT')319 available = False320 self.reset_availability()321 for address in self.candidate_addresses():322 available = True323 try:324 temp_socket = socket.socket(*address[:3])325 326 # Go through our candidate bind ports and try to bind our socket to our source address with them.327 # if no source address or ports were specified, this will have the same success/fail result as if we328 # tried to connect to the remote server without binding locally first.329 # This is actually a little bit better, as it lets us distinguish the case of "issue binding the socket330 # locally" from "remote server is unavailable" with more clarity, though this will only really be an331 # issue when no source address/port is specified if the system checking server availability is running332 # as a very unprivileged user.333 last_bind_exc = None334 socket_bind_succeeded = False335 for bind_port in candidate_bind_ports:336 try:337 temp_socket.bind((bind_address, bind_port))338 socket_bind_succeeded = True339 break340 except Exception as bind_ex:341 last_bind_exc = bind_ex342 if log_enabled(NETWORK):343 log(NETWORK, 'Unable to bind to local address <%s> with source port <%s> due to <%s>',344 bind_address, bind_port, bind_ex)345 if not socket_bind_succeeded:346 if log_enabled(ERROR):347 log(ERROR, 'Unable to locally bind to local address <%s> with any of the source ports <%s> due to <%s>',348 bind_address, candidate_bind_ports, last_bind_exc)349 raise LDAPSocketOpenError('Unable to bind socket locally to address {} with any of the source ports {} due to {}'350 .format(bind_address, candidate_bind_ports, last_bind_exc))351 352 if self.connect_timeout:353 temp_socket.settimeout(self.connect_timeout)354 else:355 temp_socket.settimeout(conf_availability_timeout) # set timeout for checking availability to default356 try:357 temp_socket.connect(address[4])358 except socket.error:359 available = False360 finally:361 try:362 temp_socket.shutdown(socket.SHUT_RDWR)363 except socket.error:364 available = False365 finally:366 temp_socket.close()367 except socket.gaierror:368 available = False369 370 if available:371 if log_enabled(BASIC):372 log(BASIC, 'server <%s> available at <%r>', self, address)373 self.update_availability(address, True)374 break # if an available address is found exits immediately375 else:376 self.update_availability(address, False)377 if log_enabled(ERROR):378 log(ERROR, 'server <%s> not available at <%r>', self, address)379 380 return available381 382 @staticmethod383 def next_message_id():384 """385 LDAP messageId is unique for all connections to same server386 """387 with Server._message_id_lock:388 Server._message_counter += 1389 if Server._message_counter >= LDAP_MAX_INT:390 Server._message_counter = 1391 if log_enabled(PROTOCOL):392 log(PROTOCOL, 'new message id <%d> generated', Server._message_counter)393 394 return Server._message_counter395 396 def _get_dsa_info(self, connection):397 """398 Retrieve DSE operational attribute as per RFC4512 (5.1).399 """400 if connection.strategy.no_real_dsa: # do not try for mock strategies401 return402 403 if not connection.strategy.pooled: # in pooled strategies get_dsa_info is performed by the worker threads404 result = connection.search(search_base='',405 search_filter='(objectClass=*)',406 search_scope=BASE,407 attributes=['altServer', # requests specific dsa info attributes408 'namingContexts',409 'supportedControl',410 'supportedExtension',411 'supportedFeatures',412 'supportedCapabilities',413 'supportedLdapVersion',414 'supportedSASLMechanisms',415 'vendorName',416 'vendorVersion',417 'subschemaSubentry',418 '*',419 '+'], # requests all remaining attributes (other),420 get_operational_attributes=True)421 422 if connection.strategy.thread_safe:423 status, result, response, _ = result424 else:425 status = result426 result = connection.result427 response = connection.response428 429 with self.dit_lock:430 if connection.strategy.sync: # sync request431 self._dsa_info = DsaInfo(response[0]['attributes'], response[0]['raw_attributes']) if status else self._dsa_info432 elif status: # asynchronous request, must check if attributes in response433 results, _ = connection.get_response(status)434 if len(results) == 1 and 'attributes' in results[0] and 'raw_attributes' in results[0]:435 self._dsa_info = DsaInfo(results[0]['attributes'], results[0]['raw_attributes'])436 437 if log_enabled(BASIC):438 log(BASIC, 'DSA info read for <%s> via <%s>', self, connection)439 440 def _get_schema_info(self, connection, entry=''):441 """442 Retrieve schema from subschemaSubentry DSE attribute, per RFC443 4512 (4.4 and 5.1); entry = '' means DSE.444 """445 if connection.strategy.no_real_dsa: # do not try for mock strategies446 return447 448 schema_entry = None449 if self._dsa_info and entry == '': # subschemaSubentry already present in dsaInfo450 if isinstance(self._dsa_info.schema_entry, SEQUENCE_TYPES):451 schema_entry = self._dsa_info.schema_entry[0] if self._dsa_info.schema_entry else None452 else:453 schema_entry = self._dsa_info.schema_entry if self._dsa_info.schema_entry else None454 else:455 result = connection.search(entry, '(objectClass=*)', BASE, attributes=['subschemaSubentry'], get_operational_attributes=True)456 if connection.strategy.thread_safe:457 status, result, response, _ = result458 else:459 status = result460 result = connection.result461 response = connection.response462 if connection.strategy.sync: # sync request463 if status and 'subschemaSubentry' in response[0]['raw_attributes']:464 if len(response[0]['raw_attributes']['subschemaSubentry']) > 0:465 schema_entry = response[0]['raw_attributes']['subschemaSubentry'][0]466 else: # asynchronous request, must check if subschemaSubentry in attributes467 results, _ = connection.get_response(status)468 if len(results) == 1 and 'raw_attributes' in results[0] and 'subschemaSubentry' in results[0]['attributes']:469 if len(results[0]['raw_attributes']['subschemaSubentry']) > 0:470 schema_entry = results[0]['raw_attributes']['subschemaSubentry'][0]471 472 if schema_entry and not connection.strategy.pooled: # in pooled strategies get_schema_info is performed by the worker threads473 if isinstance(schema_entry, bytes) and str is not bytes: # Python 3474 schema_entry = to_unicode(schema_entry, from_server=True)475 result = connection.search(schema_entry,476 search_filter='(objectClass=subschema)',477 search_scope=BASE,478 attributes=['objectClasses', # requests specific subschema attributes479 'attributeTypes',480 'ldapSyntaxes',481 'matchingRules',482 'matchingRuleUse',483 'dITContentRules',484 'dITStructureRules',485 'nameForms',486 'createTimestamp',487 'modifyTimestamp',488 '*'], # requests all remaining attributes (other)489 get_operational_attributes=True490 )491 if connection.strategy.thread_safe:492 status, result, response, _ = result493 else:494 status = result495 result = connection.result496 response = connection.response497 with self.dit_lock:498 self._schema_info = None499 if status:500 if connection.strategy.sync: # sync request501 self._schema_info = SchemaInfo(schema_entry, response[0]['attributes'], response[0]['raw_attributes'])502 else: # asynchronous request, must check if attributes in response503 results, result = connection.get_response(status)504 if len(results) == 1 and 'attributes' in results[0] and 'raw_attributes' in results[0]:505 self._schema_info = SchemaInfo(schema_entry, results[0]['attributes'], results[0]['raw_attributes'])506 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 None507 self._schema_info = None508 if self._schema_info: # if schema is valid tries to apply formatter to the "other" dict with raw values for schema and info509 for attribute in self._schema_info.other:510 self._schema_info.other[attribute] = format_attribute_values(self._schema_info, attribute, self._schema_info.raw[attribute], self.custom_formatter)511 if self._dsa_info: # try to apply formatter to the "other" dict with dsa info raw values512 for attribute in self._dsa_info.other:513 self._dsa_info.other[attribute] = format_attribute_values(self._schema_info, attribute, self._dsa_info.raw[attribute], self.custom_formatter)514 if log_enabled(BASIC):515 log(BASIC, 'schema read for <%s> via <%s>', self, connection)516 517 def get_info_from_server(self, connection):518 """519 reads info from DSE and from subschema520 """521 if connection and not connection.closed:522 if self.get_info in [DSA, ALL]:523 self._get_dsa_info(connection)524 if self.get_info in [SCHEMA, ALL]:525 self._get_schema_info(connection)526 elif self.get_info == OFFLINE_EDIR_8_8_8:527 from ..protocol.schemas.edir888 import edir_8_8_8_schema, edir_8_8_8_dsa_info528 self.attach_schema_info(SchemaInfo.from_json(edir_8_8_8_schema))529 self.attach_dsa_info(DsaInfo.from_json(edir_8_8_8_dsa_info))530 elif self.get_info == OFFLINE_EDIR_9_1_4:531 from ..protocol.schemas.edir914 import edir_9_1_4_schema, edir_9_1_4_dsa_info532 self.attach_schema_info(SchemaInfo.from_json(edir_9_1_4_schema))533 self.attach_dsa_info(DsaInfo.from_json(edir_9_1_4_dsa_info))534 elif self.get_info == OFFLINE_AD_2012_R2:535 from ..protocol.schemas.ad2012R2 import ad_2012_r2_schema, ad_2012_r2_dsa_info536 self.attach_schema_info(SchemaInfo.from_json(ad_2012_r2_schema))537 self.attach_dsa_info(DsaInfo.from_json(ad_2012_r2_dsa_info))538 elif self.get_info == OFFLINE_SLAPD_2_4:539 from ..protocol.schemas.slapd24 import slapd_2_4_schema, slapd_2_4_dsa_info540 self.attach_schema_info(SchemaInfo.from_json(slapd_2_4_schema))541 self.attach_dsa_info(DsaInfo.from_json(slapd_2_4_dsa_info))542 elif self.get_info == OFFLINE_DS389_1_3_3:543 from ..protocol.schemas.ds389 import ds389_1_3_3_schema, ds389_1_3_3_dsa_info544 self.attach_schema_info(SchemaInfo.from_json(ds389_1_3_3_schema))545 self.attach_dsa_info(DsaInfo.from_json(ds389_1_3_3_dsa_info))546 547 def attach_dsa_info(self, dsa_info=None):548 if isinstance(dsa_info, DsaInfo):549 self._dsa_info = dsa_info550 if log_enabled(BASIC):551 log(BASIC, 'attached DSA info to Server <%s>', self)552 553 def attach_schema_info(self, dsa_schema=None):554 if isinstance(dsa_schema, SchemaInfo):555 self._schema_info = dsa_schema556 if log_enabled(BASIC):557 log(BASIC, 'attached schema info to Server <%s>', self)558 559 @property560 def info(self):561 return self._dsa_info562 563 @property564 def schema(self):565 return self._schema_info566 567 @staticmethod568 def from_definition(host, dsa_info, dsa_schema, port=None, use_ssl=False, formatter=None, validator=None):569 """570 Define a dummy server with preloaded schema and info571 :param host: host name572 :param dsa_info: DsaInfo preloaded object or a json formatted string or a file name573 :param dsa_schema: SchemaInfo preloaded object or a json formatted string or a file name574 :param port: fake port575 :param use_ssl: use_ssl576 :param formatter: custom formatters577 :return: Server object578 """579 if isinstance(host, SEQUENCE_TYPES):580 dummy = Server(host=host[0], port=port, use_ssl=use_ssl, formatter=formatter, validator=validator, get_info=ALL) # for ServerPool object581 else:582 dummy = Server(host=host, port=port, use_ssl=use_ssl, formatter=formatter, validator=validator, get_info=ALL)583 if isinstance(dsa_info, DsaInfo):584 dummy._dsa_info = dsa_info585 elif isinstance(dsa_info, STRING_TYPES):586 try:587 dummy._dsa_info = DsaInfo.from_json(dsa_info) # tries to use dsa_info as a json configuration string588 except Exception:589 dummy._dsa_info = DsaInfo.from_file(dsa_info) # tries to use dsa_info as a file name590 591 if not dummy.info:592 if log_enabled(ERROR):593 log(ERROR, 'invalid DSA info for %s', host)594 raise LDAPDefinitionError('invalid dsa info')595 596 if isinstance(dsa_schema, SchemaInfo):597 dummy._schema_info = dsa_schema598 elif isinstance(dsa_schema, STRING_TYPES):599 try:600 dummy._schema_info = SchemaInfo.from_json(dsa_schema)601 except Exception:602 dummy._schema_info = SchemaInfo.from_file(dsa_schema)603 604 if not dummy.schema:605 if log_enabled(ERROR):606 log(ERROR, 'invalid schema info for %s', host)607 raise LDAPDefinitionError('invalid schema info')608 609 if log_enabled(BASIC):610 log(BASIC, 'created server <%s> from definition', dummy)611 612 return dummy613 614 def candidate_addresses(self):615 conf_reset_availability_timeout = get_config_parameter('RESET_AVAILABILITY_TIMEOUT')616 if self.ipc:617 candidates = self.address_info618 if log_enabled(BASIC):619 log(BASIC, 'candidate address for <%s>: <%s> with mode UNIX_SOCKET', self, self.name)620 else:621 # checks reset availability timeout622 for address in self.address_info:623 if address[6] and ((datetime.now() - address[6]).seconds > conf_reset_availability_timeout):624 address[5] = None625 address[6] = None626 627 # selects server address based on server mode and availability (in address[5])628 addresses = self.address_info[:] # copy to avoid refreshing while searching candidates629 candidates = []630 if addresses:631 if self.mode == IP_SYSTEM_DEFAULT:632 candidates.append(addresses[0])633 elif self.mode == IP_V4_ONLY:634 candidates = [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]635 elif self.mode == IP_V6_ONLY:636 candidates = [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]637 elif self.mode == IP_V4_PREFERRED:638 candidates = [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]639 candidates += [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]640 elif self.mode == IP_V6_PREFERRED:641 candidates = [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]642 candidates += [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]643 else:644 if log_enabled(ERROR):645 log(ERROR, 'invalid server mode for <%s>', self)646 raise LDAPInvalidServerError('invalid server mode')647 648 if log_enabled(BASIC):649 for candidate in candidates:650 log(BASIC, 'obtained candidate address for <%s>: <%r> with mode %s', self, candidate[:-2], self.mode)651 return candidates652 653 def _check_info_property(self, kind, name):654 if not self._dsa_info:655 raise LDAPInfoError('server info not loaded')656 657 if kind == 'control':658 properties = self.info.supported_controls659 elif kind == 'extension':660 properties = self.info.supported_extensions661 elif kind == 'feature':662 properties = self.info.supported_features663 else:664 raise LDAPInfoError('invalid info category')665 666 for prop in properties:667 if name == prop[0] or (prop[2] and name.lower() == prop[2].lower()): # checks oid and description668 return True669 670 return False671 672 def has_control(self, control):673 return self._check_info_property('control', control)674 675 def has_extension(self, extension):676 return self._check_info_property('extension', extension)677 678 def has_feature(self, feature):679 return self._check_info_property('feature', feature)680 681 682 683 