// Copyright (c) 1996-1999 Microsoft Corporation

//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  File:       idt_ldap.cxx
//
//  Contents:   Intra-domain table based on LDAP.
//
//  Classes:
//
//  Functions:  
//              
//
//
//  History:    18-Nov-96  BillMo      Created.
//
//  Notes:      
//
//  Codework:
//
//--------------------------------------------------------------------------



#include "pch.cxx"
#pragma hdrstop

#include "trksvr.hxx"


CLdapOMTAddModify::CLdapOMTAddModify(
                const CDomainRelativeObjId & ldKey,
                const CDomainRelativeObjId & ldNew,
                const CDomainRelativeObjId & ldBirth,
                const ULONG & seqRefresh,
                BYTE bFlags,
                int   mod_op )
                :
                 
    _lsmClass(s_objectClass, s_linkTrackOMTEntry, 0),

    _lbmCurrentLocation( s_currentLocation, reinterpret_cast<PCCH>(&ldNew), sizeof(ldNew), mod_op ),
    _lbmBirthLocation( s_birthLocation, reinterpret_cast<PCCH>(&ldBirth), sizeof(ldBirth), mod_op ),
    _ltvRefresh( seqRefresh ),
    _lsmRefresh( s_timeRefresh, _ltvRefresh, mod_op ),
    _lbmFlags( s_oMTIndxGuid, reinterpret_cast<PCCH>(&bFlags), sizeof(BYTE), mod_op )

{
    int i = 0;
    if (mod_op == LDAP_MOD_ADD)
    {
        _mods[i++] = &_lsmClass._mod;
        _mods[i++] = &_lbmFlags._mod;
    }
    _mods[i++] = &_lbmCurrentLocation._mod;

    if (ldKey != ldBirth)
        _mods[i++] = &_lbmBirthLocation._mod;

    _mods[i++] = &_lsmRefresh._mod;
    _mods[i++] = NULL;

    TrkAssert(i <= sizeof(_mods)/sizeof(_mods[0]));
}

void
CIntraDomainTable::Initialize( CTrkSvrConfiguration *pTrkSvrConfiguration, CQuotaTable* pqtable )
{
    _fInitializeCalled = TRUE;
    _pqtable = pqtable;
    _pTrkSvrConfiguration = pTrkSvrConfiguration;
    _QuotaReported.Initialize();
}

void
CIntraDomainTable::UnInitialize()
{
    if (_fInitializeCalled)
    {
    }
    _fInitializeCalled = FALSE;
}


//+----------------------------------------------------------------------------
//
//  CIntraDomainTable::Add
//
//  Add an entry to the move table.  Return TRUE if the entry was added, but
//  didn't already exist.  Return FALSE if the entry already exists.  If
//  there's an error, raise an exception.
//
//+----------------------------------------------------------------------------

