mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1113 lines
29 KiB
1113 lines
29 KiB
/*++
|
|
|
|
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
ftpcmd.cxx
|
|
|
|
Abstract:
|
|
|
|
This module defines the FTP commands supported by this FTP server
|
|
and provides a table of functions to be called for processing
|
|
such command requests.
|
|
( Some parts of the code are from old engine.cxx ( KeithMo's FTP server))
|
|
|
|
Author:
|
|
|
|
Murali R. Krishnan ( MuraliK ) 28-Mar-1995
|
|
|
|
Environment:
|
|
|
|
User Mode -- Win32
|
|
|
|
Project:
|
|
|
|
FTP Server DLL
|
|
|
|
Functions Exported:
|
|
|
|
ParseCommand
|
|
()
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
/************************************************************
|
|
* Include Headers
|
|
************************************************************/
|
|
|
|
# include <ftpdp.hxx>
|
|
# include "ftpcmd.hxx"
|
|
# include "lsaux.hxx"
|
|
# include "auxctrs.h"
|
|
|
|
#define MAX_COMMAND_NAME_LEN ( 30)
|
|
# define MAX_HELP_LINE_SIZE ( 80)
|
|
# define MAX_HELP_AUX_SIZE (100) // fixed sized aux info with HELP
|
|
# define HelpMsgSize( nCommands) ((1 + nCommands) * MAX_HELP_LINE_SIZE + \
|
|
MAX_HELP_AUX_SIZE)
|
|
|
|
#define IS_7BIT_ASCII(c) ((UINT)(c) <= 127)
|
|
|
|
|
|
/************************************************************
|
|
* Static Data containing command lookups
|
|
************************************************************/
|
|
|
|
|
|
|
|
# define UsP ( UserStateWaitingForPass)
|
|
# define UsUP ( UserStateWaitingForUser | UsP)
|
|
# define UsL ( UserStateLoggedOn)
|
|
# define UsUPL ( UsL | UsUP)
|
|
|
|
//
|
|
// Store the commands in alphabetical order ( manually stored so!)
|
|
// to enable faster search.
|
|
//
|
|
// Format is:
|
|
// Name Help Information FunctionToCall ArgumentType ValidStates
|
|
//
|
|
|
|
FTPD_COMMAND MainCommands[] ={
|
|
|
|
{ "ABOR", "(abort operation)", MainABOR, ArgTypeNone, UsL},
|
|
{ "ACCT", "(specify account)", MainACCT, ArgTypeRequired,UsL},
|
|
{ "ALLO", "(allocate storage vacuously)", MainALLO, ArgTypeRequired,UsL},
|
|
{ "APPE", "<sp> file-name", MainAPPE, ArgTypeRequired,UsL},
|
|
{ "CDUP", "change to parent directory", MainCDUP, ArgTypeNone, UsL},
|
|
{ "CWD", "[ <sp> directory-name ]", MainCWD , ArgTypeOptional,UsL},
|
|
{ "DELE", "<sp> file-name", MainDELE, ArgTypeRequired,UsL},
|
|
{ "HELP", "[ <sp> <string>]", MainHELP, ArgTypeOptional,UsUPL},
|
|
{ "LIST", "[ <sp> path-name ]", MainLIST, ArgTypeOptional,UsL},
|
|
{ "MDTM", "(sp) file-name", MainMDTM, ArgTypeRequired,UsL },
|
|
{ "MKD", "<sp> path-name", MainMKD , ArgTypeRequired,UsL},
|
|
{ "MODE", "(specify transfer mode)", MainMODE, ArgTypeRequired,UsUPL},
|
|
{ "NLST", "[ <sp> path-name ]", MainNLST, ArgTypeOptional,UsL},
|
|
{ "NOOP", "", MainNOOP, ArgTypeNone, UsUPL},
|
|
{ "PASS", "<sp> password", MainPASS, ArgTypeOptional, UsP},
|
|
{ "PASV", "(set server in passive mode)", MainPASV, ArgTypeNone, UsL},
|
|
{ "PORT", "<sp> b0,b1,b2,b3,b4,b5", MainPORT, ArgTypeRequired,UsUPL},
|
|
{ "PWD", "(return current directory)", MainPWD , ArgTypeNone, UsL},
|
|
{ "QUIT", "(terminate service)", MainQUIT, ArgTypeNone, UsUPL},
|
|
{ "REIN", "(reinitialize server state)", MainREIN, ArgTypeNone, UsL},
|
|
{ "REST", "<sp> marker", MainREST, ArgTypeRequired,UsL},
|
|
{ "RETR", "<sp> file-name", MainRETR, ArgTypeRequired,UsL},
|
|
{ "RMD", "<sp> path-name", MainRMD , ArgTypeRequired,UsL },
|
|
{ "RNFR", "<sp> file-name", MainRNFR, ArgTypeRequired,UsL},
|
|
{ "RNTO", "<sp> file-name", MainRNTO, ArgTypeRequired,UsL },
|
|
{ "SITE", "(site-specific commands)", MainSITE, ArgTypeOptional,UsL },
|
|
{ "SIZE", "(sp) file-name", MainSIZE, ArgTypeRequired,UsL },
|
|
{ "SMNT", "<sp> pathname", MainSMNT, ArgTypeRequired,UsL },
|
|
{ "STAT", "(get server status)", MainSTAT, ArgTypeOptional,UsL },
|
|
{ "STOR", "<sp> file-name", MainSTOR, ArgTypeRequired,UsL },
|
|
{ "STOU", "(store unique file)", MainSTOU, ArgTypeNone, UsL},
|
|
{ "STRU", "(specify file structure)", MainSTRU, ArgTypeRequired,UsUPL},
|
|
{ "SYST", "(get operating system type)", MainSYST, ArgTypeNone, UsL },
|
|
{ "TYPE", "<sp> [ A | E | I | L ]", MainTYPE, ArgTypeRequired,UsL },
|
|
{ "USER", "<sp> username", MainUSER, ArgTypeRequired,UsUPL},
|
|
{ "XCUP", "change to parent directory", MainCDUP, ArgTypeNone, UsL},
|
|
{ "XCWD", "[ <sp> directory-name ]", MainCWD , ArgTypeOptional,UsL },
|
|
{ "XMKD", "<sp> path-name", MainMKD , ArgTypeRequired,UsL },
|
|
{ "XPWD", "(return current directory)", MainPWD , ArgTypeNone, UsL },
|
|
{ "XRMD", "<sp> path-name", MainRMD , ArgTypeRequired,UsL }
|
|
};
|
|
|
|
#define NUM_MAIN_COMMANDS ( sizeof(MainCommands) / sizeof(MainCommands[0]) )
|
|
|
|
|
|
|
|
|
|
FTPD_COMMAND SiteCommands[] = {
|
|
|
|
{ "CKM", "(toggle directory comments)", SiteCKM , ArgTypeNone,UsL},
|
|
{ "DIRSTYLE", "(toggle directory format)", SiteDIRSTYLE, ArgTypeNone,UsL},
|
|
{ "HELP", "[ <sp> <string>]", SiteHELP , ArgTypeOptional,
|
|
UsL}
|
|
|
|
#ifdef KEEP_COMMAND_STATS
|
|
|
|
,{ "STATS", "(display per-command stats)", SiteSTATS , ArgTypeNone, UsL}
|
|
|
|
#endif // KEEP_COMMAND_STATS
|
|
|
|
};
|
|
|
|
|
|
#define NUM_SITE_COMMANDS ( sizeof(SiteCommands) / sizeof(SiteCommands[0]) )
|
|
|
|
|
|
|
|
#ifdef KEEP_COMMAND_STATS
|
|
extern CRITICAL_SECTION g_CommandStatisticsLock;
|
|
#endif // KEEP_COMMAND_STATS
|
|
|
|
|
|
#ifdef FTP_AUX_COUNTERS
|
|
|
|
LONG g_AuxCounters[NUM_AUX_COUNTERS];
|
|
|
|
#endif // FTP_AUX_COUNTERS
|
|
|
|
|
|
|
|
|
|
char PSZ_COMMAND_NOT_UNDERSTOOD[] = "'%s': command not understood";
|
|
char PSZ_INVALID_PARAMS_TO_COMMAND[] = "'%s': Invalid number of parameters";
|
|
char PSZ_ILLEGAL_PARAMS[] = "'%s': illegal parameters";
|
|
|
|
|
|
|
|
/************************************************************
|
|
* Functions
|
|
************************************************************/
|
|
|
|
|
|
|
|
LPFTPD_COMMAND
|
|
FindCommandByName(
|
|
LPSTR pszCommandName,
|
|
LPFTPD_COMMAND pCommandTable,
|
|
INT cCommands
|
|
);
|
|
|
|
|
|
VOID
|
|
HelpWorker(
|
|
LPUSER_DATA pUserData,
|
|
LPSTR pszSource,
|
|
LPSTR pszCommand,
|
|
LPFTPD_COMMAND pCommandTable,
|
|
INT cCommands,
|
|
INT cchMaxCmd
|
|
);
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: ParseCommand
|
|
|
|
SYNOPSIS: Parses a command string, dispatching to the
|
|
appropriate implementation function.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszCommandText - pointer to command text. This array of
|
|
characters will be munged while parsing.
|
|
|
|
HISTORY:
|
|
KeithMo 07-Mar-1993 Created.
|
|
MuraliK 08-18-1995 Eliminated local copy of the command text
|
|
|
|
********************************************************************/
|
|
VOID
|
|
ParseCommand(
|
|
LPUSER_DATA pUserData,
|
|
LPSTR pszCommandText
|
|
)
|
|
{
|
|
LPFTPD_COMMAND pcmd;
|
|
LPFN_COMMAND pfnCmd;
|
|
LPSTR pszSeparator;
|
|
LPSTR pszInvalidCommandText = PSZ_INVALID_PARAMS_TO_COMMAND;
|
|
CHAR chSeparator;
|
|
BOOL fValidArguments;
|
|
BOOL fReturn = FALSE;
|
|
|
|
DBG_ASSERT( pszCommandText != NULL );
|
|
DBG_ASSERT( IS_VALID_USER_DATA( pUserData ) );
|
|
DBG_ASSERT( IS_VALID_USER_STATE( pUserData->UserState ) );
|
|
|
|
IF_DEBUG( PARSING) {
|
|
|
|
DBGPRINTF( ( DBG_CONTEXT, "ParseCommand( %08x, %s)\n",
|
|
pUserData, pszCommandText));
|
|
}
|
|
|
|
//
|
|
// Ensure we didn't get entered in an invalid state.
|
|
//
|
|
|
|
|
|
//BOGUS: DBG_ASSERT( ( pUserData->UserState != UserStateEmbryonic ) &&
|
|
//BOGUS: ( pUserData->UserState != UserStateDisconnected ) );
|
|
|
|
pUserData->UpdateOffsets();
|
|
|
|
//
|
|
// The command will be terminated by either a space or a '\0'.
|
|
//
|
|
|
|
pszSeparator = strchr( pszCommandText, ' ' );
|
|
|
|
if( pszSeparator == NULL )
|
|
{
|
|
pszSeparator = pszCommandText + strlen( pszCommandText );
|
|
}
|
|
|
|
//
|
|
// Try to find the command in the command table.
|
|
//
|
|
|
|
chSeparator = *pszSeparator;
|
|
*pszSeparator = '\0';
|
|
|
|
pcmd = FindCommandByName( pszCommandText,
|
|
MainCommands,
|
|
NUM_MAIN_COMMANDS );
|
|
|
|
if( chSeparator != '\0' )
|
|
{
|
|
*pszSeparator++ = chSeparator;
|
|
}
|
|
|
|
//
|
|
// If this is an unknown command, reply accordingly.
|
|
//
|
|
|
|
if( pcmd == NULL )
|
|
{
|
|
FacIncrement( FacUnknownCommands);
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_UNRECOGNIZED_COMMAND,
|
|
PSZ_COMMAND_NOT_UNDERSTOOD,
|
|
pszCommandText );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Retrieve the implementation routine.
|
|
//
|
|
|
|
pfnCmd = pcmd->Implementation;
|
|
|
|
//
|
|
// If this is an unimplemented command, reply accordingly.
|
|
//
|
|
|
|
if( pfnCmd == NULL )
|
|
{
|
|
ReplyToUser( pUserData,
|
|
REPLY_COMMAND_NOT_IMPLEMENTED,
|
|
PSZ_COMMAND_NOT_UNDERSTOOD,
|
|
pcmd->CommandName );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Ensure we're in a valid state for the specified command.
|
|
//
|
|
|
|
if ( ( pcmd->dwUserState & pUserData->UserState) == 0) {
|
|
|
|
if( pfnCmd == MainPASS ) {
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_BAD_COMMAND_SEQUENCE,
|
|
"Login with USER first." );
|
|
} else {
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_NOT_LOGGED_IN,
|
|
"Please login with USER and PASS." );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Do a quick & dirty preliminary check of the argument(s).
|
|
//
|
|
|
|
fValidArguments = FALSE;
|
|
|
|
while( ( *pszSeparator == ' ' ) && ( *pszSeparator != '\0' ) ) {
|
|
|
|
pszSeparator++;
|
|
}
|
|
|
|
switch( pcmd->ArgumentType ) {
|
|
|
|
case ArgTypeNone :
|
|
fValidArguments = ( *pszSeparator == '\0' );
|
|
break;
|
|
|
|
case ArgTypeOptional :
|
|
fValidArguments = TRUE;
|
|
break;
|
|
|
|
case ArgTypeRequired :
|
|
fValidArguments = ( *pszSeparator != '\0' );
|
|
break;
|
|
|
|
default:
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"ParseCommand - invalid argtype %d\n",
|
|
pcmd->ArgumentType ));
|
|
DBG_ASSERT( FALSE );
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// check we did not get extended chars if we are configured not to allow that
|
|
//
|
|
|
|
if( g_fNoExtendedChars /* && !pUserDate->QueryUTF8Option() */) {
|
|
|
|
LPSTR pszCh = pszSeparator;
|
|
|
|
while( *pszCh ) {
|
|
|
|
if( !IS_7BIT_ASCII( *pszCh++ ) ) {
|
|
|
|
fValidArguments = FALSE;
|
|
pszInvalidCommandText = PSZ_ILLEGAL_PARAMS;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( fValidArguments ) {
|
|
|
|
//
|
|
// Invoke the implementation routine.
|
|
//
|
|
|
|
if( *pszSeparator == '\0' )
|
|
{
|
|
pszSeparator = NULL;
|
|
}
|
|
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"invoking %s command, args = %s\n",
|
|
pcmd->CommandName,
|
|
_strnicmp( pcmd->CommandName, "PASS", 4 )
|
|
? pszSeparator
|
|
: "{secret...}" ));
|
|
}
|
|
|
|
#ifdef KEEP_COMMAND_STATS
|
|
EnterCriticalSection( &g_CommandStatisticsLock );
|
|
|
|
//
|
|
// only increment the count if we're not re-processing a command
|
|
//
|
|
if ( !pUserData->QueryInFakeIOCompletion() )
|
|
{
|
|
pcmd->UsageCount++;
|
|
}
|
|
LeaveCriticalSection( &g_CommandStatisticsLock );
|
|
#endif // KEEP_COMMAND_STATS
|
|
|
|
//
|
|
// Keep track of what command is being executed, in case command processing doesn't
|
|
// complete in this thread and another thread has to finish processing it
|
|
// [can happen if we're in PASV mode and doing async accept on the data connection]
|
|
// Only need to do this if this thread isn't handling an IO completion we generated
|
|
// ourselves because a PASV socket became accept()'able - if it is, we've already
|
|
// set the command.
|
|
//
|
|
if ( !pUserData->QueryInFakeIOCompletion() )
|
|
{
|
|
if ( !pUserData->SetCommand( pszCommandText ) )
|
|
{
|
|
ReplyToUser( pUserData,
|
|
REPLY_LOCAL_ERROR,
|
|
"Failed to allocate necessary memory.");
|
|
}
|
|
}
|
|
fReturn = (pfnCmd)( pUserData, pszSeparator );
|
|
|
|
if ( !fReturn) {
|
|
|
|
//
|
|
// Invalid number of arguments. Inform the client.
|
|
//
|
|
ReplyToUser(pUserData,
|
|
REPLY_UNRECOGNIZED_COMMAND,
|
|
PSZ_COMMAND_NOT_UNDERSTOOD,
|
|
pszCommandText);
|
|
}
|
|
|
|
} else {
|
|
|
|
// Invalid # of arguments
|
|
|
|
ReplyToUser(pUserData,
|
|
REPLY_UNRECOGNIZED_COMMAND,
|
|
pszInvalidCommandText,
|
|
pszCommandText);
|
|
}
|
|
|
|
return;
|
|
} // ParseCommand()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: MainSITE
|
|
|
|
SYNOPSIS: Implementation for the SITE command.
|
|
|
|
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
|
|
MainSITE(
|
|
LPUSER_DATA pUserData,
|
|
LPSTR pszArg
|
|
)
|
|
{
|
|
LPFTPD_COMMAND pcmd;
|
|
LPFN_COMMAND pfnCmd;
|
|
LPSTR pszSeparator;
|
|
CHAR chSeparator;
|
|
BOOL fValidArguments;
|
|
CHAR szParsedCommand[MAX_COMMAND_LENGTH+1];
|
|
|
|
DBG_ASSERT( pUserData != NULL );
|
|
|
|
|
|
//
|
|
// If no arguments were given, just return the help text.
|
|
//
|
|
|
|
if( pszArg == NULL )
|
|
{
|
|
SiteHELP( pUserData, NULL );
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Save a copy of the command so we can muck around with it.
|
|
//
|
|
|
|
P_strncpy( szParsedCommand, pszArg, MAX_COMMAND_LENGTH );
|
|
|
|
//
|
|
// The command will be terminated by either a space or a '\0'.
|
|
//
|
|
|
|
pszSeparator = strchr( szParsedCommand, ' ' );
|
|
|
|
if( pszSeparator == NULL )
|
|
{
|
|
pszSeparator = szParsedCommand + strlen( szParsedCommand );
|
|
}
|
|
|
|
//
|
|
// Try to find the command in the command table.
|
|
//
|
|
|
|
chSeparator = *pszSeparator;
|
|
*pszSeparator = '\0';
|
|
|
|
pcmd = FindCommandByName( szParsedCommand,
|
|
SiteCommands,
|
|
NUM_SITE_COMMANDS );
|
|
|
|
if( chSeparator != '\0' )
|
|
{
|
|
*pszSeparator++ = chSeparator;
|
|
}
|
|
|
|
//
|
|
// If this is an unknown command, reply accordingly.
|
|
//
|
|
|
|
if( pcmd == NULL ) {
|
|
|
|
//
|
|
// Syntax error in command.
|
|
//
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_UNRECOGNIZED_COMMAND,
|
|
"'SITE %s': command not understood",
|
|
pszArg );
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
//
|
|
// Retrieve the implementation routine.
|
|
//
|
|
|
|
pfnCmd = pcmd->Implementation;
|
|
|
|
//
|
|
// If this is an unimplemented command, reply accordingly.
|
|
//
|
|
|
|
if( pfnCmd == NULL )
|
|
{
|
|
ReplyToUser( pUserData,
|
|
REPLY_COMMAND_NOT_IMPLEMENTED,
|
|
"SITE %s command not implemented.",
|
|
pcmd->CommandName );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Ensure we're in a valid state for the specified command.
|
|
//
|
|
|
|
if ( ( pcmd->dwUserState & pUserData->UserState) == 0) {
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_NOT_LOGGED_IN,
|
|
"Please login with USER and PASS." );
|
|
return (FALSE);
|
|
}
|
|
|
|
//
|
|
// Do a quick & dirty preliminary check of the argument(s).
|
|
//
|
|
|
|
fValidArguments = FALSE;
|
|
|
|
while( ( *pszSeparator == ' ' ) && ( *pszSeparator != '\0' ) )
|
|
{
|
|
|
|
pszSeparator++;
|
|
}
|
|
|
|
switch( pcmd->ArgumentType ) {
|
|
|
|
case ArgTypeNone:
|
|
fValidArguments = ( *pszSeparator == '\0' );
|
|
break;
|
|
|
|
case ArgTypeOptional:
|
|
fValidArguments = TRUE;
|
|
break;
|
|
|
|
case ArgTypeRequired:
|
|
fValidArguments = ( *pszSeparator != '\0' );
|
|
break;
|
|
|
|
default:
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"MainSite - invalid argtype %d\n",
|
|
pcmd->ArgumentType ));
|
|
DBG_ASSERT( FALSE );
|
|
break;
|
|
}
|
|
|
|
if( fValidArguments ) {
|
|
|
|
//
|
|
// Invoke the implementation routine.
|
|
//
|
|
|
|
if( *pszSeparator == '\0' )
|
|
{
|
|
pszSeparator = NULL;
|
|
}
|
|
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"invoking SITE %s command, args = %s\n",
|
|
pcmd->CommandName,
|
|
pszSeparator ));
|
|
}
|
|
|
|
if( (pfnCmd)( pUserData, pszSeparator ) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
|
|
// Invalid # of arguments
|
|
|
|
ReplyToUser(pUserData,
|
|
REPLY_UNRECOGNIZED_COMMAND,
|
|
PSZ_INVALID_PARAMS_TO_COMMAND,
|
|
pszArg);
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
} // MainSITE()
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: MainHELP
|
|
|
|
SYNOPSIS: Implementation for the HELP command.
|
|
|
|
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
|
|
MainHELP(
|
|
LPUSER_DATA pUserData,
|
|
LPSTR pszArg
|
|
)
|
|
{
|
|
DBG_ASSERT( pUserData != NULL );
|
|
|
|
HelpWorker(pUserData,
|
|
"",
|
|
pszArg,
|
|
MainCommands,
|
|
NUM_MAIN_COMMANDS,
|
|
4 );
|
|
|
|
return TRUE;
|
|
|
|
} // MainHELP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: SiteHELP
|
|
|
|
SYNOPSIS: Implementation for the site-specific HELP command.
|
|
|
|
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-May-1993 Created.
|
|
|
|
********************************************************************/
|
|
BOOL
|
|
SiteHELP(
|
|
LPUSER_DATA pUserData,
|
|
LPSTR pszArg
|
|
)
|
|
{
|
|
DBG_ASSERT( pUserData != NULL );
|
|
|
|
HelpWorker(pUserData,
|
|
"SITE ",
|
|
pszArg,
|
|
SiteCommands,
|
|
NUM_SITE_COMMANDS,
|
|
8 );
|
|
|
|
return TRUE;
|
|
|
|
} // SiteHELP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef KEEP_COMMAND_STATS
|
|
/*******************************************************************
|
|
|
|
NAME: SiteSTATS
|
|
|
|
SYNOPSIS: Implementation for the site-specific STATS command.
|
|
|
|
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 26-Sep-1994 Created.
|
|
|
|
********************************************************************/
|
|
BOOL
|
|
SiteSTATS(
|
|
LPUSER_DATA pUserData,
|
|
LPSTR pszArg
|
|
)
|
|
{
|
|
SOCKET ControlSocket;
|
|
LPFTPD_COMMAND pCmd;
|
|
INT i;
|
|
CHAR rgchUsageStats[NUM_MAIN_COMMANDS * 25]; // 25 chars per command
|
|
|
|
pCmd = MainCommands;
|
|
|
|
DBG_ASSERT( NUM_MAIN_COMMANDS > 0); // we know this very well!
|
|
|
|
// Print the stats for first command.
|
|
EnterCriticalSection( &g_CommandStatisticsLock );
|
|
|
|
// Find first non-zero entry.
|
|
for( i = 0; i < NUM_MAIN_COMMANDS && pCmd->UsageCount <= 0; i++, pCmd++)
|
|
;
|
|
|
|
if ( i < NUM_MAIN_COMMANDS) {
|
|
|
|
// There is some non-zero entry.
|
|
|
|
CHAR * pszStats = rgchUsageStats;
|
|
DWORD cch = 0;
|
|
|
|
// print the stats for first command
|
|
cch = wsprintfA( pszStats + cch, "%u-%-4s : %lu\r\n",
|
|
REPLY_COMMAND_OK,
|
|
pCmd->CommandName,
|
|
pCmd->UsageCount);
|
|
|
|
for( i++, pCmd++ ; i < NUM_MAIN_COMMANDS ; i++, pCmd++) {
|
|
|
|
if( pCmd->UsageCount > 0 ) {
|
|
|
|
cch += wsprintfA( pszStats + cch,
|
|
" %-4s : %lu\r\n",
|
|
pCmd->CommandName,
|
|
pCmd->UsageCount );
|
|
DBG_ASSERT( cch < NUM_MAIN_COMMANDS * 25);
|
|
}
|
|
|
|
} // for
|
|
|
|
// Ignoring the error code here! probably socket closed
|
|
SockSend( pUserData, pUserData->QueryControlSocket(),
|
|
rgchUsageStats, cch);
|
|
}
|
|
|
|
LeaveCriticalSection( &g_CommandStatisticsLock );
|
|
|
|
#ifdef FTP_AUX_COUNTERS
|
|
|
|
CHAR * pszStats = rgchUsageStats;
|
|
DWORD cch = 0;
|
|
|
|
// print the stats for first counter
|
|
cch = wsprintfA( pszStats + cch, "%u-Aux[%d] : %lu\r\n",
|
|
REPLY_COMMAND_OK,
|
|
0,
|
|
FacCounter(0));
|
|
|
|
for( i = 1; i < NUM_AUX_COUNTERS; i++) {
|
|
|
|
cch += wsprintfA( pszStats + cch,
|
|
" Aux[%d] : %lu\r\n",
|
|
i,
|
|
FacCounter(i));
|
|
DBG_ASSERT( cch < NUM_MAIN_COMMANDS * 25);
|
|
}
|
|
|
|
if ( cch > 0) {
|
|
|
|
SockSend( pUserData, pUserData->QueryControlSocket(),
|
|
rgchUsageStats, cch);
|
|
}
|
|
|
|
# endif // FTP_AUX_COUNTERS
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_COMMAND_OK,
|
|
"End of stats." );
|
|
|
|
return TRUE;
|
|
|
|
} // SiteSTATS
|
|
#endif KEEP_COMMAND_STATS
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: FindCommandByName
|
|
|
|
SYNOPSIS: Searches the command table for a command with this
|
|
specified name.
|
|
|
|
ENTRY: pszCommandName - The name of the command to find.
|
|
|
|
pCommandTable - An array of FTPD_COMMANDs detailing
|
|
the available commands.
|
|
|
|
cCommands - The number of commands in pCommandTable.
|
|
|
|
RETURNS: LPFTPD_COMMAND - Points to the command entry for
|
|
the named command. Will be NULL if command
|
|
not found.
|
|
|
|
HISTORY:
|
|
KeithMo 10-Mar-1993 Created.
|
|
MuraliK 28-Mar-1995 Completely rewrote to support binary search.
|
|
|
|
********************************************************************/
|
|
LPFTPD_COMMAND
|
|
FindCommandByName(
|
|
LPSTR pszCommandName,
|
|
LPFTPD_COMMAND pCommandTable,
|
|
INT cCommands
|
|
)
|
|
{
|
|
int iLower = 0;
|
|
int iHigher = cCommands - 1; // store the indexes
|
|
LPFTPD_COMMAND pCommandFound = NULL;
|
|
|
|
DBG_ASSERT( pszCommandName != NULL );
|
|
DBG_ASSERT( pCommandTable != NULL );
|
|
DBG_ASSERT( cCommands > 0 );
|
|
|
|
//
|
|
// Search for the command in our table.
|
|
//
|
|
|
|
_strupr( pszCommandName );
|
|
|
|
|
|
while ( iLower <= iHigher) {
|
|
|
|
int iMid = ( iHigher + iLower) / 2;
|
|
|
|
int comp = strcmp( pszCommandName, pCommandTable[ iMid].CommandName);
|
|
|
|
if ( comp == 0) {
|
|
|
|
pCommandFound = ( pCommandTable + iMid);
|
|
break;
|
|
|
|
} else if ( comp < 0) {
|
|
|
|
// reset the higher bound
|
|
iHigher = iMid - 1;
|
|
} else {
|
|
|
|
// reset the lower bound
|
|
iLower = iMid + 1;
|
|
}
|
|
}
|
|
|
|
return ( pCommandFound);
|
|
|
|
} // FindCommandByName()
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HelpWorker
|
|
|
|
SYNOPSIS: Worker function for HELP & site-specific HELP commands.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszSource - The source of these commands.
|
|
|
|
pszCommand - The command to get help for. If NULL,
|
|
then send a list of available commands.
|
|
|
|
pCommandTable - An array of FTPD_COMMANDs, one for
|
|
each available command.
|
|
|
|
cCommands - The number of commands in pCommandTable.
|
|
|
|
cchMaxCmd - Length of the maximum command.
|
|
|
|
HISTORY:
|
|
KeithMo 06-May-1993 Created.
|
|
Muralik 08-May-1995 Added Buffering for performance.
|
|
|
|
********************************************************************/
|
|
VOID
|
|
HelpWorker(
|
|
LPUSER_DATA pUserData,
|
|
LPSTR pszSource,
|
|
LPSTR pszCommand,
|
|
LPFTPD_COMMAND pCommandTable,
|
|
INT cCommands,
|
|
INT cchMaxCmd
|
|
)
|
|
{
|
|
LPFTPD_COMMAND pcmd;
|
|
DWORD dwError;
|
|
|
|
//
|
|
// We should cache the following message and use the cached message for
|
|
// sending purposes ==> improves performance.
|
|
// MuraliK NYI
|
|
//
|
|
|
|
DBG_ASSERT( pUserData != NULL );
|
|
DBG_ASSERT( pCommandTable != NULL );
|
|
DBG_ASSERT( cCommands > 0 );
|
|
|
|
if( pszCommand == NULL ) {
|
|
|
|
DWORD cch;
|
|
LS_BUFFER lsb;
|
|
CHAR szTmp[MAX_HELP_LINE_SIZE];
|
|
|
|
if ((dwError = lsb.AllocateBuffer(HelpMsgSize(cCommands)))!= NO_ERROR){
|
|
|
|
IF_DEBUG( ERROR) {
|
|
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"Buffer Allocation ( %d bytes) failed.\n",
|
|
HelpMsgSize(cCommands)));
|
|
}
|
|
|
|
ReplyToUser(pUserData,
|
|
REPLY_HELP_MESSAGE,
|
|
"HELP command failed." );
|
|
return;
|
|
}
|
|
|
|
cch = wsprintfA( lsb.QueryAppendPtr(),
|
|
"%u-The following %s commands are recognized"
|
|
"(* ==>'s unimplemented).\r\n",
|
|
REPLY_HELP_MESSAGE,
|
|
pszSource);
|
|
lsb.IncrementCB( cch * sizeof( CHAR));
|
|
|
|
for( pcmd = pCommandTable; pcmd < pCommandTable + cCommands; pcmd++) {
|
|
|
|
cch = sprintf( szTmp,
|
|
" %-*s%c\r\n",
|
|
cchMaxCmd,
|
|
pcmd->CommandName,
|
|
pcmd->Implementation == NULL ? '*' : ' ' );
|
|
|
|
DBG_ASSERT( cch*sizeof(CHAR) < sizeof(szTmp));
|
|
|
|
//
|
|
// since we calculate and preallocate the buffer, we dont
|
|
// need to worry about the overflow of the buffer.
|
|
//
|
|
|
|
DBG_ASSERT( cch*sizeof(CHAR) < lsb.QueryRemainingCB());
|
|
|
|
if ( cch * sizeof(CHAR) >= lsb.QueryRemainingCB()) {
|
|
|
|
// This is added for retail code where ASSERT may fail.
|
|
|
|
dwError = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
}
|
|
|
|
strncpy( lsb.QueryAppendPtr(), szTmp, cch);
|
|
lsb.IncrementCB( cch*sizeof(CHAR));
|
|
|
|
} // for ( all commands)
|
|
|
|
if ( dwError == NO_ERROR) {
|
|
|
|
// Append the ending sequence for success in generating HELP.
|
|
cch = sprintf( szTmp,
|
|
"%u %s\r\n",
|
|
REPLY_HELP_MESSAGE,
|
|
"HELP command successful." );
|
|
|
|
if ( cch*sizeof(CHAR) >= lsb.QueryRemainingCB()) {
|
|
|
|
dwError = ERROR_NOT_ENOUGH_MEMORY;
|
|
} else {
|
|
// copy the completion message
|
|
strncpy( lsb.QueryAppendPtr(), szTmp, cch);
|
|
lsb.IncrementCB( cch*sizeof(CHAR));
|
|
}
|
|
}
|
|
|
|
if ( dwError == NO_ERROR) {
|
|
|
|
// Send the chunk of data
|
|
|
|
dwError = SockSend( pUserData,
|
|
pUserData->QueryControlSocket(),
|
|
lsb.QueryBuffer(),
|
|
lsb.QueryCB());
|
|
} else {
|
|
|
|
IF_DEBUG( ERROR) {
|
|
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"Error = %u. Should Not happen though...\n",
|
|
dwError));
|
|
}
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_HELP_MESSAGE,
|
|
"HELP command failed.");
|
|
}
|
|
|
|
lsb.FreeBuffer();
|
|
|
|
// Ignore the errors if any from propagating outside
|
|
|
|
} else {
|
|
|
|
pcmd = FindCommandByName(pszCommand,
|
|
pCommandTable,
|
|
cCommands );
|
|
|
|
if( pcmd == NULL ) {
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_PARAMETER_SYNTAX_ERROR,
|
|
"Unknown command %s.",
|
|
pszCommand );
|
|
} else {
|
|
|
|
ReplyToUser( pUserData,
|
|
REPLY_HELP_MESSAGE,
|
|
"Syntax: %s%s %s",
|
|
pszSource,
|
|
pcmd->CommandName,
|
|
pcmd->HelpText );
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
} // HelpWorker()
|
|
|
|
|
|
/************************ End of File ***********************/
|
|
|