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.

3691 lines
88 KiB

  1. /**********************************************************************/
  2. /** Microsoft Windows NT **/
  3. /** Copyright(c) Microsoft Corp., 1993-1995 **/
  4. /**********************************************************************/
  5. /*
  6. engine.cxx
  7. Command parser & execution for FTPD Service. This module parses
  8. and executes the commands received from the control socket.
  9. Functions exported by this module:
  10. All function for processing FTP commands
  11. FILE HISTORY:
  12. KeithMo 07-Mar-1993 Created.
  13. MuraliK 21-March-1995 Modified to use common TsLogonUser()
  14. supporting anonymous logon.
  15. MuraliK 27-Mar - April 1995
  16. Cleaning up FTP server engine for
  17. - to support new Internet services interface.
  18. - to support Async Io Transfers
  19. - split and move the command tables to ftpcmd.cxx
  20. - to replace USER_DATA::DataSocket with
  21. USER_DATA::m_AioDataConnection
  22. - moved SiteSTATS and statistics to ftpcmd.cxx
  23. - replaced SockReply2 with ReplyToUser()
  24. - modified MainREIN and MainQUIT to use
  25. new USER_DATA functions.
  26. - modified usage of listing functions.
  27. - SendErrorToClient() used for managing and sending
  28. error message to client.
  29. - message strings removed to be top of the file and defined.
  30. - size of file sent along before file is transferred.
  31. terryk 18-Sep-1996 add MainSize
  32. */
  33. #include "ftpdp.hxx"
  34. #include "ftpcmd.hxx"
  35. #include <stdlib.h>
  36. #include <limits.h>
  37. //
  38. // Private constants.
  39. //
  40. //
  41. // Since the default RFC port for ftp is 21, we calculate the Data port
  42. // value from it.
  43. // However, an admin at a site may change the port number to something else
  44. // ==> this will create PROBLEMS..... NYI
  45. //
  46. /**************************************************
  47. * Constant Message Strings used by this Module
  48. **************************************************/
  49. //
  50. // Below is a list of strings, which are in
  51. // CStrM( StringName, ActualString) format
  52. // The following table contains globally all strings used within this module
  53. // Advantage: One place to look for various strings
  54. // + possible internationalization
  55. // Be careful. None of these messages may be modified. Protocol may break.
  56. // This will be expanded into
  57. // const char PSZ_StringName[] = ActualString; and
  58. // enumerated value LEN_StringName = sizeof( ActualString).
  59. //
  60. # define ConstantStringsForThisModule() \
  61. CStrM( CANNOT_OPEN_DATA_CONNECTION, "Can't open data connection.") \
  62. CStrM( TRANSFER_ABORTED, "Connection closed; transfer aborted.") \
  63. CStrM( USER_NOT_LOGGED_IN, "User %s cannot log in.") \
  64. CStrM( INSUFFICIENT_RESOURCES, "Insufficient system resources.") \
  65. CStrM( REQUEST_ID_FOR_ANONYMOUS, \
  66. "Anonymous access allowed, send identity (e-mail name) as password.")\
  67. CStrM( REQUEST_PASSWORD_FOR_USER, "Password required for %s.") \
  68. CStrM( COMMAND_NOT_IMPLEMENTED, "%s command not implemented." ) \
  69. CStrM( COMMAND_SUCCESSFUL, "%s command successful." ) \
  70. CStrM( SERVICE_READY, "Service ready for new user.") \
  71. CStrM( ENTERING_PASSIVE_MODE, \
  72. "Entering Passive Mode (%d,%d,%d,%d,%d,%d).") \
  73. CStrM( FORM_MESSAGE, "Form must be N or T." ) \
  74. CStrM( TYPE_NOT_IMPLEMENTED, "Type %c not implemented.") \
  75. CStrM( BYTE_SIZE_SPEC, "Byte size must be 8." ) \
  76. CStrM( TYPE_SET_TO, "Type set to %c.") \
  77. CStrM( UNIMPLEMENTED_STRU_TYPE, "Unimplemented STRU type." ) \
  78. CStrM( INVALID_STRU_TYPE, "Invalid STRU type." ) \
  79. CStrM( STRU_TYPE_OK, "STRU %c ok.") \
  80. CStrM( UNIMPLEMENTED_MODE, "Unimplemented MODE type.") \
  81. CStrM( MODE_OK, "Mode %c ok.") \
  82. CStrM( REPLY_MARKER_SPEC, "Reply marker is invalid.") \
  83. CStrM( REPLY_RESTARTING, "Restarting at %s." ) \
  84. CStrM( READY_FOR_DEST_FILE, "File exists, ready for destination name" ) \
  85. CStrM( BAD_COMMAND_SEQUENCE, "Bad sequence of commands." ) \
  86. CStrM( CURRENT_DIRECTORY, "\"%s\" is current directory.") \
  87. CStrM( VERSION_INFO, "Windows_NT") \
  88. CStrM( SERVER_STATUS_BEGIN, " %s Windows NT FTP Server status:") \
  89. CStrM( SERVER_STATUS_END, "End of status.") \
  90. CStrM( FILE_STATUS, "status of %s:") \
  91. CStrM( MSDOS_DIRSTYLE, "MSDOS-like directory output is %s") \
  92. CStrM( DIRECTORY_ANNOTATION, "directory annotation is %s") \
  93. CStrM( LOGGED_IN_USER_MESSAGE, "Anonymous user logged in%s.") \
  94. CStrM( USER_LOGGED_IN, "User %s logged in%s.") \
  95. CStrM( USER_CANNOT_LOG_IN, "User %s cannot log in.") \
  96. CStrM( INACCESSIBLE_HOME_DIR, \
  97. "User %s cannot log in, home directory inaccessible.") \
  98. CStrM( LICENSE_QUOTA_EXCEEDED, \
  99. "User %s cannot log in, license quota exceeded.") \
  100. CStrM( NO_GUEST_ACCESS, \
  101. "User %s cannot log in, guest access not allowed.") \
  102. CStrM( ANONYMOUS_NAME, "Anonymous") \
  103. CStrM( FTP_NAME, "Ftp") \
  104. CStrM( ARGS_DELIMITER, " \t") \
  105. CStrM( NO_FILE_OR_DIRECTORY, "No such file or directory.") \
  106. CStrM( DIRECTORY_CREATE, "\"%s\" directory created.") \
  107. CStrM( CANNOT_CREATE_FILE, "Cannot create file.") \
  108. CStrM( CANNOT_CREATE_UNIQUE_FILE, "Cannot create unique file.") \
  109. CStrM( INVALID_COMMAND, "Invalid %s Command.") \
  110. CStrM( RESPONSE_ON, "on") \
  111. CStrM( RESPONSE_OFF, "off") \
  112. CStrM( GUEST_ACCESS, " (guest access)" ) \
  113. CStrM( CREATE_VERB, "created" ) \
  114. CStrM( APPEND_VERB, "appended" ) \
  115. CStrM( USER_VERB, "USER" ) \
  116. CStrM( PASSWORD_VERB, "PASS" ) \
  117. CStrM( QUIT_VERB, "QUIT" ) \
  118. CStrM( ABORT_VERB, "ABORT" ) \
  119. CStrM( REIN_VERB, "REIN" ) \
  120. CStrM( DESTINATION_FILE_EXISTS, "Destination file already exists.") \
  121. CStrM( RNFR_VERB, "RNFR" ) \
  122. CStrM( RNTO_VERB, "RNTO" ) \
  123. CStrM( DELE_VERB, "DELE" ) \
  124. CStrM( RMD_VERB, "RMD" ) \
  125. CStrM( MKD_VERB, "MKD" ) \
  126. CStrM( DUMMY_END, "DummyMsg. Add string before this one")
  127. //
  128. // Generate the strings ( these are private globals of this module).
  129. //
  130. # define CStrM( StringName, ActualString) \
  131. const char PSZ_ ## StringName[] = ActualString ;
  132. ConstantStringsForThisModule()
  133. # undef CStrM
  134. //
  135. // Private prototypes.
  136. //
  137. BOOL
  138. ParseStringIntoAddress(
  139. LPSTR pszString,
  140. LPIN_ADDR pinetAddr,
  141. LPPORT pport
  142. );
  143. DWORD
  144. ReceiveFileFromUserAndClose(
  145. LPUSER_DATA pUserData,
  146. LPSTR pszFileName,
  147. LPHANDLE phFile
  148. );
  149. BOOL
  150. MyLogonUser(
  151. LPUSER_DATA pUserData,
  152. LPSTR pszPassword,
  153. LPBOOL pfAsGuest,
  154. LPBOOL pfHomeDirFailure,
  155. LPBOOL pfLicenseExceeded
  156. );
  157. DWORD
  158. DetermineUserAccess(FTP_SERVER_INSTANCE *pInstance);
  159. //
  160. // Public functions.
  161. //
  162. //
  163. // Functions Implementing FTP functionality.
  164. //
  165. BOOL
  166. MainUSER(
  167. LPUSER_DATA pUserData,
  168. LPSTR pszArg
  169. )
  170. /*++
  171. Implements the USER command.
  172. Format: USER <userName>
  173. Arguments:
  174. pUserData - the user initiating the request.
  175. pszArg - Command arguments. Will be NULL if no arguments given.
  176. Returns:
  177. BOOL - TRUE if arguments are OK; FALSE if syntax error.
  178. --*/
  179. {
  180. DBG_ASSERT( pUserData != NULL );
  181. LPFTP_SERVER_STATISTICS pStatsObj = pUserData->QueryInstance()->QueryStatsObj();
  182. DBG_ASSERT( pStatsObj != NULL );
  183. if( pUserData->IsLoggedOn()) {
  184. if( TEST_UF( pUserData, ANONYMOUS ) ) {
  185. pStatsObj->DecrCurrentAnonymousUsers();
  186. } else {
  187. pStatsObj->DecrCurrentNonAnonymousUsers();
  188. }
  189. SET_UF( pUserData, LOGGED_ON);
  190. }
  191. //
  192. // Squirrel away a copy of the domain\user name for later.
  193. // If the name is too long, then don't let them logon.
  194. //
  195. if ( strlen( pszArg ) >= ( MAX_USERNAME_LENGTH ) ) {
  196. ReplyToUser(pUserData,
  197. REPLY_NOT_LOGGED_IN,
  198. PSZ_USER_NOT_LOGGED_IN,
  199. pszArg );
  200. } else {
  201. BOOL fNameIsAnonymous;
  202. LPCSTR pszReply;
  203. fNameIsAnonymous = ( ( _stricmp( pszArg, PSZ_ANONYMOUS_NAME ) == 0 ) ||
  204. ( _stricmp( pszArg, PSZ_FTP_NAME ) == 0 )
  205. );
  206. pUserData->SetUserName( pszArg );
  207. if( fNameIsAnonymous ) {
  208. SET_UF( pUserData, ANONYMOUS );
  209. } else {
  210. CLEAR_UF( pUserData, ANONYMOUS );
  211. }
  212. //
  213. // remember that we're waiting for PASS command in case we
  214. // get disconnected.
  215. //
  216. SET_UF( pUserData, WAIT_PASS );
  217. //
  218. // If we already have an impersonation token, then remove it.
  219. // This will allow us to impersonate the new user.
  220. //
  221. pUserData->FreeUserToken();
  222. //
  223. // Tell the client that we need a password.
  224. //
  225. pszReply =(((fNameIsAnonymous) && pUserData->QueryInstance()->AllowAnonymous())
  226. ? PSZ_REQUEST_ID_FOR_ANONYMOUS
  227. : PSZ_REQUEST_PASSWORD_FOR_USER);
  228. ReplyToUser( pUserData,
  229. REPLY_NEED_PASSWORD,
  230. pszReply,
  231. pszArg);
  232. pUserData->LockUser();
  233. if( pUserData->QueryState() != UserStateDisconnected ) {
  234. pUserData->SetState( UserStateWaitingForPass);
  235. }
  236. pUserData->UnlockUser();
  237. }
  238. pUserData->WriteLogRecord( PSZ_USER_VERB, pszArg);
  239. return TRUE;
  240. } // MainUSER()
  241. BOOL
  242. MainPASS(
  243. LPUSER_DATA pUserData,
  244. LPSTR pszArg
  245. )
  246. /*++
  247. Implements the PASS command.
  248. Format: PASS <password>
  249. Arguments:
  250. pUserData - the user initiating the request.
  251. pszArg - command arguments. Will be NULL if no arguments present.
  252. Returns:
  253. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  254. --*/
  255. {
  256. DWORD dwError = NO_ERROR;
  257. DBG_ASSERT( pUserData != NULL );
  258. //
  259. // PASS command only valid in WaitingForPass state.
  260. //
  261. DBG_ASSERT( pUserData->QueryState() == UserStateWaitingForPass );
  262. if( ( pszArg != NULL ) && ( strlen( pszArg ) > PWLEN ) ) {
  263. return FALSE;
  264. }
  265. //
  266. // Try to logon the user.
  267. //
  268. BOOL fAsGuest;
  269. BOOL fHomeDirFailure;
  270. BOOL fLicenseExceeded;
  271. DBG_ASSERT( pUserData->QueryUserToken() == NULL );
  272. //
  273. // we got the PASS command we we're waiting for
  274. //
  275. CLEAR_UF( pUserData, WAIT_PASS );
  276. pUserData->QueryInstance()->QueryStatsObj()->IncrLogonAttempts();
  277. if( MyLogonUser(pUserData,
  278. pszArg,
  279. &fAsGuest,
  280. &fHomeDirFailure,
  281. &fLicenseExceeded )
  282. ) {
  283. const CHAR * pszGuestAccess =
  284. ( ( fAsGuest) ? PSZ_GUEST_ACCESS : "");
  285. //
  286. // Successful logon.
  287. //
  288. if( *pUserData->QueryUserName() != '-' ) {
  289. PCSTR pszMsg;
  290. pUserData->QueryInstance()->LockConfig();
  291. pszMsg = pUserData->QueryInstance()->QueryGreetingMsg();
  292. if( pszMsg && *pszMsg ) {
  293. pUserData->SendMultilineMessage(
  294. REPLY_USER_LOGGED_IN,
  295. pszMsg,
  296. TRUE, // first reply line
  297. FALSE); // lst reply line
  298. }
  299. pUserData->QueryInstance()->UnLockConfig();
  300. if( TEST_UF( pUserData, ANNOTATE_DIRS )) {
  301. pUserData->SendDirectoryAnnotation(
  302. REPLY_USER_LOGGED_IN,
  303. FALSE); // first reply line
  304. }
  305. }
  306. LPFTP_SERVER_STATISTICS pStats = pUserData->QueryInstance()->QueryStatsObj();
  307. DBG_ASSERT( pStats != NULL );
  308. if( TEST_UF( pUserData, ANONYMOUS ) ) {
  309. pStats->IncrAnonymousUsers();
  310. ReplyToUser(pUserData,
  311. REPLY_USER_LOGGED_IN,
  312. PSZ_LOGGED_IN_USER_MESSAGE,
  313. pszGuestAccess );
  314. } else {
  315. pStats->IncrNonAnonymousUsers();
  316. ReplyToUser(pUserData,
  317. REPLY_USER_LOGGED_IN,
  318. PSZ_USER_LOGGED_IN,
  319. pUserData->QueryUserName(),
  320. pszGuestAccess );
  321. }
  322. pUserData->LockUser();
  323. if( pUserData->QueryState() != UserStateDisconnected ) {
  324. pUserData->SetState( UserStateLoggedOn);
  325. SET_UF( pUserData, LOGGED_ON);
  326. }
  327. pUserData->UnlockUser();
  328. } else {
  329. const CHAR * pszReply = PSZ_USER_CANNOT_LOG_IN;
  330. //
  331. // Logon failure.
  332. //
  333. dwError = GetLastError();
  334. if( fHomeDirFailure ) {
  335. pszReply = PSZ_INACCESSIBLE_HOME_DIR;
  336. } else if ( fLicenseExceeded) {
  337. pszReply = PSZ_LICENSE_QUOTA_EXCEEDED;
  338. } else if ( fAsGuest && ! pUserData->QueryInstance()->AllowGuestAccess()) {
  339. pszReply = PSZ_NO_GUEST_ACCESS;
  340. }
  341. ReplyToUser(pUserData,
  342. REPLY_NOT_LOGGED_IN,
  343. pszReply,
  344. pUserData->QueryUserName() );
  345. pUserData->LockUser();
  346. if( pUserData->QueryState() != UserStateDisconnected ) {
  347. pUserData->SetState( UserStateWaitingForUser);
  348. CLEAR_UF( pUserData, LOGGED_ON);
  349. pUserData->ClearUserName();
  350. }
  351. pUserData->UnlockUser();
  352. }
  353. pUserData->WriteLogRecord( PSZ_PASSWORD_VERB,
  354. (TEST_UF( pUserData, ANONYMOUS)) ? pszArg : NULL,
  355. dwError);
  356. return TRUE;
  357. } // MainPASS()
  358. BOOL
  359. MainACCT(
  360. LPUSER_DATA pUserData,
  361. LPSTR pszArg
  362. )
  363. /*++
  364. This function implements the ACCT command.
  365. This is at present not implemented...
  366. Arguments:
  367. pUserData - the user initiating the request.
  368. pszArg - command arguments. Will be NULL if no arguments present.
  369. Returns:
  370. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  371. --*/
  372. {
  373. DBG_ASSERT( pUserData != NULL );
  374. ReplyToUser(pUserData,
  375. REPLY_COMMAND_SUPERFLUOUS,
  376. PSZ_COMMAND_NOT_IMPLEMENTED,
  377. "ACCT");
  378. return TRUE;
  379. } // MainACCT()
  380. BOOL
  381. MainCWD(
  382. LPUSER_DATA pUserData,
  383. LPSTR pszArg
  384. )
  385. /*++
  386. This function implements the CWD command -- Change Working Directory.
  387. Format: CWD <newDirectoryName>
  388. Arguments:
  389. pUserData - the user initiating the request.
  390. pszArg - command arguments. Will be NULL if no arguments present.
  391. Returns:
  392. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  393. --*/
  394. {
  395. APIERR err;
  396. DBG_ASSERT( pUserData != NULL );
  397. //
  398. // Ensure user is logged on properly.
  399. //
  400. DBG_ASSERT( pUserData->IsLoggedOn());
  401. //
  402. // If argument is NULL or "~", CD to home directory.
  403. //
  404. if( ( pszArg == NULL ) || ( strcmp( pszArg, "~" ) == 0 ) ) {
  405. err = pUserData->CdToUsersHomeDirectory( PSZ_ANONYMOUS_NAME);
  406. } else {
  407. err = VirtualChDir( pUserData, pszArg);
  408. }
  409. if( err == NO_ERROR ) {
  410. if( TEST_UF( pUserData, ANNOTATE_DIRS ) &&
  411. ( *pUserData->QueryUserName() != '-' )
  412. ) {
  413. pUserData->SendDirectoryAnnotation(
  414. REPLY_FILE_ACTION_COMPLETED,
  415. TRUE); // first reply line
  416. }
  417. ReplyToUser(pUserData,
  418. REPLY_FILE_ACTION_COMPLETED,
  419. PSZ_COMMAND_SUCCESSFUL,
  420. "CWD");
  421. } else {
  422. pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
  423. }
  424. return TRUE;
  425. } // MainCWD()
  426. BOOL
  427. MainCDUP(
  428. LPUSER_DATA pUserData,
  429. LPSTR pszArg
  430. )
  431. /*++
  432. CDUP -- changes to the parent directory if possible.
  433. --*/
  434. {
  435. return MainCWD( pUserData, ".." );
  436. } // MainCDUP
  437. BOOL
  438. MainSIZE(
  439. LPUSER_DATA pUserData,
  440. LPSTR pszArg
  441. )
  442. /*++
  443. This function implements SIZE command - used for retrieving the size of a file.
  444. Format: SIZE pathForFile
  445. Arguments:
  446. pUserData - the user initiating the request.
  447. pszArg - command arguments. Will be NULL if no arguments present.
  448. Returns:
  449. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  450. --*/
  451. {
  452. APIERR err = (!NO_ERROR);
  453. DBG_ASSERT( pUserData != NULL );
  454. //
  455. // Ensure user is logged on properly.
  456. //
  457. DBG_ASSERT( pUserData->IsLoggedOn() );
  458. //
  459. // Sanity check the parameters.
  460. //
  461. DBG_ASSERT( pszArg != NULL );
  462. //
  463. // Try to open the file.
  464. //
  465. if (pUserData->ImpersonateUser()){
  466. err = pUserData->OpenFileForSend( pszArg );
  467. pUserData->RevertToSelf();
  468. }
  469. else
  470. {
  471. err = GetLastError();
  472. }
  473. if( err == NO_ERROR ) {
  474. // just return the file size
  475. err = pUserData->GetFileSize();
  476. }
  477. if( err != NO_ERROR ) {
  478. pUserData->SendErrorToClient(pszArg, err,
  479. PSZ_NO_FILE_OR_DIRECTORY);
  480. pUserData->WriteLogRecordForSendError( err );
  481. }
  482. pUserData->CloseFileForSend( err); // close the file, now that we're done.
  483. return TRUE;
  484. } // MainSIZE()
  485. BOOL
  486. MainMDTM(
  487. LPUSER_DATA pUserData,
  488. LPSTR pszArg
  489. )
  490. /*++
  491. This function implements the MDTM command - used for retrieving the last
  492. modified time for a file. We open the file, get the file mod time, format
  493. it and send it.
  494. Format: SIZE pathForFile
  495. Arguments:
  496. pUserData - the user initiating the request.
  497. pszArg - command arguments. Will be NULL if no arguments present.
  498. Returns:
  499. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  500. --*/
  501. {
  502. APIERR err = (!NO_ERROR);
  503. SYSTEMTIME SystemTime;
  504. CHAR rgchBuffer[sizeof("YYYYMMDDHHMMSS")];
  505. DBG_ASSERT( pUserData != NULL );
  506. //
  507. // Ensure user is logged on properly.
  508. //
  509. DBG_ASSERT( pUserData->IsLoggedOn() );
  510. //
  511. // Sanity check the parameters.
  512. //
  513. DBG_ASSERT( pszArg != NULL );
  514. //
  515. // Try to open the file.
  516. //
  517. if (pUserData->ImpersonateUser()){
  518. err = pUserData->OpenFileForSend( pszArg );
  519. pUserData->RevertToSelf();
  520. }
  521. else
  522. {
  523. err = GetLastError();
  524. }
  525. if( err == NO_ERROR ) {
  526. // Get the last write time.
  527. err = pUserData->GetFileModTime(&SystemTime);
  528. if (err == NO_ERROR) {
  529. // Format the time.
  530. wsprintfA(rgchBuffer, "%.4hu%.2hu%.2hu%.2hu%.2hu%.2hu",
  531. SystemTime.wYear,
  532. SystemTime.wMonth,
  533. SystemTime.wDay,
  534. SystemTime.wHour,
  535. SystemTime.wMinute,
  536. SystemTime.wSecond );
  537. ReplyToUser( pUserData, REPLY_FILE_STATUS, rgchBuffer );
  538. }
  539. }
  540. if( err != NO_ERROR ) {
  541. pUserData->SendErrorToClient(pszArg, err,
  542. PSZ_NO_FILE_OR_DIRECTORY);
  543. pUserData->WriteLogRecordForSendError( err );
  544. }
  545. pUserData->CloseFileForSend( err); // close the file, now that we're done.
  546. return TRUE;
  547. } // MainMDTM()
  548. BOOL
  549. MainSMNT(
  550. LPUSER_DATA pUserData,
  551. LPSTR pszArg
  552. )
  553. /*++
  554. This function implements the SMNT command.
  555. This is at present not implemented...
  556. Arguments:
  557. pUserData - the user initiating the request.
  558. pszArg - command arguments. Will be NULL if no arguments present.
  559. Returns:
  560. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  561. --*/
  562. {
  563. DBG_ASSERT( pUserData != NULL );
  564. ReplyToUser(pUserData,
  565. REPLY_COMMAND_SUPERFLUOUS,
  566. PSZ_COMMAND_NOT_IMPLEMENTED,
  567. "SMNT");
  568. return TRUE;
  569. } // MainSMNT()
  570. BOOL
  571. MainQUIT(
  572. LPUSER_DATA pUserData,
  573. LPSTR pszArg
  574. )
  575. /*++
  576. This function implements the QUIT command.
  577. Format: QUIT
  578. Arguments:
  579. pUserData - the user initiating the request.
  580. pszArg - command arguments. Will be NULL if no arguments present.
  581. Returns:
  582. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  583. --*/
  584. {
  585. CHAR rgchBuffer[MAX_REPLY_LENGTH];
  586. DWORD len;
  587. LPCSTR pszMsg;
  588. SOCKERR serr;
  589. DBG_ASSERT( pUserData != NULL );
  590. SET_UF( pUserData, CONTROL_QUIT);
  591. //
  592. // Reply to the quit command.
  593. //
  594. pUserData->QueryInstance()->LockConfig();
  595. pszMsg = pUserData->QueryInstance()->QueryExitMsg();
  596. DBG_ASSERT( pszMsg != NULL);
  597. len = FtpFormatResponseMessage(REPLY_CLOSING_CONTROL,
  598. pszMsg,
  599. rgchBuffer,
  600. MAX_REPLY_LENGTH);
  601. pUserData->QueryInstance()->UnLockConfig();
  602. DBG_ASSERT( len <= MAX_REPLY_LENGTH);
  603. serr = SockSend( pUserData, pUserData->QueryControlSocket(),
  604. rgchBuffer, len);
  605. //
  606. // Cause a disconnection of the user.
  607. // This will blow away the sockets first. Blowing off sockets
  608. // will cause ATQ to wake up for pending data calls
  609. // and send a call back indicating failure.
  610. // Since we disconnect now, we will not submit any
  611. // Reads to control socket ==> no more control calls come back from ATQ.
  612. // At the call back processing we will decrement reference counts
  613. // appropriate for cleanup.
  614. //
  615. pUserData->DisconnectUserWithError( NO_ERROR, FALSE);
  616. pUserData->WriteLogRecord( PSZ_QUIT_VERB, "");
  617. return TRUE;
  618. } // MainQUIT()
  619. BOOL
  620. MainREIN(
  621. LPUSER_DATA pUserData,
  622. LPSTR pszArg
  623. )
  624. /*++
  625. This function executes REIN command - ReInitialize
  626. Format: REIN
  627. Arguments:
  628. pUserData - the user initiating the request.
  629. pszArg - command arguments. Will be NULL if no arguments present.
  630. Returns:
  631. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  632. --*/
  633. {
  634. DBG_ASSERT( pUserData != NULL );
  635. pUserData->ReInitializeForNewUser();
  636. ReplyToUser(pUserData,
  637. REPLY_SERVICE_READY,
  638. PSZ_SERVICE_READY);
  639. pUserData->WriteLogRecord( PSZ_REIN_VERB, pszArg);
  640. return TRUE;
  641. } // MainREIN()
  642. BOOL
  643. MainPORT(
  644. LPUSER_DATA pUserData,
  645. LPSTR pszArg
  646. )
  647. /*++
  648. This function implements the PORT command.
  649. Format: PORT <ipAddress>,<portNumber>
  650. Arguments:
  651. pUserData - the user initiating the request.
  652. pszArg - command arguments. Will be NULL if no arguments present.
  653. Returns:
  654. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  655. --*/
  656. {
  657. IN_ADDR DataIpAddress;
  658. PORT DataPort;
  659. DBG_ASSERT( pUserData != NULL );
  660. //
  661. // Parse the string into address/port pair.
  662. //
  663. if( !ParseStringIntoAddress( pszArg,
  664. &DataIpAddress,
  665. &DataPort ) )
  666. {
  667. return FALSE;
  668. }
  669. //
  670. // Determine if someone is trying to give us a bogus address/port.
  671. // If the port number is less than IPPORT_RESERVED,
  672. // then there is a possibility of port attack. Allow this only
  673. // if port attack flag is enabled
  674. //
  675. if (!pUserData->QueryInstance()->IsEnablePortAttack())
  676. {
  677. if ( ( DataIpAddress.s_addr != pUserData->HostIpAddress.s_addr ) ||
  678. ( DataPort != CONN_PORT_TO_DATA_PORT(pUserData->LocalIpPort) &&
  679. ntohs( DataPort ) < IPPORT_RESERVED) )
  680. {
  681. ReplyToUser(pUserData,
  682. REPLY_UNRECOGNIZED_COMMAND,
  683. PSZ_INVALID_COMMAND,
  684. "PORT");
  685. return TRUE;
  686. }
  687. }
  688. //
  689. // Save the address/port pair into per-user data.
  690. //
  691. pUserData->DataIpAddress = DataIpAddress;
  692. pUserData->DataPort = DataPort;
  693. //
  694. // Disable passive mode for this user.
  695. //
  696. CLEAR_UF( pUserData, PASSIVE );
  697. //
  698. // Nuke any open data socket.
  699. //
  700. pUserData->CleanupPassiveSocket( TRUE );
  701. //
  702. // Let the client know we accepted the port command.
  703. //
  704. ReplyToUser(pUserData,
  705. REPLY_COMMAND_OK,
  706. PSZ_COMMAND_SUCCESSFUL,
  707. "PORT");
  708. return TRUE;
  709. } // MainPORT()
  710. BOOL
  711. MainPASV(
  712. LPUSER_DATA pUserData,
  713. LPSTR pszArg
  714. )
  715. /*++
  716. This function implements the PASV command - used for setting passive mode.
  717. Format: PASV
  718. Arguments:
  719. pUserData - the user initiating the request.
  720. pszArg - command arguments. Will be NULL if no arguments present.
  721. Returns:
  722. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  723. --*/
  724. {
  725. SOCKET DataSocket = INVALID_SOCKET;
  726. SOCKERR serr = 0;
  727. SOCKADDR_IN saddrLocal;
  728. INT cbLocal;
  729. DBG_ASSERT( pUserData != NULL );
  730. //
  731. // Nuke the old passive socket
  732. //
  733. pUserData->CleanupPassiveSocket( TRUE );
  734. //
  735. // Ensure user is logged on properly.
  736. //
  737. DBG_ASSERT( pUserData->IsLoggedOn() );
  738. //
  739. // Create a new data socket.
  740. //
  741. serr = CreateFtpdSocket( &DataSocket,
  742. pUserData->LocalIpAddress.s_addr,
  743. 0,
  744. pUserData->QueryInstance() );
  745. if( serr == 0 )
  746. {
  747. //
  748. // Determine the port number for the new socket.
  749. //
  750. cbLocal = sizeof(saddrLocal);
  751. if( getsockname( DataSocket, (SOCKADDR *)&saddrLocal, &cbLocal ) != 0 )
  752. {
  753. serr = WSAGetLastError();
  754. }
  755. }
  756. if( serr == 0 )
  757. {
  758. //
  759. // Success!
  760. //
  761. SET_UF( pUserData, PASSIVE );
  762. pUserData->SetPassiveSocket( DataSocket);
  763. pUserData->DataIpAddress = saddrLocal.sin_addr;
  764. pUserData->DataPort = saddrLocal.sin_port;
  765. ReplyToUser(pUserData,
  766. REPLY_PASSIVE_MODE,
  767. PSZ_ENTERING_PASSIVE_MODE,
  768. saddrLocal.sin_addr.S_un.S_un_b.s_b1,
  769. saddrLocal.sin_addr.S_un.S_un_b.s_b2,
  770. saddrLocal.sin_addr.S_un.S_un_b.s_b3,
  771. saddrLocal.sin_addr.S_un.S_un_b.s_b4,
  772. HIBYTE( ntohs( saddrLocal.sin_port ) ),
  773. LOBYTE( ntohs( saddrLocal.sin_port ) ) );
  774. } else {
  775. //
  776. // Failure during data socket creation/setup. If
  777. // we managed to actually create it, nuke it.
  778. //
  779. if( DataSocket != INVALID_SOCKET )
  780. {
  781. CloseSocket( DataSocket );
  782. DataSocket = INVALID_SOCKET;
  783. }
  784. //
  785. // Tell the user the bad news.
  786. //
  787. ReplyToUser(pUserData,
  788. REPLY_CANNOT_OPEN_CONNECTION,
  789. PSZ_CANNOT_OPEN_DATA_CONNECTION);
  790. }
  791. return TRUE;
  792. } // MainPASV()
  793. BOOL
  794. MainTYPE(
  795. LPUSER_DATA pUserData,
  796. LPSTR pszArg
  797. )
  798. /*++
  799. This function implements the TYPE command - used for setting type.
  800. Format: TYPE type form <addl arguments>
  801. Arguments:
  802. pUserData - the user initiating the request.
  803. pszArg - command arguments. Will be NULL if no arguments present.
  804. Returns:
  805. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  806. --*/
  807. {
  808. XFER_TYPE newType;
  809. CHAR chType;
  810. CHAR chForm;
  811. LPSTR pszToken;
  812. BOOL fValidForm = FALSE;
  813. DBG_ASSERT( pUserData != NULL );
  814. //
  815. // Sanity check the parameters.
  816. //
  817. DBG_ASSERT( pszArg != NULL );
  818. pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER);
  819. if( pszToken == NULL ) {
  820. return FALSE;
  821. }
  822. //
  823. // Ensure we got a valid form type
  824. // (only type N supported).
  825. //
  826. if( pszToken[1] != '\0' ) {
  827. return FALSE;
  828. }
  829. chType = *pszToken;
  830. pszToken = strtok( NULL, PSZ_ARGS_DELIMITER );
  831. if( pszToken == NULL ) {
  832. chForm = 'N'; // default
  833. fValidForm = TRUE;
  834. } else {
  835. switch( *pszToken ) {
  836. case 'n':
  837. case 'N':
  838. chForm = 'N';
  839. fValidForm = TRUE;
  840. break;
  841. case 't':
  842. case 'T':
  843. chForm = 'T';
  844. fValidForm = TRUE;
  845. break;
  846. case 'c':
  847. case 'C':
  848. chForm = 'C';
  849. fValidForm = TRUE;
  850. break;
  851. default:
  852. fValidForm = FALSE;
  853. break;
  854. } // switch
  855. }
  856. //
  857. // Determine the new transfer type.
  858. //
  859. switch( chType ) {
  860. case 'a':
  861. case 'A':
  862. if( !fValidForm ) {
  863. return FALSE;
  864. }
  865. if( ( chForm != 'N' ) && ( chForm != 'T' ) ) {
  866. ReplyToUser(pUserData,
  867. REPLY_PARAMETER_NOT_IMPLEMENTED,
  868. PSZ_FORM_MESSAGE);
  869. return TRUE;
  870. }
  871. newType = XferTypeAscii;
  872. chType = 'A';
  873. break;
  874. case 'e':
  875. case 'E':
  876. if( !fValidForm ) {
  877. return FALSE;
  878. }
  879. if( ( chForm != 'N' ) && ( chForm != 'T' ) ) {
  880. ReplyToUser(pUserData,
  881. REPLY_PARAMETER_NOT_IMPLEMENTED,
  882. PSZ_FORM_MESSAGE);
  883. return TRUE;
  884. }
  885. ReplyToUser(pUserData,
  886. REPLY_PARAMETER_NOT_IMPLEMENTED,
  887. PSZ_TYPE_NOT_IMPLEMENTED, 'E');
  888. return TRUE;
  889. case 'i':
  890. case 'I':
  891. if( pszToken != NULL ) {
  892. return FALSE;
  893. }
  894. newType = XferTypeBinary;
  895. chType = 'I';
  896. break;
  897. case 'l':
  898. case 'L':
  899. if( pszToken == NULL ) {
  900. return FALSE;
  901. }
  902. if( strcmp( pszToken, "8" ) != 0 ) {
  903. if( IsDecimalNumber( pszToken ) ) {
  904. ReplyToUser(pUserData,
  905. REPLY_PARAMETER_NOT_IMPLEMENTED,
  906. PSZ_BYTE_SIZE_SPEC);
  907. return TRUE;
  908. } else {
  909. return FALSE;
  910. }
  911. }
  912. newType = XferTypeBinary;
  913. chType = 'L';
  914. break;
  915. default:
  916. return FALSE;
  917. } // switch (chType)
  918. IF_DEBUG( COMMANDS ) {
  919. DBGPRINTF(( DBG_CONTEXT,
  920. "setting transfer type to %s\n",
  921. TransferType( newType ) ));
  922. }
  923. pUserData->SetXferType( newType);
  924. ReplyToUser(pUserData,
  925. REPLY_COMMAND_OK,
  926. PSZ_TYPE_SET_TO,
  927. chType);
  928. return TRUE;
  929. } // MainTYPE()
  930. BOOL
  931. MainSTRU(
  932. LPUSER_DATA pUserData,
  933. LPSTR pszArg
  934. )
  935. /*++
  936. This function implements the STRU command - structure information
  937. Format: STRU fileName
  938. Arguments:
  939. pUserData - the user initiating the request.
  940. pszArg - command arguments. Will be NULL if no arguments present.
  941. Returns:
  942. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  943. --*/
  944. {
  945. CHAR chStruct;
  946. CHAR * pszToken;
  947. //
  948. // Sanity check the parameters.
  949. //
  950. DBG_ASSERT( pUserData != NULL );
  951. DBG_ASSERT( pszArg != NULL );
  952. pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER );
  953. if( pszToken == NULL ) {
  954. return FALSE;
  955. }
  956. //
  957. // Ensure we got a valid structure type
  958. // (only type F supported).
  959. //
  960. chStruct = *pszToken;
  961. if( pszToken[1] != '\0' ) {
  962. return FALSE;
  963. }
  964. switch( chStruct ) {
  965. case 'f':
  966. case 'F':
  967. chStruct = 'F';
  968. break;
  969. case 'r':
  970. case 'R':
  971. case 'p':
  972. case 'P':
  973. ReplyToUser(pUserData,
  974. REPLY_PARAMETER_NOT_IMPLEMENTED,
  975. PSZ_UNIMPLEMENTED_STRU_TYPE);
  976. return TRUE;
  977. default:
  978. return FALSE;
  979. }
  980. ReplyToUser(pUserData,
  981. REPLY_COMMAND_OK,
  982. PSZ_STRU_TYPE_OK,
  983. chStruct );
  984. return TRUE;
  985. } // MainSTRU()
  986. BOOL
  987. MainMODE(
  988. LPUSER_DATA pUserData,
  989. LPSTR pszArg
  990. )
  991. /*++
  992. This function implements the MODE command - to set mode of tfr.
  993. Format: MODE newMode
  994. Arguments:
  995. pUserData - the user initiating the request.
  996. pszArg - command arguments. Will be NULL if no arguments present.
  997. Returns:
  998. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  999. --*/
  1000. {
  1001. XFER_MODE newMode;
  1002. CHAR chMode;
  1003. LPSTR pszToken;
  1004. //
  1005. // Sanity check the parameters.
  1006. //
  1007. DBG_ASSERT( pUserData != NULL );
  1008. DBG_ASSERT( pszArg != NULL );
  1009. pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER );
  1010. if( pszToken == NULL ) {
  1011. return FALSE;
  1012. }
  1013. //
  1014. // Ensure we got a valid mode type
  1015. // (only type S supported).
  1016. //
  1017. if( pszToken[1] != '\0' ) {
  1018. return FALSE;
  1019. }
  1020. chMode = *pszToken;
  1021. switch( chMode )
  1022. {
  1023. case 's' :
  1024. case 'S' :
  1025. newMode = XferModeStream;
  1026. chMode = 'S';
  1027. break;
  1028. case 'b' :
  1029. case 'B' :
  1030. ReplyToUser(pUserData,
  1031. REPLY_PARAMETER_NOT_IMPLEMENTED,
  1032. PSZ_UNIMPLEMENTED_MODE );
  1033. return TRUE;
  1034. default :
  1035. return FALSE;
  1036. }
  1037. IF_DEBUG( COMMANDS )
  1038. {
  1039. DBGPRINTF(( DBG_CONTEXT,
  1040. "setting transfer mode to %s\n",
  1041. TransferMode( newMode ) ));
  1042. }
  1043. pUserData->SetXferMode(newMode);
  1044. ReplyToUser(pUserData,
  1045. REPLY_COMMAND_OK,
  1046. PSZ_MODE_OK,
  1047. chMode );
  1048. return TRUE;
  1049. } // MainMODE()
  1050. BOOL
  1051. MainRETR(
  1052. LPUSER_DATA pUserData,
  1053. LPSTR pszArg
  1054. )
  1055. /*++
  1056. This function implements RETR command - used for retrieving a file.
  1057. Format: RETR pathForFile
  1058. Arguments:
  1059. pUserData - the user initiating the request.
  1060. pszArg - command arguments. Will be NULL if no arguments present.
  1061. Returns:
  1062. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1063. --*/
  1064. {
  1065. #define MAX_FILE_SIZE_SPEC (32)
  1066. APIERR err = (!NO_ERROR);
  1067. BOOL fErrorSent = FALSE;
  1068. BOOL fTriedToOpenFile = FALSE;
  1069. LARGE_INTEGER FileSize;
  1070. DWORD dwAttribs;
  1071. TS_OPEN_FILE_INFO * pOpenFileInfo;
  1072. CHAR rgchSize[MAX_FILE_SIZE_SPEC];
  1073. CHAR rgchBuffer[MAX_FILE_SIZE_SPEC + 10];
  1074. DBG_ASSERT( pUserData != NULL );
  1075. //
  1076. // Ensure user is logged on properly.
  1077. //
  1078. DBG_ASSERT( pUserData->IsLoggedOn() );
  1079. //
  1080. // Sanity check the parameters.
  1081. //
  1082. DBG_ASSERT( pszArg != NULL );
  1083. if (pUserData->ImpersonateUser()){
  1084. err = pUserData->OpenFileForSend( pszArg );
  1085. pUserData->RevertToSelf();
  1086. }
  1087. else
  1088. {
  1089. err = GetLastError();
  1090. }
  1091. fTriedToOpenFile = TRUE;
  1092. if ( err != NO_ERROR ) {
  1093. goto retr_exit;
  1094. }
  1095. pOpenFileInfo = pUserData->QueryOpenFileInfo();
  1096. if ( pOpenFileInfo == NULL )
  1097. {
  1098. err = ERROR_FILE_NOT_FOUND;
  1099. goto retr_exit;
  1100. }
  1101. //
  1102. // Get the file size
  1103. //
  1104. if ( !pOpenFileInfo->QuerySize(FileSize) )
  1105. {
  1106. err = GetLastError();
  1107. if ( err != NO_ERROR ) {
  1108. goto retr_exit;
  1109. }
  1110. }
  1111. if( FileSize.HighPart != 0 ) {
  1112. //
  1113. // we do not support files >4GB.
  1114. //
  1115. ReplyToUser(pUserData,
  1116. REPLY_FILE_NOT_ALLOWED,
  1117. "Cannot send file larger than 4 gigabytes." );
  1118. fErrorSent = TRUE;
  1119. err = ERROR_MESSAGE_EXCEEDS_MAX_SIZE;
  1120. goto retr_exit;
  1121. }
  1122. // removed to fix copatibility problem with cute ftp
  1123. // when FTP service should report always a total size of file to be
  1124. // transfered not a remainder like it was before
  1125. //FileSize.QuadPart -= (LONGLONG)pUserData->QueryCurrentOffset();
  1126. IsLargeIntegerToDecimalChar( &FileSize, rgchSize);
  1127. wsprintfA( rgchBuffer, "(%s bytes)", rgchSize);
  1128. //
  1129. // Establish a data connection
  1130. //
  1131. err = pUserData->EstablishDataConnection( pszArg, rgchBuffer );
  1132. if ( err != NO_ERROR )
  1133. {
  1134. if ( err == ERROR_IO_PENDING )
  1135. {
  1136. //
  1137. // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
  1138. // client to establish a data connection - it takes care of setting up an event that
  1139. // allows us to deal asynchronously with the client connecting [or failing to do so].
  1140. // It indicates this asynchrony by returning ERROR_IO_PENDING
  1141. //
  1142. DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
  1143. !pUserData->QueryHavePASVConn() );
  1144. if ( fTriedToOpenFile )
  1145. {
  1146. pUserData->CloseFileForSend( err); // close it always on error
  1147. }
  1148. return TRUE;
  1149. }
  1150. fErrorSent = TRUE;
  1151. goto retr_exit;
  1152. }
  1153. err = pUserData->SendFileToUser( pszArg, &fErrorSent);
  1154. if ( err != NO_ERROR)
  1155. {
  1156. //
  1157. // Disconnect connection, since we are in error.
  1158. //
  1159. DBG_REQUIRE( pUserData->DestroyDataConnection( err ));
  1160. // since there was a failure we will close the handle right away.
  1161. IF_DEBUG( ASYNC_IO) {
  1162. DBGPRINTF( ( DBG_CONTEXT,
  1163. "SendFileToUser ( %s) failed"
  1164. " err = %u\n",
  1165. pszArg, err));
  1166. }
  1167. }
  1168. retr_exit:
  1169. if( err != NO_ERROR )
  1170. {
  1171. //
  1172. // This command failed, so drop out of PASV mode
  1173. //
  1174. CLEAR_UF( pUserData, PASSIVE );
  1175. //
  1176. // Clean up the PASV flags if necessary
  1177. //
  1178. if ( pUserData->QueryInFakeIOCompletion() )
  1179. {
  1180. pUserData->CleanupPASVFlags();
  1181. }
  1182. if ( !fErrorSent)
  1183. {
  1184. pUserData->SendErrorToClient(pszArg, err,
  1185. PSZ_NO_FILE_OR_DIRECTORY);
  1186. }
  1187. pUserData->WriteLogRecordForSendError( err );
  1188. if ( fTriedToOpenFile )
  1189. {
  1190. pUserData->CloseFileForSend( err); // close it always on error
  1191. }
  1192. }
  1193. return TRUE;
  1194. } // MainRETR()
  1195. BOOL
  1196. MainSTOR(
  1197. LPUSER_DATA pUserData,
  1198. LPSTR pszArg
  1199. )
  1200. /*++
  1201. This function implements STOR command - used for storing a file.
  1202. Format: STOR pathForFile
  1203. Arguments:
  1204. pUserData - the user initiating the request.
  1205. pszArg - command arguments. Will be NULL if no arguments present.
  1206. Returns:
  1207. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1208. --*/
  1209. {
  1210. APIERR err;
  1211. HANDLE hFile;
  1212. DBG_ASSERT( pUserData != NULL );
  1213. //
  1214. // Ensure user is logged on properly.
  1215. //
  1216. DBG_ASSERT( pUserData->IsLoggedOn());
  1217. //
  1218. // Sanity check the parameters.
  1219. //
  1220. DBG_ASSERT( pszArg != NULL );
  1221. //
  1222. // Try to create the file.
  1223. //
  1224. err = VirtualCreateFile( pUserData,
  1225. &hFile,
  1226. pszArg,
  1227. FALSE );
  1228. if( err != NO_ERROR )
  1229. {
  1230. pUserData->SendErrorToClient(pszArg, err, PSZ_CANNOT_CREATE_FILE);
  1231. }
  1232. else
  1233. {
  1234. //
  1235. // Establish a connection to the user
  1236. //
  1237. err = pUserData->EstablishDataConnection( pszArg );
  1238. if ( err != NO_ERROR )
  1239. {
  1240. CloseHandle( hFile );
  1241. if ( err == ERROR_IO_PENDING )
  1242. {
  1243. //
  1244. // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
  1245. // client to establish a data connection - it takes care of setting up an event that
  1246. // allows us to deal asynchronously with the client connecting [or failing to do so].
  1247. // It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
  1248. // any further processing
  1249. //
  1250. DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
  1251. !pUserData->QueryHavePASVConn() );
  1252. return TRUE;
  1253. }
  1254. goto stor_exit;
  1255. }
  1256. //
  1257. // Let the worker do the dirty work. ( blocking call)
  1258. // On return, hFile is closed
  1259. //
  1260. err = ReceiveFileFromUserAndClose( pUserData, pszArg, &hFile );
  1261. if( err != NO_ERROR )
  1262. {
  1263. VirtualDeleteFile( pUserData,
  1264. pszArg );
  1265. }
  1266. }
  1267. stor_exit:
  1268. pUserData->WriteLogRecord(PSZ_CREATE_VERB, pszArg, err);
  1269. return TRUE;
  1270. } // MainSTOR()
  1271. BOOL
  1272. MainSTOU(
  1273. LPUSER_DATA pUserData,
  1274. LPSTR pszArg
  1275. )
  1276. /*++
  1277. This function implements STOU command - used for storing in unique file.
  1278. Format: STOU <noArgs>
  1279. Arguments:
  1280. pUserData - the user initiating the request.
  1281. pszArg - command arguments. Will be NULL if no arguments present.
  1282. Returns:
  1283. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1284. --*/
  1285. {
  1286. APIERR err;
  1287. HANDLE hFile;
  1288. CHAR szTmpFile[MAX_PATH]; // contains entire path
  1289. CHAR * pszTmpFileName; // contains only the file name
  1290. DBG_ASSERT( pUserData != NULL );
  1291. //
  1292. // Ensure user is logged on properly.
  1293. //
  1294. DBG_ASSERT( pUserData->IsLoggedOn());
  1295. //
  1296. // Sanity check the parameters.
  1297. //
  1298. DBG_ASSERT( pszArg == NULL );
  1299. //
  1300. // Try to create the file.
  1301. //
  1302. szTmpFile[0] = '\0';
  1303. err = VirtualCreateUniqueFile( pUserData,
  1304. &hFile,
  1305. szTmpFile );
  1306. //
  1307. // extract the file name alone
  1308. //
  1309. pszTmpFileName = strrchr( szTmpFile, '\\');
  1310. if (NULL == pszTmpFileName)
  1311. { pszTmpFileName = szTmpFile; }
  1312. else
  1313. { pszTmpFileName++; }
  1314. if( err != NO_ERROR )
  1315. {
  1316. pUserData->SendErrorToClient(pszTmpFileName, err,
  1317. PSZ_CANNOT_CREATE_UNIQUE_FILE);
  1318. }
  1319. else
  1320. {
  1321. //
  1322. // Establish a connection to the user
  1323. //
  1324. err = pUserData->EstablishDataConnection( pszTmpFileName );
  1325. if ( err != NO_ERROR )
  1326. {
  1327. CloseHandle( hFile );
  1328. if ( err == ERROR_IO_PENDING )
  1329. {
  1330. //
  1331. // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
  1332. // client to establish a data connection - it takes care of setting up an event that
  1333. // allows us to deal asynchronously with the client connecting [or failing to do so].
  1334. // It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
  1335. // any further processign
  1336. DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
  1337. !pUserData->QueryHavePASVConn() );
  1338. return TRUE;
  1339. }
  1340. goto stou_exit;
  1341. }
  1342. //
  1343. // Let the worker do the dirty work.
  1344. // On return, hFile is closed
  1345. //
  1346. err = ReceiveFileFromUserAndClose( pUserData, pszTmpFileName, &hFile );
  1347. if( err != NO_ERROR ) {
  1348. //
  1349. // Note that VirtualCreateUniqueFile() returns a fully
  1350. // qualified physical path to the temporary file. Because
  1351. // of this, we cannot call VirtualDeleteFile(), as that will
  1352. // attempt to "re-canonicalize" the file, which will fail.
  1353. // So, we'll just call the DeleteFile() Win32 API directly.
  1354. //
  1355. DeleteFile( szTmpFile );
  1356. }
  1357. }
  1358. stou_exit:
  1359. pUserData->WriteLogRecord(PSZ_CREATE_VERB, szTmpFile, err);
  1360. return TRUE;
  1361. } // MainSTOU()
  1362. BOOL
  1363. MainAPPE(
  1364. LPUSER_DATA pUserData,
  1365. LPSTR pszArg
  1366. )
  1367. /*++
  1368. This function implements APPE command - used for appending to a file.
  1369. Format: APPE filename
  1370. Arguments:
  1371. pUserData - the user initiating the request.
  1372. pszArg - command arguments. Will be NULL if no arguments present.
  1373. Returns:
  1374. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1375. --*/
  1376. {
  1377. APIERR err;
  1378. HANDLE hFile = INVALID_HANDLE_VALUE;
  1379. DBG_ASSERT( pUserData != NULL );
  1380. //
  1381. // Ensure user is logged on properly.
  1382. //
  1383. DBG_ASSERT( pUserData->IsLoggedOn());
  1384. //
  1385. // Sanity check the parameters.
  1386. //
  1387. DBG_ASSERT( pszArg != NULL );
  1388. //
  1389. // Try to create the file.
  1390. //
  1391. err = VirtualCreateFile( pUserData,
  1392. &hFile,
  1393. pszArg,
  1394. TRUE );
  1395. if( err != NO_ERROR )
  1396. {
  1397. pUserData->SendErrorToClient(pszArg, err, PSZ_CANNOT_CREATE_FILE);
  1398. }
  1399. else
  1400. {
  1401. //
  1402. // Establish a connection to the user
  1403. //
  1404. err = pUserData->EstablishDataConnection( pszArg );
  1405. if ( err != NO_ERROR )
  1406. {
  1407. CloseHandle(hFile);
  1408. if ( err == ERROR_IO_PENDING )
  1409. {
  1410. //
  1411. // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
  1412. // client to establish a data connection - it takes care of setting up an event that
  1413. // allows us to deal asynchronously with the client connecting [or failing to do so].
  1414. // It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
  1415. // any further processing
  1416. //
  1417. DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
  1418. !pUserData->QueryHavePASVConn() );
  1419. return TRUE;
  1420. }
  1421. goto appe_exit;
  1422. }
  1423. //
  1424. // Let the worker do the dirty work.
  1425. // On return, hFile is closed
  1426. //
  1427. err = ReceiveFileFromUserAndClose( pUserData, pszArg, &hFile );
  1428. }
  1429. appe_exit:
  1430. pUserData->WriteLogRecord( PSZ_APPEND_VERB, pszArg, err);
  1431. return TRUE;
  1432. } // MainAPPE()
  1433. BOOL
  1434. MainALLO(
  1435. LPUSER_DATA pUserData,
  1436. LPSTR pszArg
  1437. )
  1438. /*++
  1439. This function implements ALLO command - used for allocating space for file.
  1440. Format: ALLO filename
  1441. Arguments:
  1442. pUserData - the user initiating the request.
  1443. pszArg - command arguments. Will be NULL if no arguments present.
  1444. Returns:
  1445. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1446. --*/
  1447. {
  1448. DBG_ASSERT( pUserData != NULL );
  1449. //
  1450. // Since we don't need to pre-reserve storage space for
  1451. // files, we'll treat this command as a noop.
  1452. //
  1453. ReplyToUser(pUserData,
  1454. REPLY_COMMAND_OK,
  1455. PSZ_COMMAND_SUCCESSFUL,
  1456. "ALLO");
  1457. return TRUE;
  1458. } // MainALLO()
  1459. BOOL
  1460. MainREST(
  1461. LPUSER_DATA pUserData,
  1462. LPSTR pszArg
  1463. )
  1464. /*++
  1465. This function implements REST command - used for restarting file write
  1466. Format: REST offset
  1467. Arguments:
  1468. pUserData - the user initiating the request.
  1469. pszArg - command arguments. Will be NULL if no arguments present.
  1470. Returns:
  1471. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1472. --*/
  1473. {
  1474. LPSTR pszEndPtr;
  1475. DWORD dwOffset;
  1476. DBG_ASSERT( pUserData != NULL );
  1477. DBG_ASSERT( pszArg != NULL );
  1478. // Convert the input parameter to a number, and save it for the next command.
  1479. dwOffset = strtoul(pszArg, &pszEndPtr, 10);
  1480. if( (pszEndPtr == pszArg) || (*pszEndPtr != '\0') ||
  1481. (*pszArg == '-') ||
  1482. (dwOffset == ULONG_MAX && strcmp(pszArg, "4294967295")) )
  1483. {
  1484. ReplyToUser(pUserData,
  1485. REPLY_PARAMETER_SYNTAX_ERROR,
  1486. PSZ_REPLY_MARKER_SPEC );
  1487. } else {
  1488. pUserData->SetNextOffset( dwOffset );
  1489. ReplyToUser(pUserData,
  1490. REPLY_NEED_MORE_INFO,
  1491. PSZ_REPLY_RESTARTING,
  1492. pszArg);
  1493. }
  1494. return TRUE;
  1495. } // MainREST()
  1496. BOOL
  1497. MainRNFR(
  1498. LPUSER_DATA pUserData,
  1499. LPSTR pszArg
  1500. )
  1501. /*++
  1502. This function implements RNFR command - rename from filename
  1503. Format: RNFR FromFileName
  1504. Arguments:
  1505. pUserData - the user initiating the request.
  1506. pszArg - command arguments. Will be NULL if no arguments present.
  1507. Returns:
  1508. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1509. --*/
  1510. {
  1511. APIERR err;
  1512. CHAR szCanon[MAX_PATH];
  1513. DWORD cbSize = MAX_PATH;
  1514. DBG_ASSERT( pUserData != NULL );
  1515. //
  1516. // Ensure user is logged on properly.
  1517. //
  1518. DBG_ASSERT( pUserData->IsLoggedOn());
  1519. //
  1520. // Sanity check the parameters.
  1521. //
  1522. DBG_ASSERT( pszArg != NULL );
  1523. //
  1524. // Ensure file/directory exists.
  1525. //
  1526. if (pUserData->IsFileNameShort(pszArg))
  1527. {
  1528. err = ERROR_FILE_NOT_FOUND;
  1529. }
  1530. else
  1531. {
  1532. err = pUserData->VirtualCanonicalize(szCanon,
  1533. &cbSize,
  1534. pszArg,
  1535. AccessTypeDelete );
  1536. }
  1537. if( err == NO_ERROR ) {
  1538. if ( pUserData->ImpersonateUser() ) {
  1539. if( GetFileAttributes( szCanon ) == (DWORD)-1L ) {
  1540. err = GetLastError();
  1541. }
  1542. pUserData->RevertToSelf();
  1543. } else {
  1544. err = ERROR_ACCESS_DENIED;
  1545. }
  1546. if(( err == NO_ERROR ) && ( pUserData->RenameSourceBuffer == NULL )){
  1547. pUserData->RenameSourceBuffer = (CHAR *)TCP_ALLOC( MAX_PATH );
  1548. if( pUserData->RenameSourceBuffer == NULL ) {
  1549. err = GetLastError();
  1550. }
  1551. }
  1552. if( err == NO_ERROR ) {
  1553. strcpy( pUserData->RenameSourceBuffer, pszArg );
  1554. SET_UF( pUserData, RENAME );
  1555. }
  1556. }
  1557. if( err == NO_ERROR )
  1558. {
  1559. ReplyToUser(pUserData,
  1560. REPLY_NEED_MORE_INFO,
  1561. PSZ_READY_FOR_DEST_FILE);
  1562. } else {
  1563. pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
  1564. }
  1565. pUserData->WriteLogRecord( PSZ_RNFR_VERB, pszArg, err);
  1566. return TRUE;
  1567. } // MainRNFR()
  1568. BOOL
  1569. MainRNTO(
  1570. LPUSER_DATA pUserData,
  1571. LPSTR pszArg
  1572. )
  1573. /*++
  1574. This function implements RNTO command - rename to filename
  1575. Format: RNTO ToFileName
  1576. Arguments:
  1577. pUserData - the user initiating the request.
  1578. pszArg - command arguments. Will be NULL if no arguments present.
  1579. Returns:
  1580. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1581. --*/
  1582. {
  1583. APIERR err;
  1584. DBG_ASSERT( pUserData != NULL );
  1585. //
  1586. // Ensure user is logged on properly.
  1587. //
  1588. DBG_ASSERT( pUserData->IsLoggedOn());
  1589. //
  1590. // Sanity check the parameters.
  1591. //
  1592. DBG_ASSERT( pszArg != NULL );
  1593. //
  1594. // Ensure previous command was a RNFR.
  1595. //
  1596. if( !TEST_UF( pUserData, RENAME ) )
  1597. {
  1598. ReplyToUser(pUserData,
  1599. REPLY_BAD_COMMAND_SEQUENCE,
  1600. PSZ_BAD_COMMAND_SEQUENCE);
  1601. } else {
  1602. CLEAR_UF( pUserData, RENAME );
  1603. //
  1604. // Rename the file.
  1605. //
  1606. err = VirtualRenameFile( pUserData,
  1607. pUserData->RenameSourceBuffer,
  1608. pszArg );
  1609. if( err == NO_ERROR ) {
  1610. ReplyToUser(pUserData,
  1611. REPLY_FILE_ACTION_COMPLETED,
  1612. PSZ_COMMAND_SUCCESSFUL, "RNTO");
  1613. } else if( err == ERROR_FILE_EXISTS ) {
  1614. pUserData->SendErrorToClient(
  1615. pszArg,
  1616. err,
  1617. PSZ_DESTINATION_FILE_EXISTS,
  1618. REPLY_FILE_NOT_ALLOWED
  1619. );
  1620. } else {
  1621. pUserData->SendErrorToClient(
  1622. pszArg,
  1623. err,
  1624. PSZ_NO_FILE_OR_DIRECTORY,
  1625. REPLY_FILE_NOT_FOUND
  1626. );
  1627. }
  1628. }
  1629. pUserData->WriteLogRecord( PSZ_RNTO_VERB, pszArg, err);
  1630. return TRUE;
  1631. } // MainRNTO()
  1632. BOOL
  1633. MainABOR(
  1634. LPUSER_DATA pUserData,
  1635. LPSTR pszArg
  1636. )
  1637. /*++
  1638. This function implements ABOR command - abort any ongoing data transfer
  1639. Format: ABOR
  1640. Arguments:
  1641. pUserData - the user initiating the request.
  1642. pszArg - command arguments. Will be NULL if no arguments present.
  1643. Returns:
  1644. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1645. --*/
  1646. {
  1647. DBG_ASSERT( pUserData != NULL );
  1648. ReplyToUser(pUserData,
  1649. TEST_UF( pUserData, OOB_DATA )
  1650. ? REPLY_TRANSFER_OK
  1651. : REPLY_CONNECTION_OPEN,
  1652. PSZ_COMMAND_SUCCESSFUL, "ABOR");
  1653. //
  1654. // Clear any remaining oob flag.
  1655. //
  1656. CLEAR_UF( pUserData, OOB_DATA );
  1657. pUserData->WriteLogRecord(PSZ_ABORT_VERB, "");
  1658. return TRUE;
  1659. } // MainABOR()
  1660. BOOL
  1661. MainDELE(
  1662. LPUSER_DATA pUserData,
  1663. LPSTR pszArg
  1664. )
  1665. /*++
  1666. This function implements DELE command - used to delete a file
  1667. Format: DELE filename
  1668. Arguments:
  1669. pUserData - the user initiating the request.
  1670. pszArg - command arguments. Will be NULL if no arguments present.
  1671. Returns:
  1672. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1673. --*/
  1674. {
  1675. APIERR err;
  1676. DBG_ASSERT( pUserData != NULL );
  1677. //
  1678. // Ensure user is logged on properly.
  1679. //
  1680. DBG_ASSERT( pUserData->IsLoggedOn());
  1681. if (pUserData->IsFileNameShort(pszArg))
  1682. {
  1683. err = ERROR_FILE_NOT_FOUND;
  1684. }
  1685. else
  1686. {
  1687. err = VirtualDeleteFile( pUserData, pszArg );
  1688. }
  1689. if( err == NO_ERROR ) {
  1690. ReplyToUser(pUserData,
  1691. REPLY_FILE_ACTION_COMPLETED,
  1692. PSZ_COMMAND_SUCCESSFUL, "DELE");
  1693. } else {
  1694. pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
  1695. }
  1696. pUserData->WriteLogRecord( PSZ_DELE_VERB, pszArg, err);
  1697. return TRUE;
  1698. } // MainDELE()
  1699. BOOL
  1700. MainRMD(
  1701. LPUSER_DATA pUserData,
  1702. LPSTR pszArg
  1703. )
  1704. /*++
  1705. This function implements RMD command - used to delete a directory
  1706. Format: RMD directory
  1707. Arguments:
  1708. pUserData - the user initiating the request.
  1709. pszArg - command arguments. Will be NULL if no arguments present.
  1710. Returns:
  1711. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1712. --*/
  1713. {
  1714. APIERR err;
  1715. DBG_ASSERT( pUserData != NULL );
  1716. //
  1717. // Ensure user is logged on properly.
  1718. //
  1719. DBG_ASSERT( pUserData->IsLoggedOn());
  1720. if (pUserData->IsFileNameShort(pszArg))
  1721. {
  1722. err = ERROR_PATH_NOT_FOUND;
  1723. }
  1724. else
  1725. {
  1726. err = VirtualRmDir( pUserData, pszArg );
  1727. }
  1728. if( err == NO_ERROR ) {
  1729. ReplyToUser(pUserData,
  1730. REPLY_FILE_ACTION_COMPLETED,
  1731. PSZ_COMMAND_SUCCESSFUL, "RMD");
  1732. } else {
  1733. pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
  1734. }
  1735. pUserData->WriteLogRecord( PSZ_RMD_VERB, pszArg, err);
  1736. return TRUE;
  1737. } // MainRMD()
  1738. BOOL
  1739. MainMKD(
  1740. LPUSER_DATA pUserData,
  1741. LPSTR pszArg
  1742. )
  1743. /*++
  1744. This function implements MKD command - used to create a directory
  1745. Format: MKD directory
  1746. Arguments:
  1747. pUserData - the user initiating the request.
  1748. pszArg - command arguments. Will be NULL if no arguments present.
  1749. Returns:
  1750. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1751. --*/
  1752. {
  1753. APIERR err;
  1754. DBG_ASSERT( pUserData != NULL );
  1755. //
  1756. // Ensure user is logged on properly.
  1757. //
  1758. DBG_ASSERT( pUserData->IsLoggedOn());
  1759. err = VirtualMkDir( pUserData, pszArg );
  1760. if( err == NO_ERROR ) {
  1761. ReplyToUser(pUserData,
  1762. REPLY_FILE_CREATED,
  1763. PSZ_DIRECTORY_CREATE, pszArg);
  1764. } else {
  1765. pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
  1766. }
  1767. pUserData->WriteLogRecord( PSZ_MKD_VERB, pszArg, err);
  1768. return TRUE;
  1769. } // MainMKD()
  1770. BOOL
  1771. MainPWD(
  1772. LPUSER_DATA pUserData,
  1773. LPSTR pszArg
  1774. )
  1775. /*++
  1776. This function implements PWD command - used to query path to working dir.
  1777. Format: PWD
  1778. Arguments:
  1779. pUserData - the user initiating the request.
  1780. pszArg - command arguments. Will be NULL if no arguments present.
  1781. Returns:
  1782. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1783. --*/
  1784. {
  1785. CHAR szDir[MAX_PATH];
  1786. DBG_ASSERT( pUserData != NULL );
  1787. //
  1788. // Ensure user is logged on properly.
  1789. //
  1790. DBG_ASSERT( pUserData->IsLoggedOn());
  1791. //
  1792. // We will be sending back the current directory in virtual form
  1793. // Ofcourse the client should/need not worry about the exact path info.
  1794. //
  1795. strcpy( szDir, pUserData->QueryCurrentDirectory() );
  1796. if( !TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) ) {
  1797. FlipSlashes( szDir );
  1798. }
  1799. ReplyToUser(pUserData,
  1800. REPLY_FILE_CREATED,
  1801. PSZ_CURRENT_DIRECTORY,
  1802. szDir );
  1803. return TRUE;
  1804. } // MainPWD()
  1805. /*******************************************************************
  1806. NAME: MainLIST
  1807. SYNOPSIS: Implementation for the LIST command. Similar to NLST,
  1808. except defaults to long format display.
  1809. ENTRY: pUserData - The user initiating the request.
  1810. pszArg - Command arguments. Will be NULL if no
  1811. arguments given.
  1812. RETURNS: BOOL - TRUE if arguments OK, FALSE if syntax error.
  1813. HISTORY:
  1814. KeithMo 09-Mar-1993 Created.
  1815. ********************************************************************/
  1816. BOOL
  1817. MainLIST(
  1818. LPUSER_DATA pUserData,
  1819. LPSTR pszArg
  1820. )
  1821. /*++
  1822. This function implements LIST command - used for getting dir list.
  1823. It is similar to NLST, only that this defaults to long format.
  1824. Format: LIST [options]* [path]*
  1825. Arguments:
  1826. pUserData - the user initiating the request.
  1827. pszArg - command arguments. Will be NULL if no arguments present.
  1828. Returns:
  1829. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1830. --*/
  1831. {
  1832. APIERR serr = 0;
  1833. DBG_ASSERT( pUserData != NULL );
  1834. //
  1835. // Ensure user is logged on properly.
  1836. //
  1837. DBG_ASSERT( pUserData->IsLoggedOn());
  1838. //
  1839. // Let the worker do the dirty work.
  1840. //
  1841. serr = pUserData->EstablishDataConnection("/bin/ls");
  1842. //
  1843. // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
  1844. // client to establish a data connection - it takes care of setting up an event that
  1845. // allows us to deal asynchronously with the client connecting [or failing to do so].
  1846. // It indicates this asynchrony by returning ERROR_IO_PENDING
  1847. //
  1848. if ( serr == ERROR_IO_PENDING )
  1849. {
  1850. DBG_ASSERT( TEST_UF( pUserData, PASSIVE ) );
  1851. DBG_ASSERT( pUserData->QueryWaitingForPASVConn() );
  1852. DBG_ASSERT( !pUserData->QueryHavePASVConn() );
  1853. return TRUE;
  1854. }
  1855. if ( serr == 0) {
  1856. DWORD dwError;
  1857. serr = SimulateLs(pUserData,
  1858. pszArg, // switches for path
  1859. TRUE, // use data socket
  1860. TRUE); // generate default long format
  1861. dwError = ( (!TEST_UF(pUserData, OOB_ABORT) && (serr == 0))
  1862. ? NO_ERROR : serr);
  1863. if ( dwError != NO_ERROR) {
  1864. //
  1865. // Send a soft error message indicating failure
  1866. //
  1867. pUserData->SendErrorToClient((pszArg != NULL) ? pszArg : ".",
  1868. dwError,
  1869. PSZ_NO_FILE_OR_DIRECTORY);
  1870. // since we already reported error, now just reset transfer.
  1871. CLEAR_UF( pUserData, TRANSFER);
  1872. }
  1873. DBG_REQUIRE( pUserData->DestroyDataConnection(dwError));
  1874. } else {
  1875. //
  1876. // could not establish a connection send error!
  1877. // Error is already sent by EstablishDataConnection()
  1878. //
  1879. IF_DEBUG( ERROR) {
  1880. DBGPRINTF( ( DBG_CONTEXT,
  1881. "EstablishDataConnection( %08x) failed for LIST\n",
  1882. pUserData));
  1883. }
  1884. }
  1885. return TRUE;
  1886. } // MainLIST()
  1887. BOOL
  1888. MainNLST(
  1889. LPUSER_DATA pUserData,
  1890. LPSTR pszArg
  1891. )
  1892. /*++
  1893. This function implements NLST command - used for getting dir list.
  1894. generates a short form of dir listing.
  1895. Format: NLST [options]* [path]*
  1896. Arguments:
  1897. pUserData - the user initiating the request.
  1898. pszArg - command arguments. Will be NULL if no arguments present.
  1899. Returns:
  1900. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1901. --*/
  1902. {
  1903. BOOL fSpecialLs;
  1904. APIERR serr;
  1905. DBG_ASSERT( pUserData != NULL );
  1906. //
  1907. // Ensure user is logged on properly.
  1908. //
  1909. DBG_ASSERT( pUserData->IsLoggedOn());
  1910. //
  1911. // If any switches are present, use the simulated "ls"
  1912. // command. Otherwise (no switches) use the special
  1913. // file list.
  1914. //
  1915. // Estabalish a data connection for transfer of data and simulate Ls.
  1916. fSpecialLs = ( ( pszArg == NULL) || ( *pszArg != '-')); // no switches
  1917. serr = pUserData->EstablishDataConnection( (fSpecialLs)
  1918. ? "file list" : "/bin/ls"
  1919. );
  1920. //
  1921. // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
  1922. // client to establish a data connection - it takes care of setting up an event that
  1923. // allows us to deal asynchronously with the client connecting [or failing to do so].
  1924. // It indicates this asynchrony by returning ERROR_IO_PENDING
  1925. //
  1926. if ( serr == ERROR_IO_PENDING )
  1927. {
  1928. DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
  1929. !pUserData->QueryHavePASVConn() );
  1930. return TRUE;
  1931. }
  1932. if ( serr == 0) {
  1933. DWORD dwError;
  1934. serr = ( ( fSpecialLs) ?
  1935. SpecialLs(pUserData,
  1936. pszArg,
  1937. TRUE)
  1938. : SimulateLs(pUserData,
  1939. pszArg, // switches & search path
  1940. TRUE)
  1941. );
  1942. dwError = ((!TEST_UF(pUserData, OOB_DATA) && (serr == 0))
  1943. ?NO_ERROR: serr);
  1944. if ( dwError != NO_ERROR) {
  1945. //
  1946. // Send a soft error message indicating failure
  1947. //
  1948. pUserData->SendErrorToClient((pszArg != NULL) ? pszArg : ".",
  1949. dwError,
  1950. PSZ_NO_FILE_OR_DIRECTORY);
  1951. // since we already reported error, now just reset transfer.
  1952. CLEAR_UF( pUserData, TRANSFER);
  1953. }
  1954. DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));
  1955. } else {
  1956. //
  1957. // could not establish a connection send error!
  1958. // Error is already sent by EstablishDataConnection()
  1959. //
  1960. IF_DEBUG( ERROR) {
  1961. DBGPRINTF( ( DBG_CONTEXT,
  1962. "EstablishDataConnection( %08x) failed for LIST\n",
  1963. pUserData));
  1964. }
  1965. }
  1966. return TRUE;
  1967. } // MainNLST()
  1968. BOOL
  1969. MainSYST(
  1970. LPUSER_DATA pUserData,
  1971. LPSTR pszArg
  1972. )
  1973. /*++
  1974. This function implements SYST command - used for getting system info.
  1975. Format: SYST
  1976. Arguments:
  1977. pUserData - the user initiating the request.
  1978. pszArg - command arguments. Will be NULL if no arguments present.
  1979. Returns:
  1980. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  1981. --*/
  1982. {
  1983. DBG_ASSERT( pUserData != NULL );
  1984. ReplyToUser(pUserData,
  1985. REPLY_SYSTEM_TYPE,
  1986. PSZ_VERSION_INFO );
  1987. return TRUE;
  1988. } // MainSYST()
  1989. # define MAX_STAT_BUFFER_SIZE (900)
  1990. BOOL
  1991. MainSTAT(
  1992. LPUSER_DATA pUserData,
  1993. LPSTR pszArg
  1994. )
  1995. /*++
  1996. This function implements STAT command - used for getting system stats.
  1997. Format: STAT
  1998. Arguments:
  1999. pUserData - the user initiating the request.
  2000. pszArg - command arguments. Will be NULL if no arguments present.
  2001. Returns:
  2002. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  2003. --*/
  2004. {
  2005. CHAR rgchBuffer[MAX_STAT_BUFFER_SIZE];
  2006. DWORD cchBuf;
  2007. DWORD dwError;
  2008. DBG_ASSERT( pUserData != NULL );
  2009. //
  2010. // Ensure user is logged on properly.
  2011. //
  2012. DBG_ASSERT( pUserData->IsLoggedOn());
  2013. if( pszArg == NULL )
  2014. {
  2015. HOSTENT * pHost;
  2016. //
  2017. // Determine the name of the user's host machine.
  2018. //
  2019. pHost = gethostbyaddr((CHAR *)&pUserData->HostIpAddress.s_addr,
  2020. 4, // size of the s_addr structure
  2021. PF_INET ) ;
  2022. //
  2023. // Just dump connection info.
  2024. //
  2025. cchBuf = wsprintfA( rgchBuffer,
  2026. "%u-%s status:\r\n"
  2027. " Connected to %s\r\n"
  2028. " Logged in as %s\r\n"
  2029. " TYPE: %s, FORM: %s; STRUcture: %s;"
  2030. " transfer MODE: %s\r\n"
  2031. " %s\r\n"
  2032. ,
  2033. REPLY_SYSTEM_STATUS,
  2034. g_FtpServiceNameString,
  2035. ( ( pHost != NULL )
  2036. ? pHost->h_name
  2037. : inet_ntoa( pUserData->HostIpAddress)),
  2038. pUserData->QueryUserName(),
  2039. TransferType( pUserData->QueryXferType() ),
  2040. "Nonprint",
  2041. "File",
  2042. TransferMode( pUserData->QueryXferMode()),
  2043. ( ( pUserData->QueryDataSocket() == INVALID_SOCKET )
  2044. ? "No data connection"
  2045. : "Data connection established")
  2046. );
  2047. if ( cchBuf < MAX_STAT_BUFFER_SIZE &&
  2048. pUserData->QueryControlSocket() != INVALID_SOCKET) {
  2049. dwError = SockSend(pUserData,
  2050. pUserData->QueryControlSocket(),
  2051. rgchBuffer,
  2052. cchBuf);
  2053. IF_DEBUG( SOCKETS) {
  2054. DBGPRINTF((DBG_CONTEXT,
  2055. " Sending STAT results %d bytes [%s]. Error= %u\n",
  2056. cchBuf, rgchBuffer, dwError));
  2057. }
  2058. } else {
  2059. dwError = ERROR_INSUFFICIENT_BUFFER;
  2060. }
  2061. ReplyToUser(pUserData,
  2062. REPLY_SYSTEM_STATUS,
  2063. PSZ_SERVER_STATUS_END );
  2064. } else {
  2065. //
  2066. // This should be similar to LIST, except it sends data
  2067. // over the control socket, not a data socket.
  2068. //
  2069. cchBuf = wsprintfA( rgchBuffer,
  2070. "%u-status of %s:\r\n",
  2071. REPLY_FILE_STATUS,
  2072. pszArg );
  2073. if ( cchBuf < MAX_STAT_BUFFER_SIZE &&
  2074. pUserData->QueryControlSocket() != INVALID_SOCKET) {
  2075. dwError = SockSend(pUserData,
  2076. pUserData->QueryControlSocket(),
  2077. rgchBuffer,
  2078. cchBuf);
  2079. // Error code is ignored after this point!
  2080. }
  2081. SimulateLs(pUserData,
  2082. pszArg,
  2083. FALSE, // use control socket
  2084. TRUE); // generate default long format
  2085. ReplyToUser(pUserData,
  2086. REPLY_FILE_STATUS,
  2087. PSZ_SERVER_STATUS_END);
  2088. }
  2089. return TRUE;
  2090. } // MainSTAT()
  2091. BOOL
  2092. MainNOOP(
  2093. LPUSER_DATA pUserData,
  2094. LPSTR pszArg
  2095. )
  2096. /*++
  2097. The DO Nothing NOOP command.
  2098. --*/
  2099. {
  2100. DBG_ASSERT( pUserData != NULL );
  2101. ReplyToUser(pUserData,
  2102. REPLY_COMMAND_OK,
  2103. PSZ_COMMAND_SUCCESSFUL, "NOOP");
  2104. return TRUE;
  2105. } // MainNOOP()
  2106. BOOL
  2107. SiteDIRSTYLE(
  2108. LPUSER_DATA pUserData,
  2109. LPSTR pszArg
  2110. )
  2111. /*++
  2112. This function implements DIRSTYLE command - used for getting site specific
  2113. directory style. It also toggles the style
  2114. Format: DIRSTYLE
  2115. Arguments:
  2116. pUserData - the user initiating the request.
  2117. pszArg - command arguments. Will be NULL if no arguments present.
  2118. Returns:
  2119. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  2120. --*/
  2121. {
  2122. const CHAR * pszResponse = NULL;
  2123. DBG_ASSERT( pUserData != NULL );
  2124. DBG_ASSERT( pszArg == NULL );
  2125. //
  2126. // Toggle the dir output flag.
  2127. //
  2128. if( TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) )
  2129. {
  2130. CLEAR_UF( pUserData, MSDOS_DIR_OUTPUT );
  2131. pszResponse = PSZ_RESPONSE_OFF;
  2132. }
  2133. else
  2134. {
  2135. SET_UF( pUserData, MSDOS_DIR_OUTPUT );
  2136. pszResponse = PSZ_RESPONSE_ON;
  2137. }
  2138. DBG_ASSERT( pszResponse != NULL );
  2139. ReplyToUser(pUserData,
  2140. REPLY_COMMAND_OK,
  2141. PSZ_MSDOS_DIRSTYLE,
  2142. pszResponse );
  2143. return TRUE;
  2144. } // SiteDIRSTYLE()
  2145. BOOL
  2146. SiteCKM(
  2147. LPUSER_DATA pUserData,
  2148. LPSTR pszArg
  2149. )
  2150. /*++
  2151. This function implements CKM command - used for getting site specific
  2152. Annotate Directories flag. It also toggles the flag.
  2153. Format: CKM
  2154. Arguments:
  2155. pUserData - the user initiating the request.
  2156. pszArg - command arguments. Will be NULL if no arguments present.
  2157. Returns:
  2158. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  2159. --*/
  2160. {
  2161. const CHAR * pszResponse = NULL;
  2162. DBG_ASSERT( pUserData != NULL );
  2163. DBG_ASSERT( pszArg == NULL );
  2164. //
  2165. // Toggle the directory annotation flag.
  2166. //
  2167. if( TEST_UF( pUserData, ANNOTATE_DIRS ) )
  2168. {
  2169. CLEAR_UF( pUserData, ANNOTATE_DIRS );
  2170. pszResponse = PSZ_RESPONSE_OFF;
  2171. }
  2172. else
  2173. {
  2174. SET_UF( pUserData, ANNOTATE_DIRS );
  2175. pszResponse = PSZ_RESPONSE_ON;
  2176. }
  2177. DBG_ASSERT( pszResponse != NULL );
  2178. ReplyToUser(pUserData,
  2179. REPLY_COMMAND_OK,
  2180. PSZ_DIRECTORY_ANNOTATION,
  2181. pszResponse );
  2182. return TRUE;
  2183. } // SiteCKM()
  2184. BOOL
  2185. ParseStringIntoAddress(
  2186. LPSTR pszString,
  2187. LPIN_ADDR pinetAddr,
  2188. LPPORT pport
  2189. )
  2190. /*++
  2191. Parse a comma-separated list of six decimal numbers
  2192. into an IP address and a port number. The address and the port are
  2193. in network byte order ( most significant bytes first).
  2194. Arguments:
  2195. pszString - string to be parsed. Should be of the form:
  2196. dd,dd,dd,dd,dd,dd where 'dd' us the decimal representation
  2197. of a byte (0-255)
  2198. pinetAddr - will receive the IP Address
  2199. pport - will receive the port.
  2200. Returns:
  2201. BOOL - TRUE if arguments are OK. FALSE if syntax error.
  2202. --*/
  2203. {
  2204. INT i;
  2205. UCHAR chBytes[6];
  2206. UCHAR chSum;
  2207. chSum = 0;
  2208. i = 0;
  2209. while( *pszString != '\0' )
  2210. {
  2211. UCHAR chCurrent = (UCHAR)*pszString++;
  2212. if( ( chCurrent >= '0' ) && ( chCurrent <= '9' ) )
  2213. {
  2214. chSum = ( chSum * 10 ) + chCurrent - '0';
  2215. }
  2216. else
  2217. if( ( chCurrent == ',' ) && ( i < 5 ) )
  2218. {
  2219. chBytes[i++] = chSum;
  2220. chSum = 0;
  2221. }
  2222. else
  2223. {
  2224. return FALSE;
  2225. }
  2226. }
  2227. chBytes[i] = chSum;
  2228. if( i != 5 )
  2229. {
  2230. return FALSE;
  2231. }
  2232. pinetAddr->S_un.S_un_b.s_b1 = chBytes[0];
  2233. pinetAddr->S_un.S_un_b.s_b2 = chBytes[1];
  2234. pinetAddr->S_un.S_un_b.s_b3 = chBytes[2];
  2235. pinetAddr->S_un.S_un_b.s_b4 = chBytes[3];
  2236. *pport = (PORT)( chBytes[4] + ( chBytes[5] << 8 ) );
  2237. return TRUE;
  2238. } // ParseStringIntoAddress()
  2239. /*******************************************************************
  2240. NAME: ReceiveFileFromUserAndClose
  2241. SYNOPSIS: Worker function for STOR, STOU, and APPE commands.
  2242. Will establish a connection via the (new) data
  2243. socket, then receive a file over that socket.
  2244. ENTRY: pUserData - The user initiating the request.
  2245. pszFileName - The name of the file to receive.
  2246. phFile - An handle to the file being received.
  2247. This handle is closed before this
  2248. routine returns.
  2249. Returns:
  2250. Win32 Error codes (or socket errors) as DWORD
  2251. HISTORY:
  2252. KeithMo 16-Mar-1993 Created.
  2253. MuraliK 05-April-1995 Dont free hFile here +
  2254. Alloc IoTransBuffer locally
  2255. ********************************************************************/
  2256. DWORD
  2257. ReceiveFileFromUserAndClose(
  2258. LPUSER_DATA pUserData,
  2259. LPSTR pszFileName,
  2260. LPHANDLE phFile
  2261. )
  2262. {
  2263. BOOL fResult;
  2264. DWORD cbRead = 0;
  2265. DWORD cbWritten;
  2266. DWORD dwError;
  2267. SOCKET DataSocket;
  2268. LPVOID IoTransferBuffer;
  2269. DWORD dwNumThreads;
  2270. DBG_ASSERT( pUserData != NULL );
  2271. DBG_ASSERT( pszFileName != NULL );
  2272. DBG_ASSERT( *phFile != INVALID_HANDLE_VALUE );
  2273. //
  2274. // We're about to make a blocking call to SockRecv(), so increment the # of threads in the
  2275. // ATQ thread pool.
  2276. // The decision whether to process this request is made based on how many threads are already
  2277. // blocked in a synchronous call. We do it after accepting/establishing the
  2278. // data connection, so that the clients's connect will succeed in the non-passive case, and
  2279. // then have the request aborted. This prevents clients hangs.
  2280. //
  2281. dwNumThreads = InterlockedIncrement( (long *) &g_ThreadsBlockedInSyncCalls );
  2282. if (dwNumThreads > g_MaxThreadsBlockedInSyncCalls)
  2283. {
  2284. //
  2285. // Blow away the data connection
  2286. //
  2287. InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );
  2288. dwError = ERROR_NOT_ENOUGH_MEMORY;
  2289. DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));
  2290. goto RecevieFileFromUser_exit;
  2291. }
  2292. AtqSetInfo( AtqIncMaxPoolThreads, 0 );
  2293. //
  2294. // Allocate an i/o buffer if not already allocated.
  2295. //
  2296. IoTransferBuffer = TCP_ALLOC( g_SocketBufferSize );
  2297. if( IoTransferBuffer == NULL ) {
  2298. ReplyToUser(pUserData,
  2299. REPLY_LOCAL_ERROR,
  2300. PSZ_INSUFFICIENT_RESOURCES);
  2301. InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );
  2302. AtqSetInfo( AtqDecMaxPoolThreads, 0 );
  2303. dwError = ERROR_NOT_ENOUGH_MEMORY;
  2304. goto RecevieFileFromUser_exit;
  2305. }
  2306. //
  2307. // Blast the file from the user to a local file.
  2308. //
  2309. DataSocket = pUserData->QueryDataSocket();
  2310. for( ; ; )
  2311. {
  2312. //
  2313. // Read a chunk from the socket.
  2314. //
  2315. dwError = SockRecv( pUserData,
  2316. DataSocket,
  2317. IoTransferBuffer,
  2318. g_SocketBufferSize,
  2319. &cbRead );
  2320. if( TEST_UF( pUserData, OOB_DATA ) ||
  2321. ( dwError != NO_ERROR ) || ( cbRead == 0 ) )
  2322. {
  2323. //
  2324. // Socket error during read or end of file or transfer aborted.
  2325. //
  2326. break;
  2327. }
  2328. pUserData->IncrementCbRecvd( cbRead);
  2329. //
  2330. // Write the current buffer to the local file.
  2331. //
  2332. fResult = WriteFile( *phFile,
  2333. IoTransferBuffer,
  2334. cbRead,
  2335. &cbWritten,
  2336. NULL );
  2337. if( !fResult )
  2338. {
  2339. dwError = GetLastError();
  2340. break;
  2341. }
  2342. }
  2343. if ( TEST_UF( pUserData, OOB_DATA)) {
  2344. dwError = ERROR_OPERATION_ABORTED;
  2345. }
  2346. IF_DEBUG( COMMANDS )
  2347. {
  2348. if( !fResult )
  2349. {
  2350. DBGPRINTF(( DBG_CONTEXT,
  2351. "cannot write file %s, error %lu\n",
  2352. pszFileName,
  2353. dwError ));
  2354. } else if( dwError != NO_ERROR ) {
  2355. DBGPRINTF(( DBG_CONTEXT,
  2356. "cannot read data from client, error %d\n",
  2357. dwError ));
  2358. }
  2359. if( TEST_UF( pUserData, OOB_DATA ) )
  2360. {
  2361. DBGPRINTF(( DBG_CONTEXT,
  2362. "transfer aborted by client\n" ));
  2363. }
  2364. }
  2365. //
  2366. // Close file handle before disconnecting from client. This is to serialize
  2367. // requests. If we disconnect first, then an append to this file may follow
  2368. // which may result in a sharing violation of the file (if this write has
  2369. // not been flushed and closed yet).
  2370. //
  2371. DBG_REQUIRE( CloseHandle( *phFile ) );
  2372. *phFile = INVALID_HANDLE_VALUE;
  2373. //
  2374. // Disconnect from client.
  2375. //
  2376. DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));
  2377. if( IoTransferBuffer != NULL )
  2378. {
  2379. TCP_FREE( IoTransferBuffer );
  2380. IoTransferBuffer = NULL;
  2381. }
  2382. if ( dwError == NO_ERROR) {
  2383. pUserData->QueryInstance()->QueryStatsObj()->IncrTotalFilesReceived();
  2384. }
  2385. InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );
  2386. AtqSetInfo( AtqDecMaxPoolThreads, 0 );
  2387. RecevieFileFromUser_exit:
  2388. if( *phFile != INVALID_HANDLE_VALUE ) {
  2389. CloseHandle( *phFile );
  2390. *phFile = INVALID_HANDLE_VALUE;
  2391. }
  2392. return (dwError);
  2393. } // ReceiveFileFromUserAndClose()
  2394. /*******************************************************************
  2395. NAME: MyLogonUser
  2396. SYNOPSIS: Validates a user's credentials, then sets the
  2397. impersonation for the current thread. In effect,
  2398. the current thread "becomes" the user.
  2399. ENTRY: pUserData - The user initiating the request.
  2400. pszPassword - The user's password. May be NULL.
  2401. pfAsGuest - Will receive TRUE if the user was validated
  2402. with guest privileges.
  2403. pfHomeDirFailure - Will receive TRUE if the user failed
  2404. to logon because the home directory was inaccessible.
  2405. pfLicenseExceeded - Will receive TRUE if the logon
  2406. was denied due to license restrictions.
  2407. RETURNS: BOOL - If user validated & impersonation was
  2408. successful, returns TRUE. Otherwise returns
  2409. TRUE.
  2410. HISTORY:
  2411. KeithMo 18-Mar-1993 Created.
  2412. ********************************************************************/
  2413. BOOL
  2414. MyLogonUser(
  2415. LPUSER_DATA pUserData,
  2416. LPSTR pszPassword,
  2417. LPBOOL pfAsGuest,
  2418. LPBOOL pfHomeDirFailure,
  2419. LPBOOL pfLicenseExceeded
  2420. )
  2421. {
  2422. BOOL fReturn = TRUE;
  2423. DWORD dwUserAccess;
  2424. TS_TOKEN UserToken;
  2425. BOOL fAsAnonymous;
  2426. BOOL fAsAnonymous2;
  2427. BOOL fEmptyPassword;
  2428. const CHAR * pszUser;
  2429. //
  2430. // Validate parameters & state.
  2431. //
  2432. DBG_ASSERT( pUserData != NULL );
  2433. DBG_ASSERT( pUserData->UserToken == NULL );
  2434. DBG_ASSERT( pfAsGuest != NULL );
  2435. DBG_ASSERT( pfHomeDirFailure != NULL );
  2436. DBG_ASSERT( pfLicenseExceeded != NULL );
  2437. //
  2438. // Setup.
  2439. //
  2440. *pfAsGuest = FALSE;
  2441. *pfHomeDirFailure = FALSE;
  2442. *pfLicenseExceeded = FALSE; // NOT YET SUPPORTED IN GHIA APIS!
  2443. fEmptyPassword = ( pszPassword == NULL ) || ( *pszPassword == '\0' );
  2444. pszUser = pUserData->QueryUserName();
  2445. DBG_ASSERT( pszUser != NULL );
  2446. DBG_ASSERT( *pszUser != '\0' );
  2447. //
  2448. // Check for invalid logon type.
  2449. //
  2450. fAsAnonymous = TEST_UF( pUserData, ANONYMOUS);
  2451. if( !pUserData->QueryInstance()->IsAllowedUser(fAsAnonymous)) {
  2452. // conflict between what is allowed and type of the client.
  2453. SetLastError( ERROR_LOGON_FAILURE);
  2454. return FALSE;
  2455. }
  2456. //
  2457. // Check for anonymous logon.
  2458. //
  2459. if( fAsAnonymous )
  2460. {
  2461. //
  2462. // At this point, we could copy the password specified by the
  2463. // user into the pUserData->UserName field. There's a convention
  2464. // among Internetters that the password specified for anonymous
  2465. // logon should actually be your login name. So, if we wanted
  2466. // honor this convention, we could copy the password into the
  2467. // pUserData->UserName field so the Administration UI
  2468. // could display it.
  2469. //
  2470. // If the user didn't enter a password, we'll just copy over
  2471. // "Anonymous" so we'll have SOMETHING to display...
  2472. //
  2473. pUserData->SetUserName( fEmptyPassword ? PSZ_ANONYMOUS_NAME : pszPassword);
  2474. //
  2475. // TsLogon User will logon as anonymous only when we specify the
  2476. // UserName == NULL and pszPassword == NULL.
  2477. //
  2478. pszUser = NULL;
  2479. pszPassword = NULL;
  2480. }
  2481. //
  2482. // Do that logon thang.
  2483. //
  2484. pUserData->QueryInstance()->LockConfig();
  2485. // dumb TsLogonUser() does not take const CHAR * for pszUser :(
  2486. UserToken = TsLogonUser( (CHAR *) pszUser,
  2487. pszPassword,
  2488. pfAsGuest,
  2489. &fAsAnonymous2,
  2490. pUserData->QueryInstance(),
  2491. pUserData->QueryInstance()->QueryAuthentInfo() );
  2492. pUserData->QueryInstance()->UnLockConfig();
  2493. //
  2494. // Recheck the logon requirements, just in case the user is trying
  2495. // to do something tricky, like logon with the special IUSR_xxx
  2496. // account name.
  2497. //
  2498. if( UserToken != NULL &&
  2499. !pUserData->QueryInstance()->IsAllowedUser(fAsAnonymous2) ) {
  2500. TsDeleteUserToken( UserToken );
  2501. UserToken = NULL;
  2502. SetLastError( ERROR_LOGON_FAILURE );
  2503. }
  2504. if( UserToken != NULL ) {
  2505. // reset it again even if it was anonymous
  2506. pszUser = pUserData->QueryUserName();
  2507. //
  2508. // Save away the impersonation token so we can delete
  2509. // it when the user disconnects or this client thread
  2510. // otherwise terminates.
  2511. //
  2512. pUserData->UserToken = UserToken;
  2513. //
  2514. // User validated, now impersonate.
  2515. //
  2516. if( !pUserData->ImpersonateUser()) {
  2517. //
  2518. // Impersonation failure.
  2519. //
  2520. IF_DEBUG( ERROR) {
  2521. DBGPRINTF(( DBG_CONTEXT,
  2522. "Impersonate User %08x failed. Error=%lu\n",
  2523. UserToken, GetLastError()));
  2524. }
  2525. fReturn = FALSE;
  2526. } else {
  2527. //
  2528. // We're now running in the context of the connected user.
  2529. // Check the user's access to the FTP Server.
  2530. //
  2531. dwUserAccess = DetermineUserAccess(pUserData->QueryInstance());
  2532. if( dwUserAccess == 0 ) {
  2533. //
  2534. // User cannot access the FTP Server.
  2535. //
  2536. IF_DEBUG( SECURITY ) {
  2537. DBGPRINTF(( DBG_CONTEXT,
  2538. "user %s denied FTP access\n",
  2539. pszUser ));
  2540. }
  2541. fReturn = FALSE;
  2542. } else {
  2543. const CHAR * apszSubStrings[2];
  2544. DWORD eventId = 0;
  2545. apszSubStrings[0] = pszUser;
  2546. pUserData->Flags &= ~( UF_READ_ACCESS | UF_WRITE_ACCESS );
  2547. pUserData->Flags |= dwUserAccess;
  2548. IF_DEBUG( SECURITY ) {
  2549. CHAR * pszTmp = NULL;
  2550. if( TEST_UF( pUserData, READ_ACCESS ) ) {
  2551. pszTmp = ( TEST_UF( pUserData, WRITE_ACCESS )
  2552. ? "read and write"
  2553. : "read"
  2554. );
  2555. } else {
  2556. DBG_ASSERT( TEST_UF( pUserData, WRITE_ACCESS ) );
  2557. pszTmp = "write";
  2558. }
  2559. DBG_ASSERT( pszTmp != NULL );
  2560. DBGPRINTF(( DBG_CONTEXT,
  2561. "user %s granted %s FTP access\n",
  2562. pszUser,
  2563. pszTmp ));
  2564. }
  2565. //
  2566. // Try to CD to the user's home directory. Note that
  2567. // this is VERY important for setting up some of the
  2568. // "virtual current directory" structures properly.
  2569. //
  2570. if( pUserData->CdToUsersHomeDirectory( PSZ_ANONYMOUS_NAME)
  2571. != NO_ERROR ) {
  2572. //
  2573. // Home directory inaccessible.
  2574. //
  2575. eventId = FTPD_EVENT_BAD_HOME_DIRECTORY;
  2576. } else if (fAsAnonymous &&
  2577. pUserData->QueryInstance()->QueryLogAnonymous() &&
  2578. !fEmptyPassword ) {
  2579. //
  2580. // If this is an anonymous user, and we're to log
  2581. // anonymous logons, OR if this is not an anonymous
  2582. // user, and we're to log nonanonymous logons, then
  2583. // do it.
  2584. //
  2585. // Note that we DON'T log the logon if the user is
  2586. // anonymous but specified no password.
  2587. //
  2588. eventId = FTPD_EVENT_ANONYMOUS_LOGON;
  2589. } else if (!fAsAnonymous &&
  2590. pUserData->QueryInstance()->QueryLogNonAnonymous()
  2591. ) {
  2592. DBG_ASSERT( *pszUser != '\0');
  2593. eventId = FTPD_EVENT_NONANONYMOUS_LOGON;
  2594. }
  2595. //
  2596. // Log an event so the poor admin can figure out
  2597. // what's going on.
  2598. //
  2599. switch ( eventId) {
  2600. case FTPD_EVENT_ANONYMOUS_LOGON:
  2601. case FTPD_EVENT_NONANONYMOUS_LOGON:
  2602. apszSubStrings[1] = inet_ntoa( pUserData->HostIpAddress);
  2603. g_pInetSvc->LogEvent( eventId,
  2604. 2,
  2605. apszSubStrings,
  2606. 0 );
  2607. break;
  2608. case FTPD_EVENT_BAD_HOME_DIRECTORY:
  2609. pUserData->QueryInstance()->LockThisForRead();
  2610. apszSubStrings[1] = pUserData->QueryInstance()->QueryRoot();
  2611. *pfHomeDirFailure = TRUE;
  2612. g_pInetSvc->LogEvent( eventId,
  2613. 2,
  2614. apszSubStrings,
  2615. 0 );
  2616. pUserData->QueryInstance()->UnlockThis();
  2617. fReturn = FALSE; // bad directory is a failure.
  2618. break;
  2619. default:
  2620. // do nothing
  2621. break;
  2622. } // switch
  2623. } // user Access Succeeded
  2624. pUserData->RevertToSelf(); // get out the impersonation
  2625. } // Impersonation succeeded.
  2626. } else {
  2627. fReturn = FALSE;
  2628. }
  2629. //
  2630. // Determine if we logged in with guest access, and
  2631. // if so, if guest access is allowed in our server.
  2632. //
  2633. if( *pfAsGuest && !pUserData->QueryInstance()->AllowGuestAccess() ) {
  2634. TsDeleteUserToken( pUserData->QueryUserToken() );
  2635. pUserData->UserToken = NULL;
  2636. fReturn = FALSE;
  2637. }
  2638. //
  2639. // Success!
  2640. //
  2641. return ( fReturn);
  2642. } // MyLogonUser()
  2643. DWORD
  2644. DetermineUserAccess(FTP_SERVER_INSTANCE *pInstance)
  2645. /*++
  2646. This function determines the current user's access to FTP server.
  2647. This is done by testing different RegOpenKey APIs against
  2648. the FTPD_ACCESS_KEY. This key (if it exists) will be "under" the
  2649. FTPD_PARAMETERS_KEY key.
  2650. Arguments:
  2651. None
  2652. Returns:
  2653. DWORD -- will be an OR Combination of UF_READ_ACCESS and UF_WRITE_ACCESS.
  2654. IF this is zero, then the user cannot access FTP server.
  2655. History:
  2656. KeithMo 06-May-1993 Created.
  2657. MuraliK 24-July-1995 Call this function with Impersonation.
  2658. NYI: Improve performance by avoiding reg opens per connection....
  2659. --*/
  2660. {
  2661. DWORD dwAccess = 0;
  2662. HKEY hkey;
  2663. APIERR err;
  2664. //
  2665. // Test for read access.
  2666. //
  2667. err = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
  2668. FTPD_ACCESS_KEY,
  2669. 0,
  2670. KEY_READ,
  2671. &hkey );
  2672. if( err == NO_ERROR )
  2673. {
  2674. //
  2675. // Success.
  2676. //
  2677. dwAccess |= UF_READ_ACCESS;
  2678. RegCloseKey( hkey );
  2679. }
  2680. else
  2681. if( err == ERROR_FILE_NOT_FOUND )
  2682. {
  2683. //
  2684. // Key doesn't exist.
  2685. //
  2686. dwAccess |= UF_READ_ACCESS;
  2687. }
  2688. //
  2689. // Test for write access.
  2690. //
  2691. err = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
  2692. FTPD_ACCESS_KEY,
  2693. 0,
  2694. KEY_WRITE,
  2695. &hkey );
  2696. if( err == NO_ERROR )
  2697. {
  2698. //
  2699. // Success.
  2700. //
  2701. dwAccess |= UF_WRITE_ACCESS;
  2702. RegCloseKey( hkey );
  2703. }
  2704. else
  2705. if( err == ERROR_FILE_NOT_FOUND )
  2706. {
  2707. //
  2708. // Key doesn't exist.
  2709. //
  2710. dwAccess |= UF_WRITE_ACCESS;
  2711. }
  2712. return dwAccess;
  2713. } // DetermineUserAccess()
  2714. /************************ End Of File ************************/