text: # Copyright (c) 2013-2018 CORE Security Technologies
  #
  # This software is provided under under a slightly modified version
  # of the Apache Software License. See the accompanying LICENSE file
  # for more information.
  #
  # Structures and types used in LDAP
  # Contains the Structures for the NT Security Descriptor (non-RPC format) and
  # all ACL related structures
  #
  # Author:
  #  Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com)
  #
  #
  from struct import unpack, pack
  from impacket.structure import Structure
  from impacket.uuid import string_to_bin
  
  # Global constant if the library should recalculate ACE sizes in objects that are decoded/re-encoded.
  # This defaults to True, but this causes the ACLs to not match on a binary level
  # since Active Directory for some reason sometimes adds null bytes to the end of ACEs.
  # This is valid according to the spec (see 2.4.4), but since impacket encodes them more efficiently
  # this should be turned off if running unit tests.
  RECALC_ACE_SIZE = True
  
  # LDAP SID structure - based on SAMR_RPC_SID, except the SubAuthority is LE here
  class LDAP_SID_IDENTIFIER_AUTHORITY(Structure):
      structure = (
          ('Value','6s'),
      )
  
  class LDAP_SID(Structure):
      structure = (
          ('Revision','<B'),
          ('SubAuthorityCount','<B'),
          ('IdentifierAuthority',':',LDAP_SID_IDENTIFIER_AUTHORITY),
          ('SubLen','_-SubAuthority','self["SubAuthorityCount"]*4'),
          ('SubAuthority',':'),
      )
  
      def formatCanonical(self):
          ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority']['Value'][5]))
          for i in range(self['SubAuthorityCount']):
              ans += '-%d' % ( unpack('<L',self['SubAuthority'][i*4:i*4+4])[0])
          return ans
  
      def fromCanonical(self, canonical):
          items = canonical.split('-')
          self['Revision'] = int(items[1])
          self['IdentifierAuthority'] = LDAP_SID_IDENTIFIER_AUTHORITY()
          self['IdentifierAuthority']['Value'] = '\x00\x00\x00\x00\x00' + pack('B',int(items[2]))
          self['SubAuthorityCount'] = len(items) - 3
          self['SubAuthority'] = ''
          for i in range(self['SubAuthorityCount']):
              self['SubAuthority'] += pack('<L', int(items[i+3]))
  
  """
  Self-relative security descriptor as described in 2.4.6
  https://msdn.microsoft.com/en-us/library/cc230366.aspx
  """
  class SR_SECURITY_DESCRIPTOR(Structure):
      structure = (
          ('Revision','c'),
          ('Sbz1','c'),
          ('Control','<H'),
          ('OffsetOwner','<L'),
          ('OffsetGroup','<L'),
          ('OffsetSacl','<L'),
          ('OffsetDacl','<L'),
          ('Sacl',':'),
          ('Dacl',':'),
          ('OwnerSid',':'),
          ('GroupSid',':'),
      )
  
      def fromString(self, data):
          Structure.fromString(self, data)
          # All these fields are optional, if the offset is 0 they are empty
          # there are also flags indicating if they are present
          # TODO: parse those if it adds value
          if self['OffsetOwner'] != 0:
              self['OwnerSid'] = LDAP_SID(data=data[self['OffsetOwner']:])
          else:
              self['OwnerSid'] = ''
  
          if self['OffsetGroup'] != 0:
              self['GroupSid'] = LDAP_SID(data=data[self['OffsetGroup']:])
          else:
              self['GroupSid'] = ''
  
          if self['OffsetSacl'] != 0:
              self['Sacl'] = ACL(data=data[self['OffsetSacl']:])
          else:
              self['Sacl'] = ''
  
          if self['OffsetDacl'] != 0:
              self['Dacl'] = ACL(data=data[self['OffsetDacl']:])
          else:
              self['Sacl'] = ''
  
      def getData(self):
          headerlen = 20
          # Reconstruct the security descriptor
          # flags are currently not set automatically
          # TODO: do this?
          datalen = 0
          if self['Sacl'] != '':
              self['OffsetSacl'] = headerlen + datalen
              datalen += len(self['Sacl'].getData())
          else:
              self['OffsetSacl'] = 0
  
          if self['Dacl'] != '':
              self['OffsetDacl'] = headerlen + datalen
              datalen += len(self['Dacl'].getData())
          else:
              self['OffsetDacl'] = 0
  
          if self['OwnerSid'] != '':
              self['OffsetOwner'] = headerlen + datalen
              datalen += len(self['OwnerSid'].getData())
          else:
              self['OffsetOwner'] = 0
  
          if self['GroupSid'] != '':
              self['OffsetGroup'] = headerlen + datalen
              datalen += len(self['GroupSid'].getData())
          else:
              self['OffsetGroup'] = 0
          return Structure.getData(self)
  
  """
  ACE as described in 2.4.4
  https://msdn.microsoft.com/en-us/library/cc230295.aspx
  """
  class ACE(Structure):
      # Flag constants
      CONTAINER_INHERIT_ACE       = 0x01
      FAILED_ACCESS_ACE_FLAG      = 0x80
      INHERIT_ONLY_ACE            = 0x08
      INHERITED_ACE               = 0x10
      NO_PROPAGATE_INHERIT_ACE    = 0x04
      OBJECT_INHERIT_ACE          = 0x01
      SUCCESSFUL_ACCESS_ACE_FLAG  = 0x40
  
      structure = (
          #
          # ACE_HEADER as described in 2.4.4.1
          # https://msdn.microsoft.com/en-us/library/cc230296.aspx
          #
          ('AceType','B'),
          ('AceFlags','B'),
          ('AceSize','<H'),
          # Virtual field to calculate data length from AceSize
          ('AceLen', '_-Ace', 'self["AceSize"]-4'),
          #
          # ACE body, is parsed depending on the type
          #
          ('Ace',':')
      )
  
      def fromString(self, data):
          # This will parse the header
          Structure.fromString(self, data)
          # Now we parse the ACE body according to its type
          self['TypeName'] = ACE_TYPE_MAP[self['AceType']].__name__
          self['Ace'] = ACE_TYPE_MAP[self['AceType']](data=self['Ace'])
  
      def getData(self):
          if RECALC_ACE_SIZE or 'AceSize' not in self.fields:
              self['AceSize'] = len(self['Ace'].getData())+4 # Header size (4 bytes) is included
          if self['AceSize'] % 4 != 0:
              # Make sure the alignment is correct
              self['AceSize'] += self['AceSize'] % 4
          data = Structure.getData(self)
          # For some reason ACEs are sometimes longer than they need to be
          # we fill this space up with null bytes to make sure the object
          # we create is identical to the original object
          if len(data) < self['AceSize']:
              data += '\x00' * (self['AceSize'] - len(data))
          return data
  
      def hasFlag(self, flag):
          return self['AceFlags'] & flag == flag
  
  """
  ACCESS_MASK as described in 2.4.3
  https://msdn.microsoft.com/en-us/library/cc230294.aspx
  """
  class ACCESS_MASK(Structure):
      # Flag constants
      GENERIC_READ            = 0x80000000L
      GENERIC_WRITE           = 0x04000000L
      GENERIC_EXECUTE         = 0x20000000L
      GENERIC_ALL             = 0x10000000L
      MAXIMUM_ALLOWED         = 0x02000000L
      ACCESS_SYSTEM_SECURITY  = 0x01000000L
      SYNCHRONIZE             = 0x00100000L
      WRITE_OWNER             = 0x00080000L
      WRITE_DACL              = 0x00040000L
      READ_CONTROL            = 0x00020000L
      DELETE                  = 0x00010000L
  
      structure = (
          ('Mask', '<L'),
      )
  
      def hasPriv(self, priv):
          return self['Mask'] & priv == priv
  
      def setPriv(self, priv):
          self['Mask'] |= priv
  
      def removePriv(self, priv):
          self['Mask'] ^= priv
  
  """
  ACCESS_ALLOWED_ACE as described in 2.4.4.2
  https://msdn.microsoft.com/en-us/library/cc230286.aspx
  """
  class ACCESS_ALLOWED_ACE(Structure):
      ACE_TYPE = 0x00
      structure = (
          ('Mask', ':', ACCESS_MASK),
          ('Sid', ':', LDAP_SID)
      )
  
  """
  ACCESS_ALLOWED_OBJECT_ACE as described in 2.4.4.3
  https://msdn.microsoft.com/en-us/library/cc230289.aspx
  """
  class ACCESS_ALLOWED_OBJECT_ACE(Structure):
      ACE_TYPE = 0x05
  
      # Flag contstants
      ACE_OBJECT_TYPE_PRESENT             = 0x01
      ACE_INHERITED_OBJECT_TYPE_PRESENT   = 0x02
  
      # ACE type specific mask constants
      # Note that while not documented, these also seem valid
      # for ACCESS_ALLOWED_ACE types
      ADS_RIGHT_DS_CONTROL_ACCESS         = 0x00000100
      ADS_RIGHT_DS_CREATE_CHILD           = 0x00000001
      ADS_RIGHT_DS_DELETE_CHILD           = 0x00000002
      ADS_RIGHT_DS_READ_PROP              = 0x00000010
      ADS_RIGHT_DS_WRITE_PROP             = 0x00000020
      ADS_RIGHT_DS_SELF                   = 0x00000008
  
  
      structure = (
          ('Mask', ':', ACCESS_MASK),
          ('Flags', '<L'),
          # Optional field
          ('ObjectTypeLen','_-ObjectType','self.checkObjectType(self["Flags"])'),
          ('ObjectType', ':=""'),
          # Optional field
          ('InheritedObjectTypeLen','_-InheritedObjectType','self.checkInheritedObjectType(self["Flags"])'),
          ('InheritedObjectType', ':=""'),
          ('Sid', ':', LDAP_SID)
      )
  
      @staticmethod
      def checkInheritedObjectType(flags):
          if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT:
              return 16
          return 0
  
      @staticmethod
      def checkObjectType(flags):
          if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT:
              return 16
          return 0
  
      def getData(self):
          # Set the correct flags
          if self['ObjectType'] != '':
              self['Flags'] |= self.ACE_OBJECT_TYPE_PRESENT
          if self['InheritedObjectType'] != '':
              self['Flags'] |= self.ACE_INHERITED_OBJECT_TYPE_PRESENT
          return Structure.getData(self)
  
      def hasFlag(self, flag):
          return self['Flags'] & flag == flag
  
  """
  ACCESS_DENIED_ACE as described in 2.4.4.4
  https://msdn.microsoft.com/en-us/library/cc230291.aspx
  Structure is identical to ACCESS_ALLOWED_ACE
  """
  class ACCESS_DENIED_ACE(ACCESS_ALLOWED_ACE):
      ACE_TYPE = 0x01
  
  """
  ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.5
  https://msdn.microsoft.com/en-us/library/gg750297.aspx
  Structure is identical to ACCESS_ALLOWED_OBJECT_ACE
  """
  class ACCESS_DENIED_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE):
      ACE_TYPE = 0x06
  
  """
  ACCESS_ALLOWED_CALLBACK_ACE as described in 2.4.4.6
  https://msdn.microsoft.com/en-us/library/cc230287.aspx
  """
  class ACCESS_ALLOWED_CALLBACK_ACE(Structure):
      ACE_TYPE = 0x09
      structure = (
          ('Mask', ':', ACCESS_MASK),
          ('Sid', ':', LDAP_SID),
          ('ApplicationData', ':')
      )
  
  """
  ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.7
  https://msdn.microsoft.com/en-us/library/cc230292.aspx
  Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE
  """
  class ACCESS_DENIED_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE):
      ACE_TYPE = 0x0A
  
  """
  ACCESS_ALLOWED_CALLBACK_OBJECT_ACE as described in 2.4.4.8
  https://msdn.microsoft.com/en-us/library/cc230288.aspx
  """
  class ACCESS_ALLOWED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE):
      ACE_TYPE = 0x0B
      structure = (
          ('Mask', ':', ACCESS_MASK),
          ('Flags', '<L'),
          # Optional field
          ('ObjectTypeLen','_-ObjectType','self.checkObjectType(self["Flags"])'),
          ('ObjectType', ':=""'),
          # Optional field
          ('InheritedObjectTypeLen','_-InheritedObjectType','self.checkInheritedObjectType(self["Flags"])'),
          ('InheritedObjectType', ':=""'),
          ('Sid', ':', LDAP_SID),
          ('ApplicationData', ':')
      )
  
  """
  ACCESS_DENIED_CALLBACK_OBJECT_ACE as described in 2.4.4.7
  https://msdn.microsoft.com/en-us/library/cc230292.aspx
  Structure is identical to ACCESS_ALLOWED_OBJECT_OBJECT_ACE
  """
  class ACCESS_DENIED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE):
      ACE_TYPE = 0x0C
  
  """
  SYSTEM_AUDIT_ACE as described in 2.4.4.10
  https://msdn.microsoft.com/en-us/library/cc230376.aspx
  Structure is identical to ACCESS_ALLOWED_ACE
  """
  class SYSTEM_AUDIT_ACE(ACCESS_ALLOWED_ACE):
      ACE_TYPE = 0x02
  
  
  """
  SYSTEM_AUDIT_OBJECT_ACE as described in 2.4.4.11
  https://msdn.microsoft.com/en-us/library/gg750298.aspx
  Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE
  """
  class SYSTEM_AUDIT_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE):
      ACE_TYPE = 0x07
  
  
  """
  SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.12
  https://msdn.microsoft.com/en-us/library/cc230377.aspx
  Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE
  """
  class SYSTEM_AUDIT_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE):
      ACE_TYPE = 0x0D
  
  """
  SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.13
  https://msdn.microsoft.com/en-us/library/cc230379.aspx
  Structure is identical to ACCESS_ALLOWED_ACE, but with custom masks and meanings.
  Lets keep it separate for now
  """
  class SYSTEM_MANDATORY_LABEL_ACE(Structure):
      ACE_TYPE = 0x11
      structure = (
          ('Mask', ':', ACCESS_MASK),
          ('Sid', ':', LDAP_SID)
      )
  
  """
  SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.14
  https://msdn.microsoft.com/en-us/library/cc230378.aspx
  Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE
  """
  class SYSTEM_AUDIT_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE):
      ACE_TYPE = 0x0F
  
  """
  SYSTEM_RESOURCE_ATTRIBUTE_ACE as described in 2.4.4.15
  https://msdn.microsoft.com/en-us/library/hh877837.aspx
  Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE
  The application data however is encoded in CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1
  format as described in section 2.4.10.1
  Todo: implement this substructure if needed
  """
  class SYSTEM_RESOURCE_ATTRIBUTE_ACE(ACCESS_ALLOWED_CALLBACK_ACE):
      ACE_TYPE = 0x12
  
  
  """
  SYSTEM_SCOPED_POLICY_ID_ACE as described in 2.4.4.16
  https://msdn.microsoft.com/en-us/library/hh877846.aspx
  Structure is identical to ACCESS_ALLOWED_ACE
  The Sid data MUST match a CAPID of a CentralAccessPolicy
  contained in the CentralAccessPoliciesList
  Todo: implement this substructure if needed
  Also the ACCESS_MASK must always be 0
  """
  class SYSTEM_SCOPED_POLICY_ID_ACE(ACCESS_ALLOWED_ACE):
      ACE_TYPE = 0x13
  
  # All the ACE types in a list
  ACE_TYPES = [
      ACCESS_ALLOWED_ACE,
      ACCESS_ALLOWED_OBJECT_ACE,
      ACCESS_DENIED_ACE,
      ACCESS_DENIED_OBJECT_ACE,
      ACCESS_ALLOWED_CALLBACK_ACE,
      ACCESS_DENIED_CALLBACK_ACE,
      ACCESS_ALLOWED_CALLBACK_OBJECT_ACE,
      ACCESS_DENIED_CALLBACK_OBJECT_ACE,
      SYSTEM_AUDIT_ACE,
      SYSTEM_AUDIT_OBJECT_ACE,
      SYSTEM_AUDIT_CALLBACK_ACE,
      SYSTEM_MANDATORY_LABEL_ACE,
      SYSTEM_AUDIT_CALLBACK_OBJECT_ACE,
      SYSTEM_RESOURCE_ATTRIBUTE_ACE,
      SYSTEM_SCOPED_POLICY_ID_ACE
  ]
  
  # A dict of all the ACE types indexed by their type number
  ACE_TYPE_MAP = {ace.ACE_TYPE: ace for ace in ACE_TYPES}
  
  """
  ACL as described in 2.4.5
  https://msdn.microsoft.com/en-us/library/cc230297.aspx
  """
  class ACL(Structure):
      structure = (
          ('AclRevision', 'B'),
          ('Sbz1', 'B'),
          ('AclSize', '<H'),
          ('AceCount', '<H'),
          ('Sbz2', '<H'),
          # Virtual field to calculate data length from AclSize
          ('DataLen', '_-Data', 'self["AclSize"]-8'),
          ('Data', ':'),
      )
  
      def fromString(self, data):
          self.aces = []
          Structure.fromString(self, data)
          for i in range(self['AceCount']):
              # If we don't have any data left, return
              if len(self['Data']) == 0:
                  raise Exception, "ACL header indicated there are more ACLs to unpack, but there is no more data"
              ace = ACE(data=self['Data'])
              self.aces.append(ace)
              self['Data'] = self['Data'][ace['AceSize']:]
          self['Data'] = self.aces
  
      def getData(self):
          self['AceCount'] = len(self.aces)
          # We modify the data field to be able to use the
          # parent class parsing
          self['Data'] = ''.join([ace.getData() for ace in self.aces])
          self['AclSize'] = len(self['Data'])+8 # Header size (8 bytes) is included
          data = Structure.getData(self)
          # Put the ACEs back in data
          self['Data'] = self.aces
          return data
  
  """
  objectClass mapping to GUID for some common classes (index is the ldapDisplayName).
  Reference:
      https://msdn.microsoft.com/en-us/library/ms680938(v=vs.85).aspx
  Can also be queried from the Schema
  """
  OBJECTTYPE_GUID_MAP = {
      'group': 'bf967a9c-0de6-11d0-a285-00aa003049e2',
      'domain': '19195a5a-6da0-11d0-afd3-00c04fd930c9',
      'organizationalUnit': 'bf967aa5-0de6-11d0-a285-00aa003049e2',
      'user': 'bf967aba-0de6-11d0-a285-00aa003049e2',
      'groupPolicyContainer': 'f30e3bc2-9ff0-11d1-b603-0000f80367c1'
  }
  