5336 lines
139 KiB
C++
5336 lines
139 KiB
C++
/**********************************************************************/
|
||
/** Microsoft Windows NT **/
|
||
/** Copyright(c) Microsoft Corp., 1993 **/
|
||
/**********************************************************************/
|
||
|
||
/*
|
||
userdb.cxx
|
||
|
||
This module manages the user database for the FTPD Service.
|
||
|
||
Functions exported by this module:
|
||
|
||
|
||
DisconnectUser()
|
||
DisconnectUsersWithNoAccess()
|
||
EnumerateUsers()
|
||
|
||
USER_DATA::USER_DATA()
|
||
USER_DATA::Reset()
|
||
USER_DATA::~USER_DATA()
|
||
USER_DATA::Cleanup()
|
||
USER_DATA::ProcessAsyncIoCompletion()
|
||
USER_DATA::ReInitializeForNewUser()
|
||
USER_DATA::ReadCommand()
|
||
USER_DATA::DisconnectUserWithError()
|
||
USER_DATA::SendMultilineMessage()
|
||
USER_DATA::SendDirectoryAnnotation()
|
||
USER_DATA::GetFileSize();
|
||
|
||
ProcessUserAsyncIoCompletion()
|
||
|
||
FILE HISTORY:
|
||
KeithMo 07-Mar-1993 Created.
|
||
MuraliK March-May, 1995
|
||
Adding support for Async Io/Transfers
|
||
+ new USER_DATA class functions defined.
|
||
+ oob_inline enabled; ReadCommand() issued after
|
||
data socket is established.
|
||
+ added member functions for common operations
|
||
+ added ProcessAsyncIoCompletion()
|
||
+ added Establish & Destroy of Data connection
|
||
|
||
MuraliK 26-July-1995 Added Allocation caching of client conns.
|
||
Terryk 18-Sep-1996 Added GetFileSize
|
||
AMallet Sep 1998 Added support for AcceptEx() of PASV data connections
|
||
*/
|
||
|
||
|
||
#include "ftpdp.hxx"
|
||
# include "tsunami.hxx"
|
||
#include <timer.h>
|
||
# include "auxctrs.h"
|
||
# include <mbstring.h>
|
||
#include "acptctxt.hxx"
|
||
|
||
#define FIRST_TELNET_COMMAND 240
|
||
#define TELNET_DM_COMMAND 242
|
||
#define TELNET_IP_COMMAND 244
|
||
#define TELNET_SB_CODE 250
|
||
#define TELNET_SB_CODE_MIN 251
|
||
#define TELNET_SB_CODE_MAX 254
|
||
#define TELNET_IAC_CODE 255
|
||
|
||
# define MAX_FILE_SIZE_SPEC ( 32)
|
||
|
||
|
||
//
|
||
// Private globals.
|
||
//
|
||
|
||
#define PSZ_DEFAULT_SUB_DIRECTORY "Default"
|
||
|
||
static const char PSZ_SENT_VERB[] = "sent";
|
||
static const char PSZ_CONNECTION_CLOSED_VERB[] = "closed";
|
||
static const char PSZ_FILE_ERROR[] = "%s: %s";
|
||
static const char PSZ_TRANSFER_COMPLETE[] = "Transfer complete.";
|
||
static const char PSZ_TRANSFER_ABORTED[] =
|
||
"Connection closed; transfer aborted.";
|
||
static const char PSZ_TRANSFER_STARTING[] =
|
||
"Data connection already open; Transfer starting.";
|
||
static const char PSZ_INSUFFICIENT_RESOURCES[] =
|
||
"Insufficient system resources.";
|
||
static const char PSZ_TOO_MANY_PASV_USERS[] =
|
||
"Too many passive-mode users.";
|
||
static const char PSZ_OPENING_DATA_CONNECTION[] =
|
||
"Opening %s mode data connection for %s%s.";
|
||
static const char PSZ_CANNOT_OPEN_DATA_CONNECTION[] =
|
||
"Can't open data connection.";
|
||
static const char PSZ_COMMAND_TOO_LONG[] =
|
||
"Command was too long";
|
||
|
||
static DWORD p_NextUserId = 0; // Next available user id.
|
||
|
||
static const char SZ_LOCALUSER_DIR[] = "LocalUser\\";
|
||
static const char SZ_ANONYMOUS_DIR[] = "Public";
|
||
|
||
//
|
||
// Private prototypes.
|
||
//
|
||
|
||
DWORD
|
||
UserpGetNextId(
|
||
VOID
|
||
);
|
||
|
||
|
||
|
||
inline VOID
|
||
StopControlRead( IN LPUSER_DATA pUserData)
|
||
/*++
|
||
Stops control read operation, if one is proceeding.
|
||
Resets the CONTROL_READ flag as well as decrements ref count in user data.
|
||
|
||
--*/
|
||
{
|
||
if ( TEST_UF( pUserData, CONTROL_READ)) {
|
||
|
||
if ( InterlockedDecrement( &pUserData->m_nControlRead) < 0 ) {
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"StopControLRead: no read active!!!\n"));
|
||
DBG_ASSERT( FALSE);
|
||
}
|
||
|
||
DBG_REQUIRE( pUserData->DeReference() > 0);
|
||
CLEAR_UF( pUserData, CONTROL_READ);
|
||
}
|
||
|
||
} // StopControlRead()
|
||
|
||
|
||
BOOL
|
||
FilterTelnetCommands(IN CHAR * pszLine, IN DWORD cchLine,
|
||
IN LPBOOL pfLineEnded,
|
||
IN LPDWORD pcchRequestRecvd)
|
||
/*++
|
||
Filters out the Telnet commands and
|
||
terminates the command line with linefeed.
|
||
|
||
Also this function filters out the out of band data.
|
||
This works similar to the Sockutil.cxx::DiscardOutOfBandData().
|
||
We scan for the pattern "ABOR\r\n" and
|
||
set the OOB_DATA flag if it is present.
|
||
|
||
Arguments:
|
||
pszLine pointer to null terminated string containing the input data.
|
||
cchLine count of characters of data received
|
||
pfLineEnded pointer to Boolean flag which is set to true if complete
|
||
line has been received.
|
||
pcchRequestRecvd pointer to DWORD which on return contains the number
|
||
of bytes received.
|
||
|
||
Returns:
|
||
TRUE if the filtering is successful without any out of band abort request.
|
||
FALSE if there was any abort request in the input.
|
||
|
||
--*/
|
||
{
|
||
BOOL fDontAbort = TRUE;
|
||
BOOL fStateTelnetCmd = FALSE;
|
||
BOOL fStateTelnetSB = FALSE;
|
||
BOOL fFoundTelnetIP = FALSE;
|
||
CHAR * pszSrc;
|
||
CHAR * pszDst;
|
||
|
||
LPCSTR pszAbort = "ABOR\r\n";
|
||
LPCSTR pszNext = pszAbort;
|
||
|
||
DBG_ASSERT( pszLine != NULL && cchLine > 0 &&
|
||
pfLineEnded != NULL && pcchRequestRecvd != NULL);
|
||
|
||
*pfLineEnded = FALSE;
|
||
|
||
for( pszSrc = pszDst = pszLine; pszSrc < pszLine + cchLine && *pszSrc;
|
||
pszSrc++) {
|
||
|
||
CHAR ch = *pszSrc;
|
||
BYTE uch = (BYTE)ch;
|
||
|
||
//
|
||
// Filter out TELNET commands. these are of the form: IAC <cmd> or
|
||
// IAC SB <op> (IAC = 255, SB = 250, op 251..254, cmd > 240)
|
||
//
|
||
|
||
if( fStateTelnetCmd ) {
|
||
//
|
||
// we are in a Telbent command sequence
|
||
//
|
||
|
||
fStateTelnetCmd = FALSE;
|
||
|
||
DBG_ASSERT( uch >= FIRST_TELNET_COMMAND );
|
||
|
||
if( fStateTelnetSB ) {
|
||
//
|
||
// we are in a Telnet subsequence command
|
||
//
|
||
|
||
fStateTelnetSB = FALSE;
|
||
|
||
DBG_ASSERT( (uch >= TELNET_SB_CODE_MIN) &&
|
||
(uch <= TELNET_SB_CODE_MAX) );
|
||
|
||
if( uch >= FIRST_TELNET_COMMAND ) {
|
||
//
|
||
// consider it a valid Telnet command, as long as it's in
|
||
// the Telnet range. Filter this char out.
|
||
//
|
||
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// this is a TELNET protocol error, we'll ignore it.
|
||
//
|
||
// fall through with this char
|
||
//
|
||
|
||
} else if( uch == TELNET_SB_CODE ) {
|
||
//
|
||
// enter Telnet subsequense command state
|
||
//
|
||
|
||
fStateTelnetCmd = fStateTelnetSB = TRUE;
|
||
continue;
|
||
|
||
} else if( uch == TELNET_IAC_CODE ) {
|
||
//
|
||
// this is an escape sequence for a 255 data byte
|
||
//
|
||
// let it fall through
|
||
//
|
||
|
||
} else if ( uch == TELNET_IP_COMMAND ) {
|
||
//
|
||
// remember this, it is the first in a SYNCH sequence
|
||
//
|
||
|
||
fFoundTelnetIP = TRUE;
|
||
continue;
|
||
|
||
} else if ( uch == TELNET_DM_COMMAND ) {
|
||
//
|
||
// if in a SYNCH sequence, this resets the input stream
|
||
//
|
||
|
||
if( fFoundTelnetIP ) {
|
||
pszDst = pszLine;
|
||
fFoundTelnetIP = FALSE; // completed the SYNCH sequence
|
||
}
|
||
continue;
|
||
|
||
} else {
|
||
//
|
||
// we expect a Telnet command code here. filter it out
|
||
//
|
||
|
||
DBG_ASSERT( uch >= FIRST_TELNET_COMMAND );
|
||
|
||
if ( uch >= FIRST_TELNET_COMMAND ) {
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// this is a TELNET protocol error, we'll ignore it.
|
||
//
|
||
// fall through with this char
|
||
//
|
||
}
|
||
} else if( uch == TELNET_IAC_CODE ) {
|
||
//
|
||
// entering Telnet command parsing state
|
||
//
|
||
|
||
fStateTelnetCmd = TRUE;
|
||
continue;
|
||
} else if( uch == TELNET_DM_COMMAND ) {
|
||
//
|
||
// FTP.EXE on Win2k is sending an unexpected SYNCH sequence: DM, IAC, IP. See if this is it.
|
||
//
|
||
|
||
if( ( pszSrc == pszLine ) &&
|
||
( cchLine >= 3 ) &&
|
||
( (UINT)*(pszSrc+1) == TELNET_IAC_CODE ) &&
|
||
( (UINT)*(pszSrc+2) == TELNET_DM_COMMAND ) ) {
|
||
//
|
||
// just filter the sequence out
|
||
//
|
||
|
||
pszSrc += 2;
|
||
|
||
continue;
|
||
|
||
} else if( fFoundTelnetIP ) {
|
||
//
|
||
// or, it could be a single byte URGENT notification in the telnet Sync
|
||
//
|
||
|
||
pszDst = pszLine;
|
||
|
||
fFoundTelnetIP = FALSE; // completed the SYNCH sequence
|
||
|
||
continue;
|
||
}
|
||
}
|
||
|
||
//
|
||
// if we have seen a Telnet IP, then skip everything up to a DM
|
||
//
|
||
|
||
if (fFoundTelnetIP) {
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// try matching ABOR\r\n
|
||
//
|
||
|
||
if ( *pszNext != ch) {
|
||
|
||
// the pattern match failed. reset to start at the beginning.
|
||
|
||
pszNext = pszAbort;
|
||
}
|
||
|
||
if ( *pszNext == ch) {
|
||
|
||
// pattern match at this character. move forward
|
||
pszNext++;
|
||
|
||
if ( *pszNext == '\0') { // end of string==> all matched.
|
||
|
||
// only consider this an OOB Abort if at the beginning of
|
||
// a (reset) line
|
||
|
||
if( (pszDst - pszLine + 2) == (pszNext - pszAbort) ) {
|
||
fDontAbort = FALSE;
|
||
}
|
||
|
||
pszNext = pszAbort;
|
||
}
|
||
}
|
||
|
||
//
|
||
// don't copy <CR> and <LF> to the output
|
||
//
|
||
|
||
if ( (ch != '\r') && ( ch != '\n')) {
|
||
|
||
*pszDst++ = ch;
|
||
|
||
} else if ( ch == '\n') {
|
||
|
||
// terminate at the linefeed
|
||
*pfLineEnded = TRUE;
|
||
break;
|
||
}
|
||
|
||
} // for
|
||
|
||
//
|
||
// remember Telnet IP if we have seen it
|
||
//
|
||
|
||
if (fFoundTelnetIP) {
|
||
|
||
//
|
||
// we can safely do this, as we have filtered the 2 byte sequence out earlier
|
||
//
|
||
|
||
*(UCHAR*)pszDst++ = TELNET_IAC_CODE;
|
||
*(UCHAR*)pszDst++ = TELNET_IP_COMMAND;
|
||
}
|
||
|
||
//
|
||
// remember Telnet command states
|
||
//
|
||
if( fStateTelnetCmd ) {
|
||
|
||
*(UCHAR*)pszDst++ = TELNET_IAC_CODE;
|
||
|
||
if( fStateTelnetSB ) {
|
||
*(UCHAR*)pszDst++ = TELNET_SB_CODE;
|
||
}
|
||
}
|
||
|
||
*pszDst = '\0';
|
||
|
||
*pcchRequestRecvd = DIFF(pszDst - pszLine);
|
||
DBG_ASSERT( *pcchRequestRecvd <= cchLine);
|
||
|
||
return (fDontAbort);
|
||
|
||
} // FilterTelnetCommands()
|
||
|
||
|
||
|
||
//
|
||
// Public functions.
|
||
//
|
||
|
||
|
||
|
||
|
||
|
||
USER_DATA::USER_DATA(
|
||
IN FTP_SERVER_INSTANCE *pInstance
|
||
)
|
||
/*++
|
||
This function creates a new UserData object for the information
|
||
required to process requests from a new User connection ( FTP).
|
||
|
||
Arguments:
|
||
sControl Socket used for control channel in FTP connection
|
||
clientIpAddress strcuture containing the client Ip address
|
||
|
||
Returns:
|
||
a newly constructed USER_DATA object.
|
||
Check IsValid() to ensure the object was properly created.
|
||
|
||
NOTE:
|
||
This function is to be used for dummy creation of the object so
|
||
allocation cacher can use this object.
|
||
Fields are randomly initialized. Reset() will initialize them properly.
|
||
|
||
However when a new effective USER_DATA object is needed, after allocation
|
||
one can call USER_DATA::Reset() to initialize all vars.
|
||
--*/
|
||
:
|
||
m_References ( 0),
|
||
m_ActiveRefAdded ( 0),
|
||
m_cchRecvBuffer ( 0),
|
||
m_cbRecvd ( 0),
|
||
m_cchPartialReqRecvd ( 0),
|
||
m_pOpenFileInfo ( NULL),
|
||
Flags ( 0),
|
||
UserToken ( NULL),
|
||
m_UserId ( 0),
|
||
DataPort ( 0),
|
||
UserState ( UserStateEmbryonic),
|
||
m_AioControlConnection ( ProcessUserAsyncIoCompletion),
|
||
m_AioDataConnection ( ProcessUserAsyncIoCompletion),
|
||
m_sPassiveDataListen ( INVALID_SOCKET),
|
||
CurrentDirHandle ( INVALID_HANDLE_VALUE),
|
||
RenameSourceBuffer ( NULL),
|
||
m_fCleanedup ( FALSE),
|
||
m_pMetaData ( NULL),
|
||
m_pInstance ( pInstance ),
|
||
m_acCheck ( AC_NOT_CHECKED ),
|
||
m_fNeedDnsCheck ( FALSE ),
|
||
m_dwLastReplyCode ( 0 ),
|
||
m_fHavePASVConn ( FALSE ),
|
||
m_fWaitingForPASVConn ( FALSE ),
|
||
m_fFakeIOCompletion ( FALSE ),
|
||
m_pszCmd ( NULL ),
|
||
m_hPASVAcceptEvent ( NULL )
|
||
#if DBG
|
||
,m_RefTraceLog( NULL )
|
||
#endif
|
||
{
|
||
DWORD dwTimeout = m_pInstance->QueryConnectionTimeout();
|
||
|
||
INITIALIZE_CRITICAL_SECTION( &m_UserLock );
|
||
|
||
//
|
||
// Setup the structure signature.
|
||
//
|
||
|
||
INIT_USER_SIG( this );
|
||
|
||
m_AioControlConnection.SetAioInformation( this, dwTimeout);
|
||
m_AioDataConnection.SetAioInformation( this, dwTimeout);
|
||
|
||
InitializeListHead( &ListEntry);
|
||
|
||
ZeroMemory( m_recvBuffer, DEFAULT_REQUEST_BUFFER_SIZE);
|
||
|
||
IF_DEBUG( USER_DATABASE ) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"user_data object created @ %08lX.\n",
|
||
this));
|
||
}
|
||
|
||
m_licbSent.QuadPart = 0;
|
||
|
||
#if DBG
|
||
m_RefTraceLog = CreateRefTraceLog( TRACE_LOG_SIZE, 0 );
|
||
#endif
|
||
|
||
FakeIOTimes = 0;
|
||
|
||
} // USER_DATA::USER_DATA()
|
||
|
||
|
||
|
||
USER_DATA::~USER_DATA(VOID)
|
||
{
|
||
|
||
if ( !m_fCleanedup) {
|
||
Cleanup();
|
||
}
|
||
|
||
if( RenameSourceBuffer != NULL ) {
|
||
TCP_FREE( RenameSourceBuffer);
|
||
RenameSourceBuffer = NULL;
|
||
}
|
||
|
||
if ( m_pszCmd != NULL )
|
||
{
|
||
TCP_FREE( m_pszCmd );
|
||
m_pszCmd = NULL;
|
||
}
|
||
|
||
if ( m_hPASVAcceptEvent != NULL )
|
||
{
|
||
RemovePASVAcceptEvent( TRUE );
|
||
}
|
||
|
||
if ( m_pInstance != NULL ) {
|
||
m_pInstance->DecrementCurrentConnections();
|
||
m_pInstance->Dereference();
|
||
m_pInstance = NULL;
|
||
}
|
||
|
||
#if DBG
|
||
if( m_RefTraceLog != NULL ) {
|
||
DestroyRefTraceLog( m_RefTraceLog );
|
||
}
|
||
#endif
|
||
|
||
DeleteCriticalSection( &m_UserLock );
|
||
|
||
} // USER_DATA::~USER_DATA()
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::Reset(IN SOCKET sControl,
|
||
IN PVOID EndpointObject,
|
||
IN IN_ADDR clientIpAddress,
|
||
IN const SOCKADDR_IN * psockAddrLocal /* = NULL */ ,
|
||
IN PATQ_CONTEXT pAtqContext /* = NULL */ ,
|
||
IN PVOID pvInitialRequest /* = NULL */ ,
|
||
IN DWORD cbWritten /* = 0 */ ,
|
||
IN AC_RESULT acCheck
|
||
)
|
||
{
|
||
BOOL fReturn = TRUE;
|
||
|
||
//
|
||
// Setup the structure signature.
|
||
//
|
||
|
||
INIT_USER_SIG( this );
|
||
|
||
m_References = 1; // set to 1 to prevent immediate deletion.
|
||
m_ActiveRefAdded= 1;
|
||
m_fCleanedup = FALSE;
|
||
Flags = m_pInstance->QueryUserFlags();
|
||
UserState = UserStateEmbryonic;
|
||
|
||
#if DBG
|
||
if( m_RefTraceLog != NULL ) {
|
||
ResetTraceLog( m_RefTraceLog );
|
||
}
|
||
#endif
|
||
|
||
m_pOpenFileInfo = NULL;
|
||
UserToken = NULL;
|
||
if ( m_pMetaData != NULL )
|
||
{
|
||
TsFreeMetaData( m_pMetaData->QueryCacheInfo() );
|
||
m_pMetaData = NULL;
|
||
}
|
||
m_UserId = UserpGetNextId();
|
||
m_xferType = XferTypeAscii;
|
||
m_xferMode = XferModeStream;
|
||
m_msStartingTime= 0;
|
||
m_acCheck = acCheck;
|
||
m_fNeedDnsCheck = FALSE;
|
||
m_dwLastReplyCode = 0;
|
||
|
||
HostIpAddress = clientIpAddress;
|
||
DataIpAddress = clientIpAddress;
|
||
|
||
m_cbRecvd = 0;
|
||
m_cchRecvBuffer = sizeof( m_recvBuffer) - sizeof(m_recvBuffer[0]);
|
||
m_cchPartialReqRecvd = 0;
|
||
|
||
CurrentDirHandle = INVALID_HANDLE_VALUE;
|
||
RenameSourceBuffer = NULL;
|
||
m_TimeAtConnection = GetCurrentTimeInSeconds();
|
||
m_TimeAtLastAccess = m_TimeAtConnection;
|
||
|
||
|
||
m_pvInitialRequest = pvInitialRequest;
|
||
m_cbInitialRequest = cbWritten;
|
||
|
||
//
|
||
// clean up the stuff needed to deal async with PASV command
|
||
//
|
||
if ( m_pszCmd )
|
||
{
|
||
TCP_FREE( m_pszCmd );
|
||
m_pszCmd = NULL;
|
||
}
|
||
|
||
if ( m_hPASVAcceptEvent )
|
||
{
|
||
RemovePASVAcceptEvent( TRUE );
|
||
}
|
||
|
||
CleanupPASVFlags();
|
||
|
||
// set up the async io contexts
|
||
m_AioControlConnection.SetNewSocket( sControl, pAtqContext, EndpointObject );
|
||
m_AioDataConnection.SetNewSocket(INVALID_SOCKET);
|
||
m_sPassiveDataListen = ( INVALID_SOCKET);
|
||
|
||
m_rgchFile[0] = '\0';
|
||
m_szUserName[0] = '\0'; // no user name available yet.
|
||
m_szCurrentDirectory[0] = '\0'; // initialize to no virtual dir.
|
||
m_szRootDir[0] = '\0'; // no root directory known yet
|
||
|
||
m_licbSent.QuadPart = 0;
|
||
|
||
m_pInstance->QueryStatsObj()->IncrCurrentConnections();
|
||
|
||
m_dwCurrentOffset = 0;
|
||
m_dwNextOffset = 0;
|
||
|
||
//
|
||
// get the local Ip address
|
||
//
|
||
|
||
if ( psockAddrLocal != NULL) {
|
||
|
||
LocalIpAddress = psockAddrLocal->sin_addr;
|
||
LocalIpPort = psockAddrLocal->sin_port;
|
||
} else {
|
||
|
||
SOCKADDR_IN saddrLocal;
|
||
INT cbLocal;
|
||
|
||
cbLocal = sizeof( saddrLocal);
|
||
if ( getsockname( sControl, (SOCKADDR *) &saddrLocal, &cbLocal) != 0) {
|
||
|
||
DWORD err = WSAGetLastError();
|
||
|
||
fReturn = FALSE;
|
||
|
||
IF_DEBUG( ERROR) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" Failure in getsockname( sock=%d). Error = %u\n",
|
||
sControl, err));
|
||
}
|
||
|
||
SetLastError( err);
|
||
|
||
} else {
|
||
|
||
LocalIpAddress = saddrLocal.sin_addr;
|
||
LocalIpPort = saddrLocal.sin_port;
|
||
}
|
||
}
|
||
|
||
DataPort = CONN_PORT_TO_DATA_PORT(LocalIpPort);
|
||
|
||
//
|
||
// Success!
|
||
//
|
||
|
||
IF_DEBUG( CLIENT) {
|
||
|
||
time_t now;
|
||
time( & now);
|
||
CHAR pchAddr[32];
|
||
|
||
InetNtoa( clientIpAddress, pchAddr);
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" Client Connection for %s:%d starting @ %s",
|
||
pchAddr, sControl,
|
||
asctime( localtime( &now))));
|
||
}
|
||
|
||
IF_DEBUG( USER_DATABASE ) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"user %lu reset @ %08lX.\n",
|
||
QueryId(), this));
|
||
}
|
||
|
||
m_nControlRead = 0;
|
||
|
||
FakeIOTimes = 0;
|
||
|
||
return (fReturn);
|
||
|
||
} // USER_DATA::Reset()
|
||
|
||
|
||
|
||
|
||
VOID
|
||
USER_DATA::Cleanup( VOID)
|
||
/*++
|
||
This cleans up data stored in the user data object.
|
||
|
||
Returns:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
DBG_ASSERT( QueryReference() == 0);
|
||
|
||
if ( m_pMetaData != NULL )
|
||
{
|
||
TsFreeMetaData( m_pMetaData->QueryCacheInfo() );
|
||
m_pMetaData = NULL;
|
||
}
|
||
|
||
# if DBG
|
||
|
||
if ( !IS_VALID_USER_DATA( this)) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"Encountering an invalid user data ( %08x)\n",
|
||
this));
|
||
Print();
|
||
}
|
||
# endif // DBG
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( this ) );
|
||
|
||
IF_DEBUG( USER_DATABASE ) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
" Cleaning up user %lu @ %08lX.\n",
|
||
QueryId(), this));
|
||
}
|
||
|
||
DBG_ASSERT( m_nControlRead == 0);
|
||
|
||
//
|
||
// Clean up stuff needed to deal with PASV connections
|
||
//
|
||
if ( m_hPASVAcceptEvent )
|
||
{
|
||
RemovePASVAcceptEvent( TRUE );
|
||
}
|
||
|
||
|
||
if ( m_pszCmd )
|
||
{
|
||
TCP_FREE( m_pszCmd );
|
||
m_pszCmd = NULL;
|
||
}
|
||
|
||
//
|
||
// Close any open sockets & handles.
|
||
//
|
||
|
||
CloseSockets( FALSE );
|
||
|
||
// invalidate the connections
|
||
m_AioControlConnection.SetNewSocket(INVALID_SOCKET);
|
||
m_AioDataConnection.SetNewSocket(INVALID_SOCKET);
|
||
|
||
|
||
//
|
||
// Update the statistics.
|
||
//
|
||
|
||
if( IsLoggedOn()
|
||
&& !TEST_UF( this, WAIT_PASS ) )
|
||
{
|
||
if( TEST_UF( this, ANONYMOUS))
|
||
{
|
||
m_pInstance->QueryStatsObj()->DecrCurrentAnonymousUsers();
|
||
}
|
||
else
|
||
{
|
||
m_pInstance->QueryStatsObj()->DecrCurrentNonAnonymousUsers();
|
||
}
|
||
}
|
||
|
||
m_pInstance->QueryStatsObj()->DecrCurrentConnections();
|
||
|
||
if( UserToken != NULL )
|
||
{
|
||
TsDeleteUserToken( UserToken );
|
||
UserToken = NULL;
|
||
}
|
||
|
||
if( CurrentDirHandle != INVALID_HANDLE_VALUE )
|
||
{
|
||
IF_DEBUG( VIRTUAL_IO )
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"closing directory handle %08lX\n",
|
||
CurrentDirHandle ));
|
||
}
|
||
|
||
CloseHandle( CurrentDirHandle );
|
||
CurrentDirHandle = INVALID_HANDLE_VALUE;
|
||
}
|
||
|
||
if ( m_pOpenFileInfo != NULL) {
|
||
|
||
DBG_REQUIRE( CloseFileForSend());
|
||
}
|
||
|
||
//
|
||
// Release the memory attached to this structure.
|
||
//
|
||
|
||
|
||
if( RenameSourceBuffer != NULL ) {
|
||
|
||
// do not free this location until end of usage.
|
||
RenameSourceBuffer[0] = '\0';
|
||
}
|
||
|
||
m_UserId = 0; // invalid User Id
|
||
|
||
//
|
||
// Kill the structure signature.
|
||
//
|
||
|
||
KILL_USER_SIG( this );
|
||
|
||
IF_DEBUG( CLIENT) {
|
||
|
||
time_t now;
|
||
time( & now);
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" Client Connection for %s:%d ending @ %s",
|
||
inet_ntoa( HostIpAddress), QueryControlSocket(),
|
||
asctime( localtime( &now))));
|
||
}
|
||
|
||
|
||
//
|
||
// There is a possible race condition. If the socket was abruptly closed
|
||
// and there was any pending Io, they will get blown away. This will
|
||
// cause a call-back from the ATQ layer. That is unavoidable.
|
||
// In such cases it is possible that the object was deleted.
|
||
// This can lead to problems. We need to be careful.
|
||
// But Reference Count protects against such disasters. So tread
|
||
// carefully and use Reference count.
|
||
//
|
||
|
||
DBG_ASSERT( m_sPassiveDataListen == INVALID_SOCKET);
|
||
|
||
m_fCleanedup = TRUE; // since we just cleaned up this object
|
||
|
||
return;
|
||
|
||
} // USER_DATA::Cleanup()
|
||
|
||
|
||
|
||
|
||
|
||
|
||
VOID
|
||
USER_DATA::ReInitializeForNewUser( VOID)
|
||
/*++
|
||
|
||
This function reinitializes the user data information for a new user to
|
||
communicate with the server using existing control socket connection.
|
||
|
||
--*/
|
||
{
|
||
|
||
# if DBG
|
||
|
||
if ( !IS_VALID_USER_DATA( this)) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"Encountering an invalid user data ( %08x)\n",
|
||
this));
|
||
Print();
|
||
}
|
||
# endif // DBG
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( this ) );
|
||
|
||
//
|
||
// Update the statistics.
|
||
//
|
||
|
||
if( IsLoggedOn())
|
||
{
|
||
if( TEST_UF( this, ANONYMOUS))
|
||
{
|
||
m_pInstance->QueryStatsObj()->DecrCurrentAnonymousUsers();
|
||
}
|
||
else
|
||
{
|
||
m_pInstance->QueryStatsObj()->DecrCurrentNonAnonymousUsers();
|
||
}
|
||
}
|
||
|
||
CLEAR_UF_BITS( this, (UF_LOGGED_ON | UF_ANONYMOUS | UF_PASSIVE));
|
||
|
||
LockUser();
|
||
if( QueryState() != UserStateDisconnected ) {
|
||
SetState( UserStateWaitingForUser );
|
||
}
|
||
UnlockUser();
|
||
|
||
if ( m_pMetaData != NULL )
|
||
{
|
||
TsFreeMetaData( m_pMetaData->QueryCacheInfo() );
|
||
m_pMetaData = NULL;
|
||
}
|
||
m_TimeAtConnection= GetCurrentTimeInSeconds();
|
||
m_TimeAtLastAccess= m_TimeAtConnection;
|
||
m_xferType = XferTypeAscii;
|
||
m_xferMode = XferModeStream;
|
||
DataIpAddress = HostIpAddress;
|
||
DataPort = CONN_PORT_TO_DATA_PORT(LocalIpPort);
|
||
|
||
m_szUserName[0] = '\0';
|
||
m_szCurrentDirectory[0] = '\0';
|
||
m_szRootDir[0] = '\0';
|
||
|
||
if( UserToken != NULL )
|
||
{
|
||
TsDeleteUserToken( UserToken );
|
||
UserToken = NULL;
|
||
}
|
||
|
||
if( CurrentDirHandle != INVALID_HANDLE_VALUE )
|
||
{
|
||
IF_DEBUG( VIRTUAL_IO )
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"closing directory handle %08lX\n",
|
||
CurrentDirHandle ));
|
||
}
|
||
|
||
CloseHandle( CurrentDirHandle );
|
||
CurrentDirHandle = INVALID_HANDLE_VALUE;
|
||
}
|
||
|
||
if ( m_pOpenFileInfo != NULL) {
|
||
|
||
DBG_REQUIRE( CloseFileForSend());
|
||
}
|
||
|
||
m_licbSent.QuadPart = 0;
|
||
|
||
m_pvInitialRequest = NULL;
|
||
m_cbInitialRequest = 0;
|
||
|
||
CleanupPassiveSocket( TRUE );
|
||
|
||
return;
|
||
|
||
} // USER_DATA::ReInitializeForNewUser()
|
||
|
||
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::ProcessAsyncIoCompletion(
|
||
IN DWORD cbIo,
|
||
IN DWORD dwError,
|
||
IN LPASYNC_IO_CONNECTION pAioConn,
|
||
IN BOOL fTimedOut)
|
||
/*++
|
||
This function processes the Async Io completion.
|
||
( invoked due to a callback from the ASYNC_IO_CONNECTION object)
|
||
|
||
Arguments:
|
||
pContext pointer to the context information ( UserData object).
|
||
cbIo count of bytes transferred in Io
|
||
dwError DWORD containing the error code resulting from last tfr.
|
||
pAioConn pointer to AsyncIo connection object.
|
||
fTimedOut flag indicating if the current call was made
|
||
because of timeout.
|
||
|
||
Returns:
|
||
None
|
||
--*/
|
||
{
|
||
BOOL fReturn = FALSE;
|
||
AC_RESULT acDnsAccess;
|
||
DWORD dwOriginalError;
|
||
|
||
dwOriginalError = dwError;
|
||
|
||
//
|
||
// Special processing if it's an IO completion on the control connection - we might
|
||
// be processing a completion we posted ourselves to signal that the data socket for the PASV
|
||
// data connection is now accept()'able.
|
||
//
|
||
|
||
if ( pAioConn == &m_AioControlConnection && QueryInFakeIOCompletion() )
|
||
{
|
||
// Here is a horrible race condition:
|
||
// If the FTP client closes the control socket
|
||
// right after having finished receiving the transmitted file
|
||
// than there may be a thread that enters this
|
||
// code path (because the FakeIO flag is set, and the
|
||
// Control Socket is involved) before the IO completion
|
||
// for the data sonnection has traveled this same function,
|
||
// cleaning the FakeIO flag
|
||
// Here is the race condition:
|
||
// A thread enter here, and see that the FakeIO is set
|
||
// the normal behavior is reprocessing a command like
|
||
// "RETR foo.txt", while now the command if a zero length string.
|
||
// the Second thread enter this function with the DataConnection
|
||
// it clears the flag (at a non specified point of the
|
||
// processing of the other thread) and it exit.
|
||
// the original thread is now processing a saved string
|
||
// (because of the FakeIO flag) while it is not supposed to.
|
||
// this causes problems to the time-out algorithm, because
|
||
// of a ref-count problem in the USER_DATA
|
||
|
||
LONG CurVal = InterlockedIncrement(&(this->FakeIOTimes));
|
||
if (CurVal>1){
|
||
goto NormalProcessing;
|
||
}
|
||
|
||
//
|
||
// Remove the reference used to deal with the race condition between an IO
|
||
// thread doing clean-up and the thread watching for the data socket to become
|
||
// accept()'able and holding on to this USER_DATA object
|
||
//
|
||
DeReference();
|
||
|
||
//
|
||
// There is a race condition between the thread watching for a socket to become
|
||
// accept()'able and an IO thread being woken up because the client has (unexpectedly)
|
||
// disconnected. The thread watching the socket will post a fake IO completion to
|
||
// indicate that an accept() on the socket will succeed; however, if the client
|
||
// disconnects (the control connection) before the fake completion is processed,
|
||
// we don't want to do any more processing.
|
||
//
|
||
if ( UserState == UserStateDisconnected )
|
||
{
|
||
return TRUE;
|
||
}
|
||
else
|
||
{
|
||
//
|
||
// Fast-path if we know this is the second time around we're trying to process the
|
||
// command, which happens when we're in PASV mode
|
||
//
|
||
DBG_ASSERT( UserState == UserStateLoggedOn );
|
||
goto ProcessCommand;
|
||
}
|
||
}
|
||
|
||
NormalProcessing:
|
||
|
||
if( dwError != NO_ERROR &&
|
||
dwError != ERROR_SEM_TIMEOUT )
|
||
{
|
||
|
||
//
|
||
// Geezsh, I hate my life.
|
||
//
|
||
// Once upon a time, there was a bug in ATQ that cause it to
|
||
// always pass NO_ERROR as the status to the async completion
|
||
// routine. This bug caused, among other things, FTP to never
|
||
// time out idle connections, because it never saw the
|
||
// ERROR_SEM_TIMEOUT status. So, I fixed the bug in ATQ.
|
||
//
|
||
// Now, this completion routine gets the actual status. Well,
|
||
// that breaks service shutdown when there are connected users.
|
||
// Basically, when a shutdown occurs, the connected sockets are
|
||
// closed, causing the IO to complete with ERROR_NETNAME_DELETED.
|
||
// USER_DATA::ProcessAsyncIoCompletion() is not handling this
|
||
// error properly, which causes 1) an assertion failure because
|
||
// USER_DATA::DisconnectUserWithError() is getting called *twice*
|
||
// and 2) the service never stops because of a dangling reference
|
||
// on the USER_DATA structure.
|
||
//
|
||
// Of course, the proper thing to do would be to fix the offending
|
||
// code in USER_DATA::ProcessAsyncIoCompletion() so that it DID
|
||
// handle the error properly. Unfortunately, that fix requires a
|
||
// nontrivial amount of surgery, and we're a scant three days
|
||
// from releasing K2 Beta 1. So...
|
||
//
|
||
// As a quick & dirty work around for K2 Beta 1, we'll map all
|
||
// errors other than ERROR_SEM_TIMEOUT to NO_ERROR. This should
|
||
// provide the lower software layers with the old ATQ behavior
|
||
// they're expecting.
|
||
//
|
||
// REMOVE THIS POST BETA 1 AND FIX USER_DATA PROPERLY!!!!
|
||
//
|
||
// 3/12/98
|
||
//
|
||
// N.B. The debug output below has been changed to be a little
|
||
// more customer friendly but I hate to prevent future developers
|
||
// for enjoying the original message which read:
|
||
// "Mapping error %d to NO_ERROR to mask FTP bug (FIX!)\n"
|
||
//
|
||
// I'm removing this message because it was the source of some
|
||
// embarrasment, when a checked version of this DLL was sent to
|
||
// Ernst & Young to track the now famous bug #138566.
|
||
//
|
||
|
||
DBGPRINTF((
|
||
DBG_CONTEXT,
|
||
"Mapping error %d to NO_ERROR\n",
|
||
dwError
|
||
));
|
||
|
||
dwError = NO_ERROR;
|
||
|
||
}
|
||
|
||
# if DBG
|
||
|
||
if ( !IS_VALID_USER_DATA( this)) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"Encountering an invalid user data ( %08x)\n",
|
||
this));
|
||
Print();
|
||
}
|
||
# endif // DBG
|
||
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( this ) );
|
||
|
||
IF_DEBUG( USER_DATABASE) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"[%lu] Entering USER_DATA( %08x)::Process( %u, %u, %08x)."
|
||
" RefCount = %d. State = %d\n",
|
||
GetTickCount(),
|
||
this, cbIo, dwError, pAioConn, QueryReference(),
|
||
QueryState()));
|
||
}
|
||
|
||
if ( m_fNeedDnsCheck )
|
||
{
|
||
acDnsAccess = QueryAccessCheck()->CheckDnsAccess();
|
||
|
||
UnbindInstanceAccessCheck();
|
||
|
||
m_fNeedDnsCheck = FALSE;
|
||
|
||
if ( (acDnsAccess == AC_IN_DENY_LIST) ||
|
||
(acDnsAccess == AC_NOT_IN_GRANT_LIST) ||
|
||
((m_acCheck == AC_NOT_IN_GRANT_LIST) &&
|
||
(acDnsAccess != AC_IN_GRANT_LIST) ) ) {
|
||
|
||
ReplyToUser(this,
|
||
REPLY_NOT_LOGGED_IN,
|
||
"Connection refused, unknown IP address." );
|
||
|
||
DisconnectUserWithError( NO_ERROR );
|
||
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
if ( pAioConn == &m_AioDataConnection)
|
||
{
|
||
|
||
//
|
||
// a Data transfer operation has completed.
|
||
//
|
||
|
||
DBG_REQUIRE( IsLoggedOn());
|
||
|
||
// Update last access time
|
||
m_TimeAtLastAccess = GetCurrentTimeInSeconds();
|
||
|
||
if ( dwError == NO_ERROR || !fTimedOut)
|
||
{
|
||
|
||
// dwError == NO_ERROR ==> No error in transmitting data
|
||
// so decrease ref count and blow away the sockets.
|
||
|
||
// if dwError != NO_ERROR then
|
||
// if timeout occured ==> ATQ will send another callback
|
||
// so do not decrease ref count now.
|
||
// if no timeout ==> then decrement ref count now.
|
||
|
||
DBG_REQUIRE( DeReference() > 0);
|
||
}
|
||
else
|
||
{
|
||
|
||
if ( fTimedOut)
|
||
{
|
||
SET_UF( this, DATA_TIMEDOUT);
|
||
}
|
||
else
|
||
{
|
||
SET_UF( this, DATA_ERROR);
|
||
}
|
||
|
||
}
|
||
|
||
# ifdef CHECK_DBG
|
||
if ( dwError != NO_ERROR)
|
||
{
|
||
|
||
CHAR szBuffer[100];
|
||
sprintf( szBuffer, " Data Socket Error = %u ", dwError);
|
||
Print( szBuffer);
|
||
}
|
||
# endif // CHECK_DBG
|
||
|
||
CLEAR_UF( this, ASYNC_TRANSFER);
|
||
|
||
//
|
||
// Destroy the data connection.
|
||
// Send message accordingly to indicate if this was a failure/success
|
||
// That is done by DestroyDataConnection.
|
||
//
|
||
DBG_REQUIRE( DestroyDataConnection( dwOriginalError));
|
||
|
||
|
||
if ( m_pOpenFileInfo != NULL)
|
||
{
|
||
//
|
||
// set number of bytes actually sent
|
||
//
|
||
m_licbSent.QuadPart += cbIo;
|
||
|
||
DBG_REQUIRE( CloseFileForSend( dwOriginalError));
|
||
}
|
||
|
||
if ( dwError == NO_ERROR)
|
||
{
|
||
|
||
//
|
||
// Process any Pending commands, due to the parallel
|
||
// control channel operation for this user Connection.
|
||
// For the present, we dont buffer commands ==> No processing
|
||
// to be done effectively. NYI
|
||
// Just ensure that there is a read-operation pending on
|
||
// control channel.
|
||
//
|
||
|
||
// BOGUS: DBG_ASSERT( TEST_UF( this, CONTROL_READ));
|
||
}
|
||
|
||
fReturn = TRUE; // since this function went on well.
|
||
}
|
||
else if ( pAioConn == &m_AioControlConnection)
|
||
{
|
||
|
||
//
|
||
// a control socket operation has completed.
|
||
//
|
||
|
||
if ( dwError != NO_ERROR)
|
||
{
|
||
|
||
//
|
||
// There is an error in processing the control connection request.
|
||
// the only ASYNC_IO request we submit on control is:
|
||
// Read request on control socket
|
||
//
|
||
|
||
if ( fTimedOut)
|
||
{
|
||
|
||
if ( TEST_UF( this, TRANSFER))
|
||
{
|
||
|
||
// A data transfer is going on.
|
||
// allow client to send commands later
|
||
// (client may not be async in control/data io,so allow it)
|
||
|
||
// resubmit the control read operation
|
||
// after clearing old one
|
||
|
||
//
|
||
// Since there is a pending IO in atq.
|
||
// Just resume the timeout processing in ATQ for
|
||
// this context.
|
||
//
|
||
|
||
pAioConn->ResumeIoOperation();
|
||
fReturn = TRUE;
|
||
}
|
||
else
|
||
{
|
||
|
||
// For timeouts, ATQ sends two call backs.
|
||
// So be careful to decrement reference count only once.
|
||
|
||
DBG_ASSERT( fReturn == FALSE);
|
||
|
||
DBG_ASSERT( TEST_UF( this, CONTROL_READ));
|
||
SET_UF( this, CONTROL_TIMEDOUT);
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
// Either there should be a control read pending or
|
||
// control socket should have received a timeout.
|
||
DBG_ASSERT( TEST_UF( this, CONTROL_READ) ||
|
||
TEST_UF( this, CONTROL_TIMEDOUT)
|
||
);
|
||
|
||
// a non-timeout error has occured. ==> stop read operation.
|
||
StopControlRead(this);
|
||
DBG_ASSERT( fReturn == FALSE);
|
||
SET_UF( this, CONTROL_ERROR);
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
// If this connection had an outstanding IO on wait queue, it
|
||
// got completed. Hence get rid of the reference count.
|
||
StopControlRead( this);
|
||
|
||
switch ( UserState)
|
||
{
|
||
|
||
case UserStateEmbryonic:
|
||
|
||
fReturn = StartupSession( m_pvInitialRequest,
|
||
m_cbInitialRequest);
|
||
|
||
if ( m_pvInitialRequest == NULL)
|
||
{
|
||
// No initial buffer. Wait for read to complete
|
||
break;
|
||
}
|
||
|
||
cbIo = m_cbInitialRequest; // fake the bytes read.
|
||
|
||
// Fall Through for processing request
|
||
|
||
case UserStateWaitingForUser:
|
||
case UserStateWaitingForPass:
|
||
case UserStateLoggedOn:
|
||
|
||
ProcessCommand:
|
||
//
|
||
// Input already read. Process request and submit another read.
|
||
//
|
||
|
||
fReturn = ParseAndProcessRequest(cbIo/sizeof(CHAR));
|
||
|
||
if ( fReturn && IsDisconnected() &&
|
||
TEST_UF( this, CONTROL_TIMEDOUT))
|
||
{
|
||
|
||
// disconnect only if no pending control read
|
||
// if there is a pending control read,
|
||
// atq will pop this up for cleanup.
|
||
fReturn = !(TEST_UF( this, CONTROL_READ));
|
||
|
||
IF_DEBUG( ERROR) {
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"%08x ::Timeout killed conn while "
|
||
" processing!\n State = %d(%x),"
|
||
" Ref = %d, Id = %d, fRet=%d\n",
|
||
this, QueryState(), Flags,
|
||
QueryReference(), QueryId(), fReturn
|
||
));
|
||
}
|
||
FacIncrement( CacTimeoutWhenProcessing);
|
||
}
|
||
break;
|
||
|
||
case UserStateDisconnected:
|
||
|
||
fReturn = TRUE;
|
||
if ( TEST_UF( this, CONTROL_TIMEDOUT))
|
||
{
|
||
|
||
// Timeout thread raced against me :(
|
||
|
||
IF_DEBUG( ERROR) {
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"%08x :: Conn already Disconnected !!!\n"
|
||
" State = %d(%x), Ref = %d, Id = %d\n",
|
||
this, QueryState(), Flags,
|
||
QueryReference(), QueryId()
|
||
));
|
||
}
|
||
FacIncrement( CacTimeoutInDisconnect);
|
||
fReturn = FALSE;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
|
||
DBG_ASSERT( !"Invalid UserState for processing\n");
|
||
SetLastError( ERROR_INVALID_PARAMETER);
|
||
break;
|
||
} // switch
|
||
|
||
dwError = ( fReturn) ? NO_ERROR : GetLastError();
|
||
}
|
||
|
||
if ( !fReturn)
|
||
{
|
||
DisconnectUserWithError( dwError, fTimedOut);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
|
||
DBG_ASSERT( !"call to Process() with wrong parameters");
|
||
}
|
||
|
||
IF_DEBUG( USER_DATABASE) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"[%lu] Leaving USER_DATA( %08x)::Process()."
|
||
" RefCount = %d. State = %d\n",
|
||
GetTickCount(),
|
||
this, QueryReference(), QueryState())
|
||
);
|
||
}
|
||
|
||
return ( fReturn);
|
||
} // USER_DATA::ProcessAsyncIoCompletion()
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# define min(a, b) (((a) < (b)) ? (a) : (b))
|
||
|
||
BOOL
|
||
USER_DATA::StartupSession(IN PVOID pvInitialRequest,
|
||
IN DWORD cbInitialRequest
|
||
)
|
||
/*++
|
||
This function allocates a buffer for receiving request from the client
|
||
and also sets up initial read from the control socket to
|
||
get client requests.
|
||
|
||
Arguments:
|
||
pvInitialRequest pointer to initial request buffer
|
||
cbInitialRequest count of bytes of data in the initial request
|
||
|
||
Returns:
|
||
TRUE on success and FALSE if there is any failure.
|
||
|
||
--*/
|
||
{
|
||
SOCKERR serr;
|
||
BOOL fReturn = FALSE;
|
||
PCSTR pszBanner;
|
||
|
||
# if DBG
|
||
|
||
if ( !IS_VALID_USER_DATA( this)) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"Encountering an invalid user data ( %08x)\n",
|
||
this));
|
||
Print();
|
||
}
|
||
# endif // DBG
|
||
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( this ) );
|
||
|
||
DBG_ASSERT( QueryState() == UserStateEmbryonic);
|
||
|
||
//
|
||
// Reply to the initial connection message. ( Greet the new user).
|
||
//
|
||
|
||
pszBanner = QueryInstance()->QueryBannerMsg();
|
||
|
||
if( pszBanner && *pszBanner ) {
|
||
serr = SendMultilineMessage(
|
||
REPLY_SERVICE_READY,
|
||
g_FtpServiceNameString,
|
||
TRUE,
|
||
FALSE);
|
||
|
||
serr = serr || SendMultilineMessage(
|
||
REPLY_SERVICE_READY,
|
||
pszBanner,
|
||
FALSE,
|
||
TRUE);
|
||
} else {
|
||
serr = ReplyToUser( this,
|
||
REPLY_SERVICE_READY,
|
||
"%s",
|
||
g_FtpServiceNameString );
|
||
}
|
||
|
||
if ( serr != 0) {
|
||
|
||
IF_DEBUG( ERROR) {
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" Cannot reply with initial connection message."
|
||
" Error = %lu\n",
|
||
serr));
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// enable OOB_INLINE since we are using that for our control socket
|
||
//
|
||
BOOL fOobInline = TRUE;
|
||
|
||
serr = setsockopt( QueryControlSocket(), SOL_SOCKET,
|
||
SO_OOBINLINE, (const char *) &fOobInline,
|
||
sizeof( fOobInline));
|
||
|
||
m_cchPartialReqRecvd = 0;
|
||
|
||
if ( serr == 0) {
|
||
|
||
//
|
||
// Try to set up the buffer and enter the mode for reading
|
||
// requests from the client
|
||
//
|
||
|
||
LockUser();
|
||
if( QueryState() != UserStateDisconnected ) {
|
||
SetState( UserStateWaitingForUser);
|
||
}
|
||
UnlockUser();
|
||
|
||
if ( pvInitialRequest != NULL && cbInitialRequest > 0) {
|
||
|
||
//
|
||
// No need to issue a read, since we have the data required.
|
||
// Do a safe copy to the buffer.
|
||
//
|
||
|
||
CopyMemory( QueryReceiveBuffer(), pvInitialRequest,
|
||
min( cbInitialRequest, QueryReceiveBufferSize())
|
||
);
|
||
|
||
fReturn = TRUE;
|
||
|
||
} else {
|
||
|
||
fReturn = ReadCommand();
|
||
}
|
||
|
||
} else {
|
||
|
||
IF_DEBUG( ERROR) {
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
" SetsockOpt( OOB_INLINE) failed. Error = %lu\n",
|
||
WSAGetLastError()));
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
IF_DEBUG( CLIENT) {
|
||
|
||
DWORD dwError = (fReturn) ? NO_ERROR : GetLastError();
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" connection ( %08x)::StartupSession() returns %d."
|
||
" Error = %lu\n",
|
||
this, fReturn,
|
||
dwError));
|
||
|
||
if (fReturn) { SetLastError( dwError); }
|
||
}
|
||
|
||
return ( fReturn);
|
||
|
||
} // USER_DATA::StartupSession()
|
||
|
||
|
||
|
||
VOID
|
||
CheckAndProcessAbortOperation( IN LPUSER_DATA pUserData)
|
||
{
|
||
if ( TEST_UF( pUserData, OOB_ABORT)) {
|
||
|
||
//
|
||
// An abort was requested by client. So our processing
|
||
// has unwound and we are supposed to send some message
|
||
// to the client. ==> simulate processing ABOR command
|
||
// ABORT was not processed yet; so process now.
|
||
//
|
||
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"Executing simulated Abort for %08x\n",
|
||
pUserData));
|
||
|
||
FacIncrement( FacSimulatedAborts);
|
||
|
||
// To avoid thread races, check twice.
|
||
|
||
if ( TEST_UF( pUserData, OOB_ABORT)) {
|
||
|
||
//
|
||
// we need this stack variable (szAbort), so that
|
||
// ParseCommand() can freely modify the string!
|
||
CHAR szAbort[10];
|
||
|
||
CLEAR_UF( pUserData, OOB_ABORT);
|
||
|
||
CopyMemory( szAbort, "ABOR", sizeof("ABOR"));
|
||
ParseCommand( pUserData, szAbort);
|
||
}
|
||
}
|
||
|
||
return;
|
||
|
||
} // CheckAndProcessAbortOperation()
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::ParseAndProcessRequest(IN DWORD cchRequest)
|
||
/*++
|
||
This function parses the incoming request from client, identifies the
|
||
command to execute and executes the same.
|
||
Before parsing, the input is pre-processed to remove any of telnet commands
|
||
or OOB_inlined data.
|
||
|
||
Arguments:
|
||
cchRequest count of characters of request received.
|
||
|
||
--*/
|
||
{
|
||
BOOL fLineEnded = FALSE;
|
||
DWORD cchRequestRecvd = 0;
|
||
CHAR szCommandLine[ MAX_COMMAND_LENGTH + 1];
|
||
|
||
# if DBG
|
||
|
||
if ( !IS_VALID_USER_DATA( this))
|
||
{
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"Encountering an invalid user data ( %08x)\n",
|
||
this));
|
||
Print();
|
||
}
|
||
# endif // DBG
|
||
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( this ) );
|
||
|
||
IF_DEBUG( CLIENT)
|
||
{
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"UserData(%08x)::ParseAndProcessRequest( %d chars)\n",
|
||
this, cchRequest));
|
||
}
|
||
|
||
|
||
//
|
||
// Fast-path if we're re-processing this command, which happens in PASV mode
|
||
//
|
||
if ( QueryInFakeIOCompletion() )
|
||
{
|
||
goto FastPathLabel;
|
||
}
|
||
|
||
|
||
if ( cchRequest > 0)
|
||
{
|
||
// We have a valid request. Process it
|
||
|
||
// Update last access time
|
||
m_TimeAtLastAccess = GetCurrentTimeInSeconds();
|
||
|
||
m_pInstance->QueryStatsObj()->UpdateTotalBytesReceived(
|
||
cchRequest*sizeof(CHAR));
|
||
|
||
if ( m_cchPartialReqRecvd + cchRequest >= MAX_COMMAND_LENGTH)
|
||
{
|
||
|
||
CHAR szCmdFailed[600];
|
||
wsprintfA( szCmdFailed,
|
||
" Command is too long: Partial=%d bytes. Now=%d \n"
|
||
" UserDb(%08x) = %s from Host: %s\n",
|
||
m_cchPartialReqRecvd, cchRequest,
|
||
this, QueryUserName(), QueryClientHostName());
|
||
|
||
DBGPRINTF((DBG_CONTEXT, szCmdFailed));
|
||
|
||
DisconnectUserWithError( ERROR_BUSY);
|
||
|
||
return ( TRUE); // we are done with this connection.
|
||
}
|
||
|
||
CopyMemory(szCommandLine, m_recvBuffer,
|
||
m_cchPartialReqRecvd + cchRequest);
|
||
szCommandLine[m_cchPartialReqRecvd + cchRequest] = '\0';
|
||
|
||
if ( !::FilterTelnetCommands(szCommandLine,
|
||
m_cchPartialReqRecvd + cchRequest,
|
||
&fLineEnded, &cchRequestRecvd))
|
||
{
|
||
|
||
if ( TEST_UF( this, TRANSFER))
|
||
{
|
||
|
||
//
|
||
// I am in data transfer mode. Some other thread is sending
|
||
// data for this client. Just post a OOB_DATA and OOB_ABORT
|
||
// OOB_DATA will cause the call-stack of other thread to unwind
|
||
// and get out of the command.
|
||
// Then check if any async transfer was occuring. If so
|
||
// process abort with disconnect now.
|
||
//
|
||
|
||
SET_UF_BITS( this, (UF_OOB_DATA | UF_OOB_ABORT));
|
||
|
||
if ( TEST_UF( this, ASYNC_TRANSFER))
|
||
{
|
||
|
||
//
|
||
// An async transfer is occuring. Stop it
|
||
//
|
||
DestroyDataConnection( ERROR_OPERATION_ABORTED);
|
||
|
||
CheckAndProcessAbortOperation( this);
|
||
}
|
||
|
||
# ifdef CHECK_DBG
|
||
|
||
Print( " OOB_ABORT ");
|
||
|
||
# endif // CHECK_DBG
|
||
|
||
IF_DEBUG( CLIENT) {
|
||
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"[%08x]Set up the implied ABORT command\n",
|
||
this));
|
||
}
|
||
|
||
IF_DEBUG( COMMANDS) {
|
||
|
||
DBGPRINTF((DBG_CONTEXT, " ***** [%08x] OOB_ABORT Set \n",
|
||
this));
|
||
}
|
||
|
||
// Ignore the rest of the commands that may have come in.
|
||
}
|
||
else
|
||
{
|
||
|
||
//
|
||
// Since no command is getting processed.
|
||
// atleast process the abort command, otherwise clients hang.
|
||
//
|
||
|
||
//
|
||
// we need this stack variable (szAbort), so that
|
||
// ParseCommand() can freely modify the string!
|
||
CHAR szAbort[10];
|
||
|
||
CopyMemory( szAbort, "ABOR", sizeof("ABOR"));
|
||
ParseCommand( this, szAbort);
|
||
CLEAR_UF( this, OOB_ABORT); // clear the abort flag!
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
if ( TEST_UF( this, TRANSFER))
|
||
{
|
||
|
||
//
|
||
// we are transferring data, sorry no more commands accepted.
|
||
// This could hang clients. Hey! they asked for it :( NYI
|
||
//
|
||
|
||
// Do nothing
|
||
IF_DEBUG( COMMANDS) {
|
||
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"***** [%08x] Received Request %s during"
|
||
" transfer in progress\n",
|
||
this, szCommandLine));
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
//
|
||
// Let ParseCommand do the dirty work.
|
||
//
|
||
|
||
|
||
// Remember the count of partial bytes received.
|
||
m_cchPartialReqRecvd = cchRequestRecvd;
|
||
|
||
if ( !fLineEnded)
|
||
{
|
||
|
||
// In case if command was long enough to fill all buffer but
|
||
// we haven't found new line simply tell to user about the error
|
||
// and disconnect. Some ftp clients will not see that msg, becuase
|
||
// connection was disconnected, but thats a bug in client code
|
||
|
||
if ( m_cchPartialReqRecvd >= MAX_COMMAND_LENGTH - 1)
|
||
{
|
||
ReplyToUser( this,
|
||
REPLY_UNRECOGNIZED_COMMAND,
|
||
PSZ_COMMAND_TOO_LONG);
|
||
DisconnectUserWithError( ERROR_BUSY );
|
||
|
||
return ( TRUE); // we are done with this connection.
|
||
}
|
||
|
||
|
||
//
|
||
// Complete line is not received. Continue reading
|
||
// the requests, till we receive the complete request
|
||
//
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
StartProcessingTimer();
|
||
|
||
//
|
||
// set the partial received byte count to zero.
|
||
// we will not use this value till next incomplete request
|
||
//
|
||
|
||
m_cchPartialReqRecvd = 0;
|
||
|
||
FastPathLabel:
|
||
ParseCommand( this, ( QueryInFakeIOCompletion() ? QueryCmdString() :
|
||
szCommandLine ) );
|
||
|
||
CheckAndProcessAbortOperation( this);
|
||
|
||
} // if TRANSFER is not there...
|
||
|
||
} //Parse if complete
|
||
|
||
} // if FilterTelnetCommands()
|
||
}
|
||
else
|
||
{
|
||
// if (cchRequest <= 0)
|
||
|
||
SET_UF( this, CONTROL_ZERO);
|
||
|
||
//
|
||
// after a quit a client is expected to wait for quit message from
|
||
// the server. if the client prematurely closes connection, then
|
||
// the server receives it as a receive with zero byte read.
|
||
// since, we should not be having outstanding read at this time,
|
||
// atq should not be calling us. On the contrary we are getting
|
||
// called by ATQ. Let us track this down.
|
||
//
|
||
|
||
if ( !TEST_UF( this, CONTROL_QUIT))
|
||
{
|
||
DisconnectUserWithError( NO_ERROR);
|
||
}
|
||
else
|
||
{
|
||
|
||
// Quit message is received and then ZeroBytes Received!!
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
" (%08x)::ZeroBytes recvd after QUIT message!!."
|
||
" State = %d(%x), Ref = %d\n",
|
||
this,
|
||
QueryState(), Flags,
|
||
QueryReference()
|
||
));
|
||
// Do nothing. Since Quit will take care of cleanup
|
||
return (TRUE);
|
||
}
|
||
}
|
||
|
||
//
|
||
// If the connection is not yet disconnected, submit a read command.
|
||
// else return that everything is fine (someone had disconnected it).
|
||
//
|
||
|
||
return ( IsDisconnected() ? TRUE : ReadCommand());
|
||
} // USER_DATA::ParseAndProcessRequest()
|
||
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::ReadCommand( VOID)
|
||
{
|
||
BOOL fReturn = TRUE;
|
||
|
||
DBG_CODE(
|
||
if ( !IS_VALID_USER_DATA( this)) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"Encountering an invalid user data ( %08x)\n",
|
||
this));
|
||
Print();
|
||
}
|
||
);
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( this ) );
|
||
if ( TEST_UF( this, CONTROL_TIMEDOUT) || IsDisconnected()) {
|
||
|
||
SetLastError( ERROR_SEM_TIMEOUT);
|
||
return (FALSE);
|
||
}
|
||
|
||
//
|
||
// Submit a read on control socket only if there is none pending!
|
||
// Otherwise, behave in idempotent manner.
|
||
//
|
||
|
||
if ( !TEST_UF( this, CONTROL_READ)) {
|
||
|
||
Reference(); // since we are going to set up async read.
|
||
|
||
InterlockedIncrement( &m_nControlRead);
|
||
|
||
DBG_ASSERT( m_nControlRead <= 1);
|
||
|
||
SET_UF( this, CONTROL_READ); // a read will be pending
|
||
|
||
if ( !m_AioControlConnection.ReadFile(QueryReceiveBuffer(),
|
||
QueryReceiveBufferSize())
|
||
) {
|
||
|
||
CLEAR_UF( this, CONTROL_READ); // since read failed.
|
||
|
||
DBG_REQUIRE( DeReference() > 0);
|
||
InterlockedDecrement( &m_nControlRead);
|
||
|
||
DWORD dwError = GetLastError();
|
||
|
||
IF_DEBUG( ERROR) {
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" User( %08x)::ReadCommand() failed. Ref = %d."
|
||
" Error = %d\n",
|
||
this, QueryReference(), dwError));
|
||
}
|
||
|
||
SetLastError( dwError);
|
||
fReturn = FALSE;
|
||
}
|
||
|
||
}
|
||
|
||
return ( fReturn);
|
||
} // USER_DATA::ReadCommand()
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::DisconnectUserWithError(IN DWORD dwError,
|
||
IN BOOL fNextMsg OPTIONAL)
|
||
/*++
|
||
This function disconnects a user with the error code provided.
|
||
It closes down the control connection by stopping ASYNC_IO.
|
||
If the fNextMsg is not set, then it also decrements the reference count
|
||
for the user data object, to be freed soon.
|
||
|
||
--*/
|
||
{
|
||
CHAR szBuffer[120];
|
||
|
||
# if DBG
|
||
|
||
if ( !IS_VALID_USER_DATA( this)) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"Encountering an invalid user data ( %08x)\n",
|
||
this));
|
||
Print();
|
||
}
|
||
# endif // DBG
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( this ) );
|
||
|
||
IF_DEBUG ( CLIENT) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" USER_DATA( %08x)::DisconnectUserWithError( %lu, %d)."
|
||
" RefCount = %d\n",
|
||
this, dwError, fNextMsg, QueryReference()));
|
||
}
|
||
|
||
|
||
if (!fNextMsg) {
|
||
RemoveActiveReference();
|
||
}
|
||
|
||
LockUser();
|
||
|
||
if ( QueryState() == UserStateDisconnected) {
|
||
|
||
//
|
||
// It is already in disconnected state. Do nothing for disconnect.
|
||
//
|
||
|
||
UnlockUser();
|
||
|
||
} else {
|
||
|
||
SetState( UserStateDisconnected );
|
||
UnlockUser();
|
||
|
||
if( dwError == ERROR_SEM_TIMEOUT) {
|
||
|
||
const CHAR * apszSubStrings[3];
|
||
|
||
IF_DEBUG( CLIENT )
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"client (%08x) timed-out\n", this ));
|
||
}
|
||
|
||
sprintf( szBuffer, "%lu", m_pInstance->QueryConnectionTimeout() );
|
||
|
||
apszSubStrings[0] = QueryUserName();
|
||
apszSubStrings[1] = inet_ntoa( HostIpAddress );
|
||
apszSubStrings[2] = szBuffer;
|
||
|
||
g_pInetSvc->LogEvent( FTPD_EVENT_CLIENT_TIMEOUT,
|
||
3,
|
||
apszSubStrings,
|
||
0 );
|
||
|
||
ReplyToUser(this,
|
||
REPLY_SERVICE_NOT_AVAILABLE,
|
||
"Timeout (%lu seconds): closing control connection.",
|
||
m_pInstance->QueryConnectionTimeout() );
|
||
}
|
||
|
||
if ( dwError != NO_ERROR) {
|
||
|
||
# ifdef CHECK_DBG
|
||
sprintf( szBuffer, " Control Socket Error=%u ", dwError);
|
||
Print( szBuffer);
|
||
# endif // CHECK_DBG
|
||
|
||
if( dwError != ERROR_SEM_TIMEOUT ) {
|
||
SetLastReplyCode( REPLY_TRANSFER_ABORTED );
|
||
}
|
||
|
||
// Produce a log record indicating the cause for failure.
|
||
WriteLogRecord( PSZ_CONNECTION_CLOSED_VERB, "", dwError);
|
||
}
|
||
|
||
//
|
||
// Force close the connection's sockets. This will cause the
|
||
// thread to awaken from any blocked socket operation. It
|
||
// is the destructor's responsibility to do any further cleanup.
|
||
// (such as calling UserDereference()).
|
||
//
|
||
|
||
CloseSockets(dwError != NO_ERROR);
|
||
}
|
||
|
||
return ( TRUE);
|
||
|
||
} // USER_DATA::DisconnectUserWithError()
|
||
|
||
|
||
|
||
|
||
|
||
|
||
static BOOL
|
||
DisconnectUserWorker( IN LPUSER_DATA pUserData, IN LPVOID pContext)
|
||
/*++
|
||
This disconnects (logically) a user connection, by resetting the
|
||
control connection and stopping IO. Later on the blown away socket
|
||
will cause an ATQ relinquish to occur to blow away of this connection.
|
||
|
||
Arguments:
|
||
pUserData pointer to User data object for connection to be disconnected.
|
||
pContext pointer to context information
|
||
( in this case to DWORD containing error code indicating reasong for
|
||
disconnect).
|
||
|
||
Returns:
|
||
TRUE on success and FALSE if there is any failure.
|
||
|
||
--*/
|
||
{
|
||
DWORD dwError;
|
||
BOOL retVal;
|
||
DBG_ASSERT( pContext != NULL && pUserData != NULL);
|
||
DBG_ASSERT( IS_VALID_USER_DATA( pUserData ) );
|
||
|
||
|
||
dwError = *(LPDWORD ) pContext;
|
||
|
||
|
||
retVal = pUserData->DisconnectUserWithError( dwError, TRUE);
|
||
|
||
// fix for bug 268175 : if we disconnected user we need to do normal cleanup
|
||
// for that connection
|
||
|
||
// this check is not very necessary but I leave it for future
|
||
// DisconnectUserWithError always returns TRUE
|
||
|
||
if (retVal)
|
||
{
|
||
DereferenceUserDataAndKill(pUserData);
|
||
}
|
||
|
||
|
||
return retVal;
|
||
} // DisconnectUserWorker()
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
DisconnectUser( IN DWORD UserId, FTP_SERVER_INSTANCE *pInstance )
|
||
/*++
|
||
This function disconnects a specified user identified using the UserId.
|
||
If UserId specified == 0, then all the users will be disconnected.
|
||
|
||
Arguments:
|
||
UserId user id for the connection to be disconnected.
|
||
|
||
Returns:
|
||
TRUE if atleast one of the connections is disconnected.
|
||
FALSE if no user connetion found.
|
||
|
||
History:
|
||
06-April-1995 Created.
|
||
--*/
|
||
{
|
||
BOOL fFound;
|
||
DWORD dwError = ERROR_SERVER_DISABLED;
|
||
|
||
pInstance->Reference();
|
||
pInstance->LockConnectionsList();
|
||
|
||
fFound = ( pInstance->
|
||
EnumerateConnection( DisconnectUserWorker,
|
||
(LPVOID ) &dwError,
|
||
UserId));
|
||
|
||
pInstance->UnlockConnectionsList();
|
||
pInstance->Dereference();
|
||
|
||
IF_DEBUG( CLIENT) {
|
||
|
||
DWORD dwError = (fFound) ? NO_ERROR: GetLastError();
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" DisconnectUser( %d) returns %d. Error = %lu\n",
|
||
UserId, fFound, dwError));
|
||
|
||
if (fFound) { SetLastError( dwError); }
|
||
}
|
||
|
||
return ( fFound);
|
||
} // DisconnectUser()
|
||
|
||
|
||
|
||
|
||
|
||
static BOOL
|
||
DisconnectUserWithNoAccessWorker( IN LPUSER_DATA pUserData,
|
||
IN LPVOID pContext)
|
||
/*++
|
||
This disconnects (logically) a user connection with no access.
|
||
This occurs by resetting the control connection and stopping IO.
|
||
Later on the blown away thread
|
||
will cause an ATQ relinquish to occur to blow away of this connection.
|
||
|
||
Arguments:
|
||
pUserData pointer to User data object for connection to be disconnected.
|
||
pContext pointer to context information
|
||
( in this case to DWORD containing error code indicating reasong for
|
||
disconnect).
|
||
|
||
Returns:
|
||
TRUE on success and FALSE if there is any failure.
|
||
|
||
--*/
|
||
{
|
||
BOOL fSuccess = TRUE;
|
||
DBG_ASSERT( pUserData != NULL);
|
||
|
||
// Ignode the pContext information.
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( pUserData ) );
|
||
|
||
//
|
||
// We're only interested in connected users.
|
||
//
|
||
|
||
if( pUserData->IsLoggedOn()) {
|
||
|
||
//
|
||
// If this user no longer has access to their
|
||
// current directory, blow them away.
|
||
//
|
||
|
||
if( !pUserData->VirtualPathAccessCheck(AccessTypeRead )) {
|
||
|
||
const CHAR * apszSubStrings[2];
|
||
|
||
IF_DEBUG( SECURITY ) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"User %s (%lu) @ %08lX retroactively"
|
||
" denied access to %s\n",
|
||
pUserData->QueryUserName(),
|
||
pUserData->QueryId(),
|
||
pUserData,
|
||
pUserData->QueryCurrentDirectory() ));
|
||
}
|
||
|
||
|
||
fSuccess = ( pUserData->
|
||
DisconnectUserWithError(ERROR_ACCESS_DENIED,
|
||
TRUE)
|
||
);
|
||
|
||
//
|
||
// Log an event to tell the admin what happened.
|
||
//
|
||
|
||
apszSubStrings[0] = pUserData->QueryUserName();
|
||
apszSubStrings[1] = pUserData->QueryCurrentDirectory();
|
||
|
||
g_pInetSvc->LogEvent( FTPD_EVENT_RETRO_ACCESS_DENIED,
|
||
2,
|
||
apszSubStrings,
|
||
0 );
|
||
} // no access
|
||
|
||
} // logged on user
|
||
|
||
IF_DEBUG( CLIENT) {
|
||
|
||
DWORD dwError = (fSuccess) ? NO_ERROR: GetLastError();
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" DisconnectUsersWithNoAccessWorker( %d) returns %d."
|
||
" Error = %lu\n",
|
||
pUserData->QueryId(), fSuccess,
|
||
dwError)
|
||
);
|
||
|
||
if (fSuccess) { SetLastError( dwError); }
|
||
}
|
||
|
||
return ( fSuccess);
|
||
} // DisconnectUserWithNoAccessWorker()
|
||
|
||
|
||
|
||
VOID
|
||
DisconnectUsersWithNoAccess(FTP_SERVER_INSTANCE *pInstance )
|
||
/*++
|
||
This function disconnects all users who do not have read access to
|
||
their current directory. This is typically called when the access masks
|
||
have been changed.
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Returns:
|
||
None.
|
||
--*/
|
||
{
|
||
BOOL fFound;
|
||
DWORD dwError = ERROR_ACCESS_DENIED;
|
||
|
||
pInstance->Reference();
|
||
pInstance->LockConnectionsList();
|
||
|
||
fFound = ( pInstance->
|
||
EnumerateConnection( DisconnectUserWithNoAccessWorker,
|
||
(LPVOID ) &dwError,
|
||
0));
|
||
|
||
pInstance->UnlockConnectionsList();
|
||
pInstance->Dereference();
|
||
|
||
IF_DEBUG( CLIENT) {
|
||
|
||
DWORD dwError = (fFound) ? NO_ERROR: GetLastError();
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" DisconnectUsersWithNoAccess() returns %d."
|
||
" Error = %lu\n",
|
||
fFound, dwError)
|
||
);
|
||
|
||
if (fFound) { SetLastError( dwError); }
|
||
}
|
||
|
||
|
||
} // DisconnectUsersWithNoAccess
|
||
|
||
|
||
|
||
|
||
/*++
|
||
The following structure UserEnumBuffer is required to carry the context
|
||
information for enumerating the users currently connected.
|
||
It contains a pointer to array of USER_INFO structures which contain the
|
||
specific information for the user. The user name is stored in the buffer
|
||
from the end ( so that null terminated strings are formed back to back.
|
||
This permits efficient storage of variable length strings.
|
||
|
||
The member fResult is used to carry forward the partial result of
|
||
success/failure from one user to another ( since the enumeration has
|
||
to walk through all the elements to find out all user information).
|
||
|
||
|
||
History: MuraliK ( 12-April-1995)
|
||
|
||
--*/
|
||
struct USER_ENUM_BUFFER {
|
||
|
||
DWORD cbSize; // pointer to dword containing size of
|
||
IIS_USER_INFO_1 * pUserInfo; // pointer to start of array of USER_INFO
|
||
DWORD cbRequired; // incremental count of bytes required.
|
||
DWORD nEntry; // number of current entry ( index into pUserInfo)
|
||
DWORD dwCurrentTime; // current time
|
||
WCHAR * pszNext; // pointer to next string location.
|
||
BOOL fResult; // boolean flag accumulating partial results
|
||
};
|
||
|
||
typedef USER_ENUM_BUFFER * PUSER_ENUM_BUFFER;
|
||
|
||
|
||
BOOL
|
||
EnumerateUserInBufferWorker( IN LPUSER_DATA pUserData,
|
||
IN LPVOID pContext)
|
||
{
|
||
# ifdef CHECK_DBG
|
||
CHAR szBuffer[400];
|
||
# endif // CHECK_DBG
|
||
|
||
PUSER_ENUM_BUFFER pUserEnumBuffer = (PUSER_ENUM_BUFFER ) pContext;
|
||
DWORD tConnect;
|
||
DWORD cbUserName;
|
||
LPDWORD pcbBuffer;
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( pUserData ) );
|
||
|
||
//
|
||
// We're only interested in connected users.
|
||
//
|
||
|
||
if( pUserData->IsDisconnected()) {
|
||
|
||
return ( TRUE);
|
||
}
|
||
|
||
//
|
||
// Determine required buffer size for current user.
|
||
//
|
||
|
||
cbUserName = ( strlen( pUserData->QueryUserName() ) + 1 ) * sizeof(WCHAR);
|
||
pUserEnumBuffer->cbRequired += sizeof(IIS_USER_INFO_1);
|
||
|
||
//
|
||
// If there's room for the user data, store it.
|
||
//
|
||
|
||
tConnect = ( pUserEnumBuffer->dwCurrentTime -
|
||
pUserData->QueryTimeAtConnection());
|
||
|
||
if( pUserEnumBuffer->fResult &&
|
||
( pUserEnumBuffer->cbRequired <= pUserEnumBuffer->cbSize)
|
||
) {
|
||
|
||
LPIIS_USER_INFO_1 pUserInfo =
|
||
&pUserEnumBuffer->pUserInfo[ pUserEnumBuffer->nEntry];
|
||
|
||
pUserInfo->idUser = pUserData->QueryId();
|
||
pUserInfo->pszUser = (WCHAR *)MIDL_user_allocate( cbUserName );
|
||
|
||
if( pUserInfo->pszUser ) {
|
||
|
||
pUserInfo->fAnonymous = ( pUserData->Flags & UF_ANONYMOUS ) != 0;
|
||
pUserInfo->inetHost = (DWORD)pUserData->HostIpAddress.s_addr;
|
||
pUserInfo->tConnect = tConnect;
|
||
|
||
if( !MultiByteToWideChar( CP_OEMCP,
|
||
0,
|
||
pUserData->QueryUserName(),
|
||
-1,
|
||
pUserInfo->pszUser,
|
||
(int)cbUserName )
|
||
) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"MultiByteToWideChar failed???\n" ));
|
||
|
||
pUserEnumBuffer->fResult = ( pUserEnumBuffer->fResult && FALSE);
|
||
} else {
|
||
pUserEnumBuffer->nEntry++;
|
||
}
|
||
}
|
||
else {
|
||
|
||
//
|
||
// Unable to allocate memory
|
||
//
|
||
pUserEnumBuffer->fResult = ( pUserEnumBuffer->fResult && FALSE);
|
||
}
|
||
|
||
} else {
|
||
|
||
pUserEnumBuffer->fResult = ( pUserEnumBuffer->fResult && FALSE);
|
||
}
|
||
|
||
# ifdef CHECK_DBG
|
||
|
||
sprintf( szBuffer, " Enum tLastAction=%u; tConnect=%u. " ,
|
||
( pUserEnumBuffer->dwCurrentTime -
|
||
pUserData->QueryTimeAtLastAccess()),
|
||
tConnect
|
||
);
|
||
|
||
pUserData->Print( szBuffer);
|
||
|
||
# endif // CHECK_DBG
|
||
|
||
return ( TRUE);
|
||
} // EnumerateUserInBufferWorker()
|
||
|
||
|
||
|
||
BOOL
|
||
EnumerateUsers(
|
||
PCHAR pBuffer,
|
||
PDWORD pcbBuffer,
|
||
PDWORD nRead,
|
||
FTP_SERVER_INSTANCE *pInstance
|
||
)
|
||
/*++
|
||
Enumerates the current active users into the specified buffer.
|
||
|
||
Arguments:
|
||
pvEnum pointer to enumeration buffer which will receive the number of
|
||
entries and the user information.
|
||
pcbBuffer pointer to count of bytes. On entry this contains the size in
|
||
bytes of the enumeration buffer. It receives the count
|
||
of bytes for enumerating all the users.
|
||
nRead - pointer to a DWORD to return the number of user entries filled.
|
||
|
||
Returns:
|
||
TRUE if enumeration is successful ( all connected users accounted for)
|
||
FALSE otherwise
|
||
|
||
--*/
|
||
{
|
||
USER_ENUM_BUFFER userEnumBuffer;
|
||
BOOL fSuccess;
|
||
|
||
DBG_ASSERT( pcbBuffer != NULL );
|
||
|
||
IF_DEBUG( USER_DATABASE) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" Entering EnumerateUsers( %08x, %08x[%d]).\n",
|
||
pBuffer, pcbBuffer, *pcbBuffer));
|
||
}
|
||
|
||
//
|
||
// Setup the data in user enumeration buffer.
|
||
//
|
||
|
||
userEnumBuffer.cbSize = *pcbBuffer;
|
||
userEnumBuffer.cbRequired = 0;
|
||
userEnumBuffer.pUserInfo = (LPIIS_USER_INFO_1)pBuffer;
|
||
userEnumBuffer.nEntry = 0;
|
||
userEnumBuffer.dwCurrentTime = GetCurrentTimeInSeconds();
|
||
userEnumBuffer.fResult = TRUE;
|
||
|
||
//
|
||
// CODEWORK
|
||
// This field is obsolete it now points to the extra CONN_LEEWAY
|
||
// buffer.
|
||
//
|
||
userEnumBuffer.pszNext = ((WCHAR *)( pBuffer + *pcbBuffer));
|
||
|
||
|
||
//
|
||
// Scan the users and get the information required.
|
||
//
|
||
|
||
pInstance->Reference();
|
||
pInstance->LockConnectionsList();
|
||
|
||
fSuccess = (pInstance->
|
||
EnumerateConnection( EnumerateUserInBufferWorker,
|
||
(LPVOID ) &userEnumBuffer,
|
||
0));
|
||
|
||
pInstance->UnlockConnectionsList();
|
||
pInstance->Dereference();
|
||
|
||
//
|
||
// Update enum buffer header.
|
||
//
|
||
|
||
*nRead = userEnumBuffer.nEntry;
|
||
*pcbBuffer = userEnumBuffer.cbRequired;
|
||
|
||
IF_DEBUG( USER_DATABASE) {
|
||
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
" Leaving EnumerateUsers() with %d."
|
||
" Entries read =%d. BufferSize required = %d\n",
|
||
userEnumBuffer.fResult,
|
||
userEnumBuffer.nEntry, userEnumBuffer.cbRequired));
|
||
}
|
||
|
||
return ( userEnumBuffer.fResult);
|
||
|
||
} // EnumerateUsers
|
||
|
||
|
||
|
||
|
||
SOCKERR
|
||
USER_DATA::SendMultilineMessage(
|
||
IN UINT nReplyCode,
|
||
IN LPCSTR pszzMessage,
|
||
IN BOOL fIsFirst,
|
||
IN BOOL fIsLast)
|
||
/*++
|
||
Sends a multiline message to the control socket of the client.
|
||
|
||
Arguments:
|
||
nReplyCode the reply code to use for the first line of the multi-line
|
||
message.
|
||
pszzMessage pointer to double null terminated sequence of strings
|
||
containing the message to be sent.
|
||
fIsFirst flag to indicate we are starting the multiline reply. if FALSE,
|
||
don't print the code for the first line, as it was already emmited elsewhere
|
||
fIsLast flag to indicate we are finishing the multiline reply. if FALSE,
|
||
don't print the code for the first line, as it was already emmited elsewhere
|
||
|
||
If the message is empty, we do not print anything. If there is only one line, then if
|
||
fIsLast is TRUE, we only print the terminating line, otherwise we do print the openning
|
||
line if fIsFirst is TRUE.
|
||
|
||
Returns:
|
||
SOCKERR - 0 if successful, !0 if not.
|
||
|
||
History:
|
||
MuraliK 12-April-1995
|
||
--*/
|
||
{
|
||
SOCKERR serr = 0;
|
||
LPCSTR pszMsg, pszNext;
|
||
|
||
//
|
||
// return if there is nothing to send
|
||
//
|
||
|
||
if ( pszzMessage == NULL || *pszzMessage == '\0') {
|
||
return serr;
|
||
}
|
||
|
||
|
||
for ( pszMsg = pszzMessage; serr == 0 && *pszMsg != '\0'; pszMsg = pszNext) {
|
||
|
||
//
|
||
// find next message so that we can check of pszMsg is the last line
|
||
//
|
||
|
||
pszNext = pszMsg + strlen( pszMsg) + 1;
|
||
|
||
if( fIsLast && *pszNext == '\0' ) {
|
||
//
|
||
// This is globally the last line. Print it pefixed with the reply code.
|
||
//
|
||
serr = SockPrintf2(this, QueryControlSocket(),
|
||
"%u %s",
|
||
nReplyCode,
|
||
pszMsg);
|
||
|
||
} else if( fIsFirst ) {
|
||
//
|
||
// this is globally the first line of reply, and it is not globally the last one.
|
||
// print it with '-'.
|
||
//
|
||
serr = SockPrintf2(this, QueryControlSocket(),
|
||
"%u-%s",
|
||
nReplyCode,
|
||
pszMsg);
|
||
|
||
fIsFirst = FALSE;
|
||
} else {
|
||
//
|
||
// this is either an intermediate line, or the last line in this batch (but
|
||
// not globally), so print it idented without the reply code.
|
||
//
|
||
serr = SockPrintf2(this, QueryControlSocket(),
|
||
" %s",
|
||
pszMsg);
|
||
}
|
||
} // for
|
||
|
||
return ( serr);
|
||
|
||
} // USER_DATA::SendMultilineMessge()
|
||
|
||
|
||
|
||
|
||
|
||
|
||
SOCKERR
|
||
USER_DATA::SendDirectoryAnnotation( IN UINT ReplyCode, IN BOOL fIsFirst)
|
||
/*++
|
||
SYNOPSIS: Tries to open the FTPD_ANNOTATION_FILE (~~ftpsvc~~.ckm)
|
||
file in the user's current directory. If it can be
|
||
opened, it is sent to the user over the command socket
|
||
as a multi-line reply.
|
||
|
||
ENTRY:
|
||
ReplyCode - The reply code to send as the first line
|
||
of this multi-line reply.
|
||
|
||
fIsFirst - flag to indicate if this is the first line in the multi-line
|
||
reply. If not, the ReplyCode is not shown
|
||
|
||
RETURNS: SOCKERR - 0 if successful, !0 if not.
|
||
|
||
HISTORY:
|
||
KeithMo 06-May-1993 Created.
|
||
MuraliK 12-Apr-1995 Made it to be part of USER_DATA
|
||
--*/
|
||
{
|
||
FILE * pfile;
|
||
SOCKERR serr = 0;
|
||
CHAR szLine[MAX_REPLY_LENGTH+1];
|
||
|
||
|
||
//
|
||
// Try to open the annotation file.
|
||
//
|
||
|
||
pfile = Virtual_fopen( this,
|
||
FTPD_ANNOTATION_FILE,
|
||
"r" );
|
||
|
||
if( pfile == NULL )
|
||
{
|
||
//
|
||
// File not found. Blow it off.
|
||
//
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
// protection agians attack when CKM file islarge, somebody is downloading it
|
||
// slowly on many connections and uses all ATQ threads. Note that attack is still possible
|
||
// but much more difficult to achieve
|
||
|
||
AtqSetInfo( AtqIncMaxPoolThreads, 0);
|
||
|
||
//
|
||
// While there's more text in the file, blast
|
||
// it to the user.
|
||
//
|
||
|
||
while( fgets( szLine, MAX_REPLY_LENGTH, pfile ) != NULL )
|
||
{
|
||
CHAR * pszTmp = szLine + strlen(szLine) - 1;
|
||
|
||
//
|
||
// Remove any trailing CR/LFs in the string.
|
||
//
|
||
|
||
while( ( pszTmp >= szLine ) &&
|
||
( ( *pszTmp == '\n' ) || ( *pszTmp == '\r' ) ) )
|
||
{
|
||
*pszTmp-- = '\0';
|
||
}
|
||
|
||
//
|
||
// Ensure we send the proper prefix for the
|
||
// very *first* line of the file.
|
||
//
|
||
|
||
if( fIsFirst )
|
||
{
|
||
serr = SockPrintf2(this,
|
||
QueryControlSocket(),
|
||
"%u-%s",
|
||
ReplyCode,
|
||
szLine );
|
||
|
||
fIsFirst = FALSE;
|
||
}
|
||
else
|
||
{
|
||
serr = SockPrintf2(this,
|
||
QueryControlSocket(),
|
||
" %s",
|
||
szLine );
|
||
}
|
||
|
||
if( serr != 0 )
|
||
{
|
||
//
|
||
// Socket error sending file.
|
||
//
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
AtqSetInfo( AtqDecMaxPoolThreads, 0);
|
||
|
||
//
|
||
// Cleanup.
|
||
//
|
||
|
||
if ( 0 != fclose( pfile )) {
|
||
|
||
IF_DEBUG( ERROR) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"[%08x]::SendAnnotationFile() file close failed. "
|
||
" Error = %d\n",
|
||
this,
|
||
GetLastError()
|
||
));
|
||
}
|
||
}
|
||
|
||
return serr;
|
||
|
||
} // USER_DATA::SendDirectoryAnnotation()
|
||
|
||
|
||
|
||
|
||
SOCKERR
|
||
USER_DATA::SendErrorToClient(
|
||
IN LPCSTR pszPath,
|
||
IN DWORD dwError,
|
||
IN LPCSTR pszDefaultErrorMsg,
|
||
IN UINT nReplyCode
|
||
)
|
||
/*++
|
||
Send an error message indicating that the path is not found or
|
||
a particular error occured in a path.
|
||
|
||
Arguments:
|
||
sock socket to be used for synchronously sending message
|
||
pszPath pointer to path to be used.
|
||
dwError DWORD containing the error code, used for getting error text.
|
||
pszDefaultErrorMsg pointer to null-terminated string containing the
|
||
error message to be used if we can't alloc error text.
|
||
nReplyCode UINT containing the FTP reply code.
|
||
|
||
Returns:
|
||
SOCKERR. 0 if successful and !0 if failure.
|
||
--*/
|
||
{
|
||
BOOL fDelete = TRUE;
|
||
LPCSTR pszText;
|
||
APIERR serr;
|
||
|
||
DBG_ASSERT( pszPath != NULL);
|
||
pszText = AllocErrorText( dwError );
|
||
|
||
if( pszText == NULL ) {
|
||
|
||
pszText = pszDefaultErrorMsg;
|
||
fDelete = FALSE;
|
||
}
|
||
|
||
serr = ReplyToUser( this,
|
||
nReplyCode,
|
||
PSZ_FILE_ERROR,
|
||
pszPath,
|
||
pszText );
|
||
|
||
if( fDelete ) {
|
||
|
||
FreeErrorText( (char *) pszText );
|
||
}
|
||
|
||
return ( serr);
|
||
} // USER_DATA::SendErrorToClient()
|
||
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::FreeUserToken( VOID)
|
||
/*++
|
||
|
||
This function frees the user token if present already.
|
||
Otherwise does nothing.
|
||
--*/
|
||
{
|
||
BOOL fReturn = TRUE;
|
||
|
||
if( UserToken != NULL ) {
|
||
|
||
fReturn = TsDeleteUserToken( UserToken );
|
||
|
||
UserToken = NULL;
|
||
::RevertToSelf();
|
||
}
|
||
|
||
return ( fReturn);
|
||
} // USER_DATA::FreeUserToken()
|
||
|
||
|
||
APIERR
|
||
USER_DATA::CdToUsersHomeDirectory(IN const CHAR * pszAnonymousName)
|
||
/*++
|
||
This function changes user's home directory.
|
||
First, a CD to the virtual root is attempted.
|
||
If this succeeds, a CD to pszUser is attempted.
|
||
If this fails, a CD to DEFAULT_SUB_DIRECTORY is attempted.
|
||
|
||
Returns:
|
||
APIERR. NO_ERROR on success.
|
||
--*/
|
||
{
|
||
APIERR err;
|
||
PCSTR pszUser;
|
||
CHAR rgchRoot[MAX_PATH];
|
||
|
||
//
|
||
// Try the top-level home directory. If this fails, bag out.
|
||
// Set and try to change directory to symbolic root.
|
||
//
|
||
|
||
m_szCurrentDirectory[0] = '\0'; // initially nothing.
|
||
|
||
m_pInstance->LockThisForRead();
|
||
DBG_ASSERT( strlen( m_pInstance->QueryRoot()) < MAX_PATH);
|
||
P_strncpy( rgchRoot, m_pInstance->QueryRoot(), MAX_PATH);
|
||
m_pInstance->UnlockThis();
|
||
|
||
err = VirtualChDir( this, rgchRoot); // change to default dir.
|
||
|
||
if ( (err == NO_ERROR) &&
|
||
(m_pInstance->QueryIsolationMode() == NoIsolation) ) {
|
||
|
||
//
|
||
// We successfully CD'd into the top-level home
|
||
// directory. Now see if we can CD into pszUser.
|
||
//
|
||
|
||
P_strncpy( rgchRoot, QueryRootDirectory(), MAX_PATH);
|
||
if ( !VirtualChDir( this, rgchRoot ) ) {
|
||
|
||
//
|
||
// Nope, try DEFAULT_SUB_DIRECTORY. If this fails, just
|
||
// hang-out at the top-level home directory.
|
||
//
|
||
|
||
P_strncpy( rgchRoot, PSZ_DEFAULT_SUB_DIRECTORY, MAX_PATH);
|
||
VirtualChDir( this, rgchRoot );
|
||
}
|
||
}
|
||
|
||
return ( err);
|
||
|
||
} // USER_DATA::CdToUsersHomeDirectory()
|
||
|
||
|
||
VOID
|
||
USER_DATA::SetRootDirectory(
|
||
IN PCSTR pszAnonymousName)
|
||
/*++
|
||
this function sets the home directory for the logging in user,
|
||
based on the current isolation mode. If we are in no-isolation mode,
|
||
the user name excluding domain name is used. In isolation mode <domain>\<user>
|
||
is used for domain users, and local\<user> for local domain users. The
|
||
anonymous user gets 'LocalUser\public' in the isolation mode, and pszAnonymousName in
|
||
the no-isolation mode. For full hosting isolatin mode, the home directory is
|
||
retrieved from the AD
|
||
|
||
Arguments:
|
||
pszUser the user name logging in
|
||
pszAnonymousName the name of the anonymous directory
|
||
|
||
Returns:
|
||
NO_ERROR on success, WinError otherwise.
|
||
--*/
|
||
{
|
||
PUCHAR puch;
|
||
PCSTR pszUserName;
|
||
|
||
|
||
// BUGBUG: handle UPN names
|
||
|
||
switch (m_pInstance->QueryIsolationMode()) {
|
||
|
||
case NoIsolation:
|
||
|
||
m_szRootDir[ 0 ] = '\\';
|
||
|
||
if( TEST_UF( this, ANONYMOUS ) ) {
|
||
|
||
strcpy( m_szRootDir + 1, pszAnonymousName);
|
||
|
||
} else {
|
||
pszUserName = (PCHAR)_mbspbrk( (PUCHAR)QueryUserName(), (PUCHAR)"/\\" );
|
||
|
||
pszUserName = (pszUserName == NULL) ? QueryUserName() : pszUserName + 1;
|
||
|
||
P_strncpy( m_szRootDir + 1, pszUserName, sizeof( m_szRootDir ) - 1);
|
||
}
|
||
|
||
break;
|
||
|
||
case StandAloneIsolation:
|
||
|
||
m_szRootDir[ 0 ] = '\\';
|
||
|
||
if( TEST_UF( this, ANONYMOUS ) ) {
|
||
|
||
memcpy( m_szRootDir + 1,
|
||
SZ_LOCALUSER_DIR,
|
||
sizeof( SZ_LOCALUSER_DIR ) - 1 );
|
||
|
||
memcpy( m_szRootDir + sizeof( SZ_LOCALUSER_DIR ),
|
||
SZ_ANONYMOUS_DIR,
|
||
sizeof( SZ_ANONYMOUS_DIR ));
|
||
|
||
} else {
|
||
|
||
if (_mbspbrk( (PUCHAR)QueryUserName(), (PUCHAR)"/\\" )) {
|
||
|
||
P_strncpy( m_szRootDir + 1, QueryUserName(), MAX_PATH - 1);
|
||
|
||
FlipSlashes( m_szRootDir );
|
||
|
||
} else {
|
||
|
||
memcpy( m_szRootDir + 1,
|
||
SZ_LOCALUSER_DIR,
|
||
sizeof( SZ_LOCALUSER_DIR ) - 1 );
|
||
|
||
P_strncpy( m_szRootDir + sizeof( SZ_LOCALUSER_DIR ),
|
||
QueryUserName(),
|
||
sizeof( m_szRootDir ) - sizeof( SZ_LOCALUSER_DIR ));
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
case EnterpriseIsolation:
|
||
|
||
DBG_ASSERT( FALSE ); // BUGBUG: not implemented
|
||
|
||
break;
|
||
|
||
default:
|
||
|
||
DBG_ASSERT( FALSE );
|
||
}
|
||
}
|
||
|
||
|
||
APIERR
|
||
USER_DATA::OpenFileForSend( IN LPSTR pszFile)
|
||
/*++
|
||
Open an existing file for transmission using TransmitFile.
|
||
This function converts the given relative path into canonicalized full
|
||
path and opens the file through the cached file handles manager.
|
||
|
||
Arguments:
|
||
pszFile pointer to null-terminated string containing the file name
|
||
|
||
Returns:
|
||
TRUE on success and FALSE if any failure.
|
||
--*/
|
||
{
|
||
APIERR err;
|
||
CHAR szCanonPath[MAX_PATH];
|
||
DWORD cbSize = MAX_PATH*sizeof(CHAR);
|
||
CHAR szVirtualPath[MAX_PATH+1];
|
||
DWORD cchVirtualPath = MAX_PATH;
|
||
|
||
DBG_ASSERT( pszFile != NULL );
|
||
|
||
//
|
||
// Close any file we might have open now
|
||
// N.B. There shouldn't be an open file; we're just
|
||
// being careful here.
|
||
//
|
||
|
||
if (m_pOpenFileInfo) {
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"WARNING!! Closing [%08x], before opening %s\n",
|
||
pszFile
|
||
));
|
||
DBG_REQUIRE( CloseFileForSend() );
|
||
}
|
||
|
||
//
|
||
// Open the requested file
|
||
//
|
||
err = VirtualCanonicalize(szCanonPath,
|
||
&cbSize,
|
||
pszFile,
|
||
AccessTypeRead,
|
||
NULL,
|
||
szVirtualPath,
|
||
&cchVirtualPath);
|
||
|
||
if( err == NO_ERROR ) {
|
||
|
||
DWORD dwCreateFlags = 0;
|
||
|
||
IF_DEBUG( VIRTUAL_IO ) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"Opening File: %s\n", szCanonPath ));
|
||
}
|
||
|
||
// store the virtual path name of file.
|
||
P_strncpy( m_rgchFile, szVirtualPath, sizeof(m_rgchFile));
|
||
|
||
dwCreateFlags = TS_FORBID_SHORT_NAMES | TS_NOT_IMPERSONATED ;
|
||
|
||
|
||
if ( m_pMetaData )
|
||
{
|
||
if ( m_pMetaData->QueryDoCache() )
|
||
{
|
||
dwCreateFlags |= TS_CACHING_DESIRED;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
dwCreateFlags |= TS_CACHING_DESIRED;
|
||
}
|
||
|
||
m_pOpenFileInfo = TsCreateFile( m_pInstance->GetTsvcCache(),
|
||
szCanonPath,
|
||
QueryImpersonationToken(),
|
||
dwCreateFlags
|
||
);
|
||
// caching desired.
|
||
|
||
if( m_pOpenFileInfo == NULL ) {
|
||
|
||
err = GetLastError();
|
||
|
||
} else {
|
||
|
||
DWORD dwAttrib = m_pOpenFileInfo->QueryAttributes();
|
||
|
||
FacIncrement( FacFilesOpened);
|
||
|
||
DBG_ASSERT( dwAttrib != 0xffffffff);
|
||
|
||
if (dwAttrib == 0xFFFFFFFF || // invalid attributes
|
||
dwAttrib & (FILE_ATTRIBUTE_DIRECTORY |
|
||
FILE_ATTRIBUTE_HIDDEN |
|
||
FILE_ATTRIBUTE_SYSTEM)
|
||
) {
|
||
|
||
FacIncrement( FacFilesInvalid);
|
||
|
||
err = ERROR_FILE_NOT_FOUND;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
if( err != NO_ERROR ) {
|
||
|
||
IF_DEBUG( VIRTUAL_IO ) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"cannot open %s, error %lu\n",
|
||
pszFile,
|
||
err ));
|
||
}
|
||
}
|
||
|
||
return ( err);
|
||
} // USER_DATA::OpenFileForSend()
|
||
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::CloseFileForSend( IN DWORD dwError)
|
||
{
|
||
BOOL fReturn = TRUE;
|
||
TS_OPEN_FILE_INFO * pOpenFileInfo;
|
||
|
||
// make sure it includes the full path
|
||
DBG_ASSERT( m_rgchFile[0] == '/');
|
||
|
||
pOpenFileInfo = (TS_OPEN_FILE_INFO *) InterlockedExchangePointer(
|
||
(PVOID *) &m_pOpenFileInfo,
|
||
NULL
|
||
);
|
||
|
||
if ( pOpenFileInfo != NULL) {
|
||
|
||
//
|
||
// Fabricate an appropriate reply code based on the incoming
|
||
// error code. WriteLogRecord() will pick up this reply code
|
||
// and use it in the activity log.
|
||
//
|
||
|
||
SetLastReplyCode(
|
||
( dwError == NO_ERROR )
|
||
? REPLY_TRANSFER_OK
|
||
: REPLY_TRANSFER_ABORTED
|
||
);
|
||
|
||
FacIncrement( FacFilesClosed);
|
||
TsCloseHandle( m_pInstance->GetTsvcCache(), pOpenFileInfo);
|
||
WriteLogRecord( PSZ_SENT_VERB, m_rgchFile, dwError);
|
||
}
|
||
|
||
return ( fReturn);
|
||
} // USER_DATA::CloseFileForSend()
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# define MAX_ERROR_MESSAGE_LEN ( 500)
|
||
VOID
|
||
USER_DATA::WriteLogRecord( IN LPCSTR pszVerb,
|
||
IN LPCSTR pszPath,
|
||
IN DWORD dwError)
|
||
/*++
|
||
This function writes the log record for current request made to the
|
||
Ftp server by the client.
|
||
|
||
Arguments:
|
||
pszVerb - pointer to null-terminated string containing the verb
|
||
of operation done
|
||
pszPath - pointer to string containing the path for the verb
|
||
dwError - DWORD containing the error code for operation
|
||
|
||
Returns:
|
||
None.
|
||
--*/
|
||
{
|
||
INETLOG_INFORMATION ilRequest;
|
||
DWORD dwLog;
|
||
CHAR pszClientHostName[50];
|
||
CHAR pszServerIpAddress[50];
|
||
CHAR rgchRequest[MAX_PATH + 20];
|
||
DWORD cch;
|
||
static CHAR szFTPVersion[]="FTP";
|
||
|
||
BOOL fDontLog = m_pMetaData && m_pMetaData->DontLog();
|
||
|
||
if (!fDontLog)
|
||
{
|
||
|
||
//
|
||
// Fill in the information that needs to be logged.
|
||
//
|
||
|
||
ZeroMemory(&ilRequest, sizeof(ilRequest));
|
||
|
||
strcpy( pszClientHostName, (char *)QueryClientHostName());
|
||
ilRequest.pszClientHostName = pszClientHostName;
|
||
ilRequest.cbClientHostName = strlen(pszClientHostName);
|
||
|
||
ilRequest.pszClientUserName = (char *)QueryUserName();
|
||
strcpy( pszServerIpAddress, inet_ntoa( LocalIpAddress ));
|
||
ilRequest.pszServerAddress = pszServerIpAddress;
|
||
|
||
ilRequest.msTimeForProcessing = QueryProcessingTime();
|
||
ilRequest.dwBytesSent = m_licbSent.LowPart;
|
||
ilRequest.dwBytesRecvd = m_cbRecvd;
|
||
ilRequest.dwProtocolStatus = GetLastReplyCode();
|
||
ilRequest.dwWin32Status = dwError;
|
||
ilRequest.dwPort = ntohs ((WORD)LocalIpPort);
|
||
|
||
cch = wsprintfA( rgchRequest, "[%d]%s", QueryId(), pszVerb);
|
||
DBG_ASSERT( cch < MAX_PATH + 20);
|
||
|
||
ilRequest.pszOperation = rgchRequest;
|
||
if ( rgchRequest != NULL ) {
|
||
ilRequest.cbOperation = strlen(rgchRequest);
|
||
} else {
|
||
ilRequest.cbOperation = 0;
|
||
}
|
||
|
||
ilRequest.pszTarget = (char *)pszPath;
|
||
if ( pszPath != NULL ) {
|
||
ilRequest.cbTarget = strlen((char *)pszPath);
|
||
} else {
|
||
ilRequest.cbTarget = 0;
|
||
}
|
||
|
||
ilRequest.pszParameters = "";
|
||
ilRequest.pszVersion = szFTPVersion;
|
||
|
||
dwLog = m_pInstance->m_Logging.LogInformation( &ilRequest);
|
||
|
||
if ( dwLog != NO_ERROR) {
|
||
IF_DEBUG( ERROR) {
|
||
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
" Unable to log information to logger. Error = %u\n",
|
||
dwLog));
|
||
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
" Request From %s, User %s. Request = %s %s\n",
|
||
ilRequest.pszClientHostName,
|
||
ilRequest.pszClientUserName,
|
||
ilRequest.pszOperation,
|
||
ilRequest.pszTarget));
|
||
}
|
||
}
|
||
|
||
//
|
||
// LogInformation() should not fail.
|
||
// If it does fail, the TsvcInfo will gracefully suspend logging
|
||
// for now.
|
||
// We may want to gracefully handle the same.
|
||
//
|
||
}
|
||
|
||
m_cbRecvd = 0; // reset since we wrote the record
|
||
|
||
m_pInstance->QueryStatsObj()->UpdateTotalBytesSent( m_licbSent.QuadPart );
|
||
m_licbSent.QuadPart = 0;
|
||
|
||
return;
|
||
} // USER_DATA::WriteLogRecord()
|
||
|
||
VOID
|
||
USER_DATA::WriteLogRecordForSendError( DWORD dwError )
|
||
{
|
||
//
|
||
// We put this into its own method in this file so it can access
|
||
// the common PSZ_SENT_VERB global.
|
||
//
|
||
|
||
WriteLogRecord(
|
||
PSZ_SENT_VERB,
|
||
m_rgchFile,
|
||
dwError
|
||
);
|
||
|
||
} // USER_DATA::WriteLogRecordForSendError
|
||
|
||
|
||
|
||
//
|
||
// Private functions.
|
||
//
|
||
|
||
VOID
|
||
USER_DATA::CloseSockets(IN BOOL fWarnUser)
|
||
/*++
|
||
Closes sockets (data and control) opened by the user for this session.
|
||
|
||
Arguments:
|
||
fWarnUser - If TRUE, send the user a warning shot before closing
|
||
the sockets.
|
||
--*/
|
||
{
|
||
SOCKET PassiveSocket;
|
||
SOCKET ControlSocket;
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( this ) );
|
||
|
||
//
|
||
// Close any open sockets. It is very important to set
|
||
// PassiveDataListen socket & ControlSocket to INVALID_SOCKET
|
||
// *before* we actually close the sockets.
|
||
// Since this routine is called to
|
||
// disconnect a user, and may be called from the RPC thread,
|
||
// closing one of the sockets may cause the client thread
|
||
// to unblock and try to access the socket. Setting the
|
||
// values in the per-user area to INVALID_SOCKET before
|
||
// closing the sockets keeps this from being a problem.
|
||
//
|
||
// This was a problem created by the Select or WaitForMultipleObjects()
|
||
// Investigate if such race conditions occur with Asynchronous IO?
|
||
// NYI
|
||
//
|
||
|
||
CleanupPassiveSocket( TRUE );
|
||
|
||
//
|
||
// Get rid of the async io connection used for data transfer.
|
||
//
|
||
|
||
m_AioDataConnection.StopIo( NO_ERROR);
|
||
|
||
ControlSocket = QueryControlSocket();
|
||
|
||
if( ControlSocket != INVALID_SOCKET )
|
||
{
|
||
if( fWarnUser )
|
||
{
|
||
//
|
||
// Since this may be called in a context other than
|
||
// the user we're disconnecting, we cannot rely
|
||
// on the USER_DATA fields. So, we cannot call
|
||
// SockReply, so we'll kludge one together with
|
||
// SockPrintf2.
|
||
//
|
||
|
||
SockPrintf2( this,
|
||
ControlSocket,
|
||
"%d Terminating connection.",
|
||
REPLY_SERVICE_NOT_AVAILABLE );
|
||
}
|
||
|
||
StopControlIo(); // to stop the io on control socket.
|
||
}
|
||
|
||
return;
|
||
|
||
} // USER_DATA::CloseSockets()
|
||
|
||
|
||
/*******************************************************************
|
||
|
||
NAME: UserpGetNextId
|
||
|
||
SYNOPSIS: Returns the next available user id.
|
||
|
||
RETURNS: DWORD - The user id.
|
||
|
||
HISTORY:
|
||
KeithMo 23-Mar-1993 Created.
|
||
|
||
********************************************************************/
|
||
DWORD
|
||
UserpGetNextId(
|
||
VOID
|
||
)
|
||
{
|
||
DWORD userId;
|
||
|
||
// Increment the global counter, avoiding it from becoming 0.
|
||
InterlockedIncrement( (LPLONG ) &p_NextUserId);
|
||
|
||
if ((userId = p_NextUserId) == 0) {
|
||
|
||
InterlockedIncrement( (LPLONG ) &p_NextUserId);
|
||
userId = p_NextUserId;
|
||
}
|
||
|
||
DBG_ASSERT( userId != 0);
|
||
|
||
return userId;
|
||
|
||
} // UserpGetNextId
|
||
|
||
|
||
|
||
|
||
|
||
VOID
|
||
USER_DATA::Print( IN LPCSTR pszMsg) const
|
||
/*++
|
||
|
||
Prints the UserData object in debug mode.
|
||
|
||
History:
|
||
MuraliK 28-March-1995 Created.
|
||
--*/
|
||
{
|
||
|
||
# ifdef CHECK_DBG
|
||
CHAR szBuffer[1000];
|
||
|
||
sprintf( szBuffer,
|
||
"[%d] %s: {%u} \"%s\" State=%u. Ref=%u.\n"
|
||
" Ctrl sock=%u; Atq=%x. Data sock=%u; Atq=%x. CtrlRead=%u\n"
|
||
" LastCmd= \"%s\"\n",
|
||
GetCurrentThreadId(), pszMsg,
|
||
QueryId(), QueryUserName(),
|
||
QueryState(), QueryReference(),
|
||
QueryControlSocket(), m_AioControlConnection.QueryAtqContext(),
|
||
QueryDataSocket(), m_AioDataConnection.QueryAtqContext(),
|
||
TEST_UF( this, CONTROL_READ), m_recvBuffer
|
||
);
|
||
|
||
OutputDebugString( szBuffer);
|
||
|
||
# endif // CHECK_DBG
|
||
|
||
#ifndef _NO_TRACING_
|
||
CHKINFO( ( DBG_CONTEXT,
|
||
" Printing USER_DATA( %08x) Signature: %08x\n"
|
||
" RefCount = %08x; UserState = %08x;\n"
|
||
" ControlSocket = %08x; PassiveL = %08x\n"
|
||
" FileInfo@ = %08x; CurDir( %s) Handle = %08x\n"
|
||
" UserName = %s; UserToken = %08x; UserId = %u\n"
|
||
" Behaviour Flags = %08x; XferType = %d; XferMode = %d\n",
|
||
this, Signature, m_References, UserState,
|
||
QueryControlSocket(), m_sPassiveDataListen,
|
||
m_pOpenFileInfo, QueryCurrentDirectory(), CurrentDirHandle,
|
||
QueryUserName(), UserToken, QueryId(),
|
||
Flags, m_xferType, m_xferMode));
|
||
#else
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" Printing USER_DATA( %08x) Signature: %08x\n"
|
||
" RefCount = %08x; UserState = %08x;\n"
|
||
" ControlSocket = %08x; PassiveL = %08x\n"
|
||
" FileInfo@ = %08x; CurDir( %s) Handle = %08x\n"
|
||
" UserName = %s; UserToken = %08x; UserId = %u\n"
|
||
" Behaviour Flags = %08x; XferType = %d; XferMode = %d\n",
|
||
this, Signature, m_References, UserState,
|
||
QueryControlSocket(), m_sPassiveDataListen,
|
||
m_pOpenFileInfo, QueryCurrentDirectory(), CurrentDirHandle,
|
||
QueryUserName(), UserToken, QueryId(),
|
||
Flags, m_xferType, m_xferMode));
|
||
#endif
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" Local IpAddr = %s; HostIpAddr = %s; DataIpAddr = %s;\n"
|
||
" Port = %d; TimeAtConnection = %08x;\n",
|
||
inet_ntoa( LocalIpAddress), inet_ntoa( HostIpAddress),
|
||
inet_ntoa( DataIpAddress),
|
||
DataPort,
|
||
m_TimeAtConnection));
|
||
|
||
DBGPRINTF(( DBG_CONTEXT, " ASYNC_IO_CONN Control=%08x; Data=%08x\n",
|
||
&m_AioControlConnection, m_AioDataConnection));
|
||
|
||
IF_DEBUG( ASYNC_IO) {
|
||
|
||
# if DBG
|
||
m_AioControlConnection.Print();
|
||
m_AioDataConnection.Print();
|
||
# endif // DBG
|
||
}
|
||
|
||
return;
|
||
} // USER_DATA::Print()
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::VirtualPathAccessCheck(IN ACCESS_TYPE _access, IN char * pszPath)
|
||
/*++
|
||
checks to see if the access is allowed for accessing the path
|
||
using pszPath after canonicalizing it.
|
||
|
||
Arguments:
|
||
access the access desired
|
||
pszPath pointer to string containing the path
|
||
|
||
Returns:
|
||
TRUE on success and FALSE if there is any failure.
|
||
|
||
--*/
|
||
{
|
||
DWORD dwError;
|
||
DWORD dwSize = MAX_PATH;
|
||
CHAR rgchPath[MAX_PATH];
|
||
|
||
|
||
// this following call converts the symbolic path into absolute
|
||
// and also does path access check.
|
||
dwError = VirtualCanonicalize(rgchPath, &dwSize,
|
||
pszPath, _access);
|
||
|
||
return ( dwError);
|
||
|
||
} // USER_DATA::VirtualPathAccessCheck()
|
||
|
||
|
||
|
||
|
||
|
||
APIERR
|
||
USER_DATA::VirtualCanonicalize(
|
||
OUT CHAR * pszDest,
|
||
IN OUT LPDWORD lpdwSize,
|
||
IN OUT CHAR * pszSearchPath,
|
||
IN ACCESS_TYPE _access,
|
||
OUT LPDWORD pdwAccessMask,
|
||
OUT CHAR * pchVirtualPath, /* OPTIONAL */
|
||
IN OUT LPDWORD lpcchVirtualPath /* OPTIONAL */
|
||
)
|
||
/*++
|
||
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
|
||
|
||
accesss Access type for this path ( read, write, etc.)
|
||
|
||
pdwAccessMask pointer to DWORD which on succesful deciphering
|
||
will contain the access mask.
|
||
|
||
pchVirtualPath pointer to string which will contain the sanitized
|
||
virtual path on return (on success)
|
||
lpcchVirtualPath pointer to DWORD containing the length of buffer
|
||
(contains the length on return).
|
||
|
||
Returns:
|
||
|
||
Win32 Error Code - NO_ERROR on success
|
||
|
||
MuraliK 24-Apr-1995 Created.
|
||
|
||
--*/
|
||
{
|
||
DWORD dwError = NO_ERROR;
|
||
CHAR rgchVirtual[MAX_PATH];
|
||
|
||
DBG_ASSERT( pszDest != NULL);
|
||
DBG_ASSERT( lpdwSize != NULL);
|
||
DBG_ASSERT( pszSearchPath != NULL);
|
||
|
||
IF_DEBUG( VIRTUAL_IO) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"UserData(%08x)::VirtualCanonicalize(%08x, %08x[%u],"
|
||
" %s, %d)\n",
|
||
this, pszDest, lpdwSize, *lpdwSize, pszSearchPath, _access));
|
||
}
|
||
|
||
if ( pdwAccessMask != NULL) {
|
||
|
||
*pdwAccessMask = 0;
|
||
}
|
||
|
||
//
|
||
// Form the virtual path for the given path.
|
||
//
|
||
|
||
if ( !IS_PATH_SEP( *pszSearchPath)) {
|
||
|
||
const CHAR * pszNewDir = QueryCurrentDirectory(); // get virtual dir.
|
||
|
||
//
|
||
// This is a relative path. append it to currrent directory
|
||
//
|
||
|
||
if ( strlen(pszNewDir) + strlen(pszSearchPath) + 2 <= MAX_PATH) {
|
||
|
||
// copy the current directory
|
||
wsprintfA( rgchVirtual, "%s/%s",
|
||
pszNewDir, pszSearchPath);
|
||
pszSearchPath = rgchVirtual;
|
||
|
||
} else {
|
||
|
||
// long path --> is not supported.
|
||
DBGPRINTF((DBG_CONTEXT, "Long Virtual Path %s---%s\n",
|
||
pszNewDir, pszSearchPath));
|
||
|
||
dwError = ERROR_PATH_NOT_FOUND;
|
||
}
|
||
|
||
} else {
|
||
|
||
// This is an absolute virtual path.
|
||
// need to overwrite this virtual path with absolute
|
||
// path of the root. Do nothing.
|
||
}
|
||
|
||
if ( dwError == NO_ERROR) {
|
||
|
||
DWORD dwAccessMask = 0;
|
||
DBG_ASSERT( IS_PATH_SEP(*pszSearchPath));
|
||
|
||
//
|
||
// Now we have the complete symbolic path to the target file.
|
||
// Translate it into the absolute path
|
||
//
|
||
|
||
VirtualpSanitizePath( pszSearchPath);
|
||
|
||
if ( !LookupVirtualRoot( pszSearchPath,
|
||
pszDest,
|
||
lpdwSize,
|
||
&dwAccessMask ) ) {
|
||
|
||
dwError = GetLastError();
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"LookupVirtualRoot Failed. Error = %d. pszDest = %s. BReq=%d\n",
|
||
dwError, pszDest, *lpdwSize));
|
||
|
||
} else if ( !PathAccessCheck( _access, dwAccessMask,
|
||
TEST_UF( this, READ_ACCESS),
|
||
TEST_UF( this, WRITE_ACCESS))
|
||
) {
|
||
|
||
dwError = GetLastError();
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"PathAccessCheck Failed. Error = %d. pszDest = %s\n",
|
||
dwError, pszDest));
|
||
} else if ( lpcchVirtualPath != NULL) {
|
||
|
||
// successful in getting the path.
|
||
|
||
DWORD cchVPath = strlen( pszSearchPath);
|
||
|
||
if ( *lpcchVirtualPath > cchVPath && pchVirtualPath != NULL) {
|
||
|
||
// copy the virtual path, since we have space.
|
||
strcpy( pchVirtualPath, pszSearchPath);
|
||
}
|
||
|
||
*lpcchVirtualPath = cchVPath; // set the length to required size.
|
||
}
|
||
|
||
if ( dwError == NO_ERROR ) {
|
||
// IP check
|
||
|
||
AC_RESULT acIpAccess;
|
||
AC_RESULT acDnsAccess;
|
||
BOOL fNeedDnsCheck;
|
||
|
||
BindPathAccessCheck();
|
||
acIpAccess = QueryAccessCheck()->CheckIpAccess( &fNeedDnsCheck );
|
||
|
||
if ( (acIpAccess == AC_IN_DENY_LIST) ||
|
||
((acIpAccess == AC_NOT_IN_GRANT_LIST) && !fNeedDnsCheck) ) {
|
||
dwError = ERROR_INCORRECT_ADDRESS;
|
||
}
|
||
else if ( fNeedDnsCheck ) {
|
||
if ( !QueryAccessCheck()->IsDnsResolved() ) {
|
||
BOOL fSync;
|
||
LPSTR pDns;
|
||
|
||
if ( !QueryAccessCheck()->QueryDnsName( &fSync,
|
||
(ADDRCHECKFUNCEX)NULL,
|
||
(ADDRCHECKARG)NULL,
|
||
&pDns ) ) {
|
||
dwError = ERROR_INCORRECT_ADDRESS;
|
||
}
|
||
}
|
||
if ( dwError == NO_ERROR ) {
|
||
acDnsAccess = QueryAccessCheck()->CheckDnsAccess();
|
||
|
||
if ( (acDnsAccess == AC_IN_DENY_LIST) ||
|
||
(acDnsAccess == AC_NOT_IN_GRANT_LIST) ||
|
||
((m_acCheck == AC_NOT_IN_GRANT_LIST) &&
|
||
(acDnsAccess != AC_IN_GRANT_LIST) ) ) {
|
||
dwError = ERROR_INCORRECT_ADDRESS;
|
||
}
|
||
}
|
||
}
|
||
UnbindPathAccessCheck();
|
||
}
|
||
|
||
if ( pdwAccessMask != NULL) {
|
||
|
||
*pdwAccessMask = dwAccessMask;
|
||
}
|
||
}
|
||
|
||
|
||
IF_DEBUG( VIRTUAL_IO) {
|
||
|
||
if ( dwError != NO_ERROR) {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
" Cannot Canonicalize %s -- %s, Error = %lu\n",
|
||
QueryCurrentDirectory(),
|
||
pszSearchPath,
|
||
dwError));
|
||
} else {
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"Canonicalized path is: %s\n",
|
||
pszDest));
|
||
}
|
||
}
|
||
|
||
return ( dwError);
|
||
} // USER_DATA::VirtualCanonicalize()
|
||
|
||
|
||
|
||
|
||
|
||
/*******************************************************************
|
||
|
||
********************************************************************/
|
||
SOCKERR
|
||
USER_DATA::EstablishDataConnection(
|
||
IN LPCSTR pszReason,
|
||
IN LPCSTR pszSize
|
||
)
|
||
/*++
|
||
|
||
Connects to the client's data socket.
|
||
|
||
Arguments:
|
||
pszReason - The reason for the transfer (file list, get, put, etc).
|
||
pszSize - size of data being transferred.
|
||
|
||
Returns:
|
||
socket error code on any error.
|
||
--*/
|
||
{
|
||
SOCKERR serr = 0;
|
||
SOCKET DataSocket = INVALID_SOCKET;
|
||
BOOL fPassive;
|
||
BOOL fAcceptableSocket = FALSE;
|
||
|
||
//
|
||
// if we're in passive mode and aren't dealing with a fake IO completion [ie reprocessing
|
||
// the command], we just set up the event that will get signalled when the client
|
||
// actually connects.
|
||
//
|
||
|
||
if ( TEST_UF( this, PASSIVE ) &&
|
||
!QueryInFakeIOCompletion() )
|
||
{
|
||
//
|
||
// Ensure we actually created a passive listen data socket.
|
||
// no data transfer socket is in AsyncIo object.
|
||
//
|
||
|
||
DBG_ASSERT( m_sPassiveDataListen != INVALID_SOCKET );
|
||
|
||
//
|
||
// To avoid blocking while waiting for the client to connect, we're going to use
|
||
// WSAEventSelect() to wait for the socket to be accept()'able.
|
||
//
|
||
//
|
||
|
||
if ( ( serr = AddPASVAcceptEvent( &fAcceptableSocket ) ) != 0 )
|
||
{
|
||
ReplyToUser( this,
|
||
REPLY_LOCAL_ERROR,
|
||
PSZ_TOO_MANY_PASV_USERS );
|
||
|
||
return ( serr );
|
||
}
|
||
|
||
//
|
||
// No need to wait around, we can call accept() on the socket right now
|
||
//
|
||
if ( fAcceptableSocket )
|
||
{
|
||
goto continue_label;
|
||
}
|
||
|
||
m_fWaitingForPASVConn = TRUE;
|
||
m_fHavePASVConn = FALSE;
|
||
|
||
return ERROR_IO_PENDING;
|
||
}
|
||
|
||
DBG_ASSERT( !TEST_UF(this, PASSIVE) || QueryInFakeIOCompletion() );
|
||
|
||
|
||
continue_label:
|
||
//
|
||
// Reset any oob flag.
|
||
//
|
||
|
||
CLEAR_UF( this, OOB_DATA );
|
||
|
||
//
|
||
// Capture the user's passive flag, then reset to FALSE.
|
||
//
|
||
|
||
fPassive = TEST_UF( this, PASSIVE );
|
||
CLEAR_UF( this, PASSIVE );
|
||
|
||
//
|
||
// If we're in passive mode, then accept a connection to
|
||
// the data socket.
|
||
//
|
||
// Calling accept() on this socket should -not- block because shouldn't get this
|
||
// far without being sure that calling accept() won't block - that's the point of
|
||
// jumping through the WSAEventSelect() hoops mentioned above
|
||
//
|
||
|
||
if( fPassive )
|
||
{
|
||
|
||
SOCKADDR_IN saddrClient;
|
||
|
||
//
|
||
// Ensure we actually created a passive listen data socket.
|
||
// no data transfer socket is in AsyncIo object.
|
||
//
|
||
|
||
DBG_ASSERT( m_sPassiveDataListen != INVALID_SOCKET );
|
||
|
||
//
|
||
// Wait for a connection.
|
||
//
|
||
|
||
IF_DEBUG( CLIENT )
|
||
{
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"waiting for passive connection on socket %d\n",
|
||
m_sPassiveDataListen ));
|
||
}
|
||
|
||
serr = AcceptSocket( m_sPassiveDataListen,
|
||
&DataSocket,
|
||
&saddrClient,
|
||
TRUE,
|
||
m_pInstance ); // enforce timeouts
|
||
|
||
|
||
//
|
||
// We can kill m_sPassiveDataListen now.
|
||
// We only allow one connection in passive mode.
|
||
//
|
||
|
||
CleanupPassiveSocket( TRUE );
|
||
|
||
// PASV Theft is disabled, so you MUST have the same IP
|
||
// address ad the Control Connection
|
||
if (!(QueryInstance()->IsEnablePasvTheft()))
|
||
{
|
||
if (!(HostIpAddress.S_un.S_addr == saddrClient.sin_addr.S_un.S_addr))
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"Unmatching IP - Control: %d.%d.%d.%d Data: %d.%d.%d.%d \n",
|
||
HostIpAddress.S_un.S_un_b.s_b1,
|
||
HostIpAddress.S_un.S_un_b.s_b2,
|
||
HostIpAddress.S_un.S_un_b.s_b3,
|
||
HostIpAddress.S_un.S_un_b.s_b4,
|
||
saddrClient.sin_addr.S_un.S_un_b.s_b1,
|
||
saddrClient.sin_addr.S_un.S_un_b.s_b2,
|
||
saddrClient.sin_addr.S_un.S_un_b.s_b3,
|
||
saddrClient.sin_addr.S_un.S_un_b.s_b4));
|
||
|
||
CloseSocket( DataSocket);
|
||
DataSocket = INVALID_SOCKET;
|
||
serr = WSA_OPERATION_ABORTED;
|
||
};
|
||
};
|
||
|
||
if( serr == 0 )
|
||
{
|
||
|
||
//
|
||
// Got one.
|
||
//
|
||
|
||
DBG_ASSERT( DataSocket != INVALID_SOCKET );
|
||
|
||
m_fHavePASVConn = TRUE;
|
||
m_fWaitingForPASVConn = FALSE;
|
||
|
||
FacIncrement( FacPassiveDataConnections);
|
||
|
||
if ( m_AioDataConnection.SetNewSocket( DataSocket))
|
||
{
|
||
|
||
ReplyToUser(this,
|
||
REPLY_TRANSFER_STARTING,
|
||
PSZ_TRANSFER_STARTING);
|
||
}
|
||
else
|
||
{
|
||
|
||
//
|
||
// We are possibly running low on resources. Send error.
|
||
//
|
||
|
||
ReplyToUser( this,
|
||
REPLY_LOCAL_ERROR,
|
||
PSZ_INSUFFICIENT_RESOURCES);
|
||
|
||
CloseSocket( DataSocket);
|
||
DataSocket = INVALID_SOCKET;
|
||
serr = WSAENOBUFS;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
|
||
IF_DEBUG( CLIENT )
|
||
{
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"cannot wait for connection, error %d\n",
|
||
serr ));
|
||
}
|
||
|
||
ReplyToUser(this,
|
||
REPLY_TRANSFER_ABORTED,
|
||
PSZ_TRANSFER_ABORTED);
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
//
|
||
// Announce our intentions of establishing a connection.
|
||
//
|
||
|
||
ReplyToUser(this,
|
||
REPLY_OPENING_CONNECTION,
|
||
PSZ_OPENING_DATA_CONNECTION,
|
||
TransferType(m_xferType ),
|
||
pszReason,
|
||
pszSize);
|
||
|
||
//
|
||
// Open data socket.
|
||
//
|
||
|
||
serr = CreateDataSocket(&DataSocket, // Will receive socket
|
||
0, // Local address
|
||
CONN_PORT_TO_DATA_PORT(LocalIpPort),
|
||
DataIpAddress.s_addr,// RemoteAddr
|
||
DataPort ); // Remote port
|
||
|
||
if ( serr == 0 )
|
||
{
|
||
|
||
DBG_ASSERT( DataSocket != INVALID_SOCKET );
|
||
|
||
FacIncrement( FacActiveDataConnections);
|
||
|
||
if ( !m_AioDataConnection.SetNewSocket( DataSocket))
|
||
{
|
||
|
||
CloseSocket( DataSocket);
|
||
DataSocket = INVALID_SOCKET;
|
||
|
||
serr = WSAENOBUFS;
|
||
}
|
||
}
|
||
|
||
if ( serr != 0)
|
||
{
|
||
|
||
ReplyToUser(this,
|
||
REPLY_CANNOT_OPEN_CONNECTION,
|
||
PSZ_CANNOT_OPEN_DATA_CONNECTION);
|
||
|
||
IF_DEBUG( COMMANDS )
|
||
{
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"could not create data socket, error %d\n",
|
||
serr ));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if( serr == 0 )
|
||
{
|
||
|
||
// set this to indicate a transfer might start
|
||
SET_UF( this, TRANSFER );
|
||
|
||
//
|
||
// Submit a read command on control socket, since we
|
||
// have to await possibility of an abort on OOB_INLINE.
|
||
// Can we ignore possibility of an error on read request?
|
||
//
|
||
|
||
if ( !ReadCommand())
|
||
{
|
||
|
||
DWORD dwError = GetLastError();
|
||
|
||
# ifdef CHECK_DBG
|
||
CHAR szBuffer[100];
|
||
sprintf( szBuffer, " Read while DataTfr failed Error = %u. ",
|
||
dwError);
|
||
Print( szBuffer);
|
||
# endif // CHECK_DBG
|
||
|
||
IF_DEBUG(CLIENT) {
|
||
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
" %08x::ReadCommand() failed. Error = %u\n",
|
||
this, dwError));
|
||
SetLastError( dwError);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
return ( serr);
|
||
|
||
} // USER_DATA::EstablishDataConnection()
|
||
|
||
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
USER_DATA::DestroyDataConnection( IN DWORD dwError)
|
||
/*++
|
||
Tears down the connection to the client's data socket that was created
|
||
using EstablishDataConnection()
|
||
|
||
Arguments:
|
||
dwError = NO_ERROR if data is transferred successfully.
|
||
Win32 error code otherwise
|
||
|
||
--*/
|
||
{
|
||
UINT replyCode;
|
||
LPCSTR pszReply;
|
||
BOOL fTransfer;
|
||
|
||
fTransfer = TEST_UF( this, TRANSFER);
|
||
CLEAR_UF( this, TRANSFER );
|
||
|
||
CleanupPASVFlags();
|
||
|
||
//
|
||
// Close the data socket.
|
||
//
|
||
|
||
DBG_ASSERT( m_sPassiveDataListen == INVALID_SOCKET);
|
||
|
||
|
||
// Stop Io occuring on data connection
|
||
m_AioDataConnection.StopIo(dwError);
|
||
|
||
if ( fTransfer) {
|
||
|
||
//
|
||
// Tell the client we're done with the transfer.
|
||
//
|
||
|
||
if ( dwError == NO_ERROR) {
|
||
|
||
replyCode = REPLY_TRANSFER_OK;
|
||
pszReply = PSZ_TRANSFER_COMPLETE;
|
||
} else {
|
||
|
||
replyCode = REPLY_TRANSFER_ABORTED;
|
||
pszReply = PSZ_TRANSFER_ABORTED;
|
||
}
|
||
|
||
ReplyToUser(this, replyCode, pszReply);
|
||
}
|
||
|
||
return (TRUE);
|
||
} // USER_DATA::DestroyDataConnection()
|
||
|
||
|
||
APIERR
|
||
USER_DATA::GetFileSize()
|
||
{
|
||
LARGE_INTEGER FileSize;
|
||
DWORD dwError = NO_ERROR;
|
||
TS_OPEN_FILE_INFO * pOpenFileInfo;
|
||
CHAR rgchSize[MAX_FILE_SIZE_SPEC];
|
||
|
||
pOpenFileInfo = m_pOpenFileInfo;
|
||
|
||
if ( pOpenFileInfo == NULL) {
|
||
|
||
return ( ERROR_FILE_NOT_FOUND);
|
||
}
|
||
|
||
if ( !pOpenFileInfo->QuerySize(FileSize)) {
|
||
|
||
dwError = GetLastError();
|
||
|
||
if( dwError != NO_ERROR ) {
|
||
|
||
return ( dwError);
|
||
}
|
||
}
|
||
|
||
IsLargeIntegerToDecimalChar( &FileSize, rgchSize);
|
||
|
||
ReplyToUser( this, REPLY_FILE_STATUS, rgchSize );
|
||
return(dwError);
|
||
}
|
||
|
||
APIERR
|
||
USER_DATA::GetFileModTime(LPSYSTEMTIME lpSystemTime)
|
||
{
|
||
DWORD dwError = NO_ERROR;
|
||
TS_OPEN_FILE_INFO * pOpenFileInfo;
|
||
FILETIME FileTime;
|
||
|
||
pOpenFileInfo = m_pOpenFileInfo;
|
||
|
||
DBG_ASSERT( pOpenFileInfo != NULL );
|
||
|
||
if ( !pOpenFileInfo->QueryLastWriteTime(&FileTime)) {
|
||
|
||
dwError = GetLastError();
|
||
return ( dwError);
|
||
}
|
||
|
||
if (!FileTimeToSystemTime(&FileTime, lpSystemTime)) {
|
||
|
||
return GetLastError();
|
||
}
|
||
|
||
return NO_ERROR;
|
||
}
|
||
|
||
|
||
APIERR
|
||
USER_DATA::SendFileToUser( IN LPSTR pszFileName,
|
||
IN OUT LPBOOL pfErrorSent)
|
||
/*++
|
||
This is a worker function for RETR command of FTP. It will establish
|
||
connection via the ( new ) data socket, then send a file over that
|
||
socket. This uses Async io for transmitting the file.
|
||
|
||
Arguments:
|
||
pszFileName pointer to null-terminated string containing the filename
|
||
pfErrorSent pointer to boolean flag indicating if an error has
|
||
been already sent to client.
|
||
The flag should be used only when return value is error.
|
||
|
||
Returns:
|
||
NO_ERROR on success and Win32 error code if error.
|
||
|
||
History:
|
||
30-April-1995 MuraliK
|
||
--*/
|
||
{
|
||
LARGE_INTEGER FileSize;
|
||
DWORD dwError = NO_ERROR;
|
||
BOOL fTransmit;
|
||
DWORD dwAttribs;
|
||
TS_OPEN_FILE_INFO * pOpenFileInfo;
|
||
CHAR rgchSize[MAX_FILE_SIZE_SPEC];
|
||
CHAR rgchBuffer[MAX_FILE_SIZE_SPEC + 10];
|
||
|
||
|
||
DBG_ASSERT( pszFileName != NULL && pfErrorSent != NULL);
|
||
|
||
*pfErrorSent = FALSE;
|
||
|
||
IF_DEBUG( SEND) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" USER_DATA ( %08x)::SendFileToUser( %s,"
|
||
" pfErrorSent = %08x).\n",
|
||
this, pszFileName, pfErrorSent));
|
||
}
|
||
|
||
//
|
||
// Get file size.
|
||
//
|
||
pOpenFileInfo = m_pOpenFileInfo;
|
||
|
||
if ( pOpenFileInfo == NULL) {
|
||
|
||
return ( ERROR_FILE_NOT_FOUND);
|
||
}
|
||
|
||
// Get the file size
|
||
|
||
if ( !pOpenFileInfo->QuerySize(FileSize)) {
|
||
|
||
dwError = GetLastError();
|
||
|
||
if( dwError != NO_ERROR ) {
|
||
|
||
return ( dwError);
|
||
}
|
||
}
|
||
|
||
FileSize.QuadPart -= (LONGLONG)QueryCurrentOffset();
|
||
|
||
IsLargeIntegerToDecimalChar( &FileSize, rgchSize);
|
||
wsprintfA( rgchBuffer, "(%s bytes)", rgchSize);
|
||
|
||
m_pInstance->QueryStatsObj()->IncrTotalFilesSent();
|
||
|
||
//
|
||
// Blast the file from a local file to the user.
|
||
//
|
||
|
||
Reference(); // incr ref since async data transfer is started
|
||
SET_UF( this, ASYNC_TRANSFER);
|
||
|
||
fTransmit = ( m_AioDataConnection.
|
||
TransmitFileTs( pOpenFileInfo,
|
||
FileSize, // cbToSend ( send entire file)
|
||
QueryCurrentOffset() )
|
||
);
|
||
|
||
if ( !fTransmit) {
|
||
|
||
dwError = GetLastError();
|
||
|
||
IF_DEBUG( SEND) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" Unable to transmit file ( %s) (pOpenFile = %p)."
|
||
" Error = %u\n",
|
||
pszFileName,
|
||
pOpenFileInfo,
|
||
dwError));
|
||
}
|
||
|
||
// decr refcount since async tfr failed.
|
||
DBG_REQUIRE( DeReference() > 0);
|
||
}
|
||
|
||
//
|
||
// Disconnect from client.
|
||
// ( will be done at the call back after completion of IO).
|
||
//
|
||
|
||
return ( dwError);
|
||
|
||
} // USER_DATA::SendFileToUser()
|
||
|
||
|
||
|
||
VOID
|
||
USER_DATA::SetPassiveSocket( IN SOCKET sPassive )
|
||
/*++
|
||
|
||
This function frees up an old Passive socket and resets the
|
||
passive socket to the new Passive socket.
|
||
|
||
Arguments:
|
||
sPassive - new passive socket to use
|
||
|
||
--*/
|
||
{
|
||
|
||
SOCKET sPassiveOld;
|
||
|
||
sPassiveOld = (SOCKET) InterlockedExchangePointer ( (PVOID *) &m_sPassiveDataListen,
|
||
(PVOID) sPassive);
|
||
|
||
if ( sPassiveOld != INVALID_SOCKET) {
|
||
|
||
FacDecrement( FacPassiveDataListens);
|
||
DBG_REQUIRE( CloseSocket( sPassiveOld) == 0);
|
||
}
|
||
|
||
if ( sPassive != INVALID_SOCKET) {
|
||
|
||
FacIncrement(FacPassiveDataListens);
|
||
}
|
||
|
||
|
||
return;
|
||
} // USER_DATA::SetPassiveSocket()
|
||
|
||
VOID
|
||
USER_DATA::CleanupPassiveSocket( BOOL fTellWatchThread )
|
||
/*++
|
||
|
||
This function cleans up the resources associated with the current passive socket
|
||
|
||
Arguments:
|
||
fTellWatchThread - flag indicating whether to tell thread waiting for an event on
|
||
the current passive socket to clean up as well
|
||
|
||
Returns:
|
||
Nothing
|
||
--*/
|
||
{
|
||
SOCKET sPassiveOld;
|
||
|
||
LockUser();
|
||
|
||
if ( m_sPassiveDataListen == INVALID_SOCKET )
|
||
{
|
||
UnlockUser();
|
||
|
||
return;
|
||
}
|
||
|
||
RemovePASVAcceptEvent( fTellWatchThread );
|
||
|
||
DBG_REQUIRE( CloseSocket( m_sPassiveDataListen ) == 0 );
|
||
|
||
m_sPassiveDataListen = INVALID_SOCKET;
|
||
|
||
UnlockUser();
|
||
}
|
||
|
||
|
||
BOOL
|
||
USER_DATA::SetCommand( IN LPSTR pszCmd )
|
||
/*++
|
||
|
||
Routine Description:
|
||
Used to set pointer to FTP cmd
|
||
|
||
Arguments :
|
||
pszArgs - pointer to command to execute
|
||
|
||
Returns :
|
||
BOOL indicating success/failure to set values
|
||
--*/
|
||
{
|
||
BOOL fReturn = TRUE;
|
||
|
||
if ( !pszCmd )
|
||
{
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Free any previous allocations
|
||
//
|
||
if ( m_pszCmd )
|
||
{
|
||
TCP_FREE( m_pszCmd );
|
||
m_pszCmd = NULL;
|
||
}
|
||
|
||
if ( m_pszCmd = ( LPSTR ) TCP_ALLOC( strlen(pszCmd) + 1 ) )
|
||
{
|
||
strcpy( m_pszCmd, pszCmd );
|
||
}
|
||
else
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"Failed to allocate memory for command args !\n"));
|
||
|
||
fReturn = FALSE;
|
||
}
|
||
|
||
return fReturn;
|
||
}
|
||
|
||
/************************************************************
|
||
* Auxiliary Functions
|
||
************************************************************/
|
||
|
||
|
||
VOID
|
||
ProcessUserAsyncIoCompletion(IN LPVOID pContext,
|
||
IN DWORD cbIo,
|
||
IN DWORD dwError,
|
||
IN LPASYNC_IO_CONNECTION pAioConn,
|
||
IN BOOL fTimedOut
|
||
)
|
||
/*++
|
||
This function processes the Async Io completion ( invoked as
|
||
a callback from the ASYNC_IO_CONNECTION object).
|
||
|
||
Arguments:
|
||
pContext pointer to the context information ( UserData object).
|
||
cbIo count of bytes transferred in Io
|
||
dwError DWORD containing the error code resulting from last tfr.
|
||
pAioConn pointer to AsyncIo connection object.
|
||
|
||
Returns:
|
||
None
|
||
--*/
|
||
{
|
||
|
||
LPUSER_DATA pUserData = (LPUSER_DATA ) pContext;
|
||
|
||
DBG_ASSERT( pUserData != NULL);
|
||
DBG_ASSERT( pAioConn != NULL);
|
||
|
||
IF_SPECIAL_DEBUG( CRITICAL_PATH) {
|
||
|
||
CHAR rgchBuffer[100];
|
||
|
||
wsprintfA( rgchBuffer, " ProcessAio( cb=%u, err=%u, Aio=%x). ",
|
||
cbIo, dwError, pAioConn);
|
||
|
||
pUserData->Print( rgchBuffer);
|
||
}
|
||
|
||
DBG_REQUIRE( pUserData->Reference() > 0);
|
||
|
||
# if DBG
|
||
|
||
if ( !IS_VALID_USER_DATA( pUserData)) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
"Encountering an invalid user data ( %08x)\n",
|
||
pUserData));
|
||
pUserData->Print();
|
||
}
|
||
# endif // DBG
|
||
|
||
DBG_ASSERT( IS_VALID_USER_DATA( pUserData ) );
|
||
|
||
pUserData->ProcessAsyncIoCompletion( cbIo, dwError, pAioConn, fTimedOut);
|
||
|
||
DereferenceUserDataAndKill(pUserData);
|
||
|
||
return;
|
||
|
||
} // ProcessUserAsyncIoCompletion()
|
||
|
||
|
||
VOID
|
||
USER_DATA::RemovePASVAcceptEvent( BOOL fTellWatchThread )
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Routine that cleans up the state associated with a PASV accept event
|
||
|
||
Arguments:
|
||
|
||
fTellWatchThread - BOOL indicating whether or not to inform the thread waiting on
|
||
the event to stop waiting on it
|
||
|
||
Returns:
|
||
|
||
Nothing
|
||
--*/
|
||
{
|
||
DBG_ASSERT( m_sPassiveDataListen != INVALID_SOCKET );
|
||
|
||
if ( m_hPASVAcceptEvent == NULL )
|
||
{
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Remove all network notifications for the PASV socket
|
||
//
|
||
if ( WSAEventSelect( m_sPassiveDataListen,
|
||
m_hPASVAcceptEvent,
|
||
0 ) )
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"WSAEventSelect on socket %d failed : 0x%x\n",
|
||
m_sPassiveDataListen, WSAGetLastError()));
|
||
}
|
||
|
||
//
|
||
// Stop watching for the event
|
||
//
|
||
if ( fTellWatchThread )
|
||
{
|
||
RemoveAcceptEvent( m_hPASVAcceptEvent,
|
||
this );
|
||
}
|
||
|
||
WSACloseEvent( m_hPASVAcceptEvent );
|
||
|
||
m_hPASVAcceptEvent = NULL;
|
||
}
|
||
|
||
SOCKERR
|
||
USER_DATA::AddPASVAcceptEvent( BOOL *pfAcceptableSocket )
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Routine that sets up the event to signal that the PASV socket is an accept()'able
|
||
state
|
||
|
||
Arguments:
|
||
|
||
pfAcceptableSocket - BOOL set to TRUE if socket can be accept()'ed at once, FALSE if
|
||
NOT
|
||
|
||
Returns:
|
||
|
||
Error code indicating success/failure
|
||
|
||
--*/
|
||
{
|
||
DWORD dwRet = 0;
|
||
SOCKERR serr = 0;
|
||
BOOL fRegistered = FALSE;
|
||
|
||
*pfAcceptableSocket = FALSE;
|
||
|
||
if ( ( m_hPASVAcceptEvent = WSACreateEvent() ) == WSA_INVALID_EVENT )
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"Failed to create event to wait for accept() : 0x%x\n",
|
||
WSAGetLastError()));
|
||
|
||
return WSAGetLastError();
|
||
}
|
||
|
||
//
|
||
// specify that we want to be alerted when the socket is accept()'able =)
|
||
//
|
||
if ( WSAEventSelect( m_sPassiveDataListen,
|
||
m_hPASVAcceptEvent,
|
||
FD_ACCEPT ) )
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"WSAEventSelect failed : 0x%x\n",
|
||
WSAGetLastError()));
|
||
|
||
serr = WSAGetLastError();
|
||
|
||
goto exit;
|
||
}
|
||
else
|
||
{
|
||
fRegistered = TRUE;
|
||
}
|
||
|
||
//
|
||
// In order to deal as quickly as possible with legitimate clients and avoid rejecting them
|
||
// because the queue is full, we'll wait for 0.1 sec to see whether the socket becomes
|
||
// accept()'able before queueing it
|
||
//
|
||
dwRet = WSAWaitForMultipleEvents( 1,
|
||
&m_hPASVAcceptEvent,
|
||
FALSE,
|
||
100,
|
||
FALSE );
|
||
|
||
switch ( dwRet )
|
||
{
|
||
case WSA_WAIT_EVENT_0:
|
||
{
|
||
//
|
||
// we can call accept() at once on the socket, no need to muck around with waiting
|
||
// for it
|
||
//
|
||
WSAEventSelect( m_sPassiveDataListen,
|
||
m_hPASVAcceptEvent,
|
||
0 );
|
||
WSACloseEvent( m_hPASVAcceptEvent );
|
||
|
||
m_hPASVAcceptEvent = 0;
|
||
|
||
*pfAcceptableSocket = TRUE;
|
||
|
||
}
|
||
break;
|
||
|
||
case WSA_WAIT_TIMEOUT:
|
||
{
|
||
//
|
||
// Need to queue the socket
|
||
//
|
||
serr = AddAcceptEvent( m_hPASVAcceptEvent,
|
||
this );
|
||
|
||
}
|
||
break;
|
||
|
||
default:
|
||
{
|
||
serr = WSAGetLastError();
|
||
}
|
||
break;
|
||
}
|
||
|
||
exit:
|
||
|
||
//
|
||
// clean up if something failed
|
||
//
|
||
if ( serr != 0 )
|
||
{
|
||
if ( m_hPASVAcceptEvent )
|
||
{
|
||
if ( fRegistered )
|
||
{
|
||
WSAEventSelect( m_sPassiveDataListen,
|
||
m_hPASVAcceptEvent,
|
||
0 );
|
||
}
|
||
|
||
WSACloseEvent( m_hPASVAcceptEvent );
|
||
|
||
m_hPASVAcceptEvent = NULL;
|
||
}
|
||
}
|
||
|
||
return serr;
|
||
}
|
||
|
||
|
||
|
||
|
||
VOID
|
||
DereferenceUserDataAndKill(IN OUT LPUSER_DATA pUserData)
|
||
/*++
|
||
This function dereferences User data and kills the UserData object if the
|
||
reference count hits 0. Before killing the user data, it also removes
|
||
the connection from the list of active connections.
|
||
|
||
--*/
|
||
{
|
||
|
||
FTP_SERVER_INSTANCE * pinstance;
|
||
|
||
IF_SPECIAL_DEBUG( CRITICAL_PATH) {
|
||
|
||
pUserData->Print( " Deref ");
|
||
}
|
||
|
||
//
|
||
// We must capture the instance pointer from the user data, as
|
||
// USER_DATA::RemoveConnection() will set the pointer to NULL.
|
||
// We must also reference the instance before locking it, as
|
||
// removing the last user from the instance will cause the instance
|
||
// to be destroyed. We'll defer this destruction until we're done
|
||
// with the instance.
|
||
//
|
||
|
||
pinstance = pUserData->QueryInstance();
|
||
|
||
pinstance->Reference();
|
||
pinstance->LockConnectionsList();
|
||
|
||
if ( !pUserData->DeReference()) {
|
||
|
||
//
|
||
// Deletion of the object USER_DATA is required.
|
||
//
|
||
|
||
IF_DEBUG( USER_DATABASE) {
|
||
|
||
DBGPRINTF( ( DBG_CONTEXT,
|
||
" UserData( %08x) is being deleted.\n",
|
||
pUserData));
|
||
}
|
||
|
||
pinstance->UnlockConnectionsList();
|
||
|
||
pUserData->Cleanup();
|
||
|
||
DBG_ASSERT( pUserData->QueryControlSocket() == INVALID_SOCKET );
|
||
DBG_ASSERT( pUserData->QueryDataSocket() == INVALID_SOCKET );
|
||
|
||
pinstance->RemoveConnection( pUserData);
|
||
}
|
||
else {
|
||
|
||
pinstance->UnlockConnectionsList();
|
||
}
|
||
|
||
pinstance->Dereference();
|
||
|
||
} // DereferenceUserDataAndKill()
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
PathAccessCheck(IN ACCESS_TYPE _access,
|
||
IN DWORD dwVrootAccessMask,
|
||
IN BOOL fUserRead,
|
||
IN BOOL fUserWrite
|
||
)
|
||
/*++
|
||
This function determines if the required privilege to access the specified
|
||
virtual root with a given access mask exists.
|
||
|
||
Arguments:
|
||
|
||
access - specifies type of acces desired.
|
||
dwVrootAccessMask - DWORD containing the access mask for the virtual root.
|
||
fUserRead - user's permission to read (general)
|
||
fUserWrite - user's permission to write (general)
|
||
|
||
Returns:
|
||
BOOL - TRUE if access is to be granted, else FALSE.
|
||
|
||
History:
|
||
MuraliK 20-Sept-1995
|
||
|
||
--*/
|
||
{
|
||
BOOL fAccessGranted = FALSE;
|
||
|
||
DBG_ASSERT( IS_VALID_ACCESS_TYPE( _access ) );
|
||
|
||
//
|
||
// Perform the actual access check.
|
||
//
|
||
|
||
switch( _access ) {
|
||
|
||
case AccessTypeRead :
|
||
|
||
fAccessGranted = (fUserRead &&
|
||
((dwVrootAccessMask & VROOT_MASK_READ)
|
||
== VROOT_MASK_READ)
|
||
);
|
||
break;
|
||
|
||
case AccessTypeWrite :
|
||
case AccessTypeCreate :
|
||
case AccessTypeDelete :
|
||
|
||
fAccessGranted = (fUserWrite &&
|
||
((dwVrootAccessMask & VROOT_MASK_WRITE)
|
||
== VROOT_MASK_WRITE)
|
||
);
|
||
break;
|
||
|
||
default :
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"PathAccessCheck - invalid access type %d\n",
|
||
_access ));
|
||
DBG_ASSERT( FALSE );
|
||
break;
|
||
}
|
||
|
||
if (!fAccessGranted) {
|
||
|
||
SetLastError( ERROR_ACCESS_DENIED);
|
||
}
|
||
|
||
return ( fAccessGranted);
|
||
} // PathAccessCheck()
|
||
|
||
|
||
VOID SignalAcceptableSocket( LPUSER_DATA pUserData )
|
||
/*++
|
||
|
||
Function that restarts processing the original command when a PASV data socket becomes
|
||
accept()'able [ie the client has made the connection]
|
||
|
||
Arguments:
|
||
pUserData - USER_DATA context attached to socket
|
||
|
||
Returns:
|
||
Nothing
|
||
--*/
|
||
{
|
||
PATQ_CONTEXT pAtqContext = pUserData->QueryControlAio()->QueryAtqContext();
|
||
|
||
//
|
||
// Stop waiting for events on this socket
|
||
//
|
||
pUserData->RemovePASVAcceptEvent( FALSE );
|
||
|
||
pUserData->SetInFakeIOCompletion( TRUE );
|
||
|
||
//
|
||
// do a scary thing - fake an IO completion, to trigger re-processing of the FTP command
|
||
//
|
||
if ( !AtqPostCompletionStatus( pAtqContext,
|
||
strlen( pUserData->QueryCmdString() ) + 1 ) )
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"Failed to post fake completion status to deal with PASV event : 0x%x\n",
|
||
GetLastError()));
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
|
||
VOID CleanupTimedOutSocketContext( LPUSER_DATA pUserData )
|
||
/*++
|
||
|
||
Function used to do cleanup when timeout for waiting for a PASV connection expires
|
||
|
||
Arguments:
|
||
pUserData - context pointer
|
||
|
||
Returns:
|
||
Nothing
|
||
--*/
|
||
{
|
||
DBG_ASSERT( pUserData );
|
||
|
||
pUserData->LockUser();
|
||
|
||
pUserData->CleanupPassiveSocket( FALSE );
|
||
|
||
CLEAR_UF( pUserData, PASSIVE );
|
||
|
||
pUserData->SetHavePASVConn( FALSE );
|
||
|
||
pUserData->SetWaitingForPASVConn( FALSE );
|
||
|
||
ReplyToUser( pUserData,
|
||
REPLY_CANNOT_OPEN_CONNECTION,
|
||
PSZ_CANNOT_OPEN_DATA_CONNECTION );
|
||
//
|
||
// Remove our reference to this USER_DATA object
|
||
//
|
||
pUserData->DeReference();
|
||
|
||
pUserData->UnlockUser();
|
||
}
|
||
|
||
/*******************************************************************
|
||
|
||
NAME: FtpMetaDataFree
|
||
|
||
SYNOPSIS: Frees a formatted meta data object when it's not in use.
|
||
|
||
ENTRY: pObject - Pointer to the meta data object.
|
||
|
||
RETURNS:
|
||
|
||
|
||
NOTES:
|
||
|
||
|
||
********************************************************************/
|
||
|
||
VOID
|
||
FtpMetaDataFree(
|
||
PVOID pObject
|
||
)
|
||
{
|
||
PFTP_METADATA pMD;
|
||
|
||
pMD = (PFTP_METADATA)pObject;
|
||
|
||
delete pMD;
|
||
}
|
||
|
||
|
||
BOOL
|
||
FTP_METADATA::HandlePrivateProperty(
|
||
LPSTR pszURL,
|
||
PIIS_SERVER_INSTANCE pInstance,
|
||
METADATA_GETALL_INTERNAL_RECORD *pMDRecord,
|
||
LPVOID pDataPointer,
|
||
BUFFER *pBuffer,
|
||
DWORD *pdwBytesUsed,
|
||
PMETADATA_ERROR_INFO pMDErrorInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Handle metabase properties private to FTP service
|
||
|
||
Arguments:
|
||
|
||
pszURL - URL of the requested object
|
||
pInstance - FTP server instance
|
||
pMDRecord - metadata record
|
||
pDataPointer - pointer to metabase data
|
||
pBuffer - Buffer available for storage space
|
||
pdwBytesUsed - Pointer to bytes used in *pBuffer
|
||
|
||
Returns:
|
||
|
||
BOOL - TRUE success ( or not handled ), otherwise FALSE.
|
||
|
||
--*/
|
||
{
|
||
return TRUE;
|
||
}
|
||
|
||
BOOL
|
||
FTP_METADATA::FinishPrivateProperties(
|
||
BUFFER *pBuffer,
|
||
DWORD dwBytesUsed,
|
||
BOOL bSucceeded
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Handles completion of reading metabase properties private to FTP.
|
||
|
||
Arguments:
|
||
|
||
pBuffer - Buffer previously used for storage space
|
||
dwBytesUsed - bytes used in *pBuffer
|
||
|
||
Returns:
|
||
|
||
BOOL - TRUE success ( or not handled ), otherwise FALSE.
|
||
|
||
--*/
|
||
{
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
BOOL
|
||
USER_DATA::LookupVirtualRoot(
|
||
IN const CHAR * pszURL,
|
||
OUT CHAR * pszPath,
|
||
OUT DWORD * pcchDirRoot,
|
||
OUT DWORD * pdwAccessMask
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Looks up the virtual root to find the physical drive mapping. If an
|
||
Accept-Language header was sent by the client, we look for a virtual
|
||
root prefixed by the language tag
|
||
|
||
Arguments:
|
||
|
||
pstrPath - Receives physical drive path
|
||
pszURL - URL to look for
|
||
pcchDirRoot - Number of characters in the found physical path
|
||
pdwMask - Access mask for the specified URL
|
||
|
||
Returns:
|
||
BOOL - TRUE if success, otherwise FALSE.
|
||
|
||
--*/
|
||
{
|
||
PFTP_METADATA pMD;
|
||
DWORD dwDataSetNumber;
|
||
PVOID pCacheInfo;
|
||
MB mb( (IMDCOM*) g_pInetSvc->QueryMDObject() );
|
||
STACK_STR( strFullPath, MAX_PATH );
|
||
METADATA_ERROR_INFO MDErrorInfo;
|
||
BOOL fOk;
|
||
DWORD dwError = NO_ERROR;
|
||
|
||
|
||
|
||
if ( m_pMetaData != NULL )
|
||
{
|
||
TsFreeMetaData( m_pMetaData->QueryCacheInfo() );
|
||
m_pMetaData = NULL;
|
||
}
|
||
|
||
// First read the data set number, and see if we already have it
|
||
// cached. We don't do a full open in this case.
|
||
|
||
if ( !strFullPath.Copy( m_pInstance->QueryMDVRPath() ) ||
|
||
!strFullPath.Append( ( *pszURL == '/' ) ? pszURL + 1 : pszURL ) )
|
||
{
|
||
goto LookupVirtualRoot_Error;
|
||
}
|
||
|
||
if (!mb.GetDataSetNumber( strFullPath.QueryStr(),
|
||
&dwDataSetNumber ))
|
||
{
|
||
goto LookupVirtualRoot_Error;
|
||
}
|
||
|
||
// See if we can find a matching data set already formatted.
|
||
pMD = (PFTP_METADATA)TsFindMetaData(dwDataSetNumber, METACACHE_FTP_SERVER_ID);
|
||
|
||
if (pMD == NULL)
|
||
{
|
||
pMD = new FTP_METADATA;
|
||
|
||
if (pMD == NULL)
|
||
{
|
||
goto LookupVirtualRoot_Error;
|
||
}
|
||
|
||
if ( !pMD->ReadMetaData( m_pInstance,
|
||
&mb,
|
||
(LPSTR)pszURL,
|
||
&MDErrorInfo ) )
|
||
{
|
||
delete pMD;
|
||
goto LookupVirtualRoot_Error;
|
||
}
|
||
|
||
// We were succesfull, so try and add this metadata. There is a race
|
||
// condition where someone else could have added it while we were
|
||
// formatting. This is OK - we'll have two cached, but they should be
|
||
// consistent, and one of them will eventually time out. We could have
|
||
// AddMetaData check for this, and free the new one while returning a
|
||
// pointer to the old one if it finds one, but that isn't worthwhile
|
||
// now.
|
||
|
||
pCacheInfo = TsAddMetaData(pMD, FtpMetaDataFree,
|
||
dwDataSetNumber, METACACHE_FTP_SERVER_ID);
|
||
|
||
}
|
||
|
||
m_pMetaData = pMD;
|
||
|
||
if ( m_pMetaData->QueryVrError() )
|
||
{
|
||
dwError = m_pMetaData->QueryVrError();
|
||
goto LookupVirtualRoot_Error;
|
||
}
|
||
|
||
//
|
||
// Build physical path from VR_PATH & portion of URI not used to define VR_PATH
|
||
//
|
||
|
||
if (m_pInstance->QueryIsolationMode() == NoIsolation)
|
||
{
|
||
fOk = pMD->BuildPhysicalPath( (LPSTR)pszURL, &strFullPath );
|
||
}
|
||
else
|
||
{
|
||
fOk = pMD->BuildPhysicalPathWithAltRoot( (LPSTR)pszURL, &strFullPath, m_szRootDir );
|
||
}
|
||
|
||
if ( fOk &&
|
||
*pcchDirRoot > strFullPath.QueryCCH() )
|
||
{
|
||
memcpy( pszPath, strFullPath.QueryStr(), strFullPath.QueryCCH()+1 );
|
||
*pcchDirRoot = strFullPath.QueryCCH();
|
||
if ( pdwAccessMask )
|
||
{
|
||
*pdwAccessMask = m_pMetaData->QueryAccessPerms();
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
LookupVirtualRoot_Error:
|
||
|
||
if (dwError == NO_ERROR) {
|
||
//
|
||
// best error message to send to client
|
||
//
|
||
dwError = ERROR_FILE_NOT_FOUND;
|
||
}
|
||
|
||
SetLastError( dwError );
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
BOOL
|
||
USER_DATA::BindInstanceAccessCheck(
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Bind IP/DNS access check for this request to instance data
|
||
|
||
Arguments:
|
||
|
||
None
|
||
|
||
Returns:
|
||
|
||
BOOL - TRUE if success, otherwise FALSE.
|
||
|
||
--*/
|
||
{
|
||
if ( m_rfAccessCheck.CopyFrom( m_pInstance->QueryMetaDataRefHandler() ) )
|
||
{
|
||
m_acAccessCheck.BindCheckList( (LPBYTE)m_rfAccessCheck.GetPtr(), m_rfAccessCheck.GetSize() );
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
VOID
|
||
USER_DATA::UnbindInstanceAccessCheck()
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Unbind IP/DNS access check for this request to instance data
|
||
|
||
Arguments:
|
||
|
||
None
|
||
|
||
Returns:
|
||
|
||
Nothing
|
||
|
||
--*/
|
||
{
|
||
m_acAccessCheck.UnbindCheckList();
|
||
m_rfAccessCheck.Reset( (IMDCOM*) g_pInetSvc->QueryMDObject() );
|
||
}
|
||
|
||
|
||
BOOL
|
||
USER_DATA::IsFileNameShort( IN LPSTR pszFile)
|
||
/*++
|
||
|
||
Check file name beeing short or not.
|
||
|
||
Arguments:
|
||
pszFile pointer to null-terminated string containing the file name
|
||
|
||
Returns:
|
||
TRUE if filename is short.
|
||
--*/
|
||
{
|
||
APIERR err;
|
||
CHAR szCanonPath[MAX_PATH];
|
||
DWORD cbSize = MAX_PATH*sizeof(CHAR);
|
||
CHAR szVirtualPath[MAX_PATH+1];
|
||
DWORD cchVirtualPath = MAX_PATH;
|
||
BOOL fShort;
|
||
BOOL fRet = FALSE;
|
||
|
||
DBG_ASSERT( pszFile != NULL );
|
||
|
||
//
|
||
// Close any file we might have open now
|
||
// N.B. There shouldn't be an open file; we're just
|
||
// being careful here.
|
||
//
|
||
|
||
if (m_pOpenFileInfo) {
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"WARNING!! Closing [%08x], before opening %s\n",
|
||
pszFile
|
||
));
|
||
DBG_REQUIRE( CloseFileForSend() );
|
||
}
|
||
|
||
//
|
||
// Open the requested file
|
||
//
|
||
err = VirtualCanonicalize(szCanonPath,
|
||
&cbSize,
|
||
pszFile,
|
||
AccessTypeRead,
|
||
NULL,
|
||
szVirtualPath,
|
||
&cchVirtualPath);
|
||
|
||
if( err == NO_ERROR )
|
||
{
|
||
|
||
if ( strchr( szCanonPath, '~' ))
|
||
{
|
||
|
||
err = CheckIfShortFileName( (UCHAR *) szCanonPath, TsTokenToImpHandle( QueryUserToken()), &fShort );
|
||
|
||
if ( !err && fShort)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"Short filename being rejected \"%s\"\n",
|
||
szCanonPath ));
|
||
fRet = TRUE;
|
||
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
|
||
|
||
return fRet;
|
||
} // USER_DATA::IsFileNameShort()
|
||
|
||
|
||
|
||
DWORD
|
||
USER_DATA::CheckIfShortFileName(
|
||
IN CONST UCHAR * pszPath,
|
||
IN HANDLE hImpersonation,
|
||
OUT BOOL * pfShort
|
||
)
|
||
/*++
|
||
Description:
|
||
|
||
This function takes a suspected NT/Win95 short filename and checks if there's
|
||
an equivalent long filename. For example, c:\foobar\ABCDEF~1.ABC is the same
|
||
as c:\foobar\abcdefghijklmnop.abc.
|
||
|
||
NOTE: This function should be called unimpersonated - the FindFirstFile() must
|
||
be called in the system context since most systems have traverse checking turned
|
||
off - except for the UNC case where we must be impersonated to get network access.
|
||
|
||
Arguments:
|
||
|
||
pszPath - Path to check
|
||
hImpersonation - Impersonation handle if this is a UNC path - can be NULL if not UNC
|
||
pfShort - Set to TRUE if an equivalent long filename is found
|
||
|
||
Returns:
|
||
|
||
Win32 error on failure
|
||
--*/
|
||
{
|
||
DWORD err = NO_ERROR;
|
||
WIN32_FIND_DATA FindData;
|
||
UCHAR * psz;
|
||
BOOL fUNC;
|
||
|
||
psz = _mbschr( (UCHAR *) pszPath, '~' );
|
||
*pfShort = FALSE;
|
||
fUNC = (*pszPath == '\\');
|
||
|
||
//
|
||
// Loop for multiple tildas - watch for a # after the tilda
|
||
//
|
||
|
||
while ( psz++ )
|
||
{
|
||
if ( *psz >= '0' && *psz <= '9' )
|
||
{
|
||
UCHAR achTmp[MAX_PATH];
|
||
UCHAR * pchEndSeg;
|
||
UCHAR * pchBeginSeg;
|
||
HANDLE hFind;
|
||
|
||
//
|
||
// Isolate the path up to the segment with the
|
||
// '~' and do the FindFirst with that path
|
||
//
|
||
|
||
pchEndSeg = _mbschr( psz, '\\' );
|
||
|
||
if ( !pchEndSeg )
|
||
{
|
||
pchEndSeg = psz + _mbslen( psz );
|
||
}
|
||
|
||
//
|
||
// If the string is beyond MAX_PATH then we allow it through
|
||
//
|
||
|
||
if ( ((INT) (pchEndSeg - pszPath)) >= sizeof( achTmp ))
|
||
{
|
||
return NO_ERROR;
|
||
}
|
||
|
||
memcpy( achTmp, pszPath, (INT) (pchEndSeg - pszPath) );
|
||
achTmp[pchEndSeg - pszPath] = '\0';
|
||
|
||
if ( fUNC && hImpersonation )
|
||
{
|
||
if ( !ImpersonateLoggedOnUser( hImpersonation ))
|
||
{
|
||
return GetLastError();
|
||
}
|
||
}
|
||
|
||
hFind = FindFirstFile( (CHAR *) achTmp, &FindData );
|
||
|
||
if ( fUNC && hImpersonation )
|
||
{
|
||
RevertToSelf();
|
||
}
|
||
|
||
if ( hFind == INVALID_HANDLE_VALUE )
|
||
{
|
||
err = GetLastError();
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"FindFirst failed!! - \"%s\", error %d\n",
|
||
achTmp,
|
||
GetLastError() ));
|
||
|
||
//
|
||
// If the FindFirstFile() fails to find the file then return
|
||
// success - the path doesn't appear to be a valid path which
|
||
// is ok.
|
||
//
|
||
|
||
if ( err == ERROR_FILE_NOT_FOUND ||
|
||
err == ERROR_PATH_NOT_FOUND )
|
||
{
|
||
return NO_ERROR;
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
DBG_REQUIRE( FindClose( hFind ));
|
||
|
||
//
|
||
// Isolate the last segment of the string which should be
|
||
// the potential short name equivalency
|
||
//
|
||
|
||
pchBeginSeg = _mbsrchr( achTmp, '\\' );
|
||
DBG_ASSERT( pchBeginSeg );
|
||
pchBeginSeg++;
|
||
|
||
//
|
||
// If the last segment doesn't match the long name then this is
|
||
// the short name version of the path
|
||
//
|
||
|
||
if ( _mbsicmp( (UCHAR *) FindData.cFileName, pchBeginSeg ))
|
||
{
|
||
*pfShort = TRUE;
|
||
return NO_ERROR;
|
||
}
|
||
}
|
||
|
||
psz = _mbschr( psz, '~' );
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
/******************************* End Of File *************************/
|
||
|