Simple SMTP Client

In the Network Services course at the college I had to write a pseudo code to demonstrate how SMTP clients communicate with SMTP servers to send emails over SMTP protocol.

Since I hate writing a pseudo code for such simple application I’ve decided to use C# .NET

Notes:

  1. The SMTP server is set up on a virtual machine, that explains the “192.168.184.2”
  2. The SMTP clients doesn’t support secure communications for SMTP authentication, it was meant to be as simple as possible.
  3. I use Regular Expressions to run some validations and do Dot Stuffing that is required by SMTP if your Content-Transfer-Encoding was set to “7bit”

Here is the code:


using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;

namespace Simple_SMTP_Client
{
 class Simple_SMTP_Client
 {
 private Socket socket;

 static void Main(string[] args)
 {
 Console.WriteLine("Simple SMTP Client - by Xacker\r\n");
 Simple_SMTP_Client s = new Simple_SMTP_Client();
 s.Start();
 Console.WriteLine("\nPress ENTER to continue...");
 Console.ReadLine();
 }

 public void Start()
 {
 try
 {
 // set up default SMTP server & port settings
 string server = "192.168.184.2";
 string port = "25";

 // ask the user for mail composition elements
 Console.Write("From: ");
 string mailFrom = Console.ReadLine().Trim();
 if (!IsEmailOK(mailFrom))
 {
 Console.WriteLine("Invalid email address detected!");
 return;
 }
 Console.Write("To: ");
 string sendTo = Console.ReadLine().Trim();
 if (!IsEmailOK(sendTo))
 {
 Console.WriteLine("Invalid email address detected!");
 return;
 }
 Console.Write("Subject: ");
 string subject = Console.ReadLine().Trim();
 Console.WriteLine("Message: (end with <CRLF>.<CRLF>)");
 string data = "";
 while (!data.EndsWith("\r\n.\r\n"))
 {
 data += Console.ReadLine();
 data += "\r\n";
 }

 if (!IsPrintableASCII(data))
 {
 Console.WriteLine("Message cannot contain non-printable ASCII characters!");
 return;
 }

 // dot stuffing
 data = Regex.Replace(data, "^[.]([^\r\n])", "..$1", RegexOptions.Multiline);

 string message = "";
 message += "From: <" + mailFrom + ">\r\n";
 message += "To: <" + sendTo + ">\r\n";
 message += "MIME-Version: 1.0\r\n";
 message += "Content-Type: text/plain; charset=\"us-ascii\"\r\n";
 message += "Content-Transfer-Encoding: 7bit\r\n";
 message += "Subject: " + subject + "\r\n\r\n";
 message += data;

 // create the new secure socket
 socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 // connect to the SMTP server
 socket.Connect(new IPEndPoint(Dns.GetHostEntry(server).AddressList[0], int.Parse(port)));

 // wait for the server hello message
 if (!IsReplyOK(Receive()))
 {
 Console.WriteLine("Server disallowed socket.");
 return;
 }

 string host = IPGlobalProperties.GetIPGlobalProperties().HostName;
 if (host == "")
 host = "myClient.svuonline.org";

 // issue a EHLO command. If reply wasn't 250 then stick to standard HELO command.
 Send("EHLO " + host + "\r\n");
 if (!IsReplyOK(Receive()))
 {
 Send("HELO " + host + "\r\n");
 if (!IsReplyOK(Receive()))
 {
 Console.WriteLine("HELLO failed.");
 return;
 }
 }

 // from here on, act as if the SecureSocket is a normal Socket
 Send("MAIL FROM: " + mailFrom + "\r\n");
 if (!IsReplyOK(Receive()))
 {
 Console.WriteLine("MAIL FROM failed.");
 return;
 }
 Send("RCPT TO: " + sendTo + "\r\n");
 if (!IsReplyOK(Receive()))
 {
 Console.WriteLine("RCPT TO failed.");
 return;
 }
 Send("DATA\r\n");
 if (!IsReplyOK(Receive()))
 {
 Console.WriteLine("DATA failed.");
 return;
 }
 Send(message);
 if (!IsReplyOK(Receive()))
 {
 Console.WriteLine("Sending of e-mail failed.");
 return;
 }
 Send("QUIT\r\n");
 if (!IsReplyOK(Receive()))
 {
 Console.WriteLine("QUIT failed.");
 }
 socket.Shutdown(SocketShutdown.Both);

 }
 catch (Exception e)
 {
 Console.WriteLine("An error occurred [" + e.Message + "]");
 Console.WriteLine(e);
 }
 finally
 {
 if (socket != null)
 {
 socket.Close();
 }
 }
 }

 // send data to the SMTP server
 protected void Send(string data)
 {
 Console.WriteLine(data.TrimEnd());
 byte[] toSend = Encoding.ASCII.GetBytes(data);
 int sent = socket.Send(toSend);
 while(sent != toSend.Length)
 {
 sent += socket.Send(toSend, sent, toSend.Length - sent, SocketFlags.None);
 }
 }

 // receive a reply from the SMTP server
 protected string Receive()
 {
 string reply = "";
 byte[] buffer = new byte[1024];
 int ret = socket.Receive(buffer);
 while(ret > 0)
 {
 reply += Encoding.ASCII.GetString(buffer, 0, ret);
 if (IsComplete(reply))
 break;
 ret = socket.Receive(buffer);
 }
 return reply;
 }

 // check whether the reply of the server is complete
 protected bool IsComplete(string reply)
 {
 string[] parts = reply.Replace("\r\n", "\n").Split('\n');
 if (parts.Length > 1 && ((parts[parts.Length - 2].Length > 3 && parts[parts.Length - 2].Substring(3, 1).Equals(" ")) || (parts[parts.Length - 2].Length == 3)))
 return true;
 else
 return false;
 }

 // check whether the reply of the server is positive
 protected bool IsReplyOK(string reply)
 {
 Console.Write(reply);
 try
 {
 int replyNumber = Int32.Parse(reply.Substring(0, 3)) / 100;
 if (replyNumber == 2 || replyNumber == 3)    // examine first digit in reply number, 2xx or 3xx are positive answers
 return true;
 }
 catch {}
 return false;
 }

 // check whether the email addresses entered are correct
 protected bool IsEmailOK(string email)
 {
 // RFC2822: http://rfc.net/rfc2822.html
 string MatchEmailPattern = @"^(([\w-]+\.)+[\w-]+|([a-zA-Z]{1}|[\w-]{2,}))@"
 + @"((([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\.([0-1]?
 [0-9]{1,2}|25[0-5]|2[0-4][0-9])\."
 + @"([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\.([0-1]?
 [0-9]{1,2}|25[0-5]|2[0-4][0-9])){1}|"
 + @"([a-zA-Z]+[\w-]+\.)+[a-zA-Z]{2,4})$";
 return Regex.IsMatch(email, MatchEmailPattern);
 }

 // check whether the email message contains any non-printable ASCII characters
 protected bool IsPrintableASCII(string data)
 {
 return Regex.IsMatch(data, "[^\x20-\x7E]");
 }
 }
}

Enjoy.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: