Click here to Skip to main content
15,880,725 members
Articles / Programming Languages / C#

STUN Client

Rate me:
Please Sign up or sign in to vote.
4.83/5 (36 votes)
20 Apr 2007CPOL 322.1K   14.9K   85  
STUN client C# implementation with sample application
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Security.Cryptography;

namespace LumiSoft.Net.POP3.Client
{
	/// <summary>
	/// POP3 Client.
	/// </summary>
	/// <example>
	/// <code>
	/// 
	/// /*
	///  To make this code to work, you need to import following namespaces:
	///  using LumiSoft.Net.Mime;
	///  using LumiSoft.Net.POP3.Client; 
	///  */
	/// 
	/// using(POP3_Client c = new POP3_Client()){
	///		c.Connect("ivx",110);
	///		c.Authenticate("test","test",true);
	///		
	///		POP3_MessagesInfo mInf = c.GetMessagesInfo();
	///		
	///		// Get first message if there is any
	///		if(mInf.Count > 0){
	///			byte[] messageData = c.GetMessage(mInf[0]);
	///		
	///			// Do your suff
	///			
	///			// Parse message
	///			Mime m = Mime.Parse(messageData);
	///			string from = m.MainEntity.From;
	///			string subject = m.MainEntity.Subject;			
	///			// ... 
	///		}		
	///	}
	/// </code>
	/// </example>
	public class POP3_Client : IDisposable
	{
		private SocketEx     m_pSocket       = null;
		private SocketLogger m_pLogger       = null;
		private bool         m_Connected     = false;
		private bool         m_Authenticated = false;
		private string       m_ApopHashKey   = "";
		private bool         m_LogCmds       = false;

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

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

		#region method Dispose

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		public void Dispose()
		{
			try{
				Disconnect();
			}
			catch{
			}
		}

		#endregion


		#region method Connect

        /// <summary>
		/// Connects to specified host.
		/// </summary>
		/// <param name="host">Host name.</param>
		/// <param name="port">Port number. Default POP3 port is 110.</param>
		public void Connect(string host,int port)
		{
            Connect(host,port,false);
        }

		/// <summary>
		/// Connects to specified host.
		/// </summary>
		/// <param name="host">Host name.</param>
		/// <param name="port">Port number. Default POP3 port is 110 and SSL port is 995.</param>
        /// <param name="ssl">Specifies if to connected via SSL.</param>
		public void Connect(string host,int port,bool ssl)
		{
			if(!m_Connected){
                SocketEx s = new SocketEx();
                s.Connect(host,port,ssl);
                m_pSocket = s;

				if(m_LogCmds && SessionLog != null){
					m_pLogger = new SocketLogger(s.RawSocket,SessionLog);
					m_pLogger.SessionID = Guid.NewGuid().ToString();
					m_pSocket.Logger = m_pLogger;
				}

				// Set connected flag
				m_Connected = true;

				string reply = m_pSocket.ReadLine();
				if(reply.StartsWith("+OK")){
					// Try to read APOP hash key, if supports APOP
					if(reply.IndexOf("<") > -1 && reply.IndexOf(">") > -1){
						m_ApopHashKey = reply.Substring(reply.LastIndexOf("<"),reply.LastIndexOf(">") - reply.LastIndexOf("<") + 1);
					}
				}
			}
		}

		#endregion

		#region method Disconnect

