/**********************************************************************/
/**                       Microsoft Windows NT                       **/
/**                Copyright(c) Microsoft Corp., 1993-1995           **/
/**********************************************************************/

/*
    engine.cxx

    Command parser & execution for FTPD Service.  This module parses
    and executes the commands received from the control socket.

    Functions exported by this module:
      All function for processing FTP commands

    FILE HISTORY:
        KeithMo     07-Mar-1993 Created.
        MuraliK     21-March-1995  Modified to use common TsLogonUser()
                                      supporting anonymous logon.
        MuraliK     27-Mar - April 1995
              Cleaning up FTP server engine for
                 - to support new Internet services interface.
                 - to support Async Io Transfers
                 - split and move the command tables to ftpcmd.cxx
                 - to replace USER_DATA::DataSocket with
                       USER_DATA::m_AioDataConnection
                 - moved SiteSTATS and statistics to ftpcmd.cxx
                 - replaced SockReply2 with ReplyToUser()
                 - modified MainREIN and MainQUIT to use
                       new USER_DATA functions.
                 - modified usage of listing functions.
                 - SendErrorToClient() used for managing and sending
                       error message to client.
                 - message strings removed to be top of the file and defined.
                 - size of file sent along before file is transferred.
        terryk      18-Sep-1996 add MainSize
*/

#include "ftpdp.hxx"
#include "ftpcmd.hxx"
#include <stdlib.h>
#include <limits.h>

//
//  Private constants.
//


//
// Since the default RFC port for ftp is 21, we calculate the Data port
//   value from it.
// However, an admin at a site may change the port number to something else
//  ==> this will create PROBLEMS..... NYI
//

/**************************************************
 *  Constant Message Strings used by this Module
 **************************************************/

//
// Below is a list of strings, which are in
//   CStrM( StringName,  ActualString)  format
// The following table contains globally all strings used within this module
//  Advantage:  One place to look for various strings
//               + possible internationalization
//  Be careful. None of these messages may be modified. Protocol may break.
//  This will be expanded into
//  const char  PSZ_StringName[] = ActualString;   and
//  enumerated value  LEN_StringName = sizeof( ActualString).
//
# define ConstantStringsForThisModule()            \
  CStrM( CANNOT_OPEN_DATA_CONNECTION, "Can't open data connection.")   \
  CStrM( TRANSFER_ABORTED,    "Connection closed; transfer aborted.")  \
  CStrM( USER_NOT_LOGGED_IN,          "User %s cannot log in.")        \
  CStrM( INSUFFICIENT_RESOURCES, "Insufficient system resources.")     \
  CStrM( REQUEST_ID_FOR_ANONYMOUS,                                     \
         "Anonymous access allowed, send identity (e-mail name) as password.")\
  CStrM( REQUEST_PASSWORD_FOR_USER,  "Password required for %s.")      \
  CStrM( COMMAND_NOT_IMPLEMENTED,    "%s command not implemented." )   \
  CStrM( COMMAND_SUCCESSFUL,         "%s command successful." )        \
  CStrM( SERVICE_READY,              "Service ready for new user.")    \
  CStrM( ENTERING_PASSIVE_MODE,      \
        "Entering Passive Mode (%d,%d,%d,%d,%d,%d).")                  \
  CStrM( FORM_MESSAGE,                "Form must be N or T." )         \
  CStrM( TYPE_NOT_IMPLEMENTED,        "Type %c not implemented.")      \
  CStrM( BYTE_SIZE_SPEC,              "Byte size must be 8." )         \
  CStrM( TYPE_SET_TO,                 "Type set to %c.")               \
  CStrM( UNIMPLEMENTED_STRU_TYPE,     "Unimplemented STRU type." )     \
  CStrM( INVALID_STRU_TYPE,           "Invalid STRU type." )     \
  CStrM( STRU_TYPE_OK,                "STRU %c ok.")                   \
  CStrM( UNIMPLEMENTED_MODE,          "Unimplemented MODE type.")      \
  CStrM( MODE_OK,                     "Mode %c ok.")                   \
  CStrM( REPLY_MARKER_SPEC,           "Reply marker is invalid.")       \
  CStrM( REPLY_RESTARTING,            "Restarting at %s." )             \
  CStrM( READY_FOR_DEST_FILE, "File exists, ready for destination name" )   \
  CStrM( BAD_COMMAND_SEQUENCE,        "Bad sequence of commands." )    \
  CStrM( CURRENT_DIRECTORY,           "\"%s\" is current directory.")  \
  CStrM( VERSION_INFO,                "Windows_NT")                    \
  CStrM( SERVER_STATUS_BEGIN,         " %s Windows NT FTP Server status:")  \
  CStrM( SERVER_STATUS_END,           "End of status.")                \
  CStrM( FILE_STATUS,                 "status of %s:")                 \
  CStrM( MSDOS_DIRSTYLE,              "MSDOS-like directory output is %s") \
  CStrM( DIRECTORY_ANNOTATION,        "directory annotation is %s")    \
  CStrM( LOGGED_IN_USER_MESSAGE,      "Anonymous user logged in%s.") \
  CStrM( USER_LOGGED_IN,              "User %s logged in%s.")          \
  CStrM( USER_CANNOT_LOG_IN,          "User %s cannot log in.")        \
  CStrM( INACCESSIBLE_HOME_DIR,                                        \
        "User %s cannot log in, home directory inaccessible.")         \
  CStrM( LICENSE_QUOTA_EXCEEDED,                                       \
        "User %s cannot log in, license quota exceeded.")              \
  CStrM( NO_GUEST_ACCESS,                                              \
        "User %s cannot log in, guest access not allowed.")            \
  CStrM( ANONYMOUS_NAME,              "Anonymous")                     \
  CStrM( FTP_NAME,                    "Ftp")                           \
  CStrM( ARGS_DELIMITER,              " \t")                           \
  CStrM( NO_FILE_OR_DIRECTORY,        "No such file or directory.")    \
  CStrM( DIRECTORY_CREATE,            "\"%s\" directory created.")     \
  CStrM( CANNOT_CREATE_FILE,          "Cannot create file.")           \
  CStrM( CANNOT_CREATE_UNIQUE_FILE,   "Cannot create unique file.")    \
  CStrM( INVALID_COMMAND,             "Invalid %s Command.")           \
  CStrM( RESPONSE_ON,                 "on")                            \
  CStrM( RESPONSE_OFF,                "off")                           \
  CStrM( GUEST_ACCESS,                " (guest access)" )              \
  CStrM( CREATE_VERB,                 "created" )                      \
  CStrM( APPEND_VERB,                 "appended" )                     \
  CStrM( USER_VERB,                   "USER" )                         \
  CStrM( PASSWORD_VERB,               "PASS" )                         \
  CStrM( QUIT_VERB,                   "QUIT" )                         \
  CStrM( ABORT_VERB,                  "ABORT" )                        \
  CStrM( REIN_VERB,                   "REIN" )                         \
  CStrM( DESTINATION_FILE_EXISTS,     "Destination file already exists.") \
  CStrM( RNFR_VERB,                   "RNFR" )                         \
  CStrM( RNTO_VERB,                   "RNTO" )                         \
  CStrM( DELE_VERB,                   "DELE" )                         \
  CStrM( RMD_VERB,                    "RMD" )                          \
  CStrM( MKD_VERB,                    "MKD" )                          \
  CStrM( DUMMY_END,  "DummyMsg. Add string before this one")


//
// Generate the strings ( these are private globals of this module).
//

# define CStrM( StringName, ActualString)   \
     const char PSZ_ ## StringName[] = ActualString ;

ConstantStringsForThisModule()

# undef CStrM



//
//  Private prototypes.
//

BOOL
ParseStringIntoAddress(
    LPSTR     pszString,
    LPIN_ADDR pinetAddr,
    LPPORT    pport
    );

DWORD
ReceiveFileFromUserAndClose(
    LPUSER_DATA pUserData,
    LPSTR       pszFileName,
    LPHANDLE    phFile
    );

BOOL
MyLogonUser(
    LPUSER_DATA pUserData,
    LPSTR       pszPassword,
    LPBOOL      pfAsGuest,
    LPBOOL      pfHomeDirFailure,
    LPBOOL      pfLicenseExceeded
    );

DWORD
DetermineUserAccess(FTP_SERVER_INSTANCE *pInstance);

//
//  Public functions.
//



//
//  Functions Implementing FTP functionality.
//


