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.

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