		/// <summary>
		/// Closes connection to POP3 server.
		/// </summary>
		public void Disconnect()
		{
			try{
				if(m_pSocket != null){
					// Send QUIT
					m_pSocket.WriteLine("QUIT");			

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

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

			m_pSocket       = null;
			m_Connected     = false;			
			m_Authenticated = false;
		}

		#endregion

        #region method StartTLS

        /// <summary>
        /// Switches POP3 connection to SSL.
        /// </summary>
        public void StartTLS()
        {
            /* RFC 2595 4. POP3 STARTTLS extension.
                Arguments: none

                Restrictions:
                    Only permitted in AUTHORIZATION state.
             
                Possible Responses:
                     +OK -ERR

                 Examples:
                     C: STLS
                     S: +OK Begin TLS negotiation
                     <TLS negotiation, further commands are under TLS layer>
                       ...
                     C: STLS
                     S: -ERR Command not permitted when TLS active
            */

            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("STLS");

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

            m_pSocket.SwitchToSSL_AsClient();
        }

        #endregion

		#region method Authenticate

		/// <summary>
		/// Authenticates user.
		/// </summary>
		/// <param name="userName">User login name.</param>
		/// <param name="password">Password.</param>
		/// <param name="tryApop"> If true and POP3 server supports APOP, then APOP is used, otherwise normal login used.</param>
		public void Authenticate(string userName,string password,bool tryApop)
		{
			if(!m_Connected){
				throw new Exception("You must connect first !");
			}

			if(m_Authenticated){
				throw new Exception("You are already authenticated !");
			}

			// Supports APOP, use it
			if(tryApop && m_ApopHashKey.Length > 0){
				//--- Compute md5 hash -----------------------------------------------//
				byte[] data = System.Text.Encoding.ASCII.GetBytes(m_ApopHashKey + password);
			
				MD5 md5 = new MD5CryptoServiceProvider();			
				byte[] hash = md5.ComputeHash(data);

				string hexHash = BitConverter.ToString(hash).ToLower().Replace("-","");
				//---------------------------------------------------------------------//

				m_pSocket.WriteLine("APOP " + userName + " " + hexHash);

				string reply = m_pSocket.ReadLine();
				if(reply.StartsWith("+OK")){
					m_Authenticated = true;
				}
				else{
					throw new Exception("Server returned:" + reply);
				}
			}
			else{ // Use normal LOGIN, don't support APOP 
				m_pSocket.WriteLine("USER " + userName);

				string reply = m_pSocket.ReadLine();
				if(reply.StartsWith("+OK")){
					m_pSocket.WriteLine("PASS " + password);

					reply = m_pSocket.ReadLine();
					if(reply.StartsWith("+OK")){
						m_Authenticated = true;
					}
					else{
						throw new Exception("Server returned:" + reply);
					}
				}
				else{
					throw new Exception("Server returned:" + reply);
				}				
			}

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

		#endregion


		#region function GetMessagesInfo

		/// <summary>
		/// Gets messages info.
		/// </summary>
		public POP3_MessagesInfo GetMessagesInfo()
		{
			if(!m_Connected){
				throw new Exception("You must connect first !");
			}

			if(!m_Authenticated){
				throw new Exception("You must authenticate first !");
			}

			POP3_MessagesInfo messagesInfo = new POP3_MessagesInfo();

			// Before getting list get UIDL list, then we can make full message info (UID,No,Size).
			Hashtable uidlList = GetUidlList();

			m_pSocket.WriteLine("LIST");

			/* NOTE: If reply is +OK, this is multiline respone and is terminated with '.'.
			Examples:
				C: LIST
				S: +OK 2 messages (320 octets)
				S: 1 120				
				S: 2 200
				S: .
				...
				C: LIST 3
				S: -ERR no such message, only 2 messages in maildrop
			*/

			// Read first line of reply, check if it's ok
			string line = m_pSocket.ReadLine();
			if(line.StartsWith("+OK")){
				// Read lines while get only '.' on line itshelf.
				while(true){
					line = m_pSocket.ReadLine();

					// End of data
					if(line.Trim() == "."){
						break;
					}
					else{
						string[] param = line.Trim().Split(new char[]{' '});
						int  no   = Convert.ToInt32(param[0]);
						long size = Convert.ToInt64(param[1]);

						messagesInfo.Add(uidlList[no].ToString(),no,size);
					}
				}
			}
			else{
				throw new Exception("Server returned:" + line);
			}

			return messagesInfo;
		}

		#endregion

		#region function GetUidlList

		/// <summary>
		/// Gets uid listing.
		/// </summary>
		/// <returns>Returns Hashtable containing uidl listing. Key column contains message NR and value contains message UID.</returns>
		public Hashtable GetUidlList()
		{
			if(!m_Connected){
				throw new Exception("You must connect first !");
			}

			if(!m_Authenticated){
				throw new Exception("You must authenticate first !");
			}

			Hashtable retVal = new Hashtable();

			m_pSocket.WriteLine("UIDL");

			/* NOTE: If reply is +OK, this is multiline respone and is terminated with '.'.
			Examples:
				C: UIDL
				S: +OK
				S: 1 whqtswO00WBw418f9t5JxYwZ
				S: 2 QhdPYR:00WBw1Ph7x7
				S: .
				...
				C: UIDL 3
				S: -ERR no such message
			*/

			// Read first line of reply, check if it's ok
			string line = m_pSocket.ReadLine();
			if(line.StartsWith("+OK")){
				// Read lines while get only '.' on line itshelf.				
				while(true){
					line = m_pSocket.ReadLine();

					// End of data
					if(line.Trim() == "."){
						break;
					}
					else{
						string[] param = line.Trim().Split(new char[]{' '});
						int    nr  = Convert.ToInt32(param[0]);
						string uid = param[1];

						retVal.Add(nr,uid);
					}
				}
			}
			else{
				throw new Exception("Server returned:" + line);
			}

			return retVal;
		}

		#endregion

		#region method GetMessage

		/// <summary>
		/// Gets specified message.
		/// </summary>
		/// <param name="msgInfo">Pop3 message info of message what to get.</param>
		/// <returns></returns>
		public byte[] GetMessage(POP3_MessageInfo msgInfo)
		{
			return GetMessage(msgInfo.MessageNumber);
		}

		/// <summary>
		/// Gets specified message.
		/// </summary>
		/// <param name="messageNo">Message number.</param>
		public byte[] GetMessage(int messageNo)
		{
            MemoryStream stream = new MemoryStream();
			GetMessage(messageNo,stream);
            return stream.ToArray();
		}

        /// <summary>
        /// Gets specified message and stores it to specified stream.
        /// </summary>
        /// <param name="messageNo">Message number.</param>
        /// <param name="stream">Stream where to store message.</param>
        public void GetMessage(int messageNo,Stream stream)
		{
            if(!m_Connected){
				throw new Exception("You must connect first !");
			}

			if(!m_Authenticated){
				throw new Exception("You must authenticate first !");
			}

			m_pSocket.WriteLine("RETR " + messageNo.ToString());

			// Read first line of reply, check if it's ok
			string line = m_pSocket.ReadLine();
			if(line.StartsWith("+OK")){    
                m_pSocket.ReadPeriodTerminated(stream,100000000);
			}
			else{
				throw new Exception("Server returned:" + line);
			}
        }

		#endregion

		#region method DeleteMessage

		/// <summary>
		/// Deletes specified message
		/// </summary>
		/// <param name="messageNr">Message number.</param>
		public void DeleteMessage(int messageNr)
		{
			if(!m_Connected){
				throw new Exception("You must connect first !");
			}

			if(!m_Authenticated){
				throw new Exception("You must authenticate first !");
			}

			m_pSocket.WriteLine("DELE " + messageNr.ToString());

			// Read first line of reply, check if it's ok
			string line = m_pSocket.ReadLine();
			if(!line.StartsWith("+OK")){
				throw new Exception("Server returned:" + line);
			}
		}

		#endregion

		#region method GetTopOfMessage

		/// <summary>
		/// Gets top lines of message.
		/// </summary>
		/// <param name="nr">Message number which top lines to get.</param>
		/// <param name="nLines">Number of lines to get.</param>
		public byte[] GetTopOfMessage(int nr,int nLines)
		{
			if(!m_Connected){
				throw new Exception("You must connect first !");
			}

			if(!m_Authenticated){
				throw new Exception("You must authenticate first !");
			}
			

			m_pSocket.WriteLine("TOP " + nr.ToString() + " " + nLines.ToString());

			// Read first line of reply, check if it's ok
			string line = m_pSocket.ReadLine();
			if(line.StartsWith("+OK")){
				MemoryStream strm = new MemoryStream();
                m_pSocket.ReadPeriodTerminated(strm,100000000);

                return strm.ToArray();
			}
			else{
				throw new Exception("Server returned:" + line);
			}
		}

		#endregion

		#region method Reset

		/// <summary>
		/// Resets session.
		/// </summary>
		public void Reset()
		{
			if(!m_Connected){
				throw new Exception("You must connect first !");
			}

			if(!m_Authenticated){
				throw new Exception("You must authenticate first !");
			}

			m_pSocket.WriteLine("RSET");

			// Read first line of reply, check if it's ok
			string line = m_pSocket.ReadLine();
			if(!line.StartsWith("+OK")){
				throw new Exception("Server returned:" + line);
			}
		}

		#endregion


		#region Properties Implementation

		/// <summary>
		/// Gets if pop3 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 or sets if to log commands.
		/// </summary>
		public bool LogCommands
		{
			get{ return m_LogCmds;	}

			set{ m_LogCmds = value; }
		}
        
        /// <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