async def modify(self, entry, changes, controls=None): try: req = { 'object': entry.encode(), 'changes': encode_changes(changes) } br = {'modifyRequest': ModifyRequest(req)} msg = {'protocolOp': protocolOp(br)} if controls is not None: msg['controls'] = controls msg_id = await self.send_message(msg) results = await self.recv_message(msg_id) if isinstance(results[0], Exception): return False, results[0] for message in results: msg_type = message['protocolOp'].name message = message.native if msg_type == 'modifyResponse': if message['protocolOp']['resultCode'] != 'success': return False, Exception( 'Failed to add DN! LDAP error! Reason: %s Diag: %s' % (message['protocolOp']['resultCode'], message['protocolOp']['diagnosticMessage'])) return True, None except Exception as e: return False, e
async def delete(self, entry): """ Performs the delete operation. :param entry: The DN of the object to be deleted :type entry: str :return: A tuple of (True, None) on success or (False, Exception) on error. :rtype: (:class:`bool`, :class:`Exception`) """ try: br = {'delRequest': DelRequest(entry.encode())} msg = {'protocolOp': protocolOp(br)} msg_id = await self.send_message(msg) results = await self.recv_message(msg_id) if isinstance(results[0], Exception): return False, results[0] for message in results: msg_type = message['protocolOp'].name message = message.native if msg_type == 'delResponse': if message['protocolOp']['resultCode'] != 'success': return False, Exception( 'Failed to add DN! LDAP error! Reason: %s Diag: %s' % (message['protocolOp']['resultCode'], message['protocolOp']['diagnosticMessage'])) return True, None except Exception as e: return False, e
async def add(self, entry, attributes): try: req = { 'entry': entry.encode(), 'attributes': encode_attributes(attributes) } br = {'addRequest': AddRequest(req)} msg = {'protocolOp': protocolOp(br)} msg_id = await self.send_message(msg) results = await self.recv_message(msg_id) if isinstance(results[0], Exception): return False, results[0] for message in results: msg_type = message['protocolOp'].name message = message.native if msg_type == 'addResponse': if message['protocolOp']['resultCode'] != 'success': return False, Exception( 'Failed to add DN! LDAP error! Reason: %s Diag: %s' % (message['protocolOp']['resultCode'], message['protocolOp']['diagnosticMessage'])) return True, None except Exception as e: return False, e
async def get_serverinfo(self): if self.status != MSLDAPClientStatus.RUNNING: return None, Exception('Connection not running! Probably encountered an error') attributes = [ b'subschemaSubentry', b'dsServiceName', b'namingContexts', b'defaultNamingContext', b'schemaNamingContext', b'configurationNamingContext', b'rootDomainNamingContext', b'supportedControl', b'supportedLDAPVersion', b'supportedLDAPPolicies', b'supportedSASLMechanisms', b'dnsHostName', b'ldapServiceName', b'serverName', b'supportedCapabilities' ] filt = { 'present' : 'objectClass'.encode() } searchreq = { 'baseObject' : b'', 'scope': 0, 'derefAliases': 0, 'sizeLimit': 1, 'timeLimit': self.target.timeout - 1, 'typesOnly': False, 'filter': Filter(filt), 'attributes': attributes, } br = { 'searchRequest' : SearchRequest( searchreq )} msg = { 'protocolOp' : protocolOp(br)} msg_id = await self.send_message(msg) res = await self.recv_message(msg_id) res = res[0] if isinstance(res, Exception): return None, res #print('res') #print(res) return convert_attributes(res.native['protocolOp']['attributes']), None
async def modify(self, entry, changes, controls = None): """ Performs the modify operation. :param entry: The DN of the object whose attributes are to be modified :type entry: str :param changes: Describes the changes to be made on the object. Must be a dictionary of the following format: {'attribute': [('change_type', [value])]} :type changes: dict :param controls: additional controls to be passed in the query :type controls: List[class:`Control`] :return: A tuple of (True, None) on success or (False, Exception) on error. :rtype: (:class:`bool`, :class:`Exception`) """ try: req = { 'object' : entry.encode(), 'changes' : encode_changes(changes) } br = { 'modifyRequest' : ModifyRequest(req)} msg = { 'protocolOp' : protocolOp(br)} if controls is not None: msg['controls'] = controls msg_id = await self.send_message(msg) results = await self.recv_message(msg_id) if isinstance(results[0], Exception): return False, results[0] for message in results: msg_type = message['protocolOp'].name message = message.native if msg_type == 'modifyResponse': if message['protocolOp']['resultCode'] != 'success': return False, LDAPModifyException( entry, message['protocolOp']['resultCode'], message['protocolOp']['diagnosticMessage'] ) return True, None except Exception as e: return False, e
async def whoami(self): if self.status != MSLDAPClientStatus.RUNNING: return None, Exception( 'Connection not running! Probably encountered an error') ext = { 'requestName': b'1.3.6.1.4.1.4203.1.11.3', } br = {'extendedReq': ExtendedRequest(ext)} msg = {'protocolOp': protocolOp(br)} msg_id = await self.send_message(msg) res = await self.recv_message(msg_id) res = res[0] if isinstance(res, Exception): return None, res if res.native['protocolOp']['resultCode'] != 'success': return False, LDAPBindException( res['protocolOp']['resultCode'], res['protocolOp']['diagnosticMessage']) return res.native['protocolOp']['responseValue'].decode(), None
async def add(self, entry, attributes): """ Performs the add operation. :param entry: The DN of the object to be added :type entry: str :param attributes: Attributes to be used in the operation :type attributes: dict :return: A tuple of (True, None) on success or (False, Exception) on error. :rtype: (:class:`bool`, :class:`Exception`) """ try: req = { 'entry' : entry.encode(), 'attributes' : encode_attributes(attributes) } br = { 'addRequest' : AddRequest(req)} msg = { 'protocolOp' : protocolOp(br)} msg_id = await self.send_message(msg) results = await self.recv_message(msg_id) if isinstance(results[0], Exception): return False, results[0] for message in results: msg_type = message['protocolOp'].name message = message.native if msg_type == 'addResponse': if message['protocolOp']['resultCode'] != 'success': return False, LDAPAddException( entry, message['protocolOp']['resultCode'], message['protocolOp']['diagnosticMessage'] ) return True, None except Exception as e: return False, e
async def search(self, base, query, attributes, search_scope=2, size_limit=1000, types_only=False, derefAliases=0, timeLimit=None, controls=None, return_done=False): """ Performs the search operation. :param base: base tree on which the search should be performed :type base: str :param query: filter query that defines what should be searched for :type query: str :param attributes: a list of attributes to be included in the response :type attributes: List[str] :param search_scope: Specifies the search operation's scope. Default: 2 (Subtree) :type search_scope: int :param types_only: indicates whether the entries returned should include attribute types only or both types and values. Default: False (both) :type types_only: bool :param size_limit: Size limit of result elements per query. Default: 1000 :type size_limit: int :param derefAliases: Specifies the behavior on how aliases are dereferenced. Default: 0 (never) :type derefAliases: int :param timeLimit: Maximum time the search should take. If time limit reached the server SHOULD return an error :type timeLimit: int :param controls: additional controls to be passed in the query :type controls: dict :param return_done: Controls wether the final 'done' LDAP message should be returned, or just the actual results :type return_done: bool :return: Async generator which yields (`LDAPMessage`, None) tuple on success or (None, `Exception`) on error :rtype: Iterator[(:class:`LDAPMessage`, :class:`Exception`)] """ if self.status != MSLDAPClientStatus.RUNNING: yield None, Exception( 'Connection not running! Probably encountered an error') return try: if timeLimit is None: timeLimit = 600 #not sure flt = query_syntax_converter(query) searchreq = { 'baseObject': base.encode(), 'scope': search_scope, 'derefAliases': derefAliases, 'sizeLimit': size_limit, 'timeLimit': timeLimit, 'typesOnly': types_only, 'filter': flt, 'attributes': attributes, } br = {'searchRequest': SearchRequest(searchreq)} msg = {'protocolOp': protocolOp(br)} if controls is not None: msg['controls'] = controls msg_id = await self.send_message(msg) while True: results = await self.recv_message(msg_id) for message in results: msg_type = message['protocolOp'].name message = message.native if msg_type == 'searchResDone': #print(message) #print('BREAKING!') if return_done is True: yield (message, None) break elif msg_type == 'searchResRef': #TODO: Check if we need to deal with this further continue if return_done is True: yield (message, None) else: yield (convert_result(message['protocolOp']), None) else: continue break except Exception as e: yield (None, e)
async def bind(self): """ Performs the bind operation. This is where the authentication happens. Remember to call `connect` before this function! :return: A tuple of (True, None) on success or (False, Exception) on error. :rtype: (:class:`bool`, :class:`Exception`) """ logger.debug('BIND in progress...') try: if self.creds.auth_method == LDAPAuthProtocol.SICILY: data, to_continue, err = await self.auth.authenticate(None) if err is not None: return None, err auth = {'sicily_disco': b''} bindreq = { 'version': 3, 'name': 'NTLM'.encode(), 'authentication': AuthenticationChoice(auth), } br = {'bindRequest': BindRequest(bindreq)} msg = {'protocolOp': protocolOp(br)} msg_id = await self.send_message(msg) res = await self.recv_message(msg_id) res = res[0] if isinstance(res, Exception): return False, res res = res.native if res['protocolOp']['resultCode'] != 'success': return False, Exception( 'BIND failed! Result code: "%s" Reason: "%s"' % (res['protocolOp']['resultCode'], res['protocolOp']['diagnosticMessage'])) auth = {'sicily_nego': data} bindreq = { 'version': 3, 'name': 'NTLM'.encode(), 'authentication': AuthenticationChoice(auth), } br = {'bindRequest': BindRequest(bindreq)} msg = {'protocolOp': protocolOp(br)} msg_id = await self.send_message(msg) res = await self.recv_message(msg_id) res = res[0] if isinstance(res, Exception): return False, res res = res.native if res['protocolOp']['resultCode'] != 'success': return False, Exception( 'BIND failed! Result code: "%s" Reason: "%s"' % (res['protocolOp']['resultCode'], res['protocolOp']['diagnosticMessage'])) data, to_continue, err = await self.auth.authenticate( res['protocolOp']['matchedDN']) if err is not None: return None, err auth = {'sicily_resp': data} bindreq = { 'version': 3, 'name': 'NTLM'.encode(), 'authentication': AuthenticationChoice(auth), } br = {'bindRequest': BindRequest(bindreq)} msg = {'protocolOp': protocolOp(br)} msg_id = await self.send_message(msg) res = await self.recv_message(msg_id) res = res[0] if isinstance(res, Exception): return False, res res = res.native if res['protocolOp']['resultCode'] != 'success': return False, Exception( 'BIND failed! Result code: "%s" Reason: "%s"' % (res['protocolOp']['resultCode'], res['protocolOp']['diagnosticMessage'])) self.__bind_success() return True, None elif self.creds.auth_method == LDAPAuthProtocol.SIMPLE: pw = b'' if self.auth.password != None: pw = self.auth.password.encode() user = b'' if self.auth.username != None: user = self.auth.username.encode() auth = {'simple': pw} bindreq = { 'version': 3, 'name': user, 'authentication': AuthenticationChoice(auth), } br = {'bindRequest': BindRequest(bindreq)} msg = {'protocolOp': protocolOp(br)} msg_id = await self.send_message(msg) res = await self.recv_message(msg_id) res = res[0] if isinstance(res, Exception): return False, res res = res.native if res['protocolOp']['resultCode'] == 'success': self.__bind_success() return True, None else: return False, Exception( 'BIND failed! Result code: "%s" Reason: "%s"' % (res['protocolOp']['resultCode'], res['protocolOp']['diagnosticMessage'])) elif self.creds.auth_method in MSLDAP_GSS_METHODS: challenge = None while True: try: data, to_continue, err = await self.auth.authenticate( challenge, cb_data=self.cb_data) if err is not None: raise err except Exception as e: return False, e sasl = { 'mechanism': 'GSS-SPNEGO'.encode(), 'credentials': data, } auth = {'sasl': SaslCredentials(sasl)} bindreq = { 'version': 3, 'name': b'', 'authentication': AuthenticationChoice(auth), } br = {'bindRequest': BindRequest(bindreq)} msg = {'protocolOp': protocolOp(br)} msg_id = await self.send_message(msg) res = await self.recv_message(msg_id) res = res[0] if isinstance(res, Exception): return False, res res = res.native if res['protocolOp']['resultCode'] == 'success': if 'serverSaslCreds' in res['protocolOp']: data, _, err = await self.auth.authenticate( res['protocolOp']['serverSaslCreds'], cb_data=self.cb_data) if err is not None: return False, err self.encryption_sequence_counter = self.auth.get_seq_number( ) self.__bind_success() return True, None elif res['protocolOp'][ 'resultCode'] == 'saslBindInProgress': challenge = res['protocolOp']['serverSaslCreds'] continue else: return False, Exception( 'BIND failed! Result code: "%s" Reason: "%s"' % (res['protocolOp']['resultCode'], res['protocolOp']['diagnosticMessage'])) else: raise Exception('Not implemented authentication method: %s' % self.creds.auth_method.name) except Exception as e: return False, e
async def search(self, base, filter, attributes, search_scope=2, paged_size=1000, typesOnly=False, derefAliases=0, timeLimit=None, controls=None, return_done=False): """ This function is a generator!!!!! Dont just call it but use it with "async for" """ if self.status != MSLDAPClientStatus.RUNNING: yield None, Exception( 'Connection not running! Probably encountered an error') return try: if timeLimit is None: timeLimit = 600 #not sure searchreq = { 'baseObject': base, 'scope': search_scope, 'derefAliases': derefAliases, 'sizeLimit': paged_size, 'timeLimit': timeLimit, 'typesOnly': typesOnly, 'filter': filter, 'attributes': attributes, } br = {'searchRequest': SearchRequest(searchreq)} msg = {'protocolOp': protocolOp(br)} if controls is not None: msg['controls'] = controls msg_id = await self.send_message(msg) while True: results = await self.recv_message(msg_id) for message in results: msg_type = message['protocolOp'].name message = message.native if msg_type == 'searchResDone': #print(message) #print('BREAKING!') if return_done is True: yield (message, None) break elif msg_type == 'searchResRef': #TODO: Check if we need to deal with this further continue if return_done is True: yield (message, None) else: yield (convert_result(message['protocolOp']), None) else: continue break except Exception as e: yield (None, e)