// Copyright (c) 2006-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:
//
#include <e32std.h>
#include "POPS.H"
#include "POPSOP.H"
#include <pop3set.h>
#include <iapprefs.h>
#include <hash.h>
#include <pop3set.h>
#include <e32std.h>
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
#include "cpopsaslauthhelper.h"
#endif
#include "POPS.PAN" // impp's own panic codes
#define KPeriod 30000000 // period of timer
// Specifies how long a socket is allowed to be inactive before we close it
// down. This handles the situation where the mail server closes down the
// connection, but we don't receive any indication of that. It has been seen
// when connected using GPRS, and a long telephone call is then made.
const TInt KPopSendInactivityTimeMinutes = 30;
const TInt KPopReceiveInactivityTimeMinutes = 30;
// Utility functions used by both active objects...
GLDEF_C TBool CommandAccepted(TDesC8& aResponse)
{
// +OK signifies a successful command -ERR otherwise
return (0==aResponse.Locate('+'));
}
//
// My own panic command
//
GLDEF_C void Panic(TPopsPanic aPanic)
{
_LIT(KPanicText,"Pop3 session");
User::Panic(KPanicText, aPanic);
}
CImPop3Session::CImPop3Session()
: CMsgActive(KImPopSessionPriority)
{
__DECLARE_NAME(_S("CImPop3Session"));
}
CImPop3Session* CImPop3Session::NewL(RSocketServ& aServ, CImConnect& aConnect)
{
CImPop3Session* self = new (ELeave) CImPop3Session();
CleanupStack::PushL(self);
self->ConstructL(aServ, aConnect);
CleanupStack::Pop();
return self;
}
void CImPop3Session::ConstructL(RSocketServ& aServ, CImConnect& aConnect)
{
iSocket=CImTextServerSession::NewL(KPopSendInactivityTimeMinutes, KPopReceiveInactivityTimeMinutes, aServ, aConnect);
iNoMessages=0;
iIdTab=NULL;
iSocketConnected=EFalse;
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
iSaslAuthLogin = EFalse;
#endif
CActiveScheduler::Add(this); // Add PopSession to scheduler's queue
}
CImPop3Session::~CImPop3Session()
{
Cancel();
delete iPopApop;
delete iConnectReply;
delete iSocket;
delete [] iIdTab;
delete iCaf;
delete iPopCapabilities;
}
//
// Connect to Pop3 with User and Pass
//
void CImPop3Session::ConnectL(CImPop3Settings* aPopSettings, const CImIAPPreferences& aPrefs, TRequestStatus& aStatus)
{
__ASSERT_DEBUG(!IsActive(),Panic(EImppAlreadyActive));
// copy of pop settings owned by mtm
iPopSettings = aPopSettings;
iCompleted=KErrNone;
iState=EConnectingToPop; // Initialise to 1st state of state machine
Queue(aStatus);
if(iPopSettings->SSLWrapper())
{
iSocket->SSLQueueConnectL(iStatus, iPopSettings->ServerAddress(), aPopSettings->Port(), aPrefs, iPopSettings->TlsSslDomain());
}
else
{
iSocket->QueueConnectL(iStatus, iPopSettings->ServerAddress(), aPopSettings->Port(), aPrefs, iPopSettings->TlsSslDomain());
}
SetActive();
}
//
// Queue a read if mailbox is in idle state
//
void CImPop3Session::Waiting(TRequestStatus& aStatus)
{
iState=EPopConnected;
iSocket->QueueReceiveNextTextLine(iStatus);
SetActive();
Queue(aStatus);
}
//
// Put a QUIT function
//
void CImPop3Session::Quit(TRequestStatus& aStatus)
{
__ASSERT_DEBUG(!IsActive(),Panic(EImppAlreadyActive));
iState=EStopPop;
if(iSocketConnected)
{
_LIT8(KPop3CmdQuit, "QUIT\r\n");
iSocket->SendQueueReceive(iStatus,KPop3CmdQuit());
SetActive();
Queue(aStatus);
}
else
{
TRequestStatus* pS=&iStatus;
User::RequestComplete(pS,KErrNone);
}
}
//
// Pass the TMsg array to our Pop session
//
void CImPop3Session::SetMessageArray(TInt32* anIdArray, TUint aNoMessages)
{
iIdTab=anIdArray;
iNoMessages=aNoMessages;
}
TInt32* CImPop3Session::MessageArray()
{
return iIdTab;
}
TInt CImPop3Session::GetNumMessages()
{
return iNoMessages;
}
//
// Return Pop3 message no for a given message id
//
TInt CImPop3Session::MessageNo(TMsvId aMsgId)
// AF Using TInt reduces the available range of +ve numbers
// but shouldn't be a problem should it?
{
TInt msgNo=KErrNotFound;
TInt arrayCtr=0;
if(iIdTab==NULL || iNoMessages==0)
{
Panic(EImppBabelMsgArrayNotDefined);
}
while(arrayCtr<iNoMessages && msgNo==KErrNotFound)
{
if(TInt32(*(iIdTab+arrayCtr))==aMsgId)
{
msgNo=arrayCtr+1;
}
arrayCtr++;
}
return msgNo;
}
//
// Cancel any current operation
//
void CImPop3Session::DoCancel()
{
if(iState==EStopPop)
{
iSocket->Disconnect();
iSocketConnected=EFalse;
}
iSocket->Cancel(); // clear a pending socket call
CMsgActive::DoCancel();
}
//
// Result of last active call
//
void CImPop3Session::DoRunL()
{
if(iState==EStopPop)
{
iSocket->Disconnect();
iSocketConnected=EFalse;
}
//get the response
if(iState!=EConnectingToPop && iState!=EStopPop)
{
if(iState != EWaitingForReply)
{
TInt lineState=iSocket->GetCurrentTextLine(iResponseBuffer);
__ASSERT_ALWAYS(lineState==ECRLFTerminated,User::Leave(EImppBufferNotTerminated));
if(!CommandAccepted(iResponseBuffer))
{
User::Leave(GetPopError());
}
}
if(iPopSettings->Apop() && iState==EWaitingForReply)
{
HBufC8* connectReply = HBufC8::NewL(KImMailMaxBufferSize);
// Delete iConnectReply if not NULL & assign newly created
delete iConnectReply;
iConnectReply = connectReply;
iConnectReply->Des().Copy(iResponseBuffer);
}
}
if(iState!=EStopPop)
{
// successful response decide what to do next
switch(iState)
{
case EPopCapabilities:
GetCapabilitiesL();
iState = EWaitingForReply;
break;
case EWaitingForReply:
if(iPopSettings->SecureSockets())
{
iState = ERequestingTLS;
}
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
else if(iPopSettings->POP3Auth())
{
iState = ESaslAuthInProgress;
SelectAuthExtensionProfileL();
}
#endif
else if(iPopSettings->Apop())
{
iState=EAuthorisingApop;
}
else
{
iState++;
}
ChangeStateL();
break;
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
case ESaslAuthInProgress:
// To set the server response, accodring the response will be creating
// the authentication string.
iPopAuthHelper->SetLastServerMessageL(iResponseBuffer, ETrue);
iState++;
ChangeStateL();
break;
case ESaslAuthIsDone:
iSocketConnected=ETrue;
break;
#endif
case EConnectingToPop:
case EAuthorisingUser:
iState++;
ChangeStateL();
break;
case EAuthorisingPass:
case EAuthorisingApop:
iSocketConnected=ETrue;
break;
// TLS request accepted
case ERequestingTLS:
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
if(iPopSettings->POP3Auth())
{
iState = ESaslAuthInProgress;
SelectAuthExtensionProfileL();
}
else
#endif
{
iPopSettings->Apop() ? iState = EAuthorisingApop : iState =EAuthorisingUser;
}
ChangeStateL();
break;
}
}
}
void CImPop3Session::DoComplete(TInt& aCompleteStatus)
{
TInt status=aCompleteStatus;
if (status==KErrNone) // no error
{
return;
}
if(iSocketConnected && status==KErrCancel) // Cancel() has been called
{
return;
}
// if we've connected but it all goes wrong send a synchronous quit kludged code
switch (iState)
{
case EWaitingForReply: // stay connected
case EStopPop:
break;
case EAuthorisingUser:
case EAuthorisingPass:
case EAuthorisingApop:
case EPopConnected: // idle read has returned POP server timed out
case EConnectingToPop: // lose connection
case ERequestingTLS:
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
case ESaslAuthInProgress:
case ESaslAuthIsDone:
#endif
iSocket->Disconnect();
iSocketConnected=EFalse;
break;
}
}
void CImPop3Session::ChangeStateL()
{
//
// State machine of the POP mail session.
// (But only handle states which can leave)
// Identify state on entry, change to next state and then
// start new operation associated with that new state.
//
__ASSERT_DEBUG(!IsActive(),Panic(EImppAlreadyActive));
switch (iState)
{
case EPopCapabilities:
iSocket->QueueReceiveNextTextLine(iStatus);
break;
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
case ESaslAuthInProgress:
iPopAuthHelper->GetNextClientMessageL(iResponseBuffer);
iSocket->SendQueueReceive(iStatus, iResponseBuffer);
iSocket->PerformLogging(ETrue);
break;
case ESaslAuthIsDone:
iPopAuthHelper->GetNextClientMessageL(iResponseBuffer);
iSocket->SendQueueReceive(iStatus, iResponseBuffer);
iSocket->PerformLogging(ETrue);
if(iSaslAuthLogin)
{
iState = ESaslAuthInProgress;
iSaslAuthLogin = EFalse;
}
break;
#endif
case EAuthorisingUser:
case EAuthorisingApop:
{
TPtrC8 loginName=iPopSettings->LoginName();
if (iState == EAuthorisingApop)
{
TRAPD(apopErr,ConstructApopL());
if(apopErr!=KErrNone)
{
User::Leave(KPop3CannotCreateApopLogonString);
}
iSocket->PerformLogging(EFalse);
_LIT8(KPop3CmdApop, "APOP %S %S\r\n");
iSocket->SendQueueReceive(iStatus, KPop3CmdApop(), &loginName, iPopApop);
iSocket->PerformLogging(ETrue);
delete iPopApop;
iPopApop=NULL;
delete iConnectReply;
iConnectReply=NULL;
}
else
{
iSocket->PerformLogging(EFalse);
_LIT8(KPop3CmdUser, "USER %S\r\n");
iSocket->SendQueueReceive(iStatus, KPop3CmdUser(), &loginName);
iSocket->PerformLogging(ETrue);
}
break;
}
case EAuthorisingPass:
{
TPtrC8 password=iPopSettings->Password();
iSocket->PerformLogging(EFalse);
_LIT8(KPop3CmdPass, "PASS %S\r\n");
iSocket->SendQueueReceive(iStatus, KPop3CmdPass(), &password);
iSocket->PerformLogging(ETrue);
break;
}
// new request to initiate TLS
case ERequestingTLS:
_LIT8(KPop3CmdOk, "+OK");
iSocket->SetSSLTLSResponseL(KPop3CmdOk());
_LIT8(KPop3CmdStls, "STLS\r\n");
iSocket->SendQueueReceive(iStatus, KPop3CmdStls());
break;
default: // Unknown state
Panic(EImppBadSessionState);
break;
}
SetActive();
}
//
// return a pointer to our CImTextServerSession so CPop3Operations can use it
//
CImTextServerSession* CImPop3Session::TextServerSession()
{
return iSocket;
}
//
// Return POP3 error (based on what state the POP login is in
//
TInt CImPop3Session::GetPopError()
{
TInt anError=KErrGeneral;
switch (iState)
{
case EWaitingForReply:
case EPopCapabilities:
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
if(iPopSettings->POP3Auth() && iState != EPopCapabilities)
{
anError=KPop3AuthenticationFailed;
}
else
#endif
{
anError=KPop3CannotConnect;
}
break;
case EAuthorisingUser:
anError=KPop3InvalidUser;
break;
case EAuthorisingPass:
anError=KPop3InvalidLogin;
break;
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
case ESaslAuthInProgress:
anError=KPop3InvalidUser;
break;
case ESaslAuthIsDone:
anError=KPop3InvalidLogin;
break;
#endif
case EAuthorisingApop:
anError=KPop3InvalidApopLogin;
break;
case EPopConnected:
anError=KErrDisconnected; // assume that this is Pop time out
break;
case ERequestingTLS:
anError = KErrPop3TLSNegotiateFailed;
default:
break;
}
return anError;
}
//DMC
TInt CImPop3Session::MaxHeaders()
{
return iPopSettings->InboxSynchronisationLimit();
}
//
// Set and test state of iOpPending: is a pop operation currently underway?
//
void CImPop3Session::SetPending()
{
iOpPending=ETrue;
}
void CImPop3Session::SetOpNotPending()
{
iOpPending=EFalse;
}
TBool CImPop3Session::IsPending()
{
return iOpPending;
}
TBool CImPop3Session::IsConnectedToInternet()
{
return iSocketConnected;
}
//
// construct Apop string using timestamp and user password (and MD5 hash function)
//
void CImPop3Session::ConstructApopL()
{
TPtrC8 pass(iPopSettings->Password());
TPtrC8 timeStamp;
TInt timeStampEndPos=KErrNotFound;
const TInt timeStampStartPos=iConnectReply->Des().Locate('<');
if(timeStampStartPos==KErrNotFound)
{
User::Leave(KErrNotFound);
}
timeStampEndPos=iConnectReply->Des().Locate('>');
if(timeStampEndPos!=KErrNotFound && (timeStampEndPos>timeStampStartPos))
{
TInt timeStampLength=(timeStampEndPos-timeStampStartPos)+1;
timeStamp.Set(iConnectReply->Des().Mid(timeStampStartPos,timeStampLength));
}
else
{
User::Leave(KErrNotFound);
}
HBufC8* popBuf = HBufC8::NewLC(timeStamp.Length() + pass.Length());
TPtr8 bufContent=popBuf->Des();
bufContent=timeStamp;
bufContent.Append(pass);
// do MD5
CMD5* doHash = CMD5::NewL();
CleanupStack::PushL(doHash);
HBufC8* popApop = HBufC8::NewL(2*doHash->HashSize());
delete iPopApop;
iPopApop = NULL;
iPopApop = popApop;
TPtr8 hashCont=iPopApop->Des();
hashCont = doHash->Hash(*popBuf);
TBuf<32> hexHash;
// MD5 algorithm ALWAYS returns 16 bytes of data - which will be converted into
// 32 characters; each byte represented by a 2 character hex representation,
// eg 255="ff"
for (TInt j=0;j<16;j++)
{
hexHash.AppendNumFixedWidth(hashCont[j],EHex,2);
}
// copy Hex values to iPopApop so that it can be used with the APOP command
iPopApop->Des().Copy(hexHash);
CleanupStack::PopAndDestroy(2); //popBuf & doHash
}
CImCaf* CImPop3Session::GetCafL(RFs& aFs)
{
if(!iCaf)
{
iCaf = new(ELeave) CImCaf(aFs);
}
return iCaf;
}
//
// Use CAPA command to get server capabilities
//
void CImPop3Session::GetCapabilitiesL()
{
iPopCapabilities = CImPop3Capa::NewL(this);
iPopCapabilities->Start(iStatus);
SetActive();
}
TBool CImPop3Session::PipeliningSupport()
{
return iPopCapabilities->PipeliningSupport();
}
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
/**
Function to select SASL Authentication Mechanism supported by email server.
If CRAM-MD5 Authentication is not supported by server and if Fallback flag is enabled,
will fallback to less secure authentication mechanism.
Fallback priority will be in a following order when server dosn't support CRAM-MD5.
1. APOP
2. PLAIN
3. LOGIN
4. USER/PASS
*/
void CImPop3Session::SelectAuthExtensionProfileL()
{
// get the supported SASL Capabilities
TUint supportedAuthProfiles = iPopCapabilities->SaslAuthExtensionFlag();
// to check whether CRAM-MD5 supported by the server
if(supportedAuthProfiles & CPopAuthMechanismHelper::ECramMD5)
{
//AUTHENTICATE CRAM-MD5 implementation
iPopAuthHelper = CPopAuthCramMd5MechanismHelper::NewL(*iPopSettings);
}
// if server doesn't support CRAM-MD5, check whether FallBack enabled from POP3 email settings.
else if(iPopSettings->FallBack())
{
if(iPopSettings->Apop())
{
//APOP
iState=EAuthorisingApop;
}
else if(supportedAuthProfiles & CPopAuthMechanismHelper::EPlain)
{
//AUTHENTICATE PLAIN implementation
iPopAuthHelper = CPopAuthPlainMechanismHelper::NewL(*iPopSettings);
iState = ESaslAuthIsDone;
}
else if(supportedAuthProfiles & CPopAuthMechanismHelper::ELogin)
{
//AUTHENTICATE LOGIN implementation
iPopAuthHelper = CPopAuthLoginMechanismHelper::NewL(*iPopSettings);
iSaslAuthLogin = ETrue;
}
else
{
//USER/PASS
iState = EAuthorisingUser;
}
}
else
{
// if server doesn't support CRAM-MD5 & FallBack is Off, leave with error.
iState = EWaitingForReply;
User::Leave(GetPopError());
}
}
#endif