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

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