BOOL
CIntraDomainTable::Add(const CDomainRelativeObjId &ldKey, 
                       const CDomainRelativeObjId &ldNew, 
                       const CDomainRelativeObjId &ldBirth,
                       BOOL  *pfQuotaExceeded OPTIONAL )
{
    int     err;
    BYTE    bFlags = QFLAG_UNCOUNTED;

    CLdapIdtKeyDn   dnKey(GetBaseDn(), ldKey);

    // The following constructor sets up the mods array for
    // the ldap_add_s call that we're about to do.

    CLdapOMTAddModify  lam( ldKey,              // Key
                            ldNew,              // New droid
                            ldBirth,            // Birth droid
                                                // Current sequence number
                            _pRefreshSequenceStorage->GetSequenceNumber(),
                            bFlags,             // Flags, will be pointed to
                                                //   (so can't e.g. use a #define value)
                            LDAP_MOD_ADD);      // Add this element

    // Return FALSE here means that we pretend to the caller that the entry
    // already exists. We don't want to raise an exception here. Our caller
    // always tries to add the entry, if the add fails because the entry
    // already exists, the caller will then try to modify the entry. If the
    // quota has been exceeded, we don't want to add more entries, but we
    // still want entries already in the table to be modifiable. So instead of
    // raising an exception, we really want the caller to try to modify the
    // entry instead.

    if(_pqtable->IsMoveQuotaExceeded())
    {
        if( NULL != pfQuotaExceeded )
            *pfQuotaExceeded = TRUE;

        if( !_QuotaReported.IsSet() )
        {
            _QuotaReported.Set();
            TrkReportEvent( EVENT_TRK_SERVICE_MOVE_QUOTA_EXCEEDED, EVENTLOG_WARNING_TYPE,
                            TRKREPORT_LAST_PARAM );
        }

        return FALSE;
    }
    else
    {
        _QuotaReported.Clear();
    }

    err = ldap_add_s(Ldap(), dnKey, lam._mods);
    if (err == LDAP_SUCCESS)
    {
        {
            LDAPMod*        mods[2];
            BYTE bFlags = QFLAG_UNCOUNTED;
            CLdapBinaryMod  lbm(s_oMTIndxGuid, reinterpret_cast<PCCH>(&bFlags), sizeof(BYTE), LDAP_MOD_REPLACE);

            mods[0] = &lbm._mod;
            mods[1] = NULL;

            err = ldap_modify_s(Ldap(), dnKey, mods);
        }

        _pqtable->IncrementMoveCountCache();
        return(TRUE);
    }
    else
    if (err == LDAP_ALREADY_EXISTS)
    {
        return(FALSE);
    }
    else
    {
        TrkRaiseWin32Error(LdapMapErrorToWin32(err));
        return(FALSE);
    }

}

// TRUE if found and deleted, FALSE if not found, exception on other errors

BOOL
CIntraDomainTable::Delete(const CDomainRelativeObjId &ldKey)
{
    int err;
    CLdapIdtKeyDn       dnKey(GetBaseDn(), ldKey);
    BOOL fFound;

    fFound = _pqtable->UpdateFlags(Ldap(), dnKey, QFLAG_DELETED);
    if( fFound )
        _pqtable->DecrementMoveCountCache();
    return fFound;
}

// TRUE if entry exists and modified, FALSE if not existent, exception otherwise
BOOL
CIntraDomainTable::Modify(const CDomainRelativeObjId &ldKey, 
                          const CDomainRelativeObjId &ldNew, 
                          const CDomainRelativeObjId &ldBirth )
{
    int             err;
    CLdapIdtKeyDn   dnKey(GetBaseDn(), ldKey);
    CLdapOMTAddModify  lam( ldKey,
                            ldNew, 
                            ldBirth, 
                            _pRefreshSequenceStorage->GetSequenceNumber(), 
                            0, // Only used with LDAP_MOD_ADD
                            LDAP_MOD_REPLACE);

    err = ldap_modify_s(Ldap(), dnKey, lam._mods);

    if (err == LDAP_SUCCESS)
    {
        return(TRUE);
    }
    else
    if (err == LDAP_NO_SUCH_OBJECT)
    {
        return(FALSE);
    }
    else
    {
        TrkRaiseWin32Error(LdapMapErrorToWin32(err));
        return(FALSE);
    }
}