BOOL
MainUSER(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  Implements the USER command.
  Format:    USER <userName>

  Arguments:
    pUserData  - the user initiating the request.
    pszArg     - Command arguments. Will be NULL if no arguments given.

  Returns:
    BOOL - TRUE if arguments are OK; FALSE if syntax error.
--*/
{
    DBG_ASSERT( pUserData != NULL );

    LPFTP_SERVER_STATISTICS pStatsObj = pUserData->QueryInstance()->QueryStatsObj();

    DBG_ASSERT( pStatsObj != NULL );

    if( pUserData->IsLoggedOn()) {

        if( TEST_UF( pUserData, ANONYMOUS ) ) {
            pStatsObj->DecrCurrentAnonymousUsers();
        } else {
            pStatsObj->DecrCurrentNonAnonymousUsers();
        }

        SET_UF( pUserData, LOGGED_ON);
    }

    //
    //  Squirrel away a copy of the domain\user name for later.
    //  If the name is too long, then don't let them logon.
    //

    if ( strlen( pszArg ) >= ( MAX_USERNAME_LENGTH ) ) {

        ReplyToUser(pUserData,
                    REPLY_NOT_LOGGED_IN,
                    PSZ_USER_NOT_LOGGED_IN,
                    pszArg );

    } else {

        BOOL   fNameIsAnonymous;
        LPCSTR pszReply;

        fNameIsAnonymous = ( ( _stricmp( pszArg, PSZ_ANONYMOUS_NAME ) == 0 ) ||
                            ( _stricmp( pszArg, PSZ_FTP_NAME ) == 0 )
                            );

        pUserData->SetUserName( pszArg );

        if( fNameIsAnonymous ) {

            SET_UF( pUserData, ANONYMOUS );
        } else {

            CLEAR_UF( pUserData, ANONYMOUS );
        }

        //
        // remember that we're waiting for PASS command in case we
        // get disconnected.
        //
        SET_UF( pUserData, WAIT_PASS );

        //
        //  If we already have an impersonation token, then remove it.
        //  This will allow us to impersonate the new user.
        //

        pUserData->FreeUserToken();

        //
        //  Tell the client that we need a password.
        //

        pszReply =(((fNameIsAnonymous) && pUserData->QueryInstance()->AllowAnonymous())
                   ? PSZ_REQUEST_ID_FOR_ANONYMOUS
                   : PSZ_REQUEST_PASSWORD_FOR_USER);

        ReplyToUser( pUserData,
                    REPLY_NEED_PASSWORD,
                    pszReply,
                    pszArg);

        pUserData->LockUser();
        if( pUserData->QueryState() != UserStateDisconnected ) {
            pUserData->SetState( UserStateWaitingForPass);
        }
        pUserData->UnlockUser();
    }

    pUserData->WriteLogRecord( PSZ_USER_VERB, pszArg);

    return TRUE;

}   // MainUSER()



BOOL
MainPASS(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  Implements the PASS command.
  Format:  PASS <password>

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.

--*/
{
    DWORD dwError = NO_ERROR;

    DBG_ASSERT( pUserData != NULL );

    //
    //  PASS command only valid in WaitingForPass state.
    //

    DBG_ASSERT( pUserData->QueryState() == UserStateWaitingForPass );

    if( ( pszArg != NULL ) && ( strlen( pszArg ) > PWLEN ) ) {

        return FALSE;
    }

    //
    //  Try to logon the user.
    //
    BOOL   fAsGuest;
    BOOL   fHomeDirFailure;
    BOOL   fLicenseExceeded;

    DBG_ASSERT( pUserData->QueryUserToken() == NULL );

    //
    // we got the PASS command we we're waiting for
    //
    CLEAR_UF( pUserData, WAIT_PASS );

    pUserData->QueryInstance()->QueryStatsObj()->IncrLogonAttempts();

    if( MyLogonUser(pUserData,
                    pszArg,
                    &fAsGuest,
                    &fHomeDirFailure,
                    &fLicenseExceeded )
       ) {

        const CHAR * pszGuestAccess =
          ( ( fAsGuest) ? PSZ_GUEST_ACCESS : "");

        //
        //  Successful logon.
        //

        if( *pUserData->QueryUserName() != '-' )  {

            PCSTR pszMsg;

            pUserData->QueryInstance()->LockConfig();

            pszMsg = pUserData->QueryInstance()->QueryGreetingMsg();

            if( pszMsg && *pszMsg ) {

                pUserData->SendMultilineMessage(
                                             REPLY_USER_LOGGED_IN,
                                             pszMsg,
                                             TRUE,                    // first reply line
                                             FALSE);                  // lst reply line
            }

            pUserData->QueryInstance()->UnLockConfig();

            if( TEST_UF( pUserData, ANNOTATE_DIRS )) {

                pUserData->SendDirectoryAnnotation(
                                             REPLY_USER_LOGGED_IN,
                                             FALSE);                  // first reply line
            }
        }

        LPFTP_SERVER_STATISTICS pStats = pUserData->QueryInstance()->QueryStatsObj();
        DBG_ASSERT( pStats != NULL );

        if( TEST_UF( pUserData, ANONYMOUS ) ) {

            pStats->IncrAnonymousUsers();

            ReplyToUser(pUserData,
                        REPLY_USER_LOGGED_IN,
                        PSZ_LOGGED_IN_USER_MESSAGE,
                        pszGuestAccess );

        } else {

            pStats->IncrNonAnonymousUsers();

            ReplyToUser(pUserData,
                        REPLY_USER_LOGGED_IN,
                        PSZ_USER_LOGGED_IN,
                        pUserData->QueryUserName(),
                        pszGuestAccess );
        }

        pUserData->LockUser();
        if( pUserData->QueryState() != UserStateDisconnected ) {
            pUserData->SetState( UserStateLoggedOn);
            SET_UF( pUserData, LOGGED_ON);
        }
        pUserData->UnlockUser();

    } else {

        const CHAR * pszReply = PSZ_USER_CANNOT_LOG_IN;

        //
        //  Logon failure.
        //

        dwError = GetLastError();

        if( fHomeDirFailure ) {

            pszReply = PSZ_INACCESSIBLE_HOME_DIR;

        } else if ( fLicenseExceeded) {

            pszReply = PSZ_LICENSE_QUOTA_EXCEEDED;

        } else if ( fAsGuest && ! pUserData->QueryInstance()->AllowGuestAccess()) {

            pszReply = PSZ_NO_GUEST_ACCESS;
        }

        ReplyToUser(pUserData,
                    REPLY_NOT_LOGGED_IN,
                    pszReply,
                    pUserData->QueryUserName() );

        pUserData->LockUser();
        if( pUserData->QueryState() != UserStateDisconnected ) {
            pUserData->SetState( UserStateWaitingForUser);
            CLEAR_UF( pUserData, LOGGED_ON);
            pUserData->ClearUserName();
        }
        pUserData->UnlockUser();
    }

    pUserData->WriteLogRecord( PSZ_PASSWORD_VERB,
                              (TEST_UF( pUserData, ANONYMOUS)) ? pszArg : NULL,
                              dwError);

    return TRUE;

}   // MainPASS()



BOOL
MainACCT(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the ACCT command.
  This is at present not implemented...

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.

--*/
{
    DBG_ASSERT( pUserData != NULL );

    ReplyToUser(pUserData,
                REPLY_COMMAND_SUPERFLUOUS,
                PSZ_COMMAND_NOT_IMPLEMENTED,
                "ACCT");

    return TRUE;

}   // MainACCT()




BOOL
MainCWD(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the CWD command -- Change Working Directory.
  Format:  CWD <newDirectoryName>

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.

--*/
{
    APIERR err;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    //  If argument is NULL or "~", CD to home directory.
    //

    if( ( pszArg == NULL ) || ( strcmp( pszArg, "~" ) == 0 ) ) {

        err = pUserData->CdToUsersHomeDirectory( PSZ_ANONYMOUS_NAME);

    } else {

        err = VirtualChDir( pUserData, pszArg);
    }

    if( err == NO_ERROR ) {

        if( TEST_UF( pUserData, ANNOTATE_DIRS ) &&
           ( *pUserData->QueryUserName() != '-' )
           ) {

            pUserData->SendDirectoryAnnotation(
                                           REPLY_FILE_ACTION_COMPLETED,
                                           TRUE);                        // first reply line
        }

        ReplyToUser(pUserData,
                    REPLY_FILE_ACTION_COMPLETED,
                    PSZ_COMMAND_SUCCESSFUL,
                    "CWD");
    } else {

        pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
    }

    return TRUE;

}   // MainCWD()



BOOL
MainCDUP(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  CDUP -- changes to the parent directory if possible.
--*/
{
    return MainCWD( pUserData, ".." );
}   // MainCDUP


BOOL
MainSIZE(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements SIZE command - used for retrieving the size of a file.
  Format:  SIZE pathForFile

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err = (!NO_ERROR);

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn() );

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg != NULL );

    //
    //  Try to open the file.
    //

    if (pUserData->ImpersonateUser()){
	    err = pUserData->OpenFileForSend( pszArg );
		pUserData->RevertToSelf();
	}
	else
	{
		err = GetLastError();
    }

    if( err == NO_ERROR ) {

        // just return the file size
        err = pUserData->GetFileSize();
    }

    if( err != NO_ERROR ) {

        pUserData->SendErrorToClient(pszArg, err,
                                     PSZ_NO_FILE_OR_DIRECTORY);

        pUserData->WriteLogRecordForSendError( err );
    }

    pUserData->CloseFileForSend( err);  // close the file, now that we're done.
    return TRUE;

}   // MainSIZE()

BOOL
MainMDTM(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the MDTM command - used for retrieving the last
  modified time for a file. We open the file, get the file mod time, format
  it and send it.

  Format:  SIZE pathForFile

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR      err = (!NO_ERROR);
    SYSTEMTIME  SystemTime;
    CHAR        rgchBuffer[sizeof("YYYYMMDDHHMMSS")];

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn() );

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg != NULL );

    //
    //  Try to open the file.
    //

    if (pUserData->ImpersonateUser()){
	    err = pUserData->OpenFileForSend( pszArg );
		pUserData->RevertToSelf();
	}
	else
	{
		err = GetLastError();
    }

    if( err == NO_ERROR ) {

        // Get the last write time.

        err = pUserData->GetFileModTime(&SystemTime);

        if (err == NO_ERROR) {
            // Format the time.
            wsprintfA(rgchBuffer, "%.4hu%.2hu%.2hu%.2hu%.2hu%.2hu",
                        SystemTime.wYear,
                        SystemTime.wMonth,
                        SystemTime.wDay,
                        SystemTime.wHour,
                        SystemTime.wMinute,
                        SystemTime.wSecond );

            ReplyToUser( pUserData, REPLY_FILE_STATUS, rgchBuffer );
        }
    }

    if( err != NO_ERROR ) {

        pUserData->SendErrorToClient(pszArg, err,
                                     PSZ_NO_FILE_OR_DIRECTORY);

        pUserData->WriteLogRecordForSendError( err );
    }

    pUserData->CloseFileForSend( err);  // close the file, now that we're done.
    return TRUE;

}   // MainMDTM()


BOOL
MainSMNT(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the SMNT command.
  This is at present not implemented...

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.

--*/
{
    DBG_ASSERT( pUserData != NULL );

    ReplyToUser(pUserData,
                REPLY_COMMAND_SUPERFLUOUS,
                PSZ_COMMAND_NOT_IMPLEMENTED,
                "SMNT");

    return TRUE;

}   // MainSMNT()




BOOL
MainQUIT(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the QUIT command.
  Format:  QUIT

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.

--*/
{
    CHAR    rgchBuffer[MAX_REPLY_LENGTH];
    DWORD   len;
    LPCSTR  pszMsg;
    SOCKERR serr;

    DBG_ASSERT( pUserData != NULL );

    SET_UF( pUserData, CONTROL_QUIT);

    //
    //  Reply to the quit command.
    //

    pUserData->QueryInstance()->LockConfig();

    pszMsg = pUserData->QueryInstance()->QueryExitMsg();
    DBG_ASSERT( pszMsg != NULL);

    len = FtpFormatResponseMessage(REPLY_CLOSING_CONTROL,
                                   pszMsg,
                                   rgchBuffer,
                                   MAX_REPLY_LENGTH);
    pUserData->QueryInstance()->UnLockConfig();

    DBG_ASSERT( len <= MAX_REPLY_LENGTH);

    serr = SockSend( pUserData, pUserData->QueryControlSocket(),
                    rgchBuffer, len);

    //
    //  Cause a disconnection of the user.
    //  This will blow away the sockets first. Blowing off sockets
    //    will cause ATQ to wake up for pending data calls
    //    and send a call back indicating failure.
    //  Since we disconnect now, we will not submit any
    //    Reads to control socket ==> no more control calls come back from ATQ.
    //  At the call back processing we will decrement reference counts
    //    appropriate for cleanup.
    //

    pUserData->DisconnectUserWithError( NO_ERROR, FALSE);

    pUserData->WriteLogRecord( PSZ_QUIT_VERB, "");

    return TRUE;

}   // MainQUIT()



BOOL
MainREIN(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function executes REIN command - ReInitialize
  Format:  REIN

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.

--*/
{
    DBG_ASSERT( pUserData != NULL );

    pUserData->ReInitializeForNewUser();

    ReplyToUser(pUserData,
                REPLY_SERVICE_READY,
                PSZ_SERVICE_READY);

    pUserData->WriteLogRecord( PSZ_REIN_VERB, pszArg);

    return TRUE;

}   // MainREIN()



BOOL
MainPORT(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the PORT command.
  Format:  PORT <ipAddress>,<portNumber>

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.

--*/
{
    IN_ADDR DataIpAddress;
    PORT    DataPort;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Parse the string into address/port pair.
    //

    if( !ParseStringIntoAddress( pszArg,
                                 &DataIpAddress,
                                 &DataPort ) )
    {
        return FALSE;
    }

    //
    //  Determine if someone is trying to give us a bogus address/port.
    //  If the port number is less than IPPORT_RESERVED,
    //    then there is a possibility of port attack. Allow this only
    //    if port attack flag is enabled
    //

    if (!pUserData->QueryInstance()->IsEnablePortAttack())
    {
	    if ( ( DataIpAddress.s_addr != pUserData->HostIpAddress.s_addr ) ||
    	     ( DataPort != CONN_PORT_TO_DATA_PORT(pUserData->LocalIpPort) &&
        	   ntohs( DataPort ) < IPPORT_RESERVED) )
        	{

            ReplyToUser(pUserData,
                        REPLY_UNRECOGNIZED_COMMAND,
                        PSZ_INVALID_COMMAND,
                        "PORT");

            return TRUE;
        }
    }


    //
    //  Save the address/port pair into per-user data.
    //

    pUserData->DataIpAddress = DataIpAddress;
    pUserData->DataPort = DataPort;

    //
    //  Disable passive mode for this user.
    //

    CLEAR_UF( pUserData, PASSIVE );

    //
    // Nuke any open data socket.
    //
    pUserData->CleanupPassiveSocket( TRUE );

    //
    //  Let the client know we accepted the port command.
    //

    ReplyToUser(pUserData,
                REPLY_COMMAND_OK,
                PSZ_COMMAND_SUCCESSFUL,
                "PORT");

    return TRUE;

}   // MainPORT()



BOOL
MainPASV(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the PASV command - used for setting passive mode.
  Format:  PASV

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.

--*/
{
    SOCKET        DataSocket = INVALID_SOCKET;
    SOCKERR       serr  = 0;
    SOCKADDR_IN   saddrLocal;
    INT           cbLocal;

    DBG_ASSERT( pUserData != NULL );

    //
    // Nuke the old passive socket
    //
    pUserData->CleanupPassiveSocket( TRUE );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn() );

    //
    //  Create a new data socket.
    //

    serr = CreateFtpdSocket( &DataSocket,
                             pUserData->LocalIpAddress.s_addr,
                             0,
                             pUserData->QueryInstance() );

    if( serr == 0 )
    {
        //
        //  Determine the port number for the new socket.
        //

        cbLocal = sizeof(saddrLocal);

        if( getsockname( DataSocket, (SOCKADDR *)&saddrLocal, &cbLocal ) != 0 )
        {
            serr = WSAGetLastError();
        }
    }

    if( serr == 0 )
    {
        //
        //  Success!
        //

        SET_UF( pUserData, PASSIVE );
        pUserData->SetPassiveSocket( DataSocket);
        pUserData->DataIpAddress = saddrLocal.sin_addr;
        pUserData->DataPort = saddrLocal.sin_port;

        ReplyToUser(pUserData,
                    REPLY_PASSIVE_MODE,
                    PSZ_ENTERING_PASSIVE_MODE,
                    saddrLocal.sin_addr.S_un.S_un_b.s_b1,
                    saddrLocal.sin_addr.S_un.S_un_b.s_b2,
                    saddrLocal.sin_addr.S_un.S_un_b.s_b3,
                    saddrLocal.sin_addr.S_un.S_un_b.s_b4,
                    HIBYTE( ntohs( saddrLocal.sin_port ) ),
                    LOBYTE( ntohs( saddrLocal.sin_port ) ) );

    } else {

        //
        //  Failure during data socket creation/setup.  If
        //  we managed to actually create it, nuke it.
        //

        if( DataSocket != INVALID_SOCKET )
        {
            CloseSocket( DataSocket );
            DataSocket = INVALID_SOCKET;
        }

        //
        //  Tell the user the bad news.
        //

        ReplyToUser(pUserData,
                    REPLY_CANNOT_OPEN_CONNECTION,
                    PSZ_CANNOT_OPEN_DATA_CONNECTION);
    }

    return TRUE;

}   // MainPASV()





BOOL
MainTYPE(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the TYPE command - used for setting type.
  Format:  TYPE type form <addl arguments>

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    XFER_TYPE   newType;
    CHAR        chType;
    CHAR        chForm;
    LPSTR       pszToken;
    BOOL        fValidForm = FALSE;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg != NULL );

    pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER);

    if( pszToken == NULL ) {

        return FALSE;
    }

    //
    //  Ensure we got a valid form type
    //  (only type N supported).
    //

    if( pszToken[1] != '\0' ) {

        return FALSE;
    }

    chType = *pszToken;


    pszToken = strtok( NULL, PSZ_ARGS_DELIMITER );

    if( pszToken == NULL ) {

        chForm     = 'N';       // default
        fValidForm = TRUE;

    } else {

        switch( *pszToken ) {

          case 'n':
          case 'N':
            chForm     = 'N';
            fValidForm = TRUE;
            break;

          case 't':
          case 'T':
            chForm     = 'T';
            fValidForm = TRUE;
            break;

          case 'c':
          case 'C':
            chForm     = 'C';
            fValidForm = TRUE;
            break;

          default:
            fValidForm = FALSE;
            break;
        } // switch

    }

    //
    //  Determine the new transfer type.
    //

    switch( chType ) {

      case 'a':
      case 'A':
        if( !fValidForm ) {

            return FALSE;
        }

        if( ( chForm != 'N' ) && ( chForm != 'T' ) ) {

            ReplyToUser(pUserData,
                        REPLY_PARAMETER_NOT_IMPLEMENTED,
                        PSZ_FORM_MESSAGE);
            return TRUE;
        }

        newType = XferTypeAscii;
        chType  = 'A';
        break;

      case 'e':
      case 'E':
        if( !fValidForm ) {

            return FALSE;
        }

        if( ( chForm != 'N' ) && ( chForm != 'T' ) ) {

            ReplyToUser(pUserData,
                        REPLY_PARAMETER_NOT_IMPLEMENTED,
                        PSZ_FORM_MESSAGE);
            return TRUE;
        }

        ReplyToUser(pUserData,
                    REPLY_PARAMETER_NOT_IMPLEMENTED,
                    PSZ_TYPE_NOT_IMPLEMENTED, 'E');
        return TRUE;

      case 'i':
      case 'I':
        if( pszToken != NULL ) {

            return FALSE;
        }

        newType = XferTypeBinary;
        chType  = 'I';
        break;

    case 'l':
    case 'L':
        if( pszToken == NULL ) {

            return FALSE;
        }

        if( strcmp( pszToken, "8" ) != 0 ) {

            if( IsDecimalNumber( pszToken ) ) {

                ReplyToUser(pUserData,
                            REPLY_PARAMETER_NOT_IMPLEMENTED,
                            PSZ_BYTE_SIZE_SPEC);

                return TRUE;
            } else {

                return FALSE;
            }
        }

        newType = XferTypeBinary;
        chType  = 'L';
        break;

      default:
        return FALSE;
    } // switch (chType)

    IF_DEBUG( COMMANDS ) {

        DBGPRINTF(( DBG_CONTEXT,
                    "setting transfer type to %s\n",
                    TransferType( newType ) ));
    }

    pUserData->SetXferType( newType);

    ReplyToUser(pUserData,
                REPLY_COMMAND_OK,
                PSZ_TYPE_SET_TO,
                chType);

    return TRUE;

}   // MainTYPE()




BOOL
MainSTRU(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the STRU command - structure information
  Format:  STRU fileName

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    CHAR     chStruct;
    CHAR   * pszToken;

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pUserData != NULL );
    DBG_ASSERT( pszArg != NULL );

    pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER );

    if( pszToken == NULL ) {

        return FALSE;
    }

    //
    //  Ensure we got a valid structure type
    //  (only type F supported).
    //

    chStruct = *pszToken;

    if( pszToken[1] != '\0' ) {

        return FALSE;
    }

    switch( chStruct ) {

      case 'f':
      case 'F':
        chStruct = 'F';
        break;

      case 'r':
      case 'R':
      case 'p':
      case 'P':
        ReplyToUser(pUserData,
                    REPLY_PARAMETER_NOT_IMPLEMENTED,
                    PSZ_UNIMPLEMENTED_STRU_TYPE);
        return TRUE;

      default:

        return FALSE;
    }

    ReplyToUser(pUserData,
                REPLY_COMMAND_OK,
                PSZ_STRU_TYPE_OK,
                chStruct );

    return TRUE;

}   // MainSTRU()



BOOL
MainMODE(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements the MODE command - to set mode of tfr.
  Format:  MODE newMode

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    XFER_MODE   newMode;
    CHAR        chMode;
    LPSTR       pszToken;

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pUserData != NULL );
    DBG_ASSERT( pszArg != NULL );

    pszToken = strtok( pszArg, PSZ_ARGS_DELIMITER );

    if( pszToken == NULL ) {

        return FALSE;
    }

    //
    //  Ensure we got a valid mode type
    //  (only type S supported).
    //

    if( pszToken[1] != '\0' ) {

        return FALSE;
    }
    chMode = *pszToken;


    switch( chMode )
    {
    case 's' :
    case 'S' :
        newMode = XferModeStream;
        chMode  = 'S';
        break;

    case 'b' :
    case 'B' :
        ReplyToUser(pUserData,
                    REPLY_PARAMETER_NOT_IMPLEMENTED,
                    PSZ_UNIMPLEMENTED_MODE );
        return TRUE;

    default :
        return FALSE;
    }

    IF_DEBUG( COMMANDS )
    {
        DBGPRINTF(( DBG_CONTEXT,
                    "setting transfer mode to %s\n",
                    TransferMode( newMode ) ));
    }

    pUserData->SetXferMode(newMode);

    ReplyToUser(pUserData,
                REPLY_COMMAND_OK,
                PSZ_MODE_OK,
                chMode );

    return TRUE;

}   // MainMODE()



