Source code of Windows XP (NT5)
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

  1. /*++
  2. Copyright (c) 1995 Microsoft Corporation
  3. Module Name :
  4. ftpcmd.cxx
  5. Abstract:
  6. This module defines the FTP commands supported by this FTP server
  7. and provides a table of functions to be called for processing
  8. such command requests.
  9. ( Some parts of the code are from old engine.cxx ( KeithMo's FTP server))
  10. Author:
  11. Murali R. Krishnan ( MuraliK ) 28-Mar-1995
  12. Environment:
  13. User Mode -- Win32
  14. Project:
  15. FTP Server DLL
  16. Functions Exported:
  17. ParseCommand
  18. ()
  19. Revision History:
  20. --*/
  21. /************************************************************
  22. * Include Headers
  23. ************************************************************/
  24. # include <ftpdp.hxx>
  25. # include "ftpcmd.hxx"
  26. # include "lsaux.hxx"
  27. # include "auxctrs.h"
  28. #define MAX_COMMAND_NAME_LEN ( 30)
  29. # define MAX_HELP_LINE_SIZE ( 80)
  30. # define MAX_HELP_AUX_SIZE (100) // fixed sized aux info with HELP
  31. # define HelpMsgSize( nCommands) ((1 + nCommands) * MAX_HELP_LINE_SIZE + \
  32. MAX_HELP_AUX_SIZE)
  33. #define IS_7BIT_ASCII(c) ((UINT)(c) <= 127)
  34. /************************************************************
  35. * Static Data containing command lookups
  36. ************************************************************/
  37. # define UsP ( UserStateWaitingForPass)
  38. # define UsUP ( UserStateWaitingForUser | UsP)
  39. # define UsL ( UserStateLoggedOn)
  40. # define UsUPL ( UsL | UsUP)
  41. //
  42. // Store the commands in alphabetical order ( manually stored so!)
  43. // to enable faster search.
  44. //
  45. // Format is:
  46. // Name Help Information FunctionToCall ArgumentType ValidStates
  47. //
  48. FTPD_COMMAND MainCommands[] ={
  49. { "ABOR", "(abort operation)", MainABOR, ArgTypeNone, UsL},
  50. { "ACCT", "(specify account)", MainACCT, ArgTypeRequired,UsL},
  51. { "ALLO", "(allocate storage vacuously)", MainALLO, ArgTypeRequired,UsL},
  52. { "APPE", "<sp> file-name", MainAPPE, ArgTypeRequired,UsL},
  53. { "CDUP", "change to parent directory", MainCDUP, ArgTypeNone, UsL},
  54. { "CWD", "[ <sp> directory-name ]", MainCWD , ArgTypeOptional,UsL},
  55. { "DELE", "<sp> file-name", MainDELE, ArgTypeRequired,UsL},
  56. { "HELP", "[ <sp> <string>]", MainHELP, ArgTypeOptional,UsUPL},
  57. { "LIST", "[ <sp> path-name ]", MainLIST, ArgTypeOptional,UsL},
  58. { "MDTM", "(sp) file-name", MainMDTM, ArgTypeRequired,UsL },
  59. { "MKD", "<sp> path-name", MainMKD , ArgTypeRequired,UsL},
  60. { "MODE", "(specify transfer mode)", MainMODE, ArgTypeRequired,UsUPL},
  61. { "NLST", "[ <sp> path-name ]", MainNLST, ArgTypeOptional,UsL},
  62. { "NOOP", "", MainNOOP, ArgTypeNone, UsUPL},
  63. { "PASS", "<sp> password", MainPASS, ArgTypeOptional, UsP},
  64. { "PASV", "(set server in passive mode)", MainPASV, ArgTypeNone, UsL},
  65. { "PORT", "<sp> b0,b1,b2,b3,b4,b5", MainPORT, ArgTypeRequired,UsUPL},
  66. { "PWD", "(return current directory)", MainPWD , ArgTypeNone, UsL},
  67. { "QUIT", "(terminate service)", MainQUIT, ArgTypeNone, UsUPL},
  68. { "REIN", "(reinitialize server state)", MainREIN, ArgTypeNone, UsL},
  69. { "REST", "<sp> marker", MainREST, ArgTypeRequired,UsL},
  70. { "RETR", "<sp> file-name", MainRETR, ArgTypeRequired,UsL},
  71. { "RMD", "<sp> path-name", MainRMD , ArgTypeRequired,UsL },
  72. { "RNFR", "<sp> file-name", MainRNFR, ArgTypeRequired,UsL},
  73. { "RNTO", "<sp> file-name", MainRNTO, ArgTypeRequired,UsL },
  74. { "SITE", "(site-specific commands)", MainSITE, ArgTypeOptional,UsL },
  75. { "SIZE", "(sp) file-name", MainSIZE, ArgTypeRequired,UsL },
  76. { "SMNT", "<sp> pathname", MainSMNT, ArgTypeRequired,UsL },
  77. { "STAT", "(get server status)", MainSTAT, ArgTypeOptional,UsL },
  78. { "STOR", "<sp> file-name", MainSTOR, ArgTypeRequired,UsL },
  79. { "STOU", "(store unique file)", MainSTOU, ArgTypeNone, UsL},
  80. { "STRU", "(specify file structure)", MainSTRU, ArgTypeRequired,UsUPL},
  81. { "SYST", "(get operating system type)", MainSYST, ArgTypeNone, UsL },
  82. { "TYPE", "<sp> [ A | E | I | L ]", MainTYPE, ArgTypeRequired,UsL },
  83. { "USER", "<sp> username", MainUSER, ArgTypeRequired,UsUPL},
  84. { "XCUP", "change to parent directory", MainCDUP, ArgTypeNone, UsL},
  85. { "XCWD", "[ <sp> directory-name ]", MainCWD , ArgTypeOptional,UsL },
  86. { "XMKD", "<sp> path-name", MainMKD , ArgTypeRequired,UsL },
  87. { "XPWD", "(return current directory)", MainPWD , ArgTypeNone, UsL },
  88. { "XRMD", "<sp> path-name", MainRMD , ArgTypeRequired,UsL }
  89. };
  90. #define NUM_MAIN_COMMANDS ( sizeof(MainCommands) / sizeof(MainCommands[0]) )
  91. FTPD_COMMAND SiteCommands[] = {
  92. { "CKM", "(toggle directory comments)", SiteCKM , ArgTypeNone,UsL},
  93. { "DIRSTYLE", "(toggle directory format)", SiteDIRSTYLE, ArgTypeNone,UsL},
  94. { "HELP", "[ <sp> <string>]", SiteHELP , ArgTypeOptional,
  95. UsL}
  96. #ifdef KEEP_COMMAND_STATS
  97. ,{ "STATS", "(display per-command stats)", SiteSTATS , ArgTypeNone, UsL}
  98. #endif // KEEP_COMMAND_STATS
  99. };
  100. #define NUM_SITE_COMMANDS ( sizeof(SiteCommands) / sizeof(SiteCommands[0]) )
  101. #ifdef KEEP_COMMAND_STATS
  102. extern CRITICAL_SECTION g_CommandStatisticsLock;
  103. #endif // KEEP_COMMAND_STATS
  104. #ifdef FTP_AUX_COUNTERS
  105. LONG g_AuxCounters[NUM_AUX_COUNTERS];
  106. #endif // FTP_AUX_COUNTERS
  107. char PSZ_COMMAND_NOT_UNDERSTOOD[] = "'%s': command not understood";
  108. char PSZ_INVALID_PARAMS_TO_COMMAND[] = "'%s': Invalid number of parameters";
  109. char PSZ_ILLEGAL_PARAMS[] = "'%s': illegal parameters";
  110. /************************************************************
  111. * Functions
  112. ************************************************************/
  113. LPFTPD_COMMAND
  114. FindCommandByName(
  115. LPSTR pszCommandName,
  116. LPFTPD_COMMAND pCommandTable,
  117. INT cCommands
  118. );
  119. VOID
  120. HelpWorker(
  121. LPUSER_DATA pUserData,
  122. LPSTR pszSource,
  123. LPSTR pszCommand,
  124. LPFTPD_COMMAND pCommandTable,
  125. INT cCommands,
  126. INT cchMaxCmd
  127. );
  128. /*******************************************************************
  129. NAME: ParseCommand
  130. SYNOPSIS: Parses a command string, dispatching to the
  131. appropriate implementation function.
  132. ENTRY: pUserData - The user initiating the request.
  133. pszCommandText - pointer to command text. This array of
  134. characters will be munged while parsing.
  135. HISTORY:
  136. KeithMo 07-Mar-1993 Created.
  137. MuraliK 08-18-1995 Eliminated local copy of the command text
  138. ********************************************************************/
  139. VOID
  140. ParseCommand(
  141. LPUSER_DATA pUserData,
  142. LPSTR pszCommandText
  143. )
  144. {
  145. LPFTPD_COMMAND pcmd;
  146. LPFN_COMMAND pfnCmd;
  147. LPSTR pszSeparator;
  148. LPSTR pszInvalidCommandText = PSZ_INVALID_PARAMS_TO_COMMAND;
  149. CHAR chSeparator;
  150. BOOL fValidArguments;
  151. BOOL fReturn = FALSE;
  152. DBG_ASSERT( pszCommandText != NULL );
  153. DBG_ASSERT( IS_VALID_USER_DATA( pUserData ) );
  154. DBG_ASSERT( IS_VALID_USER_STATE( pUserData->UserState ) );
  155. IF_DEBUG( PARSING) {
  156. DBGPRINTF( ( DBG_CONTEXT, "ParseCommand( %08x, %s)\n",
  157. pUserData, pszCommandText));
  158. }
  159. //
  160. // Ensure we didn't get entered in an invalid state.
  161. //
  162. //BOGUS: DBG_ASSERT( ( pUserData->UserState != UserStateEmbryonic ) &&
  163. //BOGUS: ( pUserData->UserState != UserStateDisconnected ) );
  164. pUserData->UpdateOffsets();
  165. //
  166. // The command will be terminated by either a space or a '\0'.
  167. //
  168. pszSeparator = strchr( pszCommandText, ' ' );
  169. if( pszSeparator == NULL )
  170. {
  171. pszSeparator = pszCommandText + strlen( pszCommandText );
  172. }
  173. //
  174. // Try to find the command in the command table.
  175. //
  176. chSeparator = *pszSeparator;
  177. *pszSeparator = '\0';
  178. pcmd = FindCommandByName( pszCommandText,
  179. MainCommands,
  180. NUM_MAIN_COMMANDS );
  181. if( chSeparator != '\0' )
  182. {
  183. *pszSeparator++ = chSeparator;
  184. }
  185. //
  186. // If this is an unknown command, reply accordingly.
  187. //
  188. if( pcmd == NULL )
  189. {
  190. FacIncrement( FacUnknownCommands);
  191. ReplyToUser( pUserData,
  192. REPLY_UNRECOGNIZED_COMMAND,
  193. PSZ_COMMAND_NOT_UNDERSTOOD,
  194. pszCommandText );
  195. return;
  196. }
  197. //
  198. // Retrieve the implementation routine.
  199. //
  200. pfnCmd = pcmd->Implementation;
  201. //
  202. // If this is an unimplemented command, reply accordingly.
  203. //
  204. if( pfnCmd == NULL )
  205. {
  206. ReplyToUser( pUserData,
  207. REPLY_COMMAND_NOT_IMPLEMENTED,
  208. PSZ_COMMAND_NOT_UNDERSTOOD,
  209. pcmd->CommandName );
  210. return;
  211. }
  212. //
  213. // Ensure we're in a valid state for the specified command.
  214. //
  215. if ( ( pcmd->dwUserState & pUserData->UserState) == 0) {
  216. if( pfnCmd == MainPASS ) {
  217. ReplyToUser( pUserData,
  218. REPLY_BAD_COMMAND_SEQUENCE,
  219. "Login with USER first." );
  220. } else {
  221. ReplyToUser( pUserData,
  222. REPLY_NOT_LOGGED_IN,
  223. "Please login with USER and PASS." );
  224. }
  225. return;
  226. }
  227. //
  228. // Do a quick & dirty preliminary check of the argument(s).
  229. //
  230. fValidArguments = FALSE;
  231. while( ( *pszSeparator == ' ' ) && ( *pszSeparator != '\0' ) ) {
  232. pszSeparator++;
  233. }
  234. switch( pcmd->ArgumentType ) {
  235. case ArgTypeNone :
  236. fValidArguments = ( *pszSeparator == '\0' );
  237. break;
  238. case ArgTypeOptional :
  239. fValidArguments = TRUE;
  240. break;
  241. case ArgTypeRequired :
  242. fValidArguments = ( *pszSeparator != '\0' );
  243. break;
  244. default:
  245. DBGPRINTF(( DBG_CONTEXT,
  246. "ParseCommand - invalid argtype %d\n",
  247. pcmd->ArgumentType ));
  248. DBG_ASSERT( FALSE );
  249. break;
  250. }
  251. //
  252. // check we did not get extended chars if we are configured not to allow that
  253. //
  254. if( g_fNoExtendedChars /* && !pUserDate->QueryUTF8Option() */) {
  255. LPSTR pszCh = pszSeparator;
  256. while( *pszCh ) {
  257. if( !IS_7BIT_ASCII( *pszCh++ ) ) {
  258. fValidArguments = FALSE;
  259. pszInvalidCommandText = PSZ_ILLEGAL_PARAMS;
  260. break;
  261. }
  262. }
  263. }
  264. if( fValidArguments ) {
  265. //
  266. // Invoke the implementation routine.
  267. //
  268. if( *pszSeparator == '\0' )
  269. {
  270. pszSeparator = NULL;
  271. }
  272. IF_DEBUG( PARSING )
  273. {
  274. DBGPRINTF(( DBG_CONTEXT,
  275. "invoking %s command, args = %s\n",
  276. pcmd->CommandName,
  277. _strnicmp( pcmd->CommandName, "PASS", 4 )
  278. ? pszSeparator
  279. : "{secret...}" ));
  280. }
  281. #ifdef KEEP_COMMAND_STATS
  282. EnterCriticalSection( &g_CommandStatisticsLock );
  283. //
  284. // only increment the count if we're not re-processing a command
  285. //
  286. if ( !pUserData->QueryInFakeIOCompletion() )
  287. {
  288. pcmd->UsageCount++;
  289. }
  290. LeaveCriticalSection( &g_CommandStatisticsLock );
  291. #endif // KEEP_COMMAND_STATS
  292. //
  293. // Keep track of what command is being executed, in case command processing doesn't
  294. // complete in this thread and another thread has to finish processing it
  295. // [can happen if we're in PASV mode and doing async accept on the data connection]
  296. // Only need to do this if this thread isn't handling an IO completion we generated
  297. // ourselves because a PASV socket became accept()'able - if it is, we've already
  298. // set the command.
  299. //
  300. if ( !pUserData->QueryInFakeIOCompletion() )
  301. {
  302. if ( !pUserData->SetCommand( pszCommandText ) )
  303. {
  304. ReplyToUser( pUserData,
  305. REPLY_LOCAL_ERROR,
  306. "Failed to allocate necessary memory.");
  307. }
  308. }
  309. fReturn = (pfnCmd)( pUserData, pszSeparator );
  310. if ( !fReturn) {
  311. //
  312. // Invalid number of arguments. Inform the client.
  313. //
  314. ReplyToUser(pUserData,
  315. REPLY_UNRECOGNIZED_COMMAND,
  316. PSZ_COMMAND_NOT_UNDERSTOOD,
  317. pszCommandText);
  318. }
  319. } else {
  320. // Invalid # of arguments
  321. ReplyToUser(pUserData,
  322. REPLY_UNRECOGNIZED_COMMAND,
  323. pszInvalidCommandText,
  324. pszCommandText);
  325. }
  326. return;
  327. } // ParseCommand()
  328. /*******************************************************************
  329. NAME: MainSITE
  330. SYNOPSIS: Implementation for the SITE command.
  331. ENTRY: pUserData - The user initiating the request.
  332. pszArg - Command arguments. Will be NULL if no
  333. arguments given.
  334. RETURNS: BOOL - TRUE if arguments OK, FALSE if syntax error.
  335. HISTORY:
  336. KeithMo 09-Mar-1993 Created.
  337. ********************************************************************/
  338. BOOL
  339. MainSITE(
  340. LPUSER_DATA pUserData,
  341. LPSTR pszArg
  342. )
  343. {
  344. LPFTPD_COMMAND pcmd;
  345. LPFN_COMMAND pfnCmd;
  346. LPSTR pszSeparator;
  347. CHAR chSeparator;
  348. BOOL fValidArguments;
  349. CHAR szParsedCommand[MAX_COMMAND_LENGTH+1];
  350. DBG_ASSERT( pUserData != NULL );
  351. //
  352. // If no arguments were given, just return the help text.
  353. //
  354. if( pszArg == NULL )
  355. {
  356. SiteHELP( pUserData, NULL );
  357. return TRUE;
  358. }
  359. //
  360. // Save a copy of the command so we can muck around with it.
  361. //
  362. P_strncpy( szParsedCommand, pszArg, MAX_COMMAND_LENGTH );
  363. //
  364. // The command will be terminated by either a space or a '\0'.
  365. //
  366. pszSeparator = strchr( szParsedCommand, ' ' );
  367. if( pszSeparator == NULL )
  368. {
  369. pszSeparator = szParsedCommand + strlen( szParsedCommand );
  370. }
  371. //
  372. // Try to find the command in the command table.
  373. //
  374. chSeparator = *pszSeparator;
  375. *pszSeparator = '\0';
  376. pcmd = FindCommandByName( szParsedCommand,
  377. SiteCommands,
  378. NUM_SITE_COMMANDS );
  379. if( chSeparator != '\0' )
  380. {
  381. *pszSeparator++ = chSeparator;
  382. }
  383. //
  384. // If this is an unknown command, reply accordingly.
  385. //
  386. if( pcmd == NULL ) {
  387. //
  388. // Syntax error in command.
  389. //
  390. ReplyToUser( pUserData,
  391. REPLY_UNRECOGNIZED_COMMAND,
  392. "'SITE %s': command not understood",
  393. pszArg );
  394. return (TRUE);
  395. }
  396. //
  397. // Retrieve the implementation routine.
  398. //
  399. pfnCmd = pcmd->Implementation;
  400. //
  401. // If this is an unimplemented command, reply accordingly.
  402. //
  403. if( pfnCmd == NULL )
  404. {
  405. ReplyToUser( pUserData,
  406. REPLY_COMMAND_NOT_IMPLEMENTED,
  407. "SITE %s command not implemented.",
  408. pcmd->CommandName );
  409. return TRUE;
  410. }
  411. //
  412. // Ensure we're in a valid state for the specified command.
  413. //
  414. if ( ( pcmd->dwUserState & pUserData->UserState) == 0) {
  415. ReplyToUser( pUserData,
  416. REPLY_NOT_LOGGED_IN,
  417. "Please login with USER and PASS." );
  418. return (FALSE);
  419. }
  420. //
  421. // Do a quick & dirty preliminary check of the argument(s).
  422. //
  423. fValidArguments = FALSE;
  424. while( ( *pszSeparator == ' ' ) && ( *pszSeparator != '\0' ) )
  425. {
  426. pszSeparator++;
  427. }
  428. switch( pcmd->ArgumentType ) {
  429. case ArgTypeNone:
  430. fValidArguments = ( *pszSeparator == '\0' );
  431. break;
  432. case ArgTypeOptional:
  433. fValidArguments = TRUE;
  434. break;
  435. case ArgTypeRequired:
  436. fValidArguments = ( *pszSeparator != '\0' );
  437. break;
  438. default:
  439. DBGPRINTF(( DBG_CONTEXT,
  440. "MainSite - invalid argtype %d\n",
  441. pcmd->ArgumentType ));
  442. DBG_ASSERT( FALSE );
  443. break;
  444. }
  445. if( fValidArguments ) {
  446. //
  447. // Invoke the implementation routine.
  448. //
  449. if( *pszSeparator == '\0' )
  450. {
  451. pszSeparator = NULL;
  452. }
  453. IF_DEBUG( PARSING )
  454. {
  455. DBGPRINTF(( DBG_CONTEXT,
  456. "invoking SITE %s command, args = %s\n",
  457. pcmd->CommandName,
  458. pszSeparator ));
  459. }
  460. if( (pfnCmd)( pUserData, pszSeparator ) )
  461. {
  462. return TRUE;
  463. }
  464. } else {
  465. // Invalid # of arguments
  466. ReplyToUser(pUserData,
  467. REPLY_UNRECOGNIZED_COMMAND,
  468. PSZ_INVALID_PARAMS_TO_COMMAND,
  469. pszArg);
  470. }
  471. return TRUE;
  472. } // MainSITE()
  473. /*******************************************************************
  474. NAME: MainHELP
  475. SYNOPSIS: Implementation for the HELP command.
  476. ENTRY: pUserData - The user initiating the request.
  477. pszArg - Command arguments. Will be NULL if no
  478. arguments given.
  479. RETURNS: BOOL - TRUE if arguments OK, FALSE if syntax error.
  480. HISTORY:
  481. KeithMo 09-Mar-1993 Created.
  482. ********************************************************************/
  483. BOOL
  484. MainHELP(
  485. LPUSER_DATA pUserData,
  486. LPSTR pszArg
  487. )
  488. {
  489. DBG_ASSERT( pUserData != NULL );
  490. HelpWorker(pUserData,
  491. "",
  492. pszArg,
  493. MainCommands,
  494. NUM_MAIN_COMMANDS,
  495. 4 );
  496. return TRUE;
  497. } // MainHELP
  498. /*******************************************************************
  499. NAME: SiteHELP
  500. SYNOPSIS: Implementation for the site-specific HELP command.
  501. ENTRY: pUserData - The user initiating the request.
  502. pszArg - Command arguments. Will be NULL if no
  503. arguments given.
  504. RETURNS: BOOL - TRUE if arguments OK, FALSE if syntax error.
  505. HISTORY:
  506. KeithMo 09-May-1993 Created.
  507. ********************************************************************/
  508. BOOL
  509. SiteHELP(
  510. LPUSER_DATA pUserData,
  511. LPSTR pszArg
  512. )
  513. {
  514. DBG_ASSERT( pUserData != NULL );
  515. HelpWorker(pUserData,
  516. "SITE ",
  517. pszArg,
  518. SiteCommands,
  519. NUM_SITE_COMMANDS,
  520. 8 );
  521. return TRUE;
  522. } // SiteHELP
  523. #ifdef KEEP_COMMAND_STATS
  524. /*******************************************************************
  525. NAME: SiteSTATS
  526. SYNOPSIS: Implementation for the site-specific STATS command.
  527. ENTRY: pUserData - The user initiating the request.
  528. pszArg - Command arguments. Will be NULL if no
  529. arguments given.
  530. RETURNS: BOOL - TRUE if arguments OK, FALSE if syntax error.
  531. HISTORY:
  532. KeithMo 26-Sep-1994 Created.
  533. ********************************************************************/
  534. BOOL
  535. SiteSTATS(
  536. LPUSER_DATA pUserData,
  537. LPSTR pszArg
  538. )
  539. {
  540. SOCKET ControlSocket;
  541. LPFTPD_COMMAND pCmd;
  542. INT i;
  543. CHAR rgchUsageStats[NUM_MAIN_COMMANDS * 25]; // 25 chars per command
  544. pCmd = MainCommands;
  545. DBG_ASSERT( NUM_MAIN_COMMANDS > 0); // we know this very well!
  546. // Print the stats for first command.
  547. EnterCriticalSection( &g_CommandStatisticsLock );
  548. // Find first non-zero entry.
  549. for( i = 0; i < NUM_MAIN_COMMANDS && pCmd->UsageCount <= 0; i++, pCmd++)
  550. ;
  551. if ( i < NUM_MAIN_COMMANDS) {
  552. // There is some non-zero entry.
  553. CHAR * pszStats = rgchUsageStats;
  554. DWORD cch = 0;
  555. // print the stats for first command
  556. cch = wsprintfA( pszStats + cch, "%u-%-4s : %lu\r\n",
  557. REPLY_COMMAND_OK,
  558. pCmd->CommandName,
  559. pCmd->UsageCount);
  560. for( i++, pCmd++ ; i < NUM_MAIN_COMMANDS ; i++, pCmd++) {
  561. if( pCmd->UsageCount > 0 ) {
  562. cch += wsprintfA( pszStats + cch,
  563. " %-4s : %lu\r\n",
  564. pCmd->CommandName,
  565. pCmd->UsageCount );
  566. DBG_ASSERT( cch < NUM_MAIN_COMMANDS * 25);
  567. }
  568. } // for
  569. // Ignoring the error code here! probably socket closed
  570. SockSend( pUserData, pUserData->QueryControlSocket(),
  571. rgchUsageStats, cch);
  572. }
  573. LeaveCriticalSection( &g_CommandStatisticsLock );
  574. #ifdef FTP_AUX_COUNTERS
  575. CHAR * pszStats = rgchUsageStats;
  576. DWORD cch = 0;
  577. // print the stats for first counter
  578. cch = wsprintfA( pszStats + cch, "%u-Aux[%d] : %lu\r\n",
  579. REPLY_COMMAND_OK,
  580. 0,
  581. FacCounter(0));
  582. for( i = 1; i < NUM_AUX_COUNTERS; i++) {
  583. cch += wsprintfA( pszStats + cch,
  584. " Aux[%d] : %lu\r\n",
  585. i,
  586. FacCounter(i));
  587. DBG_ASSERT( cch < NUM_MAIN_COMMANDS * 25);
  588. }
  589. if ( cch > 0) {
  590. SockSend( pUserData, pUserData->QueryControlSocket(),
  591. rgchUsageStats, cch);
  592. }
  593. # endif // FTP_AUX_COUNTERS
  594. ReplyToUser( pUserData,
  595. REPLY_COMMAND_OK,
  596. "End of stats." );
  597. return TRUE;
  598. } // SiteSTATS
  599. #endif KEEP_COMMAND_STATS
  600. /*******************************************************************
  601. NAME: FindCommandByName
  602. SYNOPSIS: Searches the command table for a command with this
  603. specified name.
  604. ENTRY: pszCommandName - The name of the command to find.
  605. pCommandTable - An array of FTPD_COMMANDs detailing
  606. the available commands.
  607. cCommands - The number of commands in pCommandTable.
  608. RETURNS: LPFTPD_COMMAND - Points to the command entry for
  609. the named command. Will be NULL if command
  610. not found.
  611. HISTORY:
  612. KeithMo 10-Mar-1993 Created.
  613. MuraliK 28-Mar-1995 Completely rewrote to support binary search.
  614. ********************************************************************/
  615. LPFTPD_COMMAND
  616. FindCommandByName(
  617. LPSTR pszCommandName,
  618. LPFTPD_COMMAND pCommandTable,
  619. INT cCommands
  620. )
  621. {
  622. int iLower = 0;
  623. int iHigher = cCommands - 1; // store the indexes
  624. LPFTPD_COMMAND pCommandFound = NULL;
  625. DBG_ASSERT( pszCommandName != NULL );
  626. DBG_ASSERT( pCommandTable != NULL );
  627. DBG_ASSERT( cCommands > 0 );
  628. //
  629. // Search for the command in our table.
  630. //
  631. _strupr( pszCommandName );
  632. while ( iLower <= iHigher) {
  633. int iMid = ( iHigher + iLower) / 2;
  634. int comp = strcmp( pszCommandName, pCommandTable[ iMid].CommandName);
  635. if ( comp == 0) {
  636. pCommandFound = ( pCommandTable + iMid);
  637. break;
  638. } else if ( comp < 0) {
  639. // reset the higher bound
  640. iHigher = iMid - 1;
  641. } else {
  642. // reset the lower bound
  643. iLower = iMid + 1;
  644. }
  645. }
  646. return ( pCommandFound);
  647. } // FindCommandByName()
  648. /*******************************************************************
  649. NAME: HelpWorker
  650. SYNOPSIS: Worker function for HELP & site-specific HELP commands.
  651. ENTRY: pUserData - The user initiating the request.
  652. pszSource - The source of these commands.
  653. pszCommand - The command to get help for. If NULL,
  654. then send a list of available commands.
  655. pCommandTable - An array of FTPD_COMMANDs, one for
  656. each available command.
  657. cCommands - The number of commands in pCommandTable.
  658. cchMaxCmd - Length of the maximum command.
  659. HISTORY:
  660. KeithMo 06-May-1993 Created.
  661. Muralik 08-May-1995 Added Buffering for performance.
  662. ********************************************************************/
  663. VOID
  664. HelpWorker(
  665. LPUSER_DATA pUserData,
  666. LPSTR pszSource,
  667. LPSTR pszCommand,
  668. LPFTPD_COMMAND pCommandTable,
  669. INT cCommands,
  670. INT cchMaxCmd
  671. )
  672. {
  673. LPFTPD_COMMAND pcmd;
  674. DWORD dwError;
  675. //
  676. // We should cache the following message and use the cached message for
  677. // sending purposes ==> improves performance.
  678. // MuraliK NYI
  679. //
  680. DBG_ASSERT( pUserData != NULL );
  681. DBG_ASSERT( pCommandTable != NULL );
  682. DBG_ASSERT( cCommands > 0 );
  683. if( pszCommand == NULL ) {
  684. DWORD cch;
  685. LS_BUFFER lsb;
  686. CHAR szTmp[MAX_HELP_LINE_SIZE];
  687. if ((dwError = lsb.AllocateBuffer(HelpMsgSize(cCommands)))!= NO_ERROR){
  688. IF_DEBUG( ERROR) {
  689. DBGPRINTF(( DBG_CONTEXT,
  690. "Buffer Allocation ( %d bytes) failed.\n",
  691. HelpMsgSize(cCommands)));
  692. }
  693. ReplyToUser(pUserData,
  694. REPLY_HELP_MESSAGE,
  695. "HELP command failed." );
  696. return;
  697. }
  698. cch = wsprintfA( lsb.QueryAppendPtr(),
  699. "%u-The following %s commands are recognized"
  700. "(* ==>'s unimplemented).\r\n",
  701. REPLY_HELP_MESSAGE,
  702. pszSource);
  703. lsb.IncrementCB( cch * sizeof( CHAR));
  704. for( pcmd = pCommandTable; pcmd < pCommandTable + cCommands; pcmd++) {
  705. cch = sprintf( szTmp,
  706. " %-*s%c\r\n",
  707. cchMaxCmd,
  708. pcmd->CommandName,
  709. pcmd->Implementation == NULL ? '*' : ' ' );
  710. DBG_ASSERT( cch*sizeof(CHAR) < sizeof(szTmp));
  711. //
  712. // since we calculate and preallocate the buffer, we dont
  713. // need to worry about the overflow of the buffer.
  714. //
  715. DBG_ASSERT( cch*sizeof(CHAR) < lsb.QueryRemainingCB());
  716. if ( cch * sizeof(CHAR) >= lsb.QueryRemainingCB()) {
  717. // This is added for retail code where ASSERT may fail.
  718. dwError = ERROR_NOT_ENOUGH_MEMORY;
  719. break;
  720. }
  721. strncpy( lsb.QueryAppendPtr(), szTmp, cch);
  722. lsb.IncrementCB( cch*sizeof(CHAR));
  723. } // for ( all commands)
  724. if ( dwError == NO_ERROR) {
  725. // Append the ending sequence for success in generating HELP.
  726. cch = sprintf( szTmp,
  727. "%u %s\r\n",
  728. REPLY_HELP_MESSAGE,
  729. "HELP command successful." );
  730. if ( cch*sizeof(CHAR) >= lsb.QueryRemainingCB()) {
  731. dwError = ERROR_NOT_ENOUGH_MEMORY;
  732. } else {
  733. // copy the completion message
  734. strncpy( lsb.QueryAppendPtr(), szTmp, cch);
  735. lsb.IncrementCB( cch*sizeof(CHAR));
  736. }
  737. }
  738. if ( dwError == NO_ERROR) {
  739. // Send the chunk of data
  740. dwError = SockSend( pUserData,
  741. pUserData->QueryControlSocket(),
  742. lsb.QueryBuffer(),
  743. lsb.QueryCB());
  744. } else {
  745. IF_DEBUG( ERROR) {
  746. DBGPRINTF(( DBG_CONTEXT,
  747. "Error = %u. Should Not happen though...\n",
  748. dwError));
  749. }
  750. ReplyToUser( pUserData,
  751. REPLY_HELP_MESSAGE,
  752. "HELP command failed.");
  753. }
  754. lsb.FreeBuffer();
  755. // Ignore the errors if any from propagating outside
  756. } else {
  757. pcmd = FindCommandByName(pszCommand,
  758. pCommandTable,
  759. cCommands );
  760. if( pcmd == NULL ) {
  761. ReplyToUser( pUserData,
  762. REPLY_PARAMETER_SYNTAX_ERROR,
  763. "Unknown command %s.",
  764. pszCommand );
  765. } else {
  766. ReplyToUser( pUserData,
  767. REPLY_HELP_MESSAGE,
  768. "Syntax: %s%s %s",
  769. pszSource,
  770. pcmd->CommandName,
  771. pcmd->HelpText );
  772. }
  773. }
  774. return;
  775. } // HelpWorker()
  776. /************************ End of File ***********************/
  777.