10393 lines
334 KiB
C++
10393 lines
334 KiB
C++
/*++
|
|
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
smtpcli.cxx
|
|
|
|
Abstract:
|
|
|
|
This module defines the functions for derived class of connections
|
|
for Internet Services ( class SMTP_CONNECTION)
|
|
|
|
Author:
|
|
|
|
Rohan Phillips ( Rohanp ) 11-Dec-1995
|
|
|
|
Project:
|
|
|
|
SMTP Server DLL
|
|
|
|
Functions Exported:
|
|
|
|
SMTP_CONNECTION::~SMTP_CONNECTION()
|
|
BOOL SMTP_CONNECTION::ProcessClient( IN DWORD cbWritten,
|
|
IN DWORD dwCompletionStatus,
|
|
IN BOOL fIOCompletion)
|
|
|
|
BOOL SMTP_CONNECTION::StartupSession( VOID)
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
|
|
|
|
/************************************************************
|
|
* Include Headers
|
|
************************************************************/
|
|
|
|
#define INCL_INETSRV_INCS
|
|
#include "smtpinc.h"
|
|
//
|
|
// ATL includes
|
|
//
|
|
#define _ATL_NO_DEBUG_CRT
|
|
#define _ASSERTE _ASSERT
|
|
#define _WINDLL
|
|
#include "atlbase.h"
|
|
extern CComModule _Module;
|
|
#include "atlcom.h"
|
|
#undef _WINDLL
|
|
|
|
//
|
|
// SEO includes
|
|
//
|
|
#include "seo.h"
|
|
#include "seolib.h"
|
|
|
|
#include <memory.h>
|
|
#include <issperr.h>
|
|
#include "smtpcli.hxx"
|
|
#include "headers.hxx"
|
|
#include "timeconv.h"
|
|
#include "base64.hxx"
|
|
|
|
#include "smtpout.hxx"
|
|
#include <smtpevents.h>
|
|
#include <smtpevent.h>
|
|
#include <smtpguid.h>
|
|
|
|
extern "C"
|
|
{
|
|
#include <secint.h>
|
|
}
|
|
|
|
//
|
|
// Dispatcher implementation
|
|
//
|
|
#include "pe_dispi.hxx"
|
|
|
|
int strcasecmp(char *s1, char *s2);
|
|
int strncasecmp(char *s1, char *s2, int n);
|
|
|
|
#define IMSG_PROGID L"Exchange.IMsg"
|
|
#define MAILMSG_PROGID L"Exchange.MailMsg"
|
|
|
|
extern CHAR g_VersionString[];
|
|
|
|
// provide memory for static declared in SMTP_CONNECTION
|
|
CPool SMTP_CONNECTION::Pool(CLIENT_CONNECTION_SIGNATURE_VALID);
|
|
|
|
static char * HelpText= "214-This server supports the following commands:\r\n"
|
|
"214 HELO EHLO STARTTLS RCPT DATA RSET MAIL QUIT HELP";
|
|
|
|
static char * Daynames[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
|
|
|
static const char * SMTP_TOO_MANY_RCPTS = "Too many recipients";
|
|
static const char * PasswordParam = "Password:";
|
|
static const char * UserParam = "Username:";
|
|
|
|
static LONG g_NumRoutingThreads = 0;
|
|
|
|
LONG g_cProcessClientThreads = 0;
|
|
|
|
// Format strings for "Received:" lines
|
|
static const char szFormatReceivedFormatSuccess[] = "Received: from %s ([%s]) by %s%s with Microsoft SMTPSVC(%s);\r\n\t %s, %s\r\n";
|
|
static const char szFormatReceivedFormatUnverified[] = "Received: from %s ([%s] unverified) by %s%s with Microsoft SMTPSVC(%s);\r\n\t %s, %s\r\n";
|
|
static const char szFormatReceivedFormatFailed[] = "Received: from %s ([%s] RDNS failed) by %s%s with Microsoft SMTPSVC(%s);\r\n\t %s, %s\r\n";
|
|
|
|
// Search strings to identify our own "Received:" lines (subsections of above lines)
|
|
static const char szFormatReceivedServer[] = ") by %s";
|
|
static const char szFormatReceivedService[] = " with Microsoft SMTPSVC(";
|
|
|
|
// Amount of time (FILETIME / 100ns units) we wait if we detect a looping message
|
|
#define SMTP_LOOP_DELAY 6000000000 // == 10 minutes
|
|
|
|
#define SMTP_TIMEOUT 99
|
|
#define SMTP_CONTENT_FILE_IO_TIMEOUT 5*60*1000
|
|
|
|
#define ATQ_LOCATOR (DWORD)'Dd9D'
|
|
|
|
//ATQ Write file modes
|
|
#define BLOCKING 0
|
|
#define NONBLOCKING 1
|
|
|
|
//Trailer byte status
|
|
#define CRLF_NEEDED 0
|
|
#define CR_SEEN 1
|
|
#define CRLF_SEEN 2
|
|
#define CR_MISSING 3
|
|
|
|
#define WHITESPACE " \t\r\n"
|
|
|
|
/************************************************************
|
|
* Functions
|
|
************************************************************/
|
|
|
|
extern void GenerateMessageId (char * Buffer, DWORD BuffLen);
|
|
extern DWORD GetIncreasingMsgId();
|
|
extern VOID InternetCompletion(PVOID pvContext, DWORD cbWritten,
|
|
DWORD dwCompletionStatus, OVERLAPPED * lpo);
|
|
|
|
/*++
|
|
|
|
Name:
|
|
|
|
SMTP_CONNECTION::SMTP_CONNECTION
|
|
|
|
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_CONNECTION::SMTP_CONNECTION(
|
|
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(FALSE),
|
|
m_securityCtx(pInstance,
|
|
TCPAUTH_SERVER| TCPAUTH_UUENCODE,
|
|
((PSMTP_SERVER_INSTANCE)pInstance)->QueryAuthentication()),
|
|
CLIENT_CONNECTION ( sClient, psockAddrRemote,
|
|
psockAddrLocal, pAtqContext,
|
|
pvInitialRequest, cbInitialData )
|
|
{
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::SMTP_CONNECTION");
|
|
DebugTrace( (LPARAM)this, "New connection created");
|
|
|
|
_ASSERT(pInstance != NULL);
|
|
|
|
//
|
|
// By default, we use the smallish receive buffer inherited from
|
|
// the case CLIENT_CONNECTION object.
|
|
//
|
|
m_precvBuffer = m_recvBuffer;
|
|
m_cbMaxRecvBuffer = sizeof(m_recvBuffer);
|
|
m_pOutputBuffer = m_OutputBuffer;
|
|
m_cbMaxOutputBuffer = sizeof(m_OutputBuffer);
|
|
|
|
m_pIMsg = NULL;
|
|
m_pIMsgRecips = NULL;
|
|
m_szHeloAddr[0] = '\0';
|
|
// m_FromAddr = NULL;
|
|
m_szFromAddress[0] = '\0';
|
|
m_pszArgs = NULL;
|
|
m_pInstance = pInstance;
|
|
m_IMsgHandle = NULL;
|
|
m_pBindInterface = NULL;
|
|
m_cbRecvBufferOffset = 0;
|
|
m_pFileWriteBuffer = NULL;
|
|
m_pFileWriteBuffer = new CBuffer();
|
|
|
|
m_HopCount = 0;
|
|
m_LocalHopCount = 0;
|
|
|
|
if(!m_pFileWriteBuffer)
|
|
{
|
|
ErrorTrace((LPARAM)this, "Failed to get the write buffer Err : %d",
|
|
GetLastError());
|
|
_ASSERT(0);
|
|
}
|
|
m_AtqLocator = ATQ_LOCATOR;
|
|
|
|
|
|
// inbound protocol extension related initializations
|
|
m_fAsyncEventCompletion = FALSE;
|
|
m_fAsyncEOD = FALSE;
|
|
m_fIsPeUnderway = FALSE;
|
|
m_pIEventRouter = NULL;
|
|
m_pCInboundDispatcher = NULL;
|
|
// m_CInboundContext is a data member
|
|
m_fPeBlobReady = FALSE;
|
|
m_pPeBlobCallback = NULL;
|
|
|
|
ZeroMemory(&m_FileOverLapped, sizeof(m_FileOverLapped));
|
|
|
|
m_FileOverLapped.SeoOverlapped.Overlapped.pfnCompletion = SmtpCompletionFIO;
|
|
m_FileOverLapped.SeoOverlapped.ThisPtr = this;
|
|
|
|
m_LineCompletionState = SEEN_NOTHING;
|
|
m_Truncate = FALSE;
|
|
m_BufrWasFull = FALSE;
|
|
m_szUsedAuthKeyword[0] = '\0';
|
|
m_szAuthenticatedUserNameFromSink[0] = '\0';
|
|
|
|
//keep track of per instance connection object
|
|
pInstance->IncConnInObjs();
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
}
|
|
|
|
SMTP_CONNECTION::~SMTP_CONNECTION (void)
|
|
{
|
|
PSMTP_SERVER_INSTANCE pInstance = NULL;
|
|
CAddr * pAddress = NULL;
|
|
char *pTempBuffer = NULL;
|
|
// CBuffer * pBuff;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "~SMTP_CONNECTION");
|
|
|
|
ReleasImsg(TRUE);
|
|
|
|
/*
|
|
pAddress = (CAddr *) InterlockedExchangePointer((PVOID *) &m_FromAddr, (PVOID) NULL);
|
|
if(pAddress != NULL)
|
|
{
|
|
delete pAddress;
|
|
}*/
|
|
|
|
pInstance = (PSMTP_SERVER_INSTANCE ) InterlockedExchangePointer((PVOID *) &m_pInstance, (PVOID) NULL);
|
|
if(pInstance != NULL)
|
|
{
|
|
pInstance->DecrementCurrentConnections();
|
|
pInstance->DecConnInObjs();
|
|
pInstance->Dereference();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if(m_pFileWriteBuffer)
|
|
{
|
|
delete m_pFileWriteBuffer;
|
|
}
|
|
|
|
if ( NULL != m_pPeBlobCallback) {
|
|
m_pPeBlobCallback->Release();
|
|
}
|
|
|
|
if(m_pCInboundDispatcher)
|
|
m_pCInboundDispatcher->Release();
|
|
|
|
if(m_pProviderPackagesInfo)
|
|
m_pProviderPackagesInfo->Release();
|
|
|
|
DebugTrace( (LPARAM)this, "Connection deleted");
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::InitializeObject
|
|
|
|
Description:
|
|
Initializes all member variables and pre-allocates
|
|
a mail context class
|
|
|
|
Arguments:
|
|
|
|
|
|
Returns:
|
|
|
|
TRUE if memory can be allocated.
|
|
FALSE if no memory can be allocated
|
|
--*/
|
|
BOOL SMTP_CONNECTION::InitializeObject (BOOL IsSecureConnection)
|
|
{
|
|
BOOL fRet = TRUE;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::InitializeObject");
|
|
|
|
_ASSERT(m_pInstance != NULL);
|
|
|
|
StartProcessingTimer();
|
|
|
|
m_cbReceived = 0;
|
|
m_cbParsable = 0;
|
|
m_cbTempBDATLen = 0;
|
|
m_OutputBufferSize = 0;
|
|
m_cbCurrentWriteBuffer = 0;
|
|
m_TotalMsgSize = 0;
|
|
m_SessionSize = 0;
|
|
m_cbDotStrippedTotalMsgSize = 0;
|
|
m_ProtocolErrors = 0;
|
|
m_dwUnsuccessfulLogons = 0;
|
|
m_HeaderSize = 0;
|
|
m_cPendingIoCount = 0;
|
|
m_cActiveThreads = 0;
|
|
m_CurrentOffset = 0;
|
|
m_HelloSent = FALSE;
|
|
m_RecvdMailCmd = FALSE;
|
|
m_RecvdAuthCmd = FALSE;
|
|
m_Nooped = FALSE;
|
|
m_TimeToRewriteHeader = TRUE;
|
|
m_RecvdRcptCmd = FALSE;
|
|
m_InHeader = TRUE;
|
|
m_LongBodyLine = FALSE;
|
|
m_fFoundEmbeddedCrlfDotCrlf = FALSE;
|
|
m_fScannedForCrlfDotCrlf = FALSE;
|
|
m_fSeenRFC822FromAddress = FALSE;
|
|
m_fSeenRFC822ToAddress = FALSE;
|
|
m_fSeenRFC822CcAddress = FALSE;
|
|
m_fSeenRFC822BccAddress = FALSE;
|
|
m_fSeenRFC822Subject = FALSE;
|
|
m_fSeenRFC822MsgId = FALSE;
|
|
m_fSeenRFC822SenderAddress = FALSE;
|
|
m_fSeenXPriority = FALSE;
|
|
m_fSeenXOriginalArrivalTime = FALSE;
|
|
m_fSeenContentType = FALSE;
|
|
m_fSetContentType = FALSE;
|
|
m_HeaderFlags = 0;
|
|
m_MailBodyError = NO_ERROR;
|
|
m_State = HELO;
|
|
m_NeedToQuit = FALSE;
|
|
m_pSmtpStats = NULL;
|
|
m_WritingData = FALSE;
|
|
//m_fReverseDnsFailed = FALSE;
|
|
m_DNSLookupRetCode = SUCCESS;
|
|
m_fUseMbsCta = FALSE;
|
|
m_fAuthenticated = FALSE;
|
|
m_fMayRelay = FALSE;
|
|
m_fClearText = FALSE;
|
|
m_fDidUserCmd = FALSE;
|
|
m_fAuthAnon = FALSE;
|
|
m_SecurePort = IsSecureConnection;
|
|
m_fNegotiatingSSL = m_SecurePort;
|
|
m_szUsedAuthKeyword[0] = '\0';
|
|
m_cbRecvBufferOffset = 0;
|
|
m_fPeBlobReady = FALSE;
|
|
if ( NULL != m_pPeBlobCallback) {
|
|
m_pPeBlobCallback->Release();
|
|
}
|
|
m_pPeBlobCallback = NULL;
|
|
|
|
m_fIsLastChunk = FALSE;
|
|
m_fIsBinaryMime = FALSE;
|
|
m_fIsChunkComplete = FALSE;
|
|
m_dwTrailerStatus = CRLF_SEEN;
|
|
m_nChunkSize = 0;
|
|
m_nBytesRemainingInChunk = 0;
|
|
m_MailBodyDiagnostic = ERR_NONE;
|
|
|
|
m_LineCompletionState = SEEN_NOTHING;
|
|
m_Truncate = FALSE;
|
|
m_BufrWasFull = FALSE;
|
|
m_fBufferFullInBDAT = FALSE;
|
|
m_fRecvBufferFull = FALSE;
|
|
|
|
m_pInstance->LockGenCrit();
|
|
|
|
if(m_pInstance->QueryAuthentication() & INET_INFO_AUTH_ANONYMOUS)
|
|
{
|
|
m_fAuthAnon = TRUE;
|
|
}
|
|
|
|
// Advertise this property to sinks (EXPS)
|
|
HrSetISessionProperty(ISESSION_PID_ANONYMOUS_AUTH, m_fAuthAnon);
|
|
|
|
//
|
|
// Copy IP Address for instance and remote into session
|
|
//
|
|
CopyIPAddressesToSession();
|
|
|
|
//
|
|
// Initialize Security Context
|
|
//
|
|
|
|
m_pProviderPackagesInfo = m_pInstance->GetAddRefdProviderPackagesInfo();
|
|
if(!m_pProviderPackagesInfo)
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
if (!m_securityCtx.SetInstanceAuthPackageNames(
|
|
m_pProviderPackagesInfo->GetProviderPackagesCount(),
|
|
m_pProviderPackagesInfo->GetProviderNames(),
|
|
m_pProviderPackagesInfo->GetProviderPackages()))
|
|
{
|
|
ErrorTrace((LPARAM)this, "SetInstanceAuthPackageNames FAILED <Err=%u>",
|
|
GetLastError());
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// 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 "".
|
|
//
|
|
|
|
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;
|
|
}
|
|
|
|
Exit:
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fRet;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::CreateSmtpConnection
|
|
|
|
Description:
|
|
This is the static member function than is the only
|
|
entity that is allowed to create an SMTP_CONNECTION
|
|
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.
|
|
|
|
|
|
Returns:
|
|
|
|
A pointer to an SMTP_CONNECTION class or NULL
|
|
--*/
|
|
SMTP_CONNECTION * SMTP_CONNECTION::CreateSmtpConnection (IN PCLIENT_CONN_PARAMS ClientParams,
|
|
IN PSMTP_SERVER_INSTANCE pInst)
|
|
{
|
|
SMTP_CONNECTION * pSmtpObj = NULL;
|
|
PIIS_ENDPOINT pTmpEndPoint = NULL;
|
|
|
|
pSmtpObj = new SMTP_CONNECTION (pInst, ClientParams->sClient, (const SOCKADDR_IN *) ClientParams->pAddrRemote, (const SOCKADDR_IN *) ClientParams->pAddrLocal,
|
|
ClientParams->pAtqContext, ClientParams->pvInitialBuff, ClientParams->cbInitialBuff);
|
|
|
|
if(pSmtpObj == NULL)
|
|
{
|
|
SetLastError (ERROR_NOT_ENOUGH_MEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
pTmpEndPoint = (PIIS_ENDPOINT)ClientParams->pEndpoint;
|
|
|
|
if(!pSmtpObj->InitializeObject(FALSE))
|
|
{
|
|
delete pSmtpObj;
|
|
return NULL;
|
|
}
|
|
|
|
return pSmtpObj;
|
|
}
|
|
|
|
|
|
#define MAX_LOG_ERROR_LEN (500)
|
|
|
|
void SMTP_CONNECTION::TransactionLog(
|
|
const char *pszOperation,
|
|
const char *pszParameters,
|
|
const char *pszTarget,
|
|
DWORD dwWin32Error,
|
|
DWORD dwServiceSpecificStatus
|
|
)
|
|
{
|
|
INETLOG_INFORMATION translog;
|
|
DWORD dwLog;
|
|
DWORD cchError = MAX_LOG_ERROR_LEN;
|
|
char VersionString[] = "SMTP";
|
|
char szParametersBuffer[1024] = ""; //Data portion of buffer information
|
|
|
|
ZeroMemory(&translog, sizeof(translog));
|
|
|
|
translog.pszVersion = VersionString;
|
|
translog.pszClientHostName = (char *) QueryClientHostName();
|
|
translog.cbClientHostName = lstrlen(translog.pszClientHostName);
|
|
translog.pszClientUserName = (char *) QueryClientUserName();
|
|
translog.pszServerAddress = (char *) QueryLocalHostName();
|
|
|
|
translog.pszOperation = (char *)pszOperation;
|
|
translog.cbOperation = lstrlen ((char *)pszOperation);
|
|
translog.pszTarget = (char *)pszTarget;
|
|
translog.cbTarget = lstrlen ((char *)pszTarget);
|
|
translog.pszParameters = (char *)pszParameters;
|
|
|
|
if (pszParameters) {
|
|
lstrcpyn(szParametersBuffer, pszParameters, sizeof(szParametersBuffer)-sizeof(CHAR));
|
|
translog.pszParameters = szParametersBuffer;
|
|
} else {
|
|
translog.pszParameters = "";
|
|
}
|
|
|
|
translog.dwBytesSent = m_dwCmdBytesSent;
|
|
translog.dwBytesRecvd = m_dwCmdBytesRecv;
|
|
translog.dwWin32Status = dwWin32Error;
|
|
|
|
translog.dwProtocolStatus = dwServiceSpecificStatus;
|
|
translog.msTimeForProcessing = QueryProcessingTime();
|
|
|
|
if(QuerySmtpInstance() != NULL)
|
|
dwLog = QuerySmtpInstance()->m_Logging.LogInformation( &translog);
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::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 if the string was sent. False otherwise
|
|
--*/
|
|
BOOL SMTP_CONNECTION::SendSmtpResponse(BOOL SyncSend)
|
|
{
|
|
BOOL RetStatus = TRUE;
|
|
DWORD cbMessage = m_OutputBufferSize;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::SendSmtpResponse");
|
|
|
|
//IncPendingIoCount();
|
|
|
|
//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 && !m_fNegotiatingSSL)
|
|
{
|
|
char *Buffer = &(m_pOutputBuffer[m_encryptCtx.GetSealHeaderSize()]);
|
|
|
|
RetStatus = m_encryptCtx.SealMessage(
|
|
(UCHAR *) Buffer,
|
|
m_OutputBufferSize,
|
|
(UCHAR *) m_pOutputBuffer,
|
|
&cbMessage);
|
|
}
|
|
|
|
if (RetStatus) {
|
|
RetStatus = CLIENT_CONNECTION::WriteFile(m_pOutputBuffer, cbMessage);
|
|
}
|
|
if(RetStatus)
|
|
{
|
|
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesSentTotal, cbMessage);
|
|
AddCommandBytesSent(cbMessage);
|
|
}
|
|
else
|
|
{
|
|
DebugTrace((LPARAM) this, "WriteFile failed with error %d", GetLastError());
|
|
}
|
|
|
|
m_OutputBufferSize = 0;
|
|
}
|
|
|
|
//DecPendingIoCount();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return RetStatus;
|
|
}
|
|
|
|
|
|
BOOL SMTP_CONNECTION::WriteMailFile(char * Buffer, DWORD BuffSize, BOOL *lpfWritePended)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::WriteMailFileBuffer");
|
|
|
|
if(!BuffSize && !Buffer)
|
|
{
|
|
//Simply flush the buffers if we have something to flush
|
|
if(!m_cbCurrentWriteBuffer)
|
|
{//Aynsc WRITE succeeded. Let this thread go thru - atq will call back
|
|
*lpfWritePended = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
if(!WriteMailFileAtq((char*)m_pFileWriteBuffer->GetData(), m_cbCurrentWriteBuffer, NONBLOCKING))
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
ErrorTrace((LPARAM) this, "Async Write of to mail file failed : %d",m_MailBodyError);
|
|
fResult = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//Aynsc WRITE succeeded. Let this thread go thru - atq will call back
|
|
*lpfWritePended = TRUE;
|
|
fResult = TRUE;
|
|
}
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fResult;
|
|
}
|
|
|
|
//If the data to be written can fit in we take it in and come back
|
|
//else we pend a write
|
|
//Though the var name is a result of its original use, we now use this
|
|
//buffer as a WRITEFILE buffer
|
|
if(m_cbCurrentWriteBuffer + BuffSize > SMTP_WRITE_BUFFER_SIZE)
|
|
{
|
|
//Update the input buffers so that we can
|
|
//go back and start processing the input buffers
|
|
//after the async WRITE completes
|
|
//MoveMemory ((void *)QueryMRcvBuffer(), Buffer, m_cbReceived);
|
|
|
|
if(!WriteMailFileAtq((char*)m_pFileWriteBuffer->GetData(), m_cbCurrentWriteBuffer, NONBLOCKING))
|
|
{
|
|
*lpfWritePended = FALSE;
|
|
m_MailBodyError = GetLastError();
|
|
ErrorTrace((LPARAM) this, "Async Write of to mail file failed : %d",m_MailBodyError);
|
|
fResult = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//Aynsc WRITE succeeded. Let this thread go thru - atq will call back
|
|
*lpfWritePended = TRUE;
|
|
fResult = TRUE;
|
|
}
|
|
}
|
|
//Copy the data into the buffer only if we have the space to do so
|
|
else
|
|
{
|
|
CopyMemory((m_pFileWriteBuffer->GetData() + m_cbCurrentWriteBuffer),Buffer,BuffSize);
|
|
m_cbCurrentWriteBuffer += BuffSize;
|
|
fResult = TRUE;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fResult;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name:
|
|
SMTP_CONNECTION::WriteMailFileAtq
|
|
|
|
Description:
|
|
While chunking, once header parsing is done the data is
|
|
dumped to the disk asynchronously using WriteMailFileAtq( )
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Returns:
|
|
TRUE if successfully pended a write to the file, FALSE otherwise
|
|
|
|
--*/
|
|
|
|
BOOL SMTP_CONNECTION::WriteMailFileAtq(char * InputLine, DWORD dwBytesToWrite, DWORD dwIoMode)
|
|
{
|
|
TraceFunctEnterEx( (LPARAM)this, "SMTP_CONNECTION::WriteMailFileAtq");
|
|
|
|
DebugTrace( (LPARAM)this, "WriteFile 0x%08X, len: %d, LPO: 0x%08X",
|
|
InputLine,
|
|
dwBytesToWrite,
|
|
m_FileOverLapped.SeoOverlapped );
|
|
|
|
//ZeroMemory( (void*)&m_FileOverLapped, sizeof(OVERLAPPED) );
|
|
//if(dwIoMode == BLOCKING)
|
|
// m_FileOverLapped.SeoOverlapped.Overlapped.hEvent = (HANDLE)1;
|
|
|
|
m_FileOverLapped.SeoOverlapped.Overlapped.Offset = m_CurrentOffset;
|
|
|
|
m_FileOverLapped.m_LastIoState = WRITEFILEIO;
|
|
m_FileOverLapped.m_cbIoSize = dwBytesToWrite;
|
|
m_FileOverLapped.m_pIoBuffer = (LPBYTE)InputLine;
|
|
|
|
//
|
|
// increment the overall pending io count for this session
|
|
//
|
|
IncPendingIoCount();
|
|
|
|
_ASSERT(m_FileOverLapped.SeoOverlapped.Overlapped.pfnCompletion != NULL);
|
|
m_FileOverLapped.SeoOverlapped.ThisPtr = this;
|
|
|
|
if (FIOWriteFile(m_IMsgHandle,
|
|
InputLine,
|
|
dwBytesToWrite,
|
|
&(m_FileOverLapped.SeoOverlapped.Overlapped)) == FALSE)
|
|
#if 0
|
|
if ( AtqWriteFile(m_pAtqFileContext,
|
|
InputLine,
|
|
dwBytesToWrite,
|
|
(OVERLAPPED *) &(m_FileOverLapped.SeoOverlapped.Overlapped) ) == FALSE)
|
|
#endif
|
|
{
|
|
DecPendingIoCount();
|
|
ErrorTrace( (LPARAM)this, "AtqWriteFile failed.");
|
|
}
|
|
else
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
ErrorTrace( (LPARAM)this, "WriteMailFileAtq failed. err: %d", GetLastError() );
|
|
|
|
if(!HandleInternalError(GetLastError()))
|
|
DisconnectClient();
|
|
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name:
|
|
SMTP_CONNECTION::HandleInternalError
|
|
|
|
Description:
|
|
Contains code to handle common error conditions during inbound message flow.
|
|
|
|
Arguments:
|
|
[IN] DWORD Error code
|
|
|
|
Returns:
|
|
TRUE if error was handled
|
|
FALSE otherwise
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::HandleInternalError(DWORD dwErr)
|
|
{
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::HandleInternalError");
|
|
if(dwErr == ERROR_DISK_FULL)
|
|
{
|
|
FormatSmtpMessage(SMTP_RESP_NORESOURCES, ENO_RESOURCES, " Unable to accept message because the server is out of disk space.\r\n");
|
|
ErrorTrace((LPARAM) this, "Rejecting message: Out of disk space");
|
|
SendSmtpResponse();
|
|
DisconnectClient();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#if 0
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::FreeAtqFileContext
|
|
|
|
Description :
|
|
Frees AtqContext associated with message file used for doing async writes
|
|
in case of chunking.
|
|
|
|
Arguments :
|
|
None. Operates on m_pAtqFileContext
|
|
|
|
Returns :
|
|
Nothing
|
|
|
|
--*/
|
|
|
|
void SMTP_CONNECTION::FreeAtqFileContext( void )
|
|
{
|
|
PFIO_CONTEXT pFIO;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::FreeAtqFileContext");
|
|
|
|
pFIO = (PATQ_CONTEXT)InterlockedExchangePointer( (PVOID *)&m_pIMsgHandle, NULL );
|
|
if ( pFIO != NULL )
|
|
{
|
|
DebugTrace((LPARAM) this, "Freeing AtqFileContext!");
|
|
ReleaseContext(pFIO);
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
}
|
|
#endif
|
|
|
|
void SMTP_CONNECTION::ReInitClassVariables(void)
|
|
{
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
|
|
//reset all variables to their initial state
|
|
m_HeaderFlags = 0;
|
|
m_TotalMsgSize = 0;
|
|
m_cbDotStrippedTotalMsgSize = 0;
|
|
m_CurrentOffset = 0;
|
|
m_State = HELO;
|
|
m_RecvdMailCmd = FALSE;
|
|
m_InHeader = TRUE;
|
|
m_LongBodyLine = FALSE;
|
|
m_fFoundEmbeddedCrlfDotCrlf = FALSE;
|
|
m_fScannedForCrlfDotCrlf = FALSE;
|
|
m_fSeenRFC822FromAddress = FALSE;
|
|
m_fSeenRFC822ToAddress = FALSE;
|
|
m_fSeenRFC822CcAddress = FALSE;
|
|
m_fSeenRFC822BccAddress = FALSE;
|
|
m_fSeenRFC822Subject = FALSE;
|
|
m_fSeenRFC822MsgId = FALSE;
|
|
m_fSeenXPriority = FALSE;
|
|
m_fSeenXOriginalArrivalTime = FALSE;
|
|
m_fSeenContentType = FALSE;
|
|
m_fSetContentType = FALSE;
|
|
m_fSeenRFC822SenderAddress = FALSE;
|
|
m_TimeToRewriteHeader = TRUE;
|
|
m_MailBodyError = NO_ERROR;
|
|
m_RecvdRcptCmd = FALSE;
|
|
m_WritingData = FALSE;
|
|
m_fIsLastChunk = FALSE;
|
|
m_fIsBinaryMime = FALSE;
|
|
m_fIsChunkComplete = FALSE;
|
|
m_dwTrailerStatus = CRLF_SEEN;
|
|
m_nChunkSize = 0;
|
|
m_nBytesRemainingInChunk = 0;
|
|
m_MailBodyDiagnostic = ERR_NONE;
|
|
m_cbCurrentWriteBuffer = 0;
|
|
m_cbRecvBufferOffset = 0;
|
|
m_fAsyncEOD = FALSE;
|
|
|
|
m_LineCompletionState = SEEN_NOTHING;
|
|
m_Truncate = FALSE;
|
|
m_BufrWasFull = FALSE;
|
|
m_fBufferFullInBDAT = FALSE;
|
|
|
|
//Clear the senders address if it's
|
|
//still there.
|
|
|
|
m_szFromAddress[0] = '\0';
|
|
|
|
ReleasImsg(TRUE);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::SmtpGetCommand
|
|
|
|
Description:
|
|
This function determines which SMTP input command was sent
|
|
by the client
|
|
|
|
Arguments:
|
|
Request - Buffer the client sent
|
|
RequestLen - Length of the buffer
|
|
|
|
Returns:
|
|
|
|
Index into our array of function pointers
|
|
--*/
|
|
int SMTP_CONNECTION::SmtpGetCommand(const char * Request, DWORD RequestLen, LPDWORD CmdSize)
|
|
{
|
|
DWORD Loop = 0;
|
|
char Cmd[SMTP_MAX_COMMANDLINE_LEN];
|
|
char * ptr = NULL;
|
|
char *psearch = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::SmtpGetCommand");
|
|
|
|
//start out with no errors
|
|
CommandErrorType = SMTP_COMMAND_NOERROR;
|
|
|
|
//we will copy the command into Cmd starting from
|
|
//the beginning
|
|
ptr = Cmd;
|
|
|
|
//start looking for the space from the beginning
|
|
//of the buffer
|
|
psearch = (char *) Request;
|
|
|
|
//search for the command and copy it into Cmd. We stop
|
|
//looking when we encounter the first space.
|
|
while (*psearch != '\0' && !isspace ((UCHAR) *psearch) && ptr < &Cmd[SMTP_MAX_COMMANDLINE_LEN - 2])
|
|
*ptr++ = *psearch++;
|
|
|
|
//null terminate the buffer
|
|
*ptr = '\0';
|
|
|
|
//now, look through the list of SMTP commands
|
|
//and compare it to what the client gave us.
|
|
while (SmtpCommands[Loop] != NULL)
|
|
{
|
|
if (!::strcasecmp((char *) Cmd, (char * )SmtpCommands[Loop]))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Loop++;
|
|
}
|
|
|
|
//set error to COMMAND_NOT FOUND if we do not recognize the command
|
|
CommandErrorType = SMTP_COMMAND_NOT_FOUND;
|
|
|
|
Exit:
|
|
|
|
//If no error, send back the index in the array
|
|
//If there was an error, send back the index of
|
|
//the last element in the array. This is our
|
|
//catch all error function.
|
|
if (CommandErrorType == SMTP_COMMAND_NOERROR)
|
|
{
|
|
*CmdSize = DWORD (ptr - Cmd);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return Loop;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is not always an error since a protocol sink may be installed
|
|
//
|
|
DebugTrace((LPARAM) this, "Native command %s was not found in command table", Cmd);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return (DWORD) LAST_SMTP_STATE;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::FormatSmtpMessage( IN const char * Format, ...)
|
|
|
|
Description:
|
|
This function will build a sting in the output buffer with
|
|
the given input parameters. If the new data cannot fit in the
|
|
buffer, this function will send whatever data is in the buffer,
|
|
then build the string.
|
|
|
|
Arguments:
|
|
String to send
|
|
|
|
Returns:
|
|
|
|
TRUE if response was queued
|
|
--*/
|
|
BOOL SMTP_CONNECTION::FormatSmtpMessage(DWORD dwCode, const char * szEnhancedCodes, 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);
|
|
char RealFormat[MAX_PATH];
|
|
RealFormat[0] = '\0';
|
|
|
|
//If we get passed dwCode we use it
|
|
//If we get passed enhance status code only if we advertise them
|
|
//
|
|
if(dwCode)
|
|
{
|
|
if(m_pInstance->AllowEnhancedCodes() && szEnhancedCodes)
|
|
{
|
|
sprintf(RealFormat,"%d %s",dwCode,szEnhancedCodes);
|
|
}
|
|
else
|
|
sprintf(RealFormat,"%d",dwCode);
|
|
}
|
|
strcat(RealFormat,Format);
|
|
|
|
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, (const char *)RealFormat, 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);
|
|
|
|
m_OutputBufferSize += (DWORD) BytesWritten;
|
|
|
|
//m_OutputBufferSize += vsprintf (&m_pOutputBuffer[m_OutputBufferSize], Format, arglist);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::FormatSmtpMessage(unsigned char *DataBuffer, DWORD dwBytes)
|
|
{
|
|
int BytesWritten = 0;
|
|
char *Buffer;
|
|
DWORD AvailableBytes = 0;
|
|
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.
|
|
if( dwBytes + 2 < AvailableBytes)//+2 for CRLF
|
|
{
|
|
CopyMemory(Buffer, DataBuffer, dwBytes);
|
|
Buffer[dwBytes] = '\r';
|
|
Buffer[dwBytes + 1] = '\n';
|
|
BytesWritten = dwBytes + 2;
|
|
|
|
}
|
|
else
|
|
{
|
|
//flush any pending response
|
|
|
|
SendSmtpResponse();
|
|
_ASSERT (m_OutputBufferSize == 0);
|
|
Buffer = &m_pOutputBuffer[HeaderOffset];
|
|
AvailableBytes = max(dwBytes, m_cbMaxOutputBuffer - SealOverhead - 2);
|
|
CopyMemory(Buffer, DataBuffer, AvailableBytes);
|
|
Buffer[dwBytes] = '\r';
|
|
Buffer[dwBytes + 1] = '\n';
|
|
BytesWritten = dwBytes + 2;
|
|
_ASSERT (BytesWritten > 0);
|
|
}
|
|
|
|
m_OutputBufferSize += (DWORD) BytesWritten;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::PE_FastFormatSmtpMessage(LPSTR pszBuffer, DWORD dwBytes)
|
|
{
|
|
LPSTR Buffer;
|
|
DWORD BytesWritten = 0;
|
|
DWORD AvailableBytes = 0;
|
|
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;
|
|
|
|
// Write as many buffers as we need to
|
|
while (BytesWritten < dwBytes)
|
|
{
|
|
if ((dwBytes - BytesWritten) <= AvailableBytes)
|
|
{
|
|
CopyMemory(Buffer, pszBuffer, dwBytes - BytesWritten);
|
|
m_OutputBufferSize += (dwBytes - BytesWritten);
|
|
BytesWritten = dwBytes; // BytesWritten += (dwBytes - BytesWritten)
|
|
}
|
|
else
|
|
{
|
|
// We don't have enough buffer space, so write whatever we have left
|
|
CopyMemory(Buffer, pszBuffer, AvailableBytes);
|
|
BytesWritten += AvailableBytes;
|
|
m_OutputBufferSize += AvailableBytes;
|
|
pszBuffer += AvailableBytes;
|
|
|
|
//flush any pending response
|
|
SendSmtpResponse();
|
|
_ASSERT (m_OutputBufferSize == 0);
|
|
|
|
Buffer = &m_pOutputBuffer[HeaderOffset];
|
|
AvailableBytes = m_cbMaxOutputBuffer - SealOverhead;
|
|
}
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoesClientHaveIpAccess()
|
|
{
|
|
AC_RESULT acIpAccess;
|
|
ADDRESS_CHECK acAccessCheck;
|
|
METADATA_REF_HANDLER rfAccessCheck;
|
|
BOOL fNeedDnsCheck = FALSE;
|
|
BOOL fRet = TRUE;
|
|
struct hostent* pH = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::DoesClientHaveIpAccess");
|
|
|
|
m_pInstance->LockGenCrit();
|
|
|
|
acAccessCheck.BindAddr( (PSOCKADDR)&m_saClient );
|
|
|
|
if ( !rfAccessCheck.CopyFrom( m_pInstance->QueryRelayMetaDataRefHandler() ) )
|
|
{
|
|
m_pInstance->UnLockGenCrit();
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return FALSE;
|
|
}
|
|
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
acAccessCheck.BindCheckList( (LPBYTE)rfAccessCheck.GetPtr(), rfAccessCheck.GetSize() );
|
|
|
|
acIpAccess = acAccessCheck.CheckIpAccess( &fNeedDnsCheck);
|
|
if ( (acIpAccess == AC_IN_DENY_LIST) ||
|
|
((acIpAccess == AC_NOT_IN_GRANT_LIST) && !fNeedDnsCheck) )
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
else if (fNeedDnsCheck)
|
|
{
|
|
pH = gethostbyaddr( (char*)(&((PSOCKADDR_IN)(&m_saClient))->sin_addr),
|
|
4, PF_INET );
|
|
if(pH != NULL)
|
|
{
|
|
acIpAccess = acAccessCheck.CheckName(pH->h_name);
|
|
}
|
|
else
|
|
{
|
|
acIpAccess = AC_IN_DENY_LIST;
|
|
}
|
|
}
|
|
|
|
if ( (acIpAccess == AC_IN_DENY_LIST) ||
|
|
(acIpAccess == AC_NOT_IN_GRANT_LIST))
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
|
|
acAccessCheck.UnbindCheckList();
|
|
rfAccessCheck.Reset( (IMDCOM*) g_pInetSvc->QueryMDObject() );
|
|
|
|
if(!fRet)
|
|
{
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::ProcessReadIO
|
|
|
|
Description:
|
|
This function gets a buffer from ATQ, parses the buffer to
|
|
find out what command the client sent, then executes that
|
|
cammnd.
|
|
|
|
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_CONNECTION::ProcessReadIO(IN DWORD InputBufferLen,
|
|
IN DWORD dwCompletionStatus,
|
|
IN OUT OVERLAPPED * lpo)
|
|
{
|
|
BOOL fReturn = TRUE;
|
|
const char * InputLine;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::ProcessReadIO");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
m_cbReceived += InputBufferLen;
|
|
|
|
// Firewall against bugs where we overflow the read buffer
|
|
if(m_cbReceived > QueryMaxReadSize())
|
|
{
|
|
DisconnectClient();
|
|
_ASSERT(0 && "Buffer overflow");
|
|
return FALSE;
|
|
}
|
|
|
|
InputLine = QueryMRcvBuffer();
|
|
|
|
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesRcvdTotal, InputBufferLen);
|
|
|
|
if (!m_fNegotiatingSSL)
|
|
{
|
|
|
|
if (m_SecurePort)
|
|
{
|
|
fReturn = DecryptInputBuffer();
|
|
}
|
|
else
|
|
{
|
|
m_cbParsable = m_cbReceived;
|
|
m_SessionSize += InputBufferLen;
|
|
}
|
|
|
|
if (fReturn)
|
|
{
|
|
fReturn = ProcessInputBuffer();
|
|
}
|
|
|
|
}
|
|
else // negotiating SSL
|
|
{
|
|
//
|
|
// If we are still doing the handshake let CEncryptCtx::Converse handle it.
|
|
// Converse returns in the following situations:
|
|
//
|
|
// o The negotiation succeeded. cbExtra returns the number of bytes that
|
|
// were not consumed by the SSL handshake. These bytes are "application
|
|
// data" which should be decrypted and processed by the SMTP protocol.
|
|
//
|
|
// o The negotiation failed. The connection will be dropped.
|
|
//
|
|
// o More data is needed from the client to complete the negotiation. A
|
|
// read should be posted.
|
|
//
|
|
|
|
DWORD dw;
|
|
BOOL fMore;
|
|
DWORD dwBytes = MAX_SSL_FRAGMENT_SIZE;
|
|
BOOL fApplicationDataAvailable = FALSE;
|
|
DWORD cbExtra = 0;
|
|
|
|
dw = m_encryptCtx.Converse( (LPVOID) InputLine,
|
|
m_cbReceived,
|
|
(PUCHAR) m_pOutputBuffer,
|
|
&dwBytes,
|
|
&fMore,
|
|
(LPSTR) QueryLocalHostName(),
|
|
(LPSTR) QueryLocalPortName(),
|
|
(LPVOID) QuerySmtpInstance(),
|
|
QuerySmtpInstance()->QueryInstanceId(),
|
|
&cbExtra
|
|
);
|
|
|
|
if ( dw == NO_ERROR )
|
|
{
|
|
//
|
|
// reset the read buffer
|
|
//
|
|
if ( cbExtra )
|
|
{
|
|
fApplicationDataAvailable = TRUE;
|
|
MoveMemory( (PVOID)InputLine, InputLine + (m_cbReceived - cbExtra), cbExtra );
|
|
}
|
|
m_cbReceived = cbExtra;
|
|
|
|
//
|
|
// send any bytes required for the client
|
|
//
|
|
if ( dwBytes != 0 )
|
|
{
|
|
WriteFile( m_pOutputBuffer, dwBytes );
|
|
}
|
|
|
|
if ( fMore )
|
|
{
|
|
//
|
|
// more handshaking required - repost the read
|
|
//
|
|
_ASSERT( dwBytes != 0 );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// completed negotiation. Turn off the flag indicating thats
|
|
// what we are doing
|
|
//
|
|
m_fNegotiatingSSL = FALSE;
|
|
}
|
|
}
|
|
else if ( dw == SEC_E_INCOMPLETE_MESSAGE )
|
|
{
|
|
//
|
|
// haven't received the full packet from the client
|
|
//
|
|
_ASSERT( dwBytes == 0 );
|
|
}
|
|
else
|
|
{
|
|
ErrorTrace((LPARAM)this, "SSL handshake failed, Error = %d", dw);
|
|
DisconnectClient( dw );
|
|
fReturn = FALSE;
|
|
}
|
|
|
|
if ( fApplicationDataAvailable )
|
|
{
|
|
//
|
|
// Application data is already available, no need to post a read
|
|
//
|
|
fReturn = DecryptInputBuffer();
|
|
|
|
if ( fReturn )
|
|
fReturn = ProcessInputBuffer();
|
|
}
|
|
else if (fReturn)
|
|
{
|
|
//
|
|
// If we are continuing, we need to post a read
|
|
//
|
|
_ASSERT (m_cbReceived < QueryMaxReadSize() );
|
|
|
|
IncPendingIoCount();
|
|
|
|
m_LastClientIo = READIO;
|
|
fReturn = ReadFile( QueryMRcvBuffer() + m_cbReceived,
|
|
QueryMaxReadSize() - m_cbReceived );
|
|
|
|
if (!fReturn)
|
|
{
|
|
DisconnectClient();
|
|
DecPendingIoCount();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_CInboundContext.SetWin32Status(dw);
|
|
ProtocolLog(STARTTLS, (char *) QueryClientUserName(), dw, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
}
|
|
|
|
}
|
|
|
|
return( fReturn );
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::ProcessFileWrite
|
|
|
|
Description:
|
|
Handles completion of an async Write issued against a message file by
|
|
WriteMailFileAtq
|
|
|
|
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_CONNECTION::ProcessFileWrite(
|
|
IN DWORD BytesWritten,
|
|
IN DWORD dwCompletionStatus,
|
|
IN OUT OVERLAPPED *lpo
|
|
)
|
|
{
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::ProcessFileWrite");
|
|
SMTPCLI_FILE_OVERLAPPED * lpFileOverlapped;
|
|
|
|
_ASSERT(lpo);
|
|
lpFileOverlapped = (SMTPCLI_FILE_OVERLAPPED*)lpo;
|
|
|
|
|
|
// In case of async Write to disk file, a successsful completion
|
|
// will always mean all the data has been written out
|
|
// Check for partial completions or errors
|
|
// and Disconnect if something failed
|
|
if( BytesWritten != lpFileOverlapped->m_cbIoSize || dwCompletionStatus != NO_ERROR )
|
|
{
|
|
ErrorTrace( (LPARAM)this,
|
|
"Message WriteFile error: %d, bytes %d, expected %d",
|
|
dwCompletionStatus,
|
|
BytesWritten,
|
|
lpFileOverlapped->m_cbIoSize );
|
|
|
|
//Close the file Handle and disconnect the client
|
|
DisconnectClient();
|
|
return( FALSE );
|
|
}
|
|
else
|
|
{
|
|
DebugTrace( (LPARAM)this,
|
|
"WriteFile complete. bytes %d, lpo: 0x%08X",
|
|
BytesWritten, lpo );
|
|
}
|
|
|
|
m_CurrentOffset += BytesWritten;
|
|
|
|
|
|
//We write out of the Write buffer
|
|
//We need throw away the data that was written out and
|
|
//move the remaining data to the start of the buffer
|
|
// if(m_cbReceived)
|
|
// MoveMemory ((void *)QueryMRcvBuffer(), (void *)(QueryMRcvBuffer() + BytesWritten), m_cbReceived);
|
|
m_cbCurrentWriteBuffer = 0;
|
|
|
|
//Time to go back and process the remaining data in the buffer
|
|
//Adjust the buffer for the next read only in case of Sync write
|
|
return(ProcessInputBuffer());
|
|
|
|
}
|
|
|
|
|
|
BOOL SMTP_CONNECTION::PendReadIO(DWORD UndecryptedTailSize)
|
|
{
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::PendReadIO");
|
|
|
|
BOOL fReturn = TRUE;
|
|
|
|
m_LastClientIo = READIO;
|
|
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
|
|
_ASSERT (m_cbReceived < QueryMaxReadSize());
|
|
|
|
//increment this IO
|
|
IncPendingIoCount();
|
|
|
|
fReturn = ReadFile(QueryMRcvBuffer() + m_cbReceived, QueryMaxReadSize() - m_cbReceived);
|
|
if(!fReturn)
|
|
{
|
|
DecPendingIoCount();
|
|
DisconnectClient();
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::ProcessInputBuffer
|
|
|
|
Description:
|
|
This function takes the receive buffer, parses the buffer to
|
|
find out what command the client sent, then executes that
|
|
command.
|
|
|
|
Arguments:
|
|
lpfMailQueued -- On return, TRUE if processing the client command
|
|
caused a mail message to be queued.
|
|
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
--*/
|
|
BOOL SMTP_CONNECTION::ProcessInputBuffer(void)
|
|
{
|
|
BOOL fReturn = TRUE;
|
|
const char * InputLine;
|
|
DWORD UndecryptedTailSize = m_cbReceived - m_cbParsable;
|
|
BOOL fWritePended = FALSE;
|
|
BOOL fShouldImposeLimit = TRUE;
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::ProcessInputBuffer");
|
|
|
|
if(m_pInstance->GetMaxMsgSizeBeforeClose() > 0 &&
|
|
m_SessionSize > m_pInstance->GetMaxMsgSizeBeforeClose())
|
|
{
|
|
hr = m_pInstance->TriggerMaxMsgSizeEvent(
|
|
GetSessionPropertyBag(), m_pIMsg, &fShouldImposeLimit );
|
|
|
|
if(FAILED(hr) || fShouldImposeLimit)
|
|
{
|
|
m_MailBodyError = ERROR_ALLOTTED_SPACE_EXCEEDED;
|
|
ErrorTrace((LPARAM) this,
|
|
"SMTP_RESP_NOSTORAGE, SMTP_MAX_SESSION_SIZE_EXCEEDED_MSG - %d",
|
|
m_SessionSize);
|
|
BUMP_COUNTER(QuerySmtpInstance(), MsgsRefusedDueToSize);
|
|
m_cbParsable = 0;
|
|
m_cbReceived = 0;
|
|
|
|
// Client won't be expecting this, but atleast an admin can tell
|
|
// what's wrong from this response.
|
|
FormatSmtpMessage(SMTP_RESP_NOSTORAGE, ENO_RESOURCES," %s\r\n",
|
|
SMTP_MAX_SESSION_SIZE_EXCEEDED_MSG);
|
|
SendSmtpResponse();
|
|
DisconnectClient();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//Start processing at the point after the temporary BDAT chunk
|
|
InputLine = QueryMRcvBuffer() + m_cbTempBDATLen + m_cbRecvBufferOffset;
|
|
|
|
if ( TRUE == m_fPeBlobReady) {
|
|
fReturn = ProcessPeBlob( InputLine, m_cbParsable);
|
|
if ( FALSE == fReturn) {
|
|
TraceFunctLeaveEx( ( LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
MoveMemory ( ( void *) QueryMRcvBuffer(), InputLine + m_cbParsable, UndecryptedTailSize);
|
|
m_cbParsable = 0;
|
|
m_cbReceived = UndecryptedTailSize;
|
|
// always pend a read in this state regardless of previous fReturn
|
|
fReturn = PendReadIO( UndecryptedTailSize);
|
|
TraceFunctLeaveEx( ( LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
if (m_State == DATA)
|
|
{
|
|
BOOL fAsyncOp = FALSE;
|
|
|
|
fReturn = ProcessDATAMailBody(InputLine, UndecryptedTailSize, &fAsyncOp);
|
|
if(!fReturn || fAsyncOp)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
//
|
|
// There is additional data pipelined behind the mailbody. DoDATACommandEx
|
|
// moves this to the beginning of the input buffer after processing the
|
|
// mail body, so the place to begin parsing is m_pRecvBuffer.
|
|
//
|
|
|
|
InputLine = QueryMRcvBuffer();
|
|
_ASSERT(m_State == HELO);
|
|
}
|
|
|
|
PROCESS_BDAT:
|
|
|
|
if(m_State == BDAT && !m_fIsChunkComplete)
|
|
{
|
|
BOOL fAsyncOp = FALSE;
|
|
|
|
fReturn = ProcessBDATMailBody(InputLine, UndecryptedTailSize, &fAsyncOp);
|
|
if(!fReturn || fAsyncOp)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
//
|
|
// All the mailbody was processed and the input buffer points to the
|
|
// next SMTP command after readjusting for any *saved* BDAT data (If a
|
|
// header spans 2 chunks, ProcessBDAT will save the partial header from
|
|
// the previous chunk till it can complete it using the next chunk.
|
|
// m_cbTempBdatLen represents the number of saved bytes)
|
|
//
|
|
|
|
InputLine = QueryMRcvBuffer() + m_cbTempBDATLen;
|
|
}
|
|
else if(m_State == AUTH)
|
|
{
|
|
fReturn = DoAuthNegotiation(InputLine, m_cbParsable);
|
|
|
|
if(!fReturn)
|
|
{
|
|
++m_ProtocolErrors;
|
|
}
|
|
|
|
if(m_State == AUTH)
|
|
{
|
|
fReturn = PendReadIO(UndecryptedTailSize);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
}
|
|
|
|
_ASSERT(m_State != DATA);
|
|
|
|
PCHAR pszSearch;
|
|
DWORD IntermediateSize;
|
|
DWORD CmdSize = 0;
|
|
|
|
//if we got here, then a read completed. Process the
|
|
//entire buffer before returning(Pipelining).
|
|
//use to be while ((pszSearch = strstr(QueryRcvBuffer(), CRLF)) != NULL)
|
|
|
|
//Reset the the offset ptr into the recv buffer
|
|
//we use this to keeo track of where to continue processing from
|
|
m_cbRecvBufferOffset = 0;
|
|
BOOL fAsyncOp = FALSE;
|
|
|
|
while ((pszSearch = IsLineComplete(InputLine,m_cbParsable - m_cbTempBDATLen)) != NULL)
|
|
{
|
|
//Null terminate the end of the command
|
|
*pszSearch = '\0';
|
|
|
|
IntermediateSize = (DWORD)(pszSearch - InputLine);
|
|
StartProcessingTimer();
|
|
ResetCommandCounters();
|
|
SetCommandBytesRecv( IntermediateSize );
|
|
|
|
if(!SmtpParseCompleteCommand((PCHAR)InputLine, pszSearch, IntermediateSize, &CmdSize))
|
|
{
|
|
ErrorTrace((LPARAM)this, "Discarding illegal SMTP command");
|
|
continue;
|
|
}
|
|
|
|
// we might run into a sink that does async operations
|
|
// So update state data that the async thread could see
|
|
m_cbRecvBufferOffset += (DWORD)(pszSearch - (QueryMRcvBuffer() + m_cbTempBDATLen) + 2);
|
|
m_cbParsable -= (IntermediateSize + 2);
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
|
|
// before firing each command load any session properties from ISession
|
|
HrGetISessionProperties();
|
|
|
|
//
|
|
// Increment the pending IO count in case this returns async
|
|
//
|
|
IncPendingIoCount();
|
|
|
|
fReturn = GlueDispatch((char *)InputLine, IntermediateSize, CmdSize, &fAsyncOp);
|
|
//
|
|
// Assert check that fAsyncOp isn't true when GlueDispatch fails
|
|
//
|
|
_ASSERT( (!fAsyncOp) || fReturn );
|
|
if(!fAsyncOp)
|
|
{
|
|
//
|
|
// Decrement the pending IO count since GlueDispatch
|
|
// returned sync (and we incremented the count above).
|
|
// ProcessClient is above us in the callstack and always has a
|
|
// pending IO reference, so this should never return zero
|
|
//
|
|
_VERIFY(DecPendingIoCount() > 0);
|
|
}
|
|
|
|
if (fReturn == TRUE && fAsyncOp)
|
|
{
|
|
//We returned from Dispatcher because some sink did an async operation
|
|
//We need to simply leave. The thread on which the async operation completes
|
|
//will continue with the sink firing. The dispacther will keep the context
|
|
//which will allow it to do so..
|
|
//So as to keep the connection alive - bump up the ref count
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
// log this command and the response
|
|
if (m_State != DATA &&
|
|
m_State != BDAT &&
|
|
m_State != AUTH &&
|
|
m_State != QUIT &&
|
|
m_dwCurrentCommand != DATA &&
|
|
m_dwCurrentCommand != BDAT &&
|
|
m_dwCurrentCommand != AUTH &&
|
|
m_dwCurrentCommand != QUIT)
|
|
{
|
|
const char *pszCommand = SmtpCommands[m_dwCurrentCommand];
|
|
DWORD cCmd = (pszCommand) ? strlen(pszCommand) : 0;
|
|
ProtocolLog(m_dwCurrentCommand,
|
|
InputLine + cCmd,
|
|
m_CInboundContext.m_dwWin32Status,
|
|
m_CInboundContext.m_dwSmtpStatus,
|
|
0,
|
|
0);
|
|
}
|
|
|
|
if (fReturn == TRUE)
|
|
{
|
|
|
|
//All Sinks completed synchronously
|
|
m_cbRecvBufferOffset = 0;
|
|
InputLine = pszSearch + 2; //skip CRLF
|
|
if(m_State != DATA && m_State != BDAT)
|
|
{
|
|
continue;
|
|
}
|
|
else if (m_State == BDAT && m_fIsLastChunk && m_nChunkSize == 0)
|
|
{
|
|
// The BDAT RFC allows the last chunk to be zero-sized. This
|
|
// should indicate that the message is done. However no chunk
|
|
// data is left to be parsed (so m_cbParsable *may* be 0 and if
|
|
// nonzero, it indicates pipelined SMTP commands after the zero
|
|
// byte BDAT chunk).
|
|
|
|
goto PROCESS_BDAT;
|
|
}
|
|
else if (m_State == BDAT && m_cbParsable)
|
|
{
|
|
//We are in BDAT mode and there is some data that can be parsed
|
|
MoveMemory ((void *)(QueryMRcvBuffer() + m_cbTempBDATLen), InputLine, m_cbReceived - m_cbTempBDATLen );
|
|
InputLine = QueryMRcvBuffer();
|
|
goto PROCESS_BDAT;
|
|
}
|
|
else
|
|
{
|
|
break; // no more data
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}//end if(fReturn == TRUE)
|
|
}//end while
|
|
|
|
// if IsLineComplete failed because we passed in a negative length
|
|
// than drop the session. something is corrupt
|
|
if ((int) m_cbParsable - (int) m_cbTempBDATLen < 0) {
|
|
DisconnectClient();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
_ASSERT(m_cbReceived <= QueryMaxReadSize());
|
|
|
|
//
|
|
// If there is additional data left at the end of the buffer move it to the
|
|
// beginning - clearing out already processed data.
|
|
//
|
|
|
|
if(m_cbParsable != 0 && // Additional data in buffer?
|
|
(QueryMRcvBuffer() + m_cbTempBDATLen != InputLine)) // Is MoveMemory needed?
|
|
{
|
|
MoveMemory ((void *)(QueryMRcvBuffer() + m_cbTempBDATLen),
|
|
InputLine, m_cbParsable+UndecryptedTailSize-m_cbTempBDATLen);
|
|
}
|
|
|
|
|
|
SendSmtpResponse();
|
|
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
|
|
|
|
//
|
|
// Bad input from the client can cause us to fill up the buffer
|
|
// completely. We can neither process the data available to us already
|
|
// because it is incomplete, nor attempt to complete it by posting more
|
|
// reads on the socket, because we do not have any more space. A well
|
|
// behaved server in such a situation should discard the data as it arrives
|
|
// and after all the data has been received, respond with an error. Note
|
|
// that legal input can never fill up the buffer because the buffer size
|
|
// used is large enough to accomodate all valid SMTP commands.
|
|
//
|
|
// This function checks for such situations, frees up buffer space when
|
|
// it detects such an error and transitions the session to an error state.
|
|
// If the recv-buffer is full, there was an error and we need to free
|
|
// some space to do error processing (see function description).
|
|
//
|
|
// m_cbReceived should never be > QueryMaxReadSize() - at most it is equal
|
|
// to QueryMaxReadSize(). The following if() is just defensive programming.
|
|
//
|
|
|
|
if(QueryMaxReadSize() <= m_cbReceived)
|
|
HandleFullRecvBuffer();
|
|
|
|
_ASSERT (m_cbReceived < QueryMaxReadSize());
|
|
|
|
//pend an I/O
|
|
IncPendingIoCount();
|
|
m_LastClientIo = READIO;
|
|
fReturn = ReadFile(QueryMRcvBuffer() + m_cbReceived, QueryMaxReadSize() - m_cbReceived);
|
|
if(!fReturn)
|
|
{
|
|
DisconnectClient();
|
|
DecPendingIoCount();
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Description:
|
|
// Helper function for SMTP command parser. This is called when we have
|
|
// a complete CRLF terminated line available from the client. It is
|
|
// responsible for parsing the Input from the client and setting
|
|
// m_dwCurrentCommand to the command received.
|
|
//
|
|
// Arguments:
|
|
// IN CHAR *InputLine - Pointer to CRLF terminated buffer containing command
|
|
// IN CHAR *pszSearch - Pointer to CRLF that marks the end of the command
|
|
// IN ULONG IntermediateSize - Length of command, excluding CRLF
|
|
// IN ULONG UndecryptedTailSize - If using TLS, these bytes are encrypted
|
|
// OUT DWORD *pdwCmdSize - If command was successfully parsed (this function
|
|
// returned TRUE), this is initialized to the size of the command
|
|
// keyword (excluding arguments).
|
|
//
|
|
// Returns:
|
|
// TRUE - If command was successfully parsed. CmdSize will be initialized
|
|
// to the size of the SMTP keyword.
|
|
// FALSE - If the input was not recognized as a command. This function
|
|
// will format the appropriate error message and send it to the
|
|
// client.
|
|
//-----------------------------------------------------------------------------
|
|
BOOL SMTP_CONNECTION::SmtpParseCompleteCommand(
|
|
const CHAR *InputLine,
|
|
CHAR *pszSearch,
|
|
ULONG IntermediateSize,
|
|
DWORD *pdwCmdSize)
|
|
{
|
|
BOOL fReturn = FALSE;
|
|
DWORD UndecryptedTailSize = m_cbReceived - m_cbParsable;
|
|
DWORD dwError = 0;
|
|
LPSTR pszExtended = NULL;
|
|
LPSTR pszText = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::SmtpParseCompleteCommand");
|
|
|
|
//
|
|
// Blank lines are accepted for interoperability purposes. Some SMTP
|
|
// implementations append an extra CRLF to some SMTP commands. We will
|
|
// silently accept and ignore blank lines.
|
|
//
|
|
|
|
if (*InputLine == 0) {
|
|
MoveMemory((PVOID)InputLine, pszSearch + 2, m_cbReceived - m_cbTempBDATLen);
|
|
m_cbParsable -= IntermediateSize + 2;
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Excessively long command was received from client and we could not fit
|
|
// it in our input buffer. So the m_fBufferFull flag was set and the command
|
|
// bytes discarded (so that we could receive the end of line and respond
|
|
// with an error message).
|
|
//
|
|
|
|
if(m_fRecvBufferFull) {
|
|
m_fRecvBufferFull = FALSE;
|
|
dwError = SMTP_RESP_BAD_CMD;
|
|
pszExtended = (LPSTR) ESYNTAX_ERROR;
|
|
pszText = (LPSTR) SMTP_BAD_CMD_STR;
|
|
fReturn = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Error case when an overly long SMTP command has to be discarded because
|
|
// there isn't enough space to hold it all in the buffer. Can happen while
|
|
// chunking (see where this member variable is set). In this situation, all
|
|
// bytes received are discarded without parsing, till a CRLF is encountered.
|
|
// Then IsLineComplete succeeds and we execute this if-statement.
|
|
//
|
|
|
|
if(m_fBufferFullInBDAT) {
|
|
m_fBufferFullInBDAT = FALSE;
|
|
dwError = SMTP_RESP_BAD_SEQ;
|
|
pszExtended = (LPSTR) ESYNTAX_ERROR;
|
|
pszText = (LPSTR) SMTP_BDAT_EXPECTED;
|
|
fReturn = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
|
|
//
|
|
// SMTP events are fired upon the _EOD event and "_EOD" appears in the
|
|
// SmtpDispatchTable and SmtpCommands[] array. However it isn't an SMTP
|
|
// command. Ideally "_EOD" should never be returned from SmtpGetCommand,
|
|
// but the event firing mechanism is tied to "_EOD" as a string. So we
|
|
// firewall this case in the parser.
|
|
//
|
|
// Other strings unimplemented by SMTP should be handled by GlueDispatch
|
|
// which checks if SMTP sinks are registered to handle that string and
|
|
// fires events for that string. "_EOD" is the only string for which
|
|
// events must not be fired from the SMTP command parser.
|
|
//
|
|
|
|
if(!strncasecmp((char *)InputLine, "_EOD", sizeof("_EOD") - 1))
|
|
{
|
|
dwError = SMTP_RESP_BAD_CMD;
|
|
pszExtended = (LPSTR) ENOT_IMPLEMENTED;
|
|
pszText = (LPSTR) SMTP_BAD_CMD_STR;
|
|
fReturn = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
m_dwCurrentCommand = SmtpGetCommand(InputLine, IntermediateSize, pdwCmdSize);
|
|
if(m_State == BDAT &&
|
|
m_dwCurrentCommand != BDAT &&
|
|
m_dwCurrentCommand != RSET &&
|
|
m_dwCurrentCommand != QUIT &&
|
|
m_dwCurrentCommand != NOOP)
|
|
{
|
|
dwError = SMTP_RESP_BAD_SEQ;
|
|
pszExtended = (LPSTR) ESYNTAX_ERROR;
|
|
pszText = (LPSTR) SMTP_BDAT_EXPECTED;
|
|
fReturn = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
fReturn = TRUE;
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Error detected during command parsing by one of the if-statements in this
|
|
// function. Discard the line containing the erroneous command (till the CRLF,
|
|
// which is at pszSearch) from the receive buffer, and return an error message,
|
|
// if one was set by the preceding if-statements, to the client.
|
|
//
|
|
|
|
if(!fReturn)
|
|
{
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
m_ProtocolErrors++;
|
|
MoveMemory((PVOID)InputLine, pszSearch + 2, m_cbReceived - m_cbTempBDATLen);
|
|
m_cbParsable -= IntermediateSize + 2;
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
|
|
if(0 != dwError && pszExtended && pszText)
|
|
{
|
|
PE_CdFormatSmtpMessage(dwError, pszExtended, " %s\r\n", pszText);
|
|
PE_SendSmtpResponse();
|
|
}
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return fReturn;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Description:
|
|
// Certain syntax errors by the client can result in the recv buffer being
|
|
// filled by with client input that does not contain CRLFs. Since SMTP
|
|
// processes line-by-line, it cannot process the data in the recv buffer
|
|
// which lacks CRLFs. It cannot receive additional data (that may contain
|
|
// the CRLFs) either, since the buffer is already full. This function
|
|
// detects such a state, switches over to an "error mode" and discards
|
|
// the received bytes to clear buffer space. Later, when the CRLF is
|
|
// received, and SMTP tries to parse the line (which is really a partial
|
|
// line, since we discarded the beginning of the line here), we will
|
|
// realize that we are in the "error-mode" and instead of parsing the
|
|
// partial line, we will discard it an respond with the appropriate error.
|
|
// Arguments:
|
|
// None.
|
|
// Returns:
|
|
// None.
|
|
// Notes:
|
|
// See SMTP_CONNECTION::SmtpParseCommand() for the rest of the processing
|
|
// done in "error-mode" when the CRLF is received.
|
|
//-----------------------------------------------------------------------------
|
|
void SMTP_CONNECTION::HandleFullRecvBuffer()
|
|
{
|
|
DWORD UndecryptedTailSize = m_cbReceived - m_cbParsable;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::HandleFullRecvBuffer");
|
|
|
|
_ASSERT(QueryMaxReadSize() == m_cbReceived);
|
|
|
|
//
|
|
// The receive buffer can fill up if we have more than QueryMaxReadSize()
|
|
// bytes in it that cannot be processed till more data is received from
|
|
// the client. This does not happen in the normal processing of SMTP
|
|
// commands, since each SMTP command is limited to 512 bytes, which can
|
|
// comfortably fit in the recv buffer.
|
|
//
|
|
// However, while processing BDAT, CreateMailBodyFromChunk may save the
|
|
// bytes of a partially received header line in the recv buffer by setting
|
|
// m_cbTempBDATLen > 0. For example, suppose the client is sending BDAT
|
|
// chunks, and the first chunk comprises of 500 bytes of data without a
|
|
// CRLF. In this case, SMTP cannot determine whether or not these 500
|
|
// bytes are part of a header. This is because unless the complete line
|
|
// has been received we cannot decide whether or not the line follows
|
|
// the header syntax. Therefore SMTP will save the 500 bytes by setting
|
|
// m_cbTempBDATLen == 500, and any bytes sent by the client will be
|
|
// appended at an offset of 500 in the recv buffer. When the next chunk
|
|
// arrives, SMTP will again try to process the 500 saved bytes + the
|
|
// bytes from the new chunk, as a complete header line.
|
|
//
|
|
// A client cannot fill up the buffer by sending thousands of bytes like
|
|
// this, without CRLFs, forcing SMTP to save the partial header. SMTP has
|
|
// a maximum limit on how long a header line can be (this is 1000 bytes).
|
|
// If this limit is exceeded, SMTP can determine that the line is not a
|
|
// syntactically correct header, and it does not have to wait for the
|
|
// terminating CRLF for the line so that it can parse the complete line
|
|
// according to header syntax.
|
|
//
|
|
// Thus it is impossible to occupy > 999 bytes of space as a saved BDAT
|
|
// partial header. However, even if we occupy the maximum possible 999
|
|
// bytes, the recv buffer (being sized 1024) will have only 26 bytes
|
|
// available for processing SMTP commands. This is clearly inadequate
|
|
// (in theory anyway) because SMTP commands have a max size of 512. In
|
|
// fact, we do not even have enough space to receive and reject illegal
|
|
// commands which can exceed 26 bytes (since a command-line must be
|
|
// completely received, till the terminating CRLF, before we can parse
|
|
// and reject it, in the ordinary course of things).
|
|
//
|
|
// Fortunately, during BDAT, the only commands allowed to be issued are
|
|
// all shorter than 26 bytes: BDAT, RSET and QUIT. We take advantage of
|
|
// this fact if we are caught in the "out of space" scenario, and the
|
|
// follwing if-statement sets m_fBufrFullInBDAT flag. When this flag is
|
|
// set, SMTP will discard every byte it receives from the client
|
|
// (following the saved m_cbTempBDATLen bytes) till a CRLF is received
|
|
// (note that the CRLF marks the end of the illegal SMTP command). Then
|
|
// SMTP responds with "BDAT expected" (see the command loop above) and
|
|
// resets the m_fBufrFullInBDAT flag to the normal state.
|
|
//
|
|
// Note that if the buffer is full, then m_cbTempBDATLen *must* be > 0.
|
|
// We only run out of recv buffer space during chunking. Thus the
|
|
// additional check for m_cbTempBDATLen > 0 in the following if-statement.
|
|
//
|
|
|
|
if(m_cbTempBDATLen > 0)
|
|
{
|
|
// Flag error so we know what response to generate when we do get CRLF
|
|
m_fBufferFullInBDAT = TRUE;
|
|
|
|
// Discard everything after the saved BDAT chunk except Undecrypted tail
|
|
PBYTE pbDiscardStart = (PBYTE) (QueryMRcvBuffer() + m_cbTempBDATLen);
|
|
PBYTE pbDiscardEnd = (PBYTE) (QueryMRcvBuffer() + m_cbParsable);
|
|
DWORD cbBytesToDiscard = m_cbParsable - m_cbTempBDATLen;
|
|
DWORD cbBytesToMove = UndecryptedTailSize;
|
|
|
|
// Keep CR if it's at the end. If the next packet has LF as the
|
|
// first byte we want to process the CRLF with IsLineComplete.
|
|
if(*(pbDiscardEnd - 1) == CR)
|
|
{
|
|
pbDiscardEnd--;
|
|
cbBytesToDiscard--;
|
|
cbBytesToMove++;
|
|
}
|
|
|
|
m_cbParsable -= cbBytesToDiscard;
|
|
m_cbReceived -= cbBytesToDiscard;
|
|
|
|
_ASSERT(pbDiscardEnd > pbDiscardStart);
|
|
MoveMemory((PVOID)pbDiscardStart, pbDiscardEnd, cbBytesToMove);
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// SMTP has a finite capacity to buffer data - QueryMaxReadSize() bytes
|
|
// at a time. So if the client sends a *very* long command that has no
|
|
// CRLFs in the first QueryMaxReadSize() bytes we cannot process that
|
|
// command. It is clearly an illegal command since SMTP commands are
|
|
// restricted to 512 bytes at the most.
|
|
//
|
|
// The error is handled by discarding all the command-bytes received so
|
|
// far, thus clearing space in the buffer. Then we can pend a read for
|
|
// the next batch of bytes before responding with "unrecognised command"
|
|
//
|
|
|
|
if(m_cbParsable > 0)
|
|
{
|
|
ErrorTrace((LPARAM)this, "Command too long, recv buffer full");
|
|
|
|
// We already handled (m_cbTempBDATLen > 0) in the previous 'if'
|
|
_ASSERT(m_cbTempBDATLen == 0);
|
|
|
|
// Flag error so we know what response to generate when we do get CRLF
|
|
m_fRecvBufferFull = TRUE;
|
|
|
|
int cbBytesToClear = m_cbParsable;
|
|
int cbBytesToMove = UndecryptedTailSize;
|
|
|
|
m_cbParsable = 0;
|
|
m_cbReceived = UndecryptedTailSize;
|
|
|
|
// If the last byte is CR, then an LF might be the first byte of the
|
|
// next packet. Save the last byte of this packet so that IsLineComplete
|
|
// will work in this situation.
|
|
|
|
if(*(QueryMRcvBuffer() + cbBytesToClear - 1) == '\r')
|
|
{
|
|
m_cbParsable = 1;
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
|
|
cbBytesToClear--;
|
|
cbBytesToMove++;
|
|
}
|
|
|
|
_ASSERT(cbBytesToClear > 0);
|
|
|
|
MoveMemory(QueryMRcvBuffer(), QueryMRcvBuffer() + cbBytesToClear,
|
|
cbBytesToMove);
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
_ASSERT(QueryMaxReadSize() > m_cbReceived);
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name:
|
|
SMTP_CONNECTION::HrGetISessionProperties
|
|
|
|
Description:
|
|
A sink may set certain properties on the session
|
|
which should control SMTP behaviour, this function
|
|
pulls those properties from ISession and writes
|
|
them as session parameters (members of SMTP_CONNECTION)
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Returns:
|
|
HRESULT - Success or Error
|
|
If there isn't a sink or if a property was not
|
|
set by sink, an error may be returned.
|
|
|
|
--*/
|
|
HRESULT SMTP_CONNECTION::HrGetISessionProperties()
|
|
{
|
|
IMailMsgPropertyBag *pISessionProperties = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::HrGetISessionProperties");
|
|
//
|
|
// This is for the EXPS (or any other) AUTH sink, which replaces SMTP
|
|
// authentication --- if the session is authenticated using the sink
|
|
// we should mark it as such. We pull the "AUTH" properties when we are
|
|
// about to process a command that is affected by the AUTH settings. We
|
|
// exclude commands like DATA and BDAT because they MUST have been
|
|
// preceded by MAIL, for which the AUTH setting would have already been
|
|
// checked.
|
|
//
|
|
if(!m_fAuthenticated &&
|
|
(m_dwCurrentCommand == MAIL ||
|
|
m_dwCurrentCommand == TURN ||
|
|
m_dwCurrentCommand == ETRN ||
|
|
m_dwCurrentCommand == VRFY ||
|
|
m_dwCurrentCommand == HELP))
|
|
{
|
|
pISessionProperties = (IMailMsgPropertyBag *)GetSessionPropertyBag();
|
|
//
|
|
// Usage of ISESSION_PID_IS_SESSION_AUTHENTICATED property: The AUTH
|
|
// sink sets this to TRUE when a successful authentication occurs.
|
|
//
|
|
hr = pISessionProperties->GetBool(
|
|
ISESSION_PID_IS_SESSION_AUTHENTICATED,
|
|
(DWORD *) &m_fAuthenticated);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
DebugTrace((LPARAM)this, "Can't get authenticated property for Session. hr - %08x", hr);
|
|
hr = S_OK;
|
|
goto Exit;
|
|
}
|
|
|
|
hr = pISessionProperties->GetStringA(
|
|
ISESSION_PID_AUTHENTICATED_USERNAME,
|
|
sizeof(m_szAuthenticatedUserNameFromSink),
|
|
m_szAuthenticatedUserNameFromSink);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
m_szAuthenticatedUserNameFromSink[0] = '\0';
|
|
DebugTrace((LPARAM)this, "Can't get username for Session. hr - %08x", hr);
|
|
hr = S_OK;
|
|
}
|
|
|
|
//
|
|
// Protocol sinks may set ISESSION_PID_MAY_RELAY if the authenticated
|
|
// user is permitted to relay even if the ip checks fail and "Allow all
|
|
// authenticated users to relay" isn't checked.
|
|
//
|
|
hr = pISessionProperties->GetBool(
|
|
ISESSION_PID_MAY_RELAY,
|
|
(DWORD *) &m_fMayRelay);
|
|
|
|
if(FAILED(hr)) {
|
|
m_fMayRelay = FALSE;
|
|
if (hr != MAILMSG_E_PROPNOTFOUND)
|
|
DebugTrace((LPARAM)this, "Can't get May Relay for Session. hr - %08x", hr);
|
|
hr = S_OK;
|
|
}
|
|
|
|
}
|
|
|
|
Exit:
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return hr;
|
|
}
|
|
|
|
/*++
|
|
Description:
|
|
This function is called set BOOL properties in the ISession property
|
|
bag that must be advertised to protocol sinks.
|
|
Arguments:
|
|
dwPropId - Propid to set
|
|
pvValue - Value of property
|
|
Returns:
|
|
Success HRESULT if the property was set.
|
|
E_FAIL if no ISession pointer exists.
|
|
Error HRESULT on other failures.
|
|
--*/
|
|
HRESULT SMTP_CONNECTION::HrSetISessionProperty(DWORD dwPropId, BOOL fValue)
|
|
{
|
|
IMailMsgPropertyBag *pISessionProperties = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::HrSetISessionProperty");
|
|
|
|
pISessionProperties = (IMailMsgPropertyBag *) GetSessionPropertyBag();
|
|
if(!pISessionProperties) {
|
|
_ASSERT(0 && "No ISession props!");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return E_FAIL;
|
|
}
|
|
|
|
hr = pISessionProperties->PutBool(dwPropId, fValue);
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return hr;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::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.
|
|
|
|
Note :
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::ProcessClient( IN DWORD InputBufferLen, IN DWORD dwCompletionStatus, IN OUT OVERLAPPED * lpo)
|
|
{
|
|
BOOL fIsAtqThread = TRUE;
|
|
BOOL RetStatus;
|
|
BOOL fMailQueued = FALSE;
|
|
PSMTP_SERVER_INSTANCE pInstance = m_pInstance; //save the instance pointer
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::ProcessClient()");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
pInstance->IncProcessClientThreads();
|
|
InterlockedIncrement(&g_cProcessClientThreads);
|
|
//
|
|
// increment the number of threads processing this client
|
|
//
|
|
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
|
|
//
|
|
IncPendingIoCount();
|
|
|
|
FormatSmtpMessage (SMTP_RESP_ERROR, NULL," %s\r\n",SMTP_TIMEOUT_MSG );
|
|
SendSmtpResponse(); //flush the response
|
|
ProtocolLog(SMTP_TIMEOUT, (char *) QueryClientUserName(), inet_addr((char *)QueryClientHostName()), ERROR_SEM_TIMEOUT);
|
|
|
|
DebugTrace( (LPARAM)this, "client timed out");
|
|
|
|
DisconnectClient();
|
|
}
|
|
else if((InputBufferLen == 0) || (dwCompletionStatus != NO_ERROR))
|
|
{
|
|
//
|
|
DebugTrace((LPARAM) this, "SMTP_CONNECTION::ProcessClient: InputBufferLen = %d dwCompletionStatus = %d - Closing connection", InputBufferLen, dwCompletionStatus);
|
|
|
|
// If this is a mail file write then we handle error: if not a mailfile write or we are unable to
|
|
// handle the error, then we simply disconnect.
|
|
if( lpo != &m_Overlapped && lpo != QueryAtqOverlapped() && !HandleInternalError(dwCompletionStatus))
|
|
DisconnectClient();
|
|
}
|
|
else if (lpo == &m_Overlapped || lpo == QueryAtqOverlapped())
|
|
{
|
|
//
|
|
// Is this a real IO completion or a completion status
|
|
// posted to notify us an async sink completed?
|
|
//
|
|
if( m_fAsyncEventCompletion )
|
|
{
|
|
//
|
|
// A portocol event sink completed async, called our
|
|
// completion routine, posted a completion status which is
|
|
// being dequeued here. Our job is now to pend the next read
|
|
// (via ProcessInputBuffer)
|
|
//
|
|
m_fAsyncEventCompletion = FALSE;
|
|
RetStatus = ProcessInputBuffer();
|
|
}
|
|
else
|
|
{
|
|
//A client based async IO completed
|
|
RetStatus = ProcessReadIO(InputBufferLen, dwCompletionStatus, lpo);
|
|
|
|
if((m_pInstance->GetMaxErrors() > 0) && (m_ProtocolErrors >= m_pInstance->GetMaxErrors()))
|
|
{
|
|
//If there are too many error, break the connection
|
|
FormatSmtpMessage (SMTP_RESP_SRV_UNAVAIL, NULL," %s\r\n",SMTP_TOO_MANY_ERR_MSG);
|
|
FatalTrace((LPARAM) this, "Too many errors. Error count = %d", m_ProtocolErrors);
|
|
SendSmtpResponse();
|
|
DisconnectClient();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//A Mail File Write completed
|
|
SMTPCLI_FILE_OVERLAPPED* lpFileOverlapped = (SMTPCLI_FILE_OVERLAPPED*)lpo;
|
|
_ASSERT( lpFileOverlapped->m_LastIoState == WRITEFILEIO );
|
|
|
|
if (lpFileOverlapped->m_LastIoState == WRITEFILEIO)
|
|
{
|
|
RetStatus = ProcessFileWrite(InputBufferLen, dwCompletionStatus, lpo);
|
|
}
|
|
}
|
|
|
|
//
|
|
// decrement the number of threads processing this client if the thread exiting
|
|
// is an Atq pool thread
|
|
//
|
|
//if(fIsAtqThread)
|
|
DecThreadCount();
|
|
|
|
DebugTrace((LPARAM)this,"SMTPLCI - Pending IOs: %d", m_cPendingIoCount);
|
|
DebugTrace((LPARAM)this,"SMTPCLI - Num Threads: %d", m_cActiveThreads);
|
|
|
|
// Do NOT Touch the member variables past this POINT!
|
|
// This object may be deleted!
|
|
|
|
//
|
|
// decrement the overall pending IO count for this session
|
|
// tracing and ASSERTs if we're going down.
|
|
//
|
|
if (DecPendingIoCount() == 0)
|
|
{
|
|
ProtocolLog(QUIT, (char *) QueryClientUserName() , QuerySessionTime(),
|
|
(m_CInboundContext.m_dwCommandStatus | EXPE_DROP_SESSION) ? ERROR_VC_DISCONNECTED : NO_ERROR);
|
|
|
|
DebugTrace((LPARAM)this, "Pending IO count == 0, disconnecting.");
|
|
if(m_DoCleanup)
|
|
DisconnectClient();
|
|
|
|
m_pInstance->RemoveConnection(this);
|
|
delete this;
|
|
}
|
|
|
|
//if(fIsAtqThread)
|
|
//{
|
|
pInstance->DecProcessClientThreads();
|
|
InterlockedDecrement(&g_cProcessClientThreads);
|
|
//}
|
|
|
|
// We are not the last thread, so we will return TRUE
|
|
// to keep the object around
|
|
//TraceFunctLeaveEx((LPARAM)this);
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::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_CONNECTION::StartSession( void)
|
|
{
|
|
SYSTEMTIME st;
|
|
BOOL fRet;
|
|
char szDateBuf [cMaxArpaDate];
|
|
char FullName[MAX_PATH + 1];
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::StartSession");
|
|
|
|
StartSessionTimer();
|
|
|
|
if(!m_pFileWriteBuffer || !m_pFileWriteBuffer->GetData())
|
|
{
|
|
ErrorTrace((LPARAM)this, "Failed to get the write buffer Err : %d",
|
|
GetLastError());
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If we are negotiating SSL, we need to let the SSL handshake happen
|
|
// first. Else, we are either done with the SSL handshake or we are
|
|
// talking over the unsecured port, so just go ahead and send the server
|
|
// greeting.
|
|
//
|
|
|
|
if (!m_fNegotiatingSSL)
|
|
{
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//Dump the header line into the buffer to format the greeting
|
|
GetLocalTime(&st);
|
|
GetArpaDate(szDateBuf);
|
|
|
|
m_pInstance->LockGenCrit();
|
|
lstrcpy(FullName, m_pInstance->GetFQDomainName());
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_READY,NULL," %s %s %s, %s \r\n",
|
|
FullName,
|
|
m_pInstance->GetConnectResponse(),
|
|
Daynames[st.wDayOfWeek],
|
|
szDateBuf);
|
|
|
|
//send the greeting
|
|
if(!PE_SendSmtpResponse())
|
|
{
|
|
DebugTrace( (LPARAM) this, "SendSmtpResponse() returned FALSE!");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
//kick off our first read
|
|
m_LastClientIo = READIO;
|
|
|
|
//
|
|
// increment the overall pending io count for this session
|
|
//
|
|
|
|
_ASSERT(m_cPendingIoCount == 0);
|
|
|
|
IncPendingIoCount();
|
|
fRet = ReadFile(QueryMRcvBuffer(), QueryMaxReadSize());
|
|
|
|
if(!fRet)
|
|
{
|
|
int err = GetLastError();
|
|
|
|
ErrorTrace((LPARAM)this, "Readfile failed, err = %d", err);
|
|
DecPendingIoCount(); //if one of these operations fail,
|
|
PE_DisconnectClient(); //AND the ReadFile failed the "last error" is lost.
|
|
|
|
if(err != ERROR_SUCCESS)
|
|
SetLastError(err); //restore last error that occurred so higher level routine knows what happened
|
|
}
|
|
|
|
DebugTrace((LPARAM)this, "SendSmtpResponse() returned %d", fRet);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::CheckArguments
|
|
|
|
Description:
|
|
|
|
checks the arguments of what the client sends
|
|
for the required space command et al.
|
|
|
|
Arguments:
|
|
|
|
Arguments from client
|
|
|
|
Returns:
|
|
|
|
NULL if arguments are not correct
|
|
A pointer into the input buffer where
|
|
the rest of the data is.
|
|
|
|
--*/
|
|
char * SMTP_CONNECTION::CheckArgument(char * Argument, BOOL WriteError)
|
|
{
|
|
|
|
if(*Argument =='\0')
|
|
{
|
|
if(WriteError)
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS, " %s\r\n","Argument missing" );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if(!isspace((UCHAR)*Argument))
|
|
{
|
|
if(WriteError)
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS, " %s\r\n",SMTP_BAD_CMD_STR);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//get rid of white space
|
|
while(isspace((UCHAR)*Argument))
|
|
Argument++;
|
|
|
|
//is there anything after here ?
|
|
if(*Argument =='\0')
|
|
{
|
|
if(WriteError)
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS," %s\r\n", "Argument missing" );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return Argument;
|
|
}
|
|
|
|
void SMTP_CONNECTION::FormatEhloResponse(void)
|
|
{
|
|
char FullName [MAX_PATH + 1];
|
|
unsigned char AuthPackages [500];
|
|
DWORD BytesRet = 0;
|
|
DWORD ConnectionStatus = 0;
|
|
|
|
if(m_SecurePort)
|
|
ConnectionStatus |= SMTP_IS_SSL_CONNECTION;
|
|
if(m_fAuthenticated)
|
|
ConnectionStatus |= SMTP_IS_AUTH_CONNECTION;
|
|
|
|
m_pInstance->LockGenCrit();
|
|
lstrcpy(FullName, m_pInstance->GetFQDomainName());
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL, "-%s %s [%s]\r\n", FullName,"Hello", QueryClientHostName());
|
|
|
|
if(m_pInstance->AllowAuth())
|
|
{
|
|
if(m_pInstance->QueryAuthentication() != 0)
|
|
{
|
|
if(m_pInstance->QueryAuthentication() & INET_INFO_AUTH_NT_AUTH)
|
|
{
|
|
BytesRet = sizeof(AuthPackages);
|
|
m_securityCtx.GetInstanceAuthPackageNames(AuthPackages, &BytesRet, PkgFmtSpace);
|
|
}
|
|
|
|
if(BytesRet > 0)
|
|
{
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-AUTH %s",AuthPackages);
|
|
|
|
if(m_pInstance->AllowLogin(ConnectionStatus))
|
|
{
|
|
PE_FormatSmtpMessage(" LOGIN\r\n");
|
|
}
|
|
else
|
|
{
|
|
PE_FormatSmtpMessage("\r\n");
|
|
}
|
|
|
|
if(m_pInstance->AllowLogin(ConnectionStatus))
|
|
{
|
|
//For backward compatibility
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-AUTH=%s\r\n","LOGIN");
|
|
}
|
|
}
|
|
else if(m_pInstance->AllowLogin(ConnectionStatus))
|
|
{
|
|
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-AUTH=LOGIN\r\n");
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-AUTH LOGIN\r\n");
|
|
}
|
|
|
|
if(g_SmtpPlatformType == PtNtServer && m_pInstance->AllowTURN())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n","TURN");
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (m_pInstance->GetMaxMsgSize() > 0)
|
|
{
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-%s %u\r\n","SIZE", m_pInstance->GetMaxMsgSize());
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-%s\r\n","SIZE");
|
|
}
|
|
|
|
if(g_SmtpPlatformType == PtNtServer && m_pInstance->AllowETRN())
|
|
{
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-%s\r\n", "ETRN");
|
|
}
|
|
|
|
if(m_pInstance->ShouldPipeLineIn())
|
|
{
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-%s\r\n","PIPELINING");
|
|
}
|
|
|
|
if(m_pInstance->AllowDSN())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n", "DSN");
|
|
}
|
|
|
|
if(m_pInstance->AllowEnhancedCodes())
|
|
{
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-%s\r\n", "ENHANCEDSTATUSCODES");
|
|
}
|
|
|
|
if(m_pInstance->AllowEightBitMime())
|
|
{
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_OK, NULL,"-%s\r\n", "8bitmime");
|
|
}
|
|
|
|
// Chunking related advertisements
|
|
if(m_pInstance->AllowBinaryMime())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n", "BINARYMIME");
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n", "CHUNKING");
|
|
}
|
|
else if(m_pInstance->AllowChunking())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n", "CHUNKING");
|
|
}
|
|
|
|
// verify - we need to advertise it whether we support it or not.
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n","VRFY");
|
|
|
|
// Expand
|
|
if(m_pInstance->AllowExpand(ConnectionStatus))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n", "EXPN");
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Check that we have a certificate installed with which we can negotiate
|
|
// a SSL session
|
|
//
|
|
if (!m_SecurePort && // TLS is advertized only if we haven't already negotiated it
|
|
m_encryptCtx.CheckServerCert(
|
|
(LPSTR) QueryLocalHostName(),
|
|
(LPSTR) QueryLocalPortName(),
|
|
(LPVOID) QuerySmtpInstance(),
|
|
QuerySmtpInstance()->QueryInstanceId()))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n","TLS");
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL,"-%s\r\n", "STARTTLS");
|
|
}
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, NULL," %s\r\n","OK");
|
|
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::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_CONNECTION::DoEHLOCommand(const char * InputLine, DWORD ParametSize)
|
|
{
|
|
BOOL RetStatus = TRUE;
|
|
char * Args = (char *) InputLine;
|
|
CAddr * NewAddress = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoEHLOCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//If the current state is BDAT the only command that can be received is BDAT
|
|
if(m_State == BDAT)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n","BDAT Expected" );
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - BDAT Expected");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
// pgopi -- don't cound multiple ehlo's as error. This is per bug 82160
|
|
//if (m_HelloSent)
|
|
// ++m_ProtocolErrors;
|
|
|
|
Args = CheckArgument(Args, !m_pInstance->ShouldAcceptNoDomain());
|
|
if(Args != NULL)
|
|
{
|
|
if(m_pInstance->ShouldValidateHeloDomain())
|
|
{
|
|
if(!ValidateDRUMSDomain(Args, lstrlen(Args)))
|
|
{
|
|
//Not a valid domain -
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
}
|
|
else
|
|
{
|
|
//Is reverse DNS lookup enabled
|
|
if(m_pInstance->IsReverseLookupEnabled())
|
|
{
|
|
m_DNSLookupRetCode = VerifiyClient (Args, QueryClientHostName());
|
|
if(m_DNSLookupRetCode == NO_MATCH)
|
|
{
|
|
//We failed in DNS lookup
|
|
if(m_pInstance->fDisconnectOnRDNSFail())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_TRANS_FAILED, ENO_SECURITY," %s\r\n",SMTP_RDNS_REJECTION);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "Rejected Connection RDNS failed for %s", Args);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if(m_DNSLookupRetCode == LOOKUP_FAILED)
|
|
{
|
|
//We had an internal DNS failure
|
|
if(m_pInstance->fDisconnectOnRDNSFail())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_SRV_UNAVAIL, EINTERNAL_ERROR," %s\r\n",SMTP_RDNS_FAILURE);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "Rejected Connection RDNS DNS failure for %s", Args);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
m_DNSLookupRetCode = SUCCESS;
|
|
}
|
|
|
|
FormatEhloResponse();
|
|
|
|
strncpy(m_szHeloAddr, Args, MAX_INTERNET_NAME);
|
|
m_szHeloAddr[MAX_INTERNET_NAME] = '\0';
|
|
m_HelloSent = TRUE;
|
|
m_State = HELO;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FormatEhloResponse();
|
|
strncpy(m_szHeloAddr, Args, MAX_INTERNET_NAME);
|
|
m_szHeloAddr[MAX_INTERNET_NAME] = '\0';
|
|
m_HelloSent = TRUE;
|
|
m_State = HELO;
|
|
}
|
|
|
|
}
|
|
else if(m_pInstance->ShouldAcceptNoDomain())
|
|
{
|
|
FormatEhloResponse();
|
|
|
|
if(m_szHeloAddr[0] != '\0')
|
|
{
|
|
m_szHeloAddr[0] = '\0';
|
|
}
|
|
|
|
m_HelloSent = TRUE;
|
|
m_State = HELO;
|
|
|
|
}
|
|
|
|
//we can either accept the HELO or EHLO as the 1st command
|
|
RetStatus = PE_SendSmtpResponse();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return RetStatus;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoHELOCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP HELO command
|
|
|
|
Arguments:
|
|
Are ignored
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoHELOCommand(const char * InputLine, DWORD ParameterSize)
|
|
{
|
|
char * Args = (char *) InputLine;
|
|
CAddr * NewAddress = NULL;
|
|
char FullName[MAX_PATH + 1];
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoHELOCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//If the current state is BDAT the only command that can be received is BDAT
|
|
if(m_State == BDAT)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n","BDAT Expected" );
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - BDAT Expected");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
// pgopi--don't cound multiple ehlo's as error. This is per bug 82160
|
|
//if (m_HelloSent)
|
|
// ++m_ProtocolErrors;
|
|
|
|
Args = CheckArgument(Args, !m_pInstance->ShouldAcceptNoDomain());
|
|
if(Args != NULL)
|
|
{
|
|
if(m_pInstance->ShouldValidateHeloDomain())
|
|
{
|
|
if(!ValidateDRUMSDomain(Args, lstrlen(Args)))
|
|
{
|
|
//Not a valid domain -
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
}
|
|
else
|
|
{
|
|
if(m_pInstance->IsReverseLookupEnabled())
|
|
{
|
|
m_DNSLookupRetCode = VerifiyClient (Args, QueryClientHostName());
|
|
if(m_DNSLookupRetCode == NO_MATCH)
|
|
{
|
|
//We failed in DNS lookup
|
|
if(m_pInstance->fDisconnectOnRDNSFail())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_TRANS_FAILED, ENO_SECURITY," %s\r\n",SMTP_RDNS_REJECTION);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "Rejected Connection RDNS failed for %s", Args);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if(m_DNSLookupRetCode == LOOKUP_FAILED)
|
|
{
|
|
//We had an internal DNS failure
|
|
if(m_pInstance->fDisconnectOnRDNSFail())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_SRV_UNAVAIL, EINTERNAL_ERROR," %s\r\n",SMTP_RDNS_FAILURE);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "Rejected Connection RDNS DNS failure for %s", Args);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
m_DNSLookupRetCode = SUCCESS;
|
|
}
|
|
|
|
m_pInstance->LockGenCrit();
|
|
strcpy(FullName, m_pInstance->GetFQDomainName());
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK,NULL," %s %s [%s]\r\n", FullName,
|
|
"Hello", QueryClientHostName());
|
|
|
|
strncpy(m_szHeloAddr, Args, MAX_INTERNET_NAME);
|
|
m_szHeloAddr[MAX_INTERNET_NAME] = '\0';
|
|
m_HelloSent = TRUE;
|
|
m_State = HELO;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pInstance->LockGenCrit();
|
|
strcpy(FullName, m_pInstance->GetFQDomainName());
|
|
m_pInstance->UnLockGenCrit();
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK,NULL," %s %s [%s]\r\n", FullName,
|
|
"Hello", QueryClientHostName());
|
|
strncpy(m_szHeloAddr, Args, MAX_INTERNET_NAME);
|
|
m_szHeloAddr[MAX_INTERNET_NAME] = '\0';
|
|
m_HelloSent = TRUE;
|
|
m_State = HELO;
|
|
}
|
|
}
|
|
else if(m_pInstance->ShouldAcceptNoDomain())
|
|
{
|
|
m_pInstance->LockGenCrit();
|
|
lstrcpy(FullName, m_pInstance->GetFQDomainName());
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK,NULL," %s %s [%s]\r\n", FullName,
|
|
"Hello", QueryClientHostName());
|
|
|
|
if(m_szHeloAddr[0] != '\0')
|
|
{
|
|
m_szHeloAddr[0] = '\0';
|
|
}
|
|
|
|
m_HelloSent = TRUE;
|
|
m_State = HELO;
|
|
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoRSETCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP RSET command.
|
|
Deletes all stored info, and resets
|
|
all flags.
|
|
|
|
Arguments:
|
|
Are ignored
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoRSETCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
|
|
BOOL RetStatus;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoRSETCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
m_State = HELO;
|
|
m_TotalMsgSize = 0;
|
|
m_cbDotStrippedTotalMsgSize = 0;
|
|
m_RecvdMailCmd = FALSE;
|
|
m_HeaderSize = 0;
|
|
m_InHeader = TRUE;
|
|
m_LongBodyLine = FALSE;
|
|
m_fFoundEmbeddedCrlfDotCrlf = FALSE;
|
|
m_fScannedForCrlfDotCrlf = FALSE;
|
|
m_fSeenRFC822FromAddress = FALSE;
|
|
m_fSeenRFC822ToAddress = FALSE;
|
|
m_fSeenRFC822CcAddress = FALSE;
|
|
m_fSeenRFC822BccAddress = FALSE;
|
|
m_fSeenRFC822Subject = FALSE;
|
|
m_fSeenRFC822MsgId = FALSE;
|
|
m_fSeenXPriority = FALSE;
|
|
m_fSeenXOriginalArrivalTime = FALSE;
|
|
m_fSeenContentType = FALSE;
|
|
m_fSetContentType = FALSE;
|
|
m_TimeToRewriteHeader = TRUE;
|
|
m_MailBodyError = NO_ERROR;
|
|
m_RecvdRcptCmd = FALSE;
|
|
m_CurrentOffset = 0;
|
|
m_HopCount = 0;
|
|
m_LocalHopCount = 0;
|
|
m_fIsLastChunk = FALSE;
|
|
m_fIsBinaryMime = FALSE;
|
|
m_fIsChunkComplete = FALSE;
|
|
m_dwTrailerStatus = CRLF_SEEN;
|
|
m_nChunkSize = 0;
|
|
m_nBytesRemainingInChunk = 0;
|
|
m_MailBodyDiagnostic = ERR_NONE;
|
|
m_cbRecvBufferOffset = 0;
|
|
m_ProtocolErrors = 0;
|
|
m_fBufferFullInBDAT = FALSE;
|
|
|
|
if(m_cbTempBDATLen)
|
|
{
|
|
m_cbParsable -= m_cbTempBDATLen;
|
|
m_cbTempBDATLen = 0;
|
|
}
|
|
|
|
m_szFromAddress[0] = '\0';
|
|
|
|
|
|
//Free the possible ATQ context associated with this File handle
|
|
//This will be if we were processing BDAT before RSET
|
|
//FreeAtqFileContext();
|
|
|
|
ReleasImsg(TRUE);
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, EPROT_SUCCESS," %s\r\n",SMTP_RSET_OK_STR);
|
|
|
|
RetStatus= PE_SendSmtpResponse();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return RetStatus;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoNOOPCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP NOOP command.
|
|
|
|
Arguments:
|
|
Are ignored
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoNOOPCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
BOOL RetStatus;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoNOOPCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//send an OK
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, EPROT_SUCCESS," OK\r\n");
|
|
RetStatus = PE_SendSmtpResponse();
|
|
|
|
if(!m_Nooped)
|
|
{
|
|
m_Nooped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_ProtocolErrors++;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return RetStatus;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoETRNCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP ETRN command.
|
|
|
|
Arguments:
|
|
Parsed to determine the ETRN domain(s) to send mail to.
|
|
|
|
Returns:
|
|
|
|
TRUE in all cases. We are agreeing to give our best effort to the ETRN command
|
|
re the protocol. Mail is not necessarily in the retry queue so it may not be delivered.
|
|
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoETRNCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
|
|
BOOL RetStatus;
|
|
char * Ptr = NULL;
|
|
CHAR szNode[SMTP_MAX_DOMAIN_NAME_LEN];
|
|
DWORD Ret = 0;
|
|
// DWORD strLen;
|
|
BOOL bSubDomain;
|
|
DWORD dwMessagesQueued;
|
|
HRESULT hr;
|
|
// BOOL bWildCard;
|
|
// DOMAIN_ROUTE_ACTION_TYPE action;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoETRNCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
bSubDomain = FALSE;
|
|
dwMessagesQueued = 0;
|
|
// bWildCard = FALSE;
|
|
|
|
//If the current state is BDAT the only command that can be received is BDAT
|
|
if(m_State == BDAT)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n","BDAT Expected" );
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - BDAT Expected");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// check that the hello has been sent, and that we are not in the middle of sending
|
|
// a message
|
|
//NimishK : removed the check for HELO EHLO
|
|
|
|
if(!m_fAuthAnon && !m_fAuthenticated)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY, " %s\r\n", "Client was not authenticated");
|
|
ErrorTrace((LPARAM) this, "DoDataCommand - SMTP_RESP_MUST_SECURE, user not authenticated");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
if(m_RecvdMailCmd)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", SMTP_NO_ETRN_IN_MSG);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "In DoETRNCommand():SMTP_RESP_BAD_SEQ, SMTP_NO_ETRN_IN_MSG");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//start parsing from the beginning of the line
|
|
Ptr = (char *) InputLine;
|
|
|
|
//check if argument have the right format (at least one parameter)
|
|
Ptr = CheckArgument(Ptr);
|
|
if (Ptr == NULL)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
// remove any whitespace
|
|
while((isspace ((UCHAR)*Ptr)))
|
|
{
|
|
Ptr++;
|
|
}
|
|
|
|
|
|
// check for ETRN subdomain character
|
|
if (*Ptr == '@')
|
|
{
|
|
//We could probably move this check to aqueue or keep it here
|
|
if (!(QuerySmtpInstance()->AllowEtrnSubDomains()))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS," %s\r\n", "Invalid domain name");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_ARGS - Invalid domain name");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
bSubDomain = TRUE;
|
|
}
|
|
|
|
// for @ command, check that this is at least a second tier domain (to avoid @com attack)
|
|
if (bSubDomain)
|
|
{
|
|
if (!strchr(Ptr,'.'))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NODE_INVALID, EINVALID_ARGS," Node %s not allowed: First tier domain\r\n", Ptr);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_NODE_INVALID - First tier domains are not allowed");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
lstrcpyn(szNode, Ptr, AB_MAX_DOMAIN);
|
|
char *szTmp;
|
|
if (bSubDomain)
|
|
szTmp = szNode + 1;
|
|
else
|
|
szTmp = szNode;
|
|
|
|
if(!ValidateDRUMSDomain(szTmp, lstrlen(szTmp)))
|
|
{
|
|
//Not a valid domain -
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS," %s\r\n", "Invalid domain name");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_ARGS - Invalid domain name");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//So we have a valid domain
|
|
//Call into connection manager to start the queue
|
|
//If multiple domains were dequed because the domain name was either wild card
|
|
//or specifed with the @ sign
|
|
hr = m_pInstance->GetConnManPtr()->ETRNDomain(lstrlen(szNode),szNode, &dwMessagesQueued);
|
|
|
|
if(!FAILED(hr))
|
|
{
|
|
//We know about this ETRN domain
|
|
if(!dwMessagesQueued)
|
|
{
|
|
if (bSubDomain || hr == AQ_S_SMTP_WILD_CARD_NODE)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_ETRN_ZERO_MSGS, EPROT_SUCCESS," Ok, no messages waiting for node %s and sub nodes\r\n",
|
|
szNode);
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_ETRN_ZERO_MSGS, EPROT_SUCCESS," Ok, no messages waiting for node %s\r\n", szNode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bSubDomain || hr == AQ_S_SMTP_WILD_CARD_NODE)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_ETRN_N_MSGS, EPROT_SUCCESS,
|
|
" OK, %d pending messages for wildcard node %s started\r\n", dwMessagesQueued, szNode);
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_ETRN_N_MSGS, EPROT_SUCCESS,
|
|
" OK, %d pending messages for node %s started\r\n",dwMessagesQueued, szNode);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (hr == AQ_E_SMTP_ETRN_NODE_INVALID)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NODE_INVALID, EINVALID_ARGS," Node %s not allowed: not configured as ETRN domain\r\n", szNode);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_ARGS - Domain not allowed");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_ERROR, EINTERNAL_ERROR," Action aborted - internal error\r\n");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
ErrorTrace((LPARAM) this, "Internal error ETRN processing");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
RetStatus = PE_SendSmtpResponse();
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return RetStatus;
|
|
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoSTARTTLSCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP STARTTLS command.
|
|
|
|
Arguments:
|
|
Parsed to determine the TLS protocol to use.
|
|
|
|
Returns:
|
|
|
|
TRUE normally. A FALSE return indicates to the caller that the
|
|
client connection should be dropped.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoSTARTTLSCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
BOOL RetStatus, fStartSSL;
|
|
char * Ptr = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoSTARTTLSCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
fStartSSL = FALSE;
|
|
|
|
//
|
|
// check that the hello has been sent, and that we are not in the middle of sending
|
|
// a message
|
|
//
|
|
if(!m_pInstance->AllowMailFromNoHello() && !m_HelloSent)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n","Send hello first" );
|
|
ProtocolLog(STARTTLS, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - Send hello first");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Check that we have a certificate installed with which we can negotiate
|
|
// a SSL session
|
|
//
|
|
|
|
if (!m_encryptCtx.CheckServerCert(
|
|
(LPSTR) QueryLocalHostName(),
|
|
(LPSTR) QueryLocalPortName(),
|
|
(LPVOID) QuerySmtpInstance(),
|
|
QuerySmtpInstance()->QueryInstanceId())) {
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_TRANS_FAILED, ENO_SECURITY," %s\r\n", SMTP_NO_CERT_MSG);
|
|
ProtocolLog(STARTTLS, (char *)InputLine, NO_ERROR, SMTP_RESP_TRANS_FAILED, 0, 0);
|
|
ErrorTrace((LPARAM) this, "In DoSTARTTLSCommand():SMTP_RESP_TRANS_FAILED, SMTP_NO_CERT_MSG");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// if we are already secure, reject the request
|
|
//
|
|
if(m_SecurePort)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", SMTP_ONLY_ONE_TLS_MSG);
|
|
ProtocolLog(STARTTLS, (char *)InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "In DoSTARTTLSCommand():SMTP_RESP_BAD_SEQ, SMTP_ONLY_ONE_TLS_MSG");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Switch over to using a large receive buffer, because a SSL fragment
|
|
// may be up to 32K big.
|
|
fStartSSL = SwitchToBigSSLBuffers();
|
|
if (fStartSSL) {
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_READY, EPROT_SUCCESS," %s\r\n", SMTP_READY_STR);
|
|
ProtocolLog(STARTTLS, (char *) InputLine, NO_ERROR, SMTP_RESP_READY, 0, 0);
|
|
} else {
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n", SMTP_NO_MEMORY);
|
|
ProtocolLog(STARTTLS, (char *) InputLine, NO_ERROR, SMTP_RESP_NORESOURCES, 0, 0);
|
|
}
|
|
fStartSSL = TRUE;
|
|
|
|
RetStatus = PE_SendSmtpResponse();
|
|
|
|
if (fStartSSL)
|
|
m_SecurePort = m_fNegotiatingSSL = TRUE;
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return RetStatus;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoTLSCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
return DoSTARTTLSCommand(InputLine, parameterSize);
|
|
}
|
|
|
|
//
|
|
// the default handler for end of data. commit the recipients list and
|
|
// insert the message into the queue
|
|
//
|
|
BOOL SMTP_CONNECTION::Do_EODCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
TraceFunctEnter("SMTP_CONNECTION::Do_EODCommand");
|
|
|
|
HRESULT hr;
|
|
char MessageId[1024];
|
|
BOOL fQRet;
|
|
|
|
MessageId[0] = 0;
|
|
|
|
if( m_pIMsg )
|
|
{
|
|
m_pIMsg->GetStringA( IMMPID_MP_RFC822_MSG_ID, sizeof( MessageId ), MessageId );
|
|
}
|
|
|
|
MessageId[sizeof(MessageId)-1] = 0; // NULL terminate it.
|
|
|
|
if(m_HopCount >= m_pInstance->GetMaxHopCount())
|
|
{
|
|
fQRet = m_pInstance->SubmitFailedMessage(m_pIMsg, MESSAGE_FAILURE_HOP_COUNT_EXCEEDED, 0);
|
|
FatalTrace((LPARAM) this, "Hop count exceeded");
|
|
}
|
|
else
|
|
{
|
|
// check to see if we need to hold this message
|
|
if (m_LocalHopCount >= 2) // if we're hitting this server for the 3rd time (or more)
|
|
{
|
|
if( m_pIMsg )
|
|
{
|
|
// Update the deferred delivery time property for this message
|
|
SYSTEMTIME SystemTime;
|
|
ULARGE_INTEGER ftDeferred; // == FILETIME
|
|
DWORD cbProp = 0;
|
|
BOOL fSuccess;
|
|
|
|
// Get the current system time
|
|
GetSystemTime (&SystemTime);
|
|
|
|
// Convert it to a file time
|
|
fSuccess = SystemTimeToFileTime(&SystemTime, (FILETIME*)&ftDeferred);
|
|
_ASSERT(fSuccess);
|
|
|
|
// Add 10 minutes
|
|
ftDeferred.QuadPart += SMTP_LOOP_DELAY;
|
|
|
|
hr = m_pIMsg->PutProperty(IMMPID_MP_DEFERRED_DELIVERY_FILETIME,
|
|
sizeof(FILETIME), (BYTE *) &ftDeferred);
|
|
|
|
DebugTrace( (LPARAM)this, "Possible Loop : Delaying message 10 minutes");
|
|
}
|
|
}
|
|
|
|
fQRet = m_pInstance->InsertIntoQueue(m_pIMsg);
|
|
}
|
|
|
|
if(fQRet)
|
|
{
|
|
ReleasImsg(FALSE);
|
|
}
|
|
else
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
//FatalTrace((LPARAM) this, "SetEndOfFile failed on file %s !!! (err=%d)", MailInfo->GetMailFileName(), m_MailBodyError);
|
|
m_CInboundContext.SetWin32Status(m_MailBodyError);
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_STORAGE );
|
|
SendSmtpResponse();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
//Get the offset of the filename and send it back to the client
|
|
//we send the status back to the client immediately, just incase
|
|
//the session breaks abnormally, or because the client times out.
|
|
//If the client times out, it would send another message and we
|
|
//would get duplicate mail messages.
|
|
|
|
//Note that the response string is limited in length, so we'll truncate
|
|
//the MessageId if it is too long. Otherwise the reponse will be truncated
|
|
//elsewhere, like before the SMTP_QUEUE_MAIL string which will be confusing.
|
|
//MAX_REWRITTEN_MSGID is actually the maximum possible length of the entire
|
|
//"Message-ID: xxx" header in a message.
|
|
|
|
_ASSERT(sizeof(MessageId)/sizeof(char) > MAX_REWRITTEN_MSGID);
|
|
MessageId[MAX_REWRITTEN_MSGID] = '\0';
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, EMESSAGE_GOOD, " %s %s\r\n",MessageId, SMTP_QUEUE_MAIL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoQUITCommand
|
|
|
|
Description:
|
|
|
|
Responds to 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_CONNECTION::DoQUITCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoQUITCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//set our state
|
|
m_State = QUIT;
|
|
|
|
//send the quit response
|
|
m_pInstance->LockGenCrit();
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_CLOSE, E_GOODBYE, " %s %s\r\n",m_pInstance->GetFQDomainName(), SMTP_QUIT_OK_STR);
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
PE_SendSmtpResponse();
|
|
|
|
//disconnect the client
|
|
PE_DisconnectClient();
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoSizeCommand(char * Value, char * InputLine)
|
|
{
|
|
DWORD EstMailSize = 0;
|
|
DWORD EstSessionSize = 0;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoSizeCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
EstMailSize = atoi (Value);
|
|
EstSessionSize = m_SessionSize + EstMailSize;
|
|
|
|
if (EstMailSize == 0)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS, " %s\r\n","Invalid arguments" );
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_MBX_SYNTAX, SMTP_BAD_CMD_STR. Client sent 0 in SIZE command");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
//check to make sure we are going over the servers max file size
|
|
if((m_pInstance->GetMaxMsgSize() > 0) && (EstMailSize > m_pInstance->GetMaxMsgSize()))
|
|
{
|
|
BOOL fShouldImposeLimit = TRUE;
|
|
if( FAILED( m_pInstance->TriggerMaxMsgSizeEvent( GetSessionPropertyBag(), m_pIMsg, &fShouldImposeLimit ) ) || fShouldImposeLimit )
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NOSTORAGE, EMESSAGE_TOO_BIG," %s\r\n", SMTP_MAX_MSG_SIZE_EXCEEDED_MSG );
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_NOSTORAGE, SMTP_MAX_MSG_SIZE_EXCEEDED_MSG - %d", EstMailSize);
|
|
++m_ProtocolErrors;
|
|
BUMP_COUNTER(QuerySmtpInstance(), MsgsRefusedDueToSize);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Check that the maximum session size has not been exceeded
|
|
if((m_pInstance->GetMaxMsgSizeBeforeClose() > 0) && (EstSessionSize > m_pInstance->GetMaxMsgSizeBeforeClose()))
|
|
{
|
|
BOOL fShouldImposeLimit = TRUE;
|
|
if( FAILED( m_pInstance->TriggerMaxMsgSizeEvent( GetSessionPropertyBag(), m_pIMsg, &fShouldImposeLimit ) ) || fShouldImposeLimit )
|
|
{
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_NOSTORAGE, SMTP_MAX_SESSION_SIZE_EXCEEDED_MSG - %d", EstMailSize);
|
|
BUMP_COUNTER(QuerySmtpInstance(), MsgsRefusedDueToSize);
|
|
DebugTrace((LPARAM) this, "GetMaxMsgSizeBeforeClose() exceeded - closing connection");
|
|
|
|
// Client won't be expecting this, but atleast an admin can tell
|
|
// what's wrong from this response.
|
|
FormatSmtpMessage(SMTP_RESP_NOSTORAGE, ENO_RESOURCES, " %s\r\n",
|
|
SMTP_MAX_SESSION_SIZE_EXCEEDED_MSG);
|
|
SendSmtpResponse();
|
|
DisconnectClient();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoBodyCommand (char * Value, char * InputLine)
|
|
{
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoBodyCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
if(!_strnicmp(Value, (char * )"8bitmime", 8))
|
|
{
|
|
m_pIMsg->PutDWORD(IMMPID_MP_EIGHTBIT_MIME_OPTION, 1);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else if((!_strnicmp(Value, (char * )"BINARYMIME", 10)) && m_pInstance->AllowBinaryMime())
|
|
{
|
|
m_fIsBinaryMime = TRUE; //Need to get rid of this **
|
|
m_pIMsg->PutDWORD(IMMPID_MP_BINARYMIME_OPTION, 1);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else if(!_strnicmp(Value, (char * )"7bit", 4))
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS," %s\r\n", SMTP_BAD_BODY_TYPE_STR );
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_MBX_SYNTAX, SMTP_BAD_BODY_TYPE_STR.");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoRetCommand (char * Value, char * InputLine)
|
|
{
|
|
char RetDsnValue[10];
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoRetCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//strict size checking
|
|
if(strlen(Value) < 5)
|
|
{
|
|
hr = m_pIMsg->GetStringA(IMMPID_MP_DSN_RET_VALUE, sizeof(RetDsnValue), RetDsnValue);
|
|
if(FAILED(hr))
|
|
{
|
|
if(!_strnicmp(Value, (char * )"FULL", 4) ||
|
|
!_strnicmp(Value, (char * )"HDRS", 4))
|
|
{
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_DSN_RET_VALUE, Value);
|
|
if(!FAILED(hr))
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_STORAGE );
|
|
ErrorTrace((LPARAM) this, "Failed to set RET value to IMSG");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS," %s\r\n","Invalid arguments" );
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_MBX_SYNTAX, SMTP_BAD_CMD_STR. Client sent bad RET value");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::IsXtext(char * Xtext)
|
|
{
|
|
char * StartPtr = Xtext;
|
|
char NextChar = '\0';
|
|
|
|
while((NextChar = *StartPtr++) != '\0')
|
|
{
|
|
if(NextChar == '+')
|
|
{
|
|
NextChar = *StartPtr++;
|
|
if(!isascii((UCHAR)NextChar) || !isxdigit((UCHAR)NextChar))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if(NextChar < '!' || NextChar > '~' || NextChar == '=')
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoEnvidCommand (char * Value, char * InputLine)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
char EnvidValue[MAX_MAIL_FROM_ENVID_LEN+1];
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoEnvidCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
_ASSERT (Value != NULL);
|
|
_ASSERT (InputLine != NULL);
|
|
|
|
hr = m_pIMsg->GetStringA(IMMPID_MP_DSN_ENVID_VALUE, sizeof(EnvidValue), EnvidValue);
|
|
if(FAILED(hr))
|
|
{
|
|
DWORD ValueLength = lstrlen(Value);
|
|
|
|
if((ValueLength != 0) && (ValueLength <= MAX_MAIL_FROM_ENVID_LEN))
|
|
{
|
|
if(IsXtext(Value))
|
|
{
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_DSN_ENVID_VALUE, Value);
|
|
if(!FAILED(hr))
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_STORAGE );
|
|
ErrorTrace((LPARAM) this, "Failed to set ENVID value to IMSG");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS," %s\r\n","Invalid arguments" );
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_MBX_SYNTAX, SMTP_BAD_CMD_STR. Client sent bad envid value");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoNotifyCommand (char * Value, DWORD * pdwNotifyValue)
|
|
{
|
|
*pdwNotifyValue = 0;
|
|
char * SearchPtr = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoNotifyCommand");
|
|
|
|
_ASSERT (Value != NULL);
|
|
|
|
if(!strcasecmp(Value, "NEVER"))
|
|
{
|
|
*pdwNotifyValue = RP_DSN_NOTIFY_NEVER;
|
|
}
|
|
else
|
|
{
|
|
for(SearchPtr = Value; SearchPtr != NULL; Value = SearchPtr)
|
|
{
|
|
SearchPtr = strchr(SearchPtr, ',');
|
|
if(SearchPtr != NULL)
|
|
{
|
|
*SearchPtr++ = '\0';
|
|
}
|
|
|
|
if(!strcasecmp(Value, "SUCCESS"))
|
|
{
|
|
*pdwNotifyValue |= RP_DSN_NOTIFY_SUCCESS;
|
|
}
|
|
else if(!strcasecmp(Value, "FAILURE"))
|
|
{
|
|
*pdwNotifyValue |= RP_DSN_NOTIFY_FAILURE;
|
|
}
|
|
else if(!strcasecmp(Value, "DELAY"))
|
|
{
|
|
*pdwNotifyValue |= RP_DSN_NOTIFY_DELAY;
|
|
}
|
|
else
|
|
{
|
|
*pdwNotifyValue = RP_DSN_NOTIFY_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!*pdwNotifyValue)
|
|
{
|
|
*pdwNotifyValue = RP_DSN_NOTIFY_INVALID;
|
|
}
|
|
}
|
|
|
|
if(*pdwNotifyValue == RP_DSN_NOTIFY_INVALID)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoMailFromAuthArg (char * Value)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
char chDummy;
|
|
DWORD ValueLength;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoMailFromAuthArg");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
_ASSERT (Value != NULL);
|
|
|
|
ValueLength = lstrlen(Value);
|
|
|
|
if((ValueLength != 0) && (ValueLength <= MAX_MAIL_FROM_AUTH_LEN)) {
|
|
//
|
|
// Verify that the property doesn't already exist.
|
|
//
|
|
hr = m_pIMsg->GetStringA(IMMPID_MP_INBOUND_MAIL_FROM_AUTH, 0, &chDummy);
|
|
if(hr == STG_E_UNKNOWN) {
|
|
if(IsXtext(Value)) {
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_INBOUND_MAIL_FROM_AUTH, Value);
|
|
if(!FAILED(hr)) {
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
} else {
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_STORAGE );
|
|
ErrorTrace((LPARAM) this, "Failed to set AUTH value to IMSG");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS," %s\r\n","Invalid arguments" );
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_MBX_SYNTAX, SMTP_BAD_CMD_STR. Client sent bad auth value");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoOrcptCommand (char * Value)
|
|
{
|
|
char * XtextPtr = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoOrcptCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
DWORD ValueLength = lstrlen(Value);
|
|
|
|
if((ValueLength != 0) && (ValueLength <= MAX_RCPT_TO_ORCPT_LEN))
|
|
{
|
|
//NK** : Parse out the address type and validate it against IANA registered
|
|
// mail address type
|
|
if(XtextPtr = strchr(Value,';'))
|
|
{
|
|
if(IsXtext(XtextPtr+1))
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoSmtpArgs (char *InputLine)
|
|
{
|
|
char * Value = NULL;
|
|
char * EndOfLine = NULL;
|
|
char * Cmd = NULL;
|
|
char * LinePtr = InputLine;
|
|
char * SearchPtr = NULL;
|
|
|
|
while(LinePtr && *LinePtr != '\0')
|
|
{
|
|
//find the beginning of the keyword
|
|
LinePtr = (char *) strchr (LinePtr, '=');
|
|
if(LinePtr != NULL)
|
|
{
|
|
|
|
SearchPtr = LinePtr;
|
|
//Cmd = LinePtr - 4;
|
|
while( (*SearchPtr != ' ') && (*SearchPtr != '\0'))
|
|
{
|
|
SearchPtr--;
|
|
}
|
|
|
|
Cmd = SearchPtr + 1;
|
|
|
|
*LinePtr++ = '\0';
|
|
Value = LinePtr;
|
|
|
|
//Back track to the beginning of the byte
|
|
//before the size command. If that character
|
|
//is a space, then we need to Null terminate
|
|
//the string there. If it's not a space, then
|
|
//this is an error.
|
|
EndOfLine = Cmd - 1;
|
|
if(*EndOfLine == ' ')
|
|
*EndOfLine = '\0';
|
|
else if(*EndOfLine != '\0')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LinePtr = (char *) strchr(LinePtr, ' ');
|
|
if(LinePtr != NULL)
|
|
{
|
|
*LinePtr++ = '\0';
|
|
}
|
|
|
|
if(!strcasecmp(Cmd, "SIZE"))
|
|
{
|
|
if(!DoSizeCommand(Value, InputLine))
|
|
return FALSE;
|
|
}
|
|
else if(!strcasecmp(Cmd, "BODY"))
|
|
{
|
|
if(!DoBodyCommand (Value, InputLine))
|
|
return FALSE;
|
|
}
|
|
else if(!strcasecmp(Cmd, "RET") && m_pInstance->AllowDSN())
|
|
{
|
|
if(!DoRetCommand (Value, InputLine))
|
|
return FALSE;
|
|
}
|
|
else if(!strcasecmp(Cmd, "ENVID") && m_pInstance->AllowDSN())
|
|
{
|
|
if(!DoEnvidCommand (Value, InputLine))
|
|
return FALSE;
|
|
}
|
|
else if(!strcasecmp(Cmd, "AUTH"))
|
|
{
|
|
if(! DoMailFromAuthArg(Value))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS, " %s\r\n","Invalid arguments" );
|
|
++m_ProtocolErrors;
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS, " %s\r\n","Invalid arguments" );
|
|
++m_ProtocolErrors;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoRcptArgs (char *InputLine, char * szORCPTval, DWORD * pdwNotifyVal)
|
|
{
|
|
char * Value = NULL;
|
|
char * EndOfLine = NULL;
|
|
char * Cmd = NULL;
|
|
char * LinePtr = InputLine;
|
|
char * SearchPtr = NULL;
|
|
*pdwNotifyVal = 0;
|
|
BOOL fDoneOrcptCmd = FALSE;
|
|
BOOL fDoneNotifyCmd = FALSE;
|
|
|
|
|
|
while(LinePtr && *LinePtr != '\0')
|
|
{
|
|
if(!m_pInstance->AllowDSN())
|
|
return FALSE;
|
|
|
|
//find the beginning of the keyword
|
|
LinePtr = (char *) strchr (LinePtr, '=');
|
|
if(LinePtr != NULL)
|
|
{
|
|
|
|
SearchPtr = LinePtr;
|
|
//Cmd = LinePtr - 4;
|
|
while( (*SearchPtr != ' ') && (*SearchPtr != '\0'))
|
|
{
|
|
SearchPtr--;
|
|
}
|
|
|
|
Cmd = SearchPtr + 1;
|
|
|
|
*LinePtr++ = '\0';
|
|
Value = LinePtr;
|
|
|
|
//Back track to the beginning of the byte
|
|
//before the size command. If that character
|
|
//is a space, then we need to Null terminate
|
|
//the string there. If it's not a space, then
|
|
//this is an error.
|
|
EndOfLine = Cmd - 1;
|
|
if(*EndOfLine == ' ')
|
|
*EndOfLine = '\0';
|
|
else if(*EndOfLine != '\0')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LinePtr = (char *) strchr(LinePtr, ' ');
|
|
if(LinePtr != NULL)
|
|
{
|
|
*LinePtr++ = '\0';
|
|
}
|
|
|
|
if(!strcasecmp(Cmd, "NOTIFY") )
|
|
{
|
|
|
|
if( fDoneNotifyCmd || ( !DoNotifyCommand( Value, pdwNotifyVal ) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
fDoneNotifyCmd = TRUE;
|
|
}
|
|
else if(!strcasecmp(Cmd, "ORCPT"))
|
|
{
|
|
if( ( !fDoneOrcptCmd ) && DoOrcptCommand(Value))
|
|
{
|
|
strcpy(szORCPTval,Value);
|
|
fDoneOrcptCmd = TRUE;
|
|
}
|
|
else
|
|
{
|
|
*szORCPTval = '\0';
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
//NK** : Receive header always completes sync
|
|
BOOL SMTP_CONNECTION::WriteRcvHeader(void)
|
|
{
|
|
char * Address = NULL;
|
|
char * VersionNum = NULL;
|
|
CHAR szText[2024];
|
|
char szDateBuf [cMaxArpaDate];
|
|
DWORD cbText = 0;
|
|
DWORD Error = NO_ERROR;
|
|
DWORD EstMailSize = 0;
|
|
SYSTEMTIME st;
|
|
HRESULT hr;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::WriteRcvHeader");
|
|
|
|
if(m_IMsgHandle == NULL)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
//get the name of the sender to place into the mail header
|
|
Address = (char *) QueryClientUserName();
|
|
_ASSERT (Address != NULL);
|
|
|
|
//Dump the header line into the buffer
|
|
GetLocalTime(&st);
|
|
GetArpaDate(szDateBuf);
|
|
|
|
VersionNum = strchr(g_VersionString, ':');
|
|
if(VersionNum)
|
|
{
|
|
VersionNum += 2;
|
|
}
|
|
else
|
|
{
|
|
VersionNum = "";
|
|
}
|
|
|
|
m_pInstance->LockGenCrit();
|
|
if(m_DNSLookupRetCode == SUCCESS)
|
|
{
|
|
m_HeaderSize = wsprintf(szText,szFormatReceivedFormatSuccess,
|
|
Address, QueryClientHostName(), m_pInstance->GetFQDomainName(),
|
|
(m_SecurePort ? " over TLS secured channel" : ""),VersionNum,
|
|
Daynames[st.wDayOfWeek], szDateBuf);
|
|
}
|
|
else if ( m_DNSLookupRetCode == LOOKUP_FAILED)
|
|
{
|
|
m_HeaderSize = wsprintf(szText,szFormatReceivedFormatUnverified,
|
|
Address, QueryClientHostName(), m_pInstance->GetFQDomainName(),
|
|
(m_SecurePort ? " over TLS secured channel" : ""),VersionNum,
|
|
Daynames[st.wDayOfWeek], szDateBuf);
|
|
}
|
|
else
|
|
{
|
|
m_HeaderSize = wsprintf(szText,szFormatReceivedFormatFailed,
|
|
Address, QueryClientHostName(), m_pInstance->GetFQDomainName(),
|
|
(m_SecurePort ? " over TLS secured channel" : ""),VersionNum,
|
|
Daynames[st.wDayOfWeek], szDateBuf);
|
|
}
|
|
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
|
|
|
|
//if EstMailSize > 0, then the size command was given. If the size command was
|
|
//given and the value was 0, we would have caught it above and would skip over
|
|
//this peice of code.
|
|
if (EstMailSize > 0)
|
|
{
|
|
if(SetFilePointer(m_IMsgHandle->m_hFile, EstMailSize + cbText,
|
|
NULL, FILE_BEGIN) == 0xFFFFFFFF)
|
|
{
|
|
Error = GetLastError();
|
|
DWORD Offset = EstMailSize + cbText;
|
|
|
|
//FormatSmtpMessage ("%d %s %s\r\n",SMTP_RESP_NORESOURCES, ENO_RESOURCES, SMTP_NO_STORAGE );
|
|
//ProtocolLog(MAIL, (char *)InputLine, Error, SMTP_RESP_NORESOURCES, 0, 0);
|
|
FatalTrace((LPARAM) this, " SetFilePointer failed - %d", GetLastError());
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!SetEndOfFile(m_IMsgHandle->m_hFile))
|
|
{
|
|
Error = GetLastError();
|
|
|
|
//ProtocolLog(MAIL, (char *)InputLine, Error, SMTP_RESP_NORESOURCES, 0, 0);
|
|
//PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_STORAGE );
|
|
FatalTrace((LPARAM) this, " SetEndOfFile failed - %d", GetLastError());
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
DWORD dwPointer;
|
|
dwPointer = SetFilePointer(m_IMsgHandle->m_hFile, 0, NULL, FILE_BEGIN);
|
|
|
|
//if we get here, this should always pass
|
|
_ASSERT (dwPointer != 0xFFFFFFFF);
|
|
}
|
|
|
|
_ASSERT(m_pFileWriteBuffer);
|
|
_ASSERT(m_pFileWriteBuffer->GetData());
|
|
CopyMemory((m_pFileWriteBuffer->GetData() + m_cbCurrentWriteBuffer),szText,m_HeaderSize);
|
|
m_cbCurrentWriteBuffer += m_HeaderSize;
|
|
|
|
wsprintf(szText,"%s, %s",Daynames[st.wDayOfWeek], szDateBuf);
|
|
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_ARRIVAL_TIME, szText);
|
|
if(FAILED(hr))
|
|
{
|
|
Error = GetLastError();
|
|
//FormatSmtpMessage ("%d %s %s\r\n",SMTP_RESP_NORESOURCES, ENO_RESOURCES, SMTP_NO_STORAGE );
|
|
FatalTrace((LPARAM) this, " MailInfo->SetSenderToStream");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::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_CONNECTION::DoMAILCommand(const char * InputLine, DWORD ParameterSize)
|
|
{
|
|
char * Ptr = NULL;
|
|
char * SizeCmd = NULL;
|
|
//char * Address = NULL;
|
|
char * Value = NULL;
|
|
CAddr * NewAddress = NULL;
|
|
DWORD EstMailSize = 0;
|
|
DWORD cbText = 0;
|
|
DWORD Error = NO_ERROR;
|
|
BOOL FoundSizeCmd = FALSE;
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoMAILCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//If the current state is BDAT the only command that can be received is BDAT
|
|
if(m_State == BDAT)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n","BDAT Expected" );
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - BDAT Expected");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//Check if HELO or EHELO
|
|
if(!m_pInstance->AllowMailFromNoHello() && !m_HelloSent)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n", "Send hello first" );
|
|
ErrorTrace((LPARAM) this, "DoMAILCommand - SMTP_RESP_BAD_SEQ, Send hello first");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(!m_HelloSent)
|
|
{
|
|
m_HelloSent = TRUE;
|
|
}
|
|
|
|
if(!m_fAuthAnon && !m_fAuthenticated)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY, " %s\r\n", "Client was not authenticated");
|
|
ErrorTrace((LPARAM) this, "DoMAILCommand - SMTP_RESP_MUST_SECURE, user not authenticated");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
//disallow mail from more than once
|
|
if(m_RecvdMailCmd)
|
|
{
|
|
++m_ProtocolErrors;
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n", "Sender already specified" );
|
|
ErrorTrace((LPARAM) this, "DoMAILCommand - SMTP_RESP_BAD_SEQ, Sender already specified");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//We should reset the current hop count when we get a new mail. If we don't
|
|
//do this, we might NDR the mail that comes later in the session.
|
|
m_HopCount = 0;
|
|
m_LocalHopCount = 0;
|
|
|
|
//start parsing from the beginning of the line
|
|
Ptr = (char *) InputLine;
|
|
|
|
//check if argument have the right format
|
|
Ptr = CheckArgument(Ptr);
|
|
if (Ptr == NULL)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
//skip over from:
|
|
Ptr = SkipWord (Ptr, "From", 4);
|
|
if (Ptr == NULL)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
DWORD dwAddressLen = 0;
|
|
DWORD dwCanonAddrLen = 0;
|
|
char * ArgPtr = NULL;
|
|
char * AddrPtr = NULL;
|
|
char * CanonAddrPtr = NULL;
|
|
char * DomainPtr = NULL;
|
|
m_szFromAddress[0] = '\0';
|
|
|
|
//Parse out the address and possible argumets from the input line
|
|
if(!Extract821AddressFromLine(Ptr,&AddrPtr,&dwAddressLen,&ArgPtr))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//Check if we have valid tail and address
|
|
if(!AddrPtr)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//Check for the special case <> from address
|
|
if(dwAddressLen == 2 && *AddrPtr == '<' && *(AddrPtr+1) == '>')
|
|
{
|
|
//simply copy this as the validated address
|
|
lstrcpy(m_szFromAddress,"<>");
|
|
}
|
|
else
|
|
{
|
|
//Extract the canonical address in the <local-part> "@" <domain> form
|
|
if(!ExtractCanonical821Address(AddrPtr,dwAddressLen,&CanonAddrPtr,&dwCanonAddrLen))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//If we have a Canonical addr - validate it
|
|
if(CanonAddrPtr)
|
|
{
|
|
strncpy(m_szFromAddress,CanonAddrPtr,dwCanonAddrLen);
|
|
*(m_szFromAddress + dwCanonAddrLen) = '\0';
|
|
|
|
if(!Validate821Address(m_szFromAddress, dwCanonAddrLen))
|
|
{
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//Extract
|
|
if(!Get821AddressDomain(m_szFromAddress,dwCanonAddrLen,&DomainPtr))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
//Is the RDNS enabled for MAILFROM command
|
|
if(m_pInstance->IsRDNSEnabledForMAIL())
|
|
{
|
|
DWORD dwDNSLookupRetCode = SUCCESS;
|
|
//If we have the RDNS option on MAIL from domain
|
|
//we cannot get a NULL domain on MAIL from
|
|
if(DomainPtr)
|
|
{
|
|
dwDNSLookupRetCode = VerifiyClient (DomainPtr,DomainPtr);
|
|
if(dwDNSLookupRetCode == NO_MATCH)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_REJECT_MAILFROM, ENO_SECURITY_TMP," %s\r\n",SMTP_RDNS_REJECTION);
|
|
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "Rejected Mail From : RDNS failed for %s", DomainPtr);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else if(m_DNSLookupRetCode == LOOKUP_FAILED)
|
|
{
|
|
//We had an internal DNS failure
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_REJECT_MAILFROM, EINTERNAL_ERROR," %s\r\n",SMTP_RDNS_FAILURE);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "Rejected MAILFROM : RDNS DNS failure for %s", DomainPtr);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_REJECT_MAILFROM, ENO_SECURITY_TMP," %s\r\n",SMTP_RDNS_REJECTION);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "Rejected Mail From : no domain specified");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
//rewrite the address if it's not the NULL address,
|
|
//and the masquerading option is on
|
|
if(m_pInstance->ShouldMasquerade() && !ISNULLADDRESS(m_szFromAddress)
|
|
&& (!DomainPtr || m_pInstance->IsALocalDomain(DomainPtr)))
|
|
{
|
|
if(!m_pInstance->MasqueradeDomain(m_szFromAddress, DomainPtr))
|
|
{
|
|
//it failed. Inform the user
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if(!DomainPtr && !ISNULLADDRESS(m_szFromAddress))
|
|
{
|
|
//If there is no domain on this address,
|
|
//then append the current domain to this
|
|
//address. However, if we get the <>
|
|
//address, don't append a domain
|
|
|
|
if(!m_pInstance->AppendLocalDomain (m_szFromAddress))
|
|
{
|
|
//it failed. Inform the user
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//parse the arguments, if any
|
|
if(!DoSmtpArgs (ArgPtr))
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
_ASSERT (m_szFromAddress != NULL);
|
|
_ASSERT (m_szFromAddress[0] != '\0');
|
|
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_SENDER_ADDRESS_SMTP, m_szFromAddress);
|
|
if(FAILED(hr))
|
|
{
|
|
Error = GetLastError();
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_STORAGE );
|
|
m_CInboundContext.SetWin32Status(Error);
|
|
FatalTrace((LPARAM) this, " MailInfo->SetSenderToStream");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
m_RecvdMailCmd = TRUE;
|
|
m_State = MAIL;
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, ESENDER_ADDRESS_VALID," %s....Sender OK\r\n", m_szFromAddress);
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoRCPTCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP RCPT command.
|
|
This funcion gets the addresses and
|
|
places then into a linked list.
|
|
Arguments:
|
|
|
|
InputLine - Buffer received from client
|
|
paramterSize - amount of data in buffer
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoRCPTCommand(const char * InputLine, DWORD ParameterSize)
|
|
{
|
|
char * ToName = NULL;
|
|
char * ThisAddress = NULL;
|
|
CAddr * NewAddress = NULL;
|
|
CAddr * TempAddress = NULL;
|
|
BOOL IsDomainValid = TRUE; //assume everyone is O.K
|
|
BOOL RelayThisMail = FALSE;
|
|
BOOL DropQuotaExceeded = FALSE;
|
|
|
|
DWORD dwPropId = IMMPID_RP_ADDRESS_SMTP;
|
|
DWORD dwNewRecipIndex = 0;
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoRCPTCommand");
|
|
|
|
_ASSERT(m_pInstance != NULL);
|
|
|
|
//If the current state is BDAT the only command that can be received is BDAT
|
|
if(m_State == BDAT)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", "BDAT Expected" );
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - BDAT Expected");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(!m_HelloSent)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", "Send hello first" );
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - Send hello first");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(!m_fAuthAnon && !m_fAuthenticated)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY, " %s\r\n","Client was not authenticated");
|
|
ErrorTrace((LPARAM) this, "DoRCPTCommand - SMTP_RESP_MUST_SECURE, user not authenticated");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//see if we got a valid return path before going on
|
|
if(!m_RecvdMailCmd || !m_pIMsg || !m_pIMsgRecips || (m_szFromAddress[0] == '\0') )
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", SMTP_NEED_MAIL_FROM_MSG);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "In DoRCPTCommand():SMTP_RESP_BAD_SEQ, SMTP_NEED_MAIL_FROM_MSG");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//start at inputline
|
|
ToName = (char *) InputLine;
|
|
|
|
//set the state
|
|
m_State = RCPT;
|
|
|
|
m_RecvdRcptCmd = TRUE;
|
|
|
|
//check if argument have the right format
|
|
ToName = CheckArgument(ToName);
|
|
if (ToName == NULL)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
ToName = SkipWord (ToName, "To", 2);
|
|
if (ToName == NULL)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
char * DomainPtr = NULL;
|
|
char * ArgPtr = NULL;
|
|
char szRcptAddress[MAX_INTERNET_NAME + 1];
|
|
szRcptAddress[0] = '\0';
|
|
|
|
DWORD dwNotifyVal;
|
|
char szOrcptVal[MAX_RCPT_TO_ORCPT_LEN + 1];
|
|
szOrcptVal[0] = '\0';
|
|
|
|
|
|
if(!ExtractAndValidateRcpt(ToName, &ArgPtr, szRcptAddress, &DomainPtr ))
|
|
{
|
|
// If failed. Inform the user
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//Parse out the DSN values if any
|
|
//parse the arguments, if any
|
|
if(!DoRcptArgs (ArgPtr, szOrcptVal, &dwNotifyVal))
|
|
{
|
|
//it failed. Inform the user
|
|
DWORD dwError = GetLastError();
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS, " %s\r\n","Invalid arguments" );
|
|
m_CInboundContext.SetWin32Status(dwError);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//check to see if there is any room in the inn.
|
|
//if the remote queue entry is allocated, check
|
|
//that also.
|
|
DWORD TotalRcpts = 0;
|
|
hr = m_pIMsgRecips->Count(&TotalRcpts);
|
|
|
|
if( FAILED(hr) || ((m_pInstance->GetMaxRcpts() > 0) && ((TotalRcpts + 1) > m_pInstance->GetMaxRcpts())))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NOSTORAGE, ETOO_MANY_RCPTS, " %s\r\n",SMTP_TOO_MANY_RCPTS);
|
|
ErrorTrace((LPARAM) this, "Exceeded MaxRcpts %d", TotalRcpts + 1);
|
|
// don't count too many rcpts as protocol errors as this will
|
|
// close the connection.
|
|
//++m_ProtocolErrors;
|
|
delete NewAddress;
|
|
NewAddress = NULL;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//If the relay check feature is enabled make sure that this
|
|
//recipient is to be accepted for relay
|
|
if(m_pInstance->IsRelayEnabled())
|
|
{
|
|
hr = HrShouldAcceptRcpt(DomainPtr);
|
|
if ( S_OK == hr ) RelayThisMail = TRUE;
|
|
else if (S_FALSE == hr ) RelayThisMail = FALSE;
|
|
else
|
|
{
|
|
// Currently, HrShouldAcceptRcpt only return error when AQueue is already shut down or out of memory.
|
|
_ASSERT(FAILED(hr));
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_SRV_UNAVAIL, ESVC_SHUTDOWN," %s\r\n",SMTP_SRV_UNAVAIL_STR);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (m_pInstance->IsDropDirQuotaCheckingEnabled())
|
|
{
|
|
//We only enforce drop dir quota checking on local and alias domains. Since
|
|
//this is turned on by default, we assume that anyone who has setup there own
|
|
//non-default drop domain is has some agent the will process mail put in it.
|
|
if (m_pInstance->IsADefaultOrAliasDropDomain(DomainPtr))
|
|
DropQuotaExceeded = m_pInstance->IsDropDirQuotaExceeded();
|
|
}
|
|
|
|
ThisAddress = szRcptAddress;
|
|
if(RelayThisMail && !DropQuotaExceeded)
|
|
{
|
|
hr = m_pIMsgRecips->AddPrimary(1, (LPCTSTR *) &ThisAddress, &dwPropId,
|
|
&dwNewRecipIndex, NULL, 0);
|
|
if(!FAILED(hr))
|
|
{
|
|
//If we have any associated DSn values set them
|
|
if(szOrcptVal && szOrcptVal[0] != '\0')
|
|
{
|
|
hr = m_pIMsgRecips->PutStringA(dwNewRecipIndex, IMMPID_RP_DSN_ORCPT_VALUE,(LPCSTR)szOrcptVal);
|
|
if(FAILED(hr))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NOSTORAGE, ENO_RESOURCES, " %s\r\n",SMTP_NO_STORAGE);
|
|
ErrorTrace((LPARAM) this, "Exceeded MaxRcpts %d", TotalRcpts + 1);
|
|
}
|
|
}
|
|
|
|
//If we have any associated DSn values set them
|
|
//If we have any associated DSn values set them
|
|
if(!FAILED(hr) && dwNotifyVal & RP_DSN_NOTIFY_MASK)
|
|
{
|
|
//hr = m_pIMsgRecips->PutDWORD(dwNewRecipIndex, IMMPID_RP_DSN_NOTIFY_VALUE, dwNotifyVal);
|
|
hr = m_pIMsgRecips->PutDWORD(dwNewRecipIndex,IMMPID_RP_RECIPIENT_FLAGS, dwNotifyVal);
|
|
if(FAILED(hr))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NOSTORAGE, ENO_RESOURCES, " %s\r\n", SMTP_NO_STORAGE);
|
|
ErrorTrace((LPARAM) this, "Exceeded MaxRcpts %d", TotalRcpts + 1);
|
|
}
|
|
}
|
|
|
|
if(!FAILED(hr))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_OK, EVALID_DEST_ADDRESS, " %s \r\n",szRcptAddress);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NOSTORAGE, ENO_RESOURCES, " %s\r\n",SMTP_NO_STORAGE);
|
|
ErrorTrace((LPARAM) this, "Exceeded MaxRcpts %d", TotalRcpts + 1);
|
|
}
|
|
}
|
|
else if (DropQuotaExceeded)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_INSUFF_STORAGE_CODE, EMAILBOX_FULL, " Mailbox full\r\n");
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NOT_FOUND, ENO_FORWARDING, " Unable to relay for %s\r\n", szRcptAddress);
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::HrShouldAcceptRcpt
|
|
|
|
Description:
|
|
Check whether we should accept the recipients
|
|
|
|
Arguments:
|
|
szDomainName
|
|
|
|
Returns:
|
|
HRESULT:
|
|
S_OK: we should accept it
|
|
S_FALSE: we should not accept it
|
|
other error code:
|
|
Currently we only return error when HrGetDomainInfoFlags returns AQUEUE_E_SHUTDOWN ( fix for bug 214591 )
|
|
or E_OUTOFMEMORY
|
|
|
|
--*/
|
|
HRESULT SMTP_CONNECTION::HrShouldAcceptRcpt(char * szDomainName )
|
|
{
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::HrShouldAcceptRcpt");
|
|
HRESULT hr;
|
|
DWORD dwDomainInfoFlags;
|
|
|
|
hr = m_pInstance->HrGetDomainInfoFlags(szDomainName, &dwDomainInfoFlags);
|
|
if (SUCCEEDED(hr)) {
|
|
if ( (dwDomainInfoFlags & DOMAIN_INFO_LOCAL_MAILBOX) ||
|
|
(dwDomainInfoFlags & DOMAIN_INFO_ALIAS) ||
|
|
(dwDomainInfoFlags & DOMAIN_INFO_LOCAL_DROP)) {
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return S_OK;
|
|
}
|
|
else if(dwDomainInfoFlags & DOMAIN_INFO_DOMAIN_RELAY) {
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return S_OK;
|
|
}
|
|
else if( (m_fAuthenticated && (dwDomainInfoFlags & DOMAIN_INFO_AUTH_RELAY)) ||
|
|
DoesClientHaveIpAccess() ) {
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return S_OK;
|
|
}
|
|
}
|
|
else if (AQUEUE_E_SHUTDOWN == hr || E_OUTOFMEMORY == hr)
|
|
{
|
|
// special case: if Aqueue is already shutdown or we're out of memory, return with error
|
|
ErrorTrace((LPARAM) this, "HrGetDomainInfoFlags() failed with 0x%x", hr);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return hr;
|
|
}
|
|
else
|
|
{
|
|
DebugTrace((LPARAM) this, "HrGetDomainInfoFlags() failed with 0x%x", hr);
|
|
if(DoesClientHaveIpAccess() ||
|
|
(m_fAuthenticated && m_pInstance->RelayForAuthUsers())) {
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
//
|
|
// See if this connection is permitted to relay..
|
|
//
|
|
if (m_fMayRelay) {
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return S_OK;
|
|
}
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return S_FALSE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::ExtractAndValidateRcpt(char * ToName, char ** ppArgument, char * szRcptAddress, char ** ppDomain )
|
|
{
|
|
DWORD dwAddressLen = 0;
|
|
DWORD dwCanonAddrLen = 0;
|
|
|
|
char * AddrPtr = NULL;
|
|
char * CanonAddrPtr = NULL;
|
|
|
|
//Parse out the address and possible argumets from the input line
|
|
if(!Extract821AddressFromLine(ToName,&AddrPtr,&dwAddressLen,ppArgument))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
//Check if we have valid tail and address
|
|
if(!AddrPtr)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
//Extract the canonical address in the <local-part> "@" <domain> form
|
|
if(!ExtractCanonical821Address(AddrPtr,dwAddressLen,&CanonAddrPtr,&dwCanonAddrLen))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
//If we have a Canonical addr - validate it
|
|
if(CanonAddrPtr)
|
|
{
|
|
strncpy(szRcptAddress,CanonAddrPtr,dwCanonAddrLen);
|
|
*(szRcptAddress + dwCanonAddrLen) = '\0';
|
|
|
|
if(!Validate821Address(szRcptAddress, dwCanonAddrLen))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
//Extract
|
|
if(!Get821AddressDomain(szRcptAddress,dwCanonAddrLen,ppDomain))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!(*ppDomain) && !ISNULLADDRESS(szRcptAddress))
|
|
{
|
|
//If there is no domain on this address,
|
|
//then append the current domain to this
|
|
//address.
|
|
if(!m_pInstance->AppendLocalDomain (szRcptAddress))
|
|
{
|
|
return FALSE;
|
|
}
|
|
//Update the DomainPtr to be after the '@' sign
|
|
//we could be safer and do a strchr
|
|
(*ppDomain) = szRcptAddress + dwCanonAddrLen + 1;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::BindToStoreDrive(void)
|
|
{
|
|
SMTP_ALLOC_PARAMS AllocParams;
|
|
DWORD Error = 0;
|
|
BOOL fRet = FALSE;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::BindToStore");
|
|
|
|
AllocParams.BindInterfacePtr = (PVOID) m_pBindInterface;
|
|
AllocParams.IMsgPtr = (PVOID) m_pIMsg;
|
|
AllocParams.hContent = NULL;
|
|
AllocParams.hr = S_OK;
|
|
//For client context pass in something that will stay around the lifetime of the
|
|
//atqcontext -
|
|
AllocParams.pAtqClientContext = m_pInstance;
|
|
|
|
if(m_pInstance->AllocNewMessage (&AllocParams) && !FAILED(AllocParams.hr))
|
|
{
|
|
m_IMsgHandle = AllocParams.hContent;
|
|
|
|
if(WriteRcvHeader())
|
|
{
|
|
fRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Error = GetLastError();
|
|
//ProtocolLog(, (char *)InputLine, Error, SMTP_RESP_NORESOURCES, 0, 0);
|
|
FatalTrace((LPARAM) this, "WriteRcvHeader failed %d", Error);
|
|
SetLastError(Error);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FatalTrace((LPARAM) this,
|
|
"AllocNewMessage failed hr 0x%08X, GetLastError %d",
|
|
AllocParams.hr, GetLastError());
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fRet;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::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_CONNECTION::DoDATACommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD TotalRcpts = 0;
|
|
DWORD fIsBinaryMime = 0;
|
|
DWORD Error = 0;
|
|
|
|
_ASSERT(m_pInstance != NULL);
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoDATACommand");
|
|
|
|
//If the current state is BDAT the only command that can be received is BDAT
|
|
if(m_State == BDAT)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n","BDAT Expected" );
|
|
ProtocolLog(DATA, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - BDAT Expected");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(m_pIMsgRecips && m_pIMsg)
|
|
{
|
|
hr = m_pIMsgRecips->Count(&TotalRcpts);
|
|
|
|
if(!FAILED(hr))
|
|
{
|
|
m_pIMsg->PutDWORD( IMMPID_MP_NUM_RECIPIENTS, TotalRcpts );
|
|
|
|
hr = m_pIMsg->GetDWORD(IMMPID_MP_BINARYMIME_OPTION, &fIsBinaryMime);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Cannot use the DATA command if the body type was BINARYMIME
|
|
if(!FAILED(hr) && fIsBinaryMime)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n", "Body type BINARYMIME requires BDAT" );
|
|
ProtocolLog(DATA, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_ARGS - Body type BINARYMIME requires BDAT");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
}
|
|
|
|
//Only save data if HELO was sent and there is at least one recipient.
|
|
//send a message stating "no good recepients",
|
|
//or something like that if both lists are empty.
|
|
else if(!m_HelloSent)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n", "Send hello first" );
|
|
ProtocolLog(DATA, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - Send hello first");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
}
|
|
else if(!m_fAuthAnon && !m_fAuthenticated)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY, " %s\r\n", "Client was not authenticated");
|
|
ProtocolLog(DATA, (char *) InputLine, NO_ERROR, SMTP_RESP_MUST_SECURE, 0, 0);
|
|
ErrorTrace((LPARAM) this, "DoDataCommand - SMTP_RESP_MUST_SECURE, user not authenticated");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else if (!m_RecvdMailCmd || !m_pIMsg)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n","Need mail command." );
|
|
ProtocolLog(DATA, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - Need mail command.");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
}
|
|
else if(TotalRcpts)
|
|
{
|
|
m_cbCurrentWriteBuffer = 0;
|
|
if(BindToStoreDrive())
|
|
{
|
|
m_State = DATA;
|
|
if(!m_pInstance->ShouldParseHdrs())
|
|
{
|
|
m_TimeToRewriteHeader = FALSE;
|
|
m_InHeader = FALSE;
|
|
}
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_START, NULL," %s\r\n",SMTP_START_MAIL_STR);
|
|
}
|
|
else
|
|
{
|
|
DWORD dwErr = GetLastError();
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_MEMORY);
|
|
m_CInboundContext.SetWin32Status(ERROR_NOT_ENOUGH_MEMORY);
|
|
ProtocolLog(DATA, (char *)InputLine, ERROR_NOT_ENOUGH_MEMORY, SMTP_RESP_NORESOURCES, 0, 0);
|
|
SmtpLogEventEx(SMTP_EVENT_CANNOT_CREATE_FILE, m_pInstance->GetMailQueueDir(), dwErr);
|
|
ErrorTrace((LPARAM) this, "DoDataCommand - SMTP_RESP_NORESOURCES, SMTP_NO_MEMORY - MailInfo = new MAILQ_ENTRY () failed");
|
|
}
|
|
}
|
|
else if (!m_RecvdRcptCmd)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", "Need Rcpt command." );
|
|
ProtocolLog(DATA, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - Need Rcpt command.");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_TRANS_FAILED, ESYNTAX_ERROR," %s\r\n", SMTP_NO_VALID_RECIPS );
|
|
ProtocolLog(DATA, (char *) InputLine, NO_ERROR, SMTP_RESP_TRANS_FAILED, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_TRANS_FAILED, SMTP_NO_VALID_RECIPS");
|
|
++m_ProtocolErrors;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoBDATCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP BDAT command. Parses to check if this
|
|
is the last BDAT.
|
|
|
|
Error processing for BDAT is more complex than other SMTP commands
|
|
because the RFC forbids a response to BDAT (even error responses that
|
|
the server cannot process BDAT). If we try to send an error response
|
|
immediately after a BDAT command, we will go out of sync with the
|
|
client because the client is not expecting a response... it is trying
|
|
to send us chunk data, and the client expects us to have posted a TCP
|
|
read to receive the chunk data.
|
|
|
|
Errors handled by this function fall into 2 categories:
|
|
|
|
(1) Errors by the client, such as sending improperly formatted
|
|
chunksizes, bad syntax for the BDAT command.
|
|
|
|
(2) Errors such as failure to allocate memory, restrictions on session
|
|
size and message size, client must authenticate first, client must
|
|
negotiate TLS first, etc.
|
|
|
|
In the first case, we can reject the command with an error response,
|
|
if the BDAT command is garbled, there's not much we can do.
|
|
|
|
|
|
In the second case, we can generate a reasonable error response to be
|
|
sent to the client. We should wait for our turn before doing so, i.e.
|
|
we should accept and discard the BDAT chunks sent by the client, and
|
|
when it it time to ack the chunk, then we send the error response. This
|
|
is implemented by setting the m_MailBodyDiagnostic to the appropriate error, and
|
|
calling AcceptAndDiscardBDAT which handles receiving and discarding
|
|
chunk data and generating error responses when appropriate.
|
|
|
|
Arguments:
|
|
|
|
InputLine - Buffer received from client
|
|
paramterSize - amount of data in buffer
|
|
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoBDATCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD TotalRcpts = 0;
|
|
MailBodyDiagnosticCode mailBodyDiagnostic = ERR_NONE;
|
|
|
|
_ASSERT(m_pInstance != NULL);
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoBDATCommand");
|
|
|
|
if(m_pIMsgRecips && m_pIMsg)
|
|
{
|
|
hr = m_pIMsgRecips->Count(&TotalRcpts);
|
|
m_pIMsg->PutDWORD( IMMPID_MP_NUM_RECIPIENTS, TotalRcpts );
|
|
}
|
|
|
|
//We parse this command ONLY if we advertise either chunking or binarymime
|
|
if(!m_pInstance->AllowChunking() && !m_pInstance->AllowBinaryMime())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_CMD, EINVALID_COMMAND," %s\r\n", SMTP_NOT_IMPL_STR);
|
|
ErrorTrace((LPARAM) this, "DoBDATCommand - SMTP_RESP_BAD_CMD, SMTP_NOT_IMPL_STR");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(!m_HelloSent)
|
|
{
|
|
ProtocolLog(BDAT, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - Send hello first");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
mailBodyDiagnostic = ERR_HELO_NEEDED;
|
|
}
|
|
else if(!m_fAuthAnon && !m_fAuthenticated)
|
|
{
|
|
ProtocolLog(BDAT, (char *) InputLine, NO_ERROR, SMTP_RESP_MUST_SECURE, 0, 0);
|
|
ErrorTrace((LPARAM) this, "DoBDATCommand - SMTP_RESP_MUST_SECURE, user not authenticated");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
mailBodyDiagnostic = ERR_AUTH_NEEDED;
|
|
}
|
|
else if (!m_RecvdMailCmd || !m_pIMsg)
|
|
{
|
|
ProtocolLog(BDAT, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - Need mail command.");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
mailBodyDiagnostic = ERR_MAIL_NEEDED;
|
|
}
|
|
else if (!m_RecvdRcptCmd)
|
|
{
|
|
ProtocolLog(BDAT, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_BAD_SEQ - Need Rcpt command.");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
mailBodyDiagnostic = ERR_RCPT_NEEDED;
|
|
}
|
|
else if (FAILED(hr) || 0 == TotalRcpts)
|
|
{
|
|
ProtocolLog(BDAT, (char *) InputLine, NO_ERROR, SMTP_RESP_TRANS_FAILED, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_TRANS_FAILED, SMTP_NO_VALID_RECIPS");
|
|
++m_ProtocolErrors;
|
|
mailBodyDiagnostic = ERR_NO_VALID_RCPTS;
|
|
}
|
|
|
|
char * Argument = (char *) InputLine;
|
|
DWORD dwEstMsgSize = 0;
|
|
DWORD dwEstSessionSize = 0;
|
|
|
|
m_nChunkSize = 0;
|
|
m_nBytesRemainingInChunk = 0;
|
|
|
|
// Just parse for size and if this is last chunk
|
|
Argument = CheckArgument(Argument);
|
|
if (Argument == NULL)
|
|
{
|
|
ProtocolLog(BDAT, (char *)InputLine, NO_ERROR, SMTP_RESP_MBX_SYNTAX, 0,0);
|
|
ErrorTrace((LPARAM) this, "DoBDATCommand - SMTP_RESP_MBX_SYNTAX, Client sent no CHUNK SIZE");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
// make sure the argument is all digits
|
|
BOOL fBadChunkSize = FALSE;
|
|
for( int i=0; ( ( Argument[i] != 0 ) && ( Argument[i] != ' ' ) ) ; i++ )
|
|
{
|
|
if( !isdigit( (UCHAR) Argument[i] ) )
|
|
{
|
|
fBadChunkSize = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Get the chunk size
|
|
m_nChunkSize = atoi (Argument);
|
|
m_nBytesRemainingInChunk = m_nChunkSize + m_cbTempBDATLen;
|
|
|
|
if(m_nChunkSize < 0)
|
|
{
|
|
fBadChunkSize = TRUE;
|
|
}
|
|
else if(m_nChunkSize == 0 && m_State != BDAT)
|
|
{
|
|
// Chunk size may be 0, but not on the first chunk
|
|
fBadChunkSize = TRUE;
|
|
}
|
|
|
|
if( fBadChunkSize )
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MBX_SYNTAX, ESYNTAX_ERROR," %s\r\n","Invalid CHUNK size value" );
|
|
ProtocolLog(BDAT, (char *)InputLine, NO_ERROR, SMTP_RESP_MBX_SYNTAX, 0,0);
|
|
ErrorTrace((LPARAM) this, "DoBDATCommand - SMTP_RESP_MBX_SYNTAX, Client sent 0 as CHUNK SIZE");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
// Add this to the mail already received
|
|
// check to make sure we are not going over the servers max message size
|
|
|
|
dwEstSessionSize = m_nBytesRemainingInChunk + m_SessionSize;
|
|
dwEstMsgSize = m_nBytesRemainingInChunk + m_TotalMsgSize;
|
|
|
|
if(ERR_NONE == mailBodyDiagnostic)
|
|
{
|
|
if((m_pInstance->GetMaxMsgSize() > 0) && (dwEstMsgSize > m_pInstance->GetMaxMsgSize()))
|
|
{
|
|
BOOL fShouldImposeLimit = TRUE;
|
|
if( FAILED( m_pInstance->TriggerMaxMsgSizeEvent( GetSessionPropertyBag(), m_pIMsg, &fShouldImposeLimit ) ) || fShouldImposeLimit )
|
|
{
|
|
ProtocolLog(BDAT, (char *)InputLine, NO_ERROR, SMTP_RESP_NOSTORAGE, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_NOSTORAGE, SMTP_MAX_MSG_SIZE_EXCEEDED_MSG - %d", dwEstMsgSize);
|
|
++m_ProtocolErrors;
|
|
BUMP_COUNTER(QuerySmtpInstance(), MsgsRefusedDueToSize);
|
|
mailBodyDiagnostic = ERR_MAX_MSG_SIZE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check that we are below the max session size allowed on the Server.
|
|
if((m_pInstance->GetMaxMsgSizeBeforeClose() > 0) && (dwEstSessionSize > m_pInstance->GetMaxMsgSizeBeforeClose()))
|
|
{
|
|
BOOL fShouldImposeLimit = TRUE;
|
|
if( FAILED( m_pInstance->TriggerMaxMsgSizeEvent( GetSessionPropertyBag(), m_pIMsg, &fShouldImposeLimit ) ) || fShouldImposeLimit )
|
|
{
|
|
ProtocolLog(BDAT, (char *)InputLine, NO_ERROR, SMTP_RESP_NOSTORAGE, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_NOSTORAGE, SMTP_MAX_SESSION_SIZE_EXCEEDED_MSG - %d", dwEstSessionSize);
|
|
BUMP_COUNTER(QuerySmtpInstance(), MsgsRefusedDueToSize);
|
|
|
|
// Client won't be expecting this, but atleast an admin can tell
|
|
// what's wrong from this response.
|
|
FormatSmtpMessage(SMTP_RESP_NOSTORAGE, ENO_RESOURCES, " %s\r\n",
|
|
SMTP_MAX_SESSION_SIZE_EXCEEDED_MSG);
|
|
SendSmtpResponse();
|
|
DisconnectClient();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//Check for the presence of the LAST keyword
|
|
Argument = strchr(Argument,' ');
|
|
if(Argument != NULL)
|
|
{
|
|
//get rid of white space
|
|
while(isspace((UCHAR)*Argument))
|
|
Argument++;
|
|
|
|
if(*Argument != '\0')
|
|
{
|
|
if(!_strnicmp(Argument,(char *)"LAST",4))
|
|
{
|
|
//This is the last BDAT chunk
|
|
m_fIsLastChunk = TRUE;
|
|
}
|
|
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MBX_SYNTAX, ESYNTAX_ERROR," %s\r\n", "Invalid CHUNK size value" );
|
|
ProtocolLog(BDAT, (char *)InputLine, NO_ERROR, SMTP_RESP_MBX_SYNTAX, 0,0);
|
|
ErrorTrace((LPARAM) this, "DoBDATCommand - SMTP_RESP_MBX_SYNTAX, Client sent 0 as CHUNK SIZE");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only the last chunk is allowed to have size = 0
|
|
if(!m_fIsLastChunk && m_nChunkSize == 0)
|
|
{
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_MBX_SYNTAX, ESYNTAX_ERROR," %s\r\n",
|
|
"Invalid CHUNK size value" );
|
|
ProtocolLog(BDAT, (char *)InputLine, NO_ERROR, SMTP_RESP_MBX_SYNTAX, 0,0);
|
|
ErrorTrace((LPARAM) this,
|
|
"DoBDATCommand - SMTP_RESP_MBX_SYNTAX, Client sent 0 as CHUNK SIZE");
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
m_fIsChunkComplete = FALSE;
|
|
|
|
if(m_State != BDAT)
|
|
{
|
|
//we are here for the first time
|
|
m_State = BDAT;
|
|
m_cbCurrentWriteBuffer = 0;
|
|
|
|
// If there was an error while processing this BDAT command, flag it (m_MailBodyDiagnostic) & exit.
|
|
m_MailBodyDiagnostic = mailBodyDiagnostic;
|
|
|
|
if(mailBodyDiagnostic != ERR_NONE)
|
|
goto Exit;
|
|
|
|
if(!BindToStoreDrive())
|
|
{
|
|
//
|
|
// Cannot process BDAT due to error accessing mail-store. Flag the error & exit.
|
|
//
|
|
|
|
m_MailBodyError = ERROR_NOT_ENOUGH_MEMORY;
|
|
m_MailBodyDiagnostic = ERR_OUT_OF_MEMORY;
|
|
m_CInboundContext.SetWin32Status(ERROR_NOT_ENOUGH_MEMORY);
|
|
ProtocolLog(BDAT, (char *)InputLine, ERROR_NOT_ENOUGH_MEMORY, SMTP_RESP_NORESOURCES, 0, 0);
|
|
ErrorTrace((LPARAM) this, "DoBDATCommand - SMTP_RESP_NORESOURCES, SMTP_NO_MEMORY - Allocating stream failed");
|
|
SmtpLogEventEx(SMTP_EVENT_INVALID_MAIL_QUEUE_DIR , "Invalid Mail Queue Directory", GetLastError());
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there was an error during this BDAT, flag it... but if there was an error in a
|
|
// previous BDAT command (within the same group of BDATs), preserve it. Once a BDAT fails,
|
|
// all subsequent BDATs will fail. m_MailBodyDiagnostic is reset only when a new BDAT group is
|
|
// started (the first BDAT in a group of BDATs or RSET).
|
|
//
|
|
|
|
if(m_MailBodyDiagnostic == ERR_NONE) // Don't overwrite
|
|
m_MailBodyDiagnostic = mailBodyDiagnostic; // Error during this BDAT
|
|
|
|
if(m_MailBodyDiagnostic != ERR_NONE) // Should this BDAT succeed?
|
|
goto Exit;
|
|
|
|
|
|
// There is no response for BDAT command
|
|
if(!m_pInstance->ShouldParseHdrs())
|
|
{
|
|
m_TimeToRewriteHeader = FALSE;
|
|
m_InHeader = FALSE;
|
|
}
|
|
|
|
Exit:
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
void SMTP_CONNECTION::ReleasRcptList(void)
|
|
{
|
|
IMailMsgRecipients * pRcptList = NULL;
|
|
IMailMsgRecipientsAdd * pRcptListAdd = NULL;
|
|
|
|
pRcptListAdd = (IMailMsgRecipientsAdd *) InterlockedExchangePointer((PVOID *) &m_pIMsgRecips, (PVOID) NULL);
|
|
if (pRcptListAdd != NULL)
|
|
{
|
|
pRcptListAdd->Release();
|
|
}
|
|
|
|
pRcptList = (IMailMsgRecipients *) InterlockedExchangePointer((PVOID *) &m_pIMsgRecipsTemp, (PVOID) NULL);
|
|
if (pRcptList != NULL)
|
|
{
|
|
pRcptList->Release();
|
|
}
|
|
}
|
|
|
|
void SMTP_CONNECTION::ReleasImsg(BOOL DeleteIMsg)
|
|
{
|
|
IMailMsgProperties * pMsg = NULL;
|
|
IMailMsgQueueMgmt * pMgmt = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
pMsg = (IMailMsgProperties *) InterlockedExchangePointer((PVOID *) &m_pIMsg, (PVOID) NULL);
|
|
if (pMsg != NULL)
|
|
{
|
|
ReleasRcptList();
|
|
|
|
if(m_pBindInterface)
|
|
{
|
|
//if(DeleteIMsg)
|
|
//{
|
|
// m_pBindAtqInterface->ReleaseATQHandle();
|
|
//}
|
|
|
|
m_pBindInterface->Release();
|
|
}
|
|
|
|
if(DeleteIMsg)
|
|
{
|
|
hr = pMsg->QueryInterface(IID_IMailMsgQueueMgmt, (void **)&pMgmt);
|
|
if(!FAILED(hr))
|
|
{
|
|
pMgmt->Delete(NULL);
|
|
pMgmt->Release();
|
|
}
|
|
}
|
|
|
|
pMsg->Release();
|
|
}
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::HandleCompletedMessage
|
|
|
|
Description:
|
|
|
|
This function gets called when the client
|
|
sends the CRLF.CRLF sequence saying the
|
|
message is complete. The function truncates
|
|
the message to the current size, if the client
|
|
lied and gave us a larger size in the FROM command,
|
|
and them queues the message to the Local/Remote
|
|
queues for processing.
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Returns:
|
|
|
|
TRUE if the message was written to disk and queued.
|
|
FALSE in all other cases.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::HandleCompletedMessage(DWORD dwCommand, BOOL *pfAsyncOp)
|
|
{
|
|
|
|
DWORD AbOffset = 0;
|
|
char MessageId[1024];
|
|
HRESULT hr = S_OK;
|
|
DWORD TrailerSize = 0; //used to strip out the trailing ".CRLF"
|
|
BOOL fPended = FALSE;
|
|
BOOL fQRet = TRUE;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::HandleCompletedMessage(void)");
|
|
|
|
_ASSERT(m_pInstance != NULL);
|
|
|
|
//hack to make transaction logging work
|
|
AddCommandBytesRecv(m_TotalMsgSize);
|
|
|
|
MessageId[0] = 0;
|
|
|
|
if( m_pIMsg )
|
|
{
|
|
m_pIMsg->GetStringA( IMMPID_MP_RFC822_MSG_ID, sizeof( MessageId ), MessageId );
|
|
m_pIMsg->PutDWORD( IMMPID_MP_MSG_SIZE_HINT, m_TotalMsgSize );
|
|
}
|
|
|
|
MessageId[sizeof(MessageId)-1] = 0; // NULL terminate it.
|
|
|
|
|
|
//Depending on dwCommand the TrailerSize will vary
|
|
//For BDAT = 0
|
|
//For DATA = 3
|
|
if(dwCommand == BDAT )
|
|
TrailerSize = 0;
|
|
|
|
DWORD cbTotalMsgSize = 0;
|
|
|
|
if( m_fFoundEmbeddedCrlfDotCrlf )
|
|
{
|
|
cbTotalMsgSize = m_cbDotStrippedTotalMsgSize;
|
|
}
|
|
else
|
|
{
|
|
cbTotalMsgSize = m_TotalMsgSize;
|
|
}
|
|
|
|
if(SetFilePointer(m_IMsgHandle->m_hFile, (cbTotalMsgSize + m_HeaderSize) - TrailerSize, NULL, FILE_BEGIN) == 0xFFFFFFFF)
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
//FatalTrace((LPARAM) this, "SetFilePointer failed on file %s !!! (err=%d)", MailInfo->GetMailFileName(), m_MailBodyError);
|
|
m_CInboundContext.SetWin32Status(m_MailBodyError);
|
|
ProtocolLog(dwCommand, MessageId, m_MailBodyError, SMTP_RESP_NORESOURCES, 0, 0);
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_STORAGE );
|
|
SendSmtpResponse();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!SetEndOfFile(m_IMsgHandle->m_hFile))
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
// FatalTrace((LPARAM) this, "SetEndOfFile failed on file %s !!! (err=%d)", MailInfo->GetMailFileName(), m_MailBodyError);
|
|
m_CInboundContext.SetWin32Status(m_MailBodyError);
|
|
ProtocolLog(dwCommand, MessageId, m_MailBodyError, SMTP_RESP_NORESOURCES, 0, 0);
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_STORAGE );
|
|
SendSmtpResponse();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
hr = m_pIMsgRecipsTemp->WriteList(m_pIMsgRecips);
|
|
if(FAILED(hr))
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
//FatalTrace((LPARAM) this, "SetEndOfFile failed on file %s !!! (err=%d)", MailInfo->GetMailFileName(), m_MailBodyError);
|
|
m_CInboundContext.SetWin32Status(m_MailBodyError);
|
|
ProtocolLog(dwCommand, MessageId, m_MailBodyError, SMTP_RESP_NORESOURCES, 0, 0);
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_STORAGE );
|
|
SendSmtpResponse();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
m_pIMsg->PutDWORD( IMMPID_MP_SCANNED_FOR_CRLF_DOT_CRLF, m_fScannedForCrlfDotCrlf );
|
|
m_pIMsg->PutDWORD( IMMPID_MP_FOUND_EMBEDDED_CRLF_DOT_CRLF, m_fFoundEmbeddedCrlfDotCrlf );
|
|
|
|
hr = m_pIMsg->Commit(NULL);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
//FatalTrace((LPARAM) this, "SetEndOfFile failed on file %s !!! (err=%d)", MailInfo->GetMailFileName(), m_MailBodyError);
|
|
m_CInboundContext.SetWin32Status(m_MailBodyError);
|
|
ProtocolLog(dwCommand, MessageId, m_MailBodyError, SMTP_RESP_NORESOURCES, 0, 0);
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_STORAGE );
|
|
SendSmtpResponse();
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
ReleasRcptList();
|
|
|
|
// Fire the end of data event. The default implementation of this
|
|
// commits the message
|
|
char szVerb[] = "_EOD";
|
|
DWORD CmdSize;
|
|
|
|
m_dwCurrentCommand = SmtpGetCommand(szVerb, sizeof(szVerb), &CmdSize);
|
|
BOOL fReturn =
|
|
GlueDispatch((char *)szVerb, sizeof(szVerb), CmdSize, pfAsyncOp);
|
|
if (!fReturn) {
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
if (*pfAsyncOp) {
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
SendSmtpResponse();
|
|
|
|
ProtocolLog(dwCommand, MessageId, NO_ERROR, SMTP_RESP_OK, 0, 0);
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
BOOL SMTP_CONNECTION::RewriteHeader
|
|
|
|
Description:
|
|
|
|
This function adds a message ID, and other
|
|
headers if they are missing
|
|
|
|
Arguments:
|
|
OUT BOOL *lfWritePended - Set to TRUE is an async write was
|
|
pended.
|
|
|
|
Returns:
|
|
|
|
FALSE if WriteFile failed
|
|
TRUE otherwise
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::RewriteHeader(BOOL *lpfWritePended)
|
|
{
|
|
char Buffer [MAX_REWRITTEN_HEADERS];
|
|
char szMsgId[MAX_REWRITTEN_MSGID];
|
|
char MessageId [REWRITTEN_GEN_MSGID_BUFFER];
|
|
char szSmtpFromAddress[MAX_INTERNET_NAME + 6] = "smtp:";
|
|
char szOriginalArrivalTime[64];
|
|
int NumToWrite;
|
|
int MsgIdSize = 0;
|
|
int CurrentRewriteBufferSize = 0;
|
|
|
|
*lpfWritePended = FALSE;
|
|
|
|
//***NOTE***
|
|
//If you add *anything* to this function, make sure you update MAX_REWRITTEN_HEADERS
|
|
//to support allow room for this as well.
|
|
|
|
//Rewrite header creates its own buffer and writes out
|
|
|
|
//add the mail from field if we did not see it.
|
|
if(!(m_HeaderFlags & H_FROM))
|
|
{
|
|
_ASSERT(m_szFromAddress[0] != '\0');
|
|
|
|
NumToWrite = sprintf(Buffer + CurrentRewriteBufferSize, "From: %s\r\n", m_szFromAddress);
|
|
CurrentRewriteBufferSize += NumToWrite;
|
|
m_pIMsg->PutStringA( IMMPID_MP_RFC822_FROM_ADDRESS, m_szFromAddress );
|
|
strncat(szSmtpFromAddress, m_szFromAddress, MAX_INTERNET_NAME);
|
|
}
|
|
|
|
_ASSERT(MAX_REWRITTEN_HEADERS > CurrentRewriteBufferSize);
|
|
|
|
if(!(m_HeaderFlags & H_RCPT))
|
|
{
|
|
NumToWrite = sprintf(Buffer + CurrentRewriteBufferSize, "Bcc:\r\n");
|
|
CurrentRewriteBufferSize += NumToWrite;
|
|
}
|
|
|
|
_ASSERT(MAX_REWRITTEN_HEADERS > CurrentRewriteBufferSize);
|
|
//add the return path field if we did not see it.
|
|
if(!(m_HeaderFlags & H_RETURNPATH))
|
|
{
|
|
_ASSERT(m_szFromAddress[0] != '\0');
|
|
|
|
NumToWrite = sprintf(Buffer + CurrentRewriteBufferSize, "Return-Path: %s\r\n", m_szFromAddress);
|
|
CurrentRewriteBufferSize += NumToWrite;
|
|
}
|
|
|
|
//add the message ID field if we did not see it.
|
|
_ASSERT(MAX_REWRITTEN_HEADERS > CurrentRewriteBufferSize);
|
|
if(!(m_HeaderFlags & H_MID))
|
|
{
|
|
GenerateMessageId (MessageId, sizeof(MessageId));
|
|
|
|
m_pInstance->LockGenCrit();
|
|
MsgIdSize = sprintf( szMsgId, "<%s%8.8x@%s>", MessageId, GetIncreasingMsgId(), m_pInstance->GetFQDomainName());
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
_ASSERT(MsgIdSize < MAX_REWRITTEN_MSGID);
|
|
|
|
NumToWrite = sprintf(Buffer + CurrentRewriteBufferSize, "Message-ID: %s\r\n", szMsgId);
|
|
szMsgId[sizeof(szMsgId)-1]=0;
|
|
|
|
CurrentRewriteBufferSize += NumToWrite;
|
|
}
|
|
|
|
if( !( m_HeaderFlags & H_X_ORIGINAL_ARRIVAL_TIME ) )
|
|
{
|
|
|
|
GetSysAndFileTimeAsString( szOriginalArrivalTime );
|
|
|
|
NumToWrite = sprintf(Buffer + CurrentRewriteBufferSize, "X-OriginalArrivalTime: %s\r\n", szOriginalArrivalTime);
|
|
szOriginalArrivalTime[sizeof(szOriginalArrivalTime)-1]=0;
|
|
|
|
CurrentRewriteBufferSize += NumToWrite;
|
|
}
|
|
|
|
|
|
|
|
//add the Date field if we did not see it.
|
|
_ASSERT(MAX_REWRITTEN_HEADERS > CurrentRewriteBufferSize);
|
|
if(!(m_HeaderFlags & H_DATE))
|
|
{
|
|
char szDateBuf [cMaxArpaDate];
|
|
|
|
GetArpaDate(szDateBuf);
|
|
NumToWrite = sprintf(Buffer + CurrentRewriteBufferSize, "Date: %s\r\n", szDateBuf);
|
|
CurrentRewriteBufferSize += NumToWrite;
|
|
}
|
|
|
|
//Did we see the seperator. If not add one
|
|
//blank line to the message
|
|
_ASSERT(MAX_REWRITTEN_HEADERS > CurrentRewriteBufferSize);
|
|
if(!(m_HeaderFlags & H_EOH))
|
|
{
|
|
sprintf(Buffer + CurrentRewriteBufferSize,"\r\n", 2);
|
|
CurrentRewriteBufferSize += 2;
|
|
|
|
}
|
|
|
|
_ASSERT(MAX_REWRITTEN_HEADERS > CurrentRewriteBufferSize);
|
|
|
|
|
|
if(CurrentRewriteBufferSize)
|
|
{
|
|
//Write out the data
|
|
if(!WriteMailFile(Buffer, CurrentRewriteBufferSize, lpfWritePended))
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
return FALSE;
|
|
}
|
|
else if(!*lpfWritePended)
|
|
{
|
|
//
|
|
// Update the header size and message-properties if the data got copied
|
|
// to the write-buffer. If a write was pended (because the write buffer is
|
|
// currently full), then we will do nothing, and ATQ will call us back when
|
|
// the write completes. This code-path will be called again.
|
|
//
|
|
m_HeaderSize += CurrentRewriteBufferSize;
|
|
|
|
if(!(m_HeaderFlags & H_FROM))
|
|
m_pIMsg->PutStringA( IMMPID_MP_FROM_ADDRESS, szSmtpFromAddress );
|
|
|
|
if(!(m_HeaderFlags & H_MID))
|
|
{
|
|
if( FAILED( m_pIMsg->PutStringA( IMMPID_MP_RFC822_MSG_ID, szMsgId ) ) )
|
|
return FALSE;
|
|
}
|
|
|
|
if(!(m_HeaderFlags & H_X_ORIGINAL_ARRIVAL_TIME))
|
|
{
|
|
if( FAILED( m_pIMsg->PutStringA(
|
|
IMMPID_MP_ORIGINAL_ARRIVAL_TIME,
|
|
szOriginalArrivalTime ) ) )
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::CreateMailBody
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP data command.
|
|
This funcion spools the mail to a
|
|
directory
|
|
Arguments:
|
|
|
|
InputLine - Buffer received from client
|
|
paramterSize - amount of data in buffer
|
|
UndecryptedTailSize -- amount of undecrypted data at end of buffer
|
|
Returns:
|
|
|
|
TRUE if all data(incliding terminating .)
|
|
has been received
|
|
FALSE on all errors (disk full, etc.)
|
|
|
|
--*/
|
|
|
|
BOOL SMTP_CONNECTION::CreateMailBody(char * InputLine, DWORD ParameterSize,
|
|
DWORD UndecryptedTailSize, BOOL *lfWritePended)
|
|
{
|
|
BOOL MailDone = FALSE;
|
|
DWORD IntermediateSize = 0;
|
|
PCHAR pszSearch = NULL;
|
|
DWORD TotalMsgSize = 0;
|
|
char c1, c2;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::CreateMailBody");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
_ASSERT (m_cbParsable <= QueryMaxReadSize());
|
|
|
|
|
|
// 7/2/99 MILANS
|
|
// The following if statement is completely unnecessary. One should not adjust the line
|
|
// state at the beginning of this function - rather, they should do so as a line is scanned
|
|
// and, for the final (potentially partial) line at the end, when they move the (partial) line
|
|
// as the last step in this function.
|
|
// Nevertheless, in the light of the time, I am leaving the if statement in place.
|
|
|
|
//the state of the line completion parsing automaton is to be set to the initial state if
|
|
//for each line except the following case:
|
|
|
|
//if the buffer was full in the last iteration --- the data got shifted out and now we are
|
|
//parsing the newly read data. so the state of the line completion automaton must
|
|
if(!m_BufrWasFull)
|
|
m_LineCompletionState = SEEN_NOTHING; //automaton state for IsLineCompleteRFC822
|
|
|
|
m_BufrWasFull = FALSE;
|
|
while ((pszSearch = IsLineCompleteRFC822(InputLine, m_cbParsable, UndecryptedTailSize, &m_BufrWasFull)) != NULL)
|
|
{
|
|
//If the buffer is full and we are not using the entire buffer; then some of the data in the full
|
|
//buffer is data that we have already parsed and processed in preceding iterations. we can discard
|
|
//this data and read in fresh data which may have the CRLFx we are looking for. to do this, break
|
|
//out of this loop, move inputline to the beginning of buffer (done outside loop) and just return.
|
|
//a read is pended and this function is called with freshly read data appended to the "inadequate
|
|
//data". If even then the no CRLFx is found, and the buffer is filled up, we need to start truncation.
|
|
|
|
if(m_BufrWasFull && InputLine > QueryMRcvBuffer())
|
|
break;
|
|
|
|
_ASSERT (m_cbParsable <= QueryMaxReadSize());
|
|
|
|
//stuff in CRLF if it wasn't there
|
|
if(!m_Truncate && m_BufrWasFull)
|
|
{
|
|
c1 = *pszSearch;
|
|
c2 = *(pszSearch+1);
|
|
*pszSearch = CR;
|
|
*(pszSearch+1) = LF;
|
|
}
|
|
|
|
IntermediateSize = (DWORD)(pszSearch - InputLine);
|
|
TotalMsgSize = m_TotalMsgSize + (IntermediateSize + 2);
|
|
|
|
//don't write the '.' to the file
|
|
if( (InputLine [0] == '.') && (IntermediateSize == 1) && !m_LongBodyLine )
|
|
{
|
|
//the minimum message size is 3 (.CRLF). If we are done, and the size
|
|
//of the message is 3 bytes, that means that the body of the message
|
|
//is missing. So, just write the headers and go.
|
|
if(TotalMsgSize == 3 && m_TimeToRewriteHeader)
|
|
{
|
|
if(!RewriteHeader(lfWritePended))
|
|
{
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
m_MailBodyError = GetLastError();
|
|
return TRUE;
|
|
}
|
|
else if(*lfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
m_TimeToRewriteHeader = FALSE;
|
|
}
|
|
|
|
//We are at the end of the message flush our write buffer
|
|
//if there is something in it
|
|
if(!WriteMailFile(NULL, 0, lfWritePended))
|
|
{
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
m_MailBodyError = GetLastError();
|
|
return TRUE;
|
|
}
|
|
else if(*lfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
m_cbParsable -= 3; // 1for the . + 2 for CRLF
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
|
|
if(m_cbReceived)
|
|
{
|
|
//we need to +2 to pszSearch because it points to the CR character
|
|
MoveMemory ((void *)QueryMRcvBuffer(), pszSearch + 2, m_cbReceived);
|
|
}
|
|
|
|
//DebugTrace((LPARAM) this, "got ending dot for file %s", MailInfo->GetMailFileName());
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(!m_Truncate && m_InHeader && ( m_MailBodyError == NO_ERROR ))
|
|
{
|
|
|
|
//the code I stole from SendMail assumes the
|
|
//line has a terminating '\0', so we terminate
|
|
//the line. We will put back the carriage
|
|
//return later.
|
|
|
|
*pszSearch = '\0';
|
|
char *pszValueBuf = NULL;
|
|
|
|
//remove header. return false if the line is not a header or the line is NULL
|
|
//chompheader returns everything after ":" into pszValueBuf
|
|
if( m_InHeader = ChompHeader( InputLine, m_HeaderFlags, &pszValueBuf) )
|
|
{
|
|
GetAndPersistRFC822Headers( InputLine, pszValueBuf);
|
|
if(FAILED(m_MailBodyError))
|
|
{
|
|
ErrorTrace((LPARAM) this, "Error persisting 822 headers 0x%08x", m_MailBodyError);
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
*pszSearch = CR;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//in case we have <header><CRLF><CRLF><sp><body><CRLF><x> we have seen
|
|
//an end of header after the 1st <CRLF> but because IsLineComplete822 halts at
|
|
//the third <CRLF> the inputstring to ChompHeader() is
|
|
//<CRLF><sp><body><NULL> rather than just <NULL> had there been no <sp> after
|
|
//the second <CRLF>. Thus ChompHeader will not set the EOH flag correctly.
|
|
//we do this here.
|
|
if( InputLine[0] == CR &&
|
|
InputLine[1] == LF &&
|
|
(InputLine[2] == ' ' || InputLine[2] == '\t') )
|
|
m_HeaderFlags |= H_EOH;
|
|
}
|
|
|
|
*pszSearch = CR;
|
|
}
|
|
|
|
|
|
//If we are not in the header and this is
|
|
//the first time m_TimeToRewriteHeader is
|
|
//TRUE, then add any missing headers we
|
|
//care about. Set m_TimeToRewriteHeader
|
|
//to false so that we don't enter this
|
|
//part of the code again. If ReWriteHeader
|
|
//fails, we are probably out of disk space,
|
|
//so set m_NoStorage to TRUE such that we
|
|
//don't waste our time writing to disk.
|
|
//We still have to accept the mail, but we
|
|
//throw it away. We will send a message
|
|
//back to our client when all is done.
|
|
if(!m_InHeader && m_TimeToRewriteHeader && (m_MailBodyError == NO_ERROR))
|
|
{
|
|
//So there was nothing to write so now write headers
|
|
if(!RewriteHeader(lfWritePended))
|
|
{
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
m_MailBodyError = GetLastError();
|
|
return TRUE;
|
|
}
|
|
else if(*lfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
m_TimeToRewriteHeader = FALSE;
|
|
}
|
|
|
|
//restore InputLine for mail file write, if we put in a CRLF
|
|
if(!m_Truncate && m_BufrWasFull)
|
|
{
|
|
*pszSearch = c1;
|
|
*(pszSearch + 1) = c2;
|
|
}
|
|
|
|
char *pInputLine = InputLine;
|
|
DWORD cbIntermediateSize = IntermediateSize + 2;
|
|
|
|
if(m_MailBodyError == NO_ERROR)
|
|
{
|
|
|
|
//if we had a full buffer without CRLFx in the previous iteration, we did a
|
|
//MoveMemory() and the beginning of the buffer is not the beginning of the line.
|
|
//dot stripping is only done for the beginning of the line.
|
|
if( !m_LongBodyLine )
|
|
{
|
|
if( *pInputLine == '.' )
|
|
{
|
|
pInputLine++;
|
|
cbIntermediateSize --;
|
|
m_fFoundEmbeddedCrlfDotCrlf = TRUE;
|
|
}
|
|
}
|
|
|
|
//write to the file if we have not gone over our alloted limit
|
|
//and if WriteFile did not give us back any errors
|
|
if((m_pInstance->GetMaxMsgSize() > 0) ||
|
|
(m_pInstance->GetMaxMsgSizeBeforeClose() > 0))
|
|
{
|
|
BOOL fShouldImposeLimit = TRUE;
|
|
|
|
// if the total msg size is not exceeded continue writing to file.
|
|
// Else, trigger the MaxMsgSize event to see
|
|
// if we can continue to write to the file.
|
|
|
|
if ( m_pInstance->GetMaxMsgSize() > 0 && TotalMsgSize > m_pInstance->GetMaxMsgSize() )
|
|
{
|
|
if( FAILED( m_pInstance->TriggerMaxMsgSizeEvent( GetSessionPropertyBag(), m_pIMsg, &fShouldImposeLimit ) ) || fShouldImposeLimit )
|
|
{
|
|
m_MailBodyError = ERROR_ALLOTTED_SPACE_EXCEEDED;
|
|
m_MailBodyDiagnostic = ERR_MAX_MSG_SIZE;
|
|
ProtocolLog(BDAT, (char *)InputLine, NO_ERROR, SMTP_RESP_NOSTORAGE, 0, 0);
|
|
ErrorTrace((LPARAM) this, "SMTP_RESP_NOSTORAGE, SMTP_MAX_MSG_SIZE_EXCEEDED_MSG - %d", TotalMsgSize);
|
|
++m_ProtocolErrors;
|
|
BUMP_COUNTER(QuerySmtpInstance(), MsgsRefusedDueToSize);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if(!WriteMailFile(pInputLine, cbIntermediateSize , lfWritePended))
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
return TRUE;
|
|
}
|
|
else if(*lfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (!WriteMailFile(pInputLine, cbIntermediateSize, lfWritePended))
|
|
{
|
|
m_MailBodyError = GetLastError();
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
return TRUE;
|
|
}
|
|
else if(*lfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//We managed to copy data to write buffer without having to write
|
|
//out
|
|
m_cbParsable -= IntermediateSize + 2;
|
|
m_cbReceived -= IntermediateSize + 2;
|
|
m_TotalMsgSize += IntermediateSize + 2;
|
|
m_cbDotStrippedTotalMsgSize += cbIntermediateSize;
|
|
m_cbRecvBufferOffset += IntermediateSize + 2;
|
|
|
|
_ASSERT (m_cbParsable <= QueryMaxReadSize());
|
|
|
|
InputLine = pszSearch + 2; //if m_BufrWasFull && UndecryptedTailSize == 0 then InputLine is pointing
|
|
//1 byte beyond end of allocated storage; this is not an error.
|
|
|
|
//if the buffer is filled up:
|
|
//since we are done parsing it, we can discard it. this is done by setting m_cbParsable to 0
|
|
//then we quit the IsLineComplete loop so that we can exit this function and pend a read for
|
|
//fresh data. If the buffer was filled up, it also means that a CRLFx was not found, so we
|
|
//ought to enter truncation mode (unless we have already done so) because this line was too
|
|
//long.
|
|
|
|
if(m_BufrWasFull)
|
|
{
|
|
// raid 196100 - We should not flush all buffers since it may contain some chars we want to save.
|
|
//m_cbParsable = 0;
|
|
m_LongBodyLine = TRUE; //Next return of pszSearch in while() is not beginning of a new line.
|
|
if(!m_Truncate)
|
|
m_Truncate = TRUE; //this looks silly... but the point is simply that we are "switching"
|
|
break; //states, to truncation mode. The if() reminds you that it is possible
|
|
} //to be in truncation mode already.
|
|
|
|
//if the buffer still has room:
|
|
//IsLineComplete returned because it found a CRLFx. in normal (non truncation mode) processing
|
|
//this case means nothing, but if we were in truncation mode, then it means that we have hit the
|
|
//end of the header we were truncating, so we need to get back to normal mode.
|
|
else
|
|
{
|
|
m_LongBodyLine = FALSE;
|
|
if(m_Truncate)
|
|
m_Truncate = FALSE;
|
|
}
|
|
}
|
|
|
|
if(m_cbParsable != 0)
|
|
{
|
|
//if there is stuff left in the buffer, move it up
|
|
//to the top, then pend a read at the end of where
|
|
//the last data left off
|
|
|
|
_ASSERT (m_cbParsable <= QueryMaxReadSize());
|
|
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
MoveMemory ((void *)QueryMRcvBuffer(), InputLine, m_cbReceived);
|
|
m_cbRecvBufferOffset = 0;
|
|
m_LineCompletionState = SEEN_NOTHING;
|
|
}
|
|
else
|
|
{ // m_cbParsable == 0
|
|
m_cbReceived = UndecryptedTailSize;
|
|
if(m_cbReceived > 0) //anything to move?
|
|
MoveMemory ((void *)QueryMRcvBuffer(), InputLine, m_cbReceived);
|
|
m_cbRecvBufferOffset = 0;
|
|
m_LineCompletionState = SEEN_NOTHING;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
NAME: SMTP_CONNECTION::IsLineCompleteRFC822
|
|
|
|
DESCRIPTION:
|
|
|
|
Parses InputLine till it encounters a CRLFx where x is a nontab
|
|
and nonspace character. When CRLFx is found it means that the true
|
|
end of a line has been found, in other words ... there are no more
|
|
continuations of this line.
|
|
|
|
Implementation is as a finite state machine (FSM).
|
|
|
|
The SMTP_CONNECTION member m_LineCompletion keeps track of the
|
|
state of the FSM between calls so that if the FSM does not hit
|
|
its exit state (CRLFx found) by the time it has scanned the input,
|
|
the caller may obtain more input data and resume the parsing. Thus
|
|
this member variable is "implicitly" passed to the function.
|
|
|
|
If the message receive buffer (returned by QueryMRcvBuffer()) is
|
|
filled up, so that the caller has no chance of getting more data
|
|
(since there is no space), this function returns a pointer to the
|
|
last but one item in InputLine and sets the flag FullBuffer. The
|
|
purpose being that the caller can put a CRLF at the position
|
|
returned and proceed as though CRLFx had been found.
|
|
|
|
A special condition occurs if the InputLine starts off as .CRLF
|
|
and the initial state is 1. This means that either the message is
|
|
just .CRLF or a CRLF.CRLF has been found. Thus the end of the message
|
|
has been found and the function returns.
|
|
|
|
PARAMETERS:
|
|
|
|
InputLine Input data
|
|
nBytes Bytes to parse
|
|
UndecryptedTailSize Data that occupies buffer space but can't
|
|
be parsed
|
|
p_FullBuffer If the function fails to find CRLFx, is there
|
|
enough room left in the buffer so that we can
|
|
get more input from the user?
|
|
|
|
m_LineCompletionState Implicit parameter... you need to Initialize
|
|
this.
|
|
|
|
RETURNS:
|
|
Pointer to CR if CRLFx found
|
|
Pointer to second last byte of buffer if there is no CRLFx
|
|
NULL if there is no CRLFx and the buffer is not full.
|
|
|
|
--*/
|
|
|
|
char * SMTP_CONNECTION::IsLineCompleteRFC822(
|
|
IN char *InputLine,
|
|
IN DWORD nBytes,
|
|
IN DWORD UndecryptedTailSize,
|
|
OUT BOOL *p_FullBuffer
|
|
)
|
|
{
|
|
ASSERT(InputLine);
|
|
|
|
char *pCh;
|
|
char *pLastChar = InputLine + nBytes - 1; //end of input
|
|
DWORD dwSave = 0;
|
|
|
|
//One exceptional case : the last line of input
|
|
if(nBytes >= 3)
|
|
{
|
|
if(InputLine[0] == '.' && InputLine[1] == CR && InputLine[2] == LF)
|
|
{
|
|
*p_FullBuffer = FALSE;
|
|
return &InputLine[1];
|
|
}
|
|
}
|
|
|
|
|
|
for(pCh = InputLine; pCh <= pLastChar; pCh++)
|
|
{
|
|
switch(m_LineCompletionState)
|
|
{
|
|
case SEEN_NOTHING: //need CR
|
|
if(*pCh == CR) {
|
|
m_LineCompletionState = SEEN_CR; //seen CR go on to state_2
|
|
dwSave = 1; // save CR if necessary
|
|
}
|
|
break;
|
|
|
|
case SEEN_CR: //seen CR need LF
|
|
if(*pCh == LF) {
|
|
m_LineCompletionState = SEEN_CRLF; //seen LF goto state_3
|
|
dwSave = 2; // save CRLF if necessary
|
|
} else if(*pCh != CR) {
|
|
m_LineCompletionState = SEEN_NOTHING;//if CR stay in this state, else go back to 1
|
|
dwSave = 0;
|
|
}
|
|
break;
|
|
|
|
case SEEN_CRLF: //seen CRLF, need x
|
|
if(*pCh != ' ' && *pCh != '\t') //CRLFx found
|
|
{
|
|
*p_FullBuffer = FALSE;
|
|
m_LineCompletionState = SEEN_NOTHING; //seen CRLFx, go back to initial state
|
|
return pCh - 2; //returning pointer to the CR
|
|
} else {
|
|
m_LineCompletionState = SEEN_NOTHING; //didn't get either x or CR, nothing matched --- start over again.
|
|
dwSave = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//buffer is full && CRLFx not found.
|
|
// raid 196100 - we need to save some bytes (CR, LF, or CRLF) if necessary
|
|
// when buffer is full
|
|
if(*p_FullBuffer = (pLastChar >= QueryMRcvBuffer() + QueryMaxReadSize() - UndecryptedTailSize - 1))
|
|
return pLastChar - 1 - dwSave; //assumption: nbytes is atleast 2
|
|
return NULL; //buffer has room left && CRLFx not found
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::CreateMailBodyFromChunk
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP BDAT command.
|
|
This function spools the mail to a
|
|
directory keeping track of chunk size
|
|
Arguments:
|
|
|
|
InputLine - Buffer received from client
|
|
paramterSize - amount of data in buffer
|
|
UndecryptedTailSize -- amount of undecrypted data at end of buffer
|
|
Returns:
|
|
|
|
TRUE if all data(incliding terminating .)
|
|
has been received
|
|
FALSE on all errors (disk full, etc.)
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::CreateMailBodyFromChunk (char * InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize, BOOL *lpfWritePended)
|
|
{
|
|
BOOL MailDone = FALSE;
|
|
DWORD IntermediateSize = 0;
|
|
PCHAR pszSearch = NULL;
|
|
char MailFileName[MAX_PATH];
|
|
DWORD TotalMsgSize = 0;
|
|
DWORD SessionSize = 0;
|
|
|
|
// HRESULT hr;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::CreateMailBodyFromChunk");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
_ASSERT (m_cbParsable <= QueryMaxReadSize());
|
|
|
|
|
|
MailFileName[0] = '\0';
|
|
|
|
//
|
|
// m_cbTempBDATLen is used to save BDAT chunk bytes for headers spanning multiple
|
|
// BDAT chunks. So we need to parse from the very beginning of the buffer if there
|
|
// are saved BDAT header bytes.
|
|
//
|
|
|
|
if(m_cbTempBDATLen)
|
|
{
|
|
InputLine = QueryMRcvBuffer();
|
|
}
|
|
|
|
//
|
|
// Process the chunk data line-by-line till a line is encountered which is not a
|
|
// header. At this point m_InHeader is set to FALSE and we never enter this loop
|
|
// again. Instead all chunk data will be directly dumped to disk as the mail body.
|
|
//
|
|
// It can be determined that a line is either header/non-header if:
|
|
// - It is CRLF terminated (it can be examined for the header syntax)
|
|
// - If we have 1000+ bytes without a CRLF it is not a header
|
|
//
|
|
// If it does not meet either condition, we must save the data (set m_cbTempBDATLen)
|
|
// and post a read for the next chunk hoping that with the fresh data, either of the
|
|
// two conditions will apply.
|
|
//
|
|
|
|
while (m_InHeader &&
|
|
((pszSearch = IsLineComplete((const char *)InputLine, m_cbParsable)) != NULL ||
|
|
m_cbParsable > 1000 ||
|
|
m_cbParsable >= (DWORD)m_nBytesRemainingInChunk))
|
|
{
|
|
_ASSERT (m_cbParsable <= QueryMaxReadSize());
|
|
|
|
// Found a CRLF in the received data
|
|
if(pszSearch != NULL)
|
|
{
|
|
IntermediateSize = (DWORD)(pszSearch - InputLine);
|
|
TotalMsgSize = m_TotalMsgSize + (IntermediateSize + 2);
|
|
|
|
//
|
|
// There are IntermediateSize+2 bytes in this CRLF terminated line.
|
|
// If that is > the number of bytes in the current BDAT chunk:
|
|
// - The chunk was fully received
|
|
// - Some extra bytes (pipelined SMTP command) were received after it.
|
|
// - The CRLF found by IslineComplete is outside this chunk.
|
|
//
|
|
|
|
if(( m_nBytesRemainingInChunk - ((int)IntermediateSize + 2) ) < 0)
|
|
{
|
|
if(m_fIsLastChunk || m_nBytesRemainingInChunk > 1000)
|
|
{
|
|
//
|
|
// We're done with header parsing because either:
|
|
// - This is the last BDAT chunk OR
|
|
// - We have 1000 bytes of mailbody data without a CRLF
|
|
//
|
|
|
|
m_InHeader = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// In this else-block the following is true:
|
|
// - This is not the last BDAT chunk
|
|
// - We have < 1000 bytes in the current BDAT chunk
|
|
// - The CRLF (pszSearch) is outside the current BDAT chunk
|
|
// - We still haven't hit a non-header line
|
|
//
|
|
// We do not know if this is a header. This chunk is saved
|
|
// as the first m_cbTempBDATLen bytes in the recv buffer. The
|
|
// next chunk is appended to this and both will be processed
|
|
// together.
|
|
//
|
|
|
|
m_cbTempBDATLen = m_nBytesRemainingInChunk;
|
|
m_nBytesRemainingInChunk = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//look for all the headers we care about.
|
|
//If we are out of storage, forget it.
|
|
if(m_InHeader && (m_MailBodyError == NO_ERROR))
|
|
{
|
|
//For parsing we terminate the line.
|
|
//We will put back the carraige return later
|
|
*pszSearch = '\0';
|
|
|
|
//skip over continuation lines
|
|
if((InputLine[0] != ' ') && (InputLine[0] != '\t'))
|
|
{
|
|
char *pszValueBuf = NULL;
|
|
//see if we are still in the header portion
|
|
//of the body
|
|
if (m_InHeader = ChompHeader(InputLine, m_HeaderFlags, &pszValueBuf))
|
|
{
|
|
//Process and Promote RFC822 Headers
|
|
GetAndPersistRFC822Headers( InputLine, pszValueBuf );
|
|
}
|
|
|
|
}
|
|
//put back the carraige return
|
|
*pszSearch = CR;
|
|
}
|
|
}
|
|
|
|
else if((m_cbParsable > 1000) || m_fIsLastChunk)
|
|
{
|
|
//We could not parse the line inspite of having 1000 parsable bytes
|
|
//That tells me it is not a 822 header
|
|
m_InHeader = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//We do not have a parsed line and we have not exceeded the 1000byte
|
|
//limit - looks like what we have is a partial header line
|
|
//Keep the data around
|
|
m_cbTempBDATLen = m_nBytesRemainingInChunk;
|
|
m_nBytesRemainingInChunk = 0;
|
|
break;
|
|
}
|
|
|
|
//if we are done with headers break
|
|
if(!m_InHeader)
|
|
break;
|
|
|
|
//We are still processing headers
|
|
if(m_MailBodyError == NO_ERROR)
|
|
{
|
|
if(!WriteMailFile(InputLine, IntermediateSize + 2, lpfWritePended))
|
|
{
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
m_MailBodyError = GetLastError();
|
|
m_InHeader = FALSE;
|
|
}
|
|
else if(*lpfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
//Update the remaining bytes expected in this chunk
|
|
else
|
|
{
|
|
//Once we write out chunk data - we should no longer have any saved of portion of chunk
|
|
//
|
|
m_cbTempBDATLen = 0;
|
|
m_nBytesRemainingInChunk -= IntermediateSize + 2;
|
|
}
|
|
}
|
|
else
|
|
m_InHeader = FALSE;
|
|
|
|
InputLine = pszSearch + 2;
|
|
m_cbParsable -= (IntermediateSize + 2);
|
|
m_cbReceived -= IntermediateSize + 2;
|
|
m_TotalMsgSize += IntermediateSize + 2;
|
|
m_cbRecvBufferOffset += (IntermediateSize + 2);
|
|
_ASSERT (m_cbParsable < QueryMaxReadSize());
|
|
}//end while
|
|
|
|
// If we are done with headers - it is time to write any missing headers
|
|
if(!m_InHeader && m_TimeToRewriteHeader && (m_MailBodyError == NO_ERROR))
|
|
{
|
|
//So there was nothing to write so now write headers
|
|
if(!RewriteHeader(lpfWritePended))
|
|
{
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
m_MailBodyError = GetLastError();
|
|
ErrorTrace( (LPARAM)this, "Rewrite headers failed. err: %d",m_MailBodyError);
|
|
}
|
|
else if(*lpfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
m_TimeToRewriteHeader = FALSE;
|
|
}
|
|
|
|
//If we did write something during our header parsing we need to update m_cbReceived
|
|
m_cbReceived = m_cbParsable + UndecryptedTailSize;
|
|
|
|
// Now that we are thru with headers and there is more data in the buffer
|
|
// that is part of the chunk - simply dump it to disk asynchronously
|
|
if(!m_InHeader && m_nBytesRemainingInChunk && m_cbParsable)
|
|
{
|
|
DWORD dwBytesToWrite = m_nBytesRemainingInChunk;
|
|
|
|
// Simply write the parsable data or the remaining bytes in this chunk
|
|
// to the file - which ever is smaller
|
|
if(m_nBytesRemainingInChunk > (int) m_cbParsable)
|
|
dwBytesToWrite = m_cbParsable;
|
|
|
|
if(m_MailBodyError == NO_ERROR)
|
|
{
|
|
//write to the file if we ahve had no errors
|
|
if(!WriteMailFile(InputLine, dwBytesToWrite, lpfWritePended))
|
|
{
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
m_MailBodyError = GetLastError();
|
|
}
|
|
else if(*lpfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
//Now that we have copied stuff to out buffer
|
|
//update the state data
|
|
m_TotalMsgSize += dwBytesToWrite;
|
|
|
|
m_nBytesRemainingInChunk -= dwBytesToWrite;
|
|
//We are down to last bytes - keep a track of trailing CRLF
|
|
if(m_fIsLastChunk && (m_nBytesRemainingInChunk < 2) )
|
|
{
|
|
//We have the second last byte - there is one more byte to be read.
|
|
if(m_nBytesRemainingInChunk)
|
|
{
|
|
if(*(InputLine + dwBytesToWrite -1) == '\r')
|
|
m_dwTrailerStatus = CR_SEEN;
|
|
else
|
|
m_dwTrailerStatus = CR_MISSING;
|
|
}
|
|
else if(dwBytesToWrite > 1) //We got the last byte
|
|
{
|
|
//We have last two bytes together
|
|
if(!strncmp((InputLine + dwBytesToWrite - 2),"\r\n", 2))
|
|
m_dwTrailerStatus = CRLF_SEEN;
|
|
else
|
|
m_dwTrailerStatus = CRLF_NEEDED;
|
|
}
|
|
else if(m_dwTrailerStatus == CR_SEEN && (*(InputLine + dwBytesToWrite -1) == '\n'))
|
|
m_dwTrailerStatus = CRLF_SEEN;
|
|
else
|
|
m_dwTrailerStatus = CRLF_NEEDED;
|
|
|
|
}
|
|
|
|
//Update IO buffer parameters to reflect the state after WRITE
|
|
m_cbReceived -= dwBytesToWrite;
|
|
m_cbParsable -= dwBytesToWrite;
|
|
|
|
//Once we write out chunk data - we should no longer have any saved of portion of chunk
|
|
//
|
|
m_cbTempBDATLen = 0;
|
|
m_cbRecvBufferOffset = 0;
|
|
|
|
}
|
|
|
|
InputLine += dwBytesToWrite;
|
|
_ASSERT (m_cbParsable < QueryMaxReadSize());
|
|
}
|
|
|
|
//Adjust the buffer for the next read only if did comsume any data
|
|
//and there is more data remaining in the input buffer
|
|
//
|
|
|
|
if(m_cbReceived && (QueryMRcvBuffer() != InputLine))
|
|
{
|
|
MoveMemory ((void *)QueryMRcvBuffer(), InputLine, m_cbReceived);
|
|
}
|
|
m_cbRecvBufferOffset = 0;
|
|
|
|
//If we are done with current chunk
|
|
if(!m_nBytesRemainingInChunk)
|
|
{
|
|
if(m_MailBodyError == NO_ERROR)
|
|
{
|
|
if(m_fIsLastChunk)
|
|
{
|
|
//we are done with all the chunks
|
|
//We have written the Last chunk and need to see if we need to put the trailing CRLF
|
|
if(m_dwTrailerStatus == CRLF_NEEDED)
|
|
{
|
|
|
|
if(!WriteMailFile("\r\n", 2, lpfWritePended))
|
|
{
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
m_MailBodyError = GetLastError();
|
|
}
|
|
else if(*lpfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
m_TotalMsgSize += 2;
|
|
m_dwTrailerStatus = CRLF_SEEN;
|
|
}
|
|
|
|
//Flush all the data to disk
|
|
if(!WriteMailFile(NULL, 0, lpfWritePended))
|
|
{
|
|
m_MailBodyDiagnostic = ERR_RETRY;
|
|
m_MailBodyError = GetLastError();
|
|
}
|
|
else if(*lpfWritePended)
|
|
{
|
|
//Go away - Atq will call us back when the write file completes
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
//Go back to HELO state
|
|
m_State = HELO;
|
|
m_fIsChunkComplete = FALSE;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
//Chunk completion response
|
|
ProtocolLog(BDAT, (char *) MailFileName, NO_ERROR, SMTP_RESP_OK, 0, 0);
|
|
FormatSmtpMessage (SMTP_RESP_OK, EMESSAGE_GOOD," %s, %d Octets\r\n", SMTP_BDAT_CHUNK_OK_STR, m_nChunkSize );
|
|
}
|
|
m_fIsChunkComplete = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// We had error during handling the Chunk, now that chunk has been consumed
|
|
//we can respond with an error
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
|
|
|
|
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoDATACommandEx
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP data command.
|
|
This funcion spools the mail to a
|
|
directory
|
|
Arguments:
|
|
|
|
InputLine - Buffer received from client
|
|
paramterSize - amount of data in buffer
|
|
Returns:
|
|
|
|
Currently this function always returns TRUE.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoDATACommandEx(const char * InputLine1, DWORD ParameterSize, DWORD UndecryptedTailSize,BOOL *lpfWritePended, BOOL *pfAsyncOp)
|
|
{
|
|
LPSTR InputLine = (LPSTR) InputLine1;
|
|
BOOL fRet = TRUE;
|
|
BOOL fProcess = TRUE;
|
|
PSMTP_IIS_SERVICE pService = (PSMTP_IIS_SERVICE) g_pInetSvc;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoDATACommandEx");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//
|
|
// we scan for <CRLF>.<CRLF> when processing the msg body of the DATA
|
|
// command.
|
|
//
|
|
|
|
m_fScannedForCrlfDotCrlf = TRUE;
|
|
|
|
if(CreateMailBody(InputLine, ParameterSize, UndecryptedTailSize, lpfWritePended))
|
|
{
|
|
if(m_MailBodyDiagnostic != NO_ERROR)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//increment our counters
|
|
//make sure the size of the file is proper.
|
|
//subtract 3 bytes accounting for the .CRLF.
|
|
//We don't write that portion to the file.
|
|
|
|
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesRcvdMsg, (m_TotalMsgSize - 3));
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumMsgRecvd);
|
|
|
|
//if there was no error while parsing the
|
|
//header, and writimg the file to disk,
|
|
//then save the rest of the file, send
|
|
//a message id back to the client, and
|
|
//queue the message
|
|
if(m_MailBodyError == NO_ERROR)
|
|
{
|
|
|
|
//call HandleCompletedMessage() to see
|
|
//if we should process this message
|
|
HandleCompletedMessage(DATA, pfAsyncOp);
|
|
if (*pfAsyncOp) {
|
|
m_fAsyncEOD = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
//We got all the data and written it to disk.
|
|
//Now, we'll pend a read to pick up the quit
|
|
//response, to get any other mail the client
|
|
//sends. We need to re-initialize our class
|
|
//variables first.
|
|
|
|
ReInitClassVariables();
|
|
|
|
_ASSERT (m_cbReceived < QueryMaxReadSize());
|
|
|
|
}
|
|
else
|
|
{
|
|
//else, format the appropriate message,
|
|
//and delete all objects pertaining to
|
|
//this file.
|
|
|
|
//flush any pending responses
|
|
SendSmtpResponse();
|
|
|
|
//re-initialize out class variables
|
|
ReInitClassVariables();
|
|
|
|
_ASSERT (m_cbReceived < QueryMaxReadSize());
|
|
}
|
|
}
|
|
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fRet;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Description:
|
|
// This function handles receiving the mailbody sent by a client using the
|
|
// DATA command. It wraps the functionality of DoDATACommandEx (the
|
|
// function to parse and process the mailbody) and AcceptAndDiscardDATA
|
|
// (the function that handles errors that occur during DoDATACommandEx).
|
|
//
|
|
// The reason for a separate function to do error processing for mailbody
|
|
// errors is that when a mailbody error occurs, SMTP cannot directly abort
|
|
// the transaction and return an error response. Instead it must continue
|
|
// to post reads for the mailbody, and after the mailbody has been completely
|
|
// received, respond with the appropriate error.
|
|
//
|
|
// So when an error occurs during DoDATACommandEx, it stops processing and
|
|
// sets m_MailBodyDiagnostic. AcceptAndDiscardData takes over and keeps
|
|
// posting reads till the body has been sent by the client. Then it uses
|
|
// m_MailBodyDiagnostic to generate the error response.
|
|
//
|
|
// Arguments:
|
|
// IN char *InputLine - Pointer to data being processed
|
|
// IN DWORD UndecryptedTailSize - If using SSL, this is the encrypted tail
|
|
// OUT BOOL *pfAsyncOp - Set to TRUE if a read of write was pended. In
|
|
// this case.
|
|
// Returns:
|
|
// TRUE - Success. All received data must be passed into this function
|
|
// till the state goes to HELO after which data should be parsed as
|
|
// SMTP commands. Failures while processing DATA are caught internally
|
|
// (flagged by m_MailBodyDiagnostic and m_MailBodyError) and we still
|
|
// return TRUE.
|
|
// FALSE - A failure occurred that cannot be handled (like a TCP/IP
|
|
// failure). In this case we should disconnect.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
BOOL SMTP_CONNECTION::ProcessDATAMailBody(const char *InputLine, DWORD UndecryptedTailSize, BOOL *pfAsyncOp)
|
|
{
|
|
BOOL fReturn = TRUE;
|
|
BOOL fWritePended = FALSE;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::ProcessDATA");
|
|
|
|
_ASSERT(m_State == DATA);
|
|
|
|
*pfAsyncOp = FALSE;
|
|
|
|
if(m_MailBodyDiagnostic != ERR_NONE)
|
|
goto MAILBODY_ERROR;
|
|
|
|
//
|
|
// DoDATACommandEx handles parsing the mailbody, processing the headers and
|
|
// persisting them to the IMailMsg, and writing the mailbody to a file. In
|
|
// addition, when the mailbody has been received, it triggers the _EOD event
|
|
// which may return async (due to installed event handlers). We pre-increment
|
|
// the pending IO count to cover this case.
|
|
//
|
|
|
|
IncPendingIoCount();
|
|
|
|
fReturn = DoDATACommandEx(InputLine, m_cbParsable, UndecryptedTailSize, &fWritePended, pfAsyncOp);
|
|
_ASSERT(fReturn && "All errors are flagged by m_MailBodyDiagnostic and m_MailBodyError");
|
|
|
|
//
|
|
// DoDATACommandEx/CreateMailBody always return immediately after they pend I/Os
|
|
// (expecting the I/O completion threads to resume processing). So if the I/O pend
|
|
// succeeds, DoDATACommandEx must have succeeded.
|
|
//
|
|
|
|
if(*pfAsyncOp || fWritePended)
|
|
_ASSERT(m_MailBodyDiagnostic == ERR_NONE);
|
|
|
|
if(*pfAsyncOp)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Decrement the pending IO count since DoDataCommandEx returned sync (and
|
|
// we incremented the count above). ProcessClient is above us in the
|
|
// callstack and always has a pending IO reference, so this should never
|
|
// return zero
|
|
//
|
|
|
|
_VERIFY(DecPendingIoCount() > 0);
|
|
|
|
//
|
|
// If a Write was pended we simply want to release this thread back to IIS
|
|
// The completion thread will continue with the processing
|
|
//
|
|
|
|
if(fWritePended)
|
|
{
|
|
*pfAsyncOp = TRUE;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(m_MailBodyDiagnostic != ERR_NONE)
|
|
{
|
|
_ASSERT(!*pfAsyncOp && !fWritePended);
|
|
|
|
//
|
|
// Shift out bytes that already got parsed during DoDATACommandEx.
|
|
//
|
|
|
|
MoveMemory(QueryMRcvBuffer(), QueryMRcvBuffer() + m_cbRecvBufferOffset, m_cbParsable);
|
|
m_cbRecvBufferOffset = 0;
|
|
goto MAILBODY_ERROR;
|
|
}
|
|
|
|
if(m_State == DATA)
|
|
{
|
|
//
|
|
// If the state is still equal to DATA, then we need to keep
|
|
// pending reads to pickup the rest of the message. When the
|
|
// state changes to HELO, then we can stop pending reads, and
|
|
// go back into parsing commands.
|
|
//
|
|
|
|
fReturn = PendReadIO(UndecryptedTailSize);
|
|
*pfAsyncOp = TRUE;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
//
|
|
// Done with mail body. Go back to parsing commands.
|
|
//
|
|
|
|
_ASSERT(m_State == HELO);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
|
|
MAILBODY_ERROR:
|
|
|
|
_ASSERT(m_MailBodyDiagnostic != ERR_NONE);
|
|
|
|
//
|
|
// DoBDATCommandEx (and other parts of smtpcli) use m_cbRecvBufferOffset to
|
|
// keep track of bytes in the beginning of the recv buffer that have been
|
|
// processed, but have not been shifted out (this is needed for header parsing).
|
|
// All reads/processing therefore start at (recv buffer + offset). During error
|
|
// processing however there's no parsing of headers, so this offset should be
|
|
// set to 0 before calling into discard, and the saved bytes shifted out. The
|
|
// ASSERT below verifies this.
|
|
//
|
|
|
|
_ASSERT(m_cbRecvBufferOffset == 0 && "All of recv buffer is not being used");
|
|
|
|
fReturn = AcceptAndDiscardDATA(QueryMRcvBuffer(), UndecryptedTailSize, pfAsyncOp);
|
|
|
|
//
|
|
// If something failed during error processing, disconnect with an error
|
|
// (this violates the RFC, but there's no option). If we succeeded and a
|
|
// read was pended, all data in the input buffer has been consumed. The
|
|
// read I/O completion thread will pick up processing.
|
|
//
|
|
|
|
if(!fReturn || *pfAsyncOp)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
//
|
|
// All the mailbody was processed and the input buffer contains only SMTP
|
|
// commands. Fall through the the command parsing code.
|
|
//
|
|
|
|
_ASSERT(m_State == HELO);
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Description:
|
|
// Analogous to ProcessDATAMailBody
|
|
// Arguments:
|
|
// Same as ProcessDATAMailBody
|
|
// Returns:
|
|
// Same as ProcessDATAMailBody
|
|
//-----------------------------------------------------------------------------
|
|
BOOL SMTP_CONNECTION::ProcessBDATMailBody(const char *InputLine, DWORD UndecryptedTailSize, BOOL *pfAsyncOp)
|
|
{
|
|
BOOL fWritePended = FALSE;
|
|
BOOL fReturn = FALSE;
|
|
_ASSERT(!m_fIsChunkComplete && m_State == BDAT);
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::ProcessBDATMailBody");
|
|
|
|
*pfAsyncOp = FALSE;
|
|
|
|
//
|
|
// We are in the midst of error processing, skip right to it.
|
|
//
|
|
|
|
if(m_MailBodyDiagnostic != ERR_NONE)
|
|
goto MAILBODY_ERROR;
|
|
|
|
//
|
|
// Increment the pending IO count in case DoBDATCommandEx returns async.
|
|
// When the message is completely received, DoBDATCommandEx will trigger
|
|
// into the _EOD event, which may optionally return async. This increment
|
|
// serves to keep track of that.
|
|
//
|
|
|
|
IncPendingIoCount();
|
|
|
|
fReturn = DoBDATCommandEx(InputLine, m_cbParsable, UndecryptedTailSize, &fWritePended, pfAsyncOp);
|
|
|
|
if(m_MailBodyDiagnostic != ERR_NONE)
|
|
{
|
|
_ASSERT(!fWritePended && !*pfAsyncOp && "Shouldn't be pending read/write on failure");
|
|
_VERIFY(DecPendingIoCount() > 0);
|
|
goto MAILBODY_ERROR;
|
|
}
|
|
|
|
// The completion thread will call us when more data arrives
|
|
if (*pfAsyncOp)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Decrement the pending IO count since DoBDATCommandEx returned sync (and
|
|
// we incremented the count above). ProcessClient is above us in the
|
|
// callstack and always has a pending IO reference, so this should never
|
|
// return zero
|
|
//
|
|
|
|
_VERIFY(DecPendingIoCount() > 0);
|
|
|
|
//
|
|
// If a Write was pended we simply want to release this thread back to IIS
|
|
// The completion thread will continue with the processing
|
|
//
|
|
|
|
if(fWritePended)
|
|
{
|
|
*pfAsyncOp = TRUE;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(m_State == BDAT && !m_fIsChunkComplete)
|
|
{
|
|
//
|
|
// We still are not done with current chunk So Pend read to pickup the
|
|
// rest of the chunk. When the state changes to HELO or the current
|
|
// chunk completes, then we can stop pending reads, and go back into
|
|
// parsing commands.
|
|
//
|
|
|
|
fReturn = PendReadIO(UndecryptedTailSize);
|
|
*pfAsyncOp = TRUE;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
else if(m_cbTempBDATLen)
|
|
{
|
|
//
|
|
// Flush the response to the chunk just processed (why is this needed
|
|
// specifically for m_cbTempBDATLen > 0 only? Shouldn't all responses
|
|
// be treated equally -- gpulla).
|
|
//
|
|
|
|
SendSmtpResponse();
|
|
}
|
|
|
|
//
|
|
// Done with current chunk. Go back to parsing commands.
|
|
//
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
|
|
MAILBODY_ERROR:
|
|
|
|
_ASSERT(m_MailBodyDiagnostic != ERR_NONE);
|
|
|
|
//
|
|
// m_cbTempBDATLen is an offset into the recv buffer. It is set to > 0 when
|
|
// DoBDATCommandEx needs to save the start of a header that spans multiple
|
|
// chunks. Since discard does no header parsing, this is unneccessary, and
|
|
// the "offset" number of bytes (if non-zero) should be reset and shifted
|
|
// out before calling into discard.
|
|
//
|
|
|
|
if(m_cbTempBDATLen)
|
|
{
|
|
MoveMemory(QueryMRcvBuffer(), QueryMRcvBuffer() + m_cbTempBDATLen, m_cbReceived);
|
|
m_cbTempBDATLen = 0;
|
|
}
|
|
|
|
fReturn = AcceptAndDiscardBDAT(QueryMRcvBuffer(), UndecryptedTailSize, pfAsyncOp);
|
|
|
|
//
|
|
// If something failed during error processing, disconnect with an error
|
|
// (this violates the RFC, but there's no option). If we succeeded and a
|
|
// read was pended, all data in the input buffer has been consumed. The
|
|
// read I/O completion thread will pick up processing.
|
|
//
|
|
|
|
if(!fReturn || *pfAsyncOp)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
_ASSERT(m_nBytesRemainingInChunk == 0 && "Expected chunk to be done");
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoBDATCommandEx
|
|
|
|
Description:
|
|
|
|
Handles the data chunks received after a valid BDAT command.
|
|
This funcion spools the mail to a directory.
|
|
In the first chunk it parses for header.
|
|
Once the header is written in current chunk and subsequent chunks it simply
|
|
dumps the data to the disk
|
|
Arguments:
|
|
InputLine - Buffer received from client
|
|
paramterSize - amount of data in buffer
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoBDATCommandEx(const char * InputLine1, DWORD ParameterSize, DWORD UndecryptedTailSize, BOOL *lpfWritePended, BOOL *pfAsyncOp)
|
|
{
|
|
LPSTR InputLine = (LPSTR) InputLine1;
|
|
BOOL fRet = TRUE;
|
|
BOOL fProcess = TRUE;
|
|
PSMTP_IIS_SERVICE pService = (PSMTP_IIS_SERVICE) g_pInetSvc;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoBDATCommandEx");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
*lpfWritePended = FALSE;
|
|
|
|
// We make this call only while we are parsing the headers
|
|
// Once we are done parsing the headers
|
|
if(CreateMailBodyFromChunk (InputLine, ParameterSize, UndecryptedTailSize, lpfWritePended))
|
|
{
|
|
|
|
m_WritingData = FALSE;
|
|
//increment our counters
|
|
//make sure the size of the file is proper.
|
|
//subtract 3 bytes accounting for the .CRLF.
|
|
//We don't write that portion to the file.
|
|
|
|
ADD_BIGCOUNTER(QuerySmtpInstance(), BytesRcvdMsg, (m_TotalMsgSize - 3));
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumMsgRecvd);
|
|
|
|
//if there was no error while parsing the
|
|
//header, and writimg the file to disk,
|
|
//then save the rest of the file, send
|
|
//a message id back to the client, and
|
|
//queue the message
|
|
if(m_MailBodyError == NO_ERROR)
|
|
{
|
|
|
|
//call HandleCompletedMessage() to see
|
|
//if we should process this message
|
|
fProcess = HandleCompletedMessage(BDAT, pfAsyncOp);
|
|
if (*pfAsyncOp) {
|
|
m_fAsyncEOD = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
//We got all the data and written it to disk.
|
|
//Now, we'll pend a read to pick up the quit
|
|
//response, to get any other mail the client
|
|
//sends. We need to re-initialize our class
|
|
//variables first.
|
|
|
|
ReInitClassVariables();
|
|
|
|
_ASSERT (m_cbReceived < QueryMaxReadSize());
|
|
}
|
|
else
|
|
{
|
|
//else, format the appropriate message,
|
|
//and delete all objects pertaining to
|
|
//this file.
|
|
|
|
if(m_MailBodyError == ERROR_BAD_LENGTH)
|
|
FormatSmtpMessage (SMTP_RESP_MBX_SYNTAX,ESYNTAX_ERROR," %s\r\n","Badly formed BDAT Chunk");
|
|
else
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES,ENO_RESOURCES," %s\r\n", SMTP_NO_STORAGE);
|
|
|
|
//flush any pending responses
|
|
SendSmtpResponse();
|
|
|
|
//re-initialize out class variables
|
|
ReInitClassVariables();
|
|
|
|
_ASSERT (m_cbReceived < QueryMaxReadSize());
|
|
}
|
|
}
|
|
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fRet;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoHELPCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP HELP command.
|
|
send a help text description
|
|
|
|
Arguments:
|
|
Are ignored
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoHELPCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
BOOL RetStatus;
|
|
char szHelpCmds[MAX_PATH];
|
|
DWORD ConnectionStatus = 0;
|
|
|
|
if(m_SecurePort)
|
|
ConnectionStatus |= SMTP_IS_SSL_CONNECTION;
|
|
if(m_fAuthenticated)
|
|
ConnectionStatus |= SMTP_IS_AUTH_CONNECTION;
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
if(!m_fAuthAnon && !m_fAuthenticated)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY," %s\r\n", "Client was not authenticated");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
return TRUE;
|
|
}
|
|
|
|
strcpy(szHelpCmds,HelpText);
|
|
|
|
if(m_pInstance->AllowAuth())
|
|
{
|
|
if(m_pInstance->QueryAuthentication() != 0)
|
|
{
|
|
strcat(szHelpCmds, " AUTH");
|
|
|
|
if(g_SmtpPlatformType == PtNtServer && m_pInstance->AllowTURN())
|
|
{
|
|
strcat(szHelpCmds, " TURN");
|
|
}
|
|
}
|
|
}
|
|
|
|
if(g_SmtpPlatformType == PtNtServer && m_pInstance->AllowETRN())
|
|
{
|
|
strcat(szHelpCmds, " ETRN");
|
|
}
|
|
|
|
// Chunking related advertisements
|
|
if(m_pInstance->AllowBinaryMime() || m_pInstance->AllowChunking())
|
|
{
|
|
strcat(szHelpCmds, " BDAT");
|
|
}
|
|
// verify
|
|
if(m_pInstance->AllowVerify(ConnectionStatus))
|
|
{
|
|
strcat(szHelpCmds, " VRFY");
|
|
}
|
|
// Expand
|
|
if(m_pInstance->AllowExpand(ConnectionStatus))
|
|
{
|
|
strcat(szHelpCmds, " EXPN");
|
|
}
|
|
|
|
PE_FormatSmtpMessage ("%s\r\n", szHelpCmds);
|
|
|
|
RetStatus = PE_SendSmtpResponse();
|
|
return RetStatus;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoVRFYCommand
|
|
|
|
Description:
|
|
|
|
Responds to the SMTP VRFY command.
|
|
send a help text description. We
|
|
do not really verify an address here.
|
|
This function is just a stub.
|
|
|
|
Arguments:
|
|
The name to verify from the client
|
|
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoVRFYCommand(const char * InputLine, DWORD ParameterSize)
|
|
{
|
|
|
|
BOOL RetStatus = TRUE;
|
|
char * VrfyAddr = NULL;
|
|
char * DomainPtr = NULL;
|
|
char * ArgPtr = NULL;
|
|
char szAddr[MAX_INTERNET_NAME + 1];
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoVRFYCommand");
|
|
|
|
szAddr[0] = '\0';
|
|
DWORD ConnectionStatus = 0;
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
if(!m_fAuthAnon && !m_fAuthenticated)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY," %s\r\n","Client was not authenticated");
|
|
ErrorTrace((LPARAM) this, "DoVrfyCommand - SMTP_RESP_MUST_SECURE, user not authenticated");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//start at the beginning;
|
|
VrfyAddr = (char *) InputLine;
|
|
|
|
//check the arguments for good form
|
|
VrfyAddr = CheckArgument(VrfyAddr);
|
|
if (VrfyAddr == NULL)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(ExtractAndValidateRcpt(VrfyAddr, &ArgPtr, szAddr, &DomainPtr))
|
|
{
|
|
//The address is valid
|
|
//Do we allow verify
|
|
if(m_SecurePort)
|
|
ConnectionStatus |= SMTP_IS_SSL_CONNECTION;
|
|
if(m_fAuthenticated)
|
|
ConnectionStatus |= SMTP_IS_AUTH_CONNECTION;
|
|
|
|
if(!m_pInstance->AllowVerify(ConnectionStatus))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_VRFY_CODE, EVALID_DEST_ADDRESS," %s <%s>\r\n", "Cannot VRFY user, but will accept message for", szAddr);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
//Check the domain to be either belonging to a ALIAS,DEFAULT,DROP,DELIVER
|
|
//We also check the relay restrictions
|
|
HRESULT hr = HrShouldAcceptRcpt(DomainPtr);
|
|
|
|
if( S_FALSE == hr )
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NOT_FOUND, ENO_FORWARDING, " Cannot relay to <%s>\r\n",szAddr);
|
|
}
|
|
else if (S_OK == hr)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_VRFY_CODE, EVALID_DEST_ADDRESS," %s <%s>\r\n", "Cannot VRFY user, but will take message for", szAddr);
|
|
}
|
|
else
|
|
{
|
|
// Currently, HrShouldAcceptRcpt only return error when AQueue is already shut down or out of memory.
|
|
_ASSERT(FAILED(hr));
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_SRV_UNAVAIL, ESVC_SHUTDOWN," %s\r\n",SMTP_SRV_UNAVAIL_STR);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//it failed. Inform the user
|
|
HandleAddressError((char *)InputLine);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Reply strings
|
|
//
|
|
|
|
typedef struct _AUTH_REPLY {
|
|
LPSTR Reply;
|
|
DWORD Len;
|
|
} AUTH_REPLY, *PAUTH_REPLY;
|
|
|
|
char *
|
|
SzNextSeparator(IN char * sz, IN CHAR ch1, IN CHAR ch2)
|
|
{
|
|
char * sz1 = NULL;
|
|
char * sz2 = NULL;
|
|
sz1 = strchr(sz, ch1);
|
|
sz2 = strchr(sz, ch2);
|
|
|
|
if (sz1 == sz2)
|
|
return sz1;
|
|
else if (sz1 > sz2)
|
|
return sz2 ? sz2 : sz1;
|
|
else
|
|
return sz1 ? sz1 : sz2;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoUSERCommand(const CHAR *InputLine, DWORD dwLineSize, unsigned char * OutputBuffer, DWORD * dwBytes, DWORD * ResponseCode, char * szECode)
|
|
{
|
|
char szUserName[MAX_USER_NAME_LEN + MAX_SERVER_NAME_LEN +1];
|
|
char szLogonDomainAndUserName[MAX_USER_NAME_LEN + (2 * (MAX_SERVER_NAME_LEN + 1))];
|
|
char lpszDefaultLogonDomain [MAX_SERVER_NAME_LEN + 1];
|
|
char * pSeparator1 = NULL;
|
|
char * pSeparator2 = NULL;
|
|
BUFFER BuffData;
|
|
DWORD DecodedLen = 0;
|
|
DWORD BuffLen = 0;
|
|
BOOL fRet = TRUE;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::DoUSERCommand");
|
|
|
|
*ResponseCode = SMTP_RESP_AUTH1_PASSED;;
|
|
|
|
m_pInstance->LockGenCrit();
|
|
|
|
lstrcpyn(lpszDefaultLogonDomain, m_pInstance->GetDefaultLogonDomain(), MAX_SERVER_NAME_LEN);
|
|
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
if(!uudecode ((char *) InputLine, &BuffData, &DecodedLen, FALSE) ||
|
|
(DecodedLen == 0))
|
|
{
|
|
*ResponseCode = SMTP_RESP_BAD_ARGS;
|
|
lstrcpy((char *)szECode, "5.7.3");
|
|
lstrcpy((char *) OutputBuffer, "Cannot decode arguments");
|
|
m_securityCtx.Reset();
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return FALSE;
|
|
}
|
|
|
|
lstrcpyn(szUserName, (char *) BuffData.QueryPtr(), sizeof(szUserName) - 1);
|
|
|
|
if (UseMbsCta())
|
|
{
|
|
// MBS clear text authentication, does not support domain
|
|
lstrcpy(szLogonDomainAndUserName, szUserName);
|
|
}
|
|
else
|
|
{
|
|
// if NT clear text authentication is used, we do some special operations:
|
|
|
|
// if no default logon domain is not present in user name, and default
|
|
// logon domain is set, then prepend default logon domain to username
|
|
pSeparator1 = SzNextSeparator(szUserName,'\\','/');
|
|
|
|
if(pSeparator1 == NULL && (lpszDefaultLogonDomain[0] != '\0'))
|
|
{
|
|
wsprintf(szLogonDomainAndUserName, "%s/%s", lpszDefaultLogonDomain, szUserName);
|
|
}
|
|
else
|
|
{
|
|
if( pSeparator1 != NULL )
|
|
{
|
|
//
|
|
// HACK-O-RAMA: bug 74776 hack, if a username comes in with "REDMOND\\user"
|
|
// allow it. This is to allow buggy Netscape 4.0 browser client to work.
|
|
//
|
|
|
|
if( ( (*pSeparator1) == '\\' ) && ( (*(pSeparator1+1)) == '\\' ) )
|
|
{
|
|
MoveMemory( pSeparator1, pSeparator1+1, strlen( pSeparator1 ) );
|
|
}
|
|
|
|
|
|
//check if we have the mailbox name at the end
|
|
pSeparator2 = SzNextSeparator(pSeparator1 + 1,'\\','/');
|
|
|
|
|
|
if(pSeparator2 != NULL)
|
|
{
|
|
//If we do simply ignore it by terminating the user string there
|
|
*pSeparator2 = '\0';
|
|
}
|
|
}
|
|
lstrcpy(szLogonDomainAndUserName, szUserName);
|
|
}
|
|
}
|
|
|
|
DebugTrace((LPARAM)this, "LogonDomainAndUser: %s", szLogonDomainAndUserName);
|
|
|
|
fRet = ProcessAuthInfo(AuthCommandUser, szLogonDomainAndUserName, OutputBuffer, &BuffLen);
|
|
if(!fRet)
|
|
{
|
|
*ResponseCode = SMTP_RESP_AUTH_REJECT;
|
|
lstrcpy((char *)szECode, "5.7.3");
|
|
lstrcpy((char *) OutputBuffer, "Invalid user name");
|
|
*dwBytes = lstrlen("Invalid user name");
|
|
}
|
|
else
|
|
{
|
|
*dwBytes = BuffLen;
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------
|
|
//
|
|
// Function: SMTP_CONNECTION::DoPASSCommand
|
|
//
|
|
// Synopsis: Valid in the Authorization State.
|
|
// pswd for clear-text logon
|
|
//
|
|
// PASS password\r\n
|
|
//
|
|
// Arguments: const CHAR *: argument line
|
|
// DWORD: sizeof argument line
|
|
//
|
|
// Returns: BOOL: continue processing
|
|
//
|
|
//----------------------------------------------------------------
|
|
BOOL SMTP_CONNECTION::DoPASSCommand(const CHAR *InputLine, DWORD dwLineSize, unsigned char * OutputBuffer, DWORD * dwBytes, DWORD *ResponseCode, char * szECode)
|
|
{
|
|
BOOL fStatus = TRUE;
|
|
BUFFER BuffData;
|
|
DWORD DecodedLen = 0;
|
|
DWORD BuffLen = 0;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::DoPASSCommand");
|
|
|
|
*ResponseCode = SMTP_RESP_AUTHENTICATED;
|
|
|
|
if(!uudecode ((char *)InputLine, &BuffData, &DecodedLen, FALSE))
|
|
{
|
|
m_RecvdAuthCmd = FALSE;
|
|
m_fClearText = FALSE;
|
|
m_State = HELO;
|
|
*ResponseCode = SMTP_RESP_BAD_ARGS;
|
|
|
|
if ( ++m_dwUnsuccessfulLogons >= m_pInstance->GetMaxLogonFailures() )
|
|
{
|
|
ErrorTrace((LPARAM)this, "Logon failed");
|
|
DisconnectClient( ERROR_LOGON_FAILURE );
|
|
}
|
|
|
|
lstrcpy((char *)szECode, "5.7.3");
|
|
lstrcpy((char *) OutputBuffer, "Cannot decode password");
|
|
m_securityCtx.Reset();
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return FALSE;
|
|
}
|
|
|
|
OutputBuffer[0] = '\0';
|
|
fStatus = ProcessAuthInfo( AuthCommandPassword, (char *) BuffData.QueryPtr(), OutputBuffer, &BuffLen );
|
|
if (!fStatus)
|
|
{
|
|
ErrorTrace( (LPARAM)this, "Bad pswd %s", QueryClientUserName() );
|
|
*ResponseCode = SMTP_RESP_AUTH_REJECT;
|
|
lstrcpy((char *)szECode, "5.7.3");
|
|
lstrcpy((char *) OutputBuffer, "Authentication unsuccessful");
|
|
|
|
if ( ++m_dwUnsuccessfulLogons >= m_pInstance->GetMaxLogonFailures() )
|
|
{
|
|
ErrorTrace((LPARAM)this, "Logon failed");
|
|
DisconnectClient( ERROR_LOGON_FAILURE );
|
|
}
|
|
else
|
|
{
|
|
m_RecvdAuthCmd = FALSE;
|
|
m_fClearText = FALSE;
|
|
m_State = HELO;
|
|
}
|
|
}
|
|
|
|
*dwBytes = BuffLen;
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return fStatus;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::ProcessAuthInfo(AUTH_COMMAND Command, LPSTR Blob, unsigned char * OutBuff, DWORD * dwBytes)
|
|
{
|
|
DWORD nbytes = 0;
|
|
REPLY_LIST replyID;
|
|
BOOL f = TRUE;
|
|
|
|
//
|
|
// Set the IP address so the security log has the info
|
|
//
|
|
SecpSetIPAddress((PUCHAR)&m_saClient, sizeof(m_saClient));
|
|
|
|
m_pInstance->LockGenCrit();
|
|
f = m_securityCtx.ProcessAuthInfo(m_pInstance, Command, Blob,
|
|
OutBuff, &nbytes, &replyID);
|
|
m_pInstance->UnLockGenCrit();
|
|
|
|
*dwBytes = nbytes;
|
|
|
|
//
|
|
// if replyID == NULL we're conversing for challenge/response logon
|
|
//
|
|
if ( replyID == SecNull )
|
|
{
|
|
_ASSERT( nbytes != 0 );
|
|
}
|
|
|
|
if (m_securityCtx.IsAuthenticated())
|
|
{
|
|
f = TRUE;
|
|
}
|
|
|
|
if ( f == FALSE )
|
|
{
|
|
//
|
|
// if we fail for any reason reset the state to accept user/auth/apop
|
|
//
|
|
m_securityCtx.Reset();
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------
|
|
//
|
|
// Function: SMTP_CONNECTION::DoAuthNegotiation
|
|
//
|
|
// Synopsis: process base64/uuendcoded blobs in AUTH_NEGOTIATE
|
|
//
|
|
// base64 text\r\n
|
|
//
|
|
// Arguments: const CHAR *: argument line
|
|
// DWORD: sizeof argument line
|
|
//
|
|
// Returns: BOOL: continue processing
|
|
//
|
|
//----------------------------------------------------------------
|
|
BOOL SMTP_CONNECTION::DoAuthNegotiation(const CHAR *InputLine, DWORD dwLineSize)
|
|
{
|
|
//The size of the output buffer should vary with the package being used.
|
|
//Currently we use a quickfix which should work for LOGIN and NTLM but will
|
|
//fail for other (new) packages if the response string > 25*255+1. Replace
|
|
//this by a CBuffer later. Size of the CBuffer should be the max token size
|
|
//for the package from CACHED_CREDENTIAL::GetCachedCredential.
|
|
//GPulla, 6/15/1999
|
|
|
|
unsigned char OutputBuff[MAX_REPLY_SIZE];
|
|
char szECode[25];
|
|
BOOL fAuthed = TRUE;
|
|
BUFFER BuffData;
|
|
DWORD ResponseCode;
|
|
DWORD dwBytes = 0;
|
|
BOOL fDoBase64 = FALSE;
|
|
char * pszSearch = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "DoAuthNegotiation");
|
|
|
|
if (m_cbParsable >= QueryMaxReadSize())
|
|
{
|
|
m_cbParsable = 0;
|
|
m_ProtocolErrors++;
|
|
}
|
|
|
|
pszSearch = IsLineComplete(InputLine,m_cbParsable);
|
|
if(pszSearch == NULL)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return TRUE;
|
|
}
|
|
|
|
*pszSearch = '\0';
|
|
m_cbParsable = 0;
|
|
if(!lstrcmp(InputLine, "*"))
|
|
{
|
|
FormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS, " %s\r\n", "Auth command cancelled");
|
|
m_fDidUserCmd = FALSE;
|
|
m_RecvdAuthCmd = FALSE;
|
|
m_fClearText = FALSE;
|
|
m_State = HELO;
|
|
m_securityCtx.Reset();
|
|
SendSmtpResponse();
|
|
ProtocolLog(AUTH, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_ARGS, 0, 0);
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(m_fClearText)
|
|
{
|
|
if(m_fDidUserCmd)
|
|
{
|
|
if(DoPASSCommand(InputLine, dwLineSize, OutputBuff, &dwBytes, &ResponseCode, szECode))
|
|
{
|
|
m_State = HELO;
|
|
m_fAuthenticated = TRUE;
|
|
lstrcpy((char *)szECode, "2.7.0");
|
|
lstrcpy((char *)OutputBuff, "Authentication successful");
|
|
}
|
|
else
|
|
{
|
|
fAuthed = FALSE;
|
|
// lstrcpy((char *)OutputBuff, "5.7.3 Authentication unsuccessful");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(DoUSERCommand(InputLine, dwLineSize, OutputBuff, &dwBytes, &ResponseCode, szECode))
|
|
{
|
|
lstrcpy((char *)OutputBuff, PasswordParam);
|
|
m_fDidUserCmd = TRUE;
|
|
fDoBase64 = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fAuthed = FALSE;
|
|
}
|
|
}
|
|
|
|
if(fDoBase64)
|
|
{
|
|
if(!uuencode ((unsigned char *)OutputBuff, lstrlen((char *) OutputBuff), &BuffData, FALSE))
|
|
{
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
FormatSmtpMessage (ResponseCode,NULL," %s\r\n", BuffData.QueryPtr());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FormatSmtpMessage (ResponseCode,szECode," %s\r\n", OutputBuff);
|
|
}
|
|
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, ResponseCode, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
fAuthed = ProcessAuthInfo( AuthCommandTransact, (LPSTR)InputLine, OutputBuff, &dwBytes );
|
|
if(fAuthed)
|
|
{
|
|
if(m_securityCtx.IsAuthenticated())
|
|
{
|
|
m_State = HELO;
|
|
m_fAuthenticated = TRUE;
|
|
fAuthed = TRUE;
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_AUTHENTICATED, ESEC_SUCCESS," %s\r\n", "Authentication successful");
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, SMTP_RESP_AUTHENTICATED, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
FormatSmtpMessage (SMTP_RESP_AUTH1_PASSED,NULL," ");
|
|
FormatSmtpMessage(OutputBuff, dwBytes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FormatSmtpMessage (SMTP_RESP_AUTH_REJECT, ENO_SECURITY, " %s\r\n", "Authentication unsuccessful");
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, SMTP_RESP_AUTH_REJECT, 0, 0);
|
|
if ( ++m_dwUnsuccessfulLogons >= m_pInstance->GetMaxLogonFailures() )
|
|
{
|
|
ErrorTrace((LPARAM)this, "Logon failed");
|
|
SendSmtpResponse();
|
|
DisconnectClient( ERROR_LOGON_FAILURE );
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!fAuthed)
|
|
{
|
|
m_State = HELO;
|
|
m_RecvdAuthCmd = FALSE;
|
|
m_fDidUserCmd= FALSE;
|
|
}
|
|
|
|
SendSmtpResponse();
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return fAuthed;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoAUTHCommand
|
|
|
|
Description:
|
|
This function performs authentication
|
|
for the SMTP service
|
|
Arguments:
|
|
Are ignored
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoAUTHCommand(const char * InputLine, DWORD ParameterSize)
|
|
{
|
|
BOOL fAuthPassed = TRUE;
|
|
char * pMechanism = NULL;
|
|
char * pInitialResponse = NULL;
|
|
DWORD dwConnectionStatus = 0;
|
|
|
|
//The size of the output buffer should vary with the package being used.
|
|
//Currently we use a quickfix which should work for LOGIN and NTLM but will
|
|
//fail for other (new) packages if the response string > 25*255+1. Replace
|
|
//this by a CBuffer later. Size of the CBuffer should be the max token size
|
|
//for the package from CACHED_CREDENTIAL::GetCachedCredential.
|
|
//GPulla, 6/15/1999
|
|
|
|
unsigned char OutputBuffer[MAX_REPLY_SIZE];
|
|
char szECode[16];
|
|
BUFFER BuffData;
|
|
DWORD DecodedLen = 0;
|
|
DWORD dwBytes = 0;
|
|
DWORD ResponseCode;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoAUTHCommand");
|
|
|
|
//Do we allow AUTH command
|
|
if(!m_pInstance->AllowAuth())
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_CMD, ENOT_IMPLEMENTED," %s\r\n", SMTP_BAD_CMD_STR);
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, SMTP_RESP_BAD_CMD, 0, 0);
|
|
++m_ProtocolErrors;
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
//check if helo was sent
|
|
if(!m_pInstance->AllowMailFromNoHello() && !m_HelloSent)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n", "Send hello first" );
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
++m_ProtocolErrors;
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
OutputBuffer[0] = '\0';
|
|
|
|
//disallow mail from more than once
|
|
if(m_RecvdAuthCmd)
|
|
{
|
|
++m_ProtocolErrors;
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", "Auth already specified" );
|
|
ProtocolLog(AUTH, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "DoAuthCommand - SMTP_RESP_BAD_SEQ, Sender already specified");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Check syntax: one required parameter, and one optional parameter
|
|
//
|
|
|
|
pMechanism = strtok((char *) InputLine, WHITESPACE);
|
|
pInitialResponse = strtok(NULL, WHITESPACE);
|
|
|
|
if(pMechanism == NULL)
|
|
{
|
|
++m_ProtocolErrors;
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS, " %s\r\n","No mechanism" );
|
|
ProtocolLog(AUTH, (char *) InputLine, NO_ERROR, SMTP_RESP_BAD_SEQ, 0, 0);
|
|
ErrorTrace((LPARAM) this, "DoAuthCommand - SMTP_RESP_BAD_SEQ, Sender already specified");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
strncpy(m_szUsedAuthKeyword, pMechanism, sizeof(m_szUsedAuthKeyword)-1);
|
|
if(!lstrcmpi(pMechanism, "login"))
|
|
{
|
|
if(m_SecurePort)
|
|
dwConnectionStatus |= SMTP_IS_SSL_CONNECTION;
|
|
|
|
if(m_pInstance->AllowLogin(dwConnectionStatus))
|
|
{
|
|
m_fClearText = TRUE;
|
|
|
|
if(pInitialResponse)
|
|
{
|
|
if(DoUSERCommand((const char *) pInitialResponse, lstrlen(pInitialResponse), OutputBuffer, &dwBytes, &ResponseCode, szECode))
|
|
{
|
|
m_fDidUserCmd = TRUE;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (ResponseCode, szECode," %s\r\n",OutputBuffer );
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, SMTP_RESP_PARM_NOT_IMP, 0, 0);
|
|
fAuthPassed = FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_PARM_NOT_IMP, ENO_SEC_PACKAGE, " %s \r\n","Unrecognized authentication type" );
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, SMTP_RESP_PARM_NOT_IMP, 0, 0);
|
|
fAuthPassed = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
//
|
|
// Switch over to using a large receive buffer, because a AUTH blob
|
|
// may be up to 32K big.
|
|
BOOL fStartSASL = SwitchToBigSSLBuffers();
|
|
if (fStartSASL) {
|
|
//PE_CdFormatSmtpMessage(SMTP_RESP_READY, EPROT_SUCCESS," %s\r\n", SMTP_READY_STR);
|
|
ProtocolLog(AUTH, (char *) InputLine, NO_ERROR, SMTP_RESP_READY, 0, 0);
|
|
} else {
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n", SMTP_NO_MEMORY);
|
|
ProtocolLog(AUTH, (char *) InputLine, NO_ERROR, SMTP_RESP_NORESOURCES, 0, 0);
|
|
}
|
|
*/
|
|
|
|
//
|
|
// Register our principal names, if necessary
|
|
//
|
|
|
|
QuerySmtpInstance()->RegisterServicePrincipalNames(TRUE);
|
|
|
|
if(ProcessAuthInfo( AuthCommandTransact, pMechanism, OutputBuffer, &dwBytes ) == FALSE )
|
|
{
|
|
fAuthPassed = FALSE;
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_PARM_NOT_IMP, ENO_SEC_PACKAGE, " %s \r\n", "Unrecognized authentication type" );
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, SMTP_RESP_PARM_NOT_IMP, 0, 0);
|
|
}
|
|
else if(pInitialResponse)
|
|
{
|
|
if(ProcessAuthInfo( AuthCommandTransact, pInitialResponse, OutputBuffer, &dwBytes ) == FALSE )
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_AUTH_REJECT, EINVALID_ARGS, " %s\r\n","Cannot authenticate parameter" );
|
|
ProtocolLog(AUTH, (char *) QueryClientUserName(), NO_ERROR, SMTP_RESP_AUTH_REJECT, 0, 0);
|
|
fAuthPassed = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(fAuthPassed)
|
|
{
|
|
m_RecvdAuthCmd = TRUE;
|
|
m_State = AUTH;
|
|
|
|
if(!pInitialResponse)
|
|
{
|
|
if(!m_fClearText)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_AUTH1_PASSED,NULL," %s supported\r\n", pMechanism);
|
|
}
|
|
else
|
|
{
|
|
if(!uuencode ((unsigned char *)UserParam, lstrlen(UserParam), &BuffData, FALSE))
|
|
{
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_AUTH1_PASSED,NULL," %s\r\n", BuffData.QueryPtr());
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!m_fClearText)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_AUTH1_PASSED,NULL," ");
|
|
OutputBuffer[dwBytes] = '\0';
|
|
PE_FormatSmtpMessage("%s\r\n",OutputBuffer);
|
|
}
|
|
else
|
|
{
|
|
if(!uuencode ((unsigned char *)PasswordParam, lstrlen(PasswordParam), &BuffData, FALSE))
|
|
{
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return TRUE;
|
|
}
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_AUTH1_PASSED,NULL," %s\r\n", BuffData.QueryPtr());
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
m_ProtocolErrors++; //even though we kick out the connection
|
|
//after MaxLogonFailures, count this as a
|
|
//protocol error
|
|
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return(TRUE);
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::DoTURNCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
sockaddr_in AddrRemote;
|
|
SMTP_CONNOUT * SmtpConn = NULL;
|
|
DWORD Error = 0;
|
|
DWORD Options = 0;
|
|
char * pszUserName = "";
|
|
MULTISZ* pmsz = NULL;
|
|
TURN_DOMAIN_LIST TurnDomainList;
|
|
|
|
LPSTR pszAuthenticatedUserName = NULL;
|
|
const char * StartPtr = NULL;
|
|
BOOL Found = FALSE;
|
|
|
|
ISMTPConnection *pISMTPConnection = NULL;
|
|
char Domain[MAX_INTERNET_NAME];
|
|
Domain[0] = '\0';
|
|
DWORD DomainOptions = 0;
|
|
HRESULT hr = S_OK;
|
|
DomainInfo DomainParams;
|
|
ZeroMemory (&DomainParams, sizeof(DomainParams));
|
|
DomainParams.cbVersion = sizeof(DomainParams);
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoTurnCommand");
|
|
|
|
//check if helo was sent
|
|
if(!m_pInstance->AllowMailFromNoHello() && !m_HelloSent)
|
|
{
|
|
ErrorTrace((LPARAM) this, "In TURN - Hello not sent");
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR, " %s\r\n","Send hello first" );
|
|
++m_ProtocolErrors;
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(!m_fAuthenticated)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY, " %s\r\n", "Client was not authenticated");
|
|
ErrorTrace((LPARAM) this, "DoTURNCommand - SMTP_RESP_MUST_SECURE, user not authenticated");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
if(m_securityCtx.QueryUserName())
|
|
ErrorTrace((LPARAM) this, "Looking up user %s in TURN table", m_securityCtx.QueryUserName());
|
|
|
|
pmsz = new MULTISZ();
|
|
if(pmsz == NULL)
|
|
{
|
|
Error = GetLastError();
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_MEMORY );
|
|
FatalTrace((LPARAM) this, "SMTP_CONNOUT::CreateSmtpConnection failed for TURN");
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
pmsz->Reset();
|
|
|
|
// Do we have a username from an AUTH sink? If not use anything from SMTP auth
|
|
pszAuthenticatedUserName =
|
|
m_szAuthenticatedUserNameFromSink[0] ?
|
|
m_szAuthenticatedUserNameFromSink : m_securityCtx.QueryUserName();
|
|
|
|
QuerySmtpInstance()->IsUserInTurnTable(pszAuthenticatedUserName, pmsz);
|
|
//QuerySmtpInstance()->IsUserInTurnTable("joe", &msz);
|
|
|
|
if(pmsz->IsEmpty())
|
|
{
|
|
delete pmsz;
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY," %s\r\n","This authenticated user is not allowed to issue TURN");
|
|
ErrorTrace((LPARAM) this, "DoTURNCommand - SMTP_RESP_MUST_SECURE, user not in turn table");
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//Determine the primary domain in the list of domains that are to be turned
|
|
//The primary domain is one that matches the domain name specified in the EHLO command
|
|
//NimishK : What if EHLO is not provided
|
|
//
|
|
for (StartPtr = pmsz->First();
|
|
( (StartPtr != NULL) && !QuerySmtpInstance()->IsShuttingDown());
|
|
StartPtr = pmsz->Next( StartPtr ))
|
|
{
|
|
ErrorTrace((LPARAM) this, "looking for %s in TURN domains", QueryClientUserName());
|
|
if(!lstrcmpi(QueryClientUserName(), StartPtr))
|
|
{
|
|
Found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//If we can determine the primary domain - we get the ISMTPCONNECTION for that domain
|
|
//else we get the first domain in the list and get the ISMTPCONNECTION for it
|
|
BOOL fPrimaryDomain = FALSE;
|
|
if(!Found)
|
|
StartPtr = pmsz->First();
|
|
else
|
|
fPrimaryDomain = TRUE;
|
|
|
|
//We need to keep walking the list of domains that are to be turned
|
|
//till we get a valid ISMTPCONN or run out of domains
|
|
while(StartPtr && !QuerySmtpInstance()->IsShuttingDown())
|
|
{
|
|
hr = QuerySmtpInstance()->GetConnManPtr()->GetNamedConnection(lstrlen(StartPtr), (CHAR*)StartPtr, &pISMTPConnection);
|
|
if(FAILED(hr))
|
|
{
|
|
//Something bad happened on this call
|
|
//report to client
|
|
delete pmsz;
|
|
PE_CdFormatSmtpMessage (SMTP_ERROR_PROCESSING_CODE, ENO_RESOURCES," %s\r\n", "Error processing the command");
|
|
ErrorTrace((LPARAM) this, "DoTURNCommand - SMTP_ERROR_PROCESSING_CODE, GetNamedConnection failed %d",hr);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//If the link corresponding to this domain does not exist in AQ, we get a NULL
|
|
//ISMTPConnection at this point
|
|
//If we do there is nothing to TURN
|
|
if(pISMTPConnection)
|
|
break;
|
|
else
|
|
{
|
|
if(fPrimaryDomain)
|
|
{
|
|
StartPtr = pmsz->First();
|
|
fPrimaryDomain = FALSE;
|
|
}
|
|
else
|
|
{
|
|
StartPtr = pmsz->Next( StartPtr );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
}
|
|
if(pISMTPConnection == NULL && StartPtr == NULL)
|
|
{
|
|
//We ran out of the domains and there is not a single link
|
|
PE_CdFormatSmtpMessage (250, EPROT_SUCCESS, " %s\r\n", "Server was turned");
|
|
PE_SendSmtpResponse();
|
|
|
|
PE_FormatSmtpMessage ("%s\r\n","QUIT");
|
|
PE_SendSmtpResponse();
|
|
|
|
//we will disconnect indirectly by returning false
|
|
//DisconnectClient();
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
else
|
|
{
|
|
//leave quickly if we are shutting down
|
|
if(QuerySmtpInstance()->IsShuttingDown()
|
|
|| (QuerySmtpInstance()->QueryServerState( ) == MD_SERVER_STATE_STOPPED)
|
|
|| (QuerySmtpInstance()->QueryServerState( ) == MD_SERVER_STATE_INVALID))
|
|
{
|
|
if(pISMTPConnection)
|
|
{
|
|
//Ack the last connection
|
|
pISMTPConnection->AckConnection((eConnectionStatus)CONNECTION_STATUS_OK);
|
|
pISMTPConnection->Release();
|
|
pISMTPConnection = NULL;
|
|
}
|
|
delete pmsz;
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return FALSE;
|
|
}
|
|
|
|
DomainParams.szDomainName = Domain;
|
|
DomainParams.cbDomainNameLength = sizeof(Domain);
|
|
hr = pISMTPConnection->GetDomainInfo(&DomainParams);
|
|
|
|
|
|
TurnDomainList.pmsz = pmsz;
|
|
//If we just got the ISMTPCOnn for the primary domain then we probably
|
|
//jumped over a few domains in the domain list.
|
|
//Set the ptr to start of the domain list
|
|
//That way we will look at all the domains, once we are done with the
|
|
//current primary domain
|
|
if(fPrimaryDomain)
|
|
{
|
|
TurnDomainList.szCurrentDomain = pmsz->First();
|
|
fPrimaryDomain = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//Looks like we failed to get primary domain
|
|
//so got to have started from the top - continue that
|
|
TurnDomainList.szCurrentDomain = StartPtr;
|
|
}
|
|
|
|
//If we could not determine a primary domain - use the base
|
|
//connection options
|
|
if(!Found)
|
|
DomainOptions = 0;
|
|
else
|
|
DomainOptions = DomainParams.dwDomainInfoFlags;
|
|
|
|
//set the remote IP address we connected to
|
|
AddrRemote.sin_addr.s_addr = 0;
|
|
|
|
//
|
|
// Create an outbound connection
|
|
// Note: The last parameter, m_pSSLVerificationName is NULL. This is the
|
|
// name used to match against the SSL certificate that the server gives
|
|
// us during an outbound session. Setting it to NULL skips certificate
|
|
// subject validation, which is fine in the case of TURN, since either
|
|
// the server is already authenticated through other means (if ATRN) or
|
|
// we don't care about authentication (if TURN is configured).
|
|
//
|
|
SmtpConn = SMTP_CONNOUT::CreateSmtpConnection(
|
|
QuerySmtpInstance(),
|
|
(SOCKET) m_pAtqContext->hAsyncIO,
|
|
(SOCKADDR_IN *)&AddrRemote,
|
|
(SOCKADDR_IN *)&AddrRemote,
|
|
NULL,
|
|
(PVOID)&TurnDomainList,
|
|
0,
|
|
DomainOptions,
|
|
NULL,
|
|
NULL);
|
|
if(SmtpConn == NULL)
|
|
{
|
|
delete pmsz;
|
|
Error = GetLastError();
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_MEMORY );
|
|
FatalTrace((LPARAM) this, "SMTP_CONNOUT::CreateSmtpConnection failed for TURN");
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
|
|
//from here on, the smtpout class is responsible for
|
|
//cleaning up the AtqContext
|
|
m_DoCleanup = FALSE;
|
|
|
|
SmtpConn->SetAtqContext(m_pAtqContext);
|
|
|
|
//copy the real domain we are connected to.
|
|
SmtpConn->SetConnectedDomain((char *) Domain);
|
|
AtqContextSetInfo(m_pAtqContext, ATQ_INFO_COMPLETION, (DWORD_PTR) InternetCompletion);
|
|
AtqContextSetInfo(m_pAtqContext, ATQ_INFO_COMPLETION_CONTEXT, (DWORD_PTR) SmtpConn);
|
|
AtqContextSetInfo(m_pAtqContext, ATQ_INFO_TIMEOUT, m_pInstance->GetRemoteTimeOut());
|
|
|
|
|
|
//insert the outbound connection object into
|
|
//our list of outbound conection objects
|
|
if(!QuerySmtpInstance()->InsertNewOutboundConnection(SmtpConn, TRUE))
|
|
{
|
|
Error = GetLastError();
|
|
FatalTrace((LPARAM) this, "m_pInstance->InsertNewOutboundConnection failed for TURN");
|
|
SmtpConn->DisconnectClient();
|
|
delete SmtpConn;
|
|
SmtpConn = NULL;
|
|
SetLastError(Error);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
SmtpConn->SetCurrentObject(pISMTPConnection);
|
|
PE_CdFormatSmtpMessage (250, EPROT_SUCCESS, " %s\r\n", "Server was turned");
|
|
PE_SendSmtpResponse();
|
|
|
|
}
|
|
|
|
//start session will pend a read to pick
|
|
//up the servers signon banner
|
|
if(!SmtpConn->StartSession())
|
|
{
|
|
//get the error
|
|
Error = GetLastError();
|
|
FatalTrace((LPARAM) this, "SmtpConn->StartSession failed for TURN");
|
|
SmtpConn->DisconnectClient();
|
|
QuerySmtpInstance()->RemoveOutboundConnection(SmtpConn);
|
|
delete SmtpConn;
|
|
SmtpConn = NULL;
|
|
SetLastError (Error);
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
m_State = TURN;
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return(FALSE);
|
|
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::DoLASTCommand
|
|
|
|
Description:
|
|
This is our catch all error function.
|
|
It will determin what kind of error
|
|
made it execute and send an appropriate
|
|
message back to the client.
|
|
Arguments:
|
|
Are ignored
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::DoLASTCommand(const char * InputLine, DWORD parameterSize)
|
|
{
|
|
BOOL RetStatus = TRUE;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoLASTCommand");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_CMD, ENOT_IMPLEMENTED," %s\r\n", SMTP_BAD_CMD_STR);
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
m_ProtocolErrors++;
|
|
PE_SendSmtpResponse();
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return RetStatus;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
SMTP_CONNECTION::VerifiyClient
|
|
|
|
Description:
|
|
This function compares the IP address the connection
|
|
was made on to the IP address of the name given with
|
|
the HELO or EHLO commands
|
|
|
|
Arguments:
|
|
ClientHostName - from HELO or EHLO command
|
|
KnownIpAddress - from accept
|
|
Returns:
|
|
|
|
TRUE if the connection should stay open.
|
|
FALSE if this object should be deleted.
|
|
|
|
--*/
|
|
DWORD SMTP_CONNECTION::VerifiyClient (const char * ClientHostName, const char * KnownIpAddress)
|
|
{
|
|
in_addr UNALIGNED * P_Addr = NULL;
|
|
PHOSTENT Hp = NULL;
|
|
DWORD KnownClientAddress = 0;
|
|
DWORD dwRet = SUCCESS;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::VerifiyClient");
|
|
|
|
Hp = gethostbyname (ClientHostName);
|
|
if (Hp == NULL)
|
|
{
|
|
DWORD dwErr = WSAGetLastError();
|
|
if(dwErr == WSANO_DATA)
|
|
dwRet = NO_MATCH;
|
|
else
|
|
dwRet = LOOKUP_FAILED;
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return dwRet;
|
|
}
|
|
|
|
KnownClientAddress = inet_addr (KnownIpAddress);
|
|
if (KnownClientAddress == INADDR_NONE)
|
|
{
|
|
dwRet = LOOKUP_FAILED;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return dwRet;
|
|
}
|
|
|
|
while( (P_Addr = (in_addr UNALIGNED *)*Hp->h_addr_list++) != NULL)
|
|
{
|
|
if (P_Addr->s_addr == KnownClientAddress)
|
|
{
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return dwRet;
|
|
}
|
|
}
|
|
|
|
dwRet = NO_MATCH;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return dwRet;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
void SMTP_CONNECTION::HandleAddressError(char * InputLine)
|
|
|
|
Description:
|
|
common code to determine what error occurred
|
|
as a result of address validation/allocation failure failure
|
|
Arguments:
|
|
|
|
none
|
|
Returns:
|
|
|
|
none
|
|
--*/
|
|
void SMTP_CONNECTION::HandleAddressError(char * InputLine)
|
|
{
|
|
DWORD Error = GetLastError();
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::HandleAddressError");
|
|
|
|
if(Error == ERROR_NOT_ENOUGH_MEMORY)
|
|
{
|
|
BUMP_COUNTER(QuerySmtpInstance(), MsgsRefusedDueToNoCAddrObjects);
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_MEMORY );
|
|
m_CInboundContext.SetWin32Status(Error);
|
|
FatalTrace((LPARAM) this, "HandleAddressError - SMTP_RESP_NORESOURCES, SMTP_NO_MEMORY");
|
|
}
|
|
else if (Error == ERROR_INVALID_DATA)
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS, EINVALID_ARGS," %s\r\n",SMTP_INVALID_ADDR_MSG);
|
|
FatalTrace((LPARAM) this, "HandleAddressError - SMTP_RESP_BAD_ARGS, SMTP_INVALID_ADDR_MSG");
|
|
m_ProtocolErrors++;
|
|
}
|
|
else
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MBX_SYNTAX, ESYNTAX_ERROR," %s\r\n","Unknown Error");
|
|
m_CInboundContext.SetWin32Status(Error);
|
|
FatalTrace((LPARAM) this, "HandleAddressError - SMTP_RESP_MBX_SYNTAX, Unknown Error");
|
|
m_ProtocolErrors++;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
void SMTP_CONNECTION::SkipWord
|
|
|
|
Description:
|
|
skips over words in a buffer and
|
|
returns pointer to next word
|
|
Arguments:
|
|
|
|
none
|
|
Returns:
|
|
|
|
none
|
|
--*/
|
|
char * SMTP_CONNECTION::SkipWord (char * Buffer, char * WordToSkip, DWORD WordLen)
|
|
{
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::SkipWord");
|
|
|
|
//find string
|
|
if (strncasecmp(Buffer, WordToSkip, WordLen))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS , EINVALID_ARGS," %s %s\r\n", "Unrecognized parameter", Buffer);
|
|
ProtocolLog(USE_CURRENT, Buffer, NO_ERROR, SMTP_RESP_BAD_ARGS, 0, 0);
|
|
return NULL;
|
|
}
|
|
|
|
//skip past word
|
|
Buffer += WordLen;
|
|
|
|
//skip white spaces looking for the ":" character
|
|
while( (*Buffer != '\0') && (isspace ((UCHAR)*Buffer) || (*Buffer == '\t')))
|
|
Buffer++;
|
|
|
|
if( (*Buffer == '\0') || (*Buffer != ':'))
|
|
{
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_ARGS , EINVALID_ARGS," %s %s\r\n", "Unrecognized parameter", Buffer);
|
|
ProtocolLog(USE_CURRENT, Buffer, NO_ERROR, SMTP_RESP_BAD_ARGS, 0, 0);
|
|
return NULL;
|
|
}
|
|
|
|
//add one to skip the ":"
|
|
Buffer++;
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return Buffer;
|
|
}
|
|
|
|
VOID SMTP_CONNECTION::ProtocolLog(DWORD dwCommand, LPCSTR pszParameters, DWORD dwWin32Error,
|
|
DWORD dwSmtpError, DWORD BytesSent, DWORD BytesRecv, LPSTR pszTarget)
|
|
{
|
|
LPCSTR pszCmd = "";
|
|
char szKeyword[100];
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::ProtocolLog");
|
|
|
|
_ASSERT (m_pInstance != NULL);
|
|
|
|
//
|
|
// verify if we should use the current command
|
|
//
|
|
if ( dwCommand == USE_CURRENT )
|
|
{
|
|
dwCommand = m_dwCurrentCommand;
|
|
}
|
|
|
|
if(dwCommand == SMTP_TIMEOUT)
|
|
{
|
|
strcpy(szKeyword, "TIMEOUT");
|
|
pszCmd = szKeyword;
|
|
}
|
|
else
|
|
{
|
|
pszCmd = (LPSTR)SmtpCommands[ dwCommand ];
|
|
if ( pszCmd == NULL )
|
|
{
|
|
// if this is a PE word then its the first word in the
|
|
// parameters
|
|
char *pszSpace = strchr(pszParameters, ' ');
|
|
if (pszSpace) {
|
|
DWORD_PTR cToCopy = (DWORD_PTR) pszSpace - (DWORD_PTR) pszParameters;
|
|
if (cToCopy >= sizeof(szKeyword)) cToCopy = sizeof(szKeyword) - 1;
|
|
strncpy(szKeyword, pszParameters, cToCopy);
|
|
szKeyword[cToCopy] = 0;
|
|
pszParameters += cToCopy;
|
|
pszCmd = szKeyword;
|
|
} else {
|
|
pszCmd = pszParameters;
|
|
pszParameters = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// filter so we only get the desired commands
|
|
//
|
|
DebugTrace( (LPARAM)this,
|
|
"Allow cmds: 0x%08X, cmd: 0x%08X",
|
|
m_pInstance->GetCmdLogFlags(),
|
|
(1<<dwCommand) );
|
|
|
|
if ( m_pInstance->GetCmdLogFlags() & (1<<dwCommand) )
|
|
{
|
|
TransactionLog(
|
|
pszCmd,
|
|
pszParameters,
|
|
pszTarget,
|
|
dwWin32Error,
|
|
dwSmtpError);
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
}
|
|
|
|
|
|
BOOL
|
|
SMTP_CONNECTION::BindInstanceAccessCheck(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Bind IP/DNS access check for this request to instance data
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
BOOL - TRUE if success, otherwise FALSE.
|
|
|
|
--*/
|
|
{
|
|
m_pInstance->LockGenCrit();
|
|
|
|
if ( m_rfAccessCheck.CopyFrom( m_pInstance->QueryMetaDataRefHandler() ) )
|
|
{
|
|
m_acAccessCheck.BindCheckList( (LPBYTE)m_rfAccessCheck.GetPtr(), m_rfAccessCheck.GetSize() );
|
|
m_pInstance->UnLockGenCrit();
|
|
return TRUE;
|
|
}
|
|
|
|
m_pInstance->UnLockGenCrit();
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
SMTP_CONNECTION::UnbindInstanceAccessCheck()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Unbind IP/DNS access check for this request to instance data
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
{
|
|
m_acAccessCheck.UnbindCheckList();
|
|
m_rfAccessCheck.Reset( (IMDCOM*) g_pInetSvc->QueryMDObject() );
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
void SMTP_CONNECTION::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_CONNECTION::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 :
|
|
void SMTP_CONNECTION::DecryptInputBuffer
|
|
|
|
Description:
|
|
Helper routine to decrypt the recieve buffer when session is SSL
|
|
encypted.
|
|
|
|
Arguments:
|
|
none
|
|
|
|
Returns:
|
|
TRUE if the receive buffer was successfully decrypted, FALSE otherwise
|
|
|
|
--*/
|
|
|
|
BOOL SMTP_CONNECTION::DecryptInputBuffer(void)
|
|
{
|
|
TraceFunctEnterEx( (LPARAM)this, "SMTP_CONNECTION::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;
|
|
m_SessionSize += 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 );
|
|
|
|
DisconnectClient( dwError );
|
|
}
|
|
|
|
return( dwError == NO_ERROR );
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
void SMTP_CONNECTION::IsClientSecureEnough
|
|
|
|
Description:
|
|
Finds out whether the client has negotiated the appropriate security
|
|
level for the server instance for which this connection was
|
|
created.
|
|
|
|
Arguments:
|
|
none
|
|
|
|
Returns:
|
|
TRUE if the client has negotiated appropriate level of security
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::IsClientSecureEnough(void)
|
|
{
|
|
BOOL fRet = TRUE;
|
|
|
|
if (QuerySmtpInstance()->RequiresSSL() && !m_SecurePort) {
|
|
m_CInboundContext.m_dwSmtpStatus = SMTP_RESP_MUST_SECURE;
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, E_TLSNEEDED, " %s\r\n", SMTP_MSG_MUST_SECURE);
|
|
fRet = FALSE;
|
|
}
|
|
else if (m_SecurePort &&
|
|
QuerySmtpInstance()->Requires128Bits() &&
|
|
m_encryptCtx.QueryKeySize() < 128) {
|
|
m_CInboundContext.m_dwSmtpStatus = SMTP_RESP_TRANS_FAILED;
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_TRANS_FAILED, E_TLSNEEDED, " %s\r\n", SMTP_MSG_NOT_SECURE_ENOUGH);
|
|
fRet = FALSE;
|
|
}
|
|
|
|
if(!fRet)
|
|
{
|
|
BUMP_COUNTER(QuerySmtpInstance(), NumProtocolErrs);
|
|
++m_ProtocolErrors;
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
BOOL SMTP_CONNECTION::OnEvent
|
|
|
|
Description:
|
|
Is invoked from GlueDispatch. Micro-manages sink firing scenarios.
|
|
|
|
Arguments:
|
|
char * InputLine null terminated chunk of inbound input buffer from beginning of cmd to CRLF
|
|
DWORD IntermediateSize length of the above
|
|
DWORD CmdSize length of command keyword
|
|
|
|
Returns:
|
|
TRUE if native handler did or by default
|
|
FALSE if native handler did
|
|
|
|
--*/
|
|
|
|
HRESULT SMTP_CONNECTION::OnEvent(
|
|
IUnknown * pIserver,
|
|
IUnknown * pISession,
|
|
IMailMsgProperties *pIMessage,
|
|
LPPE_COMMAND_NODE pCommandNode,
|
|
LPPE_BINDING_NODE pBindingNode,
|
|
char * szArgs
|
|
)
|
|
{
|
|
PMFI PointerToMemberFunction = SmtpDispatchTable[m_dwCurrentCommand];
|
|
HRESULT hr = S_OK;
|
|
BOOL fResult;
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::OnEvent");
|
|
|
|
_ASSERT(pCommandNode);
|
|
_ASSERT(pBindingNode);
|
|
m_CInboundContext.m_pCurrentBinding = pBindingNode;
|
|
|
|
// If this is not a native command
|
|
if (SmtpCommands[m_dwCurrentCommand] == NULL)
|
|
{
|
|
hr = m_pCInboundDispatcher->ChainSinks(
|
|
pIserver,
|
|
pISession,
|
|
pIMessage,
|
|
&m_CInboundContext,
|
|
PRIO_LOWEST,
|
|
pCommandNode,
|
|
&(m_CInboundContext.m_pCurrentBinding)
|
|
);
|
|
if (hr == MAILTRANSPORT_S_PENDING)
|
|
goto AsyncCompletion;
|
|
}
|
|
else
|
|
{
|
|
if (pBindingNode->dwPriority <= PRIO_DEFAULT)
|
|
{
|
|
if (pBindingNode->dwFlags & PEBN_DEFAULT)
|
|
{
|
|
// We are firing the default handler, skip the pre-loop
|
|
m_CInboundContext.m_pCurrentBinding = pBindingNode->pNext;
|
|
}
|
|
else
|
|
{
|
|
hr = m_pCInboundDispatcher->ChainSinks(
|
|
pIserver,
|
|
pISession,
|
|
pIMessage,
|
|
&m_CInboundContext,
|
|
PRIO_DEFAULT,
|
|
pCommandNode,
|
|
&(m_CInboundContext.m_pCurrentBinding)
|
|
);
|
|
if (hr == MAILTRANSPORT_S_PENDING)
|
|
goto AsyncCompletion;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
if((hr != EXPE_S_CONSUMED) && (hr != S_FALSE)) {
|
|
fResult = (this->*PointerToMemberFunction)(szArgs,strlen(szArgs));
|
|
|
|
if (!fResult)
|
|
m_CInboundContext.SetCommandStatus(EXPE_DROP_SESSION);
|
|
else
|
|
m_CInboundContext.SetCommandStatus(EXPE_SUCCESS);
|
|
}
|
|
}
|
|
|
|
if ((m_CInboundContext.m_pCurrentBinding) &&
|
|
(hr != EXPE_S_CONSUMED) && (hr != S_FALSE))
|
|
{
|
|
hr = m_pCInboundDispatcher->ChainSinks(
|
|
pIserver,
|
|
pISession,
|
|
pIMessage,
|
|
&m_CInboundContext,
|
|
PRIO_LOWEST,
|
|
pCommandNode,
|
|
&(m_CInboundContext.m_pCurrentBinding)
|
|
);
|
|
if (hr == MAILTRANSPORT_S_PENDING)
|
|
goto AsyncCompletion;
|
|
}
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return(hr);
|
|
|
|
AsyncCompletion:
|
|
|
|
DebugTrace((LPARAM)this, "Leaving because of S_PENDING");
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return(hr);
|
|
}
|
|
|
|
HRESULT SMTP_CONNECTION::OnNotifyAsyncCompletion(
|
|
HRESULT hrResult
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LPSTR pArgs = NULL;
|
|
CHAR chTerm = '\0';
|
|
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::OnNotifyAsyncCompletion");
|
|
|
|
// Make sure the sink did not call back with S_PENDING
|
|
if (hrResult == MAILTRANSPORT_S_PENDING)
|
|
hrResult = S_OK;
|
|
|
|
// See if we have to continue chaining
|
|
if (hrResult == S_OK)
|
|
{
|
|
LPPE_BINDING_NODE pNextBinding;
|
|
PE_BINDING_NODE bnDefault;
|
|
|
|
// If we have more bindings, or if we are before a native command, we have to
|
|
// go in again
|
|
_ASSERT(m_CInboundContext.m_pCurrentBinding);
|
|
pNextBinding = m_CInboundContext.m_pCurrentBinding->pNext;
|
|
|
|
// Now see if we have a native handler
|
|
if (SmtpDispatchTable[m_dwCurrentCommand] &&
|
|
(m_CInboundContext.m_pCurrentBinding->dwPriority <= PRIO_DEFAULT))
|
|
{
|
|
if (!pNextBinding || (pNextBinding->dwPriority > PRIO_DEFAULT))
|
|
{
|
|
bnDefault.dwPriority = PRIO_DEFAULT;
|
|
bnDefault.pNext = pNextBinding;
|
|
bnDefault.dwFlags = PEBN_DEFAULT;
|
|
pNextBinding = &bnDefault;
|
|
}
|
|
}
|
|
|
|
if (pNextBinding != NULL)
|
|
{
|
|
// Call the OnEvent to resume chaining ...
|
|
pArgs = strchr(m_CInboundContext.m_cabCommand.Buffer(), ' ');
|
|
if (!pArgs)
|
|
pArgs = &chTerm;
|
|
hrResult = OnEvent(
|
|
m_pInstance->GetInstancePropertyBag(),
|
|
GetSessionPropertyBag(),
|
|
m_pIMsg,
|
|
m_CInboundContext.m_pCurrentCommandContext,
|
|
pNextBinding,
|
|
pArgs
|
|
);
|
|
// We have scenarios:
|
|
// 1) S_OK, S_FALSE: We send the response and do the
|
|
// next command
|
|
// 2) ERROR: Drop the connection
|
|
// 3) S_PENDING: Just let the thread return
|
|
}
|
|
else
|
|
hrResult = S_OK;
|
|
}
|
|
|
|
if (hrResult != MAILTRANSPORT_S_PENDING)
|
|
{
|
|
// log this command and the response
|
|
if (m_State != DATA &&
|
|
m_State != BDAT &&
|
|
m_State != AUTH &&
|
|
m_dwCurrentCommand != DATA &&
|
|
m_dwCurrentCommand != BDAT &&
|
|
m_dwCurrentCommand != AUTH)
|
|
{
|
|
const char *pszCommand = SmtpCommands[m_dwCurrentCommand];
|
|
DWORD cCmd = (pszCommand) ? strlen(pszCommand) : 0;
|
|
ProtocolLog(m_dwCurrentCommand,
|
|
m_CInboundContext.m_cabCommand.Buffer() + cCmd,
|
|
m_CInboundContext.m_dwWin32Status,
|
|
m_CInboundContext.m_dwSmtpStatus,
|
|
0,
|
|
0);
|
|
}
|
|
|
|
// It's not another async operation
|
|
if (SUCCEEDED(hrResult))
|
|
{
|
|
// Send the response and process the next command
|
|
if (!ProcessAndSendResponse())
|
|
hrResult = E_FAIL;
|
|
|
|
// Disconnect client if needed
|
|
if (m_CInboundContext.m_dwCommandStatus & EXPE_DROP_SESSION)
|
|
{
|
|
DisconnectClient();
|
|
hrResult = E_FAIL;
|
|
}
|
|
|
|
// do things unique to handling the end of a message
|
|
if (m_fAsyncEOD) {
|
|
ReInitClassVariables();
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hrResult)) {
|
|
//
|
|
// ProcessClient usually posts an async ReadFile --
|
|
// This can be a problem since ntos actuall completes the
|
|
// read with the thread that issued it. If the sink
|
|
// writer has the thread exit before the read completes,
|
|
// we would get an error. To get around this, we call
|
|
// PostQueuedCompleteionStatus with the sink's thread and
|
|
// then post the next async read with an atq thread.
|
|
//
|
|
//ProcessClient(0, NO_ERROR,
|
|
//(LPOVERLAPPED)&m_CInboundContext);
|
|
m_fAsyncEventCompletion = TRUE;
|
|
|
|
if(
|
|
PostCompletionStatus(
|
|
1 // Post 1 byte (a zero byte completion means the
|
|
// remote host disconnected
|
|
) == FALSE) {
|
|
//
|
|
// We were unable to post completion status, fail
|
|
// below
|
|
//
|
|
hrResult = E_FAIL;
|
|
m_fAsyncEventCompletion = FALSE;
|
|
}
|
|
}
|
|
if(FAILED(hrResult))
|
|
ProcessClient(0, ERROR_OPERATION_ABORTED, NULL);
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
// We always keep the sink happy
|
|
return(S_OK);
|
|
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::ProcessAndSendResponse()
|
|
{
|
|
BOOL fResult = TRUE;
|
|
DWORD dwResponseSize = 0;
|
|
LPSTR pszResponse = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::ProcessAndSendResponse");
|
|
|
|
|
|
// See if we have a custom response from sinks
|
|
dwResponseSize = m_CInboundContext.m_cabResponse.Length();
|
|
pszResponse = m_CInboundContext.m_cabResponse.Buffer();
|
|
if (!dwResponseSize || !*pszResponse)
|
|
{
|
|
// Try the native buffer
|
|
dwResponseSize = m_CInboundContext.m_cabNativeResponse.Length();
|
|
pszResponse = m_CInboundContext.m_cabNativeResponse.Buffer();
|
|
// If the response buffer is NULL, we won't send any response.
|
|
// For example BDAT exhibits this behavior
|
|
}
|
|
|
|
if (dwResponseSize && *pszResponse)
|
|
PE_FastFormatSmtpMessage(pszResponse, dwResponseSize - 1);
|
|
|
|
// SendResponse if needed
|
|
if (!(m_CInboundContext.m_dwCommandStatus & EXPE_PIPELINED))
|
|
fResult = SendSmtpResponse();
|
|
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return(fResult);
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
BOOL SMTP_CONNECTION::GlueDispatch
|
|
|
|
Description:
|
|
Is invoked from ProcessInputBuffer as if it were native handler for SMTP command.
|
|
Finds out if the command is native/non-native, and,if native, extended/non-extended.
|
|
Then, fires the corresponding mix of native handler and inbound sinks.
|
|
|
|
Arguments:
|
|
char * InputLine null terminated chunk of inbound input buffer from beginning of cmd to CRLF
|
|
DWORD IntermediateSize length of the above
|
|
DWORD CmdSize length of command keyword
|
|
|
|
Returns:
|
|
TRUE if native handler did or by default
|
|
FALSE if native handler did
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::GlueDispatch(char * InputLine, DWORD IntermediateSize, DWORD CmdSize, BOOL * pfAsyncOp)
|
|
{
|
|
HRESULT hr;
|
|
PMFI PointerToMemberFunction = SmtpDispatchTable[m_dwCurrentCommand];
|
|
BOOL fSinksInstalled = FALSE;
|
|
BOOL fResult = TRUE;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::GlueDispatch");
|
|
|
|
_ASSERT(InputLine);
|
|
_ASSERT(m_pInstance);
|
|
_ASSERT(pfAsyncOp);
|
|
if (!InputLine || !m_pInstance || !pfAsyncOp)
|
|
return(FALSE);
|
|
|
|
// Default is no async operation
|
|
*pfAsyncOp = FALSE;
|
|
|
|
//
|
|
// See if we are secure enough for this command
|
|
//
|
|
|
|
switch (m_dwCurrentCommand) {
|
|
case HELO:
|
|
case EHLO:
|
|
case NOOP:
|
|
case STARTTLS:
|
|
case TLS:
|
|
case QUIT:
|
|
// These commands don't need to be done securely
|
|
break;
|
|
|
|
default:
|
|
if (!IsClientSecureEnough())
|
|
{
|
|
ErrorTrace((LPARAM) this, "GlueDispatch - SMTP_RESP_MUST_SECURE, Must do STARTTLS first");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
if(m_pCInboundDispatcher == NULL) {
|
|
m_pIEventRouter = m_pInstance->GetRouter();
|
|
|
|
_ASSERT(m_pIEventRouter);
|
|
if (!m_pIEventRouter)
|
|
return(FALSE);
|
|
|
|
hr = m_pIEventRouter->GetDispatcherByClassFactory(
|
|
CLSID_CInboundDispatcher,
|
|
&g_cfInbound,
|
|
CATID_SMTP_ON_INBOUND_COMMAND,
|
|
IID_ISmtpInboundCommandDispatcher,
|
|
(IUnknown **)&m_pCInboundDispatcher);
|
|
|
|
|
|
if (!SUCCEEDED(hr)){
|
|
ErrorTrace((LPARAM) this, "Unable to get dispatcher by CLF on router");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// pre-loading per command buffer into the context object...
|
|
m_CInboundContext.ResetInboundContext();
|
|
|
|
// null-terminating the command keyword...
|
|
char * szTemp;
|
|
char * szTemp2;
|
|
char * szArgs;
|
|
char chReplaced = '\0';
|
|
|
|
szTemp = strpbrk(InputLine, "\x09\x0a\x0b\x0c\x0d\x20");
|
|
if (szTemp == NULL)
|
|
{
|
|
szTemp = InputLine + strlen(InputLine);
|
|
_ASSERT(szTemp);
|
|
szArgs = szTemp;
|
|
}
|
|
else
|
|
{
|
|
chReplaced = *szTemp;
|
|
*szTemp='\0';
|
|
szArgs = szTemp;
|
|
}
|
|
szTemp2 = InputLine;
|
|
while (szTemp2 < szTemp)
|
|
{
|
|
*szTemp2 = (CHAR)tolower(*szTemp2);
|
|
szTemp2++;
|
|
}
|
|
|
|
// OK, we know we're dealing with native...
|
|
hr = m_pCInboundDispatcher->SinksInstalled(
|
|
InputLine,
|
|
&(m_CInboundContext.m_pCurrentCommandContext));
|
|
if (hr == S_OK)
|
|
fSinksInstalled = TRUE;
|
|
|
|
if (chReplaced != '\0')
|
|
*szTemp = chReplaced;
|
|
|
|
hr = m_CInboundContext.m_cabCommand.Append(
|
|
(char *)InputLine,
|
|
strlen(InputLine) + 1,
|
|
NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
ErrorTrace((LPARAM) this, "Unable to set command buffer");
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Make sure IMsg is instantiated...if it's not, instantiate it !!!
|
|
if (m_dwCurrentCommand == MAIL)
|
|
{
|
|
BOOL fCreateIMsg = TRUE;
|
|
|
|
if (m_pIMsg != NULL)
|
|
{
|
|
char b;
|
|
|
|
// If we have an allocated IMailMsg but we don't have a valid
|
|
// sender address yet, we will release the object and re-allocate it.
|
|
// Otherwise, we will skip the creation of another message object.
|
|
hr = m_pIMsg->GetStringA(IMMPID_MP_SENDER_ADDRESS_SMTP, 1, &b);
|
|
if(hr == MAILMSG_E_PROPNOTFOUND)
|
|
ReleasImsg(TRUE);
|
|
else
|
|
fCreateIMsg = FALSE;
|
|
}
|
|
|
|
if (fCreateIMsg)
|
|
{
|
|
// Create a new MailMsg
|
|
hr = CoCreateInstance(CLSID_MsgImp,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IMailMsgProperties,
|
|
(LPVOID *)&m_pIMsg);
|
|
|
|
// Next, check if we are over the inbound cutoff limit. If so, we will release the message
|
|
// and not proceed.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DWORD dwCreationFlags;
|
|
hr = m_pIMsg->GetDWORD(
|
|
IMMPID_MPV_MESSAGE_CREATION_FLAGS,
|
|
&dwCreationFlags);
|
|
if (FAILED(hr) ||
|
|
(dwCreationFlags & MPV_INBOUND_CUTOFF_EXCEEDED))
|
|
{
|
|
// If we fail to get this property of if the inbound cutoff
|
|
// exceeded flag is set, discard the message and return failure
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DebugTrace((LPARAM)this, "Failing because inbound cutoff reached");
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
m_pIMsg->Release();
|
|
m_pIMsg = NULL;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pIMsg->QueryInterface(IID_IMailMsgRecipients, (void **) &m_pIMsgRecipsTemp);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pIMsg->QueryInterface(IID_IMailMsgBind, (void **)&m_pBindInterface);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pIMsgRecipsTemp->AllocNewList(&m_pIMsgRecips);
|
|
if (FAILED(hr))
|
|
{
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_MEMORY);
|
|
m_CInboundContext.SetWin32Status(ERROR_NOT_ENOUGH_MEMORY);
|
|
ErrorTrace((LPARAM) this, "GlueDispatch - SMTP_RESP_NORESOURCES, SMTP_NO_MEMORY - MailInfo = new MAILQ_ENTRY () failed");
|
|
fResult = FALSE;
|
|
goto ErrorCleanup;
|
|
}
|
|
|
|
hr = SetAvailableMailMsgProperties();
|
|
if(FAILED(hr))
|
|
{
|
|
m_CInboundContext.SetWin32Status(ERROR_NOT_ENOUGH_MEMORY);
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_STORAGE);
|
|
ErrorTrace((LPARAM) this, "GlueDispatch - SMTP_RESP_NORESOURCES, SMTP_NO_MEMORY - MailInfo = new MAILQ_ENTRY () failed");
|
|
fResult = FALSE;
|
|
goto ErrorCleanup;
|
|
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_MEMORY);
|
|
m_CInboundContext.SetWin32Status(ERROR_NOT_ENOUGH_MEMORY);
|
|
ErrorTrace((LPARAM) this, "GlueDispatch - SMTP_RESP_NORESOURCES, SMTP_NO_MEMORY - MailInfo = new MAILQ_ENTRY () failed");
|
|
fResult = FALSE;
|
|
goto ErrorCleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_MEMORY);
|
|
m_CInboundContext.SetWin32Status(ERROR_NOT_ENOUGH_MEMORY);
|
|
ErrorTrace((LPARAM) this, "GlueDispatch - SMTP_RESP_NORESOURCES, SMTP_NO_MEMORY - MailInfo = new MAILQ_ENTRY () failed");
|
|
fResult = FALSE;
|
|
goto ErrorCleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BUMP_COUNTER(QuerySmtpInstance(), MsgsRefusedDueToNoMailObjects);
|
|
FormatSmtpMessage (SMTP_RESP_NORESOURCES, ENO_RESOURCES," %s\r\n", SMTP_NO_MEMORY);
|
|
m_CInboundContext.SetWin32Status(ERROR_NOT_ENOUGH_MEMORY);
|
|
ErrorTrace((LPARAM) this, "GlueDispatch - SMTP_RESP_NORESOURCES, SMTP_NO_MEMORY - MailInfo = new MAILQ_ENTRY () failed");
|
|
fResult = FALSE;
|
|
goto ErrorCleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mark the start fo protocol event processing
|
|
m_fIsPeUnderway = TRUE;
|
|
|
|
if (PointerToMemberFunction != NULL)
|
|
{
|
|
// This is native
|
|
if (fSinksInstalled)
|
|
{
|
|
LPPE_BINDING_NODE pBinding;
|
|
PE_BINDING_NODE bnDefault;
|
|
|
|
pBinding = m_CInboundContext.m_pCurrentCommandContext->pFirstBinding;
|
|
_ASSERT(pBinding);
|
|
|
|
// Now if the first sink is a post sink, we must create a sentinel
|
|
// so we know not to miss the native handler
|
|
if (pBinding->dwPriority > PRIO_DEFAULT)
|
|
{
|
|
bnDefault.dwPriority = PRIO_DEFAULT;
|
|
bnDefault.pNext = pBinding;
|
|
bnDefault.dwFlags = PEBN_DEFAULT;
|
|
pBinding = &bnDefault;
|
|
}
|
|
|
|
hr = OnEvent(
|
|
m_pInstance->GetInstancePropertyBag(),
|
|
GetSessionPropertyBag(),
|
|
m_pIMsg,
|
|
m_CInboundContext.m_pCurrentCommandContext,
|
|
pBinding,
|
|
szArgs
|
|
);
|
|
if (hr == MAILTRANSPORT_S_PENDING)
|
|
goto AsyncCompletion;
|
|
}
|
|
else
|
|
{
|
|
//OK, it's native, not extended command...
|
|
// So we just run the native handler on it
|
|
fResult = (this->*PointerToMemberFunction)(szArgs, strlen(szArgs));
|
|
// all changes got dumped into context automatically...
|
|
// the interpreter will see the default response nonempty
|
|
// and sink response empty, and it will flush just that...
|
|
// for conformity with onevent
|
|
hr = S_OK;
|
|
// however, we need to modify command status here to signify if
|
|
// we need to keep connection open
|
|
if (!fResult)
|
|
m_CInboundContext.SetCommandStatus(EXPE_DROP_SESSION);
|
|
else
|
|
m_CInboundContext.SetCommandStatus(EXPE_SUCCESS);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is not a native command
|
|
if (fSinksInstalled)
|
|
{
|
|
hr = OnEvent(
|
|
m_pInstance->GetInstancePropertyBag(),
|
|
GetSessionPropertyBag(),
|
|
m_pIMsg,
|
|
m_CInboundContext.m_pCurrentCommandContext,
|
|
m_CInboundContext.m_pCurrentCommandContext->pFirstBinding,
|
|
szArgs
|
|
);
|
|
if (hr == MAILTRANSPORT_S_PENDING)
|
|
goto AsyncCompletion;
|
|
}
|
|
// once again, all info now sits in the context
|
|
// the only thing left is just to check if any sinks were chained for this command,
|
|
// and, if not, run a chain of *-sinks on it. If they did not pick it up, either,
|
|
// we call on DoLASTCommand and that's all!
|
|
//
|
|
// jstamerj 1998/10/29 18:01:53: Check the command status code
|
|
// to see if we need to continue (in addition to smtp status)
|
|
//
|
|
if ((m_CInboundContext.m_dwSmtpStatus == 0) &&
|
|
(m_CInboundContext.m_dwCommandStatus == EXPE_UNHANDLED))
|
|
{
|
|
hr = m_pCInboundDispatcher->SinksInstalled(
|
|
"*",&(m_CInboundContext.m_pCurrentCommandContext));
|
|
if (hr == S_OK)
|
|
{
|
|
hr = OnEvent(
|
|
m_pInstance->GetInstancePropertyBag(),
|
|
GetSessionPropertyBag(),
|
|
m_pIMsg,
|
|
m_CInboundContext.m_pCurrentCommandContext,
|
|
m_CInboundContext.m_pCurrentCommandContext->pFirstBinding,
|
|
szArgs
|
|
);
|
|
if (hr == MAILTRANSPORT_S_PENDING)
|
|
goto AsyncCompletion;
|
|
}
|
|
|
|
if ((m_CInboundContext.m_dwSmtpStatus == 0) &&
|
|
(m_CInboundContext.m_dwCommandStatus == EXPE_UNHANDLED))
|
|
{
|
|
fResult=(this->DoLASTCommand)(szArgs, strlen(szArgs));
|
|
|
|
// the context at this point does not contain anything meaningful, so just return
|
|
if (!fResult)
|
|
m_CInboundContext.m_dwCommandStatus = EXPE_DROP_SESSION;
|
|
else
|
|
m_CInboundContext.m_dwCommandStatus = EXPE_SUCCESS;
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
// first, we interpret the hr-logic
|
|
// if chaining was normal, proceed, otherwise...
|
|
if ((hr != S_OK) &&
|
|
(hr != EXPE_S_CONSUMED))
|
|
{
|
|
ErrorTrace((LPARAM) this, "Error occured during chaining of sinks (%08x)", hr);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Format the message if needed
|
|
fResult = ProcessAndSendResponse();
|
|
|
|
// Disconnect client if needed
|
|
if (m_CInboundContext.m_dwCommandStatus & EXPE_DROP_SESSION)
|
|
{
|
|
//Special case for TURN
|
|
//where we do not diconnect the connection but simply destroy smtpcli
|
|
if(m_DoCleanup)
|
|
DisconnectClient();
|
|
fResult = FALSE;
|
|
}
|
|
|
|
if ( ( m_CInboundContext.m_dwCommandStatus & EXPE_BLOB_READY) &&
|
|
( NULL != m_CInboundContext.m_pICallback))
|
|
{
|
|
m_fPeBlobReady = TRUE;
|
|
m_pPeBlobCallback = m_CInboundContext.m_pICallback;
|
|
}
|
|
|
|
// do things unique to handling the end of a message
|
|
if (m_fAsyncEOD) {
|
|
ReInitClassVariables();
|
|
}
|
|
}
|
|
else
|
|
fResult = FALSE;
|
|
|
|
// Final cleanup
|
|
m_fIsPeUnderway = FALSE;
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return(fResult);
|
|
|
|
AsyncCompletion:
|
|
|
|
DebugTrace((LPARAM)this, "Leaving because of S_PENDING");
|
|
*pfAsyncOp = TRUE;
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return(TRUE);
|
|
|
|
ErrorCleanup:
|
|
|
|
// Cleanup on error. This includes sending out all final
|
|
// responses
|
|
SendSmtpResponse();
|
|
|
|
m_fIsPeUnderway = FALSE;
|
|
TraceFunctLeaveEx((LPARAM)this);
|
|
return(fResult);
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
BOOL SMTP_CONNECTION::ProcessPeBlob
|
|
|
|
Description:
|
|
Calls the protocol extension sink callback with a blob buffer.
|
|
|
|
Arguments:
|
|
pbInputLine
|
|
cbSize
|
|
|
|
Returns:
|
|
BOOL : continue processing
|
|
|
|
--*/
|
|
|
|
BOOL SMTP_CONNECTION::ProcessPeBlob(const char * pbInputLine, DWORD cbSize)
|
|
{
|
|
BOOL fResult;
|
|
HRESULT hr;
|
|
BOOL fErrorMessageSent = FALSE;
|
|
|
|
TraceQuietEnter( "SMTP_CONNECTION::ProcessPeBlob");
|
|
|
|
_ASSERT( m_fPeBlobReady);
|
|
if ( NULL == m_pPeBlobCallback) {
|
|
ErrorTrace( ( LPARAM) this, "Sink provided no callback interface");
|
|
fResult = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
m_CInboundContext.ResetInboundContext();
|
|
m_CInboundContext.m_pbBlob = ( PBYTE) pbInputLine;
|
|
m_CInboundContext.m_cbBlob = cbSize;
|
|
|
|
hr = m_pPeBlobCallback->OnSmtpInCallback(
|
|
m_pInstance->GetInstancePropertyBag(),
|
|
GetSessionPropertyBag(),
|
|
m_pIMsg,
|
|
( ISmtpInCallbackContext *) &m_CInboundContext);
|
|
|
|
if ( FAILED( hr)) {
|
|
ErrorTrace( ( LPARAM) this, "Sink callback failed, hr=%x", hr);
|
|
fResult = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
fResult = ProcessAndSendResponse();
|
|
if ( FALSE == fResult) {
|
|
ErrorTrace( ( LPARAM) this, "ProcessAndSendResponse failed");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (m_CInboundContext.m_dwCommandStatus & EXPE_DROP_SESSION) {
|
|
fResult = FALSE;
|
|
fErrorMessageSent = TRUE;
|
|
goto cleanup;
|
|
}
|
|
|
|
if ( m_CInboundContext.m_dwCommandStatus & EXPE_BLOB_DONE) {
|
|
m_fPeBlobReady = FALSE;
|
|
m_pPeBlobCallback->Release();
|
|
m_pPeBlobCallback = NULL;
|
|
}
|
|
|
|
cleanup:
|
|
if ( fResult == FALSE) {
|
|
m_fPeBlobReady = FALSE;
|
|
if ( NULL != m_pPeBlobCallback) {
|
|
m_pPeBlobCallback->Release();
|
|
m_pPeBlobCallback = NULL;
|
|
}
|
|
if (!fErrorMessageSent) {
|
|
FormatSmtpMessage( SMTP_RESP_ERROR, NULL, " %s\r\n", "Sink problem processing blob");
|
|
SendSmtpResponse();
|
|
}
|
|
}
|
|
|
|
return fResult;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
BOOL SMTP_CONNECTION::PE_FormatSmtpMessage
|
|
|
|
Description:
|
|
If we are processing non-native or extended native command, caches response in
|
|
context object, otherwise passes it along to native FormatSmtpMessage.
|
|
|
|
Arguments:
|
|
As in FormatSmtpMessage.
|
|
|
|
Returns:
|
|
TRUE always
|
|
--*/
|
|
BOOL SMTP_CONNECTION::PE_FormatSmtpMessage(IN const char * Format, ...)
|
|
{
|
|
va_list arglist;
|
|
char Buffer[MAX_NATIVE_RESPONSE_SIZE];
|
|
int BytesWritten;
|
|
DWORD AvailableBytes = MAX_NATIVE_RESPONSE_SIZE;
|
|
|
|
va_start(arglist, Format);
|
|
BytesWritten = _vsnprintf (Buffer, AvailableBytes, Format, arglist);
|
|
va_end(arglist);
|
|
|
|
if (!m_fIsPeUnderway)
|
|
{
|
|
//pass in 0 for the code and NULL for the enhanced staus code
|
|
//
|
|
FormatSmtpMessage(0,NULL,"%s", Buffer);
|
|
}
|
|
else
|
|
{
|
|
m_CInboundContext.AppendNativeResponse(Buffer, strlen(Buffer) + 1);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
BOOL SMTP_CONNECTION::PE_CdFormatSmtpMessage
|
|
|
|
Description:
|
|
As PE_FormatSmtpMessage above, except intercepts the SMTP return code.
|
|
|
|
Arguments:
|
|
As in PE_FormatSmtpMessage above, except add'l slot for SMTP return code.
|
|
|
|
Returns:
|
|
TRUE always
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::PE_CdFormatSmtpMessage(DWORD dwCode, const char * szEnhancedCodes, IN const char * Format,...)
|
|
{
|
|
va_list arglist;
|
|
char Buffer[MAX_NATIVE_RESPONSE_SIZE];
|
|
int BytesWritten;
|
|
DWORD AvailableBytes = MAX_NATIVE_RESPONSE_SIZE;
|
|
char RealFormat[MAX_PATH];
|
|
|
|
if(m_pInstance->AllowEnhancedCodes() && szEnhancedCodes)
|
|
{
|
|
sprintf(RealFormat,"%d %s",dwCode,szEnhancedCodes);
|
|
}
|
|
else
|
|
sprintf(RealFormat,"%d",dwCode);
|
|
|
|
strcat(RealFormat,Format);
|
|
|
|
va_start(arglist, Format);
|
|
BytesWritten = _vsnprintf (Buffer, AvailableBytes, (const char * )RealFormat, arglist);
|
|
if(BytesWritten == -1) {
|
|
//
|
|
// If response is too long, forcibly truncate it
|
|
// This might mean that the human readable part of the response may be
|
|
// incomplete... but there's nothing we can do about that easily.
|
|
//
|
|
Buffer[MAX_NATIVE_RESPONSE_SIZE-3] = '\r';
|
|
Buffer[MAX_NATIVE_RESPONSE_SIZE-2] = '\n';
|
|
Buffer[MAX_NATIVE_RESPONSE_SIZE-1] = '\0';
|
|
}
|
|
if (m_fIsPeUnderway)
|
|
{
|
|
PE_FormatSmtpMessage("%s",Buffer);
|
|
m_CInboundContext.SetSmtpStatusCode(dwCode);
|
|
}
|
|
else
|
|
{
|
|
//pass in 0 for the code and NULL for the enhanced staus code
|
|
//
|
|
FormatSmtpMessage(0,NULL,"%s",Buffer);
|
|
}
|
|
va_end(arglist);
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
BOOL SMTP_CONNECTION::PE_DisconnectClient
|
|
|
|
Description:
|
|
If we are processing non-native or extended native command, caches DisconnectClient
|
|
calls' occurences in context object, otherwise passes it along to native
|
|
DisconnectClient.
|
|
|
|
Arguments:
|
|
none
|
|
|
|
Returns:
|
|
TRUE always
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::PE_DisconnectClient()
|
|
{
|
|
if (m_fIsPeUnderway)
|
|
{
|
|
m_CInboundContext.m_dwCommandStatus = m_CInboundContext.m_dwCommandStatus | EXPE_DROP_SESSION;
|
|
}
|
|
else
|
|
{
|
|
DisconnectClient();
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
BOOL SMTP_CONNECTION::PE_SendSmtpResponse
|
|
|
|
Description:
|
|
If we are processing non-native or extended native command, caches SendSmtpResponse
|
|
calls' occurences in context object, otherwise passes it along to native
|
|
SendSmtpResponse.
|
|
|
|
Arguments:
|
|
none
|
|
|
|
Returns:
|
|
TRUE always if we're called from GlueDispatch callee's
|
|
SendSmtpResponse return otherwise
|
|
|
|
--*/
|
|
BOOL SMTP_CONNECTION::PE_SendSmtpResponse()
|
|
{
|
|
if (m_fIsPeUnderway)
|
|
{
|
|
m_CInboundContext.m_dwCommandStatus = m_CInboundContext.m_dwCommandStatus & (~EXPE_PIPELINED);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return SendSmtpResponse();
|
|
};
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//---[ SMTP_CONNECTION::GetAndPersistRFC822Headers ]--------------------------
|
|
//
|
|
//
|
|
// Description:
|
|
// Parses and RFC822 headers, and promotes them to the P2 if neccessary
|
|
// Parameters:
|
|
// IN InputLine String with start of link (processed by ChompHeader)
|
|
// IN psValueBuf String with value of header
|
|
// Returns:
|
|
// -
|
|
// History:
|
|
// 2/8/99 - MikeSwa Updated - moved received header parsing to this function
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void SMTP_CONNECTION::GetAndPersistRFC822Headers(
|
|
char* InputLine,
|
|
char* pszValueBuf
|
|
)
|
|
{
|
|
TraceFunctEnterEx((LPARAM)this, "SMTP_CONNECTION::GetAndPersistRFC822Headers");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
//count the number of received lines for hop count
|
|
//analysis later
|
|
if(!strncasecmp(InputLine, "Received:", strlen("Received:")) ||
|
|
!strncasecmp(InputLine, "Received :", strlen("Received :")))
|
|
{
|
|
m_HopCount++;
|
|
|
|
CHAR szText[2024];
|
|
CHAR * pszTemp;
|
|
|
|
// Check if this string represents the local server - if it does we might be
|
|
// looping around and we need to keep track of how many times this server has
|
|
// seen the message
|
|
sprintf(szText,szFormatReceivedServer, m_pInstance->GetFQDomainName());
|
|
pszTemp = strstr(InputLine, szText);
|
|
if (pszTemp)
|
|
{
|
|
// we found the string, make sure we also find "with Microsoft SMTPSVC"
|
|
sprintf(szText,szFormatReceivedService);
|
|
pszTemp = strstr(InputLine, szText);
|
|
if (pszTemp)
|
|
{
|
|
// we found that string too - we've been here before,
|
|
// increment m_LocalHopCount
|
|
m_LocalHopCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the message ID and persist it
|
|
//
|
|
if( !m_fSeenRFC822MsgId &&
|
|
( !strncasecmp( InputLine, "Message-ID:", strlen("Message-ID:") ) ||
|
|
!strncasecmp( InputLine, "Message-ID :", strlen("Message-ID :") ) ) )
|
|
{
|
|
m_fSeenRFC822MsgId = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
//
|
|
// Some MTAs fold Message-IDs. Embedded CRLFs in a Message-ID can cause trouble since
|
|
// we use it for message tracking, and in the "Queued" response, so we unfold the
|
|
// Message-ID prior to using it.
|
|
//
|
|
|
|
CHAR *pszUnfolded = NULL;
|
|
|
|
if(!UnfoldHeader(pszValueBuf, &pszUnfolded))
|
|
{
|
|
m_MailBodyError = E_OUTOFMEMORY;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return;
|
|
}
|
|
|
|
if(pszUnfolded)
|
|
pszValueBuf = pszUnfolded;
|
|
|
|
if( FAILED( hr = m_pIMsg->PutStringA( IMMPID_MP_RFC822_MSG_ID, pszValueBuf ) ) )
|
|
m_MailBodyError = hr;
|
|
|
|
if(pszUnfolded)
|
|
FreeUnfoldedHeader(pszUnfolded);
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// get the Subject: & persist it
|
|
//
|
|
|
|
if( !m_fSeenRFC822Subject &&
|
|
( !strncasecmp( InputLine, "Subject:", strlen("Subject:") ) ||
|
|
!strncasecmp( InputLine, "Subject :", strlen("Subject :") ) ) )
|
|
{
|
|
m_fSeenRFC822Subject = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
if( FAILED( hr = m_pIMsg->PutStringA( IMMPID_MP_RFC822_MSG_SUBJECT, pszValueBuf ) ) )
|
|
m_MailBodyError = hr;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// get the To: address & persist it
|
|
//
|
|
|
|
if( !m_fSeenRFC822ToAddress &&
|
|
( !strncasecmp( InputLine, "To:", strlen("To:") ) ||
|
|
!strncasecmp( InputLine, "To :", strlen("To :") ) ) )
|
|
{
|
|
m_fSeenRFC822ToAddress = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
if( FAILED( hr = m_pIMsg->PutStringA( IMMPID_MP_RFC822_TO_ADDRESS, pszValueBuf ) ) )
|
|
m_MailBodyError = hr;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// get the Cc: address & persist it
|
|
//
|
|
|
|
if( !m_fSeenRFC822CcAddress &&
|
|
( !strncasecmp( InputLine, "Cc:", strlen("Cc:") ) ||
|
|
!strncasecmp( InputLine, "Cc :", strlen("Cc :") ) ) )
|
|
{
|
|
m_fSeenRFC822CcAddress = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
if( FAILED( hr = m_pIMsg->PutStringA( IMMPID_MP_RFC822_CC_ADDRESS, pszValueBuf ) ) )
|
|
m_MailBodyError = hr;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// get the Bcc: address & persist it
|
|
//
|
|
|
|
if( !m_fSeenRFC822BccAddress &&
|
|
( !strncasecmp( InputLine, "Bcc:", strlen("Bcc:") ) ||
|
|
!strncasecmp( InputLine, "Bcc :", strlen("Bcc :") ) ) )
|
|
{
|
|
m_fSeenRFC822BccAddress = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
if( FAILED( hr = m_pIMsg->PutStringA( IMMPID_MP_RFC822_BCC_ADDRESS, pszValueBuf ) ) )
|
|
m_MailBodyError = hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// get the From: address & persist it
|
|
//
|
|
|
|
if( !m_fSeenRFC822FromAddress &&
|
|
( !strncasecmp( InputLine, "From:", strlen("From:") ) ||
|
|
!strncasecmp( InputLine, "From :", strlen("From :") ) ) )
|
|
{
|
|
m_fSeenRFC822FromAddress = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
if( FAILED( hr = m_pIMsg->PutStringA( IMMPID_MP_RFC822_FROM_ADDRESS, pszValueBuf ) ) )
|
|
m_MailBodyError = hr;
|
|
|
|
char szSmtpFromAddress[MAX_INTERNET_NAME + 6] = "smtp:";
|
|
char *pszDomainOffset;
|
|
DWORD cAddr;
|
|
if (CAddr::ExtractCleanEmailName(szSmtpFromAddress + 5, &pszDomainOffset, &cAddr, pszValueBuf)) {
|
|
if (FAILED(hr = m_pIMsg->PutStringA(IMMPID_MP_FROM_ADDRESS, szSmtpFromAddress))) {
|
|
m_MailBodyError = hr;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if( !m_fSeenRFC822SenderAddress &&
|
|
( !strncasecmp( InputLine, "Sender:", strlen("Sender:") ) ||
|
|
!strncasecmp( InputLine, "Sender :", strlen("Sender :") ) ) )
|
|
{
|
|
m_fSeenRFC822SenderAddress = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
char szSmtpSenderAddress[MAX_INTERNET_NAME + 6] = "smtp:";
|
|
char *pszDomainOffset;
|
|
DWORD cAddr;
|
|
if (CAddr::ExtractCleanEmailName(szSmtpSenderAddress + 5, &pszDomainOffset, &cAddr, pszValueBuf)) {
|
|
if (FAILED(hr = m_pIMsg->PutStringA(IMMPID_MP_SENDER_ADDRESS, szSmtpSenderAddress))) {
|
|
m_MailBodyError = hr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// get the X-Priority & persist it
|
|
//
|
|
|
|
if( !m_fSeenXPriority &&
|
|
( !strncasecmp( InputLine, "X-Priority:", strlen("X-Priority:") ) ||
|
|
!strncasecmp( InputLine, "X-Priority :", strlen("X-Priority :") ) ) )
|
|
{
|
|
m_fSeenXPriority = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
DWORD dwPri = (DWORD)atoi( pszValueBuf );
|
|
if( FAILED( hr = m_pIMsg->PutDWORD( IMMPID_MP_X_PRIORITY, dwPri ) ) )
|
|
m_MailBodyError = hr;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// get the X-OriginalArrivalTime & persist it
|
|
//
|
|
|
|
if( !m_fSeenXOriginalArrivalTime &&
|
|
( !strncasecmp( InputLine, "X-OriginalArrivalTime:", strlen("X-OriginalArrivalTime:") ) ||
|
|
!strncasecmp( InputLine, "X-OriginalArrivalTime :", strlen("X-OriginalArrivalTime :") ) ) )
|
|
{
|
|
m_fSeenXOriginalArrivalTime = TRUE;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
if( FAILED( hr = m_pIMsg->PutStringA( IMMPID_MP_ORIGINAL_ARRIVAL_TIME, pszValueBuf ) ) )
|
|
m_MailBodyError = hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// get the content type & persist it
|
|
//
|
|
|
|
if( !m_fSeenContentType &&
|
|
( !strncasecmp( InputLine, "Content-Type:", strlen("Content-Type:") ) ||
|
|
!strncasecmp( InputLine, "Content-Type :", strlen("Content-Type :") ) ) )
|
|
{
|
|
m_fSeenContentType = TRUE;
|
|
m_fSetContentType = TRUE;
|
|
DWORD dwContentType = 0;
|
|
|
|
if( pszValueBuf )
|
|
{
|
|
if( !strncasecmp( pszValueBuf, "multipart/signed", strlen("multipart/signed") ) )
|
|
{
|
|
dwContentType = 1;
|
|
}
|
|
else if( !strncasecmp( pszValueBuf, "application/x-pkcs7-mime", strlen("application/x-pkcs7-mime") ) ||
|
|
!strncasecmp( pszValueBuf, "application/pkcs7-mime", strlen("application/pkcs7-mime") ) )
|
|
{
|
|
dwContentType = 2;
|
|
}
|
|
|
|
if( FAILED( hr = m_pIMsg->PutStringA( IMMPID_MP_CONTENT_TYPE, pszValueBuf ) ) )
|
|
m_MailBodyError = hr;
|
|
|
|
}
|
|
|
|
if( FAILED( hr = m_pIMsg->PutDWORD( IMMPID_MP_ENCRYPTION_TYPE, dwContentType ) ) )
|
|
m_MailBodyError = hr;
|
|
}
|
|
|
|
if( !m_fSetContentType )
|
|
{
|
|
m_fSetContentType = TRUE;
|
|
if( FAILED( hr = m_pIMsg->PutDWORD( IMMPID_MP_ENCRYPTION_TYPE, 0 ) ) )
|
|
m_MailBodyError = hr;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT SMTP_CONNECTION::SetAvailableMailMsgProperties()
|
|
{
|
|
|
|
//set IPaddress that is already available
|
|
HRESULT hr = m_pIMsg->PutStringA(IMMPID_MP_CONNECTION_IP_ADDRESS, QueryClientHostName());
|
|
if(FAILED(hr))
|
|
{
|
|
return( hr );
|
|
}
|
|
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_CONNECTION_SERVER_IP_ADDRESS, QueryLocalHostName());
|
|
if(FAILED(hr))
|
|
{
|
|
return( hr );
|
|
}
|
|
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_SERVER_NAME, g_ComputerName);
|
|
if(FAILED(hr))
|
|
{
|
|
return( hr );
|
|
}
|
|
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_SERVER_VERSION, g_VersionString);
|
|
if(FAILED(hr))
|
|
{
|
|
return( hr );
|
|
}
|
|
|
|
if (QueryLocalPortName())
|
|
{
|
|
DWORD dwPortNum = atoi(QueryLocalPortName());
|
|
hr = m_pIMsg->PutDWORD(IMMPID_MP_CONNECTION_SERVER_PORT, dwPortNum);
|
|
if (FAILED(hr))
|
|
return ( hr );
|
|
}
|
|
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_HELO_DOMAIN, QueryClientUserName());
|
|
if(FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
if (m_fAuthenticated)
|
|
{
|
|
|
|
//Get username
|
|
if (m_securityCtx.QueryUserName())
|
|
{
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_CLIENT_AUTH_USER,
|
|
m_securityCtx.QueryUserName());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
//Get type of authentication
|
|
if (m_szUsedAuthKeyword[0])
|
|
{
|
|
hr = m_pIMsg->PutStringA(IMMPID_MP_CLIENT_AUTH_TYPE, m_szUsedAuthKeyword);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
}
|
|
return( hr );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Description:
|
|
// If a failure occurs after a client issues the BDAT command, SMTP cannot
|
|
// respond with a failure code (since the RFC forbids a BDAT response). So
|
|
// SMTP calls this function which will read BDAT chunks from the socket
|
|
// and discard them. When the chunk has been received, and it is time to
|
|
// ACK the chunk, this function calls DoBDATErrorProcessing() to send the
|
|
// correct error response.
|
|
//
|
|
// Arguments:
|
|
// IN const char *InputLine - Ptr to data from client
|
|
// IN DWORD UndecryptedTailSize - # of undecrypted bytes in buffer (TLS)
|
|
//
|
|
// OUT BOOL *pfAsyncOp - If this function succeeded, this is set to TRUE
|
|
// if all the data in the input buffer was consumed and we pended a
|
|
// read to pick up more. If this is FALSE, and the function succeeded
|
|
// there is extra (pipelined) data in the input buffer, and it must
|
|
// be parsed as an SMTP command.
|
|
//
|
|
// Notes:
|
|
// If there is data left in the input buffer after this function is done,
|
|
// that data should be parsed as an SMTP command.
|
|
//
|
|
// Returns:
|
|
// TRUE - Keep connection alive.
|
|
// FALSE - Drop connection, there was a fatal error.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
BOOL SMTP_CONNECTION::AcceptAndDiscardBDAT(
|
|
const char *InputLine,
|
|
DWORD UndecryptedTailSize,
|
|
BOOL *pfAsyncOp)
|
|
{
|
|
BOOL fReturn = FALSE;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::AcceptAndDiscardBDAT");
|
|
|
|
_ASSERT(m_MailBodyDiagnostic != ERR_NONE);
|
|
|
|
*pfAsyncOp = FALSE;
|
|
|
|
// Note: m_nBytesRemainingInChunk may be zero if "BDAT 0 LAST" is submitted.
|
|
// we should be able to handle this.
|
|
if((DWORD)m_nBytesRemainingInChunk <= m_cbParsable)
|
|
{
|
|
//
|
|
// Chunk completely received, discard it, do error processing.
|
|
//
|
|
|
|
DebugTrace((LPARAM) this, "Chunk completed");
|
|
|
|
MoveMemory((PVOID) InputLine, InputLine + m_nBytesRemainingInChunk,
|
|
m_cbReceived - m_nBytesRemainingInChunk);
|
|
|
|
m_cbParsable -= m_nBytesRemainingInChunk;
|
|
m_cbReceived -= m_nBytesRemainingInChunk;
|
|
m_nBytesRemainingInChunk = 0;
|
|
m_fIsChunkComplete = TRUE;
|
|
|
|
if(!MailBodyErrorProcessing()) {
|
|
fReturn = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// If we're done with all BDAT chunks, reset state. If this is NOT the last
|
|
// BDAT chunk, then either more BDAT commands/chunks are already buffered
|
|
// in the input buffer due to pipelining, or they're on their way. We need
|
|
// to accept and discard all of them. m_fIsLastChunk is set by DoBDATCommand.
|
|
//
|
|
|
|
if(m_fIsLastChunk)
|
|
{
|
|
DebugTrace((LPARAM) this, "Last chunk");
|
|
|
|
//
|
|
// Note: This doesn't flush the input buffer, so any pipelined data is
|
|
// still kept around. Thus the check for m_cbParsable below is valid.
|
|
//
|
|
|
|
ReInitClassVariables();
|
|
}
|
|
|
|
//
|
|
// If we got here, there is additional data left in the buffer. This must
|
|
// be pipelined data sent by the client, along with the just discarded
|
|
// chunk.
|
|
//
|
|
|
|
*pfAsyncOp = FALSE;
|
|
fReturn = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The complete chunk has not been received, discard whatever we got, update
|
|
// the counts of bytes received/expected and pend a read to pick up more.
|
|
//
|
|
|
|
DebugTrace((LPARAM) this, "Processing partial chunk");
|
|
MoveMemory((PVOID) InputLine, InputLine + m_cbParsable, m_cbReceived - m_cbParsable);
|
|
|
|
m_nBytesRemainingInChunk -= m_cbParsable;
|
|
m_cbReceived -= m_cbParsable;
|
|
m_cbParsable = 0;
|
|
_ASSERT(!m_fIsChunkComplete);
|
|
|
|
fReturn = PendReadIO(UndecryptedTailSize);
|
|
*pfAsyncOp = fReturn;
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
//
|
|
// If we succeeded, one of the following is true:
|
|
// (1) Either there wasn't enough data to complete the chunk and we pended a read.
|
|
// (2) There was enough data to complete the chunk, and the remaining bytes are
|
|
// to be interpreted as an SMTP command (m_nBytesRemainingInChunk == 0).
|
|
//
|
|
|
|
if(fReturn && !*pfAsyncOp)
|
|
_ASSERT(m_nBytesRemainingInChunk == 0);
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fReturn;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::AcceptAndDiscardDATA(
|
|
const char *InputLine,
|
|
DWORD UndecryptedTailSize,
|
|
BOOL *pfAsyncOp)
|
|
{
|
|
DWORD IntermediateSize = 0;
|
|
BOOL fRet = FALSE;
|
|
char *pszSearch = NULL;
|
|
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::AcceptAndDiscardDATA");
|
|
|
|
_ASSERT(m_MailBodyDiagnostic != ERR_NONE);
|
|
|
|
*pfAsyncOp = FALSE;
|
|
|
|
//
|
|
// Discard data line by line until we hit an incomplete line. Then break
|
|
// out of loop and pend a read to pick up more data. If we hit a line that
|
|
// has only a "." in it, that is the end of the mailbody. Kick off error
|
|
// processing, reinit the state and return.
|
|
//
|
|
|
|
while(pszSearch = IsLineComplete(InputLine, m_cbParsable))
|
|
{
|
|
IntermediateSize = (DWORD) (pszSearch - InputLine);
|
|
|
|
if(IntermediateSize == 1 &&
|
|
InputLine[0] == '.' && InputLine[1] == '\r' && InputLine[2] == '\n')
|
|
{
|
|
m_cbReceived -= IntermediateSize + 2; // +2 for CRLF since pszSearch points->CR
|
|
m_cbParsable -= IntermediateSize + 2;
|
|
MoveMemory((PVOID *)InputLine, pszSearch + 2, m_cbReceived);
|
|
|
|
if(MailBodyErrorProcessing())
|
|
{
|
|
ReInitClassVariables();
|
|
fRet = TRUE;
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fRet;
|
|
}
|
|
|
|
m_cbReceived -= IntermediateSize + 2; // +2 for CRLF since pszSearch points->CR
|
|
m_cbParsable -= IntermediateSize + 2;
|
|
MoveMemory((PVOID *)InputLine, pszSearch + 2, m_cbReceived);
|
|
}
|
|
|
|
//
|
|
// Pend a read to pick up rest of the mail body.
|
|
//
|
|
|
|
fRet = PendReadIO(UndecryptedTailSize);
|
|
*pfAsyncOp = fRet;
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return fRet;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Description:
|
|
// Based on m_MailBodyDiagnostic, this function performs error processing for
|
|
// errors that occurred during BDAT. BDAT errors that occur when BDAT
|
|
// is issued are not processed as soon as the error occurs, but after
|
|
// SMTP has accepted the BDAT chunk (see AcceptAndDiscardBDAT()).
|
|
// Arguments:
|
|
// None.
|
|
// Returns:
|
|
// TRUE - Success, error response sent.
|
|
// FALSE - Drop connection. Either there was an error, or error processing
|
|
// requires dropping the connection (This is an RFC violation if there
|
|
// is pipelined data in the input buffer).
|
|
//-----------------------------------------------------------------------------
|
|
BOOL SMTP_CONNECTION::MailBodyErrorProcessing()
|
|
{
|
|
TraceFunctEnterEx((LPARAM) this, "SMTP_CONNECTION::DoBDATErrorProcessing");
|
|
|
|
switch(m_MailBodyDiagnostic) {
|
|
|
|
case ERR_RETRY:
|
|
case ERR_OUT_OF_MEMORY:
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_MEMORY);
|
|
break;
|
|
|
|
case ERR_MAX_MSG_SIZE:
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_NOSTORAGE, ENO_RESOURCES, " %s\r\n", SMTP_MAX_MSG_SIZE_EXCEEDED_MSG );
|
|
break;
|
|
|
|
case ERR_AUTH_NEEDED:
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY, " %s\r\n", "Client was not authenticated");
|
|
break;
|
|
|
|
case ERR_TLS_NEEDED:
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_MUST_SECURE, ENO_SECURITY, " %s\r\n", SMTP_MSG_MUST_SECURE);
|
|
break;
|
|
|
|
case ERR_HELO_NEEDED:
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", "Send hello first" );
|
|
break;
|
|
|
|
case ERR_MAIL_NEEDED:
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", "Need mail command." );
|
|
break;
|
|
|
|
case ERR_RCPT_NEEDED:
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_BAD_SEQ, ESYNTAX_ERROR," %s\r\n", "Need Rcpt command." );
|
|
break;
|
|
|
|
case ERR_NO_VALID_RCPTS:
|
|
PE_CdFormatSmtpMessage (SMTP_RESP_TRANS_FAILED, ESYNTAX_ERROR," %s\r\n", SMTP_NO_VALID_RECIPS );
|
|
break;
|
|
|
|
default:
|
|
_ASSERT(0 && "Bad err value");
|
|
PE_CdFormatSmtpMessage(SMTP_RESP_NORESOURCES, ENO_RESOURCES, " %s\r\n",SMTP_NO_MEMORY);
|
|
ErrorTrace((LPARAM) this, "BUG: bad value");
|
|
}
|
|
|
|
TraceFunctLeaveEx((LPARAM) this);
|
|
return SendSmtpResponse(); // Error responses are not pipelined
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
BOOL SMTP_CONNECTION::WriteLine (char * TextToWrite, DWORD TextSize)
|
|
{
|
|
/*#if 0
|
|
|
|
char * SavedLine = TextToWrite;
|
|
char * EndOfLine = &SavedLine[TextSize];
|
|
DWORD LineSize = 0;
|
|
|
|
while( (LineSize = (EndOfLine - SavedLine)) > 78)
|
|
{
|
|
char * RealEndOfLine = &TextToWrite[78 - 2];
|
|
|
|
if(!WriteMailFile(TextToWrite, RealEndOfLine - SavedLine))
|
|
return FALSE;
|
|
|
|
m_TotalMsgSize += (RealEndOfLine - SavedLine);
|
|
|
|
if(!WriteMailFile("\r\n", 2))
|
|
return FALSE;
|
|
|
|
m_TotalMsgSize += 2;
|
|
|
|
if(!WriteMailFile("\t", 1))
|
|
return FALSE;
|
|
|
|
m_TotalMsgSize += 1;
|
|
|
|
SavedLine = RealEndOfLine;
|
|
}
|
|
|
|
if(LineSize > 0)
|
|
{
|
|
if(!WriteMailFile(TextToWrite, LineSize))
|
|
return FALSE;
|
|
|
|
m_TotalMsgSize += LineSize;
|
|
}
|
|
|
|
if(!WriteMailFile("\r\n", 2))
|
|
return FALSE;
|
|
|
|
m_TotalMsgSize += 2;
|
|
|
|
return TRUE;
|
|
#endif*/
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL SMTP_CONNECTION::AddToField(void)
|
|
{
|
|
/*#if 0
|
|
|
|
char szWriteBuffer [1000];
|
|
char szNameBuf[MAX_INTERNET_NAME + 100];
|
|
int LineSize = 0;
|
|
int PrevLineSize = 0;
|
|
DWORD NumAddress = 0;
|
|
DWORD BuffOffSet = 0;
|
|
DWORD MaxLineSize = 78;
|
|
CAddr * pCAddr = NULL;
|
|
PLIST_ENTRY pentry = NULL;
|
|
char * HeadChar ="";
|
|
|
|
//write the "To: " first. This includes the space
|
|
BuffOffSet = (DWORD) wsprintf(szWriteBuffer, "To: ");
|
|
|
|
//get the first address
|
|
pCAddr = MailInfo->GetFirstAddress(&pentry);
|
|
|
|
while(pCAddr != NULL)
|
|
{
|
|
|
|
if(NumAddress)
|
|
{
|
|
HeadChar = ", ";
|
|
}
|
|
|
|
LineSize = wsprintf(szNameBuf, "%s<%s>", HeadChar, pCAddr->GetAddress());
|
|
if( (DWORD)(LineSize + PrevLineSize) < MaxLineSize)
|
|
{
|
|
CopyMemory(&szWriteBuffer[BuffOffSet], szNameBuf, LineSize);
|
|
PrevLineSize += LineSize;
|
|
BuffOffSet += LineSize;
|
|
}
|
|
else
|
|
{
|
|
szWriteBuffer[BuffOffSet++] = CR;
|
|
szWriteBuffer[BuffOffSet++] = LF;
|
|
if(!WriteMailFile(szWriteBuffer, BuffOffSet))
|
|
return FALSE;
|
|
|
|
m_TotalMsgSize += BuffOffSet;
|
|
szWriteBuffer[0] = '\t';
|
|
CopyMemory(&szWriteBuffer[1], szNameBuf, LineSize);
|
|
BuffOffSet = LineSize + 1;
|
|
PrevLineSize = BuffOffSet;
|
|
}
|
|
|
|
NumAddress++;
|
|
|
|
//get the first address
|
|
pCAddr = MailInfo->GetNextAddress(&pentry);
|
|
}
|
|
|
|
szWriteBuffer[BuffOffSet++] = CR;
|
|
szWriteBuffer[BuffOffSet++] = LF;
|
|
|
|
if(WriteMailFile(szWriteBuffer, BuffOffSet))
|
|
{
|
|
m_TotalMsgSize += BuffOffSet ;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
#endif*/
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//---[ SMTP_CONNECTION::CopyIPAddressesToSession ]---------------------------
|
|
//
|
|
//
|
|
// Description:
|
|
// Copies the CLIENT_CONNECTION data into the session property bag used by
|
|
// inbound and outbound connections
|
|
// Parameters:
|
|
// -
|
|
// Returns:
|
|
// -
|
|
// History:
|
|
// 11/28/2001 - MikeSwa Created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
VOID SMTP_CONNECTION::CopyIPAddressesToSession()
|
|
{
|
|
TraceFunctEnterEx((LPARAM) this,
|
|
"SMTP_CONNECTION::CopyIPAddressesToSession");
|
|
HRESULT hr = S_OK;
|
|
IMailMsgPropertyBag *pISessionProperties =
|
|
(IMailMsgPropertyBag *)GetSessionPropertyBag();
|
|
|
|
if (!pISessionProperties) {
|
|
ErrorTrace((LPARAM) this, "NULL ISession - bailing");
|
|
goto Exit;
|
|
}
|
|
|
|
hr = pISessionProperties->PutStringA(ISESSION_PID_LOCAL_IP_ADDRESS,
|
|
QueryLocalHostName());
|
|
if (FAILED(hr)) {
|
|
|
|
//Trace error... otherwise ignore.
|
|
ErrorTrace((LPARAM) this,
|
|
"Failed to write ISESSION_PID_LOCAL_IP_ADDRESS 0x%08X", hr);
|
|
}
|
|
|
|
hr = pISessionProperties->PutStringA(ISESSION_PID_REMOTE_IP_ADDRESS,
|
|
QueryClientHostName());
|
|
if (FAILED(hr)) {
|
|
|
|
//Trace error... otherwise ignore.
|
|
ErrorTrace((LPARAM) this,
|
|
"Failed to write ISESSION_PID_REMOTE_IP_ADDRESS 0x%08X", hr);
|
|
}
|
|
|
|
Exit:
|
|
TraceFunctLeave();
|
|
}
|