Click here to Skip to main content
15,886,689 members
Articles / Programming Languages / C#

SMTP and POP3 Mail Server

Rate me:
Please Sign up or sign in to vote.
4.88/5 (96 votes)
29 Sep 20031 min read 1M   18.9K   315  
An SMTP and POP3 mail server written using the .NET Framework and C#.
using System;
using System.IO;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
using LumiSoft.Net;

namespace LumiSoft.Net.POP3.Server
{
	/// <summary>
	/// POP3 Session.
	/// </summary>
	public class POP3_Session
	{		
		private Socket        m_pClientSocket  = null;  // Referance to client Socket.
		private POP3_Server   m_pPOP3_Server   = null;  // Referance to POP3 server.
		private string        m_SessionID      = "";    // Holds session ID.
		private string        m_UserName       = "";    // Holds loggedIn UserName.
		private string        m_Password       = "";    // Holds loggedIn Password.
		private bool          m_Authenticated  = false; // Holds authentication flag.
		private string        m_MD5_prefix     = "";    // Session MD5 prefix for APOP command
		private int           m_BadCmdCount    = 0;     // Holds number of bad commands.
		private POP3_Messages m_POP3_Messages  = null;		
		private DateTime      m_SessionStartTime;
		private _LogWriter    m_pLogWriter     = null;
		private object        m_Tag            = null;
		
		
		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="clientSocket">Referance to socket.</param>
		/// <param name="server">Referance to POP3 server.</param>
		/// <param name="sessionID">Session ID which is assigned to this session.</param>
		/// <param name="logWriter">Log writer.</param>
		public POP3_Session(Socket clientSocket,POP3_Server server,string sessionID,_LogWriter logWriter)
		{
			m_pClientSocket    = clientSocket;
			m_pPOP3_Server     = server;
			m_SessionID        = sessionID;
			m_POP3_Messages    = new POP3_Messages();
			m_pLogWriter       = logWriter;
			m_SessionStartTime = DateTime.Now;
		}


		#region function StartProcessing

		/// <summary>
		/// Starts POP3 Session processing.
		/// </summary>
		internal void StartProcessing()
		{	
			try
			{
				// Check if ip is allowed to connect this computer
				if(m_pPOP3_Server.OnValidate_IpAddress(this.RemoteEndPoint)){

					// Notify that server is ready
					m_MD5_prefix = "<" + Guid.NewGuid().ToString().ToLower() + ">";
					SendData("+OK POP3 server ready " + m_MD5_prefix + "\r\n");
		
					//------ Create command loop --------------------------------//
					// Loop while QUIT cmd or Session TimeOut.
					long lastCmdTime = DateTime.Now.Ticks;
					string lastCmd  = "";
					while(true){
						// If there is any data available, begin command reading.
						if(m_pClientSocket.Available > 0){
							try
							{
								lastCmd = this.ReadLine();
								if(SwitchCommand(lastCmd)){
									break;
								}
							}
							catch(ReadException rX){
								//---- Check that maximum bad commands count isn't exceeded ---------------//
								if(m_BadCmdCount > m_pPOP3_Server.MaxBadCommands-1){
									SendData("-ERR Too many bad commands, closing transmission channel\r\n");
									break;
								}
								m_BadCmdCount++;
								//-------------------------------------------------------------------------//

								switch(rX.ReadReplyCode){
									case ReadReplyCode.LengthExceeded:
										SendData("-ERR Line too long.\r\n");
										break;

									case ReadReplyCode.TimeOut:
										SendData("-ERR Command timeout.\r\n");
										break;

									case ReadReplyCode.UnKnownError:
										SendData("-ERR UnKnown Error.\r\n");

										m_pPOP3_Server.OnSysError(new Exception(rX.Message),new System.Diagnostics.StackTrace());										
										break;
								}
							}
							catch(Exception x){
								// Connection lost
								if(!m_pClientSocket.Connected){
									break;
								}
								
								SendData("-ERR Unkown temp error\r\n");
								m_pPOP3_Server.OnSysError(x,new System.Diagnostics.StackTrace());
							}

							// reset last command time
							lastCmdTime = DateTime.Now.Ticks;
						}
						else{
							//----- Session timeout stuff ------------------------------------------------//
							if(DateTime.Now.Ticks > lastCmdTime + ((long)(m_pPOP3_Server.SessionIdleTimeOut)) * 10000){
								// Notify for closing
								SendData("-ERR Session timeout, OK POP3 server signing off\r\n");
								break;
							}
							
							// Wait 100ms to save CPU, otherwise while loop may take 100% CPU. 
							Thread.Sleep(100);							
							//-----------------------------------------------------------------------------//
						}
					}
				}						
			}
			catch(ThreadInterruptedException e){
				string dummy = e.Message;     // Neede for to remove compile warning
			}
			catch(Exception x){
				if(!m_pClientSocket.Connected){
					m_pLogWriter.AddEntry("Connection is aborted by client machine",this.SessionID,this.IpStr,"x");
				}
				else{
					m_pPOP3_Server.OnSysError(x,new System.Diagnostics.StackTrace());
				}				
			}
			finally{
				m_pPOP3_Server.RemoveSession(this,m_pLogWriter);

				if(m_pClientSocket.Connected){
					m_pClientSocket.Close();
				}

				// Write logs to log file, if needed
				if(m_pPOP3_Server.LogCommands){
					m_pLogWriter.Flush();
				}
			}
		}

