A POP3 Client



A POP3 Client

Now that you know everything there is to know about sending SMTP messages using C#, you might want to know how to retrieve messages from mailboxes.

As mentioned in the “E-mail Basics” section, the MUA program is responsible for allowing remote clients to read messages stored in their mailboxes. The most common network protocol used to retrieve messages from remote mail servers is the Post Office Protocol, version 3 (POP3).

This section describes how POP3 works and then presents a sample program that allows a customer to read mail messages stored on a remote mail server.

The POP3 Protocol

POP3 is a command-based protocol. Once the client establishes a connection with the server, it issues simple text commands, and the server responds with the appropriate information. You need to know which commands are used, and in what order, to properly retrieve messages from the server.

POP3 servers listen to TCP port 110 for incoming connection attempts. When a new connection is established, the POP3 server sends a welcome banner and waits for the client to send commands.

Authentication Commands

The first step (after the connection is established) is to log in to the POP3 server, using one of these methods:

  • Sending a plain text user ID and password

  • Sending an encrypted authentication token

Using the encrypted authentication token method is the more secure way of connecting to the server, but that technique is way beyond the scope of this simple example—unless you know how to easily implement the MD-5 encryption algorithm! For this example, we will use the plain text user ID and password method. This is done using the USER and PASS commands:

USER username
PASS password

Each command is entered separately and should receive a response from the server. All POP3 server responses are preceded with either +OK for a positive acknowledgement, or -ERR for a negative acknowledgement.

You can manually test the login sequence with a POP3 server by telnetting to port 110 and entering the appropriate commands:

C:\>telnet 192.168.1.100 110
+OK QPOP (version 2.53) at shadrach.ispnet1.net starting.
<6352.1030035698@shadrach.ispnet1.net>
USER rich
+OK Password required for rich.
PASS myaccount1
+OK rich has 2 messages (889 octets).
QUIT
+OK Pop server at shadrach.ispnet1.net signing off.
C:\>

In this case, each command received a positive response from the POP3 server. When the PASS command was acknowledged, the response message also included the number of messages in the mailbox. Unfortunately, this text is specific to POP3 server software and cannot be relied upon to check for the number of messages available.

Information Commands

The STAT and LIST commands can retrieve information about the mailbox. The STAT command response looks like this:

+OK n mmm

This response includes the number of messages contained in the mailbox (n), and the total size (in bytes) of the messages (mmm).

The LIST command retrieves an individual listing of the messages, along with the size of each message. Here’s what these commands look like:

STAT
+OK 2 889
LIST
+OK 2 messages (889 octets)
1 464
2 425
.

Note that the end of the LIST response is a single period on a line by itself. This indicates the end of the listing has been reached.

Retrieving Messages

Two commands are useful in retrieving message text from the mailbox: TOP and RETR.

Although the TOP command is an optional command, it is supported by most e-mail servers. It can retrieve the message header and a selected number of text lines from the message body. The format of the TOP command is as follows, where n is the message number (as shown in the LIST command results) and mm is the number of body text lines to display:

TOP n mm

If you only need to retrieve the message header, you can specify 0 as the second parameter:

TOP 1 0
+OK 464 octets
Return-Path: rich
Received: (from root@localhost)
   by shadrach.ispnet1.net (8.9.3/8.9.3) id KAA06215
   for rich; Thu, 22 Aug 2002 10:32:34 -0500 (EST)
   (envelope-from rich)
Date: Thu, 22 Aug 2002 10:32:34 -0500 (EST)
From: Rich Blum <rich>
Message-Id: <200208221532.KAA06215@shadrach.ispnet1.net>
To: rich
Subject: Another test message
X-UIDL: 5291f9ae56f2e88b4f7acdc5511c4ae9
Status: RO
.

First, the POP3 server returns with a positive acknowledgement statement (+OK). Most servers also return the number of bytes in the mail message. Next the headers for the mail message are returned. Again, a period delineates the end of the response text.

To retrieve the entire message, you use the RETR command. The RETR command uses one parameter, the number of the message to retrieve:

RETR 1
+OK 464 octets
Return-Path: rich
Received: (from root@localhost)
  by shadrach.ispnet1.net (8.9.3/8.9.3) id KAA06215
  for rich; Thu, 22 Aug 2002 10:32:34 -0500 (EST)
  (envelope-from rich)
Date: Thu, 22 Aug 2002 10:32:34 -0500 (EST)
From: Rich Blum <rich>
Message-Id: <200208221532.KAA06215@shadrach.ispnet1.net>
To: rich
Subject: Another test message
X-UIDL: 5291f9ae56f2e88b4f7acdc5511c4ae9
Status: RO
This is a test message to test the popcheck program with.
.

Similar to the TOP command, the RETR command returns the total number of bytes in the mail message and the text of the entire mail message.

When the POP3 session is finished, you should send the QUIT command to the server to properly terminate the session, and close the mailbox.

Warning 

Be careful when retrieving messages that contain binary attachments. The message will be returned in its original text format, including the text encoding of the binary attachment.

Writing a POP3 Client

Now that you are armed with the POP3 information, you can write a simple network client program to check a remote POP3 mailbox and retrieve some information about the messages in it. Listing 13.4 is the PopCheck.cs program, which demonstrates some of these basic principles.

Listing 13.4: The PopCheck.cs program
Start example
using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
class PopCheck       Form
{
  private TextBox hostname;
  private TextBox username;
  private TextBox password;
  private TextBox status;
  private ListBox messages;
  private TcpClient mailclient;
  private NetworkStream ns;
  private StreamReader sr;
  private StreamWriter sw;
  public PopCheck()
  {
   Text = "popcheck - A POP3 e-mail checker";
   Size = new Size(400, 380);
   Label label1 = new Label();
   label1.Parent = this;
   label1.Text = "Hostname:";
   label1.AutoSize = true;
   label1.Location = new Point(10, 33);
   hostname = new TextBox();
   hostname.Parent = this;
   hostname.Size = new Size(200, 2 * Font.Height);
   hostname.Location = new Point(75, 30);
   Label label2 = new Label();
   label2.Parent = this;
   label2.Text = "User name:";
   label2.AutoSize = true;
   label2.Location = new Point(10, 53);
   username = new TextBox();
   username.Parent = this;
   username.Size = new Size(200, 2 * Font.Height);
   username.Location = new Point(75, 50);
   Label label3 = new Label();
   label3.Parent = this;
   label3.Text = "Password:";
   label3.AutoSize = true;
   label3.Location = new Point(10, 73);
   password = new TextBox();
   password.Parent = this;
   password.PasswordChar = '*';
   password.Size = new Size(200, 2 * Font.Height);
   password.Location = new Point(75, 70);
   Label label4 = new Label();
   label4.Parent = this;
   label4.Text = "Status:";
   label4.AutoSize = true;
   label4.Location = new Point(10, 325);
  
   status = new TextBox();
   status.Parent = this;
   status.Text = "Not connected";
   status.Size = new Size(200, 2 * Font.Height);
   status.Location = new Point(50, 322);
   messages = new ListBox();
   messages.Parent = this;
   messages.Location = new Point(10, 108);
   messages.Size = new Size(360, 16 * Font.Height);
   messages.DoubleClick += new EventHandler(getmessagesDoubleClick);
   Button login = new Button();
   login.Parent = this;
   login.Text = "Login";
   login.Location = new Point(295, 32);
   login.Size = new Size(5 * Font.Height, 2 * Font.Height);
   login.Click += new EventHandler(ButtonloginOnClick);
   Button close = new Button();
   close.Parent = this;
   close.Text = "Close";
   close.Location = new Point(295, 62);
   close.Size = new Size(5 * Font.Height, 2 * Font.Height);
   close.Click += new EventHandler(ButtoncloseOnClick);
  }
  void ButtonloginOnClick(object obj, EventArgs ea)
  {
   status.Text = "Checking for messages...";
   Thread startlogin = new Thread(new ThreadStart(loginandretr));
   startlogin.IsBackground = true;
   startlogin.Start();
  }
  void ButtoncloseOnClick(object obj, EventArgs ea)
  {
   if (ns != null)
   {
     sw.Close();
     sr.Close();
     ns.Close();
     mailclient.Close();
   }
   Close();
  }
  void loginandretr()
  {
   string response;
   string from = null;
   string subject = null;
   int totmessages;
   try
   {
     mailclient = new TcpClient(hostname.Text, 110);
   } catch (SocketException)
   {
     status.Text = "Unable to connect to server";
     return;
   }
   ns = mailclient.GetStream();
   sr = new StreamReader(ns);
   sw = new StreamWriter(ns);
   response = sr.ReadLine(); //Get opening POP3 banner
   sw.WriteLine("User " + username.Text); //Send username
   sw.Flush();
   response = sr.ReadLine();
   if (response.Substring(0,3) == "-ER")
   {
     status.Text = "Unable to log into server";
     return;
   }
   sw.WriteLine("Pass " + password.Text); //Send password
   sw.Flush();
   try
   {
     response = sr.ReadLine();
   } catch (IOException)
   {
     status.Text = "Unable to log into server";
     return;
   }
   if (response.Substring(0,4) == "-ERR")
   {
     status.Text = "Unable to log into server";
     return;
   }
   sw.WriteLine("stat"); //Send stat command to get number of messages
   sw.Flush();
   response = sr.ReadLine();
   string[] nummess = response.Split(' ');
   totmessages = Convert.ToInt16(nummess[1]);
   if (totmessages > 0)
   {
     status.Text = "you have " + totmessages + " messages";
   } else
   {
     status.Text = "You have no messages" ;
   }
   for (int i = 1; i <= totmessages; i++)
   {
     sw.WriteLine("top " + i + " 0"); //read header of each message
     sw.Flush();
     response = sr.ReadLine();
     while (true)
     {
      response = sr.ReadLine();
      if (response == ".")
        break;
      if (response.Length > 4)
      {
        if (response.Substring(0, 5) == "From:")
         from = response;
        if (response.Substring(0, 8) == "Subject:")
         subject = response;
      }
     }
     messages.Items.Add(i + " " + from + " " + subject);
   }
  }
  void getmessagesDoubleClick(object obj, EventArgs ea)
  {
   string text = (string)messages.SelectedItem;
   string[] textarray = text.Split(' ');
   ShowMessage sm = new ShowMessage(ns, textarray[0]);
   sm.ShowDialog();
  }
  public static void Main()
  {
   Application.Run(new PopCheck());
  }
}
class ShowMessage : Form
{
  public ShowMessage(NetworkStream ns, string messnumber)
  {
   StreamReader sr = new StreamReader(ns);
   StreamWriter sw = new StreamWriter(ns);
   string response;
   Text = "Message " + messnumber;
   Size = new Size(400, 380);
   ShowInTaskbar = false;
   TextBox display = new TextBox();
   display.Parent = this;
   display.Multiline = true;
   display.Dock = DockStyle.Fill;
   display.ScrollBars = ScrollBars.Both;
   sw.WriteLine("retr " + messnumber); //Retrieve entire message
   sw.Flush();
   response = sr.ReadLine();
   while (true)
   {
     response = sr.ReadLine();
     if (response == ".")
      break;
     display.Text += response + "\r\n";
   }
  }
}
End example

The PopCheck program implements many of the network programming techniques demonstrated in previous chapters. Figure shows what the PopCheck window should look like in action.

Click To expand
Figure: The PopCheck program interface

After the customer enters a hostname, username, and password in the appropriate text boxes, they click the Login button. This starts a Thread object that uses the loginandretr() method to log in to the specified remote POP3 server with the designated username and password. After the user is logged in, the STAT command determines the number of messages in the mailbox, and the TOP command retrieves the header information for each message.

The message number, along with the From and Subject header field information, is placed in the list box. The customer can then click an individual message listed in the list box to display the entire message.

PopCheck.cs is a little different from other programs presented so far, in that it includes a second class definition in the same source file. This second class defines a DialogBox window that displays the retrieved message text. The main program passes the connected NetworkStream object, along with the desired message number, to the DialogBox class. This information is used to send a RETR command to the POP3 server and retrieve the message text. Because the already-connected NetworkStream object is passed, the DialogBox does not need to create a new connection to the server.

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows