/*++ 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 #include "smtpcli.hxx" #include "smtpout.hxx" #include #include // // 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 '' 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 ", 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 // for ( Loop = (int) (cbRecvd - 2); Loop >= 0; Loop-- ) { // // Check if consecutive characters are // 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 ***********************/