Friday, September 15, 2006 12:21 AM bart

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 of a POP3 tunnel over SOAP/HTTP using WCF. The first part of the implementation was covered in part 1 of this blog series: "A simple POP3 client in C#". In part 2 we exposed the POP3 client using a WCF service. In this final episode, we leave the server side and enter the scene of the client, where we need a WCF client as well as a little local POP3 server to capture POP3 commands and translate these into calls to the WCF service. This realizes our SOAP-based and firewall-friendly tunnel.

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

A WCF service proxy

On the client-side, we need two things: a POP3 server and a WCF service client. To keep things easy, we'll host these two in a simple Console Application.

If we want to talk to our WCF service, we'll need a service proxy on the client-side. So, let's kick off by generating one using the Visual Studio 2005 built-in support that's added by the .NET Framework 3.0 tool support setup. To do this, go to the Solution Explorer, right click the project node and choose "Add Service Reference" as depicted below:

In order to do this, make sure you're running the WCF service host application we built in part 2. Also make sure the address of the service is spelled correctly to reflect the address you've chosen when implementing the part 2 stuff (normally it should be just fine as it is).

A lightweight POP3 server

Time to start the implementation effort for the POP3 server. This lightweight POP3 server will run on the client's machine and take in all POP3 commands that have to be sent to the real target POP3 server. Instead of having the mail client talk to the target POP3 server directly (e.g. foo.mydomain.local:110) we'll make the mail client talk to localhost:110 instead. The POP3 server on the local machine will then establish a session with the target POP3 server by calling the Connect method first (e.g. Connect("foo.mydomain.local", 110)):

  • Mail client software connects to localhost:110.
  • Local POP3 server connects to the WCF service and sends Connect("foo.mydomain.local", 110) - this establishes a session on the WCF service.
  • WCF service establishes a connection with the target POP3 server "foo.mydomain.local".

From this point on, POP3 commands can flow between the mail client software and the target POP3 server using the HTTP/SOAP tunnel.

  • Mail client software sends "USER bart" to localhost:110.
  • Local POP3 server receives the "USER bart" command and parses it in order to make a User("Bart") call on the WCF service.
  • WCF service rebuilds the original "USER bart" call upon receiving the User("Bart") method call and sends it to the target POP3 server "foo.mydomain.local".

Let's start with the beginning: the class skeleton:

public class Pop3Server
{
   private TcpListener
listener;
   private Thread
tListener;
   private string
url;

   public Pop3Server(int port, string
url)
   {
      listener =
new TcpListener(IPAddress
.Loopback, port);
      this
.url = url;
   }
}

Next, add two methods to start and stop the local POP3 server:

public void Start()
{
   listener.Start();
   tListener =
new Thread(new ThreadStart
(Listen));
   tListener.Start();
}

public
void Stop()
{
   tListener.Abort();
   while (tListener.ThreadState != ThreadState
.Aborted)
      ;
   listener.Stop();
}

To do this, we just use some quick-n-dirty threading stuff. A cleaner approach can be taken to stop the server more smoothly in order to close communications with the server in a friendly way. However, for demonstration purposes this approach should be okay. Here's the Listen method that acts as the server's background listening thread:

private void Listen()
{
   while (true
)
   {
      TcpClient
client = listener.AcceptTcpClient();
      new Thread(new ParameterizedThreadStart
(StartSession)).Start(client);
   }
}

Basically, this method sits and wait for incoming client connections (i.e. connections made from the mail client software to localhost:110). When a connection is made, a background session thread is started, passing in the TcpClient object as a parameter through the ParameterizedThreadStart delegate (new in .NET 2.0):

private void StartSession(object state)
{
   TcpClient client = state as TcpClient
;
   if (client != null
)
      new Pop3Session
(client).Start();
}

Time has come to start the real work: the Pop3Session class. I've declared this class as a nested private class inside the Pop3Server class. Let's start by examining the class' skeleton:

private class Pop3Session
{
   private const string server = "foo.mydomain.local"
;
   private const int
port = 110;

   private ASCIIEncoding encoding = new ASCIIEncoding();

   private NetworkStream
stream;
   private StreamReader
sr;
   private localhost.Pop3TunnelServiceClient
svc;

   public Pop3Session(TcpClient
client)
   {
      this
.stream = client.GetStream();
      this.sr = new StreamReader
(stream);

      svc =
new localhost.Pop3TunnelServiceClient
();
   }
}

Notice that we've hardcoded the target server and port that we want our WCF service session to connect to. In reality, you'll obtain this information through configuration (app.config, the registry, whatever you want) and maybe pass it to the Pop3Session from the Pop3Server upon creation of a session.

Basically, this class has two faces. The first one is the TCP socket one, supported by the stream member. The second one is the WCF client one, supported by the svc proxy instance. As long as the local POP3 session lasts, the WCF service session keeps alive too.

On to the Start method which contains the main loop that waits for commands sent by the local mail client software to the localhost:110 local POP3 server:

public void Start()
{
   string
result = svc.Connect(server, port);
   Write(result);

  
for
(; ; )
      if
(!ExecuteCommand(sr.ReadLine()))
         break
;

   sr.Close();
   stream.Close();
}

The first thing the Start method does (which is - for the record - called on a separate thread by the Pop3Server's Listen connection listening loop) is connecting to the target server by calling the svc.Connect method. This method starts a WCF session; recall the WCF service contract defined in part 2:

   [OperationContract(IsInitiating = true)]
   string Connect(string server, int
port);

Next, we start to wait for POP3 commands sent by the local mail client. POP3 commands are straightforward to take in because they come in as single-line strings, terminated by a CRLF (\r\n) pair. Therefore, the ReadLine method of the StreamReader attached to the NetworkStream will just do its job fine.

Every command is then forwarded to the ExecuteCommand helper method, defined below:

private bool ExecuteCommand(string command)
{
   if (command == null
|| command.Length < 3)
   {
      SendError();
      return true
;
   }

   string[] cmds = command.Split(' '
);
   if
(cmds.Length == 0)
   {
      SendError();
      return true
;
   }

   switch
(cmds[0].ToUpper())
   {
      case "USER"
:
         if
(cmds.Length != 2)
            SendError();
         else
            Write(svc.User(cmds[1]));
         break
;
      case "PASS"
:
         if
(cmds.Length != 2)
            SendError();
         else
            Write(svc.Pass(cmds[1]));
         break
;
      case "STAT"
:
         if
(cmds.Length != 1)
            SendError();
         else
            Write(svc.Stat());
         break
;
      case "LIST"
:
         if
(cmds.Length > 2)
            SendError();
         if
(cmds.Length == 1)
            Write(svc.List());
        
else
         {
           
uint
msg;

            if (!uint.TryParse(cmds[1], out
msg))
               SendError();
            else
               Write(svc.List2(msg));
        
}
         break
;
      case "DELE":
         if (cmds.Length != 2)
            SendError();
         else
         {
            uint msg;
            if (!uint.TryParse(cmds[1], out msg))
               SendError();
            else
               Write(svc.Dele(msg));
         }
        
break;
      case "RETR":
         if (cmds.Length != 2)
           
SendError();
         else
         {
            uint msg;
            if (!uint.TryParse(cmds[1], out msg))
               SendError();
            else
               Write(svc.Retr(msg));
         }
         
break;
      case "TOP":
         if (cmds.Length != 3)
            SendError();
         else
         {
           
uint msg;
            uint n;
            if (!uint.TryParse(cmds[1], out msg) || !uint.TryParse(cmds[2], out n))
               SendError();
            else
               Write(svc.Top(msg, n));
         
}
         break;
      case "QUIT":
         if (cmds.Length != 1)
            SendError();
         else
        
{
            Write(svc.Quit());
            return false;
         }
        
break;
      case "RSET":
         if (cmds.Length != 1)
            SendError();
         else
            Write(svc.Rset());
         break;
      case "UIDL":
         if (cmds.Length > 2)
            SendError();
         if (cmds.Length == 1)
           
Write(svc.Uidl());
         else
         {
            uint msg;
            if (!uint.TryParse(cmds[1], out msg))
               SendError();
            else
               Write(svc.Uidl2(msg));
         }
         break;
      case "APOP":
         if (cmds.Length != 3)
            SendError();
         else
            Write(svc.Apop(cmds[1], cmds[2]));
         break;
   }
   return true;
}

Again, I've omitted all Console.Write* stuff that prints diagnostic information on the screen. This ExecuteCommand method should be very easy to understand. It performs some basic parsing of the command that's received by our local POP3 server and calls the appropriate method on the WCF service through the svc proxy instance. Two helper methods are required:

private void SendError()
{
   Write(
"-ERR Protocol error."
);
}

private void Write(string
msg)
{
   string s = msg +
"\r\n"
;
   byte
[] b = encoding.GetBytes(s.ToCharArray());
   stream.Write(b, 0, b.Length);
}

As you recall from the service contract, every POP3 equivalent web method takes in all of the parameters required for that command and returns the POP3 response string that was obtained by the target POP3 server. Using the Write method this answer is sent back to the local mail client, with the trailing CRLF pair:

   Write(svc.User(cmds[1]));

In order to run this client, we need some simple host code (read: a Main method):

class Program
{
   static void Main(string
[] args)
   {
      int
port = 110;
      string url = new BdsSoft.Net.Mail.localhost.Pop3TunnelServiceClient
().Endpoint.Address.Uri.ToString();

      Console.WriteLine("WCF POP3 Tunnel Client"
);
      Console.WriteLine("======================"
);
      Console
.WriteLine();

      Console.WriteLine("Initializing local tunnel endpoint..."
);
      Console.WriteLine("- Local POP3 server listening on TCP port {0}"
, port);
      Console.WriteLine("- Forwarding to {0}"
, url);
      Console
.WriteLine();

      new Pop3Server
(port, url).Start();
      Console
.ReadLine();
   }
}

Configuration

Last but not least, our client needs some configuration. The WCF "Add Service Dialog" will have added an app.config file already but it requires some changes, as shown below. These changes are required to deal with large responses, e.g. in response to a RETR POP3 command that retrieves an entire mail message (the default of 16 KB is not sufficient for our purposes). If you decide to implement this stuff for production purposes, you'll want to change the endpoint address to some remote server of course, not just the local machine which we are using for demo purposes (tunnel server and localhost are the same machine).

<?xml version="1.0" encoding="utf-8" ?>
<
configuration
>
   <
system.serviceModel
>
      <
bindings
>
         <
wsHttpBinding
>
            <binding name="WSHttpBinding_IPop3TunnelService" closeTimeout="00:01:00
"
               openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00
"
               bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard
"
              
maxBufferPoolSize="524288" maxReceivedMessageSize="2147483647
"
              
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true
"
              
allowCookies="false"
>
               <readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16384
"
                 
maxBytesPerRead="4096" maxNameTableCharCount="16384"
/> 
               <
reliableSession ordered="true" inactivityTimeout="00:10:00
"
                  enabled="false"
/>
               <
security mode="Message"
>
                  <
transport clientCredentialType="Windows" proxyCredentialType="None
"
                     realm=""
/>
                  <
message clientCredentialType="Windows" negotiateServiceCredential="true
"
                     algorithmSuite="Default" establishSecurityContext="true"
/>
               </
security
>
            </
binding
>
         </
wsHttpBinding
>
      </
bindings
>
      <
client
>
         <
endpoint address="http://localhost:8080/BdsSoft.Net.Mail/Pop3TunnelService
"
            binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IPop3TunnelService
"
            contract="BdsSoft.Net.Mail.localhost.IPop3TunnelService" name="WSHttpBinding_IPop3TunnelService"
>
            <
identity
>
               <
userPrincipalName value="something"
/>
            </
identity
>
         </
endpoint
>
      </
client
>
   </
system.serviceModel
>
</
configuration>

Take it to the test

Right, let's try it. Make sure the target server and port are set correctly (i.e. to some POP3 server you have a mailbox on) in the Pop3Session class:

   private const string server = "foo.mydomain.local";
   private const int
port = 110;

Next, make sure the WCF service from part 2 is up and running. When the server-side has started, you can launch the client-side tool we've been creating in this episode:

Now configure your mail client software to connect to the local POP3 server (you can set the SMTP server to whatever server you want, we won't send mail for now):

Tip: For testing purposes, I recommend to check the "Leave a copy of messages on server" checkbox, to avoid loosing mail.

You should see some result like this if you add some logging code to the client and server applications (note: the code that's available for download has this kind of logging code):

Download the code

You can download the code for the client-side implementation of the POP3 server and WCF client over here. Make sure to check out part 1 and part 2 of this series to download the associated server-side code.

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.

Conclusion

Using WCF it's fairly easy to create a HTTP/SOAP tunnel for an older protocol such as POP3. It won't be difficult at all to tunnel protocols like SMTP and IMAP4 too, although every protocol has its own characteristics and implementation complexities. Luckily, POP3 is a pretty simple protocol that allowed me to illustrate you the principle of all this beauty with a relatively small amount of code.

Time to tunnel?

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

Filed under: ,

Comments

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

Friday, September 15, 2006 11:34 AM by DeBiese

Question: could you tunnel the msn messenger protocol using wcf?

I like this example btw, nicely explained.

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

Tuesday, September 19, 2006 8:15 AM by Suddenelfilio

A very very nice example, but I do agree with DeBiese above. Do you think the Msn protocol can be tunneled also? It would open up a lot of nice features ;-)

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

Tuesday, September 19, 2006 2:24 PM by bart

Hi folks,

In theory you can tunnel anything as long as the tunneling protocol (the "wrapper") has the same bidirectional support to do it (as a last resort, tunneling might just be port-mapping but that's a really cheap solution :-)). The problem with the MSN protocol is that it isn't open and there is much more of a variation in the kind of communication performed by it. You see, the POP3 (and IMAP4, SMTP) protocol is just a client-server straightforward communcation protocol whileas MSN talks to "some server" (see netstat -n to find out) but also to peer computers to transfer files without having the MSN servers in the middle. Furthermore, MSN has the server destination hardcoded in it (might be configurable too, don't have a big amount of MSN expertise) so you'd have to trick it to send its messages to your tunnel endpoint in the first place. Taking all this stuff together, a tunnel on the "application layer" (because that is exactly what I've created here) doesn't seem to be the best solution. There are good alternatives of course, such as web messenger or the use of a VPN which is a tunnel too (but lower on the network stack).

-Bart

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

Tuesday, October 17, 2006 3:25 AM by Koen van Putten

you can Configure a program (like msn) to connect to the tunnel program with a program called SocksCap if i'm not very mistaking

# Moje z Toshiby Oli &laquo; Wiadomo??ci o technologiach IT

Wednesday, April 27, 2011 1:01 PM by Moje z Toshiby Oli « Wiadomo??ci o technologiach IT

Pingback from  Moje z Toshiby Oli &laquo; Wiadomo??ci o technologiach IT

# MyProjects &raquo; Blog Archive &raquo; Ideensammlung

Wednesday, December 07, 2011 2:27 AM by MyProjects » Blog Archive » Ideensammlung

Pingback from  MyProjects  &raquo; Blog Archive   &raquo; Ideensammlung

# VPN Tunneling using WCF | Q Sites

Monday, July 08, 2013 12:31 PM by VPN Tunneling using WCF | Q Sites

Pingback from  VPN Tunneling using WCF | Q Sites