Wednesday, September 13, 2006 2:24 AM bart

Creating a WCF POP3 tunnel - Part 1 - A simple POP3 client in C#

Introduction

In my previous post you learned about this POP3 tunneling idea using WCF. On to the real stuff now, the implementation. In this first implementation-related post, I'm showing you how to create a simple POP3 client in C# based on the POP3 RFC 1939 specification. Others have done this before - I know - but this implementation ...

  • resolves a couple of restrictions other implementations have (such as response length limitations);
  • lives very close to the POP3 specification and doesn't provide unnecessary abstractions and encapsulations;
  • allows for a service-oriented session-based approach using WCF (see later posts).

Where are we?

The diagram below shows the piece of the solution we'll be focusing on in this post:

********************LOCALHOST*******************
*                                              *
*
+-------------+              +-------------+ *
* | Mail client | -(TCP:110)-> | POP3 server | *
* +-------------+              +------+------+ *
*                                     |        *
*                              +------+------+ *
*                              | WCF client  | *
*                              +------+------+ *
*                                     |        *
**************************************|*********
                                      |
                                (HTTP/SOAP:80)
                                      |
******************TUNNEL SERVER*******|*********
*                                     |        *
* +--------------+             +------+------+ *
* | POP3 client  +-----<==-----+ WCF server  | *
* +------+-------+             +-------------+ *
*        |                                     *
*********|**************************************
         |
     (TCP:110)
         |
*********|********TARGET SERVER*****************
*        |                                     *
* +------+-------+                             *
* | POP3 server  +                             *
* +--------------+                             *
*                                              *
************************************************

Legend:

Gray  - External components
Blue  - Implemented in previous posts
Red   - Covered in this post
Green - To be covered in further posts

RFC 1939 in simple words

RFC specifications look painful to dive into but for this particular one about the POP3 protocol things are quite easy to understand. Some background information:

  • POP3 stands for Post Office Protocol version 3.0 and was submitted to IETF in May 1996. The protocol allows to retrieve mail from a user's mailbox on a mailserver. By default, POP3 servers listen on port TCP:110.
  • The basic operation consists of:
    • connecting to the server and waiting for a response from the server;
    • an authentication phase using two commands (USER and PASS);
    • a series of operational commands to obtain statistics, retrieve messages, query administrative data and delete messages;
    • disconnecting from the server after sending a QUIT command.
  • POP3 commands are case-insensitive and are terminated by a CRLF (carriage return line feed) ASCII pair. All communications are in ASCII.
  • A POP3 server response is and ASCII string. Some commands return a single line (terminated by CRLF), whileas others return multiple lines (each one terminated by CRLF; the entire message terminated by a CRLF.CRLF series).
  • There are a couple of states: the authorization state (commands USER, PASS, APOP), the transactional state (commands STAT, LIST, RETR, DELE, NOOP, RSET, TOP, UIDL) and the update state (after QUIT).

The implementation - Pop3Client

Our goal is to provide a simple class that allows for POP3 communication (command/response pairs). Therefore, we won't create encapsulations for a retrieved message or perform advanced parsing on the response.

We define our Pop3Client class as follows:

/// <summary>
///
Simple POP3 client implementation on the RFC 1939 commands level.
///
</summary>
public class Pop3Client :
IDisposable
{
   ...
}

Notice the use of the IDisposable pattern which allows consumers of the class to write this:

using (Pop3Client clnt = new Pop3Client("pop3.mydomain.local"))
{
   //use the POP3 client
}

On to the constructors:

/// <summary>
/// Creates a POP3 client object ready to connect to the specified server on the default POP3 port 110.
/// </summary>
/// <param name="server">POP3 server to connect to.</param>
public
Pop3Client(string
server) : this(server, 110)
{
}

/// <summary>
/// Creates a POP3 client object ready to connect to the specified server on the specified port.
/// </summary>
/// <param name="server">POP3 server to connect to.</param>
/// <param name="port">Port to connect on.</param>
public Pop3Client(string server, int port)
{
   this
.server = server;
   this
.port = port;
}

A few private members are required:

private NetworkStream stream;
private StreamReader
sr;

private string
server;
private int
port;

private ASCIIEncoding encoding = new ASCIIEncoding();

The stream and sr variables will be initialized by the Connect method:

/// <summary>
/// Establishes a connection with the POP3 server.
/// </summary>
/// <returns>POP3 response from the server.</returns>
public
Pop3Response Connect()
{
   TcpClient client = new TcpClient
(server, port);
   stream = client.GetStream();
   sr =
new StreamReader
(stream);
   return new Pop3Response
(Receive());
}

Time to introduce a couple of helper methods:

/// <summary>
///
Helper method to check the connection state. Throws an InvalidOperationException if the POP3 client isn't connected to the specified server.
///
</summary>
private void
CheckState()
{
   if (stream == null
)
      throw new InvalidOperationException("No connection was made to the server."
);
}

///
<summary>
///
Sends one line of data to the POP3 server.
///
</summary>
/// <param name="message">Message to be sent to the POP3 server.
</param>
private void Send(string
message)
{
   byte
[] b = encoding.GetBytes(message);
   stream.Write(b, 0, b.Length);
}

///
<summary>
///
Receives one line of data from the POP3 server.
///
</summary>
/// <returns>One line of data received from the POP3 server.
</returns>
/// <remarks>A line of data ends with \r\n.
</remarks>
private string
Receive()
{
   return
sr.ReadLine();
}

To have some basic encapsulation of a POP3 server response, we introduce the Pop3Response class:

/// <summary>
///
Represents a POP3 response received from a POP3 server.
///
</summary>
public class
Pop3Response
{
   private bool
success;
   private string
message;

   ///
<summary>
  
///
Creates a POP3 response object based on the server's response message.
   ///
</summary>
   /// <param name="message">POP3 response message received from a POP3 server.
</param>
   internal Pop3Response(string
message)
   {
      this.success = message.StartsWith("+OK"
);
      this
.message = message;
   }

  
///
<summary>
  
///
Gets the success state of the POP3 request associated with this POP3 response.
   ///
</summary>
   public bool
Success
   {
      get { return
success; }
      internal set { success = value
; }
   }

   ///
<summary>
   ///
Gets the full text POP3 response message sent by the server.
   ///
</summary>
   public string
Message
   {
      get { return
message; }
      internal set { message = value
; }
   }
}

Back to the Pop3Client class for the Close/Dispose stuff:

/// <summary>
///
Closes the connection to the POP3 server.
///
</summary>
public void
Close()
{
   if (sr != null
)
      sr.Close();
   if (stream != null
)
      stream.Close();
}

///
<summary>
///
Closes the connection to the POP3 server.
///
</summary>
/// <remarks>Equivalent to Close.
</remarks>
public void
Dispose()
{
   Close();
}

Time to implement the POP3 commands. For each defined POP3 command, we'll define a separate method with the required arguments. All of these methods will return an instance of the Pop3Response class:

public Pop3Response User(string user)
{
   return SendMessage("USER "
+ user);
}

public Pop3Response Pass(string
password)
{
   return SendMessage("PASS "
+ password);
}

public Pop3Response
Noop()
{
   return SendMessage("NOOP"
);
}

public Pop3Response
Rset()
{
   return SendMessage("RSET"
);
}

public Pop3Response
Stat()
{
   return SendMessage("STAT"
);
}

public Pop3Response
List()
{
   return SendMessage2("LIST"
);
}

public Pop3Response List(uint
msg)
{
   return SendMessage("LIST "
+ msg);
}

public Pop3Response Retr(uint msg)
{
   return SendMessage2("RETR "
+ msg);
}

public Pop3Response Dele(uint
msg)
{
   return SendMessage("DELE "
+ msg);
}

public Pop3Response Top(uint msg, uint
n)
{
   return SendMessage2("TOP " + msg + " "
+ n);
}

public Pop3Response
Uidl()
{
   return SendMessage2("UIDL"
);
}

public Pop3Response Uidl(uint
msg)
{
   return SendMessage("UIDL "
+ msg);
}

public Pop3Response Apop(string name, string
digest)
{
   return SendMessage("APOP " + name + " "
+ digest);
}

public
Pop3Response
Quit()
{
  
return SendMessage("QUIT"
);
}

These methods rely on two helper methods. The first one, SendMessage, is used for single-line response answers and is defined as follows:

/// <summary>
///
Sends a message to the server and receives the server's one-line response message.
///
</summary>
/// <param name="message">Message to be sent to the server (request).
</param>
/// <returns>POP3 response from the server.
</returns>
/// <remarks>The message will be terminated with \r\n by the method before sending.
</remarks>
private Pop3Response SendMessage(string
message)
{
   CheckState();

   Send(message +
"\r\n"
);
   return new Pop3Response
(Receive());
}

The second one, SendMessage2, is used for multi-line response answers and is slightly more complex:

/// <summary>
///
Sends a message to the server and receives the server's multiline response message.
///
</summary>
/// <param name="message">Message to be sent to the server (request).
</param>
/// <returns>POP3 response from the server.
</returns>
/// <remarks>The message will be terminated with \r\n by the method before sending.
</remarks>
private Pop3Response SendMessage2(string
message)
{
   CheckState();

   Send(message +
"\r\n"
);

   StringBuilder sb = new StringBuilder
();
   string
s;
   do
   {
      s = Receive();
      sb.Append(s + (s !=
"." ? "\r\n" : ""
));
   }
while (s != "."
);

   return new Pop3Response
(sb.ToString());
}

Testing the Pop3Client

A simple test for our Pop3Client implementation is displayed below. It assumes you have access to some server with a given user name and password and you have at least one mail message:

string server = "foo.mydomain.com";
string user = "bart"
;
string pass = "somesecret"
;

using (Pop3Client clnt = new Pop3Client
(server))
{
   Console
.WriteLine(clnt.Connect().Message);
   Console
.WriteLine(clnt.User(user).Message);
   Console
.WriteLine(clnt.Pass(pass).Message);
   Console
.WriteLine(clnt.Noop().Message);
   Console
.WriteLine(clnt.Stat().Message);
   Console
.WriteLine(clnt.List().Message);
   Console
.WriteLine(clnt.List(1).Message);
   Console
.WriteLine(clnt.Uidl().Message);
   Console
.WriteLine(clnt.Uidl(1).Message);
   Console
.WriteLine(clnt.Retr(1).Message);
   Console
.WriteLine(clnt.Dele(1).Message);
   Console
.WriteLine(clnt.Rset().Message);
   Console
.WriteLine(clnt.Quit().Message);
}

The output should be something like this:

+OK Microsoft Exchange Server 2003 POP3 server version 6.5.7638.1 (foo.mydomain.local) ready.
+OK
+OK User successfully logged on.
+OK
+OK 3 22103
+OK 3 22103
1 2387
2 15836
3 3880
.
+OK 1 2387
+OK
1 AAwvNMJAAAA8tsJnRVv8gCE5TwfxKUC1
2 AAQwNMJAAAA8tsJnRVv8gCE5TwfxKUC1
3 AAAwNMJAAAA8tsJnRVv8gCE5TwfxKUC1
.
+OK 1 AAwvNMJAAAA8tsJnRVv8gCE5TwfxKUC1
+OK
Received: from smtp.mydomain.local ([123.234.123.234]) by smtp.mydomain.local with Microsoft SMTPSVC(6.0.3790.1830);
  Fri, 8 Sep 2006 13:55:28 +0200
Received: from smtp.baldrick.local ([111.222.100.100])
 by smtp.mydomain.local (Postfix) with ESMTP id 6E3C6E0202ED
 for <
bar@mydomain.local>; Fri,  8 Sep 2006 13:57:35 +0200 (CEST)
Received: from [10.0.0.52] (edmund.baldrick.local [111.222.135.246])
 by smtp.baldrick.local (Postfix) with ESMTP id 10C08D418A
 for <
bar@mydomain.local>; Fri,  8 Sep 2006 13:57:28 +0200 (CEST)
Message-ID: <
45015AA9.5020202@baldrick.local>
Date: Fri, 08 Sep 2006 13:57:29 +0200
From: Edmund Blackadder <
edmund.blackadder@baldrick.local>
User-Agent: Melchett 1.2.3.4
MIME-Version: 1.0
To: Bar at Foo <
bar@mydomain.local>
Subject: The quick brown fox jumps over the lazy dog.
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 8bit
Return-Path:
edmund.blackadder@baldrick.local
X-OriginalArrivalTime: 08 Sep 2006 11:55:28.0048 (UTC) FILETIME=[AD4B0B00:01C6D33D]

Bart

We're about as similar as two completely dissimilar things in a pod.

Kind regards,
Edmund
.
+OK
+OK
+OK Microsoft Exchange Server 2003 POP3 server version 6.5.7638.1 signing off.

Download the code

You can download the code for this simple command-oriented POP3 client over here.

All usual disclaimers apply. The writer of this blog doesn't make any warranties or guarantuees about the quality of this software, and isn't responsible for possible information loss under any circumstance whatsoever. The code on this blog post and in the download are made available for demonstration purposes only.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# re: Creating a WCF POP3 tunnel - Part 1 - A simple POP3 client in C#

Thursday, September 14, 2006 7:05 PM by Alexey

Cool. Wait continue!!!
Thank you!!!

# Creating a WCF POP3 tunnel - Part 3 - A look on the client-side

Introduction In the introductory post of this blog series, I explained our mission statement: the creation...