# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Author: Alberto Solino (@agsolino)
#
# Description:
#   [MS-RRP] Interface implementation
#
#   Best way to learn how to use these calls is to grab the protocol standard
#   so you understand what the call does, and then read the test case located
#   at https://github.com/SecureAuthCorp/impacket/tree/master/tests/SMB_RPC
#
#   Some calls have helper functions, which makes it even easier to use.
#   They are located at the end of this file.
#   Helper functions start with "h"<name of the call>.
#   There are test cases for them too.
#


from struct import unpack, pack

from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantVaryingArray, NDRUniConformantArray
from impacket.dcerpc.v5.dtypes import DWORD, UUID, ULONG, LPULONG, BOOLEAN, SECURITY_INFORMATION, PFILETIME, \
    RPC_UNICODE_STRING, FILETIME, NULL, MAXIMUM_ALLOWED, OWNER_SECURITY_INFORMATION, PWCHAR, PRPC_UNICODE_STRING
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket import system_errors, LOG
from impacket.uuid import uuidtup_to_bin

MSRPC_UUID_RRP = uuidtup_to_bin(('338CD001-2244-31F1-AAAA-900038001003', '1.0'))

class DCERPCSessionError(DCERPCException):
    def __init__(self, error_string=None, error_code=None, packet=None):
        DCERPCException.__init__(self, error_string, error_code, packet)

    def __str__( self ):
        key = self.error_code
        if key in system_errors.ERROR_MESSAGES:
            error_msg_short = system_errors.ERROR_MESSAGES[key][0]
            error_msg_verbose = system_errors.ERROR_MESSAGES[key][1]
            return 'RRP SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
        else:
            return 'RRP SessionError: unknown error code: 0x%x' % self.error_code

################################################################################
# CONSTANTS
################################################################################
# 2.2.2 PREGISTRY_SERVER_NAME
PREGISTRY_SERVER_NAME = PWCHAR

# 2.2.3 error_status_t
error_status_t = ULONG

# 2.2.5 RRP_UNICODE_STRING
RRP_UNICODE_STRING = RPC_UNICODE_STRING
PRRP_UNICODE_STRING = PRPC_UNICODE_STRING

# 2.2.4 REGSAM
REGSAM = ULONG

KEY_QUERY_VALUE        = 0x00000001
KEY_SET_VALUE          = 0x00000002
KEY_CREATE_SUB_KEY     = 0x00000004
KEY_ENUMERATE_SUB_KEYS = 0x00000008
KEY_CREATE_LINK        = 0x00000020
KEY_WOW64_64KEY        = 0x00000100
KEY_WOW64_32KEY        = 0x00000200

REG_BINARY              = 3
REG_DWORD               = 4
REG_DWORD_LITTLE_ENDIAN = 4
REG_DWORD_BIG_ENDIAN    = 5
REG_EXPAND_SZ           = 2
REG_LINK                = 6
REG_MULTI_SZ            = 7
REG_NONE                = 0
REG_QWORD               = 11
REG_QWORD_LITTLE_ENDIAN = 11
REG_SZ                  = 1

# 3.1.5.7 BaseRegCreateKey (Opnum 6)
REG_CREATED_NEW_KEY     = 0x00000001
REG_OPENED_EXISTING_KEY = 0x00000002

# 3.1.5.19 BaseRegRestoreKey (Opnum 19)
# Flags
REG_WHOLE_HIVE_VOLATILE = 0x00000001
REG_REFRESH_HIVE        = 0x00000002
REG_NO_LAZY_FLUSH       = 0x00000004
REG_FORCE_RESTORE       = 0x00000008