BOOL
MainRETR(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements RETR command - used for retrieving a file.
  Format:  RETR pathForFile

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
#define MAX_FILE_SIZE_SPEC (32)

    APIERR err = (!NO_ERROR);
    BOOL   fErrorSent = FALSE;
    BOOL   fTriedToOpenFile = FALSE;
    LARGE_INTEGER FileSize;
    DWORD         dwAttribs;
    TS_OPEN_FILE_INFO * pOpenFileInfo;
    CHAR rgchSize[MAX_FILE_SIZE_SPEC];
    CHAR rgchBuffer[MAX_FILE_SIZE_SPEC + 10];

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn() );

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg != NULL );

    if (pUserData->ImpersonateUser()){
	    err = pUserData->OpenFileForSend( pszArg );
		pUserData->RevertToSelf();
	}
	else
	{
		err = GetLastError();
    }

    fTriedToOpenFile = TRUE;

    if ( err != NO_ERROR ) {
        goto retr_exit;
    }

    pOpenFileInfo = pUserData->QueryOpenFileInfo();

    if ( pOpenFileInfo == NULL )
    {
        err = ERROR_FILE_NOT_FOUND;
        goto retr_exit;
    }

    //
    // Get the file size
    //

    if ( !pOpenFileInfo->QuerySize(FileSize) )
    {
        err = GetLastError();

        if ( err != NO_ERROR ) {
            goto retr_exit;
        }
    }

    if( FileSize.HighPart != 0 ) {
        //
        // we do not support files >4GB.
        //
        ReplyToUser(pUserData,
                    REPLY_FILE_NOT_ALLOWED,
                    "Cannot send file larger than 4 gigabytes." );

        fErrorSent = TRUE;
        err = ERROR_MESSAGE_EXCEEDS_MAX_SIZE;
        goto retr_exit;
    }

    // removed to fix copatibility problem with cute ftp
    // when FTP service should report always a total size of file to be
    // transfered not a remainder like it was before
    //FileSize.QuadPart -= (LONGLONG)pUserData->QueryCurrentOffset();

    IsLargeIntegerToDecimalChar( &FileSize, rgchSize);
    wsprintfA( rgchBuffer, "(%s bytes)", rgchSize);

    //
    // Establish a data connection
    //
    err = pUserData->EstablishDataConnection( pszArg, rgchBuffer );

    if ( err != NO_ERROR )
    {
        if ( err == ERROR_IO_PENDING )
        {
            //
            // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
            // client to establish a data connection - it takes care of setting up an event that
            // allows us to deal asynchronously with the client connecting [or failing to do so].
            // It indicates this asynchrony by returning ERROR_IO_PENDING
            //

            DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
                        !pUserData->QueryHavePASVConn() );

            if ( fTriedToOpenFile )
            {
                pUserData->CloseFileForSend( err);  // close it always on error
            }

            return TRUE;
        }

        fErrorSent = TRUE;

        goto retr_exit;
    }


    err = pUserData->SendFileToUser( pszArg, &fErrorSent);

    if ( err != NO_ERROR)
    {
        //
        // Disconnect connection, since we are in error.
        //
        DBG_REQUIRE( pUserData->DestroyDataConnection( err ));

        // since there was a failure we will close the handle right away.

        IF_DEBUG( ASYNC_IO) {

            DBGPRINTF( ( DBG_CONTEXT,
                        "SendFileToUser ( %s) failed"
                        " err = %u\n",
                        pszArg, err));
        }
    }

