//+-------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1997 - 1997.
//
//  File:        sidcache.cxx
//
//  Contents:    Implementation of the sid/name lookup cache
//
//  History:     2-Feb-97       MacM        Created
//
//--------------------------------------------------------------------
#include <aclpch.hxx>
#pragma hdrstop

//
// Global name/sid cache
//
PACTRL_NAME_CACHE    grgNameCache[ACTRL_NAME_TABLE_SIZE];
PACTRL_NAME_CACHE    grgSidCache[ACTRL_NAME_TABLE_SIZE];

//
// Local function prototypes
//
PACTRL_NAME_CACHE AccctrlpLookupNameInCache(PWSTR   pwszName);

PACTRL_NAME_CACHE AccctrlpLookupSidInCache(PSID     pSid);

DWORD   AccctrlpNewNameSidNode(PWSTR                pwszName,
                               PSID                 pSid,
                               SID_NAME_USE         SidNameUse,
                               PACTRL_NAME_CACHE   *ppNewNode);

VOID    AccctrlpInsertNameNode(PACTRL_NAME_CACHE *ppRootNode,
                               PACTRL_NAME_CACHE  pNewNode);

VOID    AccctrlpInsertSidNode(PACTRL_NAME_CACHE *ppRootNode,
                              PACTRL_NAME_CACHE  pNewNode);

DWORD   AccctrlpConvertUserToCacheName(PWSTR      pwszServer,
                                       PWSTR      pwszName,
                                       PWSTR     *ppwszCacheName);

VOID    AccctrlpFreeUserCacheName(PWSTR      pwszName,
                                  PWSTR      pwszCacheName);

static RTL_RESOURCE gSidCacheLock;
BOOL bSidCacheLockInitialized = FALSE;

//+----------------------------------------------------------------------------
//
//  Function:   ActrlHashName
//
//  Synopsis:   Determines the hash index for the given name
//
//  Arguments:  pwszName        --      Name to hash
//
//  Returns:    Hash index of the string
//
//-----------------------------------------------------------------------------
INT
ActrlHashName(PWSTR pwszName)
{
    //
    // We'll hash off of just the user name, not the domain name or
    // any other name format
    //
    PWSTR   pwszUser = wcschr(pwszName, L'\\');
    if(pwszUser == NULL)
    {
        pwszUser = pwszName;
    }
    else
    {
        pwszUser++;
    }

    INT Hash = 0;
    if(pwszUser != NULL)
    {
        while(*pwszUser != L'\0')
        {
            Hash = (Hash * 16 + ( *pwszUser++)) % ACTRL_NAME_TABLE_SIZE;
        }
    }

    acDebugOut((DEB_TRACE_LOOKUP,"Hashing %ws to %lu\n", pwszName, Hash));

    return(Hash);
}




//+----------------------------------------------------------------------------
//
//  Function:   ActrlHashSid
//
//  Synopsis:   Determines the hash index for the given sid
//
//  Arguments:  pSid            --      Sid to hash
//
//  Returns:    Hash index of the Sid
//
//-----------------------------------------------------------------------------
INT
ActrlHashSid(PSID   pSid)
{
    DWORD   dwTotal = 0;

    //
    // Just deal with the sub authorities
    //
    for(INT i = 0; i < (INT)(((PISID)pSid)->SubAuthorityCount); i++)
    {
        dwTotal += ((PISID)pSid)->SubAuthority[i];
    }

#if DBG

    UNICODE_STRING SidString;

    memset(&SidString, 0, sizeof(UNICODE_STRING));

    if(pSid != NULL)
    {
        NTSTATUS Status = RtlConvertSidToUnicodeString(&SidString,
                                                       pSid,
                                                       TRUE);
        if(!NT_SUCCESS(Status))
        {
            acDebugOut((DEB_ERROR, "Can't convert sid to string: 0x%lx\n", Status));
        }
        else
        {
            acDebugOut((DEB_TRACE_LOOKUP,"Hashing %wZ (Total %lu) to %lu\n", &SidString,
                       dwTotal, dwTotal % ACTRL_NAME_TABLE_SIZE));
        }
    }


#endif
    return(dwTotal % ACTRL_NAME_TABLE_SIZE);
}





