Leaked source code of windows server 2003
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.
 
 
 
 
 
 

1205 lines
32 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},
{ "FEAT", "list feature extensions", MainFEAT, ArgTypeNone, 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},
{ "OPTS", "<sp> command <sp> options", MainOPTS, ArgTypeRequired,UsL},
{ "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,UsL},
{ "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
//
// Following is the reply string for the FEAT command. this is a MultiLine reply, where each
// line is terminated by a '\0', and the string is terminated by '\0\0'. First and last verbs
// must not change.
//
char PSZ_FEAT_REPLY[] =
"FEAT\0"
"SIZE\0"
"MDTM\0"
"END\0";
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";
char PSZ_UNSUPPORTED_OPTION[] = "option not supported";
/************************************************************
* 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;
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.");
return;
}
}
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_PARAMETER_SYNTAX_ERROR,
pszInvalidCommandText,
pszCommandText);
}
return;
} // ParseCommand()
/*******************************************************************
NAME: MainFEAT
SYNOPSIS: Implementation for the FEAT 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:
RobSol 22-May-2001 Created.
********************************************************************/
BOOL
MainFEAT(
LPUSER_DATA pUserData,
LPSTR pszArg
)
{
DBG_ASSERT( pUserData != NULL );
pUserData->SendMultilineMessage(
REPLY_SYSTEM_STATUS,
PSZ_FEAT_REPLY,
TRUE, // is first line
TRUE); // is last line
return TRUE;
} // MainFEAT
/*******************************************************************
NAME: MainOPTS
SYNOPSIS: Implementation for the OPTS 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:
RobSol 22-May-2001 Created.
********************************************************************/
BOOL
MainOPTS(
LPUSER_DATA pUserData,
LPSTR pszArg
)
{
DBG_ASSERT( pUserData != NULL );
ReplyToUser(pUserData,
REPLY_PARAMETER_SYNTAX_ERROR,
PSZ_UNSUPPORTED_OPTION);
return TRUE;
} // MainOPTS
/*******************************************************************
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
)
{
LPFTPD_COMMAND pCmd;
INT i;
CHAR rgchUsageStats[NUM_MAIN_COMMANDS * 25]; // 25 chars per command
CHAR * pszStats;
INT cch, cch2;
pCmd = MainCommands;
// 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.
pszStats = rgchUsageStats;
// print the stats for first command
cch = _snprintf( pszStats, sizeof( rgchUsageStats ),
"%u-%-4s : %lu\r\n",
REPLY_COMMAND_OK,
pCmd->CommandName,
pCmd->UsageCount);
// static output, sufficient to validate in checked build
DBG_ASSERT( (cch > 0) && (cch < sizeof( rgchUsageStats )) );
for( i++, pCmd++ ; i < NUM_MAIN_COMMANDS ; i++, pCmd++) {
if( pCmd->UsageCount > 0 ) {
cch2 = _snprintf( pszStats + cch, sizeof( rgchUsageStats ) - cch,
" %-4s : %lu\r\n",
pCmd->CommandName,
pCmd->UsageCount );
DBG_ASSERT( cch2 > 0 );
cch += cch2;
DBG_ASSERT( cch < sizeof( rgchUsageStats ));
}
} // for
// Ignoring the error code here! probably socket closed
SockSend( pUserData, pUserData->QueryControlSocket(),
rgchUsageStats, cch);
}
LeaveCriticalSection( &g_CommandStatisticsLock );
#ifdef FTP_AUX_COUNTERS
pszStats = rgchUsageStats;
// print the stats for first counter
cch = _snprintf( pszStats, sizeof( rgchUsageStats ),
"%u-Aux[%d] : %lu\r\n",
REPLY_COMMAND_OK,
0,
FacCounter(0));
DBG_ASSERT( (cch > 0) && (cch < sizeof( rgchUsageStats )) );
for( i = 1; i < NUM_AUX_COUNTERS; i++) {
cch2 = _snprintf( pszStats + cch, sizeof( rgchUsageStats ) - cch,
" Aux[%d] : %lu\r\n",
i,
FacCounter(i));
DBG_ASSERT( cch2 > 0 );
cch += cch2;
DBG_ASSERT( cch < sizeof( rgchUsageStats ));
}
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 ) {
INT cch;
LS_BUFFER lsb;
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 = _snprintf( lsb.QueryAppendPtr(), lsb.QueryRemainingCB(),
"%u-The following %s commands are recognized"
"(* ==>'s unimplemented).\r\n",
REPLY_HELP_MESSAGE,
pszSource);
DBG_ASSERT( (cch > 0) && (cch < (INT)lsb.QueryRemainingCB()) );
lsb.IncrementCB( cch * sizeof( CHAR));
for( pcmd = pCommandTable; pcmd < pCommandTable + cCommands; pcmd++) {
cch = _snprintf( lsb.QueryAppendPtr(), lsb.QueryRemainingCB(),
" %-*s%c\r\n",
cchMaxCmd,
pcmd->CommandName,
pcmd->Implementation == NULL ? '*' : ' ' );
DBG_ASSERT( (cch > 0) && (cch < (INT)lsb.QueryRemainingCB()) );
if ( cch < 0 ) {
// This is added for retail code where ASSERT may fail.
dwError = ERROR_NOT_ENOUGH_MEMORY;
break;
}
lsb.IncrementCB( cch*sizeof(CHAR));
} // for ( all commands)
if ( dwError == NO_ERROR) {
// Append the ending sequence for success in generating HELP.
cch = _snprintf( lsb.QueryAppendPtr(), lsb.QueryRemainingCB(),
"%u %s\r\n",
REPLY_HELP_MESSAGE,
"HELP command successful." );
DBG_ASSERT( (cch > 0) && (cch < (INT)lsb.QueryRemainingCB()) );
if ( cch < 0 ) {
dwError = ERROR_NOT_ENOUGH_MEMORY;
} else {
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 ***********************/