#include #include "wzcsvc.h" #include "tracing.h" #include "utils.h" #include "intflist.h" #include "rpcsrv.h" #include "database.h" extern HASH sessionHash; //------------------------------------------------- // Globals used for the RPC interface BOOL g_bRpcStarted = FALSE; PSECURITY_DESCRIPTOR g_pSecurityDescr = NULL; GENERIC_MAPPING g_Mapping = { WZC_READ, WZC_WRITE, WZC_EXECUTE, WZC_ALL_ACCESS}; //------------------------------------------------- // Initialize the security settings for the RPC API DWORD WZCSvcInitRPCSecurity() { DWORD dwErr = ERROR_SUCCESS; NTSTATUS ntStatus; ACE_DATA AceData[6] = { {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WZC_ACCESS_SET|WZC_ACCESS_QUERY, &LocalSystemSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WZC_ACCESS_SET|WZC_ACCESS_QUERY, &AliasAdminsSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WZC_ACCESS_SET|WZC_ACCESS_QUERY, &AliasAccountOpsSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WZC_ACCESS_SET|WZC_ACCESS_QUERY, &AliasSystemOpsSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WZC_ACCESS_SET|WZC_ACCESS_QUERY, &AliasUsersSid}, // for now (WinXP Client RTM) the decision was made to let everybody party, but based on // the acl below. Later, the security schema won't change by default, but support will be // added allowing admins to tighten up the access to the service RPC APIs. {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WZC_ACCESS_SET|WZC_ACCESS_QUERY, &WorldSid}}; DbgPrint((TRC_TRACK, "[WZCSvcInitRPCSecurity")); // create the well known SIDs; dwErr = RtlNtStatusToDosError( NetpCreateWellKnownSids(NULL) ); DbgAssert((dwErr == ERROR_SUCCESS, "Error %d creating the well known Sids!", dwErr)); // create the security object. if (dwErr == ERROR_SUCCESS) { dwErr = RtlNtStatusToDosError( NetpCreateSecurityObject( AceData, sizeof(AceData)/sizeof(ACE_DATA), NULL, NULL, &g_Mapping, &g_pSecurityDescr) ); DbgAssert((dwErr == ERROR_SUCCESS, "Error %d creating the global security object!", dwErr)); } DbgPrint((TRC_TRACK, "WZCSvcInitRPCSecurity]=%d", dwErr)); return dwErr; } //------------------------------------------------- // Check the access for the particular access mask provided DWORD WZCSvcCheckRPCAccess(DWORD dwAccess) { DWORD dwErr = ERROR_SUCCESS; if (g_pSecurityDescr != NULL) { dwErr = NetpAccessCheckAndAudit( _T("WZCSVC"), _T("WZCSVC"), g_pSecurityDescr, dwAccess, &g_Mapping); DbgPrint((TRC_GENERIC, ">>> Security check reports err=%d.", dwErr)); } return dwErr; } //------------------------------------------------- // Check the validity of a RAW_DATA pointer DWORD WZCSvcCheckParamRawData(PRAW_DATA prd) { DWORD dwErr = ERROR_SUCCESS; if (prd != NULL) { if (prd->dwDataLen != 0 && prd->pData == NULL) dwErr = ERROR_INVALID_PARAMETER; } return dwErr; } //------------------------------------------------- // Check the validity of an SSID embedded in a RAW_DATA pointer DWORD WZCSvcCheckSSID(PNDIS_802_11_SSID pndSSID, UINT nBytes) { DWORD dwErr = ERROR_SUCCESS; if (pndSSID != NULL) { if (nBytes < FIELD_OFFSET(NDIS_802_11_SSID, Ssid) || pndSSID->SsidLength > nBytes - FIELD_OFFSET(NDIS_802_11_SSID, Ssid)) dwErr = ERROR_INVALID_PARAMETER; } return dwErr; } //------------------------------------------------- // Check the validity of a list of configurations embedded in a RAW_DATA pointer DWORD WZCSvcCheckConfig(PWZC_WLAN_CONFIG pwzcConfig, UINT nBytes) { DWORD dwErr = ERROR_SUCCESS; if (pwzcConfig != NULL) { if (pwzcConfig->Length > nBytes || pwzcConfig->Length != sizeof(WZC_WLAN_CONFIG)) dwErr = ERROR_INVALID_PARAMETER; if (dwErr == ERROR_SUCCESS) dwErr = WZCSvcCheckSSID(&pwzcConfig->Ssid, sizeof(NDIS_802_11_SSID)); if (dwErr == ERROR_SUCCESS && pwzcConfig->KeyLength > WZCCTL_MAX_WEPK_MATERIAL) dwErr = ERROR_INVALID_PARAMETER; } return dwErr; } //------------------------------------------------- // Check the validity of a list of configurations embedded in a RAW_DATA pointer DWORD WZCSvcCheckConfigList(PWZC_802_11_CONFIG_LIST pwzcList, UINT nBytes) { DWORD dwErr = ERROR_SUCCESS; if (pwzcList != NULL) { UINT i; if (nBytes < FIELD_OFFSET(WZC_802_11_CONFIG_LIST, Config)) dwErr = ERROR_INVALID_PARAMETER; nBytes -= FIELD_OFFSET(WZC_802_11_CONFIG_LIST, Config); if (dwErr == ERROR_SUCCESS && ((pwzcList->NumberOfItems * sizeof(WZC_WLAN_CONFIG) > nBytes) || (pwzcList->Index > pwzcList->NumberOfItems) ) ) dwErr = ERROR_INVALID_PARAMETER; for (i = 0; i < pwzcList->NumberOfItems && dwErr == ERROR_SUCCESS; i++) dwErr = WZCSvcCheckConfig(&(pwzcList->Config[i]), sizeof(WZC_WLAN_CONFIG)); } return dwErr; } //------------------------------------------------- // Check the validity of the "input" fields from the INTF_ENTRY. DWORD WZCSvcCheckParamIntfEntry(PINTF_ENTRY pIntfEntry) { DWORD dwErr = ERROR_SUCCESS; if (pIntfEntry != NULL) { if (dwErr == ERROR_SUCCESS) dwErr = WZCSvcCheckParamRawData(&pIntfEntry->rdSSID); if (dwErr == ERROR_SUCCESS) dwErr = WZCSvcCheckParamRawData(&pIntfEntry->rdBSSID); if (dwErr == ERROR_SUCCESS) dwErr = WZCSvcCheckParamRawData(&pIntfEntry->rdStSSIDList); if (dwErr == ERROR_SUCCESS) dwErr = WZCSvcCheckConfigList( (PWZC_802_11_CONFIG_LIST)pIntfEntry->rdStSSIDList.pData, pIntfEntry->rdStSSIDList.dwDataLen); if (dwErr == ERROR_SUCCESS) dwErr = WZCSvcCheckParamRawData(&pIntfEntry->rdCtrlData); } return dwErr; } //------------------------------------------------- // Cleanup whatever data was used for RPC security settings DWORD WZCSvcTermRPCSecurity() { DWORD dwErr = ERROR_SUCCESS; DbgPrint((TRC_TRACK, "[WZCSvcTermRPCSecurity")); dwErr = RtlNtStatusToDosError(NetpDeleteSecurityObject(&g_pSecurityDescr)); DbgAssert((dwErr == ERROR_SUCCESS, "Failed to delete the global security descriptor!")); g_pSecurityDescr = NULL; NetpFreeWellKnownSids(); DbgPrint((TRC_TRACK, "WZCSvcTermRPCSecurity]=%d", dwErr)); return dwErr; } RPC_STATUS CallbackCheckLocal( IN RPC_IF_HANDLE *Interface, IN void *Context) { RPC_STATUS rpcStat = RPC_S_OK; LPTSTR pBinding = NULL; LPTSTR pProtSeq = NULL; rpcStat = RpcBindingToStringBinding(Context, &pBinding); if (rpcStat == RPC_S_OK) { rpcStat = RpcStringBindingParse( pBinding, NULL, &pProtSeq, NULL, NULL, NULL); } if (rpcStat == RPC_S_OK) { if (_tcsicmp((LPCTSTR)pProtSeq, _T("ncalrpc")) != 0) rpcStat = RPC_S_ACCESS_DENIED; } if (pBinding != NULL) RpcStringFree(&pBinding); if (pProtSeq != NULL) RpcStringFree(&pProtSeq); return rpcStat; } DWORD WZCSvcStartRPCServer() { DWORD dwStatus = RPC_S_OK; DbgPrint((TRC_TRACK, "[WZCSvcStartRPCServer")); if (dwStatus == RPC_S_OK) { dwStatus = RpcServerUseProtseqEp( L"ncalrpc", 10, L"wzcsvc", NULL); if (dwStatus == RPC_S_DUPLICATE_ENDPOINT) dwStatus = RPC_S_OK; } if (dwStatus == RPC_S_OK) { dwStatus = RpcServerRegisterIfEx( winwzc_ServerIfHandle, 0, 0, RPC_IF_ALLOW_SECURE_ONLY, // WZCSAPI is using RPC_C_PROTECT_LEVEL_PKT_PRIVACY 0, // ignored for non auto-listen interfaces CallbackCheckLocal); } if (dwStatus == RPC_S_OK) { dwStatus = RpcServerRegisterAuthInfo( 0, RPC_C_AUTHN_WINNT, 0, 0); } if (dwStatus == RPC_S_OK) { dwStatus = RpcServerRegisterAuthInfo( 0, RPC_C_AUTHN_GSS_KERBEROS, 0, 0); } if (dwStatus == RPC_S_OK) { dwStatus = RpcServerRegisterAuthInfo( 0, RPC_C_AUTHN_GSS_NEGOTIATE, 0, 0); } if (dwStatus == RPC_S_OK) { dwStatus = RpcServerListen( 3, RPC_C_LISTEN_MAX_CALLS_DEFAULT, TRUE); if (dwStatus == RPC_S_ALREADY_LISTENING) dwStatus = RPC_S_OK; } if (dwStatus != RPC_S_OK) { RpcServerUnregisterIfEx( winwzc_ServerIfHandle, 0, 0); } g_bRpcStarted = (dwStatus == RPC_S_OK); WZCSvcInitRPCSecurity(); DbgPrint((TRC_TRACK, "WZCSvcStartRPCServer]=%d", dwStatus)); return (dwStatus); } DWORD WZCSvcStopRPCServer() { DWORD dwStatus = RPC_S_OK; DbgPrint((TRC_TRACK, "[WZCSvcStopRPCServer")); if (g_bRpcStarted) { g_bRpcStarted = FALSE; WZCSvcTermRPCSecurity(); dwStatus = RpcServerUnregisterIfEx( winwzc_ServerIfHandle, 0, 0); // don't stop RPC from listening - other services could rely on this //RpcMgmtStopServerListening(0); } DbgPrint((TRC_TRACK, "WZCSvcStopRPCServer]=%d", dwStatus)); return (dwStatus); } DWORD RpcEnumInterfaces( STRING_HANDLE pSrvAddr, PINTFS_KEY_TABLE pIntfsTable) { DWORD dwErr = ERROR_SUCCESS; DWORD dwNumIntfs; InterlockedIncrement(&g_nThreads); DbgPrint((TRC_TRACK, "[RpcEnumInterfaces")); dwErr = WZCSvcCheckRPCAccess(WZC_ACCESS_QUERY); if (dwErr == ERROR_SUCCESS) { dwNumIntfs = LstNumInterfaces(); DbgPrint((TRC_GENERIC, "Num interfaces = %d", dwNumIntfs)); if (dwNumIntfs == 0) goto exit; pIntfsTable->pIntfs = RpcCAlloc(dwNumIntfs*sizeof(INTF_KEY_ENTRY)); if (pIntfsTable->pIntfs == NULL) { dwErr = GetLastError(); goto exit; } dwErr = LstGetIntfsKeyInfo(pIntfsTable->pIntfs, &dwNumIntfs); if (dwErr != ERROR_SUCCESS || dwNumIntfs == 0) { RpcFree(pIntfsTable->pIntfs); pIntfsTable->pIntfs = NULL; goto exit; } pIntfsTable->dwNumIntfs = dwNumIntfs; for (dwNumIntfs = 0; dwNumIntfs < pIntfsTable->dwNumIntfs; dwNumIntfs++) { DbgPrint((TRC_GENERIC, "Intf %d:\t%S", dwNumIntfs, pIntfsTable->pIntfs[dwNumIntfs].wszGuid == NULL ? L"(null)" : pIntfsTable->pIntfs[dwNumIntfs].wszGuid)); } } exit: DbgPrint((TRC_TRACK, "RpcEnumInterfaces]=%d", dwErr)); InterlockedDecrement(&g_nThreads); return dwErr; } DWORD RpcQueryInterface( STRING_HANDLE pSrvAddr, DWORD dwInFlags, PINTF_ENTRY pIntfEntry, LPDWORD pdwOutFlags) { DWORD dwErr = ERROR_SUCCESS; InterlockedIncrement(&g_nThreads); DbgPrint((TRC_TRACK, "[RpcQueryInterface(0x%x,%S)", dwInFlags, pIntfEntry->wszGuid)); dwErr = WZCSvcCheckRPCAccess(WZC_ACCESS_QUERY); if (dwErr == ERROR_SUCCESS) { dwErr = LstQueryInterface(dwInFlags, pIntfEntry, pdwOutFlags); } DbgPrint((TRC_TRACK, "RpcQueryInterface]=%d", dwErr)); InterlockedDecrement(&g_nThreads); return dwErr; } DWORD RpcSetInterface( STRING_HANDLE pSrvAddr, DWORD dwInFlags, PINTF_ENTRY pIntfEntry, LPDWORD pdwOutFlags) { DWORD dwErr = ERROR_SUCCESS; InterlockedIncrement(&g_nThreads); DbgPrint((TRC_TRACK, "[RpcSetInterface(0x%x,%S)", dwInFlags, pIntfEntry->wszGuid)); dwErr = WZCSvcCheckRPCAccess(WZC_ACCESS_SET); if (dwErr == ERROR_SUCCESS) { dwErr = WZCSvcCheckParamIntfEntry(pIntfEntry); } if (dwErr == ERROR_SUCCESS) { dwErr = LstSetInterface(dwInFlags, pIntfEntry, pdwOutFlags); } DbgPrint((TRC_TRACK, "RpcSetInterface]=%d", dwErr)); InterlockedDecrement(&g_nThreads); return dwErr; } DWORD RpcRefreshInterface( STRING_HANDLE pSrvAddr, DWORD dwInFlags, PINTF_ENTRY pIntfEntry, LPDWORD pdwOutFlags) { DWORD dwErr = ERROR_SUCCESS; InterlockedIncrement(&g_nThreads); DbgPrint((TRC_TRACK, "[RpcRefreshInterface(0x%x,%S)", dwInFlags, pIntfEntry->wszGuid)); dwErr = WZCSvcCheckRPCAccess(WZC_ACCESS_SET); if (dwErr == ERROR_SUCCESS) { dwErr = LstRefreshInterface(dwInFlags, pIntfEntry, pdwOutFlags); } DbgPrint((TRC_TRACK, "RpcRefreshInterface]=%d", dwErr)); InterlockedDecrement(&g_nThreads); return dwErr; } DWORD RpcQueryContext( STRING_HANDLE pSrvAddr, DWORD dwInFlags, PWZC_CONTEXT pContext, LPDWORD pdwOutFlags) { DWORD dwErr = ERROR_SUCCESS; InterlockedIncrement(&g_nThreads); DbgPrint((TRC_TRACK, "[RpcQueryContext(0x%x)", dwInFlags)); dwErr = WZCSvcCheckRPCAccess(WZC_ACCESS_QUERY); if (dwErr == ERROR_SUCCESS) { dwErr = WzcContextQuery(dwInFlags, pContext, pdwOutFlags); } DbgPrint((TRC_TRACK, "RpcQueryContext]=%d", dwErr)); InterlockedDecrement(&g_nThreads); return dwErr; } DWORD RpcSetContext( STRING_HANDLE pSrvAddr, DWORD dwInFlags, PWZC_CONTEXT pContext, LPDWORD pdwOutFlags) { DWORD dwErr = ERROR_SUCCESS; BOOL bLogEnabled = FALSE; InterlockedIncrement(&g_nThreads); DbgPrint((TRC_TRACK, "[RpcSetContext(0x%x)", dwInFlags)); dwErr = WZCSvcCheckRPCAccess(WZC_ACCESS_SET); if (dwErr == ERROR_SUCCESS) { dwErr = WzcContextSet(dwInFlags, pContext, pdwOutFlags); } DbgPrint((TRC_TRACK, "RpcSetContext]=%d", dwErr)); BAIL_ON_WIN32_ERROR(dwErr); EnterCriticalSection(&g_wzcInternalCtxt.csContext); bLogEnabled = ((g_wzcInternalCtxt.wzcContext.dwFlags & WZC_CTXT_LOGGING_ON) != 0); dwErr = WZCSetLoggingState(bLogEnabled); BAIL_ON_LOCK_ERROR(dwErr); lock: LeaveCriticalSection(&g_wzcInternalCtxt.csContext); error: InterlockedDecrement(&g_nThreads); return dwErr; } extern SERVICE_STATUS g_WZCSvcStatus; DWORD RpcCmdInterface( IN DWORD dwHandle, IN DWORD dwCmdCode, IN LPWSTR wszIntfGuid, IN PRAW_DATA prdUserData) { DWORD dwErr = ERROR_SUCCESS; InterlockedIncrement(&g_nThreads); // We need to avoid processing this command if the service is not currently running // We need this protection only for this call because other than for the other calls, // RpcCmdInterface is called from 802.1x which runs within the same service. For all // the other Rpc stubs, the RPC server is shut down prior to destroying the global data // hence there is guaranteed no other calls will be made afterwards. if (g_WZCSvcStatus.dwCurrentState == SERVICE_RUNNING) { DbgPrint((TRC_TRACK, "[RpcCmdInterface(0x%x,%S)", dwCmdCode, wszIntfGuid)); dwErr = WZCSvcCheckRPCAccess(WZC_ACCESS_SET); // currently this is not an RPC call! This is called directly from 802.1x. Consequently, // WZCSvcCheckRPCAccess will return RPC_S_NO_CALL_ACTIVE. We could either remove the // RPC check for now, or pass through the RPC_S_NO_CALL_ACTIVE (since later this could // become an RPC call). We do the latter! if (dwErr == ERROR_SUCCESS || dwErr == RPC_S_NO_CALL_ACTIVE) { dwErr = LstCmdInterface(dwHandle, dwCmdCode, wszIntfGuid, prdUserData); } DbgPrint((TRC_TRACK, "RpcCmdInterface]=%d", dwErr)); } InterlockedDecrement(&g_nThreads); return dwErr; } VOID WZC_DBLOG_SESSION_HANDLE_rundown( WZC_DBLOG_SESSION_HANDLE hSession ) { if (!g_bRpcStarted) { return; } if (!IsDBOpened()) { return; } if (hSession) { (VOID) CloseWZCDbLogSession( hSession ); } return; } DWORD RpcOpenWZCDbLogSession( STRING_HANDLE pServerName, WZC_DBLOG_SESSION_HANDLE * phSession ) { DWORD dwError = 0; InterlockedIncrement(&g_nThreads); dwError = WZCSvcCheckRPCAccess(WZC_ACCESS_QUERY); BAIL_ON_WIN32_ERROR(dwError); if (!phSession) { dwError = ERROR_INVALID_PARAMETER; BAIL_ON_WIN32_ERROR(dwError); } dwError = OpenWZCDbLogSession( pServerName, 0, phSession ); BAIL_ON_WIN32_ERROR(dwError); error: InterlockedDecrement(&g_nThreads); return (dwError); } DWORD RpcCloseWZCDbLogSession( WZC_DBLOG_SESSION_HANDLE * phSession ) { DWORD dwError = 0; InterlockedIncrement(&g_nThreads); dwError = WZCSvcCheckRPCAccess(WZC_ACCESS_QUERY); BAIL_ON_WIN32_ERROR(dwError); if (!IsDBOpened()) { dwError = ERROR_SERVICE_DISABLED; BAIL_ON_WIN32_ERROR(dwError); } if (!phSession) { InterlockedDecrement(&g_nThreads); return (ERROR_INVALID_PARAMETER); } dwError = CloseWZCDbLogSession( *phSession ); BAIL_ON_WIN32_ERROR(dwError); *phSession = NULL; error: InterlockedDecrement(&g_nThreads); return (dwError); } DWORD RpcEnumWZCDbLogRecords( WZC_DBLOG_SESSION_HANDLE hSession, PWZC_DB_RECORD_CONTAINER pTemplateRecordContainer, PBOOL pbEnumFromStart, DWORD dwPreferredNumEntries, PWZC_DB_RECORD_CONTAINER * ppRecordContainer ) { DWORD dwError = 0; PWZC_DB_RECORD pWZCRecords = NULL; DWORD dwNumRecords = 0; PWZC_DB_RECORD pTemplateRecord = NULL; InterlockedIncrement(&g_nThreads); dwError = WZCSvcCheckRPCAccess(WZC_ACCESS_QUERY); BAIL_ON_WIN32_ERROR(dwError); if (!IsDBOpened()) { dwError = ERROR_SERVICE_DISABLED; BAIL_ON_WIN32_ERROR(dwError); } if (!hSession || !pbEnumFromStart) { InterlockedDecrement(&g_nThreads); return (ERROR_NOT_SUPPORTED); } if (!pTemplateRecordContainer || !ppRecordContainer || !*ppRecordContainer) { InterlockedDecrement(&g_nThreads); return (ERROR_INVALID_PARAMETER); } if (pTemplateRecordContainer->pWZCRecords) { if (pTemplateRecordContainer->dwNumRecords != 1) { InterlockedDecrement(&g_nThreads); return (ERROR_INVALID_PARAMETER); } pTemplateRecord = pTemplateRecordContainer->pWZCRecords; } dwError = EnumWZCDbLogRecordsSummary( hSession, pTemplateRecord, pbEnumFromStart, dwPreferredNumEntries, &pWZCRecords, &dwNumRecords, NULL ); if (dwError != ERROR_NO_MORE_ITEMS) { BAIL_ON_WIN32_ERROR(dwError); } (*ppRecordContainer)->pWZCRecords = pWZCRecords; (*ppRecordContainer)->dwNumRecords = dwNumRecords; InterlockedDecrement(&g_nThreads); return (dwError); error: (*ppRecordContainer)->pWZCRecords = NULL; (*ppRecordContainer)->dwNumRecords = 0; InterlockedDecrement(&g_nThreads); return (dwError); } DWORD RpcFlushWZCDbLog( WZC_DBLOG_SESSION_HANDLE hSession ) { DWORD dwError = 0; InterlockedIncrement(&g_nThreads); dwError = WZCSvcCheckRPCAccess(WZC_ACCESS_SET); BAIL_ON_WIN32_ERROR(dwError); if (!IsDBOpened()) { dwError = ERROR_SERVICE_DISABLED; BAIL_ON_WIN32_ERROR(dwError); } if (!hSession){ InterlockedDecrement(&g_nThreads); return(ERROR_INVALID_PARAMETER); } dwError = FlushWZCDbLog( hSession ); BAIL_ON_WIN32_ERROR(dwError); error: InterlockedDecrement(&g_nThreads); return (dwError); } DWORD RpcGetWZCDbLogRecord( WZC_DBLOG_SESSION_HANDLE hSession, PWZC_DB_RECORD_CONTAINER pTemplateRecordContainer, PWZC_DB_RECORD_CONTAINER * ppRecordContainer ) { DWORD dwError = 0; PWZC_DB_RECORD pWZCRecords = NULL; DWORD dwNumRecords = 0; PWZC_DB_RECORD pTemplateRecord = NULL; InterlockedIncrement(&g_nThreads); dwError = WZCSvcCheckRPCAccess(WZC_ACCESS_QUERY); BAIL_ON_WIN32_ERROR(dwError); if (!IsDBOpened()) { dwError = ERROR_SERVICE_DISABLED; BAIL_ON_WIN32_ERROR(dwError); } if(!hSession){ InterlockedDecrement(&g_nThreads); return(ERROR_NOT_SUPPORTED); } if (!pTemplateRecordContainer || !ppRecordContainer || !*ppRecordContainer) { InterlockedDecrement(&g_nThreads); return (ERROR_INVALID_PARAMETER); } if (pTemplateRecordContainer->pWZCRecords) { if (pTemplateRecordContainer->dwNumRecords != 1) { InterlockedDecrement(&g_nThreads); return (ERROR_INVALID_PARAMETER); } pTemplateRecord = pTemplateRecordContainer->pWZCRecords; } dwError = GetWZCDbLogRecord( hSession, pTemplateRecord, &pWZCRecords, NULL ); BAIL_ON_WIN32_ERROR(dwError); (*ppRecordContainer)->pWZCRecords = pWZCRecords; (*ppRecordContainer)->dwNumRecords = 1; InterlockedDecrement(&g_nThreads); return (dwError); error: (*ppRecordContainer)->pWZCRecords = NULL; (*ppRecordContainer)->dwNumRecords = 0; InterlockedDecrement(&g_nThreads); return (dwError); }