Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3528 lines
91 KiB

/**********************************************************************/
/** 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()
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.
*/
#include "ftpdp.hxx"
# include "tsunami.hxx"
# include "auxctrs.h"
#define FIRST_TELNET_COMMAND 240
# 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_NOT_FOUND[] = "%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_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 DWORD p_NextUserId = 0; // Next available user id.
//
// 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 ) {
TCP_PRINT(( DBG_CONTEXT,
"StopControLRead: no read active!!!\n"));
DBG_ASSERT( FALSE);
}
TCP_REQUIRE( pUserData->DeReference() > 0);
CLEAR_UF( pUserData, CONTROL_READ);
}
} // StopControlRead()
static 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;
CHAR * pszSrc;
CHAR * pszDst;
LPCSTR pszAbort = "ABOR\r\n";
LPCSTR pszNext = pszAbort;
TCP_ASSERT( pszLine != NULL && cchLine > 0 &&
pfLineEnded != NULL && pcchRequestRecvd != NULL);
*pfLineEnded = FALSE;
for( pszSrc = pszDst = pszLine; pszSrc < pszLine + cchLine && *pszSrc;
pszSrc++) {
CHAR ch = *pszSrc;
if ( (UINT ) ch >= FIRST_TELNET_COMMAND) {
continue;
}
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.
fDontAbort = FALSE;
pszNext = pszAbort;
}
}
//
// skip the telnet commands ( which are mostly the OOB commands).
// as well as the new lines
//
if ( (ch != '\r') && ( ch != '\n')) {
*pszDst++ = ch;
} else if ( ch == '\n') {
// terminate at the linefeed
*pfLineEnded = TRUE;
break;
}
} // for
*pszDst = '\0';
*pcchRequestRecvd = (pszDst - pszLine);
TCP_ASSERT( *pcchRequestRecvd <= cchLine);
return (fDontAbort);
} // FilterTelnetCommands()
//
// Public functions.
//
USER_DATA::USER_DATA( VOID)
/*++
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_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_hVrootImpersonation ( NULL)
{
DWORD dwTimeout = g_pTsvcInfo->QueryConnectionTimeout();
//
// Setup the structure signature.
//
INIT_USER_SIG( this );
m_AioControlConnection.SetAioInformation( this, dwTimeout);
m_AioDataConnection.SetAioInformation( this, dwTimeout);
InitializeListHead( &ListEntry);
RtlZeroMemory( m_recvBuffer, DEFAULT_REQUEST_BUFFER_SIZE);
IF_DEBUG( USER_DATABASE ) {
TCP_PRINT(( DBG_CONTEXT,
"user_data object created @ %08lX.\n",
this));
}
} // USER_DATA::USER_DATA()
USER_DATA::~USER_DATA(VOID)
{
if ( !m_fCleanedup) {
Cleanup();
}
DBG_ASSERT( m_hVrootImpersonation == NULL);
if( RenameSourceBuffer != NULL ) {
TCP_FREE( RenameSourceBuffer);
RenameSourceBuffer = NULL;
}
} // USER_DATA::~USER_DATA()
BOOL
USER_DATA::Reset(IN SOCKET sControl,
IN IN_ADDR clientIpAddress,
IN const SOCKADDR_IN * psockAddrLocal /* = NULL */ ,
IN PATQ_CONTEXT pAtqContext /* = NULL */ ,
IN PVOID pvInitialRequest /* = NULL */ ,
IN DWORD cbWritten /* = 0 */
)
{
BOOL fReturn = TRUE;
//
// Setup the structure signature.
//
INIT_USER_SIG( this );
m_References = 1; // set to 1 to prevent immediate deletion.
m_fCleanedup = FALSE;
Flags = g_pFtpServerConfig->QueryUserFlags();
UserState = UserStateEmbryonic;
m_pOpenFileInfo = NULL;
UserToken = NULL;
m_hVrootImpersonation = NULL;
m_UserId = UserpGetNextId();
m_xferType = XferTypeAscii;
m_xferMode = XferModeStream;
m_msStartingTime= 0;
HostIpAddress = clientIpAddress;
DataIpAddress = clientIpAddress;
DataPort = g_pFtpServerConfig->QueryDataPort();
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;
// set up the async io contexts
m_AioControlConnection.SetNewSocket( sControl, pAtqContext);
m_AioDataConnection.SetNewSocket(INVALID_SOCKET);
m_sPassiveDataListen = ( INVALID_SOCKET);
m_rgchFile[0] = '\0';
UserName[ 0] = '\0'; // no user name available yet.
CurrentDirectory[0] = '\0'; // initialize to no virtual dir.
m_licbSent.QuadPart = 0;
INCR_STAT_COUNTER( CurrentConnections);
//
// get the local Ip address
//
if ( psockAddrLocal != NULL) {
LocalIpAddress = psockAddrLocal->sin_addr;
} else {
SOCKADDR_IN saddrLocal;
INT cbLocal;
cbLocal = sizeof( saddrLocal);
if ( getsockname( sControl, (SOCKADDR *) &saddrLocal, &cbLocal) != 0) {
DWORD err = WSAGetLastError();
fReturn = FALSE;
IF_DEBUG( ERROR) {
TCP_PRINT( ( DBG_CONTEXT,
" Failure in getsockname( sock=%d). Error = %u\n",
sControl, err));
}
SetLastError( err);
} else {
LocalIpAddress = saddrLocal.sin_addr;
}
}
//
// Success!
//
IF_DEBUG( CLIENT) {
time_t now;
time( & now);
CHAR pchAddr[32];
InetNtoa( clientIpAddress, pchAddr);
TCP_PRINT( ( DBG_CONTEXT,
" Client Connection for %s:%d starting @ %s",
pchAddr, sControl,
asctime( localtime( &now))));
}
IF_DEBUG( USER_DATABASE ) {
TCP_PRINT(( DBG_CONTEXT,
"user %lu reset @ %08lX.\n",
QueryId(), this));
}
m_nControlRead = 0;
return (fReturn);
} // USER_DATA::Reset()
VOID
USER_DATA::Cleanup( VOID)
/*++
This cleanups stored data in the user data object.
Returns:
None
--*/
{
TCP_ASSERT( QueryReference() == 0);
# if DBG
if ( !IS_VALID_USER_DATA( this)) {
TCP_PRINT( ( DBG_CONTEXT,
"Encountering an invalid user data ( %08x)\n",
this));
Print();
}
# endif // DBG
TCP_ASSERT( IS_VALID_USER_DATA( this ) );
IF_DEBUG( USER_DATABASE ) {
TCP_PRINT(( DBG_CONTEXT,
" Cleaning up user %lu @ %08lX.\n",
QueryId(), this));
}
DBG_ASSERT( m_nControlRead == 0);
//
// 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())
{
if( TEST_UF( this, ANONYMOUS))
{
DECR_STAT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECR_STAT_COUNTER( CurrentNonAnonymousUsers );
}
}
DECR_STAT_COUNTER( CurrentConnections);
if( UserToken != NULL )
{
TsDeleteUserToken( UserToken );
UserToken = NULL;
}
if( CurrentDirHandle != INVALID_HANDLE_VALUE )
{
IF_DEBUG( VIRTUAL_IO )
{
TCP_PRINT(( DBG_CONTEXT,
"closing directory handle %08lX\n",
CurrentDirHandle ));
}
CloseHandle( CurrentDirHandle );
CurrentDirHandle = INVALID_HANDLE_VALUE;
}
if ( m_pOpenFileInfo != NULL) {
TCP_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);
TCP_PRINT( ( 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.
//
TCP_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)) {
TCP_PRINT( ( DBG_CONTEXT,
"Encountering an invalid user data ( %08x)\n",
this));
Print();
}
# endif // DBG
TCP_ASSERT( IS_VALID_USER_DATA( this ) );
//
// Update the statistics.
//
if( IsLoggedOn())
{
if( TEST_UF( this, ANONYMOUS))
{
DECR_STAT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECR_STAT_COUNTER( CurrentNonAnonymousUsers );
}
}
CLEAR_UF_BITS( this, (UF_LOGGED_ON | UF_ANONYMOUS | UF_PASSIVE));
SetState( UserStateWaitingForUser);
m_hVrootImpersonation = NULL;
m_TimeAtConnection= GetCurrentTimeInSeconds();
m_TimeAtLastAccess= m_TimeAtConnection;
m_xferType = XferTypeAscii;
m_xferMode = XferModeStream;
DataIpAddress = HostIpAddress;
DataPort = g_pFtpServerConfig->QueryDataPort();
strcpy( UserName, "");
strcpy( CurrentDirectory, "");
if( UserToken != NULL )
{
TsDeleteUserToken( UserToken );
UserToken = NULL;
}
if( CurrentDirHandle != INVALID_HANDLE_VALUE )
{
IF_DEBUG( VIRTUAL_IO )
{
TCP_PRINT(( DBG_CONTEXT,
"closing directory handle %08lX\n",
CurrentDirHandle ));
}
CloseHandle( CurrentDirHandle );
CurrentDirHandle = INVALID_HANDLE_VALUE;
}
if ( m_pOpenFileInfo != NULL) {
TCP_REQUIRE( CloseFileForSend());
}
m_licbSent.QuadPart = 0;
m_pvInitialRequest = NULL;
m_cbInitialRequest = 0;
SetPassiveSocket( INVALID_SOCKET);
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;
# if DBG
if ( !IS_VALID_USER_DATA( this)) {
TCP_PRINT( ( DBG_CONTEXT,
"Encountering an invalid user data ( %08x)\n",
this));
Print();
}
# endif // DBG
TCP_ASSERT( IS_VALID_USER_DATA( this ) );
IF_DEBUG( USER_DATABASE) {
TCP_PRINT( ( DBG_CONTEXT,
"[%lu] Entering USER_DATA( %08x)::Process( %u, %u, %08x)."
" RefCount = %d. State = %d\n",
GetTickCount(),
this, cbIo, dwError, pAioConn, QueryReference(),
QueryState()));
}
if ( pAioConn == &m_AioDataConnection) {
//
// a Data transfer operation has completed.
//
TCP_REQUIRE( IsLoggedOn());
// Update last access time
m_TimeAtLastAccess = GetCurrentTimeInSeconds();
// it can't be no-error, no-data
TCP_ASSERT( cbIo >= 0 || dwError != NO_ERROR);
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.
TCP_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.
//
TCP_REQUIRE( DestroyDataConnection( dwError));
if ( m_pOpenFileInfo != NULL) {
TCP_REQUIRE( CloseFileForSend( dwError));
}
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: TCP_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.
TCP_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);
TCP_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:
//
// 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:
TCP_ASSERT( !"Invalid UserState for processing\n");
SetLastError( ERROR_INVALID_PARAMETER);
break;
} // switch
dwError = ( fReturn) ? NO_ERROR : GetLastError();
}
if ( !fReturn) {
DisconnectUserWithError( dwError, fTimedOut);
}
}
else {
TCP_ASSERT( !"call to Process() with wrong parameters");
}
IF_DEBUG( USER_DATABASE) {
TCP_PRINT( ( 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;
# if DBG
if ( !IS_VALID_USER_DATA( this)) {
TCP_PRINT( ( DBG_CONTEXT,
"Encountering an invalid user data ( %08x)\n",
this));
Print();
}
# endif // DBG
TCP_ASSERT( IS_VALID_USER_DATA( this ) );
TCP_ASSERT( QueryState() == UserStateEmbryonic);
//
// Reply to the initial connection message. ( Greet the new user).
//
serr = ReplyToUser( this,
REPLY_SERVICE_READY,
"%s Microsoft FTP Service (%s).",
g_pFtpServerConfig->QueryLocalHostName(),
g_FtpVersionString );
if ( serr != 0) {
IF_DEBUG( ERROR) {
TCP_PRINT( ( 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
//
SetState( UserStateWaitingForUser);
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) {
TCP_PRINT((DBG_CONTEXT,
" SetsockOpt( OOB_INLINE) failed. Error = %lu\n",
WSAGetLastError()));
}
}
}
IF_DEBUG( CLIENT) {
DWORD dwError = (fReturn) ? NO_ERROR : GetLastError();
TCP_PRINT( ( 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.
//
TCP_PRINT((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.
--*/
{
# if DBG
if ( !IS_VALID_USER_DATA( this)) {
TCP_PRINT( ( DBG_CONTEXT,
"Encountering an invalid user data ( %08x)\n",
this));
Print();
}
# endif // DBG
TCP_ASSERT( IS_VALID_USER_DATA( this ) );
IF_DEBUG( CLIENT) {
TCP_PRINT( ( DBG_CONTEXT,
"UserData(%08x)::ParseAndProcessRequest( %d chars)\n",
this, cchRequest));
}
if ( cchRequest > 0) {
BOOL fLineEnded = FALSE;
CHAR szCommandLine[ MAX_COMMAND_LENGTH + 1];
DWORD cchRequestRecvd = 0;
// We have a valid request. Process it
// Update last access time
m_TimeAtLastAccess = GetCurrentTimeInSeconds();
UPDATE_LARGE_COUNTER( TotalBytesReceived, 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());
OutputDebugString( 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) {
TCP_PRINT((DBG_CONTEXT,
"[%08x]Set up the implied ABORT command\n",
this));
}
IF_DEBUG( COMMANDS) {
TCP_PRINT((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) {
TCP_PRINT((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;
IncrementCbRecvd( cchRequest * sizeof(CHAR));
if ( !fLineEnded) {
//
// 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;
ParseCommand( this, 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!!
TCP_PRINT((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)) {
TCP_PRINT( ( DBG_CONTEXT,
"Encountering an invalid user data ( %08x)\n",
this));
Print();
}
);
TCP_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.
TCP_REQUIRE( DeReference() > 0);
InterlockedDecrement( &m_nControlRead);
DWORD dwError = GetLastError();
IF_DEBUG( ERROR) {
TCP_PRINT( ( 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)) {
TCP_PRINT( ( DBG_CONTEXT,
"Encountering an invalid user data ( %08x)\n",
this));
Print();
}
# endif // DBG
TCP_ASSERT( IS_VALID_USER_DATA( this ) );
IF_DEBUG ( CLIENT) {
TCP_PRINT( ( DBG_CONTEXT,
" USER_DATA( %08x)::DisconnectUserWithError( %lu, %d)."
" RefCount = %d\n",
this, dwError, fNextMsg, QueryReference()));
}
if (!fNextMsg ) {
IF_DEBUG( ERROR) {
if ( QueryReference() <= 1) {
TCP_PRINT(( DBG_CONTEXT, "Invalid Call for Dereference\n"
" UserConn = %08x; Ref = %d\n",
this, QueryReference()));
}
}
// TCP_REQUIRE( DeReference() > 0); // take off the initial ref count.
// 02/11/96 -- some problem causes the assertion to fail.
// code to track the problem.....
if ( DeReference() <= 0) {
DBGPRINTF(( DBG_CONTEXT,
"[%08x] DeReference(initialCount) ==> returns value < 0"
" Error = %d; Ref = %d; Client=%s; LastCmd = %s\n",
this, dwError, QueryReference(),
inet_ntoa(HostIpAddress), m_recvBuffer
));
Print(" Dereference of initial Count failed\n");
DBG_ASSERT( FALSE); // intentional assert
}
// code to track the problem ends here.
}
if ( dwError != NO_ERROR) {
# ifdef CHECK_DBG
sprintf( szBuffer, " Control Socket Error=%u ", dwError);
Print( szBuffer);
# endif // CHECK_DBG
// Produce a log record indicating the cause for failure.
WriteLogRecord( PSZ_CONNECTION_CLOSED_VERB, "", dwError);
}
if ( QueryState() == UserStateDisconnected) {
//
// It is already in disconnected state. Do nothing for disconnect.
//
} else {
SetState( UserStateDisconnected);
if( dwError == ERROR_SEM_TIMEOUT) {
const CHAR * apszSubStrings[3];
IF_DEBUG( CLIENT )
{
TCP_PRINT(( DBG_CONTEXT,
"client (%08x) timed-out\n", this ));
}
sprintf( szBuffer, "%lu", g_pTsvcInfo->QueryConnectionTimeout() );
apszSubStrings[0] = UserName;
apszSubStrings[1] = inet_ntoa( HostIpAddress );
apszSubStrings[2] = szBuffer;
g_pTsvcInfo->LogEvent( FTPD_EVENT_CLIENT_TIMEOUT,
3,
apszSubStrings,
0 );
ReplyToUser(this,
REPLY_SERVICE_NOT_AVAILABLE,
"Timeout (%lu seconds): closing control connection.",
g_pTsvcInfo->QueryConnectionTimeout() );
}
//
// 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;
TCP_ASSERT( pContext != NULL && pUserData != NULL);
TCP_ASSERT( IS_VALID_USER_DATA( pUserData ) );
dwError = *(LPDWORD ) pContext;
return ( pUserData->DisconnectUserWithError( dwError, TRUE));
} // DisconnectUserWorker()
BOOL
DisconnectUser( IN DWORD UserId)
/*++
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;
g_pFtpServerConfig->LockConnectionsList();
fFound = (g_pFtpServerConfig->
EnumerateConnection( DisconnectUserWorker,
(LPVOID ) &dwError,
UserId));
g_pFtpServerConfig->UnlockConnectionsList();
IF_DEBUG( CLIENT) {
DWORD dwError = (fFound) ? NO_ERROR: GetLastError();
TCP_PRINT( ( 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;
TCP_ASSERT( pUserData != NULL);
// Ignode the pContext information.
TCP_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 ) {
TCP_PRINT(( DBG_CONTEXT,
"User %s (%lu) @ %08lX retroactively"
" denied access to %s\n",
pUserData->UserName,
pUserData->QueryId(),
pUserData,
pUserData->CurrentDirectory ));
}
fSuccess = ( pUserData->
DisconnectUserWithError(ERROR_ACCESS_DENIED,
TRUE)
);
//
// Log an event to tell the admin what happened.
//
apszSubStrings[0] = pUserData->UserName;
apszSubStrings[1] = pUserData->CurrentDirectory;
g_pTsvcInfo->LogEvent( FTPD_EVENT_RETRO_ACCESS_DENIED,
2,
apszSubStrings,
0 );
} // no access
} // logged on user
IF_DEBUG( CLIENT) {
DWORD dwError = (fSuccess) ? NO_ERROR: GetLastError();
TCP_PRINT( ( DBG_CONTEXT,
" DisconnectUsersWithNoAccessWorker( %d) returns %d."
" Error = %lu\n",
pUserData->QueryId(), fSuccess,
dwError)
);
if (fSuccess) { SetLastError( dwError); }
}
return ( fSuccess);
} // DisconnectUserWithNoAccessWorker()
VOID
DisconnectUsersWithNoAccess(VOID)
/*++
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;
g_pFtpServerConfig->LockConnectionsList();
fFound = (g_pFtpServerConfig->
EnumerateConnection( DisconnectUserWithNoAccessWorker,
(LPVOID ) &dwError,
0));
g_pFtpServerConfig->UnlockConnectionsList();
IF_DEBUG( CLIENT) {
DWORD dwError = (fFound) ? NO_ERROR: GetLastError();
TCP_PRINT( ( 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
FTP_USER_INFO * 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;
TCP_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->UserName ) + 1 ) * sizeof(WCHAR);
pUserEnumBuffer->cbRequired += cbUserName + sizeof(FTP_USER_INFO);
//
// If there's room for the user data, store it.
//
tConnect = ( pUserEnumBuffer->dwCurrentTime -
pUserData->QueryTimeAtConnection());
if( pUserEnumBuffer->fResult &&
( pUserEnumBuffer->cbRequired <= pUserEnumBuffer->cbSize)
) {
FTP_USER_INFO * pUserInfo =
&pUserEnumBuffer->pUserInfo[ pUserEnumBuffer->nEntry];
pUserEnumBuffer->pszNext -= ( cbUserName / sizeof(WCHAR) );
TCP_ASSERT( (BYTE *) pUserEnumBuffer->pszNext >=
( (BYTE *)pUserInfo + sizeof(FTP_USER_INFO) ) );
pUserInfo->idUser = pUserData->QueryId();
pUserInfo->pszUser = pUserEnumBuffer->pszNext;
pUserInfo->fAnonymous = ( pUserData->Flags & UF_ANONYMOUS ) != 0;
pUserInfo->inetHost = (DWORD)pUserData->HostIpAddress.s_addr;
pUserInfo->tConnect = tConnect;
if( !MultiByteToWideChar( CP_OEMCP,
0,
pUserData->UserName,
-1,
pUserEnumBuffer->pszNext,
(int)cbUserName )
) {
TCP_PRINT(( DBG_CONTEXT,
"MultiByteToWideChar failed???\n" ));
pUserEnumBuffer->fResult = ( pUserEnumBuffer->fResult && FALSE);
} else {
pUserEnumBuffer->nEntry++;
}
} 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(
VOID * pvEnum,
DWORD * pcbBuffer
)
/*++
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.
Returns:
TRUE if enumeration is successful ( all connected users accounted for)
FALSE otherwise
--*/
{
USER_ENUM_BUFFER userEnumBuffer;
FTP_USER_ENUM_STRUCT * pEnum;
BOOL fSuccess;
TCP_ASSERT( pcbBuffer != NULL );
IF_DEBUG( USER_DATABASE) {
TCP_PRINT( ( DBG_CONTEXT,
" Entering EnumerateUsers( %08x, %08x[%d]).\n",
pvEnum, pcbBuffer, *pcbBuffer));
}
//
// Setup the data in user enumeration buffer.
//
pEnum = (FTP_USER_ENUM_STRUCT *)pvEnum;
userEnumBuffer.cbSize = *pcbBuffer;
userEnumBuffer.cbRequired = 0;
userEnumBuffer.pUserInfo = pEnum->Buffer;
userEnumBuffer.nEntry = 0;
userEnumBuffer.dwCurrentTime = GetCurrentTimeInSeconds();
userEnumBuffer.pszNext = ((WCHAR *)
( (BYTE *) pEnum->Buffer + *pcbBuffer));
userEnumBuffer.fResult = TRUE;
//
// Scan the users and get the information required.
//
g_pFtpServerConfig->LockConnectionsList();
fSuccess = (g_pFtpServerConfig->
EnumerateConnection( EnumerateUserInBufferWorker,
(LPVOID ) &userEnumBuffer,
0));
g_pFtpServerConfig->UnlockConnectionsList();
//
// Update enum buffer header.
//
pEnum->EntriesRead = userEnumBuffer.nEntry;
*pcbBuffer = userEnumBuffer.cbRequired;
IF_DEBUG( USER_DATABASE) {
TCP_PRINT((DBG_CONTEXT,
" Leaving EnumerateUsers() with %d."
" Entires 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)
/*++
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.
Returns:
SOCKERR - 0 if successful, !0 if not.
History:
MuraliK 12-April-1995
--*/
{
SOCKERR serr = 0;
//
// Send messages if there is any thing to send
//
if ( pszzMessage != NULL && *pszzMessage != '\0') {
LPCSTR pszNext = pszzMessage;
TCP_ASSERT( *pszNext != '\0');
serr = SockPrintf2(this, QueryControlSocket(),
"%u-%s",
nReplyCode, pszNext);
for( pszNext += strlen( pszNext) + 1; // skip to next message.
( serr == 0 && *pszNext != '\0'); // loop till done or error.
pszNext += strlen( pszNext) + 1 // goto next message
) {
serr = SockPrintf2(this, QueryControlSocket(),
" %s", // note the leading blank space!!
pszNext);
} // for
} // if ( valid message)
return ( serr);
} // USER_DATA::SendMultilineMessge()
SOCKERR
USER_DATA::SendDirectoryAnnotation( IN UINT ReplyCode)
/*++
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.
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;
BOOL fFirstReply = TRUE;
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;
}
//
// 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( fFirstReply )
{
serr = ReplyToUser( this,
ReplyCode,
"%s",
szLine );
fFirstReply = FALSE;
}
else
{
serr = SockPrintf2(this,
QueryControlSocket(),
" %s", // <--- note the leading blank
szLine );
}
if( serr != 0 )
{
//
// Socket error sending file.
//
break;
}
}
//
// 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
)
/*++
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.
Returns:
SOCKERR. 0 if successful and !0 if failure.
--*/
{
BOOL fDelete = TRUE;
LPCSTR pszText;
APIERR serr;
TCP_ASSERT( pszPath != NULL);
pszText = AllocErrorText( dwError );
if( pszText == NULL ) {
pszText = pszDefaultErrorMsg;
fDelete = FALSE;
}
serr = ReplyToUser( this,
REPLY_FILE_NOT_FOUND,
PSZ_FILE_NOT_FOUND,
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;
LPSTR pszUser;
CHAR rgchRoot[MAX_PATH];
//
// Find the appropriate user name.
//
if( TEST_UF( this, ANONYMOUS ) ) {
pszUser = (char *) pszAnonymousName;
} else {
pszUser = strpbrk( UserName, "/\\" );
pszUser = ( pszUser == NULL) ? UserName : pszUser + 1;
}
//
// Try the top-level home directory. If this fails, bag out.
// Set and try to change directory to symbolic root.
//
CurrentDirectory[0] = '\0'; // initially nothing.
g_pTsvcInfo->LockThisForRead();
TCP_ASSERT( strlen( g_pTsvcInfo->QueryRoot()) < MAX_PATH);
strncpy( rgchRoot, g_pTsvcInfo->QueryRoot(), MAX_PATH - 1);
g_pTsvcInfo->UnlockThis();
err = VirtualChDir( this, rgchRoot); // change to default dir.
if( err == NO_ERROR ) {
//
// We successfully CD'd into the top-level home
// directory. Now see if we can CD into pszUser.
//
if( VirtualChDir( this, pszUser ) != NO_ERROR ) {
//
// Nope, try DEFAULT_SUB_DIRECTORY. If this fails, just
// hang-out at the top-level home directory.
//
VirtualChDir( this, PSZ_DEFAULT_SUB_DIRECTORY );
}
}
return ( err);
} // USER_DATA::CdToUsersHomeDirectory()
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;
TCP_ASSERT( pszFile != NULL );
err = VirtualCanonicalize(szCanonPath,
&cbSize,
pszFile,
AccessTypeRead,
NULL,
szVirtualPath,
&cchVirtualPath);
if( err == NO_ERROR ) {
IF_DEBUG( VIRTUAL_IO ) {
TCP_PRINT(( DBG_CONTEXT,
"Opening File: %s\n", szCanonPath ));
}
// store the vitual path name of file.
strncpy( m_rgchFile, szVirtualPath, sizeof(m_rgchFile) - 1);
if ( ImpersonateUser()) {
m_pOpenFileInfo = TsCreateFile( g_pTsvcInfo->GetTsvcCache(),
szCanonPath,
QueryUserToken(),
TS_CACHING_DESIRED); // caching desired.
RevertToSelf();
} else {
m_pOpenFileInfo = NULL;
}
if( m_pOpenFileInfo == NULL ) {
err = GetLastError();
WriteLogRecord( PSZ_SENT_VERB, m_rgchFile, err);
} else {
DWORD dwAttrib = m_pOpenFileInfo->QueryAttributes();
FacIncrement( FacFilesOpened);
TCP_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 ) {
TCP_PRINT(( 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;
// make sure it includes the full path
DBG_ASSERT( m_rgchFile[0] == '/');
TS_OPEN_FILE_INFO * pOpenFileInfo =
(TS_OPEN_FILE_INFO *) InterlockedExchange( (LPLONG ) &m_pOpenFileInfo,
NULL);
if ( pOpenFileInfo != NULL) {
FacIncrement( FacFilesClosed);
TsCloseHandle( g_pTsvcInfo->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_INFORMATIONA ilRequest;
DWORD dwLog;
CHAR pszErrorMessage[MAX_ERROR_MESSAGE_LEN] = "";
DWORD cchErrorMessage = MAX_ERROR_MESSAGE_LEN;
CHAR rgchRequest[MAX_PATH + 20];
DWORD cch;
//
// Fill in the information that needs to be logged.
//
ilRequest.pszClientHostName = QueryClientHostName();
ilRequest.pszClientUserName = QueryUserName();
ilRequest.pszClientPassword = NULL;
ilRequest.pszServerIpAddress = NULL; // NYI
ilRequest.msTimeForProcessing = QueryProcessingTime();
ilRequest.liBytesSent = m_licbSent;
ilRequest.liBytesRecvd.LowPart = m_cbRecvd;
ilRequest.liBytesRecvd.HighPart = 0; // since we'll typically recv less
ilRequest.dwServiceSpecificStatus = 0;
ilRequest.dwWin32Status = dwError;
cch = wsprintfA( rgchRequest, "[%d] %s", QueryId(), pszVerb);
TCP_ASSERT( cch < MAX_PATH + 20);
ilRequest.pszOperation = rgchRequest;
ilRequest.pszTarget = pszPath;
ilRequest.pszParameters = NULL;
dwLog = g_pTsvcInfo->LogInformation( &ilRequest, pszErrorMessage,
&cchErrorMessage);
if ( dwLog != NO_ERROR) {
IF_DEBUG( ERROR) {
TCP_PRINT((DBG_CONTEXT,
" Unable to log information to logger. Error = %u\n",
dwLog));
TCP_PRINT((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
UPDATE_LARGE_COUNTER( TotalBytesSent, m_licbSent.QuadPart);
m_licbSent.QuadPart = 0;
return;
} // USER_DATA::WriteLogRecord()
//
// 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;
TCP_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
//
SetPassiveSocket( INVALID_SOCKET);
//
// 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;
}
TCP_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
TCP_PRINT( ( 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, CurrentDirectory, CurrentDirHandle,
UserName, UserToken, QueryId(),
Flags, m_xferType, m_xferMode));
TCP_PRINT( ( 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));
TCP_PRINT(( 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];
TCP_ASSERT( pszDest != NULL);
TCP_ASSERT( lpdwSize != NULL);
TCP_ASSERT( pszSearchPath != NULL);
IF_DEBUG( VIRTUAL_IO) {
TCP_PRINT(( 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.
TCP_PRINT((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;
TCP_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 ( !TsLookupVirtualRoot(g_pTsvcInfo->GetTsvcCache(),
pszSearchPath,
pszDest, // get absolute path
lpdwSize,
&dwAccessMask, // Access mask
NULL, // pcchDirRoot
NULL, // pcchVRoot
&m_hVrootImpersonation,
NULL, // pszAddress
NULL
)
) {
dwError = GetLastError();
TCP_PRINT(( DBG_CONTEXT,
"TsLookup 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();
TCP_PRINT(( 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 ( pdwAccessMask != NULL) {
*pdwAccessMask = dwAccessMask;
}
}
IF_DEBUG( VIRTUAL_IO) {
if ( dwError != NO_ERROR) {
TCP_PRINT(( DBG_CONTEXT,
" Cannot Canonicalize %s -- %s, Error = %lu\n",
QueryCurrentDirectory(),
pszSearchPath,
dwError));
} else {
TCP_PRINT(( 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;
//
// 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.
//
if( fPassive ) {
SOCKADDR_IN saddrClient;
//
// Ensure we actually created a passive listen data socket.
// no data transfer socket is in AsyncIo object.
//
TCP_ASSERT( m_sPassiveDataListen != INVALID_SOCKET );
//
// Wait for a connection.
//
IF_DEBUG( CLIENT ) {
TCP_PRINT(( DBG_CONTEXT,
"waiting for passive connection on socket %d\n",
m_sPassiveDataListen ));
}
serr = AcceptSocket( m_sPassiveDataListen,
&DataSocket,
&saddrClient,
TRUE ); // enforce timeouts
//
// We can kill m_sPassiveDataListen now.
// We only allow one connection in passive mode.
//
SetPassiveSocket( INVALID_SOCKET);
if( serr == 0 ) {
//
// Got one.
//
TCP_ASSERT( DataSocket != INVALID_SOCKET );
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 ){
TCP_PRINT(( 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
htonl( INADDR_ANY ), // Local address
g_pFtpServerConfig->QueryDataPort(),
DataIpAddress.s_addr,// RemoteAddr
DataPort ); // Remote port
if ( serr == 0 ) {
TCP_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 ) {
TCP_PRINT(( 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) {
TCP_PRINT((DBG_CONTEXT,
" %08x::ReadCommand() failed. Error = %u\n",
this, dwError));
SetLastError( dwError);
}
serr = 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 );
//
// Close the data socket.
//
TCP_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::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;
HANDLE hFile;
DWORD dwAttribs;
TS_OPEN_FILE_INFO * pOpenFileInfo;
CHAR rgchSize[MAX_FILE_SIZE_SPEC];
CHAR rgchBuffer[MAX_FILE_SIZE_SPEC + 10];
TCP_ASSERT( pszFileName != NULL && pfErrorSent != NULL);
*pfErrorSent = FALSE;
IF_DEBUG( SEND) {
TCP_PRINT( ( 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 handle and file size
hFile = pOpenFileInfo->QueryFileHandle();
TCP_ASSERT( hFile != INVALID_HANDLE_VALUE );
if ( !pOpenFileInfo->QuerySize(FileSize)) {
dwError = GetLastError();
if( dwError != NO_ERROR ) {
return ( dwError);
}
}
//
// Connect to the client.
//
IsLargeIntegerToDecimalChar( &FileSize, rgchSize);
wsprintfA( rgchBuffer, "(%s bytes)", rgchSize);
dwError = EstablishDataConnection( pszFileName, rgchBuffer );
if ( dwError != NO_ERROR) {
//
// EstablishDataConnection has already notified the
// user of the failure. Return with *pfErrorSent = TRUE so the
// caller won't bother sending the notification again.
//
*pfErrorSent = TRUE;
return ( dwError);
}
INCR_STAT_COUNTER( TotalFilesSent );
//
// Blast the file from a local file to the user.
//
Reference(); // incr ref since async data transfer is started
SET_UF( this, ASYNC_TRANSFER);
m_licbSent.QuadPart = m_licbSent.QuadPart + FileSize.QuadPart;
fTransmit = ( m_AioDataConnection.
TransmitFile( hFile,
FileSize, // cbToSend ( send entire file)
NULL) // no FileBuffers.
);
if ( !fTransmit) {
dwError = GetLastError();
TCP_PRINT( ( DBG_CONTEXT,
" %08x:: Unable to transmit file ( %s) (h = %08x)."
" Error = %u\n",
this,
pszFileName,
hFile,
dwError));
// decr refcount since async tfr failed.
TCP_REQUIRE( DeReference() > 0);
//
// Disconnect connection, since we are in error.
//
TCP_REQUIRE( DestroyDataConnection( dwError));
}
//
// 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.
--*/
{
SOCKET sPassiveOld;
sPassiveOld = (SOCKET) InterlockedExchange( (LPLONG) &m_sPassiveDataListen,
sPassive);
if ( sPassiveOld != INVALID_SOCKET) {
FacDecrement( FacPassiveDataListens);
#ifndef CHICAGO
DBG_REQUIRE( CloseSocket( sPassiveOld) == 0);
#else
CloseSocket( sPassiveOld); // !!!
#endif
}
if ( sPassive != INVALID_SOCKET) {
FacIncrement(FacPassiveDataListens);
}
return;
} // USER_DATA::SetPassiveSocket()
/************************************************************
* 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;
TCP_ASSERT( pUserData != NULL);
TCP_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);
}
TCP_REQUIRE( pUserData->Reference() > 0);
# if DBG
if ( !IS_VALID_USER_DATA( pUserData)) {
TCP_PRINT( ( DBG_CONTEXT,
"Encountering an invalid user data ( %08x)\n",
pUserData));
pUserData->Print();
}
# endif // DBG
TCP_ASSERT( IS_VALID_USER_DATA( pUserData ) );
pUserData->ProcessAsyncIoCompletion( cbIo, dwError, pAioConn, fTimedOut);
DereferenceUserDataAndKill(pUserData);
return;
} // ProcessUserAsyncIoCompletion()
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.
--*/
{
IF_SPECIAL_DEBUG( CRITICAL_PATH) {
pUserData->Print( " Deref ");
}
if ( !pUserData->DeReference()) {
//
// Deletion of the object USER_DATA is required.
//
IF_DEBUG( USER_DATABASE) {
TCP_PRINT( ( DBG_CONTEXT,
" UserData( %08x) is being deleted.\n",
pUserData));
}
pUserData->Cleanup();
g_pFtpServerConfig->RemoveConnection( pUserData);
pUserData = NULL;
}
} // 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;
TCP_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 :
TCP_PRINT(( DBG_CONTEXT,
"PathAccessCheck - invalid access type %d\n",
_access ));
TCP_ASSERT( FALSE );
break;
}
if (!fAccessGranted) {
SetLastError( ERROR_ACCESS_DENIED);
}
return ( fAccessGranted);
} // PathAccessCheck()
/******************************* End Of File *************************/