Click here to Skip to main content
15,885,309 members
Articles / Programming Languages / C#

SIP Stack with SIP Proxy - (VOIP)

Rate me:
Please Sign up or sign in to vote.
4.86/5 (45 votes)
11 Jun 2007CPOL2 min read 1.6M   28.1K   162  
C# implementation of SIP
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

using LumiSoft.Net;
using LumiSoft.Net.Dns.Client;
using LumiSoft.Net.Mime;

namespace LumiSoft.Net.SMTP.Client
{
	/// <summary>
	/// Is called when asynchronous command had completed.
	/// </summary>
	public delegate void CommadCompleted(SocketCallBackResult result,Exception exception);

	/// <summary>
	/// SMTP client.
	/// </summary>
	public class SmtpClientEx : IDisposable
    {
        #region private class Auth_state_data

        /// <summary>
        /// Provides state date for BeginAuthenticate method.
        /// </summary>
        private class Auth_state_data
        {
            private string          m_UserName  = "";
            private string          m_Password  = "";
            private CommadCompleted m_pCallback = null;
            private object          m_pTag      = null;

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="userName">User name.</param>
            /// <param name="password">Password.</param>
            /// <param name="callback">Callback what must be called when aynchrounous execution completes.</param>
            public Auth_state_data(string userName,string password,CommadCompleted callback)
            {
                m_UserName  = userName;
                m_Password  = password;
                m_pCallback = callback;
            }


            #region Properties Implementation

            /// <summary>
            /// Gets user name.
            /// </summary>
            public string UserName
            {
                get{ return m_UserName; }
            }

            /// <summary>
            /// Gets user password.
            /// </summary>
            public string Password
            {
                get{ return m_Password; }
            }

            /// <summary>
            /// Gets callback what must be called when aynchrounous execution completes.
            /// </summary>
            public CommadCompleted Callback
            {
                get{ return m_pCallback; }
            }

            /// <summary>
            /// Gets or sets user data.
            /// </summary>
            public object Tag
            {
                get{ return m_pTag; }

                set{ m_pTag = value; }
            }

            #endregion

        }

        #endregion

        private SocketEx     m_pSocket          = null;
		private SocketLogger m_pLogger          = null;
		private bool         m_Connected        = false;
		private bool         m_Authenticated    = false;
		private bool         m_Supports_Size    = false;
		private bool         m_Supports_Bdat    = false;
		private bool         m_Supports_Login   = false;
		private bool         m_Supports_CramMd5 = false;
		private string[]     m_pDnsServers      = null;

		/// <summary>
		/// Occurs when SMTP session has finished and session log is available.
		/// </summary>
		public event LogEventHandler SessionLog = null;

		/// <summary>
		/// Default constructor.
		/// </summary>
		public SmtpClientEx()
		{			
		}

		#region method Dispose

		/// <summary>
		/// Cleasns up resources and disconnect smtp client if open.
		/// </summary>
		public void Dispose()
		{
			try{
				Disconnect();				
			}
			catch{
			}	
		}

		#endregion


		#region Events handling

		#endregion


		#region method Connect

		/// <summary>
		/// Connects to sepcified host.
		/// </summary>
		/// <param name="host">Host name or IP address.</param>
		/// <param name="port">Port where to connect.</param>
		public void Connect(string host,int port)
		{
			Connect(null,host,port,false);
		}

        /// <summary>
		/// Connects to sepcified host.
		/// </summary>
		/// <param name="host">Host name or IP address.</param>
		/// <param name="port">Port where to connect. Default SMTP port is 25 and SSL port is 465.</param>
        /// <param name="ssl">Specifies if to connected via SSL.</param>
		public void Connect(string host,int port,bool ssl)
		{
			Connect(null,host,port,ssl);
		}

        /// <summary>
		/// Connects to sepcified host.
		/// </summary>
		/// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param>
		/// <param name="host">Host name or IP address.</param>
		/// <param name="port">Port where to connect.</param>
		public void Connect(IPEndPoint localEndpoint,string host,int port)
		{
            Connect(localEndpoint,host,port,false);
        }

		/// <summary>
		/// Connects to sepcified host.
		/// </summary>
		/// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param>
		/// <param name="host">Host name or IP address.</param>
		/// <param name="port">Port where to connect.</param>
        /// <param name="ssl">Specifies if to connected via SSL. Default SMTP port is 25 and SSL port is 465.</param>
		public void Connect(IPEndPoint localEndpoint,string host,int port,bool ssl)
		{				
			m_pSocket = new SocketEx();
			if(localEndpoint != null){
				m_pSocket.Bind(localEndpoint);
			}
            
            // Create logger
            if(this.SessionLog != null){
				m_pLogger = new SocketLogger(m_pSocket.RawSocket,SessionLog);
				m_pLogger.SessionID = Guid.NewGuid().ToString();
				m_pSocket.Logger = m_pLogger;
			}
						
			if(host.IndexOf("@") == -1){
				m_pSocket.Connect(host,port,ssl);	
			}
			else{
				//---- Parse e-domain -------------------------------//
				string domain = host;

				// eg. Ivx <ivx@lumisoft.ee>
				if(domain.IndexOf("<") > -1 && domain.IndexOf(">") > -1){
					domain = domain.Substring(domain.IndexOf("<")+1,domain.IndexOf(">") - domain.IndexOf("<")-1);
				}

				if(domain.IndexOf("@") > -1){
					domain = domain.Substring(domain.LastIndexOf("@") + 1);
				}

				if(domain.Trim().Length == 0){
					if(m_pLogger != null){
						m_pLogger.AddTextEntry("Destination address '" + host + "' is invalid, aborting !");
					}
					throw new Exception("Destination address '" + host + "' is invalid, aborting !");
				}

				//--- Get MX record -------------------------------------------//
				Dns_Client dns = new Dns_Client();
				Dns_Client.DnsServers = m_pDnsServers;
				DnsServerResponse dnsResponse = dns.Query(domain,QTYPE.MX);

                bool connected = false;
				switch(dnsResponse.ResponseCode)
				{
					case RCODE.NO_ERROR:
						DNS_rr_MX[] mxRecords = dnsResponse.GetMXRecords();

						// Try all available hosts by MX preference order, if can't connect specified host.
						foreach(DNS_rr_MX mx in mxRecords){
							try{
								if(m_pLogger != null){
									m_pLogger.AddTextEntry("Connecting with mx record to: " + mx.Host);
								}
								m_pSocket.Connect(mx.Host,port,ssl);                                
                                connected = true;
								break;
							}
							catch(Exception x){ // Just skip and let for to try next host.									
								if(m_pLogger != null){
									m_pLogger.AddTextEntry("Failed connect to: " + mx.Host + " error:" + x.Message);
								}
							}
						}

						// None of MX didn't connect
						if(mxRecords.Length > 0 && !connected){
							throw new Exception("Destination email server is down");
						}

						/* Rfc 2821 5
						 If no MX records are found, but an A RR is found, the A RR is treated as
						 if it was associated with an implicit MX RR, with a preference of 0,
						 pointing to that host.
						*/
						if(!connected){
							// Try to connect with A record
							IPAddress[] ipEntry = null;
							try{
								if(m_pLogger != null){
									m_pLogger.AddTextEntry("No mx record, trying to get A record for: " + domain);
								}
								ipEntry = Dns_Client.Resolve(domain);								
							}
							catch{
								if(m_pLogger != null){
									m_pLogger.AddTextEntry("Invalid domain,no MX or A record: " + domain);
								}
								throw new Exception("Invalid domain,no MX or A record: " + domain);
							}

							try{
								if(m_pLogger != null){
									m_pLogger.AddTextEntry("Connecting with A record to:" + domain);
								}
								m_pSocket.Connect(domain,port,ssl);
							}
							catch{
								if(m_pLogger != null){
									m_pLogger.AddTextEntry("Failed connect to:" + domain);
								}
								throw new Exception("Destination email server is down");
							}
						}
						break;

					case RCODE.NAME_ERROR:
						if(m_pLogger != null){
							m_pLogger.AddTextEntry("Invalid domain,no MX or A record: " + domain);
						}
						throw new Exception("Invalid domain,no MX or A record: " + domain);

					case RCODE.SERVER_FAILURE:
						if(m_pLogger != null){
							m_pLogger.AddTextEntry("Dns server unvailable.");
						}
						throw new Exception("Dns server unvailable.");
					}					
			}
					
			/*
			 * Notes: Greeting may be single or multiline response.
			 *		
			 * Examples:
			 *		220<SP>SMTP server ready<CRLF> 
			 * 
			 *		220-SMTP server ready<CRLF>
			 *		220-Addtitional text<CRLF>
			 *		220<SP>final row<CRLF>
			 * 
			*/
     
			// Read server response
			string responseLine = m_pSocket.ReadLine(1000);
			while(!responseLine.StartsWith("220 ")){
				// If lisne won't start with 220, then its error response
				if(!responseLine.StartsWith("220")){
					throw new Exception(responseLine);
				}

				responseLine = m_pSocket.ReadLine(1000);
			}

			m_Connected = true;
		}