// must leave outputs unchanged if returning FALSE
BOOL
CIntraDomainTable::Query(const CDomainRelativeObjId &ldKey, 
                         CDomainRelativeObjId *pldNew,
                         CDomainRelativeObjId *pldBirth,
                         BOOL *pfDeleted OPTIONAL,
                         BOOL *pfCounted OPTIONAL )
{
    int             err;
    TCHAR           *aptszAttrs[] = { const_cast<TCHAR*>(s_currentLocation),
                                      const_cast<TCHAR*>(s_birthLocation),
                                      const_cast<TCHAR*>(s_oMTIndxGuid),
                                      NULL };
    LDAPMessage *   pRes = NULL;
    CLdapIdtKeyDn   dnKey(GetBaseDn(), ldKey);
    BOOL            fFound = FALSE;

    __try
    {
        err = ldap_search_s( Ldap(),
                             dnKey,
                             LDAP_SCOPE_BASE,
                             TEXT("(objectclass=*)"),
                             aptszAttrs,
                             0, // attribute types and values are wanted
                             &pRes );

        if (err == LDAP_SUCCESS)
        {
            // found it, lets get the attributes out

            if (ldap_count_entries(Ldap(), pRes) == 1)
            {
                LDAPMessage * pEntry = ldap_first_entry(Ldap(), pRes);
                if (pEntry == NULL)
                {
                    TrkRaiseWin32Error(LdapMapErrorToWin32(Ldap()->ld_errno));
                }

                fFound = Query( Ldap(), pEntry, ldKey, pldNew, pldBirth, pfDeleted, pfCounted );
            }
        }
        else
        if (err != LDAP_NO_SUCH_OBJECT)
        {
            TrkRaiseWin32Error(LdapMapErrorToWin32(err));
        }
    }
    __finally
    {
        if (NULL != pRes)
            ldap_msgfree(pRes);
    }

    return(fFound);
}


BOOL
CIntraDomainTable::Query( LDAP* pLdap,
                          LDAPMessage *pEntry,
                          const CDomainRelativeObjId ldKey,
                          CDomainRelativeObjId *pldNew,
                          CDomainRelativeObjId *pldBirth,
                          BOOL *pfDeleted OPTIONAL,
                          BOOL *pfCounted OPTIONAL )
{
    BOOL fFound = FALSE;
    struct berval **ppbvCurrentLocation = NULL;
    struct berval **ppbvBirthLocation = NULL;
    struct berval **ppbvQuotaFlags = NULL;
    BYTE bQuotaFlags = 0;

    if( NULL != pfDeleted )
        *pfDeleted = FALSE;

    __try
    {
        ppbvBirthLocation = ldap_get_values_len(pLdap, pEntry,
                                                const_cast<TCHAR*>(s_birthLocation) );

        if (NULL != ppbvBirthLocation
            &&
            sizeof(*pldBirth) > (*ppbvBirthLocation)->bv_len)
        {
            TrkLog((TRKDBG_ERROR, TEXT("Couldn't get current location for %s"),
                               (const TCHAR*)CDebugString(ldKey) ));
            TrkRaiseWin32Error( LdapMapErrorToWin32(pLdap->ld_errno) );
        }

        ppbvCurrentLocation = ldap_get_values_len(pLdap, pEntry,
                                                  const_cast<TCHAR*>(s_currentLocation) );
        if (NULL == ppbvCurrentLocation
            ||
            sizeof(*pldNew) > (*ppbvCurrentLocation)->bv_len)
        {
            TrkLog((TRKDBG_ERROR, TEXT("Couldn't get current location for %s"),
                    (const TCHAR*)CDebugString(ldKey) ));
            TrkRaiseWin32Error( LdapMapErrorToWin32(pLdap->ld_errno) );
        }

        ppbvQuotaFlags = ldap_get_values_len(pLdap, pEntry,
                                             const_cast<TCHAR*>(s_oMTIndxGuid) );

        if (NULL != ppbvQuotaFlags
            &&
            sizeof(BYTE) <= (*ppbvQuotaFlags)->bv_len)
        {
            bQuotaFlags = *(BYTE*)(*ppbvQuotaFlags)->bv_val;
        }

        if( NULL != pfCounted )
        {
            if( bQuotaFlags & QFLAG_UNCOUNTED )
                *pfCounted = FALSE;
            else
                *pfCounted = TRUE;
        }
    

        if( bQuotaFlags & QFLAG_DELETED )
        {
            if( NULL != pfDeleted )
                *pfDeleted = TRUE;

            TrkLog(( TRKDBG_IDT, TEXT("IdtQuery: Entry marked deleted will be ignored (0x%x): %s"),
                     bQuotaFlags, 
                     (const TCHAR*)CDebugString(ldKey) ));
        }
        else
        {
            *pldNew   = *reinterpret_cast<CDomainRelativeObjId*>( (*ppbvCurrentLocation)->bv_val );

            if (NULL != ppbvBirthLocation)
                *pldBirth = *reinterpret_cast<CDomainRelativeObjId*>( (*ppbvBirthLocation)->bv_val );
            else
                *pldBirth = ldKey;

            fFound = TRUE;
        }
    }
    __finally
    {
        if (NULL != ppbvCurrentLocation)
            ldap_value_free_len(ppbvCurrentLocation);

        if (NULL != ppbvBirthLocation)
            ldap_value_free_len(ppbvBirthLocation);

        if (NULL != ppbvQuotaFlags)
            ldap_value_free_len(ppbvQuotaFlags);
    }

    return( fFound );
}



