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.
 
 
 
 

879 lines
48 KiB

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