/*++ 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 # 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", " file-name", MainAPPE, ArgTypeRequired,UsL}, { "CDUP", "change to parent directory", MainCDUP, ArgTypeNone, UsL}, { "CWD", "[ directory-name ]", MainCWD , ArgTypeOptional,UsL}, { "DELE", " file-name", MainDELE, ArgTypeRequired,UsL}, { "HELP", "[ ]", MainHELP, ArgTypeOptional,UsUPL}, { "LIST", "[ path-name ]", MainLIST, ArgTypeOptional,UsL}, { "MDTM", "(sp) file-name", MainMDTM, ArgTypeRequired,UsL }, { "MKD", " path-name", MainMKD , ArgTypeRequired,UsL}, { "MODE", "(specify transfer mode)", MainMODE, ArgTypeRequired,UsUPL}, { "NLST", "[ path-name ]", MainNLST, ArgTypeOptional,UsL}, { "NOOP", "", MainNOOP, ArgTypeNone, UsUPL}, { "PASS", " password", MainPASS, ArgTypeOptional, UsP}, { "PASV", "(set server in passive mode)", MainPASV, ArgTypeNone, UsL}, { "PORT", " 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", " marker", MainREST, ArgTypeRequired,UsL}, { "RETR", " file-name", MainRETR, ArgTypeRequired,UsL}, { "RMD", " path-name", MainRMD , ArgTypeRequired,UsL }, { "RNFR", " file-name", MainRNFR, ArgTypeRequired,UsL}, { "RNTO", " file-name", MainRNTO, ArgTypeRequired,UsL }, { "SITE", "(site-specific commands)", MainSITE, ArgTypeOptional,UsL }, { "SIZE", "(sp) file-name", MainSIZE, ArgTypeRequired,UsL }, { "SMNT", " pathname", MainSMNT, ArgTypeRequired,UsL }, { "STAT", "(get server status)", MainSTAT, ArgTypeOptional,UsL }, { "STOR", " 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", " [ A | E | I | L ]", MainTYPE, ArgTypeRequired,UsL }, { "USER", " username", MainUSER, ArgTypeRequired,UsUPL}, { "XCUP", "change to parent directory", MainCDUP, ArgTypeNone, UsL}, { "XCWD", "[ directory-name ]", MainCWD , ArgTypeOptional,UsL }, { "XMKD", " path-name", MainMKD , ArgTypeRequired,UsL }, { "XPWD", "(return current directory)", MainPWD , ArgTypeNone, UsL }, { "XRMD", " 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", "[ ]", 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 ***********************/