Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

3691 lines
88 KiB

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1993-1995 **/
/**********************************************************************/
/*
engine.cxx
Command parser & execution for FTPD Service. This module parses
and executes the commands received from the control socket.
Functions exported by this module:
All function for processing FTP commands
FILE HISTORY:
KeithMo 07-Mar-1993 Created.
MuraliK 21-March-1995 Modified to use common TsLogonUser()
supporting anonymous logon.
MuraliK 27-Mar - April 1995
Cleaning up FTP server engine for
- to support new Internet services interface.
- to support Async Io Transfers
- split and move the command tables to ftpcmd.cxx
- to replace USER_DATA::DataSocket with
USER_DATA::m_AioDataConnection
- moved SiteSTATS and statistics to ftpcmd.cxx
- replaced SockReply2 with ReplyToUser()
- modified MainREIN and MainQUIT to use
new USER_DATA functions.
- modified usage of listing functions.
- SendErrorToClient() used for managing and sending
error message to client.
- message strings removed to be top of the file and defined.
- size of file sent along before file is transferred.
terryk 18-Sep-1996 add MainSize
*/
#include "ftpdp.hxx"
#include "ftpcmd.hxx"
#include <stdlib.h>
#include <limits.h>
//
// Private constants.
//
//
// Since the default RFC port for ftp is 21, we calculate the Data port
// value from it.
// However, an admin at a site may change the port number to something else
// ==> this will create PROBLEMS..... NYI
//
/**************************************************
* Constant Message Strings used by this Module
**************************************************/
//
// Below is a list of strings, which are in
// CStrM( StringName, ActualString) format
// The following table contains globally all strings used within this module
// Advantage: One place to look for various strings
// + possible internationalization
// Be careful. None of these messages may be modified. Protocol may break.
// This will be expanded into
// const char PSZ_StringName[] = ActualString; and
// enumerated value LEN_StringName = sizeof( ActualString).
//
# define ConstantStringsForThisModule() \
CStrM( CANNOT_OPEN_DATA_CONNECTION, "Can't open data connection.") \
CStrM( TRANSFER_ABORTED, "Connection closed; transfer aborted.") \
CStrM( USER_NOT_LOGGED_IN, "User %s cannot log in.") \
CStrM( INSUFFICIENT_RESOURCES, "Insufficient system resources.") \
CStrM( REQUEST_ID_FOR_ANONYMOUS, \
"Anonymous access allowed, send identity (e-mail name) as password.")\
CStrM( REQUEST_PASSWORD_FOR_USER, "Password required for %s.") \
CStrM( COMMAND_NOT_IMPLEMENTED, "%s command not implemented." ) \
CStrM( COMMAND_SUCCESSFUL, "%s command successful." ) \
CStrM( SERVICE_READY, "Service ready for new user.") \
CStrM( ENTERING_PASSIVE_MODE, \
"Entering Passive Mode (%d,%d,%d,%d,%d,%d).") \
CStrM( FORM_MESSAGE, "Form must be N or T." ) \
CStrM( TYPE_NOT_IMPLEMENTED, "Type %c not implemented.") \
CStrM( BYTE_SIZE_SPEC, "Byte size must be 8." ) \
CStrM( TYPE_SET_TO, "Type set to %c.") \
CStrM( UNIMPLEMENTED_STRU_TYPE, "Unimplemented STRU type." ) \
CStrM( INVALID_STRU_TYPE, "Invalid STRU type." ) \
CStrM( STRU_TYPE_OK, "STRU %c ok.") \
CStrM( UNIMPLEMENTED_MODE, "Unimplemented MODE type.") \
CStrM( MODE_OK, "Mode %c ok.") \
CStrM( REPLY_MARKER_SPEC, "Reply marker is invalid.") \
CStrM( REPLY_RESTARTING, "Restarting at %s." ) \
CStrM( READY_FOR_DEST_FILE, "File exists, ready for destination name" ) \
CStrM( BAD_COMMAND_SEQUENCE, "Bad sequence of commands." ) \
CStrM( CURRENT_DIRECTORY, "\"%s\" is current directory.") \
CStrM( VERSION_INFO, "Windows_NT") \
CStrM( SERVER_STATUS_BEGIN, " %s Windows NT FTP Server status:") \
CStrM( SERVER_STATUS_END, "End of status.") \
CStrM( FILE_STATUS, "status of %s:") \
CStrM( MSDOS_DIRSTYLE, "MSDOS-like directory output is %s") \
CStrM( DIRECTORY_ANNOTATION, "directory annotation is %s") \
CStrM( LOGGED_IN_USER_MESSAGE, "Anonymous user logged in%s.") \
CStrM( USER_LOGGED_IN, "User %s logged in%s.") \
CStrM( USER_CANNOT_LOG_IN, "User %s cannot log in.") \
CStrM( INACCESSIBLE_HOME_DIR, \
"User %s cannot log in, home directory inaccessible.") \
CStrM( LICENSE_QUOTA_EXCEEDED, \
"User %s cannot log in, license quota exceeded.") \
CStrM( NO_GUEST_ACCESS, \
"User %s cannot log in, guest access not allowed.") \
CStrM( ANONYMOUS_NAME, "Anonymous") \
CStrM( FTP_NAME, "Ftp") \
CStrM( ARGS_DELIMITER, " \t") \
CStrM( NO_FILE_OR_DIRECTORY, "No such file or directory.") \
CStrM( DIRECTORY_CREATE, "\"%s\" directory created.") \
CStrM( CANNOT_CREATE_FILE, "Cannot create file.") \
CStrM( CANNOT_CREATE_UNIQUE_FILE, "Cannot create unique file.") \
CStrM( INVALID_COMMAND, "Invalid %s Command.") \
CStrM( RESPONSE_ON, "on") \
CStrM( RESPONSE_OFF, "off") \
CStrM( GUEST_ACCESS, " (guest access)" ) \
CStrM( CREATE_VERB, "created" ) \
CStrM( APPEND_VERB, "appended" ) \
CStrM( USER_VERB, "USER" ) \
CStrM( PASSWORD_VERB, "PASS" ) \
CStrM( QUIT_VERB, "QUIT" ) \
CStrM( ABORT_VERB, "ABORT" ) \
CStrM( REIN_VERB, "REIN" ) \
CStrM( DESTINATION_FILE_EXISTS, "Destination file already exists.") \
CStrM( RNFR_VERB, "RNFR" ) \
CStrM( RNTO_VERB, "RNTO" ) \
CStrM( DELE_VERB, "DELE" ) \
CStrM( RMD_VERB, "RMD" ) \
CStrM( MKD_VERB, "MKD" ) \
CStrM( DUMMY_END, "DummyMsg. Add string before this one")
//
// Generate the strings ( these are private globals of this module).
//
# define CStrM( StringName, ActualString) \
const char PSZ_ ## StringName[] = ActualString ;
ConstantStringsForThisModule()
# undef CStrM
//
// Private prototypes.
//
BOOL
ParseStringIntoAddress(
LPSTR pszString,
LPIN_ADDR pinetAddr,
LPPORT pport
);
DWORD
ReceiveFileFromUserAndClose(
LPUSER_DATA pUserData,
LPSTR pszFileName,
LPHANDLE phFile
);
BOOL
MyLogonUser(
LPUSER_DATA pUserData,
LPSTR pszPassword,
LPBOOL pfAsGuest,
LPBOOL pfHomeDirFailure,
LPBOOL pfLicenseExceeded
);
DWORD
DetermineUserAccess(FTP_SERVER_INSTANCE *pInstance);
//
// Public functions.
//
//
// Functions Implementing FTP functionality.
//
BOOL
MainUSER(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
Implements the USER command.
Format: USER <userName>
Arguments:
pUserData - the user initiating the request.
pszArg - Command arguments. Will be NULL if no arguments given.
Returns:
BOOL - TRUE if arguments are OK; FALSE if syntax error.
--*/
{
DBG_ASSERT( pUserData != NULL );
LPFTP_SERVER_STATISTICS pStatsObj = pUserData->QueryInstance()->QueryStatsObj();
DBG_ASSERT( pStatsObj != NULL );
if( pUserData->IsLoggedOn()) {
if( TEST_UF( pUserData, ANONYMOUS ) ) {
pStatsObj->DecrCurrentAnonymousUsers();
} else {
pStatsObj->DecrCurrentNonAnonymousUsers();
}
SET_UF( pUserData, LOGGED_ON);
}
//
// Squirrel away a copy of the domain\user name for later.
// If the name is too long, then don't let them logon.
//
if ( strlen( pszArg ) >= ( MAX_USERNAME_LENGTH ) ) {
ReplyToUser(pUserData,
REPLY_NOT_LOGGED_IN,
PSZ_USER_NOT_LOGGED_IN,
pszArg );
} else {
BOOL fNameIsAnonymous;
LPCSTR pszReply;
fNameIsAnonymous = ( ( _stricmp( pszArg, PSZ_ANONYMOUS_NAME ) == 0 ) ||
( _stricmp( pszArg, PSZ_FTP_NAME ) == 0 )
);
pUserData->SetUserName( pszArg );
if( fNameIsAnonymous ) {
SET_UF( pUserData, ANONYMOUS );
} else {
CLEAR_UF( pUserData, ANONYMOUS );
}
//
// remember that we're waiting for PASS command in case we
// get disconnected.
//
SET_UF( pUserData, WAIT_PASS );
//
// If we already have an impersonation token, then remove it.
// This will allow us to impersonate the new user.
//
pUserData->FreeUserToken();
//
// Tell the client that we need a password.
//
pszReply =(((fNameIsAnonymous) && pUserData->QueryInstance()->AllowAnonymous())
? PSZ_REQUEST_ID_FOR_ANONYMOUS
: PSZ_REQUEST_PASSWORD_FOR_USER);
ReplyToUser( pUserData,
REPLY_NEED_PASSWORD,
pszReply,
pszArg);
pUserData->LockUser();
if( pUserData->QueryState() != UserStateDisconnected ) {
pUserData->SetState( UserStateWaitingForPass);
}
pUserData->UnlockUser();
}
pUserData->WriteLogRecord( PSZ_USER_VERB, pszArg);
return TRUE;
} // MainUSER()
BOOL
MainPASS(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
Implements the PASS command.
Format: PASS <password>
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
DWORD dwError = NO_ERROR;
DBG_ASSERT( pUserData != NULL );
//
// PASS command only valid in WaitingForPass state.
//
DBG_ASSERT( pUserData->QueryState() == UserStateWaitingForPass );
if( ( pszArg != NULL ) && ( strlen( pszArg ) > PWLEN ) ) {
return FALSE;
}
//
// Try to logon the user.
//
BOOL fAsGuest;
BOOL fHomeDirFailure;
BOOL fLicenseExceeded;
DBG_ASSERT( pUserData->QueryUserToken() == NULL );
//
// we got the PASS command we we're waiting for
//
CLEAR_UF( pUserData, WAIT_PASS );
pUserData->QueryInstance()->QueryStatsObj()->IncrLogonAttempts();
if( MyLogonUser(pUserData,
pszArg,
&fAsGuest,
&fHomeDirFailure,
&fLicenseExceeded )
) {
const CHAR * pszGuestAccess =
( ( fAsGuest) ? PSZ_GUEST_ACCESS : "");
//
// Successful logon.
//
if( *pUserData->QueryUserName() != '-' ) {
PCSTR pszMsg;
pUserData->QueryInstance()->LockConfig();
pszMsg = pUserData->QueryInstance()->QueryGreetingMsg();
if( pszMsg && *pszMsg ) {
pUserData->SendMultilineMessage(
REPLY_USER_LOGGED_IN,
pszMsg,
TRUE, // first reply line
FALSE); // lst reply line
}
pUserData->QueryInstance()->UnLockConfig();
if( TEST_UF( pUserData, ANNOTATE_DIRS )) {
pUserData->SendDirectoryAnnotation(
REPLY_USER_LOGGED_IN,
FALSE); // first reply line
}
}
LPFTP_SERVER_STATISTICS pStats = pUserData->QueryInstance()->QueryStatsObj();
DBG_ASSERT( pStats != NULL );
if( TEST_UF( pUserData, ANONYMOUS ) ) {
pStats->IncrAnonymousUsers();
ReplyToUser(pUserData,
REPLY_USER_LOGGED_IN,
PSZ_LOGGED_IN_USER_MESSAGE,
pszGuestAccess );
} else {
pStats->IncrNonAnonymousUsers();
ReplyToUser(pUserData,
REPLY_USER_LOGGED_IN,
PSZ_USER_LOGGED_IN,
pUserData->QueryUserName(),
pszGuestAccess );
}
pUserData->LockUser();
if( pUserData->QueryState() != UserStateDisconnected ) {
pUserData->SetState( UserStateLoggedOn);
SET_UF( pUserData, LOGGED_ON);
}
pUserData->UnlockUser();
} else {
const CHAR * pszReply = PSZ_USER_CANNOT_LOG_IN;
//
// Logon failure.
//
dwError = GetLastError();
if( fHomeDirFailure ) {
pszReply = PSZ_INACCESSIBLE_HOME_DIR;
} else if ( fLicenseExceeded) {
pszReply = PSZ_LICENSE_QUOTA_EXCEEDED;
} else if ( fAsGuest && ! pUserData->QueryInstance()->AllowGuestAccess()) {
pszReply = PSZ_NO_GUEST_ACCESS;
}
ReplyToUser(pUserData,
REPLY_NOT_LOGGED_IN,
pszReply,
pUserData->QueryUserName() );
pUserData->LockUser();
if( pUserData->QueryState() != UserStateDisconnected ) {
pUserData->SetState( UserStateWaitingForUser);
CLEAR_UF( pUserData, LOGGED_ON);
pUserData->ClearUserName();
}
pUserData->UnlockUser();
}
pUserData->WriteLogRecord( PSZ_PASSWORD_VERB,
(TEST_UF( pUserData, ANONYMOUS)) ? pszArg : NULL,
dwError);
return TRUE;
} // MainPASS()
BOOL
MainACCT(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the ACCT command.
This is at present not implemented...
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
DBG_ASSERT( pUserData != NULL );
ReplyToUser(pUserData,
REPLY_COMMAND_SUPERFLUOUS,
PSZ_COMMAND_NOT_IMPLEMENTED,
"ACCT");
return TRUE;
} // MainACCT()
BOOL
MainCWD(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the CWD command -- Change Working Directory.
Format: CWD <newDirectoryName>
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// If argument is NULL or "~", CD to home directory.
//
if( ( pszArg == NULL ) || ( strcmp( pszArg, "~" ) == 0 ) ) {
err = pUserData->CdToUsersHomeDirectory( PSZ_ANONYMOUS_NAME);
} else {
err = VirtualChDir( pUserData, pszArg);
}
if( err == NO_ERROR ) {
if( TEST_UF( pUserData, ANNOTATE_DIRS ) &&
( *pUserData->QueryUserName() != '-' )
) {
pUserData->SendDirectoryAnnotation(
REPLY_FILE_ACTION_COMPLETED,
TRUE); // first reply line
}
ReplyToUser(pUserData,
REPLY_FILE_ACTION_COMPLETED,
PSZ_COMMAND_SUCCESSFUL,
"CWD");
} else {
pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
}
return TRUE;
} // MainCWD()
BOOL
MainCDUP(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
CDUP -- changes to the parent directory if possible.
--*/
{
return MainCWD( pUserData, ".." );
} // MainCDUP
BOOL
MainSIZE(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements SIZE command - used for retrieving the size of a file.
Format: SIZE pathForFile
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err = (!NO_ERROR);
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn() );
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg != NULL );
//
// Try to open the file.
//
if (pUserData->ImpersonateUser()){
err = pUserData->OpenFileForSend( pszArg );
pUserData->RevertToSelf();
}
else
{
err = GetLastError();
}
if( err == NO_ERROR ) {
// just return the file size
err = pUserData->GetFileSize();
}
if( err != NO_ERROR ) {
pUserData->SendErrorToClient(pszArg, err,
PSZ_NO_FILE_OR_DIRECTORY);
pUserData->WriteLogRecordForSendError( err );
}
pUserData->CloseFileForSend( err); // close the file, now that we're done.
return TRUE;
} // MainSIZE()
BOOL
MainMDTM(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the MDTM command - used for retrieving the last
modified time for a file. We open the file, get the file mod time, format
it and send it.
Format: SIZE pathForFile
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err = (!NO_ERROR);
SYSTEMTIME SystemTime;
CHAR rgchBuffer[sizeof("YYYYMMDDHHMMSS")];
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn() );
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg != NULL );
//
// Try to open the file.
//
if (pUserData->ImpersonateUser()){
err = pUserData->OpenFileForSend( pszArg );
pUserData->RevertToSelf();
}
else
{
err = GetLastError();
}
if( err == NO_ERROR ) {
// Get the last write time.
err = pUserData->GetFileModTime(&SystemTime);
if (err == NO_ERROR) {
// Format the time.
wsprintfA(rgchBuffer, "%.4hu%.2hu%.2hu%.2hu%.2hu%.2hu",
SystemTime.wYear,
SystemTime.wMonth,
SystemTime.wDay,
SystemTime.wHour,
SystemTime.wMinute,
SystemTime.wSecond );
ReplyToUser( pUserData, REPLY_FILE_STATUS, rgchBuffer );
}
}
if( err != NO_ERROR ) {
pUserData->SendErrorToClient(pszArg, err,
PSZ_NO_FILE_OR_DIRECTORY);
pUserData->WriteLogRecordForSendError( err );
}
pUserData->CloseFileForSend( err); // close the file, now that we're done.
return TRUE;
} // MainMDTM()
BOOL
MainSMNT(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the SMNT command.
This is at present not implemented...
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
DBG_ASSERT( pUserData != NULL );
ReplyToUser(pUserData,
REPLY_COMMAND_SUPERFLUOUS,
PSZ_COMMAND_NOT_IMPLEMENTED,
"SMNT");
return TRUE;
} // MainSMNT()
BOOL
MainQUIT(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the QUIT command.
Format: QUIT
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
CHAR rgchBuffer[MAX_REPLY_LENGTH];
DWORD len;
LPCSTR pszMsg;
SOCKERR serr;
DBG_ASSERT( pUserData != NULL );
SET_UF( pUserData, CONTROL_QUIT);
//
// Reply to the quit command.
//
pUserData->QueryInstance()->LockConfig();
pszMsg = pUserData->QueryInstance()->QueryExitMsg();
DBG_ASSERT( pszMsg != NULL);
len = FtpFormatResponseMessage(REPLY_CLOSING_CONTROL,
pszMsg,
rgchBuffer,
MAX_REPLY_LENGTH);
pUserData->QueryInstance()->UnLockConfig();
DBG_ASSERT( len <= MAX_REPLY_LENGTH);
serr = SockSend( pUserData, pUserData->QueryControlSocket(),
rgchBuffer, len);
//
// Cause a disconnection of the user.
// This will blow away the sockets first. Blowing off sockets
// will cause ATQ to wake up for pending data calls
// and send a call back indicating failure.
// Since we disconnect now, we will not submit any
// Reads to control socket ==> no more control calls come back from ATQ.
// At the call back processing we will decrement reference counts
// appropriate for cleanup.
//
pUserData->DisconnectUserWithError( NO_ERROR, FALSE);
pUserData->WriteLogRecord( PSZ_QUIT_VERB, "");
return TRUE;
} // MainQUIT()
BOOL
MainREIN(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function executes REIN command - ReInitialize
Format: REIN
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
DBG_ASSERT( pUserData != NULL );
pUserData->ReInitializeForNewUser();
ReplyToUser(pUserData,
REPLY_SERVICE_READY,
PSZ_SERVICE_READY);
pUserData->WriteLogRecord( PSZ_REIN_VERB, pszArg);
return TRUE;
} // MainREIN()
BOOL
MainPORT(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the PORT command.
Format: PORT <ipAddress>,<portNumber>
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
IN_ADDR DataIpAddress;
PORT DataPort;
DBG_ASSERT( pUserData != NULL );
//
// Parse the string into address/port pair.
//
if( !ParseStringIntoAddress( pszArg,
&DataIpAddress,
&DataPort ) )
{
return FALSE;
}
//
// Determine if someone is trying to give us a bogus address/port.
// If the port number is less than IPPORT_RESERVED,
// then there is a possibility of port attack. Allow this only
// if port attack flag is enabled
//
if (!pUserData->QueryInstance()->IsEnablePortAttack())
{
if ( ( DataIpAddress.s_addr != pUserData->HostIpAddress.s_addr ) ||
( DataPort != CONN_PORT_TO_DATA_PORT(pUserData->LocalIpPort) &&
ntohs( DataPort ) < IPPORT_RESERVED) )
{
ReplyToUser(pUserData,
REPLY_UNRECOGNIZED_COMMAND,
PSZ_INVALID_COMMAND,
"PORT");
return TRUE;
}
}
//
// Save the address/port pair into per-user data.
//
pUserData->DataIpAddress = DataIpAddress;
pUserData->DataPort = DataPort;
//
// Disable passive mode for this user.
//
CLEAR_UF( pUserData, PASSIVE );
//
// Nuke any open data socket.
//
pUserData->CleanupPassiveSocket( TRUE );
//
// Let the client know we accepted the port command.
//
ReplyToUser(pUserData,
REPLY_COMMAND_OK,
PSZ_COMMAND_SUCCESSFUL,
"PORT");
return TRUE;
} // MainPORT()
BOOL
MainPASV(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the PASV command - used for setting passive mode.
Format: PASV
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
SOCKET DataSocket = INVALID_SOCKET;
SOCKERR serr = 0;
SOCKADDR_IN saddrLocal;
INT cbLocal;
DBG_ASSERT( pUserData != NULL );
//
// Nuke the old passive socket
//
pUserData->CleanupPassiveSocket( TRUE );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn() );
//
// Create a new data socket.
//
serr = CreateFtpdSocket( &DataSocket,
pUserData->LocalIpAddress.s_addr,
0,
pUserData->QueryInstance() );
if( serr == 0 )
{
//
// Determine the port number for the new socket.
//
cbLocal = sizeof(saddrLocal);
if( getsockname( DataSocket, (SOCKADDR *)&saddrLocal, &cbLocal ) != 0 )
{
serr = WSAGetLastError();
}
}
if( serr == 0 )
{
//
// Success!
//
SET_UF( pUserData, PASSIVE );
pUserData->SetPassiveSocket( DataSocket);
pUserData->DataIpAddress = saddrLocal.sin_addr;
pUserData->DataPort = saddrLocal.sin_port;
ReplyToUser(pUserData,
REPLY_PASSIVE_MODE,
PSZ_ENTERING_PASSIVE_MODE,
saddrLocal.sin_addr.S_un.S_un_b.s_b1,
saddrLocal.sin_addr.S_un.S_un_b.s_b2,
saddrLocal.sin_addr.S_un.S_un_b.s_b3,
saddrLocal.sin_addr.S_un.S_un_b.s_b4,
HIBYTE( ntohs( saddrLocal.sin_port ) ),
LOBYTE( ntohs( saddrLocal.sin_port ) ) );
} else {
//
// Failure during data socket creation/setup. If
// we managed to actually create it, nuke it.
//
if( DataSocket != INVALID_SOCKET )
{
CloseSocket( DataSocket );
DataSocket = INVALID_SOCKET;
}
//
// Tell the user the bad news.
//
ReplyToUser(pUserData,
REPLY_CANNOT_OPEN_CONNECTION,
PSZ_CANNOT_OPEN_DATA_CONNECTION);
}
return TRUE;
} // MainPASV()
BOOL
MainTYPE(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the TYPE command - used for setting type.
Format: TYPE type form <addl arguments>
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
XFER_TYPE newType;
CHAR chType;
CHAR chForm;
LPSTR pszToken;
BOOL fValidForm = FALSE;
DBG_ASSERT( pUserData != NULL );
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg != NULL );
pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER);
if( pszToken == NULL ) {
return FALSE;
}
//
// Ensure we got a valid form type
// (only type N supported).
//
if( pszToken[1] != '\0' ) {
return FALSE;
}
chType = *pszToken;
pszToken = strtok( NULL, PSZ_ARGS_DELIMITER );
if( pszToken == NULL ) {
chForm = 'N'; // default
fValidForm = TRUE;
} else {
switch( *pszToken ) {
case 'n':
case 'N':
chForm = 'N';
fValidForm = TRUE;
break;
case 't':
case 'T':
chForm = 'T';
fValidForm = TRUE;
break;
case 'c':
case 'C':
chForm = 'C';
fValidForm = TRUE;
break;
default:
fValidForm = FALSE;
break;
} // switch
}
//
// Determine the new transfer type.
//
switch( chType ) {
case 'a':
case 'A':
if( !fValidForm ) {
return FALSE;
}
if( ( chForm != 'N' ) && ( chForm != 'T' ) ) {
ReplyToUser(pUserData,
REPLY_PARAMETER_NOT_IMPLEMENTED,
PSZ_FORM_MESSAGE);
return TRUE;
}
newType = XferTypeAscii;
chType = 'A';
break;
case 'e':
case 'E':
if( !fValidForm ) {
return FALSE;
}
if( ( chForm != 'N' ) && ( chForm != 'T' ) ) {
ReplyToUser(pUserData,
REPLY_PARAMETER_NOT_IMPLEMENTED,
PSZ_FORM_MESSAGE);
return TRUE;
}
ReplyToUser(pUserData,
REPLY_PARAMETER_NOT_IMPLEMENTED,
PSZ_TYPE_NOT_IMPLEMENTED, 'E');
return TRUE;
case 'i':
case 'I':
if( pszToken != NULL ) {
return FALSE;
}
newType = XferTypeBinary;
chType = 'I';
break;
case 'l':
case 'L':
if( pszToken == NULL ) {
return FALSE;
}
if( strcmp( pszToken, "8" ) != 0 ) {
if( IsDecimalNumber( pszToken ) ) {
ReplyToUser(pUserData,
REPLY_PARAMETER_NOT_IMPLEMENTED,
PSZ_BYTE_SIZE_SPEC);
return TRUE;
} else {
return FALSE;
}
}
newType = XferTypeBinary;
chType = 'L';
break;
default:
return FALSE;
} // switch (chType)
IF_DEBUG( COMMANDS ) {
DBGPRINTF(( DBG_CONTEXT,
"setting transfer type to %s\n",
TransferType( newType ) ));
}
pUserData->SetXferType( newType);
ReplyToUser(pUserData,
REPLY_COMMAND_OK,
PSZ_TYPE_SET_TO,
chType);
return TRUE;
} // MainTYPE()
BOOL
MainSTRU(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the STRU command - structure information
Format: STRU fileName
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
CHAR chStruct;
CHAR * pszToken;
//
// Sanity check the parameters.
//
DBG_ASSERT( pUserData != NULL );
DBG_ASSERT( pszArg != NULL );
pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER );
if( pszToken == NULL ) {
return FALSE;
}
//
// Ensure we got a valid structure type
// (only type F supported).
//
chStruct = *pszToken;
if( pszToken[1] != '\0' ) {
return FALSE;
}
switch( chStruct ) {
case 'f':
case 'F':
chStruct = 'F';
break;
case 'r':
case 'R':
case 'p':
case 'P':
ReplyToUser(pUserData,
REPLY_PARAMETER_NOT_IMPLEMENTED,
PSZ_UNIMPLEMENTED_STRU_TYPE);
return TRUE;
default:
return FALSE;
}
ReplyToUser(pUserData,
REPLY_COMMAND_OK,
PSZ_STRU_TYPE_OK,
chStruct );
return TRUE;
} // MainSTRU()
BOOL
MainMODE(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements the MODE command - to set mode of tfr.
Format: MODE newMode
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
XFER_MODE newMode;
CHAR chMode;
LPSTR pszToken;
//
// Sanity check the parameters.
//
DBG_ASSERT( pUserData != NULL );
DBG_ASSERT( pszArg != NULL );
pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER );
if( pszToken == NULL ) {
return FALSE;
}
//
// Ensure we got a valid mode type
// (only type S supported).
//
if( pszToken[1] != '\0' ) {
return FALSE;
}
chMode = *pszToken;
switch( chMode )
{
case 's' :
case 'S' :
newMode = XferModeStream;
chMode = 'S';
break;
case 'b' :
case 'B' :
ReplyToUser(pUserData,
REPLY_PARAMETER_NOT_IMPLEMENTED,
PSZ_UNIMPLEMENTED_MODE );
return TRUE;
default :
return FALSE;
}
IF_DEBUG( COMMANDS )
{
DBGPRINTF(( DBG_CONTEXT,
"setting transfer mode to %s\n",
TransferMode( newMode ) ));
}
pUserData->SetXferMode(newMode);
ReplyToUser(pUserData,
REPLY_COMMAND_OK,
PSZ_MODE_OK,
chMode );
return TRUE;
} // MainMODE()
BOOL
MainRETR(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements RETR command - used for retrieving a file.
Format: RETR pathForFile
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
#define MAX_FILE_SIZE_SPEC (32)
APIERR err = (!NO_ERROR);
BOOL fErrorSent = FALSE;
BOOL fTriedToOpenFile = FALSE;
LARGE_INTEGER FileSize;
DWORD dwAttribs;
TS_OPEN_FILE_INFO * pOpenFileInfo;
CHAR rgchSize[MAX_FILE_SIZE_SPEC];
CHAR rgchBuffer[MAX_FILE_SIZE_SPEC + 10];
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn() );
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg != NULL );
if (pUserData->ImpersonateUser()){
err = pUserData->OpenFileForSend( pszArg );
pUserData->RevertToSelf();
}
else
{
err = GetLastError();
}
fTriedToOpenFile = TRUE;
if ( err != NO_ERROR ) {
goto retr_exit;
}
pOpenFileInfo = pUserData->QueryOpenFileInfo();
if ( pOpenFileInfo == NULL )
{
err = ERROR_FILE_NOT_FOUND;
goto retr_exit;
}
//
// Get the file size
//
if ( !pOpenFileInfo->QuerySize(FileSize) )
{
err = GetLastError();
if ( err != NO_ERROR ) {
goto retr_exit;
}
}
if( FileSize.HighPart != 0 ) {
//
// we do not support files >4GB.
//
ReplyToUser(pUserData,
REPLY_FILE_NOT_ALLOWED,
"Cannot send file larger than 4 gigabytes." );
fErrorSent = TRUE;
err = ERROR_MESSAGE_EXCEEDS_MAX_SIZE;
goto retr_exit;
}
// removed to fix copatibility problem with cute ftp
// when FTP service should report always a total size of file to be
// transfered not a remainder like it was before
//FileSize.QuadPart -= (LONGLONG)pUserData->QueryCurrentOffset();
IsLargeIntegerToDecimalChar( &FileSize, rgchSize);
wsprintfA( rgchBuffer, "(%s bytes)", rgchSize);
//
// Establish a data connection
//
err = pUserData->EstablishDataConnection( pszArg, rgchBuffer );
if ( err != NO_ERROR )
{
if ( err == ERROR_IO_PENDING )
{
//
// if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
// client to establish a data connection - it takes care of setting up an event that
// allows us to deal asynchronously with the client connecting [or failing to do so].
// It indicates this asynchrony by returning ERROR_IO_PENDING
//
DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
!pUserData->QueryHavePASVConn() );
if ( fTriedToOpenFile )
{
pUserData->CloseFileForSend( err); // close it always on error
}
return TRUE;
}
fErrorSent = TRUE;
goto retr_exit;
}
err = pUserData->SendFileToUser( pszArg, &fErrorSent);
if ( err != NO_ERROR)
{
//
// Disconnect connection, since we are in error.
//
DBG_REQUIRE( pUserData->DestroyDataConnection( err ));
// since there was a failure we will close the handle right away.
IF_DEBUG( ASYNC_IO) {
DBGPRINTF( ( DBG_CONTEXT,
"SendFileToUser ( %s) failed"
" err = %u\n",
pszArg, err));
}
}
retr_exit:
if( err != NO_ERROR )
{
//
// This command failed, so drop out of PASV mode
//
CLEAR_UF( pUserData, PASSIVE );
//
// Clean up the PASV flags if necessary
//
if ( pUserData->QueryInFakeIOCompletion() )
{
pUserData->CleanupPASVFlags();
}
if ( !fErrorSent)
{
pUserData->SendErrorToClient(pszArg, err,
PSZ_NO_FILE_OR_DIRECTORY);
}
pUserData->WriteLogRecordForSendError( err );
if ( fTriedToOpenFile )
{
pUserData->CloseFileForSend( err); // close it always on error
}
}
return TRUE;
} // MainRETR()
BOOL
MainSTOR(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements STOR command - used for storing a file.
Format: STOR pathForFile
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
HANDLE hFile;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg != NULL );
//
// Try to create the file.
//
err = VirtualCreateFile( pUserData,
&hFile,
pszArg,
FALSE );
if( err != NO_ERROR )
{
pUserData->SendErrorToClient(pszArg, err, PSZ_CANNOT_CREATE_FILE);
}
else
{
//
// Establish a connection to the user
//
err = pUserData->EstablishDataConnection( pszArg );
if ( err != NO_ERROR )
{
CloseHandle( hFile );
if ( err == ERROR_IO_PENDING )
{
//
// if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
// client to establish a data connection - it takes care of setting up an event that
// allows us to deal asynchronously with the client connecting [or failing to do so].
// It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
// any further processing
//
DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
!pUserData->QueryHavePASVConn() );
return TRUE;
}
goto stor_exit;
}
//
// Let the worker do the dirty work. ( blocking call)
// On return, hFile is closed
//
err = ReceiveFileFromUserAndClose( pUserData, pszArg, &hFile );
if( err != NO_ERROR )
{
VirtualDeleteFile( pUserData,
pszArg );
}
}
stor_exit:
pUserData->WriteLogRecord(PSZ_CREATE_VERB, pszArg, err);
return TRUE;
} // MainSTOR()
BOOL
MainSTOU(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements STOU command - used for storing in unique file.
Format: STOU <noArgs>
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
HANDLE hFile;
CHAR szTmpFile[MAX_PATH]; // contains entire path
CHAR * pszTmpFileName; // contains only the file name
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg == NULL );
//
// Try to create the file.
//
szTmpFile[0] = '\0';
err = VirtualCreateUniqueFile( pUserData,
&hFile,
szTmpFile );
//
// extract the file name alone
//
pszTmpFileName = strrchr( szTmpFile, '\\');
if (NULL == pszTmpFileName)
{ pszTmpFileName = szTmpFile; }
else
{ pszTmpFileName++; }
if( err != NO_ERROR )
{
pUserData->SendErrorToClient(pszTmpFileName, err,
PSZ_CANNOT_CREATE_UNIQUE_FILE);
}
else
{
//
// Establish a connection to the user
//
err = pUserData->EstablishDataConnection( pszTmpFileName );
if ( err != NO_ERROR )
{
CloseHandle( hFile );
if ( err == ERROR_IO_PENDING )
{
//
// if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
// client to establish a data connection - it takes care of setting up an event that
// allows us to deal asynchronously with the client connecting [or failing to do so].
// It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
// any further processign
DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
!pUserData->QueryHavePASVConn() );
return TRUE;
}
goto stou_exit;
}
//
// Let the worker do the dirty work.
// On return, hFile is closed
//
err = ReceiveFileFromUserAndClose( pUserData, pszTmpFileName, &hFile );
if( err != NO_ERROR ) {
//
// Note that VirtualCreateUniqueFile() returns a fully
// qualified physical path to the temporary file. Because
// of this, we cannot call VirtualDeleteFile(), as that will
// attempt to "re-canonicalize" the file, which will fail.
// So, we'll just call the DeleteFile() Win32 API directly.
//
DeleteFile( szTmpFile );
}
}
stou_exit:
pUserData->WriteLogRecord(PSZ_CREATE_VERB, szTmpFile, err);
return TRUE;
} // MainSTOU()
BOOL
MainAPPE(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements APPE command - used for appending to a file.
Format: APPE filename
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
HANDLE hFile = INVALID_HANDLE_VALUE;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg != NULL );
//
// Try to create the file.
//
err = VirtualCreateFile( pUserData,
&hFile,
pszArg,
TRUE );
if( err != NO_ERROR )
{
pUserData->SendErrorToClient(pszArg, err, PSZ_CANNOT_CREATE_FILE);
}
else
{
//
// Establish a connection to the user
//
err = pUserData->EstablishDataConnection( pszArg );
if ( err != NO_ERROR )
{
CloseHandle(hFile);
if ( err == ERROR_IO_PENDING )
{
//
// if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
// client to establish a data connection - it takes care of setting up an event that
// allows us to deal asynchronously with the client connecting [or failing to do so].
// It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
// any further processing
//
DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
!pUserData->QueryHavePASVConn() );
return TRUE;
}
goto appe_exit;
}
//
// Let the worker do the dirty work.
// On return, hFile is closed
//
err = ReceiveFileFromUserAndClose( pUserData, pszArg, &hFile );
}
appe_exit:
pUserData->WriteLogRecord( PSZ_APPEND_VERB, pszArg, err);
return TRUE;
} // MainAPPE()
BOOL
MainALLO(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements ALLO command - used for allocating space for file.
Format: ALLO filename
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
DBG_ASSERT( pUserData != NULL );
//
// Since we don't need to pre-reserve storage space for
// files, we'll treat this command as a noop.
//
ReplyToUser(pUserData,
REPLY_COMMAND_OK,
PSZ_COMMAND_SUCCESSFUL,
"ALLO");
return TRUE;
} // MainALLO()
BOOL
MainREST(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements REST command - used for restarting file write
Format: REST offset
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
LPSTR pszEndPtr;
DWORD dwOffset;
DBG_ASSERT( pUserData != NULL );
DBG_ASSERT( pszArg != NULL );
// Convert the input parameter to a number, and save it for the next command.
dwOffset = strtoul(pszArg, &pszEndPtr, 10);
if( (pszEndPtr == pszArg) || (*pszEndPtr != '\0') ||
(*pszArg == '-') ||
(dwOffset == ULONG_MAX && strcmp(pszArg, "4294967295")) )
{
ReplyToUser(pUserData,
REPLY_PARAMETER_SYNTAX_ERROR,
PSZ_REPLY_MARKER_SPEC );
} else {
pUserData->SetNextOffset( dwOffset );
ReplyToUser(pUserData,
REPLY_NEED_MORE_INFO,
PSZ_REPLY_RESTARTING,
pszArg);
}
return TRUE;
} // MainREST()
BOOL
MainRNFR(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements RNFR command - rename from filename
Format: RNFR FromFileName
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
CHAR szCanon[MAX_PATH];
DWORD cbSize = MAX_PATH;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg != NULL );
//
// Ensure file/directory exists.
//
if (pUserData->IsFileNameShort(pszArg))
{
err = ERROR_FILE_NOT_FOUND;
}
else
{
err = pUserData->VirtualCanonicalize(szCanon,
&cbSize,
pszArg,
AccessTypeDelete );
}
if( err == NO_ERROR ) {
if ( pUserData->ImpersonateUser() ) {
if( GetFileAttributes( szCanon ) == (DWORD)-1L ) {
err = GetLastError();
}
pUserData->RevertToSelf();
} else {
err = ERROR_ACCESS_DENIED;
}
if(( err == NO_ERROR ) && ( pUserData->RenameSourceBuffer == NULL )){
pUserData->RenameSourceBuffer = (CHAR *)TCP_ALLOC( MAX_PATH );
if( pUserData->RenameSourceBuffer == NULL ) {
err = GetLastError();
}
}
if( err == NO_ERROR ) {
strcpy( pUserData->RenameSourceBuffer, pszArg );
SET_UF( pUserData, RENAME );
}
}
if( err == NO_ERROR )
{
ReplyToUser(pUserData,
REPLY_NEED_MORE_INFO,
PSZ_READY_FOR_DEST_FILE);
} else {
pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
}
pUserData->WriteLogRecord( PSZ_RNFR_VERB, pszArg, err);
return TRUE;
} // MainRNFR()
BOOL
MainRNTO(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements RNTO command - rename to filename
Format: RNTO ToFileName
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// Sanity check the parameters.
//
DBG_ASSERT( pszArg != NULL );
//
// Ensure previous command was a RNFR.
//
if( !TEST_UF( pUserData, RENAME ) )
{
ReplyToUser(pUserData,
REPLY_BAD_COMMAND_SEQUENCE,
PSZ_BAD_COMMAND_SEQUENCE);
} else {
CLEAR_UF( pUserData, RENAME );
//
// Rename the file.
//
err = VirtualRenameFile( pUserData,
pUserData->RenameSourceBuffer,
pszArg );
if( err == NO_ERROR ) {
ReplyToUser(pUserData,
REPLY_FILE_ACTION_COMPLETED,
PSZ_COMMAND_SUCCESSFUL, "RNTO");
} else if( err == ERROR_FILE_EXISTS ) {
pUserData->SendErrorToClient(
pszArg,
err,
PSZ_DESTINATION_FILE_EXISTS,
REPLY_FILE_NOT_ALLOWED
);
} else {
pUserData->SendErrorToClient(
pszArg,
err,
PSZ_NO_FILE_OR_DIRECTORY,
REPLY_FILE_NOT_FOUND
);
}
}
pUserData->WriteLogRecord( PSZ_RNTO_VERB, pszArg, err);
return TRUE;
} // MainRNTO()
BOOL
MainABOR(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements ABOR command - abort any ongoing data transfer
Format: ABOR
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
DBG_ASSERT( pUserData != NULL );
ReplyToUser(pUserData,
TEST_UF( pUserData, OOB_DATA )
? REPLY_TRANSFER_OK
: REPLY_CONNECTION_OPEN,
PSZ_COMMAND_SUCCESSFUL, "ABOR");
//
// Clear any remaining oob flag.
//
CLEAR_UF( pUserData, OOB_DATA );
pUserData->WriteLogRecord(PSZ_ABORT_VERB, "");
return TRUE;
} // MainABOR()
BOOL
MainDELE(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements DELE command - used to delete a file
Format: DELE filename
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
if (pUserData->IsFileNameShort(pszArg))
{
err = ERROR_FILE_NOT_FOUND;
}
else
{
err = VirtualDeleteFile( pUserData, pszArg );
}
if( err == NO_ERROR ) {
ReplyToUser(pUserData,
REPLY_FILE_ACTION_COMPLETED,
PSZ_COMMAND_SUCCESSFUL, "DELE");
} else {
pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
}
pUserData->WriteLogRecord( PSZ_DELE_VERB, pszArg, err);
return TRUE;
} // MainDELE()
BOOL
MainRMD(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements RMD command - used to delete a directory
Format: RMD directory
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
if (pUserData->IsFileNameShort(pszArg))
{
err = ERROR_PATH_NOT_FOUND;
}
else
{
err = VirtualRmDir( pUserData, pszArg );
}
if( err == NO_ERROR ) {
ReplyToUser(pUserData,
REPLY_FILE_ACTION_COMPLETED,
PSZ_COMMAND_SUCCESSFUL, "RMD");
} else {
pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
}
pUserData->WriteLogRecord( PSZ_RMD_VERB, pszArg, err);
return TRUE;
} // MainRMD()
BOOL
MainMKD(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements MKD command - used to create a directory
Format: MKD directory
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR err;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
err = VirtualMkDir( pUserData, pszArg );
if( err == NO_ERROR ) {
ReplyToUser(pUserData,
REPLY_FILE_CREATED,
PSZ_DIRECTORY_CREATE, pszArg);
} else {
pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
}
pUserData->WriteLogRecord( PSZ_MKD_VERB, pszArg, err);
return TRUE;
} // MainMKD()
BOOL
MainPWD(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements PWD command - used to query path to working dir.
Format: PWD
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
CHAR szDir[MAX_PATH];
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// We will be sending back the current directory in virtual form
// Ofcourse the client should/need not worry about the exact path info.
//
strcpy( szDir, pUserData->QueryCurrentDirectory() );
if( !TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) ) {
FlipSlashes( szDir );
}
ReplyToUser(pUserData,
REPLY_FILE_CREATED,
PSZ_CURRENT_DIRECTORY,
szDir );
return TRUE;
} // MainPWD()
/*******************************************************************
NAME: MainLIST
SYNOPSIS: Implementation for the LIST command. Similar to NLST,
except defaults to long format display.
ENTRY: pUserData - The user initiating the request.
pszArg - Command arguments. Will be NULL if no
arguments given.
RETURNS: BOOL - TRUE if arguments OK, FALSE if syntax error.
HISTORY:
KeithMo 09-Mar-1993 Created.
********************************************************************/
BOOL
MainLIST(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements LIST command - used for getting dir list.
It is similar to NLST, only that this defaults to long format.
Format: LIST [options]* [path]*
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
APIERR serr = 0;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// Let the worker do the dirty work.
//
serr = pUserData->EstablishDataConnection("/bin/ls");
//
// if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
// client to establish a data connection - it takes care of setting up an event that
// allows us to deal asynchronously with the client connecting [or failing to do so].
// It indicates this asynchrony by returning ERROR_IO_PENDING
//
if ( serr == ERROR_IO_PENDING )
{
DBG_ASSERT( TEST_UF( pUserData, PASSIVE ) );
DBG_ASSERT( pUserData->QueryWaitingForPASVConn() );
DBG_ASSERT( !pUserData->QueryHavePASVConn() );
return TRUE;
}
if ( serr == 0) {
DWORD dwError;
serr = SimulateLs(pUserData,
pszArg, // switches for path
TRUE, // use data socket
TRUE); // generate default long format
dwError = ( (!TEST_UF(pUserData, OOB_ABORT) && (serr == 0))
? NO_ERROR : serr);
if ( dwError != NO_ERROR) {
//
// Send a soft error message indicating failure
//
pUserData->SendErrorToClient((pszArg != NULL) ? pszArg : ".",
dwError,
PSZ_NO_FILE_OR_DIRECTORY);
// since we already reported error, now just reset transfer.
CLEAR_UF( pUserData, TRANSFER);
}
DBG_REQUIRE( pUserData->DestroyDataConnection(dwError));
} else {
//
// could not establish a connection send error!
// Error is already sent by EstablishDataConnection()
//
IF_DEBUG( ERROR) {
DBGPRINTF( ( DBG_CONTEXT,
"EstablishDataConnection( %08x) failed for LIST\n",
pUserData));
}
}
return TRUE;
} // MainLIST()
BOOL
MainNLST(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements NLST command - used for getting dir list.
generates a short form of dir listing.
Format: NLST [options]* [path]*
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
BOOL fSpecialLs;
APIERR serr;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
//
// If any switches are present, use the simulated "ls"
// command. Otherwise (no switches) use the special
// file list.
//
// Estabalish a data connection for transfer of data and simulate Ls.
fSpecialLs = ( ( pszArg == NULL) || ( *pszArg != '-')); // no switches
serr = pUserData->EstablishDataConnection( (fSpecialLs)
? "file list" : "/bin/ls"
);
//
// if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
// client to establish a data connection - it takes care of setting up an event that
// allows us to deal asynchronously with the client connecting [or failing to do so].
// It indicates this asynchrony by returning ERROR_IO_PENDING
//
if ( serr == ERROR_IO_PENDING )
{
DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
!pUserData->QueryHavePASVConn() );
return TRUE;
}
if ( serr == 0) {
DWORD dwError;
serr = ( ( fSpecialLs) ?
SpecialLs(pUserData,
pszArg,
TRUE)
: SimulateLs(pUserData,
pszArg, // switches & search path
TRUE)
);
dwError = ((!TEST_UF(pUserData, OOB_DATA) && (serr == 0))
?NO_ERROR: serr);
if ( dwError != NO_ERROR) {
//
// Send a soft error message indicating failure
//
pUserData->SendErrorToClient((pszArg != NULL) ? pszArg : ".",
dwError,
PSZ_NO_FILE_OR_DIRECTORY);
// since we already reported error, now just reset transfer.
CLEAR_UF( pUserData, TRANSFER);
}
DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));
} else {
//
// could not establish a connection send error!
// Error is already sent by EstablishDataConnection()
//
IF_DEBUG( ERROR) {
DBGPRINTF( ( DBG_CONTEXT,
"EstablishDataConnection( %08x) failed for LIST\n",
pUserData));
}
}
return TRUE;
} // MainNLST()
BOOL
MainSYST(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements SYST command - used for getting system info.
Format: SYST
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
DBG_ASSERT( pUserData != NULL );
ReplyToUser(pUserData,
REPLY_SYSTEM_TYPE,
PSZ_VERSION_INFO );
return TRUE;
} // MainSYST()
# define MAX_STAT_BUFFER_SIZE (900)
BOOL
MainSTAT(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements STAT command - used for getting system stats.
Format: STAT
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
CHAR rgchBuffer[MAX_STAT_BUFFER_SIZE];
DWORD cchBuf;
DWORD dwError;
DBG_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
DBG_ASSERT( pUserData->IsLoggedOn());
if( pszArg == NULL )
{
HOSTENT * pHost;
//
// Determine the name of the user's host machine.
//
pHost = gethostbyaddr((CHAR *)&pUserData->HostIpAddress.s_addr,
4, // size of the s_addr structure
PF_INET ) ;
//
// Just dump connection info.
//
cchBuf = wsprintfA( rgchBuffer,
"%u-%s status:\r\n"
" Connected to %s\r\n"
" Logged in as %s\r\n"
" TYPE: %s, FORM: %s; STRUcture: %s;"
" transfer MODE: %s\r\n"
" %s\r\n"
,
REPLY_SYSTEM_STATUS,
g_FtpServiceNameString,
( ( pHost != NULL )
? pHost->h_name
: inet_ntoa( pUserData->HostIpAddress)),
pUserData->QueryUserName(),
TransferType( pUserData->QueryXferType() ),
"Nonprint",
"File",
TransferMode( pUserData->QueryXferMode()),
( ( pUserData->QueryDataSocket() == INVALID_SOCKET )
? "No data connection"
: "Data connection established")
);
if ( cchBuf < MAX_STAT_BUFFER_SIZE &&
pUserData->QueryControlSocket() != INVALID_SOCKET) {
dwError = SockSend(pUserData,
pUserData->QueryControlSocket(),
rgchBuffer,
cchBuf);
IF_DEBUG( SOCKETS) {
DBGPRINTF((DBG_CONTEXT,
" Sending STAT results %d bytes [%s]. Error= %u\n",
cchBuf, rgchBuffer, dwError));
}
} else {
dwError = ERROR_INSUFFICIENT_BUFFER;
}
ReplyToUser(pUserData,
REPLY_SYSTEM_STATUS,
PSZ_SERVER_STATUS_END );
} else {
//
// This should be similar to LIST, except it sends data
// over the control socket, not a data socket.
//
cchBuf = wsprintfA( rgchBuffer,
"%u-status of %s:\r\n",
REPLY_FILE_STATUS,
pszArg );
if ( cchBuf < MAX_STAT_BUFFER_SIZE &&
pUserData->QueryControlSocket() != INVALID_SOCKET) {
dwError = SockSend(pUserData,
pUserData->QueryControlSocket(),
rgchBuffer,
cchBuf);
// Error code is ignored after this point!
}
SimulateLs(pUserData,
pszArg,
FALSE, // use control socket
TRUE); // generate default long format
ReplyToUser(pUserData,
REPLY_FILE_STATUS,
PSZ_SERVER_STATUS_END);
}
return TRUE;
} // MainSTAT()
BOOL
MainNOOP(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
The DO Nothing NOOP command.
--*/
{
DBG_ASSERT( pUserData != NULL );
ReplyToUser(pUserData,
REPLY_COMMAND_OK,
PSZ_COMMAND_SUCCESSFUL, "NOOP");
return TRUE;
} // MainNOOP()
BOOL
SiteDIRSTYLE(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements DIRSTYLE command - used for getting site specific
directory style. It also toggles the style
Format: DIRSTYLE
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
const CHAR * pszResponse = NULL;
DBG_ASSERT( pUserData != NULL );
DBG_ASSERT( pszArg == NULL );
//
// Toggle the dir output flag.
//
if( TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) )
{
CLEAR_UF( pUserData, MSDOS_DIR_OUTPUT );
pszResponse = PSZ_RESPONSE_OFF;
}
else
{
SET_UF( pUserData, MSDOS_DIR_OUTPUT );
pszResponse = PSZ_RESPONSE_ON;
}
DBG_ASSERT( pszResponse != NULL );
ReplyToUser(pUserData,
REPLY_COMMAND_OK,
PSZ_MSDOS_DIRSTYLE,
pszResponse );
return TRUE;
} // SiteDIRSTYLE()
BOOL
SiteCKM(
LPUSER_DATA pUserData,
LPSTR pszArg
)
/*++
This function implements CKM command - used for getting site specific
Annotate Directories flag. It also toggles the flag.
Format: CKM
Arguments:
pUserData - the user initiating the request.
pszArg - command arguments. Will be NULL if no arguments present.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
const CHAR * pszResponse = NULL;
DBG_ASSERT( pUserData != NULL );
DBG_ASSERT( pszArg == NULL );
//
// Toggle the directory annotation flag.
//
if( TEST_UF( pUserData, ANNOTATE_DIRS ) )
{
CLEAR_UF( pUserData, ANNOTATE_DIRS );
pszResponse = PSZ_RESPONSE_OFF;
}
else
{
SET_UF( pUserData, ANNOTATE_DIRS );
pszResponse = PSZ_RESPONSE_ON;
}
DBG_ASSERT( pszResponse != NULL );
ReplyToUser(pUserData,
REPLY_COMMAND_OK,
PSZ_DIRECTORY_ANNOTATION,
pszResponse );
return TRUE;
} // SiteCKM()
BOOL
ParseStringIntoAddress(
LPSTR pszString,
LPIN_ADDR pinetAddr,
LPPORT pport
)
/*++
Parse a comma-separated list of six decimal numbers
into an IP address and a port number. The address and the port are
in network byte order ( most significant bytes first).
Arguments:
pszString - string to be parsed. Should be of the form:
dd,dd,dd,dd,dd,dd where 'dd' us the decimal representation
of a byte (0-255)
pinetAddr - will receive the IP Address
pport - will receive the port.
Returns:
BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
INT i;
UCHAR chBytes[6];
UCHAR chSum;
chSum = 0;
i = 0;
while( *pszString != '\0' )
{
UCHAR chCurrent = (UCHAR)*pszString++;
if( ( chCurrent >= '0' ) && ( chCurrent <= '9' ) )
{
chSum = ( chSum * 10 ) + chCurrent - '0';
}
else
if( ( chCurrent == ',' ) && ( i < 5 ) )
{
chBytes[i++] = chSum;
chSum = 0;
}
else
{
return FALSE;
}
}
chBytes[i] = chSum;
if( i != 5 )
{
return FALSE;
}
pinetAddr->S_un.S_un_b.s_b1 = chBytes[0];
pinetAddr->S_un.S_un_b.s_b2 = chBytes[1];
pinetAddr->S_un.S_un_b.s_b3 = chBytes[2];
pinetAddr->S_un.S_un_b.s_b4 = chBytes[3];
*pport = (PORT)( chBytes[4] + ( chBytes[5] << 8 ) );
return TRUE;
} // ParseStringIntoAddress()
/*******************************************************************
NAME: ReceiveFileFromUserAndClose
SYNOPSIS: Worker function for STOR, STOU, and APPE commands.
Will establish a connection via the (new) data
socket, then receive a file over that socket.
ENTRY: pUserData - The user initiating the request.
pszFileName - The name of the file to receive.
phFile - An handle to the file being received.
This handle is closed before this
routine returns.
Returns:
Win32 Error codes (or socket errors) as DWORD
HISTORY:
KeithMo 16-Mar-1993 Created.
MuraliK 05-April-1995 Dont free hFile here +
Alloc IoTransBuffer locally
********************************************************************/
DWORD
ReceiveFileFromUserAndClose(
LPUSER_DATA pUserData,
LPSTR pszFileName,
LPHANDLE phFile
)
{
BOOL fResult;
DWORD cbRead = 0;
DWORD cbWritten;
DWORD dwError;
SOCKET DataSocket;
LPVOID IoTransferBuffer;
DWORD dwNumThreads;
DBG_ASSERT( pUserData != NULL );
DBG_ASSERT( pszFileName != NULL );
DBG_ASSERT( *phFile != INVALID_HANDLE_VALUE );
//
// We're about to make a blocking call to SockRecv(), so increment the # of threads in the
// ATQ thread pool.
// The decision whether to process this request is made based on how many threads are already
// blocked in a synchronous call. We do it after accepting/establishing the
// data connection, so that the clients's connect will succeed in the non-passive case, and
// then have the request aborted. This prevents clients hangs.
//
dwNumThreads = InterlockedIncrement( (long *) &g_ThreadsBlockedInSyncCalls );
if (dwNumThreads > g_MaxThreadsBlockedInSyncCalls)
{
//
// Blow away the data connection
//
InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );
dwError = ERROR_NOT_ENOUGH_MEMORY;
DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));
goto RecevieFileFromUser_exit;
}
AtqSetInfo( AtqIncMaxPoolThreads, 0 );
//
// Allocate an i/o buffer if not already allocated.
//
IoTransferBuffer = TCP_ALLOC( g_SocketBufferSize );
if( IoTransferBuffer == NULL ) {
ReplyToUser(pUserData,
REPLY_LOCAL_ERROR,
PSZ_INSUFFICIENT_RESOURCES);
InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );
AtqSetInfo( AtqDecMaxPoolThreads, 0 );
dwError = ERROR_NOT_ENOUGH_MEMORY;
goto RecevieFileFromUser_exit;
}
//
// Blast the file from the user to a local file.
//
DataSocket = pUserData->QueryDataSocket();
for( ; ; )
{
//
// Read a chunk from the socket.
//
dwError = SockRecv( pUserData,
DataSocket,
IoTransferBuffer,
g_SocketBufferSize,
&cbRead );
if( TEST_UF( pUserData, OOB_DATA ) ||
( dwError != NO_ERROR ) || ( cbRead == 0 ) )
{
//
// Socket error during read or end of file or transfer aborted.
//
break;
}
pUserData->IncrementCbRecvd( cbRead);
//
// Write the current buffer to the local file.
//
fResult = WriteFile( *phFile,
IoTransferBuffer,
cbRead,
&cbWritten,
NULL );
if( !fResult )
{
dwError = GetLastError();
break;
}
}
if ( TEST_UF( pUserData, OOB_DATA)) {
dwError = ERROR_OPERATION_ABORTED;
}
IF_DEBUG( COMMANDS )
{
if( !fResult )
{
DBGPRINTF(( DBG_CONTEXT,
"cannot write file %s, error %lu\n",
pszFileName,
dwError ));
} else if( dwError != NO_ERROR ) {
DBGPRINTF(( DBG_CONTEXT,
"cannot read data from client, error %d\n",
dwError ));
}
if( TEST_UF( pUserData, OOB_DATA ) )
{
DBGPRINTF(( DBG_CONTEXT,
"transfer aborted by client\n" ));
}
}
//
// Close file handle before disconnecting from client. This is to serialize
// requests. If we disconnect first, then an append to this file may follow
// which may result in a sharing violation of the file (if this write has
// not been flushed and closed yet).
//
DBG_REQUIRE( CloseHandle( *phFile ) );
*phFile = INVALID_HANDLE_VALUE;
//
// Disconnect from client.
//
DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));
if( IoTransferBuffer != NULL )
{
TCP_FREE( IoTransferBuffer );
IoTransferBuffer = NULL;
}
if ( dwError == NO_ERROR) {
pUserData->QueryInstance()->QueryStatsObj()->IncrTotalFilesReceived();
}
InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );
AtqSetInfo( AtqDecMaxPoolThreads, 0 );
RecevieFileFromUser_exit:
if( *phFile != INVALID_HANDLE_VALUE ) {
CloseHandle( *phFile );
*phFile = INVALID_HANDLE_VALUE;
}
return (dwError);
} // ReceiveFileFromUserAndClose()
/*******************************************************************
NAME: MyLogonUser
SYNOPSIS: Validates a user's credentials, then sets the
impersonation for the current thread. In effect,
the current thread "becomes" the user.
ENTRY: pUserData - The user initiating the request.
pszPassword - The user's password. May be NULL.
pfAsGuest - Will receive TRUE if the user was validated
with guest privileges.
pfHomeDirFailure - Will receive TRUE if the user failed
to logon because the home directory was inaccessible.
pfLicenseExceeded - Will receive TRUE if the logon
was denied due to license restrictions.
RETURNS: BOOL - If user validated & impersonation was
successful, returns TRUE. Otherwise returns
TRUE.
HISTORY:
KeithMo 18-Mar-1993 Created.
********************************************************************/
BOOL
MyLogonUser(
LPUSER_DATA pUserData,
LPSTR pszPassword,
LPBOOL pfAsGuest,
LPBOOL pfHomeDirFailure,
LPBOOL pfLicenseExceeded
)
{
BOOL fReturn = TRUE;
DWORD dwUserAccess;
TS_TOKEN UserToken;
BOOL fAsAnonymous;
BOOL fAsAnonymous2;
BOOL fEmptyPassword;
const CHAR * pszUser;
//
// Validate parameters & state.
//
DBG_ASSERT( pUserData != NULL );
DBG_ASSERT( pUserData->UserToken == NULL );
DBG_ASSERT( pfAsGuest != NULL );
DBG_ASSERT( pfHomeDirFailure != NULL );
DBG_ASSERT( pfLicenseExceeded != NULL );
//
// Setup.
//
*pfAsGuest = FALSE;
*pfHomeDirFailure = FALSE;
*pfLicenseExceeded = FALSE; // NOT YET SUPPORTED IN GHIA APIS!
fEmptyPassword = ( pszPassword == NULL ) || ( *pszPassword == '\0' );
pszUser = pUserData->QueryUserName();
DBG_ASSERT( pszUser != NULL );
DBG_ASSERT( *pszUser != '\0' );
//
// Check for invalid logon type.
//
fAsAnonymous = TEST_UF( pUserData, ANONYMOUS);
if( !pUserData->QueryInstance()->IsAllowedUser(fAsAnonymous)) {
// conflict between what is allowed and type of the client.
SetLastError( ERROR_LOGON_FAILURE);
return FALSE;
}
//
// Check for anonymous logon.
//
if( fAsAnonymous )
{
//
// At this point, we could copy the password specified by the
// user into the pUserData->UserName field. There's a convention
// among Internetters that the password specified for anonymous
// logon should actually be your login name. So, if we wanted
// honor this convention, we could copy the password into the
// pUserData->UserName field so the Administration UI
// could display it.
//
// If the user didn't enter a password, we'll just copy over
// "Anonymous" so we'll have SOMETHING to display...
//
pUserData->SetUserName( fEmptyPassword ? PSZ_ANONYMOUS_NAME : pszPassword);
//
// TsLogon User will logon as anonymous only when we specify the
// UserName == NULL and pszPassword == NULL.
//
pszUser = NULL;
pszPassword = NULL;
}
//
// Do that logon thang.
//
pUserData->QueryInstance()->LockConfig();
// dumb TsLogonUser() does not take const CHAR * for pszUser :(
UserToken = TsLogonUser( (CHAR *) pszUser,
pszPassword,
pfAsGuest,
&fAsAnonymous2,
pUserData->QueryInstance(),
pUserData->QueryInstance()->QueryAuthentInfo() );
pUserData->QueryInstance()->UnLockConfig();
//
// Recheck the logon requirements, just in case the user is trying
// to do something tricky, like logon with the special IUSR_xxx
// account name.
//
if( UserToken != NULL &&
!pUserData->QueryInstance()->IsAllowedUser(fAsAnonymous2) ) {
TsDeleteUserToken( UserToken );
UserToken = NULL;
SetLastError( ERROR_LOGON_FAILURE );
}
if( UserToken != NULL ) {
// reset it again even if it was anonymous
pszUser = pUserData->QueryUserName();
//
// Save away the impersonation token so we can delete
// it when the user disconnects or this client thread
// otherwise terminates.
//
pUserData->UserToken = UserToken;
//
// User validated, now impersonate.
//
if( !pUserData->ImpersonateUser()) {
//
// Impersonation failure.
//
IF_DEBUG( ERROR) {
DBGPRINTF(( DBG_CONTEXT,
"Impersonate User %08x failed. Error=%lu\n",
UserToken, GetLastError()));
}
fReturn = FALSE;
} else {
//
// We're now running in the context of the connected user.
// Check the user's access to the FTP Server.
//
dwUserAccess = DetermineUserAccess(pUserData->QueryInstance());
if( dwUserAccess == 0 ) {
//
// User cannot access the FTP Server.
//
IF_DEBUG( SECURITY ) {
DBGPRINTF(( DBG_CONTEXT,
"user %s denied FTP access\n",
pszUser ));
}
fReturn = FALSE;
} else {
const CHAR * apszSubStrings[2];
DWORD eventId = 0;
apszSubStrings[0] = pszUser;
pUserData->Flags &= ~( UF_READ_ACCESS | UF_WRITE_ACCESS );
pUserData->Flags |= dwUserAccess;
IF_DEBUG( SECURITY ) {
CHAR * pszTmp = NULL;
if( TEST_UF( pUserData, READ_ACCESS ) ) {
pszTmp = ( TEST_UF( pUserData, WRITE_ACCESS )
? "read and write"
: "read"
);
} else {
DBG_ASSERT( TEST_UF( pUserData, WRITE_ACCESS ) );
pszTmp = "write";
}
DBG_ASSERT( pszTmp != NULL );
DBGPRINTF(( DBG_CONTEXT,
"user %s granted %s FTP access\n",
pszUser,
pszTmp ));
}
//
// Try to CD to the user's home directory. Note that
// this is VERY important for setting up some of the
// "virtual current directory" structures properly.
//
if( pUserData->CdToUsersHomeDirectory( PSZ_ANONYMOUS_NAME)
!= NO_ERROR ) {
//
// Home directory inaccessible.
//
eventId = FTPD_EVENT_BAD_HOME_DIRECTORY;
} else if (fAsAnonymous &&
pUserData->QueryInstance()->QueryLogAnonymous() &&
!fEmptyPassword ) {
//
// If this is an anonymous user, and we're to log
// anonymous logons, OR if this is not an anonymous
// user, and we're to log nonanonymous logons, then
// do it.
//
// Note that we DON'T log the logon if the user is
// anonymous but specified no password.
//
eventId = FTPD_EVENT_ANONYMOUS_LOGON;
} else if (!fAsAnonymous &&
pUserData->QueryInstance()->QueryLogNonAnonymous()
) {
DBG_ASSERT( *pszUser != '\0');
eventId = FTPD_EVENT_NONANONYMOUS_LOGON;
}
//
// Log an event so the poor admin can figure out
// what's going on.
//
switch ( eventId) {
case FTPD_EVENT_ANONYMOUS_LOGON:
case FTPD_EVENT_NONANONYMOUS_LOGON:
apszSubStrings[1] = inet_ntoa( pUserData->HostIpAddress);
g_pInetSvc->LogEvent( eventId,
2,
apszSubStrings,
0 );
break;
case FTPD_EVENT_BAD_HOME_DIRECTORY:
pUserData->QueryInstance()->LockThisForRead();
apszSubStrings[1] = pUserData->QueryInstance()->QueryRoot();
*pfHomeDirFailure = TRUE;
g_pInetSvc->LogEvent( eventId,
2,
apszSubStrings,
0 );
pUserData->QueryInstance()->UnlockThis();
fReturn = FALSE; // bad directory is a failure.
break;
default:
// do nothing
break;
} // switch
} // user Access Succeeded
pUserData->RevertToSelf(); // get out the impersonation
} // Impersonation succeeded.
} else {
fReturn = FALSE;
}
//
// Determine if we logged in with guest access, and
// if so, if guest access is allowed in our server.
//
if( *pfAsGuest && !pUserData->QueryInstance()->AllowGuestAccess() ) {
TsDeleteUserToken( pUserData->QueryUserToken() );
pUserData->UserToken = NULL;
fReturn = FALSE;
}
//
// Success!
//
return ( fReturn);
} // MyLogonUser()
DWORD
DetermineUserAccess(FTP_SERVER_INSTANCE *pInstance)
/*++
This function determines the current user's access to FTP server.
This is done by testing different RegOpenKey APIs against
the FTPD_ACCESS_KEY. This key (if it exists) will be "under" the
FTPD_PARAMETERS_KEY key.
Arguments:
None
Returns:
DWORD -- will be an OR Combination of UF_READ_ACCESS and UF_WRITE_ACCESS.
IF this is zero, then the user cannot access FTP server.
History:
KeithMo 06-May-1993 Created.
MuraliK 24-July-1995 Call this function with Impersonation.
NYI: Improve performance by avoiding reg opens per connection....
--*/
{
DWORD dwAccess = 0;
HKEY hkey;
APIERR err;
//
// Test for read access.
//
err = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
FTPD_ACCESS_KEY,
0,
KEY_READ,
&hkey );
if( err == NO_ERROR )
{
//
// Success.
//
dwAccess |= UF_READ_ACCESS;
RegCloseKey( hkey );
}
else
if( err == ERROR_FILE_NOT_FOUND )
{
//
// Key doesn't exist.
//
dwAccess |= UF_READ_ACCESS;
}
//
// Test for write access.
//
err = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
FTPD_ACCESS_KEY,
0,
KEY_WRITE,
&hkey );
if( err == NO_ERROR )
{
//
// Success.
//
dwAccess |= UF_WRITE_ACCESS;
RegCloseKey( hkey );
}
else
if( err == ERROR_FILE_NOT_FOUND )
{
//
// Key doesn't exist.
//
dwAccess |= UF_WRITE_ACCESS;
}
return dwAccess;
} // DetermineUserAccess()
/************************ End Of File ************************/