//+----------------------------------------------------------------------------
//
//  CIntraDomainTable::Touch
//
//  Update the refresh attribute for an entry in the move table.
//  Return TRUE if the entry exists and was touched, FALSE if it
//  doesn't exist, and raise an exception if there's an error.
//
//+----------------------------------------------------------------------------

// BUGBUG: if we ever move to per-user quotas,
// check ownership of entry being touched.

BOOL
CIntraDomainTable::Touch(
    const CDomainRelativeObjId &ldKey
    )
{
    BOOL            fReturn = FALSE;
    int             err;
    CLdapTimeValue  ltvRefresh( _pRefreshSequenceStorage->GetSequenceNumber());
    CLdapStringMod  lsmRefresh( s_timeRefresh, ltvRefresh, LDAP_MOD_REPLACE );
    CLdapIdtKeyDn   dnKey(GetBaseDn(), ldKey);
    TCHAR **        pptszRefresh = NULL;
    LDAPMessage   * pEntry = NULL;
    LDAPMessage*    pRes = NULL;

    LDAPMod *       mods[2];
    CObjId          objZero;

    __try
    {
        //
        // if the birth id is zero, then it is invalid and not worth doing a refresh
        //

        if (ldKey.GetObjId() == objZero)
        {
            __leave;
        }

        //
        // Check to see if the object already has a recent sequence number.
        //

        TCHAR*          rgptszAttrs[2];
        rgptszAttrs[0] = const_cast<TCHAR*>(s_timeRefresh);
        rgptszAttrs[1] = NULL;

        err = ldap_search_s(Ldap(),
                            dnKey,
                            LDAP_SCOPE_BASE,
                            TEXT("(ObjectClass=*)"),
                            rgptszAttrs,
                            0,
                            &pRes);

        if (err == LDAP_SUCCESS)
        {
            // The search call worked, but did we find an object?
            if( 1 == ldap_count_entries(Ldap(), pRes) )
            {
                // The object already exists
                pEntry = ldap_first_entry(Ldap(), pRes);
                if( NULL != pEntry )
                {
                    // Get the refresh counter
                    pptszRefresh = ldap_get_values( Ldap(), pEntry, const_cast<TCHAR*>(s_timeRefresh) );
                    if( NULL != pptszRefresh )
                    {
                        SequenceNumber seqRefresh = 0;
                        if( 1 == _stscanf( *pptszRefresh, TEXT("%d"), &seqRefresh ))
                        {
                            // Is the refresh counter already set to a recent value?
                            // We'll consider it recent enough if it's within half of the
                            // refresh cycle (15 days)

                            // First, how long is the GC timer in seconds?
                            LONG lGCTimerInSeconds = _pTrkSvrConfiguration->GetGCPeriod()     // 30 days in seconds
                                                     / _pTrkSvrConfiguration->GetGCDivisor(); // => 1 day in seconds

                            // Next, how many ticks is half the period?
                            LONG lWindow =  _pTrkSvrConfiguration->GetGCPeriod()    // 30 days (in seconds)
                                            / 2                                     // => 15 days (in seconds)
                                            / lGCTimerInSeconds;                    // => 15

                            TrkLog(( TRKDBG_WARNING, TEXT("Window = %d"), lWindow ));

                            if( seqRefresh + lWindow
                                >= _pRefreshSequenceStorage->GetSequenceNumber()
                              )
                            {
                                TrkLog(( TRKDBG_GARBAGE_COLLECT | TRKDBG_IDT,
                                         TEXT("Not touching %s with %d, seq %d already set"),
                                         (const TCHAR*)CDebugString(ldKey),
                                         _pRefreshSequenceStorage->GetSequenceNumber(),
                                         seqRefresh ));
                                __leave;
                            }
                        }
                    }
                }
            }
        }
        else if (err == LDAP_NO_SUCH_OBJECT)
        {
            TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_IDT,
                TEXT("Touch: object %s not found"),
                (const TCHAR*) CDebugString(ldKey) ));
            __leave;
        }


        //
        // Set the correct sequence number
        //

        mods[0] = &lsmRefresh._mod;
        mods[1] = NULL;

        err = ldap_modify_s(Ldap(), dnKey, mods);

        if (err == LDAP_SUCCESS)
        {
            TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_IDT,
                TEXT("Touch: object %s touched"),
                (const TCHAR*) CDebugString(ldKey) ));
            fReturn = TRUE;
            __leave;
        }
        else
        if (err == LDAP_NO_SUCH_OBJECT)
        {
            TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_IDT,
                TEXT("Touch: object %s not found"),
                (const TCHAR*) CDebugString(ldKey) ));
            __leave;
        }
        else
        if (err == LDAP_NO_SUCH_ATTRIBUTE)
        {
            TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR,
                TEXT("Touch: object %s attribute not found"),
                (const TCHAR*) CDebugString(ldKey) ));

            // deal with old server data
            CLdapStringMod lsmRefresh( s_timeRefresh, ltvRefresh, LDAP_MOD_ADD );
            mods[0] = &lsmRefresh._mod;

            err = ldap_modify_s(Ldap(), dnKey, mods);
        }

        if (err != LDAP_SUCCESS)
        {
            TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_IDT,
                TEXT("Touch: object %s --> exceptional error"),
                (const TCHAR*) CDebugString(ldKey) ));
            __leave;
        }
    }
    __except( BreakOnDebuggableException() )
    {
        TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception during IDT::Touch (%08x)"), GetExceptionCode() ));
    }

    if (pptszRefresh != NULL)
        ldap_value_free(pptszRefresh);
    if(pRes != NULL)
        ldap_msgfree(pRes);

    return( fReturn );
}




