1630 lines
51 KiB
C++
1630 lines
51 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
|
|
/*
|
|
|
|
File Replication Sample
|
|
Client System Service
|
|
|
|
FILE: FileRepClientProc.cpp
|
|
|
|
PURPOSE: Remote procedures for client system service
|
|
|
|
FUNCTIONS:
|
|
RequestFile() - receives file replication requests
|
|
ThreadProcRequest() - processes file replication requests
|
|
in an independent thread.
|
|
|
|
COMMENTS:
|
|
|
|
*/
|
|
|
|
#include "common.h"
|
|
|
|
#include <stddef.h>
|
|
#include <Ntdsapi.h>
|
|
#include <time.h>
|
|
|
|
// header file generated by MIDL compiler
|
|
#include "FileRepClient.h"
|
|
#include "FileRepServer.h"
|
|
|
|
// Contains declarations for system service functions.
|
|
#include "Service.h"
|
|
|
|
#ifdef DEBUG2
|
|
#include "DbgMsg.h"
|
|
#endif
|
|
|
|
#ifdef PROF
|
|
#include "Prof.h"
|
|
#endif
|
|
|
|
extern HANDLE ClientCompletionPort;
|
|
extern LONG nThreadsAtClientCompletionPort;
|
|
|
|
// Files that are being written will be extended by this
|
|
// size to allow writes to be trully asyncronous.
|
|
#define FILE_SIZE_EXTENSION (1*1024*1024)
|
|
|
|
// Timeout for threads waiting on the client completion port
|
|
#define ClientCompletionPortTimeout (20*1000)
|
|
|
|
|
|
// The types of IO posted onto the completion port
|
|
typedef enum {
|
|
IoFileRep, // Posted by FileRep
|
|
IoPipe, // Posted by the RPC runtime
|
|
IoFile // Posted by the File IO subsystem
|
|
} IoCompletionType;
|
|
|
|
// Action to be taken next for a given request
|
|
typedef enum {
|
|
Activate,
|
|
Pull,
|
|
Wait
|
|
} ActionType;
|
|
|
|
// The state of the client request.
|
|
typedef enum tReqState {
|
|
StateArrived,
|
|
StateQueued,
|
|
StateActive
|
|
} ReqState;
|
|
|
|
#ifdef DEBUG1
|
|
// Used to track the number of outstanding requests
|
|
unsigned nClientReqs = 0;
|
|
#endif
|
|
|
|
//
|
|
// Packages up the variables to be passed
|
|
// to the processing thread.
|
|
//
|
|
typedef struct tReq{
|
|
|
|
// Binding handle to the server
|
|
handle_t hFileRepServer;
|
|
// Explicit binding to the client system service
|
|
handle_t hFileRepClient;
|
|
|
|
HANDLE hTokenHandle;
|
|
HANDLE hLocalFile;
|
|
|
|
RPC_STR ServerName;
|
|
LPTSTR RemoteFileName;
|
|
LPTSTR LocalFileName;
|
|
|
|
// Set when we are impersonating the client
|
|
BOOL bImpersonating;
|
|
|
|
// Priority of this request
|
|
UINT Pri;
|
|
|
|
// User SID
|
|
PSID pSID;
|
|
|
|
// The state of the current request
|
|
ReqState State;
|
|
|
|
// The out-pipe we use to pull data from the server.
|
|
ASYNC_CHAR_PIPE_TYPE OutPipe;
|
|
|
|
// The async handle for the call to RemoteOpen.
|
|
RPC_ASYNC_STATE Async;
|
|
|
|
BYTE pbBuf[PULL_BUFSIZE];
|
|
|
|
LONG FileWritePos;
|
|
LONG CurrentExtendedSize;
|
|
|
|
OVERLAPPED FileOl;
|
|
|
|
CRITICAL_SECTION Lock;
|
|
|
|
LONG nWritesOutstanding;
|
|
LONG nBytesOutstanding;
|
|
|
|
// There is an outstanding pull.
|
|
BOOL bPullOutstanding;
|
|
|
|
// There is a buffer that has been pulled
|
|
// but haven't been written to the server yet.
|
|
BOOL bBuf;
|
|
|
|
// A receive complete notification has been received,
|
|
// but no pulls have been done yet, and so the next pull
|
|
// should succeed.
|
|
BOOL bDataAvailable;
|
|
|
|
// Set when all the pulls have completed.
|
|
BOOL bAllPullsDone;
|
|
|
|
BOOL bCallMade;
|
|
BOOL bCallCancelled;
|
|
|
|
// Set when call complete notification has been received or
|
|
// a syncronous failure has been received from the pull routine.
|
|
BOOL bCallComplete;
|
|
|
|
// Set when we received a premature call completed notification and query
|
|
// call status returned an error.
|
|
BOOL bAsyncCallFailureReceived;
|
|
|
|
// We use this wariable to set an error code so that
|
|
// ClientShutdownRequest can know whether an error has occurred.
|
|
DWORD Status;
|
|
|
|
// Set after pull returned an error.
|
|
BOOL bFailureFromPull;
|
|
|
|
// Set after WriteFile returned an error.
|
|
BOOL bFailureFromWrite;
|
|
|
|
#ifdef PROF
|
|
// Used to track arrival and completion times of requests.
|
|
ULONG nReqId;
|
|
#endif
|
|
|
|
} Req;
|
|
|
|
|
|
//
|
|
// Extends file associated with hFile to Size bytes.
|
|
// This is necessary to allow the async writes to be "trully"
|
|
// async. Because of the inner workings of NTFS when writes extend
|
|
// the size of the file they may end up being syncronous.
|
|
//
|
|
VOID SetFileSize (HANDLE hFile, DWORD Size) {
|
|
SetFilePointer(hFile,
|
|
Size,
|
|
NULL,
|
|
FILE_BEGIN);
|
|
SetEndOfFile(hFile);
|
|
}
|
|
|
|
//
|
|
// Checks request queues in the order of decreasing priority. If a request is found and
|
|
// there is space in the corresponding quota of active requests, then it is returned.
|
|
//
|
|
Req* FindCReq(void) {
|
|
|
|
#ifdef DEBUG2
|
|
TCHAR Msg[MSG_SIZE];
|
|
ULONG bufSize = MSG_SIZE; // Keeps track of remaining size of buffer for _stprintf_s
|
|
int nCharWritten;
|
|
#endif
|
|
|
|
Req *pReq = NULL;
|
|
|
|
//
|
|
// We will now go and check if the request or the active request sets of queues have anything that we
|
|
// can service. If they do not, then this thread can terminate.
|
|
//
|
|
|
|
// Check if the request queues have anything on them. If they do,
|
|
// pick a request off the highest priority queue and handle it, but only if we
|
|
// are not handling enough of those requests already.
|
|
for (UINT pri = NumPriGroups; pri > 0; pri--) {
|
|
pReq = (Req *) QueueRemove(ClientReqQueues[pri-1]);
|
|
if (pReq != NULL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We had found a request
|
|
if (pReq != NULL) {
|
|
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("RequestFile: Took req %p off Req queue %p\n"), pReq, ClientReqQueues[pReq->Pri]);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
|
|
if (CounterIncrement(pClientActiveReqCounters[pReq->Pri])) {
|
|
if (QueueHashIncrementCounter(ClientActiveReqHashCounters[pReq->Pri], pReq->pSID)) {
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("Incremented ClientActiveReqCounters[%d] and ClientActiveReqHashCounters[%d]\n"), pReq->Pri, pReq->Pri);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
// The request now resides on a new queue.
|
|
CounterDecrement(pClientReqCounters[pReq->Pri]);
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("Decremented ClientReqCounters[%d]\n"), pReq->Pri);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
pReq->State = StateActive;
|
|
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("RequestFile: Handling active req %p\n"), pReq);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
|
|
return pReq;
|
|
|
|
}
|
|
else {
|
|
// There were too many requests for a given SID, place the request back
|
|
// onto the queue.
|
|
|
|
// Don't forget to decrement the counter for the group!
|
|
CounterDecrement(pClientActiveReqCounters[pReq->Pri]);
|
|
|
|
QueueAdd(ClientReqQueues[pReq->Pri], pReq, TRUE);
|
|
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("Too many active requests for a SID\n"));
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("RequestFile: Put req %p onto Req queue %p\n"), pReq, ClientReqQueues[pReq->Pri]);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
// There were too many active requests for the user group, place the request back
|
|
// onto the queue.
|
|
|
|
QueueAdd(ClientReqQueues[pReq->Pri], pReq, TRUE);
|
|
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("Too many active requests for priority %d\n"), pReq->Pri);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("RequestFile: Put req %p onto Req queue %p\n"), pReq, ClientReqQueues[pReq->Pri]);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Attempts to find a queued request and post an IO completion
|
|
// packet telling the worker threads to begin processing the request.
|
|
//
|
|
VOID FindAndActivateCReq(VOID) {
|
|
DWORD status;
|
|
|
|
tReq *pReq = FindCReq();
|
|
|
|
if (pReq) {
|
|
|
|
#ifdef DEBUG1
|
|
QueueAdd(ClientActiveReqQueue, pReq, TRUE);
|
|
#endif
|
|
|
|
// All that is requeired to activate the request is to queue a completion
|
|
// packet with Activate key and the Req as Overlapped.
|
|
status = PostQueuedCompletionStatus(ClientCompletionPort,
|
|
0,
|
|
IoFileRep,
|
|
(LPOVERLAPPED)pReq);
|
|
ASSERT(status);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Closes the file handle and frees all thread data if an error occured
|
|
// in one of the following functions.
|
|
//
|
|
// The proc may be called while holding pReq->Lock. It will release it.
|
|
//
|
|
VOID ClientShutdownRequest(Req *pReq) {
|
|
DWORD status;
|
|
RPC_STATUS rpcstatus;
|
|
|
|
#ifdef DEBUG2
|
|
TCHAR Msg[MSG_SIZE];
|
|
ULONG bufSize = MSG_SIZE; // Keeps track of remaining size of buffer for _stprintf_s
|
|
int nCharWritten;
|
|
DbgMsgRecord(TEXT("-> ClientShutdownRequest\n"));
|
|
#endif
|
|
|
|
ASSERT(pReq != NULL);
|
|
|
|
|
|
/*
|
|
The failuire handling for async pipe call is as follows:
|
|
|
|
Pull returned failure:
|
|
RPC has done all the clenaup. No need to do anything.
|
|
|
|
Non-RPC failure occurred:
|
|
Call RpcAsyncCalncelCall
|
|
Wait for call-complete notification
|
|
Call RpcAsyncCompleteCall
|
|
|
|
Received call-complete with failure:
|
|
Call RpcAsyncCompleteCall
|
|
|
|
*/
|
|
|
|
// Complete or cancel the async call, depending on whether
|
|
// an error has occurred and whether complete is needed.
|
|
if (pReq->bCallMade && !pReq->bFailureFromPull) {
|
|
|
|
// If we are processing a syncronous failure and the call has not yet
|
|
// been cancelled, then cancel the call and return. We will later get a
|
|
// call-complete event and will complete the call then.
|
|
if (!pReq->bCallComplete && pReq->Status && !pReq->bAsyncCallFailureReceived && !pReq->bCallCancelled) {
|
|
|
|
RpcAsyncCancelCall(&(pReq->Async),
|
|
TRUE);
|
|
// After cancelling the RPC we return and wait for a
|
|
// call-complete notification. We will continue to clean up afterwards.
|
|
pReq->bCallCancelled = TRUE;
|
|
|
|
if (CriticalSectionOwned(&(pReq->Lock))) {
|
|
LeaveCriticalSection(&(pReq->Lock));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Complete a call if either an error has occurred and we already received
|
|
// a completion notification, or if we had already cancelled it, or
|
|
// if the request has been handled.
|
|
else {
|
|
|
|
// If there is an outstanding pull, we should wait for it to complete.
|
|
// This can only happen if WriteFile has failed.
|
|
ASSERT(!pReq->bPullOutstanding || pReq->bFailureFromWrite);
|
|
if (pReq->bPullOutstanding) {
|
|
if (CriticalSectionOwned(&(pReq->Lock))) {
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
RpcTryExcept {
|
|
RpcAsyncCompleteCall(&(pReq->Async),
|
|
&status);
|
|
}
|
|
RpcExcept ( ( (RpcExceptionCode() != STATUS_ACCESS_VIOLATION) &&
|
|
(RpcExceptionCode() != STATUS_DATATYPE_MISALIGNMENT) &&
|
|
(RpcExceptionCode() != STATUS_PRIVILEGED_INSTRUCTION) &&
|
|
(RpcExceptionCode() != STATUS_ILLEGAL_INSTRUCTION) &&
|
|
(RpcExceptionCode() != STATUS_BREAKPOINT) )
|
|
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) {
|
|
ASSERT(pReq->Status != RPC_S_OK);
|
|
}
|
|
RpcEndExcept;
|
|
}
|
|
}
|
|
|
|
|
|
// If there are outstanding writes we should wait for them to complete
|
|
// before destroying the object.
|
|
if (pReq->nWritesOutstanding > 0) {
|
|
if (CriticalSectionOwned(&(pReq->Lock))) {
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If there have been no failures, then all the bytes written
|
|
// should have been written and there must not be any
|
|
// bytes unaccounted for.
|
|
ASSERT(pReq->Status != RPC_S_OK || pReq->nBytesOutstanding == 0);
|
|
|
|
|
|
if (CriticalSectionOwned(&(pReq->Lock))) {
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
}
|
|
|
|
#ifdef DEBUG1
|
|
nClientReqs--;
|
|
#endif
|
|
|
|
|
|
// We are actually going to wipe out a request after this point
|
|
// and need to bring in a new one from the queue.
|
|
FindAndActivateCReq();
|
|
|
|
// Stop impersonating if we are.
|
|
// The first thing we do in handling a request is impersonating the client.
|
|
if (pReq->bImpersonating) {
|
|
if (RevertToSelf() == 0) {
|
|
AddToMessageLogProcFailure(TEXT("ClientShutdownRequest: RevertToSelf"), GetLastError());
|
|
}
|
|
pReq->bImpersonating = FALSE;
|
|
}
|
|
|
|
if(pReq->hFileRepServer != NULL) {
|
|
rpcstatus = RpcBindingFree(&(pReq->hFileRepServer));
|
|
ASSERT(rpcstatus == RPC_S_OK);
|
|
}
|
|
// Check that hLocalFile has been initialized and that initialization
|
|
// was successful.
|
|
if(pReq->hLocalFile != NULL && pReq->hLocalFile != INVALID_HANDLE_VALUE) {
|
|
|
|
// The file has been extended, truncate its size:
|
|
if (pReq->FileWritePos < pReq->CurrentExtendedSize) {
|
|
SetFileSize(pReq->hLocalFile, pReq->FileWritePos);
|
|
}
|
|
|
|
status = CloseHandle(pReq->hLocalFile);
|
|
ASSERT(status != 0);
|
|
}
|
|
|
|
// Check if any of the counters need to be decremented
|
|
// since we are removing the request.
|
|
if (pReq->State == StateQueued) {
|
|
CounterDecrement(pClientReqCounters[pReq->Pri]);
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("Decremented ClientReqCounters[%d]\n"), pReq->Pri);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
}
|
|
if (pReq->State == StateActive) {
|
|
CounterDecrement(pClientActiveReqCounters[pReq->Pri]);
|
|
QueueHashDecrementCounter(ClientActiveReqHashCounters[pReq->Pri], pReq->pSID);
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("Decremented ClientActiveReqCounters[%d] and ClientActiveReqHashCounters[%d]\n"), pReq->Pri, pReq->Pri);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
|
|
#ifdef DEBUG1
|
|
QueueRemoveData(ClientActiveReqQueue, pReq);
|
|
#endif
|
|
}
|
|
|
|
if (pReq->hTokenHandle != NULL) {
|
|
status = CloseHandle(pReq->hTokenHandle);
|
|
ASSERT(status != NULL);
|
|
}
|
|
|
|
if (pReq->ServerName != NULL) {
|
|
AutoHeapFree(pReq->ServerName);
|
|
}
|
|
if (pReq->LocalFileName != NULL) {
|
|
AutoHeapFree(pReq->LocalFileName);
|
|
}
|
|
if (pReq->RemoteFileName != NULL) {
|
|
AutoHeapFree(pReq->RemoteFileName);
|
|
}
|
|
|
|
if (pReq->pSID != NULL) {
|
|
AutoHeapFree(pReq->pSID);
|
|
}
|
|
|
|
// If the critical section has been allocated, free it.
|
|
if (pReq->Lock.DebugInfo) {
|
|
DeleteCriticalSection(&pReq->Lock);
|
|
}
|
|
|
|
#ifdef PROF
|
|
ProfRecordTime(pReq->nReqId, TEXT("handled"));
|
|
#endif
|
|
|
|
AutoHeapFree(pReq);
|
|
|
|
#ifdef DEBUG2
|
|
DbgMsgRecord(TEXT("<- ClientShutdownRequest\n"));
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Handles a request taken off req queue.
|
|
// Returns TRUE on sucess.
|
|
//
|
|
BOOL HandleReq(tReq *pReq) {
|
|
|
|
RPC_STATUS rpcstatus;
|
|
|
|
// Default connection to server system service is over TCP/IP.
|
|
RPC_STR DefaultProtocolSequence = (RPC_STR)TEXT("ncacn_ip_tcp");
|
|
|
|
// An empty endpoint string is used, since we are going to
|
|
// connect to the endpoint dynamically generated by the
|
|
// RPC run-time library. Server calls RpcServerUseProtseq to
|
|
// obtain a binding hadnle and a dynamic endpoint.
|
|
RPC_STR DefaultEndpoint = (RPC_STR)TEXT("");
|
|
|
|
RPC_STR pszUuid = NULL;
|
|
RPC_STR pszOptions = NULL;
|
|
RPC_STR pszStringBinding = NULL;
|
|
|
|
RPC_STR pszServerPrincipalName = NULL;
|
|
|
|
//
|
|
// If a request is picked of the request queue.
|
|
// Then we need to set up binding, bind to the server,
|
|
// and do the initial read.
|
|
//
|
|
|
|
// We need to impersonate the user that has issued the
|
|
// remote call.
|
|
// We want to impersonate as soon as possible to minimise
|
|
// the amount of resources that can be consumed by an attack.
|
|
if (ImpersonateLoggedOnUser(pReq->hTokenHandle) == 0) {
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailure(TEXT("HandleReq: ImpersonateLoggedOnUser"), GetLastError());
|
|
return FALSE;
|
|
}
|
|
pReq->bImpersonating = TRUE;
|
|
|
|
// Prepare the binding information.
|
|
if ((rpcstatus = RpcStringBindingCompose(pszUuid,
|
|
DefaultProtocolSequence,
|
|
pReq->ServerName,
|
|
DefaultEndpoint,
|
|
pszOptions,
|
|
&pszStringBinding)) != RPC_S_OK) {
|
|
pReq->Status = rpcstatus;
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailureEEInfo(TEXT("HandleReq: RpcStringBindingCompose"), rpcstatus);
|
|
return FALSE;
|
|
}
|
|
|
|
if ((rpcstatus = RpcBindingFromStringBinding(pszStringBinding, &(pReq->hFileRepServer))) != RPC_S_OK) {
|
|
// The binding handle is invalid
|
|
pReq->hFileRepServer = NULL;
|
|
|
|
// Deallocate the string binding.
|
|
rpcstatus = RpcStringFree(&pszStringBinding);
|
|
ASSERT(rpcstatus == RPC_S_OK);
|
|
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailureEEInfo(TEXT("HandleReq: RpcBindingFromStringBinding"), rpcstatus);
|
|
return FALSE;
|
|
}
|
|
|
|
// We need to delete the string binding, since it is no longer
|
|
// necessary. All the binding information is now contained in the
|
|
// binding handle.
|
|
rpcstatus = RpcStringFree(&pszStringBinding);
|
|
ASSERT(rpcstatus == RPC_S_OK);
|
|
|
|
#ifndef NO_SEC
|
|
DWORD status;
|
|
|
|
// Quality of service structure to ensure authentication.
|
|
RPC_SECURITY_QOS SecurityQOS;
|
|
|
|
// The length of the principal name and the principal name
|
|
// for the client system service.
|
|
TCHAR szSpn[MAX_PATH];
|
|
ULONG ulSpn = sizeof(szSpn);
|
|
|
|
// Generate a principal name for the local system service.
|
|
if ((status = DsMakeSpn(SERVICENAME,
|
|
(LPCTSTR)pReq->ServerName,
|
|
NULL,
|
|
0,
|
|
0,
|
|
&ulSpn,
|
|
szSpn)) != ERROR_SUCCESS) {
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailure(TEXT("HandleReq: DsMakeSpn"), GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
pszServerPrincipalName = (RPC_STR)szSpn;
|
|
|
|
// Specify quality of service parameters.
|
|
SecurityQOS.Version = RPC_C_SECURITY_QOS_VERSION;
|
|
SecurityQOS.Capabilities = RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;
|
|
// Use static identity tracking since we will be issuing multiple
|
|
// RPC calls from this thread with different identities: while
|
|
// impersonating, and while not. This insures that all the calls will
|
|
// go to server under the identity of the user who created the binding
|
|
// handle.
|
|
SecurityQOS.IdentityTracking = RPC_C_QOS_IDENTITY_STATIC;
|
|
// The client system service needs to impersonate the security context
|
|
// of the client utility on the remote systems where it may replicate
|
|
// files from. It will also need to obtain client's SID and hence
|
|
// requires impersonation.
|
|
SecurityQOS.ImpersonationType = RPC_C_IMP_LEVEL_IMPERSONATE;
|
|
|
|
// Set authentification and authorisation information for
|
|
// the binding handle.
|
|
if ((rpcstatus = RpcBindingSetAuthInfoEx(pReq->hFileRepServer, pszServerPrincipalName, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_KERBEROS, NULL, RPC_C_AUTHZ_NAME, &SecurityQOS)) != RPC_S_OK) {
|
|
|
|
pReq->Status = rpcstatus;
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailureEEInfo(TEXT("HandleReq: RpcBindingSetAuthInfoEx"), rpcstatus);
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
// Initialize the async handle.
|
|
RpcAsyncInitializeHandle(&(pReq->Async),
|
|
sizeof(RPC_ASYNC_STATE));
|
|
pReq->Async.Flags = RPC_C_NOTIFY_ON_SEND_COMPLETE;
|
|
pReq->Async.UserInfo = pReq;
|
|
pReq->Async.NotificationType = RpcNotificationTypeIoc;
|
|
pReq->Async.u.IOC.hIOPort = ClientCompletionPort;
|
|
pReq->Async.u.IOC.dwCompletionKey = IoPipe;
|
|
pReq->Async.u.IOC.lpOverlapped = (LPOVERLAPPED) pReq;
|
|
|
|
// Initialize the request fileds for throtteling the overlapped structure.
|
|
pReq->FileOl.Offset = 0;
|
|
pReq->FileOl.OffsetHigh = 0;
|
|
pReq->FileOl.hEvent = NULL;
|
|
|
|
pReq->bPullOutstanding = FALSE;
|
|
pReq->bBuf = FALSE;
|
|
pReq->bDataAvailable = FALSE;
|
|
pReq->bAllPullsDone = FALSE;
|
|
pReq->bCallComplete = FALSE;
|
|
pReq->bAsyncCallFailureReceived = FALSE;
|
|
pReq->bFailureFromPull = FALSE;
|
|
pReq->bFailureFromWrite = FALSE;
|
|
|
|
pReq->FileWritePos = 0;
|
|
pReq->CurrentExtendedSize = 0;
|
|
|
|
// Attempt to open the local file.
|
|
if ((pReq->hLocalFile = CreateFile(pReq->LocalFileName,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_FLAG_OVERLAPPED,
|
|
NULL)) == INVALID_HANDLE_VALUE) {
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailure(TEXT("HandleReq: CreateFile"), GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
SetFileSize(pReq->hLocalFile, FILE_SIZE_EXTENSION);
|
|
|
|
// Link this file to the client requests's IO completion port:
|
|
if ((ClientCompletionPort = CreateIoCompletionPort (pReq->hLocalFile,
|
|
ClientCompletionPort,
|
|
IoFile,
|
|
0)) == NULL) {
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailure(TEXT("HandleActiveReq: CreateIoCompletionPort"), GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
// We need to enter critical section since right after the call is made
|
|
// another thread can receive an asyncronous failure and clean up.
|
|
EnterCriticalSection(&pReq->Lock);
|
|
|
|
#ifdef RETRY_EXCEPTION
|
|
// When making aggressive calls to the server, this flag is
|
|
// set when the call succeeded. This makes the client service attempt
|
|
// to contact the server even after an exception occured.
|
|
BOOL bRetryCall = TRUE;
|
|
while (bRetryCall) {
|
|
#endif
|
|
|
|
// Attempt to open the remote file.
|
|
RpcTryExcept {
|
|
c_RemoteReadAsyncPipe(&(pReq->Async),
|
|
pReq->hFileRepServer,
|
|
pReq->RemoteFileName,
|
|
&(pReq->OutPipe));
|
|
|
|
#ifdef RETRY_EXCEPTION
|
|
bRetryCall = FALSE;
|
|
#endif
|
|
}
|
|
RpcExcept ( ( (RpcExceptionCode() != STATUS_ACCESS_VIOLATION) &&
|
|
(RpcExceptionCode() != STATUS_DATATYPE_MISALIGNMENT) &&
|
|
(RpcExceptionCode() != STATUS_PRIVILEGED_INSTRUCTION) &&
|
|
(RpcExceptionCode() != STATUS_ILLEGAL_INSTRUCTION) &&
|
|
(RpcExceptionCode() != STATUS_BREAKPOINT) )
|
|
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) {
|
|
|
|
#ifdef RETRY_EXCEPTION
|
|
// This code attempts to make aggressive calls, retrying
|
|
// if no endpoints are available. It is used to put servers
|
|
// under stress.
|
|
if (RpcExceptionCode() != EPT_S_NOT_REGISTERED &&
|
|
RpcExceptionCode() != RPC_S_SERVER_UNAVAILABLE) {
|
|
#endif
|
|
// Deallocate all the thread data before failing.
|
|
pReq->Status = RpcExceptionCode();
|
|
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailureEEInfo(TEXT("HandleReq: c_RemoteOpen"), RpcExceptionCode());
|
|
|
|
return FALSE;
|
|
#ifdef RETRY_EXCEPTION
|
|
}
|
|
#endif
|
|
}
|
|
RpcEndExcept;
|
|
#ifdef RETRY_EXCEPTION
|
|
}
|
|
#endif
|
|
|
|
pReq->bCallMade = true;
|
|
|
|
// We need to stop impersonating before putting this request back into
|
|
// a queue or quitting.
|
|
if (RevertToSelf() == 0) {
|
|
pReq->bImpersonating = FALSE;
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailure(TEXT("HandleReq: RevertToSelf"), GetLastError());
|
|
}
|
|
pReq->bImpersonating = FALSE;
|
|
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Handles a request taken off active req queue
|
|
//
|
|
VOID ServiceRequests(VOID) {
|
|
DWORD Status;
|
|
|
|
// The request that we will be processing
|
|
tReq *pReq;
|
|
|
|
#ifdef DEBUG2
|
|
TCHAR Msg[MSG_SIZE];
|
|
ULONG bufSize = MSG_SIZE; // Keeps track of remaining size of buffer for _stprintf_s
|
|
int nCharWritten;
|
|
#endif
|
|
|
|
// Number of bytes read by a pull
|
|
ULONG cbRead;
|
|
|
|
// Values returned after a wait on the completion port
|
|
DWORD dwNumberOfBytesTransferred;
|
|
DWORD dwCompletionKey;
|
|
OVERLAPPED *lpOverlapped;
|
|
|
|
// A worker thread starts of by waiting for IO
|
|
ActionType Action = Wait;
|
|
|
|
while(TRUE) {
|
|
|
|
//
|
|
// Make a pull to try and receive data.
|
|
//
|
|
if (Action == Pull) {
|
|
|
|
EnterCriticalSection(&pReq->Lock);
|
|
|
|
// This will be set if we have processed an async failure notification.
|
|
// Do not issue any more pulls and just wait for all file
|
|
// IO to complete.
|
|
if (pReq->bAsyncCallFailureReceived) {
|
|
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
|
|
Action = Wait;
|
|
continue;
|
|
}
|
|
|
|
// This will be the case if an async write has failed and we want to
|
|
// terminate the request.
|
|
// Wait for all the writes to be completed.
|
|
if (pReq->bFailureFromWrite == TRUE) {
|
|
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
|
|
Action = Wait;
|
|
continue;
|
|
}
|
|
|
|
// We did not read anything yet.
|
|
cbRead = 0;
|
|
|
|
RpcTryExcept {
|
|
|
|
WINAPI_MY_PIPE_PULL pPull = (WINAPI_MY_PIPE_PULL)pReq->OutPipe.pull;
|
|
|
|
pReq->bPullOutstanding = TRUE;
|
|
|
|
|
|
Status = pPull (pReq->OutPipe.state,
|
|
(char *) pReq->pbBuf,
|
|
PULL_BUFSIZE,
|
|
&cbRead);
|
|
}
|
|
RpcExcept ( ( (RpcExceptionCode() != STATUS_ACCESS_VIOLATION) &&
|
|
(RpcExceptionCode() != STATUS_DATATYPE_MISALIGNMENT) &&
|
|
(RpcExceptionCode() != STATUS_PRIVILEGED_INSTRUCTION) &&
|
|
(RpcExceptionCode() != STATUS_ILLEGAL_INSTRUCTION) &&
|
|
(RpcExceptionCode() != STATUS_BREAKPOINT))
|
|
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) {
|
|
|
|
Status = RpcExceptionCode();
|
|
ASSERT(Status != RPC_S_OK && Status != RPC_S_ASYNC_CALL_PENDING);
|
|
}
|
|
RpcEndExcept;
|
|
|
|
// The pull has failed either through and exception or an
|
|
// error return code.
|
|
if (Status != RPC_S_OK && Status != RPC_S_ASYNC_CALL_PENDING) {
|
|
|
|
AddToMessageLogProcFailureEEInfo(TEXT("HandleActiveReq: c_RemoteRead"), Status);
|
|
|
|
pReq->bFailureFromPull = TRUE;
|
|
pReq->bAllPullsDone = TRUE;
|
|
pReq->bPullOutstanding = FALSE;
|
|
pReq->bCallComplete = TRUE;
|
|
|
|
// Dump this request.
|
|
pReq->Status = Status;
|
|
ClientShutdownRequest(pReq);
|
|
|
|
Action = Wait;
|
|
}
|
|
|
|
// The pull has succeeded.
|
|
// We read some data.
|
|
else if (Status == RPC_S_OK && cbRead > 0) {
|
|
|
|
pReq->FileOl.Offset = pReq->FileWritePos;
|
|
|
|
pReq->bDataAvailable = FALSE;
|
|
pReq->bPullOutstanding = FALSE;
|
|
|
|
pReq->nWritesOutstanding++;
|
|
pReq->nBytesOutstanding+=cbRead;
|
|
|
|
// Access check was done on opening when we were impersonating.
|
|
// Thus we do not need to impersonate in this code.
|
|
Status = WriteFile(pReq->hLocalFile, pReq->pbBuf, cbRead, NULL, &pReq->FileOl);
|
|
|
|
// Write has failed.
|
|
if(!Status && GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
AddToMessageLogProcFailure(TEXT("HandleActiveReq: WriteFile"), GetLastError());
|
|
|
|
pReq->nWritesOutstanding--;
|
|
|
|
pReq->nBytesOutstanding-=cbRead;
|
|
|
|
// If we hit a failure on write we will
|
|
// stop the pulls, wait for all outstanding IO to
|
|
// complete and then finish.
|
|
pReq->bFailureFromWrite = TRUE;
|
|
|
|
// Dump this request.
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
|
|
Action = Wait;
|
|
}
|
|
|
|
// Write has suceeded.
|
|
else {
|
|
|
|
pReq->FileWritePos+=cbRead;
|
|
|
|
if (pReq->FileWritePos > pReq->CurrentExtendedSize) {
|
|
pReq->CurrentExtendedSize+=FILE_SIZE_EXTENSION;
|
|
SetFileSize(pReq->hLocalFile, pReq->CurrentExtendedSize);
|
|
}
|
|
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
|
|
Action = Pull;
|
|
}
|
|
}
|
|
|
|
// The pull has suceeded.
|
|
// We did not get any data.
|
|
// If the pipe is empty and pull returned 0 bytes, the transfer is complete.
|
|
else if (Status == RPC_S_OK && cbRead == 0) {
|
|
|
|
pReq->bDataAvailable = FALSE;
|
|
pReq->bPullOutstanding = FALSE;
|
|
|
|
pReq->bAllPullsDone = TRUE;
|
|
|
|
// If we have done all the pulls and received call-complete, finish.
|
|
if(pReq->bAllPullsDone && pReq->bCallComplete) {
|
|
ClientShutdownRequest(pReq);
|
|
}
|
|
// Otherwise, wait.
|
|
else {
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
}
|
|
|
|
// We will either complete this request ot wait for all the file
|
|
// IO to complete. In either case, the next action will be Wait.
|
|
Action = Wait;
|
|
}
|
|
|
|
// No data is present yet. A notification will be issued later.
|
|
else if (Status == RPC_S_ASYNC_CALL_PENDING && cbRead == 0) {
|
|
|
|
ASSERT(pReq->bDataAvailable == FALSE);
|
|
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
|
|
Action = Wait;
|
|
}
|
|
|
|
// Should not get here
|
|
else {
|
|
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Wait for IO completion
|
|
//
|
|
else if (Action == Wait) {
|
|
|
|
InterlockedIncrement(&nThreadsAtClientCompletionPort);
|
|
|
|
ASSERT(nThreadsAtClientCompletionPort >= 0);
|
|
|
|
Status = GetQueuedCompletionStatus(ClientCompletionPort,
|
|
&dwNumberOfBytesTransferred,
|
|
(PULONG_PTR)&dwCompletionKey,
|
|
&lpOverlapped,
|
|
ClientCompletionPortTimeout);
|
|
|
|
// Failure to dequeue an IO completion packet.
|
|
if (!Status && lpOverlapped == NULL) {
|
|
|
|
ULONG nThreads = InterlockedDecrement(&nThreadsAtClientCompletionPort);
|
|
|
|
// Wait timed out.
|
|
if (GetLastError() == WAIT_TIMEOUT) {
|
|
|
|
|
|
// If wait timed out and there are other threads waiting on the completion port,
|
|
// we can exit.
|
|
if (nThreads != 0) {
|
|
return;
|
|
}
|
|
// If there are no other threads, go back to wait for the IO.
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Failure occurred and we were unable to dequeue an IO completion
|
|
// packet.
|
|
else {
|
|
AddToMessageLogProcFailure(TEXT("HandleActiveReq: GetQueuedCompletionStatus"), GetLastError());
|
|
|
|
// Nothing we can do, just wait a little and re-try.
|
|
Sleep(100);
|
|
Action = Wait;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If this is the last thread to come off a completion port,
|
|
// spin up an extra worker thread. Thi ensures that there is
|
|
// always a waiter on the port.
|
|
if (InterlockedDecrement(&nThreadsAtClientCompletionPort) == 0) {
|
|
|
|
HANDLE hThread;
|
|
ULONG ThreadIdentifier;
|
|
DWORD status;
|
|
|
|
if ((hThread = CreateThread(NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE) ServiceRequests,
|
|
NULL,
|
|
0,
|
|
&ThreadIdentifier)) == NULL) {
|
|
|
|
// If CreateThread has failed there is nothing that we can do.
|
|
// Just continue...
|
|
AddToMessageLogProcFailure(TEXT("ServiceRequests: CreateThread"), GetLastError());
|
|
}
|
|
else {
|
|
// Unless we close a handle to the thread, it will remain in the
|
|
// system even after its execution has terminated.
|
|
status = CloseHandle(hThread);
|
|
ASSERT(status != 0);
|
|
}
|
|
}
|
|
|
|
ASSERT(nThreadsAtClientCompletionPort >= 0);
|
|
|
|
// Dequeued an IO completion packet for a successful operation.
|
|
// or for an unsucessful operation. If Status != 0 then the
|
|
// operation has suceeded.
|
|
|
|
// There is at most one async WriteFile outstanding,
|
|
// so only one thread may be executing here for a given request.
|
|
if (dwCompletionKey == IoFile) {
|
|
pReq = (Req *) ((size_t)lpOverlapped - offsetof(Req, FileOl));
|
|
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("HandleActiveReq: received File IO for req %p\n"), pReq);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
|
|
EnterCriticalSection(&pReq->Lock);
|
|
|
|
|
|
// File IO suceeded.
|
|
if (Status) {
|
|
|
|
pReq->nWritesOutstanding--;
|
|
|
|
pReq->nBytesOutstanding-=dwNumberOfBytesTransferred;
|
|
|
|
ASSERT(pReq->nWritesOutstanding >= 0);
|
|
|
|
Action = Wait;
|
|
|
|
if(pReq->bAllPullsDone &&
|
|
pReq->nWritesOutstanding <= 0 &&
|
|
(pReq->bCallComplete || pReq->bAsyncCallFailureReceived || pReq->bFailureFromWrite)) {
|
|
|
|
ClientShutdownRequest(pReq);
|
|
|
|
Action = Wait;
|
|
}
|
|
else {
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
}
|
|
}
|
|
|
|
// File IO has failed.
|
|
else {
|
|
AddToMessageLogProcFailure(TEXT("HandleActiveReq: WriteFile"), GetLastError());
|
|
|
|
pReq->nWritesOutstanding--;
|
|
|
|
pReq->nBytesOutstanding-=dwNumberOfBytesTransferred;
|
|
|
|
pReq->bFailureFromWrite = TRUE;
|
|
|
|
// We do not want to pull any more data once file IO has failed.
|
|
pReq->bAllPullsDone = TRUE;
|
|
|
|
ASSERT(pReq->nWritesOutstanding >= 0);
|
|
|
|
// If we hit a failure on write we will
|
|
// wait for the async IO to complete.
|
|
|
|
// Dump this request.
|
|
pReq->Status = GetLastError();
|
|
if (pReq->Status == 0) {
|
|
pReq->Status = ERROR_OPERATION_ABORTED;
|
|
}
|
|
|
|
ClientShutdownRequest(pReq);
|
|
|
|
Action = Wait;
|
|
}
|
|
}
|
|
|
|
// There is at most one async pull outstanding,
|
|
// so at most one thread may be executing here for a given request.
|
|
// The only possibility that we need to syncronize against is a pull and a write
|
|
// completing simultaneously and racing.
|
|
//
|
|
// The IO completion packets posted by the RPC runtime correspond to
|
|
// sucessful IO only. Failures are reported as call-complete events.
|
|
else if(dwCompletionKey == IoPipe) {
|
|
pReq = (Req *) lpOverlapped;
|
|
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("HandleActiveReq: received Pipe IO for req %p\n"), pReq);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
|
|
EnterCriticalSection(&pReq->Lock);
|
|
|
|
|
|
//
|
|
// Received a call-complete notification from the RPC runtime.
|
|
//
|
|
if(pReq->Async.Event == RpcCallComplete) {
|
|
|
|
// It is possible that the server terminated the call prematurely.
|
|
// If this is the case find an error.
|
|
pReq->Status = RpcAsyncGetCallStatus(&pReq->Async);
|
|
|
|
ASSERT(pReq->bCallComplete == FALSE ||
|
|
pReq->Status == RPC_S_SERVER_UNAVAILABLE ||
|
|
pReq->Status == RPC_S_CALL_FAILED_DNE ||
|
|
pReq->Status == RPC_S_CALL_FAILED ||
|
|
pReq->Status == RPC_S_CALL_CANCELLED);
|
|
|
|
// The call did in fact fail.
|
|
if (pReq->Status != RPC_S_OK) {
|
|
|
|
// If the call has failed, forget about doing
|
|
// more pulls.
|
|
pReq->bAllPullsDone = TRUE;
|
|
pReq->bPullOutstanding = FALSE;
|
|
pReq->bAsyncCallFailureReceived = TRUE;
|
|
|
|
// Try to dump the request.
|
|
ClientShutdownRequest(pReq);
|
|
|
|
Action = Wait;
|
|
}
|
|
|
|
// The call did not fail. We received notification of it's
|
|
// sucessful completion.
|
|
else {
|
|
|
|
ASSERT(pReq->bAsyncCallFailureReceived == FALSE);
|
|
|
|
// Call complete should be the last notification
|
|
// received. We will note this, and wait for the file
|
|
// IO to completion if necessary.
|
|
pReq->bCallComplete = TRUE;
|
|
|
|
Action = Wait;
|
|
|
|
if(pReq->bAllPullsDone) {
|
|
ClientShutdownRequest(pReq);
|
|
}
|
|
else {
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Got a receive-complete notification from the RPC runtime.
|
|
//
|
|
else if(pReq->Async.Event == RpcReceiveComplete){
|
|
|
|
Action = Pull;
|
|
|
|
ASSERT(pReq->bCallComplete == FALSE);
|
|
ASSERT(pReq->bPullOutstanding == TRUE);
|
|
|
|
// If a pull has completed then there are no more
|
|
// outstanding pulls.
|
|
pReq->bPullOutstanding = FALSE;
|
|
|
|
// If an async WriteFile has failed, we would have cancelled the
|
|
// call. After getting a call-complete notification we
|
|
// will clean it up.
|
|
if (pReq->bCallCancelled) {
|
|
Action = Wait;
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
continue;
|
|
}
|
|
|
|
// If the call is still a sucess, then there must be
|
|
// data for us.
|
|
pReq->bDataAvailable = TRUE;
|
|
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
}
|
|
|
|
// We should never be here.
|
|
else {
|
|
LeaveCriticalSection(&pReq->Lock);
|
|
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
|
|
//
|
|
// We got a FileRep internal packet to activate a new request.
|
|
//
|
|
else if(dwCompletionKey == IoFileRep) {
|
|
pReq = (Req *) lpOverlapped;
|
|
|
|
|
|
if(HandleReq(pReq)) {
|
|
Action = Pull;
|
|
}
|
|
else {
|
|
Action = Wait;
|
|
}
|
|
}
|
|
|
|
else { // dwCompletionKey
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
|
|
else { // Action
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
FUNCTIONS: IsSecureClient
|
|
|
|
PURPOSE:
|
|
Returns TRUE if the service is being contacted by a proper
|
|
authenticated client.
|
|
|
|
PARAMETERS:
|
|
Self-explanatory.
|
|
|
|
RETURN VALUE: TRUE if YES, FALSE otherwise or on error.
|
|
|
|
*/
|
|
BOOL IsSecureClient(handle_t hFileRepClient, Req *pReq) {
|
|
|
|
#ifndef NO_SEC
|
|
RPC_AUTHZ_HANDLE hAuth;
|
|
ULONG ulAuthnLevel;
|
|
|
|
// Get client security info.
|
|
if (RpcBindingInqAuthClient(pReq->hFileRepClient,
|
|
&hAuth,
|
|
NULL,
|
|
&ulAuthnLevel,
|
|
NULL,
|
|
NULL) != RPC_S_OK) {
|
|
AddRpcEEInfo(GetLastError(), TEXT("RequestFile: RpcBindingInqAuthClient failed"));
|
|
return FALSE;
|
|
}
|
|
|
|
// Make sure the client has adequate security measures.
|
|
// Since this is LRPC, we get the security level of privacy
|
|
// because it is the only available security level.
|
|
// We do not interoperate with old versions of FileRep
|
|
// client utilities, since appropriate versions should be
|
|
// available with the local system service.
|
|
if (ulAuthnLevel != RPC_C_AUTHN_LEVEL_PKT_PRIVACY) {
|
|
AddRpcEEInfo(RPC_S_SEC_PKG_ERROR, TEXT("RequestFile: ulAuthnLevel != RPC_C_AUTHN_LEVEL_PKT_PRIVACY"));
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
FUNCTIONS: RequestFile
|
|
|
|
PURPOSE:
|
|
Called by FileRep to have the Client System
|
|
Service request file replication from a Server System Service.
|
|
The function processes rpc request and places it in a request
|
|
queue. It then checks the request and active request queues for
|
|
requests to process, or exits.
|
|
|
|
PARAMETERS:
|
|
Self-explanatory.
|
|
|
|
RETURN VALUE: none
|
|
|
|
*/
|
|
VOID RequestFile(handle_t hFileRepClient,
|
|
LPTSTR ServerName,
|
|
LPTSTR RemoteFileName,
|
|
LPTSTR LocalFileName) {
|
|
|
|
RPC_STATUS rpcstatus;
|
|
|
|
Req *pReq = NULL;
|
|
|
|
// The string binding that is used to make sure clients contact the
|
|
// service via an LRPC call.
|
|
RPC_STR StringBinding;
|
|
|
|
// The protocol sequence that gets extracted from the string binding.
|
|
RPC_STR ProtSeq;
|
|
|
|
TCHAR Msg[MSG_SIZE];
|
|
ULONG bufSize = MSG_SIZE; // Keeps track of remaining size of buffer for _stprintf_s
|
|
int nCharWritten;
|
|
|
|
#ifdef DEBUG2
|
|
DbgMsgRecord(TEXT("-> RequestFile\n"));
|
|
#endif
|
|
|
|
if((pReq = (Req *) AutoHeapAlloc(sizeof(Req))) == NULL) {
|
|
AddRpcEEInfoAndRaiseException(ERROR_OUTOFMEMORY, TEXT("RequestFile: AutoHeapAlloc failed"));
|
|
return;
|
|
}
|
|
|
|
// Set pReq' fields to NULL so that we will know
|
|
// in ClientShutdownRequest which ones have been initialized.
|
|
pReq->hFileRepServer = NULL;
|
|
|
|
pReq->hFileRepClient = hFileRepClient;
|
|
|
|
pReq->hTokenHandle = NULL;
|
|
|
|
pReq->hLocalFile = NULL;
|
|
pReq->bImpersonating = FALSE;
|
|
pReq->Pri = 0;
|
|
pReq->State = StateArrived;
|
|
|
|
pReq->ServerName = NULL;
|
|
pReq->LocalFileName = NULL;
|
|
pReq->RemoteFileName = NULL;
|
|
|
|
pReq->pSID = NULL;
|
|
|
|
pReq->Status = 0;
|
|
|
|
pReq->bCallMade = FALSE;
|
|
pReq->bCallCancelled = FALSE;
|
|
|
|
pReq->Lock.DebugInfo = NULL;
|
|
|
|
|
|
pReq->nWritesOutstanding = 0;
|
|
pReq->nBytesOutstanding = 0;
|
|
|
|
#ifdef DEBUG1
|
|
nClientReqs++;
|
|
|
|
#endif
|
|
|
|
// Copy strings out since they may need to be allocated longer then
|
|
// the arguments that live on the stack.
|
|
// It is conceivable that this thread will put the
|
|
// request on the queue and some other thread will
|
|
// handle the request after this thread has exited.
|
|
if ((pReq->ServerName = (RPC_STR) AutoHeapAlloc((_tcslen(ServerName)+1) * sizeof(TCHAR))) == NULL) {
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(ERROR_OUTOFMEMORY, TEXT("RequestFile: AutoHeapAlloc failed"));
|
|
return;
|
|
}
|
|
CopyMemory(pReq->ServerName, ServerName, (_tcslen(ServerName)+1) * sizeof(TCHAR));
|
|
if ((pReq->RemoteFileName = (LPTSTR) AutoHeapAlloc((_tcslen(RemoteFileName)+1) * sizeof(TCHAR))) == NULL) {
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(ERROR_OUTOFMEMORY, TEXT("RequestFile: AutoHeapAlloc failed"));
|
|
return;
|
|
}
|
|
CopyMemory(pReq->RemoteFileName, RemoteFileName, (_tcslen(RemoteFileName)+1) * sizeof(TCHAR));
|
|
if ((pReq->LocalFileName = (LPTSTR) AutoHeapAlloc((_tcslen(LocalFileName)+1) * sizeof(TCHAR))) == NULL) {
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(ERROR_OUTOFMEMORY, TEXT("RequestFile: AutoHeapAlloc failed"));
|
|
return;
|
|
}
|
|
CopyMemory(pReq->LocalFileName, LocalFileName, (_tcslen(LocalFileName)+1) * sizeof(TCHAR));
|
|
|
|
#ifdef PROF
|
|
static ULONG nTotalId;
|
|
|
|
EnterCriticalSection(&ProfCriticalSection);
|
|
|
|
// Record the number of the current request.
|
|
pReq->nReqId = nTotalId;
|
|
// Increment the number of the request.
|
|
nTotalId++;
|
|
|
|
LeaveCriticalSection(&ProfCriticalSection);
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("name=\"%s\" arrived"), RemoteFileName);
|
|
bufSize -= nCharWritten;
|
|
|
|
// Record the time of arrival of this request.
|
|
ProfRecordTime(pReq->nReqId, Msg);
|
|
#endif
|
|
|
|
// Make sure that the client utility is local.
|
|
// Client system service only services LRPC calls.
|
|
if ((rpcstatus = RpcBindingToStringBinding(pReq->hFileRepClient, &StringBinding)) != RPC_S_OK) {
|
|
pReq->Status = rpcstatus;
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(rpcstatus, TEXT("RequestFile: RpcBindingToStringBinding failed"));
|
|
return;
|
|
}
|
|
if ((rpcstatus = RpcStringBindingParse(StringBinding, NULL, &ProtSeq, NULL, NULL, NULL)) != RPC_S_OK) {
|
|
pReq->Status = rpcstatus;
|
|
|
|
// Free the string binding.
|
|
rpcstatus = RpcStringFree(&StringBinding);
|
|
ASSERT(rpcstatus == RPC_S_OK);
|
|
|
|
rpcstatus = pReq->Status;
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(rpcstatus, TEXT("RequestFile: RpcStringBindingParse failed"));
|
|
return;
|
|
}
|
|
if (_tcscmp((LPCTSTR)ProtSeq, TEXT("ncalrpc")) != 0) {
|
|
|
|
// Free the string binding.
|
|
rpcstatus = RpcStringFree(&StringBinding);
|
|
ASSERT(rpcstatus == RPC_S_OK);
|
|
// And the protocol sequence
|
|
rpcstatus = RpcStringFree(&ProtSeq);
|
|
ASSERT(rpcstatus == RPC_S_OK);
|
|
|
|
pReq->Status = ERROR_ACCESS_DENIED;
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(RPC_S_INVALID_RPC_PROTSEQ, TEXT("RequestFile: _tcscmp(ProtSeq, TEXT(\"ncalrpc\")) != 0"));
|
|
return;
|
|
}
|
|
|
|
// Free the string binding.
|
|
rpcstatus = RpcStringFree(&StringBinding);
|
|
ASSERT(rpcstatus == RPC_S_OK);
|
|
// And the protocol sequence.
|
|
rpcstatus = RpcStringFree(&ProtSeq);
|
|
ASSERT(rpcstatus == RPC_S_OK);
|
|
|
|
#ifndef NO_SEC
|
|
if (!IsSecureClient(hFileRepClient, pReq)) {
|
|
pReq->Status = ERROR_ACCESS_DENIED;
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(RPC_S_SEC_PKG_ERROR, TEXT("RequestFile: IsSecureClient failed"));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Check that we are not exceeding the bound on the number
|
|
// of concurrent requests for this user's priority group.
|
|
// And update the number of such requests.
|
|
//
|
|
|
|
// Impersonate the caller so that we can get the caller's SID
|
|
// Sometimes RpcImpersonateClient may fail under heavy load.
|
|
#ifdef RETRY_EXCEPTION
|
|
rpcstatus = RPC_S_OK;
|
|
unsigned tries = 0;
|
|
do {
|
|
if (rpcstatus != RPC_S_OK) {
|
|
tries++;
|
|
Sleep(100);
|
|
}
|
|
rpcstatus = RpcImpersonateClient(pReq->hFileRepClient);
|
|
}
|
|
while (rpcstatus != RPC_S_OK && tries < 10);
|
|
#else
|
|
rpcstatus = RpcImpersonateClient(pReq->hFileRepClient);
|
|
#endif
|
|
|
|
if (rpcstatus != RPC_S_OK) {
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(rpcstatus, TEXT("RequestFile: RpcImpersonateClient failed"));
|
|
return;
|
|
}
|
|
pReq->bImpersonating = TRUE;
|
|
|
|
// We need to write down the access token for the user so that
|
|
// we can use it for impersonation later when the client binding handle
|
|
// gets deallocated.
|
|
if (OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_QUERY | TOKEN_IMPERSONATE,
|
|
TRUE,
|
|
&pReq->hTokenHandle) == 0) {
|
|
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(GetLastError(), TEXT("RequestFile: OpenThreadToken failed"));
|
|
return;
|
|
}
|
|
|
|
// Determine current user's priority level. This corresponds to the
|
|
// priority level of the callee, since we are impersonating.
|
|
pReq->Pri = GetCurrentUserPriority();
|
|
|
|
// Write down the SID for the user.
|
|
pReq->pSID = GetUserSID();
|
|
if (pReq->pSID == NULL) {
|
|
pReq->Status = RPC_S_CALL_FAILED;
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(RPC_S_CALL_FAILED, TEXT("RequestFile: GetUserSID failed"));
|
|
return;
|
|
}
|
|
|
|
// Stop impersonating. We got what we wanted.
|
|
if ((rpcstatus = RpcRevertToSelf()) != RPC_S_OK) {
|
|
pReq->Status = GetLastError();
|
|
pReq->bImpersonating = FALSE;
|
|
ClientShutdownRequest(pReq);
|
|
AddRpcEEInfoAndRaiseException(rpcstatus, TEXT("RequestFile: RpcRevertToSelf failed"));
|
|
return;
|
|
}
|
|
pReq->bImpersonating = FALSE;
|
|
|
|
// Init the critsec.
|
|
if (InitializeCriticalSectionAndSpinCount(&pReq->Lock, 10) == 0) {
|
|
AddRpcEEInfoAndRaiseException(GetLastError(), TEXT("ProfOpenLog: InitializeCriticalSectionAndSpinCount"));
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("RequestFile: Pri=%d for req %p\n"), pReq->Pri, pReq);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
|
|
HANDLE hThread;
|
|
ULONG ThreadIdentifier;
|
|
DWORD status;
|
|
|
|
// If the number of worker threads is lower then the maximum, create one.
|
|
if (nThreadsAtClientCompletionPort < 1) {
|
|
|
|
// Go service some requests, remember that you are
|
|
// an RPC thread.
|
|
if ((hThread = CreateThread(NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE) ServiceRequests,
|
|
NULL,
|
|
0,
|
|
&ThreadIdentifier)) == NULL) {
|
|
|
|
pReq->Status = GetLastError();
|
|
ClientShutdownRequest(pReq);
|
|
AddToMessageLogProcFailure(TEXT("ServiceRequests: CreateThread"), GetLastError());
|
|
return;
|
|
}
|
|
|
|
// Unless we close a handle to the thread, it will remain in the
|
|
// system even after its execution has terminated.
|
|
status = CloseHandle(hThread);
|
|
ASSERT(status != 0);
|
|
}
|
|
|
|
// We need to wait putting the request onto the queue untill we know that we could create
|
|
// a worker thread.
|
|
|
|
// Increment the counter for the number of concurrent requests.
|
|
if (!CounterIncrement(pClientReqCounters[pReq->Pri])) {
|
|
|
|
pReq->Status = RPC_S_SERVER_TOO_BUSY;
|
|
ClientShutdownRequest(pReq);
|
|
// We have to raise exception after deallocating the data
|
|
// since there is a possibility that a client thread
|
|
// will retry and will attempt to open this local file
|
|
// while it's previous request is still holding a lock.
|
|
// This has been actually hit on an overloaded system.
|
|
AddRpcEEInfoAndRaiseException(RPC_S_SERVER_TOO_BUSY, TEXT("RequestFile: CounterIncrement failed"));
|
|
#ifdef DEBUG2
|
|
DbgMsgRecord(TEXT("<- RequestFile\n"));
|
|
#endif
|
|
return;
|
|
}
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("Incremented ClientReqCounters[%d]\n"), pReq->Pri);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
|
|
pReq->State = StateQueued;
|
|
|
|
// Place the request onto the queue.
|
|
// The request will be picked off the queue by a worker thread from the
|
|
// completion port and activate later.
|
|
QueueAdd(ClientReqQueues[pReq->Pri], pReq, TRUE);
|
|
#ifdef DEBUG2
|
|
nCharWritten = _stprintf_s(Msg, bufSize, TEXT("RequestFile: Put req %p onto Req queue %p\n"), pReq, ClientReqQueues[pReq->Pri]);
|
|
bufSize -= nCharWritten;
|
|
DbgMsgRecord(Msg);
|
|
#endif
|
|
|
|
// Now if we can, find a queued request and activate it.
|
|
FindAndActivateCReq();
|
|
|
|
#ifdef DEBUG2
|
|
DbgMsgRecord(TEXT("<- RequestFile\n"));
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// end FileRepClientProc.cpp
|