		#endregion

		#region method BeginConnect

		/// <summary>
		/// Starts connection to specified host.
		/// </summary>
		/// <param name="host">Host name or IP address.</param>
		/// <param name="port">Port where to connect.</param>
		/// <param name="callback">Callback to be called if connect ends.</param>
		public void BeginConnect(string host,int port,CommadCompleted callback)
		{
			BeginConnect(null,host,port,false,callback);
		}

        /// <summary>
		/// Starts connection to specified host.
		/// </summary>
		/// <param name="host">Host name or IP address.</param>
		/// <param name="port">Port where to connect.</param>
        /// <param name="ssl">Specifies if to connected via SSL.</param>
		/// <param name="callback">Callback to be called if connect ends.</param>
		public void BeginConnect(string host,int port,bool ssl,CommadCompleted callback)
		{
			BeginConnect(null,host,port,ssl,callback);
		}

        /// <summary>
		/// Starts connection to specified host.
		/// </summary>
		/// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param>
		/// <param name="host">Host name or IP address.</param>
		/// <param name="port">Port where to connect.</param>
		/// <param name="callback">Callback to be called if connect ends.</param>
		public void BeginConnect(IPEndPoint localEndpoint,string host,int port,CommadCompleted callback)
		{  
            BeginConnect(localEndpoint,host,port,false,callback);
        }

		/// <summary>
		/// Starts connection to specified host.
		/// </summary>
		/// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param>
		/// <param name="host">Host name or IP address.</param>
		/// <param name="port">Port where to connect.</param>
        /// <param name="ssl">Specifies if to connected via SSL.</param>
		/// <param name="callback">Callback to be called if connect ends.</param>
		public void BeginConnect(IPEndPoint localEndpoint,string host,int port,bool ssl,CommadCompleted callback)
		{            
			ThreadPool.QueueUserWorkItem(new WaitCallback(this.BeginConnect_workerThread),new object[]{localEndpoint,host,port,ssl,callback});
		}

		#endregion

		#region method BeginConnect_workerThread

		/// <summary>
		/// Is called from ThreadPool Thread. This method just call synchrounous Connect.
		/// </summary>
		/// <param name="tag"></param>
		private void BeginConnect_workerThread(object tag)
		{
			CommadCompleted callback = (CommadCompleted)((object[])tag)[4];

			try{
				IPEndPoint localEndpoint = (IPEndPoint)((object[])tag)[0];
				string     host          = (string)((object[])tag)[1];
				int        port          = (int)((object[])tag)[2];                
                bool       ssl           = (bool)((object[])tag)[3];
			
				Connect(localEndpoint,host,port,ssl);

				// Connect completed susscessfully, call callback method.
				callback(SocketCallBackResult.Ok,null);
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


/*		/// <summary>
		/// Starts disconnecting SMTP client.
		/// </summary>
		public void BeginDisconnect()
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}
		}*/

		#region method Disconnect

		/// <summary>
		/// Disconnects smtp client from server.
		/// </summary>
		public void Disconnect()
		{
            try{		
				if(m_pSocket != null && m_pSocket.Connected){
					m_pSocket.WriteLine("QUIT");

					m_pSocket.Shutdown(SocketShutdown.Both);
				}
			}
			catch{	
			}

			m_pSocket = null;
			m_Connected = false;
			m_Supports_Size = false;
			m_Supports_Bdat = false;
			m_Supports_Login = false;
			m_Supports_CramMd5 = false;

			if(m_pLogger != null){
				m_pLogger.Flush();
				m_pLogger = null;
			}
		}

		#endregion


        #region method StartTLS

        /// <summary>
        /// Switches SMTP connection to SSL.
        /// </summary>
        public void StartTLS()
        {
            /* RFC 2487 STARTTLS 5. STARTTLS Command.
                STARTTLS with no parameters.
              
                After the client gives the STARTTLS command, the server responds with
                one of the following reply codes:

                220 Ready to start TLS
                501 Syntax error (no parameters allowed)
                454 TLS not available due to temporary reason
            */

            if(!m_Connected){
				throw new Exception("You must connect first !");
			}
			if(m_Authenticated){
				throw new Exception("The STLS command is only valid in non-authenticated state !");
			}
            if(m_pSocket.SSL){
                throw new Exception("Connection is already secure !");
            }

            m_pSocket.WriteLine("STARTTLS");

            string reply = m_pSocket.ReadLine();
			if(!reply.ToUpper().StartsWith("220")){
				throw new Exception("Server returned:" + reply);
			}

            m_pSocket.SwitchToSSL_AsClient();
        }

        #endregion

        #region method BeginStartTLS

        /// <summary>
        /// Start TLS(SSL) negotiation asynchronously.
        /// </summary>
        /// <param name="callback">The method to be called when the asynchronous StartTLS operation is completed.</param>
        public void BeginStartTLS(CommadCompleted callback)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(this.BeginStartTLS_workerThread),callback);
        }