ULONG
CIntraDomainTable::GarbageCollect( SequenceNumber seqCurrent, SequenceNumber seqOldestToKeep, const BOOL * pfAbort )
{
    CLdapIdtKeyDn       dn(GetBaseDn());
    TCHAR *             apszAttrs[3];
    GC_ENUM_CONTEXT     EnumContext;

    apszAttrs[0] = const_cast<TCHAR*>(s_Cn);
    apszAttrs[1] = const_cast<TCHAR*>(s_timeRefresh);
    apszAttrs[2] = 0;

    TrkLog(( TRKDBG_GARBAGE_COLLECT | TRKDBG_IDT, TEXT("GC-ing move table (%d/%d)"),
             seqCurrent, seqOldestToKeep ));

    memset( &EnumContext, 0, sizeof(EnumContext) );
    EnumContext.seqOldestToKeep = seqOldestToKeep;
    EnumContext.seqCurrent = seqCurrent;
    EnumContext.pfAbort = pfAbort;
    EnumContext.dwRepetitiveTaskDelay = _pTrkSvrConfiguration->GetRepetitiveTaskDelay();
    EnumContext.pqtable = _pqtable;

    if (!LdapEnumerate(
        Ldap(),
        dn,
        LDAP_SCOPE_ONELEVEL,
        TEXT("(objectClass=*)"),
        apszAttrs,
        GcEnumerateCallback,
        &EnumContext ))
    {
        TrkRaiseException(TRK_E_SERVICE_STOPPING);
    }

    TrkLog(( TRKDBG_GARBAGE_COLLECT | TRKDBG_IDT,
             TEXT("GC-ed %d entries from the move table"),
             EnumContext.cEntries ));

    _pqtable->OnMoveTableGcComplete( EnumContext.cEntries );


    return EnumContext.cEntries;
}

