123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- """
- """
-
- # Created on 2013.07.15
- #
- # Author: Giovanni Cannata
- #
- # Copyright 2013 - 2018 Giovanni Cannata
- #
- # This file is part of ldap3.
- #
- # ldap3 is free software: you can redistribute it and/or modify
- # it under the terms of the GNU Lesser General Public License as published
- # by the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # ldap3 is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public License
- # along with ldap3 in the COPYING and COPYING.LESSER files.
- # If not, see <http://www.gnu.org/licenses/>.
-
- import socket
-
- from .. import SEQUENCE_TYPES, get_config_parameter
- from ..core.exceptions import LDAPSocketReceiveError, communication_exception_factory, LDAPExceptionError, LDAPExtensionError, LDAPOperationResult
- from ..strategy.base import BaseStrategy, SESSION_TERMINATED_BY_SERVER, RESPONSE_COMPLETE, TRANSACTION_ERROR
- from ..protocol.rfc4511 import LDAPMessage
- from ..utils.log import log, log_enabled, ERROR, NETWORK, EXTENDED, format_ldap_message
- from ..utils.asn1 import decoder, decode_message_fast
-
- LDAP_MESSAGE_TEMPLATE = LDAPMessage()
-
-
- # noinspection PyProtectedMember
- class SyncStrategy(BaseStrategy):
- """
- This strategy is synchronous. You send the request and get the response
- Requests return a boolean value to indicate the result of the requested Operation
- Connection.response will contain the whole LDAP response for the messageId requested in a dict form
- Connection.request will contain the result LDAP message in a dict form
- """
-
- def __init__(self, ldap_connection):
- BaseStrategy.__init__(self, ldap_connection)
- self.sync = True
- self.no_real_dsa = False
- self.pooled = False
- self.can_stream = False
- self.socket_size = get_config_parameter('SOCKET_SIZE')
-
- def open(self, reset_usage=True, read_server_info=True):
- BaseStrategy.open(self, reset_usage, read_server_info)
- if read_server_info:
- try:
- self.connection.refresh_server_info()
- except LDAPOperationResult: # catch errors from server if raise_exception = True
- self.connection.server._dsa_info = None
- self.connection.server._schema_info = None
-
- def _start_listen(self):
- if not self.connection.listening and not self.connection.closed:
- self.connection.listening = True
-
- def receiving(self):
- """
- Receive data over the socket
- Checks if the socket is closed
- """
- messages = []
- receiving = True
- unprocessed = b''
- data = b''
- get_more_data = True
- exc = None
- while receiving:
- if get_more_data:
- try:
- data = self.connection.socket.recv(self.socket_size)
- except (OSError, socket.error, AttributeError) as e:
- self.connection.last_error = 'error receiving data: ' + str(e)
- exc = e
-
- if exc:
- try: # try to close the connection before raising exception
- self.close()
- except (socket.error, LDAPExceptionError):
- pass
- if log_enabled(ERROR):
- log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
- raise communication_exception_factory(LDAPSocketReceiveError, exc)(self.connection.last_error)
-
- unprocessed += data
- if len(data) > 0:
- length = BaseStrategy.compute_ldap_message_size(unprocessed)
- if length == -1: # too few data to decode message length
- get_more_data = True
- continue
- if len(unprocessed) < length:
- get_more_data = True
- else:
- if log_enabled(NETWORK):
- log(NETWORK, 'received %d bytes via <%s>', len(unprocessed[:length]), self.connection)
- messages.append(unprocessed[:length])
- unprocessed = unprocessed[length:]
- get_more_data = False
- if len(unprocessed) == 0:
- receiving = False
- else:
- receiving = False
-
- if log_enabled(NETWORK):
- log(NETWORK, 'received %d ldap messages via <%s>', len(messages), self.connection)
- return messages
-
- def post_send_single_response(self, message_id):
- """
- Executed after an Operation Request (except Search)
- Returns the result message or None
- """
- responses, result = self.get_response(message_id)
- self.connection.result = result
- if result['type'] == 'intermediateResponse': # checks that all responses are intermediates (there should be only one)
- for response in responses:
- if response['type'] != 'intermediateResponse':
- self.connection.last_error = 'multiple messages received error'
- if log_enabled(ERROR):
- log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
- raise LDAPSocketReceiveError(self.connection.last_error)
-
- responses.append(result)
- return responses
-
- def post_send_search(self, message_id):
- """
- Executed after a search request
- Returns the result message and store in connection.response the objects found
- """
- responses, result = self.get_response(message_id)
- self.connection.result = result
- if isinstance(responses, SEQUENCE_TYPES):
- self.connection.response = responses[:] # copy search result entries
- return responses
-
- self.connection.last_error = 'error receiving response'
- if log_enabled(ERROR):
- log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
- raise LDAPSocketReceiveError(self.connection.last_error)
-
- def _get_response(self, message_id):
- """
- Performs the capture of LDAP response for SyncStrategy
- """
- ldap_responses = []
- response_complete = False
- while not response_complete:
- responses = self.receiving()
- if responses:
- for response in responses:
- if len(response) > 0:
- if self.connection.usage:
- self.connection._usage.update_received_message(len(response))
- if self.connection.fast_decoder:
- ldap_resp = decode_message_fast(response)
- dict_response = self.decode_response_fast(ldap_resp)
- else:
- ldap_resp, _ = decoder.decode(response, asn1Spec=LDAP_MESSAGE_TEMPLATE) # unprocessed unused because receiving() waits for the whole message
- dict_response = self.decode_response(ldap_resp)
- if log_enabled(EXTENDED):
- log(EXTENDED, 'ldap message received via <%s>:%s', self.connection, format_ldap_message(ldap_resp, '<<'))
- if int(ldap_resp['messageID']) == message_id:
- ldap_responses.append(dict_response)
- if dict_response['type'] not in ['searchResEntry', 'searchResRef', 'intermediateResponse']:
- response_complete = True
- elif int(ldap_resp['messageID']) == 0: # 0 is reserved for 'Unsolicited Notification' from server as per RFC4511 (paragraph 4.4)
- if dict_response['responseName'] == '1.3.6.1.4.1.1466.20036': # Notice of Disconnection as per RFC4511 (paragraph 4.4.1)
- return SESSION_TERMINATED_BY_SERVER
- elif dict_response['responseName'] == '2.16.840.1.113719.1.27.103.4': # Novell LDAP transaction error unsolicited notification
- return TRANSACTION_ERROR
- else:
- self.connection.last_error = 'unknown unsolicited notification from server'
- if log_enabled(ERROR):
- log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
- raise LDAPSocketReceiveError(self.connection.last_error)
- elif int(ldap_resp['messageID']) != message_id and dict_response['type'] == 'extendedResp':
- self.connection.last_error = 'multiple extended responses to a single extended request'
- if log_enabled(ERROR):
- log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
- raise LDAPExtensionError(self.connection.last_error)
- # pass # ignore message with invalid messageId when receiving multiple extendedResp. This is not allowed by RFC4511 but some LDAP server do it
- else:
- self.connection.last_error = 'invalid messageId received'
- if log_enabled(ERROR):
- log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
- raise LDAPSocketReceiveError(self.connection.last_error)
- # response = unprocessed
- # if response: # if this statement is removed unprocessed data will be processed as another message
- # self.connection.last_error = 'unprocessed substrate error'
- # if log_enabled(ERROR):
- # log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
- # raise LDAPSocketReceiveError(self.connection.last_error)
- else:
- return SESSION_TERMINATED_BY_SERVER
- ldap_responses.append(RESPONSE_COMPLETE)
-
- return ldap_responses
-
- def set_stream(self, value):
- raise NotImplementedError
-
- def get_stream(self):
- raise NotImplementedError
|