		#endregion

		#region function SwitchCommand

		/// <summary>
		/// Parses and executes POP3 commmand.
		/// </summary>
		/// <param name="POP3_commandTxt">POP3 command text.</param>
		/// <returns>Returns true,if session must be terminated.</returns>
		private bool SwitchCommand(string POP3_commandTxt)
		{
			//---- Parse command --------------------------------------------------//
			string[] cmdParts = POP3_commandTxt.TrimStart().Split(new char[]{' '});
			string POP3_command = cmdParts[0].ToUpper().Trim();
			string argsText = Core.GetArgsText(POP3_commandTxt,POP3_command);
			//---------------------------------------------------------------------//

			switch(POP3_command)
			{
				case "USER":
					USER(argsText);
					break;

				case "PASS":
					PASS(argsText);
					break;
					
				case "STAT":
					STAT();
					break;

				case "LIST":
					LIST(argsText);
					break;

				case "RETR":					
					RETR(argsText);
					break;

				case "DELE":
					DELE(argsText);
					break;

				case "NOOP":
					NOOP();
					break;

				case "RSET":
					RSET();
					break;

				case "QUIT":
					QUIT();
					return true;


				//----- Optional commands ----- //
				case "UIDL":
					UIDL(argsText);
					break;

				case "APOP":
					APOP(argsText);
					break;

				case "TOP":
					TOP(argsText);
					break;

				case "AUTH":
					AUTH(argsText);
					break;
										
				default:					
					SendData("-ERR Invalid command\r\n");

					//---- Check that maximum bad commands count isn't exceeded ---------------//
					if(m_BadCmdCount > m_pPOP3_Server.MaxBadCommands-1){
						SendData("421 Too many bad commands, closing transmission channel\r\n");
						return true;
					}
					m_BadCmdCount++;
					//-------------------------------------------------------------------------//

					break;				
			}
			
			return false;
		}

		#endregion


		#region function USER

		private void USER(string argsText)
		{
			/* RFC 1939 7. USER
			Arguments:
				a string identifying a mailbox (required), which is of
				significance ONLY to the server
				
			NOTE:
				If the POP3 server responds with a positive
				status indicator ("+OK"), then the client may issue
				either the PASS command to complete the authentication,
				or the QUIT command to terminate the POP3 session.
			 
			*/

			if(m_Authenticated){
				SendData("-ERR You are already authenticated\r\n");
				return;
			}
			if(m_UserName.Length > 0){
				SendData("-ERR username is already specified, please specify password\r\n");
				return;
			}

			string[] param = argsText.Split(new char[]{' '});

			// There must be only one parameter - userName
			if(argsText.Length > 0 && param.Length == 1){
				string userName = param[0];
							
				// Check if user isn't logged in already
				if(!m_pPOP3_Server.IsUserLoggedIn(userName)){
					SendData("+OK User:'" + userName + "' ok \r\n");
					m_UserName = userName;
				}
				else{
					SendData("-ERR User:'" + userName + "' already logged in\r\n");
				}
			}
			else{
				SendData("-ERR Syntax error. Syntax:{USER username}\r\n");
			}
		}

		#endregion

		#region function PASS

