Development of an internal social media platform with personalised dashboards for students
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.

server.py 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. """
  2. """
  3. # Created on 2014.05.31
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2014 - 2018 Giovanni Cannata
  8. #
  9. # This file is part of ldap3.
  10. #
  11. # ldap3 is free software: you can redistribute it and/or modify
  12. # it under the terms of the GNU Lesser General Public License as published
  13. # by the Free Software Foundation, either version 3 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # ldap3 is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU Lesser General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU Lesser General Public License
  22. # along with ldap3 in the COPYING and COPYING.LESSER files.
  23. # If not, see <http://www.gnu.org/licenses/>.
  24. import socket
  25. from threading import Lock
  26. from datetime import datetime, MINYEAR
  27. from .. import DSA, SCHEMA, ALL, BASE, get_config_parameter, OFFLINE_EDIR_8_8_8, 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
  28. from .exceptions import LDAPInvalidServerError, LDAPDefinitionError, LDAPInvalidPortError, LDAPInvalidTlsSpecificationError, LDAPSocketOpenError
  29. from ..protocol.formatters.standard import format_attribute_values
  30. from ..protocol.rfc4511 import LDAP_MAX_INT
  31. from ..protocol.rfc4512 import SchemaInfo, DsaInfo
  32. from .tls import Tls
  33. from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL
  34. from ..utils.conv import to_unicode
  35. try:
  36. from urllib.parse import unquote # Python 3
  37. except ImportError:
  38. from urllib import unquote # Python 2
  39. try: # try to discover if unix sockets are available for LDAP over IPC (ldapi:// scheme)
  40. # noinspection PyUnresolvedReferences
  41. from socket import AF_UNIX
  42. unix_socket_available = True
  43. except ImportError:
  44. unix_socket_available = False
  45. class Server(object):
  46. """
  47. LDAP Server definition class
  48. Allowed_referral_hosts can be None (default), or a list of tuples of
  49. allowed servers ip address or names to contact while redirecting
  50. search to referrals.
  51. The second element of the tuple is a boolean to indicate if
  52. authentication to that server is allowed; if False only anonymous
  53. bind will be used.
  54. Per RFC 4516. Use [('*', False)] to allow any host with anonymous
  55. bind, use [('*', True)] to allow any host with same authentication of
  56. Server.
  57. """
  58. _message_counter = 0
  59. _message_id_lock = Lock() # global lock for message_id shared by all Server objects
  60. def __init__(self,
  61. host,
  62. port=None,
  63. use_ssl=False,
  64. allowed_referral_hosts=None,
  65. get_info=SCHEMA,
  66. tls=None,
  67. formatter=None,
  68. connect_timeout=None,
  69. mode=IP_V6_PREFERRED,
  70. validator=None):
  71. self.ipc = False
  72. url_given = False
  73. host = host.strip()
  74. if host.lower().startswith('ldap://'):
  75. self.host = host[7:]
  76. use_ssl = False
  77. url_given = True
  78. elif host.lower().startswith('ldaps://'):
  79. self.host = host[8:]
  80. use_ssl = True
  81. url_given = True
  82. elif host.lower().startswith('ldapi://') and unix_socket_available:
  83. self.ipc = True
  84. use_ssl = False
  85. url_given = True
  86. elif host.lower().startswith('ldapi://') and not unix_socket_available:
  87. raise LDAPSocketOpenError('LDAP over IPC not available - UNIX sockets non present')
  88. else:
  89. self.host = host
  90. if self.ipc:
  91. if str is bytes: # Python 2
  92. self.host = unquote(host[7:]).decode('utf-8')
  93. else: # Python 3
  94. self.host = unquote(host[7:]) # encoding defaults to utf-8 in python3
  95. self.port = None
  96. elif ':' in self.host and self.host.count(':') == 1:
  97. hostname, _, hostport = self.host.partition(':')
  98. try:
  99. port = int(hostport) or port
  100. except ValueError:
  101. if log_enabled(ERROR):
  102. log(ERROR, 'port <%s> must be an integer', port)
  103. raise LDAPInvalidPortError('port must be an integer')
  104. self.host = hostname
  105. elif url_given and self.host.startswith('['):
  106. hostname, sep, hostport = self.host[1:].partition(']')
  107. if sep != ']' or not self._is_ipv6(hostname):
  108. if log_enabled(ERROR):
  109. log(ERROR, 'invalid IPv6 server address for <%s>', self.host)
  110. raise LDAPInvalidServerError()
  111. if len(hostport):
  112. if not hostport.startswith(':'):
  113. if log_enabled(ERROR):
  114. log(ERROR, 'invalid URL in server name for <%s>', self.host)
  115. raise LDAPInvalidServerError('invalid URL in server name')
  116. if not hostport[1:].isdecimal():
  117. if log_enabled(ERROR):
  118. log(ERROR, 'port must be an integer for <%s>', self.host)
  119. raise LDAPInvalidPortError('port must be an integer')
  120. port = int(hostport[1:])
  121. self.host = hostname
  122. elif not url_given and self._is_ipv6(self.host):
  123. pass
  124. elif self.host.count(':') > 1:
  125. if log_enabled(ERROR):
  126. log(ERROR, 'invalid server address for <%s>', self.host)
  127. raise LDAPInvalidServerError()
  128. if not self.ipc:
  129. self.host.rstrip('/')
  130. if not use_ssl and not port:
  131. port = 389
  132. elif use_ssl and not port:
  133. port = 636
  134. if isinstance(port, int):
  135. if port in range(0, 65535):
  136. self.port = port
  137. else:
  138. if log_enabled(ERROR):
  139. log(ERROR, 'port <%s> must be in range from 0 to 65535', port)
  140. raise LDAPInvalidPortError('port must in range from 0 to 65535')
  141. else:
  142. if log_enabled(ERROR):
  143. log(ERROR, 'port <%s> must be an integer', port)
  144. raise LDAPInvalidPortError('port must be an integer')
  145. if allowed_referral_hosts is None: # defaults to any server with authentication
  146. allowed_referral_hosts = [('*', True)]
  147. if isinstance(allowed_referral_hosts, SEQUENCE_TYPES):
  148. self.allowed_referral_hosts = []
  149. for referral_host in allowed_referral_hosts:
  150. if isinstance(referral_host, tuple):
  151. if isinstance(referral_host[1], bool):
  152. self.allowed_referral_hosts.append(referral_host)
  153. elif isinstance(allowed_referral_hosts, tuple):
  154. if isinstance(allowed_referral_hosts[1], bool):
  155. self.allowed_referral_hosts = [allowed_referral_hosts]
  156. else:
  157. self.allowed_referral_hosts = []
  158. self.ssl = True if use_ssl else False
  159. if tls and not isinstance(tls, Tls):
  160. if log_enabled(ERROR):
  161. log(ERROR, 'invalid tls specification: <%s>', tls)
  162. raise LDAPInvalidTlsSpecificationError('invalid Tls object')
  163. self.tls = Tls() if self.ssl and not tls else tls
  164. if not self.ipc:
  165. if self._is_ipv6(self.host):
  166. self.name = ('ldaps' if self.ssl else 'ldap') + '://[' + self.host + ']:' + str(self.port)
  167. else:
  168. self.name = ('ldaps' if self.ssl else 'ldap') + '://' + self.host + ':' + str(self.port)
  169. else:
  170. self.name = host
  171. self.get_info = get_info
  172. self._dsa_info = None
  173. self._schema_info = None
  174. self.dit_lock = Lock()
  175. self.custom_formatter = formatter
  176. self.custom_validator = validator
  177. self._address_info = [] # property self.address_info resolved at open time (or when check_availability is called)
  178. self._address_info_resolved_time = datetime(MINYEAR, 1, 1) # smallest date ever
  179. self.current_address = None
  180. self.connect_timeout = connect_timeout
  181. self.mode = mode
  182. self.get_info_from_server(None) # load offline schema if needed
  183. if log_enabled(BASIC):
  184. log(BASIC, 'instantiated Server: <%r>', self)
  185. @staticmethod
  186. def _is_ipv6(host):
  187. try:
  188. socket.inet_pton(socket.AF_INET6, host)
  189. except (socket.error, AttributeError, ValueError):
  190. return False
  191. return True
  192. def __str__(self):
  193. if self.host:
  194. s = self.name + (' - ssl' if self.ssl else ' - cleartext') + (' - unix socket' if self.ipc else '')
  195. else:
  196. s = object.__str__(self)
  197. return s
  198. def __repr__(self):
  199. r = 'Server(host={0.host!r}, port={0.port!r}, use_ssl={0.ssl!r}'.format(self)
  200. r += '' if not self.allowed_referral_hosts else ', allowed_referral_hosts={0.allowed_referral_hosts!r}'.format(self)
  201. r += '' if self.tls is None else ', tls={0.tls!r}'.format(self)
  202. r += '' if not self.get_info else ', get_info={0.get_info!r}'.format(self)
  203. r += '' if not self.connect_timeout else ', connect_timeout={0.connect_timeout!r}'.format(self)
  204. r += '' if not self.mode else ', mode={0.mode!r}'.format(self)
  205. r += ')'
  206. return r
  207. @property
  208. def address_info(self):
  209. conf_refresh_interval = get_config_parameter('ADDRESS_INFO_REFRESH_TIME')
  210. if not self._address_info or (datetime.now() - self._address_info_resolved_time).seconds > conf_refresh_interval:
  211. # 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
  212. addresses = None
  213. try:
  214. if self.ipc:
  215. addresses = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, None, self.host, None)]
  216. else:
  217. addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
  218. except (socket.gaierror, AttributeError):
  219. pass
  220. if not addresses: # if addresses not found or raised an exception (for example for bad flags) tries again without flags
  221. try:
  222. addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP)
  223. except socket.gaierror:
  224. pass
  225. if addresses:
  226. self._address_info = [list(address) + [None, None] for address in addresses]
  227. self._address_info_resolved_time = datetime.now()
  228. else:
  229. self._address_info = []
  230. self._address_info_resolved_time = datetime(MINYEAR, 1, 1) # smallest date
  231. if log_enabled(BASIC):
  232. for address in self._address_info:
  233. log(BASIC, 'address for <%s> resolved as <%r>', self, address[:-2])
  234. return self._address_info
  235. def update_availability(self, address, available):
  236. cont = 0
  237. while cont < len(self._address_info):
  238. if self.address_info[cont] == address:
  239. self._address_info[cont][5] = True if available else False
  240. self._address_info[cont][6] = datetime.now()
  241. break
  242. cont += 1
  243. def reset_availability(self):
  244. for address in self._address_info:
  245. address[5] = None
  246. address[6] = None
  247. def check_availability(self):
  248. """
  249. Tries to open, connect and close a socket to specified address
  250. and port to check availability. Timeout in seconds is specified in CHECK_AVAILABITY_TIMEOUT if not specified in
  251. the Server object
  252. """
  253. conf_availability_timeout = get_config_parameter('CHECK_AVAILABILITY_TIMEOUT')
  254. available = False
  255. self.reset_availability()
  256. for address in self.candidate_addresses():
  257. available = True
  258. try:
  259. temp_socket = socket.socket(*address[:3])
  260. if self.connect_timeout:
  261. temp_socket.settimeout(self.connect_timeout)
  262. else:
  263. temp_socket.settimeout(conf_availability_timeout) # set timeout for checking availability to default
  264. try:
  265. temp_socket.connect(address[4])
  266. except socket.error:
  267. available = False
  268. finally:
  269. try:
  270. temp_socket.shutdown(socket.SHUT_RDWR)
  271. except socket.error:
  272. available = False
  273. finally:
  274. temp_socket.close()
  275. except socket.gaierror:
  276. available = False
  277. if available:
  278. if log_enabled(BASIC):
  279. log(BASIC, 'server <%s> available at <%r>', self, address)
  280. self.update_availability(address, True)
  281. break # if an available address is found exits immediately
  282. else:
  283. self.update_availability(address, False)
  284. if log_enabled(ERROR):
  285. log(ERROR, 'server <%s> not available at <%r>', self, address)
  286. return available
  287. @staticmethod
  288. def next_message_id():
  289. """
  290. LDAP messageId is unique for all connections to same server
  291. """
  292. with Server._message_id_lock:
  293. Server._message_counter += 1
  294. if Server._message_counter >= LDAP_MAX_INT:
  295. Server._message_counter = 1
  296. if log_enabled(PROTOCOL):
  297. log(PROTOCOL, 'new message id <%d> generated', Server._message_counter)
  298. return Server._message_counter
  299. def _get_dsa_info(self, connection):
  300. """
  301. Retrieve DSE operational attribute as per RFC4512 (5.1).
  302. """
  303. if connection.strategy.no_real_dsa: # do not try for mock strategies
  304. return
  305. if not connection.strategy.pooled: # in pooled strategies get_dsa_info is performed by the worker threads
  306. result = connection.search(search_base='',
  307. search_filter='(objectClass=*)',
  308. search_scope=BASE,
  309. attributes=['altServer', # requests specific dsa info attributes
  310. 'namingContexts',
  311. 'supportedControl',
  312. 'supportedExtension',
  313. 'supportedFeatures',
  314. 'supportedCapabilities',
  315. 'supportedLdapVersion',
  316. 'supportedSASLMechanisms',
  317. 'vendorName',
  318. 'vendorVersion',
  319. 'subschemaSubentry',
  320. '*',
  321. '+'], # requests all remaining attributes (other),
  322. get_operational_attributes=True)
  323. with self.dit_lock:
  324. if isinstance(result, bool): # sync request
  325. self._dsa_info = DsaInfo(connection.response[0]['attributes'], connection.response[0]['raw_attributes']) if result else self._dsa_info
  326. elif result: # asynchronous request, must check if attributes in response
  327. results, _ = connection.get_response(result)
  328. if len(results) == 1 and 'attributes' in results[0] and 'raw_attributes' in results[0]:
  329. self._dsa_info = DsaInfo(results[0]['attributes'], results[0]['raw_attributes'])
  330. if log_enabled(BASIC):
  331. log(BASIC, 'DSA info read for <%s> via <%s>', self, connection)
  332. def _get_schema_info(self, connection, entry=''):
  333. """
  334. Retrieve schema from subschemaSubentry DSE attribute, per RFC
  335. 4512 (4.4 and 5.1); entry = '' means DSE.
  336. """
  337. if connection.strategy.no_real_dsa: # do not try for mock strategies
  338. return
  339. schema_entry = None
  340. if self._dsa_info and entry == '': # subschemaSubentry already present in dsaInfo
  341. if isinstance(self._dsa_info.schema_entry, SEQUENCE_TYPES):
  342. schema_entry = self._dsa_info.schema_entry[0] if self._dsa_info.schema_entry else None
  343. else:
  344. schema_entry = self._dsa_info.schema_entry if self._dsa_info.schema_entry else None
  345. else:
  346. result = connection.search(entry, '(objectClass=*)', BASE, attributes=['subschemaSubentry'], get_operational_attributes=True)
  347. if isinstance(result, bool): # sync request
  348. if result and 'subschemaSubentry' in connection.response[0]['raw_attributes']:
  349. if len(connection.response[0]['raw_attributes']['subschemaSubentry']) > 0:
  350. schema_entry = connection.response[0]['raw_attributes']['subschemaSubentry'][0]
  351. else: # asynchronous request, must check if subschemaSubentry in attributes
  352. results, _ = connection.get_response(result)
  353. if len(results) == 1 and 'raw_attributes' in results[0] and 'subschemaSubentry' in results[0]['attributes']:
  354. if len(results[0]['raw_attributes']['subschemaSubentry']) > 0:
  355. schema_entry = results[0]['raw_attributes']['subschemaSubentry'][0]
  356. if schema_entry and not connection.strategy.pooled: # in pooled strategies get_schema_info is performed by the worker threads
  357. if isinstance(schema_entry, bytes) and str is not bytes: # Python 3
  358. schema_entry = to_unicode(schema_entry, from_server=True)
  359. result = connection.search(schema_entry,
  360. search_filter='(objectClass=subschema)',
  361. search_scope=BASE,
  362. attributes=['objectClasses', # requests specific subschema attributes
  363. 'attributeTypes',
  364. 'ldapSyntaxes',
  365. 'matchingRules',
  366. 'matchingRuleUse',
  367. 'dITContentRules',
  368. 'dITStructureRules',
  369. 'nameForms',
  370. 'createTimestamp',
  371. 'modifyTimestamp',
  372. '*'], # requests all remaining attributes (other)
  373. get_operational_attributes=True
  374. )
  375. with self.dit_lock:
  376. self._schema_info = None
  377. if result:
  378. if isinstance(result, bool): # sync request
  379. self._schema_info = SchemaInfo(schema_entry, connection.response[0]['attributes'], connection.response[0]['raw_attributes']) if result else None
  380. else: # asynchronous request, must check if attributes in response
  381. results, result = connection.get_response(result)
  382. if len(results) == 1 and 'attributes' in results[0] and 'raw_attributes' in results[0]:
  383. self._schema_info = SchemaInfo(schema_entry, results[0]['attributes'], results[0]['raw_attributes'])
  384. 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
  385. self._schema_info = None
  386. if self._schema_info: # if schema is valid tries to apply formatter to the "other" dict with raw values for schema and info
  387. for attribute in self._schema_info.other:
  388. self._schema_info.other[attribute] = format_attribute_values(self._schema_info, attribute, self._schema_info.raw[attribute], self.custom_formatter)
  389. if self._dsa_info: # try to apply formatter to the "other" dict with dsa info raw values
  390. for attribute in self._dsa_info.other:
  391. self._dsa_info.other[attribute] = format_attribute_values(self._schema_info, attribute, self._dsa_info.raw[attribute], self.custom_formatter)
  392. if log_enabled(BASIC):
  393. log(BASIC, 'schema read for <%s> via <%s>', self, connection)
  394. def get_info_from_server(self, connection):
  395. """
  396. reads info from DSE and from subschema
  397. """
  398. if connection and not connection.closed:
  399. if self.get_info in [DSA, ALL]:
  400. self._get_dsa_info(connection)
  401. if self.get_info in [SCHEMA, ALL]:
  402. self._get_schema_info(connection)
  403. elif self.get_info == OFFLINE_EDIR_8_8_8:
  404. from ..protocol.schemas.edir888 import edir_8_8_8_schema, edir_8_8_8_dsa_info
  405. self.attach_schema_info(SchemaInfo.from_json(edir_8_8_8_schema))
  406. self.attach_dsa_info(DsaInfo.from_json(edir_8_8_8_dsa_info))
  407. elif self.get_info == OFFLINE_AD_2012_R2:
  408. from ..protocol.schemas.ad2012R2 import ad_2012_r2_schema, ad_2012_r2_dsa_info
  409. self.attach_schema_info(SchemaInfo.from_json(ad_2012_r2_schema))
  410. self.attach_dsa_info(DsaInfo.from_json(ad_2012_r2_dsa_info))
  411. elif self.get_info == OFFLINE_SLAPD_2_4:
  412. from ..protocol.schemas.slapd24 import slapd_2_4_schema, slapd_2_4_dsa_info
  413. self.attach_schema_info(SchemaInfo.from_json(slapd_2_4_schema))
  414. self.attach_dsa_info(DsaInfo.from_json(slapd_2_4_dsa_info))
  415. elif self.get_info == OFFLINE_DS389_1_3_3:
  416. from ..protocol.schemas.ds389 import ds389_1_3_3_schema, ds389_1_3_3_dsa_info
  417. self.attach_schema_info(SchemaInfo.from_json(ds389_1_3_3_schema))
  418. self.attach_dsa_info(DsaInfo.from_json(ds389_1_3_3_dsa_info))
  419. def attach_dsa_info(self, dsa_info=None):
  420. if isinstance(dsa_info, DsaInfo):
  421. self._dsa_info = dsa_info
  422. if log_enabled(BASIC):
  423. log(BASIC, 'attached DSA info to Server <%s>', self)
  424. def attach_schema_info(self, dsa_schema=None):
  425. if isinstance(dsa_schema, SchemaInfo):
  426. self._schema_info = dsa_schema
  427. if log_enabled(BASIC):
  428. log(BASIC, 'attached schema info to Server <%s>', self)
  429. @property
  430. def info(self):
  431. return self._dsa_info
  432. @property
  433. def schema(self):
  434. return self._schema_info
  435. @staticmethod
  436. def from_definition(host, dsa_info, dsa_schema, port=None, use_ssl=False, formatter=None, validator=None):
  437. """
  438. Define a dummy server with preloaded schema and info
  439. :param host: host name
  440. :param dsa_info: DsaInfo preloaded object or a json formatted string or a file name
  441. :param dsa_schema: SchemaInfo preloaded object or a json formatted string or a file name
  442. :param port: dummy port
  443. :param use_ssl: use_ssl
  444. :param formatter: custom formatter
  445. :return: Server object
  446. """
  447. if isinstance(host, SEQUENCE_TYPES):
  448. dummy = Server(host=host[0], port=port, use_ssl=use_ssl, formatter=formatter, validator=validator, get_info=ALL) # for ServerPool object
  449. else:
  450. dummy = Server(host=host, port=port, use_ssl=use_ssl, formatter=formatter, validator=validator, get_info=ALL)
  451. if isinstance(dsa_info, DsaInfo):
  452. dummy._dsa_info = dsa_info
  453. elif isinstance(dsa_info, STRING_TYPES):
  454. try:
  455. dummy._dsa_info = DsaInfo.from_json(dsa_info) # tries to use dsa_info as a json configuration string
  456. except Exception:
  457. dummy._dsa_info = DsaInfo.from_file(dsa_info) # tries to use dsa_info as a file name
  458. if not dummy.info:
  459. if log_enabled(ERROR):
  460. log(ERROR, 'invalid DSA info for %s', host)
  461. raise LDAPDefinitionError('invalid dsa info')
  462. if isinstance(dsa_schema, SchemaInfo):
  463. dummy._schema_info = dsa_schema
  464. elif isinstance(dsa_schema, STRING_TYPES):
  465. try:
  466. dummy._schema_info = SchemaInfo.from_json(dsa_schema)
  467. except Exception:
  468. dummy._schema_info = SchemaInfo.from_file(dsa_schema)
  469. if not dummy.schema:
  470. if log_enabled(ERROR):
  471. log(ERROR, 'invalid schema info for %s', host)
  472. raise LDAPDefinitionError('invalid schema info')
  473. if log_enabled(BASIC):
  474. log(BASIC, 'created server <%s> from definition', dummy)
  475. return dummy
  476. def candidate_addresses(self):
  477. conf_reset_availability_timeout = get_config_parameter('RESET_AVAILABILITY_TIMEOUT')
  478. if self.ipc:
  479. candidates = self.address_info
  480. if log_enabled(BASIC):
  481. log(BASIC, 'candidate address for <%s>: <%s> with mode UNIX_SOCKET', self, self.name)
  482. else:
  483. # checks reset availability timeout
  484. for address in self.address_info:
  485. if address[6] and ((datetime.now() - address[6]).seconds > conf_reset_availability_timeout):
  486. address[5] = None
  487. address[6] = None
  488. # selects server address based on server mode and availability (in address[5])
  489. addresses = self.address_info[:] # copy to avoid refreshing while searching candidates
  490. candidates = []
  491. if addresses:
  492. if self.mode == IP_SYSTEM_DEFAULT:
  493. candidates.append(addresses[0])
  494. elif self.mode == IP_V4_ONLY:
  495. candidates = [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
  496. elif self.mode == IP_V6_ONLY:
  497. candidates = [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
  498. elif self.mode == IP_V4_PREFERRED:
  499. candidates = [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
  500. candidates += [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
  501. elif self.mode == IP_V6_PREFERRED:
  502. candidates = [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
  503. candidates += [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
  504. else:
  505. if log_enabled(ERROR):
  506. log(ERROR, 'invalid server mode for <%s>', self)
  507. raise LDAPInvalidServerError('invalid server mode')
  508. if log_enabled(BASIC):
  509. for candidate in candidates:
  510. log(BASIC, 'obtained candidate address for <%s>: <%r> with mode %s', self, candidate[:-2], self.mode)
  511. return candidates