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.

sync.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 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 .. import SEQUENCE_TYPES, get_config_parameter
  26. from ..core.exceptions import LDAPSocketReceiveError, communication_exception_factory, LDAPExceptionError, LDAPExtensionError, LDAPOperationResult
  27. from ..strategy.base import BaseStrategy, SESSION_TERMINATED_BY_SERVER, RESPONSE_COMPLETE, TRANSACTION_ERROR
  28. from ..protocol.rfc4511 import LDAPMessage
  29. from ..utils.log import log, log_enabled, ERROR, NETWORK, EXTENDED, format_ldap_message
  30. from ..utils.asn1 import decoder, decode_message_fast
  31. LDAP_MESSAGE_TEMPLATE = LDAPMessage()
  32. # noinspection PyProtectedMember
  33. class SyncStrategy(BaseStrategy):
  34. """
  35. This strategy is synchronous. You send the request and get the response
  36. Requests return a boolean value to indicate the result of the requested Operation
  37. Connection.response will contain the whole LDAP response for the messageId requested in a dict form
  38. Connection.request will contain the result LDAP message in a dict form
  39. """
  40. def __init__(self, ldap_connection):
  41. BaseStrategy.__init__(self, ldap_connection)
  42. self.sync = True
  43. self.no_real_dsa = False
  44. self.pooled = False
  45. self.can_stream = False
  46. self.socket_size = get_config_parameter('SOCKET_SIZE')
  47. def open(self, reset_usage=True, read_server_info=True):
  48. BaseStrategy.open(self, reset_usage, read_server_info)
  49. if read_server_info:
  50. try:
  51. self.connection.refresh_server_info()
  52. except LDAPOperationResult: # catch errors from server if raise_exception = True
  53. self.connection.server._dsa_info = None
  54. self.connection.server._schema_info = None
  55. def _start_listen(self):
  56. if not self.connection.listening and not self.connection.closed:
  57. self.connection.listening = True
  58. def receiving(self):
  59. """
  60. Receive data over the socket
  61. Checks if the socket is closed
  62. """
  63. messages = []
  64. receiving = True
  65. unprocessed = b''
  66. data = b''
  67. get_more_data = True
  68. exc = None
  69. while receiving:
  70. if get_more_data:
  71. try:
  72. data = self.connection.socket.recv(self.socket_size)
  73. except (OSError, socket.error, AttributeError) as e:
  74. self.connection.last_error = 'error receiving data: ' + str(e)
  75. exc = e
  76. if exc:
  77. try: # try to close the connection before raising exception
  78. self.close()
  79. except (socket.error, LDAPExceptionError):
  80. pass
  81. if log_enabled(ERROR):
  82. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  83. raise communication_exception_factory(LDAPSocketReceiveError, exc)(self.connection.last_error)
  84. unprocessed += data
  85. if len(data) > 0:
  86. length = BaseStrategy.compute_ldap_message_size(unprocessed)
  87. if length == -1: # too few data to decode message length
  88. get_more_data = True
  89. continue
  90. if len(unprocessed) < length:
  91. get_more_data = True
  92. else:
  93. if log_enabled(NETWORK):
  94. log(NETWORK, 'received %d bytes via <%s>', len(unprocessed[:length]), self.connection)
  95. messages.append(unprocessed[:length])
  96. unprocessed = unprocessed[length:]
  97. get_more_data = False
  98. if len(unprocessed) == 0:
  99. receiving = False
  100. else:
  101. receiving = False
  102. if log_enabled(NETWORK):
  103. log(NETWORK, 'received %d ldap messages via <%s>', len(messages), self.connection)
  104. return messages
  105. def post_send_single_response(self, message_id):
  106. """
  107. Executed after an Operation Request (except Search)
  108. Returns the result message or None
  109. """
  110. responses, result = self.get_response(message_id)
  111. self.connection.result = result
  112. if result['type'] == 'intermediateResponse': # checks that all responses are intermediates (there should be only one)
  113. for response in responses:
  114. if response['type'] != 'intermediateResponse':
  115. self.connection.last_error = 'multiple messages received error'
  116. if log_enabled(ERROR):
  117. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  118. raise LDAPSocketReceiveError(self.connection.last_error)
  119. responses.append(result)
  120. return responses
  121. def post_send_search(self, message_id):
  122. """
  123. Executed after a search request
  124. Returns the result message and store in connection.response the objects found
  125. """
  126. responses, result = self.get_response(message_id)
  127. self.connection.result = result
  128. if isinstance(responses, SEQUENCE_TYPES):
  129. self.connection.response = responses[:] # copy search result entries
  130. return responses
  131. self.connection.last_error = 'error receiving response'
  132. if log_enabled(ERROR):
  133. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  134. raise LDAPSocketReceiveError(self.connection.last_error)
  135. def _get_response(self, message_id):
  136. """
  137. Performs the capture of LDAP response for SyncStrategy
  138. """
  139. ldap_responses = []
  140. response_complete = False
  141. while not response_complete:
  142. responses = self.receiving()
  143. if responses:
  144. for response in responses:
  145. if len(response) > 0:
  146. if self.connection.usage:
  147. self.connection._usage.update_received_message(len(response))
  148. if self.connection.fast_decoder:
  149. ldap_resp = decode_message_fast(response)
  150. dict_response = self.decode_response_fast(ldap_resp)
  151. else:
  152. ldap_resp, _ = decoder.decode(response, asn1Spec=LDAP_MESSAGE_TEMPLATE) # unprocessed unused because receiving() waits for the whole message
  153. dict_response = self.decode_response(ldap_resp)
  154. if log_enabled(EXTENDED):
  155. log(EXTENDED, 'ldap message received via <%s>:%s', self.connection, format_ldap_message(ldap_resp, '<<'))
  156. if int(ldap_resp['messageID']) == message_id:
  157. ldap_responses.append(dict_response)
  158. if dict_response['type'] not in ['searchResEntry', 'searchResRef', 'intermediateResponse']:
  159. response_complete = True
  160. elif int(ldap_resp['messageID']) == 0: # 0 is reserved for 'Unsolicited Notification' from server as per RFC4511 (paragraph 4.4)
  161. if dict_response['responseName'] == '1.3.6.1.4.1.1466.20036': # Notice of Disconnection as per RFC4511 (paragraph 4.4.1)
  162. return SESSION_TERMINATED_BY_SERVER
  163. elif dict_response['responseName'] == '2.16.840.1.113719.1.27.103.4': # Novell LDAP transaction error unsolicited notification
  164. return TRANSACTION_ERROR
  165. else:
  166. self.connection.last_error = 'unknown unsolicited notification from server'
  167. if log_enabled(ERROR):
  168. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  169. raise LDAPSocketReceiveError(self.connection.last_error)
  170. elif int(ldap_resp['messageID']) != message_id and dict_response['type'] == 'extendedResp':
  171. self.connection.last_error = 'multiple extended responses to a single extended request'
  172. if log_enabled(ERROR):
  173. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  174. raise LDAPExtensionError(self.connection.last_error)
  175. # pass # ignore message with invalid messageId when receiving multiple extendedResp. This is not allowed by RFC4511 but some LDAP server do it
  176. else:
  177. self.connection.last_error = 'invalid messageId received'
  178. if log_enabled(ERROR):
  179. log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  180. raise LDAPSocketReceiveError(self.connection.last_error)
  181. # response = unprocessed
  182. # if response: # if this statement is removed unprocessed data will be processed as another message
  183. # self.connection.last_error = 'unprocessed substrate error'
  184. # if log_enabled(ERROR):
  185. # log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
  186. # raise LDAPSocketReceiveError(self.connection.last_error)
  187. else:
  188. return SESSION_TERMINATED_BY_SERVER
  189. ldap_responses.append(RESPONSE_COMPLETE)
  190. return ldap_responses
  191. def set_stream(self, value):
  192. raise NotImplementedError
  193. def get_stream(self):
  194. raise NotImplementedError