		private void PASS(string argsText)
		{	
			/* RFC 7. PASS
			Arguments:
				a server/mailbox-specific password (required)
				
			Restrictions:
				may only be given in the AUTHORIZATION state immediately
				after a successful USER command
				
			NOTE:
				When the client issues the PASS command, the POP3 server
				uses the argument pair from the USER and PASS commands to
				determine if the client should be given access to the
				appropriate maildrop.
				
			Possible Responses:
				+OK maildrop locked and ready
				-ERR invalid password
				-ERR unable to lock maildrop
						
			*/

			if(m_Authenticated){
				SendData("-ERR You are already authenticated\r\n");
				return;
			}
			if(m_UserName.Length == 0){
				SendData("-ERR please specify username first\r\n");
				return;
			}

			string[] param = argsText.Split(new char[]{' '});

			// There may be only one parameter - password
			if(param.Length == 1){
				string password = param[0];
									
				// Authenticate user
				if(m_pPOP3_Server.OnAuthUser(this,m_UserName,password,"",AuthType.Plain)){					
					m_Password = password;
					m_Authenticated = true;

					// Get user messages info.
					m_pPOP3_Server.OnGetMessagesInfo(this,m_POP3_Messages);

					SendData("+OK Password ok\r\n");
				}
				else{						
					SendData("-ERR UserName or Password is incorrect\r\n");					
					m_UserName = ""; // Reset userName !!!
				}
			}
			else{
				SendData("-ERR Syntax error. Syntax:{PASS userName}\r\n");
			}
		}

		#endregion

		#region function STAT

		private void STAT()
		{	
			/* RFC 1939 5. STAT
			NOTE:
				The positive response consists of "+OK" followed by a single
				space, the number of messages in the maildrop, a single
				space, and the size of the maildrop in octets.
				
				Note that messages marked as deleted are not counted in
				either total.
			 
			Example:
				C: STAT
				S: +OK 2 320
			*/

			if(!m_Authenticated){
				SendData("-ERR You must authenticate first\r\n");
				return;
			}
		
			SendData("+OK " + m_POP3_Messages.Count.ToString() + " " + m_POP3_Messages.GetTotalMessagesSize() + "\r\n");			
		}

		#endregion

		#region function LIST

		private void LIST(string argsText)
		{	
			/* RFC 1939 5. LIST
			Arguments:
				a message-number (optional), which, if present, may NOT
				refer to a message marked as deleted
			 
			NOTE:
				If an argument was given and the POP3 server issues a
				positive response with a line containing information for
				that message.

				If no argument was given and the POP3 server issues a
				positive response, then the response given is multi-line.
				
				Note that messages marked as deleted are not listed.
			
			Examples:
				C: LIST
				S: +OK 2 messages (320 octets)
				S: 1 120				
				S: 2 200
				S: .
				...
				C: LIST 2
				S: +OK 2 200
				...
				C: LIST 3
				S: -ERR no such message, only 2 messages in maildrop
			 
			*/

			if(!m_Authenticated){
				SendData("-ERR You must authenticate first\r\n");
				return;
			}

			string[] param = argsText.Split(new char[]{' '});

			// Argument isn't specified, multiline response.
			if(argsText.Length == 0){									
				SendData("+OK " + m_POP3_Messages.Count.ToString() + " messages\r\n");
			
				// Send message number and size for each message
				foreach(POP3_Message msg in m_POP3_Messages.ActiveMessages){
					SendData(msg.MessageNr.ToString() + " " + msg.MessageSize + "\r\n");
				}

				// ".<CRLF>" - means end of list
				SendData(".\r\n");
			}
			else{
				// If parameters specified,there may be only one parameter - messageNr
				if(param.Length == 1){
					// Check if messageNr is valid
					if(Core.IsNumber(param[0])){
						int messageNr = Convert.ToInt32(param[0]);
						if(m_POP3_Messages.MessageExists(messageNr)){
							POP3_Message msg = m_POP3_Messages[messageNr];

							SendData("+OK " + messageNr.ToString() + " " + msg.MessageSize + "\r\n");
						}
						else{
							SendData("-ERR no such message, or marked for deletion\r\n");
						}
					}
					else{
						SendData("-ERR message-number is invalid\r\n");
					}
				}
				else{
					SendData("-ERR Syntax error. Syntax:{LIST [messageNr]}\r\n");
				}
			}
		}

		#endregion

		#region function RETR

		private void RETR(string argsText)
		{
			/* RFC 1939 5. RETR
			Arguments:
				a message-number (required) which may NOT refer to a
				message marked as deleted
			 
			NOTE:
				If the POP3 server issues a positive response, then the
				response given is multi-line.  After the initial +OK, the
				POP3 server sends the message corresponding to the given
				message-number, being careful to byte-stuff the termination
				character (as with all multi-line responses).
				
			Example:
				C: RETR 1
				S: +OK 120 octets
				S: <the POP3 server sends the entire message here>
				S: .
			
			*/

			if(!m_Authenticated){
				SendData("-ERR You must authenticate first\r\n");
				return;
			}
	
			string[] param = argsText.Split(new char[]{' '});

			// There must be only one parameter - messageNr
			if(argsText.Length > 0 && param.Length == 1){
				// Check if messageNr is valid
				if(Core.IsNumber(param[0])){
					int messageNr = Convert.ToInt32(param[0]);					
					if(m_POP3_Messages.MessageExists(messageNr)){
						POP3_Message msg = m_POP3_Messages[messageNr];

						SendData("+OK " + msg.MessageSize + " octets\r\n");
									
						// Raise Event, request message
						byte[] message = m_pPOP3_Server.OnGetMail(this,msg,m_pClientSocket);
						if(message != null){
							//------- Do period handling and send message -----------------------//
							// If line starts with '.', add additional '.'.(Read rfc for more info)
							using(MemoryStream msgStrm = Core.DoPeriodHandling(message,true)){
								// Send message to client
								SendData(msgStrm);								
							}
							//-------------------------------------------------------------------//															
						}
									
						// "."<CRLF> - means end of message
						SendData(".\r\n");
					}
					else{
						SendData("-ERR no such message\r\n");
					}
				}
				else{
					SendData("-ERR message-number is invalid\r\n");
				}
			}
			else{
				SendData("-ERR Syntax error. Syntax:{RETR messageNr}\r\n");
			}		
		}

		#endregion

		#region function DELE

		private void DELE(string argsText)
		{	
			/* RFC 1939 5. DELE
			Arguments:
				a message-number (required) which may NOT refer to a
				message marked as deleted
			 
			NOTE:
				The POP3 server marks the message as deleted.  Any future
				reference to the message-number associated with the message
				in a POP3 command generates an error.  The POP3 server does
				not actually delete the message until the POP3 session
				enters the UPDATE state.
			*/

			if(!m_Authenticated){
				SendData("-ERR You must authenticate first\r\n");
				return;
			}

			string[] param = argsText.Split(new char[]{' '});

			// There must be only one parameter - messageNr
			if(argsText.Length > 0 && param.Length == 1){
				// Check if messageNr is valid
				if(Core.IsNumber(param[0])){
					int nr = Convert.ToInt32(param[0]);					
					if(m_POP3_Messages.MessageExists(nr)){
						POP3_Message msg = m_POP3_Messages[nr];
						msg.MarkedForDelete = true;

						SendData("+OK marked for delete\r\n");
					}
					else{
						SendData("-ERR no such message\r\n");
					}
				}
				else{
					SendData("-ERR message-number is invalid\r\n");
				}
			}
			else{
				SendData("-ERR Syntax error. Syntax:{DELE messageNr}\r\n");
			}
		}

		#endregion

		#region function NOOP

		private void NOOP()
		{
			/* RFC 1939 5. NOOP
			NOTE:
				The POP3 server does nothing, it merely replies with a
				positive response.
			*/

			if(!m_Authenticated){
				SendData("-ERR You must authenticate first\r\n");
				return;
			}

			SendData("+OK\r\n");
		}

		#endregion

		#region function RSET

		private void RSET()
		{
			/* RFC 1939 5. RSET
			Discussion:
				If any messages have been marked as deleted by the POP3
				server, they are unmarked.  The POP3 server then replies
				with a positive response.
			*/

			if(!m_Authenticated){
				SendData("-ERR You must authenticate first\r\n");
				return;
			}

			Reset();

			// Raise SessionResetted event
			m_pPOP3_Server.OnSessionResetted(this);

			SendData("+OK\r\n");
		}

		#endregion

		#region function QUIT

		private void QUIT()
		{
			/* RFC 1939 6. QUIT
			NOTE:
				The POP3 server removes all messages marked as deleted
				from the maildrop and replies as to the status of this
				operation.  If there is an error, such as a resource
				shortage, encountered while removing messages, the
				maildrop may result in having some or none of the messages
				marked as deleted be removed.  In no case may the server
				remove any messages not marked as deleted.

				Whether the removal was successful or not, the server
				then releases any exclusive-access lock on the maildrop
				and closes the TCP connection.
			*/					
			Update();

			SendData("+OK POP3 server signing off\r\n");			
		}

		#endregion


		//--- Optional commands

