Responder/tools/MultiRelay/impacket-dev/impacket/dcerpc/v5/nspi.py

1362 lines
38 KiB
Python

# SECUREAUTH LABS. Copyright 2020 SecureAuth Corporation. All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description:
# [MS-NSPI]: Name Service Provider Interface (NSPI) Protocol
# [MS-OXNSPI]: Exchange Server Name Service Provider Interface (NSPI) Protocol
#
# Authors:
# Arseniy Sharoglazov <mohemiv@gmail.com> / Positive Technologies (https://www.ptsecurity.com/)
#
# Tested for MS-OXNSPI, some operation may not work for MS-NSPI
#
# ToDo:
# [ ] Test commented NDRCALLs and write helpers for them
# [ ] Test restriction structures
from __future__ import division
from __future__ import print_function
from struct import unpack
from datetime import datetime
from six import PY2
import binascii
from impacket import hresult_errors, mapi_constants, uuid
from impacket.uuid import EMPTY_UUID
from impacket.structure import Structure
from impacket.dcerpc.v5.dtypes import NULL, STR, DWORD, LPDWORD, UUID, PUUID, LONG, ULONG, \
FILETIME, PFILETIME, BYTE, SHORT, LPSTR, LPWSTR, USHORT, LPLONG, DWORD_ARRAY
from impacket.ldap.ldaptypes import LDAP_SID
from impacket.dcerpc.v5.ndr import NDR, NDRCALL, NDRPOINTER, NDRSTRUCT, NDRUNION, \
NDRUniConformantVaryingArray, NDRUniConformantArray, NDRUniVaryingArray
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.uuid import string_to_bin, uuidtup_to_bin, EMPTY_UUID
MSRPC_UUID_NSPI = uuidtup_to_bin(('F5CC5A18-4264-101A-8C59-08002B2F8426', '56.0'))
class DCERPCSessionError(DCERPCException):
def __str__( self ):
key = self.error_code
if key in mapi_constants.ERROR_MESSAGES:
error_msg_short = mapi_constants.ERROR_MESSAGES[key]
return 'NSPI SessionError: code: 0x%x - %s' % (self.error_code, error_msg_short)
elif key in hresult_errors.ERROR_MESSAGES:
error_msg_short = hresult_errors.ERROR_MESSAGES[key][0]
error_msg_verbose = hresult_errors.ERROR_MESSAGES[key][1]
return 'NSPI SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
else:
return 'NSPI SessionError: unknown error code: 0x%x' % self.error_code
################################################################################
# STRUCTURES
################################################################################
class handle_t(NDRSTRUCT):
structure = (
('context_handle_attributes',ULONG),
('context_handle_uuid',UUID),
)
def __init__(self, data=None, isNDR64=False):
NDRSTRUCT.__init__(self, data, isNDR64)
self['context_handle_uuid'] = b'\x00'*16
def isNull(self):
return self['context_handle_uuid'] == b'\x00'*16
# 2.2.1 Permitted Property Type Values
PtypEmbeddedTable = 0x0000000D
PtypNull = 0x00000001
PtypUnspecified = 0x00000000
# 2.2.3 Display Type Values
DT_MAILUSER = 0x00000000
DT_DISTLIST = 0x00000001
DT_FORUM = 0x00000002
DT_AGENT = 0x00000003
DT_ORGANIZATION = 0x00000004
DT_PRIVATE_DISTLIST = 0x00000005
DT_REMOTE_MAILUSER = 0x00000006
DT_CONTAINER = 0x00000100
DT_TEMPLATE = 0x00000101
DT_ADDRESS_TEMPLATE = 0x00000102
DT_SEARCH = 0x00000200
# 2.2.4 Default Language Code Identifier
NSPI_DEFAULT_LOCALE = 0x00000409
# 2.2.5 Required Codepages
CP_TELETEX = 0x00004F25
CP_WINUNICODE = 0x000004B0
# 2.2.6.1 Comparison Flags
NORM_IGNORECASE = 1 << 0
NORM_IGNORENONSPACE = 1 << 1
NORM_IGNORESYMBOLS = 1 << 2
SORT_STRINGSORT = 1 << 12
NORM_IGNOREKANATYPE = 1 << 16
NORM_IGNOREWIDTH = 1 << 17
# 2.2.7 Permanent Entry ID GUID
GUID_NSPI = string_to_bin("C840A7DC-42C0-1A10-B4B9-08002B2FE182")
# 2.2.8 Positioning Minimal Entry IDs
MID_BEGINNING_OF_TABLE = 0x00000000
MID_END_OF_TABLE = 0x00000002
MID_CURRENT = 0x00000001
# 2.2.9 Ambiguous Name Resolution Minimal Entry IDs
MID_UNRESOLVED = 0x00000000
MID_AMBIGUOUS = 0x00000001
MID_RESOLVED = 0x00000002
# 2.2.10 Table Sort Orders
SortTypeDisplayName = 0
SortTypePhoneticDisplayName = 0x00000003
SortTypeDisplayName_RO = 0x000003E8
SortTypeDisplayName_W = 0x000003E9
# 2.2.11 NspiBind Flags
fAnonymousLogin = 0x00000020
# 2.2.12 Retrieve Property Flags
fSkipObjects = 0x00000001
fEphID = 0x00000002
# 2.2.13 NspiGetSpecialTable Flags
NspiAddressCreationTemplates = 0x00000002
NspiUnicodeStrings = 0x00000004
# 2.2.14 NspiQueryColumns Flags
NspiUnicodeProptypes = 0x80000000
# 2.2.15 NspiGetIDsFromNames Flags
NspiVerifyNames = 0x00000002
# 2.2.16 NspiGetTemplateInfo Flags
TI_TEMPLATE = 0x00000001
TI_SCRIPT = 0x00000004
TI_EMT = 0x00000010
TI_HELPFILE_NAME = 0x00000020
TI_HELPFILE_CONTENTS = 0x00000040
# 2.2.17 NspiModLinkAtt Flags
fDelete = 0x00000001
# 2.3.1.1 FlatUID_r
FlatUID_r = UUID
PFlatUID_r = PUUID
# 2.3.1.2 PropertyTagArray_r
class PropertyTagArray(NDRUniConformantVaryingArray):
item = DWORD
class PropertyTagArray_r(NDRSTRUCT):
structure = (
('cValues', ULONG),
('aulPropTag', PropertyTagArray)
)
class PPropertyTagArray_r(NDRPOINTER):
referent = (
('Data', PropertyTagArray_r),
)
# 2.3.1.3 Binary_r
class Binary(NDRUniConformantArray):
item = 'c'
class PBinary(NDRPOINTER):
referent = (
('Data', Binary),
)
class Binary_r(NDRSTRUCT):
structure = (
('cValues', DWORD),
('lpb', PBinary),
)
# 2.3.1.4 ShortArray_r
class ShortArray(NDRUniConformantArray):
item = SHORT
class PShortArray(NDRPOINTER):
referent = (
('Data', ShortArray),
)
class ShortArray_r(NDRSTRUCT):
structure = (
('cValues', DWORD),
('lpi', PShortArray),
)
# 2.3.1.5 LongArray_r
class LongArray(NDRUniConformantArray):
item = LONG
class PLongArray(NDRPOINTER):
referent = (
('Data', LongArray),
)
class LongArray_r(NDRSTRUCT):
structure = (
('cValues', DWORD),
('lpl', PLongArray)
)
# 2.3.1.6 StringArray_r
class StringArray(NDRUniConformantArray):
item = LPSTR
class PStringArray(NDRPOINTER):
referent = (
('Data', StringArray),
)
class StringArray_r(NDRSTRUCT):
structure = (
('cValues', DWORD),
('lppszA', PStringArray)
)
# 2.3.1.7 BinaryArray_r
class BinaryArray(NDRUniConformantArray):
item = Binary_r
class PBinaryArray(NDRPOINTER):
referent = (
('Data', BinaryArray),
)
class BinaryArray_r(NDRSTRUCT):
structure = (
('cValues', DWORD),
('lpbin', PBinaryArray)
)
# 2.3.1.8 FlatUIDArray_r
class FlatUIDArray(NDRUniConformantArray):
item = PFlatUID_r
class PFlatUIDArray(NDRPOINTER):
referent = (
('Data', FlatUIDArray),
)
class FlatUIDArray_r(NDRSTRUCT):
structure = (
('cValues', DWORD),
('lpguid', PFlatUIDArray)
)
# 2.3.1.9 WStringArray_r
class WStringArray(NDRUniConformantArray):
item = LPWSTR
class PWStringArray(NDRPOINTER):
referent = (
('Data', WStringArray),
)
class WStringArray_r(NDRSTRUCT):
structure = (
('cValues', DWORD),
('lppszW', PWStringArray)
)
# 2.3.1.10 DateTimeArray_r
class DateTimeArray(NDRUniConformantArray):
item = PFILETIME
class PDateTimeArray(NDRPOINTER):
referent = (
('Data', DateTimeArray),
)
class DateTimeArray_r(NDRSTRUCT):
structure = (
('cValues', DWORD),
('lpft', PDateTimeArray)
)
# 2.3.1.11 PROP_VAL_UNION
class PROP_VAL_UNION(NDRUNION):
commonHdr = (
('tag', DWORD),
)
union = {
0x0002: ('i', SHORT), # PtypInteger16
0x0003: ('l', LONG), # PtypInteger32
0x000B: ('b', USHORT), # PtypBoolean
0x001E: ('lpszA', LPSTR), # PtypString8
0x0102: ('bin', Binary_r), # PtypBinary
0x001F: ('lpszW', LPWSTR), # PtypString
0x0048: ('lpguid', PFlatUID_r), # PtypGuid
0x0040: ('ft', FILETIME), # PtypTime
0x000A: ('err', ULONG), # PtypErrorCode
0x1002: ('MVi', ShortArray_r), # PtypMultipleInteger16
0x1003: ('MVl', LongArray_r), # PtypMultipleInteger32
0x101E: ('MVszA', StringArray_r), # PtypMultipleString8
0x1102: ('MVbin', BinaryArray_r), # PtypMultipleBinary
0x1048: ('MVguid', FlatUIDArray_r), # PtypMultipleGuid
0x101F: ('MVszW', WStringArray_r), # PtypMultipleString
0x1040: ('MVft', DateTimeArray_r), # PtypMultipleTime
0x0001: ('lReserved', LONG), # PtypNull
0x000D: ('lReserved', LONG), # PtypEmbeddedTable
0x0000: ('lReserved', LONG), # PtypUnspecified
}
# 2.3.1.12 PropertyValue_r
class PropertyValue_r(NDRSTRUCT):
structure = (
('ulPropTag', DWORD),
('ulReserved', DWORD), # dwAlignPad
('Value', PROP_VAL_UNION),
)
class PPropertyValue_r(NDRPOINTER):
referent = (
('Data', PropertyValue_r),
)
# 2.3.2 PropertyRow_r
class PropertyValue(NDRUniConformantArray):
item = PropertyValue_r
class PPropertyValue(NDRPOINTER):
referent = (
('Data', PropertyValue),
)
class PropertyRow_r(NDRSTRUCT):
structure = (
('Reserved', DWORD), # ulAdrEntryPad
('cValues', DWORD),
('lpProps', PPropertyValue)
)
class PPropertyRow_r(NDRPOINTER):
referent = (
('Data', PropertyRow_r),
)
# 2.3.3 PropertyRowSet_r
class PropertyRowSet(NDRUniConformantArray):
item = PropertyRow_r
class PropertyRowSet_r(NDRSTRUCT):
structure = (
('cRows', DWORD),
('aRow', PropertyRowSet),
)
class PPropertyRowSet_r(NDRPOINTER):
referent = (
('Data', PropertyRowSet_r),
)
# 2.3.4 Restrictions
class Restriction_r(NDRSTRUCT):
pass
class PRestriction_r(NDRPOINTER):
referent = (
('Data', Restriction_r),
)
# 2.3.4.1 AndRestriction_r, OrRestriction_r
class AndRestriction(NDRUniConformantArray):
item = Restriction_r
class PAndRestriction(NDRPOINTER):
referent = (
('Data', AndRestriction),
)
class AndRestriction_r(NDRSTRUCT):
structure = (
('cRes', DWORD),
('lpRes', PAndRestriction),
)
OrRestriction_r = AndRestriction_r
# 2.3.4.2 NotRestriction_r
class NotRestriction_r(NDRSTRUCT):
structure = (
('lpRes', PRestriction_r),
)
# 2.3.4.3 ContentRestriction_r
class ContentRestriction_r(NDRSTRUCT):
structure = (
('ulFuzzyLevel', DWORD),
('ulPropTag', DWORD),
('lpProp', PPropertyValue_r),
)
# 2.3.4.4 BitMaskRestriction_r
class BitMaskRestriction_r(NDRSTRUCT):
structure = (
('relBMR', DWORD),
('ulPropTag', DWORD),
('ulMask', DWORD),
)
# 2.3.4.5 PropertyRestriction_r
class PropertyRestriction_r(NDRSTRUCT):
structure = (
('relop', DWORD),
('ulPropTag', DWORD),
('lpProp', PPropertyValue_r),
)
# 2.3.4.6 ComparePropsRestriction_r
class ComparePropsRestriction_r(NDRSTRUCT):
structure = (
('relop', DWORD),
('ulPropTag1', DWORD),
('ulPropTag2', DWORD),
)
# 2.3.4.7 SubRestriction_r
class SubRestriction_r(NDRSTRUCT):
structure = (
('ulSubObject', DWORD),
('lpRes', PRestriction_r),
)
# 2.3.4.8 SizeRestriction_r
class SizeRestriction_r(NDRSTRUCT):
structure = (
('relop', DWORD),
('ulPropTag', DWORD),
('cb', DWORD),
)
# 2.3.4.9 ExistRestriction_r
class ExistRestriction_r(NDRSTRUCT):
structure = (
('ulReserved1', DWORD),
('ulPropTag', DWORD),
('ulReserved2', DWORD),
)
# 2.3.4.10 RestrictionUnion_r
class RestrictionUnion_r(NDRUNION):
commonHdr = (
('tag', DWORD),
)
union = {
0x00000000: ('resAnd', AndRestriction_r),
0x00000001: ('resOr', OrRestriction_r),
0x00000002: ('resNot', NotRestriction_r),
0x00000003: ('resContent', ContentRestriction_r),
0x00000004: ('resProperty', PropertyRestriction_r),
0x00000005: ('resCompareProps', ComparePropsRestriction_r),
0x00000006: ('resBitMask', BitMaskRestriction_r),
0x00000007: ('resSize', SizeRestriction_r),
0x00000008: ('resExist', ExistRestriction_r),
0x00000009: ('resSubRestriction', SubRestriction_r),
}
# 2.3.4.11 Restriction_r
Restriction_r.structure = (
('rt', DWORD),
('res', RestrictionUnion_r),
)
# 2.3.5.1 PropertyName_r
class PropertyName_r(NDRSTRUCT):
structure = (
('lpguid', PFlatUID_r),
('ulReserved', DWORD),
('lID', LONG),
)
class PPropertyName_r(NDRPOINTER):
referent = (
('Data', PropertyName_r),
)
# 2.3.5.2 PropertyNameSet_r
class PropertyNameSet(NDRUniConformantArray):
item = PropertyName_r
class PropertyNameSet_r(NDRSTRUCT):
structure = (
('cNames', DWORD),
('aulPropTag', PropertyNameSet)
)
class PPropertyNameSet_r(NDRPOINTER):
referent = (
('Data', PropertyNameSet_r),
)
# 2.3.6.1 StringsArray_r
class StringsArray(NDRUniConformantArray):
item = LPSTR
class StringsArray_r(NDRSTRUCT):
structure = (
('Count', DWORD),
('Strings', StringsArray)
)
# 2.3.6.1 StringsArray_r
class WStringsArray(NDRUniConformantArray):
item = LPWSTR
class WStringsArray_r(NDRSTRUCT):
structure = (
('Count', DWORD),
('Strings', WStringsArray)
)
# 2.3.7 STAT
class STAT(NDRSTRUCT):
structure = (
('SortType', DWORD),
('ContainerID', DWORD),
('CurrentRec', DWORD),
('Delta', LONG),
('NumPos', DWORD),
('TotalRecs', DWORD),
('CodePage', DWORD),
('TemplateLocale', DWORD),
('SortLocale', DWORD),
)
class PSTAT(NDRPOINTER):
referent = (
('Data', STAT),
)
# 2.3.8.1 MinimalEntryID
MinEntryID = '<L=0'
# 2.3.8.2 EphemeralEntryID
class EphemeralEntryID(Structure):
structure = (
('IDType','<B=0x87'),
('R1','<B=0'),
('R2','<B=0'),
('R3','<B=0'),
('ProviderUID','16s=b"\\x00"*16'),
('R4','<L=0x0000001'),
('DisplayType','<L'),
('MId',MinEntryID),
)
# 2.3.8.3 PermanentEntryID
class PermanentEntryID(Structure):
default_guid = GUID_NSPI
structure = (
('IDType','<B=0'),
('R1','<B=0'),
('R2','<B=0'),
('R3','<B=0'),
('ProviderUID','16s=self["default_guid"]'),
('R4','<L=0x0000001'),
('DisplayType','<L'),
('DistinguishedName','z'),
)
def __str__(self):
return self["DistinguishedName"]
################################################################################
# RPC CALLS
################################################################################
# 3.1.4.1 RfrGetNewDSA (opnum 0)
class NspiBind(NDRCALL):
opnum = 0
structure = (
('dwFlags', DWORD),
('pStat', STAT),
('pServerGuid', PFlatUID_r),
)
class NspiBindResponse(NDRCALL):
structure = (
('pServerGuid', PFlatUID_r),
('contextHandle', handle_t),
('ErrorCode', ULONG),
)
# 3.1.4.2 NspiUnbind (Opnum 1)
class NspiUnbind(NDRCALL):
opnum = 1
structure = (
('contextHandle', handle_t),
('Reserved', DWORD), # flags
)
class NspiUnbindResponse(NDRCALL):
structure = (
('contextHandle', handle_t),
('ErrorCode', ULONG),
)
# 3.1.4.4 NspiUpdateStat (Opnum 2)
class NspiUpdateStat(NDRCALL):
opnum = 2
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('pStat', STAT),
('plDelta', LPLONG),
)
class NspiUpdateStatResponse(NDRCALL):
structure = (
('pStat', STAT),
('plDelta', LPLONG),
('ErrorCode', ULONG),
)
# 3.1.4.8 NspiQueryRows (Opnum 3)
class DWORD_ARRAY(NDRUniConformantArray):
item = DWORD
class PDWORD_ARRAY(NDRPOINTER):
referent = (
('Data', DWORD_ARRAY),
)
class NspiQueryRows(NDRCALL):
opnum = 3
structure = (
('hRpc', handle_t),
('dwFlags', DWORD),
('pStat', STAT),
('dwETableCount', DWORD),
('lpETable', PDWORD_ARRAY),
('Count', DWORD),
('pPropTags', PPropertyTagArray_r),
)
class NspiQueryRowsResponse(NDRCALL):
structure = (
('pStat', STAT),
('ppRows', PPropertyRowSet_r),
('ErrorCode', ULONG),
)
# 3.1.4.9 NspiSeekEntries (Opnum 4)
class NspiSeekEntries(NDRCALL):
opnum = 4
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('pStat', STAT),
('pTarget', PropertyValue_r),
('lpETable', PropertyTagArray_r),
('pPropTags', PropertyTagArray_r),
)
class NspiSeekEntriesResponse(NDRCALL):
structure = (
('pStat', STAT),
('ppRows', PPropertyRowSet_r),
('ErrorCode', ULONG),
)
# 3.1.4.10 NspiGetMatches (Opnum 5)
#class NspiGetMatches(NDRCALL):
# opnum = 5
# structure = (
# ('hRpc', handle_t),
# ('Reserved1', DWORD), # flags
# ('pStat', STAT),
# ('pReserved', PropertyTagArray_r), # mids
# ('Reserved2', DWORD), # interfaceOptions
# ('Filter', Restriction_r),
# ('lpPropName', PropertyName_r),
# ('ulRequested', DWORD),
# ('pPropTags', PropertyTagArray_r),
# )
#class NspiGetMatchesResponse(NDRCALL):
# structure = (
# ('pStat', PSTAT),
# ('ppOutMIds', PPropertyTagArray_r),
# ('ppRows', PPropertyRowSet_r),
# ('ErrorCode', ULONG),
# )
# 3.1.4.11 NspiResortRestriction (Opnum 6)
#class NspiResortRestriction(NDRCALL):
# opnum = 6
# structure = (
# ('hRpc', handle_t),
# ('Reserved', DWORD),
# ('pStat', STAT),
# ('pInMIds', PropertyTagArray_r),
# ('ppOutMIds', PPropertyTagArray_r),
# )
#class NspiResortRestrictionResponse(NDRCALL):
# structure = (
# ('pStat', PSTAT),
# ('ppOutMIds', PPropertyTagArray_r),
# ('ErrorCode', ULONG),
# )
# 3.1.4.13 NspiDNToMId (Opnum 7)
class NspiDNToMId(NDRCALL):
opnum = 7
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('pNames', StringsArray_r),
)
class NspiDNToMIdResponse(NDRCALL):
structure = (
('ppOutMIds', PPropertyTagArray_r),
('ErrorCode', ULONG),
)
# 3.1.4.6 NspiGetPropList (Opnum 8)
class NspiGetPropList(NDRCALL):
opnum = 8
structure = (
('hRpc', handle_t),
('dwFlags', DWORD),
('dwMId', DWORD),
('CodePage', DWORD),
)
class NspiGetPropListResponse(NDRCALL):
structure = (
('ppOutMIds', PPropertyTagArray_r),
('ErrorCode', ULONG),
)
# 3.1.4.7 NspiGetProps (Opnum 9)
class NspiGetProps(NDRCALL):
opnum = 9
structure = (
('hRpc', handle_t),
('dwFlags', DWORD),
('pStat', PSTAT),
('pPropTags', PPropertyTagArray_r),
)
class NspiGetPropsResponse(NDRCALL):
structure = (
('ppRows', PPropertyRow_r),
('ErrorCode', ULONG),
)
# 3.1.4.12 NspiCompareMIds (Opnum 10)
class NspiCompareMIds(NDRCALL):
opnum = 10
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('pStat', STAT),
('MId1', DWORD),
('MId2', DWORD),
)
class NspiCompareMIdsResponse(NDRCALL):
structure = (
('plResult', LONG),
('ErrorCode', ULONG),
)
# 3.1.4.14 NspiModProps (Opnum 11)
#class NspiModProps(NDRCALL):
# opnum = 11
# structure = (
# ('hRpc', handle_t),
# ('Reserved', DWORD), # flags
# ('pStat', STAT),
# ('pPropTags', PropertyTagArray_r),
# ('pRow', PropertyRow_r),
# )
#class NspiModPropsResponse(NDRCALL):
# structure = (
# ('plResult', LPLONG),
# ('ErrorCode', ULONG),
# )
# 3.1.4.3 NspiGetSpecialTable (Opnum 12)
class NspiGetSpecialTable(NDRCALL):
opnum = 12
structure = (
('hRpc', handle_t),
('dwFlags', DWORD),
('pStat', PSTAT),
('lpVersion', LPDWORD),
)
class NspiGetSpecialTableResponse(NDRCALL):
structure = (
# In Exchange 2013 / 2016 / 2019 lpVersion is
# a RuntimeHelpers.GetHashCode value, and it will be
# different each call
('lpVersion', DWORD),
('ppRows', PPropertyRowSet_r),
('ErrorCode', DWORD),
)
# 3.1.4.20 NspiGetTemplateInfo (Opnum 13)
class NspiGetTemplateInfo(NDRCALL):
opnum = 13
structure = (
('hRpc', handle_t),
('dwFlags', DWORD),
('ulType', DWORD),
('pDN', LPSTR),
('dwCodePage', DWORD),
('dwLocaleID', DWORD),
)
class NspiGetTemplateInfoResponse(NDRCALL):
structure = (
('ppData', PPropertyRow_r),
('ErrorCode', ULONG),
)
# 3.1.4.15 NspiModLinkAtt (Opnum 14)
class NspiModLinkAtt(NDRCALL):
opnum = 14
structure = (
('hRpc', handle_t),
('dwFlags', DWORD),
('ulPropTag', DWORD),
('dwMId', DWORD),
('lpEntryIds', BinaryArray_r),
)
class NspiModLinkAttResponse(NDRCALL):
structure = (
('ErrorCode', ULONG),
)
# Undocumented opnum 15
#class NspiDeleteEntries(NDRCALL):
# opnum = 15
# structure = (
# ('hRpc', handle_t),
# ('dwFlags', DWORD),
# ('dwMId', DWORD),
# ('entryIds', BinaryArray_r),
# )
#class NspiDeleteEntriesResponse(NDRCALL):
# structure = (
# ('ErrorCode', ULONG),
# )
# 3.1.4.5 NspiQueryColumns (Opnum 16)
class NspiQueryColumns(NDRCALL):
opnum = 16
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('dwFlags', DWORD), # mapiFlags
)
class NspiQueryColumnsResponse(NDRCALL):
structure = (
('ppColumns', PPropertyTagArray_r),
('ErrorCode', ULONG),
)
# 3.1.4.16 NspiGetNamesFromIDs (Opnum 17)
class NspiGetNamesFromIDs(NDRCALL):
opnum = 17
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('lpguid', PFlatUID_r),
('pPropTags', PPropertyTagArray_r),
)
class NspiGetNamesFromIDsResponse(NDRCALL):
structure = (
('ppReturnedPropTags', PPropertyTagArray_r),
('ppNames', PPropertyNameSet_r),
('ErrorCode', ULONG),
)
# 3.1.4.17 NspiGetIDsFromNames (Opnum 18)
class PropertyName_r_ARRAY(NDRUniConformantVaryingArray):
item = PropertyName_r
class NspiGetIDsFromNames(NDRCALL):
opnum = 18
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('dwFlags', DWORD), # mapiFlags
('cPropNames', DWORD),
('pNames', PropertyName_r_ARRAY),
)
class NspiGetIDsFromNamesResponse(NDRCALL):
structure = (
('ppPropTags', PPropertyTagArray_r),
('ErrorCode', ULONG),
)
# 3.1.4.18 NspiResolveNames (Opnum 19)
class NspiResolveNames(NDRCALL):
opnum = 19
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('pStat', STAT),
('pPropTags', PPropertyTagArray_r),
('paStr', StringsArray_r),
)
class NspiResolveNamesResponse(NDRCALL):
structure = (
('ppMIds', PPropertyTagArray_r),
('ppRows', PPropertyRowSet_r),
('ErrorCode', ULONG),
)
# 3.1.4.19 NspiResolveNamesW (Opnum 20)
class NspiResolveNamesW(NDRCALL):
opnum = 20
structure = (
('hRpc', handle_t),
('Reserved', DWORD), # flags
('pStat', STAT),
('pPropTags', PPropertyTagArray_r),
('paStr', WStringsArray_r),
)
class NspiResolveNamesWResponse(NDRCALL):
structure = (
('ppMIds', PPropertyTagArray_r),
('ppRows', PPropertyRowSet_r),
('ErrorCode', ULONG),
)
################################################################################
# OPNUMs and their corresponding structures
################################################################################
OPNUMS = {
0 : (NspiBind, NspiBindResponse),
1 : (NspiUnbind, NspiUnbindResponse),
2 : (NspiUpdateStat, NspiUpdateStatResponse),
3 : (NspiQueryRows, NspiQueryRowsResponse),
4 : (NspiSeekEntries, NspiSeekEntriesResponse),
# 5 : (NspiGetMatches, NspiGetMatchesResponse),
# 6 : (NspiResortRestriction, NspiResortRestrictionResponse),
7 : (NspiDNToMId, NspiDNToMIdResponse),
8 : (NspiGetPropList, NspiGetPropListResponse),
9 : (NspiGetProps, NspiGetPropsResponse),
10 : (NspiCompareMIds, NspiCompareMIdsResponse),
# 11 : (NspiModProps, NspiModPropsResponse),
12 : (NspiGetSpecialTable, NspiGetSpecialTableResponse),
13 : (NspiGetTemplateInfo, NspiGetTemplateInfoResponse),
14 : (NspiModLinkAtt, NspiModLinkAttResponse),
# 15 : (NspiDeleteEntries, NspiDeleteEntriesResponse),
16 : (NspiQueryColumns, NspiQueryColumnsResponse),
17 : (NspiGetNamesFromIDs, NspiGetNamesFromIDsResponse),
18 : (NspiGetIDsFromNames, NspiGetIDsFromNamesResponse),
19 : (NspiResolveNames, NspiResolveNamesResponse),
20 : (NspiResolveNamesW, NspiResolveNamesWResponse),
}
################################################################################
# HELPER FUNCTIONS
################################################################################
def checkNullString(string):
if string == NULL:
return string
if string[-1:] != '\x00':
return string + '\x00'
else:
return string
def get_guid_from_dn(legacyDN):
legacyDN = str(legacyDN)
guid = legacyDN[legacyDN.rfind("=")+1:]
return uuid.string_to_bin(guid)
def get_dn_from_guid(guid, minimize=False):
if minimize:
# MS-OXNSPI
dn_template = "/guid="
else:
# MS-NSPI and MS-OXNSPI
dn_template = "/o=NT5/ou=00000000000000000000000000000000/cn="
guid_bin = string_to_bin(guid)
if PY2:
return "%s%s" % (dn_template, binascii.hexlify(guid_bin))
else:
return "%s%s" % (dn_template, str(binascii.hexlify(guid_bin), 'ascii'))
class EXCH_SID(LDAP_SID):
def __str__(self):
return self.formatCanonical()
class ExchBinaryObject(bytes):
pass
def getUnixTime(t):
t -= 116444736000000000
t //= 10000000
return t
def simplifyPropertyRow(rowSetElem):
row = {}
for prop in rowSetElem['lpProps']:
prop_name_in_union = prop['Value'].structure[0][0]
prop_value = prop['Value'].fields[prop_name_in_union]
PropTag = prop['ulPropTag']
if isinstance(prop_value, SHORT) or \
isinstance(prop_value, USHORT) or \
isinstance(prop_value, LONG) or \
isinstance(prop_value, ULONG):
row[PropTag] = int(prop_value['Data'])
elif isinstance(prop_value, LPWSTR):
if PropTag in [0x8c38001f]:
# What is this field for?
row[PropTag] = ExchBinaryObject(prop_value['Data'].encode("utf-16le")[:-2])
else:
row[PropTag] = prop_value['Data'][:-1]
elif isinstance(prop_value, LPSTR):
row[PropTag] = prop_value['Data'][:-1]
elif isinstance(prop_value, Binary_r):
value = b''.join(prop_value['lpb'])
if PropTag in [0x80270102, 0x8c750102]:
value = EXCH_SID(value)
elif PropTag == 0x300b0102:
value = value[:-1].decode("utf-8")
elif value[4:20] == GUID_NSPI and value[20:24] == b'\x01\x00\x00\x00' and value[:4] == b'\x00\x00\x00\x00':
value = PermanentEntryID(value)
elif value[:4] == b'\x87\x00\x00\x00' and value[20:24] == b'\x01\x00\x00\x00' and len(value) == 32:
value = EphemeralEntryID(value)
elif PropTag in [0x8c6d0102, 0x68c40102, 0x8c730102, 0x0ff80102]:
value = uuid.bin_to_string(value).lower()
elif PropTag == 0x0ff60102:
value = unpack('<l', value)[0]
else:
value = ExchBinaryObject(value)
row[PropTag] = value
elif isinstance(prop_value, BinaryArray_r):
array = []
for value in prop_value['lpbin']:
array.append(b''.join(value['lpb']))
row[PropTag] = array
elif isinstance(prop_value, StringArray_r):
array = []
for value in prop_value['lppszA']:
array.append(value['Data'][:-1])
row[PropTag] = array
elif isinstance(prop_value, WStringArray_r):
array = []
for value in prop_value['lppszW']:
array.append(value['Data'][:-1])
row[PropTag] = array
elif isinstance(prop_value, FILETIME):
row[PropTag] = datetime.fromtimestamp( \
getUnixTime(unpack('<Q', prop_value.getData())[0]))
else:
row[PropTag] = prop_value
return row
def simplifyPropertyRowSet(propertyRowSet):
ret = []
for rowSet in propertyRowSet['aRow']:
ret.append(simplifyPropertyRow(rowSet))
return ret
def hNspiBind(dce, pStat=None):
request = NspiBind()
if pStat == None:
request['pStat']['CodePage'] = CP_TELETEX
else:
request['pStat'] = pStat
resp = dce.request(request)
return resp
def hNspiUnbind(dce, handler):
request = NspiUnbind()
request['contextHandle'] = handler
resp = dce.request(request, checkError=False)
return resp
def hNspiUpdateStat(dce, handler, pStat, plDelta=NULL):
request = NspiUpdateStat()
request['hRpc'] = handler
request['pStat'] = pStat
request['plDelta'] = plDelta
resp = dce.request(request, checkError=False)
return resp
def hNspiQueryRows(dce, handler, dwFlags=fSkipObjects, pStat=None, ContainerID=0,
Count=50, pPropTags=[], pPropTagsRaw=NULL, lpETable=[]):
request = NspiQueryRows()
request['hRpc'] = handler
request['dwFlags'] = dwFlags
request['Count'] = Count
if pStat == None:
request['pStat']['ContainerID'] = ContainerID
else:
request['pStat'] = pStat
if len(pPropTags) > 0:
for aulPropTag in pPropTags:
prop = DWORD()
prop['Data'] = aulPropTag
request['pPropTags']['aulPropTag'].append(prop)
request['pPropTags']['cValues'] = len(pPropTags)
request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1
else:
request['pPropTags'] = pPropTagsRaw
if len(lpETable) > 0:
for mID in lpETable:
elem = DWORD()
elem['Data'] = mID
request['lpETable'].append(elem)
request['dwETableCount'] = len(lpETable)
else:
request['lpETable'] = NULL
request['dwETableCount'] = 0
resp = dce.request(request)
return resp
def hNspiSeekEntries(dce, handler, displayName, ContainerID=0, SortType=0, \
lpETable=[], lpETableRaw=NULL, pPropTags=[], pPropTagsRaw=NULL):
request = NspiSeekEntries()
request['hRpc'] = handler
request['pStat']['ContainerID'] = ContainerID
# MS-OXNSPI 3.1.4.1.9.9
# If the SortType field in the input parameter pStat has any value other than
# SortTypeDisplayName, the server MUST return the value GeneralFailure.
request['pStat']['SortType'] = SortTypeDisplayName
# MS-OXNSPI 3.1.4.1.9.10
# If the SortType field in the input parameter pStat is SortTypeDisplayName and the property
# specified in the input parameter pTarget is anything other than PidTagDisplayName (with either
# the Property Type PtypString8 or PtypString), the server MUST return the value
# GeneralFailure.
request['pTarget']['ulPropTag'] = 0x3001001F
request['pTarget']['Value']['tag'] = 0x0000001F
request['pTarget']['Value']['lpszW'] = checkNullString(displayName)
if len(lpETable) > 0:
for mID in lpETable:
elem = DWORD()
elem['Data'] = mID
request['lpETable'].append(elem)
else:
request['lpETable'] = lpETableRaw
if len(pPropTags) > 0:
for aulPropTag in pPropTags:
prop = DWORD()
prop['Data'] = aulPropTag
request['pPropTags']['aulPropTag'].append(prop)
request.fields['pPropTags'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1
else:
request['pPropTags'] = pPropTagsRaw
resp = dce.request(request)
return resp
def hNspiDNToMId(dce, handler, pNames=[]):
request = NspiDNToMId()
request['hRpc'] = handler
request['pNames']['Count'] = len(pNames)
for name in pNames:
lpstr = LPSTR()
lpstr['Data'] = checkNullString(name)
request['pNames']['Strings'].append(lpstr)
resp = dce.request(request)
return resp
def hNspiGetPropList(dce, handler, dwMId=0, dwFlags=fSkipObjects, CodePage=CP_TELETEX):
request = NspiGetPropList()
request['hRpc'] = handler
request['dwMId'] = dwMId
request['dwFlags'] = dwFlags
request['CodePage'] = CodePage
resp = dce.request(request)
return resp
def hNspiGetProps(dce, handler, ContainerID=0, CurrentRec=0, dwFlags=fSkipObjects, CodePage=CP_TELETEX, pPropTags=[]):
request = NspiGetProps()
request['hRpc'] = handler
request['dwFlags'] = dwFlags
request['pStat']['CurrentRec'] = CurrentRec
request['pStat']['ContainerID'] = ContainerID
request['pStat']['CodePage'] = CodePage
for aulPropTag in pPropTags:
prop = DWORD()
prop['Data'] = aulPropTag
request['pPropTags']['aulPropTag'].append(prop)
request['pPropTags']['cValues'] = len(pPropTags) + 1
request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1
resp = dce.request(request)
return resp
def hNspiGetSpecialTable(dce, handler, dwFlags=NspiUnicodeStrings, pStat=STAT(), lpVersion=NULL):
request = NspiGetSpecialTable()
request['hRpc'] = handler
request['dwFlags'] = dwFlags
request['pStat'] = pStat
request['lpVersion'] = lpVersion
resp = dce.request(request)
return resp
# Lookups specified LegacyDN or CN={ulType},CN={dwLocaleID},CN=Display-Templates,CN=Addressing in Configuration Naming Context
def hNspiGetTemplateInfo(dce, handler, pDN=NULL, dwLocaleID=0, ulType=0, dwCodePage=0, dwFlags=0xFFFFFFFF):
request = NspiGetTemplateInfo()
request['hRpc'] = handler
request['dwFlags'] = dwFlags
request['ulType'] = ulType
request['pDN'] = checkNullString(pDN)
request['dwCodePage'] = dwCodePage
request['dwLocaleID'] = dwLocaleID
resp = dce.request(request)
return resp
def hNspiModLinkAtt(dce, handler, dwFlags, ulPropTag, dwMId, lpEntryIds):
request = NspiModLinkAtt()
request['hRpc'] = handler
request['dwFlags'] = dwFlags
request['ulPropTag'] = ulPropTag
request['dwMId'] = dwMId
for lpEntryId in lpEntryIds:
prop = Binary_r()
prop['lpb'] = lpEntryId.getData()
prop['cValues'] = len(prop['lpb'])
request['lpEntryIds']['lpbin'].append(prop)
request['lpEntryIds']['cValues'] = len(lpEntryIds)
resp = dce.request(request)
return resp
def hNspiQueryColumns(dce, handler, dwFlags=NspiUnicodeProptypes):
request = NspiQueryColumns()
request['hRpc'] = handler
request['dwFlags'] = dwFlags
resp = dce.request(request)
return resp
def hNspiGetNamesFromIDs(dce, handler, lpguid=EMPTY_UUID, pPropTags=[], pPropTagsRaw=NULL):
request = NspiGetNamesFromIDs()
request['hRpc'] = handler
request['lpguid'] = lpguid
if len(pPropTags) > 0:
for aulPropTag in pPropTags:
prop = DWORD()
prop['Data'] = aulPropTag
request['pPropTags']['aulPropTag'].append(prop)
request['pPropTags']['cValues'] = len(pPropTags)
request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1
elif pPropTagsRaw == NULL:
request.fields['pPropTags'] = NULL
else:
request['pPropTags'] = pPropTagsRaw
resp = dce.request(request)
return resp
def hNspiResolveNames(dce, handler, ContainerID=0, pPropTags=[], pPropTagsRaw=NULL, paStr=[]):
request = NspiResolveNames()
request['hRpc'] = handler
request['pStat']['ContainerID'] = ContainerID
if len(pPropTags) > 0:
for aulPropTag in pPropTags:
prop = DWORD()
prop['Data'] = aulPropTag
request['pPropTags']['aulPropTag'].append(prop)
request['pPropTags']['cValues'] = len(pPropTags)
request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1
elif pPropTagsRaw == NULL:
request.fields['pPropTags'] = NULL
else:
request['pPropTags'] = pPropTagsRaw
if len(paStr) > 0:
for paStrElem in paStr:
value = LPSTR()
value['Data'] = checkNullString(paStrElem)
request['paStr']['Strings'].append(value)
request['paStr']['Count'] = len(paStr)
resp = dce.request(request)
return resp
def hNspiResolveNamesW(dce, handler, ContainerID=0, pPropTags=[], pPropTagsRaw=NULL, paStr=[]):
request = NspiResolveNamesW()
request['hRpc'] = handler
request['pStat']['ContainerID'] = ContainerID
if len(pPropTags) > 0:
for aulPropTag in pPropTags:
prop = DWORD()
prop['Data'] = aulPropTag
request['pPropTags']['aulPropTag'].append(prop)
request['pPropTags']['cValues'] = len(pPropTags)
request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1
elif pPropTagsRaw == NULL:
request.fields['pPropTags'] = NULL
else:
request['pPropTags'] = pPropTagsRaw
if len(paStr) > 0:
for paStrElem in paStr:
value = LPWSTR()
value['Data'] = checkNullString(paStrElem)
request['paStr']['Strings'].append(value)
request['paStr']['Count'] = len(paStr)
resp = dce.request(request)
return resp