async def get_all_schemaentry(self): """ Fetches all Schema entries under CN=Schema,CN=Configuration,... :return: Async generator which yields (`MSADSchemaEntry`, None) tuple on success or (None, `Exception`) on error :rtype: Iterator[(:class:`MSADSchemaEntry`, :class:`Exception`)] """ res = await self.get_tree_plot('CN=Schema,CN=Configuration,' + self._tree, level=1) for x in res: for dn in res[x]: async for entry, err in self._con.pagedsearch( dn, r'(distinguishedName=%s)' % escape_filter_chars(dn), attributes=[x.encode() for x in MSADSCHEMAENTRY_ATTRS], size_limit=self.ldap_query_page_size, search_scope=BASE, controls=None, ): if err is not None: yield None, err return yield MSADSchemaEntry.from_ldap(entry), None break else: yield None, None logger.debug('Finished polling for entries!')
async def get_all_service_users(self, include_machine=False): """ Fetches all service user objects from the AD, and returns MSADUser object. Service user refers to an user with SPN (servicePrincipalName) attribute set :param include_machine: Specifies wether machine accounts should be included in the query :type include_machine: bool :return: Async generator which yields (`MSADUser`, None) tuple on success or (None, `Exception`) on error :rtype: Iterator[(:class:`MSADUser`, :class:`Exception`)] """ logger.debug( 'Polling AD for all user objects, machine accounts included: %s' % include_machine) if include_machine == True: ldap_filter = r'(servicePrincipalName=*)' else: ldap_filter = r'(&(servicePrincipalName=*)(!(sAMAccountName=*$)))' async for entry, err in self.pagedsearch(ldap_filter, MSADUser_ATTRS): if err is not None: yield None, err return yield MSADUser.from_ldap(entry, self._ldapinfo), None logger.debug('Finished polling for entries!')
async def handle_in_q(self): try: while True: preread = 6 lb = await asyncio.wait_for(self.reader.readexactly(preread), self.timeout) if lb is None: logger.debug('Server timed out!') return if lb == b'': logger.debug('Server finished!') return if self.is_plain_msg is True: remaining_length = calcualte_length(lb) - preread else: remaining_length = int.from_bytes(lb[:4], byteorder = 'big', signed = False) remaining_length = (remaining_length + 4) - preread #print('Reading %s' % remaining_length) remaining_data = await asyncio.wait_for(self.reader.readexactly(remaining_length), self.timeout) await self.in_queue.put((lb+remaining_data, None)) #except asyncio.CancelledError: # return except Exception as e: #logger.exception('handle_in_q') await self.in_queue.put((None, e)) finally: self.handle_out_task.cancel()
async def get_tree_plot(self, dn, level=2): """ Returns a dictionary representing a tree starting from 'dn' containing all subtrees. Parameters: dn (str): Distinguished name of the root of the tree level (int): Recursion level Returns: dict """ logger.debug('Tree, dn: %s level: %s' % (dn, level)) tree = {} async for entry, err in self._con.pagedsearch( dn.encode(), query_syntax_converter('(distinguishedName=*)'), attributes=[b'distinguishedName'], paged_size=self.ldap_query_page_size, search_scope=LEVEL, controls=None, ): if err is not None: raise err if level == 0: return {} #print(entry) #print(entry['attributes']['distinguishedName']) if 'distinguishedName' not in entry['attributes'] or entry[ 'attributes']['distinguishedName'] is None or entry[ 'attributes']['distinguishedName'] == []: continue subtree = await self.get_tree_plot( entry['attributes']['distinguishedName'], level=level - 1) tree[entry['attributes']['distinguishedName']] = subtree return {dn: tree}
async def handle_in_q(self): try: data = b'' while True: while True: msg_data = self.get_one_message(data) if msg_data is None: break await self.in_queue.put((msg_data, None)) data = data[len(msg_data):] temp, err = await self.proxy_in_queue.get() #print(temp) if err is not None: raise err if temp == b'' or temp is None: logger.debug('Server finished!') return data += temp continue except asyncio.CancelledError: return except Exception as e: logger.exception('handle_in_q') await self.in_queue.put((None, e)) finally: self.proxy_task.cancel()
async def get_all_spn_entries(self): logger.debug('Polling AD for all SPN entries') ldap_filter = r'(&(sAMAccountType=805306369))' attributes = ['objectSid', 'sAMAccountName', 'servicePrincipalName'] async for entry in self.pagedsearch(ldap_filter, attributes): yield entry
async def connect(self): """ Connects to the remote server. Establishes the session, but doesn't perform binding. This function MUST be called first before the `bind` operation. :return: A tuple of (True, None) on success or (False, Exception) on error. :rtype: (:class:`bool`, :class:`Exception`) """ try: logger.debug('Connecting!') self.network = await MSLDAPNetworkSelector.select(self.target) res, err = await self.network.run() if res is False: return False, err # now processing channel binding options if self.target.proto == LDAPProtocol.SSL: certdata = self.network.get_peer_certificate() #cert = Certificate.load(certdata).native #print(cert) cb_struct = ChannelBindingsStruct() cb_struct.application_data = b'tls-server-end-point:' + sha256( certdata).digest() self.cb_data = cb_struct.to_bytes() self.handle_incoming_task = asyncio.create_task( self.__handle_incoming()) logger.debug('Connection succsessful!') return True, None except Exception as e: return False, e
async def connect(self): try: logger.debug('Connecting!') self.network = await MSLDAPNetworkSelector.select(self.target) res, err = await self.network.run() if res is False: return False, err # now processing channel binding options if self.target.proto == LDAPProtocol.SSL: certdata = self.network.get_peer_certificate() #cert = Certificate.load(certdata).native #print(cert) cb_struct = ChannelBindingsStruct() cb_struct.application_data = b'tls-server-end-point:' + sha256( certdata).digest() self.cb_data = cb_struct.to_bytes() self.handle_incoming_task = asyncio.create_task( self.__handle_incoming()) logger.debug('Connection succsessful!') return True, None except Exception as e: return False, e
def get_tree_plot(self, dn, level=2): logger.debug('Tree, dn: %s level: %s' % (dn, level)) tree = {} entries = self._con.extend.standard.paged_search( dn, '(distinguishedName=*)', attributes='distinguishedName', paged_size=self.ldap_query_page_size, search_scope=LEVEL, controls=None) for entry in entries: if 'raw_attributes' in entry and 'attributes' in entry: # TODO: return ldapuser object if level == 0: return {} #print(entry['attributes']['distinguishedName']) if entry['attributes']['distinguishedName'] is None or entry[ 'attributes']['distinguishedName'] == []: continue subtree = self.get_tree_plot( entry['attributes']['distinguishedName'], level=level - 1) tree[entry['attributes']['distinguishedName']] = subtree return {dn: tree}
async def get_all_user_raw(self): """ Fetches all user objects from the AD, and returns MSADUser object """ logger.debug('Polling AD for all user objects') ldap_filter = r'(sAMAccountType=805306368)' return self.pagedsearch(ldap_filter, MSADUser_ATTRS)
async def pagedsearch(self, ldap_filter, attributes, controls=None): """ Performs a paged search on the AD, using the filter and attributes as a normal query does. Needs to connect to the server first! Parameters: ldap_filter (str): LDAP query filter attributes (list): Attributes list to recieve in the result controls (obj): Additional control dict Returns: generator """ logger.debug('Paged search, filter: %s attributes: %s' % (ldap_filter, ','.join(attributes))) if self._con.status != MSLDAPClientStatus.RUNNING: if self._con.status == MSLDAPClientStatus.ERROR: print('There was an error in the connection!') return elif self._con.status == MSLDAPClientStatus.ERROR: print('Theconnection is in stopped state!') return if self._tree is None: raise Exception('BIND first!') t = [] for x in attributes: t.append(x.encode()) attributes = t ldap_filter = query_syntax_converter(ldap_filter) t = [] if controls is not None: for control in controls: t.append( Control({ 'controlType': control[0].encode(), 'criticality': control[1], 'controlValue': control[2] })) controls = t async for entry, err in self._con.pagedsearch( self._tree.encode(), ldap_filter, attributes=attributes, paged_size=self.ldap_query_page_size, controls=controls): if err is not None: raise err if entry['objectName'] == '' and entry['attributes'] == '': #searchresref... continue #print('et %s ' % entry) yield entry
async def get_all_user_objects(self): """ Fetches all user objects from the AD, and returns MSADUser object """ logger.debug('Polling AD for all user objects') ldap_filter = r'(sAMAccountType=805306368)' async for entry in self.pagedsearch(ldap_filter, MSADUser_ATTRS): yield MSADUser.from_ldap(entry, self._ldapinfo) logger.debug('Finished polling for entries!')
async def get_user(self, sAMAccountName): """ Fetches one user object from the AD, based on the sAMAccountName attribute (read: username) """ logger.debug('Polling AD for user %s' % sAMAccountName) ldap_filter = r'(&(objectClass=user)(sAMAccountName=%s))' % sAMAccountName async for entry in self.pagedsearch(ldap_filter, MSADUser_ATTRS): # TODO: return ldapuser object yield MSADUser.from_ldap(entry, self._ldapinfo) logger.debug('Finished polling for entries!')
async def get_ad_info(self): """ Polls for basic AD information (needed for determine password usage characteristics!) """ logger.debug('Polling AD for basic info') ldap_filter = r'(distinguishedName=%s)' % self._tree async for entry in self.pagedsearch(ldap_filter, MSADInfo_ATTRS): self._ldapinfo = MSADInfo.from_ldap(entry) return self._ldapinfo logger.debug('Poll finished!')
def get_all_user_objects(self): """ Fetches all user objects from the AD, and returns MSADUser object """ logger.debug('Polling AD for all user objects') ldap_filter = r'(objectClass=user)' attributes = MSADUser.ATTRS for entry in self.pagedsearch(ldap_filter, attributes): yield MSADUser.from_ldap(entry, self._ldapinfo) logger.debug('Finished polling for entries!')
def get_all_machine_objects(self): """ Fetches all machine objects from the AD, and returns MSADMachine object """ logger.debug('Polling AD for all user objects') ldap_filter = r'(&(sAMAccountType=805306369))' attributes = MSADMachine.ATTRS for entry in self.pagedsearch(ldap_filter, attributes): yield MSADMachine.from_ldap(entry, self._ldapinfo) logger.debug('Finished polling for entries!')
def __bind_success(self): """ Internal function invoked after bind finished. Instructs the network layer that upcoming messages might be wrapped """ logger.debug('BIND Success!') self.bind_ok = True if self.creds.auth_method in MSLDAP_GSS_METHODS or self.creds.auth_method == LDAPAuthProtocol.SICILY: self.__sign_messages = self.auth.signing_needed() self.__encrypt_messages = self.auth.encryption_needed() if self.__encrypt_messages or self.__sign_messages: self.network.is_plain_msg = False
async def disconnect(self): """ Tears down the connection. :return: Nothing :rtype: None """ logger.debug('Disconnecting!') self.bind_ok = False self.handle_incoming_task.cancel() await self.network.terminate()
def convert_attributes(x): t = {} for e in x: #print(e) k = e['type'].decode() #print('k: %s' % k) if k in LDAP_ATTRIBUTE_TYPES: t[k] = LDAP_ATTRIBUTE_TYPES[k](e['attributes']) else: logger.debug('Unknown type! %s data: %s' % (k, e['attributes'])) t[k] = e['attributes'] return t
async def login(self): """Performs connection and login""" try: logger.debug(self.conn_url.get_credential()) logger.debug(self.conn_url.get_target()) self.connection = self.conn_url.get_client() _, err = await self.connection.connect() if err is not None: raise err return True, None except Exception as e: return False, e
async def recv_message(self, message_id): if message_id not in self.message_table_notify: logger.debug('Requested message id %s which is not in the message notify table!' % message_id) return None #print('Waiting for %s' % message_id) await self.message_table_notify[message_id].wait() #print(self.message_table) messages = self.message_table[message_id] #print('%s arrived!' % message_id) self.message_table[message_id] = [] self.message_table_notify[message_id].clear() return messages
async def get_ad_info(self): """ Polls for basic AD information (needed for determine password usage characteristics!) :return: A tuple with the domain information as `MSADInfo` and an `Exception` is there was any :rtype: (:class:`MSADInfo`, :class:`Exception`) """ logger.debug('Polling AD for basic info') ldap_filter = r'(distinguishedName=%s)' % self._tree async for entry, err in self.pagedsearch(ldap_filter, MSADInfo_ATTRS): if err is not None: return None, err self._ldapinfo = MSADInfo.from_ldap(entry) return self._ldapinfo, None logger.debug('Poll finished!')
async def get_all_users(self): """ Fetches all user objects available in the LDAP tree and yields them as MSADUser object. :return: Async generator which yields (`MSADUser`, None) tuple on success or (None, `Exception`) on error :rtype: Iterator[(:class:`MSADUser`, :class:`Exception`)] """ logger.debug('Polling AD for all user objects') ldap_filter = r'(sAMAccountType=805306368)' async for entry, err in self.pagedsearch(ldap_filter, MSADUser_ATTRS): if err is not None: yield None, err return yield MSADUser.from_ldap(entry, self._ldapinfo), None logger.debug('Finished polling for entries!')
def get_all_knoreq_user_objects(self, include_machine = False): """ Fetches all user objects with useraccountcontrol DONT_REQ_PREAUTH flag set from the AD, and returns MSADUser object. """ logger.debug('Polling AD for all user objects, machine accounts included: %s'% include_machine) if include_machine == True: ldap_filter = r'(userAccountControl:1.2.840.113556.1.4.803:=4194304)' else: ldap_filter = r'(&(userAccountControl:1.2.840.113556.1.4.803:=4194304)(!(sAMAccountName = *$)))' attributes = MSADUser.ATTRS for entry in self.pagedsearch(ldap_filter, attributes): # TODO: return ldapuser object yield MSADUser.from_ldap(entry, self._ldapinfo) logger.debug('Finished polling for entries!')
def get_all_service_user_objects(self, include_machine = False): """ Fetches all service user objects from the AD, and returns MSADUser object. Service user refers to an user whith SPN (servicePrincipalName) attribute set """ logger.debug('Polling AD for all user objects, machine accounts included: %s'% include_machine) if include_machine == True: ldap_filter = r'(servicePrincipalName=*)' else: ldap_filter = r'(&(servicePrincipalName=*)(!(sAMAccountName = *$)))' attributes = MSADUser.ATTRS for entry in self.pagedsearch(ldap_filter, attributes): # TODO: return ldapuser object yield MSADUser.from_ldap(entry, self._ldapinfo) logger.debug('Finished polling for entries!')
def connect(self): if self.login_credential.is_anonymous() == True: logger.debug( 'Getting server info via Anonymous BIND on server %s' % self.target_server.get_host()) self._srv = Server(self.target_server.get_host(), use_ssl=self.target_server.is_ssl(), get_info=ALL) self._con = Connection(self._srv, auto_bind=True) else: self._srv = Server(self.target_server.get_host(), use_ssl=self.target_server.is_ssl(), get_info=ALL) if self.login_credential.secret_type == MSLDAPSecretType.SSPI: self.monkeypatch() self._con = Connection( self._srv, user=self.login_credential.get_msuser(), password=self.login_credential.get_password(), authentication=self.login_credential.get_authmethod(), auto_bind=True) logger.debug('Performing BIND to server %s' % self.target_server.get_host()) if not self._con.bind(): if 'description' in self._con.result: raise Exception('Failed to bind to server! Reason: %s' % self._con.result['description']) raise Exception('Failed to bind to server! Reason: %s' % self._con.result) if not self._tree: logger.debug('Search tree base not defined, selecting root tree') info = self.get_server_info() if 'rootDomainNamingContext' not in info.other: #really rare cases, the DC doesnt reply to DSA requests!!! #in this case you will need to manually instruct the connection object on which tree it should perform the searches on raise Exception( 'Could not get the rootDomainNamingContext! You will need to specify the "tree" parameter manually!' ) self._tree = info.other['rootDomainNamingContext'][0] logger.debug('Selected tree: %s' % self._tree) logger.debug('Connected to server!')
def pagedsearch(self, ldap_filter, attributes, controls = None): """ Performs a paged search on the AD, using the filter and attributes as a normal query does. Needs to connect to the server first! ldap_filter: str : LDAP query filter attributes: list : Attributes list to recieve in the result """ logger.debug('Paged search, filter: %s attributes: %s' % (ldap_filter, ','.join(attributes))) ctr = 0 entries = self._con.extend.standard.paged_search(self._tree, ldap_filter, attributes = attributes, paged_size = self.ldap_query_page_size, controls = controls) for entry in entries: if 'raw_attributes' in entry and 'attributes' in entry: # TODO: return ldapuser object ctr += 1 if ctr % self.ldap_query_page_size == 0: logger.info('New page requested. Result count: %d' % ctr) yield entry
async def get_all_spn_entries(self): """ Fetches all service user objects from the AD, and returns MSADUser object. Service user refers to an user with SPN (servicePrincipalName) attribute set :param include_machine: Specifies wether machine accounts should be included in the query :type include_machine: bool :return: Async generator which yields tuples with a string in SPN format and an Exception if there was any :rtype: Iterator[(:class:`str`, :class:`Exception`)] """ logger.debug('Polling AD for all SPN entries') ldap_filter = r'(&(sAMAccountType=805306369))' attributes = ['objectSid', 'sAMAccountName', 'servicePrincipalName'] async for entry, err in self.pagedsearch(ldap_filter, attributes): yield entry, err
async def get_user(self, sAMAccountName): """ Fetches one user object from the AD, based on the sAMAccountName attribute (read: username) :param sAMAccountName: The username of the user. :type sAMAccountName: str :return: A tuple with the user as `MSADUser` and an `Exception` is there was any :rtype: (:class:`MSADUser`, :class:`Exception`) """ logger.debug('Polling AD for user %s' % sAMAccountName) ldap_filter = r'(&(objectClass=user)(sAMAccountName=%s))' % sAMAccountName async for entry, err in self.pagedsearch(ldap_filter, MSADUser_ATTRS): if err is not None: return None, err return MSADUser.from_ldap(entry, self._ldapinfo), None else: return None, None logger.debug('Finished polling for entries!')
async def handle_out_q(self): try: while True: data = await self.out_queue.get() if data is None: logger.debug('Client finished!') return self.writer.write(data) await self.writer.drain() except asyncio.CancelledError: return except: logger.exception('handle_out_q') finally: self.writer.close() self.handle_in_task.cancel()