		#region function TOP

		private void TOP(string argsText)
		{		
			/* RFC 1939 7. TOP
			Arguments:
				a message-number (required) which may NOT refer to to a
				message marked as deleted, and a non-negative number
				of lines (required)
		
			NOTE:
				If the POP3 server issues a positive response, then the
				response given is multi-line.  After the initial +OK, the
				POP3 server sends the headers of the message, the blank
				line separating the headers from the body, and then the
				number of lines of the indicated message's body, being
				careful to byte-stuff the termination character (as with
				all multi-line responses).
			
			Examples:
				C: TOP 1 10
				S: +OK
				S: <the POP3 server sends the headers of the
					message, a blank line, and the first 10 lines
					of the body of the message>
				S: .
                ...
				C: TOP 100 3
				S: -ERR no such message
			 
			*/

			if(!m_Authenticated){
				SendData("-ERR You must authenticate first\r\n");
				return;
			}

			string[] param = argsText.Split(new char[]{' '});
			
			// There must be at two parameters - messageNr and nrLines
			if(param.Length == 2){
				// Check if messageNr and nrLines is valid
				if(Core.IsNumber(param[0]) && Core.IsNumber(param[1])){
					int messageNr = Convert.ToInt32(param[0]);
					if(m_POP3_Messages.MessageExists(messageNr)){
						POP3_Message msg = m_POP3_Messages[messageNr];

						byte[] lines = m_pPOP3_Server.OnGetTopLines(this,msg,Convert.ToInt32(param[1]));
						if(lines != null){
							SendData("+OK\r\n");
							SendData(lines);
							SendData(".\r\n");
						}
						else{
							SendData("-ERR no such message\r\n");
						}
					}
					else{
						SendData("-ERR no such message\r\n");
					}
				}
				else{
					SendData("-ERR message-number or number of lines is invalid\r\n");
				}
			}
			else{
				SendData("-ERR Syntax error. Syntax:{TOP messageNr nrLines}\r\n");
			}
		}

		#endregion

		#region function UIDL

		private void UIDL(string argsText)
		{
			/* RFC 1939 UIDL [msg]
			Arguments:
			    a message-number (optional), which, if present, may NOT
				refer to a message marked as deleted
				
			NOTE:
				If an argument was given and the POP3 server issues a positive
				response with a line containing information for that message.

				If no argument was given and the POP3 server issues a positive
				response, then the response given is multi-line.  After the
				initial +OK, for each message in the maildrop, the POP3 server
				responds with a line containing information for that message.	
				
			Examples:
				C: UIDL
				S: +OK
				S: 1 whqtswO00WBw418f9t5JxYwZ
				S: 2 QhdPYR:00WBw1Ph7x7
				S: .
				...
				C: UIDL 2
				S: +OK 2 QhdPYR:00WBw1Ph7x7
				...
				C: UIDL 3
				S: -ERR no such message
			*/

			if(!m_Authenticated){
				SendData("-ERR You must authenticate first\r\n");
				return;
			}

			string[] param = argsText.Split(new char[]{' '});

			// Argument isn't specified, multiline response.
			if(argsText.Length == 0){
				SendData("+OK\r\n");

				// List all messages UID's
				foreach(POP3_Message msg in m_POP3_Messages.ActiveMessages){
					SendData(msg.MessageNr.ToString() + " " + msg.MessageID + "\r\n");
				}

				SendData(".\r\n");
			}
			else{
				// If parameters specified,there may be only one parameter - messageID
				if(param.Length == 1){
					// Check if messageNr is valid
					if(Core.IsNumber(param[0])){
						int messageNr = Convert.ToInt32(param[0]);
						if(m_POP3_Messages.MessageExists(messageNr)){
							POP3_Message msg = m_POP3_Messages[messageNr];

							SendData("+OK " + messageNr.ToString() + " " + msg.MessageID + "\r\n");
						}
						else{
							SendData("-ERR no such message\r\n");
						}
					}
					else{
						SendData("-ERR message-number is invalid\r\n");
					}
				}
				else{
					SendData("-ERR Syntax error. Syntax:{UIDL [messageNr]}\r\n");
				}
			}	
		}

		#endregion

		#region function APOP