retr_exit:

    if( err != NO_ERROR )
    {
        //
        // This command failed, so drop out of PASV mode
        //
        CLEAR_UF( pUserData, PASSIVE );

        //
        // Clean up the PASV flags if necessary
        //
        if ( pUserData->QueryInFakeIOCompletion() )
        {
            pUserData->CleanupPASVFlags();
        }

        if ( !fErrorSent)
        {
            pUserData->SendErrorToClient(pszArg, err,
                                         PSZ_NO_FILE_OR_DIRECTORY);
        }

        pUserData->WriteLogRecordForSendError( err );

        if ( fTriedToOpenFile )
        {
            pUserData->CloseFileForSend( err);  // close it always on error
        }
    }

    return TRUE;

}   // MainRETR()




BOOL
MainSTOR(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements STOR command - used for storing a file.
  Format:  STOR pathForFile

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err;
    HANDLE hFile;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg != NULL );

    //
    //  Try to create the file.
    //

    err = VirtualCreateFile( pUserData,
                             &hFile,
                             pszArg,
                             FALSE );

    if( err != NO_ERROR )
    {
        pUserData->SendErrorToClient(pszArg, err, PSZ_CANNOT_CREATE_FILE);
    }
    else
    {

        //
        // Establish a connection to the user
        //
        err = pUserData->EstablishDataConnection( pszArg );

        if ( err != NO_ERROR )
        {
            CloseHandle( hFile );

            if ( err == ERROR_IO_PENDING )
            {
                //
                // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
                // client to establish a data connection - it takes care of setting up an event that
                // allows us to deal asynchronously with the client connecting [or failing to do so].
                // It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
                // any further processing
                //

                DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
                            !pUserData->QueryHavePASVConn() );


                return TRUE;
            }

            goto stor_exit;
        }


        //
        //  Let the worker do the dirty work. ( blocking call)
        //  On return, hFile is closed
        //

        err = ReceiveFileFromUserAndClose( pUserData, pszArg, &hFile );

        if( err != NO_ERROR )
        {
            VirtualDeleteFile( pUserData,
                pszArg );
        }
    }

stor_exit:

    pUserData->WriteLogRecord(PSZ_CREATE_VERB, pszArg, err);

    return TRUE;

}   // MainSTOR()



BOOL
MainSTOU(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements STOU command - used for storing in unique file.
  Format:  STOU <noArgs>

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err;
    HANDLE hFile;
    CHAR   szTmpFile[MAX_PATH]; // contains entire path
    CHAR * pszTmpFileName;  // contains only the file name

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg == NULL );

    //
    //  Try to create the file.
    //

    szTmpFile[0] = '\0';
    err = VirtualCreateUniqueFile( pUserData,
                                   &hFile,
                                   szTmpFile );


    //
    // extract the file name alone
    //

    pszTmpFileName = strrchr( szTmpFile, '\\');
    if (NULL == pszTmpFileName)
      {  pszTmpFileName = szTmpFile; }
    else
      { pszTmpFileName++; }

    if( err != NO_ERROR )
    {

        pUserData->SendErrorToClient(pszTmpFileName, err,
                                     PSZ_CANNOT_CREATE_UNIQUE_FILE);
    }
    else
    {

        //
        // Establish a connection to the user
        //
        err = pUserData->EstablishDataConnection( pszTmpFileName );

        if ( err != NO_ERROR )
        {
            CloseHandle( hFile );

            if ( err == ERROR_IO_PENDING )
            {
                //
                // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
                // client to establish a data connection - it takes care of setting up an event that
                // allows us to deal asynchronously with the client connecting [or failing to do so].
                // It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
                // any further processign

                DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
                            !pUserData->QueryHavePASVConn() );

                return TRUE;
            }

            goto stou_exit;
        }

        //
        //  Let the worker do the dirty work.
        //  On return, hFile is closed
        //

        err = ReceiveFileFromUserAndClose( pUserData, pszTmpFileName, &hFile );

        if( err != NO_ERROR ) {
            //
            // Note that VirtualCreateUniqueFile() returns a fully
            // qualified physical path to the temporary file. Because
            // of this, we cannot call VirtualDeleteFile(), as that will
            // attempt to "re-canonicalize" the file, which will fail.
            // So, we'll just call the DeleteFile() Win32 API directly.
            //

            DeleteFile( szTmpFile );
        }
    }



