2-legged OAuth with Gmail

October 21st, 2011

OAuth is an open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.

In this post I’ll show how to access Gmail account using 2-legged OAuth authentication method. The basic idea is that domain administrator can use this method to access user email without knowing user’s password.

You can read more on OAuth authentication with Google accounts here:
http://code.google.com/apis/accounts/docs/OAuth_ref.html

Gmail IMAP and SMTP using OAuth:
http://code.google.com/apis/gmail/oauth/protocol.html

Remember to add reference to Maill.dll and appropriate namespaces.

// C#

using Lesnikowski.Client.IMAP;
using Lesnikowski.Client.Authentication;
using Lesnikowski.Client.Authentication.Google;

const string consumerKey = "example.com";
const string consumerSecret = "secret";
const string email = "pat@example.com";

Gmail2LeggedOAuth oauth = new Gmail2LeggedOAuth(
    consumerKey, consumerSecret);

using (Imap client = new Imap())
{
    client.ConnectSSL(TestConstants.GmailImapServer);

    string oauthImapKey = oauth.GetXOAuthKeyForImap(email);

    client.LoginOAUTH(oauthImapKey);

    //...

    client.Close();
}
' VB.NET

Imports Lesnikowski.Client.IMAP
Imports Lesnikowski.Client.Authentication
Imports Lesnikowski.Client.Authentication.Google

Const  consumerKey As String = "example.com"
Const  consumerSecret As String = "secret"
Const  email As String = "pat@example.com"

Dim oauth As New Gmail2LeggedOAuth(consumerKey, consumerSecret)

Using client As New Imap()
	client.ConnectSSL(TestConstants.GmailImapServer)

	Dim oauthImapKey As String = oauth.GetXOAuthKeyForImap(email)

	client.LoginOAUTH(oauthImapKey)

	'...

	client.Close()
End Using

Here are the google apps configuration screens:

FTP Active vs Passive

October 19th, 2011

Ftp.dll .NET FTP component supports both Active and Passive mode FTP transfers.

In Active mode client waits for incomming data connections, in Passive mode client establishes data connections.

Passive mode is default. You can switch to Active mode using Mode property:

// C#

using (Ftp client = new Ftp())
{
    client.Mode = FtpMode.Active;

    client.Connect("ftp.example.com");
    client.Login("user", "password");

    // ...

    client.Close();
}
' VB.NET

Using client As New Ftp()
    client.Mode = FtpMode.Active

    client.Connect("ftp.example.com")
    client.Login("user", "password")

    ' ...

    client.Close()
End Using

Specify different port for FTP

October 19th, 2011

With Ftp.dll .NET FTP component establishing connection using default port is easy:

// C#

client.Connect("ftp.example.com");
' VB.NET

client.Connect("ftp.example.com")

If you need to specify different port just use overloaded version of Connect method:

// C#

client.Connect("ftp.example.com", 999);
// -or-
client.Connect("ftp.example.com", 999, false);
' VB.NET

client.Connect("ftp.example.com", 999)
' -or-
client.Connect("ftp.example.com", 999, False)

If you are using SSL:

// C#

client.ConnectSSL("ftp.example.com", 999);
// -or-
client.Connect("ftp.example.com", 999, true);
' VB.NET

client.ConnectSSL("ftp.example.com", 999)
' -or-
client.Connect("ftp.example.com", 999, True)

You can also specify the port range used in Active mode.

// C#

client.ActiveModePorts = new Range(1024, 1025);
' VB.NET

client.ActiveModePorts = New Range(1024, 1025)

You can strong>set the IP address announced to the FTP server in Active mode data transfer.
By default, the value of this property is IPAddress.None which means that the address of the network interface is used instead.

// C#

client.ActiveModeAddress = IPAddress.Parse("127.0.0.1");
' VB.NET

client.ActiveModeAddress = IPAddress.Parse("127.0.0.1")

How to access To, Cc, Bcc fields

October 10th, 2011

Recently some of the IMail interface address properties changed its types. The reason for this was to handle email groups nicely.

Now To, Cc, Bcc and ReplyTo are of IList<MailAddress> type (That is IList Of MailAddress in VB.NET).

In those collections you can find two kinds of objectes:

  • MailBox class which represents single mailbox,
  • MailGroup class which represents group of email addresses.

Here’s the simplest code that prints only mailboxes:

// C#
using Lesnikowski.Mail.Headers;

private string PrintMailboxes(IEnumerable<MailAddress> addresses)
{
   List<string> parts = new List<string>();
   foreach (MailBox mailbox in addresses.OfType<MailBox>())
   {
       parts.Add(string.Format("{0} <{1}>",
           mailbox.Name,
           mailbox.Address));
   }
   return string.Join(", ", parts.ToArray());
}
' VB.NET
Imports Lesnikowski.Mail.Headers

Private Function PrintMailboxes(addresses As IEnumerable(Of MailAddress)) As String
  Dim parts As New List(Of String)()
  For Each mailbox As MailBox In addresses.OfType(Of MailBox)()
      parts.Add(String.Format("{0} <{1}>", _
		mailbox.Name, _
		mailbox.Address))
  Next
  Return String.Join(", ", parts.ToArray())
End Function

Here’s a slightly more complicated version that handles groups also:

// C#
using Lesnikowski.Mail.Headers;

private string PrintAllAddresses(IEnumerable<MailAddress> addresses)
{
   List<string> parts = new List<string>();
   foreach (MailAddress address in addresses)
   {
       if (address is MailGroup)
       {
           MailGroup group = (MailGroup)address;
           parts.Add(string.Format("{0} ({1})",
               group.Name,
               PrintAllAddresses(group.Addresses))); // recursion
       }
       if (address is MailBox)
       {
           MailBox mailbox = (MailBox)address;
           parts.Add(string.Format("{0} <{1}>",
               mailbox.Name,
               mailbox.Address));
       }
   }
   return string.Join(", ", parts.ToArray());
}
' VB.NET
Imports Lesnikowski.Mail.Headers

Private Function PrintAllAddresses(addresses As IEnumerable(Of MailAddress)) As String
   Dim parts As New List(Of String)()
   For Each address As MailAddress In addresses

      If TypeOf address Is MailGroup Then
         Dim group As MailGroup = DirectCast(address, MailGroup)
         parts.Add(String.Format("{0} ({1})", _
            group.Name, _
            PrintAllAddresses(group.Addresses))) ' note the recursion
      End If

      If TypeOf address Is MailBox Then
         Dim mailbox As MailBox = DirectCast(address, MailBox)
         parts.Add(String.Format("{0} <{1}>", _
            mailbox.Name, _
            mailbox.Address))
      End If

   Next
   Return String.Join(", ", parts.ToArray())
End Function

Get supported authentication methods (IMAP, POP3, SMTP)

October 6th, 2011

You can use SupportedAuthenticationMethodsto retrieve all authentication methods supported by the server:

// C#

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.example.org");
    client.UseBestLogin("user", "password");

    Console.WriteLine("Supported methods:");

    foreach (var method in client.SupportedAuthenticationMethods())
    {
        Console.WriteLine(method.Name);
    }

    Console.WriteLine("Supports CramMD5:");

    bool supportsCramMD5 = client.SupportedAuthenticationMethods()
        .Contains(ImapAuthenticationMethod.CramMD5);

    Console.WriteLine(supportsCramMD5);

    client.Close();
}
' VB.NET

Using client As New Imap()
    client.ConnectSSL("imap.example.org")
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported methods:")

    For Each method As ImapAuthenticationMethod In client.SupportedAuthenticationMethods()
	    Console.WriteLine(method.Name)
    Next

    Console.WriteLine("Supports CramMD5:")

    Dim supportsCramMD5 As Boolean = client.SupportedAuthenticationMethods() _
        .Contains(ImapAuthenticationMethod.CramMD5)

    Console.WriteLine(supportsCramMD5)

    client.Close()
End Using

For example Gmail produces following output:

Supported method s:
IMAP4rev1
UNSELECT
IDLE
NAMESPACE
QUOTA
ID
XLIST
CHILDREN
X-GM-EXT-1
UIDPLUS
COMPRESS

Supports CramMD5:
False

We take great care for the API to look similar for all protocols (IMAP, POP3, SMTP).

Here’s the sample for POP3:

// C#

using (Pop3 client = new Pop3())
{
    client.ConnectSSL("pop3.example.org");
    client.UseBestLogin("user", "password");

    Console.WriteLine("Supported methods:");

    foreach (var method in client.SupportedAuthenticationMethods())
    {
        Console.WriteLine(method.Name);
    }

    Console.WriteLine("Supports CramMD5:");

    bool supportsCramMD5 = client.SupportedAuthenticationMethods()
        .Contains(Pop3AuthenticationMethod.CramMD5);

    Console.WriteLine(supportsCramMD5);

    client.Close();
}
' VB.NET

Using client As New Pop3()
    client.ConnectSSL("pop3.example.org")
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported methods:")

    For Each method As Pop3AuthenticationMethod In client.SupportedAuthenticationMethods()
	    Console.WriteLine(method.Name)
    Next

    Console.WriteLine("Supports CramMD5:")

    Dim supportsCramMD5 As Boolean = client.SupportedAuthenticationMethods() _
        .Contains(Pop3AuthenticationMethod.CramMD5)

    Console.WriteLine(supportsCramMD5)

    client.Close()
End Using

Here’s the sample for SMTP:

// C#

using (Smtpclient = new Smtp())
{
    client.Connect("smtp.example.org");
    client.UseBestLogin("smtp", "password");

    Console.WriteLine("Supported methods:");

    foreach (var method in client.SupportedAuthenticationMethods())
    {
        Console.WriteLine(method.Name);
    }

    Console.WriteLine("Supports CramMD5:");

    bool supportsCramMD5 = client.SupportedAuthenticationMethods()
        .Contains(SmtpAuthenticationMethod.CramMD5);

    Console.WriteLine(supportsCramMD5);

    client.Close();
}
' VB.NET

Using client As New Smtp()
    client.Connect("smtp.example.org")
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported methods:")

    For Each method As SmtpAuthenticationMethod In client.SupportedAuthenticationMethods()
	    Console.WriteLine(method.Name)
    Next

    Console.WriteLine("Supports CramMD5:")

    Dim supportsCramMD5 As Boolean = client.SupportedAuthenticationMethods() _
        .Contains(SmtpAuthenticationMethod.CramMD5)

    Console.WriteLine(supportsCramMD5)

    client.Close()
End Using