#if DBG
void
CIntraDomainTable::PurgeAll()
{
    int             err;
    TCHAR            *apszAttrs[2] = { TEXT("cn"), NULL };
    LDAPMessage *   pRes;
    TCHAR           tszObjectMoveTable[MAX_PATH+1];
    
    __try
    {
        _tcscpy(tszObjectMoveTable, s_ObjectMoveTableRDN);
        _tcscat(tszObjectMoveTable, GetBaseDn());

        err = ldap_search_s( Ldap(),
                             tszObjectMoveTable,
                             LDAP_SCOPE_ONELEVEL,
                             TEXT("(objectclass=*)"),
                             apszAttrs,
                             0, // attribute types and values are wanted
                             &pRes );

        if (err == LDAP_SUCCESS)
        {
            // found it, lets get the attributes out
    
            int cEntries = ldap_count_entries(Ldap(), pRes);
            LDAPMessage * pEntry = ldap_first_entry(Ldap(), pRes);
            if (pEntry != NULL)
            {
                do
                {
                    TCHAR * ptszDn = ldap_get_dn(Ldap(), pEntry);

                    int errd = ldap_delete_s(Ldap(),ptszDn);

                    TrkLog((TRKDBG_ERROR, TEXT("Purged %s status=%d"), ptszDn, errd));
                    ldap_memfree(ptszDn);

                } while ( pEntry = ldap_next_entry(Ldap(), pEntry));
            }
        }
    }
    __finally
    {
        if (err == LDAP_SUCCESS)
        {
            ldap_msgfree(pRes);
        }
    }
}
#endif

void
CDomainRelativeObjId::FillLdapIdtKeyBuffer(TCHAR * const pchCN,
                                DWORD cch) const
{
    TCHAR *pchBuf = pchCN;
    _tcscpy(pchBuf, TEXT("CN="));
    pchBuf = pchBuf + 3;
    _volume.Stringize(pchBuf);
    _object.Stringize(pchBuf);
    TrkAssert(pchBuf <= pchCN+cch);
}

void
CDomainRelativeObjId::ReadLdapIdtKeyBuffer(const TCHAR * pchCN )
{
    const TCHAR *pchBuf;
    Init();
    if( 0 == _tcsncmp( pchCN, TEXT("CN="), 3 ))
    {
        pchBuf = &pchCN[3];
        if( !_volume.Unstringize(pchBuf)
            ||
            !_object.Unstringize(pchBuf) )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't unstringize droid from %s"), pchCN ));
            Init();
        }
    }
}

void
CDomainRelativeObjId::InitFromLdapBuffer(char * pVolumeId, int cbVolumeId,
                              char * pObjId, int cbObjId)
{
    DWORD iBuf = 0;

    if (cbVolumeId != sizeof(_volume) ||
        cbObjId != sizeof(_object))
    {
        TrkRaiseException(TRK_E_CORRUPT_IDT);
    }

    memcpy(&_volume, pVolumeId, sizeof(_volume));
    memcpy(&_object, pObjId, sizeof(_object));
}