stou_exit:

    pUserData->WriteLogRecord(PSZ_CREATE_VERB, szTmpFile, err);

    return TRUE;

}   // MainSTOU()



BOOL
MainAPPE(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements APPE command - used for appending to a file.
  Format:  APPE filename

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err;
    HANDLE hFile = INVALID_HANDLE_VALUE;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg != NULL );

    //
    //  Try to create the file.
    //

    err = VirtualCreateFile( pUserData,
                             &hFile,
                             pszArg,
                             TRUE );



    if( err != NO_ERROR )
    {

        pUserData->SendErrorToClient(pszArg, err, PSZ_CANNOT_CREATE_FILE);
    }
    else
    {

        //
        // Establish a connection to the user
        //
        err = pUserData->EstablishDataConnection( pszArg );

        if ( err != NO_ERROR )
        {

            CloseHandle(hFile);

            if ( err == ERROR_IO_PENDING )
            {
                //
                // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
                // client to establish a data connection - it takes care of setting up an event that
                // allows us to deal asynchronously with the client connecting [or failing to do so].
                // It indicates this asynchrony by returning ERROR_IO_PENDING and we don't do
                // any further processing
                //

                DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
                            !pUserData->QueryHavePASVConn() );

                return TRUE;
            }

            goto appe_exit;
        }



        //
        //  Let the worker do the dirty work.
        //  On return, hFile is closed
        //

        err = ReceiveFileFromUserAndClose( pUserData, pszArg, &hFile );
    }

appe_exit:

    pUserData->WriteLogRecord( PSZ_APPEND_VERB, pszArg, err);

    return TRUE;

}   // MainAPPE()




BOOL
MainALLO(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements ALLO command - used for allocating space for file.
  Format:  ALLO filename

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    DBG_ASSERT( pUserData != NULL );

    //
    //  Since we don't need to pre-reserve storage space for
    //  files, we'll treat this command as a noop.
    //

    ReplyToUser(pUserData,
                REPLY_COMMAND_OK,
                PSZ_COMMAND_SUCCESSFUL,
                "ALLO");

    return TRUE;

}   // MainALLO()



BOOL
MainREST(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements REST command - used for restarting file write
  Format:  REST offset

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    LPSTR       pszEndPtr;
    DWORD       dwOffset;

    DBG_ASSERT( pUserData != NULL );

    DBG_ASSERT( pszArg != NULL );

    // Convert the input parameter to a number, and save it for the next command.

    dwOffset = strtoul(pszArg, &pszEndPtr, 10);

    if( (pszEndPtr == pszArg) || (*pszEndPtr != '\0') ||
        (*pszArg == '-') ||
        (dwOffset == ULONG_MAX && strcmp(pszArg, "4294967295")) )
    {
        ReplyToUser(pUserData,
                    REPLY_PARAMETER_SYNTAX_ERROR,
                    PSZ_REPLY_MARKER_SPEC );
    } else {

        pUserData->SetNextOffset( dwOffset );

        ReplyToUser(pUserData,
                    REPLY_NEED_MORE_INFO,
                    PSZ_REPLY_RESTARTING,
                    pszArg);
    }

    return TRUE;

}   // MainREST()



BOOL
MainRNFR(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements RNFR command - rename from filename
  Format:  RNFR FromFileName

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err;
    CHAR   szCanon[MAX_PATH];
    DWORD  cbSize = MAX_PATH;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg != NULL );

    //
    //  Ensure file/directory exists.
    //

    if (pUserData->IsFileNameShort(pszArg))
    {
        err = ERROR_FILE_NOT_FOUND;
    }
    else
    {
        err = pUserData->VirtualCanonicalize(szCanon,
                                             &cbSize,
                                             pszArg,
                                             AccessTypeDelete );
    }

    if( err == NO_ERROR ) {


        if ( pUserData->ImpersonateUser() ) {
            if( GetFileAttributes( szCanon ) == (DWORD)-1L ) {

                err = GetLastError();
            }

            pUserData->RevertToSelf();
        } else {
            err = ERROR_ACCESS_DENIED;
        }

        if(( err == NO_ERROR ) && ( pUserData->RenameSourceBuffer == NULL )){

            pUserData->RenameSourceBuffer = (CHAR *)TCP_ALLOC( MAX_PATH );

            if( pUserData->RenameSourceBuffer == NULL ) {

                err = GetLastError();
            }
        }

        if( err == NO_ERROR ) {

            strcpy( pUserData->RenameSourceBuffer, pszArg );
            SET_UF( pUserData, RENAME );
        }
    }

    if( err == NO_ERROR )
    {
        ReplyToUser(pUserData,
                    REPLY_NEED_MORE_INFO,
                    PSZ_READY_FOR_DEST_FILE);
    } else {

        pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
    }


    pUserData->WriteLogRecord( PSZ_RNFR_VERB, pszArg, err);

    return TRUE;

}   // MainRNFR()



BOOL
MainRNTO(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements RNTO command - rename to filename
  Format:  RNTO ToFileName

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    //  Sanity check the parameters.
    //

    DBG_ASSERT( pszArg != NULL );

    //
    //  Ensure previous command was a RNFR.
    //

    if( !TEST_UF( pUserData, RENAME ) )
    {
        ReplyToUser(pUserData,
                    REPLY_BAD_COMMAND_SEQUENCE,
                    PSZ_BAD_COMMAND_SEQUENCE);
    } else {

        CLEAR_UF( pUserData, RENAME );

        //
        //  Rename the file.
        //

        err = VirtualRenameFile( pUserData,
                                pUserData->RenameSourceBuffer,
                                pszArg );

        if( err == NO_ERROR ) {

            ReplyToUser(pUserData,
                        REPLY_FILE_ACTION_COMPLETED,
                        PSZ_COMMAND_SUCCESSFUL, "RNTO");

        } else if( err == ERROR_FILE_EXISTS ) {

            pUserData->SendErrorToClient(
                pszArg,
                err,
                PSZ_DESTINATION_FILE_EXISTS,
                REPLY_FILE_NOT_ALLOWED
                );

        } else {

            pUserData->SendErrorToClient(
                pszArg,
                err,
                PSZ_NO_FILE_OR_DIRECTORY,
                REPLY_FILE_NOT_FOUND
                );

        }
    }

    pUserData->WriteLogRecord( PSZ_RNTO_VERB, pszArg, err);

    return TRUE;

}   // MainRNTO()



BOOL
MainABOR(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements ABOR command - abort any ongoing data transfer
  Format:  ABOR

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    DBG_ASSERT( pUserData != NULL );

    ReplyToUser(pUserData,
                TEST_UF( pUserData, OOB_DATA )
                    ? REPLY_TRANSFER_OK
                    : REPLY_CONNECTION_OPEN,
                PSZ_COMMAND_SUCCESSFUL, "ABOR");

    //
    //  Clear any remaining oob flag.
    //

    CLEAR_UF( pUserData, OOB_DATA );

    pUserData->WriteLogRecord(PSZ_ABORT_VERB, "");

    return TRUE;

}   // MainABOR()



BOOL
MainDELE(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements DELE command - used to delete a file
  Format:  DELE filename

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());


    if (pUserData->IsFileNameShort(pszArg))
    {
        err = ERROR_FILE_NOT_FOUND;
    }
    else
    {
        err = VirtualDeleteFile( pUserData, pszArg );
    }

    if( err == NO_ERROR ) {

        ReplyToUser(pUserData,
                    REPLY_FILE_ACTION_COMPLETED,
                    PSZ_COMMAND_SUCCESSFUL, "DELE");

    } else {

        pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
    }

    pUserData->WriteLogRecord( PSZ_DELE_VERB, pszArg, err);

    return TRUE;

}   // MainDELE()



BOOL
MainRMD(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements RMD command - used to delete a directory
  Format:  RMD directory

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    if (pUserData->IsFileNameShort(pszArg))
    {
        err = ERROR_PATH_NOT_FOUND;
    }
    else
    {
        err = VirtualRmDir( pUserData, pszArg );
    }

    if( err == NO_ERROR ) {

        ReplyToUser(pUserData,
                    REPLY_FILE_ACTION_COMPLETED,
                    PSZ_COMMAND_SUCCESSFUL, "RMD");
    } else {

        pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
    }

    pUserData->WriteLogRecord( PSZ_RMD_VERB, pszArg, err);

    return TRUE;

}   // MainRMD()



BOOL
MainMKD(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements MKD command - used to create a directory
  Format:  MKD directory

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR err;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    err = VirtualMkDir( pUserData, pszArg );

    if( err == NO_ERROR ) {

        ReplyToUser(pUserData,
                    REPLY_FILE_CREATED,
                    PSZ_DIRECTORY_CREATE, pszArg);
    } else {

        pUserData->SendErrorToClient(pszArg, err, PSZ_NO_FILE_OR_DIRECTORY);
    }

    pUserData->WriteLogRecord( PSZ_MKD_VERB, pszArg, err);

    return TRUE;

}   // MainMKD()



BOOL
MainPWD(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements PWD command - used to query path to working dir.
  Format:  PWD

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    CHAR     szDir[MAX_PATH];

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    // We will be sending back the current directory in virtual form
    // Ofcourse the client should/need not worry about the exact path info.
    //
    strcpy( szDir, pUserData->QueryCurrentDirectory() );

    if( !TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) ) {

        FlipSlashes( szDir );
    }

    ReplyToUser(pUserData,
                REPLY_FILE_CREATED,
                PSZ_CURRENT_DIRECTORY,
                szDir );

    return TRUE;

}   // MainPWD()





