windows-nt/Source/XPSP1/NT/com/svcdlls/trksvcs/trksvr/svrsvc.cxx
2020-09-26 16:20:57 +08:00

2448 lines
79 KiB
C++

// Copyright (c) 1996-1999 Microsoft Corporation
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// File: svrsvc.cxx
//
// Contents: Code for CTrkSvrSvc
//
// Classes:
//
// Functions:
//
//
//
// History: 18-Nov-96 BillMo Created.
//
// Notes:
//
// Codework:
//
//--------------------------------------------------------------------------
#include "pch.cxx"
#pragma hdrstop
#include "trksvr.hxx"
#include "ntlsa.h"
#define THIS_FILE_NUMBER SVRSVC_CXX_FILE_NO
#if DBG
DWORD g_Debug = 0;
#endif
const extern TCHAR s_tszKeyNameLinkTrack[] = TEXT("System\\CurrentControlSet\\Services\\TrkSvr\\Parameters");
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::Initialize
//
// Initialize the TrkSvr service.
//
//+----------------------------------------------------------------------------
void
CTrkSvrSvc::Initialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData )
{
__try
{
_cLowestAvailableThreads = _cAvailableThreads = MAX_SVR_THREADS;
_fInitializeCalled = TRUE;
g_ptrksvr = this;
_pSvcsGlobalData = pSvcsGlobalData;
// Initialize the object that manages the SCM.
_svcctrl.Initialize(TEXT("TrkSvr"), this);
// Initialize registry-configurable parameters.
_configSvr.Initialize();
// If requested, prepare to log all operations (to a file)
if( _configSvr.UseOperationLog() )
_OperationLog.Initialize( _configSvr.GetOperationLog() );
TrkLog(( TRKDBG_SVR, TEXT("Distributed Link Tracking (Server) service starting on thread=%d(0x%x)"),
GetCurrentThreadId(), GetCurrentThreadId() ));
// This is a hacked stub that looks and acts like the Win32 thread pool services
#ifdef PRIVATE_THREAD_POOL
{
HRESULT hr = S_OK;
g_pworkman2 = new CThreadPoolStub;
if( NULL == g_pworkman2 )
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't create the thread pool manager") ));
TrkRaiseWin32Error( ERROR_NOT_ENOUGH_MEMORY );
}
hr = g_pworkman2->Initialize();
if( FAILED(hr) )
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't initialize the thread pool manager") ));
TrkRaiseException( hr );
}
}
#endif
// The denial checker provides protection against a denial-of-service
// attack, where a client floods us with calls.
_denial.Initialize(_configSvr.GetHistoryPeriod() );
// This critsec protects _cWritesPerHour & _cftWritesPerHour.
// See CTrkSvrSvc::CheckWritesPerHour
_csWritesPerHour.Initialize();
// This maintains the "time" for purposes of refreshing entries.
_refreshSequence.Initialize();
// The cross-domain table
_cdt.Initialize();
// The intra-domain table
_idt.Initialize( &_configSvr, &_qtable );
// The volume table
_voltab.Initialize( &_configSvr, &_qtable );
// The quota manager
_qtable.Initialize(&_voltab, &_idt, this, &_configSvr );
// Set the quota timer. This was originally every 30 days, but is now
// every day. In order to maintain compatibility with the tests, we
// still use the GCPeriod value (30 days), but divide it by the new
// GCDivisor value (30) to get the correct period.
// This timer doesn't have a standard retry, because of the way
// we hesitate 30 minutes before doing anything. So retries are
// done explicitely.
_timerGC.Initialize(this,
TEXT("NextGarbageCollectTime"), // This is a persistent timer
0, // Context ID
_configSvr.GetGCPeriod() / _configSvr.GetGCDivisor(),
CNewTimer::NO_RETRY,
0, 0, 0 ); // No retries or max lifetime
_timerGC.SetRecurring();
TrkLog(( TRKDBG_VOLUME, TEXT("GC timer: %s"),
(const TCHAR*) CDebugString(_timerGC) ));
// Used in the Timer method to determine if we should reset the
// move table counter value.
_MoveCounterReset.Initialize();
// Initialize ourself as an RPC server
_rpc.Initialize( _pSvcsGlobalData, &_configSvr );
// Tell the SCM that we're running.
_svcctrl.SetServiceStatus(SERVICE_RUNNING,
SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_SHUTDOWN,
NO_ERROR);
_OperationLog.Add( COperationLog::TRKSVR_START );
}
__except( BreakOnDebuggableException() )
{
// Don't log an event for protseq-not-supported; this happens during a normal
// setup.
if( HRESULT_FROM_WIN32(RPC_S_PROTSEQ_NOT_SUPPORTED) != GetExceptionCode() )
{
TrkReportEvent( EVENT_TRK_SERVICE_START_FAILURE, EVENTLOG_ERROR_TYPE,
static_cast<const TCHAR*>( CHexStringize( GetExceptionCode() )),
NULL );
}
TrkRaiseException( GetExceptionCode() );
}
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::UnInitialize
//
// Cancel any out-going RPCs, stop all timers, close everything down,
// and send a service_stopped to the SCM.
//
//+----------------------------------------------------------------------------
void
CTrkSvrSvc::UnInitialize(HRESULT hr)
{
if (_fInitializeCalled)
{
_fInitializeCalled = FALSE;
// Cancel any out-going RPCs on threads in this service
if( NULL != g_pActiveThreadList )
g_pActiveThreadList->CancelAllRpc();
// stop classes that use threads first ...
_rpc.UnInitialize( _pSvcsGlobalData );
_timerGC.UnInitialize();
_csWritesPerHour.UnInitialize();
// ... then release used resources
_qtable.UnInitialize();
_voltab.UnInitialize();
_idt.UnInitialize();
_cdt.UnInitialize();
_dbc.UnInitialize();
_denial.UnInitialize();
if (_configSvr.GetTestFlags() & TRK_TEST_FLAG_WAIT_ON_EXIT)
{
TrkLog((TRKDBG_ERROR, TEXT("Waiting 60 seconds before exitting for heap dump")));
Sleep(60000);
}
#if PRIVATE_THREAD_POOL
{
g_pworkman2->UnInitialize();
delete g_pworkman2;
g_pworkman2 = NULL;
}
#endif
g_ptrksvr = NULL;
// If the error is protseq-not-supported, ignore it. This is normal
// during setup.
if( (hr & 0x0FFF0000) == FACILITY_WIN32 )
hr = hr & ~(0x0FFF0000);
_svcctrl.SetServiceStatus(SERVICE_STOPPED, 0,
HRESULT_FROM_WIN32(RPC_S_PROTSEQ_NOT_SUPPORTED) == hr ? 0 : hr
);
//_svcctrl.UnInitialize();
}
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::ServiceHandler
//
// This method gets called by the SCM for notification of all service
// activity.
//
// NOTE: In services.exe, this method is called on the one and only ServiceHandler
// thread. So while we execute, no other service in this process can
// receive notifications. Thus it is important that we do nothing
// blocking or time-consuming here.
//
//+----------------------------------------------------------------------------
DWORD
CTrkSvrSvc::ServiceHandler(DWORD dwControl,
DWORD dwEventType,
PVOID EventData,
PVOID pData)
{
DWORD dwRet = NO_ERROR;
switch (dwControl)
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
_fStopping = TRUE;
_qtable.OnServiceStopRequest();
ServiceStopCallback( this, FALSE );
break;
case SERVICE_CONTROL_PAUSE:
break;
case SERVICE_CONTROL_CONTINUE:
break;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
dwRet = ERROR_CALL_NOT_IMPLEMENTED;
break;
}
return(dwRet);
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::RaiseIfStopped
//
// This method raises an exception if a global flag is set indicating
// that we've received a service stop/shutdown request. This is used
// in places where we have a thread that could run for a while; we periodically
// call this method to prevent service stop from blocking.
//
//+----------------------------------------------------------------------------
void
CTrkSvrSvc::RaiseIfStopped()
{
if ( * _svcctrl.GetStopFlagAddress() )
TrkRaiseException( TRK_E_SERVICE_STOPPING );
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::CheckWritesPerHour
//
// Check _cWritesPerHour to see if we're writing to much to the DS.
// This is a simplistic algorithm in an effort to reduce risk. We
// just let _cWritesPerHour increment until it hits the max, then
// check to see when that count was started. If more than an
// hour ago, then reset the count & the clock.
//
//+----------------------------------------------------------------------------
BOOL
CTrkSvrSvc::CheckWritesPerHour()
{
BOOL fExceeded = FALSE;
if( _cWritesPerHour >= _configSvr.GetMaxDSWritesPerHour() )
{
_csWritesPerHour.Enter();
__try
{
// Check the count again, as it may have changed whil we
// were waiting for the critsec.
if( _cWritesPerHour >= _configSvr.GetMaxDSWritesPerHour() )
{
CFILETIME cft; // Defaults to current time
// Did the "hour" for _cWritesPerHour actually start more
// than an hour ago?
cft.DecrementSeconds( _configSvr.GetMaxDSWritesPeriod() ); // An hour
if( cft > _cftWritesPerHour )
{
TrkLog(( TRKDBG_SVR, TEXT("Resetting writes-per-hour clock (%d)"),
_cWritesPerHour ));
// Yes, this write is OK, and we should reset the write time.
_cftWritesPerHour = CFILETIME();
_cWritesPerHour = 0;
_Stats.cCurrentFailedWrites = 0;
}
else
{
TrkLog(( TRKDBG_WARNING,
TEXT("Exceeded writes-per-hour (started at %s)"),
(const TCHAR*) CDebugString(_cftWritesPerHour) ));
if( 0 == _Stats.cCurrentFailedWrites )
_Stats.cMaxDsWriteEvents++;
_Stats.cCurrentFailedWrites++;
fExceeded = TRUE;
}
}
}
__finally
{
_csWritesPerHour.Leave();
}
}
return fExceeded;
}
void
CTrkSvrSvc::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
)
{
CDomainRelativeObjId droidNextBirth, droidNextNew;
BOOL fFound = FALSE;
BOOL fBirthSame = FALSE;
BOOL fCycle = FALSE;
*pfStringDeleted = FALSE;
//
// loop through the string until the birth ids don't match, or
// we've run out of buffer space, or we get to the end of the string
//
do
{
if (pdroidNotificationCurrent && *pdroidScan == *pdroidNotificationCurrent)
{
TrkAssert(pdroidNotificationNew);
droidNextNew = *pdroidNotificationNew;
droidNextBirth = droidBirth;
fFound = TRUE;
*pfStringDeleted = FALSE;
}
else
{
fFound = _idt.Query(*pdroidScan, &droidNextNew, &droidNextBirth, pfStringDeleted );
RaiseIfStopped();
}
if (fFound)
{
TrkLog((TRKDBG_MEND, TEXT("CTrkSvrSvc::Scan() - iSegment=%d, %s --> %s [%s] found"),
*pcSegments,
static_cast<const TCHAR*>(CAbbreviatedIDString(*pdroidScan)),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidNextNew)),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidNextBirth)) ));
// Check to see if we've already been here before.
// E.g., don't loop forever on A->Ba, B->Aa.
for( int j = 0; j < *pcSegments; j++ )
{
if( pdroidList[ j ] == droidNextNew )
{
TrkLog(( TRKDBG_MEND, TEXT("Cycle detected during mend (on %s)"),
static_cast<const TCHAR*>(CAbbreviatedIDString(*pdroidScan)) ));
fCycle = TRUE;
break;
}
}
if( !fCycle )
{
fBirthSame = droidNextBirth == droidBirth;
if (fBirthSame)
{
pdroidList[ (*pcSegments)++ ] = *pdroidScan;
*pdroidScan = droidNextNew;
}
else
{
// We can stop searching. We found a segment that starts
// with *pdroidScan, but it's from another string because
// it has a different birth ID.
TrkLog(( TRKDBG_MEND, TEXT("Birth IDs don't match: %s, %s"),
(const TCHAR*) CDebugString(droidBirth),
(const TCHAR*) CDebugString(droidNextBirth) ));
}
}
}
} while ( *pcSegments < cdroidList && fFound && fBirthSame && !fCycle );
if ( *pcSegments == cdroidList || fCycle )
{
TrkRaiseException(TRK_E_TOO_MANY_UNSHORTENED_NOTIFICATIONS);
}
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::MoveNotify
//
// Handle a move notify request from trkwks.
//
// This routine is complicated by DS replication. It is possible that two trksvr
// services may modify the same entry in the IDT within a replication window.
// The only way to prevent this is to design such that only the designated DC
// modifies entries. For this MoveNotify routine, that would mean that an
// entry would be added for each notification, the designated DC would then
// shorten the base entry, and delete this new one. That's not friendly
// to the DS, however, because deleted objects must continue to be stored
// for an extended period of time.
//
// Consequently, if this notify modifies an existing entry, this routine
// performs a modify rather than an add. For example, if a file is
// moved from A to B to C, and this routine is being called for that
// second move, it would just modify the existing entry from A->B to
// A->C.
//
// The risk here is that another DC will attempt to modify this entry
// within the same replication window. We don't have to worry though
// about another DC doing a notify; trkwks only sends to one DC.
// There are two cases to worry about. One is the case where another DC
// marks an entry to be deleted. If that happens after we do the modify
// here, then there is no problem; the entry is no longer needed anyway.
// If that happens before we do our modify here, then the delete flag
// will be lost. This case is rare, and the unnecessary entry won't
// stay in the table forever; it will be garbage collected.
//
// The other case of potential conflict is if an entry has not
// yet been counted; in this case the designated DC might count
// it and clear the uncounted flag. If our modify causes that
// flag to be uncleared, then the move table count would be corrupted.
// So if the uncounted flag is set, we do an add rather than a modify.
//
//+----------------------------------------------------------------------------
void
CTrkSvrSvc::MoveNotify(const CDomainRelativeObjId &droidCurrent,
const CDomainRelativeObjId &droidBirth,
const CDomainRelativeObjId &droidNew,
BOOL *pfQuotaExceeded )
{
BOOL fAdded = FALSE, fModified = FALSE, fExists = FALSE;
BOOL fDeleted = FALSE, fCounted = FALSE;
// ignore cross-domain moves for now
CDomainRelativeObjId droidNextNew;
CDomainRelativeObjId droidNextBirth;
CDomainRelativeObjId droidNewIDT, droidBirthIDT;
TrkLog((TRKDBG_MOVE, TEXT("CTrkSvrSvc::MoveNotify\n curr = %s\n new = %s\n birth = %s"),
(const TCHAR*)CDebugString(droidCurrent),
(const TCHAR*) CDebugString(droidNew),
(const TCHAR*) CDebugString(droidBirth) ));
// Does the entry exist already?
fExists = _idt.Query( droidBirth, &droidNewIDT, &droidBirthIDT, &fDeleted, &fCounted );
#if DBG
if( fExists )
TrkLog(( TRKDBG_MOVE, TEXT("Birth entry already exists (%s, %s)"),
fDeleted ? TEXT("deleted") : TEXT("not deleted"),
fCounted ? TEXT("counted") : TEXT("not counted") ));
#endif
if( fExists
&&
fCounted
&&
droidNewIDT == droidCurrent )
{
TrkLog(( TRKDBG_MOVE, TEXT("Attempting to modify existing entry") ));
// The birth entry for this file already points to the source
// of the notify. We can just modify it.
fModified = _idt.Modify( droidBirth, droidNew, droidBirth );
}
// If the modify didn't work or wasn't attempted, then just add this
// new entry
if( !fModified )
fAdded = _idt.Add( droidCurrent, droidNew, droidBirth, pfQuotaExceeded );
TrkLog((TRKDBG_MEND, TEXT("CTrkSvrSvc::MoveNotify() %s %s --> %s [%s]"),
fModified ? TEXT("modified")
: (fAdded ? TEXT("added") : TEXT("couldn't be added") ),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidCurrent)),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidNew)),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidBirth)) ));
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::Search
//
// Given a droid, look up the new droid for that object, and look up
// the mcid of the machine that owns that droid's volume.
//
//+----------------------------------------------------------------------------
void
CTrkSvrSvc::Search(/*in, out*/ TRK_FILE_TRACKING_INFORMATION *pSearch)
{
HRESULT hr = S_OK;
CDomainRelativeObjId droidNew;
CDomainRelativeObjId droidBirth;
CMachineId mcidNew;
BOOL fFoundObject;
IFDBG( TCHAR * ptszRoute=TEXT(""); )
TrkLog(( TRKDBG_MEND, TEXT("Searching for %s"),
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidLast)) ));
// If all the move notifies for a file have reached the DC, we can do a
// lookup based on the birth ID. But if one segment is missing, this would fail.
// So we look up based on the last ID first, and if that files try the birth ID.
// Try to map the last ID to the current droid.
fFoundObject = _idt.Query( pSearch->droidLast, &droidNew, &droidBirth);
if ( fFoundObject )
{
IFDBG( ptszRoute = TEXT("'last' found in IDT"); )
}
else
{
// We couldn't find the last known ID. Try mapping
// from the birth ID.
fFoundObject = _idt.Query( pSearch->droidBirth, &droidNew, &droidBirth );
if( fFoundObject )
{
IFDBG( ptszRoute = TEXT("'birth' found in IDT"); )
}
}
// Did we find the new droid for the file?
if ( fFoundObject )
{
// Yes, we found it. Is it really the same file (the birth ID matches)?
if( droidBirth != pSearch->droidBirth )
{
TrkLog(( TRKDBG_MEND, TEXT("Birth ID unexpected:\n %s,\n %s,\n %s"),
(const TCHAR*) CDebugString(droidBirth),
(const TCHAR*) CDebugString(pSearch->droidBirth),
(const TCHAR*) CDebugString(droidNew) ));
pSearch->hr = TRK_E_NOT_FOUND;
goto Exit;
}
// We have a good ID. This file may have multiple segments in the DS.
// Starting with the one we have, scan across any additional segments
// to find the most up-to-date droid.
CDomainRelativeObjId droidList[MAX_SHORTENABLE_SEGMENTS];
int cSegments = 0;
BOOL fStringDeleted = FALSE;
Scan(
NULL,
NULL,
droidBirth,
droidList,
sizeof(droidList)/sizeof(droidList[0]),
&cSegments,
&droidNew,
&fStringDeleted
);
}
else
{
// We couldn't find either the birth or last ID.
pSearch->hr = TRK_E_NOT_FOUND;
TrkLog(( TRKDBG_MEND, TEXT("neither 'birth' nor 'last' found") ));
}
// If we found the object in the move table, look up the machine ID in the
// volume table.
if (fFoundObject)
{
TrkLog((TRKDBG_MEND, TEXT("CTrkSvrSvc::Search( birth=%s last=%s ) successful, 'new=%s', %s"),
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidBirth)),
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidLast)),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidNew)),
ptszRoute ));
// Find the volume that holds this droid.
pSearch->hr = _voltab.FindVolume( droidNew.GetVolumeId(),
&mcidNew );
if( S_OK == pSearch->hr )
{
// We found the volume.
TrkLog(( TRKDBG_MEND, TEXT("CTrkSvrSvc::Search, volid found (%s -> %s)"),
(const TCHAR*) CDebugString(droidNew.GetVolumeId()),
(const TCHAR*) CDebugString(mcidNew) ));
pSearch->hr = S_OK;
pSearch->droidLast = droidNew;
pSearch->mcidLast = mcidNew;
}
else
{
// We were able to find the object in the move table, but couldn't
// find the volume in the volume table.
TrkLog(( TRKDBG_MEND, TEXT("CTrkSvrSvc::Search, volid not found (%s, %08x)"),
(const TCHAR*) CDebugString(droidNew.GetVolumeId()),
pSearch->hr ));
pSearch->hr = TRK_E_NOT_FOUND;
}
}
else
{
HRESULT hr = S_OK;
// We couldn't find the object in the move table.
TrkLog((TRKDBG_MEND, TEXT("CTrkSvrSvc::Search( birth=%s last=%s ) not found, %s"),
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidBirth)),
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidLast)),
ptszRoute));
// As an optimization, try looking up the last volume anyway. When this
// search request fails, the trkwks service typically looks up the location
// of the volume ID in droidLast, so we do that lookup now instead of forcing
// trkwks to make a separate request.
hr = _voltab.FindVolume( pSearch->droidLast.GetVolumeId(),
&mcidNew );
if( S_OK == hr )
{
TrkLog(( TRKDBG_MEND, TEXT("CTrkSvrSvc::Search, but last volid found (%s -> %s)"),
(const TCHAR*) CDebugString(pSearch->droidLast.GetVolumeId()),
(const TCHAR*) CDebugString(mcidNew) ));
pSearch->hr = TRK_E_NOT_FOUND_BUT_LAST_VOLUME_FOUND;
pSearch->mcidLast = mcidNew;
}
else
{
TrkLog(( TRKDBG_MEND, TEXT("CTrkSvrSvc::Search, volid not found either (%s, %08x)"),
(const TCHAR*) CDebugString(pSearch->droidLast.GetVolumeId()),
pSearch->hr ));
pSearch->hr = TRK_E_NOT_FOUND_AND_LAST_VOLUME_NOT_FOUND;
}
}
Exit:
return;
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::old_Search
//
// This method is provided for compatibility with NT5/Beta2 clients. Those
// clients would do two RPCs, one to get the new droid, then another to map
// the volid in that droid to an mcid. In the modern SEARCH request, the
// client gets both back in a single call.
//
// The distinction between the two kinds of clients is made by the
// TRKSVR_MESSAGE_TYPE in the request. Old clients pass the value
// now defined as old_SEARCH, new clients pass the value SEARCH.
//
//+----------------------------------------------------------------------------
void
CTrkSvrSvc::old_Search(/*in, out*/ old_TRK_FILE_TRACKING_INFORMATION *pSearch)
{
TRK_FILE_TRACKING_INFORMATION FileTrkInfo;
FileTrkInfo.droidBirth = pSearch->droidBirth;
FileTrkInfo.droidLast = pSearch->droidLast;
Search(&FileTrkInfo);
pSearch->hr = FileTrkInfo.hr;
pSearch->droidLast = FileTrkInfo.droidLast;
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::Timer
//
// This callback method is called by the GC timer when it's time to do a
// GC.
//
// This method doesn't raise.
//
//+----------------------------------------------------------------------------
PTimerCallback::TimerContinuation
CTrkSvrSvc::Timer( ULONG ulTimerContext )
{
HRESULT hr = S_OK;
BOOL fInvalidateCache = FALSE;
TimerContinuation continuation = CONTINUE_TIMER;
NTSTATUS Status = STATUS_SUCCESS;
TrkLog(( TRKDBG_SVR, TEXT("\nGC timer has fired") ));
__try
{
// Only the designated DC does garbage collecting.
if( !_qtable.IsDesignatedDc( TRUE ) ) // TRUE => raise on error
{
TrkLog(( TRKDBG_SVR, TEXT("Not GC-ing; not the designated DC") ));
continuation = CONTINUE_TIMER;
__leave;
}
// See if this domain is too young to do anything.
if( _refreshSequence.GetSequenceNumber()
<
static_cast<SequenceNumber>(_configSvr.GetGCMinCycles()) )
{
TrkLog(( TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR,
TEXT("Nothing to GC (%d)"),
_refreshSequence.GetSequenceNumber() ));
continuation = CONTINUE_TIMER;
__leave;
}
// Is this the part one of the timer ?
if( !_fHesitatingBeforeGC )
{
// Yes, this is part one. We'll do some work, then
// reset the timer for a small delay (so that we don't
// do a bunch of work during system initialization).
#if DBG
{
if( _configSvr.GetGCHesitation() > (5*60) )
TrkLog(( TRKDBG_SVR, TEXT("Hesitating for %d minutes before running GC"),
_configSvr.GetGCHesitation() / 60 ));
else
TrkLog(( TRKDBG_SVR, TEXT("Hesitating for %d seconds before running GC"),
_configSvr.GetGCHesitation() ));
}
#endif
_fHesitatingBeforeGC = TRUE;
_timerGC.ReInitialize( _configSvr.GetGCHesitation() ); // Doesn't raise
continuation = CONTINUE_TIMER;
}
else
{
_fHesitatingBeforeGC = FALSE;
_Stats.cEntriesGCed = 0;
// Update the sequence number
_refreshSequence.IncrementSequenceNumber();
TrkLog(( TRKDBG_SVR, TEXT("Updated the GC counter to %d"),
_refreshSequence.GetSequenceNumber() ));
// See if we need to invalidate the move table count cache (once a month).
// This is done for robustness, so that if the count gets out of sync for any
// reason, we self-correct. _MoveCounterReset holds the sequence number
// of the last time we did an invalidate.
if( (SequenceNumber) _MoveCounterReset.GetValue()
>=
_refreshSequence.GetSequenceNumber() )
{
// Invalid value
TrkLog(( TRKDBG_WARNING,
TEXT("_MoveCounterReset is invalid (%d, %d), resetting"),
_MoveCounterReset.GetValue(),
_refreshSequence.GetSequenceNumber() ));
_MoveCounterReset.Set
( (DWORD) _refreshSequence.GetSequenceNumber() );
}
else if( _MoveCounterReset.GetValue() + _configSvr.GetGCDivisor()
<= _refreshSequence.GetSequenceNumber()
)
{
TrkLog(( TRKDBG_SVR | TRKDBG_GARBAGE_COLLECT,
TEXT("Cache will be invalidated (%d)"), _MoveCounterReset.GetValue() ));
fInvalidateCache = TRUE;
}
// Calculate the seq number of the oldest entry to keep.
ULONG seqOldestToKeep = _refreshSequence.GetSequenceNumber() - _configSvr.GetGCMinCycles() + 1;
TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR,
TEXT("\nGarbage collecting all entries older than %d"),
seqOldestToKeep));
// Delete old entries from the move table
_Stats.cEntriesGCed
+= (SHORT)_idt.GarbageCollect( _refreshSequence.GetSequenceNumber(),
seqOldestToKeep,
_svcctrl.GetStopFlagAddress() );
// And delete old entries from the volume table
_Stats.cEntriesGCed
+= (SHORT)_voltab.GarbageCollect( _refreshSequence.GetSequenceNumber(),
seqOldestToKeep,
_svcctrl.GetStopFlagAddress() );
_OperationLog.Add( COperationLog::TRKSVR_GC, S_OK, CMachineId(MCID_INVALID),
seqOldestToKeep, _Stats.cEntriesGCed );
// Reset the timer to its normal period.
_timerGC.ReInitialize( _configSvr.GetGCPeriod() / _configSvr.GetGCDivisor() );
continuation = CONTINUE_TIMER;
}
}
__except( BreakOnDebuggableException() )
{
hr = GetExceptionCode();
TrkLog(( TRKDBG_WARNING,
TEXT("Ignoring exception in CTrkSvrSvc::Timer (%08x)"),
hr ));
_OperationLog.Add( COperationLog::TRKSVR_GC, hr, CMachineId(MCID_INVALID) );
}
// The Quota table's cached counts may be bad now that we've deleted
// entries from the tables.
if( fInvalidateCache )
{
_qtable.InvalidateCache();
_MoveCounterReset.Set( (DWORD) _refreshSequence.GetSequenceNumber() );
}
TrkAssert( _timerGC.IsRecurring() );
return( continuation );
}
SequenceNumber
CTrkSvrSvc::GetSequenceNumber( const CMachineId & mcidClient, const CVolumeId & volume )
{
HRESULT hr;
SequenceNumber seq;
FILETIME ftLastRefresh;
hr = _voltab.QueryVolume(mcidClient, volume, &seq, &ftLastRefresh);
if( S_OK != hr )
{
// Raise on error. E.g. if mcidClient doesn't own this volume.
TrkLog(( TRKDBG_ERROR,
TEXT("CTrkSvrSvc::GetSequenceNumber --> %08x"), hr ));
TrkRaiseException(hr);
}
return(seq);
}
void
CTrkSvrSvc::SetSequenceNumber( const CVolumeId & volume, // must ensure that validation already done for volume
SequenceNumber seq )
{
HRESULT hr;
hr = _voltab.SetSequenceNumber( volume, seq );
if (hr != S_OK)
{
TrkRaiseException( hr );
}
}
HRESULT
CTrkSvrSvc::MoveNotify( const CMachineId & mcidClient,
TRKSVR_CALL_MOVE_NOTIFICATION * pMove )
{
HRESULT hr = S_OK;
SequenceNumber seqExpected;
CVolumeId volid;
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyRequests) );
pMove->cProcessed = 0;
//
// ensure we have at least one notification because we assume that
// the current volume of the first notification is the same for all
// of the notifications in this rpc.
//
if (pMove->cNotifications == 0)
{
return(S_OK);
}
// Get the machine for this volume and the sequence number expected
// ensure that the machine is actually the owner of the volume.
// (This will raise if mcidClient doesn't own this volid.)
volid = *pMove->pvolid;
seqExpected = GetSequenceNumber(mcidClient, volid);
// Is this the sequence number we were expecting for this client volume?
TrkLog((TRKDBG_MOVE, TEXT("sequence no %d %sexpected for %s (%sforcing, expected %d)"),
pMove->seq,
seqExpected != pMove->seq ? TEXT("un") : TEXT(""),
(const TCHAR*) CDebugString(volid),
pMove->fForceSeqNumber ? TEXT("") : TEXT("not "),
seqExpected
));
if( seqExpected != pMove->seq )
{
// No, it's not the right sequence number.
if( !pMove->fForceSeqNumber )
{
// The caller hasn't requested an override, so this is an error.
pMove->seq = seqExpected;
return TRK_S_OUT_OF_SYNC;
}
}
//
// Before processing the actual move notifications, ensure that we
// have enough quota... assume that each move notify is going to
// to need one unit of quota (the writes will actually update
// the quota accurately.)
//
#ifdef VOL_QUOTA
if (pMove->cNotifications > GetAvailableNotificationQuota( ) )
{
TrkRaiseException( TRK_E_NOTIFICATION_QUOTA_EXCEEDED );
}
#endif
while (pMove->cProcessed < pMove->cNotifications)
{
// the only errors are fatal since we always make a record of
// a notification or merge it with an existing record
BOOL fQuotaExceeded = FALSE;
if( CheckWritesPerHour() )
{
TrkLog(( TRKDBG_SVR, TEXT("Stopping move-notifications due to too many writes (%d)"),
NumWritesThisHour() ));
break;
}
MoveNotify(CDomainRelativeObjId( volid, pMove->rgobjidCurrent[pMove->cProcessed] ),
pMove->rgdroidBirth[pMove->cProcessed],
pMove->rgdroidNew[pMove->cProcessed],
&fQuotaExceeded
);
if( fQuotaExceeded )
{
hr = TRK_S_NOTIFICATION_QUOTA_EXCEEDED;
break;
}
pMove->cProcessed++;
IncrementWritesPerHour();
RaiseIfStopped();
}
if( 0 != pMove->cProcessed )
{
SetSequenceNumber( volid, pMove->seq + pMove->cProcessed );
TrkLog(( TRKDBG_SVR, TEXT("Updated sequence number to %d"), pMove->seq+pMove->cProcessed ));
}
if( 0 != pMove->cNotifications )
{
//TrkLog(( TRKDBG_WARNING, TEXT("pMove = %p"), pMove ));
pMove->cNotifications = 0; // don't need to send the data back
//TrkLog(( TRKDBG_WARNING, TEXT("Free rgdroidNew (%p)"), pMove->rgdroidNew ));
//MIDL_user_free( pMove->rgdroidNew );
pMove->rgdroidNew = NULL;
//TrkLog(( TRKDBG_WARNING, TEXT("Free rgobjidCurrent (%p)"), pMove->rgobjidCurrent ));
//MIDL_user_free( pMove->rgobjidCurrent );
pMove->rgobjidCurrent = NULL;
//TrkLog(( TRKDBG_WARNING, TEXT("Free rgdroidBirth (%p)"), pMove->rgdroidBirth ));
//MIDL_user_free( pMove->rgdroidBirth );
pMove->rgdroidBirth = NULL;
}
return(hr);
}
BOOL
CTrkSvrSvc::VerifyMachineOwnsVolume( const CMachineId &mcid, const CVolumeId & volid )
{
HRESULT hr;
SequenceNumber seq;
FILETIME ft;
hr = _voltab.QueryVolume(
mcid,
volid,
&seq,
&ft);
if (hr != S_OK)
return FALSE;
else
return TRUE;
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::DeleteNotify
//
// Process a delete-notify request from a client. This request provides
// information about a file that has been deleted, so that we can purge
// it from the move table.
//
//+----------------------------------------------------------------------------
void
CTrkSvrSvc::DeleteNotify( const CMachineId & mcidClient, TRKSVR_CALL_DELETE * pDelete )
{
CVolumeId vol;
HRESULT hr = TRK_S_VOLUME_NOT_FOUND;
// Loop through all of the notifications in this batch.
for (ULONG i=0; i < pDelete->cdroidBirth; i++)
{
// Look up the current location of the file, and if it
// is on an owned volume, then allow the delete.
CDomainRelativeObjId droidCurrent;
CDomainRelativeObjId droidBirth;
// Don't embark on a slow operation if the service is stopping.
RaiseIfStopped();
// If we've already written a lot to the DS in the past hour,
// abort so we don't flood the replication queue.
if( CheckWritesPerHour() )
{
TrkLog(( TRKDBG_WARNING, TEXT("Stopping delete-notify due to too many writes") ));
TrkRaiseException( TRK_E_SERVER_TOO_BUSY );
}
// Read the existing entry for this file.
if (_idt.Query(pDelete->adroidBirth[i], &droidCurrent, &droidBirth))
{
// The entry exists.
TrkAssert(droidBirth == pDelete->adroidBirth[i]);
// See if this is the same volume that we checked on the
// previous iteration through the loop. If so, no need to
// look up again.
if (vol == droidCurrent.GetVolumeId())
{
hr = S_OK;
}
else
{
// We need to check that whoever sent this delete-notify
// request really owns the volume.
vol = droidCurrent.GetVolumeId();
if( !VerifyMachineOwnsVolume( mcidClient, vol ))
{
TrkLog((TRKDBG_OBJID_DELETIONS,
TEXT("DeleteNotify _voltab.QueryVolume( %s ) -> %s\n"),
(const TCHAR*) CDebugString( vol ),
GetErrorString(hr) ));
vol = CVolumeId();
}
}
// If the volume is owned, go ahead with the deletion.
if (hr == S_OK)
{
BOOL f = _idt.Delete( pDelete->adroidBirth[i] );
TrkLog((TRKDBG_OBJID_DELETIONS,
TEXT("DeleteNotify _idt.Delete( %s ) -> %s\n"),
(const TCHAR*) CDebugString( pDelete->adroidBirth[i] ),
f ? TEXT("Ok") : TEXT("Not Found") ));
if( f )
IncrementWritesPerHour();
}
} // if (_idt.Query(pDelete->adroidBirth[i], &droidCurrent, &droidBirth))
else
{
// Attempted to delete an entry that doesn't exist.
TrkLog((TRKDBG_OBJID_DELETIONS,
TEXT("DeleteNotify _idt.Query( droidBirth=%s ) not found\n"),
(const TCHAR*) CDebugString( pDelete->adroidBirth[i] ) ));
}
}
if( 0 != pDelete->cdroidBirth )
{
//MIDL_user_free( pDelete->adroidBirth );
//pDelete->adroidBirth = NULL;
pDelete->cdroidBirth = 0;
}
}
void
CTrkSvrSvc::Refresh( const CMachineId &mcidClient, TRKSVR_CALL_REFRESH * pRefresh )
{
// save away the input and zero the output so we don't marshall a
// ton of refresh info back to the client
ULONG cSources = pRefresh->cSources;
ULONG cVolumes = pRefresh->cVolumes;
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cRefreshRequests) );
pRefresh->cSources = 0;
pRefresh->cVolumes = 0;
// Touch the move table entries
for (ULONG i=0; i < cSources ; i++)
{
// Ensure we're not overloading the replication log
if( CheckWritesPerHour() )
{
TrkLog(( TRKDBG_SVR, TEXT("Aborting refresh due to writes-per-hour") ));
TrkRaiseException( TRK_E_SERVER_TOO_BUSY );
}
// Touch the entry in the move table.
if( _idt.Touch( pRefresh->adroidBirth[i] ))
IncrementWritesPerHour();
}
// Touch the volume table entries
for (i=0; i < cVolumes ; i++)
{
// Ensure we're not overloading the replication log
if( CheckWritesPerHour() )
{
TrkLog(( TRKDBG_SVR, TEXT("Aborting refresh due to writes-per-hour") ));
TrkRaiseException( TRK_E_SERVER_TOO_BUSY );
}
// Ensure this volume is owned by the machine.
// mikehill_test
if( !VerifyMachineOwnsVolume( mcidClient, pRefresh->avolid[i] ))
{
TrkLog(( TRKDBG_WARNING,
TEXT("Machine can't touch volume it doesn't own (%s, %s)"),
(const TCHAR*) CDebugString(mcidClient),
(const TCHAR*) CDebugString(pRefresh->avolid[i]) ));
continue;
}
// Touch the entry in the volume table
if( _voltab.Touch( pRefresh->avolid[i] ))
IncrementWritesPerHour();
}
}
void
CTrkSvrSvc::Statistics( TRKSVR_STATISTICS *pStatistics )
{
pStatistics->cSyncVolumeRequests = _Stats.cSyncVolumeRequests;
pStatistics->cSyncVolumeErrors = _Stats.cSyncVolumeErrors;
pStatistics->cSyncVolumeThreads = _Stats.cSyncVolumeThreads;
pStatistics->cCreateVolumeRequests = _Stats.cCreateVolumeRequests;
pStatistics->cCreateVolumeErrors = _Stats.cCreateVolumeErrors;
pStatistics->cClaimVolumeRequests = _Stats.cClaimVolumeRequests;
pStatistics->cClaimVolumeErrors = _Stats.cClaimVolumeErrors;
pStatistics->cQueryVolumeRequests = _Stats.cQueryVolumeRequests;
pStatistics->cQueryVolumeErrors = _Stats.cQueryVolumeErrors;
pStatistics->cFindVolumeRequests = _Stats.cFindVolumeRequests;
pStatistics->cFindVolumeErrors = _Stats.cFindVolumeErrors;
pStatistics->cTestVolumeRequests = _Stats.cTestVolumeRequests;
pStatistics->cTestVolumeErrors = _Stats.cTestVolumeErrors;
pStatistics->cSearchRequests = _Stats.cSearchRequests;
pStatistics->cSearchErrors = _Stats.cSearchErrors;
pStatistics->cSearchThreads = _Stats.cSearchThreads;
pStatistics->cMoveNotifyRequests = _Stats.cMoveNotifyRequests;
pStatistics->cMoveNotifyErrors = _Stats.cMoveNotifyErrors;
pStatistics->cMoveNotifyThreads = _Stats.cMoveNotifyThreads;
pStatistics->cRefreshRequests = _Stats.cRefreshRequests;
pStatistics->cRefreshErrors = _Stats.cRefreshErrors;
pStatistics->cRefreshThreads = _Stats.cRefreshThreads;
pStatistics->lRefreshCounter = _refreshSequence.GetSequenceNumber();
pStatistics->cDeleteNotifyRequests = _Stats.cDeleteNotifyRequests;
pStatistics->cDeleteNotifyErrors = _Stats.cDeleteNotifyErrors;
pStatistics->cDeleteNotifyThreads = _Stats.cDeleteNotifyThreads;
pStatistics->ftLastSuccessfulRequest = _Stats.cftLastSuccessfulRequest;
pStatistics->ftServiceStart = _Stats.cftServiceStartTime;
//pStatistics->ulGCIterationPeriod = _Stats.ulGCIterationPeriod;
//pStatistics->cEntriesToGC = _Stats.cEntriesToGC;
pStatistics->cEntriesGCed = _Stats.cEntriesGCed;
pStatistics->hrLastError = _Stats.hrLastError;
pStatistics->ftNextGC = _timerGC.QueryOriginalDueTime();
pStatistics->cLowestAvailableRpcThreads=_cLowestAvailableThreads;
pStatistics->cAvailableRpcThreads = _cAvailableThreads;
pStatistics->cMaxRpcThreads = MAX_SVR_THREADS;
pStatistics->cNumThreadPoolThreads = g_cThreadPoolThreads;
pStatistics->cMostThreadPoolThreads = g_cThreadPoolMaxThreads;
//pStatistics->SvcCtrlState = _svcctrl.GetState();
pStatistics->cMaxDsWriteEvents = _Stats.cMaxDsWriteEvents;
pStatistics->cCurrentFailedWrites = _Stats.cCurrentFailedWrites;
_qtable.Statistics( pStatistics );
OSVERSIONINFO verinfo;
memset( &verinfo, 0, sizeof(verinfo) );
verinfo.dwOSVersionInfoSize = sizeof(verinfo);
if( GetVersionEx( &verinfo ))
{
pStatistics->Version.dwMajor = verinfo.dwMajorVersion;
pStatistics->Version.dwMinor = verinfo.dwMinorVersion;
pStatistics->Version.dwBuildNumber = verinfo.dwBuildNumber;
}
else
{
TrkLog(( TRKDBG_ERROR, TEXT("Failed GetVersionInfo (%lu)"), GetLastError() ));
}
return;
}
HRESULT
CTrkSvrSvc::SyncVolume(const CMachineId & mcidClient, TRKSVR_SYNC_VOLUME * pSyncVolume,
ULONG cUncountedCreates )
{
HRESULT hr = S_OK;
switch (pSyncVolume->SyncType)
{
case CREATE_VOLUME:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cCreateVolumeRequests) );
if( CheckWritesPerHour() )
{
hr = TRK_E_SERVER_TOO_BUSY;
TrkLog(( TRKDBG_VOLTAB | TRKDBG_WARNING,
TEXT("Rejected CreateVolume, too many writes (%d)"),
NumWritesThisHour() ));
}
else
{
hr = _voltab.PreCreateVolume(
mcidClient,
pSyncVolume->secret,
cUncountedCreates,
&pSyncVolume->volume );
if( SUCCEEDED(hr) )
IncrementWritesPerHour();
}
if(hr == S_OK)
{
TrkLog((TRKDBG_VOLTAB,
TEXT("CreateVolume(machine=%s secret=%s volid(out)=%s) -> VOLUME_OK"),
(const TCHAR*) CDebugString(mcidClient),
(const TCHAR*) CDebugString(pSyncVolume->secret),
(const TCHAR*) CDebugString(pSyncVolume->volume) ));
}
else
{
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cCreateVolumeErrors) );
TrkLog((TRKDBG_VOLTAB,
TEXT("CreateVolume(machine=%s secret=%s) -> CreateFailed (%08x)"),
(const TCHAR*) CDebugString(mcidClient),
(const TCHAR*) CDebugString(pSyncVolume->secret),
hr ));
}
break;
case QUERY_VOLUME:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cQueryVolumeRequests) );
hr = _voltab.QueryVolume(
mcidClient,
pSyncVolume->volume,
&pSyncVolume->seq,
&pSyncVolume->ftLastRefresh
);
TrkLog((TRKDBG_VOLTAB,
TEXT("QueryVolume(machine=%s volid=%s seq(out)=%d ftLastRefresh(out)=%d) -> %s"),
(const TCHAR*) CDebugString(mcidClient),
(const TCHAR*) CDebugString(pSyncVolume->volume),
pSyncVolume->seq,
pSyncVolume->ftLastRefresh.dwLowDateTime,
GetErrorString(hr)));
if( FAILED(hr) )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cQueryVolumeErrors) );
break;
case FIND_VOLUME:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cFindVolumeRequests) );
hr = _voltab.FindVolume(
pSyncVolume->volume,
&pSyncVolume->machine
);
TrkLog((TRKDBG_VOLTAB,
TEXT("FindVolume(volid=%s machine(out)=%s) -> %s"),
(const TCHAR*) CDebugString(pSyncVolume->volume),
(const TCHAR*) CDebugString(pSyncVolume->machine),
GetErrorString(hr)));
if( FAILED(hr) )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cFindVolumeErrors) );
break;
case CLAIM_VOLUME:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cClaimVolumeRequests) );
if( CheckWritesPerHour() )
{
hr = TRK_E_SERVER_TOO_BUSY;
TrkLog(( TRKDBG_VOLTAB | TRKDBG_WARNING,
TEXT("Rejected ClaimVolume, too many writes (%d)"),
NumWritesThisHour() ));
}
else
{
hr = _voltab.ClaimVolume(
mcidClient,
pSyncVolume->volume,
pSyncVolume->secretOld,
pSyncVolume->secret,
&pSyncVolume->seq,
&pSyncVolume->ftLastRefresh
);
if( S_OK == hr ) // Might return TRK_S_VOLUME_NOT_FOUND
IncrementWritesPerHour();
}
TrkLog((TRKDBG_VOLTAB,
TEXT("ClaimVolume(machine=%s volid=%s secret=%s->%s seq(out)=%d ftLastRefresh(out)=%s) -> %s"),
(const TCHAR*) CDebugString(mcidClient),
(const TCHAR*) CDebugString(pSyncVolume->volume),
(const TCHAR*) CDebugString(pSyncVolume->secretOld),
(const TCHAR*) CDebugString(pSyncVolume->secret),
pSyncVolume->seq,
(const TCHAR*) CDebugString(pSyncVolume->ftLastRefresh),
GetErrorString(hr)));
if( FAILED(hr) )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cClaimVolumeErrors) );
break;
case TEST_VOLUME:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cTestVolumeRequests) );
if( !(_configSvr.GetTestFlags() & TRK_TEST_FLAG_ALLOC_TEST_VOLUME) )
{
hr = E_NOTIMPL;
break;
}
if( CVolumeSecret() != pSyncVolume->secret )
{
hr = _voltab.SetSecret( pSyncVolume->volume, pSyncVolume->secret );
if( FAILED(hr) ) break;
}
hr = _voltab.SetSequenceNumber( pSyncVolume->volume, pSyncVolume->seq );
if( FAILED(hr) ) break;
if( CMachineId() != pSyncVolume->machine )
{
hr = _voltab.SetMachine( pSyncVolume->volume, pSyncVolume->machine );
if ( FAILED(hr) ) break;
}
if( SUCCEEDED(hr) )
IncrementWritesPerHour();
if( FAILED(hr) )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cTestVolumeErrors) );
break;
case DELETE_VOLUME:
//InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cDeleteVolumeRequests) );
if( CheckWritesPerHour() )
{
hr = TRK_E_SERVER_TOO_BUSY;
TrkLog(( TRKDBG_VOLTAB | TRKDBG_WARNING,
TEXT("Rejected DeleteVolume, too many writes (%d)"),
NumWritesThisHour() ));
}
else
{
hr = _voltab.DeleteVolume(
mcidClient,
pSyncVolume->volume
);
if( SUCCEEDED(hr) )
IncrementWritesPerHour();
}
TrkLog((TRKDBG_VOLTAB,
TEXT("DeleteVolume(machine=%s volid=%s -> %s"),
(const TCHAR*) CDebugString(mcidClient),
(const TCHAR*) CDebugString(pSyncVolume->volume),
GetErrorString(hr)));
/*
if( FAILED(hr) )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cClaimVolumeErrors) );
*/
break;
default:
TrkAssert(0 && "unknown switch type in SyncVolume");
hr = TRK_S_VOLUME_NOT_FOUND;
break;
}
return(hr);
}
//
// If we have 0 threads, then accept any pri <=9
// If we have 1 thread, then accept any pri <=9
// If we have 2 threads, then accept any pri <=7
// If we have 3 threads, then accept any pri <=6
// If we have 4 threads, then accept any pri <=5
// If we have 5 threads, then accept any pri <=4
// If we have 6 threads, then accept any pri <=3
// If we have 7 threads, then accept any pri <=0
// If we have 8 threads, then accept any pri <=0
// If we have 9 threads, then accept any pri <=0
// If we have 10 threads, don't accept any
//
BOOL
CTrkSvrSvc::CountPrioritizedThread( const TRKSVR_MESSAGE_UNION * pMsg )
{
static LONG Accept[10] = { 0, 0, 0, 3, 4, 5, 6, 7, 9, 9 };
TrkAssert( ELEMENTS(Accept) == MAX_SVR_THREADS );
LONG l = InterlockedDecrement( &_cAvailableThreads );
// It's not worth a lock to protect this statistic, we'll just hope that we
// don't get pre-empted during the update.
_cLowestAvailableThreads = min( _cLowestAvailableThreads, l );
TrkAssert( l >= -1 && l < MAX_SVR_THREADS );
if (l == -1 || pMsg->Priority > Accept[ l ])
{
InterlockedIncrement( &_cAvailableThreads );
return( FALSE );
}
return(TRUE);
}
void
CTrkSvrSvc::ReleasePrioritizedThread()
{
LONG l = InterlockedIncrement( &_cAvailableThreads );
TrkAssert( l >= 0 && l <= MAX_SVR_THREADS );
}
HRESULT
CTrkSvrSvc::CreateVolume(const CMachineId & mcidClient, const TRKSVR_SYNC_VOLUME& pSyncVolume)
{
return _voltab.AddVolidToTable(pSyncVolume.volume, mcidClient, pSyncVolume.secret );
}
void
CTrkSvrSvc::OnRequestStart( TRKSVR_MESSAGE_TYPE MsgType )
{
switch( MsgType )
{
case SEARCH:
case old_SEARCH:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSearchRequests) );
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSearchThreads) );
break;
case MOVE_NOTIFICATION:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyRequests) );
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyThreads) );
break;
case REFRESH:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cRefreshRequests) );
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cRefreshThreads) );
break;
case SYNC_VOLUMES:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSyncVolumeRequests) );
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSyncVolumeThreads) );
break;
case DELETE_NOTIFY:
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cDeleteNotifyRequests) );
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cDeleteNotifyThreads) );
break;
case STATISTICS:
break;
default:
TrkLog(( TRKDBG_ERROR, TEXT("Invalid MsgType in CTrkSvrSvc::OnRequestStart(%d)"),
MsgType ));
TrkAssert( FALSE );
}
}
void
CTrkSvrSvc::OnRequestEnd( TRKSVR_MESSAGE_UNION * pMsg, const CMachineId &mcid, HRESULT hr )
{
int i = 0;
__try
{
switch( pMsg->MessageType )
{
case SEARCH:
case old_SEARCH:
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cSearchThreads) );
if( FAILED(hr)
||
( 1 <= pMsg->Search.cSearch && S_OK != pMsg->Search.pSearches->hr ))
{
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSearchErrors) );
}
_OperationLog.Add( COperationLog::TRKSVR_SEARCH, hr, mcid, pMsg->Search.pSearches->droidBirth );
break;
case MOVE_NOTIFICATION:
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyThreads) );
if( S_OK != hr )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyErrors) );
_OperationLog.Add( COperationLog::TRKSVR_MOVE_NOTIFICATION, hr, mcid );
break;
case REFRESH:
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cRefreshThreads) );
if( FAILED(hr) )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cRefreshErrors) );
_OperationLog.Add( COperationLog::TRKSVR_REFRESH, hr, mcid, pMsg->Refresh.cSources, pMsg->Refresh.cVolumes );
break;
case SYNC_VOLUMES:
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cSyncVolumeThreads) );
if( FAILED(hr) )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSyncVolumeErrors) );
_OperationLog.Add( COperationLog::TRKSVR_SYNC_VOLUMES, hr, mcid );
break;
case DELETE_NOTIFY:
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cDeleteNotifyThreads) );
if( FAILED(hr) )
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cDeleteNotifyErrors) );
_OperationLog.Add( COperationLog::TRKSVR_DELETE_NOTIFY, hr, mcid );
break;
case STATISTICS:
break;
}
if( FAILED(hr) )
SetLastError( hr );
else if( STATISTICS != pMsg->MessageType )
_Stats.cftLastSuccessfulRequest = CFILETIME();
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_WARNING, TEXT("Ignoring exception in OnRequestEnd") ));
}
return;
}
//+----------------------------------------------------------------------------
//
// CTrkSvrSvc::SvrMessage
//
// This is the primary starting point for processing of the trksvr
// service's LnkSvrMessage RPC method. For the most part, it looks
// at the MessageType (the request is in the form of a union, with
// a message-type and type-appropriate parameters), then switches
// to specific handler routine.
//
//+----------------------------------------------------------------------------
HRESULT
CTrkSvrSvc::SvrMessage(
handle_t IDL_handle,
TRKSVR_MESSAGE_UNION * pMsg)
{
HRESULT hr = S_OK;
ULONG i;
ULONG cSources;
CMachineId mcidClient;
SThreadFromPoolState OriginalThreadFromPoolState;
if (GetState() != SERVICE_RUNNING)
{
return(TRK_E_SERVICE_NOT_RUNNING);
}
// If we're getting busy (wrt active threads), we may have
// to reject this request, based on how busy we are and the
// priority of the request.
if (!CountPrioritizedThread( pMsg ))
{
return(TRK_E_SERVER_TOO_BUSY);
}
__try
{
// Set thread-specific settings, saving the old settings.
OriginalThreadFromPoolState = InitializeThreadFromPool();
// Update statistics
OnRequestStart( pMsg->MessageType );
// Query the IDL handle for the client's mcid. Don't do it, though, if
// we're not using secure RPC (which only happens in testing),
// or if the message type is statistics.
if( STATISTICS == pMsg->MessageType )
{
// All messages that come in to this routine are from a machine account
// (in which trkwks runs). The exception to this rule is the Statistics
// request, which comes from a user. We'll allow all Authenticated Users
// access to the statistics (they can access the DS tables by default
// anyway).
if( RequireSecureRPC() )
{
RPC_STATUS RpcStatus;
RpcStatus = RpcImpersonateClient(IDL_handle);
if (RpcStatus != RPC_S_OK)
TrkRaiseWin32Error(RpcStatus);
RpcRevertToSelf();
}
mcidClient = CMachineId( MCID_LOCAL );
}
else
{
mcidClient = NULL != pMsg->ptszMachineID && !g_ptrksvr->RequireSecureRPC()
? CMachineId(pMsg->ptszMachineID)
: CMachineId(IDL_handle);
}
// Check for a client doing a denial-of-service attack.
if( RequireSecureRPC() ) // Always true except in testing
CheckClient(mcidClient);
// Switch on the message type.
switch (pMsg->MessageType)
{
case SEARCH:
TrkLog((TRKDBG_MEND|TRKDBG_SVR, TEXT("SEARCH from \\\\%s"),
(const TCHAR*) CDebugString( mcidClient )));
for (i=0; i<pMsg->Search.cSearch; i++)
{
TrkAssert( NULL != pMsg->Search.pSearches );
pMsg->Search.pSearches[i].hr = TRK_E_UNAVAILABLE;
}
for (i=0; i<pMsg->Search.cSearch; i++)
{
Search(&pMsg->Search.pSearches[i]);
}
break;
case old_SEARCH:
TrkLog((TRKDBG_MEND|TRKDBG_SVR, TEXT("old_SEARCH from \\\\%s"),
(const TCHAR*) CDebugString( mcidClient )));
for (i=0; i<pMsg->old_Search.cSearch; i++)
{
pMsg->old_Search.pSearches[i].hr = TRK_E_UNAVAILABLE;
}
for (i=0; i<pMsg->old_Search.cSearch; i++)
{
old_Search(&pMsg->old_Search.pSearches[i]);
}
break;
case MOVE_NOTIFICATION:
TrkLog((TRKDBG_MOVE|TRKDBG_SVR, TEXT("MOVE_NOTIFICATION from \\\\%s (%d notifications)"),
(const TCHAR*) CDebugString( mcidClient ),
pMsg->MoveNotification.cNotifications ));
hr = MoveNotify( mcidClient, &pMsg->MoveNotification );
break;
case REFRESH:
TrkLog((TRKDBG_GARBAGE_COLLECT|TRKDBG_SVR, TEXT("REFRESH from \\\\%s"),
(const TCHAR*) CDebugString( mcidClient )));
Refresh( mcidClient, &pMsg->Refresh );
break;
case SYNC_VOLUMES:
{
BOOL fHaveCreateVolume = FALSE;
TRKSVR_SYNC_VOLUME rgCopyOfSyncVolumes[NUM_VOLUMES];
ULONG cUncountedCreates = 0;
// Validate the number of volumes in this request to protect against an unruly
// client.
if(pMsg->SyncVolumes.cVolumes > NUM_VOLUMES)
{
TrkLog((TRKDBG_ERROR, TEXT("Number of volumes exceeded per machine limit %d"), pMsg->SyncVolumes.cVolumes));
TrkRaiseException( E_INVALIDARG );
}
// Pre-initialize the return buffer.
for (i=0; i < pMsg->SyncVolumes.cVolumes; i++)
{
pMsg->SyncVolumes.pVolumes[i].hr = TRK_S_VOLUME_NOT_FOUND;
}
// Perform the sync for each of the volumes.
for (i=0; i < pMsg->SyncVolumes.cVolumes; i++)
{
// Perform the sync.
pMsg->SyncVolumes.pVolumes[i].hr
= SyncVolume(mcidClient, pMsg->SyncVolumes.pVolumes + i, cUncountedCreates );
// Keep track of the number of CREATE_VOLUME sub-requests.
if(CREATE_VOLUME == pMsg->SyncVolumes.pVolumes[i].SyncType &&
pMsg->SyncVolumes.pVolumes[i].hr == S_OK)
{
fHaveCreateVolume = TRUE;
cUncountedCreates++;
}
// Make a copy of the newly generated volume id so that we can
// add it to the DS later. We want to make sure we don't
// add the entry to the DS until we're sure it made it onto
// the client.
// (There once was a bug where the newly created
// volid was written to the DS, then returned to the client,
// but the client wasn't receiving the response due to an
// security problem. Consequently, the client kept asking
// and asking for the volume ID, all of which went into the DS.
// So now we make sure the client gets it, and we have a
// per-client volume quota.)
rgCopyOfSyncVolumes[i] = pMsg->SyncVolumes.pVolumes[i];
}
#ifdef VOL_REPL
if (pMsg->SyncVolumes.ppVolumeChanges != NULL)
{
CFILETIME ftChangesUntilThisTime;
CVolumeMap VolMap;
if (CFILETIME(-1) != CFILETIME(pMsg->SyncVolumes.ftFirstChange))
{
_voltab.QueryVolumeChanges(
CFILETIME(pMsg->SyncVolumes.ftFirstChange),
&VolMap );
VolMap.MoveTo( &pMsg->SyncVolumes.cChanges, pMsg->SyncVolumes.ppVolumeChanges );
pMsg->SyncVolumes.ftFirstChange = ftChangesUntilThisTime;
}
}
#endif
// If there were successful CREATE_VOLUME sub-requests in this
// SYNC_VOLUME request, send the new IDs back to the client now,
// and write the volids to the DS.
if(TRUE == fHaveCreateVolume)
{
TrkLog((TRKDBG_LOG, TEXT("Calling back to trkwks")));
__try
{
// Note: A trkwks could tie up several threads by blocking
// in this callback. Worse case this DOS attack blocks
// trksvr, but it can't cause any great harm to the DC or DS.
hr = LnkSvrMessageCallback(pMsg);
TrkLog(( TRKDBG_SVR, TEXT("LnkSvrMessageCallback returned %08x"), hr ));
}
__except(BreakOnDebuggableException())
{
hr = GetExceptionCode();
TrkLog((TRKDBG_ERROR, TEXT("Exception in Callback, %08x"), hr));
}
// If callback is successful, add the created volumes into the DS. Otherwise don't do
// anything.
TrkAssert(pMsg->SyncVolumes.cVolumes <= 26);
if( SUCCEEDED(hr) )
{
for (i=0; i < pMsg->SyncVolumes.cVolumes; i++)
{
if(CREATE_VOLUME == pMsg->SyncVolumes.pVolumes[i].SyncType && S_OK == pMsg->SyncVolumes.pVolumes[i].hr)
{
pMsg->SyncVolumes.pVolumes[i].hr =
CreateVolume(mcidClient, rgCopyOfSyncVolumes[i]);
}
}
}
pMsg->SyncVolumes.cVolumes = 0;
}
break;
}
case DELETE_NOTIFY:
TrkLog((TRKDBG_OBJID_DELETIONS|TRKDBG_SVR,
TEXT("DELETE_NOTIFY from \\\\%s"),
(const TCHAR*) CDebugString( mcidClient )));
DeleteNotify( mcidClient, &pMsg->Delete );
break;
case STATISTICS:
TrkLog(( TRKDBG_SVR, TEXT("TRKSVR_STATISTICS"),
(const TCHAR*) CDebugString(mcidClient) ));
memset( &pMsg->Statistics, 0, sizeof(TRKSVR_STATISTICS) );
Statistics( &pMsg->Statistics );
break;
default:
hr = TRK_E_UNKNOWN_SVR_MESSAGE_TYPE;
break;
}
}
__except(BreakOnDebuggableException())
{
hr = GetExceptionCode();
TrkLog((TRKDBG_ERROR, TEXT("LnkSvrMessage exception %08X caught during %s"),
hr,
(const TCHAR*) CDebugString(pMsg->MessageType) ));
}
// Update statistics
OnRequestEnd( pMsg, mcidClient, hr );
ReleasePrioritizedThread();
// Restore the thread-specif settings.
UnInitializeThreadFromPool( OriginalThreadFromPoolState );
return(hr);
}
HRESULT
StubLnkSvrMessage_Old(
handle_t IDL_handle,
TRKSVR_MESSAGE_UNION_OLD * pMsg)
{
HRESULT hr;
TrkLog((TRKDBG_SVR, TEXT("Received downlevel call: ... thunking")));
TRKSVR_MESSAGE_UNION Msg2;
Msg2.MessageType = pMsg->MessageType;
Msg2.Priority = PRI_5;
switch (Msg2.MessageType)
{
case (SEARCH):
Msg2.Search = pMsg->Search;
break;
case (MOVE_NOTIFICATION):
Msg2.MoveNotification = pMsg->MoveNotification;
break;
case (REFRESH):
Msg2.Refresh = pMsg->Refresh;
break;
case (SYNC_VOLUMES):
Msg2.SyncVolumes = pMsg->SyncVolumes;
break;
case (DELETE_NOTIFY):
Msg2.Delete = pMsg->Delete;
break;
}
Msg2.ptszMachineID = pMsg->ptszMachineID;
hr = StubLnkSvrMessage( IDL_handle, &Msg2 );
switch (Msg2.MessageType)
{
case (SEARCH):
pMsg->Search = Msg2.Search;
break;
case (MOVE_NOTIFICATION):
pMsg->MoveNotification = Msg2.MoveNotification;
break;
case (REFRESH):
pMsg->Refresh = Msg2.Refresh;
break;
case (SYNC_VOLUMES):
pMsg->SyncVolumes = Msg2.SyncVolumes ;
break;
case (DELETE_NOTIFY):
pMsg->Delete = Msg2.Delete;
break;
}
pMsg->ptszMachineID = Msg2.ptszMachineID;
return(hr);
}
// must return a positive number (success code) if it doesn't want the caller
// to find another DC to do it on.
HRESULT
StubLnkSvrMessage(
handle_t IDL_handle,
TRKSVR_MESSAGE_UNION * pMsg)
{
return( g_ptrksvr->SvrMessage( IDL_handle, pMsg ));
}
void
CTrkSvrRpcServer::Initialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData, CTrkSvrConfiguration * pTrkSvrConfig )
{
RPC_STATUS rpcstatus;
NET_API_STATUS netstatus;
// Ensure there's a tcp/ip binding handle
rpcstatus = RpcServerUseProtseq( const_cast<TCHAR*>(s_tszTrkSvrRpcProtocol),
pTrkSvrConfig->GetSvrMaxRpcCalls(), NULL);
if (rpcstatus != RPC_S_OK &&
rpcstatus != RPC_S_DUPLICATE_ENDPOINT)
{
// Log an event, unless it's a not-supported error, which happens during
// a normal setup.
if( RPC_S_PROTSEQ_NOT_SUPPORTED != rpcstatus )
{
TrkLog((TRKDBG_ERROR, TEXT("RpcServerUseProtseqEp %08x"), rpcstatus));
TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, HRESULT_FROM_WIN32(rpcstatus),
TRKREPORT_LAST_PARAM );
}
TrkRaiseWin32Error(rpcstatus);
}
// If we don't have a pSvcsGlobalData (we're not running in services.exe),
// tell RpcServerRegisterIfEx to automatically set up a listen thread.
CRpcServer::Initialize( Stubtrksvr_v1_0_s_ifspec,
NULL == pSvcsGlobalData ? RPC_IF_AUTOLISTEN : 0,
pTrkSvrConfig->GetSvrMaxRpcCalls(),
RpcSecurityEnabled(), // fSetAuthInfo
s_tszTrkSvrRpcProtocol );
TrkLog(( TRKDBG_RPC, TEXT("Registered TrkSvr RPC server %s (%d)"),
RpcSecurityEnabled() ? TEXT("") : TEXT("(without authorization)"),
pTrkSvrConfig->GetSvrMaxRpcCalls() ));
}
void
CTrkSvrRpcServer::UnInitialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData )
{
TrkLog(( TRKDBG_RPC, TEXT("Unregistering TrkSvr RPC server") ));
CRpcServer::UnInitialize( );
TrkLog(( TRKDBG_RPC, TEXT("Unregistered TrkSvr RPC server") ));
}
CMachineId::CMachineId(handle_t ClientBinding)
{
RPC_STATUS RpcStatus;
NTSTATUS status;
BOOL f;
HANDLE hToken = NULL;
PTOKEN_USER pToken;
BYTE * pbToken = NULL;
PSID LocalSystemSid = NULL;
PUNICODE_STRING pUserName = NULL;
PUNICODE_STRING pUserDomainName = NULL;
NET_API_STATUS NetStatus;
WCHAR *pwszDomain = NULL;
BOOLEAN fIsWorkGroup;
LPBYTE pbAccountInfo = NULL;
BOOL fImpersonating = FALSE;
// Begin impersonating the user.
RpcStatus = RpcImpersonateClient(ClientBinding);
if (RpcStatus != RPC_S_OK)
TrkRaiseWin32Error(RpcStatus);
fImpersonating = TRUE;
__try
{
WCHAR wszCurrentUser[ UNLEN+1 ];
WCHAR wszDomainName[ CNLEN+1 ];
// Get the local domain name (so we can verify it against the user's).
NetStatus = NetpGetDomainNameEx(&pwszDomain, &fIsWorkGroup);
if (NetStatus != NO_ERROR)
{
pwszDomain = NULL;
TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, HRESULT_FROM_WIN32(NetStatus),
TRKREPORT_LAST_PARAM );
TrkRaiseWin32Error(NetStatus);
}
TrkAssert( !fIsWorkGroup );
// Get the user's name & domain (possible because we're
// impersonating).
status = LsaGetUserName( &pUserName, &pUserDomainName );
if( !NT_SUCCESS(status) )
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get user name (%08x)"), status ));
TrkRaiseNtStatus( status );
}
memcpy( wszCurrentUser, pUserName->Buffer, pUserName->Length );
wszCurrentUser[ pUserName->Length / sizeof(WCHAR) ] = L'\0';
// Ensure this is a user in this domain
{
UNICODE_STRING LocalDomainName = { 0, 0, NULL };
RtlInitUnicodeString( &LocalDomainName, pwszDomain );
if( 0 != RtlCompareUnicodeString( pUserDomainName, &LocalDomainName, TRUE ))
{
TrkLog(( TRKDBG_WARNING, TEXT("User %s is from another domain"),
wszCurrentUser ));
TrkRaiseException( TRK_E_UNKNOWN_SID );
}
}
// The user name should end in a '$', since it should be a
// machine account.
if (wszCurrentUser[ pUserName->Length / sizeof(WCHAR) - 1] != L'$')
{
// No dollar suffix, it's probably local system on the DC
DWORD SizeRequired;
// Get the user's SID
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken))
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open thread token for %ws (%08x)"), wszCurrentUser, HRESULT_FROM_WIN32(GetLastError()) ));
TrkRaiseLastError();
}
if (!GetTokenInformation( hToken,
TokenUser,
NULL,
0,
&SizeRequired ) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get thread token information (1) for %ws (%08x)"), wszCurrentUser, HRESULT_FROM_WIN32(GetLastError()) ));
TrkRaiseLastError();
}
pbToken = new BYTE [SizeRequired];
if (pbToken == NULL)
TrkRaiseWin32Error(ERROR_NOT_ENOUGH_MEMORY);
pToken = (PTOKEN_USER) pbToken;
if (!GetTokenInformation( hToken,
TokenUser,
pToken,
SizeRequired,
&SizeRequired ))
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get thread token information (2) for %ws (%08x)"), wszCurrentUser, HRESULT_FROM_WIN32(GetLastError()) ));
TrkRaiseLastError();
}
// Get the local system SID
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
if (! AllocateAndInitializeSid(
&NtAuthority,
1,
SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0,
&LocalSystemSid ))
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't Alloc/Init SID for %ws (%08x)"), wszCurrentUser, HRESULT_FROM_WIN32(GetLastError()) ));
TrkRaiseLastError();
}
// Verify that the user is local system.
BOOL fEqual = EqualSid( pToken->User.Sid, LocalSystemSid );
if (!fEqual)
{
TrkLog(( TRKDBG_ERROR, TEXT("Unknown SID: %ws"), wszCurrentUser ));
TrkRaiseException(TRK_E_UNKNOWN_SID);
}
*this = CMachineId(MCID_LOCAL);
Normalize();
AssertValid();
} // if (wszCurrentUser[ pUserName->Length / sizeof(WCHAR) - 1] != L'$')
else
{
// We don't need to be impersonating any longer, potentially (though
// not likely) ACLs on the SAM could prevent the impersonated user
// from making this call. So we might as well revert now.
TrkAssert( fImpersonating );
if( fImpersonating )
{
RpcRevertToSelf();
fImpersonating = FALSE;
}
// Get account info for this user, so we can
// verify that it's a machine account.
NetStatus = NetUserGetInfo( NULL, // Check on this server
wszCurrentUser,
1, // Get USER_INFO_1
&pbAccountInfo );
if (NetStatus != 0)
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get user %s info (%08x)"),
wszCurrentUser,
NetStatus));
TrkRaiseWin32Error(NetStatus);
}
TrkLog(( TRKDBG_SVR, TEXT("\nUser is %s"), wszCurrentUser));
if ((((USER_INFO_1*)pbAccountInfo)->usri1_flags &
(UF_WORKSTATION_TRUST_ACCOUNT | UF_SERVER_TRUST_ACCOUNT)) == 0)
{
// if the account is not a workstation or backup dc account, fail
TrkRaiseException( TRK_E_CALLER_NOT_MACHINE_ACCOUNT );
}
// overwrite the $
wszCurrentUser[ pUserName->Length / sizeof(WCHAR) - 1] = L'\0';
if (_tcslen(wszCurrentUser) + 1 > sizeof(_szMachine))
TrkRaiseException(TRK_E_IMPERSONATED_COMPUTERNAME_TOO_LONG);
else if( TEXT('\0') == wszCurrentUser[0] )
TrkRaiseException( TRK_E_NULL_COMPUTERNAME );
memset(&_szMachine, 0, sizeof(_szMachine));
// Convert the Unicode computer name into Ansi, using
// the OEMCP codepage (NetBios/computer names are always
// in OEMCP, not Window/Ansi).
if( 0 == WideCharToMultiByte( CP_OEMCP, 0,
wszCurrentUser,
-1,
_szMachine,
sizeof(_szMachine),
NULL, NULL ))
{
TrkLog(( TRKDBG_ERROR,
TEXT("Couldn't convert machine name %s to multi-byte (%lu)"),
wszCurrentUser, GetLastError() ));
TrkRaiseLastError();
}
TrkLog(( TRKDBG_WARNING,
TEXT("Converted machine name: %hs (from %s, ")
MCID_BYTE_FORMAT_STRING,
_szMachine, wszCurrentUser,
(BYTE)_szMachine[0], (BYTE)_szMachine[1], (BYTE)_szMachine[2], (BYTE)_szMachine[3],
(BYTE)_szMachine[4], (BYTE)_szMachine[5], (BYTE)_szMachine[6], (BYTE)_szMachine[7],
(BYTE)_szMachine[8], (BYTE)_szMachine[9], (BYTE)_szMachine[10], (BYTE)_szMachine[11],
(BYTE)_szMachine[12], (BYTE)_szMachine[13], (BYTE)_szMachine[14], (BYTE)_szMachine[15] ));
Normalize();
AssertValid();
} // if (wszCurrentUser[ pUserName->Length / ... else
}
__finally
{
if( fImpersonating )
{
RpcRevertToSelf();
fImpersonating = FALSE;
}
if (hToken != NULL)
{
CloseHandle(hToken);
}
if (pbToken != NULL)
{
delete [] pbToken;
}
if (LocalSystemSid != NULL)
{
FreeSid( LocalSystemSid );
}
if (pUserName != NULL)
{
LsaFreeMemory(pUserName->Buffer);
LsaFreeMemory(pUserName);
}
if (pUserDomainName != NULL)
{
LsaFreeMemory(pUserDomainName->Buffer);
LsaFreeMemory(pUserDomainName);
}
if (pbAccountInfo != NULL)
{
NetApiBufferFree(pbAccountInfo);
}
if( NULL != pwszDomain )
NetApiBufferFree(pwszDomain);
}
}