		private void APOP(string argsText)
		{
			/* RFC 1939 7. APOP
			Arguments:
				a string identifying a mailbox and a MD5 digest string
				(both required)
				
			NOTE:
				A POP3 server which implements the APOP command will
				include a timestamp in its banner greeting.  The syntax of
				the timestamp corresponds to the `msg-id' in [RFC822], and
				MUST be different each time the POP3 server issues a banner
				greeting.
				
			Examples:
				S: +OK POP3 server ready <1896.697170952@dbc.mtview.ca.us>
				C: APOP mrose c4c9334bac560ecc979e58001b3e22fb
				S: +OK maildrop has 1 message (369 octets)

				In this example, the shared  secret  is  the  string  `tan-
				staaf'.  Hence, the MD5 algorithm is applied to the string

				<1896.697170952@dbc.mtview.ca.us>tanstaaf
				 
				which produces a digest value of
		            c4c9334bac560ecc979e58001b3e22fb
			 
			*/

			if(m_Authenticated){
				SendData("-ERR You are already authenticated\r\n");
				return;
			}

			string[] param = argsText.Split(new char[]{' '});

			// There must be two params
			if(param.Length == 2){
				string userName   = param[0];
				string md5HexHash = param[1];

				// Check if user isn't logged in already
				if(m_pPOP3_Server.IsUserLoggedIn(userName)){
					SendData("-ERR User:'" + userName + "' already logged in\r\n");
					return;
				}

				// Authenticate user
				if(m_pPOP3_Server.OnAuthUser(this,userName,md5HexHash,m_MD5_prefix,AuthType.APOP)){
					m_UserName = userName;
					m_Authenticated = true;

					// Get user messages info.
					m_pPOP3_Server.OnGetMessagesInfo(this,m_POP3_Messages);

					SendData("+OK authentication was successful\r\n");
				}
				else{
					SendData("-ERR authentication failed\r\n");
				}
			}
			else{
				SendData("-ERR syntax error. Syntax:{APOP userName md5HexHash}\r\n");
			}
		}

		#endregion

		#region function AUTH

