Windows2003-3790/inetsrv/iis/svcs/smtp/server/connect.cxx
2020-09-30 16:53:55 +02:00

763 lines
18 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., 1994 **/
/**********************************************************************/
/*
connect.cxx
This module contains the connection accept routine called by the connection
thread.
FILE HISTORY:
Johnl 08-Aug-1994 Lifted from FTP server
*/
#define INCL_INETSRV_INCS
#include "smtpinc.h"
#include <rdns.hxx>
#include "smtpcli.hxx"
//
// Private prototypes.
//
VOID
SmtpCompletion(
PVOID pvContext,
DWORD cbWritten,
DWORD dwCompletionStatus,
OVERLAPPED * lpo
);
SOCKERR CloseSocket( SOCKET sock );
BOOL
SendError(
SOCKET socket,
DWORD ids
);
//
// Private functions.
//
/*++
This function dereferences User data and kills the UserData object if the
reference count hits 0. Before killing the user data, it also removes
the connection from the list of active connections.
--*/
VOID DereferenceUserDataAndKill(IN OUT CLIENT_CONNECTION * pUserData)
{
pUserData->DisconnectClient();
((SMTP_CONNECTION*)pUserData)->QuerySmtpInstance()->RemoveConnection(pUserData);
delete pUserData;
} // DereferenceUserDataAndKill()
void HandleErrorCondition(SOCKET sock, SMTP_SERVER_INSTANCE *pInstance, DWORD dwError, PSOCKADDR_IN pSockAddr)
{
char ErrorBuf[4 *MAX_PATH];
const CHAR * apszSubStrings[2];
CHAR pchAddr1[50] = "";
CHAR pchAddr2[50] = "";
DWORD SendSize;
_ASSERT(pSockAddr != NULL);
_ASSERT(pInstance != NULL);
_ASSERT(sock != INVALID_SOCKET);
SendSize = wsprintf (ErrorBuf, "%d-%s %s\r\n",
SMTP_RESP_SRV_UNAVAIL,
pInstance->GetConnectResponse(),
SMTP_SRV_UNAVAIL_MSG);
CLIENT_CONNECTION::WriteSocket(sock, ErrorBuf, SendSize );
if(dwError == ERROR_REMOTE_SESSION_LIMIT_EXCEEDED)
{
_itoa(pInstance->QueryInstanceId(), pchAddr1, 10);
InetNtoa(pSockAddr->sin_addr, pchAddr2 );
apszSubStrings[0] = pchAddr1;
apszSubStrings[1] = pchAddr2;
SmtpLogEvent(SMTP_MAX_CONNECTION_REACHED,
2,
apszSubStrings,
NO_ERROR );
}
}
BOOL VerifyClient(SMTP_CONNECTION * pNewClient, PSOCKADDR_IN psockAddrRemote)
{
AC_RESULT acIpAccess;
BOOL fNeedDnsCheck = FALSE;
BOOL fRet = TRUE;
struct hostent* pH = NULL;
pNewClient->QueryAccessCheck()->BindAddr( (PSOCKADDR)psockAddrRemote );
if ( pNewClient->BindInstanceAccessCheck() )
{
acIpAccess = pNewClient->QueryAccessCheck()->CheckIpAccess( &fNeedDnsCheck);
if ( (acIpAccess == AC_IN_DENY_LIST) ||
((acIpAccess == AC_NOT_IN_GRANT_LIST) && !fNeedDnsCheck) )
{
fRet = FALSE;
}
else if (fNeedDnsCheck)
{
pH = gethostbyaddr( (char*)(&((PSOCKADDR_IN)psockAddrRemote)->sin_addr),
4, PF_INET );
if(pH != NULL)
{
acIpAccess = pNewClient->QueryAccessCheck()->CheckName(pH->h_name);
}
else
{
acIpAccess = AC_IN_DENY_LIST;
}
}
if ( (acIpAccess == AC_IN_DENY_LIST) ||
(acIpAccess == AC_NOT_IN_GRANT_LIST))
{
fRet = FALSE;
}
pNewClient->UnbindInstanceAccessCheck();
}
if(!fRet)
{
SetLastError(ERROR_ACCESS_DENIED);
}
return fRet;
}
BOOL
ProcessNewClient(
IN SOCKET sNew,
IN PVOID EndpointObject,
IN SMTP_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
)
{
SMTP_CONNECTION * pNewClient = NULL;
DWORD err = NO_ERROR;
BOOL fReturn = FALSE;
BOOL fMaxExceeded = FALSE;
DBG_CODE( CHAR pchAddr[32];);
BOOL fSockToBeFreed = TRUE;
BOOL fDereferenceInstance = FALSE;
BOOL fSupressErrorResponse = FALSE;
CLIENT_CONN_PARAMS clientParams;
TraceFunctEnterEx((LPARAM) NULL, "ProcessNewClient");
DBG_CODE( InetNtoa( psockAddrRemote->sin_addr, pchAddr));
if ( pfAtqToBeFreed != NULL)
{
*pfAtqToBeFreed = TRUE;
}
clientParams.sClient = sNew;
clientParams.pAtqContext = patqContext;
clientParams.pAddrLocal = (PSOCKADDR) psockAddrLocal;
clientParams.pAddrRemote = (PSOCKADDR)psockAddrRemote;
clientParams.pvInitialBuff = pvBuff;
clientParams.cbInitialBuff = cbWritten ;
clientParams.pEndpoint = (PIIS_ENDPOINT)EndpointObject;
if( pInstance && (pInstance->IsShuttingDown() || (pInstance->QueryServerState( ) != MD_SERVER_STATE_STARTED)))
{
DBGPRINTF((DBG_CONTEXT," Service instance is shutting down\n"));
}
else if ( !fMaxConnExceeded)
{
// DBGPRINTF((DBG_CONTEXT,"Getting a connection object\n"));
pNewClient = (SMTP_CONNECTION *) pInstance->CreateNewConnection( &clientParams);
if(pNewClient)
{
if(!VerifyClient(pNewClient, psockAddrRemote))
{
//
// Depending on metabase configuration, either require clients
// whose IPs are blocked to authenticate, or drop the connection.
//
if(pInstance->GetDeniedIpAction() == SMTPDENIEDIP_REQAUTH)
pNewClient->RequireAuth();
else
{
_ASSERT(pInstance->GetDeniedIpAction() == SMTPDENIEDIP_DROPCONN);
DereferenceUserDataAndKill(pNewClient);
pNewClient = NULL;
fSockToBeFreed = FALSE;
fDereferenceInstance = FALSE;
SetLastError(ERROR_ACCESS_DENIED);
//
// We have closed the socket... don't write to it!!!
//
fSupressErrorResponse = TRUE;
}
}
}
}
else
{
err = ERROR_REMOTE_SESSION_LIMIT_EXCEEDED;
SetLastError(err);
}
if( pNewClient != NULL)
{
//DBGPRINTF((DBG_CONTEXT,"New connection object is non-null\n"));
//
// 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;
//
// At this point we have the context for the AcceptExed socket.
// Set the context in the AtqContext if need be.
//
if ( patqContext != NULL)
{
//DBGPRINTF((DBG_CONTEXT,"AtqContext is not NULL\n"));
//
// Associate client connection object with this control socket
// handle for future completions.
//
AtqContextSetInfo(patqContext,
ATQ_INFO_COMPLETION_CONTEXT,
(UINT_PTR) pNewClient);
}
else
{
//DBGPRINTF((DBG_CONTEXT,"AtqContext is NULL\n"));
if(!pNewClient->AddToAtqHandles((HANDLE) sNew,EndpointObject, pInstance->QueryConnectionTimeout(),
SmtpCompletion))
{
err = GetLastError();
DBGPRINTF((DBG_CONTEXT,"AddToAtqHandles() failed- err= %d\n", err));
DebugTrace((LPARAM) NULL, "pNewClient->AddToAtqHandles failed- err = %d", err);
DereferenceUserDataAndKill(pNewClient);
fDereferenceInstance = FALSE;
fSockToBeFreed = FALSE;
pNewClient = NULL;
}
}
}
else
{
err = GetLastError();
if(err != ERROR_ACCESS_DENIED)
fDereferenceInstance = TRUE;
}
if ( (pNewClient == NULL) || (err != NO_ERROR) )
{
// DBGPRINTF((DBG_CONTEXT,"New connection object is NULL\n"));
//
// Failed to allocate new connection
// Reasons:
// 1) Max connections might have been exceeded.
// 2) Not enough memory is available.
//
// handle the failures and notify client.
//
if (!fSupressErrorResponse)
HandleErrorCondition(sNew, pInstance, err, psockAddrRemote);
}
else
{
//DBGPRINTF((DBG_CONTEXT,"Calling StartSession()\n"));
if(!pNewClient->StartSession())
{
err = GetLastError();
DBGPRINTF((DBG_CONTEXT,"StartSession() failed - err= %d\n", err));
DebugTrace((LPARAM) NULL, "pNewClient->StartSession() failed- err = %d", err);
DereferenceUserDataAndKill(pNewClient);
pNewClient = NULL;
fSockToBeFreed = FALSE;
fDereferenceInstance = FALSE;
}
else
{
fReturn = TRUE;
}
}
if ( fSockToBeFreed )
{
if ( patqContext != NULL)
{
// ensure that socket is shut down.
DBG_REQUIRE( AtqCloseSocket( patqContext, TRUE));
}
else
{
CloseSocket( sNew);
}
}
if ( pfAtqToBeFreed != NULL)
{
*pfAtqToBeFreed = fSockToBeFreed;
}
if (pInstance && fDereferenceInstance )
{
pInstance->DecrementCurrentConnections();
pInstance->Dereference();
}
TraceFunctLeaveEx((LPARAM) NULL);
return (fReturn);
} // ProcessNewClient()
/*******************************************************************
NAME: SmtpOnConnect
SYNOPSIS: Handles the incoming connection indication from the
connection thread
ENTRY: sNew - New client socket
HISTORY:
KeithMo 09-Mar-1993 Created.
Johnl 02-Aug-1994 Reworked from FTP server
********************************************************************/
VOID SmtpOnConnect( IN SOCKET sNew,
IN SOCKADDR_IN * psockaddr, //Should be SOCKADDR *
IN PVOID pEndpointContext,
IN PVOID pEndpointObject )
{
PIIS_ENDPOINT pEndpoint = (PIIS_ENDPOINT)pEndpointContext;
INT cbAddr = sizeof( sockaddr );
SOCKADDR_IN sockaddr;
SMTP_SERVER_INSTANCE *pInstance;
BOOL fProcessed;
BOOL fMaxConnExceeded;
DBG_ASSERT( sNew != INVALID_SOCKET );
DBG_ASSERT( psockaddr != NULL );
if ( g_pInetSvc->QueryCurrentServiceState() != SERVICE_RUNNING )
{
DBGPRINTF((DBG_CONTEXT,"Connection attempt on inactive service\n"));
goto error_exit;
}
if ( getsockname( sNew, (PSOCKADDR) &sockaddr, &cbAddr ) != 0 )
{
goto error_exit;
}
//
// Find Instance
//
pInstance = (SMTP_SERVER_INSTANCE *)
((PIIS_ENDPOINT)pEndpointContext)->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.
//
goto error_exit;
}
fProcessed = ProcessNewClient( sNew,
pEndpointObject,
pInstance,
fMaxConnExceeded,
psockaddr);
if ( fProcessed)
{
//StatCheckAndSetMaxConnections();
}
return;
error_exit:
CloseSocket( sNew );
return;
} // SmtpOnConnect
VOID
SmtpOnConnectEx(
VOID * patqContext,
DWORD cbWritten,
DWORD err,
OVERLAPPED * lpo
)
{
BOOL fAllowConnection = FALSE;
PVOID pvBuff = NULL;
PSOCKADDR_IN psockAddrLocal = NULL;
PSOCKADDR_IN psockAddrRemote = NULL;
SOCKET sNew;
PIIS_ENDPOINT pEndpoint;
PSMTP_SERVER_INSTANCE pInstance;
BOOL fProcessed = FALSE;
BOOL fAtqContextToBeFreed = TRUE;
BOOL fMaxConnExceeded;
if ( err || !lpo || g_IsShuttingDown)
{
if(g_IsShuttingDown)
{
DBGPRINTF(( DBG_CONTEXT,
"[SmtpOnConnectEx] Completion failed because of shutdown %d, Atq context %lx\n",
err,
patqContext ));
}
else
{
DBGPRINTF(( DBG_CONTEXT,
"[SmtpOnConnectEx] Completion failed with error %d, Atq context %lx\n",
err,
patqContext ));
}
goto exit;
}
//
// Get AcceptEx parameters
//
AtqGetAcceptExAddrs( (PATQ_CONTEXT) patqContext,
&sNew,
&pvBuff,
(PVOID*)&pEndpoint,
(PSOCKADDR *) &psockAddrLocal,
(PSOCKADDR *) &psockAddrRemote );
if ( g_pInetSvc->QueryCurrentServiceState() != SERVICE_RUNNING )
{
DBGPRINTF((DBG_CONTEXT,"Connection attempt on inactive service\n"));
goto exit ;
}
//
// Find Instance
//
pInstance = (SMTP_SERVER_INSTANCE *)
((PIIS_ENDPOINT)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,
(UINT_PTR) pInstance->QueryConnectionTimeout());
fProcessed = ProcessNewClient( sNew,
pEndpoint,
pInstance,
fMaxConnExceeded,
psockAddrRemote,
psockAddrLocal,
(PATQ_CONTEXT ) patqContext,
pvBuff,
cbWritten,
&fAtqContextToBeFreed);
exit:
if ( !fProcessed && fAtqContextToBeFreed )
{
// DBGPRINTF((DBG_CONTEXT,
// "ProcessNewClient returned false\n"));
//
// We failed to process this connection. Free up resources properly
//
DBG_REQUIRE( AtqCloseSocket( (PATQ_CONTEXT )patqContext, FALSE));
AtqFreeContext( (PATQ_CONTEXT ) patqContext, TRUE );
}
return;
} // SmtpOnConnectEx
#if 0
BOOL
SendError(
SOCKET socket,
DWORD ids
)
{
STR strResponse;
if ( !strResponse.Resize( 512 ) ||
!HTTP_REQ_BASE::BuildExtendedStatus( &strResponse,
HT_FORBIDDEN,
NO_ERROR,
ids ))
{
DBGPRINTF((DBG_CONTEXT,
"[SendError] Failed to build status (error %d)\n",
GetLastError()));
return FALSE;
}
//
// Do a synchronous send
//
send( socket,
strResponse.QueryStr(),
strResponse.QueryCB(),
0 );
return TRUE ;
} // SendError
#endif
/*******************************************************************
NAME: CloseSocket
SYNOPSIS: Closes the specified socket. This is just a thin
wrapper around the "real" closesocket() API.
ENTRY: sock - The socket to close.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 26-Apr-1993 Created.
********************************************************************/
SOCKERR CloseSocket( SOCKET sock )
{
SOCKERR serr = 0;
//
// Close the socket.
//
#if 0
shutdown( sock, 1 ); // Davidtr sez not needed
#endif
if( closesocket( sock ) != 0 )
{
serr = WSAGetLastError();
}
if( serr == 0 )
{
// DBGPRINTF(( DBG_CONTEXT,
// "closed socket %d\n",
// sock ));
}
else
{
DBGPRINTF(( DBG_CONTEXT,
"cannot close socket %d, error %d\n",
sock,
serr ));
}
return serr;
} // CloseSocket
/*++
Description:
Handles a completed IO.
Arguments:
pvContext: the context pointer specified in the initial IO
cbWritten: the number of bytes sent
dwCompletionStatus: the status of the completion (usually NO_ERROR)
lpo: the overlapped structure associated with the IO
Returns:
nothing.
--*/
VOID
SmtpCompletion(
PVOID pvContext,
DWORD cbWritten,
DWORD dwCompletionStatus,
OVERLAPPED * lpo
)
{
BOOL WasProcessed;
SMTP_CONNECTION *pCC = (SMTP_CONNECTION *) pvContext;
_ASSERT(pCC);
_ASSERT(pCC->IsValid());
_ASSERT(pCC->QuerySmtpInstance() != NULL);
//
// if we could not process a command, or we were
// told to destroy this object, close the connection.
//
WasProcessed = pCC->ProcessClient(cbWritten, dwCompletionStatus, lpo);
}
/*++
Description:
Handles a completed IO.
Arguments:
pvContext: the context pointer specified in the initial IO
cbWritten: the number of bytes sent
dwCompletionStatus: the status of the completion (usually NO_ERROR)
lpo: the overlapped structure associated with the IO
Returns:
nothing.
--*/
VOID
SmtpCompletionFIO(
PFIO_CONTEXT pFIOContext,
FH_OVERLAPPED *pOverlapped,
DWORD cbWritten,
DWORD dwCompletionStatus)
{
BOOL WasProcessed;
SMTP_CONNECTION *pCC = (SMTP_CONNECTION *) (((SERVEREVENT_OVERLAPPED *) pOverlapped)->ThisPtr);
_ASSERT(pCC);
_ASSERT(pCC->IsValid());
_ASSERT(pCC->QuerySmtpInstance() != NULL);
//
// if we could not process a command, or we were
// told to destroy this object, close the connection.
//
WasProcessed = pCC->ProcessClient(cbWritten, dwCompletionStatus, (OVERLAPPED *) pOverlapped);
}
#if 0
VOID
ServerEventCompletion(
PVOID pvContext,
DWORD cbWritten,
DWORD dwCompletionStatus,
OVERLAPPED * lpo
)
{
SERVEREVENT_OVERLAPPED * Ov = (SERVEREVENT_OVERLAPPED *) lpo;
_ASSERT(pvContext);
Ov->Overlapped.pfnCompletion(Ov->ThisPtr, cbWritten, dwCompletionStatus, lpo);
}
#endif