//+----------------------------------------------------------------------------
//
//  Function:   AccctrlInitializeSidNameCache
//
//  Synopsis:   Initialize the name/sid lookup cache
//
//  Arguments:  VOID
//
//  Returns:    ERROR_SUCCESS   --      Success
//
//-----------------------------------------------------------------------------
DWORD AccctrlInitializeSidNameCache(VOID)
{
    DWORD dwErr;

    if (TRUE == bSidCacheLockInitialized)
    {
        //
        // Just a precautionary measure to make sure that we do not initialize
        // multiple times.
        //

        ASSERT(FALSE);
        return ERROR_SUCCESS;
    }

    memset(grgNameCache, 0, sizeof(PACTRL_NAME_CACHE) * ACTRL_NAME_TABLE_SIZE);
    memset(grgSidCache, 0, sizeof(PACTRL_NAME_CACHE) * ACTRL_NAME_TABLE_SIZE);

    __try
    {
        RtlInitializeResource(&gSidCacheLock);
        dwErr = ERROR_SUCCESS;
        bSidCacheLockInitialized = TRUE;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        dwErr = RtlNtStatusToDosError(GetExceptionCode());
    }

    return dwErr;
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlFreeSidNameCache
//
//  Synopsis:   Frees any memory allocated for the name/sid cache
//
//  Arguments:  VOID
//
//  Returns:    VOID
//
//-----------------------------------------------------------------------------
VOID AccctrlFreeSidNameCache(VOID)
{
    INT i;
    PACTRL_NAME_CACHE   pNode, pNext;

    if (FALSE == bSidCacheLockInitialized)
    {
        return;
    }

    for(i = 0; i < ACTRL_NAME_TABLE_SIZE; i++)
    {
        //
        // Nodes are only inserted into the name cache, so that is the only
        // place we delete them from
        //
        pNode = grgNameCache[i];
        while(pNode != NULL)
        {
            pNext = pNode->pNextName;
            AccFree(pNode->pwszName);
            AccFree(pNode->pSid);
            AccFree(pNode);
            pNode = pNext;
        }
    }

    RtlDeleteResource(&gSidCacheLock);

    bSidCacheLockInitialized = FALSE;

}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlpLookupNameInCache
//
//  Synopsis:   Determines if the given name exists in the cache or not
//
//  Arguments:  [pwszName]      --      Name to be looked up
//
//  Returns:    Matching node if found, NULL if not
//
//-----------------------------------------------------------------------------
PACTRL_NAME_CACHE AccctrlpLookupNameInCache(PWSTR   pwszName)
{
    PACTRL_NAME_CACHE   pNode = NULL;

    pNode =  grgNameCache[ActrlHashName(pwszName)];

    while(pNode != NULL)
    {
        if(_wcsicmp(pwszName, pNode->pwszName) == 0)
        {
            break;
        }
        pNode = pNode->pNextName;
    }

    return(pNode);
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlpLookupSidInCache
//
//  Synopsis:   Determines if the given sid exists in the cache or not
//
//  Arguments:  [pSid]          --      Sid to be looked up
//
//  Returns:    Matching node if found, NULL if not
//
//-----------------------------------------------------------------------------
PACTRL_NAME_CACHE AccctrlpLookupSidInCache(PSID     pSid)
{
    PACTRL_NAME_CACHE   pNode = grgSidCache[ActrlHashSid(pSid)];

    while(pNode != NULL)
    {
        if(RtlEqualSid(pSid, pNode->pSid) == TRUE)
        {
            break;
        }
        pNode = pNode->pNextSid;
    }

    return(pNode);
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlpNewNameSidNode
//
//  Synopsis:   Allocates a new node and inserts them into the caches
//
//  Arguments:  [pwszName]      --      Name to insert
//              [pSid]          --      Sid to insert
//              [SidNameUse]    --      Name use
//              [pNewNode]      --      Newly added node
//
//  Returns:    ERROR_SUCCESS   --      Success
//              ERROR_NOT_ENOUGH_MEMORY A memory allocation failed
//
//-----------------------------------------------------------------------------
DWORD   AccctrlpNewNameSidNode(PWSTR                pwszName,
                               PSID                 pSid,
                               SID_NAME_USE         SidNameUse,
                               PACTRL_NAME_CACHE   *ppNewNode)
{
    DWORD   dwErr = ERROR_SUCCESS;

    PACTRL_NAME_CACHE   pNewNode = (PACTRL_NAME_CACHE)AccAlloc(
                                                    sizeof(ACTRL_NAME_CACHE));
    if(pNewNode == NULL)
    {
        dwErr = ERROR_NOT_ENOUGH_MEMORY;
    }
    else
    {
        pNewNode->pwszName = pwszName;
        pNewNode->pSid     = pSid;
        pNewNode->SidUse   = SidNameUse;
        pNewNode->pNextName= NULL;
        pNewNode->pNextSid = NULL;

        AccctrlpInsertNameNode(&(grgNameCache[ActrlHashName(pwszName)]),
                               pNewNode);

        AccctrlpInsertSidNode(&(grgSidCache[ActrlHashSid(pSid)]),
                              pNewNode);

        *ppNewNode = pNewNode;

    }
    return(dwErr);
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlpInsertNameNode
//
//  Synopsis:   Inserts the specified new node into the caches
//
//  Arguments:  [ppRootNode]    --      Root node in the name cache
//              [pNewNode]      --      Node to insert
//
//  Returns:    VOID
//
//-----------------------------------------------------------------------------
VOID    AccctrlpInsertNameNode(PACTRL_NAME_CACHE *ppRootNode,
                               PACTRL_NAME_CACHE  pNewNode)
{
    PACTRL_NAME_CACHE   pNext = NULL;

    if(*ppRootNode == NULL)
    {
        *ppRootNode = pNewNode;
    }
    else
    {
        acDebugOut((DEB_TRACE_LOOKUP, "Collision inserting %ws with:\n", pNewNode->pwszName));

        pNext = *ppRootNode;
        acDebugOut((DEB_TRACE_LOOKUP, "\t%ws\n", pNext->pwszName));
        while(pNext->pNextName != NULL)
        {
#if DBG
            if(_wcsicmp(pNewNode->pwszName, pNext->pwszName) == 0)
            {
                acDebugOut((DEB_ERROR, "Name %ws already in list: 0x%lx\n",
                            pNewNode->pwszName,
                            *ppRootNode));
//                ASSERT(FALSE);
            }
#endif

            pNext = pNext->pNextName;
            acDebugOut((DEB_TRACE_LOOKUP, "\t%ws\n", pNext->pwszName));
        }
        pNext->pNextName = pNewNode;
    }
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlpInsertSidNode
//
//  Synopsis:   Inserts the specified new node into the caches
//
//  Arguments:  [ppRootNode]    --      Root node in the name cache
//              [pNewNode]      --      Node to insert
//
//  Returns:    VOID
//
//-----------------------------------------------------------------------------
VOID    AccctrlpInsertSidNode(PACTRL_NAME_CACHE *ppRootNode,
                              PACTRL_NAME_CACHE  pNewNode)
{
    PACTRL_NAME_CACHE   pNext = NULL;

    if(*ppRootNode == NULL)
    {
        *ppRootNode = pNewNode;
    }
    else
    {
        acDebugOut((DEB_TRACE_LOOKUP, "Collision inserting %ws with:\n", pNewNode->pwszName));

        pNext = *ppRootNode;
        acDebugOut((DEB_TRACE_LOOKUP, "\t%ws\n", pNext->pwszName));
        while(pNext->pNextSid != NULL)
        {
#if DBG
            if(RtlEqualSid(pNewNode->pSid, pNext->pSid) == TRUE)
            {
                acDebugOut((DEB_ERROR, "Sid for %ws already in list: 0x%lx\n",
                            pNewNode->pwszName,
                            *ppRootNode));
//                ASSERT(FALSE);
            }
#endif

            pNext = pNext->pNextSid;

            acDebugOut((DEB_TRACE_LOOKUP, "\t%ws\n", pNext->pwszName));
        }
        pNext->pNextSid = pNewNode;
    }
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlLookupName
//
//  Synopsis:   Looks up the name for the specified SID
//
//  Arguments:  [pwszServer]    --      Name of the server to remote the call to
//              [pSid]          --      Sid to lookup
//              [fAllocateReturn]-      If true, the name returned is allocated
//                                      into a new buffer.  Otherwise, a
//                                      reference is returned.
//              [ppwszName]     --      Where the name is returned.
//              [pSidNameUse]   --      Type of the name that's returned.
//
//  Returns:    VOID
//
//-----------------------------------------------------------------------------
DWORD
AccctrlLookupName(IN  PWSTR          pwszServer,
                  IN  PSID           pSid,
                  IN  BOOL           fAllocateReturn,
                  OUT PWSTR         *ppwszName,
                  OUT PSID_NAME_USE  pSidNameUse)
{
    DWORD   dwErr = ERROR_SUCCESS;

    if(pSid == NULL)
    {
        return(ERROR_NONE_MAPPED);
    }

    RtlAcquireResourceShared(&gSidCacheLock, TRUE);

#if DBG
    UNICODE_STRING SidString;

    memset(&SidString, 0, sizeof(UNICODE_STRING));

    if(pSid != NULL)
    {
        NTSTATUS Status = RtlConvertSidToUnicodeString(&SidString,
                                                       pSid,
                                                       TRUE);
        if(!NT_SUCCESS(Status))
        {
            acDebugOut((DEB_ERROR, "Can't convert sid to string: 0x%lx\n", Status));
        }
    }
#endif

    //
    // First, see if the sid alreadt exists in our cache
    //
    PACTRL_NAME_CACHE pNode = AccctrlpLookupSidInCache(pSid);
    if(pNode == NULL)
    {
#if DBG
        acDebugOut((DEB_TRACE_LOOKUP, "Sid %wZ not found in cache\n", &SidString));
#endif
        //
        // Grab a write lock
        //
        RtlConvertSharedToExclusive(&gSidCacheLock);


        //
        // We'll have to look it up...
        //
        PWSTR   pwszName, pwszDomain;
        dwErr = AccLookupAccountName(pwszServer,
                                     pSid,
                                     &pwszName,
                                     &pwszDomain,
                                     pSidNameUse);
        if(dwErr == ERROR_SUCCESS)
        {
            PSID    pNewSid = NULL;
            ACC_ALLOC_AND_COPY_SID(pSid, pNewSid, dwErr);
            if(dwErr == ERROR_SUCCESS)
            {
                dwErr = AccctrlpNewNameSidNode(pwszName,
                                               pNewSid,
                                               *pSidNameUse,
                                               &pNode);
            }

            if(dwErr != ERROR_SUCCESS)
            {
                AccFree(pwszName);
                AccFree(pwszDomain);
                AccFree(pNewSid);
            }
        }
    }
#if DBG
    else
    {
        acDebugOut((DEB_TRACE_LOOKUP, "Sid %wZ found in cache\n", &SidString));
    }
#endif

    //
    // Finally, return the information
    //
    if(dwErr == ERROR_SUCCESS)
    {
        if(fAllocateReturn == TRUE)
        {
            ACC_ALLOC_AND_COPY_STRINGW(pNode->pwszName, *ppwszName, dwErr);
        }
        else
        {
            *ppwszName = pNode->pwszName;
        }

        *pSidNameUse = pNode->SidUse;
    }

    RtlReleaseResource(&gSidCacheLock);

    return(dwErr);
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlLookupSid
//
//  Synopsis:   Looks up the SID for the specified name
//
//  Arguments:  [pwszServer]    --      Name of the server to remote the call to
//              [pwszName]      --      Name to lookup
//              [fAllocateReturn]-      If true, the name returned is allocated
//                                      into a new buffer.  Otherwise, a
//                                      reference is returned.
//              [ppwszName]     --      Where the name is returned.
//              [pSidNameUse]   --      Type of the sid that's returned.
//
//  Returns:    VOID
//
//-----------------------------------------------------------------------------
DWORD
AccctrlLookupSid(IN  PWSTR          pwszServer,
                 IN  PWSTR          pwszName,
                 IN  BOOL           fAllocateReturn,
                 OUT PSID          *ppSid,
                 OUT PSID_NAME_USE  pSidNameUse)
{
    DWORD   dwErr = ERROR_SUCCESS;
    PWSTR   pwszLookupName = pwszName;

    RtlAcquireResourceShared(&gSidCacheLock, TRUE);

    //
    // If we get a local name, convert it into machine/domain relative, so we can
    // look it up properly.
    //
    dwErr = AccctrlpConvertUserToCacheName(pwszServer, pwszName, &pwszLookupName);

    //
    // Just return if the conversion failed.
    //

    if (pwszLookupName == NULL)
    {
        if (dwErr == ERROR_SUCCESS)
        {
            dwErr = ERROR_ACCESS_DENIED;
        }
    }

    if(dwErr == ERROR_SUCCESS)
    {
        //
        // First, see if the sid already exists in our cache
        //
        PACTRL_NAME_CACHE pNode = AccctrlpLookupNameInCache(pwszLookupName);
        if(pNode == NULL)
        {
            //
            // Grab a write lock
            //
            RtlConvertSharedToExclusive(&gSidCacheLock);

            acDebugOut((DEB_TRACE_LOOKUP,"Name %ws not found in cache\n", pwszLookupName));
            //
            // We'll have to look it up...
            //
            TRUSTEE_W   Trustee;
            memset(&Trustee, 0, sizeof(TRUSTEE_W));
            Trustee.TrusteeForm = TRUSTEE_IS_NAME;
            Trustee.ptstrName = pwszLookupName;

            dwErr = AccLookupAccountSid(pwszServer,
                                        &Trustee,
                                        ppSid,
                                        pSidNameUse);
            if(dwErr == ERROR_SUCCESS)
            {
                PWSTR   pwszNewName = NULL;
                ACC_ALLOC_AND_COPY_STRINGW(pwszLookupName, pwszNewName, dwErr);
                if(dwErr == ERROR_SUCCESS)
                {
                    dwErr = AccctrlpNewNameSidNode(pwszNewName,
                                                   *ppSid,
                                                   *pSidNameUse,
                                                   &pNode);
                }

                if(dwErr != ERROR_SUCCESS)
                {
                    AccFree(pwszNewName);
                    AccFree(*ppSid);
                }
            }
        }
    #if DBG
        else
        {
            acDebugOut((DEB_TRACE_LOOKUP,"Name %ws found in cache\n", pwszLookupName));
        }
    #endif

        //
        // Finally, return the information
        //
        if(dwErr == ERROR_SUCCESS)
        {
            if(fAllocateReturn == TRUE)
            {
                ACC_ALLOC_AND_COPY_SID(pNode->pSid, *ppSid, dwErr);
            }
            else
            {
                *ppSid = pNode->pSid;
            }

            *pSidNameUse = pNode->SidUse;
        }

        AccctrlpFreeUserCacheName(pwszName, pwszLookupName);
    }

    RtlReleaseResource(&gSidCacheLock);


    return(dwErr);
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlpConvertUserToCacheName
//
//  Synopsis:   Converts an input name that could be domain relative into a
//              standard format for caching/returning
//
//  Arguments:  [pwszServer]    --      Server to lookup the name on
//              [pwszName]      --      Original name format
//              [ppwszCacheName]--      Name in the proper format
//
//  Returns:    ERROR_SUCCESS   --      Success
//              ERROR_NOT_ENOUGH_MEMORY A memory allocation failed
//
//-----------------------------------------------------------------------------
DWORD   AccctrlpConvertUserToCacheName(PWSTR      pwszServer,
                                       PWSTR      pwszName,
                                       PWSTR     *ppwszCacheName)
{
    DWORD   dwErr = ERROR_SUCCESS;

    //
    // This is temporary until the name conversion APIs come into being
    //
    PSID    pSid;
    SID_NAME_USE    SNE;
    TRUSTEE_W   Trustee;
    memset(&Trustee, 0, sizeof(TRUSTEE_W));
    Trustee.TrusteeForm = TRUSTEE_IS_NAME;
    Trustee.ptstrName = pwszName;

    dwErr = AccLookupAccountSid(pwszServer,
                                &Trustee,
                                &pSid,
                                &SNE);
    if(dwErr == ERROR_SUCCESS)
    {
        PWSTR   pwszDomain;
        dwErr = AccLookupAccountName(pwszServer,
                                     pSid,
                                     ppwszCacheName,
                                     &pwszDomain,
                                     &SNE);
        if(dwErr == ERROR_SUCCESS)
        {
            AccFree(pwszDomain);
        }

        AccFree(pSid);
    }


    return(dwErr);
}




//+----------------------------------------------------------------------------
//
//  Function:   AccctrlpFreeUserCacheName
//
//  Synopsis:   Frees any memory potentially allocated by
//              AccctrlpConvertUserToCacheName
//
//  Arguments:  [pwszName]      --      Original name format
//              [pwszCacheName] --      Name returned by
//                                      AccctrlpConvertUserToCacheName
//
//  Returns:    VOID
//
//-----------------------------------------------------------------------------
VOID    AccctrlpFreeUserCacheName(PWSTR      pwszName,
                                  PWSTR      pwszCacheName)
{
    if(pwszName != pwszCacheName)
    {
        AccFree(pwszCacheName);
    }
}