1414from winacl .dtyp .ace import ACCESS_ALLOWED_OBJECT_ACE , ADS_ACCESS_MASK
1515from winacl .dtyp .guid import GUID
1616from winacl .dtyp .security_descriptor import SECURITY_DESCRIPTOR
17+ from winacl .dtyp .ace import ACCESS_ALLOWED_OBJECT_ACE
1718from winacl .dtyp .sid import SID
1819
1920from msldap import logger
@@ -70,6 +71,8 @@ def __init__(self, target:MSLDAPTarget, creds:UniCredential, connection = None,
7071 self .disconnected_evt = None
7172 self ._sid_cache = {} #SID -> (domain, user)
7273 self ._domainsid_cache = {} # SID -> domain
74+ self .domainname = None
75+ self .domainsid = None
7376
7477
7578 async def __aenter__ (self ):
@@ -138,7 +141,8 @@ async def connect(self):
138141 if err is not None :
139142 raise err
140143 self ._domainsid_cache [self ._ldapinfo .objectSid ] = self ._ldapinfo .name
141-
144+ self .domainname = self ._ldapinfo .name
145+ self .domainsid = self ._ldapinfo .objectSid
142146 if self .keepalive is True :
143147 self .__keepalive_task = asyncio .create_task (self .__keepalive (res ['defaultNamingContext' ]))
144148 return True , None
@@ -149,11 +153,13 @@ def get_server_info(self):
149153 return self ._serverinfo
150154
151155 async def get_domain_name (self ):
156+ if self .domainname is not None :
157+ return self .domainname , None
152158 self ._ldapinfo , err = await self .get_ad_info ()
153159 if err is not None :
154160 return None , err
155- domain = self ._ldapinfo .name
156- return domain , err
161+ self . domainname = self ._ldapinfo .name
162+ return self . domainname , err
157163
158164
159165 async def pagedsearch (self , query :str , attributes :List [str ], controls :List [Tuple [str , str , str ]] = None , tree :str = None , search_scope :int = 2 ):
@@ -719,6 +725,24 @@ async def get_user_by_dn(self, user_dn:str):
719725 if err is not None :
720726 return None , err
721727 return MSADUser .from_ldap (entry ), None
728+
729+ return None , Exception ('Search returned no results!' )
730+
731+ async def get_user_by_sid (self , sid :str ):
732+ """
733+ Fetches the DN for an object specified by `sid`
734+
735+ :param sid: The user's SID
736+ :type sid: str
737+ """
738+
739+ ldap_filter = r'(objectSid=%s)' % str (sid )
740+ async for entry , err in self .pagedsearch (ldap_filter , MSADUser_ATTRS ):
741+ if err is not None :
742+ return None , err
743+ return MSADUser .from_ldap (entry ), None
744+
745+ return None , Exception ('Search returned no results!' )
722746
723747 async def get_group_members (self , dn :str , recursive :bool = False ):
724748 """
@@ -963,6 +987,30 @@ async def create_user_dn(self, user_dn:str, password:str):
963987 except Exception as e :
964988 return False , e
965989
990+ async def create_broken_dmsa_user (self , user_dn :str , computer_sid :str ):
991+ """
992+ This will create a dmsa service user that can be used for neferious reasons, but DO NOT USE THIS FOR ANYTHING ELSE!
993+
994+ """
995+ try :
996+ sn = user_dn .split (',' )[0 ][3 :]
997+ attributes = {
998+ 'objectClass' : ['msDS-DelegatedManagedServiceAccount' ,'organizationalPerson' , 'person' , 'top' , 'user' , 'computer' ],
999+ 'sn' : sn ,
1000+ 'sAMAccountName' : sn ,
1001+ 'displayName' : sn ,
1002+ 'msDS-DelegatedMSAState' : 0 ,
1003+ 'msDS-ManagedPasswordInterval' : 30 ,
1004+ 'msDS-SupportedEncryptionTypes' : 28 ,
1005+ 'userAccountControl' : 4096 ,
1006+ 'msDS-GroupMSAMembership' : SECURITY_DESCRIPTOR .from_sddl ('O:S-1-5-32-544D:(A;;0xf01ff;;;%s)' % computer_sid .upper ())
1007+ }
1008+ _ , err = await self ._con .add (user_dn , attributes )
1009+ if err is not None :
1010+ return False , err
1011+ return True , None
1012+ except Exception as e :
1013+ return False , e
9661014
9671015 async def unlock_user (self , user_dn :str ):
9681016 """
@@ -1789,7 +1837,59 @@ async def dnsentries(self, zone = None, with_tombstones = False):
17891837 except Exception as e :
17901838 yield None , None , None , e
17911839
1792-
1840+ async def get_all_domain_controllers (self ):
1841+ """Lists all domain controllers in the forest"""
1842+ # domain controller is a machine account that has the flag MSLDAP_UAC.SERVER_TRUST_ACCOUNT
1843+ ldap_filter = r'(&(sAMAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=8192))'
1844+ async for entry , err in self .pagedsearch (ldap_filter , MSADMachine_ATTRS ):
1845+ if err is not None :
1846+ yield None , err
1847+ continue
1848+ yield MSADMachine .from_ldap (entry ), None
1849+
1850+ async def get_all_dmsas (self ):
1851+ """Lists all DGMAs in the forest"""
1852+ async for entry , err in self .pagedsearch (r'(objectClass=msDS-DelegatedManagedServiceAccount)' , MSADDMSAUser_ATTRS ):
1853+ if err is not None :
1854+ raise err
1855+ yield MSADDMSAUser .from_ldap (entry ), None
1856+
1857+ async def get_obj_by_dn (self , dn :str , class_type :str ):
1858+ """
1859+ Fetches one object from the AD, based on the DN attribute
1860+
1861+ :param dn: The DN of the object.
1862+ :type dn: str
1863+ :param class_type: The class of the object.
1864+ :type class_type: str
1865+ :return: A tuple with the object and an `Exception` is there was any
1866+ :rtype: (:class:`MSADUser`, :class:`Exception`)
1867+ """
1868+ if class_type == 'user' :
1869+ oc = MSADUser
1870+ elif class_type == 'computer' :
1871+ oc = MSADMachine
1872+ elif class_type == 'group' :
1873+ oc = MSADGroup
1874+ elif class_type == 'container' :
1875+ oc = MSADContainer
1876+ elif class_type == 'dmsa' :
1877+ oc = MSADDMSAUser
1878+ else :
1879+ oc = None
1880+
1881+ logger .debug ('Polling AD for object %s' % dn )
1882+ ldap_filter = r'(distinguishedName=%s)' % dn
1883+ async for entry , err in self .pagedsearch (ldap_filter , MSADUser_ATTRS ):
1884+ if err is not None :
1885+ return None , err
1886+ if oc is not None :
1887+ return oc .from_ldap (entry , self ._ldapinfo ), None
1888+ else :
1889+ return entry , None
1890+ else :
1891+ return None , None
1892+ logger .debug ('Finished polling for entries!' )
17931893
17941894 #async def get_permissions_for_dn(self, dn):
17951895 # """
0 commit comments