/*******************************************************************

    NAME:       MainLIST

    SYNOPSIS:   Implementation for the LIST command.  Similar to NLST,
                except defaults to long format display.

    ENTRY:      pUserData - The user initiating the request.

                pszArg - Command arguments.  Will be NULL if no
                    arguments given.

    RETURNS:    BOOL - TRUE if arguments OK, FALSE if syntax error.

    HISTORY:
        KeithMo     09-Mar-1993 Created.

********************************************************************/
BOOL
MainLIST(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements LIST command - used for getting dir list.
    It is similar to NLST, only that this defaults to long format.
  Format:  LIST [options]* [path]*

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    APIERR serr = 0;
    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    //  Let the worker do the dirty work.
    //

    serr = pUserData->EstablishDataConnection("/bin/ls");

    //
    // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
    // client to establish a data connection - it takes care of setting up an event that
    // allows us to deal asynchronously with the client connecting [or failing to do so].
    // It indicates this asynchrony by returning ERROR_IO_PENDING
    //
    if ( serr == ERROR_IO_PENDING )
    {
        DBG_ASSERT( TEST_UF( pUserData, PASSIVE ) );
        DBG_ASSERT( pUserData->QueryWaitingForPASVConn() );
        DBG_ASSERT( !pUserData->QueryHavePASVConn() );

        return TRUE;
    }

    if ( serr == 0) {

        DWORD dwError;

        serr = SimulateLs(pUserData,
                          pszArg,   // switches for path
                          TRUE,     // use data socket
                          TRUE);    // generate default long format

        dwError = ( (!TEST_UF(pUserData, OOB_ABORT) && (serr == 0))
                   ? NO_ERROR : serr);

        if ( dwError != NO_ERROR) {

            //
            // Send a soft error message indicating failure
            //

            pUserData->SendErrorToClient((pszArg != NULL) ? pszArg : ".",
                                         dwError,
                                         PSZ_NO_FILE_OR_DIRECTORY);

            // since we already reported error, now just reset transfer.
            CLEAR_UF( pUserData, TRANSFER);
        }

        DBG_REQUIRE( pUserData->DestroyDataConnection(dwError));

    } else {

        //
        // could not establish a connection send error!
        //  Error is already sent by EstablishDataConnection()
        //

        IF_DEBUG( ERROR) {

            DBGPRINTF( ( DBG_CONTEXT,
                        "EstablishDataConnection( %08x) failed for LIST\n",
                        pUserData));
        }
    }

    return TRUE;

}   // MainLIST()



BOOL
MainNLST(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements NLST command - used for getting dir list.
    generates a short form of dir listing.
  Format:  NLST [options]* [path]*

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    BOOL fSpecialLs;
    APIERR serr;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    //
    //  If any switches are present, use the simulated "ls"
    //  command.  Otherwise (no switches) use the special
    //  file list.
    //

    // Estabalish a data connection for transfer of data and simulate Ls.

    fSpecialLs = ( ( pszArg == NULL) || ( *pszArg != '-')); // no switches

    serr = pUserData->EstablishDataConnection( (fSpecialLs)
                                              ? "file list" : "/bin/ls"
                                              );

    //
    // if we're in PASV mode, EstablishDataConnection() doesn't actually wait for the
    // client to establish a data connection - it takes care of setting up an event that
    // allows us to deal asynchronously with the client connecting [or failing to do so].
    // It indicates this asynchrony by returning ERROR_IO_PENDING
    //
    if ( serr == ERROR_IO_PENDING )
    {
        DBG_ASSERT( pUserData->QueryWaitingForPASVConn() &&
                    !pUserData->QueryHavePASVConn() );

        return TRUE;
    }

    if ( serr == 0) {

        DWORD  dwError;

        serr = ( ( fSpecialLs) ?
                SpecialLs(pUserData,
                          pszArg,
                          TRUE)
                : SimulateLs(pUserData,
                             pszArg,      // switches & search path
                             TRUE)
                );

        dwError = ((!TEST_UF(pUserData, OOB_DATA) && (serr == 0))
                   ?NO_ERROR: serr);

        if ( dwError != NO_ERROR) {

            //
            // Send a soft error message indicating failure
            //

            pUserData->SendErrorToClient((pszArg != NULL) ? pszArg : ".",
                                         dwError,
                                         PSZ_NO_FILE_OR_DIRECTORY);

            // since we already reported error, now just reset transfer.
            CLEAR_UF( pUserData, TRANSFER);
        }

        DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));

    } else {

        //
        // could not establish a connection send error!
        //  Error is already sent by EstablishDataConnection()
        //

        IF_DEBUG( ERROR) {

            DBGPRINTF( ( DBG_CONTEXT,
                        "EstablishDataConnection( %08x) failed for LIST\n",
                        pUserData));
        }
    }

    return TRUE;

}   // MainNLST()



BOOL
MainSYST(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements SYST command - used for getting system info.
  Format:  SYST

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    DBG_ASSERT( pUserData != NULL );

    ReplyToUser(pUserData,
                REPLY_SYSTEM_TYPE,
                PSZ_VERSION_INFO );

    return TRUE;

}   // MainSYST()




# define MAX_STAT_BUFFER_SIZE        (900)

BOOL
MainSTAT(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements STAT command - used for getting system stats.
  Format:  STAT

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    CHAR rgchBuffer[MAX_STAT_BUFFER_SIZE];
    DWORD cchBuf;
    DWORD dwError;

    DBG_ASSERT( pUserData != NULL );

    //
    //  Ensure user is logged on properly.
    //

    DBG_ASSERT( pUserData->IsLoggedOn());

    if( pszArg == NULL )
    {
        HOSTENT * pHost;

        //
        //  Determine the name of the user's host machine.
        //

        pHost = gethostbyaddr((CHAR *)&pUserData->HostIpAddress.s_addr,
                              4,        // size of the s_addr structure
                              PF_INET ) ;

        //
        //  Just dump connection info.
        //

        cchBuf = wsprintfA( rgchBuffer,
                           "%u-%s status:\r\n"
                           "     Connected to %s\r\n"
                           "     Logged in as %s\r\n"
                           "     TYPE: %s, FORM: %s; STRUcture: %s;"
                           " transfer MODE: %s\r\n"
                           "     %s\r\n"
                           ,
                           REPLY_SYSTEM_STATUS,
                           g_FtpServiceNameString,
                           ( ( pHost != NULL )
                            ? pHost->h_name
                            : inet_ntoa( pUserData->HostIpAddress)),
                           pUserData->QueryUserName(),
                           TransferType( pUserData->QueryXferType() ),
                           "Nonprint",
                           "File",
                           TransferMode( pUserData->QueryXferMode()),
                           ( ( pUserData->QueryDataSocket() == INVALID_SOCKET )
                            ? "No data connection"
                            : "Data connection established")
                           );

        if ( cchBuf < MAX_STAT_BUFFER_SIZE &&
             pUserData->QueryControlSocket() != INVALID_SOCKET) {

            dwError = SockSend(pUserData,
                               pUserData->QueryControlSocket(),
                               rgchBuffer,
                               cchBuf);

            IF_DEBUG( SOCKETS) {

                DBGPRINTF((DBG_CONTEXT,
                           " Sending STAT results %d bytes [%s]. Error= %u\n",
                           cchBuf, rgchBuffer, dwError));
            }

        } else {

            dwError = ERROR_INSUFFICIENT_BUFFER;
        }

        ReplyToUser(pUserData,
                    REPLY_SYSTEM_STATUS,
                    PSZ_SERVER_STATUS_END );
    } else {

        //
        //  This should be similar to LIST, except it sends data
        //  over the control socket, not a data socket.
        //

        cchBuf = wsprintfA( rgchBuffer,
                           "%u-status of %s:\r\n",
                           REPLY_FILE_STATUS,
                           pszArg );

        if ( cchBuf < MAX_STAT_BUFFER_SIZE &&
             pUserData->QueryControlSocket() != INVALID_SOCKET) {

            dwError = SockSend(pUserData,
                               pUserData->QueryControlSocket(),
                               rgchBuffer,
                               cchBuf);

            // Error code is ignored after this point!
        }

        SimulateLs(pUserData,
                   pszArg,
                   FALSE,   // use control socket
                   TRUE);   // generate default long format

        ReplyToUser(pUserData,
                    REPLY_FILE_STATUS,
                    PSZ_SERVER_STATUS_END);
    }

    return TRUE;

}   // MainSTAT()



BOOL
MainNOOP(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  The DO Nothing  NOOP command.
--*/
{

    DBG_ASSERT( pUserData != NULL );

    ReplyToUser(pUserData,
                REPLY_COMMAND_OK,
                PSZ_COMMAND_SUCCESSFUL, "NOOP");

    return TRUE;

}   // MainNOOP()



BOOL
SiteDIRSTYLE(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements DIRSTYLE command - used for getting site specific
     directory style. It also toggles the style
  Format:  DIRSTYLE

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    const CHAR *   pszResponse = NULL;

    DBG_ASSERT( pUserData != NULL );
    DBG_ASSERT( pszArg == NULL );

    //
    //  Toggle the dir output flag.
    //

    if( TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) )
    {
        CLEAR_UF( pUserData, MSDOS_DIR_OUTPUT );
        pszResponse = PSZ_RESPONSE_OFF;
    }
    else
    {
        SET_UF( pUserData, MSDOS_DIR_OUTPUT );
        pszResponse = PSZ_RESPONSE_ON;
    }

    DBG_ASSERT( pszResponse != NULL );

    ReplyToUser(pUserData,
                REPLY_COMMAND_OK,
                PSZ_MSDOS_DIRSTYLE,
                pszResponse );

    return TRUE;

}   // SiteDIRSTYLE()