		private void AUTH(string argsText)
		{
			/* Rfc 1734
				
				AUTH mechanism

					Arguments:
						a string identifying an IMAP4 authentication mechanism,
						such as defined by [IMAP4-AUTH].  Any use of the string
						"imap" used in a server authentication identity in the
						definition of an authentication mechanism is replaced with
						the string "pop".
						
					Possible Responses:
						+OK maildrop locked and ready
						-ERR authentication exchange failed

					Restrictions:
						may only be given in the AUTHORIZATION state

					Discussion:
						The AUTH command indicates an authentication mechanism to
						the server.  If the server supports the requested
						authentication mechanism, it performs an authentication
						protocol exchange to authenticate and identify the user.
						Optionally, it also negotiates a protection mechanism for
						subsequent protocol interactions.  If the requested
						authentication mechanism is not supported, the server						
						should reject the AUTH command by sending a negative
						response.

						The authentication protocol exchange consists of a series
						of server challenges and client answers that are specific
						to the authentication mechanism.  A server challenge,
						otherwise known as a ready response, is a line consisting
						of a "+" character followed by a single space and a BASE64
						encoded string.  The client answer consists of a line
						containing a BASE64 encoded string.  If the client wishes
						to cancel an authentication exchange, it should issue a
						line with a single "*".  If the server receives such an
						answer, it must reject the AUTH command by sending a
						negative response.

						A protection mechanism provides integrity and privacy
						protection to the protocol session.  If a protection
						mechanism is negotiated, it is applied to all subsequent
						data sent over the connection.  The protection mechanism
						takes effect immediately following the CRLF that concludes
						the authentication exchange for the client, and the CRLF of
						the positive response for the server.  Once the protection
						mechanism is in effect, the stream of command and response
						octets is processed into buffers of ciphertext.  Each
						buffer is transferred over the connection as a stream of
						octets prepended with a four octet field in network byte
						order that represents the length of the following data.
						The maximum ciphertext buffer length is defined by the
						protection mechanism.

						The server is not required to support any particular
						authentication mechanism, nor are authentication mechanisms
						required to support any protection mechanisms.  If an AUTH
						command fails with a negative response, the session remains
						in the AUTHORIZATION state and client may try another
						authentication mechanism by issuing another AUTH command,
						or may attempt to authenticate by using the USER/PASS or
						APOP commands.  In other words, the client may request
						authentication types in decreasing order of preference,
						with the USER/PASS or APOP command as a last resort.

						Should the client successfully complete the authentication
						exchange, the POP3 server issues a positive response and
						the POP3 session enters the TRANSACTION state.
						
				Examples:
							S: +OK POP3 server ready
							C: AUTH KERBEROS_V4
							S: + AmFYig==
							C: BAcAQU5EUkVXLkNNVS5FRFUAOCAsho84kLN3/IJmrMG+25a4DT
								+nZImJjnTNHJUtxAA+o0KPKfHEcAFs9a3CL5Oebe/ydHJUwYFd
								WwuQ1MWiy6IesKvjL5rL9WjXUb9MwT9bpObYLGOKi1Qh
							S: + or//EoAADZI=
							C: DiAF5A4gA+oOIALuBkAAmw==
							S: +OK Kerberos V4 authentication successful
								...
							C: AUTH FOOBAR
							S: -ERR Unrecognized authentication type
			 
			*/
			if(m_Authenticated){
				SendData("-ERR already authenticated\r\n");
				return;
			}
			
				
			//------ Parse parameters -------------------------------------//
			string userName = "";
			string password = "";

			string[] param = argsText.Split(new char[]{' '});
			switch(param[0].ToUpper())
			{
				case "PLAIN":
					SendData("-ERR Unrecognized authentication type.\r\n");
					break;

				case "LOGIN":

					#region LOGIN authentication

				    //---- AUTH = LOGIN ------------------------------
					/* Login
					C: AUTH LOGIN-MD5
					S: + VXNlcm5hbWU6
					C: username_in_base64
					S: + UGFzc3dvcmQ6
					C: password_in_base64
					
					   VXNlcm5hbWU6 base64_decoded= USERNAME
					   UGFzc3dvcmQ6 base64_decoded= PASSWORD
					*/
					// Note: all strings are base64 strings eg. VXNlcm5hbWU6 = UserName.
			
					
					// Query UserName
					SendData("+ VXNlcm5hbWU6\r\n");

					string userNameLine = ReadLine();
					// Encode username from base64
					if(userNameLine.Length > 0){
						userName = System.Text.Encoding.Default.GetString(Convert.FromBase64String(userNameLine));
					}
						
					// Query Password
					SendData("+ UGFzc3dvcmQ6\r\n");

					string passwordLine = ReadLine();
					// Encode password from base64
					if(passwordLine.Length > 0){
						password = System.Text.Encoding.Default.GetString(Convert.FromBase64String(passwordLine));
					}
																							
					if(m_pPOP3_Server.OnAuthUser(this,userName,password,"",AuthType.Plain)){
						SendData("+OK Authentication successful.\r\n");
						
						m_UserName = userName;
						m_Authenticated = true;

						// Get user messages info.
						m_pPOP3_Server.OnGetMessagesInfo(this,m_POP3_Messages);
					}
					else{
						SendData("-ERR Authentication failed\r\n");
					}

					#endregion

					break;

				case "CRAM-MD5":
					
					#region CRAM-MD5 authentication

					/* Cram-M5
					C: AUTH CRAM-MD5
					S: + <md5_calculation_hash_in_base64>
					C: base64(decoded:username password_hash)
					*/
					
					string md5Hash = "<" + Guid.NewGuid().ToString().ToLower() + ">";
					SendData("+ " + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(md5Hash)) + "\r\n");

					string reply = this.ReadLine();

					reply = System.Text.Encoding.Default.GetString(Convert.FromBase64String(reply));
					string[] replyArgs = reply.Split(' ');
					userName = replyArgs[0];
					
					if(m_pPOP3_Server.OnAuthUser(this,userName,replyArgs[1],md5Hash,AuthType.CRAM_MD5)){
						SendData("+OK Authentication successful.\r\n");
						
						m_UserName = userName;
						m_Authenticated = true;

						// Get user messages info.
						m_pPOP3_Server.OnGetMessagesInfo(this,m_POP3_Messages);
					}
					else{
						SendData("-ERR Authentication failed\r\n");
					}

					#endregion

					break;

				case "DIGEST-MD5":

					/*	string md5Hash1 = "<" + Guid.NewGuid().ToString().ToLower() + ">";
						SendData("334 " + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(md5Hash1)) + "\r\n");

						string reply1 = ReadLine();

						reply1 = System.Text.Encoding.Default.GetString(Convert.FromBase64String(reply1));
						m_pLogWriter.AddEntry(reply1 + "<CRLF>",this.SessionID,m_ConnectedIp,"C");
*/
					// ToDo: can't find any examples ???
					SendData("-ERR Unrecognized authentication type.\r\n");
					break;

				default:
					SendData("-ERR Unrecognized authentication type.\r\n");
					break;
			}
			//-----------------------------------------------------------------//
		}

