/****************************************************************************** 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; } } } }