mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
767 lines
19 KiB
767 lines
19 KiB
/*++
|
|
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
conn.cxx
|
|
|
|
Abstract:
|
|
|
|
This module defines the functions for base class of connections
|
|
for Internet Services ( class CLIENT_CONNECTION)
|
|
|
|
Author:
|
|
|
|
Rohan Phillips ( Rohanp ) 11-Dec-1995
|
|
|
|
Project:
|
|
|
|
Gibraltar Services Common Code
|
|
|
|
Functions Exported:
|
|
|
|
CLIENT_CONNECTION::Initialize()
|
|
CLIENT_CONNECTION::Cleanup()
|
|
CLIENT_CONNECTION::~CLIENT_CONNECTION()
|
|
BOOL CLIENT_CONNECTION::ProcessClient( IN DWORD cbWritten,
|
|
IN DWORD dwCompletionStatus,
|
|
IN BOOL fIOCompletion)
|
|
VOID CLIENT_CONNECTION::DisconnectClient( IN DWORD ErrorReponse)
|
|
|
|
BOOL CLIENT_CONNECTION::StartupSession( VOID)
|
|
BOOL CLIENT_CONNECTION::ReceiveRequest(
|
|
OUT LPBOOL pfFullRequestRecd)
|
|
|
|
BOOL CLIENT_CONNECTION::ReadFile( OUT LPVOID pvBuffer,
|
|
IN DWORD dwSize)
|
|
BOOL CLIENT_CONNECTION::WriteFile( IN LPVOID pvBuffer,
|
|
IN DWORD dwSize)
|
|
BOOL CLIENT_CONNECTION::TransmitFile( IN HANDLE hFile,
|
|
IN DWORD cbToSend)
|
|
BOOL CLIENT_CONNECTION::PostCompletionStatus( IN DWORD dwBytes )
|
|
|
|
Revision History:
|
|
Revision History:
|
|
Richard Kamicar ( rkamicar ) 31-Dec-1995
|
|
Moved to common directory, filled in more
|
|
|
|
--*/
|
|
|
|
|
|
/************************************************************
|
|
* Include Headers
|
|
************************************************************/
|
|
|
|
#define INCL_INETSRV_INCS
|
|
#include "smtpinc.h"
|
|
#include "conn.hxx"
|
|
|
|
|
|
|
|
/************************************************************
|
|
* Functions
|
|
************************************************************/
|
|
|
|
|
|
|
|
/*++
|
|
|
|
ICLIENT_CONNECTION::Initialize()
|
|
|
|
Constructor for ICLIENT_CONNECTION object.
|
|
Initializes the fields of the client connection.
|
|
|
|
Arguments:
|
|
|
|
sClient socket for communicating with client
|
|
|
|
psockAddrRemote pointer to address of the remote client
|
|
( the value should be copied).
|
|
psockAddrLocal pointer to address for the local card through
|
|
which the client came in.
|
|
pAtqContext pointer to ATQ Context used for AcceptEx'ed conn.
|
|
pvInitialRequest pointer to void buffer containing the initial request
|
|
cbInitialData count of bytes of data read initially.
|
|
|
|
Note:
|
|
TO keep the number of connected users <= Max connections specified.
|
|
Make sure to add this object to global list of connections,
|
|
after creating it.
|
|
If there is a failure to add to global list, delete this object.
|
|
|
|
--*/
|
|
|
|
CLIENT_CONNECTION::Initialize(
|
|
IN SOCKET sClient,
|
|
IN const SOCKADDR_IN * psockAddrRemote,
|
|
IN const SOCKADDR_IN * psockAddrLocal /* Default = NULL */,
|
|
IN PATQ_CONTEXT pAtqContext /* Default = NULL */,
|
|
IN PVOID pvInitialRequest/* Default = NULL */,
|
|
IN DWORD cbInitialData /* Default = 0 */
|
|
)
|
|
{
|
|
DWORD dwError = NO_ERROR;
|
|
|
|
|
|
m_sClient = ( sClient);
|
|
m_pAtqContext = pAtqContext;
|
|
m_cbReceived = 0;
|
|
m_pvInitial = pvInitialRequest;
|
|
m_cbInitial = cbInitialData;
|
|
m_Destroy = FALSE;
|
|
|
|
_ASSERT( psockAddrRemote != NULL);
|
|
|
|
m_saClient = *psockAddrRemote;
|
|
|
|
// Obtain the socket addresses for the socket
|
|
m_pchRemoteHostName[0] =
|
|
m_pchLocalHostName[0] =
|
|
m_pchLocalPortName[0] = '\0';
|
|
|
|
// InetNtoa() wants just 16 byte buffer.
|
|
_ASSERT( 16 <= MAX_HOST_NAME_LEN);
|
|
dwError = InetNtoa( psockAddrRemote->sin_addr, m_pchRemoteHostName);
|
|
|
|
_ASSERT( dwError == NO_ERROR); // since we had given sufficient buffer
|
|
|
|
if ( psockAddrLocal != NULL)
|
|
{
|
|
dwError = InetNtoa( psockAddrLocal->sin_addr, m_pchLocalHostName);
|
|
_itoa( ntohs(psockAddrLocal->sin_port), m_pchLocalPortName, 10);
|
|
} else
|
|
{
|
|
SOCKADDR_IN sockAddr;
|
|
int cbAddr = sizeof( sockAddr);
|
|
|
|
if ( getsockname( sClient,
|
|
(struct sockaddr *) &sockAddr,
|
|
&cbAddr ))
|
|
{
|
|
|
|
dwError = InetNtoa( sockAddr.sin_addr, m_pchLocalHostName );
|
|
_itoa( ntohs(sockAddr.sin_port), m_pchLocalPortName, 10);
|
|
|
|
}
|
|
}
|
|
|
|
// DBG_ASSERT( dwError == NO_ERROR); // since we had given sufficient buffer
|
|
|
|
#if 0
|
|
DBG_CODE(
|
|
if ( dwError != NO_ERROR) {
|
|
|
|
DBGPRINTF( ( DBG_CONTEXT,
|
|
"Obtaining Local Host Name Failed. Error = %u\n",
|
|
dwError)
|
|
);
|
|
SetLastError( dwError);
|
|
}
|
|
);
|
|
|
|
DEBUG_IF( CLIENT, {
|
|
|
|
DBGPRINTF( ( DBG_CONTEXT,
|
|
" Constructed ICLIENT_CONNECTION object ( %08x)."
|
|
" Socket (%d), Host (%s).\n",
|
|
this,
|
|
sClient,
|
|
QueryClientHostName()
|
|
));
|
|
});
|
|
|
|
#endif
|
|
|
|
return ( TRUE);
|
|
|
|
}
|
|
|
|
|
|
/*++
|
|
ICLIENT_CONNECTION::Cleanup()
|
|
|
|
Destructor function for client connection object.
|
|
Checks and frees the AtqContext.
|
|
|
|
Note:
|
|
If enlisted in the global list of connections,
|
|
ensure that this object is freed from that list before deletion.
|
|
|
|
--*/
|
|
VOID CLIENT_CONNECTION::Cleanup( VOID)
|
|
{
|
|
PATQ_CONTEXT pAtqContext;
|
|
|
|
if(m_DoCleanup)
|
|
{
|
|
//release the context from Atq
|
|
pAtqContext = (PATQ_CONTEXT)InterlockedExchangePointer( (PVOID *)&m_pAtqContext, NULL);
|
|
if ( pAtqContext != NULL )
|
|
{
|
|
AtqFreeContext( pAtqContext, TRUE );
|
|
}
|
|
}
|
|
|
|
} // ICLIENT_CONNECTION::Cleanup()
|
|
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
Checks to see if we have received the complete request from the client.
|
|
( A complete request is a line of text terminated by <cr><lf> )
|
|
if a CRLF is found, it returns a pointer into the buffer were the CR
|
|
starts, else it returns NULL. If this routine finds a CR without a
|
|
LF it will return NULL
|
|
|
|
Arguments:
|
|
|
|
InputBuffer pointer to character buffer containing received data.
|
|
|
|
cbRecvd count of bytes of data received
|
|
|
|
Returns:
|
|
|
|
a pointer to the CR if CRLF is found
|
|
NULL if CRLF is not found.
|
|
|
|
--*/
|
|
// VIRTUAL
|
|
char * CLIENT_CONNECTION::IsLineComplete(IN OUT const char * InputBuffer, IN DWORD cbRecvd)
|
|
{
|
|
register DWORD Loop = 0;
|
|
|
|
_ASSERT(InputBuffer != NULL);
|
|
|
|
//we need at least 2 bytes to find a
|
|
//CRLF pair
|
|
if( cbRecvd < 2)
|
|
return NULL;
|
|
|
|
//we are going to start at the 2nd byte
|
|
//looking for the LF, then look backwards
|
|
//for the CR
|
|
Loop = 1;
|
|
|
|
while (Loop < cbRecvd)
|
|
{
|
|
if(InputBuffer[Loop] == LF)
|
|
{
|
|
if(InputBuffer[Loop - 1] == CR)
|
|
return (char *) &InputBuffer[Loop - 1];
|
|
else
|
|
{
|
|
//skip 2 bytes since we saw a LF
|
|
//without a CR.
|
|
Loop += 2;
|
|
}
|
|
}
|
|
else if(InputBuffer[Loop] == CR)
|
|
{
|
|
//we saw a CR, so increment out
|
|
//loop variable by one so that
|
|
//we can catch the LF on the next
|
|
//go around
|
|
Loop += 1;
|
|
}
|
|
else
|
|
{
|
|
//This character is neither a CR
|
|
//or a LF, so we can increment by
|
|
//2
|
|
Loop += 2;
|
|
}
|
|
}
|
|
|
|
//didn't find a CRLF pair
|
|
return NULL;
|
|
}
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
VIRTUAL Method that MUST be defined by the derived class
|
|
|
|
Main function for this class. Processes the connection based
|
|
on current state of the connection.
|
|
It may invoke or be invoked by ATQ functions.
|
|
|
|
Arguments:
|
|
|
|
cbWritten count of bytes written
|
|
|
|
dwCompletionStatus Error Code for last IO operation
|
|
|
|
fIOCompletion TRUE if this was an IO completion
|
|
|
|
|
|
Returns:
|
|
|
|
TRUE when processing is incomplete.
|
|
FALSE when the connection is completely processed and this
|
|
object may be deleted.
|
|
|
|
--*/
|
|
// VIRTUAL
|
|
BOOL CLIENT_CONNECTION::ProcessClient( IN DWORD cbWritten, IN DWORD dwCompletionStatus, IN OUT OVERLAPPED * lpo)
|
|
{
|
|
return ( TRUE);
|
|
} // CLIENT_CONNECTION::ProcessClient()
|
|
|
|
|
|
/*++
|
|
|
|
Reads contents using ATQ into the given buffer.
|
|
( thin wrapper for ATQ call and managing references)
|
|
|
|
Arguments:
|
|
|
|
pvBuffer pointer to buffer where to read in the contents
|
|
|
|
cbSize size of the buffer
|
|
|
|
Returns:
|
|
|
|
TRUE on success and FALSE on a failure.
|
|
|
|
--*/
|
|
// VIRTUAL
|
|
BOOL CLIENT_CONNECTION::ReadFile(
|
|
IN LPVOID pBuffer,
|
|
IN DWORD cbSize /* = MAX_READ_BUFF_SIZE */
|
|
)
|
|
{
|
|
_ASSERT(pBuffer != NULL);
|
|
_ASSERT(cbSize > 0);
|
|
|
|
ZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
|
|
|
|
return AtqReadFile(m_pAtqContext, // Atq context
|
|
pBuffer, // Buffer
|
|
cbSize, // BytesToRead
|
|
&m_Overlapped) ;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Writes contents from given buffer using ATQ.
|
|
( thin wrapper for ATQ call and managing references)
|
|
|
|
Arguments:
|
|
|
|
pvBuffer pointer to buffer containing contents for write
|
|
|
|
cbSize size of the buffer
|
|
|
|
Returns:
|
|
|
|
TRUE on success and FALSE on a failure.
|
|
|
|
--*/
|
|
// VIRTUAL
|
|
BOOL CLIENT_CONNECTION::WriteFile( IN LPVOID pBuffer, IN DWORD cbSize )
|
|
{
|
|
BOOL fReturn = TRUE;
|
|
int BytesAlreadySent = 0;
|
|
DWORD BytesSent;
|
|
DWORD dwError = NO_ERROR;
|
|
DWORD cTimesBlocked = 0;
|
|
DWORD dwSleepTime = 1000;
|
|
|
|
_ASSERT(pBuffer != NULL);
|
|
|
|
for (BytesSent = 0; BytesSent < cbSize; BytesSent += BytesAlreadySent)
|
|
{
|
|
BytesAlreadySent = send(m_sClient,
|
|
(const char FAR *) pBuffer + BytesSent,
|
|
(int) (cbSize - BytesSent),
|
|
0);
|
|
if (BytesAlreadySent == SOCKET_ERROR)
|
|
{
|
|
//The above send will fail with WSAEWOULDBLOCK when
|
|
//the TCP buffer is full... this can easily happen for blob
|
|
//protocol sinks. The correct thing to do is rely on memory
|
|
//instead of TCP buffers to store pending sends, but the
|
|
//low impact work-around is to sleep after we would block
|
|
dwError = GetLastError();
|
|
if ((WSAEWOULDBLOCK == dwError) && (cTimesBlocked < 500))
|
|
{
|
|
SetLastError(NO_ERROR);
|
|
cTimesBlocked++;
|
|
BytesAlreadySent = 0;
|
|
Sleep(dwSleepTime);
|
|
dwSleepTime += dwSleepTime;
|
|
continue;
|
|
}
|
|
fReturn = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
return fReturn;
|
|
}
|
|
|
|
BOOL CLIENT_CONNECTION::WriteSocket( IN SOCKET Sock, IN LPVOID pBuffer, IN DWORD cbSize )
|
|
{
|
|
BOOL fReturn = TRUE;
|
|
int BytesAlreadySent = 0;
|
|
DWORD BytesSent;
|
|
|
|
_ASSERT(pBuffer != NULL);
|
|
|
|
for (BytesSent = 0; BytesSent < cbSize; BytesSent += BytesAlreadySent)
|
|
{
|
|
BytesAlreadySent = send(Sock,
|
|
(const char FAR *) pBuffer + BytesSent,
|
|
(int) (cbSize - BytesSent),
|
|
0);
|
|
if (BytesAlreadySent == SOCKET_ERROR)
|
|
{
|
|
fReturn = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
return fReturn;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Writes contents from given buffer using ATQ.
|
|
(thin wrapper for ATQ call and managing references)
|
|
|
|
Arguments:
|
|
|
|
Pov pointer to OVERLAPPED structure describing the write
|
|
|
|
Returns:
|
|
|
|
TRUE on success and FALSE on a failure.
|
|
|
|
--*/
|
|
// VIRTUAL
|
|
BOOL CLIENT_CONNECTION::WriteFile(
|
|
IN LPVOID lpvBuffer,
|
|
IN DWORD cbSize,
|
|
IN OVERLAPPED *lpo)
|
|
{
|
|
BOOL fReturn = TRUE;
|
|
|
|
_ASSERT(lpo != NULL);
|
|
_ASSERT(lpvBuffer != NULL);
|
|
_ASSERT(cbSize != 0);
|
|
|
|
fReturn = AtqWriteFile(m_pAtqContext, lpvBuffer, cbSize, lpo);
|
|
return fReturn;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Transmits contents of the file ( of specified size)
|
|
using the ATQ and client socket.
|
|
( thin wrapper for ATQ call and managing references)
|
|
|
|
Arguments:
|
|
|
|
hFile handle for file to be transmitted
|
|
|
|
liSize large integer containing the size of file
|
|
|
|
lpTransmitBuffers
|
|
buffers containing the head and tail buffers that
|
|
need to be transmitted along with the file.
|
|
|
|
Returns:
|
|
|
|
TRUE on success and FALSE on a failure.
|
|
|
|
--*/
|
|
|
|
BOOL CLIENT_CONNECTION::TransmitFile(
|
|
IN HANDLE hFile,
|
|
IN LARGE_INTEGER &liSize,
|
|
IN LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers
|
|
)
|
|
{
|
|
_ASSERT(hFile != INVALID_HANDLE_VALUE);
|
|
_ASSERT(liSize.QuadPart > 0);
|
|
|
|
return AtqTransmitFile(
|
|
m_pAtqContext, // Atq context
|
|
hFile, // file data comes from
|
|
(DWORD) liSize.LowPart, // Bytes To Send
|
|
lpTransmitBuffers, // header/tail buffers
|
|
0 // Flags
|
|
);
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------
|
|
//
|
|
// Function: CLIENT_CONNECTION::PostCompletionStatus
|
|
//
|
|
// Synopsis: Wrapper around atq for posting completion status
|
|
//
|
|
// Arguments:
|
|
// dwBytes: The number of bytes to indicate in the completion status
|
|
//
|
|
// Returns:
|
|
// TRUE: Success
|
|
// FALSE: Failure
|
|
//
|
|
// History:
|
|
// jstamerj 1998/11/03 20:16:17: Created.
|
|
//
|
|
//-------------------------------------------------------------
|
|
BOOL CLIENT_CONNECTION::PostCompletionStatus(
|
|
IN DWORD dwBytes)
|
|
{
|
|
return AtqPostCompletionStatus(
|
|
m_pAtqContext,
|
|
dwBytes);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Starts up a session for new client.
|
|
Adds the client socket to the ATQ completion port and gets an ATQ context.
|
|
Then prepares receive buffer and starts off a receive request from client.
|
|
( Also moves the client connection to CcsGettingRequest state)
|
|
|
|
Parameters:
|
|
pvInitial pointer to void buffer containing the initial data
|
|
cbWritten count of bytes in the buffer
|
|
|
|
Returns:
|
|
|
|
TRUE on success and FALSE if there is any error.
|
|
--*/
|
|
|
|
// VIRTUAL
|
|
BOOL CLIENT_CONNECTION::StartSession( void)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Receive full Request from the client.
|
|
If the entire request is received,
|
|
*pfFullRequestRecvd will be set to TRUE and
|
|
the request will be parsed.
|
|
|
|
Arguments:
|
|
|
|
cbWritten count of bytes written in last IO operation.
|
|
pfFullRequestRecvd pointer to boolean, which on successful return
|
|
indicates if the full request was received.
|
|
|
|
Returns:
|
|
|
|
TRUE on success and
|
|
FALSE if there is any error ( to abort this connection).
|
|
|
|
--*/
|
|
|
|
BOOL CLIENT_CONNECTION::ReceiveRequest(IN DWORD cbWritten, OUT LPBOOL pfFullRequestRecvd)
|
|
{
|
|
return ( TRUE);
|
|
}
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
Initiates a disconnect operation for current connection.
|
|
If already shutdown, this function returns doing nothing.
|
|
Optionally if there is any error message to be sent, they may be sent
|
|
|
|
Arguments:
|
|
|
|
dwErrorCode
|
|
error code for server errors if any ( Win 32 error code)
|
|
If dwErrorCode != NO_ERROR, then there is a system level error code.
|
|
by the REQUEST object. But the disconnection occurs immediately; Hence
|
|
the REQUEST object should send synchronous error messages.
|
|
|
|
Returns:
|
|
None
|
|
|
|
--*/
|
|
VOID CLIENT_CONNECTION::DisconnectClient(IN DWORD dwErrorCode)
|
|
{
|
|
SOCKET hSocket;
|
|
|
|
hSocket = (SOCKET)InterlockedExchangePointer( (PVOID *)&m_sClient, (PVOID) INVALID_SOCKET );
|
|
if ( hSocket != INVALID_SOCKET )
|
|
{
|
|
if ( QueryAtqContext() != NULL )
|
|
{
|
|
AtqCloseSocket(QueryAtqContext() , (dwErrorCode == NO_ERROR));
|
|
}
|
|
else
|
|
{
|
|
ShutAndCloseSocket( m_sClient );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
returns the client user name
|
|
VIRTUAL function so apps can override its return value
|
|
|
|
Arguments:
|
|
|
|
void
|
|
|
|
Returns:
|
|
returns ptr to user name
|
|
|
|
--*/
|
|
LPCTSTR CLIENT_CONNECTION::QueryClientUserName( VOID )
|
|
{
|
|
return m_pchLocalHostName;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Private Functions
|
|
//
|
|
|
|
|
|
# if DBG
|
|
|
|
// VIRTUAL
|
|
VOID CLIENT_CONNECTION::Print( VOID) const
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
} // ICLIENT_CONNECTION::Print()
|
|
|
|
|
|
# endif // DBG
|
|
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
Performs a hard close on the socket using shutdown before close.
|
|
|
|
Arguments:
|
|
|
|
sock socket to be closed
|
|
|
|
Returns:
|
|
|
|
0 if no errors or
|
|
socket specific error code
|
|
|
|
--*/
|
|
|
|
INT ShutAndCloseSocket( IN SOCKET sock)
|
|
{
|
|
|
|
INT serr = 0;
|
|
|
|
//
|
|
// Shut the socket. ( Assumes this to be a TCP socket.)
|
|
// Prevent future sends from occuring. hence 2nd param is "1"
|
|
//
|
|
|
|
if( sock != INVALID_SOCKET )
|
|
{
|
|
if ( shutdown( sock, 1) == SOCKET_ERROR)
|
|
serr = WSAGetLastError();
|
|
|
|
closesocket( sock);
|
|
}
|
|
|
|
return ( serr);
|
|
|
|
} // ShutAndCloseSocket()
|
|
|
|
|
|
|
|
/*++
|
|
This function canonicalizes the path, taking into account the current
|
|
user's current directory value.
|
|
|
|
Arguments:
|
|
pszDest string that will on return contain the complete
|
|
canonicalized path. This buffer will be of size
|
|
specified in *lpdwSize.
|
|
|
|
lpdwSize Contains the size of the buffer pszDest on entry.
|
|
On return contains the number of bytes written
|
|
into the buffer or number of bytes required.
|
|
|
|
pszSearchPath pointer to string containing the path to be converted.
|
|
IF NULL, use the current directory only
|
|
|
|
Returns:
|
|
|
|
Win32 Error Code - NO_ERROR on success
|
|
|
|
MuraliK 24-Apr-1995 Created.
|
|
|
|
--*/
|
|
BOOL
|
|
ResolveVirtualRoot(
|
|
OUT CHAR * pszDest,
|
|
IN OUT LPDWORD lpdwSize,
|
|
IN OUT CHAR * pszSearchPath,
|
|
OUT HANDLE * phToken /* = NULL */
|
|
)
|
|
{
|
|
TraceFunctEnter("ResolveVirtualRoot");
|
|
|
|
_ASSERT(pszDest != NULL);
|
|
_ASSERT(lpdwSize != NULL);
|
|
_ASSERT(pszSearchPath != NULL);
|
|
//
|
|
// Now we have the complete symbolic path to the target file.
|
|
// Translate it into the absolute path
|
|
//
|
|
|
|
#if 0
|
|
if (!TsLookupVirtualRoot(g_pTsvcInfo->GetTsvcCache(), // TSvcCache
|
|
pszSearchPath, // pszRoot
|
|
pszDest, // pszDirectory
|
|
lpdwSize, // lpcbSize
|
|
NULL, // lpdwAccessMask
|
|
NULL, // pcchDirRoot
|
|
NULL, // pcchVroot
|
|
phToken, // phImpersonationToken
|
|
NULL, // pszAddress
|
|
NULL // lpdwFileSystem
|
|
))
|
|
{
|
|
ErrorTrace(NULL, "TsLookupVirtualRoot failed looking for %s: %d", pszSearchPath, GetLastError());
|
|
TraceFunctLeave();
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
TraceFunctLeave();
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/************************ End of File ***********************/
|