        #region method BeginConnect_workerThread

		/// <summary>
		/// Is called from ThreadPool Thread. This method just call synchrounous StartTLS.
		/// </summary>
		/// <param name="tag">User data.</param>
		private void BeginStartTLS_workerThread(object tag)
		{
			CommadCompleted callback = (CommadCompleted)tag;

			try{			
				StartTLS();

				// Connect completed susscessfully, call callback method.
				callback(SocketCallBackResult.Ok,null);
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

        #endregion


        #region method Ehlo

        /// <summary>
		/// Does EHLO command. If server don't support EHLO, tries HELO.
		/// </summary>
		/// <param name="hostName">Host name which is reported to SMTP server.</param>
		public void Ehlo(string hostName)
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}

			/* Rfc 2821 4.1.1.1 EHLO
			 * Syntax: "EHLO" SP Domain CRLF
			*/

			if(hostName.Length == 0){
				hostName = System.Net.Dns.GetHostName();
			}

			// Send EHLO command to server
			m_pSocket.WriteLine("EHLO " + hostName);
			
			string responseLine = m_pSocket.ReadLine();
			// Response line must start with 250 or otherwise it's error response,
			// try HELO
			if(!responseLine.StartsWith("250")){
				// Send HELO command to server
				m_pSocket.WriteLine("HELO " + hostName);

				responseLine = m_pSocket.ReadLine();
				// HELO failed, return error
				if(!responseLine.StartsWith("250")){
					throw new Exception(responseLine);
				}
			}

			/* RFC 2821 4.1.1.1 EHLO
				*	Examples:
				*		250-domain<SP>free_text<CRLF>
				*       250-EHLO_keyword<CRLF>
				*		250<SP>EHLO_keyword<CRLF>
				* 
				*		250<SP> specifies that last EHLO response line.
			*/

			while(!responseLine.StartsWith("250 ")){
				//---- Store supported ESMTP features --------------------//
				if(responseLine.ToLower().IndexOf("size") > -1){
					m_Supports_Size = true;
				}
				else if(responseLine.ToLower().IndexOf("chunking") > -1){
					m_Supports_Bdat = true;
				}
				else if(responseLine.ToLower().IndexOf("cram-md5") > -1){
					m_Supports_CramMd5 = true;
				}
				else if(responseLine.ToLower().IndexOf("login") > -1){
					m_Supports_Login = true;
				}
				//--------------------------------------------------------//

				// Read next EHLO response line
				responseLine = m_pSocket.ReadLine();
			}			
		}

		#endregion

		#region method BeginEhlo

		/// <summary>
		/// Begins EHLO command.
		/// </summary>
		/// <param name="hostName">Host name which is reported to SMTP server.</param>
		/// <param name="callback">Callback to be called if command ends.</param>
		public void BeginEhlo(string hostName,CommadCompleted callback)
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}

			/* Rfc 2821 4.1.1.1 EHLO
			 * Syntax: "EHLO" SP Domain CRLF
			*/

			if(hostName.Length == 0){
				hostName = System.Net.Dns.GetHostName();
			}

