#include <precomp.h>
#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 (FIELD_OFFSET(NDIS_802_11_SSID, Ssid) + pndSSID->SsidLength > nBytes)
            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);
    LeaveCriticalSection(&g_wzcInternalCtxt.csContext);
    
    if (bLogEnabled) {
        if (!IsDBOpened()) {
            dwErr = InitWZCDbGlobals();
            BAIL_ON_WIN32_ERROR(dwErr);
        }
    }
    else {
        if (IsDBOpened()) {
            DeInitWZCDbGlobals();
        }
    }

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 (!IsDBOpened()) {
        dwError = ERROR_SERVICE_DISABLED;
        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
RpcAddWZCDbLogRecord(
    STRING_HANDLE pServerName,
    PWZC_DB_RECORD_CONTAINER pRecordContainer
    )
{
    DWORD dwError = 0;
    PWZC_DB_RECORD pWZCRecord = NULL;

    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 (!pRecordContainer) {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_WIN32_ERROR(dwError);
    }

    if (!(pRecordContainer->pWZCRecords) ||
        !(pRecordContainer->dwNumRecords)) {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_WIN32_ERROR(dwError);
    }

    pWZCRecord = pRecordContainer->pWZCRecords;

    dwError = AddWZCDbLogRecord(
                  pServerName,
                  0,
                  pWZCRecord,
                  NULL
                  );
    BAIL_ON_WIN32_ERROR(dwError);

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);
}