		#endregion
				

		#region function Reset

		private void Reset()
		{		
			/* RFC 1939 5. RSET
			Discussion:
				If any messages have been marked as deleted by the POP3
				server, they are unmarked.
			*/
			m_POP3_Messages.ResetDeleteFlags();
		}

		#endregion

		#region function Update

		private void Update()
		{
			/* RFC 1939 6.
			NOTE:
				When the client issues the QUIT command from the TRANSACTION state,
				the POP3 session enters the UPDATE state.  (Note that if the client
				issues the QUIT command from the AUTHORIZATION state, the POP3
				session terminates but does NOT enter the UPDATE state.)

				If a session terminates for some reason other than a client-issued
				QUIT command, the POP3 session does NOT enter the UPDATE state and
				MUST not remove any messages from the maildrop.
			*/

			if(m_Authenticated){

				// Delete all message which are marked for deletion ---//
				foreach(POP3_Message msg in m_POP3_Messages.Messages){
					if(msg.MarkedForDelete){
						m_pPOP3_Server.OnDeleteMessage(this,msg);
					}
				}
				//-----------------------------------------------------//
			}
		}

		#endregion


		#region function SendData (3)
			
		private void SendData(string data)
		{
			Byte[] byte_data = System.Text.Encoding.ASCII.GetBytes(data.ToCharArray());
			int size = m_pClientSocket.Send(byte_data,byte_data.Length,0);

			if(m_pPOP3_Server.LogCommands){
				string reply = System.Text.Encoding.ASCII.GetString(byte_data);
				reply = reply.Replace("\r\n","<CRLF>");
				m_pLogWriter.AddEntry(reply,this.SessionID,this.IpStr,"S");
			}
		}

		private void SendData(byte[] data)
		{
			using(MemoryStream strm = new MemoryStream(data)){
				SendData(strm);
			}
		}

		private void SendData(MemoryStream strm)
		{
			//---- split message to blocks -------------------------------//
			long totalSent = 0;
			while(strm.Position < strm.Length){
				int blockSize = 4024;
				byte[] dataBuf = new byte[blockSize];
				int nCount = strm.Read(dataBuf,0,blockSize);
				int countSended = m_pClientSocket.Send(dataBuf,nCount,SocketFlags.None);

				totalSent += countSended;

				if(countSended != nCount){
					strm.Position = totalSent;
				}
			}
			//-------------------------------------------------------------//

			if(m_pPOP3_Server.LogCommands){
				m_pLogWriter.AddEntry("big binary " + strm.Length.ToString() + " bytes",this.SessionID,this.IpStr,"S");
			}
		}

		#endregion		

		#region function ReadLine

		/// <summary>
		/// Reads line from socket.
		/// </summary>
		/// <returns></returns>
		private string ReadLine()
		{
			string line = Core.ReadLine(m_pClientSocket,500,m_pPOP3_Server.CommandIdleTimeOut);
				
			if(m_pPOP3_Server.LogCommands){
				m_pLogWriter.AddEntry(line + "<CRLF>",this.SessionID,this.IpStr,"C");
			}

			return line;
		}

		#endregion


		#region Properties Implementation

		/// <summary>
		/// Gets session ID.
		/// </summary>
		public string SessionID
		{
			get{ return m_SessionID; }
		}

		/// <summary>
		/// Gets session start time.
		/// </summary>
		public DateTime SessionStartTime
		{
			get{ return m_SessionStartTime; }
		}

		/// <summary>
		/// Gets loggded in user name (session owner).
		/// </summary>
		public string UserName
		{
			get{ return m_UserName; }
		}

		/// <summary>
		/// Gets EndPoint which accepted conection.
		/// </summary>
		public EndPoint LocalEndPoint
		{
			get{ return m_pClientSocket.LocalEndPoint; }
		}

		/// <summary>
		/// Gets connected Host(client) EndPoint.
		/// </summary>
		public EndPoint RemoteEndPoint
		{
			get{ return m_pClientSocket.RemoteEndPoint; }
		}
		
		/// <summary>
		/// Gets or sets custom user data.
		/// </summary>
		public object Tag
		{
			get{ return m_Tag; }

			set{ m_Tag = value; }
		}


		/// <summary>
		/// Gets connected Host(client) Ip address.
		/// </summary>
		internal string IpStr
		{
			get{ return Core.ParseIP_from_EndPoint(this.RemoteEndPoint.ToString()); }
		}

		#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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


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