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.
 
 
 
 

926 lines
51 KiB

  1. """
  2. """
  3. # Created on 2013.07.15
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2013 - 2020 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 dectails.
  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. try: # try to discover if unix sockets are available for LDAP over IPC (ldapi:// scheme)
  26. # noinspection PyUnresolvedReferences
  27. from socket import AF_UNIX
  28. unix_socket_available = True
  29. except ImportError:
  30. unix_socket_available = False
  31. from struct import pack
  32. from platform import system
  33. from random import choice
  34. from .. import SYNC, ANONYMOUS, get_config_parameter, BASE, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, NO_ATTRIBUTES, DIGEST_MD5
  35. from ..core.results import DO_NOT_RAISE_EXCEPTIONS, RESULT_REFERRAL
  36. from ..core.exceptions import LDAPOperationResult, LDAPSASLBindInProgressError, LDAPSocketOpenError, LDAPSessionTerminatedByServerError,\
  37. LDAPUnknownResponseError, LDAPUnknownRequestError, LDAPReferralError, communication_exception_factory, LDAPStartTLSError, \
  38. LDAPSocketSendError, LDAPExceptionError, LDAPControlError, LDAPResponseTimeoutError, LDAPTransactionError
  39. from ..utils.uri import parse_uri
  40. from ..protocol.rfc4511 import LDAPMessage, ProtocolOp, MessageID, SearchResultEntry
  41. from ..operation.add import add_response_to_dict, add_request_to_dict
  42. from ..operation.modify import modify_request_to_dict, modify_response_to_dict
  43. from ..operation.search import search_result_reference_response_to_dict, search_result_done_response_to_dict,\
  44. search_result_entry_response_to_dict, search_request_to_dict, search_result_entry_response_to_dict_fast,\
  45. search_result_reference_response_to_dict_fast, attributes_to_dict, attributes_to_dict_fast
  46. from ..operation.bind import bind_response_to_dict, bind_request_to_dict, sicily_bind_response_to_dict, bind_response_to_dict_fast, \
  47. sicily_bind_response_to_dict_fast
  48. from ..operation.compare import compare_response_to_dict, compare_request_to_dict
  49. from ..operation.extended import extended_request_to_dict, extended_response_to_dict, intermediate_response_to_dict, extended_response_to_dict_fast, intermediate_response_to_dict_fast
  50. from ..core.server import Server
  51. from ..operation.modifyDn import modify_dn_request_to_dict, modify_dn_response_to_dict
  52. from ..operation.delete import delete_response_to_dict, delete_request_to_dict
  53. from ..protocol.convert import prepare_changes_for_request, build_controls_list
  54. from ..operation.abandon import abandon_request_to_dict
  55. from ..core.tls import Tls
  56. from ..protocol.oid import Oids
  57. from ..protocol.rfc2696 import RealSearchControlValue
  58. from ..protocol.microsoft import DirSyncControlResponseValue
  59. from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, NETWORK, EXTENDED, format_ldap_message
  60. from ..utils.asn1 import encode, decoder, ldap_result_to_dict_fast, decode_sequence
  61. from ..utils.conv import to_unicode
  62. from ..protocol.sasl.digestMd5 import md5_h, md5_hmac
  63. SESSION_TERMINATED_BY_SERVER = 'TERMINATED_BY_SERVER'
  64. TRANSACTION_ERROR = 'TRANSACTION_ERROR'
  65. RESPONSE_COMPLETE = 'RESPONSE_FROM_SERVER_COMPLETE'
  66. # noinspection PyProtectedMember
  67. class BaseStrategy(object):
  68. """
  69. Base class for connection strategy
  70. """
  71. def __init__(self, ldap_connection):
  72. self.connection = ldap_connection
  73. self._outstanding = None
  74. self._referrals = []
  75. self.sync = None # indicates a synchronous connection
  76. self.no_real_dsa = None # indicates a connection to a fake LDAP server
  77. self.pooled = None # Indicates a connection with a connection pool
  78. self.can_stream = None # indicates if a strategy keeps a stream of responses (i.e. LdifProducer can accumulate responses with a single header). Stream must be initialized and closed in _start_listen() and _stop_listen()
  79. self.referral_cache = {}
  80. self.thread_safe = False # Indicates that connection can be used in a multithread application
  81. if log_enabled(BASIC):
  82. log(BASIC, 'instantiated <%s>: <%s>', self.__class__.__name__, self)
  83. def __str__(self):
  84. s = [
  85. str(self.connection) if self.connection else 'None',
  86. 'sync' if self.sync else 'async',
  87. 'no real DSA' if self.no_real_dsa else 'real DSA',
  88. 'pooled' if self.pooled else 'not pooled',
  89. 'can stream output' if self.can_stream else 'cannot stream output',
  90. ]
  91. return ' - '.join(s)
  92. def open(self, reset_usage=True, read_server_info=True):
  93. """
  94. Open a socket to a server. Choose a server from the server pool if available
  95. """
  96. if log_enabled(NETWORK):
  97. log(NETWORK, 'opening connection for <%s>', self.connection)
  98. if self.connection.lazy and not self.connection._executing_deferred:
  99. self.connection._deferred_open = True
  100. self.connection.closed = False
  101. if log_enabled(NETWORK):
  102. log(NETWORK, 'deferring open connection for <%s>', self.connection)
  103. else:
  104. if not self.connection.closed and not self.connection._executing_deferred: # try to close connection if still open
  105. self.close()
  106. self._outstanding = dict()
  107. if self.connection.usage:
  108. if reset_usage or not self.connection._usage.initial_connection_start_time:
  109. self.connection._usage.start()
  110. if self.connection.server_pool:
  111. new_server = self.connection.server_pool.get_server(self.connection) # get a server from the server_pool if available
  112. if self.connection.server != new_server:
  113. self.connection.server = new_server
  114. if self.connection.usage:
  115. self.connection._usage.servers_from_pool += 1
  116. exception_history = []
  117. if not self.no_real_dsa: # tries to connect to a real server
  118. for candidate_address in self.connection.server.candidate_addresses():
  119. try:
  120. if log_enabled(BASIC):
  121. log(BASIC, 'try to open candidate address %s', candidate_address[:-2])
  122. self._open_socket(candidate_address, self.connection.server.ssl, unix_socket=self.connection.server.ipc)
  123. self.connection.server.current_address = candidate_address
  124. self.connection.server.update_availability(candidate_address, True)
  125. break
  126. except Exception as e:
  127. self.connection.server.update_availability(candidate_address, False)
  128. # exception_history.append((datetime.now(), exc_type, exc_value, candidate_address[4]))
  129. exception_history.append((type(e)(str(e)), candidate_address[4]))
  130. if not self.connection.server.current_address and exception_history:
  131. if len(exception_history) == 1: # only one exception, reraise
  132. if log_enabled(ERROR):
  133. log(ERROR, '<%s> for <%s>', str(exception_history[0][0]) + ' ' + str((exception_history[0][1])), self.connection)
  134. raise exception_history[0][0]
  135. else:
  136. if log_enabled(ERROR):
  137. log(ERROR, 'unable to open socket for <%s>', self.connection)
  138. raise LDAPSocketOpenError('unable to open socket', exception_history)
  139. elif not self.connection.server.current_address:
  140. if log_enabled(ERROR):
  141. log(ERROR, 'invalid server address for <%s>', self.connection)
  142. raise LDAPSocketOpenError('invalid server address')
  143. self.connection._deferred_open = False
  144. self._start_listen()
  145. if log_enabled(NETWORK):
  146. log(NETWORK, 'connection open for <%s>', self.connection)
  147. def close(self):
  148. """
  149. Close connection
  150. """
  151. if log_enabled(NETWORK):
  152. log(NETWORK, 'closing connection for <%s>', self.connection)
  153. if self.connection.lazy and not self.connection._executing_deferred and (self.connection._deferred_bind or self.connection._deferred_open):
  154. self.connection.listening = False
  155. self.connection.closed = True
  156. if log_enabled(NETWORK):
  157. log(NETWORK, 'deferred connection closed for <%s>', self.connection)
  158. else:
  159. if not self.connection.closed:
  160. self._stop_listen()
  161. if not self. no_real_dsa:
  162. self._close_socket()
  163. if log_enabled(NETWORK):
  164. log(NETWORK, 'connection closed for <%s>', self.connection)
  165. self.connection.bound = False
  166. self.connection.request = None
  167. self.connection.response = None
  168. self.connection.tls_started = False
  169. self._outstanding = None
  170. self._referrals = []
  171. if not self.connection.strategy.no_real_dsa:
  172. self.connection.server.current_address = None
  173. if self.connection.usage:
  174. self.connection._usage.stop()
  175. def _open_socket(self, address, use_ssl=False, unix_socket=False):
  176. """
  177. Tries to open and connect a socket to a Server
  178. raise LDAPExceptionError if unable to open or connect socket
  179. """
  180. try:
  181. self.connection.socket = socket.socket(*address[:3])
  182. except Exception as e:
  183. self.connection.last_error = 'socket creation error: ' + str(e)
  184. if log_enabled(ERROR):
  185. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  186. # raise communication_exception_factory(LDAPSocketOpenError, exc)(self.connection.last_error)
  187. raise communication_exception_factory(LDAPSocketOpenError, type(e)(str(e)))(self.connection.last_error)
  188. # Try to bind the socket locally before connecting to the remote address
  189. # We go through our connection's source ports and try to bind our socket to our connection's source address
  190. # with them.
  191. # If no source address or ports were specified, this will have the same success/fail result as if we
  192. # tried to connect to the remote server without binding locally first.
  193. # This is actually a little bit better, as it lets us distinguish the case of "issue binding the socket
  194. # locally" from "remote server is unavailable" with more clarity, though this will only really be an
  195. # issue when no source address/port is specified if the system checking server availability is running
  196. # as a very unprivileged user.
  197. last_bind_exc = None
  198. if unix_socket_available and self.connection.socket.family != socket.AF_UNIX:
  199. socket_bind_succeeded = False
  200. for source_port in self.connection.source_port_list:
  201. try:
  202. self.connection.socket.bind((self.connection.source_address, source_port))
  203. socket_bind_succeeded = True
  204. break
  205. except Exception as bind_ex:
  206. last_bind_exc = bind_ex
  207. # we'll always end up logging at error level if we cannot bind any ports to the address locally.
  208. # but if some work and some don't you probably don't want the ones that don't at ERROR level
  209. if log_enabled(NETWORK):
  210. log(NETWORK, 'Unable to bind to local address <%s> with source port <%s> due to <%s>',
  211. self.connection.source_address, source_port, bind_ex)
  212. if not socket_bind_succeeded:
  213. self.connection.last_error = 'socket connection error while locally binding: ' + str(last_bind_exc)
  214. if log_enabled(ERROR):
  215. log(ERROR, 'Unable to locally bind to local address <%s> with any of the source ports <%s> for connection <%s due to <%s>',
  216. self.connection.source_address, self.connection.source_port_list, self.connection, last_bind_exc)
  217. raise communication_exception_factory(LDAPSocketOpenError, type(last_bind_exc)(str(last_bind_exc)))(last_bind_exc)
  218. try: # set socket timeout for opening connection
  219. if self.connection.server.connect_timeout:
  220. self.connection.socket.settimeout(self.connection.server.connect_timeout)
  221. self.connection.socket.connect(address[4])
  222. except socket.error as e:
  223. self.connection.last_error = 'socket connection error while opening: ' + str(e)
  224. if log_enabled(ERROR):
  225. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  226. # raise communication_exception_factory(LDAPSocketOpenError, exc)(self.connection.last_error)
  227. raise communication_exception_factory(LDAPSocketOpenError, type(e)(str(e)))(self.connection.last_error)
  228. # Set connection recv timeout (must be set after connect,
  229. # because socket.settimeout() affects both, connect() as
  230. # well as recv(). Set it before tls.wrap_socket() because
  231. # the recv timeout should take effect during the TLS
  232. # handshake.
  233. if self.connection.receive_timeout is not None:
  234. try: # set receive timeout for the connection socket
  235. self.connection.socket.settimeout(self.connection.receive_timeout)
  236. if system().lower() == 'windows':
  237. self.connection.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, int(1000 * self.connection.receive_timeout))
  238. else:
  239. self.connection.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, pack('LL', self.connection.receive_timeout, 0))
  240. except socket.error as e:
  241. self.connection.last_error = 'unable to set receive timeout for socket connection: ' + str(e)
  242. # if exc:
  243. # if log_enabled(ERROR):
  244. # log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  245. # raise communication_exception_factory(LDAPSocketOpenError, exc)(self.connection.last_error)
  246. if log_enabled(ERROR):
  247. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  248. raise communication_exception_factory(LDAPSocketOpenError, type(e)(str(e)))(self.connection.last_error)
  249. if use_ssl:
  250. try:
  251. self.connection.server.tls.wrap_socket(self.connection, do_handshake=True)
  252. if self.connection.usage:
  253. self.connection._usage.wrapped_sockets += 1
  254. except Exception as e:
  255. self.connection.last_error = 'socket ssl wrapping error: ' + str(e)
  256. if log_enabled(ERROR):
  257. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  258. raise communication_exception_factory(LDAPSocketOpenError, type(e)(str(e)))(self.connection.last_error)
  259. if self.connection.usage:
  260. self.connection._usage.open_sockets += 1
  261. self.connection.closed = False
  262. def _close_socket(self):
  263. """
  264. Try to close a socket
  265. don't raise exception if unable to close socket, assume socket is already closed
  266. """
  267. try:
  268. self.connection.socket.shutdown(socket.SHUT_RDWR)
  269. except Exception:
  270. pass
  271. try:
  272. self.connection.socket.close()
  273. except Exception:
  274. pass
  275. self.connection.socket = None
  276. self.connection.closed = True
  277. if self.connection.usage:
  278. self.connection._usage.closed_sockets += 1
  279. def _stop_listen(self):
  280. self.connection.listening = False
  281. def send(self, message_type, request, controls=None):
  282. """
  283. Send an LDAP message
  284. Returns the message_id
  285. """
  286. self.connection.request = None
  287. if self.connection.listening:
  288. if self.connection.sasl_in_progress and message_type not in ['bindRequest']: # as per RFC4511 (4.2.1)
  289. self.connection.last_error = 'cannot send operation requests while SASL bind is in progress'
  290. if log_enabled(ERROR):
  291. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  292. raise LDAPSASLBindInProgressError(self.connection.last_error)
  293. message_id = self.connection.server.next_message_id()
  294. ldap_message = LDAPMessage()
  295. ldap_message['messageID'] = MessageID(message_id)
  296. ldap_message['protocolOp'] = ProtocolOp().setComponentByName(message_type, request)
  297. message_controls = build_controls_list(controls)
  298. if message_controls is not None:
  299. ldap_message['controls'] = message_controls
  300. self.connection.request = BaseStrategy.decode_request(message_type, request, controls)
  301. self._outstanding[message_id] = self.connection.request
  302. self.sending(ldap_message)
  303. else:
  304. self.connection.last_error = 'unable to send message, socket is not open'
  305. if log_enabled(ERROR):
  306. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  307. raise LDAPSocketOpenError(self.connection.last_error)
  308. return message_id
  309. def get_response(self, message_id, timeout=None, get_request=False):
  310. """
  311. Get response LDAP messages
  312. Responses are returned by the underlying connection strategy
  313. Check if message_id LDAP message is still outstanding and wait for timeout to see if it appears in _get_response
  314. Result is stored in connection.result
  315. Responses without result is stored in connection.response
  316. A tuple (responses, result) is returned
  317. """
  318. if timeout is None:
  319. timeout = get_config_parameter('RESPONSE_WAITING_TIMEOUT')
  320. response = None
  321. result = None
  322. # request = None
  323. if self._outstanding and message_id in self._outstanding:
  324. responses = self._get_response(message_id, timeout)
  325. if not responses:
  326. if log_enabled(ERROR):
  327. log(ERROR, 'socket timeout, no response from server for <%s>', self.connection)
  328. raise LDAPResponseTimeoutError('no response from server')
  329. if responses == SESSION_TERMINATED_BY_SERVER:
  330. try: # try to close the session but don't raise any error if server has already closed the session
  331. self.close()
  332. except (socket.error, LDAPExceptionError):
  333. pass
  334. self.connection.last_error = 'session terminated by server'
  335. if log_enabled(ERROR):
  336. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  337. raise LDAPSessionTerminatedByServerError(self.connection.last_error)
  338. elif responses == TRANSACTION_ERROR: # Novell LDAP Transaction unsolicited notification
  339. self.connection.last_error = 'transaction error'
  340. if log_enabled(ERROR):
  341. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  342. raise LDAPTransactionError(self.connection.last_error)
  343. # if referral in response opens a new connection to resolve referrals if requested
  344. if responses[-2]['result'] == RESULT_REFERRAL:
  345. if self.connection.usage:
  346. self.connection._usage.referrals_received += 1
  347. if self.connection.auto_referrals:
  348. ref_response, ref_result = self.do_operation_on_referral(self._outstanding[message_id], responses[-2]['referrals'])
  349. if ref_response is not None:
  350. responses = ref_response + [ref_result]
  351. responses.append(RESPONSE_COMPLETE)
  352. elif ref_result is not None:
  353. responses = [ref_result, RESPONSE_COMPLETE]
  354. self._referrals = []
  355. if responses:
  356. result = responses[-2]
  357. response = responses[:-2]
  358. self.connection.result = None
  359. self.connection.response = None
  360. if self.connection.raise_exceptions and result and result['result'] not in DO_NOT_RAISE_EXCEPTIONS:
  361. if log_enabled(PROTOCOL):
  362. log(PROTOCOL, 'operation result <%s> for <%s>', result, self.connection)
  363. self._outstanding.pop(message_id)
  364. self.connection.result = result.copy()
  365. raise LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], response_type=result['type'])
  366. # checks if any response has a range tag
  367. # self._auto_range_searching is set as a flag to avoid recursive searches
  368. if self.connection.auto_range and not hasattr(self, '_auto_range_searching') and any((True for resp in response if 'raw_attributes' in resp for name in resp['raw_attributes'] if ';range=' in name)):
  369. self._auto_range_searching = result.copy()
  370. temp_response = response[:] # copy
  371. if self.do_search_on_auto_range(self._outstanding[message_id], response):
  372. for resp in temp_response:
  373. if resp['type'] == 'searchResEntry':
  374. keys = [key for key in resp['raw_attributes'] if ';range=' in key]
  375. for key in keys:
  376. del resp['raw_attributes'][key]
  377. del resp['attributes'][key]
  378. response = temp_response
  379. result = self._auto_range_searching
  380. del self._auto_range_searching
  381. if self.connection.empty_attributes:
  382. for entry in response:
  383. if entry['type'] == 'searchResEntry':
  384. for attribute_type in self._outstanding[message_id]['attributes']:
  385. if attribute_type not in entry['raw_attributes'] and attribute_type not in (ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, NO_ATTRIBUTES):
  386. entry['raw_attributes'][attribute_type] = list()
  387. entry['attributes'][attribute_type] = list()
  388. if log_enabled(PROTOCOL):
  389. log(PROTOCOL, 'attribute set to empty list for missing attribute <%s> in <%s>', attribute_type, self)
  390. if not self.connection.auto_range:
  391. attrs_to_remove = []
  392. # removes original empty attribute in case a range tag is returned
  393. for attribute_type in entry['attributes']:
  394. if ';range' in attribute_type.lower():
  395. orig_attr, _, _ = attribute_type.partition(';')
  396. attrs_to_remove.append(orig_attr)
  397. for attribute_type in attrs_to_remove:
  398. if log_enabled(PROTOCOL):
  399. log(PROTOCOL, 'attribute type <%s> removed in response because of same attribute returned as range by the server in <%s>', attribute_type, self)
  400. del entry['raw_attributes'][attribute_type]
  401. del entry['attributes'][attribute_type]
  402. request = self._outstanding.pop(message_id)
  403. else:
  404. if log_enabled(ERROR):
  405. log(ERROR, 'message id not in outstanding queue for <%s>', self.connection)
  406. raise(LDAPResponseTimeoutError('message id not in outstanding queue'))
  407. if get_request:
  408. return response, result, request
  409. else:
  410. return response, result
  411. @staticmethod
  412. def compute_ldap_message_size(data):
  413. """
  414. Compute LDAP Message size according to BER definite length rules
  415. Returns -1 if too few data to compute message length
  416. """
  417. if isinstance(data, str): # fix for Python 2, data is string not bytes
  418. data = bytearray(data) # Python 2 bytearray is equivalent to Python 3 bytes
  419. ret_value = -1
  420. if len(data) > 2:
  421. if data[1] <= 127: # BER definite length - short form. Highest bit of byte 1 is 0, message length is in the last 7 bits - Value can be up to 127 bytes long
  422. ret_value = data[1] + 2
  423. else: # BER definite length - long form. Highest bit of byte 1 is 1, last 7 bits counts the number of following octets containing the value length
  424. bytes_length = data[1] - 128
  425. if len(data) >= bytes_length + 2:
  426. value_length = 0
  427. cont = bytes_length
  428. for byte in data[2:2 + bytes_length]:
  429. cont -= 1
  430. value_length += byte * (256 ** cont)
  431. ret_value = value_length + 2 + bytes_length
  432. return ret_value
  433. def decode_response(self, ldap_message):
  434. """
  435. Convert received LDAPMessage to a dict
  436. """
  437. message_type = ldap_message.getComponentByName('protocolOp').getName()
  438. component = ldap_message['protocolOp'].getComponent()
  439. controls = ldap_message['controls'] if ldap_message['controls'].hasValue() else None
  440. if message_type == 'bindResponse':
  441. if not bytes(component['matchedDN']).startswith(b'NTLM'): # patch for microsoft ntlm authentication
  442. result = bind_response_to_dict(component)
  443. else:
  444. result = sicily_bind_response_to_dict(component)
  445. elif message_type == 'searchResEntry':
  446. result = search_result_entry_response_to_dict(component, self.connection.server.schema, self.connection.server.custom_formatter, self.connection.check_names)
  447. elif message_type == 'searchResDone':
  448. result = search_result_done_response_to_dict(component)
  449. elif message_type == 'searchResRef':
  450. result = search_result_reference_response_to_dict(component)
  451. elif message_type == 'modifyResponse':
  452. result = modify_response_to_dict(component)
  453. elif message_type == 'addResponse':
  454. result = add_response_to_dict(component)
  455. elif message_type == 'delResponse':
  456. result = delete_response_to_dict(component)
  457. elif message_type == 'modDNResponse':
  458. result = modify_dn_response_to_dict(component)
  459. elif message_type == 'compareResponse':
  460. result = compare_response_to_dict(component)
  461. elif message_type == 'extendedResp':
  462. result = extended_response_to_dict(component)
  463. elif message_type == 'intermediateResponse':
  464. result = intermediate_response_to_dict(component)
  465. else:
  466. if log_enabled(ERROR):
  467. log(ERROR, 'unknown response <%s> for <%s>', message_type, self.connection)
  468. raise LDAPUnknownResponseError('unknown response')
  469. result['type'] = message_type
  470. if controls:
  471. result['controls'] = dict()
  472. for control in controls:
  473. decoded_control = self.decode_control(control)
  474. result['controls'][decoded_control[0]] = decoded_control[1]
  475. return result
  476. def decode_response_fast(self, ldap_message):
  477. """
  478. Convert received LDAPMessage from fast ber decoder to a dict
  479. """
  480. if ldap_message['protocolOp'] == 1: # bindResponse
  481. if not ldap_message['payload'][1][3].startswith(b'NTLM'): # patch for microsoft ntlm authentication
  482. result = bind_response_to_dict_fast(ldap_message['payload'])
  483. else:
  484. result = sicily_bind_response_to_dict_fast(ldap_message['payload'])
  485. result['type'] = 'bindResponse'
  486. elif ldap_message['protocolOp'] == 4: # searchResEntry'
  487. result = search_result_entry_response_to_dict_fast(ldap_message['payload'], self.connection.server.schema, self.connection.server.custom_formatter, self.connection.check_names)
  488. result['type'] = 'searchResEntry'
  489. elif ldap_message['protocolOp'] == 5: # searchResDone
  490. result = ldap_result_to_dict_fast(ldap_message['payload'])
  491. result['type'] = 'searchResDone'
  492. elif ldap_message['protocolOp'] == 19: # searchResRef
  493. result = search_result_reference_response_to_dict_fast(ldap_message['payload'])
  494. result['type'] = 'searchResRef'
  495. elif ldap_message['protocolOp'] == 7: # modifyResponse
  496. result = ldap_result_to_dict_fast(ldap_message['payload'])
  497. result['type'] = 'modifyResponse'
  498. elif ldap_message['protocolOp'] == 9: # addResponse
  499. result = ldap_result_to_dict_fast(ldap_message['payload'])
  500. result['type'] = 'addResponse'
  501. elif ldap_message['protocolOp'] == 11: # delResponse
  502. result = ldap_result_to_dict_fast(ldap_message['payload'])
  503. result['type'] = 'delResponse'
  504. elif ldap_message['protocolOp'] == 13: # modDNResponse
  505. result = ldap_result_to_dict_fast(ldap_message['payload'])
  506. result['type'] = 'modDNResponse'
  507. elif ldap_message['protocolOp'] == 15: # compareResponse
  508. result = ldap_result_to_dict_fast(ldap_message['payload'])
  509. result['type'] = 'compareResponse'
  510. elif ldap_message['protocolOp'] == 24: # extendedResp
  511. result = extended_response_to_dict_fast(ldap_message['payload'])
  512. result['type'] = 'extendedResp'
  513. elif ldap_message['protocolOp'] == 25: # intermediateResponse
  514. result = intermediate_response_to_dict_fast(ldap_message['payload'])
  515. result['type'] = 'intermediateResponse'
  516. else:
  517. if log_enabled(ERROR):
  518. log(ERROR, 'unknown response <%s> for <%s>', ldap_message['protocolOp'], self.connection)
  519. raise LDAPUnknownResponseError('unknown response')
  520. if ldap_message['controls']:
  521. result['controls'] = dict()
  522. for control in ldap_message['controls']:
  523. decoded_control = self.decode_control_fast(control[3])
  524. result['controls'][decoded_control[0]] = decoded_control[1]
  525. return result
  526. @staticmethod
  527. def decode_control(control):
  528. """
  529. decode control, return a 2-element tuple where the first element is the control oid
  530. and the second element is a dictionary with description (from Oids), criticality and decoded control value
  531. """
  532. control_type = str(control['controlType'])
  533. criticality = bool(control['criticality'])
  534. control_value = bytes(control['controlValue'])
  535. unprocessed = None
  536. if control_type == '1.2.840.113556.1.4.319': # simple paged search as per RFC2696
  537. control_resp, unprocessed = decoder.decode(control_value, asn1Spec=RealSearchControlValue())
  538. control_value = dict()
  539. control_value['size'] = int(control_resp['size'])
  540. control_value['cookie'] = bytes(control_resp['cookie'])
  541. elif control_type == '1.2.840.113556.1.4.841': # DirSync AD
  542. control_resp, unprocessed = decoder.decode(control_value, asn1Spec=DirSyncControlResponseValue())
  543. control_value = dict()
  544. control_value['more_results'] = bool(control_resp['MoreResults']) # more_result if nonzero
  545. control_value['cookie'] = bytes(control_resp['CookieServer'])
  546. elif control_type == '1.3.6.1.1.13.1' or control_type == '1.3.6.1.1.13.2': # Pre-Read control, Post-Read Control as per RFC 4527
  547. control_resp, unprocessed = decoder.decode(control_value, asn1Spec=SearchResultEntry())
  548. control_value = dict()
  549. control_value['result'] = attributes_to_dict(control_resp['attributes'])
  550. if unprocessed:
  551. if log_enabled(ERROR):
  552. log(ERROR, 'unprocessed control response in substrate')
  553. raise LDAPControlError('unprocessed control response in substrate')
  554. return control_type, {'description': Oids.get(control_type, ''), 'criticality': criticality, 'value': control_value}
  555. @staticmethod
  556. def decode_control_fast(control, from_server=True):
  557. """
  558. decode control, return a 2-element tuple where the first element is the control oid
  559. and the second element is a dictionary with description (from Oids), criticality and decoded control value
  560. """
  561. control_type = str(to_unicode(control[0][3], from_server=from_server))
  562. criticality = False
  563. control_value = None
  564. for r in control[1:]:
  565. if r[2] == 4: # controlValue
  566. control_value = r[3]
  567. else:
  568. criticality = False if r[3] == 0 else True # criticality (booleand default to False)
  569. if control_type == '1.2.840.113556.1.4.319': # simple paged search as per RFC2696
  570. control_resp = decode_sequence(control_value, 0, len(control_value))
  571. control_value = dict()
  572. control_value['size'] = int(control_resp[0][3][0][3])
  573. control_value['cookie'] = bytes(control_resp[0][3][1][3])
  574. elif control_type == '1.2.840.113556.1.4.841': # DirSync AD
  575. control_resp = decode_sequence(control_value, 0, len(control_value))
  576. control_value = dict()
  577. control_value['more_results'] = True if control_resp[0][3][0][3] else False # more_result if nonzero
  578. control_value['cookie'] = control_resp[0][3][2][3]
  579. elif control_type == '1.3.6.1.1.13.1' or control_type == '1.3.6.1.1.13.2': # Pre-Read control, Post-Read Control as per RFC 4527
  580. control_resp = decode_sequence(control_value, 0, len(control_value))
  581. control_value = dict()
  582. control_value['result'] = attributes_to_dict_fast(control_resp[0][3][1][3])
  583. return control_type, {'description': Oids.get(control_type, ''), 'criticality': criticality, 'value': control_value}
  584. @staticmethod
  585. def decode_request(message_type, component, controls=None):
  586. # message_type = ldap_message.getComponentByName('protocolOp').getName()
  587. # component = ldap_message['protocolOp'].getComponent()
  588. if message_type == 'bindRequest':
  589. result = bind_request_to_dict(component)
  590. elif message_type == 'unbindRequest':
  591. result = dict()
  592. elif message_type == 'addRequest':
  593. result = add_request_to_dict(component)
  594. elif message_type == 'compareRequest':
  595. result = compare_request_to_dict(component)
  596. elif message_type == 'delRequest':
  597. result = delete_request_to_dict(component)
  598. elif message_type == 'extendedReq':
  599. result = extended_request_to_dict(component)
  600. elif message_type == 'modifyRequest':
  601. result = modify_request_to_dict(component)
  602. elif message_type == 'modDNRequest':
  603. result = modify_dn_request_to_dict(component)
  604. elif message_type == 'searchRequest':
  605. result = search_request_to_dict(component)
  606. elif message_type == 'abandonRequest':
  607. result = abandon_request_to_dict(component)
  608. else:
  609. if log_enabled(ERROR):
  610. log(ERROR, 'unknown request <%s>', message_type)
  611. raise LDAPUnknownRequestError('unknown request')
  612. result['type'] = message_type
  613. result['controls'] = controls
  614. return result
  615. def valid_referral_list(self, referrals):
  616. referral_list = []
  617. for referral in referrals:
  618. candidate_referral = parse_uri(referral)
  619. if candidate_referral:
  620. for ref_host in self.connection.server.allowed_referral_hosts:
  621. if ref_host[0] == candidate_referral['host'] or ref_host[0] == '*':
  622. if candidate_referral['host'] not in self._referrals:
  623. candidate_referral['anonymousBindOnly'] = not ref_host[1]
  624. referral_list.append(candidate_referral)
  625. break
  626. return referral_list
  627. def do_next_range_search(self, request, response, attr_name):
  628. done = False
  629. current_response = response
  630. while not done:
  631. attr_type, _, returned_range = attr_name.partition(';range=')
  632. _, _, high_range = returned_range.partition('-')
  633. response['raw_attributes'][attr_type] += current_response['raw_attributes'][attr_name]
  634. response['attributes'][attr_type] += current_response['attributes'][attr_name]
  635. if high_range != '*':
  636. if log_enabled(PROTOCOL):
  637. log(PROTOCOL, 'performing next search on auto-range <%s> via <%s>', str(int(high_range) + 1), self.connection)
  638. requested_range = attr_type + ';range=' + str(int(high_range) + 1) + '-*'
  639. result = self.connection.search(search_base=response['dn'],
  640. search_filter='(objectclass=*)',
  641. search_scope=BASE,
  642. dereference_aliases=request['dereferenceAlias'],
  643. attributes=[attr_type + ';range=' + str(int(high_range) + 1) + '-*'])
  644. if self.connection.strategy.thread_safe:
  645. status, result, _response, _ = result
  646. else:
  647. status = result
  648. result = self.connection.result
  649. _response = self.connection.response
  650. if self.connection.strategy.sync:
  651. if status:
  652. current_response = _response[0]
  653. else:
  654. done = True
  655. else:
  656. current_response, _ = self.get_response(status)
  657. current_response = current_response[0]
  658. if not done:
  659. if requested_range in current_response['raw_attributes'] and len(current_response['raw_attributes'][requested_range]) == 0:
  660. del current_response['raw_attributes'][requested_range]
  661. del current_response['attributes'][requested_range]
  662. attr_name = list(filter(lambda a: ';range=' in a, current_response['raw_attributes'].keys()))[0]
  663. continue
  664. done = True
  665. def do_search_on_auto_range(self, request, response):
  666. for resp in [r for r in response if r['type'] == 'searchResEntry']:
  667. for attr_name in list(resp['raw_attributes'].keys()): # generate list to avoid changing of dict size error
  668. if ';range=' in attr_name:
  669. attr_type, _, range_values = attr_name.partition(';range=')
  670. if range_values in ('1-1', '0-0'): # DirSync returns these values for adding and removing members
  671. return False
  672. if attr_type not in resp['raw_attributes'] or resp['raw_attributes'][attr_type] is None:
  673. resp['raw_attributes'][attr_type] = list()
  674. if attr_type not in resp['attributes'] or resp['attributes'][attr_type] is None:
  675. resp['attributes'][attr_type] = list()
  676. self.do_next_range_search(request, resp, attr_name)
  677. return True
  678. def create_referral_connection(self, referrals):
  679. referral_connection = None
  680. selected_referral = None
  681. cachekey = None
  682. valid_referral_list = self.valid_referral_list(referrals)
  683. if valid_referral_list:
  684. preferred_referral_list = [referral for referral in valid_referral_list if
  685. referral['ssl'] == self.connection.server.ssl]
  686. selected_referral = choice(preferred_referral_list) if preferred_referral_list else choice(
  687. valid_referral_list)
  688. cachekey = (selected_referral['host'], selected_referral['port'] or self.connection.server.port, selected_referral['ssl'])
  689. if self.connection.use_referral_cache and cachekey in self.referral_cache:
  690. referral_connection = self.referral_cache[cachekey]
  691. else:
  692. referral_server = Server(host=selected_referral['host'],
  693. port=selected_referral['port'] or self.connection.server.port,
  694. use_ssl=selected_referral['ssl'],
  695. get_info=self.connection.server.get_info,
  696. formatter=self.connection.server.custom_formatter,
  697. connect_timeout=self.connection.server.connect_timeout,
  698. mode=self.connection.server.mode,
  699. allowed_referral_hosts=self.connection.server.allowed_referral_hosts,
  700. tls=Tls(local_private_key_file=self.connection.server.tls.private_key_file,
  701. local_certificate_file=self.connection.server.tls.certificate_file,
  702. validate=self.connection.server.tls.validate,
  703. version=self.connection.server.tls.version,
  704. ca_certs_file=self.connection.server.tls.ca_certs_file) if
  705. selected_referral['ssl'] else None)
  706. from ..core.connection import Connection
  707. referral_connection = Connection(server=referral_server,
  708. user=self.connection.user if not selected_referral['anonymousBindOnly'] else None,
  709. password=self.connection.password if not selected_referral['anonymousBindOnly'] else None,
  710. version=self.connection.version,
  711. authentication=self.connection.authentication if not selected_referral['anonymousBindOnly'] else ANONYMOUS,
  712. client_strategy=SYNC,
  713. auto_referrals=True,
  714. read_only=self.connection.read_only,
  715. check_names=self.connection.check_names,
  716. raise_exceptions=self.connection.raise_exceptions,
  717. fast_decoder=self.connection.fast_decoder,
  718. receive_timeout=self.connection.receive_timeout,
  719. sasl_mechanism=self.connection.sasl_mechanism,
  720. sasl_credentials=self.connection.sasl_credentials)
  721. if self.connection.usage:
  722. self.connection._usage.referrals_connections += 1
  723. referral_connection.open()
  724. referral_connection.strategy._referrals = self._referrals
  725. if self.connection.tls_started and not referral_server.ssl: # if the original server was in start_tls mode and the referral server is not in ssl then start_tls on the referral connection
  726. if not referral_connection.start_tls():
  727. error = 'start_tls in referral not successful' + (' - ' + referral_connection.last_error if referral_connection.last_error else '')
  728. if log_enabled(ERROR):
  729. log(ERROR, '%s for <%s>', error, self)
  730. self.unbind()
  731. raise LDAPStartTLSError(error)
  732. if self.connection.bound:
  733. referral_connection.bind()
  734. if self.connection.usage:
  735. self.connection._usage.referrals_followed += 1
  736. return selected_referral, referral_connection, cachekey
  737. def do_operation_on_referral(self, request, referrals):
  738. if log_enabled(PROTOCOL):
  739. log(PROTOCOL, 'following referral for <%s>', self.connection)
  740. selected_referral, referral_connection, cachekey = self.create_referral_connection(referrals)
  741. if selected_referral:
  742. if request['type'] == 'searchRequest':
  743. referral_connection.search(selected_referral['base'] or request['base'],
  744. selected_referral['filter'] or request['filter'],
  745. selected_referral['scope'] or request['scope'],
  746. request['dereferenceAlias'],
  747. selected_referral['attributes'] or request['attributes'],
  748. request['sizeLimit'],
  749. request['timeLimit'],
  750. request['typesOnly'],
  751. controls=request['controls'])
  752. elif request['type'] == 'addRequest':
  753. referral_connection.add(selected_referral['base'] or request['entry'],
  754. None,
  755. request['attributes'],
  756. controls=request['controls'])
  757. elif request['type'] == 'compareRequest':
  758. referral_connection.compare(selected_referral['base'] or request['entry'],
  759. request['attribute'],
  760. request['value'],
  761. controls=request['controls'])
  762. elif request['type'] == 'delRequest':
  763. referral_connection.delete(selected_referral['base'] or request['entry'],
  764. controls=request['controls'])
  765. elif request['type'] == 'extendedReq':
  766. referral_connection.extended(request['name'],
  767. request['value'],
  768. controls=request['controls'],
  769. no_encode=True
  770. )
  771. elif request['type'] == 'modifyRequest':
  772. referral_connection.modify(selected_referral['base'] or request['entry'],
  773. prepare_changes_for_request(request['changes']),
  774. controls=request['controls'])
  775. elif request['type'] == 'modDNRequest':
  776. referral_connection.modify_dn(selected_referral['base'] or request['entry'],
  777. request['newRdn'],
  778. request['deleteOldRdn'],
  779. request['newSuperior'],
  780. controls=request['controls'])
  781. else:
  782. self.connection.last_error = 'referral operation not permitted'
  783. if log_enabled(ERROR):
  784. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  785. raise LDAPReferralError(self.connection.last_error)
  786. response = referral_connection.response
  787. result = referral_connection.result
  788. if self.connection.use_referral_cache:
  789. self.referral_cache[cachekey] = referral_connection
  790. else:
  791. referral_connection.unbind()
  792. else:
  793. response = None
  794. result = None
  795. return response, result
  796. def sending(self, ldap_message):
  797. if log_enabled(NETWORK):
  798. log(NETWORK, 'sending 1 ldap message for <%s>', self.connection)
  799. try:
  800. encoded_message = encode(ldap_message)
  801. if self.connection.sasl_mechanism == DIGEST_MD5 and self.connection._digest_md5_kic and not self.connection.sasl_in_progress:
  802. # If we are using DIGEST-MD5 and LDAP signing is enabled: add a signature to the message
  803. sec_num = self.connection._digest_md5_sec_num # added underscore GC
  804. kic = self.connection._digest_md5_kic # lowercase GC
  805. # RFC 2831 : encoded_message = sizeOf(encored_message + signature + 0x0001 + secNum) + encoded_message + signature + 0x0001 + secNum
  806. signature = bytes.fromhex(md5_hmac(kic, int(sec_num).to_bytes(4, 'big') + encoded_message)[0:20])
  807. encoded_message = int(len(encoded_message) + 4 + 2 + 10).to_bytes(4, 'big') + encoded_message + signature + int(1).to_bytes(2, 'big') + int(sec_num).to_bytes(4, 'big')
  808. self.connection._digest_md5_sec_num += 1
  809. self.connection.socket.sendall(encoded_message)
  810. if log_enabled(EXTENDED):
  811. log(EXTENDED, 'ldap message sent via <%s>:%s', self.connection, format_ldap_message(ldap_message, '>>'))
  812. if log_enabled(NETWORK):
  813. log(NETWORK, 'sent %d bytes via <%s>', len(encoded_message), self.connection)
  814. except socket.error as e:
  815. self.connection.last_error = 'socket sending error' + str(e)
  816. encoded_message = None
  817. if log_enabled(ERROR):
  818. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  819. # raise communication_exception_factory(LDAPSocketSendError, exc)(self.connection.last_error)
  820. raise communication_exception_factory(LDAPSocketSendError, type(e)(str(e)))(self.connection.last_error)
  821. if self.connection.usage:
  822. self.connection._usage.update_transmitted_message(self.connection.request, len(encoded_message))
  823. def _start_listen(self):
  824. # overridden on strategy class
  825. raise NotImplementedError
  826. def _get_response(self, message_id, timeout):
  827. # overridden in strategy class
  828. raise NotImplementedError
  829. def receiving(self):
  830. # overridden in strategy class
  831. raise NotImplementedError
  832. def post_send_single_response(self, message_id):
  833. # overridden in strategy class
  834. raise NotImplementedError
  835. def post_send_search(self, message_id):
  836. # overridden in strategy class
  837. raise NotImplementedError
  838. def get_stream(self):
  839. raise NotImplementedError
  840. def set_stream(self, value):
  841. raise NotImplementedError
  842. def unbind_referral_cache(self):
  843. while len(self.referral_cache) > 0:
  844. cachekey, referral_connection = self.referral_cache.popitem()
  845. referral_connection.unbind()