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.
3525 lines
88 KiB
3525 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") /* must match userdb.cxx */ \
|
|
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( CWD_VERB, "CWD" ) \
|
|
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
|
|
);
|
|
|
|
BOOL
|
|
MyLogonUser(
|
|
LPUSER_DATA pUserData,
|
|
LPSTR pszPassword,
|
|
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 fHomeDirFailure;
|
|
BOOL fLicenseExceeded;
|
|
|
|
DBG_ASSERT( pUserData->QueryUserToken() == NULL || pUserData->QueryInFakeIOCompletion());
|
|
|
|
//
|
|
// we got the PASS command we we're waiting for
|
|
//
|
|
CLEAR_UF( pUserData, WAIT_PASS );
|
|
|
|
//
|
|
// increment the number of logon attempts, only if we are not reprocessing this command
|
|
// (which happens when an AD async home directory lookup completed)
|
|
//
|
|
if (!pUserData->QueryInFakeIOCompletion()) {
|
|
pUserData->QueryInstance()->QueryStatsObj()->IncrLogonAttempts();
|
|
}
|
|
|
|
if (MyLogonUser(pUserData,
|
|
pszArg,
|
|
&fHomeDirFailure,
|
|
&fLicenseExceeded)) {
|
|
|
|
const CHAR * pszGuestAccess =
|
|
( TEST_UF( pUserData, AS_GUEST) ? 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 {
|
|
|
|
dwError = GetLastError();
|
|
|
|
if (dwError == ERROR_IO_PENDING) {
|
|
//
|
|
// an asynchronous request for user home directory is in progress
|
|
// restore the state so that next time we get back in here again.
|
|
// return and wait for the completion of the AD query
|
|
//
|
|
|
|
SET_UF( pUserData, WAIT_PASS );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
const CHAR * pszReply = PSZ_USER_CANNOT_LOG_IN;
|
|
|
|
//
|
|
// Logon failure.
|
|
//
|
|
|
|
if( fHomeDirFailure ) {
|
|
|
|
pszReply = PSZ_INACCESSIBLE_HOME_DIR;
|
|
|
|
} else if ( fLicenseExceeded) {
|
|
|
|
pszReply = PSZ_LICENSE_QUOTA_EXCEEDED;
|
|
|
|
} else if ( TEST_UF( pUserData, AS_GUEST) && ! 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();
|
|
|
|
} 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);
|
|
}
|
|
|
|
pUserData->WriteLogRecord(PSZ_CWD_VERB, pszArg, err);
|
|
|
|
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->CloseFileForSend( err, TRUE); // close the file, now that we're done. NoLogging
|
|
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.
|
|
DBG_REQUIRE(
|
|
_snprintf(rgchBuffer, sizeof( rgchBuffer ),
|
|
"%.4hu%.2hu%.2hu%.2hu%.2hu%.2hu",
|
|
SystemTime.wYear & 0xFFF, // this will give us to year 4095
|
|
SystemTime.wMonth & 0xF, // this will let expanding the year into 15 months
|
|
SystemTime.wDay & 0x1F, // did they design day of month with 5 bits in mind?
|
|
SystemTime.wHour & 0x1F,
|
|
SystemTime.wMinute & 0x3F,
|
|
SystemTime.wSecond & 0x3F) > 0);
|
|
|
|
ReplyToUser( pUserData, REPLY_FILE_STATUS, rgchBuffer );
|
|
}
|
|
}
|
|
|
|
if( err != NO_ERROR ) {
|
|
|
|
pUserData->SendErrorToClient(pszArg, err,
|
|
PSZ_NO_FILE_OR_DIRECTORY);
|
|
}
|
|
|
|
pUserData->CloseFileForSend( err, TRUE); // close the file, now that we're done, NoLogging.
|
|
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. This flag was renamed to
|
|
// enable_data_conn_to_3rd_ip
|
|
//
|
|
|
|
if (!pUserData->QueryInstance()->IsEnableDataConnTo3rdIP())
|
|
{
|
|
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,
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
DBG_REQUIRE( _snprintf( rgchBuffer, sizeof( rgchBuffer ), "(%s bytes)", rgchSize) > 0);
|
|
|
|
//
|
|
// 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);
|
|
goto stor_exit;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
pUserData->SetCommandVerb( PSZ_CREATE_VERB);
|
|
|
|
//
|
|
// Let the worker do the dirty work.
|
|
//
|
|
|
|
err = pUserData->ReceiveFileFromUser( pszArg, &hFile );
|
|
|
|
if( err != NO_ERROR )
|
|
{
|
|
VirtualDeleteFile( pUserData,
|
|
pszArg );
|
|
|
|
IF_DEBUG( ASYNC_IO) {
|
|
|
|
DBGPRINTF( ( DBG_CONTEXT,
|
|
"ReceiveFileFromUser ( %s) failed"
|
|
" err = %u\n",
|
|
pszArg, err));
|
|
}
|
|
}
|
|
|
|
stor_exit:
|
|
|
|
if ( err != NO_ERROR )
|
|
{
|
|
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);
|
|
goto stou_exit;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
pUserData->SetCommandVerb( PSZ_CREATE_VERB);
|
|
|
|
//
|
|
// Let the worker do the dirty work.
|
|
//
|
|
|
|
err = pUserData->ReceiveFileFromUser( 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 );
|
|
|
|
IF_DEBUG( ASYNC_IO) {
|
|
|
|
DBGPRINTF( ( DBG_CONTEXT,
|
|
"ReceiveFileFromUser ( %s) failed"
|
|
" err = %u\n",
|
|
pszArg, err));
|
|
}
|
|
}
|
|
|
|
stou_exit:
|
|
|
|
if( err != NO_ERROR ) {
|
|
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);
|
|
goto appe_exit;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
pUserData->SetCommandVerb( PSZ_APPEND_VERB);
|
|
|
|
//
|
|
// Let the worker do the dirty work.
|
|
//
|
|
|
|
err = pUserData->ReceiveFileFromUser( pszArg, &hFile );
|
|
|
|
appe_exit:
|
|
|
|
if ( err != NO_ERROR ) {
|
|
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+1];
|
|
DWORD cbSize = sizeof(szCanon);
|
|
|
|
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 ) {
|
|
|
|
P_strncpy( pUserData->RenameSourceBuffer, pszArg, MAX_PATH );
|
|
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.
|
|
//
|
|
P_strncpy( szDir, pUserData->QueryCurrentDirectory().QueryStr(), MAX_PATH);
|
|
|
|
if( !TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) ) {
|
|
|
|
FlipSlashes( szDir, FLIP_SLASHES_TO_UNIX );
|
|
}
|
|
|
|
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];
|
|
INT 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 = _snprintf( rgchBuffer, sizeof( 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 > 0) && (cchBuf < sizeof( rgchBuffer )) &&
|
|
(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 = _snprintf( rgchBuffer, sizeof( rgchBuffer ),
|
|
"%u-status of %s:\r\n",
|
|
REPLY_FILE_STATUS,
|
|
pszArg );
|
|
|
|
if ( (cchBuf > 0) && (cchBuf < sizeof( rgchBuffer )) &&
|
|
(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: 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.
|
|
|
|
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 pfHomeDirFailure,
|
|
LPBOOL pfLicenseExceeded
|
|
)
|
|
{
|
|
//
|
|
// Validate parameters & state.
|
|
//
|
|
|
|
DBG_ASSERT( pUserData != NULL );
|
|
DBG_ASSERT( pUserData->UserToken == NULL || pUserData->QueryInFakeIOCompletion());
|
|
DBG_ASSERT( pfHomeDirFailure != NULL );
|
|
DBG_ASSERT( pfLicenseExceeded != NULL );
|
|
|
|
BOOL fReturn = TRUE;
|
|
TS_TOKEN UserToken = NULL;
|
|
BOOL fAsAnonymous = TEST_UF( pUserData, ANONYMOUS);
|
|
BOOL fAsAnonymous2 = FALSE;
|
|
BOOL fEmptyPassword = ( pszPassword == NULL ) || ( *pszPassword == '\0' );
|
|
DWORD dwError = ERROR_ACCESS_DENIED;
|
|
|
|
PCSTR pszUser = pUserData->QueryUserName();
|
|
DBG_ASSERT( pszUser != NULL );
|
|
DBG_ASSERT( *pszUser != '\0' );
|
|
|
|
//
|
|
// Setup.
|
|
//
|
|
|
|
*pfHomeDirFailure = FALSE;
|
|
*pfLicenseExceeded = FALSE; // NOT YET SUPPORTED IN GHIA APIS!
|
|
|
|
if (pUserData->QueryInFakeIOCompletion()) {
|
|
//
|
|
// came back after an async query to obtain the home directory from AD completed.
|
|
// Clean up, and pick up where we left off.
|
|
//
|
|
|
|
pUserData->CleanupAsyncADIOFlags();
|
|
|
|
goto after_set_root_dir;
|
|
}
|
|
|
|
//
|
|
// Check for invalid logon type.
|
|
//
|
|
|
|
if( !pUserData->QueryInstance()->IsAllowedUser(fAsAnonymous)) {
|
|
|
|
// conflict between what is allowed and type of the client.
|
|
dwError = ERROR_LOGON_FAILURE;
|
|
goto FailureExitPoint;
|
|
}
|
|
|
|
//
|
|
// 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. make local copy of authentication info, so that we
|
|
// don't need to lock
|
|
//
|
|
|
|
BOOL fAsGuest = FALSE;
|
|
|
|
{ // to set scope for TAI
|
|
TCP_AUTHENT_INFO TAI( pUserData->QueryInstance()->QueryAuthentInfo());
|
|
|
|
// dumb TsLogonUser() does not take const CHAR * for pszUser :(
|
|
UserToken = TsLogonUser( (CHAR *) pszUser,
|
|
pszPassword,
|
|
&fAsGuest,
|
|
&fAsAnonymous2,
|
|
pUserData->QueryInstance(),
|
|
&TAI );
|
|
}
|
|
|
|
if (fAsGuest) {
|
|
SET_UF(pUserData, AS_GUEST);
|
|
} else {
|
|
CLEAR_UF(pUserData, AS_GUEST);
|
|
}
|
|
|
|
if (UserToken == NULL) {
|
|
dwError = GetLastError();
|
|
goto FailureExitPoint;
|
|
}
|
|
|
|
//
|
|
// 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( !pUserData->QueryInstance()->IsAllowedUser(fAsAnonymous2) ) {
|
|
|
|
dwError = ERROR_LOGON_FAILURE;
|
|
goto FailureExitPoint;
|
|
}
|
|
|
|
//
|
|
// Determine if we logged in with guest access, and
|
|
// if so, if guest access is allowed in our server.
|
|
//
|
|
|
|
if( TEST_UF(pUserData, AS_GUEST) && !pUserData->QueryInstance()->AllowGuestAccess() ) {
|
|
|
|
dwError = ERROR_LOGON_FAILURE;
|
|
goto FailureExitPoint;
|
|
}
|
|
|
|
// 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;
|
|
UserToken = NULL;
|
|
|
|
//
|
|
// go set the user's root directory.
|
|
//
|
|
|
|
if ( pUserData->SetRootDirectory( pszPassword ) == ERROR_IO_PENDING ) {
|
|
//
|
|
// we are in enterprise isolation mode, and an asynchronous fetch from active directory
|
|
// was initiated. we'll get back in here when it is complete.
|
|
//
|
|
dwError = ERROR_IO_PENDING;
|
|
goto FailureExitPoint;
|
|
}
|
|
|
|
after_set_root_dir:
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
DWORD 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 ));
|
|
}
|
|
|
|
//
|
|
// has the user home directory been properly initialized?
|
|
//
|
|
|
|
if( pUserData->QueryRootDirStatus() != NO_ERROR ) {
|
|
|
|
//
|
|
// Failed to determine home directory.
|
|
//
|
|
|
|
eventId = FTPD_EVENT_BAD_HOME_DIRECTORY;
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
else if( pUserData->CdToUsersHomeDirectory() != 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.
|
|
|
|
//
|
|
// Success!
|
|
//
|
|
|
|
SetLastError( fReturn ? ERROR_SUCCESS : ERROR_ACCESS_DENIED );
|
|
return fReturn;
|
|
|
|
FailureExitPoint:
|
|
|
|
if (UserToken != NULL) {
|
|
TsDeleteUserToken( UserToken );
|
|
}
|
|
|
|
SetLastError( dwError );
|
|
return FALSE;
|
|
|
|
} // 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 ************************/
|