BOOL
SiteCKM(
    LPUSER_DATA pUserData,
    LPSTR       pszArg
    )
/*++
  This function implements CKM command - used for getting site specific
     Annotate Directories flag. It also toggles the flag.
  Format:  CKM

  Arguments:
    pUserData - the user initiating the request.
    pszArg    - command arguments. Will be NULL if no arguments present.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    const CHAR   * pszResponse = NULL;

    DBG_ASSERT( pUserData != NULL );
    DBG_ASSERT( pszArg == NULL );

    //
    //  Toggle the directory annotation flag.
    //

    if( TEST_UF( pUserData, ANNOTATE_DIRS ) )
    {
        CLEAR_UF( pUserData, ANNOTATE_DIRS );
        pszResponse = PSZ_RESPONSE_OFF;
    }
    else
    {
        SET_UF( pUserData, ANNOTATE_DIRS );
        pszResponse = PSZ_RESPONSE_ON;
    }

    DBG_ASSERT( pszResponse != NULL );

    ReplyToUser(pUserData,
                REPLY_COMMAND_OK,
                PSZ_DIRECTORY_ANNOTATION,
                pszResponse );

    return TRUE;

}   // SiteCKM()




BOOL
ParseStringIntoAddress(
    LPSTR     pszString,
    LPIN_ADDR pinetAddr,
    LPPORT    pport
    )
/*++
  Parse a comma-separated list of six decimal numbers
   into an IP address and a port number. The address and the port are
   in network byte order ( most significant bytes first).

  Arguments:
    pszString - string to be parsed. Should be of the form:
                 dd,dd,dd,dd,dd,dd where 'dd' us the decimal representation
                 of a byte (0-255)
    pinetAddr - will receive the IP Address
    pport     - will receive the port.

  Returns:
    BOOL - TRUE if arguments are OK. FALSE if syntax error.
--*/
{
    INT     i;
    UCHAR   chBytes[6];
    UCHAR   chSum;

    chSum = 0;
    i     = 0;

    while( *pszString != '\0' )
    {
        UCHAR chCurrent = (UCHAR)*pszString++;

        if( ( chCurrent >= '0' ) && ( chCurrent <= '9' ) )
        {
            chSum = ( chSum * 10 ) + chCurrent - '0';
        }
        else
        if( ( chCurrent == ',' ) && ( i < 5 ) )
        {
            chBytes[i++] = chSum;
            chSum = 0;
        }
        else
        {
            return FALSE;
        }
    }

    chBytes[i] = chSum;

    if( i != 5 )
    {
        return FALSE;
    }

    pinetAddr->S_un.S_un_b.s_b1 = chBytes[0];
    pinetAddr->S_un.S_un_b.s_b2 = chBytes[1];
    pinetAddr->S_un.S_un_b.s_b3 = chBytes[2];
    pinetAddr->S_un.S_un_b.s_b4 = chBytes[3];

    *pport = (PORT)( chBytes[4] + ( chBytes[5] << 8 ) );

    return TRUE;

}   // ParseStringIntoAddress()



/*******************************************************************

    NAME:       ReceiveFileFromUserAndClose

    SYNOPSIS:   Worker function for STOR, STOU, and APPE commands.
                Will establish a connection via the (new) data
                socket, then receive a file over that socket.

    ENTRY:      pUserData - The user initiating the request.

                pszFileName - The name of the file to receive.

                phFile - An handle to the file being received.
                    This handle is closed before this
                    routine returns.

    Returns:
       Win32 Error codes (or socket errors) as DWORD

    HISTORY:
        KeithMo     16-Mar-1993 Created.
        MuraliK     05-April-1995 Dont free hFile here +
                                    Alloc IoTransBuffer locally

********************************************************************/
DWORD
ReceiveFileFromUserAndClose(
    LPUSER_DATA pUserData,
    LPSTR       pszFileName,
    LPHANDLE    phFile
    )
{
    BOOL    fResult;
    DWORD   cbRead = 0;
    DWORD   cbWritten;
    DWORD   dwError;
    SOCKET  DataSocket;
    LPVOID  IoTransferBuffer;
    DWORD dwNumThreads;

    DBG_ASSERT( pUserData != NULL );
    DBG_ASSERT( pszFileName != NULL );
    DBG_ASSERT( *phFile != INVALID_HANDLE_VALUE );

    //
    // We're about to make a blocking call to SockRecv(), so increment the # of threads in the
    // ATQ thread pool.
    // The decision whether to process this request is made based on how many threads are already
    // blocked in a synchronous call.  We do it after accepting/establishing the
    // data connection, so that the clients's connect will succeed in the non-passive case, and
    // then have the request aborted. This prevents clients hangs.
    //

    dwNumThreads = InterlockedIncrement( (long *) &g_ThreadsBlockedInSyncCalls );

    if (dwNumThreads > g_MaxThreadsBlockedInSyncCalls)
    {

        //
        // Blow away the data connection
        //

        InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );

        dwError = ERROR_NOT_ENOUGH_MEMORY;

        DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));

        goto RecevieFileFromUser_exit;
    }

    AtqSetInfo( AtqIncMaxPoolThreads, 0 );

    //
    //  Allocate an i/o buffer if not already allocated.
    //

    IoTransferBuffer = TCP_ALLOC( g_SocketBufferSize );

    if( IoTransferBuffer == NULL ) {

        ReplyToUser(pUserData,
                    REPLY_LOCAL_ERROR,
                    PSZ_INSUFFICIENT_RESOURCES);

        InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );
        AtqSetInfo( AtqDecMaxPoolThreads, 0 );

        dwError = ERROR_NOT_ENOUGH_MEMORY;
        goto RecevieFileFromUser_exit;
    }

    //
    //  Blast the file from the user to a local file.
    //

    DataSocket = pUserData->QueryDataSocket();

    for( ; ; )
    {
        //
        //  Read a chunk from the socket.
        //

        dwError = SockRecv( pUserData,
                           DataSocket,
                           IoTransferBuffer,
                           g_SocketBufferSize,
                           &cbRead );

        if( TEST_UF( pUserData,  OOB_DATA ) ||
           ( dwError != NO_ERROR ) || ( cbRead == 0 ) )
        {
            //
            //  Socket error during read or end of file or transfer aborted.
            //

            break;
        }

        pUserData->IncrementCbRecvd( cbRead);

        //
        //  Write the current buffer to the local file.
        //

        fResult = WriteFile( *phFile,
                             IoTransferBuffer,
                             cbRead,
                             &cbWritten,
                             NULL );

        if( !fResult )
        {
            dwError = GetLastError();
            break;
        }
    }

    if ( TEST_UF( pUserData, OOB_DATA)) {

        dwError = ERROR_OPERATION_ABORTED;
    }

    IF_DEBUG( COMMANDS )
    {
        if( !fResult )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "cannot write file %s, error %lu\n",
                        pszFileName,
                        dwError ));
        } else if( dwError != NO_ERROR ) {

            DBGPRINTF(( DBG_CONTEXT,
                       "cannot read data from client, error %d\n",
                       dwError ));
        }

        if( TEST_UF( pUserData,  OOB_DATA ) )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "transfer aborted by client\n" ));
        }
    }

    //
    // Close file handle before disconnecting from client. This is to serialize
    // requests. If we disconnect first, then an append to this file may follow
    // which may result in a sharing violation of the file (if this write has
    // not been flushed and closed yet).
    //

    DBG_REQUIRE( CloseHandle( *phFile ) );
    *phFile = INVALID_HANDLE_VALUE;

    //
    //  Disconnect from client.
    //

    DBG_REQUIRE(pUserData->DestroyDataConnection( dwError));

    if( IoTransferBuffer != NULL )
    {
        TCP_FREE( IoTransferBuffer );
        IoTransferBuffer = NULL;
    }

    if ( dwError == NO_ERROR) {

        pUserData->QueryInstance()->QueryStatsObj()->IncrTotalFilesReceived();
    }

    InterlockedDecrement( (long *) &g_ThreadsBlockedInSyncCalls );
    AtqSetInfo( AtqDecMaxPoolThreads, 0 );

RecevieFileFromUser_exit:

    if( *phFile != INVALID_HANDLE_VALUE ) {

       CloseHandle( *phFile );
       *phFile = INVALID_HANDLE_VALUE;
    }

    return (dwError);
}   // ReceiveFileFromUserAndClose()




