// Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// Implementation file for class CImStmpSession -- representing
// a connection to a remote SMTP server.
//
//
#include <miutpars.h> // TImMessageField
#include <imcvsend.h> // CImSendConvert
#include <logwrap.h>
#include <logwraplimits.h>
#include "SMTS.H"
#include "IMSM.H" // CImSmtpSession
#include "IMSMSEND.H" // CImSmtpFile
#include "SMTSUTIL.H" // forward declarations for utility fns
#include <imutcon.h>
#include "mobilitytestmtmapi.h"
const TInt KSmtpAuthCodeSuccessful=235;
const TInt KSmtpAuthCodeFailed=535;
const TInt KSmtpUnableToAuthAtPresent=435;
const TInt KSmtpAuthCodeReadyResponse=334;
const TInt KSmtpBadParameter=501;
/**
Factory constructor
@param aServerEntry Server entry
@param aSettings SMTP settings
@param aServ Socket server
@param aConnect Network connection
@return Constructed class
*/
CImSmtpSession* CImSmtpSession::NewL(CMsvServerEntry& aServerEntry, CSmtpSettings& aSettings, RSocketServ& aServ, CImConnect& aConnect, TMsvId aServiceId)
{
CImSmtpSession* self = new (ELeave) CImSmtpSession(aServerEntry, aSettings, aServiceId);
CleanupStack::PushL(self);
self->ConstructL(aServ, aConnect);
CleanupStack::Pop(self);
return self;
}
/**
Connect the session to the server
@param aClientStatus Client status to be completed
@param aIsSilent If ETrue then Silent Connection is required
*/
void CImSmtpSession::ConnectL(TRequestStatus& aClientStatus, TBool aIsSilent)
{
__ASSERT_DEBUG(!iSocketIsConnected, gPanic(EImsmSocketAlreadyConnected));
if (!iSocketIsConnected)
{
if(iSettings.SecureSockets())
iThisSession = ESecureSession; // if the user has chosen to use TLS then we have to use ESMTP+TLS
else if(iSettings.SMTPAuth())
iThisSession = EEnhancedSmtpSession; // if the user has chosen to use AUTH, when we have to use ESMTP
else
iThisSession = ESmtpSession; // use SMTP session for now
iCompleted = KErrNone;
iState = EConnectingToSmtp; // Initialise to 1st state of state machine
DoStateL(aIsSilent); // Here we go...
Queue(aClientStatus);
}
else
{
// Nothing to do so just complete the caller
RequestComplete(aClientStatus, KErrNone);
}
}
/**
Send all the emails to the server
@param aSendFiles Files to send
@param aClientStatus Client status to be completed
*/
void CImSmtpSession::SendFilesL(CMsgImOutboxSend& aSendFiles, TRequestStatus& aClientStatus)
{
__ASSERT_DEBUG(iSocketIsConnected, gPanic(EImsmSocketNotConnected1));
TInt error = aSendFiles.NextFile();
if (iSocketIsConnected && error == KErrNone)
{
iSendFiles = &aSendFiles;
iState = ESendingImail;
DoStateL();
Queue(aClientStatus);
}
else if(error == KErrNotFound)
{
// Nothing to do so just complete the caller
RequestComplete(aClientStatus, KErrNotFound);
}
else
{
// Nothing to do so just complete the caller
RequestComplete(aClientStatus, KErrNone);
}
}
/**
Quit from the server
@param aClientStatus Client status to be completed
*/
void CImSmtpSession::QuitL(TRequestStatus& aClientStatus)
{
__ASSERT_DEBUG(iSocketIsConnected, gPanic(EImsmSocketNotConnected2));
if (iSocketIsConnected)
{
iState = EClosingSmtp;
DoStateL();
Queue(aClientStatus);
}
else
{
// Nothing to do so just complete the caller
RequestComplete(aClientStatus, KErrNone);
}
}
/**
Constructor
@param aServerEntry Server entry
@param aSettings SMTP settings
*/
CImSmtpSession::CImSmtpSession(CMsvServerEntry& aServerEntry, CSmtpSettings& aSettings, TMsvId aServiceId)
: CMsgActive(KImSmtpSessionPriority),
iServerEntry(aServerEntry),
iSettings(aSettings),
iServiceId(aServiceId),
iCurrentAuthProfile(CSmtpAuthMechanismHelper::EUndefined) // always start with undefined
{
__DECLARE_NAME(_S("CImSmtpSession"));
}
/**
Second phase constructor
@param aServ Socket server
@param aConnect Network connection
*/
void CImSmtpSession::ConstructL(RSocketServ& aServ, CImConnect& aConnect)
{
iSocket = CImTextServerSession::NewL(aServ, aConnect);
//if log fails, ignore the error and continue.
TRAP_IGNORE(iLogMessage = CImLogMessage::NewL(iServerEntry.FileSession()));
CActiveScheduler::Add(this); // Add SmtpSession to scheduler's queue
}
/**
Destructor
*/
CImSmtpSession::~CImSmtpSession()
{
Cancel();
if (iSocketIsConnected)
{
iSocket->Disconnect();
}
delete iSmtpFile; // should be dead already
delete iSocket;
delete iSmtpAuthHelper;
delete iLogMessage;
}
//
// CreateFormattedAddressL()
//
// Creates a new HDesC* containing the concateneatd alias and address strings
//
HBufC* CImSmtpSession::CreateFormattedAddressLC(const TDesC& aString, const TDesC& aAlias)
{
TImMessageField msgField;
// Make a buffer..
TInt strLen = aString.Length();
TInt aliasLen = aAlias.Length();
HBufC* outString = HBufC::NewLC(strLen+aliasLen+5);
HBufC* buffer = HBufC::NewL(strLen);
*buffer = msgField.GetValidInternetEmailAddressFromString(aString);
if (aliasLen)
{
outString->Des().Format(KSmtpEmailAddressAndAliasFmtStr,&aAlias,buffer);
}
else
{
*outString = *buffer; // just copy email address; as there's no alias to be added
}
delete buffer;
return outString;
}
TImImailFileProgress CImSmtpSession::FileProgress()
{
// Return info about how much of message has been sent so far
TImImailFileProgress progress;
if (iSmtpFile && (iState==ESendingImail))
{
// get progress from CImSmtpFile object as it is doing the work at the moment
iSmtpFile->GetProgress(progress);
// If there is more than 1 message being sent(ie when sending to BCC's), then
// update the Progress to take account of the Total Num of Messages sent
if (iNumMsgsToSend > 1)
{
// Calc the Total number of Bytes to send
TInt fileByteSize = iSmtpFile->BytesToSend();
TInt bytesSentBefore = (iNumMsgsSent-1)*fileByteSize; // iNumMsgsSent-1, as this msg is not yet complete
TInt totalBytesSent = bytesSentBefore + progress.iBytesSent;
// Update the Progress
progress.iBytesToSend = iTotalBytesToSend;
progress.iBytesSent = totalBytesSent;
}
}
else
{
progress.iSessionState = EClosingSmtp;
progress.iBytesSent = 0;
progress.iBytesToSend = 0;
}
return progress;
}
void CImSmtpSession::SendFileL()
{
// Start the 'file mailer' object
__ASSERT_DEBUG(!iSmtpFile,gPanic(EImsmSmtpFileObjectAlreadyExists));
// log the info of the message to be sent
if (iLogMessage)
iLogMessage->Reset();
// Update the header info for this message..
CImHeader* header = CImHeader::NewLC();
CMsvStore* store = iServerEntry.EditStoreL();
CleanupStack::PushL(store);
header->RestoreL(*store);
TBool isBccRcpt = EFalse;
CDesCArray* rcptArray = NULL;
TBool sendBccNow = iToRcptHeaderUpdated;
if(!iToRcptHeaderUpdated)
{
// Set the new info...
//set from address
HBufC* formatedAddress = CreateFormattedAddressLC(iSettings.EmailAddress(), iSettings.EmailAlias());//Format the address fields
header->SetFromL(*formatedAddress);
CleanupStack::PopAndDestroy(formatedAddress);
formatedAddress = NULL;
// set ReceiptAddress if one exists in the settings
if( header->ReceiptAddress().Length() == 0 && iSettings.ReceiptAddress().Length() > 0 )
{
formatedAddress=CreateFormattedAddressLC(iSettings.ReceiptAddress(),iSettings.EmailAlias());//Format the address fields
header->SetReceiptAddressL(*formatedAddress);
CleanupStack::PopAndDestroy(formatedAddress);
formatedAddress = NULL;
}
if(header->ToRecipients().Count()==0 && header->CcRecipients().Count()==0)
{
sendBccNow=ETrue;
}
// ReplyToAddress
if ((header->ReplyTo().Length() == 0 || header->ReplyTo() == iSettings.ReplyToAddress()) &&
(iSettings.ReplyToAddress().Length() > 0))
{
formatedAddress= CreateFormattedAddressLC(iSettings.ReplyToAddress(),iSettings.EmailAlias());//Format the address fields
header->SetReplyToL(*formatedAddress);
CleanupStack::PopAndDestroy(formatedAddress);
formatedAddress = NULL;
}
//make a copy of 'Bcc' recipients then remove from Msg header
rcptArray= &(header->BccRecipients());
if (iSettings.SendCopyToSelf()==ESendCopyAsBccRecipient)
{
if ( header->ReceiptAddress().Length() )
rcptArray->AppendL(header->ReceiptAddress());
else if ( header->ReplyTo().Length() )
rcptArray->AppendL(header->ReplyTo());
}
if(rcptArray->Count() > 0)
{
iBccRcptFound=ETrue; // 'Bcc' recipients exists
iBccRcptIndex=0; //reset the counter
iFinishedWithBccRcpts=EFalse;
iSendFiles->ResetBccRcptArrayL();
TInt numberRcpts = rcptArray->Count();
if(numberRcpts)
{
iSendFiles->BccRcptArray().AppendL((*rcptArray)[0]);
CDesCArray& tempArr = iSendFiles->BccRcptArray();
for(TInt counter=1; counter<numberRcpts; counter++)
{
TInt aPos = 0;
TInt found = tempArr.Find((*rcptArray)[counter],aPos,ECmpFolded16);
if(found == KErrNone && aPos > 0)
{
iSendFiles->BccRcptArray().AppendL((*rcptArray)[counter]);
}
}
}
rcptArray->Reset();
}
iToRcptHeaderUpdated=ETrue;
isBccRcpt=EFalse;
}
// Reset the number of Sent Messages, if all previous messages have been sent
if (iNumMsgsSent >= iNumMsgsToSend)
{
iNumMsgsSent = 0;
}
// Calc the num of messages we're sending. If there is a To or CC,
// recipient then there will be a message sent
if(header->ToRecipients().Count()>0 || header->CcRecipients().Count()>0)
iNumMsgsToSend = 1;
else
iNumMsgsToSend = 0;
// If there are BCC recipients, then we will be sending a message to each
if (iBccRcptFound)
iNumMsgsToSend += iSendFiles->BccRcptArray().Count();
// Make sure there are recipients to send to.
__ASSERT_ALWAYS(iNumMsgsToSend,gPanic(EImsmZeroRecipientsInMessage));
// sending Msg to 'Bcc' recipients so update header
if (sendBccNow && iBccRcptFound)
{
rcptArray= &(header->BccRecipients());
rcptArray->Reset();
header->BccRecipients().AppendL(iSendFiles->BccRcptArray()[iBccRcptIndex]);
++iBccRcptIndex;//for the next recipient
if(iBccRcptIndex < iSendFiles->BccRcptArray().Count())
iFinishedWithBccRcpts=EFalse;
else
iFinishedWithBccRcpts=ETrue;
isBccRcpt=ETrue;
}
header->StoreL(*store);
store->CommitL();
if (iLogMessage)
{
iLogMessage->LogEvent().SetEventType(KLogMailEventTypeUid);
if ((header->ToRecipients().Count()
+ header->CcRecipients().Count()
+ header->BccRecipients().Count()) > 1)
// If there are multiple recipients then set the recipient to 'multiple'
{
TBuf<KLogMaxSharedStringLength> multipleRecipientsString;
iLogMessage->GetString(multipleRecipientsString, R_LOG_REMOTE_MULTIPLE);
iLogMessage->LogEvent().SetRemoteParty(multipleRecipientsString);
}
else
// If there is only one recipient then set the recipient name
{
if (header->ToRecipients().Count())
iLogMessage->LogEvent().SetRemoteParty(header->ToRecipients()[0]);
else if (header->CcRecipients().Count())
iLogMessage->LogEvent().SetRemoteParty(header->CcRecipients()[0]);
else if (header->BccRecipients().Count())
iLogMessage->LogEvent().SetRemoteParty(header->BccRecipients()[0]);
}
iLogMessage->LogEvent().SetSubject(header->Subject());
TBuf<KLogMaxSharedStringLength> outString;
iLogMessage->GetString(outString, R_LOG_DIR_OUT);
iLogMessage->LogEvent().SetDirection(outString);
iLogMessage->LogEvent().SetLink(iServerEntry.Entry().Id());
}
CleanupStack::PopAndDestroy(2, header);
iTimeNow.UniversalTime(); // Set time/date of sending this message file
if (iLogMessage)
iLogMessage->LogEvent().SetTime(iTimeNow);
TRAPD(error,iSmtpFile = CImSmtpFile::NewL( *iSocket,
iServerEntry,
iTimeNow,
iSmtpBuffer,
iSettings,
isBccRcpt)); // Create the file mailing object
if(error == KErrNone)
{
// If this is the first message to send, then calc the total bytes to send
if (iNumMsgsSent == 0)
{
iTotalBytesToSend = iSmtpFile->TotalMsgSizeL() * iNumMsgsToSend;
}
// Tell the file how many bytes it will be sending. This is just a
// rough estimate for progress
iSmtpFile->SetBytesToSend(iTotalBytesToSend/iNumMsgsToSend);
// Increment the number of messages sent
++iNumMsgsSent;
iSmtpFile->StartL(iStatus); // start sending the message
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpSendFile);
}
else
{
iCompleted=error;
RequestComplete(iStatus,0-error); // Failed to create File object, but don't want to stop session prematurely, so negate error code
}
}
void CImSmtpSession::DoCancel()
{
if (iSmtpFile)
{
iSmtpFile->Cancel(); //which calls iSocket->Cancel()
delete iSmtpFile; // this is expected to be deleted
iSmtpFile=NULL;
}
else if (iSocket)
{
iSocket->Cancel();
}
if (iLogMessage)
{
iLogMessage->Cancel();
}
CMsgActive::DoCancel(); // this MUST be the last statement in this function
}
void CImSmtpSession::DoRunL()
{
// Received a KErrNone signal completing last pending asynchronous operation.
// So now decide what to do next...
delete iSmtpFile; // delete mailing object; (new one created later if need to mail another message)
iSmtpFile=NULL;
if (iStatus.Int() != KErrNone && iState!=ELogDataEvent)
{
iCompleted=iStatus.Int(); // retain any non-zero completion code for future reference when DoComplete() is called
}
iState = NextStateL();
if (!iOperationComplete)
{
if (iSmtpMultiLineResponse)
{
// Don't change state, just read next line of multi-line
// response from remote SMTP server...
iSmtpLastMultiLineResponse=EFalse;
iSocket->QueueReceiveNextTextLine(iStatus);
SetActive();
}
else
{
DoStateL(); // move to state that was chosen by this function
}
}
else
{
iOperationComplete = EFalse;
}
}
void CImSmtpSession::DoComplete(TInt& aStatus)
{
if (aStatus==KErrDisconnected && iState==EClosingSmtp)
{
// ignore sudden disconnect if in closing state
aStatus = KErrNone;
}
if (iCompleted && aStatus==KErrNone) // last non-zero error in aStatus takes preference over older error stored in iCompleted
{
aStatus = iCompleted;
}
if (aStatus>KErrNone)
{
switch (aStatus)
{
case ESmtpMailboxNoAccess:
case ESmtpMailboxName:
case ESmtpTransactionFailed:
aStatus=KSmtpLoginRefused;
break;
case -KSmtpBadMailFromAddress:
case -KSmtpBadRcptToAddress:
case -KSmtpNoMailFromErr:
case -KSmtpLoginRefused:
// Negate the status because it was negated earlier so that it wouldn't be fatal
aStatus = -aStatus;
break;
default:
aStatus=KSmtpUnknownErr;
}
}
}
TInt CImSmtpSession::NextStateL()
// this returns the next state according to the current state and the response text received from the server
// if the text has not been accepted by the server, then we close our session with it, i.e. return EClosingSmtp.
{
switch(iState)
{
case EConnectingToSmtp:
return EWaitingForReply;
case EWaitingForReply:
return NextStateFromWaitingForReply();
case EAuthorisingSmtp:
return NextStateFromAuthorisingSmtpL();
case ESendingStarttls:
return NextStateFromSendStarttls();
case ESettingSecurity:
return NextStateFromSettingSecurityL();
case ESendingImail:
if (iBccRcptFound && !iFinishedWithBccRcpts)
{
return ESendingImail;
}
else
{
// 'Bcc' recipients are dealt with at this point
iSendFiles->SetLastMessageStatusL(iTimeNow,(TSmtpSessionError)iStatus.Int());
// ... which unlocks the server entry (except the first time)
// now move on to the next Email message
iToRcptHeaderUpdated=EFalse; // for the new msg
iBccRcptFound=EFalse;
iFinishedWithBccRcpts=ETrue;
return ELogDataEvent;
}
case ELogDataEvent:
return NextStateFromNextFile();
case EClosingSmtp:
{
iOperationComplete = ETrue;
return iState;
}
case EAuthInProgress:
// for SMTP auth, call specific function, to stop cluttering up this one
return NextStateFromAuthInProgressL();
case EResetSmtp:
return NextStateFromResetSmtp();
default:
return iState;
}
}
TInt CImSmtpSession::NextStateFromWaitingForReply()
{
if (CommandAccepted())
{
if (!iEsmtpSpokenHere)
{
iEsmtpSpokenHere = (iSmtpBuffer.Match(KEsmtpMatchString) != KErrNotFound);
if (iEsmtpSpokenHere && iThisSession == ESmtpSession)
iThisSession = EEnhancedSmtpSession;
}
if (iSmtpMultiLineResponse)
{
// If this is not last reply of a mullti-line response... request next
// line and don't change state
return EWaitingForReply;
}
return EAuthorisingSmtp;
}
else
{
// SMTP server didn't return "220" to "HELO" command, so quit session
// without sending any Emails.
return EClosingSmtp;
}
}
TInt CImSmtpSession::NextStateFromAuthorisingSmtpL()
{
if (CommandAccepted())
{
UpdateAuthorisingInfo();
if (iSmtpMultiLineResponse)
{
// If this is not last reply of a mullti-line response... request next
// line and don't change state.
return EAuthorisingSmtp;
}
if (iThisSession==ESecureSession)
{
if(iStartTlsAcceptedHere)
{
return ESendingStarttls;
}
else
{
// The SMTP server doesn't support TLS.
// Save the error and close down the session.
iCompleted = KErrSmtpTLSNegotiateFailed;
return EClosingSmtp;
}
}
else if (iThisSession==EEnhancedSmtpSession)
{
if (iSettings.SMTPAuth() && SelectNextSMTPAuthProfileL())
{
// if possible, perform SMTP AUTH
return EAuthInProgress;
}
iOperationComplete = ETrue;
return iState;
}
else
{
__ASSERT_DEBUG(iThisSession==ESmtpSession, gPanic(EImsmBadSessionState));
iOperationComplete = ETrue;
return iState;
}
}
else
{
// EHLO or HELO rejected by server
if( iSettings.SecureSockets() )
{
// Cannot continue if we're trying for secure?
iCompleted = KErrSmtpTLSNegotiateFailed;
return EClosingSmtp;
}
else if((iLastSmtpCode == KSmtpBadParameter) && (iRetryAuthWithHostname==EFalse))
{
// The server did not like the supplied IP parameter, reset and retry with a named
// string as paramter using the same EHLO or HELO command if it hasn't already been
// attempted
iRetryAuthWithHostname = ETrue;
iCompleted = KErrNone;
return EResetSmtp;
}
else if(iThisSession==EEnhancedSmtpSession)
{
// EHLO has failed, reset and retry with HELO
iCompleted = KErrNone;
iRetryAuthWithHostname = EFalse;
iThisSession = ESmtpSession;
return EResetSmtp;
}
else
{
// Every attempt has failed, iCompleted has been set with an appropriate
// error code in the CommandAccepted() call, so simply close the smtp session
return EClosingSmtp;
}
}
}
TInt CImSmtpSession::NextStateFromSendStarttls()
{
if (CommandAccepted() && iSmtpMultiLineResponse)
{
// If this is not last reply of a mullti-line response... request next
// line and don't change state
return ESendingStarttls;
}
if(iCompleted)
return EClosingSmtp;
else
return ESettingSecurity;
}
TInt CImSmtpSession::NextStateFromSettingSecurityL()
{
if ( CommandAccepted() )
{
UpdateAuthorisingInfo();
if (iSmtpMultiLineResponse)
{
// If this is not last reply of a mullti-line response... request next
// line and don't change state
return ESettingSecurity;
}
}
if(iCompleted)
return EClosingSmtp;
if (iSettings.SMTPAuth() && SelectNextSMTPAuthProfileL())
return EAuthInProgress;
else
{
iOperationComplete = ETrue;
return iState;
}
}
TInt CImSmtpSession::NextStateFromAuthInProgressL()
{
TInt smtpCode=ESmtpNoReturnCode;
TBool commandAccepted=GetCurrentTextLine();
if (commandAccepted)
{
// parse out SMTP code from text response
smtpCode=SmtpResponseCode(iSmtpBuffer,iSmtpMultiLineResponse,iSmtpLastMultiLineResponse);
if (iSmtpMultiLineResponse)
{
// Check for response code 535
if (smtpCode == KSmtpAuthCodeFailed)
{
User::Leave(KSmtpLoginRefused);
}
iSmtpAuthHelper->SetLastServerMessageL(iSmtpBuffer,iSmtpMultiLineResponse);
return iState;
}
if(smtpCode==KSmtpAuthCodeSuccessful || smtpCode==KSmtpAuthCodeFailed || smtpCode==KSmtpUnableToAuthAtPresent)
{
//if KSmtpAuthCodeSuccessful then SmtpAuth concluded - move onto next state.
//if KSmtpAuthCodeFailed then SmtpAuth failed, but try to send anyway
iOperationComplete = ETrue;
return iState;
}
else if(smtpCode==ESmtpParamNotImplemented)
{
// this mechanism not recognised by the server.
delete iSmtpAuthHelper;
iSmtpAuthHelper=NULL;
if (SelectNextSMTPAuthProfileL())
{
return EAuthInProgress;
}
else
{
// all AUTH attempts fail, try to send anyway
iOperationComplete = ETrue;
return iState;
}
}
else if(smtpCode==KSmtpAuthCodeReadyResponse)
{
iSmtpAuthHelper->SetLastServerMessageL(iSmtpBuffer,iSmtpMultiLineResponse);
return EAuthInProgress;
}
// if here, we will execute default error case, unless someone adds
// a clause not resulting in a "return" statement
}
// default error case
iCompleted=SmtpSessionError(smtpCode);
return EClosingSmtp;
}
TInt CImSmtpSession::NextStateFromResetSmtp()
{
if( CommandAccepted() )
{
if (iSmtpMultiLineResponse)
{
// If this is not last reply of a mullti-line response... request next
// line and don't change state
return EResetSmtp;
}
// The reset was accepted - send the HELO command.
return EAuthorisingSmtp;
}
// The reset was not accepted - didn't get a 2xx response. Might as
// well end the SMTP session.
return EClosingSmtp;
}
void CImSmtpSession::DoStateL(TBool aIsSilent)
// performs the operation required by iState
{
switch (iState)
{
case EConnectingToSmtp:
iEsmtpSpokenHere = EFalse;
i8BitMimeAcceptedHere = EFalse;
iSizeAcceptedHere = EFalse;
iStartTlsAcceptedHere = EFalse;
iSocket->SetSilentConnection(aIsSilent);
if(iSettings.SSLWrapper())
{
// Open secure socket on port 465 for SMTP session
iSocket->SSLQueueConnectL(iStatus, iSettings.ServerAddress(), iSettings.Port(), iSettings.IapPrefs(), iSettings.TlsSslDomain());
}
else
{
// Open socket on port 25 for SMTP session
iSocket->QueueConnectL(iStatus, iSettings.ServerAddress(), iSettings.Port(), iSettings.IapPrefs(), iSettings.TlsSslDomain());
}
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpConnectingToSmtp);
break;
case EWaitingForReply:
iSocketIsConnected=ETrue;
iSocket->QueueReceiveNextTextLine(iStatus);
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpWaitingForReply);
break;
case EAuthorisingSmtp:
{
TBuf8<KImskIPAddressLen> address;
GetIpAddress(address);
if(iRetryAuthWithHostname)
{
// Try sending a text string hostname instead of IP address
_LIT(KDefaultNameString, "localhost");
address.Copy(KDefaultNameString);
}
if (iThisSession!=ESmtpSession)
iSmtpBuffer.Format(KSmtpEhloCommand,&address);
else
iSmtpBuffer.Format(KSmtpHeloCommand,&address);
iSocket->SendQueueReceive(iStatus, iSmtpBuffer);
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpAuthorisingSmtp);
break;
}
case EAuthInProgress:
{
iSmtpAuthHelper->GetNextClientMessageL(iSmtpBuffer);
iSocket->SendQueueReceive(iStatus, iSmtpBuffer);
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpAuthInProgress);
break;
}
case ESendingStarttls:
{
// User has chosen to use Tls - send the STARTTLS cmd.
iSocket->SetSSLTLSResponseL(KSmtpTlsResponse);
iSocket->SendQueueReceive(iStatus, KSmtpStartTlsCommand());
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpSendingStartTls);
break;
}
case ESettingSecurity:
{
// Reset the Auth profiles and flags.
i8BitMimeAcceptedHere = EFalse;
iSizeAcceptedHere = EFalse;
iStartTlsAcceptedHere = EFalse;
iSupportedAuthProfiles = ENone;
// Set Security, send a NOOP
// and then queues asynch read request for a response
// from the remote SMTP server.
TBuf8<KImskIPAddressLen> address;
GetIpAddress(address);
iSmtpBuffer.Format(KSmtpEhloCommand, &address);
iSocket->SendQueueReceive(iStatus,iSmtpBuffer);
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpSettingSecurity);
break;
}
case ESendingImail: // send one Imail message
SendFileL();
break;
case ELogDataEvent:
{
if (iLogMessage)
{
const TInt logError = iServerEntry.Entry().iError;
if (logError == KErrNone)
{
TBuf<KLogMaxStatusLength> status;
if (iLogMessage->GetString(status, R_LOG_DEL_SENT) == KErrNone)
iLogMessage->LogEvent().SetStatus(status);
}
iLogMessage->Start(logError, iStatus);
}
else
{
TRequestStatus* status = &iStatus;
User::RequestComplete(status, KErrNone);
}
break;
}
case EResetSmtp:
iSocket->SendQueueReceive(iStatus, KSmtpResetCommand());
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpResetSmtp);
break;
case EClosingSmtp: //finished running - end SMTP session
iSocket->SendQueueReceive(iStatus, KSmtpQuitCommand());
MOBILITY_TEST_MTM_STATE(iServiceId, KMobilityTestMtmStateSmtpClosingSmtp);
break;
default: // Unknown state
gPanic(EImsmBadSessionState);
break;
}
SetActive();
}
TInt CImSmtpSession::SmtpSessionError(const TInt aSmtpErrorCode)
{
// This function is called when no socket/memory error occurred whilst completing
// last state, but the remote SMTP server did not return the expected 3 digit
// completion code.
// Select an appropriate error code determined from the current state of CImSmtpSession.
switch (iState)
{
case EAuthorisingSmtp:
if (aSmtpErrorCode==ESmtpSyntaxError)
return (KSmtpLoginRefused);
break;
case EWaitingForReply:
// assume that ANY failure in this state means I have been refused connection to SMTP server
return (KSmtpLoginRefused);
case ESendingStarttls:
case ESettingSecurity:
case EConnectingToSmtp:
case ESendingImail:
case EResetSmtp:
case EClosingSmtp:
break;
case EAuthInProgress:
if (aSmtpErrorCode==KSmtpAuthCodeFailed || aSmtpErrorCode==KSmtpUnableToAuthAtPresent)
return (KSmtpLoginRefused);
break;
default: // Unknown state
gPanic(EImsmBadSessionState);
break;
}
return IdentifySmtpError(aSmtpErrorCode);
}
TBool CImSmtpSession::GetCurrentTextLine()
{
TInt result=iSocket->GetCurrentTextLine(iSmtpBuffer);
__ASSERT_DEBUG(result==ECRLFTerminated,gPanic(EImsmBadSmtpBuffer));
if(result==ECRLFTerminated)
return ETrue;
else
return EFalse;
}
void CImSmtpSession::GetIpAddress(TDes8& aAddress)
/** Gets the local IP address from the socket without any trailing scope identifiers
@param aAddress Buffer in which the IP address of the socket will be returned */
{
aAddress.Copy(iSocket->LocalName());
// Remove the %nn scope identifier if present Eg. x.x.x.x.x.x%1 becomes x.x.x.x.x.x
// This only occurs with IPv6 and was a requirement of multi-homing.
TInt pos = aAddress.Locate(TChar('%'));
if (pos>0)
{
aAddress.SetLength(pos);
}
}
/*
From knowledge of the last profile used, and the profiles available, select a next profile
to try, create the helper class
otherwise return false
*/
TBool CImSmtpSession::SelectNextSMTPAuthProfileL()
{
CSmtpAuthMechanismHelper::TSmtpAuthProfileFlag nextProfile=NextSMTPAuthProfile(iCurrentAuthProfile);
while (!(nextProfile&iSupportedAuthProfiles) && nextProfile!=CSmtpAuthMechanismHelper::ENoProfile) // while "nextProfile" is not supported
// and there are profiles to try
nextProfile=NextSMTPAuthProfile(nextProfile); // try the next one
iCurrentAuthProfile=nextProfile;
if (iCurrentAuthProfile==CSmtpAuthMechanismHelper::ENoProfile) // run out of profiles to try
return EFalse;
else
{
__ASSERT_DEBUG(!iSmtpAuthHelper, gPanic(EImsmSmtpAuthHelperAlreadyExists));
iSmtpAuthHelper=CreateSMTPAuthHelperL(iCurrentAuthProfile,iSettings);
return ETrue;
}
}
// Get the value of the connecting IAP from CImTextServerSession
// Returns the value of the currently connecting IAP or a system-wide error code.
TInt CImSmtpSession::GetConnectionIAP()
{
TUint32 iap;
TInt err = iSocket->GetIAPValue(iap);
if (err == KErrNone)
{
return iap;
}
else
{
return err;
}
}
CSmtpAuthMechanismHelper::TSmtpAuthProfileFlag CImSmtpSession::NextSMTPAuthProfile(CSmtpAuthMechanismHelper::TSmtpAuthProfileFlag aPreviousProfile)
{
// we try the following profiles in this order, CRAM-MD5, LOGIN, PLAIN
switch(aPreviousProfile)
{
case CSmtpAuthMechanismHelper::EUndefined:
return CSmtpAuthMechanismHelper::ECramMD5;
case CSmtpAuthMechanismHelper::ECramMD5:
return CSmtpAuthMechanismHelper::ELogin;
case CSmtpAuthMechanismHelper::ELogin:
return CSmtpAuthMechanismHelper::EPlain;
case CSmtpAuthMechanismHelper::EPlain:
return CSmtpAuthMechanismHelper::ENoProfile;
default:
gPanic(EImsmBadSmtpAuthProfile1);
}
return CSmtpAuthMechanismHelper::ENoProfile;
}
TInt CImSmtpSession::NextStateFromNextFile()
{
if (iSendFiles->NextFile() == KErrNone)
{
return ESendingImail;
}
else
{
iOperationComplete = ETrue;
return iState;
}
}
TBool CImSmtpSession::CommandAccepted()
{
iLastSmtpCode = ESmtpNoReturnCode;
TBool commandAccepted = GetCurrentTextLine();
if (commandAccepted)
{
iLastSmtpCode = SmtpResponseCode(iSmtpBuffer, iSmtpMultiLineResponse, iSmtpLastMultiLineResponse); // parse out SMTP code from text response
commandAccepted = LastSmtpCommandAccepted(iLastSmtpCode, 2); // was response accepted by remote server?
}
if (!commandAccepted)
{
// overrride completion code with suitable error number
iCompleted = SmtpSessionError(iLastSmtpCode);
}
return commandAccepted;
}
void CImSmtpSession::UpdateAuthorisingInfo()
{
if (!i8BitMimeAcceptedHere)
{
// i.e. don't do test again if flag is already set
i8BitMimeAcceptedHere = (iSmtpBuffer.Match(K8BitMimeMatchString) != KErrNotFound);
}
if (!iSizeAcceptedHere)
{
iSizeAcceptedHere = (iSmtpBuffer.Match(KSizeMatchString) != KErrNotFound);
// ESMTP: V1.1 parse string to extract max message size from string here...
}
if (!iStartTlsAcceptedHere)
{
iStartTlsAcceptedHere = (iSmtpBuffer.Match(KStartTlsMatchString) != KErrNotFound);
}
//Support added for Domino server.
if (iSmtpBuffer.Match(KAuthMatchString) != KErrNotFound ||
iSmtpBuffer.Match(KAuthDominoMatchString) != KErrNotFound)
{
// check for each supported profile
if (iSmtpBuffer.Match(KAuthPlainMatchString) != KErrNotFound)
iSupportedAuthProfiles|=CSmtpAuthMechanismHelper::EPlain;
if (iSmtpBuffer.Match(KAuthLoginMatchString) != KErrNotFound)
iSupportedAuthProfiles|=CSmtpAuthMechanismHelper::ELogin;
if (iSmtpBuffer.Match(KAuthCramMD5MatchString) != KErrNotFound)
iSupportedAuthProfiles|=CSmtpAuthMechanismHelper::ECramMD5;
// Special case for Domino servers tagging "=" after "AUTH"
if (iSmtpBuffer.Match(KAuthDominoPlainMatchString) != KErrNotFound)
iSupportedAuthProfiles|=CSmtpAuthMechanismHelper::EPlain;
if (iSmtpBuffer.Match(KAuthDominoLoginMatchString) != KErrNotFound)
iSupportedAuthProfiles|=CSmtpAuthMechanismHelper::ELogin;
if (iSmtpBuffer.Match(KAuthDominoCramMD5MatchString) != KErrNotFound)
iSupportedAuthProfiles|=CSmtpAuthMechanismHelper::ECramMD5;
}
}