################################################################################
# STRUCTURES
################################################################################
# 2.2.1 RPC_HKEY
class RPC_HKEY(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.6 RVALENT
class RVALENT(NDRSTRUCT):
    structure =  (
        ('ve_valuename',PRRP_UNICODE_STRING),
        ('ve_valuelen',DWORD),
        ('ve_valueptr',DWORD),
        ('ve_type',DWORD),
    )

class RVALENT_ARRAY(NDRUniConformantVaryingArray):
    item = RVALENT

# 2.2.9 RPC_SECURITY_DESCRIPTOR
class BYTE_ARRAY(NDRUniConformantVaryingArray):
    pass

class PBYTE_ARRAY(NDRPOINTER):
    referent = (
        ('Data', BYTE_ARRAY),
    )

class RPC_SECURITY_DESCRIPTOR(NDRSTRUCT):
    structure =  (
        ('lpSecurityDescriptor',PBYTE_ARRAY),
        ('cbInSecurityDescriptor',DWORD),
        ('cbOutSecurityDescriptor',DWORD),
    )

# 2.2.8 RPC_SECURITY_ATTRIBUTES
class RPC_SECURITY_ATTRIBUTES(NDRSTRUCT):
    structure =  (
        ('nLength',DWORD),
        ('RpcSecurityDescriptor',RPC_SECURITY_DESCRIPTOR),
        ('bInheritHandle',BOOLEAN),
    )

class PRPC_SECURITY_ATTRIBUTES(NDRPOINTER):
    referent = (
        ('Data', RPC_SECURITY_ATTRIBUTES),
    )

################################################################################
# RPC CALLS
################################################################################
# 3.1.5.1 OpenClassesRoot (Opnum 0)
class OpenClassesRoot(NDRCALL):
    opnum = 0
    structure = (
       ('ServerName', PREGISTRY_SERVER_NAME),
       ('samDesired', REGSAM),
    )

class OpenClassesRootResponse(NDRCALL):
    structure = (
       ('phKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.2 OpenCurrentUser (Opnum 1)
class OpenCurrentUser(NDRCALL):
    opnum = 1
    structure = (
       ('ServerName', PREGISTRY_SERVER_NAME),
       ('samDesired', REGSAM),
    )

class OpenCurrentUserResponse(NDRCALL):
    structure = (
       ('phKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.3 OpenLocalMachine (Opnum 2)
class OpenLocalMachine(NDRCALL):
    opnum = 2
    structure = (
       ('ServerName', PREGISTRY_SERVER_NAME),
       ('samDesired', REGSAM),
    )

class OpenLocalMachineResponse(NDRCALL):
    structure = (
       ('phKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.4 OpenPerformanceData (Opnum 3)
class OpenPerformanceData(NDRCALL):
    opnum = 3
    structure = (
       ('ServerName', PREGISTRY_SERVER_NAME),
       ('samDesired', REGSAM),
    )

class OpenPerformanceDataResponse(NDRCALL):
    structure = (
       ('phKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.5 OpenUsers (Opnum 4)
class OpenUsers(NDRCALL):
    opnum = 4
    structure = (
       ('ServerName', PREGISTRY_SERVER_NAME),
       ('samDesired', REGSAM),
    )

class OpenUsersResponse(NDRCALL):
    structure = (
       ('phKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.6 BaseRegCloseKey (Opnum 5)
class BaseRegCloseKey(NDRCALL):
    opnum = 5
    structure = (
       ('hKey', RPC_HKEY),
    )

class BaseRegCloseKeyResponse(NDRCALL):
    structure = (
       ('hKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.7 BaseRegCreateKey (Opnum 6)
class BaseRegCreateKey(NDRCALL):
    opnum = 6
    structure = (
       ('hKey', RPC_HKEY),
       ('lpSubKey', RRP_UNICODE_STRING),
       ('lpClass', RRP_UNICODE_STRING),
       ('dwOptions', DWORD),
       ('samDesired', REGSAM),
       ('lpSecurityAttributes', PRPC_SECURITY_ATTRIBUTES),
       ('lpdwDisposition', LPULONG),
    )

class BaseRegCreateKeyResponse(NDRCALL):
    structure = (
       ('phkResult', RPC_HKEY),
       ('lpdwDisposition', LPULONG),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.8 BaseRegDeleteKey (Opnum 7)
class BaseRegDeleteKey(NDRCALL):
    opnum = 7
    structure = (
       ('hKey', RPC_HKEY),
       ('lpSubKey', RRP_UNICODE_STRING),
    )

class BaseRegDeleteKeyResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.9 BaseRegDeleteValue (Opnum 8)
class BaseRegDeleteValue(NDRCALL):
    opnum = 8
    structure = (
       ('hKey', RPC_HKEY),
       ('lpValueName', RRP_UNICODE_STRING),
    )

class BaseRegDeleteValueResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.10 BaseRegEnumKey (Opnum 9)
class BaseRegEnumKey(NDRCALL):
    opnum = 9
    structure = (
       ('hKey', RPC_HKEY),
       ('dwIndex', DWORD),
       ('lpNameIn', RRP_UNICODE_STRING),
       ('lpClassIn', PRRP_UNICODE_STRING),
       ('lpftLastWriteTime', PFILETIME),
    )

class BaseRegEnumKeyResponse(NDRCALL):
    structure = (
       ('lpNameOut', RRP_UNICODE_STRING),
       ('lplpClassOut', PRRP_UNICODE_STRING),
       ('lpftLastWriteTime', PFILETIME),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.11 BaseRegEnumValue (Opnum 10)
class BaseRegEnumValue(NDRCALL):
    opnum = 10
    structure = (
       ('hKey', RPC_HKEY),
       ('dwIndex', DWORD),
       ('lpValueNameIn', RRP_UNICODE_STRING),
       ('lpType', LPULONG),
       ('lpData', PBYTE_ARRAY),
       ('lpcbData', LPULONG),
       ('lpcbLen', LPULONG),
    )

class BaseRegEnumValueResponse(NDRCALL):
    structure = (
       ('lpValueNameOut', RRP_UNICODE_STRING),
       ('lpType', LPULONG),
       ('lpData', PBYTE_ARRAY),
       ('lpcbData', LPULONG),
       ('lpcbLen', LPULONG),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.12 BaseRegFlushKey (Opnum 11)
class BaseRegFlushKey(NDRCALL):
    opnum = 11
    structure = (
       ('hKey', RPC_HKEY),
    )

class BaseRegFlushKeyResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.13 BaseRegGetKeySecurity (Opnum 12)
class BaseRegGetKeySecurity(NDRCALL):
    opnum = 12
    structure = (
       ('hKey', RPC_HKEY),
       ('SecurityInformation', SECURITY_INFORMATION),
       ('pRpcSecurityDescriptorIn', RPC_SECURITY_DESCRIPTOR),
    )

class BaseRegGetKeySecurityResponse(NDRCALL):
    structure = (
       ('pRpcSecurityDescriptorOut', RPC_SECURITY_DESCRIPTOR),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.14 BaseRegLoadKey (Opnum 13)
class BaseRegLoadKey(NDRCALL):
    opnum = 13
    structure = (
       ('hKey', RPC_HKEY),
       ('lpSubKey', RRP_UNICODE_STRING),
       ('lpFile', RRP_UNICODE_STRING),
    )

class BaseRegLoadKeyResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.15 BaseRegOpenKey (Opnum 15)
class BaseRegOpenKey(NDRCALL):
    opnum = 15
    structure = (
       ('hKey', RPC_HKEY),
       ('lpSubKey', RRP_UNICODE_STRING),
       ('dwOptions', DWORD),
       ('samDesired', REGSAM),
    )

class BaseRegOpenKeyResponse(NDRCALL):
    structure = (
       ('phkResult', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.16 BaseRegQueryInfoKey (Opnum 16)
class BaseRegQueryInfoKey(NDRCALL):
    opnum = 16
    structure = (
       ('hKey', RPC_HKEY),
       ('lpClassIn', RRP_UNICODE_STRING),
    )

class BaseRegQueryInfoKeyResponse(NDRCALL):
    structure = (
       ('lpClassOut', RPC_UNICODE_STRING),
       ('lpcSubKeys', DWORD),
       ('lpcbMaxSubKeyLen', DWORD),
       ('lpcbMaxClassLen', DWORD),
       ('lpcValues', DWORD),
       ('lpcbMaxValueNameLen', DWORD),
       ('lpcbMaxValueLen', DWORD),
       ('lpcbSecurityDescriptor', DWORD),
       ('lpftLastWriteTime', FILETIME),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.17 BaseRegQueryValue (Opnum 17)
class BaseRegQueryValue(NDRCALL):
    opnum = 17
    structure = (
       ('hKey', RPC_HKEY),
       ('lpValueName', RRP_UNICODE_STRING),
       ('lpType', LPULONG),
       ('lpData', PBYTE_ARRAY),
       ('lpcbData', LPULONG),
       ('lpcbLen', LPULONG),
    )

class BaseRegQueryValueResponse(NDRCALL):
    structure = (
       ('lpType', LPULONG),
       ('lpData', PBYTE_ARRAY),
       ('lpcbData', LPULONG),
       ('lpcbLen', LPULONG),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.18 BaseRegReplaceKey (Opnum 18)
class BaseRegReplaceKey(NDRCALL):
    opnum = 18
    structure = (
       ('hKey', RPC_HKEY),
       ('lpSubKey', RRP_UNICODE_STRING),
       ('lpNewFile', RRP_UNICODE_STRING),
       ('lpOldFile', RRP_UNICODE_STRING),
    )

class BaseRegReplaceKeyResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.19 BaseRegRestoreKey (Opnum 19)
class BaseRegRestoreKey(NDRCALL):
    opnum = 19
    structure = (
       ('hKey', RPC_HKEY),
       ('lpFile', RRP_UNICODE_STRING),
       ('Flags', DWORD),
    )

class BaseRegRestoreKeyResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.20 BaseRegSaveKey (Opnum 20)
class BaseRegSaveKey(NDRCALL):
    opnum = 20
    structure = (
       ('hKey', RPC_HKEY),
       ('lpFile', RRP_UNICODE_STRING),
       ('pSecurityAttributes', PRPC_SECURITY_ATTRIBUTES),
    )

class BaseRegSaveKeyResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.21 BaseRegSetKeySecurity (Opnum 21)
class BaseRegSetKeySecurity(NDRCALL):
    opnum = 21
    structure = (
       ('hKey', RPC_HKEY),
       ('SecurityInformation', SECURITY_INFORMATION),
       ('pRpcSecurityDescriptor', RPC_SECURITY_DESCRIPTOR),
    )

class BaseRegSetKeySecurityResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.22 BaseRegSetValue (Opnum 22)
class BaseRegSetValue(NDRCALL):
    opnum = 22
    structure = (
       ('hKey', RPC_HKEY),
       ('lpValueName', RRP_UNICODE_STRING),
       ('dwType', DWORD),
       ('lpData', NDRUniConformantArray),
       ('cbData', DWORD),
    )

class BaseRegSetValueResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.23 BaseRegUnLoadKey (Opnum 23)
class BaseRegUnLoadKey(NDRCALL):
    opnum = 23
    structure = (
       ('hKey', RPC_HKEY),
       ('lpSubKey', RRP_UNICODE_STRING),
    )

class BaseRegUnLoadKeyResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.24 BaseRegGetVersion (Opnum 26)
class BaseRegGetVersion(NDRCALL):
    opnum = 26
    structure = (
       ('hKey', RPC_HKEY),
    )

class BaseRegGetVersionResponse(NDRCALL):
    structure = (
       ('lpdwVersion', DWORD),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.25 OpenCurrentConfig (Opnum 27)
class OpenCurrentConfig(NDRCALL):
    opnum = 27
    structure = (
       ('ServerName', PREGISTRY_SERVER_NAME),
       ('samDesired', REGSAM),
    )

class OpenCurrentConfigResponse(NDRCALL):
    structure = (
       ('phKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.26 BaseRegQueryMultipleValues (Opnum 29)
class BaseRegQueryMultipleValues(NDRCALL):
    opnum = 29
    structure = (
       ('hKey', RPC_HKEY),
       ('val_listIn', RVALENT_ARRAY),
       ('num_vals', DWORD),
       ('lpvalueBuf', PBYTE_ARRAY),
       ('ldwTotsize', DWORD),
    )

class BaseRegQueryMultipleValuesResponse(NDRCALL):
    structure = (
       ('val_listOut', RVALENT_ARRAY),
       ('lpvalueBuf', PBYTE_ARRAY),
       ('ldwTotsize', DWORD),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.27 BaseRegSaveKeyEx (Opnum 31)
class BaseRegSaveKeyEx(NDRCALL):
    opnum = 31
    structure = (
       ('hKey', RPC_HKEY),
       ('lpFile', RRP_UNICODE_STRING),
       ('pSecurityAttributes', PRPC_SECURITY_ATTRIBUTES),
       ('Flags', DWORD),
    )

class BaseRegSaveKeyExResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

# 3.1.5.28 OpenPerformanceText (Opnum 32)
class OpenPerformanceText(NDRCALL):
    opnum = 32
    structure = (
       ('ServerName', PREGISTRY_SERVER_NAME),
       ('samDesired', REGSAM),
    )

class OpenPerformanceTextResponse(NDRCALL):
    structure = (
       ('phKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.29 OpenPerformanceNlsText (Opnum 33)
class OpenPerformanceNlsText(NDRCALL):
    opnum = 33
    structure = (
       ('ServerName', PREGISTRY_SERVER_NAME),
       ('samDesired', REGSAM),
    )

class OpenPerformanceNlsTextResponse(NDRCALL):
    structure = (
       ('phKey', RPC_HKEY),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.30 BaseRegQueryMultipleValues2 (Opnum 34)
class BaseRegQueryMultipleValues2(NDRCALL):
    opnum = 34
    structure = (
       ('hKey', RPC_HKEY),
       ('val_listIn', RVALENT_ARRAY),
       ('num_vals', DWORD),
       ('lpvalueBuf', PBYTE_ARRAY),
       ('ldwTotsize', DWORD),
    )

class BaseRegQueryMultipleValues2Response(NDRCALL):
    structure = (
       ('val_listOut', RVALENT_ARRAY),
       ('lpvalueBuf', PBYTE_ARRAY),
       ('ldwRequiredSize', DWORD),
       ('ErrorCode', error_status_t),
    )

# 3.1.5.31 BaseRegDeleteKeyEx (Opnum 35)
class BaseRegDeleteKeyEx(NDRCALL):
    opnum = 35
    structure = (
       ('hKey', RPC_HKEY),
       ('lpSubKey', RRP_UNICODE_STRING),
       ('AccessMask', REGSAM),
       ('Reserved', DWORD),
    )

class BaseRegDeleteKeyExResponse(NDRCALL):
    structure = (
       ('ErrorCode', error_status_t),
    )

################################################################################
# OPNUMs and their corresponding structures
################################################################################
OPNUMS = {
 0 : (OpenClassesRoot, OpenClassesRootResponse),
 1 : (OpenCurrentUser, OpenCurrentUserResponse),
 2 : (OpenLocalMachine, OpenLocalMachineResponse),
 3 : (OpenPerformanceData, OpenPerformanceDataResponse),
 4 : (OpenUsers, OpenUsersResponse),
 5 : (BaseRegCloseKey, BaseRegCloseKeyResponse),
 6 : (BaseRegCreateKey, BaseRegCreateKeyResponse),
 7 : (BaseRegDeleteKey, BaseRegDeleteKeyResponse),
 8 : (BaseRegDeleteValue, BaseRegDeleteValueResponse),
 9 : (BaseRegEnumKey, BaseRegEnumKeyResponse),
10 : (BaseRegEnumValue, BaseRegEnumValueResponse),
11 : (BaseRegFlushKey, BaseRegFlushKeyResponse),
12 : (BaseRegGetKeySecurity, BaseRegGetKeySecurityResponse),
13 : (BaseRegLoadKey, BaseRegLoadKeyResponse),
15 : (BaseRegOpenKey, BaseRegOpenKeyResponse),
16 : (BaseRegQueryInfoKey, BaseRegQueryInfoKeyResponse),
17 : (BaseRegQueryValue, BaseRegQueryValueResponse),
18 : (BaseRegReplaceKey, BaseRegReplaceKeyResponse),
19 : (BaseRegRestoreKey, BaseRegRestoreKeyResponse),
20 : (BaseRegSaveKey, BaseRegSaveKeyResponse),
21 : (BaseRegSetKeySecurity, BaseRegSetKeySecurityResponse),
22 : (BaseRegSetValue, BaseRegSetValueResponse),
23 : (BaseRegUnLoadKey, BaseRegUnLoadKeyResponse),
26 : (BaseRegGetVersion, BaseRegGetVersionResponse),
27 : (OpenCurrentConfig, OpenCurrentConfigResponse),
29 : (BaseRegQueryMultipleValues, BaseRegQueryMultipleValuesResponse),
31 : (BaseRegSaveKeyEx, BaseRegSaveKeyExResponse),
32 : (OpenPerformanceText, OpenPerformanceTextResponse),
33 : (OpenPerformanceNlsText, OpenPerformanceNlsTextResponse),
34 : (BaseRegQueryMultipleValues2, BaseRegQueryMultipleValues2Response),
35 : (BaseRegDeleteKeyEx, BaseRegDeleteKeyExResponse),
}

################################################################################
# HELPER FUNCTIONS
################################################################################
def checkNullString(string):
    if string == NULL:
        return string

    if string[-1:] != '\x00':
        return string + '\x00'
    else:
        return string

def packValue(valueType, value):
    if valueType == REG_DWORD:
        retData = pack('<L', value)
    elif valueType == REG_DWORD_BIG_ENDIAN:
        retData = pack('>L', value)
    elif valueType == REG_EXPAND_SZ:
        try:
            retData = value.encode('utf-16le')
        except UnicodeDecodeError:
            import sys
            retData = value.decode(sys.getfilesystemencoding()).encode('utf-16le')
    elif valueType == REG_MULTI_SZ:
        try:
            retData = value.encode('utf-16le')
        except UnicodeDecodeError:
            import sys
            retData = value.decode(sys.getfilesystemencoding()).encode('utf-16le')
    elif valueType == REG_QWORD:
        retData = pack('<Q', value)
    elif valueType == REG_QWORD_LITTLE_ENDIAN:
        retData = pack('>Q', value)
    elif valueType == REG_SZ:
        try:
            retData = value.encode('utf-16le')
        except UnicodeDecodeError:
            import sys
            retData = value.decode(sys.getfilesystemencoding()).encode('utf-16le')
    else:
        retData = value

    return retData

def unpackValue(valueType, value):
    if valueType == REG_DWORD:
        retData = unpack('<L', b''.join(value))[0]
    elif valueType == REG_DWORD_BIG_ENDIAN:
        retData = unpack('>L', b''.join(value))[0]
    elif valueType == REG_EXPAND_SZ:
        retData = b''.join(value).decode('utf-16le')
    elif valueType == REG_MULTI_SZ:
        retData = b''.join(value).decode('utf-16le')
    elif valueType == REG_QWORD:
        retData = unpack('<Q', b''.join(value))[0]
    elif valueType == REG_QWORD_LITTLE_ENDIAN:
        retData = unpack('>Q', b''.join(value))[0]
    elif valueType == REG_SZ:
        retData = b''.join(value).decode('utf-16le')
    else:
        retData = b''.join(value)

    return retData

def hOpenClassesRoot(dce, samDesired = MAXIMUM_ALLOWED):
    request = OpenClassesRoot()
    request['ServerName'] = NULL
    request['samDesired'] = samDesired
    return dce.request(request)

def hOpenCurrentUser(dce, samDesired = MAXIMUM_ALLOWED):
    request = OpenCurrentUser()
    request['ServerName'] = NULL
    request['samDesired'] = samDesired
    return dce.request(request)

def hOpenLocalMachine(dce, samDesired = MAXIMUM_ALLOWED):
    request = OpenLocalMachine()
    request['ServerName'] = NULL
    request['samDesired'] = samDesired
    return dce.request(request)

def hOpenPerformanceData(dce, samDesired = MAXIMUM_ALLOWED):
    request = OpenPerformanceData()
    request['ServerName'] = NULL
    request['samDesired'] = samDesired
    return dce.request(request)

def hOpenUsers(dce, samDesired = MAXIMUM_ALLOWED):
    request = OpenUsers()
    request['ServerName'] = NULL
    request['samDesired'] = samDesired
    return dce.request(request)

def hBaseRegCloseKey(dce, hKey):
    request = BaseRegCloseKey()
    request['hKey'] = hKey
    return dce.request(request)

def hBaseRegCreateKey(dce, hKey, lpSubKey, lpClass = NULL, dwOptions = 0x00000001, samDesired = MAXIMUM_ALLOWED, lpSecurityAttributes = NULL, lpdwDisposition = REG_CREATED_NEW_KEY):
    request = BaseRegCreateKey()
    request['hKey'] = hKey
    request['lpSubKey'] = checkNullString(lpSubKey)
    request['lpClass'] = checkNullString(lpClass)
    request['dwOptions'] = dwOptions
    request['samDesired'] = samDesired
    if lpSecurityAttributes == NULL:
        request['lpSecurityAttributes']['RpcSecurityDescriptor']['lpSecurityDescriptor'] = NULL
    else:
        request['lpSecurityAttributes'] = lpSecurityAttributes
    request['lpdwDisposition'] = lpdwDisposition

    return dce.request(request)

def hBaseRegDeleteKey(dce, hKey, lpSubKey):
    request = BaseRegDeleteKey()
    request['hKey'] = hKey
    request['lpSubKey'] = checkNullString(lpSubKey)
    return dce.request(request)

def hBaseRegEnumKey(dce, hKey, dwIndex, lpftLastWriteTime = NULL):
    request = BaseRegEnumKey()
    request['hKey'] = hKey
    request['dwIndex'] = dwIndex
    request.fields['lpNameIn'].fields['MaximumLength'] = 1024
    request.fields['lpNameIn'].fields['Data'].fields['Data'].fields['MaximumCount'] = 1024//2
    request['lpClassIn'] = ' '* 64
    request['lpftLastWriteTime'] = lpftLastWriteTime

    return dce.request(request)

def hBaseRegEnumValue(dce, hKey, dwIndex, dataLen=256):
    request = BaseRegEnumValue()
    request['hKey'] = hKey
    request['dwIndex'] = dwIndex
    retries = 1

    # We need to be aware the size might not be enough, so let's catch ERROR_MORE_DATA exception
    while True:
        try:
            # Only the maximum length field of the lpValueNameIn is used to determine the buffer length to be allocated
            # by the service. Specify a string with a zero length but maximum length set to the largest buffer size
            # needed to hold the value names.
            request.fields['lpValueNameIn'].fields['MaximumLength'] = dataLen*2
            request.fields['lpValueNameIn'].fields['Data'].fields['Data'].fields['MaximumCount'] = dataLen

            request['lpData'] = b' ' * dataLen
            request['lpcbData'] = dataLen
            request['lpcbLen'] = dataLen
            resp = dce.request(request)
        except DCERPCSessionError as e:
            if retries > 1:
                LOG.debug('Too many retries when calling hBaseRegEnumValue, aborting')
                raise
            if e.get_error_code() == system_errors.ERROR_MORE_DATA:
                # We need to adjust the size
                retries +=1
                dataLen = e.get_packet()['lpcbData']
                continue
            else:
                raise
        else:
            break

    return resp

def hBaseRegFlushKey(dce, hKey):
    request = BaseRegFlushKey()
    request['hKey'] = hKey
    return dce.request(request)

def hBaseRegGetKeySecurity(dce, hKey, securityInformation = OWNER_SECURITY_INFORMATION ):
    request = BaseRegGetKeySecurity()
    request['hKey'] = hKey
    request['SecurityInformation'] = securityInformation
    request['pRpcSecurityDescriptorIn']['lpSecurityDescriptor'] = NULL
    request['pRpcSecurityDescriptorIn']['cbInSecurityDescriptor'] = 1024

    return dce.request(request)

def hBaseRegLoadKey(dce, hKey, lpSubKey, lpFile):
    request = BaseRegLoadKey()
    request['hKey'] = hKey
    request['lpSubKey'] = checkNullString(lpSubKey)
    request['lpFile'] = checkNullString(lpFile)
    return dce.request(request)

def hBaseRegUnLoadKey(dce, hKey, lpSubKey):
    request = BaseRegUnLoadKey()
    request['hKey'] = hKey
    request['lpSubKey'] = checkNullString(lpSubKey)
    return dce.request(request)

def hBaseRegOpenKey(dce, hKey, lpSubKey, dwOptions=0x00000001, samDesired = MAXIMUM_ALLOWED):
    request = BaseRegOpenKey()
    request['hKey'] = hKey
    request['lpSubKey'] = checkNullString(lpSubKey)
    request['dwOptions'] = dwOptions
    request['samDesired'] = samDesired
    return dce.request(request)

def hBaseRegQueryInfoKey(dce, hKey):
    request = BaseRegQueryInfoKey()
    request['hKey'] = hKey
    # Not the cleanest way, but oh well
    # Plus, Windows XP needs MaximumCount also set
    request.fields['lpClassIn'].fields['MaximumLength'] = 1024
    request.fields['lpClassIn'].fields['Data'].fields['Data'].fields['MaximumCount'] = 1024//2
    return dce.request(request)

def hBaseRegQueryValue(dce, hKey, lpValueName, dataLen=512):
    request = BaseRegQueryValue()
    request['hKey'] = hKey
    request['lpValueName'] = checkNullString(lpValueName)
    retries = 1

    # We need to be aware the size might not be enough, so let's catch ERROR_MORE_DATA exception
    while True:
        try:
            request['lpData'] =b' ' * dataLen
            request['lpcbData'] = dataLen
            request['lpcbLen'] = dataLen
            resp = dce.request(request)
        except DCERPCSessionError as e:
            if retries > 1:
                LOG.debug('Too many retries when calling hBaseRegQueryValue, aborting')
                raise
            if e.get_error_code() == system_errors.ERROR_MORE_DATA:
                # We need to adjust the size
                dataLen = e.get_packet()['lpcbData']
                continue
            else:
                raise
        else:
            break

    # Returns
    # ( dataType, data )
    return resp['lpType'], unpackValue(resp['lpType'], resp['lpData'])

def hBaseRegReplaceKey(dce, hKey, lpSubKey, lpNewFile, lpOldFile):
    request = BaseRegReplaceKey()
    request['hKey'] = hKey
    request['lpSubKey'] = checkNullString(lpSubKey)
    request['lpNewFile'] = checkNullString(lpNewFile)
    request['lpOldFile'] = checkNullString(lpOldFile)
    return dce.request(request)

def hBaseRegRestoreKey(dce, hKey, lpFile, flags=REG_REFRESH_HIVE):
    request = BaseRegRestoreKey()
    request['hKey'] = hKey
    request['lpFile'] = checkNullString(lpFile)
    request['Flags'] = flags
    return dce.request(request)

def hBaseRegSaveKey(dce, hKey, lpFile, pSecurityAttributes = NULL):
    request = BaseRegSaveKey()
    request['hKey'] = hKey
    request['lpFile'] = checkNullString(lpFile)
    request['pSecurityAttributes'] = pSecurityAttributes
    return dce.request(request)

def hBaseRegSetValue(dce, hKey, lpValueName, dwType, lpData):
    request = BaseRegSetValue()
    request['hKey'] = hKey
    request['lpValueName'] = checkNullString(lpValueName)
    request['dwType'] = dwType
    request['lpData'] = packValue(dwType,lpData)
    request['cbData'] = len(request['lpData'])
    return dce.request(request)

def hBaseRegGetVersion(dce, hKey):
    request = BaseRegGetVersion()
    request['hKey'] = hKey
    return dce.request(request)

def hOpenCurrentConfig(dce, samDesired = MAXIMUM_ALLOWED):
    request = OpenCurrentConfig()
    request['ServerName'] = NULL
    request['samDesired'] = samDesired
    return dce.request(request)

def hBaseRegQueryMultipleValues(dce, hKey, val_listIn):
    # ToDo, check the result to see whether we need to
    # have a bigger buffer for the data to receive
    request = BaseRegQueryMultipleValues()
    request['hKey'] = hKey

    for item in  val_listIn:
        itemn = RVALENT()
        itemn['ve_valuename'] = checkNullString(item['ValueName'])
        itemn['ve_valuelen'] = len(itemn['ve_valuename'])
        itemn['ve_valueptr'] = NULL
        itemn['ve_type'] = item['ValueType']
        request['val_listIn'].append(itemn)

    request['num_vals'] = len(request['val_listIn'])
    request['lpvalueBuf'] = list(b' '*128)
    request['ldwTotsize'] = 128

    resp = dce.request(request)
    retVal = list()
    for item in resp['val_listOut']:
        itemn = dict()
        itemn['ValueName'] = item['ve_valuename']
        itemn['ValueData'] = unpackValue(item['ve_type'], resp['lpvalueBuf'][item['ve_valueptr'] : item['ve_valueptr']+item['ve_valuelen']])
        retVal.append(itemn)

    return retVal

def hBaseRegSaveKeyEx(dce, hKey, lpFile, pSecurityAttributes = NULL, flags=1):
    request = BaseRegSaveKeyEx()
    request['hKey'] = hKey
    request['lpFile'] = checkNullString(lpFile)
    request['pSecurityAttributes'] = pSecurityAttributes
    request['Flags'] = flags
    return dce.request(request)

def hOpenPerformanceText(dce, samDesired = MAXIMUM_ALLOWED):
    request = OpenPerformanceText()
    request['ServerName'] = NULL
    request['samDesired'] = samDesired
    return dce.request(request)

def hOpenPerformanceNlsText(dce, samDesired = MAXIMUM_ALLOWED):
    request = OpenPerformanceNlsText()
    request['ServerName'] = NULL
    request['samDesired'] = samDesired
    return dce.request(request)

def hBaseRegDeleteValue(dce, hKey, lpValueName):
    request = BaseRegDeleteValue()
    request['hKey'] = hKey
    request['lpValueName'] = checkNullString(lpValueName)
    return dce.request(request)
