Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

5034 lines
112 KiB

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1993 **/
/**********************************************************************/
/*
engine.c
Command parser & execution for FTPD Service. This module parses
and executes the commands received from the control socket.
FILE HISTORY:
KeithMo 07-Mar-1993 Created.
*/
#include "ftpdp.h"
#pragma hdrstop
//
// Private constants.
//
#define DEFAULT_SUB_DIRECTORY "Default"
#define MAX_HELP_WIDTH 70
//
// Private types.
//
//
// Private globals.
//
CHAR * pszAnonymous = "Anonymous";
CHAR * pszFTP = "Ftp";
CHAR * pszCommandDelimiters = " \t";
BOOL fIsAnonymousGuest = TRUE;
#ifdef KEEP_COMMAND_STATS
CRITICAL_SECTION csCommandStats;
#endif // KEEP_COMMAND_STATS
//
// These messages are used often.
//
CHAR * pszNoFileOrDirectory = "No such file or directory.";
//
// Private prototypes.
//
FTPD_COMMAND *
FindCommandByName(
CHAR * pszCommandName,
FTPD_COMMAND * pCommandTable,
INT cCommands
);
BOOL
ParseStringIntoAddress(
CHAR * pszString,
IN_ADDR * pinetAddr,
PORT * pport
);
VOID
ReceiveFileFromUser(
USER_DATA * pUserData,
CHAR * pszFileName,
HANDLE hFile
);
APIERR
SendFileToUser(
USER_DATA * pUserData,
CHAR * pszFileName,
HANDLE hFile
);
APIERR
CdToUsersHomeDirectory(
USER_DATA * pUserData
);
SOCKERR
SendDirectoryAnnotation(
USER_DATA * pUserData,
UINT ReplyCode
);
VOID
HelpWorker(
USER_DATA * pUserData,
CHAR * pszSource,
CHAR * pszCommand,
FTPD_COMMAND * pCommandTable,
INT cCommands,
INT cchMaxCmd
);
VOID
LogonWorker(
USER_DATA * pUserData,
CHAR * pszPassword
);
BOOL
MyLogonUser(
USER_DATA * pUserData,
CHAR * pszPassword,
BOOL * pfAsGuest,
BOOL * pfHomeDirFailure,
BOOL * pfLicenseExceeded
);
BOOL
MainUSER(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainPASS(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainACCT(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainCWD(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainCDUP(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainSMNT(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainQUIT(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainREIN(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainPORT(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainPASV(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainTYPE(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainSTRU(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainMODE(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainRETR(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainSTOR(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainSTOU(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainAPPE(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainALLO(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainREST(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainRNFR(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainRNTO(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainABOR(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainDELE(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainRMD(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainMKD(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainPWD(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainLIST(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainNLST(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainSITE(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainSYST(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainSTAT(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainHELP(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
MainNOOP(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
SiteDIRSTYLE(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
SiteCKM(
USER_DATA * pUserData,
CHAR * pszArg
);
BOOL
SiteHELP(
USER_DATA * pUserData,
CHAR * pszArg
);
#ifdef KEEP_COMMAND_STATS
BOOL
SiteSTATS(
USER_DATA * pUserData,
CHAR * pszArg
);
#endif // KEEP_COMMAND_STATS
//
// Command lookup tables.
//
FTPD_COMMAND MainCommands[] =
{
{ "PORT", "<sp> b0,b1,b2,b3,b4,b5", MainPORT, Required },
{ "CWD", "[ <sp> directory-name ]", MainCWD , Optional },
{ "LIST", "[ <sp> path-name ]", MainLIST, Optional },
{ "TYPE", "<sp> [ A | E | I | L ]", MainTYPE, Required },
{ "PWD", "(return current directory)", MainPWD , None },
{ "RETR", "<sp> file-name", MainRETR, Required },
{ "NLST", "[ <sp> path-name ]", MainNLST, Optional },
{ "USER", "<sp> username", MainUSER, Required },
{ "PASS", "<sp> password", MainPASS, Optional },
{ "QUIT", "(terminate service)", MainQUIT, None },
{ "CDUP", "change to parent directory", MainCDUP, None },
{ "PASV", "(set server in passive mode)", MainPASV, None },
{ "SYST", "(get operating system type)", MainSYST, None },
{ "ABOR", "(abort operation)", MainABOR, None },
{ "XCWD", "[ <sp> directory-name ]", MainCWD , Optional },
{ "HELP", "[ <sp> <string>]", MainHELP, Optional },
{ "XPWD", "(return current directory)", MainPWD , None },
{ "REST", "<sp> marker", MainREST, Required },
{ "NOOP", "", MainNOOP, None },
{ "STRU", "(specify file structure)", MainSTRU, Required },
{ "STOR", "<sp> file-name", MainSTOR, Required },
{ "RNFR", "<sp> file-name", MainRNFR, Required },
{ "RNTO", "<sp> file-name", MainRNTO, Required },
{ "DELE", "<sp> file-name", MainDELE, Required },
{ "RMD", "<sp> path-name", MainRMD , Required },
{ "XRMD", "<sp> path-name", MainRMD , Required },
{ "MKD", "<sp> path-name", MainMKD , Required },
{ "XMKD", "<sp> path-name", MainMKD , Required },
{ "ACCT", "(specify account)", MainACCT, Required },
{ "XCUP", "change to parent directory", MainCDUP, None },
{ "SMNT", "<sp> pathname", MainSMNT, Required },
{ "REIN", "(reinitialize server state)", MainREIN, None },
{ "MODE", "(specify transfer mode)", MainMODE, Required },
{ "STOU", "(store unique file)", MainSTOU, None },
{ "APPE", "<sp> file-name", MainAPPE, Required },
{ "ALLO", "(allocate storage vacuously)", MainALLO, Required },
{ "SITE", "(site-specific commands)", MainSITE, Optional },
{ "STAT", "(get server status)", MainSTAT, Optional }
};
#define NUM_MAIN_COMMANDS ( sizeof(MainCommands) / sizeof(MainCommands[0]) )
FTPD_COMMAND SiteCommands[] =
{
{ "DIRSTYLE", "(toggle directory format)", SiteDIRSTYLE, None },
{ "CKM", "(toggle directory comments)", SiteCKM , None },
{ "HELP", "[ <sp> <string>]", SiteHELP , Optional }
#ifdef KEEP_COMMAND_STATS
,{ "STATS", "(display per-command stats)", SiteSTATS , None }
#endif // KEEP_COMMAND_STATS
};
#define NUM_SITE_COMMANDS ( sizeof(SiteCommands) / sizeof(SiteCommands[0]) )
//
// Public functions.
//
/*******************************************************************
NAME: ParseCommand
SYNOPSIS: Parses a command string, dispatching to the
appropriate implementation function.
ENTRY: pUserData - The user initiating the request.
pszCommandText - The command text received from
the control socket.
HISTORY:
KeithMo 07-Mar-1993 Created.
********************************************************************/
VOID
ParseCommand(
USER_DATA * pUserData,
CHAR * pszCommandText
)
{
FTPD_COMMAND * pcmd;
PFN_COMMAND pfnCmd;
SOCKET sControl;
CHAR * pszSeparator;
CHAR chSeparator;
BOOL fValidArguments;
BOOL fValidState;
CHAR szParsedCommand[MAX_COMMAND_LENGTH+1];
FTPD_ASSERT( pszCommandText != NULL );
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( ( pUserData->state > FirstUserState ) &&
( pUserData->state < LastUserState ) );
sControl = pUserData->sControl;
//
// Ensure we didn't get entered in an invalid state.
//
FTPD_ASSERT( ( pUserData->state != Embryonic ) &&
( pUserData->state != Disconnected ) );
//
// Save a copy of the command so we can muck around with it.
//
strncpy( szParsedCommand, pszCommandText, MAX_COMMAND_LENGTH );
//
// The command will be terminated by either a space or a '\0'.
//
pszSeparator = strchr( szParsedCommand, ' ' );
if( pszSeparator == NULL )
{
pszSeparator = szParsedCommand + strlen( szParsedCommand );
}
//
// Try to find the command in the command table.
//
chSeparator = *pszSeparator;
*pszSeparator = '\0';
pcmd = FindCommandByName( szParsedCommand,
MainCommands,
NUM_MAIN_COMMANDS );
if( chSeparator != '\0' )
{
*pszSeparator++ = chSeparator;
}
//
// If this is an unknown command, reply accordingly.
//
if( pcmd == NULL )
{
goto SyntaxError;
}
//
// Retrieve the implementation routine.
//
pfnCmd = pcmd->pfnCmd;
//
// If this is an unimplemented command, reply accordingly.
//
if( pfnCmd == NULL )
{
SockReply2( sControl,
REPLY_COMMAND_NOT_IMPLEMENTED,
"%s command not implemented.",
pcmd->pszCommand );
return;
}
//
// Ensure we're in a valid state for the specified command.
//
// If this logic gets any more complex, it would be wise to
// use a lookup table instead.
//
fValidState = FALSE;
switch( pUserData->state )
{
case WaitingForUser :
fValidState = ( pfnCmd == MainUSER ) ||
( pfnCmd == MainQUIT ) ||
( pfnCmd == MainPORT ) ||
( pfnCmd == MainTYPE ) ||
( pfnCmd == MainSTRU ) ||
( pfnCmd == MainMODE ) ||
( pfnCmd == MainHELP ) ||
( pfnCmd == MainNOOP );
break;
case WaitingForPass :
fValidState = ( pfnCmd == MainUSER ) ||
( pfnCmd == MainPASS ) ||
( pfnCmd == MainQUIT ) ||
( pfnCmd == MainPORT ) ||
( pfnCmd == MainTYPE ) ||
( pfnCmd == MainSTRU ) ||
( pfnCmd == MainMODE ) ||
( pfnCmd == MainHELP ) ||
( pfnCmd == MainNOOP );
break;
case LoggedOn :
fValidState = ( pfnCmd != MainPASS );
break;
default :
fValidState = FALSE;
break;
}
if( !fValidState )
{
if( pfnCmd == MainPASS )
{
SockReply2( sControl,
REPLY_BAD_COMMAND_SEQUENCE,
"Login with USER first." );
}
else
{
SockReply2( sControl,
REPLY_NOT_LOGGED_IN,
"Please login with USER and PASS." );
}
return;
}
//
// Do a quick & dirty preliminary check of the argument(s).
//
fValidArguments = FALSE;
while( ( *pszSeparator == ' ' ) && ( *pszSeparator != '\0' ) )
{
pszSeparator++;
}
switch( pcmd->argType )
{
case None :
fValidArguments = ( *pszSeparator == '\0' );
break;
case Optional :
fValidArguments = TRUE;
break;
case Required :
fValidArguments = ( *pszSeparator != '\0' );
break;
default :
FTPD_PRINT(( "ParseCommand - invalid argtype %d\n",
pcmd->argType ));
FTPD_ASSERT( FALSE );
break;
}
if( fValidArguments )
{
//
// Invoke the implementation routine.
//
if( *pszSeparator == '\0' )
{
pszSeparator = NULL;
}
IF_DEBUG( PARSING )
{
FTPD_PRINT(( "invoking %s command, args = %s\n",
pcmd->pszCommand,
_strnicmp( pcmd->pszCommand, "PASS", 4 )
? pszSeparator
: "{secret...}" ));
}
#ifdef KEEP_COMMAND_STATS
EnterCriticalSection( &csCommandStats );
pcmd->cUsage++;
LeaveCriticalSection( &csCommandStats );
#endif // KEEP_COMMAND_STATS
if( (pfnCmd)( pUserData, pszSeparator ) )
{
return;
}
}
//
// Syntax error in command.
//
SyntaxError:
SockReply2( sControl,
REPLY_UNRECOGNIZED_COMMAND,
"'%s': command not understood",
pszCommandText );
} // ParseCommand
/*******************************************************************
NAME: EstablishDataConnection
SYNOPSIS: Connects to the client's data socket.
ENTRY: pUserData - The user initiating the request.
pszReason - The reason for the transfer (file list,
get, put, etc).
HISTORY:
KeithMo 12-Mar-1993 Created.
KeithMo 07-Sep-1993 Bind to FTP data port, not wildcard port.
********************************************************************/
SOCKERR
EstablishDataConnection(
USER_DATA * pUserData,
CHAR * pszReason
)
{
SOCKERR serr = 0;
SOCKET sData = INVALID_SOCKET;
SOCKET sControl;
BOOL fPassive;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Reset any oob flag.
//
CLEAR_UF( pUserData, OOB_DATA );
//
// Capture the user's passive flag, then reset to FALSE.
//
fPassive = TEST_UF( pUserData, PASSIVE );
CLEAR_UF( pUserData, PASSIVE );
//
// Allocate an i/o buffer if not already allocated.
//
if( pUserData->pIoBuffer == NULL )
{
pUserData->pIoBuffer = (CHAR *)FTPD_ALLOC( max( cbSendBuffer,
cbReceiveBuffer ) );
}
if( pUserData->pIoBuffer == NULL )
{
SockReply2( sControl,
REPLY_LOCAL_ERROR,
"Insufficient system resources." );
return WSAENOBUFS; // BUGBUG??
}
//
// If we're in passive mode, then accept a connection to
// the data socket.
//
if( fPassive )
{
SOCKADDR_IN saddrClient;
//
// Ensure we actually created a data socket.
//
FTPD_ASSERT( pUserData->sData != INVALID_SOCKET );
//
// Wait for a connection.
//
IF_DEBUG( CONNECTION )
{
FTPD_PRINT(( "waiting for passive connection on socket %d\n",
pUserData->sData ));
}
serr = AcceptSocket( pUserData->sData,
&sData,
&saddrClient,
TRUE ); // enforce timeouts
//
// We can nuke pUserData->sData now. We only allow one
// connection in passive mode.
//
CloseSocket( pUserData->sData );
pUserData->sData = INVALID_SOCKET;
if( serr == 0 )
{
//
// Got one.
//
FTPD_ASSERT( sData != INVALID_SOCKET );
pUserData->sData = sData;
IF_DEBUG( CONNECTION )
{
FTPD_PRINT(( "data connection received from %s, socket = %d\n",
inet_ntoa( saddrClient.sin_addr ),
sData ));
}
SockReply2( sControl,
REPLY_TRANSFER_STARTING,
"Data connection already open; transfer starting." );
}
else
{
IF_DEBUG( CONNECTION )
{
FTPD_PRINT(( "cannot wait for connection, error %d\n",
serr ));
}
SockReply2( sControl,
REPLY_TRANSFER_ABORTED,
"Connection closed; transfer aborted." );
}
}
else
{
//
// There should not be a open data socket for this user yet.
//
FTPD_ASSERT( pUserData->sData == INVALID_SOCKET );
//
// Announce our intentions.
//
SockReply2( sControl,
REPLY_OPENING_CONNECTION,
"Opening %s mode data connection for %s.",
TransferType( pUserData->xferType ),
pszReason );
//
// Open data socket.
//
serr = CreateDataSocket( &sData, // Will receive socket
htonl( INADDR_ANY ), // Local address
portFtpData, // Local port
pUserData->inetData.s_addr, // Remote address
pUserData->portData ); // Remote port
if( serr == 0 )
{
pUserData->sData = sData;
}
else
{
SockReply2( sControl,
REPLY_CANNOT_OPEN_CONNECTION,
"Can't open data connection." );
IF_DEBUG( COMMANDS )
{
FTPD_PRINT(( "could not create data socket, error %d\n",
serr ));
}
}
}
if( serr == 0 )
{
//
// Success!
//
FTPD_ASSERT( pUserData->sData != INVALID_SOCKET );
SET_UF( pUserData, TRANSFER );
}
else
{
FTPD_ASSERT( pUserData->sData == INVALID_SOCKET );
}
return serr;
} // EstablishDataConnection
/*******************************************************************
NAME: DestroyDataConnection
SYNOPSIS: Tears down the connection to the client's data socket
that was created in EstablishDataConnection.
ENTRY: pUserData - The user initiating the request.
fSuccess - TRUE if data was transferred successfully,
FALSE otherwise.
NOTES: The first time EstablishDataConnection is invoked it
will attempt to allocate an i/o buffer. We do not
delete this buffer here; it will get reused on
subsequent transfers.
HISTORY:
KeithMo 12-Mar-1993 Created.
********************************************************************/
VOID
DestroyDataConnection(
USER_DATA * pUserData,
BOOL fSuccess
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Close the data socket.
//
CLEAR_UF( pUserData, TRANSFER );
CloseSocket( pUserData->sData );
pUserData->sData = INVALID_SOCKET;
//
// Tell the client we're done with the transfer.
//
if( fSuccess )
{
SockReply2( sControl,
REPLY_TRANSFER_OK,
"Transfer complete." );
}
else
{
SockReply2( sControl,
REPLY_TRANSFER_ABORTED,
"Connection closed, transfer aborted." );
}
} // DestroyDataConnection
/*******************************************************************
NAME: CdToUsersHomeDirectory
SYNOPSIS: CDs to the user's home directory. First, a CD to
pszHomeDir is attempted. If this succeeds, a CD
to pszUser is attempted. If this fails, a CD to
"default" is attempted.
ENTRY: pUserData - The user initiating the request.
EXIT: APIERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 28-May-1993 Created.
********************************************************************/
APIERR
CdToUsersHomeDirectory(
USER_DATA * pUserData
)
{
APIERR err;
CHAR * pszUser;
FTPD_ASSERT( pUserData != NULL );
//
// Find the appropriate user name.
//
if( TEST_UF( pUserData, ANONYMOUS ) )
{
pszUser = pszAnonymous;
}
else
{
pszUser = strpbrk( pUserData->szUser, "/\\" );
if( pszUser == NULL )
{
pszUser = pUserData->szUser;
}
else
{
pszUser++;
}
}
//
// Try the top-level home directory. If this fails, bag out.
//
strcpy( pUserData->szDir, pszHomeDir );
err = VirtualChDir( pUserData,
"." );
if( err == NERR_Success )
{
//
// We successfully CD'd into the top-level home
// directory. Now see if we can CD into pszUser.
//
if( VirtualChDir( pUserData,
pszUser ) != NO_ERROR )
{
//
// Nope, try "default". If this fails, just
// hang-out at the top-level home directory.
//
VirtualChDir( pUserData,
DEFAULT_SUB_DIRECTORY );
}
}
return err;
} // CdToUsersHomeDirectory
//
// Private functions.
//
/*******************************************************************
NAME: MainUSER
SYNOPSIS: Implementation for the USER command.
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
MainUSER(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
BOOL fNameIsAnonymous;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
if( pUserData->state == LoggedOn )
{
if( TEST_UF( pUserData, ANONYMOUS ) )
{
DECREMENT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECREMENT_COUNTER( CurrentNonAnonymousUsers );
}
}
//
// Squirrel away a copy of the domain\user name for later.
// If the name is too long, then don't let them logon.
//
fNameIsAnonymous = ( _stricmp( pszArg, pszAnonymous ) == 0 ) ||
( _stricmp( pszArg, pszFTP ) == 0 );
if( strlen( pszArg ) >= ( sizeof(pUserData->szUser) - 1 ) )
{
SockReply2( sControl,
REPLY_NOT_LOGGED_IN,
"User %s cannot log in.",
pszArg );
return TRUE;
}
strcpy( pUserData->szUser, pszArg );
if( fNameIsAnonymous )
{
SET_UF( pUserData, ANONYMOUS );
}
else
{
CLEAR_UF( pUserData, ANONYMOUS );
}
//
// If we already have an impersonation token, then remove
// it. This will allow us to impersonate the new user.
//
if( pUserData->hToken != NULL )
{
if( pUserData->hToken != hAnonymousToken )
{
DeleteUserToken( pUserData->hToken );
}
pUserData->hToken = NULL;
ImpersonateUser( NULL );
}
//
// Tell the client that we need a password.
//
if( fNameIsAnonymous )
{
SockReply2( sControl,
REPLY_NEED_PASSWORD,
"Anonymous access allowed, send identity (e-mail name) as password." );
}
else
{
SockReply2( sControl,
REPLY_NEED_PASSWORD,
"Password required for %s.",
pszArg );
}
pUserData->state = WaitingForPass;
return TRUE;
} // MainUSER
/*******************************************************************
NAME: MainPASS
SYNOPSIS: Implementation for the PASS command.
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
MainPASS(
USER_DATA * pUserData,
CHAR * pszArg
)
{
FTPD_ASSERT( pUserData != NULL );
//
// PASS command only valid in WaitingForPass state.
//
FTPD_ASSERT( pUserData->state == WaitingForPass );
if( ( pszArg != NULL ) && ( strlen( pszArg ) > PWLEN ) )
{
return FALSE;
}
//
// Try to logon the user. pszArg is the password.
//
LogonWorker( pUserData, pszArg );
return TRUE;
} // MainPASS
/*******************************************************************
NAME: MainACCT
SYNOPSIS: Implementation for the ACCT command.
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
MainACCT(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
SockReply2( sControl,
REPLY_COMMAND_SUPERFLUOUS,
"ACCT command not implemented." );
return TRUE;
} // MainACCT
/*******************************************************************
NAME: MainCWD
SYNOPSIS: Implementation for the CWD command.
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
MainCWD(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// If argument is NULL or "~", CD to home directory.
//
if( ( pszArg == NULL ) || ( strcmp( pszArg, "~" ) == 0 ) )
{
err = CdToUsersHomeDirectory( pUserData );
}
else
{
err = VirtualChDir( pUserData,
pszArg );
}
if( err == NO_ERROR )
{
if( TEST_UF( pUserData, ANNOTATE_DIRS ) && ( pUserData->szUser[0] != '-' ) )
{
SendDirectoryAnnotation( pUserData,
REPLY_FILE_ACTION_COMPLETED );
}
SockReply2( sControl,
REPLY_FILE_ACTION_COMPLETED,
"CWD command successful." );
}
else
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = pszNoFileOrDirectory;
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
}
return TRUE;
} // MainCWD
/*******************************************************************
NAME: MainCDUP
SYNOPSIS: Implementation for the CDUP command.
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
MainCDUP(
USER_DATA * pUserData,
CHAR * pszArg
)
{
FTPD_ASSERT( pUserData != NULL );
return MainCWD( pUserData, ".." );
} // MainCDUP
/*******************************************************************
NAME: MainSMNT
SYNOPSIS: Implementation for the SMNT command.
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
MainSMNT(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
SockReply2( sControl,
REPLY_COMMAND_SUPERFLUOUS,
"SMNT command not implemented." );
return TRUE;
} // MainSMNT
/*******************************************************************
NAME: MainQUIT
SYNOPSIS: Implementation for the QUIT command.
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
MainQUIT(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Reply to the quit command.
//
SockReply2( sControl,
REPLY_CLOSING_CONTROL,
"%s",
pszExitMessage );
//
// Close the current thread's control socket. This will cause
// the read/parse/execute loop to terminate.
//
CloseSocket( sControl );
pUserData->sControl = INVALID_SOCKET;
return TRUE;
} // MainQUIT
/*******************************************************************
NAME: MainREIN
SYNOPSIS: Implementation for the REIN command.
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
MainREIN(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
INT i;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
if( pUserData->state == LoggedOn )
{
if( TEST_UF( pUserData, ANONYMOUS ) )
{
DECREMENT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECREMENT_COUNTER( CurrentNonAnonymousUsers );
}
}
pUserData->state = WaitingForUser;
pUserData->tConnect = GetFtpTime();
pUserData->tAccess = pUserData->tConnect;
pUserData->xferType = AsciiType;
pUserData->xferMode = StreamMode;
pUserData->inetData = pUserData->inetHost;
pUserData->portData = portFtpData;
strcpy( pUserData->szUser, "" );
strcpy( pUserData->szDir, "" );
CLEAR_UF( pUserData, PASSIVE );
CLEAR_UF( pUserData, ANONYMOUS );
//
// Nuke any open data socket.
//
if( pUserData->sData != INVALID_SOCKET )
{
ResetSocket( pUserData->sData );
pUserData->sData = INVALID_SOCKET;
}
if( pUserData->hDir != INVALID_HANDLE_VALUE )
{
IF_DEBUG( VIRTUAL_IO )
{
FTPD_PRINT(( "closing directory handle %08lX\n",
pUserData->hDir ));
}
NtClose( pUserData->hDir );
pUserData->hDir = INVALID_HANDLE_VALUE;
}
for( i = 0 ; i < 26 ; i++ )
{
if( pUserData->apszDirs[i] != NULL )
{
FTPD_FREE( pUserData->apszDirs[i] );
pUserData->apszDirs[i] = NULL;
}
}
SockReply2( sControl,
REPLY_SERVICE_READY,
"Service ready for new user." );
return TRUE;
} // MainREIN
/*******************************************************************
NAME: MainPORT
SYNOPSIS: Implementation for the PORT command.
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
MainPORT(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
IN_ADDR inetData;
PORT portData;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Parse the string into address/port pair.
//
if( !ParseStringIntoAddress( pszArg,
&inetData,
&portData ) )
{
return FALSE;
}
//
// Determine if someone is trying to give us a bogus address/port.
//
if( ( inetData.s_addr != pUserData->inetHost.s_addr ) ||
( ntohs( portData ) < IPPORT_RESERVED ) )
{
if( fEnablePortAttack )
{
FTPD_PRINT(( "allowing PORT transfer to bogus address %s:%d\n",
inet_ntoa( inetData ),
ntohs( portData ) ));
}
else
{
SockReply2( sControl,
REPLY_UNRECOGNIZED_COMMAND,
"Invalid PORT command." );
return TRUE;
}
}
//
// Save the address/port pair into per-user data.
//
pUserData->inetData = inetData;
pUserData->portData = portData;
//
// Disable passive mode for this user.
//
CLEAR_UF( pUserData, PASSIVE );
//
// Nuke any open data socket.
//
if( pUserData->sData != INVALID_SOCKET )
{
ResetSocket( pUserData->sData );
pUserData->sData = INVALID_SOCKET;
}
//
// Let the client know we accepted the port command.
//
SockReply2( sControl,
REPLY_COMMAND_OK,
"PORT command successful." );
return TRUE;
} // MainPORT
/*******************************************************************
NAME: MainPASV
SYNOPSIS: Implementation for the PASV command.
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
MainPASV(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sData = INVALID_SOCKET;
SOCKET sControl;
SOCKERR serr = 0;
SOCKADDR_IN saddrLocal;
INT cbLocal;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Nuke any open data socket.
//
if( pUserData->sData != INVALID_SOCKET )
{
FTPD_ASSERT( TEST_UF( pUserData, PASSIVE ) );
CLEAR_UF( pUserData, PASSIVE );
ResetSocket( pUserData->sData );
pUserData->sData = INVALID_SOCKET;
}
//
// Create a new data socket.
//
serr = CreateFtpdSocket( &sData,
pUserData->inetLocal.s_addr,
0 );
if( serr == 0 )
{
//
// Determine the port number for the new socket.
//
cbLocal = sizeof(saddrLocal);
if( getsockname( sData, (SOCKADDR *)&saddrLocal, &cbLocal ) != 0 )
{
serr = WSAGetLastError();
}
}
if( serr == 0 )
{
//
// Success!
//
SET_UF( pUserData, PASSIVE );
FTPD_ASSERT( pUserData->sData == INVALID_SOCKET );
pUserData->sData = sData;
pUserData->inetData = saddrLocal.sin_addr;
pUserData->portData = saddrLocal.sin_port;
SockReply2( sControl,
REPLY_PASSIVE_MODE,
"Entering Passive Mode (%d,%d,%d,%d,%d,%d).",
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( sData != INVALID_SOCKET )
{
CloseSocket( sData );
sData = INVALID_SOCKET;
}
//
// Tell the user the bad news.
//
SockReply2( sControl,
REPLY_CANNOT_OPEN_CONNECTION,
"Can't open data connection." );
}
return TRUE;
} // MainPASV
/*******************************************************************
NAME: MainTYPE
SYNOPSIS: Implementation for the TYPE command.
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
MainTYPE(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
XFER_TYPE newType;
CHAR chType;
CHAR chForm;
CHAR * pszToken;
BOOL fValidForm = FALSE;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Sanity check the parameters.
//
FTPD_ASSERT( pszArg != NULL );
pszToken = strtok( pszArg, pszCommandDelimiters );
if( pszToken == NULL )
{
return FALSE;
}
//
// Ensure we got a valid form type
// (only type N supported).
//
chType = *pszToken;
if( pszToken[1] != '\0' )
{
return FALSE;
}
pszToken = strtok( NULL, pszCommandDelimiters );
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;
}
}
//
// Determine the new transfer type.
//
switch( chType )
{
case 'a' :
case 'A' :
if( !fValidForm )
{
return FALSE;
}
if( ( chForm != 'N' ) && ( chForm != 'T' ) )
{
SockReply2( sControl,
REPLY_PARAMETER_NOT_IMPLEMENTED,
"Form must be N or T." );
return TRUE;
}
newType = AsciiType;
chType = 'A';
break;
case 'e' :
case 'E' :
if( !fValidForm )
{
return FALSE;
}
if( ( chForm != 'N' ) && ( chForm != 'T' ) )
{
SockReply2( sControl,
REPLY_PARAMETER_NOT_IMPLEMENTED,
"Form must be N or T." );
return TRUE;
}
SockReply2( sControl,
REPLY_PARAMETER_NOT_IMPLEMENTED,
"Type E not implemented." );
return TRUE;
case 'i' :
case 'I' :
if( pszToken != NULL )
{
return FALSE;
}
newType = BinaryType;
chType = 'I';
break;
case 'l' :
case 'L' :
if( pszToken == NULL )
{
return FALSE;
}
if( strcmp( pszToken, "8" ) != 0 )
{
if( IsDecimalNumber( pszToken ) )
{
SockReply2( sControl,
REPLY_PARAMETER_NOT_IMPLEMENTED,
"Byte size must be 8." );
return TRUE;
}
else
{
return FALSE;
}
}
newType = BinaryType;
chType = 'L';
break;
default :
return FALSE;
}
IF_DEBUG( COMMANDS )
{
FTPD_PRINT(( "setting transfer type to %s\n",
TransferType( newType ) ));
}
pUserData->xferType = newType;
SockReply2( sControl,
REPLY_COMMAND_OK,
"Type set to %c.",
chType );
return TRUE;
} // MainTYPE
/*******************************************************************
NAME: MainSTRU
SYNOPSIS: Implementation for the STRU command.
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
MainSTRU(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
CHAR chStruct;
CHAR * pszToken;
//
// Sanity check the parameters.
//
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( pszArg != NULL );
sControl = pUserData->sControl;
pszToken = strtok( pszArg, pszCommandDelimiters );
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' :
SockReply2( sControl,
REPLY_PARAMETER_NOT_IMPLEMENTED,
"Unimplemented STRU type." );
return TRUE;
default :
return FALSE;
}
SockReply2( sControl,
REPLY_COMMAND_OK,
"STRU %c ok.",
chStruct );
return TRUE;
} // MainSTRU
/*******************************************************************
NAME: MainMODE
SYNOPSIS: Implementation for the MODE command.
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
MainMODE(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
XFER_MODE newMode;
CHAR chMode;
CHAR * pszToken;
//
// Sanity check the parameters.
//
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( pszArg != NULL );
sControl = pUserData->sControl;
pszToken = strtok( pszArg, pszCommandDelimiters );
if( pszToken == NULL )
{
return FALSE;
}
//
// Ensure we got a valid mode type
// (only type S supported).
//
chMode = *pszToken;
if( pszToken[1] != '\0' )
{
return FALSE;
}
switch( chMode )
{
case 's' :
case 'S' :
newMode = StreamMode;
chMode = 'S';
break;
case 'b' :
case 'B' :
SockReply2( sControl,
REPLY_PARAMETER_NOT_IMPLEMENTED,
"Unimplemented MODE type." );
return TRUE;
default :
return FALSE;
}
IF_DEBUG( COMMANDS )
{
FTPD_PRINT(( "setting transfer mode to %s\n",
TransferMode( newMode ) ));
}
pUserData->xferMode = newMode;
SockReply2( sControl,
REPLY_COMMAND_OK,
"Mode %c ok.",
chMode );
return TRUE;
} // MainMODE
/*******************************************************************
NAME: MainRETR
SYNOPSIS: Implementation for the RETR command.
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
MainRETR(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
HANDLE hFile;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Sanity check the parameters.
//
FTPD_ASSERT( pszArg != NULL );
//
// Try to open the file.
//
err = VirtualOpenFile( pUserData,
&hFile,
pszArg );
if( err == NO_ERROR )
{
err = SendFileToUser( pUserData, pszArg, hFile );
CloseHandle( hFile );
}
if( err != NO_ERROR )
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = pszNoFileOrDirectory;
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
return TRUE;
}
return TRUE;
} // MainRETR
/*******************************************************************
NAME: MainSTOR
SYNOPSIS: Implementation for the STOR command.
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
MainSTOR(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
HANDLE hFile;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Sanity check the parameters.
//
FTPD_ASSERT( pszArg != NULL );
//
// Try to create the file.
//
err = VirtualCreateFile( pUserData,
&hFile,
pszArg,
FALSE );
if( err != NO_ERROR )
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = "Cannot create file.";
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
return TRUE;
}
//
// Let the worker do the dirty work.
//
ReceiveFileFromUser( pUserData, pszArg, hFile );
return TRUE;
} // MainSTOR
/*******************************************************************
NAME: MainSTOU
SYNOPSIS: Implementation for the STOU command.
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
MainSTOU(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
HANDLE hFile;
CHAR szTmpFile[MAX_PATH];
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Sanity check the parameters.
//
FTPD_ASSERT( pszArg == NULL );
//
// Try to create the file.
//
err = VirtualCreateUniqueFile( pUserData,
&hFile,
szTmpFile );
if( err != NO_ERROR )
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = "Cannot create unique file.";
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
szTmpFile,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
return TRUE;
}
//
// Let the worker do the dirty work.
//
ReceiveFileFromUser( pUserData, szTmpFile, hFile );
return TRUE;
} // MainSTOU
/*******************************************************************
NAME: MainAPPE
SYNOPSIS: Implementation for the APPE command.
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
MainAPPE(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
HANDLE hFile;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Sanity check the parameters.
//
FTPD_ASSERT( pszArg != NULL );
//
// Try to create the file.
//
err = VirtualCreateFile( pUserData,
&hFile,
pszArg,
TRUE );
if( err != NO_ERROR )
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = "Cannot create file.";
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
return TRUE;
}
//
// Let the worker do the dirty work.
//
ReceiveFileFromUser( pUserData, pszArg, hFile );
return TRUE;
} // MainAPPE
/*******************************************************************
NAME: MainALLO
SYNOPSIS: Implementation for the ALLO command.
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
MainALLO(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Since we don't need to pre-reserve storage space for
// files, we'll treat this command as a noop.
//
SockReply2( sControl,
REPLY_COMMAND_OK,
"ALLO command successful." );
return TRUE;
} // MainALLO
/*******************************************************************
NAME: MainREST
SYNOPSIS: Implementation for the REST command.
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
MainREST(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// We don't really implement this command, but some
// clients depend on it...
//
// We'll only support restarting at zero.
//
if( strcmp( pszArg, "0" ) )
{
SockReply2( sControl,
REPLY_PARAMETER_NOT_IMPLEMENTED,
"Reply marker must be 0." );
return TRUE;
}
SockReply2( sControl,
REPLY_NEED_MORE_INFO,
"Restarting at 0." );
return TRUE;
} // MainREST
/*******************************************************************
NAME: MainRNFR
SYNOPSIS: Implementation for the RNFR command.
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
MainRNFR(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
CHAR szCanon[MAX_PATH];
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Sanity check the parameters.
//
FTPD_ASSERT( pszArg != NULL );
//
// Ensure file/directory exists.
//
err = VirtualCanonicalize( pUserData,
szCanon,
pszArg,
DeleteAccess );
if( err == NO_ERROR )
{
if( GetFileAttributes( szCanon ) == (DWORD)-1L )
{
err = GetLastError();
}
if( ( err == NO_ERROR ) && ( pUserData->pszRename == NULL ) )
{
pUserData->pszRename = (CHAR *)FTPD_ALLOC( MAX_PATH );
if( pUserData->pszRename == NULL )
{
err = GetLastError();
}
}
if( err == NO_ERROR )
{
strcpy( pUserData->pszRename, pszArg );
SET_UF( pUserData, RENAME );
}
}
if( err == NO_ERROR )
{
SockReply2( sControl,
REPLY_NEED_MORE_INFO,
"File exists, ready for destination name" );
}
else
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = pszNoFileOrDirectory;
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
}
return TRUE;
} // MainRNFR
/*******************************************************************
NAME: MainRNTO
SYNOPSIS: Implementation for the RNTO command.
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
MainRNTO(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Sanity check the parameters.
//
FTPD_ASSERT( pszArg != NULL );
//
// Ensure previous command was a RNFR.
//
if( !TEST_UF( pUserData, RENAME ) )
{
SockReply2( sControl,
REPLY_BAD_COMMAND_SEQUENCE,
"Bad sequence of commands." );
return TRUE;
}
CLEAR_UF( pUserData, RENAME );
//
// Rename the file.
//
err = VirtualRenameFile( pUserData,
pUserData->pszRename,
pszArg );
if( err == NO_ERROR )
{
SockReply2( sControl,
REPLY_FILE_ACTION_COMPLETED,
"RNTO command successful." );
}
else
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = pszNoFileOrDirectory;
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
}
return TRUE;
} // MainRNTO
/*******************************************************************
NAME: MainABOR
SYNOPSIS: Implementation for the ABOR command.
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
MainABOR(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
SockReply2( sControl,
TEST_UF( pUserData, OOB_DATA )
? REPLY_TRANSFER_OK
: REPLY_CONNECTION_OPEN,
"ABOR command successful." );
//
// Clear any remaining oob flag.
//
CLEAR_UF( pUserData, OOB_DATA );
return TRUE;
} // MainABOR
/*******************************************************************
NAME: MainDELE
SYNOPSIS: Implementation for the DELE command.
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
MainDELE(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Do it.
//
err = VirtualDeleteFile( pUserData,
pszArg );
if( err == NO_ERROR )
{
SockReply2( sControl,
REPLY_FILE_ACTION_COMPLETED,
"DELE command successful." );
}
else
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = pszNoFileOrDirectory;
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
}
return TRUE;
} // MainDELE
/*******************************************************************
NAME: MainRMD
SYNOPSIS: Implementation for the RMD command.
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
MainRMD(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Do it.
//
err = VirtualRmDir( pUserData,
pszArg );
if( err == NO_ERROR )
{
SockReply2( sControl,
REPLY_FILE_ACTION_COMPLETED,
"RMD command successful." );
}
else
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = pszNoFileOrDirectory;
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
}
return TRUE;
} // MainRMD
/*******************************************************************
NAME: MainMKD
SYNOPSIS: Implementation for the MKD command.
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
MainMKD(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
APIERR err;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Do it.
//
err = VirtualMkDir( pUserData,
pszArg );
if( err == NO_ERROR )
{
SockReply2( sControl,
REPLY_FILE_CREATED,
"MKD command successful." );
}
else
{
BOOL fDelete = TRUE;
CHAR * pszText;
pszText = AllocErrorText( err );
if( pszText == NULL )
{
pszText = pszNoFileOrDirectory;
fDelete = FALSE;
}
SockReply2( sControl,
REPLY_FILE_NOT_FOUND,
"%s: %s",
pszArg,
pszText );
if( fDelete )
{
FreeErrorText( pszText );
}
}
return TRUE;
} // MainMKD
/*******************************************************************
NAME: MainPWD
SYNOPSIS: Implementation for the PWD command.
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
MainPWD(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
CHAR * pszDir;
CHAR szDir[MAX_PATH];
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
strcpy( szDir, pUserData->szDir );
pszDir = szDir;
if( !TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) )
{
FlipSlashes( pszDir );
}
SockReply2( sControl,
REPLY_FILE_CREATED,
"\"%s\" is current directory.",
pszDir );
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(
USER_DATA * pUserData,
CHAR * pszArg
)
{
FTPD_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// Let the worker do the dirty work.
//
SimulateLsDefaultLong( pUserData,
INVALID_SOCKET, // no connection yet
pszArg ); // switches & search path
return TRUE;
} // MainLIST
/*******************************************************************
NAME: MainNLST
SYNOPSIS: Implementation for the NLST command.
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
MainNLST(
USER_DATA * pUserData,
CHAR * pszArg
)
{
FTPD_ASSERT( pUserData != NULL );
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
//
// If any switches are present, use the simulated "ls"
// command. Otherwise (no switches) use the special
// file list.
//
if( ( pszArg != NULL ) && ( *pszArg == '-' ) )
{
SimulateLs( pUserData,
INVALID_SOCKET, // no connection yet
pszArg ); // switches & search path
}
else
{
SpecialLs( pUserData, pszArg ); // search path (no switches)
}
return TRUE;
} // MainNLST
/*******************************************************************
NAME: MainSITE
SYNOPSIS: Implementation for the SITE command.
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
MainSITE(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_COMMAND * pcmd;
PFN_COMMAND pfnCmd;
CHAR * pszSeparator;
CHAR chSeparator;
BOOL fValidArguments;
CHAR szParsedCommand[MAX_COMMAND_LENGTH+1];
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// If no arguments were given, just return the help text.
//
if( pszArg == NULL )
{
SiteHELP( pUserData, NULL );
return TRUE;
}
//
// Save a copy of the command so we can muck around with it.
//
strncpy( szParsedCommand, pszArg, MAX_COMMAND_LENGTH );
//
// The command will be terminated by either a space or a '\0'.
//
pszSeparator = strchr( szParsedCommand, ' ' );
if( pszSeparator == NULL )
{
pszSeparator = szParsedCommand + strlen( szParsedCommand );
}
//
// Try to find the command in the command table.
//
chSeparator = *pszSeparator;
*pszSeparator = '\0';
pcmd = FindCommandByName( szParsedCommand,
SiteCommands,
NUM_SITE_COMMANDS );
if( chSeparator != '\0' )
{
*pszSeparator++ = chSeparator;
}
//
// If this is an unknown command, reply accordingly.
//
if( pcmd == NULL )
{
goto SyntaxError;
}
//
// Retrieve the implementation routine.
//
pfnCmd = pcmd->pfnCmd;
//
// If this is an unimplemented command, reply accordingly.
//
if( pfnCmd == NULL )
{
SockReply2( sControl,
REPLY_COMMAND_NOT_IMPLEMENTED,
"SITE %s command not implemented.",
pcmd->pszCommand );
return TRUE;
}
//
// Do a quick & dirty preliminary check of the argument(s).
//
fValidArguments = FALSE;
while( ( *pszSeparator == ' ' ) && ( *pszSeparator != '\0' ) )
{
pszSeparator++;
}
switch( pcmd->argType )
{
case None :
fValidArguments = ( *pszSeparator == '\0' );
break;
case Optional :
fValidArguments = TRUE;
break;
case Required :
fValidArguments = ( *pszSeparator != '\0' );
break;
default :
FTPD_PRINT(( "MainSite - invalid argtype %d\n",
pcmd->argType ));
FTPD_ASSERT( FALSE );
break;
}
if( fValidArguments )
{
//
// Invoke the implementation routine.
//
if( *pszSeparator == '\0' )
{
pszSeparator = NULL;
}
IF_DEBUG( PARSING )
{
FTPD_PRINT(( "invoking SITE %s command, args = %s\n",
pcmd->pszCommand,
pszSeparator ));
}
if( (pfnCmd)( pUserData, pszSeparator ) )
{
return TRUE;
}
}
//
// Syntax error in command.
//
SyntaxError:
SockReply2( sControl,
REPLY_UNRECOGNIZED_COMMAND,
"'SITE %s': command not understood",
pszArg );
return TRUE;
} // MainSITE
/*******************************************************************
NAME: MainSYST
SYNOPSIS: Implementation for the SYST command.
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
MainSYST(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
WORD wVersion;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
wVersion = LOWORD( GetVersion() );
SockReply2( sControl,
REPLY_SYSTEM_TYPE,
"Windows_NT version %d.%02d",
LOBYTE( wVersion ),
HIBYTE( wVersion ) );
return TRUE;
} // MainSYST
/*******************************************************************
NAME: MainSTAT
SYNOPSIS: Implementation for the STAT command.
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
MainSTAT(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Ensure user is logged on properly.
//
FTPD_ASSERT( pUserData->state == LoggedOn );
if( pszArg == NULL )
{
HOSTENT * pHost;
//
// Determine the name of the user's host machine.
//
pHost = gethostbyaddr( (CHAR *)&pUserData->inetHost.s_addr, 4, PF_INET );
//
// Just dump connection info.
//
SockReplyFirst2( sControl,
REPLY_SYSTEM_STATUS,
" %s Windows NT FTP Server status:",
pszHostName );
SockPrintf2( sControl,
" %s",
pszFtpVersion );
SockPrintf2( sControl,
" Connected to %s",
( pHost != NULL )
? pHost->h_name
: inet_ntoa( pUserData->inetHost ) );
SockPrintf2( sControl,
" Logged in as %s",
pUserData->szUser );
SockPrintf2( sControl,
" TYPE: %s, FORM: %s; STRUcture: %s; transfer MODE: %s",
TransferType( pUserData->xferType ),
"Nonprint",
"File",
TransferMode( pUserData->xferMode ) );
SockPrintf2( sControl,
" %s",
( pUserData->sData == INVALID_SOCKET )
? "No data connection"
: "Data connection established" );
SockReply2( sControl,
REPLY_SYSTEM_STATUS,
"End of status." );
}
else
{
//
// This should be similar to LIST, except it sends data
// over the control socket, not a data socket.
//
SockReplyFirst2( sControl,
REPLY_FILE_STATUS,
"status of %s:",
pszArg );
SimulateLsDefaultLong( pUserData,
sControl, // connection established
pszArg ); // switches & search path
SockReply2( sControl,
REPLY_FILE_STATUS,
"End of Status." );
}
return TRUE;
} // MainSTAT
/*******************************************************************
NAME: MainHELP
SYNOPSIS: Implementation for the HELP command.
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
MainHELP(
USER_DATA * pUserData,
CHAR * pszArg
)
{
FTPD_ASSERT( pUserData != NULL );
HelpWorker( pUserData,
"",
pszArg,
MainCommands,
NUM_MAIN_COMMANDS,
4 );
return TRUE;
} // MainHELP
/*******************************************************************
NAME: MainNOOP
SYNOPSIS: Implementation for the NOOP command.
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
MainNOOP(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
SockReply2( sControl,
REPLY_COMMAND_OK,
"NOOP command successful." );
return TRUE;
} // MainNOOP
/*******************************************************************
NAME: SiteDIRSTYLE
SYNOPSIS: Implementation for the site-specific DIRSTYLE command.
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-May-1993 Created.
********************************************************************/
BOOL
SiteDIRSTYLE(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
CHAR * pszResponse = NULL;
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( pszArg == NULL );
sControl = pUserData->sControl;
//
// Toggle the dir output flag.
//
if( TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) )
{
CLEAR_UF( pUserData, MSDOS_DIR_OUTPUT );
pszResponse = "off";
}
else
{
SET_UF( pUserData, MSDOS_DIR_OUTPUT );
pszResponse = "on";
}
FTPD_ASSERT( pszResponse != NULL );
SockReply2( sControl,
REPLY_COMMAND_OK,
"MSDOS-like directory output is %s",
pszResponse );
return TRUE;
} // SiteDIRSTYLE
/*******************************************************************
NAME: SiteCKM
SYNOPSIS: Implementation for the site-specific CKM command.
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-May-1993 Created.
********************************************************************/
BOOL
SiteCKM(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
CHAR * pszResponse = NULL;
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( pszArg == NULL );
sControl = pUserData->sControl;
//
// Toggle the directory annotation flag.
//
if( TEST_UF( pUserData, ANNOTATE_DIRS ) )
{
CLEAR_UF( pUserData, ANNOTATE_DIRS );
pszResponse = "off";
}
else
{
SET_UF( pUserData, ANNOTATE_DIRS );
pszResponse = "on";
}
FTPD_ASSERT( pszResponse != NULL );
SockReply2( sControl,
REPLY_COMMAND_OK,
"directory annotation is %s",
pszResponse );
return TRUE;
} // SiteCKM
/*******************************************************************
NAME: SiteHELP
SYNOPSIS: Implementation for the site-specific HELP command.
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-May-1993 Created.
********************************************************************/
BOOL
SiteHELP(
USER_DATA * pUserData,
CHAR * pszArg
)
{
FTPD_ASSERT( pUserData != NULL );
HelpWorker( pUserData,
"SITE ",
pszArg,
SiteCommands,
NUM_SITE_COMMANDS,
8 );
return TRUE;
} // SiteHELP
#ifdef KEEP_COMMAND_STATS
/*******************************************************************
NAME: SiteSTATS
SYNOPSIS: Implementation for the site-specific STATS command.
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 26-Sep-1994 Created.
********************************************************************/
BOOL
SiteSTATS(
USER_DATA * pUserData,
CHAR * pszArg
)
{
SOCKET sControl;
FTPD_COMMAND * pCmd;
BOOL fFirst;
SOCKERR serr;
INT i;
sControl = pUserData->sControl;
pCmd = MainCommands;
fFirst = TRUE;
EnterCriticalSection( &csCommandStats );
for( i = 0 ; i < NUM_MAIN_COMMANDS ; i++ )
{
if( pCmd->cUsage > 0 )
{
if( fFirst )
{
serr = SockReplyFirst2( sControl,
REPLY_COMMAND_OK,
"%-4s : %lu",
pCmd->pszCommand,
pCmd->cUsage );
fFirst = FALSE;
}
else
{
serr = SockPrintf2( sControl,
" %-4s : %lu",
pCmd->pszCommand,
pCmd->cUsage );
}
if( serr != 0 )
{
break;
}
}
pCmd++;
}
LeaveCriticalSection( &csCommandStats );
SockReply2( sControl,
REPLY_COMMAND_OK,
"End of stats." );
return TRUE;
} // SiteSTATS
#endif KEEP_COMMAND_STATS
/*******************************************************************
NAME: FindCommandByName
SYNOPSIS: Searches the command table for a command with this
specified name.
ENTRY: pszCommandName - The name of the command to find.
pCommandTable - An array of FTPD_COMMANDs detailing
the available commands.
cCommands - The number of commands in pCommandTable.
RETURNS: FTPD_COMMAND * - Points to the command entry for
the named command. Will be NULL if command
not found.
HISTORY:
KeithMo 10-Mar-1993 Created.
********************************************************************/
FTPD_COMMAND *
FindCommandByName(
CHAR * pszCommandName,
FTPD_COMMAND * pCommandTable,
INT cCommands
)
{
FTPD_ASSERT( pszCommandName != NULL );
FTPD_ASSERT( pCommandTable != NULL );
FTPD_ASSERT( cCommands > 0 );
//
// Search for the command in our table.
//
_strupr( pszCommandName );
while( cCommands-- > 0 )
{
if( !strcmp( pszCommandName, pCommandTable->pszCommand ) )
{
break;
}
pCommandTable++;
}
//
// Check for unknown command.
//
if( cCommands < 0 )
{
pCommandTable = NULL;
}
return pCommandTable;
} // FindCommandByName
/*******************************************************************
NAME: ParseStringIntoAddress
SYNOPSIS: Parses a comma-separated list of six decimal numbers
into an Internet address and port number. The address
and the port are in network byte order (most signifigant
byte first).
ENTRY: pszString - The string to parse. Should be of the form
dd,dd,dd,dd,dd,dd where "dd" is the decimal
representation of a byte (0-255).
pinetAddr - Will receive the Internet address
pport - Will receive the port.
RETURNS: BOOL - TRUE if arguments OK, FALSE if syntax error.
HISTORY:
KeithMo 10-Mar-1993 Created.
********************************************************************/
BOOL
ParseStringIntoAddress(
CHAR * pszString,
IN_ADDR * pinetAddr,
PORT * pport
)
{
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 < 6 ) )
{
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: ReceiveFileFromUser
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.
hFile - An handle to the file being received.
This handle *must* be closed before this
routine returns.
HISTORY:
KeithMo 16-Mar-1993 Created.
********************************************************************/
VOID
ReceiveFileFromUser(
USER_DATA * pUserData,
CHAR * pszFileName,
HANDLE hFile
)
{
BOOL fResult;
DWORD cbRead;
DWORD cbWritten;
SOCKERR serr;
SOCKET sData;
CHAR * pIoBuffer;
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( pszFileName != NULL );
FTPD_ASSERT( hFile != INVALID_HANDLE_VALUE );
//
// Connect to the client.
//
serr = EstablishDataConnection( pUserData, pszFileName );
if( serr != 0 )
{
CloseHandle( hFile );
return;
}
INCREMENT_COUNTER( TotalFilesReceived );
//
// Blast the file from the user to a local file.
//
sData = pUserData->sData;
pIoBuffer = pUserData->pIoBuffer;
for( ; ; )
{
//
// Read a chunk from the socket.
//
serr = SockRecv( pUserData,
sData,
pIoBuffer,
cbReceiveBuffer,
&cbRead );
if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) || ( cbRead == 0 ) )
{
//
// Socket error during read or end of file or transfer aborted.
//
break;
}
//
// Write the current buffer to the local file.
//
fResult = WriteFile( hFile,
pIoBuffer,
cbRead,
&cbWritten,
NULL );
if( !fResult )
{
break;
}
}
IF_DEBUG( COMMANDS )
{
if( !fResult )
{
APIERR err = GetLastError();
FTPD_PRINT(( "cannot write file %s, error %lu\n",
pszFileName,
err ));
}
if( serr != 0 )
{
FTPD_PRINT(( "cannot read data from client, error %d\n",
serr ));
}
if( TEST_UF( pUserData, OOB_DATA ) )
{
FTPD_PRINT(( "transfer aborted by client\n" ));
}
}
CloseHandle( hFile );
//
// Disconnect from client.
//
DestroyDataConnection( pUserData,
!TEST_UF( pUserData, OOB_DATA ) && fResult && ( serr == 0 ) );
} // ReceiveFileFromUser
/*******************************************************************
NAME: SendFileToUser
SYNOPSIS: Worker function for RETR command. Will establish
a connection via the (new) data socket, then send
a file over that socket.
ENTRY: pUserData - The user initiating the request.
pszFileName - The name of the file to send.
hFile - An handle to the file being sent.
RETURNS: APIERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 17-Mar-1993 Created.
KeithMo 01-Nov-1993 Uses mapped files.
********************************************************************/
APIERR
SendFileToUser(
USER_DATA * pUserData,
CHAR * pszFileName,
HANDLE hFile
)
{
LARGE_INTEGER FileSize;
LARGE_INTEGER ViewOffset;
SOCKERR serr;
SOCKET sData;
HANDLE hMap = NULL;
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( pszFileName != NULL );
FTPD_ASSERT( hFile != INVALID_HANDLE_VALUE );
//
// Get file size.
//
FileSize.LowPart = (ULONG)GetFileSize( hFile, (LPDWORD)&FileSize.HighPart );
if( FileSize.LowPart == (ULONG)-1L )
{
APIERR err = GetLastError();
if( err != NO_ERROR )
{
return err;
}
}
if( FileSize.QuadPart )
{
//
// Create the file mapping object.
//
hMap = CreateFileMapping( hFile,
NULL,
PAGE_READONLY,
0,
0,
NULL );
if( hMap == NULL )
{
return GetLastError();
}
}
//
// Connect to the client.
//
serr = EstablishDataConnection( pUserData, pszFileName );
if( serr != 0 )
{
//
// EstablishDataConnection has already notified the
// user of the failure. Return NO_ERROR so the
// caller won't bother sending the notification again.
//
if( hMap )
{
CloseHandle( hMap );
}
return NO_ERROR;
}
INCREMENT_COUNTER( TotalFilesSent );
//
// Blast the file from a local file to the user.
//
ViewOffset.QuadPart = 0;
sData = pUserData->sData;
while( FileSize.QuadPart )
{
DWORD ViewSize;
CHAR * pView;
ViewSize = ( FileSize.QuadPart > AllocationGranularity.QuadPart )
? AllocationGranularity.LowPart
: FileSize.LowPart;
pView = (CHAR *)MapViewOfFile( hMap,
FILE_MAP_READ,
(DWORD)ViewOffset.HighPart,
(DWORD)ViewOffset.LowPart,
ViewSize );
if( pView == NULL )
{
serr = WSAEFAULT;
break;
}
ViewOffset.QuadPart += ViewSize;
FileSize.QuadPart -= ViewSize;
try
{
serr = SockSend( sData, pView, ViewSize );
}
except( EXCEPTION_EXECUTE_HANDLER )
{
serr = WSAEFAULT;
}
UnmapViewOfFile( pView );
if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) )
{
//
// Socket send error or transfer aborted.
//
break;
}
}
IF_DEBUG( COMMANDS )
{
if( serr != 0 )
{
FTPD_PRINT(( "cannot send data to client, error %d\n",
serr ));
}
if( TEST_UF( pUserData, OOB_DATA ) )
{
FTPD_PRINT(( "transfer aborted by client\n" ));
}
}
if( hMap )
{
CloseHandle( hMap );
}
//
// Disconnect from client.
//
DestroyDataConnection( pUserData,
!TEST_UF( pUserData, OOB_DATA ) && ( serr == 0 ) );
return NO_ERROR;
} // SendFileToUser
/*******************************************************************
NAME: SendDirectoryAnnotation
SYNOPSIS: Tries to open the FTPD_ANNOTATION_FILE (~~ftpsvc~~.ckm)
file in the user's current directory. If it can be
opened, it is sent to the user over the command socket
as a multi-line reply.
ENTRY: pUserData - The user that initiated the request.
ReplyCode - The reply code to send as the first line
of this multi-line reply.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 06-May-1993 Created.
********************************************************************/
SOCKERR
SendDirectoryAnnotation(
USER_DATA * pUserData,
UINT ReplyCode
)
{
FILE * pfile;
SOCKET sControl;
SOCKERR serr = 0;
BOOL fFirstReply = TRUE;
CHAR szLine[MAX_REPLY_LENGTH+1];
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
//
// Try to open the annotation file.
//
pfile = Virtual_fopen( pUserData,
FTPD_ANNOTATION_FILE );
if( pfile == NULL )
{
//
// File not found. Blow it off.
//
return 0;
}
//
// While there's more text in the file, blast
// it to the user.
//
while( fgets( szLine, MAX_REPLY_LENGTH, pfile ) != NULL )
{
CHAR * pszTmp = szLine + strlen(szLine) - 1;
//
// Remove any trailing CR/LFs in the string.
//
while( ( pszTmp >= szLine ) &&
( ( *pszTmp == '\n' ) || ( *pszTmp == '\r' ) ) )
{
*pszTmp-- = '\0';
}
//
// Ensure we send the proper prefix for the
// very *first* line of the file.
//
if( fFirstReply )
{
serr = SockReplyFirst2( sControl,
ReplyCode,
"%s",
szLine );
fFirstReply = FALSE;
}
else
{
serr = SockPrintf2( sControl,
" %s",
szLine );
}
if( serr != 0 )
{
//
// Socket error sending file.
//
break;
}
}
//
// Cleanup.
//
fclose( pfile );
return serr;
} // SendDirectoryAnnotation
/*******************************************************************
NAME: HelpWorker
SYNOPSIS: Worker function for HELP & site-specific HELP commands.
ENTRY: pUserData - The user initiating the request.
pszSource - The source of these commands.
pszCommand - The command to get help for. If NULL,
then send a list of available commands.
pCommandTable - An array of FTPD_COMMANDs, one for
each available command.
cCommands - The number of commands in pCommandTable.
cchMaxCmd - Length of the maximum command.
HISTORY:
KeithMo 06-May-1993 Created.
********************************************************************/
VOID
HelpWorker(
USER_DATA * pUserData,
CHAR * pszSource,
CHAR * pszCommand,
FTPD_COMMAND * pCommandTable,
INT cCommands,
INT cchMaxCmd
)
{
FTPD_COMMAND * pcmd;
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( pCommandTable != NULL );
FTPD_ASSERT( cCommands > 0 );
sControl = pUserData->sControl;
if( pszCommand == NULL )
{
CHAR szLine[MAX_HELP_WIDTH];
SockReplyFirst2( sControl,
REPLY_HELP_MESSAGE,
"The following %scommands are recognized (* =>'s unimplemented).",
pszSource );
pcmd = pCommandTable;
szLine[0] = '\0';
while( cCommands-- > 0 )
{
CHAR szTmp[16];
sprintf( szTmp,
" %-*s%c",
cchMaxCmd,
pcmd->pszCommand,
pcmd->pfnCmd == NULL ? '*' : ' ' );
if( ( strlen( szLine ) + strlen( szTmp ) ) >= sizeof(szLine) )
{
SockPrintf2( sControl,
"%s",
szLine );
szLine[0] = '\0';
}
strcat( szLine, szTmp );
pcmd++;
}
if( szLine[0] != '\0' )
{
SockPrintf2( sControl,
"%s",
szLine );
}
SockReply2( sControl,
REPLY_HELP_MESSAGE,
"HELP command successful." );
}
else
{
pcmd = FindCommandByName( pszCommand,
pCommandTable,
cCommands );
if( pcmd == NULL )
{
SockReply2( sControl,
REPLY_PARAMETER_SYNTAX_ERROR,
"Unknown command %s.",
pszCommand );
}
else
{
SockReply2( sControl,
REPLY_HELP_MESSAGE,
"Syntax: %s%s %s",
pszSource,
pcmd->pszCommand,
pcmd->pszHelpText );
}
}
} // HelpWorker
/*******************************************************************
NAME: LogonWorker
SYNOPSIS: Logon worker function for USER and PASS commands.
ENTRY: pUserData - The user initiating the request.
pszPassword - The user's password. May be NULL.
HISTORY:
KeithMo 18-Mar-1993 Created.
********************************************************************/
VOID
LogonWorker(
USER_DATA * pUserData,
CHAR * pszPassword
)
{
SOCKET sControl;
BOOL fAsGuest;
BOOL fHomeDirFailure;
BOOL fLicenseExceeded;
FTPD_ASSERT( pUserData != NULL );
FTPD_ASSERT( pUserData->hToken == NULL );
sControl = pUserData->sControl;
//
// Try to logon the user.
//
INCREMENT_COUNTER( LogonAttempts );
if( MyLogonUser( pUserData,
pszPassword,
&fAsGuest,
&fHomeDirFailure,
&fLicenseExceeded ) )
{
CHAR * pszGuestAccess;
//
// Determine if we logged in with guest access, and
// if so, if guest access is allowed in our server.
//
if( fAsGuest )
{
if( !fAllowGuestAccess )
{
if( pUserData->hToken != hAnonymousToken )
{
DeleteUserToken( pUserData->hToken );
}
pUserData->hToken = NULL;
goto LogonFailure;
}
pszGuestAccess = " (guest access)";
}
else
{
pszGuestAccess = "";
}
//
// Successful logon.
//
if( pUserData->szUser[0] != '-' )
{
SendMultilineMessage2( sControl,
REPLY_USER_LOGGED_IN,
pszGreetingMessage );
}
if( TEST_UF( pUserData, ANNOTATE_DIRS ) && ( pUserData->szUser[0] != '-' ) )
{
SendDirectoryAnnotation( pUserData,
REPLY_USER_LOGGED_IN );
}
if( TEST_UF( pUserData, ANONYMOUS ) )
{
InterlockedIncrement( (LPLONG)&FtpStats.TotalAnonymousUsers );
InterlockedIncrement( (LPLONG)&FtpStats.CurrentAnonymousUsers );
if( FtpStats.CurrentAnonymousUsers > FtpStats.MaxAnonymousUsers )
{
LockStatistics();
if( FtpStats.CurrentAnonymousUsers > FtpStats.MaxAnonymousUsers )
{
FtpStats.MaxAnonymousUsers = FtpStats.CurrentAnonymousUsers;
}
UnlockStatistics();
}
SockReply2( sControl,
REPLY_USER_LOGGED_IN,
"Anonymous user logged in as %s%s.",
pszAnonymousUser,
pszGuestAccess );
}
else
{
InterlockedIncrement( (LPLONG)&FtpStats.TotalNonAnonymousUsers );
InterlockedIncrement( (LPLONG)&FtpStats.CurrentNonAnonymousUsers );
if( FtpStats.CurrentNonAnonymousUsers > FtpStats.MaxNonAnonymousUsers )
{
LockStatistics();
if( FtpStats.CurrentNonAnonymousUsers > FtpStats.MaxNonAnonymousUsers )
{
FtpStats.MaxNonAnonymousUsers = FtpStats.CurrentNonAnonymousUsers;
}
UnlockStatistics();
}
SockReply2( sControl,
REPLY_USER_LOGGED_IN,
"User %s logged in%s.",
pUserData->szUser,
pszGuestAccess );
}
pUserData->state = LoggedOn;
}
else
{
LogonFailure:
//
// Logon failure.
//
if( fHomeDirFailure )
{
SockReply2( sControl,
REPLY_NOT_LOGGED_IN,
"User %s cannot log in, home directory inaccessible.",
pUserData->szUser );
}
else
if( fLicenseExceeded )
{
SockReply2( sControl,
REPLY_NOT_LOGGED_IN,
"User %s cannot log in, license quota exceeded.",
pUserData->szUser );
}
else
if( fAsGuest && !fAllowGuestAccess )
{
SockReply2( sControl,
REPLY_NOT_LOGGED_IN,
"User %s cannot log in, guest access not allowed.",
pUserData->szUser );
}
else
{
SockReply2( sControl,
REPLY_NOT_LOGGED_IN,
"User %s cannot log in.",
pUserData->szUser );
}
pUserData->state = WaitingForUser;
pUserData->szUser[0] = '\0';
}
} // LogonWorker
/*******************************************************************
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(
USER_DATA * pUserData,
CHAR * pszPassword,
BOOL * pfAsGuest,
BOOL * pfHomeDirFailure,
BOOL * pfLicenseExceeded
)
{
CHAR * pszUser;
CHAR * pszDomain;
BOOL fEmptyPassword;
DWORD dwUserAccess;
HANDLE hToken;
CHAR szPasswordFromSecret[PWLEN+1];
CHAR szDomainAndUser[DNLEN+UNLEN+2];
//
// Validate parameters & state.
//
FTPD_ASSERT( pUserData != NULL );
pszUser = pUserData->szUser;
FTPD_ASSERT( pUserData->hToken == NULL );
FTPD_ASSERT( pszUser != NULL );
FTPD_ASSERT( strlen(pszUser) < sizeof(szDomainAndUser) );
FTPD_ASSERT( pfAsGuest != NULL );
FTPD_ASSERT( pfHomeDirFailure != NULL );
FTPD_ASSERT( pfLicenseExceeded != NULL );
if( pszPassword == NULL )
{
pszPassword = "";
}
else
{
FTPD_ASSERT( strlen(pszPassword) <= PWLEN );
}
fEmptyPassword = ( *pszPassword == '\0' );
*pfHomeDirFailure = FALSE;
*pfLicenseExceeded = FALSE;
//
// Save a copy of the domain\user so we can squirrel around
// with it a bit.
//
strcpy( szDomainAndUser, pszUser );
//
// Check for invalid logon type.
//
if( ( TEST_UF( pUserData, ANONYMOUS ) && !fAllowAnonymous ) ||
( !TEST_UF( pUserData, ANONYMOUS ) && fAnonymousOnly ) )
{
return FALSE;
}
//
// Check for anonymous logon.
//
if( TEST_UF( pUserData, ANONYMOUS ) )
{
if( hAnonymousToken != NULL )
{
hToken = hAnonymousToken;
*pfAsGuest = fIsAnonymousGuest;
strncpy( pUserData->szUser,
fEmptyPassword ? pszAnonymous : pszPassword,
sizeof(pUserData->szUser) );
goto GotToken;
}
if( !GetAnonymousPassword( szPasswordFromSecret ) )
{
//
// Cannot retrieve anonymous password from
// the LSA Secret object.
//
return FALSE;
}
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "mapping logon request for %s to %s\n",
szDomainAndUser,
pszAnonymousUser ));
}
//
// Replace the user specified name ("Anonymous") with
// the proper anonymous logon alias.
//
strcpy( szDomainAndUser, pszAnonymousUser );
//
// At this point, we could copy the password specified by the
// user into the pUserData->szUser 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->szUser 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...
//
strncpy( pUserData->szUser,
fEmptyPassword ? pszAnonymous : pszPassword,
sizeof(pUserData->szUser) );
pszPassword = szPasswordFromSecret;
}
//
// Crack the name into domain/user components.
//
pszDomain = szDomainAndUser;
pszUser = strpbrk( szDomainAndUser, "/\\" );
if( pszUser == NULL )
{
//
// No domain name specified, just user.
//
pszDomain = szDefaultDomain;
pszUser = szDomainAndUser;
}
else
{
//
// Both domain & user specified, skip delimiter.
//
*pszUser++ = '\0';
if( ( *pszUser == '\0' ) ||
( *pszUser == '\\' ) ||
( *pszUser == '/' ) )
{
//
// Name is of one of the following (invalid) forms:
//
// "domain\"
// "domain\\..."
// "domain/..."
//
return FALSE;
}
}
//
// Validate the domain/user/password combo and create
// an impersonation token.
//
hToken = ValidateUser( pszDomain,
pszUser,
pszPassword,
pfAsGuest,
pfLicenseExceeded );
RtlZeroMemory( pszPassword, strlen(pszPassword) );
if( hToken == NULL )
{
//
// Validation failure.
//
return FALSE;
}
if( ( hAnonymousToken == NULL ) && TEST_UF( pUserData, ANONYMOUS ) )
{
fIsAnonymousGuest = *pfAsGuest;
hAnonymousToken = hToken;
}
GotToken:
//
// Save away the impersonation token so we can delete
// it when the user disconnects or this client thread
// otherwise terminates.
//
pUserData->hToken = hToken;
//
// User validated, now impersonate.
//
if( !ImpersonateUser( hToken ) )
{
//
// Impersonation failure.
//
return FALSE;
}
//
// We're now running in the context of the connected user.
// Check the user's access to the FTP server.
//
dwUserAccess = DetermineUserAccess();
if( dwUserAccess == 0 )
{
//
// User cannot access the FTP Server.
//
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "user %s denied FTP access\n",
pszUser ));
}
return FALSE;
}
pUserData->dwFlags &= ~( UF_READ_ACCESS | UF_WRITE_ACCESS );
pUserData->dwFlags |= dwUserAccess;
IF_DEBUG( SECURITY )
{
CHAR * pszTmp = NULL;
if( TEST_UF( pUserData, READ_ACCESS ) )
{
pszTmp = TEST_UF( pUserData, WRITE_ACCESS ) ? "read and write"
: "read";
}
else
{
FTPD_ASSERT( TEST_UF( pUserData, WRITE_ACCESS ) );
pszTmp = "write";
}
FTPD_ASSERT( pszTmp != NULL );
FTPD_PRINT(( "user %s granted %s FTP access\n",
pszUser,
pszTmp ));
}
//
// 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( CdToUsersHomeDirectory( pUserData ) != NO_ERROR )
{
CHAR * apszSubStrings[2];
//
// Home directory inaccessible.
//
//
// Log an event so the poor admin can figure out
// what's going on.
//
apszSubStrings[0] = pUserData->szUser;
apszSubStrings[1] = pszHomeDir;
FtpdLogEvent( FTPD_EVENT_BAD_HOME_DIRECTORY,
2,
apszSubStrings,
0 );
*pfHomeDirFailure = TRUE;
return FALSE;
}
//
// 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.
//
if( TEST_UF( pUserData, ANONYMOUS ) && fLogAnonymous && !fEmptyPassword )
{
CHAR * apszSubStrings[2];
apszSubStrings[0] = pUserData->szUser;
apszSubStrings[1] = inet_ntoa( pUserData->inetHost );
FtpdLogEvent( FTPD_EVENT_ANONYMOUS_LOGON,
2,
apszSubStrings,
0 );
}
else
if( !TEST_UF( pUserData, ANONYMOUS ) && fLogNonAnonymous )
{
CHAR * apszSubStrings[2];
FTPD_ASSERT( *pUserData->szUser != '\0' );
apszSubStrings[0] = pUserData->szUser;
apszSubStrings[1] = inet_ntoa( pUserData->inetHost );
FtpdLogEvent( FTPD_EVENT_NONANONYMOUS_LOGON,
2,
apszSubStrings,
0 );
}
//
// Success!
//
return TRUE;
} // MyLogonUser