windows-nt/Source/XPSP1/NT/inetsrv/iis/svcs/ftp/server/connect.cxx
2020-09-26 16:20:57 +08:00

744 lines
20 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1993 **/
/**********************************************************************/
/*
connect.cxx
This module contains the main function for handling new connections.
After receiving a new connection this module creates a new USER_DATA
object to contain the information about a connection for processing.
Functions exported by this module:
FtpdNewConnction
FtpdNewConnectionEx
FILE HISTORY:
KeithMo 08-Mar-1993 Created.
MuraliK 03-April-1995
Rewrote to separate the notion of one thread/control connection +
other mods.
MuraliK 11-Oct-1995
Completely rewrote to support AcceptEx connections
*/
#include "ftpdp.hxx"
static CHAR PSZ_SERVICE_NOT_AVAILABLE[] =
"Service not available, closing control connection.";
//
// Private prototypes.
//
VOID
FtpReqResolveCallback(
ADDRCHECKARG pArg,
BOOL fSt,
LPSTR pName
)
{
// ignore fSt : DNS name is simply unavailable
//((LPUSER_DATA)pArg)->Reference();
if ( !AtqPostCompletionStatus( ((LPUSER_DATA)pArg)->QueryControlAio()->QueryAtqContext(),
0 ))
{
DereferenceUserDataAndKill( ((LPUSER_DATA)pArg) );
}
}
BOOL
ProcessNewClient(
IN SOCKET sNew,
IN PVOID EndpointObject,
IN FTP_SERVER_INSTANCE *pInstance,
IN BOOL fMaxConnExceeded,
IN PSOCKADDR_IN psockAddrRemote,
IN PSOCKADDR_IN psockAddrLocal = NULL,
IN PATQ_CONTEXT patqContext = NULL,
IN PVOID pvBuff = NULL,
IN DWORD cbWritten = 0,
OUT LPBOOL pfAtqToBeFreed = NULL
)
{
LPUSER_DATA pUserData = NULL;
DWORD err = NO_ERROR;
BOOL fReturn = FALSE;
DBG_CODE( CHAR pchAddr[32];);
BOOL fSockToBeFreed = TRUE;
BOOL fDereferenceInstance = FALSE;
AC_RESULT acIpAccess;
BOOL fNeedDnsCheck = FALSE;
BOOL fValid;
BOOL fUnbindOnFail;
DBG_CODE( InetNtoa( psockAddrRemote->sin_addr, pchAddr));
if ( pfAtqToBeFreed != NULL) {
*pfAtqToBeFreed = TRUE;
}
//
// Create a new connection object
//
if ( !fMaxConnExceeded ) {
pUserData = pInstance->AllocNewConnection();
}
if ( pUserData != NULL) {
pUserData->QueryAccessCheck()->BindAddr( (PSOCKADDR)psockAddrRemote );
if ( !pUserData->BindInstanceAccessCheck() ) {
fValid = FALSE;
fUnbindOnFail = FALSE;
}
else {
fUnbindOnFail = TRUE;
acIpAccess = pUserData->QueryAccessCheck()->CheckIpAccess( &fNeedDnsCheck );
if ( (acIpAccess == AC_IN_DENY_LIST) ||
((acIpAccess == AC_NOT_IN_GRANT_LIST) && !fNeedDnsCheck) ) {
SockPrintf2(
NULL,
sNew,
"%u Connection refused, unknown IP address.",
REPLY_NOT_LOGGED_IN
);
fValid = FALSE;
}
else {
fValid = TRUE;
}
}
//
// Start off processing this client connection.
//
// Once we make a reset call, the USER_DATA object is created
// with the socket and atq context.
// From now on USER_DATA will take care of freeing
// ATQ context & socket
//
fSockToBeFreed = FALSE;
if ( fValid && pUserData->Reset(sNew,
EndpointObject,
psockAddrRemote->sin_addr,
psockAddrLocal,
patqContext,
pvBuff,
cbWritten,
acIpAccess )
) {
#ifndef _NO_TRACING_
IF_CHKDEBUG( CLIENT) {
CHKINFO( ( DBG_CONTEXT,
" Established a new connection to %s"
" ( Socket = %d)\n",
pchAddr,
sNew));
#else
IF_DEBUG( CLIENT) {
DBGPRINTF( ( DBG_CONTEXT,
" Established a new connection to %s"
" ( Socket = %d)\n",
pchAddr,
sNew));
#endif
}
//
// At this point we have the context for the AcceptExed socket.
// Set the context in the AtqContext if need be.
//
if ( patqContext != NULL) {
//
// Associate client connection object with this control socket
// handle for future completions.
//
AtqContextSetInfo(patqContext,
ATQ_INFO_COMPLETION_CONTEXT,
(ULONG_PTR) pUserData->QueryControlAio());
}
if ( fNeedDnsCheck )
{
if ( !pUserData->QueryAccessCheck()->IsDnsResolved() ) {
BOOL fSync;
LPSTR pDns;
pUserData->SetNeedDnsCheck( TRUE );
if ( pUserData->QueryAccessCheck()->QueryDnsName( &fSync,
(ADDRCHECKFUNCEX)FtpReqResolveCallback,
(ADDRCHECKARG)pUserData,
&pDns )
&& !fSync ) {
return TRUE;
}
}
}
else {
pUserData->UnbindInstanceAccessCheck();
}
DBG_REQUIRE( pUserData->Reference() > 0);
fReturn = pUserData->ProcessAsyncIoCompletion(0, NO_ERROR,
pUserData->
QueryControlAio()
);
if ( !fReturn) {
err = GetLastError();
#ifndef _NO_TRACING_
IF_CHKDEBUG( ERROR) {
CHKINFO(( DBG_CONTEXT,
" Unable to start off a read to client(%s,%d)."
" Error = %lu\n",
pchAddr,
sNew,
err ));
#else
IF_DEBUG( ERROR) {
DBGPRINTF(( DBG_CONTEXT,
" Unable to start off a read to client(%s,%d)."
" Error = %lu\n",
pchAddr,
sNew,
err ));
#endif
}
}
//
// Decrement the ref count and free the connection.
//
DBG_ASSERT( (err == NO_ERROR) || pUserData->QueryReference() == 1);
DereferenceUserDataAndKill( pUserData);
} else {
if ( fUnbindOnFail )
{
pUserData->UnbindInstanceAccessCheck();
}
// reset operation failed. relase memory and exit.
err = GetLastError();
pUserData->Cleanup();
pInstance->RemoveConnection( pUserData);
pUserData = NULL;
}
} else {
err = GetLastError();
fDereferenceInstance = TRUE;
}
if ( (pUserData == NULL) || (err != NO_ERROR) ) {
//
// Failed to allocate new connection
// Reasons:
// 1) Max connecitons might have been exceeded.
// 2) Not enough memory is available.
// 3) Access check failed
//
// handle the failures and notify client.
//
if ( fMaxConnExceeded) {
CHAR rgchBuffer[MAX_REPLY_LENGTH];
DWORD len;
//
// Unable to insert new connection.
// The maxConnections may have exceeded.
// Destroy the client connection object and return.
// Possibly need to send an error message.
//
#ifndef _NO_TRACING_
IF_CHKDEBUG( ERROR) {
CHKINFO( ( DBG_CONTEXT,
" MaxConnections Exceeded. "
" Connection from %s refused at socket %d\n",
pchAddr, sNew));
#else
IF_DEBUG( ERROR) {
DBGPRINTF( ( DBG_CONTEXT,
" MaxConnections Exceeded. "
" Connection from %s refused at socket %d\n",
pchAddr, sNew));
#endif
}
// Format a message to send for the error case.
pInstance->LockConfig();
LPCSTR pszMsg = pInstance->QueryMaxClientsMsg();
pszMsg = (pszMsg == NULL) ? PSZ_SERVICE_NOT_AVAILABLE : pszMsg;
len = FtpFormatResponseMessage(REPLY_SERVICE_NOT_AVAILABLE,
pszMsg,
rgchBuffer,
MAX_REPLY_LENGTH);
pInstance->UnLockConfig();
DBG_ASSERT( len < MAX_REPLY_LENGTH);
// Send the formatted message
// Ignore error in sending this message.
SockSend( NULL, sNew, rgchBuffer, len);
} else {
// not enough memory for running this client connection
const CHAR * apszSubStrings[1];
CHAR pchAddr2[32];
InetNtoa( psockAddrRemote->sin_addr, pchAddr2 );
apszSubStrings[0] = pchAddr2;
g_pInetSvc->LogEvent(FTPD_EVENT_CANNOT_CREATE_CLIENT_THREAD,
1,
apszSubStrings,
err );
#ifndef _NO_TRACING_
IF_CHKDEBUG( ERROR) {
CHKINFO(( DBG_CONTEXT,
"Cannot create Client Connection for %s,"
" Error %lu\n",
pchAddr,
err ));
#else
IF_DEBUG( ERROR) {
DBGPRINTF(( DBG_CONTEXT,
"Cannot create Client Connection for %s,"
" Error %lu\n",
pchAddr,
err ));
#endif
}
//
// Send a message to client if the socket is to be freed.
// If it is already freed, then we cannot send message
//
if ( fSockToBeFreed) {
SockPrintf2(NULL, sNew,
"%u Service not available,"
" closing control connection.",
REPLY_SERVICE_NOT_AVAILABLE );
} else {
IF_DEBUG( CLIENT) {
DBGPRINTF( ( DBG_CONTEXT,
" Unable to send closed error message to "
" %s (%d)\n",
pchAddr2, sNew
));
}
}
}
//
// Unable to create a new connection object.
// Report error and shut this.
//
#ifndef _NO_TRACING_
IF_CHKDEBUG( ERROR) {
CHKINFO( ( DBG_CONTEXT,
"Cannot create new FTP Request object to %s."
" Error= %u\n",
pchAddr,
err));
#else
IF_DEBUG( ERROR) {
DBGPRINTF( ( DBG_CONTEXT,
"Cannot create new FTP Request object to %s."
" Error= %u\n",
pchAddr,
err));
#endif
}
if ( fSockToBeFreed ) {
if ( patqContext != NULL) {
// ensure that socket is shut down.
DBG_REQUIRE( AtqCloseSocket( patqContext, TRUE));
} else {
CloseSocket( sNew);
}
}
fReturn = (FALSE);
} // if ( pcc == NULL)
if ( pfAtqToBeFreed != NULL) {
*pfAtqToBeFreed = fSockToBeFreed;
}
if ( fDereferenceInstance ) {
pInstance->DecrementCurrentConnections();
pInstance->Dereference();
}
return (fReturn);
} // ProcessNewClient()
//
// Public functions.
//
VOID
FtpdNewConnection(
IN SOCKET sNew,
IN SOCKADDR_IN * psockaddr,
IN PVOID EndpointContext,
IN PVOID EndpointObject
)
/*++
Call back function for processing the connections from clients.
This function creates a new UserData object if permitted for the new
client request and starts off a receive for the given connection
using Async read on control channel established.
Arguments:
sNew control socket for the new client connection
psockAddr pointer to the client's address.
Returns:
None
History:
KeithMo 08-Mar-1993 Created.
MuraliK 04-April-1995
ReCreated for using async Io threading model.
--*/
{
SOCKERR serr;
BOOL fProcessed;
BOOL fMaxConnExceeded;
INT cbAddr = sizeof(SOCKADDR);
SOCKADDR_IN sockaddr;
FTP_SERVER_INSTANCE *pInstance;
DBG_ASSERT( sNew != INVALID_SOCKET );
DBG_ASSERT( psockaddr != NULL );
DBG_ASSERT( psockaddr->sin_family == AF_INET ); // temporary
g_pFTPStats->IncrConnectionAttempts();
if ( g_pInetSvc->QueryCurrentServiceState() != SERVICE_RUNNING ) {
#ifndef _NO_TRACING_
IF_CHKDEBUG( ERROR) {
#else
IF_DEBUG( ERROR) {
#endif
DBG_CODE( CHAR pchAddr[32];);
DBG_CODE( InetNtoa(((SOCKADDR_IN *) psockaddr)->sin_addr,
pchAddr));
#ifndef _NO_TRACING_
CHKINFO( ( DBG_CONTEXT,
"Service is not running or AccessCheck failed for"
" Connection from %s\n",
pchAddr));
#else
DBGPRINTF( ( DBG_CONTEXT,
"Service is not running or AccessCheck failed for"
" Connection from %s\n",
pchAddr));
#endif
}
SockPrintf2(NULL,
sNew,
"%u %s", // the blank after %u is essential
REPLY_SERVICE_NOT_AVAILABLE,
"Service not available, closing control connection." );
goto error_exit;
}
if (getsockname( sNew, (PSOCKADDR)&sockaddr, &cbAddr ) != 0) {
goto error_exit;
}
//
// Find Instance
//
pInstance = (FTP_SERVER_INSTANCE *)
((PIIS_ENDPOINT)EndpointContext)->FindAndReferenceInstance(
(LPCSTR)NULL,
sockaddr.sin_addr.s_addr,
&fMaxConnExceeded
);
if ( pInstance == NULL ) {
//
// Site is not permitted to access this server.
// Dont establish this connection. We should send a message.
//
SockPrintf2(NULL, sNew,
"%u Connection refused, unknown IP address.",
REPLY_NOT_LOGGED_IN);
goto error_exit;
}
fProcessed = ProcessNewClient( sNew,
EndpointObject,
pInstance,
fMaxConnExceeded,
psockaddr);
if ( fProcessed) {
pInstance->QueryStatsObj()->CheckAndSetMaxConnections();
}
return;
error_exit:
CloseSocket( sNew);
return;
} // FtpdNewConnection()
VOID
FtpdNewConnectionEx(
IN VOID * patqContext,
IN DWORD cbWritten,
IN DWORD dwError,
IN OVERLAPPED * lpo
)
/*++
Description:
Callback function for new connections when using AcceptEx.
This function verifies if this is a valid connection
( maybe using IP level authentication)
and creates a new connection object
The connection object is added to list of active connections.
If the max number of connections permitted is exceeded,
the client connection object is destroyed and
connection is rejected.
Arguments:
patqContext: pointer to ATQ context for the IO operation
cbWritten: count of bytes available from first read operation
dwError: error if any from initial operation
lpo: indicates if this function was called as a result
of IO completion or due to some error.
Returns:
None.
--*/
{
DWORD err = NO_ERROR;
BOOL fProcessed = FALSE;
BOOL fAtqContextToBeFreed = TRUE;
BOOL fMaxConnExceeded;
PSOCKADDR_IN psockAddrLocal = NULL;
PSOCKADDR_IN psockAddrRemote = NULL;
SOCKET sNew = INVALID_SOCKET;
PVOID pvBuff = NULL;
PIIS_ENDPOINT pEndpoint;
FTP_SERVER_INSTANCE *pInstance;
if ( (dwError != NO_ERROR) || !lpo) {
DBGPRINTF(( DBG_CONTEXT, "FtpdNewConnectionEx() completion failed."
" Error = %d. AtqContext=%08x\n",
dwError, patqContext));
//
// For now free up the resources.
//
goto exit;
}
g_pFTPStats->IncrConnectionAttempts();
DBG_ASSERT( patqContext != NULL);
AtqGetAcceptExAddrs( (PATQ_CONTEXT ) patqContext,
&sNew,
&pvBuff,
(PVOID*)&pEndpoint,
(PSOCKADDR *)&psockAddrLocal,
(PSOCKADDR *)&psockAddrRemote);
DBG_ASSERT( pEndpoint != NULL );
IF_DEBUG( CONNECTION ) {
DBGPRINTF(( DBG_CONTEXT,
" New connection. AtqCont=%08x, buff=%08x, cb=%d\n",
patqContext, pvBuff, cbWritten));
}
if ( g_pInetSvc->QueryCurrentServiceState() != SERVICE_RUNNING ) {
DBGPRINTF((DBG_CONTEXT,"Connection attempt on inactive service\n"));
SockPrintf2(NULL,
sNew,
"%u %s", // the blank after %u is essential
REPLY_SERVICE_NOT_AVAILABLE,
"Service not available, closing control connection." );
goto exit;
}
//
// Find Instance
//
pInstance = (FTP_SERVER_INSTANCE*)pEndpoint->FindAndReferenceInstance(
(LPCSTR)NULL,
psockAddrLocal->sin_addr.s_addr,
&fMaxConnExceeded
);
if (pInstance == NULL ) {
//
// Site is not permitted to access this server.
// Dont establish this connection. We should send a message.
//
DBGPRINTF((DBG_CONTEXT,
"Unable to find instance [err %d]\n",GetLastError()));
goto exit;
}
//
// Set the timeout for future IOs on this context
//
AtqContextSetInfo( (PATQ_CONTEXT) patqContext,
ATQ_INFO_TIMEOUT,
(ULONG_PTR) pInstance->QueryConnectionTimeout()
);
if ( pInstance->QueryBandwidthInfo() )
{
AtqContextSetInfo( (PATQ_CONTEXT) patqContext,
ATQ_INFO_BANDWIDTH_INFO,
(ULONG_PTR) pInstance->QueryBandwidthInfo() );
}
fProcessed = ProcessNewClient( sNew,
NULL,
pInstance,
fMaxConnExceeded,
psockAddrRemote,
psockAddrLocal,
(PATQ_CONTEXT ) patqContext,
pvBuff,
cbWritten,
&fAtqContextToBeFreed);
if ( fProcessed) {
pInstance->QueryStatsObj()->CheckAndSetMaxConnections();
}
exit:
if ( !fProcessed && fAtqContextToBeFreed ) {
//
// We failed to process this connection. Free up resources properly
//
DBG_REQUIRE( AtqCloseSocket( (PATQ_CONTEXT )patqContext, FALSE));
AtqFreeContext( (PATQ_CONTEXT ) patqContext, TRUE );
}
return;
} // FtpdNewConnectionEx