/******************************************************************************
Copyright 2003-2004 Hamid Qureshi and Unruled Boy
OpenPOP.Net is free software; you can redistribute it and/or modify
it under the terms of the Lesser GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
OpenPOP.Net is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Lesser GNU General Public License for more details.
You should have received a copy of the Lesser GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/*******************************************************************************/
/*
*Name: OpenPOP.POP3.POPClient
*Function: POP Client
*Author: Hamid Qureshi
*Created: 2003/8
*Modified: 2004/6/16 12:47 GMT+8 by Unruled Boy
*Description:
*Changes:
* 2004/6/16 12:47 GMT+8 by Unruled Boy
* 1.Added new high performance WaitForResponse function;
* 2004/5/26 09:25 GMT+8 by Unruled Boy
* 1.Fixed some parameter description errors and tidy up some codes
* 2004/5/21 00:00 by dteviot via Unruled Boy
* 1.Added support for the LIST command
* 2.Heavily refactored replicated code
* 2004/5/4 20:52 GMT+8 by Unruled Boy
* 1.Renamed DeleteMessages to DeleteAllMessages
* 2004/5/3 12:53 GMT+8 by Unruled Boy
* 1.Adding ReceiveContentSleepInterval property
* 2.Adding WaitForResponseInterval property
* 2004/5/1 14:13 GMT+8 by Unruled Boy
* 1.Adding descriptions to every public functions/property/void
* 2.Now with 6 events!
* 2004/4/23 21:07 GMT+8 by Unruled Boy
* 1.Modifies the construction for new Message
* 2.Tidy up the codes to follow Hungarian Notation
* 2004/4/2 21:25 GMT+8 by Unruled Boy
* 1.modifies the WaitForResponse
* 2.added handling for PopServerLockException
*/
using System;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using Org.Mentalis.Security.Ssl;
namespace OpenPOP.POP3
{
///
/// POPClient
///
public class POPClient
{
///
/// Event that fires when begin to connect with target POP3 server.
///
public event EventHandler CommunicationBegan;
///
/// Event that fires when connected with target POP3 server.
///
public event EventHandler CommunicationOccured;
///
/// Event that fires when disconnected with target POP3 server.
///
public event EventHandler CommunicationLost;
///
/// Event that fires when authentication began with target POP3 server.
///
public event EventHandler AuthenticationBegan;
///
/// Event that fires when authentication finished with target POP3 server.
///
public event EventHandler AuthenticationFinished;
///
/// Event that fires when message transfer has begun.
///
public event EventHandler MessageTransferBegan;
///
/// Event that fires when message transfer has finished.
///
public event EventHandler MessageTransferFinished;
internal void OnCommunicationBegan(EventArgs e)
{
if (CommunicationBegan != null)
CommunicationBegan(this, e);
}
internal void OnCommunicationOccured(EventArgs e)
{
if (CommunicationOccured != null)
CommunicationOccured(this, e);
}
internal void OnCommunicationLost(EventArgs e)
{
if (CommunicationLost != null)
CommunicationLost(this, e);
}
internal void OnAuthenticationBegan(EventArgs e)
{
if (AuthenticationBegan != null)
AuthenticationBegan(this, e);
}
internal void OnAuthenticationFinished(EventArgs e)
{
if (AuthenticationFinished != null)
AuthenticationFinished(this, e);
}
internal void OnMessageTransferBegan(EventArgs e)
{
if (MessageTransferBegan != null)
MessageTransferBegan(this, e);
}
internal void OnMessageTransferFinished(EventArgs e)
{
if (MessageTransferFinished != null)
MessageTransferFinished(this, e);
}
private const string RESPONSE_OK="+OK";
//private const string RESPONSE_ERR="-ERR";
private SecureTcpClient clientSocket=null;
private StreamReader reader;
private StreamWriter writer;
private string _Error = "";
private int _receiveTimeOut=60000;
private int _sendTimeOut=60000;
private int _receiveBufferSize=4090;
private int _sendBufferSize=4090;
private string _basePath=null;
private bool _receiveFinish=false;
private bool _autoDecodeMSTNEF=true;
private int _waitForResponseInterval=200;
private int _receiveContentSleepInterval=100;
private string _aPOPTimestamp;
private string _lastCommandResponse;
private bool _connected=true;
public bool Connected
{
get{return _connected;}
}
public string APOPTimestamp
{
get{return _aPOPTimestamp;}
}
///
/// receive content sleep interval
///
public int ReceiveContentSleepInterval
{
get{return _receiveContentSleepInterval;}
set{_receiveContentSleepInterval=value;}
}
///
/// wait for response interval
///
public int WaitForResponseInterval
{
get{return _waitForResponseInterval;}
set{_waitForResponseInterval=value;}
}
///
/// whether auto decoding MS-TNEF attachment files
///
public bool AutoDecodeMSTNEF
{
get{return _autoDecodeMSTNEF;}
set{_autoDecodeMSTNEF=value;}
}
///
/// path to extract MS-TNEF attachment files
///
public string BasePath
{
get{return _basePath;}
set
{
try
{
if(value.EndsWith("\\"))
_basePath=value;
else
_basePath=value+"\\";
}
catch
{
}
}
}
///
/// Receive timeout for the connection to the SMTP server in milliseconds.
/// The default value is 60000 milliseconds.
///
public int ReceiveTimeOut
{
get{return _receiveTimeOut;}
set{_receiveTimeOut=value;}
}
///
/// Send timeout for the connection to the SMTP server in milliseconds.
/// The default value is 60000 milliseconds.
///
public int SendTimeOut
{
get{return _sendTimeOut;}
set{_sendTimeOut=value;}
}
///
/// Receive buffer size
///
public int ReceiveBufferSize
{
get{return _receiveBufferSize;}
set{_receiveBufferSize=value;}
}
///
/// Send buffer size
///
public int SendBufferSize
{
get{return _sendBufferSize;}
set{_sendBufferSize=value;}
}
private void WaitForResponse(bool blnCondiction, int intInterval)
{
if(intInterval==0)
intInterval=WaitForResponseInterval;
while(!blnCondiction==true)
{
Thread.Sleep(intInterval);
}
}
private void WaitForResponse(ref StreamReader rdReader, int intInterval)
{
if(intInterval==0)
intInterval=WaitForResponseInterval;
//while(rdReader.Peek()==-1 || !rdReader.BaseStream.CanRead)
while(!rdReader.BaseStream.CanRead)
{
Thread.Sleep(intInterval);
}
}
private void WaitForResponse(ref StreamReader rdReader)
{
DateTime dtStart=DateTime.Now;
TimeSpan tsSpan;
while(!rdReader.BaseStream.CanRead)
{
tsSpan=DateTime.Now.Subtract(dtStart);
if(tsSpan.Milliseconds>_receiveTimeOut)
break;
Thread.Sleep(_waitForResponseInterval);
}
}
private void WaitForResponse(ref StreamWriter wrWriter, int intInterval)
{
if(intInterval==0)
intInterval=WaitForResponseInterval;
while(!wrWriter.BaseStream.CanWrite)
{
Thread.Sleep(intInterval);
}
}
///
/// Examines string to see if it contains a timestamp to use with the APOP command
/// If it does, sets the ApopTimestamp property to this value
///
/// string to examine
private void ExtractApopTimestamp(string strResponse)
{
Match match = Regex.Match(strResponse, "<.+>");
if (match.Success)
{
_aPOPTimestamp = match.Value;
}
}
///
/// Tests a string to see if it's a "+OK" string
///
/// string to examine
/// true if response is an "+OK" string
private bool IsOkResponse(string strResponse)
{
return (strResponse.Substring(0, 3) == RESPONSE_OK);
}
///
/// get response content
///
/// string to examine
/// response content
private string GetResponseContent()
{
return _lastCommandResponse.Substring(3);
}
///
/// Sends a command to the POP server.
///
/// command to send to server
/// Do not give error
/// true if server responded "+OK"
private bool SendCommand(string strCommand, bool blnSilent)
{
_lastCommandResponse = "";
try
{
if(writer.BaseStream.CanWrite)
{
writer.WriteLine(strCommand);
writer.Flush();
//WaitForResponse(ref reader,WaitForResponseInterval);
WaitForResponse(ref reader);
_lastCommandResponse = reader.ReadLine();
return IsOkResponse(_lastCommandResponse);
}
else
return false;
}
catch(Exception e)
{
if(!blnSilent)
{
_Error = strCommand + ":" +e.Message;
Utility.LogError(_Error);
}
return false;
}
}
///
/// Sends a command to the POP server.
///
/// command to send to server
/// true if server responded "+OK"
private bool SendCommand(string strCommand)
{
return SendCommand(strCommand,false);
}
///
/// Sends a command to the POP server, expects an integer reply in the response
///
/// command to send to server
/// integer value in the reply
private int SendCommandIntResponse(string strCommand)
{
int retVal = 0;
if(SendCommand(strCommand))
{
try
{
retVal = int.Parse(_lastCommandResponse.Split(' ')[1]);
}
catch(Exception e)
{
Utility.LogError(strCommand + ":" + e.Message);
}
}
return retVal;
}
///
/// Construct new POPClient
///
public POPClient()
{
Utility.Log=false;
}
///
/// Construct new POPClient
///
public POPClient(string strHost,int intPort,string strlogin,string strPassword,AuthenticationMethod authenticationMethod)
{
Connect(strHost, intPort, false);
Authenticate(strlogin,strPassword,authenticationMethod);
}
public POPClient(string strHost,int intPort,string strlogin,string strPassword,AuthenticationMethod authenticationMethod, bool secure)
{
Connect(strHost, intPort, secure);
Authenticate(strlogin,strPassword,authenticationMethod);
}
///
/// connect to remote server
///
/// POP3 host
/// POP3 port
public void Connect(string strHost,int intPort,bool secure)
{
OnCommunicationBegan(EventArgs.Empty);
SecureProtocol protocol = secure ? SecureProtocol.Tls1 | SecureProtocol.Ssl3 : SecureProtocol.None;
SecurityOptions options = new SecurityOptions(protocol);
options.Certificate = null;
options.Entity = ConnectionEnd.Client;
options.CommonName = strHost;
options.VerificationType = CredentialVerification.Auto;
options.Flags = SecurityFlags.Default;
options.AllowedAlgorithms = SslAlgorithms.SECURE_CIPHERS;
clientSocket = new SecureTcpClient(options);
// clientSocket=new TcpClient();
clientSocket.ReceiveTimeout=_receiveTimeOut;
clientSocket.SendTimeout=_sendTimeOut;
clientSocket.ReceiveBufferSize=_receiveBufferSize;
clientSocket.SendBufferSize=_sendBufferSize;
try
{
clientSocket.Connect(strHost,intPort);
}
catch(SocketException e)
{
Disconnect();
Utility.LogError("Connect():"+e.Message);
throw new PopServerNotFoundException();
}
reader=new StreamReader(clientSocket.GetStream(),Encoding.Default,true);
writer=new StreamWriter(clientSocket.GetStream());
writer.AutoFlush=true;
WaitForResponse(ref reader,WaitForResponseInterval);
string strResponse=reader.ReadLine();
if(IsOkResponse(strResponse))
{
ExtractApopTimestamp(strResponse);
_connected=true;
OnCommunicationOccured(EventArgs.Empty);
}
else
{
Disconnect();
Utility.LogError("Connect():"+"Error when login, maybe POP3 server not exist");
throw new PopServerNotAvailableException();
}
}
///
/// Disconnect from POP3 server
///
public void Disconnect()
{
try
{
clientSocket.ReceiveTimeout=500;
clientSocket.SendTimeout=500;
SendCommand("QUIT",true);
clientSocket.ReceiveTimeout=_receiveTimeOut;
clientSocket.SendTimeout=_sendTimeOut;
reader.Close();
writer.Close();
clientSocket.GetStream().Close();
clientSocket.Close();
}
catch
{
//Utility.LogError("Disconnect():"+e.Message);
}
finally
{
reader=null;
writer=null;
clientSocket=null;
}
OnCommunicationLost(EventArgs.Empty);
}
///
/// release me
///
~POPClient()
{
Disconnect();
}
///
/// verify user and password
///
/// user name
/// password
public void Authenticate(string strlogin,string strPassword)
{
Authenticate(strlogin,strPassword,AuthenticationMethod.USERPASS);
}
///
/// verify user and password
///
/// user name
/// strPassword
/// verification mode
public void Authenticate(string strlogin,string strPassword,AuthenticationMethod authenticationMethod)
{
if(authenticationMethod==AuthenticationMethod.USERPASS)
{
AuthenticateUsingUSER(strlogin,strPassword);
}
else if(authenticationMethod==AuthenticationMethod.APOP)
{
AuthenticateUsingAPOP(strlogin,strPassword);
}
else if(authenticationMethod==AuthenticationMethod.TRYBOTH)
{
try
{
AuthenticateUsingUSER(strlogin,strPassword);
}
catch(InvalidLoginException e)
{
Utility.LogError("Authenticate():"+e.Message);
throw e;
}
catch(InvalidPasswordException e)
{
Utility.LogError("Authenticate():"+e.Message);
throw e;
}
catch(Exception e)
{
Utility.LogError("Authenticate():"+e.Message);
AuthenticateUsingAPOP(strlogin,strPassword);
}
}
}
///
/// verify user and password
///
/// user name
/// password
private void AuthenticateUsingUSER(string strlogin,string strPassword)
{
OnAuthenticationBegan(EventArgs.Empty);
if(!SendCommand("USER " + strlogin))
{
Utility.LogError("AuthenticateUsingUSER():wrong user");
throw new InvalidLoginException();
}
WaitForResponse(ref writer,WaitForResponseInterval);
if(!SendCommand("PASS " + strPassword))
{
if(_lastCommandResponse.ToLower().IndexOf("lock")!=-1)
{
Utility.LogError("AuthenticateUsingUSER():maildrop is locked");
throw new PopServerLockException();
}
else
{
Utility.LogError("AuthenticateUsingUSER():wrong password or " + GetResponseContent());
throw new InvalidPasswordException();
}
}
OnAuthenticationFinished(EventArgs.Empty);
}
///
/// verify user and password using APOP
///
/// user name
/// password
private void AuthenticateUsingAPOP(string strlogin,string strPassword)
{
OnAuthenticationBegan(EventArgs.Empty);
if(!SendCommand("APOP " + strlogin + " " + MyMD5.GetMD5HashHex(strPassword)))
{
Utility.LogError("AuthenticateUsingAPOP():wrong user or password");
throw new InvalidLoginOrPasswordException();
}
OnAuthenticationFinished(EventArgs.Empty);
}
/* private string GetCommand(string input)
{
try
{
return input.Split(' ')[0];
}
catch(Exception e)
{
Utility.LogError("GetCommand():"+e.Message);
return "";
}
}*/
private string[] GetParameters(string input)
{
string []temp=input.Split(' ');
string []retStringArray=new string[temp.Length-1];
Array.Copy(temp,1,retStringArray,0,temp.Length-1);
return retStringArray;
}
///
/// get message count
///
/// message count
public int GetMessageCount()
{
return SendCommandIntResponse("STAT");
}
///
/// Deletes message with given index when Close() is called
///
///
public bool DeleteMessage(int intMessageIndex)
{
return SendCommand("DELE " + intMessageIndex.ToString());
}
///
/// Deletes messages
///
public bool DeleteAllMessages()
{
int messageCount=GetMessageCount();
for(int messageItem=messageCount;messageItem>0;messageItem--)
{
if (!DeleteMessage(messageItem))
return false;
}
return true;
}
///
/// quit POP3 server
///
public bool QUIT()
{
return SendCommand("QUIT");
}
///
/// keep server active
///
public bool NOOP()
{
return SendCommand("NOOP");
}
///
/// keep server active
///
public bool RSET()
{
return SendCommand("RSET");
}
///
/// identify user
///
public bool USER()
{
return SendCommand("USER");
}
///
/// get messages info
///
/// message number
/// Message object
public MIMEParser.Message GetMessageHeader(int intMessageNumber)
{
OnMessageTransferBegan(EventArgs.Empty);
MIMEParser.Message msg=FetchMessage("TOP "+intMessageNumber.ToString()+" 0", true);
OnMessageTransferFinished(EventArgs.Empty);
return msg;
}
///
/// get message uid
///
/// message number
public string GetMessageUID(int intMessageNumber)
{
string[] strValues=null;
if(SendCommand("UIDL " + intMessageNumber.ToString()))
{
strValues = GetParameters(_lastCommandResponse);
}
return strValues[1];
}
///
/// get message uids
///
public ArrayList GetMessageUIDs()
{
ArrayList uids=new ArrayList();
if(SendCommand("UIDL"))
{
string strResponse=reader.ReadLine();
while (strResponse!=".")
{
uids.Add(strResponse.Split(' ')[1]);
strResponse=reader.ReadLine();
}
return uids;
}
else
{
return null;
}
}
///
/// Get the sizes of all the messages
/// CAUTION: Assumes no messages have been deleted
///
/// Size of each message
public ArrayList LIST()
{
ArrayList sizes=new ArrayList();
if(SendCommand("LIST"))
{
string strResponse=reader.ReadLine();
while (strResponse!=".")
{
sizes.Add(int.Parse(strResponse.Split(' ')[1]));
strResponse=reader.ReadLine();
}
return sizes;
}
else
{
return null;
}
}
///
/// get the size of a message
///
/// message number
/// Size of message
public int LIST(int intMessageNumber)
{
return SendCommandIntResponse("LIST " + intMessageNumber.ToString());
}
///
/// read stream content
///
/// length of content to read
/// content
private string ReceiveContent(int intContentLength)
{
string strResponse=null;
StringBuilder builder = new StringBuilder();
WaitForResponse(ref reader,WaitForResponseInterval);
strResponse = reader.ReadLine();
int intLines=0;
int intLen=0;
while (strResponse!=".")// || (intLen
/// get message info
///
/// message number on server
/// Message object
public MIMEParser.Message GetMessage(int intNumber, bool blnOnlyHeader)
{
OnMessageTransferBegan(EventArgs.Empty);
MIMEParser.Message msg=FetchMessage("RETR " + intNumber.ToString(), blnOnlyHeader);
OnMessageTransferFinished(EventArgs.Empty);
return msg;
}
///
/// fetches a message or a message header
///
/// Command to send to Pop server
/// Only return message header?
/// Message object
public MIMEParser.Message FetchMessage(string strCommand, bool blnOnlyHeader)
{
_receiveFinish=false;
if(!SendCommand(strCommand))
return null;
try
{
string receivedContent=ReceiveContent(-1);
MIMEParser.Message msg=new MIMEParser.Message(ref _receiveFinish,_basePath,_autoDecodeMSTNEF,receivedContent,blnOnlyHeader);
WaitForResponse(_receiveFinish,WaitForResponseInterval);
return msg;
}
catch(Exception e)
{
Utility.LogError("FetchMessage():"+e.Message);
return null;
}
}
}
}