Received an unexpected EOF or 0 bytes from the transport stream

When you are getting the following error:
Received an unexpected EOF or 0 bytes from the transport stream.
while using ConnectSSL method, most likely your server incorrectly advertises TLS support. You might be required to use SSLv2 or SSLv3.

This article describes how to force SSLv3 or SSLv2 in Mail.dll or in regular .NET SslStream class.

Force SSLv3 or SSLv2 in Mail.dll

// C# version
using (Imap client = new Imap())
{
   // Force to use SSL3
   client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
   // Ignore certificate errors
   client.ServerCertificateValidate += (sender, e) => { e.IsValid = true; };
   client.ConnectSSL("imap.gmail.com");
   client.Login("user@gmail.com", "password");

   client.SelectInbox();
   foreach(long uid in client.Search(Flag.Unseen))
   {
       var eml = client.GetMessageByUID(uid);
       IMail email = new MailBuilder().CreateFromEml(eml);
       Console.WriteLine(email.Subject);
   }
   client.Close();
}
' VB.NET version

Using client As New Imap()
	' Force to use SSL3
	client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3
	' Ignore certificate errors
	AddHandler client.ServerCertificateValidate, AddressOf Validate
	client.ConnectSSL("imap.gmail.com")
	client.Login("user@gmail.com", "password")

	client.SelectInbox()
	For Each uid As Long In client.Search(Flag.Unseen)
		Dim eml = client.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)
		Console.WriteLine(email.Subject)
	Next
	client.Close()
End Using

Private Sub ValidateCerificate( _
    ByVal sender As Object, _
    ByVal e As ServerCertificateValidateEventArgs)

    Const ignoredErrors As SslPolicyErrors = _
        SslPolicyErrors.RemoteCertificateChainErrors Or _
        SslPolicyErrors.RemoteCertificateNameMismatch

    If (e.SslPolicyErrors And Not ignoredErrors) = SslPolicyErrors.None Then
        e.IsValid = True
        Return
    End If
    e.IsValid = False
End Sub

Force SSLv3 or SSLv2 in SslStream class

// C# version
const string host = "imap.gmail.com";

Socket socket = new Socket(
   AddressFamily.InterNetwork,
   SocketType.Stream,
   ProtocolType.Tcp);
socket.Connect(host, 993);
using(SslStream ssl = new SslStream(new NetworkStream(socket), true))
{
   ssl.AuthenticateAsClient(
      host,
      new X509CertificateCollection(),
      SslProtocols.Ssl3,
      true);
   using(StreamReader reader = new StreamReader(ssl))
   {
       Console.WriteLine(reader.ReadLine());
   }
}
socket.Close();
' VB.NET version

Const host As String = "imap.gmail.com"

Dim socket As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
socket.Connect(host, 993)
Using ssl As New SslStream(New NetworkStream(socket), True)
	ssl.AuthenticateAsClient(host, New X509CertificateCollection(), SslProtocols.Ssl3, True)
	Using reader As New StreamReader(ssl)
		Console.WriteLine(reader.ReadLine())
	End Using
End Using
socket.Close()

2-legged OAuth with IMAP

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

This article describes generic OAuth class.
If you are using Gmail please read 2-legged OAuth authentication with Gmail.

Remember to add reference to Mail.dll .NET IMAP component and appropriate namespaces.

// C#

using Limilabs.Client.Authentication;
using Limilabs.Client.IMAP;

string consumerKey = "your_domain.com";
string consumerSecret = "your_oauth_consumer_secret";
string emailAccount = "address@your_domain.com";

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.gmail.com");

    string imapUrl = string.Format(
        "https://mail.google.com/mail/b/{0}/imap/?xoauth_requestor_id={1}",
        emailAccount,
        HttpUtility.UrlEncode(emailAccount));

    string oauthImapKey = OAuth.ForUrl(imapUrl)
        .Consumer(consumerKey, consumerSecret)
        .SignatureMethod(SignatureType.HMACSHA1)
        .Sign()
        .GetXOAuthKey();

    client.LoginOAUTH(oauthImapKey);

    //...

    client.Close();
}
' VB.NET

Imports Limilabs.Client.Authentication
Imports Limilabs.Client.IMAP

Dim consumerKey As String = "example.com"
Dim consumerSecret As String = "secret"
Dim emailAccount As String = "pat@example.com"

Using client As New Imap()
    client.ConnectSSL("imap.gmail.com")

    Dim imapUrl As String = String.Format( _
        "https://mail.google.com/mail/b/{0}/imap/?xoauth_requestor_id={1}", _
        emailAccount, _
        HttpUtility.UrlEncode(emailAccount))

    Dim oauthImapKey As String = OAuth.ForUrl(imapUrl) _
        .Consumer(consumerKey, consumerSecret) _
        .SignatureMethod(SignatureType.HMACSHA1) _
        .Sign() _
        .GetXOAuthKeyForImap()

    client.LoginOAUTH(oauthImapKey)

    '...

    client.Close()
End Using

Process emails embedded as attachments

This article describes how to process emails embedded within emails as attachments. Mail.dll supports opening and extraction of embedded emails and attachments within.

Extracting all attachments

There is an easy way out – using ExtractAttachmentsFromInnerMessages method. It extracts all attachments from the email and from all attached messages. It returns easy to use collection of MimeData objects that represent each attachment.

// C#

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.example.com");
    client.UseBestLogin("user", "password");
    client.SelectInbox();
    foreach (long uid in client.GetAll())
    {
        IMail mail = new MailBuilder().CreateFromEml(
            client.GetMessageByUID(uid));

        ReadOnlyCollection<MimeData> attachments = 
            mail.ExtractAttachmentsFromInnerMessages();
      
        foreach (MimeData mime in attachments)
        {
            mime.Save(@"c:\" + mime.SafeFileName);
        }
    }
    client.Close();
}
' VB.NET version

