3698 lines
88 KiB
C++
3698 lines
88 KiB
C++
|
/**********************************************************************/
|
|||
|
/** 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 ));
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// initialize the user root directory
|
|||
|
//
|
|||
|
|
|||
|
pUserData->SetRootDirectory( PSZ_ANONYMOUS_NAME );
|
|||
|
|
|||
|
//
|
|||
|
// 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 ************************/
|