// Copyright (c) 1996-1999 Microsoft Corporation //+------------------------------------------------------------------------- // // Microsoft Windows // // File: trksvr.hxx // // Contents: Definitions local to this directory. // Put includes of files liable to change often in this file. // // Classes: CTrkSvrSvc // CTrkSvrConfiguration // CDbConnection // CIntraDomainTable // CCrossDomainTable // CVolumeTable // CDenialChecker // CGhostTable - DBG only // CQuotaTable // // Functions: // // // // History: 18-Nov-96 BillMo Created. // 18-Nov-97 WeiruC Added CQuotaTable class. // // Notes: Put includes of files that don't change often into pch.cxx. // // Codework: Put in services.exe : _hDllReference // //-------------------------------------------------------------------------- #include "trklib.hxx" #include #include #include "resource.h" //+------------------------------------------------------------------------- // // Strings // //-------------------------------------------------------------------------- const extern TCHAR tszValueNameSeq[] INIT( TEXT("seqRefresh") ); #ifndef EVENT_SOURCE_DEFINED #define EVENT_SOURCE_DEFINED const extern TCHAR* g_ptszEventSource INIT( TEXT("Distributed Link Tracking Server") ); #endif // // LDAP classes and container names // const extern TCHAR s_VolumeTableRDN[] INIT( TEXT("CN=VolumeTable,CN=FileLinks,CN=System,") ); const extern TCHAR s_ObjectMoveTableRDN[] INIT( TEXT("CN=ObjectMoveTable,CN=FileLinks,CN=System,") ); const extern TCHAR s_objectClass[] INIT( TEXT("objectClass") ); const extern TCHAR s_linkTrackVolumeTable[] INIT( TEXT("linkTrackVolumeTable") ); const extern TCHAR s_linkTrackObjectMoveTable[] INIT( TEXT("linkTrackObjectMoveTable") ); const extern TCHAR s_linkTrackVolEntry[] INIT( TEXT("linkTrackVolEntry") ); const extern TCHAR s_linkTrackOMTEntry[] INIT( TEXT("linkTrackOMTEntry") ); const extern TCHAR s_Cn[] INIT( TEXT("CN") ); // // Tables // // Volume Table (voltab.cxx) // // Design VOLUMEID -> MACHINEID SECRET TIME NotificationSequence // Logical DN currMachineId volumeSecret timeVolChange seqNotification // // DN is stringized volume id const extern TCHAR s_currMachineId[] INIT( TEXT("volTableIdxGUID") ); // was "currMachineId" const extern TCHAR s_volumeSecret[] INIT( TEXT("linkTrackSecret") ); const extern TCHAR s_objectCount[] INIT( TEXT("objectCount") ); const extern TCHAR s_seqNotification[] INIT( TEXT("seqNotification") ); const extern TCHAR s_timeRefresh[] INIT( TEXT("timeRefresh") ); const extern TCHAR s_timeVolChange[] INIT( TEXT("timeVolChange") ); const extern TCHAR s_volTableGUID[] INIT( TEXT("volTableGUID") ); //const extern TCHAR s_volTableIdxGUID[] INIT( TEXT("volTableIdxGUID") ); // used for currMachineId #ifdef VOL_REPL const extern TCHAR s_timeVolChangeSearch[] INIT( TEXT("(timeVolChange;binary>=") ); #endif const extern TCHAR s_currMachineIdSearch[] INIT( TEXT("(volTableIdxGUID;binary=") ); // Object Move Table (idt_ldap.cxx) // // Design Birth/Current -> New Birth RefreshSequence // Design VOLUMEID:OBJID -> VOLUMEID:OBJID VOLUMEID:BIRTHID ULONG // Logical DN currVolumeId:currObjectId birthVolumeId:birthObjectId seqRefresh // Actual str(VOLUMEID:OBJID) currMachineId:objId birthMachineId:currReplsetid linkTrackSecret // // DN is stringized Birth/Current const extern TCHAR s_birthLocation[] INIT( TEXT("birthLocation") ); const extern TCHAR s_currentLocation[] INIT( TEXT("currentLocation") ); const extern TCHAR s_oMTGuid[] INIT( TEXT("oMTGuid") ); const extern TCHAR s_oMTIndxGuid[] INIT( TEXT("oMTIndxGuid") ); const extern TCHAR s_RestoreTime[] INIT( TEXT("System\\CurrentControlSet\\Services\\NtDs\\Parameters\\RestoreTime") ); const extern TCHAR s_RestoreBegin[] INIT( TEXT("RestoreBegin") ); const extern TCHAR s_rIDManagerReference[] INIT( TEXT("rIDManagerReference") ); const extern TCHAR s_fSMORoleOwner[] INIT( TEXT("fSMORoleOwner") ); //-------------------------------------------------------------------------- // // General defines // //-------------------------------------------------------------------------- #define MAX_SHORTENABLE_SEGMENTS (NUM_VOLUMES) #define CCH_UINT32 10 // Max chars for a DWORD value #define CCH_UINT64 20 // Max chars for a QUADWORD value #define MAX_WAIT_FOR_DS_STARTUP_SECONDS 360 //-------------------------------------------------------------------------- // // Forward declarations // //-------------------------------------------------------------------------- class CTrkSvrConfiguration; class CTrkSvrSvc; //-------------------------------------------------------------------// // // // Global variables // // // //-------------------------------------------------------------------// EXTERN CTrkSvrSvc * g_ptrksvr; // For RPC stubs to call service EXTERN LONG g_ctrksvr INIT(0); // Used to protect against multiple service instances //-------------------------------------------------------------------------- // // Ldap enumeration // //-------------------------------------------------------------------------- typedef enum { ENUM_DELETE_ENTRY, ENUM_KEEP_ENTRY, ENUM_DELETE_QUOTAFLAGS, ENUM_ABORT } ENUM_ACTION; typedef ENUM_ACTION (*PFN_LDAP_ENUMERATE_CALLBACK)( LDAP * pLdap, LDAPMessage * pResult, void* UserParam1, void* UserParam2 ); ENUM_ACTION GcEnumerateCallback( LDAP * pLdap, LDAPMessage * pResult, PVOID pvContext, void* ); BOOL // FALSE if aborted (ENUM_ABORT returned by callback) LdapEnumerate( LDAP * pLdap, TCHAR * ptszBaseDn, ULONG Scope, TCHAR * Filter, TCHAR * Attributes[], PFN_LDAP_ENUMERATE_CALLBACK pCallback, PVOID pvContext, void* UserParam2 = NULL ); //-------------------------------------------------------------------------- // // Garbage collection // //-------------------------------------------------------------------------- class CQuotaTable; // This structure holds context during enumeration // in a GC typedef struct { // All entries with lower sequence numbers // should be deleted. SequenceNumber seqOldestToKeep; // The highest sequence number we should see. SequenceNumber seqCurrent; // If true, abort (happens on service stop). const BOOL *pfAbort; // How long to sleep between non-noop iterations DWORD dwRepetitiveTaskDelay; // The quota table CQuotaTable *pqtable; // The number of entries that have been deleted (otherwise). ULONG cEntries; } GC_ENUM_CONTEXT; typedef struct { LONG cDelta; DWORD dwRepetitiveTaskDelay; DWORD dwPass; BOOL fCountAll; enum EPass { FIRST_PASS, SECOND_PASS }; } TRUE_COUNT_ENUM_CONTEXT; //-------------------------------------------------------------------------- // // Quota table // //-------------------------------------------------------------------------- #define QFLAG_UNCOUNTED 0x8 #define QFLAG_DELETED 0x4 //+------------------------------------------------------------------------- // // Class: CDbConnection // // Purpose: Handles shared connection to database // // Notes: // //-------------------------------------------------------------------------- class CDbConnection { public: // --------------------> indent 24 characters inline CDbConnection(); inline ~CDbConnection(); void Initialize(CSvcCtrlInterface * psvc, OPTIONAL const TCHAR *ptszHostName = NULL ); void UnInitialize(); inline const TCHAR *GetBaseDn() { Ldap(); return _pszBaseDn; } LDAP * Ldap(); private: BOOL _fInitializeCalled:1; CCriticalSection _cs; LDAP * _pldap; TCHAR * _pszBaseDn; }; inline // put all types/attributes on same line as inline CDbConnection::CDbConnection() : _fInitializeCalled(FALSE) { // Normally critsecs are initialized in the Initialize method. // But for this class the Initialize method doesn't get called right // away, so we'll try to initialize here and deal with it in Ldap() // if this fails. __try { _cs.Initialize(); } __except( EXCEPTION_EXECUTE_HANDLER ) { } } inline CDbConnection::~CDbConnection() { UnInitialize(); _cs.UnInitialize(); } //+------------------------------------------------------------------------- // // Class: CCrossDomainTable // // Purpose: Contains cross domain links // // Notes: Columns // ------- // // prev_location new_location birth_id // //-------------------------------------------------------------------------- class CCrossDomainTable { public: inline CCrossDomainTable(CDbConnection & dbc); inline ~CCrossDomainTable(); void Initialize(); void UnInitialize(); private: BOOL _fInitializeCalled:1; const CDbConnection & _dbc; }; inline CCrossDomainTable::CCrossDomainTable(CDbConnection & dbc) : _fInitializeCalled(FALSE), _dbc(dbc) { } inline CCrossDomainTable::~CCrossDomainTable() { UnInitialize(); } //+------------------------------------------------------------------------- // // Class: CRefreshSequenceStorage // // Purpose: Stores the refresh sequence number in the DS's restorable // table data. // //-------------------------------------------------------------------------- class CVolumeTable; class CRefreshSequenceStorage { public: inline CRefreshSequenceStorage( CVolumeTable * pVolTab, CQuotaTable *pQuotaTab, CTrkSvrConfiguration *psvrconfig ); inline ~CRefreshSequenceStorage(); inline void Initialize(); public: SequenceNumber GetSequenceNumber(); void IncrementSequenceNumber(); private: SequenceNumber _seq; CCriticalSection _cs; CVolumeTable * _pVolTab; CQuotaTable * _pQuotaTab; CTrkSvrConfiguration * _psvrconfig; CFILETIME _cftLastRead; }; inline CRefreshSequenceStorage::CRefreshSequenceStorage( CVolumeTable * pVolTab, CQuotaTable *pQuotaTab, CTrkSvrConfiguration *psvrconfig ) : _pVolTab(pVolTab), _pQuotaTab(pQuotaTab), _cftLastRead(0), _psvrconfig(psvrconfig) { } inline void CRefreshSequenceStorage::Initialize() { _cs.Initialize(); } inline CRefreshSequenceStorage::~CRefreshSequenceStorage() { _cs.UnInitialize(); } //+---------------------------------------------------------------------------- // // Class CAbbreviatedIDString // // Take an ID (e.g. a Droid) and create an abbreviated string for // dbg outs. // //+---------------------------------------------------------------------------- class CAbbreviatedIDString { private: TCHAR _tsz[ 8 ]; // e.g. {01:02} public: inline CAbbreviatedIDString( const CDomainRelativeObjId &droid ); inline operator const TCHAR *() const; }; inline CAbbreviatedIDString::CAbbreviatedIDString( const CDomainRelativeObjId &droid ) { GUID guidObjId, guidVolId; droid.GetVolumeId().SerializeRaw( reinterpret_cast(&guidVolId) ); droid.GetObjId().SerializeRaw( reinterpret_cast(&guidObjId) ); _stprintf( _tsz, TEXT("{%02x:%02x}"), guidVolId.Data1 & 0xFF, guidObjId.Data1 & 0xFF ); } inline CAbbreviatedIDString::operator const TCHAR *() const { return( _tsz ); } //+------------------------------------------------------------------------- // // Class: CIntraDomainTable (a.k.a. Object Move Table) // // Purpose: Handles updates and queries of the location database. // // Notes: Columns // ------- // // prev_location(indexed) new_location birth_id // //-------------------------------------------------------------------------- struct IDT_ENTRY; class CQuotaTable; class CIntraDomainTable { public: inline CIntraDomainTable( CDbConnection & dbc, CRefreshSequenceStorage * pseqRefresh ); inline ~CIntraDomainTable(); void Initialize( CTrkSvrConfiguration *pTrkSvrConfiguration, CQuotaTable* pqtable ); void UnInitialize(); public: BOOL Add(const CDomainRelativeObjId &droidKey, const CDomainRelativeObjId &droidNew, const CDomainRelativeObjId &droidBirth, BOOL *pfQuotaExceeded = NULL OPTIONAL ); //-> indent 4 chars BOOL Delete(const CDomainRelativeObjId &droidKey); ULONG GarbageCollect( SequenceNumber seqCurrent, SequenceNumber seqOldestToKeep, const BOOL * pfAbort ); BOOL Modify(const CDomainRelativeObjId &droidKey, const CDomainRelativeObjId &droidNew, const CDomainRelativeObjId &droidBirth ); BOOL Query(const CDomainRelativeObjId &droidKey, CDomainRelativeObjId *pdroidNew, CDomainRelativeObjId *pdroidBirth, BOOL *pfDeleted = NULL OPTIONAL, BOOL *pfCounted = NULL OPTIONAL ); BOOL Query( LDAP* pLdap, LDAPMessage *pEntry, const CDomainRelativeObjId ldKey, CDomainRelativeObjId *pldNew, CDomainRelativeObjId *pldBirth, BOOL *pfDeleted = NULL OPTIONAL, BOOL *pfCounted = NULL OPTIONAL ); BOOL Touch(const CDomainRelativeObjId &droidKey); #if DBG void PurgeAll(); #endif private: inline const TCHAR *GetBaseDn() { return _dbc.GetBaseDn(); } inline LDAP * Ldap() { return _dbc.Ldap(); } private: BOOL _fInitializeCalled:1; CDbConnection & _dbc; CRegBoolParameter _QuotaReported; CRefreshSequenceStorage * _pRefreshSequenceStorage; CQuotaTable* _pqtable; CTrkSvrConfiguration *_pTrkSvrConfiguration; }; inline CIntraDomainTable::CIntraDomainTable(CDbConnection & dbc, CRefreshSequenceStorage * pRefreshSequenceStorage ) : _fInitializeCalled(FALSE), _dbc(dbc), _pRefreshSequenceStorage( pRefreshSequenceStorage ), _QuotaReported( TEXT("IntraDomainTableQuotaReported") ) { } inline CIntraDomainTable::~CIntraDomainTable() { UnInitialize(); } // Objects to make constructing quota table keys easy. class CLdapSeqNum { public: CLdapSeqNum() { _tcscpy( _tszSeq, TEXT("0") ); } CLdapSeqNum(SequenceNumber seq) { _stprintf( _tszSeq, TEXT("%lu"), seq ); } operator TCHAR*() { return( _tszSeq ); } TCHAR _tszSeq[ CCH_UINT32 ]; }; class CLdapQuotaKeyDn { public: CLdapQuotaKeyDn(const TCHAR* ptszBaseDn, const CMachineId& mcid) { TCHAR* ptsz; _tcscpy(_tszDn, TEXT("CN=")); _tcscat(_tszDn, TEXT("QTDC_")); ptsz = _tszDn + _tcslen(_tszDn); mcid.Stringize(ptsz); *ptsz++ = TEXT(','); _tcscpy(ptsz, s_VolumeTableRDN); _tcscat(_tszDn, ptszBaseDn); TrkAssert(_tcslen(_tszDn) < ELEMENTS(_tszDn)); } inline operator TCHAR* () { return _tszDn; } private: TCHAR _tszDn[MAX_PATH]; }; class CLdapQuotaCounterKeyDn { public: CLdapQuotaCounterKeyDn(const TCHAR* ptszBaseDn) { TCHAR* ptsz; _tcscpy(_tszDn, TEXT("CN=")); _tcscat(_tszDn, TEXT("QT_Counter")); ptsz = _tszDn + _tcslen(_tszDn); *ptsz++ = TEXT(','); _tcscpy(ptsz, s_VolumeTableRDN); _tcscat(_tszDn, ptszBaseDn); TrkAssert(_tcslen(_tszDn) < ELEMENTS(_tszDn)); } inline operator TCHAR* () { return _tszDn; } private: TCHAR _tszDn[MAX_PATH]; }; //+------------------------------------------------------------------------ // // Class: CQuotaTable // // Purpose: Encapsulate functionalities that enforce quota on the // tables in the DC (move table, volume table). // //------------------------------------------------------------------------- class CQuotaTable : public PTimerCallback { public: // constructor/destructor CQuotaTable(CDbConnection& dbc); ~CQuotaTable(); // initialization/uninitialization void Initialize(CVolumeTable *pvoltab, CIntraDomainTable *pidt, CTrkSvrSvc *psvrsvc, CTrkSvrConfiguration *pcfgsvr ); void UnInitialize(); BOOL IsDesignatedDc( BOOL fRaiseOnError = FALSE ); // timer call back virtual TimerContinuation Timer(ULONG); // checking quota BOOL IsMoveQuotaExceeded(); BOOL IsVolumeQuotaExceeded(const CMachineId& mcid, ULONG cUncountedVolumes); BOOL UpdateFlags(LDAP* pLdap, TCHAR* dnKey, BYTE bFlags); void DeleteFlags(LDAP* pLdap, TCHAR* dnKey); void OnServiceStopRequest(); void IncrementMoveCountCache() { InterlockedIncrement(&_lCachedMoveTableCount); } void DecrementMoveCountCache() { InterlockedDecrement(&_lCachedMoveTableCount); } inline void IncrementVolumeCountCache(); inline void DecrementVolumeCountCache(); void InvalidateCache() { _cftCacheLastUpdated = 0; DeleteCounter(); } void Statistics( TRKSVR_STATISTICS *pTrkSvrStatistics ); void OnMoveTableGcComplete( ULONG cEntriesDeleted ); private: enum EBackgroundForegroundTask { BACKGROUND = 1, FOREGROUND }; private: void Lock(); void Unlock(); DWORD CalculateMoveLimit(); // Doesn't raise inline LDAP* Ldap() const { return _dbc.Ldap(); } inline const TCHAR *GetBaseDn() { return _dbc.GetBaseDn(); } BOOL ReadCounter(DWORD* dwCounter); HRESULT DeleteCounter(); void GetTrueCount( DWORD* pdwTrueCount, EBackgroundForegroundTask eBackgroundForegroundTask ); void WriteCounter(DWORD); void ValidateCache( BOOL fForceHint = FALSE ); HRESULT ReadFlags(LDAP* pLdap, TCHAR* dnKey, BYTE* bFlags); BOOL DeleteOrphanedEntries( const CDomainRelativeObjId rgdroidList[], ULONG cSegments, const CDomainRelativeObjId &droidBirth, const CDomainRelativeObjId &droidCurrent ); void ShortenString( LDAP* pLdap, LDAPMessage* pMessage, BYTE *pbFlags, const CDomainRelativeObjId &droidCurrent ); TCHAR * GetFirstCN( LDAP *pLdap, LDAPMessage *pMessage ); static ENUM_ACTION MoveTableEnumCallback( LDAP * pLdap, LDAPMessage * pResult, void* cEntries, void* pvThis ); static ENUM_ACTION VolumeTableEnumCallback( LDAP * pLdap, LDAPMessage * pResult, void* pcEntries, void* pvThis ); private: enum { QUOTA_TIMER = 0, QUOTA_CLEANUP_TIMER = 1 } EQuotaTimer; BOOL _fInitializeCalled:1; BOOL _fIsDesignatedDc:1; BOOL _fStopping:1; CFILETIME _cftDesignatedDc; CTrkSvrConfiguration *_pcfgsvr; CDbConnection& _dbc; DWORD _dwMoveLimit; CMachineId _mcid; LONG _lCachedMoveTableCount; LONG _lCachedVolumeTableCount; CFILETIME _cftCacheLastUpdated; CVolumeTable *_pvoltab; CIntraDomainTable *_pidt; CTrkSvrSvc *_psvrsvc; CNewTimer _timer; CCriticalSection _cs; #if DBG LONG _cLocks; #endif }; inline void CQuotaTable::OnServiceStopRequest() { _fStopping = TRUE; } inline void CQuotaTable::Lock() { _cs.Enter(); #if DBG _cLocks++; #endif } inline void CQuotaTable::Unlock() { TrkAssert( 0 < _cLocks ); _cs.Leave(); #if DBG _cLocks--; #endif } inline void CQuotaTable::IncrementVolumeCountCache() { Lock(); _lCachedVolumeTableCount++; _dwMoveLimit = CalculateMoveLimit(); // Doesn't raise Unlock(); } inline void CQuotaTable::DecrementVolumeCountCache() { Lock(); --_lCachedVolumeTableCount; _dwMoveLimit = CalculateMoveLimit(); // Doesn't raise Unlock(); } //+------------------------------------------------------------------------- // // Class: CVolumeTable // // Purpose: Table of volumes // // Notes: // //-------------------------------------------------------------------------- class CVolumeTable #ifdef VOL_REPL : PTimerCallback #endif { public: // Construction/destruction inline CVolumeTable( CDbConnection & dbc, CRefreshSequenceStorage * pRefreshSequenceStorage ); inline ~CVolumeTable(); void Initialize(CTrkSvrConfiguration *pconfigSvr, CQuotaTable *pqtable); void UnInitialize(); public: // Public RPC level interface - must be parameter checked HRESULT ClaimVolume( const CMachineId & mcidClient, const CVolumeId & volume, const CVolumeSecret & secretOld, const CVolumeSecret & secretNew, SequenceNumber * pseq, FILETIME * pftLastRefresh ); HRESULT PreCreateVolume( const CMachineId & mcidClient, const CVolumeSecret & secret, ULONG cUncountedVolumes, CVolumeId * pvolume ); HRESULT DeleteVolume( const CMachineId & mcidClient, const CVolumeId & volume ); HRESULT FindVolume( const CVolumeId & volume, CMachineId * pmcid ); HRESULT QueryVolume( const CMachineId & mcidClient, const CVolumeId & volume, SequenceNumber * pseq, FILETIME * pftLastRefresh ); //-> indent 4 chars BOOL Touch( const CVolumeId & volume ); public: HRESULT SetSequenceNumber(const CVolumeId & volume, SequenceNumber seq); HRESULT SetSecret( const CVolumeId & volume, const CVolumeSecret & secret ); int GetAvailableNotificationQuota( const CVolumeId & volid ); DWORD CountVolumes( const CMachineId & mcidClient ); ULONG GarbageCollect( SequenceNumber seqCurrent, SequenceNumber seqOldestToKeep, const BOOL * pfAbort ); HRESULT GetVolumeInfo( const CVolumeId & volume, CMachineId * pmcid, CVolumeSecret * psecret, SequenceNumber * pseq, CFILETIME *pcftRefresh ); HRESULT GetMachine( const CVolumeId & volume, CMachineId * pmcid ); HRESULT SetMachine( const CVolumeId & volume, const CMachineId & mcid ); HRESULT SetMachineAndSecret(const CVolumeId & volume, const CMachineId & mcid, const CVolumeSecret & secret); inline const TCHAR *GetBaseDn() { return _dbc.GetBaseDn(); } inline LDAP * Ldap() const { return _dbc.Ldap(); } #if DBG void PurgeCache( ); #endif HRESULT AddVolidToTable( const CVolumeId & volume, const CMachineId & mcidClient, const CVolumeSecret & secret ); void PurgeAll(); #ifdef VOL_REPL void QueryVolumeChanges( const CFILETIME &ftFirstChange, CVolumeMap * pVolMap ); void SimpleTimer( DWORD dwTimerId ); #endif private: HRESULT MapResult(int ldap_err) const; #ifdef VOL_REPL void _QueryVolumeChanges( const CFILETIME &ftFirstChange, CVolumeMap * pVolMap ); #endif private: BOOL _fInitializeCalled:1; CDbConnection & _dbc; CRefreshSequenceStorage * _pRefreshSequenceStorage; CTrkSvrConfiguration * _pconfigSvr; // State for caching the volume table so that // synchronizing the workstation's volume tables is efficient (normally the workstation // queries will hit the cached query.) CQuotaTable* _pqtable; #ifdef VOL_REPL CCritcalSection _csQueryCache; CVolumeMap _VolMap; // keeps cache of query since we have many machines // getting the updates CFILETIME _cftCacheLowest; CSimpleTimer _timerQueryCache; DWORD _SecondsPreviousToNow; #endif }; inline CVolumeTable::CVolumeTable( CDbConnection & dbc, CRefreshSequenceStorage * pRefreshSequenceStorage ) : _dbc(dbc), _fInitializeCalled(FALSE), _pRefreshSequenceStorage( pRefreshSequenceStorage ), _pconfigSvr(NULL) { } inline CVolumeTable::~CVolumeTable() { UnInitialize(); } //+---------------------------------------------------------------------------- // // CActiveCreates // // Keep track of Create-Volume requests that are in the callback. // This is necessary to ensure that we don't have one machine tying up // more than one thread on the server. // //+---------------------------------------------------------------------------- class CActiveCreates { private: // Keep an array of the active machines. CMachineId *_prg; ULONG _cEntries; CCriticalSection _cs; public: CActiveCreates() { _prg = NULL; _cEntries = 0; } void Initialize( ULONG cEntries ) { // Allocate an array of machine IDs. _prg = new CMachineId[ cEntries ]; if( NULL == _prg ) TrkRaiseWin32Error( ERROR_NOT_ENOUGH_MEMORY ); _cEntries = cEntries; memset( _prg, 0, cEntries * sizeof(CMachineId) ); // Initialize the critical section. If this raises, ignore the // exception; we'll catch it later whenever we call _cs.Lock. __try { _cs.Initialize(); } __except( EXCEPTION_EXECUTE_HANDLER ) { TrkLog(( TRKDBG_WARNING, TEXT("Exception in CActiveCreates constructor (%08x)"), GetExceptionCode() )); } } ~CActiveCreates() { if( NULL != _prg ) delete[] _prg; _cs.UnInitialize(); } public: // Add a machine to the list of active creates. // This raises if the machine is already in the list. void Add( const CMachineId &mcid ) { _cs.Enter(); __try // __finally { int iFree = -1; // Search the array to see if this machine is already in // the list. While we're at it, assuming it's not already in // the list, find a free entry. for( int i = 0; i < _cEntries; i++ ) { // Is this the machine? if( mcid == _prg[i] ) { // Yes, this machine is already in the list. Raise. TrkLog(( TRKDBG_ERROR, TEXT("Machine attempting simultaneous creates (%s)"), (const TCHAR*) CDebugString(mcid) )); TrkRaiseException(TRK_E_DENIAL_OF_SERVICE_ATTACK); } // Otherwise, is this a free entry? else if( CMachineId() == _prg[i] ) iFree = i; } // If we reach this point, the machine isn't already on the list, // so we'll add it. if( -1 != iFree ) // Add the machine to a free entry in the list. _prg[ iFree ] = mcid; else { // There was no free entry in the list (this should never // happen). TrkLog(( TRKDBG_ERROR, TEXT("Couldn't find free space in CActiveCreates") )); TrkRaiseWin32Error( ERROR_INTERNAL_DB_CORRUPTION ); } } __finally { _cs.Leave(); } } // Remove an entry from the list void Remove( const CMachineId &mcid ) { _cs.Enter(); __try { for( int i = 0; i < _cEntries; i++ ) { if( mcid == _prg[i] ) { _prg[i] = CMachineId(); break; } } #if DBG if( i == _cEntries ) { TrkLog(( TRKDBG_ERROR, TEXT("*** Not found in CActiveCreates::Remove ***") )); } #endif } __finally { _cs.Leave(); } } }; //+------------------------------------------------------------------------- // // Class: CDenialChecker // // Purpose: Checks for denial of service attacks // // Notes: // //-------------------------------------------------------------------------- struct ACTIVECLIENT; class CDenialChecker : public PTimerCallback { public: inline CDenialChecker(); inline ~CDenialChecker(); void Initialize( ULONG ulHistoryPeriod ); void UnInitialize(); void CheckClient(const CMachineId &mcidClient); virtual TimerContinuation Timer( ULONG ulTimerId ); // timeout for clearing out old ACTIVECLIENTs private: BOOL _fInitializeCalled; CNewTimer _timer; ACTIVECLIENT * _pListHead; CCriticalSection _cs; #if DBG LONG _lAllocs; #endif }; inline CDenialChecker::CDenialChecker() : _fInitializeCalled(FALSE) { } inline CDenialChecker::~CDenialChecker() { UnInitialize(); } class CTrkSvrRpcServer : public CRpcServer { public: void Initialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData, CTrkSvrConfiguration *pTrkSvrConfig ); void UnInitialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData ); }; //+------------------------------------------------------------------------- // // Class: CTrkSvrSvc // // Purpose: Tracking (Server) Service // // Notes: // //-------------------------------------------------------------------------- #define MAX_SVR_THREADS 10 class CTrkSvrSvc : public PTimerCallback, public IServiceHandler { public: // Initialization inline CTrkSvrSvc(); inline ~CTrkSvrSvc(); void Initialize(SVCHOST_GLOBAL_DATA * pSvcsGlobalData ); void UnInitialize(HRESULT hr); public: // RPC server methods void DeleteNotify( const CMachineId & mcidClient, TRKSVR_CALL_DELETE * pDelete ); HRESULT MoveNotify( const CMachineId & mcidClient, TRKSVR_CALL_MOVE_NOTIFICATION * pMove ); void Search( TRK_FILE_TRACKING_INFORMATION * pSearches ); void old_Search( old_TRK_FILE_TRACKING_INFORMATION * pSearches ); HRESULT SyncVolume( const CMachineId & mcidClient, TRKSVR_SYNC_VOLUME * pSyncVolume, ULONG cUncountedCreates ); void Refresh( const CMachineId & mcidClient, TRKSVR_CALL_REFRESH * pRefresh ); void Statistics( TRKSVR_STATISTICS *pTrkSvrStatistics ); public: // General publics HRESULT SvrMessage( handle_t IDL_handle, TRKSVR_MESSAGE_UNION * pMsg ); inline void CheckClient( const CMachineId & mcid ); BOOL CountPrioritizedThread( const TRKSVR_MESSAGE_UNION * pMsg ); void ReleasePrioritizedThread( ); void DoRefresh( const CRpcClientBinding & rc ); inline DWORD GetState(); virtual TimerContinuation Timer( ULONG ulTimerContext ); // refresh timer DWORD ServiceHandler(DWORD dwControl, // IServiceHandler implementation DWORD dwEventType, PVOID EventData, PVOID pData); inline const BOOL * GetStopFlagAddress(); BOOL RequireSecureRPC(); HRESULT CreateVolume(const CMachineId & mcidClient, const TRKSVR_SYNC_VOLUME& pSyncVolume); inline void SetLastError( HRESULT hr ); void OnRequestStart( TRKSVR_MESSAGE_TYPE MsgType ); void OnRequestEnd( TRKSVR_MESSAGE_UNION * pMsg, const CMachineId &mcid, HRESULT hr ); void RaiseIfStopped(); void Scan( IN const CDomainRelativeObjId * pdroidNotificationCurrent, OPTIONAL IN const CDomainRelativeObjId * pdroidNotificationNew, OPTIONAL IN const CDomainRelativeObjId & droidBirth, OUT CDomainRelativeObjId * pdroidList, IN int cdroidList, OUT int * pcSegments, IN OUT CDomainRelativeObjId * pdroidScan, OUT BOOL *pfStringDeleted ); public: // testing friend class CTestSvrSvc; void TestRestore( const TCHAR * ptszMachine ); private: void MoveNotify(const CDomainRelativeObjId &droidCurrent, const CDomainRelativeObjId &droidBirth, const CDomainRelativeObjId &droidNew, BOOL *pfQuotaExceeded ); SequenceNumber GetSequenceNumber( const CMachineId & mcidClient, const CVolumeId & volume); void SetSequenceNumber( const CVolumeId & volume, // must ensure that validation already done for volume SequenceNumber seq ); inline BOOL IncrementAndCheckWritesPerHour(); inline void IncrementWritesPerHour(); BOOL CheckWritesPerHour(); inline ULONG NumWritesThisHour() const; BOOL VerifyMachineOwnsVolume( const CMachineId &mcid, const CVolumeId & volid ); private: // data members BOOL _fInitializeCalled:1; // Indicates if we're hesitating before doing a GC // (see DoWork) BOOL _fHesitatingBeforeGC:1; // Indicates that service_stop/shutdown has been received BOOL _fStopping:1; // If true, and we're in a GC phase, GC the volume table // rather than the move table. BOOL _fGCVolumeTable:1; SVCHOST_GLOBAL_DATA * _pSvcsGlobalData; // Protect against denail-of-service attacks. CDenialChecker _denial; // This protects against a client doing too many creates. CActiveCreates _activeCreates; // LDAP manager CDbConnection _dbc; CRefreshSequenceStorage _refreshSequence; CCrossDomainTable _cdt; CIntraDomainTable _idt; CVolumeTable _voltab; CRegDwordParameter _MoveCounterReset; CNewTimer _timerGC; LONG _cGarbageCollectionsRunning; CQuotaTable _qtable; CTrkSvrRpcServer _rpc; CSvcCtrlInterface _svcctrl; CTrkSvrConfiguration _configSvr; LONG _cAvailableThreads, _cLowestAvailableThreads; // The following values track the number of writes (to the DS) // in an hour. If this gets too high, trksvr disallows writes // for the rest of the hour. CFILETIME _cftWritesPerHour; ULONG _cWritesPerHour; CCriticalSection _csWritesPerHour; // Statistics struct tagStats { ULONG cSyncVolumeRequests, cSyncVolumeErrors, cSyncVolumeThreads; ULONG cCreateVolumeRequests, cCreateVolumeErrors; ULONG cClaimVolumeRequests, cClaimVolumeErrors; ULONG cQueryVolumeRequests, cQueryVolumeErrors; ULONG cFindVolumeRequests, cFindVolumeErrors; ULONG cTestVolumeRequests, cTestVolumeErrors; ULONG cSearchRequests, cSearchErrors, cSearchThreads; ULONG cMoveNotifyRequests, cMoveNotifyErrors, cMoveNotifyThreads; ULONG cRefreshRequests, cRefreshErrors, cRefreshThreads; ULONG cDeleteNotifyRequests, cDeleteNotifyErrors, cDeleteNotifyThreads; //ULONG ulGCIterationPeriod; //SHORT cEntriesToGC; SHORT cEntriesGCed; SHORT cMaxDsWriteEvents; SHORT cCurrentFailedWrites; CFILETIME cftLastSuccessfulRequest; CFILETIME cftServiceStartTime; HRESULT hrLastError; } _Stats; public: COperationLog _OperationLog; }; inline CTrkSvrSvc::CTrkSvrSvc() : _fInitializeCalled(FALSE), _fHesitatingBeforeGC(FALSE), _fStopping(FALSE), _idt(_dbc, &_refreshSequence), _cdt(_dbc), _voltab(_dbc, &_refreshSequence), _qtable(_dbc), _cWritesPerHour(0), _cGarbageCollectionsRunning(0), _fGCVolumeTable(FALSE), _MoveCounterReset( TEXT("MoveCounterReset") ), _refreshSequence( &_voltab, &_qtable, &_configSvr ) // refreshSequence uses voltab to store its state { // Statistics memset( &_Stats, 0, sizeof(_Stats) ); _Stats.cftLastSuccessfulRequest = 0; _Stats.cftServiceStartTime = CFILETIME(); } inline CTrkSvrSvc::~CTrkSvrSvc() { TrkAssert(!_fInitializeCalled); } inline const BOOL * CTrkSvrSvc::GetStopFlagAddress() { return(_svcctrl.GetStopFlagAddress()); } inline BOOL CTrkSvrSvc::RequireSecureRPC() { return( _rpc.RpcSecurityEnabled() ); } inline void CTrkSvrSvc::SetLastError( HRESULT hr ) { _Stats.hrLastError = hr; } inline void CTrkSvrSvc::CheckClient( const CMachineId & mcid ) { _denial.CheckClient( mcid ); } inline DWORD CTrkSvrSvc::GetState() { return( _svcctrl.GetState() ); } inline void CTrkSvrSvc::IncrementWritesPerHour() { #if DBG LONG l = InterlockedIncrement( (LONG*)&_cWritesPerHour ); TrkLog(( TRKDBG_SVR, TEXT("WritesPerHour: %d"), l )); #else InterlockedIncrement( (LONG*)&_cWritesPerHour ); #endif } inline BOOL CTrkSvrSvc::IncrementAndCheckWritesPerHour() { IncrementWritesPerHour(); return CheckWritesPerHour(); } inline ULONG CTrkSvrSvc::NumWritesThisHour() const { return _cWritesPerHour; } //+------------------------------------------------------------------------- // // Class: CLdapBinaryMod // // Purpose: Wrapper for all the structures used by LDAP for binary // attribute modifications // // Notes: // //-------------------------------------------------------------------------- class CLdapBinaryMod { public: inline CLdapBinaryMod(); // note: the pszAttrName is not copied! inline CLdapBinaryMod(const TCHAR * pszAttrName, PCCH bv_val, ULONG bv_len, int mod_op); // note: the pszAttrName is not copied! void Init(const TCHAR * pszAttrName, PCCH bv_val, ULONG bv_len, int mod_op); public: LDAPMod _mod; private: LDAP_BERVAL _BerVal; LDAP_BERVAL * _apBerVals[2]; }; inline CLdapBinaryMod::CLdapBinaryMod() { } inline CLdapBinaryMod::CLdapBinaryMod(const TCHAR * pszAttrName, PCCH bv_val, ULONG bv_len, int mod_op) { Init(pszAttrName, bv_val, bv_len, mod_op); } inline void CLdapBinaryMod::Init(const TCHAR * pszAttrName, PCCH bv_val, ULONG bv_len, int mod_op) { _mod.mod_op = LDAP_MOD_BVALUES | mod_op; _mod.mod_type = (TCHAR*)pszAttrName; // bv_val might be null if this is a delete. if( NULL != bv_val ) { _BerVal.bv_len = bv_len; _BerVal.bv_val = const_cast(bv_val); _apBerVals[0] = &_BerVal; _apBerVals[1] = NULL; _mod.mod_bvalues = _apBerVals; } else _mod.mod_bvalues = NULL; } //+------------------------------------------------------------------------- // // Class: CLdapStringMod // // Purpose: Wrapper for all the structures used by LDAP for string // attribute modifications // // Notes: // //-------------------------------------------------------------------------- class CLdapStringMod { public: inline CLdapStringMod(); // note: the pszAttrName is not copied! inline CLdapStringMod(const TCHAR * pszAttrName, const TCHAR * str_val, int mod_op); // note: the pszAttrName is not copied! void Init(const TCHAR * pszAttrName, const TCHAR * str_val, int mod_op); public: LDAPMod _mod; private: TCHAR * _apsz[2]; }; inline CLdapStringMod::CLdapStringMod() { } inline CLdapStringMod::CLdapStringMod(const TCHAR * pszAttrName, const TCHAR * str_val, int mod_op) { // if we're specifying the objectClass we don't TrkAssert(_tcscmp(pszAttrName, s_objectClass) != 0 || mod_op == 0); Init(pszAttrName, str_val, mod_op); } inline void CLdapStringMod::Init(const TCHAR * pszAttrName, const TCHAR * str_val, int mod_op) { _mod.mod_op = mod_op; _mod.mod_type = (TCHAR*)pszAttrName; _mod.mod_vals.modv_strvals = _apsz; _apsz[0] = (TCHAR*)str_val; _apsz[1] = NULL; } //+------------------------------------------------------------------------- // // Class: CLdapTimeValue // //-------------------------------------------------------------------------- class CLdapTimeValue { public: CLdapTimeValue() { //memset(_abPad,0,sizeof(_abPad)); _stprintf( _tszTime, TEXT("%I64u"), (LONGLONG)CFILETIME() ); } CLdapTimeValue(const CFILETIME &cft) { _stprintf( _tszTime, TEXT("%I64u"), (LONGLONG) cft ); } // mikehill: is this still necessary? void Swap(); inline BYTE & Byte(int i) { return( ((BYTE*)this)[i] ); } operator TCHAR*() { return( _tszTime ); } TCHAR _tszTime[ CCH_UINT64 ]; //BYTE _abPad[sizeof(GUID) - sizeof(CFILETIME)]; }; inline void CLdapTimeValue::Swap() { BYTE b; for (int i=0; i sizeof(_szDn)/sizeof(_szDn[0])) { TrkRaiseException(TRK_E_DN_TOO_LONG); } _tcscat(_szDn, TEXT(",")); _tcscat(_szDn, s_ObjectMoveTableRDN); _tcscat(_szDn, pszBaseDn); TrkAssert(_tcslen(_szDn) < sizeof(_szDn)/sizeof(_szDn[0])); } inline CLdapIdtKeyDn::CLdapIdtKeyDn(const TCHAR * pszBaseDn) { // make sure room for that string and the table name and the base dn if (1 + _tcslen(s_ObjectMoveTableRDN) + _tcslen(pszBaseDn) + 1 > sizeof(_szDn)/sizeof(_szDn[0])) { TrkRaiseException(TRK_E_DN_TOO_LONG); } _tcscpy(_szDn, s_ObjectMoveTableRDN); _tcscat(_szDn, pszBaseDn); TrkAssert(_tcslen(_szDn) < sizeof(_szDn)/sizeof(_szDn[0])); } inline CLdapIdtKeyDn::operator TCHAR * () { return(_szDn); }