/*******************************************************************

    NAME:       MyLogonUser

    SYNOPSIS:   Validates a user's credentials, then sets the
                impersonation for the current thread.  In effect,
                the current thread "becomes" the user.

    ENTRY:      pUserData - The user initiating the request.

                pszPassword - The user's password.  May be NULL.

                pfAsGuest - Will receive TRUE if the user was validated
                    with guest privileges.

                pfHomeDirFailure - Will receive TRUE if the user failed
                    to logon because the home directory was inaccessible.

                pfLicenseExceeded - Will receive TRUE if the logon
                    was denied due to license restrictions.

    RETURNS:    BOOL - If user validated & impersonation was
                    successful, returns TRUE.  Otherwise returns
                    TRUE.

    HISTORY:
        KeithMo     18-Mar-1993 Created.

********************************************************************/
BOOL
MyLogonUser(
    LPUSER_DATA pUserData,
    LPSTR       pszPassword,
    LPBOOL      pfAsGuest,
    LPBOOL      pfHomeDirFailure,
    LPBOOL      pfLicenseExceeded
    )
{
    BOOL     fReturn = TRUE;
    DWORD    dwUserAccess;
    TS_TOKEN UserToken;
    BOOL     fAsAnonymous;
    BOOL     fAsAnonymous2;
    BOOL     fEmptyPassword;
    const CHAR   * pszUser;

    //
    //  Validate parameters & state.
    //

    DBG_ASSERT( pUserData != NULL );
    DBG_ASSERT( pUserData->UserToken == NULL );
    DBG_ASSERT( pfAsGuest != NULL );
    DBG_ASSERT( pfHomeDirFailure != NULL );
    DBG_ASSERT( pfLicenseExceeded != NULL );

    //
    //  Setup.
    //

    *pfAsGuest         = FALSE;
    *pfHomeDirFailure  = FALSE;
    *pfLicenseExceeded = FALSE; // NOT YET SUPPORTED IN GHIA APIS!

    fEmptyPassword = ( pszPassword == NULL ) || ( *pszPassword == '\0' );

    pszUser = pUserData->QueryUserName();
    DBG_ASSERT( pszUser != NULL );
    DBG_ASSERT( *pszUser != '\0' );

    //
    //  Check for invalid logon type.
    //

    fAsAnonymous = TEST_UF( pUserData, ANONYMOUS);

    if( !pUserData->QueryInstance()->IsAllowedUser(fAsAnonymous)) {

        // conflict between what is allowed and type of the client.
        SetLastError( ERROR_LOGON_FAILURE);
        return FALSE;
    }

    //
    //  Check for anonymous logon.
    //

    if( fAsAnonymous )
    {
        //
        //  At this point, we could copy the password specified by the
        //  user into the pUserData->UserName field.  There's a convention
        //  among Internetters that the password specified for anonymous
        //  logon should actually be your login name.  So, if we wanted
        //  honor this convention, we could copy the password into the
        //  pUserData->UserName field so the Administration UI
        // could display it.
        //
        //  If the user didn't enter a password, we'll just copy over
        //  "Anonymous" so we'll have SOMETHING to display...
        //

        pUserData->SetUserName( fEmptyPassword ? PSZ_ANONYMOUS_NAME : pszPassword);

        //
        // TsLogon User will logon as anonymous only when we specify the
        //  UserName == NULL and pszPassword == NULL.
        //
        pszUser = NULL;
        pszPassword = NULL;
    }

    //
    //  Do that logon thang.
    //

    pUserData->QueryInstance()->LockConfig();

    // dumb TsLogonUser() does not take const CHAR * for pszUser :(
    UserToken = TsLogonUser( (CHAR *) pszUser,
                            pszPassword,
                            pfAsGuest,
                            &fAsAnonymous2,
                            pUserData->QueryInstance(),
                            pUserData->QueryInstance()->QueryAuthentInfo() );

    pUserData->QueryInstance()->UnLockConfig();

    //
    // Recheck the logon requirements, just in case the user is trying
    // to do something tricky, like logon with the special IUSR_xxx
    // account name.
    //

    if( UserToken != NULL &&
        !pUserData->QueryInstance()->IsAllowedUser(fAsAnonymous2) ) {

        TsDeleteUserToken( UserToken );
        UserToken = NULL;
        SetLastError( ERROR_LOGON_FAILURE );

    }

    if( UserToken != NULL ) {

        // reset it again even if it was anonymous
        pszUser = pUserData->QueryUserName();

        //
        //  Save away the impersonation token so we can delete
        //  it when the user disconnects or this client thread
        //  otherwise terminates.
        //

        pUserData->UserToken = UserToken;

        //
        //  User validated, now impersonate.
        //

        if( !pUserData->ImpersonateUser()) {

            //
            //  Impersonation failure.
            //

            IF_DEBUG( ERROR) {

                DBGPRINTF(( DBG_CONTEXT,
                           "Impersonate User %08x failed. Error=%lu\n",
                           UserToken, GetLastError()));
            }

            fReturn = FALSE;

        } else {

            //
            //  We're now running in the context of the connected user.
            //  Check the user's access to the FTP Server.
            //

            dwUserAccess = DetermineUserAccess(pUserData->QueryInstance());

            if( dwUserAccess == 0 ) {

                //
                //  User cannot access the FTP Server.
                //

                IF_DEBUG( SECURITY ) {

                    DBGPRINTF(( DBG_CONTEXT,
                               "user %s denied FTP access\n",
                               pszUser ));
                }

                fReturn = FALSE;

            } else {

                const CHAR * apszSubStrings[2];
                DWORD  eventId = 0;

                apszSubStrings[0] = pszUser;

                pUserData->Flags &= ~( UF_READ_ACCESS | UF_WRITE_ACCESS );
                pUserData->Flags |= dwUserAccess;

                IF_DEBUG( SECURITY ) {

                    CHAR * pszTmp = NULL;

                    if( TEST_UF( pUserData, READ_ACCESS ) ) {

                        pszTmp = ( TEST_UF( pUserData, WRITE_ACCESS )
                                  ? "read and write"
                                  : "read"
                                  );
                    } else {

                        DBG_ASSERT( TEST_UF( pUserData, WRITE_ACCESS ) );

                        pszTmp = "write";
                    }

                    DBG_ASSERT( pszTmp != NULL );

                    DBGPRINTF(( DBG_CONTEXT,
                               "user %s granted %s FTP access\n",
                               pszUser,
                               pszTmp ));
                }

                //
                //  initialize the user root directory
                //

                pUserData->SetRootDirectory( PSZ_ANONYMOUS_NAME );

                //
                //  Try to CD to the user's home directory.  Note that
                //  this is VERY important for setting up some of the
                //  "virtual current directory" structures properly.
                //

                if( pUserData->CdToUsersHomeDirectory( PSZ_ANONYMOUS_NAME)
                   != NO_ERROR ) {

                    //
                    //  Home directory inaccessible.
                    //

                    eventId = FTPD_EVENT_BAD_HOME_DIRECTORY;

                } else if (fAsAnonymous &&
                          pUserData->QueryInstance()->QueryLogAnonymous() &&
                          !fEmptyPassword ) {

                    //
                    //  If this is an anonymous user, and we're to log
                    //  anonymous logons, OR if this is not an anonymous
                    //  user, and we're to log nonanonymous logons, then
                    //  do it.
                    //
                    //  Note that we DON'T log the logon if the user is
                    //  anonymous but specified no password.
                    //

                    eventId =  FTPD_EVENT_ANONYMOUS_LOGON;

                } else if (!fAsAnonymous &&
                          pUserData->QueryInstance()->QueryLogNonAnonymous()
                          ) {

                    DBG_ASSERT( *pszUser != '\0');
                    eventId = FTPD_EVENT_NONANONYMOUS_LOGON;
                }

                //
                //  Log an event so the poor admin can figure out
                //  what's going on.
                //

                switch ( eventId) {

                  case FTPD_EVENT_ANONYMOUS_LOGON:
                  case FTPD_EVENT_NONANONYMOUS_LOGON:
                    apszSubStrings[1] = inet_ntoa( pUserData->HostIpAddress);

                    g_pInetSvc->LogEvent( eventId,
                                          2,
                                          apszSubStrings,
                                          0 );
                    break;

                  case FTPD_EVENT_BAD_HOME_DIRECTORY:

                    pUserData->QueryInstance()->LockThisForRead();

                    apszSubStrings[1] = pUserData->QueryInstance()->QueryRoot();

                    *pfHomeDirFailure = TRUE;
                    g_pInetSvc->LogEvent( eventId,
                                          2,
                                          apszSubStrings,
                                          0 );

                    pUserData->QueryInstance()->UnlockThis();

                    fReturn = FALSE;  // bad directory is a failure.
                    break;

                  default:
                    // do nothing
                    break;
                } // switch
            } // user Access Succeeded

            pUserData->RevertToSelf();  // get out the impersonation

        } // Impersonation succeeded.

    } else {

        fReturn = FALSE;
    }

    //
    //  Determine if we logged in with guest access, and
    //  if so, if guest access is allowed in our server.
    //

    if( *pfAsGuest && !pUserData->QueryInstance()->AllowGuestAccess() ) {

        TsDeleteUserToken( pUserData->QueryUserToken() );

        pUserData->UserToken = NULL;

        fReturn = FALSE;
    }

    //
    //  Success!
    //

    return ( fReturn);

}   // MyLogonUser()



DWORD
DetermineUserAccess(FTP_SERVER_INSTANCE *pInstance)
/*++
  This function determines the current user's access to FTP server.
  This is done by testing different RegOpenKey APIs against
  the FTPD_ACCESS_KEY. This key (if it exists) will be "under" the
  FTPD_PARAMETERS_KEY key.

  Arguments:
    None

  Returns:
    DWORD -- will be an OR Combination of UF_READ_ACCESS and UF_WRITE_ACCESS.
    IF this is zero, then the user cannot access FTP server.

  History:
    KeithMo     06-May-1993 Created.
    MuraliK     24-July-1995  Call this function with Impersonation.

  NYI:  Improve performance by avoiding reg opens per connection....
--*/
{
    DWORD  dwAccess = 0;
    HKEY   hkey;
    APIERR err;

    //
    //  Test for read access.
    //

    err = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                        FTPD_ACCESS_KEY,
                        0,
                        KEY_READ,
                        &hkey );

    if( err == NO_ERROR )
    {
        //
        //  Success.
        //

        dwAccess |= UF_READ_ACCESS;
        RegCloseKey( hkey );
    }
    else
    if( err == ERROR_FILE_NOT_FOUND )
    {
        //
        //  Key doesn't exist.
        //

        dwAccess |= UF_READ_ACCESS;
    }

    //
    //  Test for write access.
    //

    err = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                        FTPD_ACCESS_KEY,
                        0,
                        KEY_WRITE,
                        &hkey );

    if( err == NO_ERROR )
    {
        //
        //  Success.
        //

        dwAccess |= UF_WRITE_ACCESS;
        RegCloseKey( hkey );
    }
    else
    if( err == ERROR_FILE_NOT_FOUND )
    {
        //
        //  Key doesn't exist.
        //

        dwAccess |= UF_WRITE_ACCESS;
    }

    return dwAccess;
} // DetermineUserAccess()


/************************ End Of File ************************/