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

5393 lines
174 KiB
C++

/*++
Copyright (c) 1994 Microsoft Corporation
Module Name :
smtpout.cxx
Abstract:
This module defines the functions for derived class of connections
for Internet Services ( class SMTP_CONNOUT)
Author:
Rohan Phillips ( Rohanp ) 02-Feb-1996
Project:
SMTP Server DLL
Functions Exported:
SMTP_CONNOUT::~SMTP_CONNOUT()
BOOL SMTP_CONNOUT::ProcessClient( IN DWORD cbWritten,
IN DWORD dwCompletionStatus,
IN BOOL fIOCompletion)
BOOL SMTP_CONNOUT::StartupSession( VOID)
Revision History:
--*/
/************************************************************
* Include Headers
************************************************************/
#define INCL_INETSRV_INCS
#include "smtpinc.h"
#include "remoteq.hxx"
#include "smtpmsg.h"
//
// ATL includes
//
#define _ATL_NO_DEBUG_CRT
#define _ASSERTE _ASSERT
#define _WINDLL
#include "atlbase.h"
extern CComModule _Module;
#include "atlcom.h"
#undef _WINDLL
//
// SEO includes
//
#include "seo.h"
#include "seolib.h"
#include <memory.h>
#include "smtpcli.hxx"
#include "smtpout.hxx"
#include <smtpevent.h>
#include <smtpguid.h>
//
// Dispatcher implementation
//
#include "pe_dispi.hxx"
int strcasecmp(char *s1, char *s2);
int strncasecmp(char *s1, char *s2, int n);
extern CHAR g_VersionString[];
static char * IsLineCompleteBW(IN OUT char * pchRecvd, IN DWORD cbRecvd, IN DWORD cbMaxRecvBuffer);
#define SMTP_DUMMY_FAILURE (0x1000 | SMTP_ACTION_ABORTED_CODE)
#define SMTPOUT_CONTENT_FILE_IO_TIMEOUT 2*60*1000
static const char * TO_MANY_RCPTS_ERROR = "552 Too many recipients";
#define KNOWN_AUTH_FLAGS ((DWORD)(DOMAIN_INFO_USE_NTLM | DOMAIN_INFO_USE_PLAINTEXT | DOMAIN_INFO_USE_DPA \
| DOMAIN_INFO_USE_KERBEROS))
#define INVALID_RCPT_IDX_VALUE 0xFFFFFFFF
// provide memory for static declared in SMTP_CONNOUT
CPool SMTP_CONNOUT::Pool(CLIENT_CONNECTION_SIGNATURE_VALID);
//
// Statics for outbound protocol events
//
CInboundDispatcherClassFactory g_cfInbound;
COutboundDispatcherClassFactory g_cfOutbound;
CResponseDispatcherClassFactory g_cfResponse;
/************************************************************
* Functions
************************************************************/
#define MAX_LOG_ERROR_LEN (500)
extern void DeleteDnsRec(PSMTPDNS_RECS pDnsRec);
VOID
SmtpCompletion(
PVOID pvContext,
DWORD cbWritten,
DWORD dwCompletionStatus,
OVERLAPPED * lpo
);
/*++
Name :
InternetCompletion
Description:
Handles a completed I/O for outbound connections.
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 InternetCompletion(PVOID pvContext, DWORD cbWritten,
DWORD dwCompletionStatus, OVERLAPPED * lpo)
{
BOOL WasProcessed;
SMTP_CONNOUT *pCC = (SMTP_CONNOUT *) pvContext;
_ASSERT(pCC);
_ASSERT(pCC->IsValid());
_ASSERT(pCC->QuerySmtpInstance() != NULL);
TraceFunctEnterEx((LPARAM) pCC, "InternetCompletion");
// if we could not process a command, or we were
// told to destroy this object, close the connection.
WasProcessed = pCC->ProcessClient(cbWritten, dwCompletionStatus, lpo);
if (!WasProcessed) {
pCC->DisconnectClient();
pCC->QuerySmtpInstance()->RemoveOutboundConnection(pCC);
delete pCC;
pCC = NULL;
}
//TraceFunctLeaveEx((LPARAM)pCC);
}
VOID FIOInternetCompletion(PFIO_CONTEXT pFIOContext,
PFH_OVERLAPPED lpo,
DWORD cbWritten,
DWORD dwCompletionStatus)
{
BOOL WasProcessed;
SMTP_CONNOUT *pCC = (SMTP_CONNOUT *) (((SERVEREVENT_OVERLAPPED *) lpo)->ThisPtr);
_ASSERT(pCC);
_ASSERT(pCC->IsValid());
_ASSERT(pCC->QuerySmtpInstance() != NULL);
TraceFunctEnterEx((LPARAM) pCC, "InternetCompletion");
// if we could not process a command, or we were
// told to destroy this object, close the connection.
WasProcessed = pCC->ProcessClient(cbWritten, dwCompletionStatus, (OVERLAPPED *) lpo);
if (!WasProcessed) {
pCC->DisconnectClient();
pCC->QuerySmtpInstance()->RemoveOutboundConnection(pCC);
delete pCC;
pCC = NULL;
}
//TraceFunctLeaveEx((LPARAM)pCC);
}
void SMTP_CONNOUT::ProtocolLogCommand(LPSTR pszCommand,
DWORD cParameters,
LPCSTR pszIpAddress,
FORMAT_SMTP_MESSAGE_LOGLEVEL eLogLevel)
{
char *pszCR = NULL, *pszParameters = NULL, *pszSpace = NULL;
DWORD cBytesSent;
if (eLogLevel == FSM_LOG_NONE) return;
if (pszCR = strchr(pszCommand, '\r')) *pszCR = 0;
if (pszSpace = strchr(pszCommand, ' ')) {
*pszSpace = 0;
pszParameters = (eLogLevel == FSM_LOG_ALL) ? pszSpace + 1 : NULL;
}
cBytesSent = strlen(pszCommand);
LogRemoteDeliveryTransaction(
pszCommand,
NULL,
pszParameters,
pszIpAddress,
0,
0,
cBytesSent,
0,
FALSE);
if (pszCR) *pszCR = '\r';
if (pszSpace) *pszSpace = ' ';
}
void SMTP_CONNOUT::ProtocolLogResponse(LPSTR pszResponse,
DWORD cParameters,
LPCSTR pszIpAddress)
{
char *pszCR = NULL;
DWORD cBytesReceived;
if (pszCR = strchr(pszResponse, '\r')) *pszCR = 0;
cBytesReceived = strlen(pszResponse);
LogRemoteDeliveryTransaction(
NULL,
NULL,
pszResponse,
pszIpAddress,
0,
0,
cBytesReceived,
0,
TRUE);
if (pszCR) *pszCR = '\r';
}
void SMTP_CONNOUT::LogRemoteDeliveryTransaction(
LPCSTR pszOperation,
LPCSTR pszTarget,
LPCSTR pszParameters,
LPCSTR pszIpAddress,
DWORD dwWin32Error,
DWORD dwServiceSpecificStatus,
DWORD dwBytesSent,
DWORD dwBytesReceived,
BOOL fResponse
)
{
INETLOG_INFORMATION translog;
DWORD dwLog;
LPSTR lpNull = "";
DWORD cchError = MAX_LOG_ERROR_LEN;
char VersionString[] = "SMTP";
char szClientUserNameCommand[] = "OutboundConnectionCommand";
char szClientUserNameResponse[] = "OutboundConnectionResponse";
//Buffers to prevent overwrite by IIS logging
//which does evil things like change '<sp>' to '+'
// 6/23/99 - MikeSwa
char szOperationBuffer[20] = ""; //This is the protocol verb
char szTargetBuffer[20] = ""; //Currently unused by all callers
char szParametersBuffer[1024] = ""; //Data portion of buffer information
ZeroMemory(&translog, sizeof(translog));
if (pszParameters == NULL)
pszParameters = lpNull;
if (pszIpAddress == NULL)
pszIpAddress = lpNull;
translog.pszVersion = VersionString;
translog.msTimeForProcessing = QueryProcessingTime();;
if (fResponse) {
translog.pszClientUserName = szClientUserNameResponse;
} else {
translog.pszClientUserName = szClientUserNameCommand;
}
translog.pszClientHostName = m_ConnectedDomain;
translog.cbClientHostName = lstrlen(m_ConnectedDomain);
//Make sure we log the correct port number
if (IsSecure()) {
translog.dwPort = QuerySmtpInstance()->GetRemoteSmtpSecurePort();
} else {
translog.dwPort = QuerySmtpInstance()->GetRemoteSmtpPort();
}
//Copy buffers
if (pszOperation) {
lstrcpyn(szOperationBuffer, pszOperation, sizeof(szOperationBuffer)-sizeof(CHAR));
translog.pszOperation = szOperationBuffer;
translog.cbOperation = lstrlen(szOperationBuffer);
} else {
translog.pszOperation = "";
translog.cbOperation = 0;
}
if (pszTarget) {
lstrcpyn(szTargetBuffer, pszTarget, sizeof(szTargetBuffer)-sizeof(CHAR));
translog.pszTarget = szTargetBuffer;
translog.cbTarget = lstrlen(szTargetBuffer);
} else {
translog.pszTarget = "";
translog.cbTarget = 0;
}
if (pszParameters) {
lstrcpyn(szParametersBuffer, pszParameters, sizeof(szParametersBuffer)-sizeof(CHAR));
translog.pszParameters = szParametersBuffer;
} else {
translog.pszParameters = "";
}
//Detect if usage drastically changes... but don't check parameters, because
//we don't care about logging more than 1K per command
_ASSERT(sizeof(szOperationBuffer) > lstrlen(pszOperation));
_ASSERT(sizeof(szTargetBuffer) > lstrlen(pszTarget));
translog.dwBytesSent = dwBytesSent;
translog.dwBytesRecvd = dwBytesReceived;
translog.dwWin32Status = dwWin32Error;
translog.dwProtocolStatus = dwServiceSpecificStatus;
dwLog = QuerySmtpInstance()->m_Logging.LogInformation( &translog);
}
/*++
Name:
SMTP_CONNOUT::SMTP_CONNOUT
Constructs a new SMTP connection object for the client
connection given the client connection socket and socket
address. This constructor is private. Only the Static
member funtion, declared below, can call it.
Arguments:
sClient socket for communicating with client
psockAddrRemote pointer to address of the remote client
( the value should be copied).
psockAddrLocal pointer to address for the local card through
which the client came in.
pAtqContext pointer to ATQ Context used for AcceptEx'ed conn.
pvInitialRequest pointer to void buffer containing the initial request
cbInitialData count of bytes of data read initially.
--*/
SMTP_CONNOUT::SMTP_CONNOUT(
IN PSMTP_SERVER_INSTANCE pInstance,
IN SOCKET sClient,
IN const SOCKADDR_IN * psockAddrRemote,
IN const SOCKADDR_IN * psockAddrLocal /* = NULL */ ,
IN PATQ_CONTEXT pAtqContext /* = NULL */ ,
IN PVOID pvInitialRequest/* = NULL*/ ,
IN DWORD cbInitialData /* = 0 */
)
: m_encryptCtx( TRUE ),
m_securityCtx(pInstance,
TCPAUTH_CLIENT| TCPAUTH_UUENCODE,
((PSMTP_SERVER_INSTANCE)pInstance)->QueryAuthentication()),
CLIENT_CONNECTION ( sClient, psockAddrRemote,
psockAddrRemote, pAtqContext,
pvInitialRequest, cbInitialData )
{
_ASSERT(pInstance != NULL);
m_cActiveThreads = 0;
m_cPendingIoCount = 0;
m_MsgOptions = 0;
m_AuthToUse = 0;
m_pInstance = pInstance;
m_UsingSSL = FALSE;
m_fCanTurn = TRUE;
m_pIMsg = NULL;
m_pIMsgRecips = NULL;
m_pISMTPConnection = NULL;
m_AdvContext = NULL;
m_pDnsRec = NULL;
m_EhloSent = FALSE;
pInstance->IncConnOutObjs();
//
// By default, we use the smallish receive buffer inherited from
// the base CLIENT_CONNECTION object and a smallish output buffer defined in
// SMTP_CONNOUT
//
m_precvBuffer = m_recvBuffer;
m_cbMaxRecvBuffer = sizeof(m_recvBuffer);
m_pOutputBuffer = m_OutputBuffer;
m_cbMaxOutputBuffer = sizeof(m_OutputBuffer);
m_OutboundContext.m_cabNativeCommand.SetBuffer(
m_NativeCommandBuffer,
SMTP_MAX_COMMAND_LENGTH);
m_pmszTurnList = NULL;
m_szCurrentTURNDomain = NULL;
m_IMsgDotStuffedFileHandle = NULL;
m_ConnectedDomain [0] = '\0';
m_pBindInterface = NULL;
m_SeoOverlapped.ThisPtr = (PVOID) this;
m_SeoOverlapped.pfnCompletion = InternetCompletion;
//initialize this error in case the connection gets
//broken early.
m_Error = ERROR_BROKEN_PIPE;
m_fNeedRelayedDSN = FALSE;
m_fHadHardError = FALSE;
m_fHadTempError = FALSE;
m_fHadSuccessfulDelivery = FALSE;
//
// Protocol Events
//
m_fNativeHandlerFired = FALSE;
m_pOutboundDispatcher = NULL;
m_pResponseDispatcher = NULL;
//
// Diagnostic Information
//
m_hrDiagnosticError = S_OK;
m_szDiagnosticVerb = NULL;
m_szDiagnosticResponse[0]= '\0';
m_szCurrentETRNDomain = NULL;
m_pszSSLVerificationName = NULL;
}
/*++
Name :
SMTP_CONNOUT::~SMTP_CONNOUT (void)
Description:
Destructor for outbound connection object.
This routine checks to see if there was a
current mail object that was being processed
before this destructor was called and does
whatever is necessary to clean up its' memory.
Then it checks the mailbag and cleans up any
mail objects it finds in there.
Arguments:
none
Returns:
none
--*/
SMTP_CONNOUT::~SMTP_CONNOUT (void)
{
PSMTP_SERVER_INSTANCE pInstance = NULL;
HRESULT hrDiagnostic = S_OK;
char * pTempBuffer = NULL;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::~SMTP_CONNOUT (void)");
//We need to call our cleanup function... so that the ATQ context will be
//freed. We do this first, because a message ack or connection ack may
//trigger DSN generation, which may take long enough to cause the context
//to time out and complete on us (causing one thread to AV).
Cleanup();
//catch all message ack call
//NK** : Need to substitute it with an HRESULT based on the actual internal error
//mikeswa - 9/11/98 - Add check recips to flags
HandleCompletedMailObj(MESSAGE_STATUS_RETRY_ALL | MESSAGE_STATUS_CHECK_RECIPS, "451 Remote host dropped connection", 0);
//if we were doing a TLS transmission that got interrupted, we need to
//destroy the AtqContext we created for reading from the mail file.
//FreeAtqFileContext();
if (m_pISMTPConnection) {
//Ack the connection
m_pISMTPConnection->AckConnection((eConnectionStatus)m_dwConnectionStatus);
if (FAILED(m_hrDiagnosticError))
{
m_pISMTPConnection->SetDiagnosticInfo(m_hrDiagnosticError,
m_szDiagnosticVerb, m_szDiagnosticResponse);
}
else if (CONNECTION_STATUS_OK != m_dwConnectionStatus)
{
//Report appropriate diagnostic information if we don't have specific failures
switch (m_dwConnectionStatus)
{
case CONNECTION_STATUS_DROPPED:
hrDiagnostic = AQUEUE_E_CONNECTION_DROPPED;
break;
case CONNECTION_STATUS_FAILED:
hrDiagnostic = AQUEUE_E_CONNECTION_FAILED;
break;
default:
hrDiagnostic = E_FAIL;
}
m_pISMTPConnection->SetDiagnosticInfo(hrDiagnostic, NULL, NULL);
}
m_pISMTPConnection->Release();
m_pISMTPConnection = NULL;
}
//If we had a TURN list free it up
if (m_pmszTurnList) {
delete m_pmszTurnList;
m_pmszTurnList = NULL;
m_szCurrentTURNDomain = NULL;
}
pInstance = (PSMTP_SERVER_INSTANCE ) InterlockedExchangePointer((PVOID *) &m_pInstance, (PVOID) NULL);
if (pInstance != NULL) {
pInstance->DecConnOutObjs();
}
pTempBuffer = (char *) InterlockedExchangePointer((PVOID *) &m_precvBuffer, (PVOID) &m_recvBuffer[0]);
if (pTempBuffer != m_recvBuffer) {
delete [] pTempBuffer;
}
pTempBuffer = (char *) InterlockedExchangePointer((PVOID *) &m_pOutputBuffer, (PVOID) &m_OutputBuffer[0]);
if (pTempBuffer != m_OutputBuffer) {
delete [] pTempBuffer;
}
// Protocol events: Release the dispatchers
if (m_pOutboundDispatcher)
m_pOutboundDispatcher->Release();
if (m_pResponseDispatcher)
m_pResponseDispatcher->Release();
if (m_pDnsRec) {
DeleteDnsRec(m_pDnsRec);
m_pDnsRec = NULL;
}
if (m_pszSSLVerificationName) {
delete [] m_pszSSLVerificationName;
m_pszSSLVerificationName;
}
DebugTrace((LPARAM) this,"%X was deleted", this);
TraceFunctLeaveEx((LPARAM)this);
}
/*++
Name :
SMTP_CONNOUT::DisconnectClient(DWORD dwErrorCode)
Description:
Disconnects from the remote server. It first calls
CLIENT_CONNECTION::DisconnectClient, and then shuts down any mail-file
read handles we may be pending reads on.
Arguments:
dwErrorCode -- Passed through to CLIENT_CONNECTION::Disconnect
Returns:
nothing
--*/
VOID SMTP_CONNOUT::DisconnectClient(DWORD dwErrorCode)
{
TraceFunctEnter("SMTP_CONNOUT::DisconnectClient");
if (m_DoCleanup)
CLIENT_CONNECTION::DisconnectClient();
}
/*++
Name :
SMTP_CONNOUT::HandleCompletedMailObj
Description:
This routinr gets called after the mail file
has been sent. It will either requeue the object
in the outbound queue, retry queue, BadMail,etc.
Arguments:
none
Returns:
none
--*/
void SMTP_CONNOUT::HandleCompletedMailObj(DWORD MsgStatus, char * szExtendedStatus, DWORD cbExtendedStatus)
{
MessageAck MsgAck;
HRESULT hr = S_OK;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::HandleCompletedMailObj");
_ASSERT(IsValid());
if (m_pISMTPConnection) {
if (m_pIMsgRecips) {
//Uncheck all marked recipients if the connection has been dropped
if (((m_dwConnectionStatus != CONNECTION_STATUS_OK) || (szExtendedStatus[0] != SMTP_COMPLETE_SUCCESS)) &&
m_NumRcptSentSaved) {
UnMarkHandledRcpts();
}
m_pIMsgRecips->Release();
m_pIMsgRecips = NULL;
}
if (m_pIMsg) {
MsgAck.pvMsgContext = (DWORD *) m_AdvContext;
MsgAck.pIMailMsgProperties = m_pIMsg;
if ( (MsgStatus & MESSAGE_STATUS_RETRY_ALL) ||
(MsgStatus & MESSAGE_STATUS_NDR_ALL)) {
//DebugTrace((LPARAM) this,"CompObj:file %s going to %s was retryed", FileName, m_ConnectedDomain);
} else {
//DebugTrace((LPARAM) this,"CompObj:file %s going to %s was delivered", FileName, m_ConnectedDomain);
}
MsgAck.dwMsgStatus = MsgStatus;
MsgAck.dwStatusCode = 0;
//We will have an extended status string to go along with the Status code
//in case of some major failure that makes us fail the complete message
if (MsgStatus & MESSAGE_STATUS_EXTENDED_STATUS_CODES ) {
MsgAck.cbExtendedStatus = cbExtendedStatus;
MsgAck.szExtendedStatus = szExtendedStatus;
}
if (m_pBindInterface) {
m_pBindInterface->ReleaseContext();
m_pBindInterface->Release();
m_pBindInterface = NULL;
if( NULL != m_IMsgDotStuffedFileHandle )
{
ReleaseContext( m_IMsgDotStuffedFileHandle );
m_IMsgDotStuffedFileHandle = NULL;
}
}
//
// Do Message Tracking
//
MSG_TRACK_INFO msgTrackInfo;
ZeroMemory( &msgTrackInfo, sizeof( msgTrackInfo ) );
msgTrackInfo.dwEventId = MTE_END_OUTBOUND_TRANSFER;
msgTrackInfo.pszPartnerName = m_ConnectedDomain;
if( MsgStatus & MESSAGE_STATUS_RETRY_ALL )
{
msgTrackInfo.dwRcptReportStatus = MP_STATUS_RETRY;
}
else if( MsgStatus & MESSAGE_STATUS_NDR_ALL )
{
msgTrackInfo.dwEventId = MTE_NDR_ALL;
msgTrackInfo.pszPartnerName = NULL;
msgTrackInfo.dwRcptReportStatus = MP_STATUS_ABORT_DELIVERY;
}
m_pInstance->WriteLog( &msgTrackInfo, m_pIMsg, NULL, NULL );
m_pISMTPConnection->AckMessage(&MsgAck);
m_pIMsg->Release();
m_pIMsg = NULL;
}
}
TraceFunctLeaveEx((LPARAM) this);
}
/*++
Name :
SMTP_CONNOUT::UnMarkHandledRcpts
Description:
When we send out recipients we assumptively mark the recipients as
delivered or failed based on the responses. Later if it turns out that we
could never completely send the message. we need to rset the status of
successful recipients. However, if we have a hard per-recipient error,
we should leave the error code intact (otherwise the sender may receive
a DELAY DSN with a 500-level status code).
Arguments:
none
Returns:
none
--*/
BOOL SMTP_CONNOUT::UnMarkHandledRcpts(void)
{
DWORD i;
HRESULT hr = S_OK;
DWORD dwRecipientFlags;
DWORD dwRcptsSaved = m_NumRcptSentSaved;
//
// It is possible for this to be called after HandleCompletedMailObj (after
// a 421 response to a DATA command for example). We should bail if we
// do not have a mailmsg ptr.
//
if (!m_pIMsgRecips)
return (TRUE);
for (i = m_FirstAddressinCurrentMail; (i < m_NumRcpts) && dwRcptsSaved;i++) {
//Get to the next rcpt that we send out this time
if (m_RcptIndexList[i] != INVALID_RCPT_IDX_VALUE) {
//
// The ideal way to handle this situation is to use the
// RP_VOLATILE_FLAGS_MASK bits in the recipient flags a tmp
// storage and then "commit" the handled bit after a successful
// connection. Given how mailmsg works, this is not a problem
// - While we are processing the message... it is not possible
// for it to be saved to disk until we are done with it.
// - If we can write a property once before committing... we
// can always rewrite a property of the same size (since
// the required portion of the property stream is already
// in memory.
// I have added the ASSERT(SUCEEDED(hr)) below
// - mikeswa 9/11/98 (updated 10/05/2000)
dwRecipientFlags = 0;
hr = m_pIMsgRecips->GetDWORD(m_RcptIndexList[i], IMMPID_RP_RECIPIENT_FLAGS,&dwRecipientFlags);
if (FAILED(hr)) {
//Problemmo
SetLastError(ERROR_OUTOFMEMORY);
return (FALSE);
}
//Check to see if we marked it as delivered... and unmark it if we did
if (RP_DELIVERED == (dwRecipientFlags & RP_DELIVERED)) {
dwRecipientFlags &= ~RP_DELIVERED;
hr = m_pIMsgRecips->PutDWORD(m_RcptIndexList[i], IMMPID_RP_RECIPIENT_FLAGS,dwRecipientFlags);
if (FAILED(hr)) {
//
// We need to understand how this can fail... mailmsg
// is designed so this should not happen.
//
ASSERT(FALSE && "Potential loss of recipient");
SetLastError(ERROR_OUTOFMEMORY);
return (FALSE);
}
}
dwRcptsSaved--;
}
}
return (TRUE);
}
/*++
Name :
SMTP_CONNOUT::InitializeObject
Description:
Initializes all member variables and pre-allocates
a mail context class
Arguments:
Options - SSL etc.
pszSSLVerificationName - Subject name to look for in server certificate
if using SSL
Returns:
TRUE if memory can be allocated.
FALSE if no memory can be allocated
--*/
BOOL SMTP_CONNOUT::InitializeObject (
DWORD Options,
LPSTR pszSSLVerificationName)
{
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::InitializeObject");
m_szCurrentETRNDomain = NULL;
m_cbReceived = 0;
m_cbParsable = 0;
m_OutputBufferSize = 0;
m_NumRcptSent = 0;
m_FirstAddressinCurrentMail = 0;
m_NumRcptSentSaved = 0;
m_SizeOptionSize = 0;
m_Flags = 0;
m_NumFailedAddrs = 0;
m_cActiveThreads = 0;
m_cPendingIoCount = 0;
m_FileSize = 0;
m_NextAddress = 0;
m_FirstPipelinedAddress = 0;
m_First552Address = -1;
m_NextState = NULL;
m_HeloSent = FALSE;
m_EhloFailed = FALSE;
m_FirstRcpt = FALSE;
m_SendAgain = FALSE;
m_Active = TRUE;
m_HaveDataResponse = FALSE;
m_SecurePort = FALSE;
m_fNegotiatingSSL = FALSE;
m_MsgOptions = Options;
m_dwConnectionStatus = CONNECTION_STATUS_OK;
m_fUseBDAT = FALSE;
m_fNeedRelayedDSN = FALSE;
m_fHadHardError = FALSE;
m_fHadTempError = FALSE;
m_fHadSuccessfulDelivery = FALSE;
if (Options & KNOWN_AUTH_FLAGS) {
m_pInstance->LockGenCrit();
// Initialize Security Context
//
if (!m_securityCtx.SetInstanceAuthPackageNames(
m_pInstance->GetProviderPackagesCount(),
m_pInstance->GetProviderNames(),
m_pInstance->GetProviderPackages())) {
m_Error = GetLastError();
ErrorTrace((LPARAM)this, "SetInstanceAuthPackageNames FAILED <Err=%u>",
m_Error);
fRet = FALSE;
}
//
// We want to set up the Cleartext authentication package
// for this connection based on the instance configuration.
// To enable MBS CTA,
// MD_SMTP_CLEARTEXT_AUTH_PROVIDER must be set to the package name.
// To disable it, the md value must be set to "".
//
if (fRet) {
m_securityCtx.SetCleartextPackageName(
m_pInstance->GetCleartextAuthPackage(),
m_pInstance->GetMembershipBroker());
if (*m_pInstance->GetCleartextAuthPackage() == '\0' ||
*m_pInstance->GetMembershipBroker() == '\0') {
m_fUseMbsCta = FALSE;
} else {
m_fUseMbsCta = TRUE;
}
}
m_pInstance->UnLockGenCrit();
}
m_pmszTurnList = NULL;
m_szCurrentTURNDomain = NULL;
m_UsingSSL = (Options & DOMAIN_INFO_USE_SSL);
m_TlsState = (Options & DOMAIN_INFO_USE_SSL) ? MUST_DO_TLS : DONT_DO_TLS;
m_TransmitTailBuffer[0] = '.';
m_TransmitTailBuffer[1] = '\r';
m_TransmitTailBuffer[2] = '\n';
m_TransmitBuffers.Head = NULL;
m_TransmitBuffers.HeadLength = 0;
m_TransmitBuffers.Tail = m_TransmitTailBuffer;
m_TransmitBuffers.TailLength = 3;
//
// Protocol events: get the response dispatcher for the session
//
m_pIEventRouter = m_pInstance->GetRouter();
if (m_pIEventRouter) {
HRESULT hr = m_pIEventRouter->GetDispatcherByClassFactory(
CLSID_CResponseDispatcher,
&g_cfResponse,
CATID_SMTP_ON_SERVER_RESPONSE,
IID_ISmtpServerResponseDispatcher,
(IUnknown **)&m_pResponseDispatcher);
if (!SUCCEEDED(hr)) {
// If we fail, we don't process protocol events
m_pResponseDispatcher = NULL;
ErrorTrace((LPARAM) this,
"Unable to get response dispatcher from router (%08x)", hr);
}
}
if (pszSSLVerificationName) {
m_pszSSLVerificationName = new char [lstrlen (pszSSLVerificationName) + 1];
if (!m_pszSSLVerificationName) {
fRet = FALSE;
SetLastError (ERROR_NOT_ENOUGH_MEMORY);
goto Exit;
}
lstrcpy (m_pszSSLVerificationName, pszSSLVerificationName);
}
StartProcessingTimer();
Exit:
TraceFunctLeaveEx((LPARAM) this);
return fRet;
}
BOOL SMTP_CONNOUT::GoToWaitForConnectResponseState(void)
{
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::GoToWaitForConnectResponseState( void)");
SetNextState (&SMTP_CONNOUT::WaitForConnectResponse);
m_Error = NO_ERROR;
m_LastClientIo = SMTP_CONNOUT::READIO;
IncPendingIoCount();
fRet = ReadFile(QueryMRcvBuffer(), m_cbMaxRecvBuffer);
if (!fRet) {
m_Error = GetLastError();
DebugTrace((LPARAM) this, "SMTP_CONNOUT::WaitForConnectResponseState - ReadFile failed with error %d", m_Error);
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
DecPendingIoCount();
SetLastError(m_Error);
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
fRet = FALSE;
}
return fRet;
}
BOOL SMTP_CONNOUT::GetNextTURNConnection(void)
{
HRESULT hr = S_OK;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::GetNextTURNConnection( void)");
//We are on a TURNed connection and need to start the queue for next
//domain in the turn list if it exists
//Before getting the next connection release the current one.
//
if (m_pISMTPConnection) {
//Ack the last connection
m_dwConnectionStatus = CONNECTION_STATUS_OK;
m_pISMTPConnection->AckConnection((eConnectionStatus)m_dwConnectionStatus);
m_pISMTPConnection->Release();
m_pISMTPConnection = NULL;
}
m_szCurrentTURNDomain = m_pmszTurnList->Next( m_szCurrentTURNDomain );
while (m_szCurrentTURNDomain && !QuerySmtpInstance()->IsShuttingDown()) {
//We have another domain to start
hr = QuerySmtpInstance()->GetConnManPtr()->GetNamedConnection(lstrlen(m_szCurrentTURNDomain), (CHAR*)m_szCurrentTURNDomain, &m_pISMTPConnection);
if (FAILED(hr)) {
//Something bad happened on this call
ErrorTrace((LPARAM) this, "StartSession - SMTP_ERROR_PROCESSING_CODE, GetNamedConnection failed %d",hr);
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
//If the link corresponding to this domain does not exist in AQ, we get a NULL
//ISMTPConnection at this point
if (m_pISMTPConnection)
break;
else {
m_szCurrentTURNDomain = m_pmszTurnList->Next( m_szCurrentTURNDomain );
continue;
}
}
TraceFunctLeaveEx((LPARAM) this);
return TRUE;
}
/*++
Name :
SMTP_CONNOUT::StartSession
Description:
Starts up a session for new client.
starts off a receive request from client.
Arguments:
Returns:
TRUE if everything is O.K
FALSE if a write or a pended read failed
--*/
BOOL SMTP_CONNOUT::StartSession( void)
{
HRESULT hr = S_OK;
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::StartSession( void)");
_ASSERT(IsValid());
//We do not do s restart if the connection is a tunr connection
if (!m_pmszTurnList || !m_szCurrentTURNDomain) {
if (m_pDnsRec->pMailMsgObj) {
fRet = ReStartSession();
TraceFunctLeaveEx((LPARAM) this);
return fRet;
}
}
//
// We are either not doing SSL or are done establishing an SSL session.
// Lets do the real work of starting a session with a remote SMTP server.
//
m_IMsgFileHandle = NULL;
m_IMsgDotStuffedFileHandle = NULL;
//get the next object to send
//This is in loop because - we might have to pickup a new connection in case
//we are handling a TURN list
while (1) {
// we can't call into GetNextMessage if we are on a TURN-only
// connection. if we did and a message queued up between the
// last time we were in StartSession and now then we would
// get back into the waitforconnect state, which would be really
// bad. so if we see the m_Flags set to TURN_ONLY_OPTION then we
// know that this is an empty TURN and we just pretend that there
// is no message to pick up.
if (!(m_Flags & TURN_ONLY_OPTION)) {
hr = m_pISMTPConnection->GetNextMessage(&m_pIMsg, (DWORD **) &m_AdvContext, &m_NumRcpts, &m_RcptIndexList);
} else {
m_pIMsg = NULL;
hr = HRESULT_FROM_WIN32(ERROR_EMPTY);
}
if(FAILED(hr) || (m_pIMsg == NULL))
{
m_fCanTurn = FALSE;
if (m_pmszTurnList && m_szCurrentTURNDomain) {
//We have valid TURN list - Try and get the connetion for next domain to TURN
if (GetNextTURNConnection()) {
//We loop back if we got a valid connection. Otherwise we drop thru
if (m_pISMTPConnection)
continue;
} else { //some error happened
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
}
if (m_MsgOptions & DOMAIN_INFO_SEND_TURN) {
if (m_HeloSent || m_EhloSent) {
// we will fall into this if we have already sent
// the helo
FormatSmtpMessage(FSM_LOG_ALL, "TURN\r\n");
m_cbReceived = 0;
m_cbParsable = 0;
m_pmszTurnList = NULL;
m_szCurrentTURNDomain = NULL;
SendSmtpResponse();
SetNextState (&SMTP_CONNOUT::DoTURNCommand);
TraceFunctLeaveEx((LPARAM) this);
return TRUE;
} else {
// we fall into this if we are sending TURN on an
// otherwise empty connection. At this point we have
// not yet sent EHLO, so it is not safe to send TURN.
m_Flags |= TURN_ONLY_OPTION;
return GoToWaitForConnectResponseState();
}
} else if ((m_MsgOptions & DOMAIN_INFO_SEND_ETRN) &&
(m_NextState == NULL) &&
!IsOptionSet(ETRN_SENT)) {
m_Flags |= ETRN_ONLY_OPTION;
return GoToWaitForConnectResponseState();
} else if (!(m_EhloSent)) {
// This is an empty connection
m_MsgOptions |= EMPTY_CONNECTION_OPTION;
return GoToWaitForConnectResponseState();
} else {
// 1/11/99 - MikeSwa
//There just happened to be no mail at this time. Could
//have been serviced by another connection, or just link
//state detection
if (HRESULT_FROM_WIN32(ERROR_EMPTY) == hr)
SetLastError(ERROR_EMPTY); //AQueue does not setlast error
else
{
SetDiagnosticInfo(hr, NULL, NULL);
}
DebugTrace((LPARAM) this,"Mailbag empty - returning FALSE");
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
}
else
{
//
// The actual file may have been deleted from the queue. If so, we
// need to ack the message and get the next one.
//
hr = m_pIMsg->QueryInterface(IID_IMailMsgBind, (void **)&m_pBindInterface);
if (FAILED(hr))
{
MessageAck MsgAck;
ErrorTrace((LPARAM)this, "Unable to Queryinterface message, going on to next one.");
m_IMsgFileHandle = NULL;
MsgAck.pvMsgContext = (DWORD *) m_AdvContext;
MsgAck.pIMailMsgProperties = m_pIMsg;
MsgAck.dwMsgStatus = MESSAGE_STATUS_RETRY;
MsgAck.dwStatusCode = 0;
m_pISMTPConnection->AckMessage(&MsgAck);
SetDiagnosticInfo(AQUEUE_E_BIND_ERROR, NULL, NULL);
m_pIMsg->Release();
m_pIMsg = NULL;
continue;
}
hr = m_pBindInterface->GetBinding(&m_IMsgFileHandle, NULL);
if (SUCCEEDED(hr))
{
MSG_TRACK_INFO msgTrackInfo;
ZeroMemory( &msgTrackInfo, sizeof( msgTrackInfo ) );
msgTrackInfo.pszServerName = g_ComputerName;
msgTrackInfo.dwEventId = MTE_BEGIN_OUTBOUND_TRANSFER;
m_pInstance->WriteLog( &msgTrackInfo, m_pIMsg, NULL, NULL );
break;
}
else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
MessageAck MsgAck;
DebugTrace(NULL,
"Message from queue has been deleted - ignoring it");
m_pBindInterface->Release();
m_IMsgFileHandle = NULL;
MsgAck.pvMsgContext = (DWORD *) m_AdvContext;
MsgAck.pIMailMsgProperties = m_pIMsg;
MsgAck.dwMsgStatus = MESSAGE_STATUS_ALL_DELIVERED;
MsgAck.dwStatusCode = 0;
m_pISMTPConnection->AckMessage(&MsgAck);
m_pIMsg->Release();
m_pIMsg = NULL;
}
else
{
ASSERT(FAILED(hr));
MessageAck MsgAck;
ErrorTrace((LPARAM)this, "Unable to Bind message, going on to next one.");
m_pBindInterface->Release();
m_IMsgFileHandle = NULL;
MsgAck.pvMsgContext = (DWORD *) m_AdvContext;
MsgAck.pIMailMsgProperties = m_pIMsg;
MsgAck.dwMsgStatus = MESSAGE_STATUS_RETRY;
MsgAck.dwStatusCode = 0;
m_pISMTPConnection->AckMessage(&MsgAck);
SetDiagnosticInfo(AQUEUE_E_BIND_ERROR, NULL, NULL);
m_pIMsg->Release();
m_pIMsg = NULL;
continue;
}
}
}
// Bump both the remote and total recipient counters
ADD_COUNTER (QuerySmtpInstance(), NumRcptsRecvdRemote, m_NumRcpts);
ADD_COUNTER (QuerySmtpInstance(), NumRcptsRecvd, m_NumRcpts);
hr = m_pIMsg->QueryInterface(IID_IMailMsgRecipients, (void **) &m_pIMsgRecips);
if (FAILED(hr)) {
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
m_FirstPipelinedAddress = 0;
//Nk** I moved this here from PerRcptEvent
m_NextAddress = 0;
//if m_NextState is NULL, this is the
//first time this routine has been called
//as a result of a connection. If m_NextState
//is not NULL, this means we just finished
//sending mail and we are about to send another
//mail message
if (m_NextState == NULL) {
m_Error = NO_ERROR;
DebugTrace((LPARAM) this,"start session called because of new connection");
m_FirstPipelinedAddress = 0;
SetNextState (&SMTP_CONNOUT::WaitForConnectResponse);
m_LastClientIo = SMTP_CONNOUT::READIO;
IncPendingIoCount();
fRet = ReadFile(QueryMRcvBuffer(), m_cbMaxRecvBuffer);
if (!fRet) {
m_Error = GetLastError();
DebugTrace((LPARAM) this, "SMTP_CONNOUT::StartSession - ReadFile failed with error %d", m_Error);
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
DecPendingIoCount();
SetLastError(m_Error);
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
}
} else {
DebugTrace((LPARAM) this,"start session called because item was found in mailbag");
m_cbReceived = 0;
m_cbParsable = 0;
m_OutputBufferSize = 0;
m_Error = NO_ERROR;
m_NumRcptSent = 0;
m_FirstAddressinCurrentMail = 0;
m_NumRcptSentSaved = 0;
m_NumFailedAddrs = 0;
m_SendAgain = FALSE;
m_HaveDataResponse = FALSE;
m_FirstPipelinedAddress = 0;
m_FirstRcpt = FALSE;
m_TransmitTailBuffer[0] = '.';
m_TransmitTailBuffer[1] = '\r';
m_TransmitTailBuffer[2] = '\n';
m_TransmitBuffers.Head = NULL;
m_TransmitBuffers.HeadLength = 0;
m_TransmitBuffers.Tail = m_TransmitTailBuffer;
m_TransmitBuffers.TailLength = 3;
//send a reset
m_fNativeHandlerFired = FALSE;
m_RsetReasonCode = BETWEEN_MSG;
fRet = DoRSETCommand(NULL, 0, 0);
if (!fRet) {
m_Error = GetLastError();
DebugTrace((LPARAM) this,"reset command failed in StartSession");
TraceFunctLeaveEx((LPARAM) this);
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
return FALSE;
}
// WaitForRSETResponse is smart and will raise the message
// start event
SetNextState (&SMTP_CONNOUT::WaitForRSETResponse);
}
TraceFunctLeaveEx((LPARAM) NULL);
return fRet;
}
BOOL SMTP_CONNOUT::DecPendingIoCountEx(void)
{
BOOL fRet = FALSE;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::DecPendingIoCountEx");
_ASSERT(IsValid());
if (InterlockedDecrement( &m_cPendingIoCount ) == 0) {
DebugTrace((LPARAM) this, "DecPendingIoCountEx deleting Smtp_Connout");
fRet = TRUE;
DisconnectClient();
QuerySmtpInstance()->RemoveOutboundConnection(this);
delete this;
}
TraceFunctLeaveEx((LPARAM) NULL);
return fRet;
}
BOOL SMTP_CONNOUT::ConnectToNextIpAddress(void)
{
BOOL fRet = TRUE;
REMOTE_QUEUE * pRemoteQ = NULL;
PSMTPDNS_RECS pDnsRec = NULL;
ISMTPConnection * pISMTPConnection = NULL;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::ConnectToNextIpAddress( void)");
if (m_pmszTurnList && m_szCurrentTURNDomain) {
ErrorTrace((LPARAM) this, "Failing ConnectToNextIpAddress because of TURN");
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
if (!m_fCanTurn) {
ErrorTrace((LPARAM) this, "Failing ConnectToNextIpAddress because of m_fCanTurn");
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
if (m_pDnsRec == NULL) {
ErrorTrace((LPARAM) this, "Failing ConnectToNextIpAddress becuase m_pDnsRec is NULL");
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
if (m_pDnsRec->StartRecord > m_pDnsRec->NumRecords) {
ErrorTrace((LPARAM) this, "Failing ConnectToNextIpAddress because StartRecord > NumRecords");
TraceFunctLeaveEx((LPARAM) NULL);
return FALSE;
}
if (m_pDnsRec->StartRecord == m_pDnsRec->NumRecords) {
if (IsListEmpty(&m_pDnsRec->DnsArray[m_pDnsRec->NumRecords - 1]->IpListHead)) {
ErrorTrace((LPARAM) this, "Failing ConnectToNextIpAddress because list is empty");
TraceFunctLeaveEx((LPARAM) NULL);
return FALSE;
}
m_pDnsRec->StartRecord = m_pDnsRec->NumRecords - 1;
} else if (IsListEmpty(&m_pDnsRec->DnsArray[m_pDnsRec->StartRecord]->IpListHead)) {
m_pDnsRec->StartRecord++;
if (m_pDnsRec->StartRecord > m_pDnsRec->NumRecords) {
TraceFunctLeaveEx((LPARAM) NULL);
return FALSE;
}
if (m_pDnsRec->StartRecord == m_pDnsRec->NumRecords) {
if (IsListEmpty(&m_pDnsRec->DnsArray[m_pDnsRec->NumRecords - 1]->IpListHead)) {
TraceFunctLeaveEx((LPARAM) NULL);
return FALSE;
}
m_pDnsRec->StartRecord = m_pDnsRec->NumRecords - 1;
}
}
if (m_NumRcptSentSaved) {
UnMarkHandledRcpts();
}
m_pDnsRec->pMailMsgObj = (PVOID) m_pIMsg;
m_pDnsRec->pAdvQContext = m_AdvContext;
m_pDnsRec->pRcptIdxList = (PVOID) m_RcptIndexList;
m_pDnsRec->dwNumRcpts = m_NumRcpts;
pDnsRec = m_pDnsRec;
m_pDnsRec = NULL;
pISMTPConnection = m_pISMTPConnection;
if (m_pIMsgRecips) {
m_pIMsgRecips->Release();
m_pIMsgRecips = NULL;
}
if (m_pBindInterface) {
m_pBindInterface->ReleaseContext();
m_pBindInterface->Release();
m_pBindInterface = NULL;
if( NULL != m_IMsgDotStuffedFileHandle )
{
ReleaseContext( m_IMsgDotStuffedFileHandle );
m_IMsgDotStuffedFileHandle = NULL;
}
}
pRemoteQ = (REMOTE_QUEUE *) QuerySmtpInstance()->QueryRemoteQObj();
fRet = pRemoteQ->ReStartAsyncConnections(
pDnsRec,
pISMTPConnection,
m_MsgOptions,
m_pszSSLVerificationName);
if (fRet) {
m_pISMTPConnection = NULL;
ErrorTrace((LPARAM)this, "RestartAsyncConnections failed.");
//close the socket
DisconnectClient();
}
TraceFunctLeaveEx((LPARAM) this);
return fRet;
}
BOOL SMTP_CONNOUT::ReStartSession(void)
{
BOOL fRet = TRUE;
HRESULT hr = S_OK;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::ReStartSession( void)");
DebugTrace((LPARAM) this,"restart session called because of new connection");
m_cbReceived = 0;
m_cbParsable = 0;
m_OutputBufferSize = 0;
m_Error = NO_ERROR;
m_NumRcptSent = 0;
m_NumRcptSentSaved = 0;
m_NumFailedAddrs = 0;
m_SendAgain = FALSE;
m_HaveDataResponse = FALSE;
m_FirstPipelinedAddress = 0;
m_FirstRcpt = FALSE;
m_TransmitTailBuffer[0] = '.';
m_TransmitTailBuffer[1] = '\r';
m_TransmitTailBuffer[2] = '\n';
m_TransmitBuffers.Head = NULL;
m_TransmitBuffers.HeadLength = 0;
m_TransmitBuffers.Tail = m_TransmitTailBuffer;
m_TransmitBuffers.TailLength = 3;
SetNextState (&SMTP_CONNOUT::WaitForConnectResponse);
m_pIMsg = (IMailMsgProperties *) m_pDnsRec->pMailMsgObj;
m_AdvContext = m_pDnsRec->pAdvQContext;
m_RcptIndexList = (DWORD *) m_pDnsRec->pRcptIdxList;
m_NumRcpts = m_pDnsRec->dwNumRcpts;
m_pDnsRec->pMailMsgObj = NULL;
m_pDnsRec->pAdvQContext = NULL;
m_pDnsRec->pRcptIdxList = NULL;
m_pDnsRec->dwNumRcpts = 0;
// Bump both the remote and total recipient counters
ADD_COUNTER (QuerySmtpInstance(), NumRcptsRecvdRemote, m_NumRcpts);
ADD_COUNTER (QuerySmtpInstance(), NumRcptsRecvd, m_NumRcpts);
hr = m_pIMsg->QueryInterface(IID_IMailMsgRecipients, (void **) &m_pIMsgRecips);
if (FAILED(hr)) {
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
hr = m_pIMsg->QueryInterface(IID_IMailMsgBind, (void **)&m_pBindInterface);
if (FAILED(hr)) {
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
hr = m_pBindInterface->GetBinding(&m_IMsgFileHandle, NULL);
if(FAILED(hr))
{
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
DWORD fFoundEmbeddedCrlfDot = FALSE;
DWORD fScanned = FALSE;
m_LastClientIo = SMTP_CONNOUT::READIO;
IncPendingIoCount();
fRet = ReadFile(QueryMRcvBuffer(), m_cbMaxRecvBuffer);
if (!fRet) {
m_Error = GetLastError();
DebugTrace((LPARAM) this, "SMTP_CONNOUT::StartSession - ReadFile failed with error %d", m_Error);
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
DecPendingIoCount();
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
SetLastError(m_Error);
}
TraceFunctLeaveEx((LPARAM)this);
return fRet;
}
/*++
Name :
SMTP_CONNOUT::CreateSmtpConnection
Description:
This is the static member function than is the only
entity that is allowed to create an SMTP_CONNOUT
class. This class cannot be allocated on the stack.
Arguments:
sClient socket for communicating with client
psockAddrRemote pointer to address of the remote client
( the value should be copied).
psockAddrLocal pointer to address for the local card through
which the client came in.
pAtqContext pointer to ATQ Context used for AcceptEx'ed conn.
pvInitialRequest pointer to void buffer containing the initial request
cbInitialData count of bytes of data read initially.
fUseSSL Indiates whether the connection is to use SSL
Returns:
A pointer to an SMTP_CONNOUT class or NULL
--*/
SMTP_CONNOUT * SMTP_CONNOUT::CreateSmtpConnection (
IN PSMTP_SERVER_INSTANCE pInstance,
IN SOCKET sClient,
IN const SOCKADDR_IN * psockAddrRemote,
IN const SOCKADDR_IN * psockAddrLocal /* = NULL */ ,
IN PATQ_CONTEXT pAtqContext /* = NULL */ ,
IN PVOID pTurnList/* = NULL*/ ,
IN DWORD cbInitialData /* = 0 */,
IN DWORD Options /* = 0 */,
IN LPSTR pszSSLVerificationName)
{
SMTP_CONNOUT * pSmtpObj;
TraceFunctEnter("SMTP_CONNOUT::CreateSmtpConnection");
pSmtpObj = new SMTP_CONNOUT (pInstance, sClient, psockAddrRemote, psockAddrLocal, pAtqContext,
pTurnList, cbInitialData);
if (pSmtpObj == NULL) {
SetLastError (ERROR_NOT_ENOUGH_MEMORY);
FatalTrace(NULL, "new SMTP_CONNOUT failed (err=%d)", GetLastError());
TraceFunctLeave();
return NULL;
}
if (!pSmtpObj->InitializeObject(Options, pszSSLVerificationName)) {
ErrorTrace(NULL, "InitializeObject failed (err=%d)", GetLastError());
delete pSmtpObj;
TraceFunctLeave();
return NULL;
}
if (pTurnList) {
//Set the TURN domainlist
pSmtpObj->SetTurnList((PTURN_DOMAIN_LIST)pTurnList);
}
TraceFunctLeave();
return pSmtpObj;
}
/*++
Name :
SMTP_CONNOUT::SendSmtpResponse
Description:
This function sends data that was queued in the internal
m_pOutputBuffer buffer
Arguments:
SyncSend - Flag that signifies sync or async send
Returns:
TRUE is the string was sent. False otherwise
--*/
BOOL SMTP_CONNOUT::SendSmtpResponse(BOOL SyncSend)
{
BOOL RetStatus = TRUE;
DWORD cbMessage = m_OutputBufferSize;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::SendSmtpResponse");
//if m_OutputBufferSize > 0that means there is
//something in the buffer, therefore, we will send it.
if (m_OutputBufferSize) {
//
// If we are using SSL, encrypt the output buffer now. Note that
// FormatSmtpMsg already left header space for the seal header.
//
if (m_SecurePort) {
char *Buffer = &m_pOutputBuffer[m_encryptCtx.GetSealHeaderSize()];
RetStatus = m_encryptCtx.SealMessage(
(UCHAR *) Buffer,
m_OutputBufferSize,
(UCHAR *) m_pOutputBuffer,
&cbMessage);
if (!RetStatus)
{
ErrorTrace ((LPARAM)this, "Sealmessage failed");
SetLastError(AQUEUE_E_SSL_ERROR);
}
}
if (RetStatus) {
RetStatus = CLIENT_CONNECTION::WriteFile(m_pOutputBuffer, cbMessage);
}
if (RetStatus) {
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesSentTotal, m_OutputBufferSize);
} else {
DebugTrace((LPARAM) this, "WriteFile failed with error %d", GetLastError());
}
m_OutputBufferSize = 0;
}
TraceFunctLeaveEx((LPARAM) this);
return ( RetStatus );
}
/*++
Name :
SMTP_CONNOUT::FormatSmtpMessage( IN const char * Format, ...)
Description:
This function operates likes sprintf, printf, etc. It
just places it's data in the output buffer.
Arguments:
Format - Data to place in the buffer
Returns:
--*/
BOOL SMTP_CONNOUT::FormatSmtpMessage( FORMAT_SMTP_MESSAGE_LOGLEVEL eLogLevel, IN const char * Format, ...)
{
int BytesWritten;
va_list arglist;
char *Buffer;
DWORD AvailableBytes;
DWORD HeaderOffset = (m_SecurePort ? m_encryptCtx.GetSealHeaderSize() : 0);
DWORD SealOverhead = (m_SecurePort ?
(m_encryptCtx.GetSealHeaderSize() +
m_encryptCtx.GetSealTrailerSize()) : 0);
Buffer = &m_pOutputBuffer[m_OutputBufferSize + HeaderOffset];
AvailableBytes = m_cbMaxOutputBuffer - m_OutputBufferSize - SealOverhead;
//if BytesWritten is < 0, that means there is no space
//left in the buffer. Therefore, we flush any pending
//responses to make space. Then we try to place the
//information in the buffer again. It should never
//fail this time.
va_start (arglist, Format);
BytesWritten = _vsnprintf (Buffer, AvailableBytes, Format, arglist);
if (BytesWritten < 0) {
//flush any pending response
SendSmtpResponse();
_ASSERT (m_OutputBufferSize == 0);
Buffer = &m_pOutputBuffer[HeaderOffset];
AvailableBytes = m_cbMaxOutputBuffer - SealOverhead;
BytesWritten = _vsnprintf (Buffer, AvailableBytes, Format, arglist);
_ASSERT (BytesWritten > 0);
}
va_end(arglist);
// log this transaction
ProtocolLogCommand(Buffer, BytesWritten, QueryClientHostName(), eLogLevel);
m_OutputBufferSize += (DWORD) BytesWritten;
//m_OutputBufferSize += vsprintf (&m_OutputBuffer[m_OutputBufferSize], Format, arglist);
return TRUE;
}
/*++
Name :
SMTP_CONNOUT::FormatBinaryBlob(IN PBYTE pbBlob, IN DWORD cbSize)
Description:
Places pbBlob of size cbSize into buffer
Arguments:
pbBlob - blob to place in the buffer
cbSize - blob size
Returns:
BOOL
--*/
BOOL SMTP_CONNOUT::FormatBinaryBlob( IN PBYTE pbBlob, IN DWORD cbSize)
{
char *Buffer;
DWORD AvailableBytes;
TraceQuietEnter( "SMTP_CONNOUT::FormatBinaryBlob");
DWORD HeaderOffset = ( m_SecurePort ? m_encryptCtx.GetSealHeaderSize() : 0);
DWORD SealOverhead = ( m_SecurePort ?
( m_encryptCtx.GetSealHeaderSize() +
m_encryptCtx.GetSealTrailerSize()) : 0);
Buffer = &m_pOutputBuffer[ m_OutputBufferSize + HeaderOffset];
AvailableBytes = m_cbMaxOutputBuffer - m_OutputBufferSize - SealOverhead;
while ( AvailableBytes < cbSize) {
memcpy( Buffer, pbBlob, AvailableBytes);
pbBlob += AvailableBytes;
cbSize -= AvailableBytes;
m_OutputBufferSize += AvailableBytes;
SendSmtpResponse();
_ASSERT ( m_OutputBufferSize == 0);
Buffer = &m_pOutputBuffer[ HeaderOffset];
AvailableBytes = m_cbMaxOutputBuffer - SealOverhead;
}
memcpy( Buffer, pbBlob, cbSize);
m_OutputBufferSize += cbSize;
return TRUE;
}
/*++
Name :
SMTP_CONNOUT::ProcessWriteIO
Description:
Handles an async write completion event.
Arguments:
InputBufferLen - Number of bytes that was written
dwCompletionStatus -Holds error code from ATQ, if any
lpo - Pointer to overlapped structure
Returns:
TRUE if the object should continue to survive
FALSE if the object should be deleted
--*/
BOOL SMTP_CONNOUT::ProcessWriteIO ( IN DWORD InputBufferLen, IN DWORD dwCompletionStatus, IN OUT OVERLAPPED * lpo)
{
CBuffer* pBuffer;
TraceQuietEnter("SMTP_CONNOUT::ProcessWriteIO");
_ASSERT(IsValid());
_ASSERT(lpo);
pBuffer = ((DIRNOT_OVERLAPPED*)lpo)->pBuffer;
//
// check for partial completions or errors
//
if ( pBuffer->GetSize() != InputBufferLen || dwCompletionStatus != NO_ERROR ) {
ErrorTrace( (LPARAM)this,
"WriteFile error: %d, bytes %d, expected: %d, lpo: 0x%08X",
dwCompletionStatus,
InputBufferLen,
pBuffer->GetSize(),
lpo );
m_Error = dwCompletionStatus;
SetDiagnosticInfo(HRESULT_FROM_WIN32(dwCompletionStatus), NULL, NULL);
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
return ( FALSE );
} else {
DebugTrace( (LPARAM)this,
"WriteFile complete. bytes %d, lpo: 0x%08X",
InputBufferLen, lpo );
}
//
// free up IO buffer
//
delete pBuffer;
//
// increment only after write completes
//
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesSentMsg, InputBufferLen);
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesSentTotal, InputBufferLen);
DebugTrace( (LPARAM)this, "m_bComplete: %s",
m_bComplete ? "TRUE" : "FALSE" );
return ( TRUE );
}
/*++
Name :
SMTP_CONNOUT::ProcessTransmitFileIO
Description:
processes the return from TransmitFile.
Right not it just posts a read.
Arguments:
Returns:
TRUE
--*/
BOOL SMTP_CONNOUT::ProcessTransmitFileIO ( IN DWORD InputBufferLen, IN DWORD dwCompletionStatus, IN OUT OVERLAPPED * lpo)
{
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::ProcessTransmitFileIO");
_ASSERT(IsValid());
//we need to set this outside of the if statement,
//because we will always have a read pended
m_LastClientIo = SMTP_CONNOUT::READIO;
if (dwCompletionStatus != NO_ERROR) {
m_Error = dwCompletionStatus;
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DebugTrace((LPARAM) this, "TranmitFile in ProcessTransmitFileIO failed with error %d !!!!", dwCompletionStatus);
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
DisconnectClient();
TraceFunctLeave();
return FALSE;
}
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesSentMsg, InputBufferLen);
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesSentTotal, InputBufferLen);
//pend an IO to pickup the "250 XXXX queued for delivery response"
IncPendingIoCount();
fRet = ReadFile(QueryMRcvBuffer(), m_cbMaxRecvBuffer);
if (!fRet) {
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
m_Error = GetLastError();
DebugTrace((LPARAM) this, "ReadFile in ProcessTransmitFileIO failed with error %d !!!!", m_Error);
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
DisconnectClient();
DecPendingIoCount();
}
TraceFunctLeave();
return fRet;
}
/*++
Name :
SMTP_CONNOUT::WaitForConnectResponse
Description:
This function gets called when the SMTP
server sends it's opening response
Arguments:
InputLine - Buffer containing opening response
ParameterSize - Size of opening response
Returns:
TRUE if Object is not to be destroyed
FALSE if Object is to be destroyed
--*/
BOOL SMTP_CONNOUT::WaitForConnectResponse(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
char * pszSearch = NULL;
DWORD IntermediateSize = 0;
BOOL IsNextLine;
DWORD Error;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::WaitForConnectResponse");
//get rid of all continuation lines
while ((pszSearch = IsLineComplete(InputLine, ParameterSize))) {
*pszSearch = '\0';
//check to see if there is a continuation line
IsNextLine = (InputLine[3] == '-');
//calculate the length of the line, with the CRLF
IntermediateSize = (DWORD)((pszSearch - InputLine) + 2);
ShrinkBuffer(
pszSearch + 2,
ParameterSize - IntermediateSize + UndecryptedTailSize);
ParameterSize -= IntermediateSize;
}
//If ParameterSize is > 0 but pszSearch == NULL, this means
//there is data in the buffer, but it does not have a CRLF
//to delimit it, therefore, we need more data to continue.
//set m_cbReceived equal to where in our input buffer we
//should pend another read and then return TRUE to pend
//that read.
if (ParameterSize != 0) {
//if we need more data, move the remaining data to the
//front of the buffer
MoveMemory(
(void *)QueryMRcvBuffer(),
InputLine,
ParameterSize + UndecryptedTailSize);
m_cbParsable = ParameterSize;
m_cbReceived = ParameterSize + UndecryptedTailSize;
return (TRUE);
} else if (IsNextLine) {
m_cbParsable = 0;
m_cbReceived = UndecryptedTailSize;
return (TRUE);
} else {
BOOL fResult = TRUE;
//we got a line that is not a continuation line.
// Process the connect response
switch (InputLine [0]) {
case SMTP_COMPLETE_FAILURE:
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, NULL, NULL);
DisconnectClient();
InputLine [3] = '\0';
Error = atoi (InputLine);
InputLine [3] = ' ';
SaveToErrorFile(InputLine, ParameterSize);
SaveToErrorFile("\r\n", 2);
fResult = FALSE;
break;
case SMTP_COMPLETE_SUCCESS: {
//If the domain has been specified to use only HELO then we
//fake it such that EHLO has been already sentand failed
if (m_MsgOptions & DOMAIN_INFO_USE_HELO )
m_EhloFailed = TRUE;
// copy the domain name from the ehlo banner
strncpy(m_ConnectedDomain, &InputLine[4], sizeof(m_ConnectedDomain)-1);
// trunc at the first space
char *pszSpace = strchr(m_ConnectedDomain, ' ');
if (pszSpace) *pszSpace = 0;
fResult = DoSessionStartEvent(InputLine, ParameterSize, UndecryptedTailSize);
break;
}
default:
DebugTrace( (LPARAM) this,
"SMTP_CONNOUT::WaitForConnectResponse executing quit command err = %c%c%c",
InputLine [0],
InputLine [1],
InputLine [2]);
fResult = DoCompletedMessage(InputLine, ParameterSize, UndecryptedTailSize);
break;
}
TraceFunctLeaveEx((LPARAM)this);
return fResult;
}
}
/*++
Name :
SMTP_CONNOUT::ProcessReadIO
Description:
This function gets a buffer from ATQ, parses the buffer to
find out what command the client sent, then executes that
command.
Arguments:
InputBufferLen - Number of bytes that was written
dwCompletionStatus -Holds error code from ATQ, if any
lpo - Pointer to overlapped structure
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::ProcessReadIO ( IN DWORD InputBufferLen, IN DWORD dwCompletionStatus, IN OUT OVERLAPPED * lpo)
{
BOOL fReturn = TRUE;
char * InputLine = NULL;
char * pszSearch = NULL;
DWORD IntermediateSize = 0;
DWORD UndecryptedTailSize = 0;
PMFI PreviousState = NULL;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::ProcessReadIO");
//make sure the next state is not NULL
_ASSERT(m_NextState != NULL || m_fNegotiatingSSL);
_ASSERT(IsValid());
//get a pointer to our buffer
InputLine = (char *) QueryMRcvBuffer();
//add up the number of bytes we received thus far
m_cbReceived += InputBufferLen;
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesRcvdTotal, InputBufferLen);
//if we are in the middle of negotiating a SSL session, handle it now.
if (m_fNegotiatingSSL) {
fReturn = DoSSLNegotiation(QueryMRcvBuffer(),InputBufferLen, 0);
if (!fReturn) {
m_Error = GetLastError();
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
}
return ( fReturn );
}
//if we are using the secure port, decrypt the input buffer now.
if (m_SecurePort) {
fReturn = DecryptInputBuffer();
if (!fReturn) {
m_Error = GetLastError();
SetDiagnosticInfo(AQUEUE_E_SSL_ERROR, NULL, NULL);
return ( fReturn );
}
} else {
m_cbParsable = m_cbReceived;
}
//we only process lines that have CRLFs at the end, so if
//we don't find the CRLF pair, pend another read. Note
//that we start at the end of the buffer looking for the
//CRLF pair. We just need to know if atleast one line
//has a CRLF to continue.
if ((pszSearch = IsLineCompleteBW(InputLine,m_cbParsable, m_cbMaxRecvBuffer)) == NULL) {
if (m_cbReceived >= m_cbMaxRecvBuffer)
m_cbReceived = 0;
IncPendingIoCount();
fReturn = ReadFile(QueryMRcvBuffer() + m_cbReceived, m_cbMaxRecvBuffer - m_cbReceived);
if (!fReturn) {
m_Error = GetLastError();
DebugTrace((LPARAM) this, "SMTP_CONNOUT::ProcessReadIO - ReadFile # 1 failed with error %d", m_Error);
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
DisconnectClient();
DecPendingIoCount();
}
TraceFunctLeaveEx((LPARAM)this);
return fReturn;
}
//save the number of bytes that we received,
//so we can pass it to the function call below.
//we set m_cbReceived = 0, because other functions
//will set it the offset in the buffer where we
//should pend the next read.
IntermediateSize = m_cbParsable;
UndecryptedTailSize = m_cbReceived - m_cbParsable;
m_cbParsable = 0;
m_cbReceived = 0;
//save the previous state
PreviousState = m_NextState;
ProtocolLogResponse(InputLine, IntermediateSize, QueryClientHostName());
//execute the next state
fReturn = (this->*m_NextState)(InputLine, IntermediateSize, UndecryptedTailSize);
if (fReturn) {
//if we are in STARTTLS state, don't pend a read since
//DoSSLNegotiation would have pended a read
if ((m_NextState == &SMTP_CONNOUT::DoSTARTTLSCommand) &&
(m_TlsState == SSL_NEG)) {
DebugTrace((LPARAM) this, "SMTP_CONNOUT::ProcessReadIO - leaving because we are in middle of SSL negotiation");
TraceFunctLeaveEx((LPARAM)this);
return fReturn;
}
//do't pend a read if the previous state was
//DoDataCommandEx. We want either the transmitfile
//or the read to fail. Not both. Later, we will fix
//it right for both to complete concurrently.
if (PreviousState == &SMTP_CONNOUT::DoDATACommandEx) {
DebugTrace((LPARAM) this, "SMTP_CONNOUT::ProcessReadIO - leaving because we did a transmitfile");
TraceFunctLeaveEx((LPARAM)this);
return fReturn;
} else if (m_fUseBDAT) {
//We also don't want to pend a read if we did BDAT processing
//It is a special case, because of the fact that DoBDATCommand synchronously
//calls DoDATACommandEx without pending a read. So we never come thru here and get
//chance to set PreviousState = DoDATACommandEx
//So I have to hack it
if ((m_NextState == &SMTP_CONNOUT::DoContentResponse) ||
(m_SendAgain && (m_NextState == &SMTP_CONNOUT::DoMessageStartEvent))) {
DebugTrace((LPARAM) this, "SMTP_CONNOUT::ProcessReadIO - leaving because we did a transmitfile");
TraceFunctLeaveEx((LPARAM)this);
return fReturn;
}
}
if (m_cbReceived >= m_cbMaxRecvBuffer)
m_cbReceived = 0;
IncPendingIoCount();
fReturn = ReadFile(QueryMRcvBuffer() + m_cbReceived, m_cbMaxRecvBuffer - m_cbReceived);
if (!fReturn) {
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
m_Error = GetLastError();
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
DisconnectClient();
DecPendingIoCount();
DebugTrace((LPARAM) this, "SMTP_CONNOUT::ProcessReadIO - ReadFile # 2 failed with error %d", m_Error);
}
}
TraceFunctLeaveEx((LPARAM)this);
return fReturn;
}
/*++
Name :
SMTP_CONNOUT::ProcessFileIO
Description:
Handles completion of an async read issued against a message file by
MessageReadFile
Arguments:
cbRead count of bytes read
dwCompletionStatus Error code for IO operation
lpo Overlapped structure
Returns:
TRUE if connection should stay open
FALSE if this object should be deleted
--*/
BOOL SMTP_CONNOUT::ProcessFileIO(
IN DWORD BytesRead,
IN DWORD dwCompletionStatus,
IN OUT OVERLAPPED *lpo
)
{
CBuffer* pBuffer;
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNOUT::ProcessFileIO");
_ASSERT(lpo);
pBuffer = ((DIRNOT_OVERLAPPED*)lpo)->pBuffer;
//
// check for partial completions or errors
//
if ( BytesRead != pBuffer->GetSize() || dwCompletionStatus != NO_ERROR ) {
ErrorTrace( (LPARAM)this,
"Message ReadFile error: %d, bytes %d, expected %d",
dwCompletionStatus,
BytesRead,
pBuffer->GetSize() );
m_Error = dwCompletionStatus;
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_dwConnectionStatus), NULL, NULL);
return ( FALSE );
} else {
DebugTrace( (LPARAM)this,
"ReadFile complete. bytes %d, lpo: 0x%08X",
BytesRead, lpo );
}
m_bComplete = (m_dwFileOffset + BytesRead) >= m_FileSize;
m_dwFileOffset += BytesRead;
//
// If anything to write to the client
//
if ( BytesRead > 0 ) {
//
// set buffer specific IO state
//
pBuffer->SetIoState( CLIENT_WRITE );
pBuffer->SetSize( BytesRead );
ZeroMemory( (void*)&pBuffer->m_Overlapped.SeoOverlapped, sizeof(OVERLAPPED) );
DebugTrace( (LPARAM)this, "WriteFile 0x%08X, len: %d, LPO: 0x%08X",
pBuffer->GetData(),
BytesRead,
&pBuffer->m_Overlapped.SeoOverlapped.Overlapped );
//
// If we're on the SSL port, encrypt the data.
//
if ( m_SecurePort ) {
//
// Seal the message in place as we've already reserved room
// for both the header and trailer
//
if ( m_encryptCtx.SealMessage( pBuffer->GetData() +
m_encryptCtx.GetSealHeaderSize(),
BytesRead,
pBuffer->GetData(),
&BytesRead ) == FALSE ) {
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
m_Error = GetLastError();
ErrorTrace( (LPARAM)this, "SealMessage failed. err: %d", m_Error);
SetDiagnosticInfo(AQUEUE_E_SSL_ERROR, NULL, NULL);
DisconnectClient();
} else {
//
// adjust the byte count to include header and trailer
//
_ASSERT(BytesRead == pBuffer->GetSize() +
m_encryptCtx.GetSealHeaderSize() +
m_encryptCtx.GetSealTrailerSize() );
pBuffer->SetSize( BytesRead );
}
}
IncPendingIoCount();
if ( WriteFile( pBuffer->GetData(),
BytesRead,
(LPOVERLAPPED)&pBuffer->m_Overlapped ) == FALSE ) {
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
m_Error = GetLastError();
//
// reset the bytes read so we drop out
//
BytesRead = 0;
ErrorTrace( (LPARAM)this, "WriteFile failed (err=%d)", m_Error );
delete pBuffer;
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
//
// treat as fatal error
//
DisconnectClient( );
//
// cleanup after write failure
//
DecPendingIoCount();
}
}
if ( m_bComplete ) {
BOOL fRet = TRUE;
//FreeAtqFileContext();
//We do not have any trailers to write if we are processing BDAT
//
if (!m_fUseBDAT) {
if (m_SecurePort) {
//Nimishk : Is this right ***
m_OutputBufferSize = m_cbMaxOutputBuffer;
fRet = m_encryptCtx.SealMessage(
(LPBYTE) m_TransmitBuffers.Tail,
m_TransmitBuffers.TailLength,
(LPBYTE) m_pOutputBuffer,
&m_OutputBufferSize);
if (fRet)
fRet = WriteFile(m_pOutputBuffer, m_OutputBufferSize);
} else {
fRet = WriteFile(
(LPVOID)m_TransmitBuffers.Tail,
m_TransmitBuffers.TailLength );
}
}
m_OutputBufferSize = 0;
IncPendingIoCount();
if (fRet) {
//pend an IO to pickup the "250 XXXX queued for delivery response"
m_LastClientIo = SMTP_CONNOUT::READIO;
fRet = ReadFile(QueryMRcvBuffer(), m_cbMaxRecvBuffer);
}
if (!fRet) {
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
m_Error = GetLastError();
DebugTrace((LPARAM) this, "Error %d sending tail or posting read", m_Error);
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
DisconnectClient();
DecPendingIoCount();
}
return ( fRet );
} else {
return (MessageReadFile());
}
}
/*++
Name :
SMTP_CONNOUT::ProcessClient
Description:
Main function for this class. Processes the connection based
on current state of the connection.
It may invoke or be invoked by ATQ functions.
Arguments:
cbWritten count of bytes written
dwCompletionStatus Error Code for last IO operation
lpo Overlapped stucture
Returns:
TRUE when processing is incomplete.
FALSE when the connection is completely processed and this
object may be deleted.
--*/
BOOL SMTP_CONNOUT::ProcessClient( IN DWORD InputBufferLen, IN DWORD dwCompletionStatus, IN OUT OVERLAPPED * lpo)
{
BOOL RetStatus;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::ProcessClient");
IncThreadCount();
//if lpo == NULL, then we timed out. Send an appropriate message
//then close the connection
if ((lpo == NULL) && (dwCompletionStatus == ERROR_SEM_TIMEOUT)) {
//
// fake a pending IO as we'll dec the overall count in the
// exit processing of this routine needs to happen before
// DisconnectClient else completing threads could tear us down
//
SetDiagnosticInfo(HRESULT_FROM_WIN32(ERROR_SEM_TIMEOUT), NULL, NULL);
IncPendingIoCount();
m_Error = dwCompletionStatus;
DebugTrace((LPARAM) this, "SMTP_CONNOUT::ProcessClient: - Timing out");
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
} else if ((InputBufferLen == 0) || (dwCompletionStatus != NO_ERROR)) {
//if InputBufferLen == 0, then the connection was closed.
if (m_Error == NO_ERROR)
m_Error = ERROR_BROKEN_PIPE;
SetDiagnosticInfo(AQUEUE_E_CONNECTION_DROPPED, NULL, NULL);
//
// If the lpo points to an IO buffer allocated for SSL IO, delete it
//
if (lpo != NULL &&
lpo != &m_Overlapped &&
lpo != &QueryAtqContext()->Overlapped) {
CBuffer* pBuffer = ((DIRNOT_OVERLAPPED*)lpo)->pBuffer;
delete pBuffer;
}
DebugTrace((LPARAM) this, "SMTP_CONNOUT::ProcessClient: InputBufferLen = %d dwCompletionStatus = %d - Closing connection", InputBufferLen, dwCompletionStatus);
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
} else if (lpo == &m_Overlapped || lpo == &QueryAtqContext()->Overlapped
|| lpo == (OVERLAPPED *) &m_SeoOverlapped) {
switch (m_LastClientIo) {
case SMTP_CONNOUT::READIO:
RetStatus = ProcessReadIO (InputBufferLen, dwCompletionStatus, lpo);
break;
case SMTP_CONNOUT::TRANSFILEIO:
RetStatus = ProcessTransmitFileIO (InputBufferLen, dwCompletionStatus, lpo);
break;
default:
_ASSERT (FALSE);
RetStatus = FALSE;
break;
}
} else {
//
// This lpo belongs to a CBuffer allocated for message transfers using
// async reads and writes.
//
CBuffer* pBuffer = ((DIRNOT_OVERLAPPED*)lpo)->pBuffer;
if (pBuffer->GetIoState() == MESSAGE_READ) {
RetStatus = ProcessFileIO(InputBufferLen, dwCompletionStatus, lpo);
} else {
_ASSERT( pBuffer->GetIoState() == CLIENT_WRITE );
RetStatus = ProcessWriteIO(InputBufferLen, dwCompletionStatus, lpo);
}
}
DecThreadCount();
//
// decrement the overall pending IO count for this session
// tracing and ASSERTs if we're going down.
//
if ( DecPendingIoCount() == 0 ) {
if (m_dwConnectionStatus == CONNECTION_STATUS_DROPPED) {
if (!QuerySmtpInstance()->IsShuttingDown())
ConnectToNextIpAddress();
}
DebugTrace( (LPARAM)this,"Num Threads: %d",m_cActiveThreads);
_ASSERT( m_cActiveThreads == 0 );
m_Active = FALSE;
RetStatus = FALSE;
} else {
DebugTrace( (LPARAM)this,"SMTP_CONNOUT::ProcessClient Pending IOs: %d",m_cPendingIoCount);
DebugTrace( (LPARAM)this,"SMTP_CONNOUT::ProcessClient Num Threads: %d",m_cActiveThreads);
RetStatus = TRUE;
}
TraceFunctLeaveEx((LPARAM)this);
return RetStatus;
}
/*++
Name :
SMTP_CONNOUT::DoSSLNegotiation
Description:
Does the SSL Handshake with a remote SMTP server.
Arguments:
Returns:
TRUE if successful so far
FALSE if this object should be deleted
--*/
BOOL SMTP_CONNOUT::DoSSLNegotiation(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
BOOL fRet = TRUE, fMore, fPostRead;
DWORD dwErr;
HRESULT hr = S_OK;
ULONG cbExtra = 0;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::DoSSLNegotiations");
m_OutputBufferSize = m_cbMaxOutputBuffer;
dwErr = m_encryptCtx.Converse(
QueryMRcvBuffer(),
m_cbReceived,
(LPBYTE) m_pOutputBuffer,
&m_OutputBufferSize,
&fMore,
(LPSTR) QueryLocalHostName(),
(LPSTR) QueryLocalPortName(),
(LPVOID) QuerySmtpInstance(),
QuerySmtpInstance()->QueryInstanceId(),
&cbExtra
);
if (dwErr == NO_ERROR) {
//
// reset the receive buffer
//
if (cbExtra)
MoveMemory (QueryMRcvBuffer(), QueryMRcvBuffer() + (m_cbReceived - cbExtra), cbExtra);
m_cbReceived = cbExtra;
if (m_OutputBufferSize != 0) {
// Send the last negotiation blob to the server and start with Encrypting.
// Reset the output buffer size
fRet = WriteFile(m_pOutputBuffer, m_OutputBufferSize);
m_OutputBufferSize = 0;
}
if (fMore) {
fPostRead = TRUE;
} else {
m_fNegotiatingSSL = FALSE;
if (ValidateSSLCertificate ()) {
m_TlsState = CHANNEL_SECURE;
//If we negotiated because of STARTTLS, we need to send EHLO and get the
//options all over again. According to jump_thru_hoops draft
if (IsOptionSet(STARTTLS)) {
//Do ehlo again - this to get the response
_ASSERT(m_EhloFailed == FALSE);
char szInput[25];
strcpy(szInput,"220 OK");
fRet = DoSessionStartEvent(szInput, strlen(szInput), 0);
} else if (!(m_MsgOptions & KNOWN_AUTH_FLAGS)) {
if (m_MsgOptions & EMPTY_CONNECTION_OPTION) {
fRet = DoSessionEndEvent(InputLine, ParameterSize, UndecryptedTailSize);
} else {
fRet = DoMessageStartEvent(InputLine, ParameterSize, UndecryptedTailSize);
}
} else {
fRet = DoSASLCommand(InputLine, ParameterSize, UndecryptedTailSize);
}
fPostRead = fRet;
} else {
fPostRead = fRet = FALSE;
//Fill in the response context buffer so as to generate the right response
// Get the error code
m_ResponseContext.m_dwSmtpStatus = SMTP_RESP_BAD_CMD;
hr = m_ResponseContext.m_cabResponse.Append(
(char *)SMTP_REMOTE_HOST_REJECTED_SSL,
strlen(SMTP_REMOTE_HOST_REJECTED_SSL),
NULL);
}
}
if (fPostRead) {
//
// The negotiation is going well so far, but there is still more
// negotiation to do. So post a read to receive the next leg of
// the negotiation.
//
DebugTrace((LPARAM) this,"StartSession negotiating SSL");
IncPendingIoCount();
m_LastClientIo = SMTP_CONNOUT::READIO;
fRet = ReadFile(QueryMRcvBuffer() + cbExtra, m_cbMaxRecvBuffer - cbExtra);
if (!fRet) {
dwErr = GetLastError();
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSSLNegotiation failed %d", m_Error);
DecPendingIoCount();
SetLastError(dwErr);
SetDiagnosticInfo(HRESULT_FROM_WIN32(dwErr), NULL, NULL);
//Fill in the response context buffer so as to generate the right response
// Get the error code
}
}
} else {
fRet = FALSE;
SetLastError(ERROR_NO_SECURITY_ON_OBJECT);
SetDiagnosticInfo(AQUEUE_E_TLS_NOT_SUPPORTED_ERROR, NULL, NULL);
}
TraceFunctLeaveEx((LPARAM) this);
return ( fRet );
}
//----------------------------------------------------------------------------------
// Description:
// Check to see if the SSL cert is valid:
// (1) Always check if it has expired.
// (2) If configured, check if the issuer is trusted.
// (3) If configured, check if the subject matches who we are sending to. This
// is not simply the server-fqdn we are connected to. Since the server-fqdn
// may have been obtained via DNS (through MX or CNAME indirection), and
// since DNS is an insecure protocol, we cannot trust that for the subject.
// Instead we will use the domain we were passed in prior to DNS... the
// one passed into the REMOTE_QUEUE. This domain name is passed into
// SMTP_CONNOUT when it is created as m_pszSSLVerificationDomain. We will
// do some wildcard matching as well if the certificate subject has the
// '*' character in it (see simssl.cpp). m_pszSSLVerificationName may be
// NULL if there is no target hostname --- such as in the case of a literal
// IP address. In this case, subject verification is skipped.
// Returns:
// TRUE - success, certificate verified
// FALSE - failure, stick queue into retry
//----------------------------------------------------------------------------------
BOOL SMTP_CONNOUT::ValidateSSLCertificate ()
{
BOOL fRet = FALSE;
DWORD dwAQDiagnostic = 0;
TraceFunctEnterEx ((LPARAM) this, "SMTP_CONNECTION::ValidateCertificate");
fRet = m_encryptCtx.CheckCertificateExpired();
if (!fRet) {
ErrorTrace ((LPARAM) this, "SSL Certificate Expired");
dwAQDiagnostic = AQUEUE_E_SSL_CERT_EXPIRED;
goto Exit;
}
if (m_pInstance->RequiresSSLCertVerifyIssuer()) {
DebugTrace ((LPARAM) this, "Verifying certificate issuing authority");
//
// Failure in these checks could occur due to temporary conditions (like
// out of memory). So the AQueue diagnostic is not 100% accurate, but it's
// OK as we don't NDR message. Anyway we DID fail during cert validation.
//
fRet = m_encryptCtx.CheckCertificateTrust();
if (!fRet) {
ErrorTrace ((LPARAM) this, "SSL Certificate trust verification failure");
dwAQDiagnostic = AQUEUE_E_SSL_CERT_ISSUER_UNTRUSTED;
goto Exit;
}
}
if (!m_pszSSLVerificationName) {
DebugTrace ((LPARAM) this,
"Skipping certificate subject validation, no name to validate against");
goto Exit;
}
DebugTrace ((LPARAM) this,
"Validating certificate subject against: %s", m_pszSSLVerificationName);
fRet = m_encryptCtx.CheckCertificateSubjectName (m_pszSSLVerificationName);
if (!fRet) {
ErrorTrace ((LPARAM) this, "SSL certificate subject verification failure");
dwAQDiagnostic = AQUEUE_E_SSL_CERT_SUBJECT_MISMATCH;
}
Exit:
if (!fRet && dwAQDiagnostic)
SetDiagnosticInfo(dwAQDiagnostic, NULL, NULL);
TraceFunctLeaveEx ((LPARAM) this);
return fRet;
}
/*++
Name :
void SMTP_CONNECTION::SkipWord
Description:
skips over words in a buffer and
returns pointer to next word
Arguments:
none
Returns:
TRUE if the receive buffer was successfully decrypted, FALSE otherwise
--*/
BOOL SMTP_CONNOUT::DecryptInputBuffer(void)
{
TraceFunctEnterEx( (LPARAM)this, "SMTP_CONNOUT::DecryptInputBuffer");
DWORD cbExpected;
DWORD cbReceived;
DWORD cbParsable;
DWORD dwError;
dwError = m_encryptCtx.DecryptInputBuffer(
(LPBYTE) QueryMRcvBuffer() + m_cbParsable,
m_cbReceived - m_cbParsable,
&cbReceived,
&cbParsable,
&cbExpected );
if ( dwError == NO_ERROR ) {
//
// new total received size is the residual from last processing
// and whatever is left in the current decrypted read buffer
//
m_cbReceived = m_cbParsable + cbReceived;
//
// new total parsable size is the residual from last processing
// and whatever was decrypted from this read io operation
//
m_cbParsable += cbParsable;
} else {
//
// errors from this routine indicate that the stream has been
// tampered with or we have an internal error
//
ErrorTrace( (LPARAM)this,
"DecryptInputBuffer failed: 0x%08X",
dwError );
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient( dwError );
}
TraceFunctLeaveEx((LPARAM)this);
return ( dwError == NO_ERROR );
}
BOOL SMTP_CONNOUT::DoSASLNegotiation(char *InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
BUFFER BuffOut;
DWORD BytesRet = 0;
BOOL fMoreData = FALSE;
BOOL fRet = TRUE;
BOOL fReturn = TRUE;
DomainInfo DomainParams;
LPSTR szClearTextPassword = NULL;
HRESULT hr = S_OK;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoSASLNegotiation");
//we need to receive a 354 from the server to go on
if (InputLine[0] == SMTP_INTERMEDIATE_SUCCESS) {
if (m_AuthToUse & SMTP_AUTH_NTLM || m_AuthToUse & SMTP_AUTH_KERBEROS) {
if (m_securityCtx.ClientConverse(
InputLine + 4, ParameterSize - 4, &BuffOut, &BytesRet,
&fMoreData, &AuthInfoStruct, m_szAuthPackage,
NULL, NULL, (PIIS_SERVER_INSTANCE) m_pInstance)) {
if (BytesRet) {
FormatSmtpMessage(FSM_LOG_NONE, "%s\r\n", BuffOut.QueryPtr());
SendSmtpResponse();
}
} else {
DebugTrace((LPARAM) this, "m_securityCtx.Converse failed - %d",
GetLastError());
m_Error = ERROR_LOGON_FAILURE;
//Fill in the response context buffer so as to generate the right response
// Get the error code
m_ResponseContext.m_dwSmtpStatus = SMTP_RESP_BAD_CMD;
hr = m_ResponseContext.m_cabResponse.Append(
(char *)SMTP_REMOTE_HOST_REJECTED_AUTH,
strlen(SMTP_REMOTE_HOST_REJECTED_AUTH),
NULL);
fRet = FALSE;
SetDiagnosticInfo(AQUEUE_E_SASL_REJECTED, NULL, NULL);
}
} else {
ZeroMemory (&DomainParams, sizeof(DomainParams));
DomainParams.cbVersion = sizeof(DomainParams);
hr = m_pISMTPConnection->GetDomainInfo(&DomainParams);
if (FAILED(hr)) {
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DisconnectClient();
SetLastError(m_Error = ERROR_LOGON_FAILURE);
SetDiagnosticInfo(AQUEUE_E_SASL_REJECTED, NULL, NULL);
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
szClearTextPassword = DomainParams.szPassword;
if (!szClearTextPassword) szClearTextPassword = "";
fReturn = uuencode ((unsigned char *)szClearTextPassword,
lstrlen(szClearTextPassword), &BuffOut, FALSE);
if (fReturn) {
FormatSmtpMessage(FSM_LOG_NONE, "%s\r\n", BuffOut.QueryPtr());
SendSmtpResponse();
} else {
m_Error = GetLastError();
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
fRet = FALSE;
}
}
} else if (InputLine[0] == SMTP_COMPLETE_SUCCESS) {
if (m_MsgOptions & EMPTY_CONNECTION_OPTION) {
fRet = DoSessionEndEvent(InputLine, ParameterSize, UndecryptedTailSize);
} else {
fRet = DoMessageStartEvent(InputLine, ParameterSize, UndecryptedTailSize);
}
} else {
fRet = FALSE;
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
m_Error = ERROR_LOGON_FAILURE;
SetDiagnosticInfo(AQUEUE_E_SASL_REJECTED, NULL, NULL);
DisconnectClient();
}
TraceFunctLeaveEx((LPARAM)this);
return fRet;
}
/*++
Name :
SMTP_CONNOUT::DoEHLOCommand
Description:
Responds to the SMTP EHLO command
Arguments:
Are ignored
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoEHLOCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoEHLOCommand");
if (m_EhloFailed)
{
//server did not understand EHLO command, so
//just send the HELO command
m_HeloSent = TRUE;
QuerySmtpInstance()->LockGenCrit();
PE_FormatSmtpMessage("HELO %s\r\n", QuerySmtpInstance()->GetFQDomainName());
QuerySmtpInstance()->UnLockGenCrit();
} else {
QuerySmtpInstance()->LockGenCrit();
PE_FormatSmtpMessage("EHLO %s\r\n", QuerySmtpInstance()->GetFQDomainName());
QuerySmtpInstance()->UnLockGenCrit();
}
m_OutboundContext.m_dwCommandStatus = EXPE_SUCCESS;
m_EhloSent = TRUE;
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
}
/*++
Name :
SMTP_CONNOUT::DoEHLOResponse
Description:
Responds to the SMTP EHLO command
Arguments:
Are ignored
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoEHLOResponse(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
BOOL fGotAllOptions;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoEHLOResponse");
char chInputGood = 0;
BOOL fEndSession = FALSE;
if ( isdigit( (UCHAR)InputLine[1] ) && isdigit( (UCHAR)InputLine[2] ) && ( ( InputLine[3] == ' ' ) || ( InputLine[3] == '-' ) ) ) {
chInputGood = InputLine[0];
} else {
chInputGood = SMTP_COMPLETE_FAILURE;
fEndSession = TRUE;
}
//get the response
switch ( chInputGood ) {
case SMTP_COMPLETE_SUCCESS:
m_ResponseContext.m_dwResponseStatus = EXPE_SUCCESS;
fGotAllOptions = GetEhloOptions(
InputLine,
ParameterSize,
UndecryptedTailSize,
m_HeloSent);
_ASSERT(fGotAllOptions);
if (IsOptionSet(ETRN_ONLY_OPTION) && !IsOptionSet(ETRN_OPTION)) {
//We have no messages to send... only ETRN, but ETRN is not
//supported by the remote server, we should send quit
m_ResponseContext.m_dwResponseStatus = EXPE_CHANGE_STATE;
m_ResponseContext.m_dwNextState = PE_STATE_SESSION_END;
m_OutboundContext.m_pCurrentCommandContext = NULL;
}
break;
case SMTP_COMPLETE_FAILURE:
//if the HELO was sent and we got a 55X reply,
//just quit. This server is hosed.
if ( m_HeloSent || fEndSession ) {
DebugTrace((LPARAM) this,
"SMTP_CONNOUT::DoEHLOCommand executing quit command err from client = %c%c%c",
InputLine [0],InputLine [1],InputLine [2]);
m_ResponseContext.m_dwResponseStatus = EXPE_CHANGE_STATE;
m_ResponseContext.m_dwNextState = PE_STATE_SESSION_END;
m_OutboundContext.m_pCurrentCommandContext = NULL;
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, m_HeloSent ? "HELO" : "EHLO", InputLine);
} else {
//server did not understand EHLO command, so
//just send the HELO command
m_EhloFailed = TRUE;
m_ResponseContext.m_dwResponseStatus = EXPE_REPEAT_COMMAND;
}
break;
default:
DebugTrace((LPARAM) this,
"SMTP_CONNOUT::DoEHLOCommand executing quit command err = %c%c%c",
InputLine [0],InputLine [1],InputLine [2]);
m_ResponseContext.m_dwResponseStatus = EXPE_CHANGE_STATE;
m_ResponseContext.m_dwNextState = PE_STATE_SESSION_END;
m_OutboundContext.m_pCurrentCommandContext = NULL;
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, m_HeloSent ? "HELO" : "EHLO", InputLine);
}
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
}
/*++
Name :
SMTP_CONNOUT::GetEhloOptions(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
Description:
Parses the response from the EHLO command
Arguments:
Line containing the response and line size
Returns:
TRUE if it parses all data
FALSE if it needs more data to continue parsing
--*/
BOOL SMTP_CONNOUT::GetEhloOptions(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize, BOOL fIsHelo)
{
register char * pszValue = NULL;
register char * pszSearch = NULL;
DWORD IntermediateSize = 0;
BOOL IsNextLine = FALSE;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::GetEhloOptions");
//get each line and parse the commands
while ((pszSearch = IsLineComplete(InputLine, ParameterSize)) != NULL) {
//Null terminate the end of the option
*pszSearch = '\0';
//check to see if there is a continuation line
IsNextLine = (InputLine[3] == '-');
IntermediateSize= (DWORD)((pszSearch - InputLine) + 2); //+2 for CRLF
// Only process if it is an EHLO command, otherwise, just
// eat up all the response data.
if (!fIsHelo) {
//skip over code and space
//look for a space in the line.
//options that have paramters
//need to have a space after
//their name
pszValue = strchr(InputLine + 4, ' ');
//does the server support the SIZE option ???
if (strncasecmp(InputLine + 4, (char * )"SIZE", 4) == 0) {
//yes sireee bob. If the server advertised
//a maximum file size, get it while we are
//here.
m_Flags |= SIZE_OPTION;
if (pszValue != NULL)
m_SizeOptionSize = atoi (pszValue);
} else if (strcasecmp(InputLine + 4, (char * )"PIPELINING") == 0) {
m_Flags |= PIPELINE_OPTION;
} else if (strcasecmp(InputLine + 4, (char * )"8bitmime") == 0) {
m_Flags |= EBITMIME_OPTION;
} else if (strcasecmp(InputLine + 4, (char * )"dsn") == 0) {
m_Flags |= DSN_OPTION;
} else if (strncasecmp(InputLine + 4, (char *)"TLS", 3) == 0) {
m_Flags |= TLS_OPTION;
} else if (strncasecmp(InputLine + 4, (char *)"STARTTLS", 8) == 0) {
m_Flags |= STARTTLS_OPTION;
} else if (strncasecmp(InputLine + 4, (char *)"ETRN", 4) == 0) {
m_Flags |= ETRN_OPTION;
} else if (strcasecmp(InputLine + 4, (char *)"CHUNKING") == 0) {
m_Flags |= CHUNKING_OPTION;
} else if (strcasecmp(InputLine + 4, (char *)"BINARYMIME") == 0) {
m_Flags |= BINMIME_OPTION;
} else if (strcasecmp(InputLine + 4, (char *)"ENHANCEDSTATUSCODES") == 0) {
m_Flags |= ENHANCEDSTATUSCODE_OPTION;
} else if (strncasecmp(InputLine + 4, (char *)"AUTH", 4) == 0) {
pszValue = strchr(InputLine + 4, '=');
if (pszValue == NULL)
pszValue = strchr(InputLine + 4, ' ');
while (pszValue != NULL) {
if (strncasecmp(pszValue + 1, (char *)"NTLM", 4) == 0) {
m_Flags |= AUTH_NTLM;
} else if (strncasecmp(pszValue + 1, (char *)"LOGIN", 5) == 0) {
m_Flags |= AUTH_CLEARTEXT;
} else if (strncasecmp(pszValue + 1, (char *)"DPA", 3) == 0) {
m_Flags |= AUTH_NTLM;
} else if (strncasecmp(pszValue + 1, (char *)"GSSAPI", 6) == 0) {
m_Flags |= AUTH_GSSAPI;
}
pszValue = strchr(pszValue + 1, ' ');
}
}
}
InputLine += IntermediateSize;
ParameterSize -= IntermediateSize;
}
// We don't want to bail out for weird responses, but we want to
// note such occurrences.
if ((ParameterSize > 1) ||
(!ParameterSize) ||
(*InputLine)) {
ErrorTrace((LPARAM)this, "Weird response tail <%*s>",
ParameterSize, InputLine);
_ASSERT(FALSE);
}
TraceFunctLeaveEx((LPARAM)this);
return TRUE;
}
BOOL SMTP_CONNOUT::DoSASLCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
BOOL fRet = TRUE;
BOOL fMoreData = TRUE;
BUFFER BuffOut;
DWORD BytesRet = 0;
DomainInfo DomainParams;
LPSTR szUserName = NULL;
LPSTR szPassword = NULL;
HRESULT hr = S_OK;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoSASLCommand");
ZeroMemory (&DomainParams, sizeof(DomainParams));
if ((m_MsgOptions & DOMAIN_INFO_USE_NTLM ||
m_MsgOptions & DOMAIN_INFO_USE_KERBEROS)) {
if (!IsOptionSet(AUTH_NTLM) && !(IsOptionSet(AUTH_GSSAPI))) {
//
// We were told to do secure connection, but the remote server doesn't
// support any form of login authentication! We have to drop this connection now.
//
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSASLCommand: ERROR! Remote server does not support AUTH!");
DisconnectClient();
SetLastError(m_Error = ERROR_NO_SUCH_PACKAGE);
SetDiagnosticInfo(AQUEUE_E_SASL_REJECTED, "AUTH", InputLine);
TraceFunctLeaveEx((LPARAM)this);
return ( FALSE );
}
}
if (m_MsgOptions & DOMAIN_INFO_USE_PLAINTEXT) {
if (!IsOptionSet(AUTH_CLEARTEXT)) {
//
// We were told to do secure connection, but the remote server doesn't
// support any form of login authentication! We have to drop this connection now.
//
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSASLCommand: ERROR! Remote server does not support AUTH!");
DisconnectClient();
SetLastError(m_Error = ERROR_NO_SUCH_PACKAGE);
SetDiagnosticInfo(AQUEUE_E_SASL_REJECTED, "AUTH", InputLine);
TraceFunctLeaveEx((LPARAM)this);
return ( FALSE );
}
}
//set the next state
SetNextState (&SMTP_CONNOUT::DoSASLNegotiation);
ZeroMemory (&DomainParams, sizeof(DomainParams));
DomainParams.cbVersion = sizeof(DomainParams);
hr = m_pISMTPConnection->GetDomainInfo(&DomainParams);
if (FAILED(hr)) {
DisconnectClient();
SetLastError(m_Error = ERROR_LOGON_FAILURE);
SetDiagnosticInfo(AQUEUE_E_SASL_LOGON_FAILURE, "AUTH", InputLine);
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
szUserName = DomainParams.szUserName;
szPassword = DomainParams.szPassword;
//If a username is specified, but NULL password, IIS will attempt
//anonymous logon. Force "" password so the correct user is
//used.
if (szUserName && !szPassword)
szPassword = "";
if (m_MsgOptions & DOMAIN_INFO_USE_NTLM ||
m_MsgOptions & DOMAIN_INFO_USE_KERBEROS) {
m_szAuthPackage[0] = '\0';
BOOL fReturn = FALSE;
char szTarget[MAX_INTERNET_NAME + 1];
if ((m_MsgOptions & DOMAIN_INFO_USE_KERBEROS) && (IsOptionSet(AUTH_GSSAPI))) {
m_AuthToUse = SMTP_AUTH_KERBEROS;
strcpy(m_szAuthPackage,"GSSAPI");
//
// For Kerberos, we need to set the target server SPN, for which we
// pickup info from m_pDnsRec
//
MX_NAMES *pmx;
_ASSERT( m_pDnsRec != NULL );
_ASSERT( m_pDnsRec->StartRecord < m_pDnsRec->NumRecords );
pmx = m_pDnsRec->DnsArray[m_pDnsRec->StartRecord];
_ASSERT( pmx != NULL );
_ASSERT( pmx->DnsName != NULL );
m_securityCtx.SetTargetPrincipalName(
SMTP_SERVICE_PRINCIPAL_PREFIX,pmx->DnsName);
DebugTrace((LPARAM) this, "Setting Target Server SPN to %s",
pmx->DnsName);
} else {
m_AuthToUse = SMTP_AUTH_NTLM;
strcpy(m_szAuthPackage,"NTLM");
}
DebugTrace((LPARAM) this, "Using NTLM/KERBEROS for user %s",
DomainParams.szUserName);
fReturn = m_securityCtx.ClientConverse(
NULL, 0, &BuffOut, &BytesRet,
&fMoreData, &AuthInfoStruct,
m_szAuthPackage,
(char *) szUserName,
(char *) szPassword,
(PIIS_SERVER_INSTANCE) m_pInstance);
if (fReturn) {
if (BytesRet) {
FormatSmtpMessage(FSM_LOG_ALL, "AUTH %s %s\r\n", m_szAuthPackage,
BuffOut.QueryPtr());
}
if (!fMoreData && (m_MsgOptions & DOMAIN_INFO_USE_NTLM)) {
fRet = FALSE;
}
} else {
m_Error = GetLastError();
DebugTrace((LPARAM) this, "m_securityCtx.Converse for user %s - %d",
szUserName, m_Error);
DisconnectClient();
m_Error = ERROR_LOGON_FAILURE;
SetDiagnosticInfo(AQUEUE_E_SASL_LOGON_FAILURE, "AUTH", NULL); //Should not pass param Inputline: encrypted
fRet = FALSE;
}
} else {
BOOL fReturn = FALSE;
m_AuthToUse = SMTP_AUTH_CLEARTEXT;
if (!szUserName) szUserName = "";
if (!szPassword) szPassword = "";
DebugTrace((LPARAM) this, "Using ClearText for user %s",
szUserName);
fReturn = uuencode ((unsigned char *)szUserName,
lstrlen(szUserName), &BuffOut, FALSE);
if (fReturn) {
FormatSmtpMessage(FSM_LOG_VERB_ONLY, "AUTH LOGIN %s\r\n", (char *)
BuffOut.QueryPtr());
BytesRet = lstrlen((char *) BuffOut.QueryPtr());
} else {
DisconnectClient();
m_Error = ERROR_LOGON_FAILURE;
SetDiagnosticInfo(AQUEUE_E_SASL_LOGON_FAILURE, "AUTH", NULL); //Should not pass param Inputline: uuencoded
fRet = FALSE;
}
}
if (fRet && BytesRet) {
SendSmtpResponse();
}
TraceFunctLeaveEx((LPARAM)this);
return fRet;
}
/*++
Name :
SMTP_CONNOUT::DoSTARTTLSCommand
Description:
Negotiates use of SSL via the STARTTLS command
Arguments:
InputLine -- Indicates response of remote server
ParameterSize -- Size of Inputline
UndecryptedTailSize -- The part of the received buffer that could not
be decrypted.
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoSTARTTLSCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoSTARTTLSCommand");
if (m_TlsState == MUST_DO_TLS) {
if (IsOptionSet(TLS_OPTION) || IsOptionSet(STARTTLS_OPTION)) {
FormatSmtpMessage(FSM_LOG_ALL, "STARTTLS\r\n");
SendSmtpResponse();
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSTARTTLSCommand: STARTTLS command sent");
m_TlsState = STARTTLS_SENT;
TraceFunctLeaveEx((LPARAM)this);
return ( TRUE );
} else {
//
// We were told to do secure connection, but the remote server doesn't
// support TLS! We have to drop this connection now.
//
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSTARTTLSCommand: ERROR! Remote server does not support TLS!");
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
SetDiagnosticInfo(AQUEUE_E_TLS_NOT_SUPPORTED_ERROR, "STARTTLS", InputLine);
DisconnectClient();
SetLastError(m_Error = ERROR_NO_SECURITY_ON_OBJECT);
TraceFunctLeaveEx((LPARAM)this);
return ( FALSE );
}
} else {
//
// We sent the STARTTLS command, and InputLine has the server's response
// Handle the server's response
//
_ASSERT(m_TlsState == STARTTLS_SENT);
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSTARTTLSCommand: Server response to STARTTLS is \"%s\"", InputLine);
switch (InputLine[0]) {
case SMTP_COMPLETE_FAILURE:
{
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSTARTTLSCommand: Server returned error");
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
SetDiagnosticInfo(AQUEUE_E_TLS_ERROR, "STARTTLS", InputLine);
DisconnectClient();
SetLastError(m_Error = ERROR_NO_SECURITY_ON_OBJECT);
TraceFunctLeaveEx((LPARAM)this);
return ( FALSE );
}
break;
case SMTP_COMPLETE_SUCCESS:
{
m_SecurePort = TRUE;
m_fNegotiatingSSL = TRUE;
m_TlsState = SSL_NEG;
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSTARTTLSCommand: Server accepted, beginning SSL handshake");
//
// Switch over to using a large receive buffer, because a SSL fragment
// may be up to 32K big.
if (!SwitchToBigSSLBuffers()) {
DebugTrace((LPARAM) this,
"SMTP_CONNOUT::DoSTARTTLSCommand: Failed to allocate Big SSL buffers %d\n",
GetLastError());
DisconnectClient();
TraceFunctLeaveEx((LPARAM)this);
return ( FALSE );
}
if (!DoSSLNegotiation(NULL, 0, 0)) {
DebugTrace(
(LPARAM) this,
"SMTP_CONNOUT::DoSTARTTLSCommand: SSL Client Hello failed %d!\n",
GetLastError());
DisconnectClient();
SetLastError(m_Error = ERROR_NO_SECURITY_ON_OBJECT);
TraceFunctLeaveEx((LPARAM)this);
return ( FALSE );
}
}
break;
default:
{
DebugTrace((LPARAM) this, "SMTP_CONNOUT::DoSTARTTLSCommand: Server sent malformed response to STARTTLS");
SetDiagnosticInfo(AQUEUE_E_TLS_ERROR, "STARTTLS", InputLine);
DisconnectClient();
SetLastError(m_Error = ERROR_NO_SECURITY_ON_OBJECT);
TraceFunctLeaveEx((LPARAM)this);
return ( FALSE );
}
break;
}
}
TraceFunctLeaveEx((LPARAM)this);
return TRUE;
}
/*++
Name :
SMTP_CONNOUT::DoRSETCommand
Description:
Sends the SMTP RSET command.
Arguments:
Are ignored
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoRSETCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
FormatSmtpMessage(FSM_LOG_ALL, "RSET\r\n");
m_cbReceived = 0;
m_cbParsable = 0;
m_fUseBDAT = FALSE;
//If we are issuing rset during the Per rcpt event due to some problem
//we need to preserve the state till we handle the
//this message and ack it
if (m_RsetReasonCode != ALL_RCPTS_FAILED) {
m_fNeedRelayedDSN = FALSE;
m_fHadHardError = FALSE;
m_fHadTempError = FALSE;
}
return SendSmtpResponse();
}
/*++
Name :
SMTP_CONNOUT::SendRemainingRecipients
Description:
SendRemainingRecipients
gets called when we get a 552 error during the
RCPT to command. This means that the server we connected
to has a fixed max amount of rcpts that it accepts, and we
went over that limit. Therefore, we send the mail file to
the recipients that it accepted and then start over sending
the mail to the remaining recipients. However, before doing
this, check the response to see if the server took the previous
mail O.K.
Arguments:
none
Returns:
nothing
--*/
void SMTP_CONNOUT::SendRemainingRecipients (void)
{
#if 0
CAddr * pwalk = NULL;
PLIST_ENTRY pListTemp = NULL;
//increment our counter
BUMP_COUNTER(QuerySmtpInstance(), NumMsgsSent);
//yea, baby. Save the start address
//so we can walk the list deleting
//any addresses that were successfully
//sent.
pwalk = m_StartAddress;
pListTemp = m_StartAddressLink;
_ASSERT (pwalk != NULL);
//set the start address to NULL, so
//that we can set it to a known good
//address below. See the other comment
//below.
m_StartAddress = NULL;
m_StartAddressLink = NULL;
//step through all the previously sent addresses.
//If they have an error code of 250, then they
//were received correctly. An error code of
//552 means that we sent too many, so we are
//going to attempt to send the remaining recipients.
//We will not attempt to send a rcpt that has any
//other error code.
while (pwalk && (pwalk->GetErrorCode() != SMTP_DUMMY_FAILURE)) {
if ((pwalk->GetErrorCode() == SMTP_OK_CODE) ||
(pwalk->GetErrorCode() == SMTP_USER_NOT_LOCAL_CODE)) {
MailInfo->RemoveAddress(pwalk);
delete pwalk;
}
//get the next address
pwalk = MailInfo->GetNextAddress(&pListTemp);
}
_ASSERT (pwalk != NULL);
_ASSERT (pwalk->GetErrorCode() == SMTP_DUMMY_FAILURE);
m_StartAddress = pwalk;
m_StartAddressLink = pListTemp;
_ASSERT (m_StartAddress != NULL);
_ASSERT (m_StartAddressLink != NULL);
//update the queue file to reflect the remaining
//recipients, if any
MailInfo->UpdateQueueFile(REMOTE_NAME);
//save the current RCPT address
m_NextAddress = pwalk;
m_NextAddressLink = pListTemp;
#endif
}
BOOL SMTP_CONNOUT::DoETRNCommand(void)
{
DWORD WaitRes = WAIT_TIMEOUT;
DWORD TimeToWait = 0;
HRESULT hr = S_OK;
DomainInfo DomainParams;
char szETRNDomainBuffer[MAX_PATH+1];
char *szNextETRNDomain = NULL;
const char *szETRNDomain = szETRNDomainBuffer;
DWORD cbETRNDomain = 0;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoETRNCommand");
ZeroMemory(&DomainParams, sizeof(DomainParams));
ZeroMemory (&DomainParams, sizeof(DomainParams));
DomainParams.cbVersion = sizeof(DomainParams);
hr = m_pISMTPConnection->GetDomainInfo(&DomainParams);
if (FAILED(hr) || (DomainParams.szETRNDomainName == NULL)) {
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
if (!m_szCurrentETRNDomain) {
m_szCurrentETRNDomain = DomainParams.szETRNDomainName;
}
//Find next in comma-delimited list of domains
szNextETRNDomain = strchr(m_szCurrentETRNDomain, ',');
if (!szNextETRNDomain) {
//We are done... this is the last domain
szETRNDomain = m_szCurrentETRNDomain;
m_Flags |= ETRN_SENT;
} else {
//There are more domains left... we need to copy the domain
//to our buffer where we can NULL terminate it.
cbETRNDomain = (DWORD) (sizeof(char)*(szNextETRNDomain-m_szCurrentETRNDomain));
if ((cbETRNDomain >= sizeof(szETRNDomainBuffer)) ||
(cbETRNDomain > DomainParams.cbETRNDomainNameLength)) {
//There is not enough room for this domain
ErrorTrace((LPARAM) this, "Domain configured for ETRN is greater than MAX_PATH");
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
memcpy(szETRNDomainBuffer, m_szCurrentETRNDomain, cbETRNDomain);
szETRNDomainBuffer[cbETRNDomain/sizeof(char)] = '\0';
//Skip to beginning of next domain
m_szCurrentETRNDomain = szNextETRNDomain;
while (isspace((UCHAR)*m_szCurrentETRNDomain) || *m_szCurrentETRNDomain == ',') {
if (!(*m_szCurrentETRNDomain)) {
//End of string... we're done
m_Flags |= ETRN_SENT;
break;
}
m_szCurrentETRNDomain++;
}
}
#if 0
TimeToWait = m_pHashEntry->GetEtrnWaitTime() * 60 * 1000; //time in milliseconds
if (TimeToWait) {
WaitRes = WaitForSingleObject(QuerySmtpInstance()->GetQStopEvent(), m_pHashEntry->GetEtrnWaitTime());
}
if (WaitRes == WAIT_OBJECT_0) {
DisconnectClient();
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
#endif
FormatSmtpMessage(FSM_LOG_ALL, "ETRN %s\r\n", szETRNDomain);
SendSmtpResponse();
//Keep on sending ETRNs until we are out of domains.
if (!IsOptionSet(ETRN_ONLY_OPTION) || !(m_Flags & ETRN_SENT)) {
SetNextState (&SMTP_CONNOUT::DoMessageStartEvent);
} else {
SetNextState (&SMTP_CONNOUT::DoCompletedMessage);
}
TraceFunctLeaveEx((LPARAM)this);
return TRUE;
}
/*++
Name :
SMTP_CONNOUT::DoMAILCommand
Description:
Responds to the SMTP MAIL command.
Arguments:
Are ignored
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoMAILCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
char Options[1024] = "";
char BodySize[32];
char Address[MAX_INTERNET_NAME];
DWORD MsgOption = 0;
HRESULT hr = S_OK;
BOOL fFailedDueToBMIME = FALSE;
_ASSERT(m_pIMsg);
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoMAILCommand");
m_OutboundContext.m_dwCommandStatus = EXPE_SUCCESS;
//clear the options
Options[0] = '\0';
//If the message has 8bitmime, make sure the
//server also supports it.
hr = m_pIMsg->GetDWORD(IMMPID_MP_EIGHTBIT_MIME_OPTION, &MsgOption);
if (MsgOption) {
if (IsOptionSet(EBITMIME_OPTION)) {
lstrcat(Options, " BODY=8bitmime");
} else {
// SetLastError(SMTP_OPTION_NOT_SUPPORTED_8BIT);
DebugTrace((LPARAM) this, "Message has 8bitmime but server does not support it");
m_OutboundContext.m_dwCommandStatus = EXPE_COMPLETE_FAILURE;
//Fill in the response context buffer so as to generate the right response
// Get the error code
m_ResponseContext.m_dwSmtpStatus = SMTP_RESP_BAD_CMD;
hr = m_ResponseContext.m_cabResponse.Append(
(char *)SMTP_REMOTE_HOST_REJECTED_FOR_TYPE,
strlen(SMTP_REMOTE_HOST_REJECTED_FOR_TYPE),
NULL);
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
}
} else {
MsgOption = 0;
hr = m_pIMsg->GetDWORD(IMMPID_MP_BINARYMIME_OPTION, &MsgOption);
if (MsgOption) {
//Check if we allow BINARYMIME outbound at domain level
if (m_MsgOptions & DOMAIN_INFO_DISABLE_BMIME) {
fFailedDueToBMIME = TRUE;
} else {
//Do we disallow it globally
if (QuerySmtpInstance()->AllowOutboundBMIME()) {
if (IsOptionSet(BINMIME_OPTION) && IsOptionSet(CHUNKING_OPTION)) {
lstrcat(Options, " BODY=BINARYMIME");
m_fUseBDAT = TRUE;
} else {
fFailedDueToBMIME = TRUE;
}
} else {
fFailedDueToBMIME = TRUE;
}
}
}
}
if (fFailedDueToBMIME) {
// SetLastError(SMTP_OPTION_NOT_SUPPORTED_BMIME);
DebugTrace((LPARAM) this, "Message has BINARYMIME but server does not support it");
m_OutboundContext.m_dwCommandStatus = EXPE_COMPLETE_FAILURE;
//Fill in the response context buffer so as to generate the right response
// Get the error code
m_ResponseContext.m_dwSmtpStatus = SMTP_RESP_BAD_CMD;
hr = m_ResponseContext.m_cabResponse.Append(
(char *)SMTP_REMOTE_HOST_REJECTED_FOR_TYPE,
strlen(SMTP_REMOTE_HOST_REJECTED_FOR_TYPE),
NULL);
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
}
//See if CHUNKING is preferred on this connection
//If it is and the remote site advertised chunking then we set the UseBDAT flag
else if (!m_fUseBDAT) {
//Does the remote server advertises chunking
if (IsOptionSet(CHUNKING_OPTION)) {
//Do we disallow chunking at domain level
if (m_MsgOptions & DOMAIN_INFO_DISABLE_CHUNKING) {
DebugTrace((LPARAM) this, "We disable chunking for this domain");
} else if ((m_MsgOptions & DOMAIN_INFO_USE_CHUNKING) || QuerySmtpInstance()->ShouldChunkOut()) {
m_fUseBDAT = TRUE;
}
} else if (m_MsgOptions & DOMAIN_INFO_USE_CHUNKING)
DebugTrace((LPARAM) this, "Remote server does not advertise chunking");
}
// produce a dot-stuffed handle if we aren't using bdat
if (!m_fUseBDAT) {
DWORD fFoundEmbeddedCrlfDot = FALSE;
DWORD fScanned = FALSE;
//
// get the properties to see if we have scanned for crlf.crlf, and
// to see if the message contained crlf.crlf when it was scanned.
// if either of these lookups fail then we will set fScanned to
// FALSE
//
if (FAILED(m_pIMsg->GetDWORD(IMMPID_MP_SCANNED_FOR_CRLF_DOT_CRLF,
&fScanned)) ||
FAILED(m_pIMsg->GetDWORD(IMMPID_MP_FOUND_EMBEDDED_CRLF_DOT_CRLF,
&fFoundEmbeddedCrlfDot)))
{
fScanned = FALSE;
}
//
// if we didn't scan, or if we found an embedded crlf.crlf then
// produce a dot stuff context
//
if (!fScanned || fFoundEmbeddedCrlfDot) {
if (!m_IMsgDotStuffedFileHandle) {
m_IMsgDotStuffedFileHandle = ProduceDotStuffedContext(
m_IMsgFileHandle,
NULL,
TRUE );
if (NULL == m_IMsgDotStuffedFileHandle)
{
SetDiagnosticInfo(AQUEUE_E_BIND_ERROR , NULL, NULL);
ErrorTrace((LPARAM) this, "Failed to get dot stuffed context");
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
}
}
}
//get the size of the file
DWORD dwSizeHigh;
m_FileSize = GetFileSizeFromContext( (m_IMsgDotStuffedFileHandle && !m_fUseBDAT) ? m_IMsgDotStuffedFileHandle : m_IMsgFileHandle, &dwSizeHigh);
_ASSERT(dwSizeHigh == 0);
//if the server supports the size command
// give him the size of the file
if (IsOptionSet(SIZE_OPTION) && (m_SizeOptionSize > 0)) {
if ((m_FileSize != 0XFFFFFFFF) && (m_FileSize <= m_SizeOptionSize))
{
wsprintf(BodySize, " SIZE=%d", m_FileSize);
lstrcat(Options, BodySize);
}
else {
// SetLastError(SMTP_MSG_LARGER_THAN_SIZE);
DebugTrace((LPARAM) this, "(m_FileSize != 0XFFFFFFFF) && (m_FileSize <= m_SizeOptionSize) failed");
DebugTrace((LPARAM) this, "m_FileSize = %d, m_SizeOptionSize = %d - quiting", m_FileSize, m_SizeOptionSize );
m_OutboundContext.m_dwCommandStatus = EXPE_COMPLETE_FAILURE;
//Fill in the response context buffer so as to generate the right response
// Get the error code
m_ResponseContext.m_dwSmtpStatus = SMTP_RESP_BAD_CMD;
hr = m_ResponseContext.m_cabResponse.Append(
(char *)SMTP_REMOTE_HOST_REJECTED_FOR_SIZE,
strlen(SMTP_REMOTE_HOST_REJECTED_FOR_SIZE),
NULL);
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
}
}
if (IsOptionSet(DSN_OPTION)) {
char RetDsnValue[200];
BOOL fDSNDisallowed = TRUE;
//Do we disallow DSN at domain level
if (m_MsgOptions & DOMAIN_INFO_DISABLE_DSN) {
DebugTrace((LPARAM) this, "We disable DSN for this domain");
} else if (QuerySmtpInstance()->AllowOutboundDSN()) {
fDSNDisallowed = FALSE;
}
lstrcpy(RetDsnValue, " RET=");
hr = m_pIMsg->GetStringA(IMMPID_MP_DSN_RET_VALUE, sizeof(RetDsnValue) - lstrlen(RetDsnValue), RetDsnValue + lstrlen(RetDsnValue));
if (!FAILED(hr) && !fDSNDisallowed) {
lstrcat(Options, RetDsnValue);
}
lstrcpy(RetDsnValue, " ENVID=");
hr = m_pIMsg->GetStringA(IMMPID_MP_DSN_ENVID_VALUE, sizeof(RetDsnValue) - lstrlen(RetDsnValue), RetDsnValue + lstrlen(RetDsnValue));
if (!FAILED(hr) && !fDSNDisallowed) {
lstrcat(Options, RetDsnValue);
}
}
hr = m_pIMsg->GetStringA(IMMPID_MP_SENDER_ADDRESS_SMTP, sizeof(Address), Address);
if (!FAILED(hr)) {
//format the MAIL FROM command, with SIZE extension if necessary.
if ( (Address[0] == '<') && (Address[1] == '>'))
PE_FormatSmtpMessage("MAIL FROM:<>%s\r\n", Options);
else
PE_FormatSmtpMessage("MAIL FROM:<%s>%s\r\n", Address, Options);
} else {
DebugTrace((LPARAM) this, "Could not get Sender Address %x", hr);
m_OutboundContext.m_dwCommandStatus = EXPE_COMPLETE_FAILURE;
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
}
TraceFunctLeaveEx((LPARAM)this);
return TRUE;
}
/*++
Name :
SMTP_CONNOUT::DoMAILResponse
Description:
Responds to the SMTP MAIL command
Arguments:
Are ignored
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoMAILResponse(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoMAILResponse");
m_ResponseContext.m_dwResponseStatus = EXPE_SUCCESS;
char chInputGood = 0;
if ( isdigit( (UCHAR)InputLine[1] ) && isdigit( (UCHAR)InputLine[2] ) && ( ( InputLine[3] == ' ' ) || ( InputLine[3] == '-' ) ) ) {
chInputGood = InputLine[0];
} else {
chInputGood = SMTP_COMPLETE_FAILURE;
}
//the MAIL FROM: was rejected
if ( chInputGood != SMTP_COMPLETE_SUCCESS) {
// We expect either a 4xx or 5xx response. If it's 4xx we return
// a transient, all others will become a complete failure.
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, "MAIL", InputLine);
if ( chInputGood == SMTP_TRANSIENT_FAILURE) {
m_ResponseContext.m_dwResponseStatus = EXPE_TRANSIENT_FAILURE;
m_OutboundContext.m_pCurrentCommandContext = NULL;
} else {
m_ResponseContext.m_dwResponseStatus = EXPE_COMPLETE_FAILURE;
m_OutboundContext.m_pCurrentCommandContext = NULL;
DebugTrace((LPARAM) this,
"SMTP_CONNOUT::DoMAILResponse executing quit command err = %c%c%c",
InputLine [0],InputLine [1],InputLine [2]);
}
}
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
}
BOOL SMTP_CONNOUT::AddRcptsDsns(DWORD NotifyOptions, char * OrcptVal, char * AddrBuf, int& AddrSize)
{
BOOL FirstOption = TRUE;
if (NotifyOptions & ~(RP_DSN_NOTIFY_NEVER | RP_DSN_NOTIFY_SUCCESS | RP_DSN_NOTIFY_FAILURE | RP_DSN_NOTIFY_DELAY)) {
NotifyOptions = 0;
}
if (NotifyOptions) {
lstrcat(AddrBuf, " NOTIFY=");
AddrSize += 8;
if (NotifyOptions & RP_DSN_NOTIFY_SUCCESS) {
lstrcat(AddrBuf, "SUCCESS");
AddrSize += 7;
FirstOption = FALSE;
}
if (NotifyOptions & RP_DSN_NOTIFY_FAILURE) {
if (!FirstOption) {
lstrcat(AddrBuf, ",");
AddrSize += 1;
}
lstrcat(AddrBuf, "FAILURE");
AddrSize += 7;
FirstOption = FALSE;
}
if (NotifyOptions & RP_DSN_NOTIFY_DELAY) {
if (!FirstOption) {
lstrcat(AddrBuf, ",");
AddrSize += 1;
}
lstrcat(AddrBuf, "DELAY");
AddrSize += 5;
FirstOption = FALSE;
}
if (FirstOption) {
lstrcat(AddrBuf, "NEVER");
AddrSize += 5;
}
}
if (*OrcptVal != '\0') {
lstrcat(AddrBuf, " ORCPT=");
AddrSize += 7;
lstrcat(AddrBuf, OrcptVal);
AddrSize += lstrlen(OrcptVal);
}
return TRUE;
}
/*++
Name :
SMTP_CONNOUT::SaveToErrorFile
Description:
This function saves all errors to
a file so the NDR thread can send
a transcript of what tranpired back
to the original user
Arguments:
Buffer with recipient data, size of buffer,
Returns:
TRUE if we were able to open and write to the file
FALSE otherwise
--*/
BOOL SMTP_CONNOUT::SaveToErrorFile(char * Buffer, DWORD BufSize)
{
return TRUE;
}
/*++
Name :
SMTP_CONNOUT::DoRCPTCommand
Description:
This function sends the SMTP RCPT command.
Arguments:
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoRCPTCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
char AddrBuf [MAX_INTERNET_NAME + 2024];
char szRecipName[MAX_INTERNET_NAME];
char szOrcptVal[501];
BOOL fOrcptSpecified;
BOOL fFoundUnhandledRcpt = FALSE;
int AddrSize = 0;
HRESULT hr = S_OK;
DWORD NextAddress = 0;
_ASSERT(QuerySmtpInstance() != NULL);
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoRCPTCommand");
//MessageTrace((LPARAM) this, (LPBYTE) InputLine, ParameterSize);
// We report success
m_OutboundContext.m_dwCommandStatus = EXPE_SUCCESS;
//format as much of the recipients that could fit into
//the output buffer
NextAddress = m_NextAddress;
while (NextAddress < m_NumRcpts) {
DWORD dwRecipientFlags = 0;
hr = m_pIMsgRecips->GetDWORD(m_RcptIndexList[NextAddress], IMMPID_RP_RECIPIENT_FLAGS,&dwRecipientFlags);
if (FAILED(hr) && hr != MAILMSG_E_PROPNOTFOUND) {
//Problemmo
// Get property shouldn't fail since we've come this far
//_ASSERT(FALSE);
goto RcptError;
}
//NK** : I am moving this out as it breaks us when the first recipient is a handled recipient
// Default is pipelined
m_OutboundContext.m_dwCommandStatus = EXPE_PIPELINED | EXPE_REPEAT_COMMAND;
//check to see if this recipient needs to be looked at
if ((dwRecipientFlags & RP_HANDLED) ) {
//This recipient can be skipped over
//Mark it inaccesible so that when we sweep the tried recipient
//in case of connection drop we do not touch the guys we did not send
//We need to get atleast one guy each time we come in.
m_RcptIndexList[NextAddress] = INVALID_RCPT_IDX_VALUE;
m_NextAddress = NextAddress + 1;
NextAddress ++;
continue;
} else {
hr = m_pIMsgRecips->GetStringA(m_RcptIndexList[NextAddress], IMMPID_RP_ADDRESS_SMTP, sizeof(szRecipName), szRecipName);
if (!FAILED(hr)) {
//Format the first recipient
AddrSize = wsprintf (AddrBuf, "RCPT TO:<%s>", szRecipName);
fOrcptSpecified = FALSE;
hr = m_pIMsgRecips->GetStringA(m_RcptIndexList[NextAddress], IMMPID_RP_DSN_ORCPT_VALUE, sizeof(szOrcptVal), szOrcptVal);
if (!FAILED(hr) && (szOrcptVal[0] != '\0')) {
fOrcptSpecified = TRUE;
} else if (FAILED(hr) && hr != MAILMSG_E_PROPNOTFOUND) {
//Problemmo
// Get property shouldn't fail since we've come this far
//_ASSERT(FALSE);
goto RcptError;
} else {
szOrcptVal[0] = '\0';
}
//If some DSN property is set then
if ((dwRecipientFlags & RP_DSN_NOTIFY_MASK) || fOrcptSpecified) {
BOOL fAllowDSN = FALSE;
//Do we disallow DSN at domain level
if (m_MsgOptions & DOMAIN_INFO_DISABLE_DSN) {
DebugTrace((LPARAM) this, "We disable DSN for this domain");
} else if (QuerySmtpInstance()->AllowOutboundDSN()) {
fAllowDSN = TRUE;
}
if (IsOptionSet(DSN_OPTION) && fAllowDSN) {
AddRcptsDsns(dwRecipientFlags, szOrcptVal, AddrBuf, AddrSize);
m_fNeedRelayedDSN = FALSE;
} else {
//let AQ know that the remote server did not advertise DSN
m_fNeedRelayedDSN = TRUE;
dwRecipientFlags |= RP_REMOTE_MTA_NO_DSN;
hr = m_pIMsgRecips->PutDWORD(m_RcptIndexList[NextAddress],
IMMPID_RP_RECIPIENT_FLAGS,dwRecipientFlags);
if (FAILED(hr)) {
//Problemmo
goto RcptError;
}
}
}
lstrcat(AddrBuf, "\r\n");
AddrSize += 2;
if (PE_FormatSmtpMessage("%s", AddrBuf)) {
DebugTrace((LPARAM) this,"Sending %s", szRecipName);
m_NumRcptSent++;
m_NextAddress = NextAddress + 1;
if (!IsOptionSet(PIPELINE_OPTION))
m_OutboundContext.m_dwCommandStatus = EXPE_NOT_PIPELINED;
} else {
//no more space in the buffer, send what we have;
//_ASSERT(FALSE);
m_OutboundContext.m_dwCommandStatus = EXPE_NOT_PIPELINED;
return (TRUE);
}
} else {
//Problemmo
// Get property shouldn't fail since we've come this far
//_ASSERT(FALSE);
goto RcptError;
}
}
NextAddress ++;
break;
}
// If we are done, we will not pipeline further, this will save a loop
if (m_NumRcpts == NextAddress)
m_OutboundContext.m_dwCommandStatus = EXPE_NOT_PIPELINED;
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
RcptError:
m_OutboundContext.m_dwCommandStatus = EXPE_TRANSIENT_FAILURE;
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
/*++
Name :
SMTP_CONNOUT::DoRCPTResponse
Description:
This function handles the SMTP RCPT response.
Arguments:
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoRCPTResponse(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
HRESULT hr = S_OK;
DWORD NextAddress = 0;
DWORD dwRecipientFlags;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoRCPTResponse");
_ASSERT(QuerySmtpInstance() != NULL);
//MessageTrace((LPARAM) this, (LPBYTE) InputLine, ParameterSize);
m_ResponseContext.m_dwResponseStatus = EXPE_SUCCESS;
//start at the beginning of the last
//pipelined address
NextAddress = m_FirstPipelinedAddress;
DebugTrace((LPARAM)this, "Response: %*s", ParameterSize, InputLine);
//step through the returned recipients and check
//their error code.
if (m_NumRcptSent) {
//Get to the next rcpt that we send out this time
while ((NextAddress < m_NumRcpts) && (m_RcptIndexList[NextAddress] == INVALID_RCPT_IDX_VALUE))
NextAddress++;
_ASSERT(NextAddress < m_NumRcpts);
if (NextAddress >= m_NumRcpts) {
//Problemo
SetLastError(ERROR_INVALID_DATA);
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
dwRecipientFlags = 0;
hr = m_pIMsgRecips->GetDWORD(m_RcptIndexList[NextAddress], IMMPID_RP_RECIPIENT_FLAGS,&dwRecipientFlags);
if (FAILED(hr) && hr != MAILMSG_E_PROPNOTFOUND) {
//Problemmo
SetLastError(ERROR_OUTOFMEMORY);
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
m_NumRcptSent--;
//Once we get 552 Too many recipients error .. we do not bother to look at the real responses
//Logically they will all be 552 as well
if (m_SendAgain)
m_ResponseContext.m_dwSmtpStatus = SMTP_ACTION_ABORTED_CODE;
// If we have no response status code set, we will abort action
if (m_ResponseContext.m_dwSmtpStatus == 0) {
m_NumFailedAddrs++;
m_NumRcptSentSaved ++;
hr = m_pIMsgRecips->PutDWORD(m_RcptIndexList[NextAddress], IMMPID_RP_ERROR_CODE, SMTP_ACTION_ABORTED_CODE);
if (FAILED(hr)) {
//Problemmo
SetLastError(ERROR_OUTOFMEMORY);
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
//Create the temp error string
char sztemp[100];
sprintf(sztemp,"%d %s",SMTP_ACTION_ABORTED_CODE, "Invalid recipient response");
hr = m_pIMsgRecips->PutStringA(m_RcptIndexList[NextAddress], IMMPID_RP_SMTP_STATUS_STRING,sztemp);
if (FAILED(hr)) {
//Problemmo
SetLastError(ERROR_OUTOFMEMORY);
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
//real bad error. SCuttle this message
MessageTrace((LPARAM) this, (LPBYTE) InputLine, ParameterSize);
SetLastError(ERROR_CAN_NOT_COMPLETE);
m_ResponseContext.m_dwResponseStatus = EXPE_TRANSIENT_FAILURE;
m_OutboundContext.m_pCurrentCommandContext = NULL;
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, "RCPT", InputLine);
return (TRUE);
} else {
//save the number of recipients sent so that
//we can compare this number to the number of
//failed recipients.
m_NumRcptSentSaved ++;
BUMP_COUNTER(QuerySmtpInstance(), NumRcptsSent);
// Save the error code (if there was an error)
// We do not want to save "250 OK" or "251 OK" responses because
// it wastes increases the memory footprint of messages in
// the queue
if ((SMTP_OK_CODE != m_ResponseContext.m_dwSmtpStatus) &&
(SMTP_USER_NOT_LOCAL_CODE != m_ResponseContext.m_dwSmtpStatus)) {
ErrorTrace((LPARAM)this,
"Saving rcpt error code %u - %s",
m_ResponseContext.m_dwSmtpStatus, InputLine);
hr = m_pIMsgRecips->PutDWORD(
m_RcptIndexList[NextAddress],
IMMPID_RP_ERROR_CODE,
m_ResponseContext.m_dwSmtpStatus);
if (FAILED(hr)) {
//Problemmo
SetLastError(ERROR_OUTOFMEMORY);
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
//Set the ful response as error string
hr = m_pIMsgRecips->PutStringA(m_RcptIndexList[NextAddress], IMMPID_RP_SMTP_STATUS_STRING, InputLine);
if (FAILED(hr)) {
//Problemmo
SetLastError(ERROR_OUTOFMEMORY);
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
} else {
//Recipient successful... trace it
DebugTrace((LPARAM) this,
"Recipient %d OK with response %s",
NextAddress, InputLine);
}
switch (m_ResponseContext.m_dwSmtpStatus) {
//4XX level code will lead to retry
case SMTP_ERROR_PROCESSING_CODE:
case SMTP_MBOX_BUSY_CODE:
case SMTP_SERVICE_UNAVAILABLE_CODE:
//Buffer[3] = ' '; put back the space character
m_fHadTempError = TRUE;
dwRecipientFlags |= (RP_ERROR_CONTEXT_MTA);
m_NumFailedAddrs++;
break;
case SMTP_ACTION_ABORTED_CODE:
//this means we sent too many recipients.
//set the m_SendAgain flag which tells us
//to send whatever we have now, then start
//sending to the other recipients afterwards.
//We have to switch the error code because 552
//means different things depending on what operation
//we are doing.
if (!m_SendAgain) {
if (m_fHadSuccessfulDelivery) {
m_NumFailedAddrs++;
m_SendAgain = TRUE;
if( m_NumFailedAddrs >= m_NumRcptSentSaved )
{
m_fHadHardError = TRUE;
}
if (m_First552Address == -1)
m_First552Address = NextAddress;
break;
}
} else {
m_NumFailedAddrs++;
break;
}
//A 552 response is treated as failure if we did not even send
//a single recipient
//fall thru
//5XX level codes will lead to NDR for the rcpt
case SMTP_UNRECOG_COMMAND_CODE:
case SMTP_SYNTAX_ERROR_CODE:
case SMTP_NOT_IMPLEMENTED_CODE:
case SMTP_BAD_SEQUENCE_CODE :
case SMTP_PARAM_NOT_IMPLEMENTED_CODE:
case SMTP_MBOX_NOTFOUND_CODE:
case SMTP_USERNOTLOCAL_CODE:
case SMTP_ACTION_NOT_TAKEN_CODE:
case SMTP_TRANSACT_FAILED_CODE:
// Buffer[3] = ' '; //put back the space character
//SaveToErrorFile(Buffer, IntermediateSize);
// DebugTrace((LPARAM) this, "User %s failed because of %d", pwalk->GetAddress(), Error);
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, "RCPT", InputLine);
m_fHadHardError = TRUE;
dwRecipientFlags |= (RP_FAILED | RP_ERROR_CONTEXT_MTA);
m_NumFailedAddrs++;
break;
case SMTP_OK_CODE:
case SMTP_USER_NOT_LOCAL_CODE:
m_fHadSuccessfulDelivery = TRUE;
BUMP_COUNTER(QuerySmtpInstance(), NumRcptsSent);
dwRecipientFlags |= (RP_DELIVERED | RP_ERROR_CONTEXT_MTA);
break;
case SMTP_SERVICE_CLOSE_CODE:
case SMTP_INSUFF_STORAGE_CODE:
//fall through. This is deliberate.
//we don't want to continue if we get
//these errors
default:
//real bad error. Copy this error to the
//front of the input buffer, because this
//buffer will be passed to DoCompletedMessage
//to do the right thing.
{
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, "RCPT", InputLine);
char chInputGood = 0;
if ( isdigit( (UCHAR)InputLine[1] ) &&
isdigit( (UCHAR)InputLine[2] ) &&
( ( InputLine[3] == ' ' ) || ( InputLine[3] == '-' ) ) ) {
chInputGood = InputLine[0];
} else {
chInputGood = SMTP_COMPLETE_FAILURE;
}
MessageTrace((LPARAM) this, (LPBYTE) InputLine, ParameterSize);
SetLastError(ERROR_CAN_NOT_COMPLETE);
if ( chInputGood == SMTP_TRANSIENT_FAILURE) {
m_ResponseContext.m_dwResponseStatus = EXPE_TRANSIENT_FAILURE;
m_OutboundContext.m_pCurrentCommandContext = NULL;
} else {
m_ResponseContext.m_dwResponseStatus = EXPE_COMPLETE_FAILURE;
m_OutboundContext.m_pCurrentCommandContext = NULL;
}
return (TRUE);
}
}
}
//Set the flags back
hr = m_pIMsgRecips->PutDWORD(m_RcptIndexList[NextAddress], IMMPID_RP_RECIPIENT_FLAGS,dwRecipientFlags);
if (FAILED(hr)) {
//Problemmo
SetLastError(ERROR_OUTOFMEMORY);
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
NextAddress++;
}
// Mark where we should pick up next time ...
m_FirstPipelinedAddress = NextAddress;
// No more unprocessed recipients, we are either done or we have to
// issue more RCPT commands
if ((NextAddress < m_NumRcpts) && !m_SendAgain)
m_ResponseContext.m_dwResponseStatus = EXPE_REPEAT_COMMAND;
TraceFunctLeaveEx((LPARAM)this);
return (TRUE);
}
/*++
Name :
SMTP_CONNOUT::DoDATACommand
Description:
Responds to the SMTP DATA command.
Arguments:
InputLine - Buffer received from client
paramterSize - amount of data in buffer
both are ignored
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoDATACommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
_ASSERT(QuerySmtpInstance() != NULL);
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoDATACommand");
SetNextState (&SMTP_CONNOUT::DoDATACommandEx);
FormatSmtpMessage(FSM_LOG_ALL, "DATA\r\n");
TraceFunctLeaveEx((LPARAM)this);
return SendSmtpResponse();
}
/*++
Name :
SMTP_CONNOUT::DoBDATCommand
Description:
Send out the SMTP BDAT command.
Arguments:
InputLine - Buffer received from client
paramterSize - amount of data in buffer
both are ignored
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoBDATCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
_ASSERT(QuerySmtpInstance() != NULL);
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoBDATCommand");
SetNextState (&SMTP_CONNOUT::DoDATACommandEx);
//We send the whole mesage file as a single BDAT chunk
//Verify if the CHUNK size should be FileSize or +2 for trailing CRLF
//
FormatSmtpMessage(FSM_LOG_ALL, "BDAT %d LAST\r\n", m_FileSize);
if (!SendSmtpResponse()) {
//BDAT command failed for some reason
DebugTrace((LPARAM) this, "Failed to send BDAT command");
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
TraceFunctLeaveEx((LPARAM)this);
return DoDATACommandEx(InputLine, ParameterSize, UndecryptedTailSize);
}
/*++
Name :
SMTP_CONNOUT::DoDATACommandEx
Description:
This funcion sends the message body to remote server
after the DATA or BDAT command
Arguments:
InputLine - Buffer received from remote Server
paramterSize - amount of data in buffer
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoDATACommandEx(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
LARGE_INTEGER LSize = {0,0};
BOOL fRet = TRUE;
HRESULT hr = S_OK;
char szResponse[] = "123";
LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoDATACommandEx");
//we need to receive a 354 from the server to go on in case of DATA command
// In case of BDAT, the remote server never responds and so ignore the input line
//
if (InputLine[0] == SMTP_INTERMEDIATE_SUCCESS || m_fUseBDAT) {
SetNextState (&SMTP_CONNOUT::DoContentResponse);
LSize.HighPart = 0;
LSize.LowPart = m_FileSize;
m_LastClientIo = SMTP_CONNOUT::TRANSFILEIO;
//use the trailer buffers only for DATA command
lpTransmitBuffers = (m_fUseBDAT) ? NULL : &m_TransmitBuffers;
fRet = TransmitFileEx ((!m_fUseBDAT && m_IMsgDotStuffedFileHandle) ? m_IMsgDotStuffedFileHandle->m_hFile : m_IMsgFileHandle->m_hFile,
LSize,
0,
lpTransmitBuffers);
if (!fRet) {
m_Error = GetLastError();
DebugTrace((LPARAM) this, "TranmitFile in DoDATACommandEx failed with error %d !!!!", m_Error);
DisconnectClient();
return FALSE;
}
//need to reset the ATQ timeout thread since we pended two
//I/Os back to back.
AtqContextSetInfo(QueryAtqContext(), ATQ_INFO_RESUME_IO, 0);
} else { //quit if we don't get the 354 response
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, "DATA", InputLine);
ErrorTrace((LPARAM) this, "DoDATACommandEx executing quit command err = %c%c%c", InputLine [0],InputLine [1],InputLine [2]);
if (InputLine[0] == SMTP_COMPLETE_SUCCESS) {
CopyMemory(InputLine, "599", 3);
DebugTrace((LPARAM) this, "DoDATACommandEx executing quit command err = %c%c%c", InputLine [0],InputLine [1],InputLine [2]);
}
//DoCompletedMessage uses the m_ResponseContext to get the error
if ((ParameterSize > 3) &&
((InputLine[3] == ' ') || (InputLine[3] == CR))) {
//Try to get error from 3 digit code on input line
//In some cases (DoDataCommandEx), the m_ResponseContext is not
//used
memcpy(szResponse, InputLine, sizeof(szResponse)-sizeof(char));
m_ResponseContext.m_dwSmtpStatus = atoi(szResponse);
hr = m_ResponseContext.m_cabResponse.Append(
InputLine + sizeof(szResponse)/sizeof(char),
ParameterSize-sizeof(szResponse),
NULL);
if (FAILED(hr))
{
ErrorTrace((LPARAM) this,
"Unable to append Input line to Response Context", hr);
//Really nothing we can do about this... DoCompletedMailObj
//will send RSET and try again
}
}
fRet = DoCompletedMessage(InputLine, ParameterSize, UndecryptedTailSize);
//If DoCompletedMessage returns TRUE... then we must post a read for the
//response. Functions that call into this function expect it to handle
//the IO state if it returns TRUE.
if (fRet)
{
if (m_cbReceived >= m_cbMaxRecvBuffer)
m_cbReceived = 0;
IncPendingIoCount();
m_LastClientIo = SMTP_CONNOUT::READIO;
fRet = ReadFile(QueryMRcvBuffer() + m_cbReceived,
m_cbMaxRecvBuffer - m_cbReceived);
if (!fRet) {
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
m_Error = GetLastError();
SetDiagnosticInfo(HRESULT_FROM_WIN32(m_Error), NULL, NULL);
DisconnectClient();
DecPendingIoCount();
DebugTrace((LPARAM) this, "ReadFile in DoDataCommandEx failed with error %d", m_Error);
}
}
}
TraceFunctLeaveEx((LPARAM)this);
return fRet;
}
/*++
Name :
SMTP_CONNOUT::DoContentResponse
Description:
This function catches the final response after transmitting
the message content
Arguments:
InputLine - Buffer received from remote Server
paramterSize - amount of data in buffer
Returns:
TRUE if the connection should stay open.
FALSE if this object should be deleted.
--*/
BOOL SMTP_CONNOUT::DoContentResponse(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoContentResponse");
//send again gets sent when we get a 552 error during the
//RCPT to command. This means that the server we connected
//to has a fixed max amount of rcpts that it accepts, and we
//went over that limit. Therefore, we send the mail file to
//the recipients that it accepted and then start over sending
//the mail to the remaining recipients. However, before doing
//this, check the response to see if the server took the previous
//mail O.K.
if (m_SendAgain) {
m_cbReceived = 0;
m_OutputBufferSize = 0;
m_Error = NO_ERROR;
m_NumRcptSent = 0;
m_NumRcptSentSaved = 0;
m_NumFailedAddrs = 0;
//NK** : I am moving this down as the check below seems
//meaningless with this in here
//m_SendAgain = FALSE;
m_FirstRcpt = FALSE;
_ASSERT(m_First552Address != -1);
//NK** : I am not sure what we are trying to do here by setting pipelined addr to 552addr.
//I am going to leave this in - but also assign this value to NextAddr which decides
//from where we should really start
m_FirstPipelinedAddress = (DWORD) m_First552Address;
m_NextAddress = (DWORD) m_First552Address;
m_FirstAddressinCurrentMail = (DWORD) m_First552Address;
m_First552Address = -1;
m_TransmitTailBuffer[0] = '.';
m_TransmitTailBuffer[1] = '\r';
m_TransmitTailBuffer[2] = '\n';
m_TransmitBuffers.Head = NULL;
m_TransmitBuffers.HeadLength = 0;
m_TransmitBuffers.Tail = m_TransmitTailBuffer;
m_TransmitBuffers.TailLength = 3;
BUMP_COUNTER(QuerySmtpInstance(), NumMsgsSent);
//reset the file pointer to the beginning
if (SetFilePointer(m_IMsgDotStuffedFileHandle ? m_IMsgDotStuffedFileHandle->m_hFile: m_IMsgFileHandle->m_hFile, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF)
{
m_Error = GetLastError();
DebugTrace((LPARAM) this, "SetFilePointer failed--err = %d", m_Error);
m_SendAgain = FALSE;
} else if (InputLine [0] != SMTP_COMPLETE_SUCCESS) {
//something went wrong before, so let's quit
DebugTrace((LPARAM) this,
"SMTP_CONNOUT::DoMAILCommand executing quit command err = %c%c%c",
InputLine [0],InputLine [1],InputLine [2]);
m_SendAgain = FALSE;
}
}
// Note the if clause above might change this flag so a simple
// else won't cut it
if (!m_SendAgain) {
fRet = DoCompletedMessage(InputLine, ParameterSize, UndecryptedTailSize);
} else {
m_SendAgain = FALSE;
fRet = DoMessageStartEvent(InputLine, ParameterSize, UndecryptedTailSize);
}
TraceFunctLeaveEx((LPARAM)this);
return (fRet);
}
BOOL SMTP_CONNOUT::TransmitFileEx (HANDLE hFile, LARGE_INTEGER &liSize,
DWORD Offset, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers)
{
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::TransmitFileEx");
BOOL fRet;
_ASSERT(hFile != INVALID_HANDLE_VALUE);
_ASSERT(liSize.QuadPart > 0);
QueryAtqContext()->Overlapped.Offset = Offset;
if (!m_SecurePort) {
DebugTrace((LPARAM) this, "Calling AtqTransmitFile");
TraceFunctLeaveEx((LPARAM)this);
IncPendingIoCount();
fRet = AtqTransmitFile(
QueryAtqContext(), // Atq context
hFile, // file data comes fro
liSize.LowPart, // Bytes To Send
lpTransmitBuffers, // header/tail buffers
0
);
if (!fRet) {
DecPendingIoCount();
}
return ( fRet );
} else {
//
// If we are connected over an SSL port, we cannot use TransmitFile,
// since the data has to be encrypted first.
//
DebugTrace((LPARAM)this, "Doing async reads and writes on handle %x", hFile);
DebugTrace( (DWORD_PTR)this, "hfile %x", hFile );
//
// Send the transmit buffer header synchronously
//
fRet = TRUE;
//We have no headers or Trailers if we are doing BDAT processing
if (lpTransmitBuffers && m_TransmitBuffers.HeadLength > 0) {
m_OutputBufferSize = m_cbMaxOutputBuffer;
//NimishK : We look for Head length and then send Tail
//Dosn't seem right.
fRet = m_encryptCtx.SealMessage(
(LPBYTE) m_TransmitBuffers.Tail,
m_TransmitBuffers.TailLength,
(LPBYTE) m_pOutputBuffer,
&m_OutputBufferSize);
if (fRet)
fRet = WriteFile(m_pOutputBuffer, m_OutputBufferSize);
else
SetLastError(AQUEUE_E_SSL_ERROR);
}
if (fRet) {
//
// issue the first async read against the file
//
m_dwFileOffset = 0;
m_bComplete = FALSE;
if ( MessageReadFile() ) {
TraceFunctLeaveEx((LPARAM) this);
return TRUE;
}
}
//
// WriteFile\MessageReadFile will teardown the connection on errors
//
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
}
/*++
Name:
SMTP_CONNOUT::MessageReadFile
Description:
When TransmitFile cannot be used to transfer a message (for example,
when using SSL), the message is transferred in chunks using
MessageReadFile to retrieve chunks of the file and issueing
corresponding asynchronous WriteFiles to the remote server.
Arguments:
None.
Returns:
TRUE if successfully read the next chunk of the file, FALSE otherwise
--*/
BOOL SMTP_CONNOUT::MessageReadFile( void )
{
TraceFunctEnterEx( (LPARAM)this, "SMTP_CONNOUT::MessageReadFile");
//
// Main line code path
//
CBuffer* pBuffer = new CBuffer();
if ( pBuffer != NULL ) {
LPBYTE lpData = pBuffer->GetData();
if ( lpData != NULL ) {
DWORD cbBufSize = CIoBuffer::Pool.GetInstanceSize();
// we never want to make SSL data > 16k
if (cbBufSize > 16*1024) cbBufSize = 16*1024;
DWORD cb = cbBufSize -
m_encryptCtx.GetSealHeaderSize() -
m_encryptCtx.GetSealTrailerSize();
lpData += m_encryptCtx.GetSealHeaderSize();
cb = min( cb, m_FileSize - m_dwFileOffset );
//
// set buffer specific IO state
//
pBuffer->SetIoState( MESSAGE_READ );
pBuffer->SetSize( cb );
DebugTrace( (LPARAM)this, "ReadFile 0x%08X, len: %d, LPO: 0x%08X",
lpData,
cb,
&pBuffer->m_Overlapped.SeoOverlapped.Overlapped );
ZeroMemory( (void*)&pBuffer->m_Overlapped.SeoOverlapped, sizeof(OVERLAPPED) );
pBuffer->m_Overlapped.SeoOverlapped.Overlapped.Offset = m_dwFileOffset;
pBuffer->m_Overlapped.SeoOverlapped.Overlapped.pfnCompletion = FIOInternetCompletion;
pBuffer->m_Overlapped.SeoOverlapped.ThisPtr = this;
// pBuffer->m_Overlapped.m_pIoBuffer = (LPBYTE)InputLine;
//
// increment the overall pending io count for this session
//
IncPendingIoCount();
if ( FIOReadFile(m_IMsgDotStuffedFileHandle ? m_IMsgDotStuffedFileHandle : m_IMsgFileHandle,
lpData,
cb,
&pBuffer->m_Overlapped.SeoOverlapped.Overlapped ) == FALSE ) {
DecPendingIoCount();
ErrorTrace( (LPARAM)this, "AtqReadFile failed.");
} else {
TraceFunctLeaveEx((LPARAM) this);
return TRUE;
}
}
delete pBuffer;
}
m_Error = GetLastError();
ErrorTrace( (LPARAM)this, "MessageReadFile failed. err: %d", m_Error );
DisconnectClient();
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
/*++
Name :
SMTP_CONNOUT::FreeAtqFileContext
Description :
Frees AtqContext associated with message files transfered using
async reads and writes.
Arguments :
None. Operates on m_pAtqFileContext
Returns :
Nothing
--*/
void SMTP_CONNOUT::FreeAtqFileContext( void )
{
PATQ_CONTEXT pAtq;
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::FreeAtqFileContext");
#if 0
pAtq = (PATQ_CONTEXT)InterlockedExchangePointer( (PVOID *)&m_pAtqFileContext, (PVOID) NULL );
if ( pAtq != NULL ) {
DebugTrace((LPARAM) this, "Freeing AtqFileContext!");
pAtq->hAsyncIO = NULL;
AtqFreeContext( pAtq, FALSE );
}
#endif
TraceFunctLeaveEx((LPARAM) this);
}
/*++
Name :
SMTP_CONNOUT::DoCompletedMessage
Description:
Sends the SMTP QUIT command.
This function always returns false.
This will stop all processing and
delete this object.
Arguments:
Are ignored
Returns:
Always return FALSE
--*/
BOOL SMTP_CONNOUT::DoCompletedMessage(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
BOOL fRet = TRUE;
DWORD MsgStatus = 0;
DWORD Error = 0;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoCompletedMessage");
//see if the response received has some bad code
if (InputLine[0] != SMTP_COMPLETE_SUCCESS) {
// NimishK :
//Assumption : If I get 5XX response that leads to DOQUIT it means something real bad happened.
//We will consider this as a permanent error and NDR all recipients
//If the code is 4XX we will Retry all recipients
if (InputLine[0] == SMTP_COMPLETE_FAILURE) {
MsgStatus = MESSAGE_STATUS_NDR_ALL | MESSAGE_STATUS_EXTENDED_STATUS_CODES;
} else {
MsgStatus = MESSAGE_STATUS_RETRY_ALL | MESSAGE_STATUS_EXTENDED_STATUS_CODES;
if (m_fHadHardError) {
MsgStatus |= MESSAGE_STATUS_CHECK_RECIPS;
}
}
if (m_ResponseContext.m_cabResponse.Length() > 1) {
InputLine = m_ResponseContext.m_cabResponse.Buffer();
ParameterSize = m_ResponseContext.m_cabResponse.Length();
Error = m_ResponseContext.m_dwSmtpStatus;
}
} else if (m_Error == NO_ERROR) {
//if the message was delivered successfully
//then bump up our counter
BUMP_COUNTER(QuerySmtpInstance(), NumMsgsSent);
if (!IsOptionSet(DSN_OPTION)) {
MsgStatus |= MESSAGE_STATUS_DSN_NOT_SUPPORTED;
}
//If we have generate DELAY DSN or NDR DSN we need to tell
//AQ to look at the recipients
if (m_fNeedRelayedDSN || m_fHadHardError) {
MsgStatus |= MESSAGE_STATUS_CHECK_RECIPS;
}
//If we had no failures we can set this special status for optimization
//if only error we had was a hard error - there is no reason to report anything
if (!m_fHadTempError && !m_fHadHardError )
MsgStatus |= MESSAGE_STATUS_ALL_DELIVERED;
else if (m_fHadTempError)
MsgStatus |= MESSAGE_STATUS_RETRY_ALL;
} else {
//The remote server did not have a problem, but we had internal
//problem
//NimishK : Add an extended Status
MsgStatus = MESSAGE_STATUS_RETRY_ALL;
}
//figure out what to do with the completed
//message(e.g. send to retryq, remoteq, badmail,
//etc.)
//Includes per recipient 4xx level errors
if (InputLine[0] == SMTP_TRANSIENT_FAILURE) {
//If we cannot connect to the next IpAddress, then ack the message as is
if (ConnectToNextIpAddress()) {
//$$REVIEW - mikeswa 9/11/98
//In this case we will attempt to connect to another IP address.
//Most of the state is reset at this point (except for recipient
//failures and status strings). What happens if we fail to connect?
//Do we:
// - Ack the message as RETRY (current implementation)
// - Attempt to ack the message with CHECK_RECIPS as well and
// let the per-recip flags be enought detail for the DSN code?
TraceFunctLeaveEx((LPARAM)this);
return (FALSE);
}
}
HandleCompletedMailObj(MsgStatus, InputLine, ParameterSize);
if ((Error == SMTP_SERVICE_UNAVAILABLE_CODE)
|| (Error == SMTP_INSUFF_STORAGE_CODE)|| (Error == SMTP_SERVICE_CLOSE_CODE)
|| QuerySmtpInstance()->IsShuttingDown()) {
//No point in continuing with other messages in this connection
//Set the connection ack status to DROPPED - AQ will look at my last
//Msg Ack and determine what to do with remaining messages
m_dwConnectionStatus = CONNECTION_STATUS_DROPPED;
m_Active = FALSE;
DebugTrace( (LPARAM)this, "got this error %u on response, calling DoSessionEndEvent", Error );
fRet = DoSessionEndEvent(InputLine, ParameterSize, UndecryptedTailSize);
} else {
fRet = StartSession();
if (!fRet) {
//Set the connection Ack status
m_dwConnectionStatus = CONNECTION_STATUS_OK;
DebugTrace( (LPARAM)this, "StartSession failed, calling DoSessionEndEvent", Error );
fRet = DoSessionEndEvent(InputLine, ParameterSize, UndecryptedTailSize);
}
}
TraceFunctLeaveEx((LPARAM)this);
return (fRet);
}
/*++
Name :
SMTP_CONNOUT::DoQUITCommand
Description:
Just generates a QUIT command
Arguments:
Are ignored
Returns:
Always return FALSE
--*/
BOOL SMTP_CONNOUT::DoQUITCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoQUITCommand");
m_OutboundContext.m_dwCommandStatus = EXPE_SUCCESS;
fRet = PE_FormatSmtpMessage("QUIT\r\n");
TraceFunctLeaveEx((LPARAM)this);
return (fRet);
}
/*++
Name :
SMTP_CONNOUT::WaitForRSETResponse
Description:
Waits for the response to the quit command
Arguments:
Are ignored
Returns:
FALSE always
--*/
BOOL SMTP_CONNOUT::WaitForRSETResponse(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::WaitForRSETResponse");
_ASSERT (QuerySmtpInstance() != NULL);
if (m_RsetReasonCode == BETWEEN_MSG)
fRet = DoMessageStartEvent(InputLine, ParameterSize, UndecryptedTailSize);
else if (m_RsetReasonCode == ALL_RCPTS_FAILED) {
DebugTrace( (LPARAM)this, "m_RsetReasonCode = ALL_RCPTS_FAILED calling DoCompletedMessage" );
//Check to see if all of the failures were hard (and not transient)
//If so, set the error code to '5' instead of '4'
if (!m_fHadTempError) { //no temp errors
_ASSERT(m_fHadHardError); //therefore all errors must have been 5xx
//Must have had as many failures as processed RCPT TO's
_ASSERT(m_NumFailedAddrs >= m_NumRcptSentSaved);
InputLine[0] = SMTP_COMPLETE_FAILURE;
} else {
InputLine[0] = SMTP_TRANSIENT_FAILURE;
}
fRet = DoCompletedMessage(InputLine, ParameterSize, UndecryptedTailSize);
} else if (m_RsetReasonCode == NO_RCPTS_SENT) {
DebugTrace( (LPARAM)this, "m_RsetReasonCode = NO_RCPTS_SENT calling DoCompletedMessage" );
InputLine[0] = '2';
fRet = DoCompletedMessage(InputLine, ParameterSize, UndecryptedTailSize);
} else if (m_RsetReasonCode == FATAL_ERROR) {
DebugTrace( (LPARAM)this, "m_RsetReasonCode = FATAL_ERROR calling DoCompletedMessage" );
//make sure the quit code does not think everything is O.K.
InputLine[0] = '4';
fRet = DoCompletedMessage(InputLine, ParameterSize, UndecryptedTailSize);
}
return (fRet);
}
/*++
Name :
SMTP_CONNOUT::WaitForQuitResponse
Description:
Waits for the response to the quit command
Arguments:
Are ignored
Returns:
FALSE always
--*/
BOOL SMTP_CONNOUT::WaitForQuitResponse(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
_ASSERT (QuerySmtpInstance() != NULL);
DisconnectClient();
return (FALSE);
}
BOOL SMTP_CONNOUT::DoTURNCommand(char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize)
{
CLIENT_CONN_PARAMS clientParams;
SMTP_CONNECTION * SmtpConnIn = NULL;
DWORD Error = 0;
TraceFunctEnterEx((LPARAM) this,"SMTP_CONNOUT::DoTURNCommand");
if (InputLine[0] != SMTP_COMPLETE_SUCCESS) {
SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, "TURN", InputLine);
FormatSmtpMessage(FSM_LOG_ALL, "QUIT\r\n");
SendSmtpResponse();
DisconnectClient();
return FALSE;
}
SOCKADDR_IN sockAddr;
int cbAddr = sizeof( sockAddr);
if ( getsockname((SOCKET) m_pAtqContext->hAsyncIO,
(struct sockaddr *) &sockAddr,
&cbAddr )) {
}
clientParams.sClient = (SOCKET) m_pAtqContext->hAsyncIO;
clientParams.pAtqContext = m_pAtqContext;
clientParams.pAddrLocal = (PSOCKADDR) NULL;
clientParams.pAddrRemote = (PSOCKADDR)&sockAddr;
clientParams.pvInitialBuff = NULL;
clientParams.cbInitialBuff = 0 ;
clientParams.pEndpoint = (PIIS_ENDPOINT)NULL;
QuerySmtpInstance()->Reference();
QuerySmtpInstance()->IncrementCurrentConnections();
SmtpConnIn = (SMTP_CONNECTION *) QuerySmtpInstance()->CreateNewConnection( &clientParams);
if (SmtpConnIn == NULL) {
Error = GetLastError();
SendSmtpResponse();
DisconnectClient();
TraceFunctLeaveEx((LPARAM) this);
return FALSE;
}
//from here on, the smtpout class is responsible for
//cleaning up the AtqContext
m_DoCleanup = FALSE;
//copy the real domain we are connected to.
AtqContextSetInfo(m_pAtqContext, ATQ_INFO_COMPLETION, (DWORD_PTR) InternetCompletion);
AtqContextSetInfo(m_pAtqContext, ATQ_INFO_COMPLETION_CONTEXT, (DWORD_PTR) SmtpConnIn);
AtqContextSetInfo(m_pAtqContext, ATQ_INFO_TIMEOUT, QuerySmtpInstance()->GetRemoteTimeOut());
if (SmtpConnIn->StartSession()) {
}
return ( FALSE );
}
/*++
Name :
BOOL SMTP_CONNOUT::SwitchToBigReceiveBuffer
Description:
Helper routine to allocate a 32K buffer and use it for posting reads.
SSL fragments can be up to 32K large, and we need to accumulate an
entire fragment to be able to decrypt it.
Arguments:
none
Returns:
TRUE if the receive buffer was successfully allocated, FALSE otherwise
--*/
BOOL SMTP_CONNOUT::SwitchToBigSSLBuffers(void)
{
char *pTempBuffer;
pTempBuffer = new char [MAX_SSL_FRAGMENT_SIZE];
if (pTempBuffer != NULL) {
m_precvBuffer = pTempBuffer;
m_cbMaxRecvBuffer = MAX_SSL_FRAGMENT_SIZE;
pTempBuffer = new char [MAX_SSL_FRAGMENT_SIZE];
if (pTempBuffer != NULL) {
m_pOutputBuffer = pTempBuffer;
m_cbMaxOutputBuffer = MAX_SSL_FRAGMENT_SIZE;
return ( TRUE );
}
}
return ( FALSE );
}
/*++
Name :
char * IsLineCompleteBW
Description:
Looks for a CRLF starting at the back
of the buffer. This is from some Gibraltar
code.
Arguments:
pchRecv - The buffer to be scanned
cbRecvd - size of data in the buffer
Returns:
A pointer where the CR is if both CRLF is found,
or NULL
--*/
char * IsLineCompleteBW(IN OUT char * pchRecvd, IN DWORD cbRecvd, IN DWORD cbMaxRecvBuffer)
{
register int Loop;
_ASSERT( pchRecvd != NULL);
if (cbRecvd == 0)
return NULL;
if (cbRecvd > cbMaxRecvBuffer)
return NULL;
//
// Scan the entire buffer ( from back) looking for pattern <cr><lf>
//
for ( Loop = (int) (cbRecvd - 2); Loop >= 0; Loop-- ) {
//
// Check if consecutive characters are <cr> <lf>
//
if ( ( pchRecvd[Loop] == '\r') &&
( pchRecvd[Loop + 1]== '\n')) {
//return where the CR is in the buffer
return &pchRecvd[Loop];
}
} // for
return (NULL);
}
/*++
Name :
SMTP_CONNOUT::PE_FormatSmtpMessage( IN const char * Format, ...)
Description:
This function operates likes sprintf, printf, etc. It
just places it's data in the native command output buffer.
Arguments:
Format - Data to place in the buffer
Returns:
--*/
BOOL SMTP_CONNOUT::PE_FormatSmtpMessage( IN const char * Format, ...)
{
int BytesWritten;
va_list arglist;
//if BytesWritten is < 0, that means there is no space
//left in the buffer. Therefore, we flush any pending
//responses to make space. Then we try to place the
//information in the buffer again. It should never
//fail this time.
va_start (arglist, Format);
_ASSERT(m_OutboundContext.m_cabNativeCommand.Buffer());
BytesWritten = _vsnprintf(
(char *)m_OutboundContext.m_cabNativeCommand.Buffer(),
m_OutboundContext.m_cabNativeCommand.MaxLength(),
Format,
arglist);
va_end(arglist);
if (BytesWritten < 0)
return (FALSE);
m_OutboundContext.m_cabNativeCommand.SetLength(BytesWritten);
return (TRUE);
}
//---[ SMTP_CONNOUT::SetDiagnosticInfo ]---------------------------------------
//
//
// Description:
// Sets member diagnostic information that is later Ack'd back to AQueue
// Parameters:
// IN hrDiagnosticError Error code... if SUCCESS we thow away
// the rest of the information
// IN szDiagnosticVerb Constant String pointing to the SMTP
// verb that caused the failure or NULL
// if it was not a protocol failure.
// IN szDiagnosticResponse String that contains the remote
// servers response or NULL if the
// failure was not a protocol failure.
// Returns:
// -
// History:
// 2/18/99 - MikeSwa Created
// 7/12/99 - GPulla Modified
//
//-----------------------------------------------------------------------------
void SMTP_CONNOUT::SetDiagnosticInfo(IN HRESULT hrDiagnosticError,
IN LPCSTR szDiagnosticVerb,
IN LPCSTR szDiagnosticResponse)
{
m_hrDiagnosticError = hrDiagnosticError;
m_szDiagnosticVerb = szDiagnosticVerb; //this should be a constant
ZeroMemory(&m_szDiagnosticResponse, sizeof(m_szDiagnosticResponse));
//Force terminating NULL
m_szDiagnosticResponse[sizeof(m_szDiagnosticResponse)-1] = '\0';
//Not an SMTP protocol failure
if(!szDiagnosticResponse) return;
//copy buffers
strncpy(m_szDiagnosticResponse, szDiagnosticResponse,
sizeof(m_szDiagnosticResponse)-1);
}
/************************ End of File ***********************/