Using client As New Imap()
    client.ConnectSSL("imap.example.com")
    client.UseBestLogin("user", "password")
    client.SelectInbox()
    For Each uid As Long In client.GetAll()
        Dim mail As IMail = New MailBuilder() _
            .CreateFromEml(client.GetMessageByUID(uid))

        Dim attachments As ReadOnlyCollection(Of MimeData) _
            = mail.ExtractAttachmentsFromInnerMessages()

        For Each mime As MimeData In attachments
            mime.Save("c:\" + mime.SafeFileName)
        Next
           
    Next
    client.Close()
End Using

Manual approach

This gives you more control of which attachments to process, and which for some reason you wisch to ignore. We’ll create a simple method that processes all attached emails and extracts all attachments to specified folder.

First we’ll use Mail.dll IMAP component to download emails:

// C#

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.example.com");
    client.UseBestLogin("user", "password");
    client.SelectInbox();
    foreach (long uid in client.GetAll())
    {
        IMail mail = new MailBuilder().CreateFromEml(
            client.GetMessageByUID(uid));

        SaveAttachmentsTo(mail, @"c:\tmp");
    }
    client.Close();
}
' VB.NET version

Using client As New Imap()
    client.ConnectSSL("imap.example.com")
    client.UseBestLogin("user", "password")
    client.SelectInbox()
    For Each uid As Long In client.GetAll()
        Dim mail As IMail = New MailBuilder() _
            .CreateFromEml(client.GetMessageByUID(uid))

        SaveAttachmentsTo(mail, @"c:\tmp")
    Next
    client.Close()
End Using

The following method traverses through all attachments. If regular attachment is found it is saved to specified folder. If email message is attached it parses it and saves all its attachments using recursion:

// C#

private void SaveAttachmentsTo(IMail mail, string folder)
{
    foreach (MimeData attachment in mail.Attachments)
    {
        if (attachment is IMailContainer)
        {
            IMail attachedMessage = ((IMailContainer) attachment).Message;
            SaveAttachmentsTo(attachedMessage, folder);
        }
        else
        {
            attachment.Save(
                Path.Combine(folder, attachment.SafeFileName));
        }
    }
}
' VB.NET version

Private Sub SaveAttachmentsTo(mail As IMail, folder As String)
    For Each attachment As MimeData In mail.Attachments
        If TypeOf attachment Is IMailContainer Then
            Dim attachedMessage As IMail = DirectCast(attachment, IMailContainer).Message
    	    SaveAttachmentsTo(attachedMessage, folder)
        Else
            attachment.Save( _
                Path.Combine(folder, attachment.SafeFileName))
        End If
    Next
End Sub

Unique ID in POP3 protocol

RFC 1939 specification says:

“The unique-id of a message is an arbitrary server-determined string, […], which uniquely identifies a message within a maildrop and which persists across sessions.”

“The server should never reuse an unique-id in a given maildrop, for as long as the entity using the unique-id exists.”

So in theory if the email is deleted server may reuse the same unique-id.

“While it is generally preferable for server implementations to store arbitrarily assigned unique-ids in the maildrop, this specification is intended to permit unique-ids to be calculated as a hash of the message.

Clients should be able to handle a situation where two identical copies of a message in a maildrop have the same unique-id.”

Mail.dll will not throw exception in such case, but please note, that internally Mail.dll uses dictionaries to store unique-ids and Pop3.GetAll() method will not return duplicates.

Unique ID in IMAP protocol

RFC 3501 specification says:

“2.3.1.1. Unique Identifier (UID) Message Attribute

A 32-bit value assigned to each message, which when used with the unique identifier validity value (see below) forms a 64-bit value that MUST NOT refer to any other message in the mailbox (folder) or any subsequent mailbox (folder) with the same name forever. Unique identifiers are assigned in a strictly ascending fashion”

“The unique identifier of a message MUST NOT change during the session, and SHOULD NOT change between sessions.”

“Any change of unique identifiers between sessions MUST be detectable using the UIDVALIDITY mechanism”

“The unique identifier validity value is sent in a UIDVALIDITY response code in an OK untagged response at mailbox (folder) selection time.”

Any change of unique identifiers between session causes the change of FolderStatus.UIDValidity:

using(Imap client = new Imap())
{
    client.Connect("imap.example.org");
    FolderStatus folderStatus = client.Select("Inbox");
    Console.WriteLine(
        "Folder UIDValidity: " + folderStatus.UIDValidity);
    client.Close();
}
Using client As New Imap()
    client.Connect("imap.example.org")
    Dim folderStatus As FolderStatus = client.Select("Inbox")
    Console.WriteLine( _
        "Folder UIDValidity: " + folderStatus.UIDValidity)
    client.Close()
End Using

Uniqueness

Unfortunately: “There is no assurance that the UIDVALIDITY values of two mailboxes (folders) be different, so the UIDVALIDITY in no way identifies a mailbox (folder).”

This means that:

  • UID of the email may be not unique on the server (2 messages in different folders may have same UID)
  • FolderStatus.UIDValidity + UID may be not unique on the server (2 folders may have same UIDValidity)
  • To identify a message across all folders you need 3 variables: UID, folder name and FolderStatus.UIDValidity.

UIDs across sessions

  • UIDs are ‘supposed’ to be stable across sessions, and never change, and always increase in value.
  • You need to check the FolderStatus.UIDValidity when you select the folder. If the FolderStatus.UIDValidity number hasn’t changed, then the UIDs are still valid across sessions.

Other

  • UIDs are assigned in ascending fashion – higher the value newer the message.