email/pop3andsmtpmtm/popservermtm/src/POPS.CPP
changeset 0 72b543305e3a
child 21 c6838af47512
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/pop3andsmtpmtm/popservermtm/src/POPS.CPP	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,679 @@
+// 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
+