			// Start sending EHLO command to server
			m_pSocket.BeginWriteLine("EHLO " + hostName,new object[]{hostName,callback},new SocketCallBack(this.OnEhloSendFinished));			
		}

		#endregion

		#region method OnEhloSendFinished

		/// <summary>
		/// Is called when smtp client has finished EHLO command sending.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnEhloSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[1]);

			try{
				if(result == SocketCallBackResult.Ok){
					// Begin reading server EHLO command response
					MemoryStream ms = new MemoryStream();
					m_pSocket.BeginReadLine(ms,1000,new object[]{((object[])tag)[0],callback,ms},new SocketCallBack(this.OnEhloReadServerResponseFinished));
				}
				else{ 
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnEhloReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading EHLO command server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnEhloReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[1]);

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)(((object[])tag)[2])).ToArray());

					/* RFC 2821 4.1.1.1 EHLO
					*	Examples:
					*		250-domain<SP>free_text<CRLF>
					*       250-EHLO_keyword<CRLF>
					*		250<SP>EHLO_keyword<CRLF>
					* 
					* 250<SP> specifies that last EHLO response line.
					*/

					// Response line must start with 250 or otherwise it's error response
					if(!responseLine.StartsWith("250")){
						// Server isn't required to support EHLO, try HELO
						string hostName = (string)(((object[])tag)[0]);
						m_pSocket.BeginWriteLine("HELO " + hostName,callback,new SocketCallBack(this.OnHeloSendFinished));					
					}
					else{
						//---- Store supported ESMTP features --------------------//
						if(responseLine.ToLower().IndexOf("size") > -1){
							m_Supports_Size = true;
						}
						else if(responseLine.ToLower().IndexOf("chunking") > -1){
							m_Supports_Bdat = true;
						}
						else if(responseLine.ToLower().IndexOf("cram-md5") > -1){
							m_Supports_CramMd5 = true;
						}
						else if(responseLine.ToLower().IndexOf("login") > -1){
							m_Supports_Login = true;
						}
						//--------------------------------------------------------//

						// This isn't last EHLO response line
						if(!responseLine.StartsWith("250 ")){
							MemoryStream ms = new MemoryStream();
							m_pSocket.BeginReadLine(ms,1000,new object[]{(((object[])tag)[0]),callback,ms},new SocketCallBack(this.OnEhloReadServerResponseFinished));
						}
						else{
							// EHLO completed susscessfully, call callback method.
							callback(SocketCallBackResult.Ok,null);
						}
					}
				}
				else{ 
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


		#region method OnHeloSendFinished

		/// <summary>
		/// Is called when smtp client has finished HELO command sending.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnHeloSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					// Begin reading server HELO command response
					MemoryStream ms = new MemoryStream();
					m_pSocket.BeginReadLine(ms,1000,new object[]{callback,ms},new SocketCallBack(this.OnHeloReadServerResponseFinished));
				}
				else{ 
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnHeloReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading EHLO command server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnHeloReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[0]);

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)(((object[])tag)[1])).ToArray());

					/* RFC 2821 4.1.1.1 HELO
					*	Examples:
					*		250<SP>domain<SP>free_text<CRLF>
					* 
					*/

					// Response line must start with 250 or otherwise it's error response
					if(!responseLine.StartsWith("250")){
						throw new Exception(responseLine);
					}
					else{
						// EHLO completed susscessfully, call callback method.
						callback(SocketCallBackResult.Ok,null);
					}		
				}
				else{ 
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


		#region method Authenticate

		/// <summary>
		/// Does AUTH command.
		/// </summary>
		/// <param name="userName">Uesr name.</param>
		/// <param name="password">Password.</param>
		public void Authenticate(string userName,string password)
		{
			if(!m_Connected){
				throw new Exception("You must connect first !");
			}
			if(!(m_Supports_CramMd5 || m_Supports_Login)){
				throw new Exception("Authentication isn't supported.");
			}

			/* LOGIN
			 * Example:
			 *	C: AUTH<SP>LOGIN<CRLF>
			 *	S: 334<SP>base64(USERNAME)<CRLF>   // USERNAME is string constant
			 *	C: base64(username)<CRLF>
			 *  S: 334<SP>base64(PASSWORD)<CRLF>   // PASSWORD is string constant
			 *  C: base64(password)<CRLF>
			 *	S: 235 Ok<CRLF>
			*/ 

			/* Cram-M5
			   Example:
					C: AUTH<SP>CRAM-MD5<CRLF>
					S: 334<SP>base64(md5_calculation_hash)<CRLF>
					C: base64(username<SP>password_hash)<CRLF>
					S: 235 Ok<CRLF>
			*/

			if(m_Supports_CramMd5){
				m_pSocket.WriteLine("AUTH CRAM-MD5");

				string responseLine = m_pSocket.ReadLine();
				// Response line must start with 334 or otherwise it's error response
				if(!responseLine.StartsWith("334")){
					throw new Exception(responseLine);
				}

				string md5HashKey = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(responseLine.Split(' ')[1]));
								
				HMACMD5 kMd5 = new HMACMD5(System.Text.Encoding.ASCII.GetBytes(password));
				byte[] md5HashByte = kMd5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(md5HashKey));
				string hashedPwd = BitConverter.ToString(md5HashByte).ToLower().Replace("-","");
				
				m_pSocket.WriteLine(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(userName + " " + hashedPwd)));

				responseLine = m_pSocket.ReadLine();
				// Response line must start with 235 or otherwise it's error response
				if(!responseLine.StartsWith("235")){
					throw new Exception(responseLine);
				}

                m_Authenticated = true;
			}
			else if(m_Supports_Login){
				m_pSocket.WriteLine("AUTH LOGIN");

				string responseLine = m_pSocket.ReadLine();
				// Response line must start with 334 or otherwise it's error response
				if(!responseLine.StartsWith("334")){
					throw new Exception(responseLine);
				}

				// Send user name to server
				m_pSocket.WriteLine(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(userName)));

				responseLine = m_pSocket.ReadLine();
				// Response line must start with 334 or otherwise it's error response
				if(!responseLine.StartsWith("334")){
					throw new Exception(responseLine);
				}

				// Send password to server
				m_pSocket.WriteLine(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(password)));

				responseLine = m_pSocket.ReadLine();
				// Response line must start with 235 or otherwise it's error response
				if(!responseLine.StartsWith("235")){
					throw new Exception(responseLine);
				}

                m_Authenticated = true;
			}

            if(m_Authenticated && m_pSocket.Logger != null){
                m_pSocket.Logger.UserName = userName;
            }
		}

		#endregion

		#region method BeginAuthenticate

		/// <summary>
		/// Begins authenticate.
		/// </summary>
		/// <param name="userName">Uesr name.</param>
		/// <param name="password">Password.</param> 
		/// <param name="callback">Callback to be called if command ends.</param>
		public void BeginAuthenticate(string userName,string password,CommadCompleted callback)
		{
			if(!m_Connected){
				throw new Exception("You must connect first !");
			}
			if(!(m_Supports_CramMd5 || m_Supports_Login)){
				throw new Exception("Authentication isn't supported.");
			}

			/* LOGIN
			 * Example:
			 *	C: AUTH<SP>LOGIN<CRLF>
			 *	S: 334<SP>base64(USERNAME)<CRLF>   // USERNAME is string constant
			 *	C: base64(username)<CRLF>
			 *  S: 334<SP>base64(PASSWORD)<CRLF>   // PASSWORD is string constant
			 *  C: base64(password)<CRLF>
			 *	S: 235 Ok<CRLF>
			*/ 

			/* Cram-M5
			   Example:
					C: AUTH<SP>CRAM-MD5<CRLF>
					S: 334<SP>base64(md5_calculation_hash)<CRLF>
					C: base64(username<SP>password_hash)<CRLF>
					S: 235 Ok<CRLF>
			*/

			if(m_Supports_CramMd5){
				m_pSocket.BeginWriteLine("AUTH CRAM-MD5",new Auth_state_data(userName,password,callback),new SocketCallBack(this.OnAuthCramMd5SendFinished));
			}
			else if(m_Supports_Login){
				m_pSocket.BeginWriteLine("AUTH LOGIN",new Auth_state_data(userName,password,callback),new SocketCallBack(this.OnAuthLoginSendFinished));
			}
		}

		#endregion

		#region method OnAuthCramMd5SendFinished

		/// <summary>
		/// Is called when smtp client has finished AUTH CRAM-MD5 command sending.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnAuthCramMd5SendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					MemoryStream ms = new MemoryStream();
                    stateData.Tag = ms;
					m_pSocket.BeginReadLine(ms,1000,stateData,new SocketCallBack(this.OnAuthCramMd5ReadServerResponseFinished));
				}
				else{					
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnAuthCramMd5ReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading AUTH CRAM-MD% server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param> 
		private void OnAuthCramMd5ReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{            
			Auth_state_data stateData = (Auth_state_data)tag;
            
			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)stateData.Tag).ToArray());

					// Response line must start with 334 or otherwise it's error response
					if(!responseLine.StartsWith("334")){
						throw new Exception(responseLine);
					}
					else{
						string md5HashKey = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(responseLine.Split(' ')[1]));
											
						HMACMD5 kMd5 = new HMACMD5(System.Text.Encoding.ASCII.GetBytes(stateData.Password));
						byte[] md5HashByte = kMd5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(md5HashKey));
						string hashedPwd = BitConverter.ToString(md5HashByte).ToLower().Replace("-","");

						// Start sending user name to server
						m_pSocket.BeginWriteLine(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(stateData.UserName + " " + hashedPwd)),stateData,new SocketCallBack(this.OnAuthCramMd5UserPwdSendFinished));
					}
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnAuthCramMd5UserPwdSendFinished

		/// <summary>
		/// Is called when smtp client has finished sending username and password to smtp server.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnAuthCramMd5UserPwdSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					MemoryStream ms = new MemoryStream();
                    stateData.Tag = ms;
					m_pSocket.BeginReadLine(ms,1000,stateData,new SocketCallBack(this.OnAuthCramMd5UserPwdReadServerResponseFinished));
				}
				else{					
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnAuthCramMd5UserPwdReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading user name and password send server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param> 
		private void OnAuthCramMd5UserPwdReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)stateData.Tag).ToArray());

					// Response line must start with 235 or otherwise it's error response
					if(!responseLine.StartsWith("235")){
						throw new Exception(responseLine);
					}
					else{
                        m_Authenticated = true;

                        if(m_Authenticated && m_pSocket.Logger != null){
                            m_pSocket.Logger.UserName = stateData.UserName;
                        }

						// AUTH CRAM-MD5 completed susscessfully, call callback method.
						stateData.Callback(SocketCallBackResult.Ok,null);
					}
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


		#region method OnAuthLoginSendFinished

		/// <summary>
		/// Is called when smtp client has finished AUTH LOGIN command sending.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnAuthLoginSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
            Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					MemoryStream ms = new MemoryStream();
                    stateData.Tag = ms;
					m_pSocket.BeginReadLine(ms,1000,stateData,new SocketCallBack(this.OnAuthLoginReadServerResponseFinished));
				}
				else{					
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnAuthLoginReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading MAIL FROM: command server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param> 
		private void OnAuthLoginReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)(stateData.Tag)).ToArray());

					// Response line must start with 334 or otherwise it's error response
					if(!responseLine.StartsWith("334")){
						throw new Exception(responseLine);
					}
					else{
						// Start sending user name to server
						m_pSocket.BeginWriteLine(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(stateData.UserName)),stateData,new SocketCallBack(this.OnAuthLoginUserSendFinished));
					}
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnAuthLoginUserSendFinished

		/// <summary>
		/// Is called when smtp client has finished sending user name to SMTP server.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnAuthLoginUserSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					MemoryStream ms = new MemoryStream();
                    stateData.Tag = ms;
					m_pSocket.BeginReadLine(ms,1000,stateData,new SocketCallBack(this.OnAuthLoginUserReadServerResponseFinished));
				}
				else{					
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnAuthLoginUserReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading AUTH LOGIN user send server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param> 
		private void OnAuthLoginUserReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)stateData.Tag).ToArray());

					// Response line must start with 334 or otherwise it's error response
					if(!responseLine.StartsWith("334")){
						throw new Exception(responseLine);
					}
					else{
						// Start sending password to server
						m_pSocket.BeginWriteLine(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(stateData.Password)),stateData,new SocketCallBack(this.OnAuthLoginPasswordSendFinished));
					}
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnAuthLoginPasswordSendFinished

		/// <summary>
		/// Is called when smtp client has finished sending password to SMTP server.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnAuthLoginPasswordSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					MemoryStream ms = new MemoryStream();
                    stateData.Tag = ms;
					m_pSocket.BeginReadLine(ms,1000,stateData,new SocketCallBack(this.OnAuthLoginPwdReadServerResponseFinished));
				}
				else{					
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnAuthLoginPwdReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading AUTH LOGIN password send server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param> 
		private void OnAuthLoginPwdReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			Auth_state_data stateData = (Auth_state_data)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)stateData.Tag).ToArray());

					// Response line must start with 235 or otherwise it's error response
					if(!responseLine.StartsWith("235")){
						throw new Exception(responseLine);
					}
					else{
                        m_Authenticated = true;

                        if(m_Authenticated && m_pSocket.Logger != null){
                            m_pSocket.Logger.UserName = stateData.UserName;
                        }

						// AUTH LOGIN completed susscessfully, call callback method.
						stateData.Callback(SocketCallBackResult.Ok,null);
					}
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				stateData.Callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


		#region method SetSender

		/// <summary>
		/// Does MAIL FROM: command.
		/// </summary>
		/// <param name="senderEmail">Sender email address what is reported to smtp server</param>
		/// <param name="messageSize">Message size in bytes or -1 if message size isn't known.</param>
		public void SetSender(string senderEmail,long messageSize)
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}

			/* RFC 2821 4.1.1.2 MAIL
			 *  Examples:
			 *		MAIL FROM:<ivx@lumisoft.ee>
			 * 
			 * RFC 1870 adds optional SIZE keyword support.
			 * SIZE keyword may only be used if it's reported in EHLO command response.
			 *	Examples:
			 *		MAIL FROM:<ivx@lumisoft.ee> SIZE=1000
			*/

			if(m_Supports_Size && messageSize > -1){
				m_pSocket.WriteLine("MAIL FROM:<" + senderEmail + "> SIZE=" + messageSize.ToString());
			}
			else{
				m_pSocket.WriteLine("MAIL FROM:<" + senderEmail + ">");
			}

			string responseLine = m_pSocket.ReadLine();
			// Response line must start with 250 or otherwise it's error response
			if(!responseLine.StartsWith("250")){
				throw new Exception(responseLine);
			}
		}

		#endregion

		#region method BeginSetSender
		
		/// <summary>
		/// Begin setting sender.
		/// </summary>
		/// <param name="senderEmail">Sender email address what is reported to smtp server.</param>
		/// <param name="messageSize">Message size in bytes or -1 if message size isn't known.</param>
		/// <param name="callback">Callback to be called if command ends.</param>
		public void BeginSetSender(string senderEmail,long messageSize,CommadCompleted callback)
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}

			/* RFC 2821 4.1.1.2 MAIL
			 *  Examples:
			 *		MAIL FROM:<ivx@lumisoft.ee>
			 * 
			 * RFC 1870 adds optional SIZE keyword support.
			 * SIZE keyword may only be used if it's reported in EHLO command response.
			 *	Examples:
			 *		MAIL FROM:<ivx@lumisoft.ee> SIZE=1000
			*/

			if(m_Supports_Size && messageSize > -1){
				m_pSocket.BeginWriteLine("MAIL FROM:<" + senderEmail + "> SIZE=" + messageSize.ToString(),callback,new SocketCallBack(this.OnMailSendFinished));
			}
			else{
				m_pSocket.BeginWriteLine("MAIL FROM:<" + senderEmail + ">",callback,new SocketCallBack(this.OnMailSendFinished));
			}
		}

		#endregion
		
		#region method OnMailSendFinished

		/// <summary>
		/// Is called when smtp client has finished MAIL FROM: command sending.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnMailSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					MemoryStream ms = new MemoryStream();
					m_pSocket.BeginReadLine(ms,1000,new object[]{callback,ms},new SocketCallBack(this.OnMailReadServerResponseFinished));
				}
				else{					
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnMailReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading MAIL FROM: command server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param> 
		private void OnMailReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[0]);

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)(((object[])tag)[1])).ToArray());

					// Response line must start with 250 or otherwise it's error response
					if(!responseLine.StartsWith("250")){
						throw new Exception(responseLine);
					}
					else{
						// MAIL FROM: completed susscessfully, call callback method.
						callback(SocketCallBackResult.Ok,null);
					}
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


		#region method AddRecipient

		/// <summary>
		/// Does RCPT TO: command.
		/// </summary>
		/// <param name="recipientEmail">Recipient email address.</param>
		public void AddRecipient(string recipientEmail)
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}

			/* RFC 2821 4.1.1.2 RCPT
			 *  Examples:
			 *		RCPT TO:<ivx@lumisoft.ee>
			*/

			m_pSocket.WriteLine("RCPT TO:<" + recipientEmail + ">");

			string responseLine = m_pSocket.ReadLine();
			// Response line must start with 250 or otherwise it's error response
			if(!responseLine.StartsWith("250")){
				throw new Exception(responseLine);
			}
		}

		#endregion

		#region method BeginAddRecipient

		/// <summary>
		/// Begin adding recipient.
		/// </summary>
		/// <param name="recipientEmail">Recipient email address.</param>
		/// <param name="callback">Callback to be called if command ends.</param>
		public void BeginAddRecipient(string recipientEmail,CommadCompleted callback)
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}

			/* RFC 2821 4.1.1.2 RCPT
			 *  Examples:
			 *		RCPT TO:<ivx@lumisoft.ee>
			*/

			m_pSocket.BeginWriteLine("RCPT TO:<" + recipientEmail + ">",callback,new SocketCallBack(this.OnRcptSendFinished));
		}

		#endregion

		#region method OnRcptSendFinished

		/// <summary>
		/// Is called when smtp client has finished RCPT TO: command sending.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnRcptSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					MemoryStream ms = new MemoryStream();
					m_pSocket.BeginReadLine(ms,1000,new object[]{callback,ms},new SocketCallBack(this.OnRcptReadServerResponseFinished));	
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnRcptReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading RCPT TO: command server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnRcptReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[0]);

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)(((object[])tag)[1])).ToArray());
			
					// Response line must start with 250 or otherwise it's error response
					if(!responseLine.StartsWith("250")){
						throw new Exception(responseLine);
					}
					else{
						// RCPT TO: completed susscessfully, call callback method.
						callback(SocketCallBackResult.Ok,null);
					}					
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


		#region method SendMessage

		/// <summary>
		/// Sends message to server. NOTE: Message sending starts from message stream current posision.
		/// </summary>
		/// <param name="message">Message what will be sent to server. NOTE: Message sending starts from message stream current posision.</param>
		public void SendMessage(Stream message)
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}

			/* RFC 2821 4.1.1.4 DATA
			 * Notes:
			 *		Message must be period handled for DATA command. This meas if message line starts with .,
			 *		additional .(period) must be added.
			 *		Message send is ended with <CRLF>.<CRLF>.
			 *	Examples:
			 *		C: DATA<CRLF>
			 *		S: 354 Start sending message, end with <crlf>.<crlf><CRLF>
			 *		C: send_message
			 *		C: <CRLF>.<CRLF>
			*/

			/* RFC 3030 BDAT
			 *	Syntax:BDAT<SP>message_size_in_bytes<SP>LAST<CRLF>
			 *	
			 *	Exapmle:
			 *		C: BDAT 1000 LAST<CRLF>
			 *		C: send_1000_byte_message
			 *		S: 250 OK<CRLF>
			 * 
			*/
                        
			if(m_Supports_Bdat){
				m_pSocket.WriteLine("BDAT " + (message.Length - message.Position) + " LAST");

				m_pSocket.Write(message);

				string responseLine = m_pSocket.ReadLine();
				// Response line must start with 250 or otherwise it's error response
				if(!responseLine.StartsWith("250")){
					throw new Exception(responseLine);
				}
			}
			else{
				m_pSocket.WriteLine("DATA");
		
				string responseLine = m_pSocket.ReadLine();
				// Response line must start with 334 or otherwise it's error response
				if(!responseLine.StartsWith("354")){
					throw new Exception(responseLine);
				}

				m_pSocket.WritePeriodTerminated(message);

				responseLine = m_pSocket.ReadLine();
				// Response line must start with 250 or otherwise it's error response
				if(!responseLine.StartsWith("250")){
					throw new Exception(responseLine);
				}
			}
		}

		#endregion

		#region method BeginSendMessage

		/// <summary>
		/// Starts sending message.
		/// </summary>
		/// <param name="message">Message what will be sent to server. NOTE: Message sending starts from message stream current posision.</param>
		/// <param name="callback">Callback to be called if command ends.</param>
		public void BeginSendMessage(Stream message,CommadCompleted callback)
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}

			/* RFC 2821 4.1.1.4 DATA
			 * Notes:
			 *		Message must be period handled for DATA command. This meas if message line starts with .,
			 *		additional .(period) must be added.
			 *		Message send is ended with <CRLF>.<CRLF>.
			 *	Examples:
			 *		C: DATA<CRLF>
			 *		S: 354 Start sending message, end with <crlf>.<crlf><CRLF>
			 *		C: send_message
			 *		C: <CRLF>.<CRLF>
			*/

			/* RFC 3030 BDAT
			 *	Syntax:BDAT<SP>message_size_in_bytes<SP>LAST<CRLF>
			 *	
			 *	Exapmle:
			 *		C: BDAT 1000 LAST<CRLF>
			 *		C: send_1000_byte_message
			 *		S: 250 OK<CRLF>
			 * 
			*/

			if(m_Supports_Bdat){
				m_pSocket.BeginWriteLine("BDAT " + (message.Length - message.Position) + " LAST",new object[]{message,callback},new SocketCallBack(this.OnBdatSendFinished));
			}
			else{
				m_pSocket.BeginWriteLine("DATA",new object[]{message,callback},new SocketCallBack(this.OnDataSendFinished));				
			}
		}

		#endregion

		#region method OnBdatSendFinished

		/// <summary>
		/// Is called when smtp client has finished BDAT command sending.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnBdatSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[1]);

			try{
				if(result == SocketCallBackResult.Ok){
					// BDAT command successfully sent to SMTP server, start sending DATA.
					m_pSocket.BeginWrite((Stream)(((object[])tag)[0]),callback,new SocketCallBack(this.OnBdatDataSendFinished));	
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnBdatDataSendFinished

		/// <summary>
		/// Is called when smtp client has finished sending BDAT message data to smtp server.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnBdatDataSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					// BDAT message data successfully sent to SMTP server, start reading server response
					MemoryStream ms = new MemoryStream();
					m_pSocket.BeginReadLine(ms,1000,new object[]{callback,ms},new SocketCallBack(this.OnBdatReadServerResponseFinished));	
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnBdatReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading BDAT: command server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnBdatReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[0]);

			try{
				if(result == SocketCallBackResult.Ok){					
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)(((object[])tag)[1])).ToArray());
			
					// Response line must start with 250 or otherwise it's error response
					if(!responseLine.StartsWith("250")){
						throw new Exception(responseLine);
					}
					else{
						// BDAT: completed susscessfully, call callback method.
						callback(SocketCallBackResult.Ok,null);
					}					
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


		#region method OnDataSendFinished

		/// <summary>
		/// Is called when smtp client has finished DATA command sending.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnDataSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[1]);

			try{
				if(result == SocketCallBackResult.Ok){
					// DATA command has sent to SMTP server, start reading server response.
					MemoryStream ms = new MemoryStream();
					m_pSocket.BeginReadLine(ms,1000,new object[]{(Stream)(((object[])tag)[0]),callback,ms},new SocketCallBack(this.OnDataReadServerResponseFinished));	
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnDataReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading DATA command server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnDataReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[1]);

			try{
				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)(((object[])tag)[2])).ToArray());

					// Response line must start with 334 or otherwise it's error response
					if(!responseLine.StartsWith("354")){
						throw new Exception(responseLine);
					}
					else{
						Stream message = (Stream)(((object[])tag)[0]);
					
						// Start sending message to smtp server
						m_pSocket.BeginWritePeriodTerminated(message,callback,new SocketCallBack(this.OnDataMessageSendFinished));	
					}				
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnDataMessageSendFinished

		/// <summary>
		/// Is called when smtp client has sending MESSAGE to smtp server.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnDataMessageSendFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)tag;

			try{
				if(result == SocketCallBackResult.Ok){
					// Message has successfully sent to smtp server, start reading server response
					MemoryStream ms = new MemoryStream();
					m_pSocket.BeginReadLine(ms,1000,new object[]{callback,ms},new SocketCallBack(this.OnDataMessageSendReadServerResponseFinished));	
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion

		#region method OnDataMessageSendReadServerResponseFinished

		/// <summary>
		/// Is called when smtp client has finished reading MESSAGE send smtp server response line.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void OnDataMessageSendReadServerResponseFinished(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			CommadCompleted callback = (CommadCompleted)(((object[])tag)[0]);

			try{
                // TODO: some servers close connection after DATA command, hanndle Socket closed.

				if(result == SocketCallBackResult.Ok){
					string responseLine = System.Text.Encoding.ASCII.GetString(((MemoryStream)(((object[])tag)[1])).ToArray());

					// Response line must start with 250 or otherwise it's error response
					if(!responseLine.StartsWith("250")){
						throw new Exception(responseLine);
					}
					else{
						// DATA: completed susscessfully, call callback method.
						callback(SocketCallBackResult.Ok,null);
					}
				}
				else{
					HandleSocketError(result,exception);
				}
			}
			catch(Exception x){
				// Pass exception to callback method
				callback(SocketCallBackResult.Exception,x);
			}
		}

		#endregion


		#region method Reset

		/// <summary>
		/// Send RSET command to SMTP server, resets SMTP session.
		/// </summary>
		public void Reset()
		{
			if(!m_Connected){
				throw new Exception("You must connect first");
			}


			m_pSocket.WriteLine("RSET");

			string responseLine = m_pSocket.ReadLine();
			if(!responseLine.StartsWith("250")){
				throw new Exception(responseLine);
			}
		}

		#endregion


		#region method HandleSocketError

		/// <summary>
		/// Handles socket errors.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="x"></param>
		private void HandleSocketError(SocketCallBackResult result,Exception x)
		{
            // Log socket errors to log
            if(m_pSocket.Logger != null){
                if(result == SocketCallBackResult.SocketClosed){
                    m_pSocket.Logger.AddTextEntry("Server closed socket !");
                }
                else if(x != null && x is SocketException){
                    SocketException socketException = (SocketException)x;
                    // Server disconnected or aborted connection
                    if(socketException.ErrorCode == 10054 || socketException.ErrorCode == 10053){
                        m_pSocket.Logger.AddTextEntry("Server closed socket or aborted connection !");
                    }
                }
                else{
                    m_pSocket.Logger.AddTextEntry("Unknown error !");
                }
            }

			if(result == SocketCallBackResult.Exception){
				throw x;
			}
			else{
				throw new Exception(result.ToString());
			}
		}

		#endregion


        #region method GetDestinations

        /// <summary>
        /// Gets specified email domain possible connect points. Values are in priority descending order.
        /// </summary>
        /// <param name="domain">Email address or domain name.</param>
        /// <returns>Returns possible email server connection points.</returns>
        public IPAddress[] GetDestinations(string domain)
        {
            /* 
                1) Add MX records
                2) Add A records
            */

            // We have email address, just get domain from it.
            if(domain.IndexOf('@') > -1){
                domain = domain.Substring(domain.IndexOf('@') + 1);
            }

            List<IPAddress> retVal = new List<IPAddress>();
            Dns_Client dns = new Dns_Client();
            Dns_Client.DnsServers = this.DnsServers;
            DnsServerResponse response = dns.Query(domain,QTYPE.MX);
            // Add MX            
            foreach(DNS_rr_MX mx in response.GetMXRecords()){
                try{
                    IPAddress[] ips = System.Net.Dns.GetHostAddresses(mx.Host);
                    foreach(IPAddress ip in ips){
                        if(!retVal.Contains(ip)){
                           retVal.Add(ip);
                        }
                    }
                }
                catch{
                    // Probably wrong MX record, no A reocrd for it, so we don't get IP. Just skip it.
                }
            }
            // Add A records only if no MX records.
            if(retVal.Count == 0){                
                response = dns.Query(domain,QTYPE.A);
                foreach(DNS_rr_A a in response.GetARecords()){
                    IPAddress ip = a.IP;
                    if(!retVal.Contains(ip)){
                        retVal.Add(ip);
                    }
                }
            }

            return retVal.ToArray();
        }

        #endregion

        //	public static void QuickSendDirect(string[] dnsServers,string hostName,string from,string[] to,Stream messageStream)

		#region static method QuickSendSmartHost

		/// <summary>
		/// Sends specified message to specified smart host.
		/// </summary>
		/// <param name="smartHost">Smarthost name or IP.</param>
		/// <param name="port">SMTP port number. Normally this is 25.</param>
		/// <param name="hostName">Host name reported to SMTP server.</param>
		/// <param name="message">Mime message to send.</param>
		public static void QuickSendSmartHost(string smartHost,int port,string hostName,LumiSoft.Net.Mime.Mime message)
		{
			QuickSendSmartHost(smartHost,port,hostName,"","",message);
		}

        /// <summary>
		/// Sends specified message to specified smart host.
		/// </summary>
		/// <param name="smartHost">Smarthost name or IP.</param>
		/// <param name="port">SMTP port number. Normally this is 25.</param>
		/// <param name="hostName">Host name reported to SMTP server.</param>
		/// <param name="userName">SMTP user name. Note: Pass empty string if no authentication wanted.</param>
		/// <param name="password">SMTP password.</param>
		/// <param name="message">Mime message to send.</param>
		public static void QuickSendSmartHost(string smartHost,int port,string hostName,string userName,string password,LumiSoft.Net.Mime.Mime message)
		{
            QuickSendSmartHost(smartHost,port,false,hostName,userName,password,message);
        }

		/// <summary>
		/// Sends specified message to specified smart host.
		/// </summary>
		/// <param name="smartHost">Smarthost name or IP.</param>
		/// <param name="port">SMTP port number. Default SMTP port is 25 and SSL port is 465.</param>
		/// <param name="ssl">Specifies if to connected via SSL.</param>
		/// <param name="hostName">Host name reported to SMTP server.</param>
		/// <param name="userName">SMTP user name. Note: Pass empty string if no authentication wanted.</param>
		/// <param name="password">SMTP password.</param>
		/// <param name="message">Mime message to send.</param>
		public static void QuickSendSmartHost(string smartHost,int port,bool ssl,string hostName,string userName,string password,LumiSoft.Net.Mime.Mime message)
		{
			string from = "";
			if(message.MainEntity.From != null){
				MailboxAddress[] addresses = message.MainEntity.From.Mailboxes;
				if(addresses.Length > 0){
					from = addresses[0].EmailAddress;
				}
			}

			ArrayList recipients = new ArrayList();
			if(message.MainEntity.To != null){
				MailboxAddress[] addresses = message.MainEntity.To.Mailboxes;				
				foreach(MailboxAddress address in addresses){
					recipients.Add(address.EmailAddress);
				}
			}
			if(message.MainEntity.Cc != null){
				MailboxAddress[] addresses = message.MainEntity.Cc.Mailboxes;				
				foreach(MailboxAddress address in addresses){
					recipients.Add(address.EmailAddress);
				}
			}
			if(message.MainEntity.Bcc != null){
				MailboxAddress[] addresses = message.MainEntity.Bcc.Mailboxes;				
				foreach(MailboxAddress address in addresses){
					recipients.Add(address.EmailAddress);
				}
			}
			string[] to = new string[recipients.Count];
			recipients.CopyTo(to);

			MemoryStream messageStream = new MemoryStream();
			message.ToStream(messageStream);
			messageStream.Position = 0;

			// messageStream
			QuickSendSmartHost(smartHost,port,ssl,hostName,userName,password,from,to,messageStream);
		}

		/// <summary>
		/// Sends specified message to specified smart host. NOTE: Message sending starts from message stream current posision.
		/// </summary>
		/// <param name="smartHost">Smarthost name or IP.</param>
		/// <param name="port">SMTP port number. Normally this is 25.</param>
		/// <param name="hostName">Host name reported to SMTP server.</param>
		/// <param name="from">From address reported to SMTP server.</param>
		/// <param name="to">Message recipients.</param>
		/// <param name="messageStream">Message stream. NOTE: Message sending starts from message stream current posision.</param>
		public static void QuickSendSmartHost(string smartHost,int port,string hostName,string from,string[] to,Stream messageStream)
		{
			QuickSendSmartHost(smartHost,port,false,hostName,"","",from,to,messageStream);
		}

        /// <summary>
		/// Sends specified message to specified smart host. NOTE: Message sending starts from message stream current posision.
		/// </summary>
		/// <param name="smartHost">Smarthost name or IP.</param>
		/// <param name="port">SMTP port number. Normally this is 25.</param>
		/// <param name="hostName">Host name reported to SMTP server.</param>
		/// <param name="userName">SMTP user name. Note: Pass empty string if no authentication wanted.</param>
		/// <param name="password">SMTP password.</param>
		/// <param name="from">From address reported to SMTP server.</param>
		/// <param name="to">Message recipients.</param>
		/// <param name="messageStream">Message stream. NOTE: Message sending starts from message stream current posision.</param>
		public static void QuickSendSmartHost(string smartHost,int port,string hostName,string userName,string password,string from,string[] to,Stream messageStream)
		{
            QuickSendSmartHost(smartHost,port,false,hostName,userName,password,from,to,messageStream);
        }

		/// <summary>
		/// Sends specified message to specified smart host. NOTE: Message sending starts from message stream current posision.
		/// </summary>
		/// <param name="smartHost">Smarthost name or IP.</param>
		/// <param name="port">SMTP port number. Default SMTP port is 25 and SSL port is 465.</param>
		/// <param name="ssl">Specifies if to connected via SSL.</param>
		/// <param name="hostName">Host name reported to SMTP server.</param>
		/// <param name="userName">SMTP user name. Note: Pass empty string if no authentication wanted.</param>
		/// <param name="password">SMTP password.</param>
		/// <param name="from">From address reported to SMTP server.</param>
		/// <param name="to">Message recipients.</param>
		/// <param name="messageStream">Message stream. NOTE: Message sending starts from message stream current posision.</param>
		public static void QuickSendSmartHost(string smartHost,int port,bool ssl,string hostName,string userName,string password,string from,string[] to,Stream messageStream)
		{
			using(SmtpClientEx smtp = new SmtpClientEx()){
				smtp.Connect(smartHost,port,ssl);
				smtp.Ehlo(hostName);
	            if(userName.Length > 0){
					smtp.Authenticate(userName,password);
				}
				smtp.SetSender(MailboxAddress.Parse(from).EmailAddress,messageStream.Length - messageStream.Position);
				foreach(string t in to){
					smtp.AddRecipient(MailboxAddress.Parse(t).EmailAddress);
				}

				smtp.SendMessage(messageStream);
			}
		}

		#endregion


		#region Properties Implementation

		/// <summary>
		/// Gets local endpoint. Returns null if smtp client isn't connected.
		/// </summary>
		public EndPoint LocalEndpoint
		{
			get{
				if(m_pSocket != null){
					return m_pSocket.LocalEndPoint; 
				}
				else{
					return null;
				}
			}
		}

		/// <summary>
		/// Gets remote endpoint. Returns null if smtp client isn't connected.
		/// </summary>
		public EndPoint RemoteEndPoint
		{
			get{
				if(m_pSocket != null){
					return m_pSocket.RemoteEndPoint; 
				}
				else{
					return null;
				}
			}
		}

		/// <summary>
		/// Gets or sets dns servers.
		/// </summary>
		public string[] DnsServers
		{
			get{ return m_pDnsServers; }

			set{ m_pDnsServers = value; }
		}

		/// <summary>
		/// Gets if smtp client is connected.
		/// </summary>
		public bool Connected
		{
			get{ return m_Connected; }
		}

        /// <summary>
		/// Gets if pop3 client is authenticated.
		/// </summary>
		public bool Authenticated
		{
			get{ return m_Authenticated; }
		}

		/// <summary>
		/// Gets when was last activity.
		/// </summary>
		public DateTime LastDataTime
		{
			get{ return m_pSocket.LastActivity; }
		}

        /// <summary>
        /// Gets log entries that are currently in log buffer. Returns null if socket not connected or no logging enabled.
        /// </summary>
        public SocketLogger SessionActiveLog
        {
            get{ 
                if(m_pSocket == null){
                    return null;
                }
                else{
                    return m_pSocket.Logger; 
                }
            }
        }

        /// <summary>
        /// Gets how many bytes are readed through smtp client.
        /// </summary>
        public long ReadedCount
        {
            get{ 
                if(!m_Connected){
				    throw new Exception("You must connect first");
			    }

                return m_pSocket.ReadedCount; 
            }
        }
        
        /// <summary>
        /// Gets how many bytes are written through smtp client.
        /// </summary>
        public long WrittenCount
        {
            get{ 
                if(!m_Connected){
				    throw new Exception("You must connect first");
			    }

                return m_pSocket.WrittenCount; 
            }
        }
        
        /// <summary>
        /// Gets if the connection is an SSL connection.
        /// </summary>
        public bool IsSecureConnection
        {
            get{ 
                if(!m_Connected){
				    throw new Exception("You must connect first");
			    }

                return m_pSocket.SSL; 
            }
        }

		#endregion		

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions