Quantcast
Channel: Blog | Limilabs
Viewing all 120 articles
Browse latest View live

System.Net.Mail vs Mail.dll

$
0
0

In this article we’ll try to describe advantages of Mail.dll over standard .NET System.Net.Mail namespace.

The fundamental difference is that with System.Net.Mail you can’t receive emails. System.Net.Mail does not have support for POP3 and IMAP protocols – two fundamental protocols for email retrieval, also .NET does not have any classes that would parse received email.

System.Net.Mail is great for sending simple emails, but Mail.dll gives you much more, even in terms of sending. You get appointments (iCal) and vCard support, you can send S/MIME signed and encrypted emails (if you plan to use EDI). It gives you easy to use template engine and VERP support out-of-the-box.

Here’s the comparison chart:

System.Net.Mail Mail.dll component
Send emails yes yes
SMTP protocol support (over SSL/TLS) yes yes
Send emails using VERP no yes
Send S/MIME encrypted emails no yes
Send S/MIME signed emails no yes
Send S/MIME signed emails (detached) no yes
Send DKIM (Domain Key Identified Mail) no yes
Templates support no yes
Receive emails no yes
IMAP protocol support (over SSL/TLS) no yes
POP3 protocol support (over SSL/TLS) no yes
Retrieve and parse emails no yes
Extract HTML, plain text, images no yes
Attachment retrieval no yes
Send and retrieve iCalendar appointments no yes
Send and retrieve vCards no yes
OAuth 1.1a/2.0 support no yes
Spam filter no yes
Bounce handling no yes
Convert HTML only emails to plain text no yes

If you need help or more information about any of these features visit Mail.dll samples.


How to shorten Connect timeout

$
0
0

Connect and BeginConnect methods take quite a long time to timeout, when you use incorrect server address. It usually takes almost 20 seconds for those methods to decide that connection is impossible. Setting SendTimeout/ReceiveTimeout doesn’t influence this in any way.

Not only Imap, Pop3, and Smtp classes suffer from this problem, it’s the same for regular Socket class.

There is a solution however, using BeginConnect and simply waiting for specified amount of time (AsyncWaitHandle.WaitOne) without calling EndConnect (which blocks):

// C#

using(Imap imap =new Imap())
{
    IAsyncResult result = imap.BeginConnectSSL("imap.example.com");

    // 5 seconds timeout
    bool success = result.AsyncWaitHandle.WaitOne(5000, true);

    if (success == false)
    {
        throw new Exception("Failed to connect server.");
    }
    imap.EndConnect(result);

    //...

    imap.Close();
}
' VB.NET

Using imap As New Imap()
	Dim result As IAsyncResult = imap.BeginConnectSSL("imap.example.com")

	' 5 seconds timeout
	Dim success As Boolean = result.AsyncWaitHandle.WaitOne(5000, True)

	If success = False Then
		Throw New Exception("Failed to connect server.")
	End If
	imap.EndConnect(result)

	'...

	imap.Close()
End Using

HTML formatted content in the description field of an iCalendar

$
0
0

By default, the iCalendar specification allows only plain text to be used in the description of an Event object.

X-ALT-DESC header

However Outlook can recognize HTML formatted content. This is supported using an additional field in the Event object called “X-ALT-DESC”, rather than the existing field:

DESCRIPTION:Reminder
X-ALT-DESC;FMTTYPE=text/html:<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 3.2//E
 N""><HTML><BODY>\nhtml goes here\n</BODY></HTML>

Using Mail.dll you can set this field with no extra hassle:

// C#

Appointment appointment = new Appointment();
Event e = appointment.AddEvent();
e.XAltDescription = @"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 3.2//EN""><HTML><BODY>
html goes here
</BODY></HTML>";
' VB.NET

Dim appointment As New Appointment()
Dim e As [Event] = appointment.AddEvent()
e.XAltDescription = "<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 3.2//EN""><HTML><BODY>" _ 
 & vbCr & vbLf & "html goes here"  _ 
 & vbCr & vbLf & "</BODY></HTML>"

Adding custom headers

This is also good sample to show how to add a custom header to any PDI object:

// C#

Appointment appointment = new Appointment();
Event e = appointment.AddEvent();

const string html = @"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 3.2//EN""><HTML><BODY>
html goes here
</BODY></HTML>";

PdiHeader header = new PdiHeader("X-ALT-DESC", html);
header.KeyParameters.Add(new KeyValues("FMTTYPE", "text/html"));
e.AddCustomHeader(header);
' VB.NET

Dim appointment As New Appointment()
Dim e As [Event] = appointment.AddEvent()

Const  html As String = "<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 3.2//EN""><HTML><BODY>" _
 & vbCr & vbLf & "html goes here" _
 & vbCr & vbLf & "</BODY></HTML>"

Dim header As New PdiHeader("X-ALT-DESC", html)
header.KeyParameters.Add(New KeyValues("FMTTYPE", "text/html"))
e.AddCustomHeader(header)

ALTREP parameter

It is worth mentioning that RFC defines a way of specifying HTML content. The problem is that, it requires additional source (like email attachment or http address). Address of this resource, for example using “cid:” is set using ALTREP DESCRIPTION header:

DESCRIPTION;ALTREP="CID:part3.msg.970415T083000@example.com":
 Project XYZ Review Meeting will include the following agenda
   items: (a) Market Overview\, (b) Finances\, (c) Project Man
 agement

Mail.dll supports this feature also.

First we’ll define MIME object containing html data. Note that we are setting ContentId property.

// C#

MimeText html = new MimeFactory().CreateMimeText();
html.ContentType = ContentType.TextHtml;
html.Text = "<html><body>Html</body></html>";
html.ContentId = "part3.msg.970415T083000@example.com";
' VB.NET

Dim html As MimeText = New MimeFactory().CreateMimeText()
html.ContentType = ContentType.TextHtml
html.Text = "<html><body>Html</body></html>"
html.ContentId = "part3.msg.970415T083000@example.com"

Now we’ll create event:

// C#

Appointment appointment = new Appointment();
Event e = appointment.AddEvent();

e.Description = "Project XYZ Review Meeting will include the following agenda items: (a) Market Overview, (b) Finances, (c) Project Management";
e.DescriptionAltRep = "CID:" + html.ContentId;
' VB.NET

Dim appointment As New Appointment()
Dim e As [Event] = appointment.AddEvent()

e.Description = "Project XYZ Review Meeting will include the following agenda items: (a) Market Overview, (b) Finances, (c) Project Management"
e.DescriptionAltRep = "CID:" + html.ContentId

Finally we need to create an email with the event and html data:

// C#

MailBuilder builder = new MailBuilder();
builder.AddAttachment(html);
builder.AddAppointment(appointment);
IMail email = builder.Create();
' VB.NET

Dim builder As New MailBuilder()
builder.AddAttachment(html)
builder.AddAppointment(appointment)
Dim email As IMail = builder.Create()

Gmail’s SPECIAL-USE capability is broken

$
0
0

Recently it came to my attention that Gmail’s XLIST command is deprecated.

XLIST is a custom Gmail command that retrieves information about the folder purpose, without the need of knowing the folders name. It works by returning additional flags, such as \Spam, for folders of a known purpose (e.g. “[Gmail]/Spam”)

More standardized feature, deigned for the same purpose, is SPECIAL-USE extension.

Both XLIST and SPECIAL-USE are supported by Mail.dll. You can use CommonFolders class to get folder by its function.

On this site Gmail claims that:

Gmail supports the IMAP LIST Extension for Special-Use Mailboxes [RFC 6154], which provides new attributes for special folders.

And in the next paragraph:

The Gmail-specific XLIST command is deprecated in favor of the IMAP Special-Use List Standard [RFC 6154].

The problem is that their Special-Use support is seriously broken.

Broken CAPABILITY response

RFC 6855 clearly states:

Supporting implementations MUST include the “SPECIAL-USE” capability string in response to an IMAP CAPABILITY command.

Here’s the Gmail’s response to CAPABILITY command:

Initial:

S: * OK Gimap ready for requests from 89.67.10.122 z2if15323304eeo.17
C: f3a21e43c6c648e8 CAPABILITY
S: * CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH AUTH=XOAUTH2

After logging in:

 C: 77776ad235694614 CAPABILITY
 S: * CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 UIDPLUS COMPRESS=DEFLATE ENABLE MOVE
 S: 77776ad235694614 OK Success

There is no “SPECIAL-USE” capability returned. What is funny deprecated XLIST is still there.

Broken LIST response

RFC 6855 clearly states:

If the client specifies the “SPECIAL-USE” return option, the LIST command MUST return the new special-use attributes on those mailboxes that have them set.

Gmail server fails to parse such command:

C: 7ab9a06868fa4717 LIST "" * RETURN (SPECIAL-USE)
S: 7ab9a06868fa4717 BAD Could not parse command

Correct LIST response?

It seems however that regular LIST response contains flags defined in RFC 6154 (\All, \Flagged, \Junk and so on):

* LIST (\HasNoChildren) "/" "INBOX"
S: * LIST (\Noselect \HasChildren) "/" "[Gmail]"
S: * LIST (\HasChildren \HasNoChildren \All) "/" "[Gmail]/All Mail"
S: * LIST (\HasNoChildren \Drafts) "/" "[Gmail]/Drafts"
S: * LIST (\HasChildren \HasNoChildren \Important) "/" "[Gmail]/Important"
S: * LIST (\HasChildren \HasNoChildren \Sent) "/" "[Gmail]/Sent Mail"
S: * LIST (\HasChildren \HasNoChildren \Junk) "/" "[Gmail]/Spam"
S: * LIST (\HasChildren \HasNoChildren \Flagged) "/" "[Gmail]/Starred"
S: * LIST (\HasChildren \HasNoChildren \Trash) "/" "[Gmail]/Trash"

The response follows the Special-Use standard with an additional \Important attribute added for Gmail’s Priority Inbox

Why not X-Important?

Processing a read receipt (MDN)

$
0
0
You can also read how to:

In this article we’ll show how to process a read receipt.

Read receipts also known as MDNs or Message Delivery Notifications are used to notify the message sender that some action has happened with their message (it was displayed, processed, deleted)

All MDNs for a received message are available through IMail.ReadReceipts property.

// C#

string eml = imap.GetMessageByUID(uid);
IMail email = new MailBuilder().CreateFromEml(eml);
MimeMessageDispositionNotification mdn = email.ReadReceipts[0];

string finalRecipient = mdn.FinalRecipient; 
      // recipient@example.com
DispositonActionMode actionmode = mdn.ActionMode; 
      // e.g. DispositonActionMode.ManualAction
DispositonSendingMode sendingMode = mdn.SendingMode; 
      // e.g. DispositonSendingMode.SentManually
string originalMessageID= mdn.OriginalMessageID; 
      // e.g. "message-id@original.com"
DispositonType dispositionType = mdn.Type; 
      // e.g. DispositonType.Displayed, DispositonType.Deleted
' VB.NET

Dim eml As String = imap.GetMessageByUID(uid)
Dim email As IMail = New MailBuilder().CreateFromEml(eml)
Dim mdn As MimeMessageDispositionNotification = email.ReadReceipts(0)

Dim finalRecipient As String = mdn.FinalRecipient 
     ' recipient@example.com
Dim actionmode As DispositonActionMode = mdn.ActionMode 
      ' e.g. DispositonActionMode.ManualAction
Dim sendingMode As DispositonSendingMode = mdn.SendingMode 
      ' e.g. DispositonSendingMode.SentManually
Dim originalMessageID As String = mdn.OriginalMessageID 
      ' e.g. "message-id@original.com"
Dim dispositionType As DispositonType = mdn.Type 
      ' e.g. DispositonType.Displayed, DispositonType.Deleted

Creating read receipt (MDN)

$
0
0

In this article we’ll show how to create and send read receipt.

Read receipts also known as MDNs or Message Delivery Notifications are used to notify the message sender that some action has happened with their message (it was displayed, processed, deleted)

Check if read receipt was requested

Although several email headers can be used by sender to request a read receipt (‘Disposition-Notification-To’, ‘Return-Receipt-To’, ‘X-Confirm-Reading-To’)
checking if read receipt was requested is quite easy. You just need to use IMail.GetReadReceiptAddresses method.

This method checks all previously mentioned headers and removes duplicates. If the returned list is not empty, it means that sender have requested read receipt.

The recipient’s email software may silently ignore the request, or it may prompt the user for permission to send the MDN. There is no obligation or guarantee of the return-receipt sending.

// C#

IMail mail = ...

List<MailBox> addresses = mail.GetReadReceiptAddresses();
if (addresses.Count > 0)
{
    // Read receipt was requested
}
' VB.NET

Dim mail As IMail = ...

Dim addresses As List(Of MailBox) = mail.GetReadReceiptAddresses()
    ' Read receipt was requested
If addresses.Count > 0 Then
End If

Creating read receipt for a message

Use ReadReceiptBuilder class to create MailBuilder that can be used to create actual message (IMail).

// C#
IMail mail = ...

ReadReceiptBuilder mdnBuilder = new ReadReceiptBuilder(mail);
//mdnBuilder.ReadSubjectTemplate = "Read: [Original.Subject]";
//mdnBuilder.ReadTextTemplate = 
//  @"This is a confirmation that your message sent to [OriginalRecipient.Address] was displayed.";
MailBuilder builder = mdnBuilder.WasDisplayed(new MailBox("bob@example.com"));
IMail mdn = builder.Create();

' VB.NET

Dim mail As IMail = ...

Dim mdnBuilder As New ReadReceiptBuilder(mail)
'mdnBuilder.ReadSubjectTemplate = "Read: [Original.Subject]";
'mdnBuilder.ReadTextTemplate =  
'   @"This is a confirmation that your message sent to [OriginalRecipient.Address] was displayed.";
Dim builder As MailBuilder = mdnBuilder.WasDisplayed(New MailBox("bob@example.com"))
Dim mdn As IMail = builder.Create()

Creating new read receipt

You can use ReadReceiptBuilder constructor overloads to create new read receipt when you don’t have IMail object available. You’ll need original message-id however.

// C#

ReadReceiptBuilder mdnBuilder = new ReadReceiptBuilder("messageid@original.com", new MailBox("sender@original.com"));
MailBuilder builder = mdnBuilder.WasDisplayed(new MailBox("recipient@original.com"));
IMail mdn = builder.Create();
' VB.NET

Dim mdnBuilder As New ReadReceiptBuilder("messageid@original.com", New MailBox("sender@original.com"))
Dim builder As MailBuilder = mdnBuilder.WasDisplayed(New MailBox("recipient@original.com"))
Dim mdn As IMail = builder.Create()

Sending read receipt

There some restrictions regarding sending MDNs that you should consider, RFC 3798:

MDNs SHOULD NOT be sent automatically if the address in the
Disposition-Notification-To header differs from the address in the
Return-Path header (IMail.ReturnPath). In this case, confirmation
from the user SHOULD be obtained, if possible. If obtaining consent
is not possible (e.g., because the user is not online at the time),
then an MDN SHOULD NOT be sent.

Confirmation from the user SHOULD be obtained (or no MDN sent) if
there is no Return-Path header (IMail.ReturnPath) in the message, or if there is more
than one distinct address in the Disposition-Notification-To header (IMail.GetReadReceiptAddresses).

// C#

using(Smtp smtp = new Smtp())
{
    smtp.Connect("smtp.server.com");  // or ConnectSSL for SSL
    smtp.UseBestLogin("user", "password");
 
    smtp.SendMessage(mdn);                     
 
    smtp.Close();   
}  
' VB.NET

Using smtp As New Smtp()
    smtp.Connect("smtp.server.com")	' or ConnectSSL for SSL
    smtp.UseBestLogin("user", "password")

    smtp.SendMessage(mdn)

    smtp.Close()
End Using

Requesting Delivery Status Notifications (DSN)

$
0
0

In this article I am going to explain how to request Delivery Status Notifications (DSN) regarding email delivery.

Delivery notifications are used to trace, if the email is delivered, bounced or delayed.

You can find more information on how to process bounced messages here.

Please also note that there is a fundamental difference between Delivery Status Notifications (DSN, read receipts), which are sent by SMTP servers and
Message Delivery Notifications (MDN), which are sent by mail user agents (such as Outlook or AS2 interface).

In case of delayed or failed delivery email server will send an email containing DSN back to sender. Once mail is delivered to the recipient mailbox, delivery notification mail will be sent to the sender mailbox.

There are several delivery notification options, you can request when sending a massage:

  • DeliveryNotificationOptions.None – No notification information will be sent. The mail server will utilize its configured behavior to determine whether it should generate a delivery notification. Usually notification is send when failure or delay occurs.
  • DeliveryNotificationOptions.OnSuccess – Notify if the delivery is successful.
  • DeliveryNotificationOptions.OnFailure – Notify if the delivery is unsuccessful.
  • DeliveryNotificationOptions.Delay – Notify if the delivery is delayed.
  • DeliveryNotificationOptions.Never – A notification should not be generated under any circumstances.

You can set delivery notification options easily. Please check the below code. It is normal mail sending code with one additional line that requests delivery notification to be sent. It uses Smtp.DeliveryNotification property:

using (Smtp smtp = new Smtp())
{
    smtp.Connect("smtp.example.com");
    smtp.UseBestLogin("user", "password");
    smtp.DeliveryNotification = DeliveryNotificationOptions.OnFailure |
        DeliveryNotificationOptions.Delay;

    IMail email = Fluent.Mail.Text("Some text")
        .Subject("Some subject")
        .From(new MailBox("from@example.com"))
        .To(new MailBox("to@example.com"))
        .Create();

    smtp.SendMessage(email);

    smtp.Close();
}
Using smtp As New Smtp()
    smtp.Connect("smtp.example.com")
    smtp.UseBestLogin("user", "password")
    smtp.DeliveryNotification =  _
        DeliveryNotificationOptions.OnFailure _
        Or DeliveryNotificationOptions.Delay

    Dim email As IMail = Fluent.Mail.Text("Some text") _
        .Subject("Some subject") _
        .From(New MailBox("from@example.com")) _
        .[To](New MailBox("to@example.com")) _
        .Create()

    smtp.SendMessage(email)

    smtp.Close()
End Using

Please note that SMTP servers may ignore requests for such notifications, especially OnSuccess options tend to be ignored.

Get Google contacts with OAuth 2.0

$
0
0

Although neither POP3 nor IMAP protocol allows retrieving the list of user’s contacts, it is possible to use Google API for that.

As long as you are using one of OAuth 2.0 scenarios:

Mail.dll email component allows you to easy download Gmail contacts of a particular user.

Remember to add request for calendar data access using GoogleScope.ContactsScope.

// C#

List<GoogleScope> scope = new List<GoogleScope>
    {
        GoogleScope.ImapAndSmtp.Name,
        GoogleScope.EmailAddressScope,
        GoogleScope.ContactsScope
    };
' VB.NET

Dim scope As New List(Of GoogleScope)() { _
    GoogleScope.ImapAndSmtp.Name, _
    GoogleScope.EmailAddressScope, _
    GoogleScope.ContactsScope _
}

// C#

GoogleApi api = new GoogleApi(accessToken);

XmlDocument contacts = google.GetContacts();

XmlNamespaceManager nsmgr = new XmlNamespaceManager(contacts.NameTable);
nsmgr.AddNamespace("gd", "http://schemas.google.com/g/2005");
nsmgr.AddNamespace("a", "http://www.w3.org/2005/Atom");

foreach (XmlNode contact in contacts.GetElementsByTagName("entry"))
{
    XmlNode title = contact.SelectSingleNode("a:title", nsmgr);
    XmlNode email = contact.SelectSingleNode("gd:email", nsmgr);

    Console.WriteLine("{0}: {1}", 
        title.InnerText, 
        email.Attributes["address"].Value);
}
' VB.NET

Dim google As New GoogleApiaccessToken)

Dim contacts As XmlDocument = google.GetContacts()

Dim nsmgr As New XmlNamespaceManager(contacts.NameTable)
nsmgr.AddNamespace("gd", "http://schemas.google.com/g/2005")
nsmgr.AddNamespace("a", "http://www.w3.org/2005/Atom")

For Each contact As XmlNode In contacts.GetElementsByTagName("entry")
	Dim title As XmlNode = contact.SelectSingleNode("a:title", nsmgr)
	Dim email As XmlNode = contact.SelectSingleNode("gd:email", nsmgr)
	Console.WriteLine("{0}: {1}", _
            title.InnerText, _
            email.Attributes("address").Value)
Next

OAuth 2.0 with Gmail over IMAP for service account

$
0
0
You can also read how to use:

In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0 and service accounts. The basic idea is that domain administrator can use this method to access user email without knowing user’s password.

This scenario is very similar to 2-legged OAuth, which uses OAuth 1.0a. Although it still works, it has been deprecated by Google and OAuth 2.0 service accounts were introduced.

The following describes how to use XOAUTH2 and OAuth 2.0 to achieve the equivalent of 2-legged OAuth.

Google APIs console

Firstly you need to visit the Google API Console and create a service account:

console_1

console_2

console_3

Download and save this private key, you’ll need that later.

console_4

Make a note of the Client ID (Your-ID.apps.googleusercontent.com), Email address (Your-ID@developer.gserviceaccount.com) and makes sure you have saved the private key (XYZ-privatekey.p12) somewhere – you will need these later.

Google Apps Dashboard

Next step is to authorize access for newly created service account.

Visit your domain administration panel:

https://www.google.com/a/cpanel/yourdomain.com/ManageOauthClients

Then click “Advanced tools”, “Authentication” and “Manage third party OAuth Client access”.

On this screen you can authorize service account to access email scope:

cpanel_1

Use previously remembered Client ID and “https://mail.google.com/”, which is IMAP/SMTP API scope:

Client Name: Your-ID.apps.googleusercontent.com
One or More API Scopes: https://mail.google.com/

DotNetOpenAuth

DotNetOpenAuth is free, open source library that implements OAuth 2.0.

However you can not use the latest version. This is because Goolge has not updated their code to work with the most recent release. You’ll need to use 4.0 version.

You can download it here: DotNetOpenAuth.zip 4.0 or from Google APIs Client Library for .NET.

Newtonsoft.Json

The other library you’ll need is Newtonsoft.Json JSON library. You can download it here:
http://json.codeplex.com/releases/

Access IMAP/SMTP server

using System;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Newtonsoft.Json;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2;
using DotNetOpenAuth.OAuth2.Messages;
using Limilabs.Client.Authentication.Google;
using Limilabs.Client.IMAP;
using Limilabs.Mail;


const string serviceAccountEmail = "Your-ID@developer.gserviceaccount.com";
const string serviceAccountCertPath = @"c:\XYZ-privatekey.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "lesnikowski@limilabs.com";

X509Certificate2 certificate =  new X509Certificate2(
    serviceAccountCertPath, 
    serviceAccountCertPassword,
    X509KeyStorageFlags.Exportable);

AuthorizationServerDescription server = new AuthorizationServerDescription
{
    AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
    TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"),
    ProtocolVersion = ProtocolVersion.V20,
};

AssertionFlowClient provider = new AssertionFlowClient(server, certificate)
{
    ServiceAccountId = serviceAccountEmail,
    Scope = Limilabs.Client.Authentication.Google.GoogleScope.ImapAndSmtp.Name,
    ServiceAccountUser = userEmail,
};

IAuthorizationState grantedAccess = AssertionFlowClient.GetState(provider);
string accessToken = grantedAccess.AccessToken;

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.gmail.com");
    client.LoginOAUTH2(userEmail, accessToken);

    client.SelectInbox();
    List<long> uids = client.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        string eml = client.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    client.Close();
}

AssertionFlowClient

I’ll need to add several classes defined below. You can also browse Google APIs Client Library for .NET.

/// <summary>
/// Assertion flow header used to generate the assertion flow message header.
/// </summary>
public class AssertionFlowHeader
{
    /// <summary>
    /// Gets or sets the encryption algorithm used by the assertion flow message.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("alg")]
    public String Algorithm { get; set; }

    /// <summary>
    /// Gets or sets the type of the claim.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("typ")]
    public String Type { get; set; }
};

/// <summary>
/// Google assertion flow header holding Google supported values.
/// </summary>
public class GoogleAssertionFlowHeader : AssertionFlowHeader
{

    /// <summary>
    /// The google signing algorithm, currently RSA-SHA256
    /// </summary>
    public const string GoogleSigningAlgorithm = "RS256";

    /// <summary>
    /// The type of the google assertion, currently JSON Web Token
    /// </summary>
    public const string GoogleAssertionType = "JWT";

    public GoogleAssertionFlowHeader()
    {
        Algorithm = GoogleSigningAlgorithm;
        Type = GoogleAssertionType;
    }
};

/// <summary>
/// Assertion flow claim used to generate the assertion flow message claim.
/// </summary>
public class AssertionFlowClaim
{
    public AssertionFlowClaim()
    {
        DateTime begin = new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc);
        IssuedAt = (long) (DateTime.UtcNow - begin).TotalSeconds;
        ExpiresAt = IssuedAt + 3600;
    }

    public AssertionFlowClaim(AuthorizationServerDescription authorizationServer)
        : this()
    {
        Audience = authorizationServer.TokenEndpoint.ToString();
    }

    /// <summary>
    /// Gets or sets the assertion flow issuer (e.g client ID).
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("iss")]
    public String Issuer { get; set; }

    /// <summary>
    /// Gets or sets the service account user (for domain-wide delegation).
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("prn")]
    public String Principal { get; set; }

    /// <summary>
    /// Gets or sets the scope.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("scope")]
    public String Scope { get; set; }

    /// <summary>
    /// Gets or sets the token endpoint.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("aud")]
    public String Audience { get; set; }

    /// <summary>
    /// Gets or sets the expected expiration of the token to retrieve.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("exp")]
    public long ExpiresAt { get; set; }

    /// <summary>
    /// Gets or sets the UTC timestamp at which this claim has been built.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("iat")]
    public long IssuedAt { get; set; }
};

/// <summary>
/// Assertion flow message to be sent to the token endpoint.
/// </summary>
public class AssertionFlowMessage : MessageBase
{
    /// <summary>
    /// Google supported assertion type 
    /// </summary>
    public const string GoogleAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer";

    /// <summary>
    /// Initializes a new instance of the <see cref="AssertionFlowMessage"/> class.
    /// </summary>
    /// <param name='authorizationServer'> Authorization server description. </param>
    public AssertionFlowMessage(AuthorizationServerDescription authorizationServer) :
        base(new Version(2, 0), MessageTransport.Direct, authorizationServer.TokenEndpoint)
    {
        GrantType = "assertion";
        AssertionType = GoogleAssertionType;
        this.HttpMethods = HttpDeliveryMethods.PostRequest;
    }

    /// <summary>
    /// Gets or sets the type of the grant (defaults to "assertion").
    /// </summary>
    [MessagePart("grant_type", IsRequired = true)]
    public String GrantType { get; set; }

    /// <summary>
    /// Gets or sets the type of the assertion 
    /// (defaults to "http://oauth.net/grant_type/jwt/1.0/bearer").
    /// </summary>
    [MessagePart("assertion_type", IsRequired = true)]
    public String AssertionType { get; set; }

    /// <summary>
    /// Gets or sets the assertion message.
    /// </summary>
    [MessagePart("assertion", IsRequired = true)]
    public String Assertion { get; set; }
};

public class AssertionFlowClient : ClientBase
{
    /// <summary>
    /// Gets or sets the service account identifier.
    /// </summary>
    /// <value>
    /// The service account identifier.
    /// </value>
    public String ServiceAccountId { get; set; }

    /// <summary>
    /// Gets or sets the service account user (used for domain-wide delegation).
    /// </summary>
    public String ServiceAccountUser { get; set; }

    /// <summary>
    /// Gets or sets the scope to get access for.
    /// </summary>
    public String Scope { get; set; }

    /// <summary>
    /// Gets the certificate used to sign the assertion.
    /// </summary>
    public X509Certificate2 Certificate { get; private set; }

    /// <summary>
    /// Gets or sets the JWT claim's header (defaults to Google's supported values).
    /// </summary>
    public AssertionFlowHeader Header { get; set; }

    public RSACryptoServiceProvider Key { get; private set; }

    /// <summary>
    /// Initializes a new instance of the
    /// <see cref="AssertionFlowClient"/> class.
    /// </summary>
    /// <param name='authorizationServer'>
    /// Authorization server description.
    /// </param>
    /// <param name='certificate'>
    /// Certificate to use to sign the assertion flow messages.
    /// </param>
    public AssertionFlowClient(
        AuthorizationServerDescription authorizationServer,
        X509Certificate2 certificate)
        : base(authorizationServer, null, null)
    {
        if (certificate == null)
            throw new ArgumentNullException("certificate");
        if (certificate.PrivateKey == null)
            throw new ArgumentNullException("certificate.PrivateKey");

        Header = new GoogleAssertionFlowHeader();
        Certificate = certificate;

        // Workaround to correctly cast the private key as a RSACryptoServiceProvider type 24
        RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) certificate.PrivateKey;
        byte[] privateKeyBlob = rsa.ExportCspBlob(true);

        Key = new RSACryptoServiceProvider();
        Key.ImportCspBlob(privateKeyBlob);
    }

    /// <summary>
    /// Helper method to retrieve the Authorization State.
    /// </summary>
    /// <returns>
    /// The authorization state.
    /// </returns>
    /// <param name='provider'>
    /// The provider to use to retrieve the authorization state.
    /// </param>
    public static IAuthorizationState GetState(AssertionFlowClient provider)
    {
        if (provider.Scope == null)
            throw new ArgumentNullException("Scope");
        IAuthorizationState state = new AuthorizationState(provider.Scope.Split(' '));

        if (provider.RefreshToken(state, null))
        {
            return state;
        }
        return null;
    }

    /// <summary>
    /// Request a new access token using the OAuth 2.0 assertion flow.
    /// </summary>
    /// <returns>
    /// Whether or not a new access token has been successfully retrieved.
    /// </returns>
    /// <param name='authorization'>
    /// Object containing the current authorization state.
    /// </param>
    /// <param name='skipIfUsefulLifeExceeds'>
    /// If set to <c>true</c> skip if useful life exceeds.
    /// </param>
    public new bool RefreshToken(
        IAuthorizationState authorization, 
        TimeSpan? skipIfUsefulLifeExceeds)
    {
        return RefreshToken(authorization, skipIfUsefulLifeExceeds, this.Channel.Request);
    }

    public bool RefreshToken(
        IAuthorizationState authorization,
        TimeSpan? skipIfUsefulLifeExceeds,
        Func<IDirectedProtocolMessage, IProtocolMessage> requestProvider)
    {
        if (authorization == null)
            throw new ArgumentNullException("authorization");
        if (this.Certificate == null)
            throw new ArgumentNullException("Certificate");

        // Check if the token is still valid.
        if (skipIfUsefulLifeExceeds.HasValue && authorization.AccessTokenExpirationUtc.HasValue)
        {
            TimeSpan timeSpan = authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow;
            if (timeSpan > skipIfUsefulLifeExceeds.Value)
            {
                return false;
            }
        }

        AssertionFlowMessage requestMessage = GenerateMessage();

        var response = requestProvider(requestMessage);

        // Response is not strongly-typed to an AccessTokenSuccessResponse because DotNetOpenAuth can't infer the
        // type from the request message type. The only way to get access to the result data is through the
        // resulting Dictionary.
        if (response.ExtraData.ContainsKey("access_token") && response.ExtraData.ContainsKey("expires_in"))
        {
            authorization.AccessToken = response.ExtraData["access_token"];
            long expiresIn = long.Parse(response.ExtraData["expires_in"]);
            DateTime utcNow = DateTime.UtcNow;
            authorization.AccessTokenExpirationUtc = utcNow.AddSeconds(expiresIn);
            authorization.AccessTokenIssueDateUtc = utcNow;
            authorization.SaveChanges();
            return true;
        }
        return false;
    }

    /// <summary>
    /// Generates the assertion flow message to be sent to the token endpoint.
    /// </summary>
    /// <returns>
    /// The assertion flow message.
    /// </returns>
    private AssertionFlowMessage GenerateMessage()
    {
        string header = JsonConvert.SerializeObject(Header);
        string claim = JsonConvert.SerializeObject(
            new AssertionFlowClaim(AuthorizationServer)
                       {
                           Issuer = this.ServiceAccountId,
                           Principal = this.ServiceAccountUser,
                           Scope = this.Scope
                       });

        StringBuilder assertion = new StringBuilder();
        assertion.Append(UnpaddedUrlSafeBase64Encode(header));
        assertion.Append(".");
        assertion.Append(UnpaddedUrlSafeBase64Encode(claim));

        // TODO: Check if this is working on FIPS enabled systems.
        byte[] data = Encoding.ASCII.GetBytes(assertion.ToString());
        String signature = UnpaddedUrlSafeBase64Encode(Key.SignData(data, "SHA256"));
        assertion.Append(".");
        assertion.Append(signature);

        return new AssertionFlowMessage(this.AuthorizationServer)
                   {
                       Assertion = assertion.ToString()
                   };
    }

    /// <summary>
    /// Encode the provided UTF8 string into an URL safe base64 string.
    /// </summary>
    /// <returns>
    /// The URL safe base64 string.
    /// </returns>
    /// <param name='value'>
    /// String to encode.
    /// </param>
    private String UnpaddedUrlSafeBase64Encode(String value)
    {
        return UnpaddedUrlSafeBase64Encode(Encoding.UTF8.GetBytes(value));
    }

    /// <summary>
    /// Encode the byte array into an URL safe base64 string.
    /// </summary>
    /// <returns>
    /// The URL safe base64 string.
    /// </returns>
    /// <param name='bytes'>
    /// Bytes to encode.
    /// </param>
    private String UnpaddedUrlSafeBase64Encode(Byte[] bytes)
    {
        return Convert.ToBase64String(bytes)
            .Replace("=", String.Empty)
            .Replace('+', '-')
            .Replace('/', '_');
    }
};

Download Gmail Chat Logs via IMAP

$
0
0

You can download all Gmail and Google Talk conversations using IMAP protocol.

Gmail can turn your instant messaging conversations into threads of emails — available conveniently for download anywhere via IMAP.

To access and export Gmail and Google Talk chat logs make sure IMAP access is enabled for your Gmail account.

Make also sure Show in IMAP is checked for Chats under System Labels.
Gmail-chats

All chat logs are stored inside the “[Gmail]/Chats” folder.

Each email message stored in that folder contains one conversation, stored as HTML (available through IMail.HTML property) and XML (available through IMail.Visuals attachment property). Of course XML is much more interesting from the processing point of view and you can use XDocument class to parse it. Each conversation consists of multiple messages.

// C#

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap.gmail.com");
    imap.Login("user@gmail.com", "password");

    imap.Select("[Gmail]/Chats");

    IEnumerable<long> firstFive = imap.GetAll().Take(5);
    foreach (long uid in firstFive)
    {
        IMail email = new MailBuilder().CreateFromEml(
            imap.GetMessageByUID(uid));

        MimeText xml = (MimeText)email.Visuals[0];
        XDocument document = XDocument.Parse(xml.Text);

        XNamespace con = XNamespace.Get("google:archive:conversation");
        XNamespace cli = XNamespace.Get("jabber:client");

        XElement conversation = document.Element(con + "conversation");
        foreach (XElement message in conversation.Elements(cli + "message"))
        {
            XElement body = message.Element(cli + "body");

            Console.WriteLine("{0} -> {1}: {2}",
                message.Attribute("from").Value,
                message.Attribute("to").Value,
                body.Value);
        }
    }
    imap.Close();
}
' VB.NET

Using imap As New Imap()
    imap.ConnectSSL("imap.gmail.com")
    imap.Login("user@gmail.com", "password")

    imap.Select("[Gmail]/Chats")

    Dim firstFive As IEnumerable(Of Long) = imap.GetAll().Take(5)
    For Each uid As Long In firstFive
        Dim email As IMail = New MailBuilder().CreateFromEml( _
            imap.GetMessageByUID(uid))

        Dim xml As MimeText = DirectCast(email.Visuals(0), MimeText)
	Dim document As XDocument = XDocument.Parse(xml.Text)

	Dim con As XNamespace = XNamespace.[Get]("google:archive:conversation")
	Dim cli As XNamespace = XNamespace.[Get]("jabber:client")

	Dim conversation As XElement = document.Element(con + "conversation")
	For Each message As XElement In conversation.Elements(cli + "message")
	    Dim body As XElement = message.Element(cli + "body")

            Console.WriteLine("{0} -> {1}: {2}", _
                message.Attribute("from").Value,  _
                message.Attribute("to").Value,  _
                body.Value)
        Next
    Next
    imap.Close()
End Using

Send email with custom header

$
0
0

In this article we’ll show how to create and send email message with custom header added.

As a prerequisite you need to add reference to Mail.dll .NET email component to your project.

In contrast to System.Net.Mail, Mail.dll allows almost any manipulation to email message’s MIME tree. This includes adding custom headers on the root level. The easiest way to achieve this, is to use MailBuilder class and AddCustomHeader method:

MailBuilder builder = new MailBuilder();
builder.AddCustomHeader("x-spam-value", "90%");

As you can see this method operates on higher level of abstraction than MIME document, but when the email is created, you can observe that the custom header was actually added to the MIME document root:

IMail email = builder.Create();
string header = email.Document.Root.Headers["x-spam-value"];

Custom headers (those that are not defined by email standards like Date or Subject) should be prefixed with “X-” string (header names case is not important).

Following is the entire sample, that creates new email message, adds custom header. Than it connects to specified SMTP server and sends the message. Please note that some error handling is missing for simplicity and you should examine SendMessageResult result object returned by SendMessage to be sure that email sending was successful.

// C# version

using System;
using Limilabs.Mail;
using Limilabs.Mail.Headers;
using Limilabs.Client.SMTP;

class Program
{
    static void Main(string[] args)
    {
        // Use builder object to create new email message
        MailBuilder builder = new MailBuilder();
        builder.Subject = "Test";
        builder.Text = "This is plain text message.";
        builder.From.Add(new MailBox("alice@mail.com", "Alice"));
        builder.To.Add(new MailBox("bob@mail.com", "Bob"));

        builder.AddCustomHeader("x-spam-value", "90%");

        IMail email = builder.Create();

        // Send the message
        using (Smtp smtp = new Smtp())
        {
            smtp.Connect("server.example.com");    // or ConnectSSL
            smtp.UseBestLogin("user", "password");

            smtp.SendMessage(email);

            smtp.Close();
        }
    }
};
' VB.NET version

Imports System;
Imports Limilabs.Mail
Imports Limilabs.Mail.Headers
Imports Limilabs.Client.SMTP

Public Module Module1
    Public Sub Main(ByVal args As String())

        ' Use builder object to create new email message
        Dim builder As New MailBuilder()
        builder.Subject = "Test"
        builder.Text = "This is plain text message."
        builder.From.Add(New MailBox("alice@mail.com", "Alice"))
        builder.[To].Add(New MailBox("bob@mail.com", "Bob"))

        builder.AddCustomHeader("x-spam-value", "90%")

        Dim email As IMail = builder.Create()

        ' Send the message
        Using smtp As New Smtp()
            smtp.Connect("server.example.com")    ' or ConnectSSL
            smtp.UseBestLogin("user", "password")

            smtp.SendMessage(email)

            smtp.Close()
        End Using

    End Sub
End Module

Fluent interface version:

// C# version

IMail email = Mail.Text("This is plain text message.")
    .Subject("Test")
    .From(New MailBox("alice@mail.com", "Alice"))
    .To("to@mail.com")
    .AddCustomHeader("X-Header", "x header value")
    .Create();

// Send the message
using (Smtp smtp = new Smtp())
{
    smtp.Connect("server.example.com");
    smtp.UseBestLogin("user", "password");
    smtp.SendMessage(email);
    smtp.Close();
}
' VB.NET version

Dim email As IMail = Mail.Text("This is plain text message.") _
  .Subject("Test") _
  .From(New MailBox("alice@mail.com", "Alice")) _
  .[To](New MailBox("bob@mail.com", "Bob")) _
  .AddCustomHeader("X-Header", "x header value") _
  .Create()

' Send the message
Using smtp As New Smtp()
    smtp.Connect("server.example.com")
    smtp.UseBestLogin("user", "password")
    smtp.SendMessage(email)
    smtp.Close()
End Using

The handshake failed due to an unexpected packet format

$
0
0

Most likely your server requires explicit SSL, sometimes also known as TLS. It is called explicit SSL mode because after the connection is established, client explicitly issues a command to the server that initiates SSL/TLS negotiation. This is in contrast to implicit SSL mode where SSL negotiation is initiated just after successful connection. In implicit mode server and client knows to use SSL, because client uses protocol port, that is commonly used for secured traffic.

First try to connect to your server without SSL:

// C#

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

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

Then, before logging-in, start explicit SSL negotiation. The command name differs for different protocols:

IMAP explicit SSL/TLS negotiation

// C#

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

client.Connect("mail.example.com")
client.StartTLS()

POP3 explicit SSL/TLS negotiation

// C#

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

client.Connect("mail.example.com")
client.STLS()

SMTP explicit SSL/TLS negotiation

// C#

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

client.Connect("mail.example.com")
client.StartTLS()

StartTLS and STLS methods negotiate security protocol with the server and secure channel using SSL or TLS. Now, your connection is secured.

Please note, that your server may not need SSL/TLS at all. In such case simply use Connect method.

Enabled SSL Protocols

On very rare occasions “handshake failed…” error may indicate that TLS is incorrectly configured on the client machine or on the server.

It is possible to force SSL v3.0 usage instead of TLS in explicit mode:

// C#

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.Connect("mail.example.com");
client.StartTLS();
' VB.NET

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.Connect("mail.example.com");
client.StartTLS();

It is also possible to force SSL v3.0 usage instead of TLS in implicit mode:

// C#

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.ConnectSSL("mail.example.com");
' VB.NET

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.ConnectSSL("mail.example.com");

Self-signed certificates

Remember that you can ignore SSL certificate errors using ServerCertificateValidate event:

// C#

static void Validate(
    object sender,
    ServerCertificateValidateEventArgs e)
{
    const SslPolicyErrors ignoredErrors =
        SslPolicyErrors.RemoteCertificateChainErrors |
        SslPolicyErrors.RemoteCertificateNameMismatch;

    if ((e.SslPolicyErrors & ~ignoredErrors) == SslPolicyErrors.None)
    {
        e.IsValid = true;
        return;
    }
    e.IsValid = false;
}

client.ServerCertificateValidate += Validate;
client.Connect...
' VB.NET

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

AddHandler client.ServerCertificateValidate, AddressOf Validate
client.Connect...

Read system.net/mailSettings/smtp settings from web.config

$
0
0

There is a standard way of specifying SMTP settings in .NET applications. .NET uses config files (app.config or web.config in case of ASP.NET) and element to specify the appropriate SMTP parameters to send e-mail.

Sample configuration (in this case Gmail SMTP settings) looks as follows:

<configuration>

<system.net>
  <mailSettings>
    <smtp deliveryMethod="network" from="pam@gmail.com">
      <network
        host="smtp.gmail.com"
        port="465"
        enableSsl="true"
        userName="pam@gmail.com"
        password="password"
    />
    </smtp>
  </mailSettings>
</system.net>

</configuration>

If port attribute is omitted default value (25) is used. SMTP protocol typically uses ports 587 and 25 for non SSL connections, and port 465 for SSL ones.

Although Mail.dll SMTP component does not support reading from web.config directly, it is quite easy to read those settings programmatically and use them with Mail.dll classes.

Here’s the simple sample that reads from mailSettings section:

SmtpSection section = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");

string from = section.From;
string host = section.Network.Host;
int port = section.Network.Port;
bool enableSsl = section.Network.EnableSsl;
string user = section.Network.UserName;
string password = section.Network.Password;

Use web.config’s mailSettings with Mail.dll

In most cases you want to send email via SMTP server (DeliveryMethod set to SmtpDeliveryMethod.Network). Here’s the sample that uses web.config settings and Mail.dll’s STMP component to send an email message:

SmtpSection section = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");

IMail email = Fluent.Mail
            .Text("Hi, how are you?")
            .Subject("Hello")
            .To("to@example.com")
            .From(section.From)
            .Create();

using (Smtp client = new Smtp())
{
    client.Connect(section.Network.Host, section.Network.Port, 
        section.Network.EnableSsl);
    client.UseBestLogin(section.Network.UserName, section.Network.Password);
    client.SendMessage(email);
    client.Close();
}

IIS pickup folder

If you plan to use local IIS SMTP service to send emails you created using Mail.dll, you’ll need to save those emails to IIS pickup folder.

You can specify folder location explicitly using SpecifiedPickupDirectory.PickupDirectoryLocation (default location is “c:\Inetpub\mailroot\Pickup”). You can also use SmtpDeliveryMethod.PickupDirectoryFromIis constant – in this case we’ll get pickup folder location directly from IIS metabase.

Following is the code that recognizes different DeliveryMethods and acts accordingly:

SmtpSection section = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");
IMail email = Fluent.Mail
            .Text("Hi, how are you?")
            .Subject("Hello")
            .To("lesnikowski@limilabs.com")
            .From(section.From)
            .Create();

if (section.DeliveryMethod == SmtpDeliveryMethod.Network)
{
    using (Smtp client = new Smtp())
    {
        client.Connect(section.Network.Host, section.Network.Port, 
            section.Network.EnableSsl);
        client.UseBestLogin(section.Network.UserName, section.Network.Password);
        client.SendMessage(email);
        client.Close();
    }
}
else if (section.DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory)
{
    string pickupFolder = section.SpecifiedPickupDirectory.PickupDirectoryLocation;
    email.Save(pickupFolder);
}
else if (section.DeliveryMethod == SmtpDeliveryMethod.PickupDirectoryFromIis)
{
    Assembly system = AppDomain.CurrentDomain.GetAssemblies()
        .First(x => x.GetName().Name == "System");
    
    Type iisPickupType = Type.GetType(
        "System.Net.Mail.IisPickupDirectory, " 
        + system.GetName().FullName, 
        true);
    // -or- use fully qualified system assembly name directly:
    //Type iisPickupType = Type.GetType(
    //    "System.Net.Mail.IisPickupDirectory, " 
    //    + "System, Version=4.0.0.0, Culture=neutral, "
    //    + "PublicKeyToken=b77a5c561934e089",
    //    true);

    string pickupFolder = (string)iisPickupType.InvokeMember(
        "GetPickupDirectory", 
        BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, 
        null, null, null);

    email.Save(pickupFolder);
}

Get IIS pickup directory location

$
0
0

If you plan to use local IIS SMTP service to send emails you created using Mail.dll, you’ll need to save those emails to IIS pickup folder.

Default folder location is “c:\Inetpub\mailroot\Pickup”

There is a way to get IIS pickup folder location directly from IIS metabase. To get this path programmatically we’ll use IisPickupDirectory class. Unfortunatelly this class is not public, we’ll use its name to get its type and Activator class to invoke private static method GetPickupDirectory.

To get type reference, we need to specify fully qualified type name “System.Net.Mail.IisPickupDirectory System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″. We can also search CurrentDomain’s assemblies to find System assembly and get its full name.

Assembly system = AppDomain.CurrentDomain.GetAssemblies()
    .First(x => x.GetName().Name == "System");

Type iisPickupType = Type.GetType(
    "System.Net.Mail.IisPickupDirectory, "
    + system.GetName().FullName,
    true);
// -or- use fully qualified assembly name directly:
//Type iisPickupType = Type.GetType(
//    "System.Net.Mail.IisPickupDirectory, "
//    + "System, Version=4.0.0.0, Culture=neutral, " 
//    + "PublicKeyToken=b77a5c561934e089",
//    true);

string pickupFolder = (string)iisPickupType.InvokeMember(
    "GetPickupDirectory",
    BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
    null, null, null);

“Cannot get IIS pickup directory.” error

Unfortunately, this exception is raised when any kind of problem occurs, while trying to determine the location of IIS/SMTP pickup directory.

A common cause is simply missing IIS SMTP service.

The pickup directory is stored in the IIS Metabase, so if the account that your web-app runs as does not have access to the required nodes, this error can be thrown. Metabase permissions are separate from file permissions, so you explore it with Metabase explorer (part of the IIS resource kit).

These nodes need to have read permission given to your web-app user: \LM, \LM\Smtpsrv\ and \LM\Smtpsrv\1

Copy to public folder: “There is no replica for that mailbox on this server.”

$
0
0

When copying an mail from the personal folder to a Public folder, you may receive “There is no replica for that mailbox on this server.” error.

This error is generated by Exchange server and unfortunately this is the Exchange limitation. Here’s Microsoft’s response to this problem:

Copying from user mailboxes to public folders does not work with Exchange IMAP4.
Your users need to copy the message to a personal folder and then back up to the public folder (append) or forward it to an address that gets archived to a public folder.

Workaround

It seems the only way to workaround this is by downloading a message and uploading it to public folder. Important thing is that you don’t need to parse email message at all – you just download and upload raw eml data. Please also note that public folder may be in fact stored on a another IMAP server instance (different server address) – this is usually indicated during logon with REFERRAL error.

// C#

using(Imap imap = new Imap())
{
    imap.Connect("imap.example.com");   // or ConnectSSL for SSL
    imap.UseBestLogin("user", "password");

    imap.SelectInbox();
    List<long> uids = imap.Search(Flag.All);

    foreach (long uid in uids)
    {
        string eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);

        if (email.Subject.Contains("[REF"))
            UploadToPublic(eml);
    }
    imap.Close();
}
' VB.NET

Using imap As New Imap()
	imap.Connect("imap.example.com")	' or ConnectSSL for SSL
	imap.UseBestLogin("user", "password")

	imap.SelectInbox()
	Dim uids As List(Of Long) = imap.Search(Flag.All)

	For Each uid As Long In uids
		Dim eml As String = imap.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)

		If email.Subject.Contains("[REF") Then
			UploadToPublic(eml)
		End If
	Next
	imap.Close()
End Using

Here is the body of UploadToPublic method:

// C# code

private void UploadToPublic(string eml)
{
    using (Imap imap = new Imap())
    {
        imap.Connect("server");  // or ConnectSSL for SSL
        imap.Login("user", "password");
    
        imap.UploadMessage("#Public/Cases", eml);
    
        imap.Close();
    }
}
' VB.NET code

Private Sub UploadToPublic(eml As String)
	Using imap As New Imap()
		imap.Connect("server")
		' or ConnectSSL for SSL
		imap.Login("user", "password")

		imap.UploadMessage("#Public/Cases", eml)

		imap.Close()
	End Using
End Sub

Outlook.com announces IMAP support

$
0
0

We’re happy to announce that Microsoft finally added IMAP (and OAuth) support for Outlook.com (this includes @hotmail accounts). With yesterday’s announcement, Outlook has a richer email experience across devices and apps.

Here you can find Outlook.com settings.

OAuth 2.0 with Outlook.com over IMAP for web applications

$
0
0
You can also read how to use:

Outlook

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

This article describes using OAuth 2.0 to access Outlook.com IMAP and SMTP servers using .NET IMAP component in web application scenario (ASP.NET/ASP.NET MVC). You can also use OAuth 2.0 with Outlook.com for installed/native applications.

DotNetOpenAuth

First download the latest version of DotNetOpenAuth – it’s free, open source library that implements OAuth 2.0: http://www.dotnetopenauth.net

Add it as a reference and import namespaces:

// c#

using DotNetOpenAuth.OAuth2;
' VB.NET 

Imports DotNetOpenAuth.OAuth2

Register Application

Before you can use OAuth 2.0, you must register your application using the application management site. After you’ve registered, go to the My applications and create new application. On “API Settings” page, copy “Client ID” and “Client secret” values and specify “Redirect domain“, which you’ll need later.

Outlook_AddAppKeys

Redirect domain must be valid domain address. You can’t use localhost, which is problematic during development phase. For testing purposes you can use fake domain. You just need to modify your computer’s hosts file (c:\Windows\System32\drivers\etc\hosts) so it redirects to localhost:
127.0.0.1 fake-domain-9065436322.com

Now we can define clientID, clientSecret, redirect url and scope variables, as well as Outlook.com OAuth 2.0 server addresses. Scope basically specifies what services we want to have access to. In our case it is user’s email address and IMAP/SMTP access:

string clientID = "000000014810009D";
string clientSecret = "wiRCccXnq1uyKcXnq1uyK";
string redirectUri = "http://fake-domain-9650932456.com/OAuth2.aspx";

AuthorizationServerDescription server = new AuthorizationServerDescription
{
    AuthorizationEndpoint = new Uri("https://login.live.com/oauth20_authorize.srf"),
    TokenEndpoint = new Uri("https://login.live.com/oauth20_token.srf"),
    ProtocolVersion = ProtocolVersion.V20,
};

List<string> scope = new List<string> { OutlookScope.ImapAndSmtp.Name, OutlookScope.EmailAddress.Name };

Obtain an OAuth 2.0 access token

As we are using ASP.NET we’ll use WebServerClient class:

WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);

// Here redirect to authorization site occurs
consumer.RequestUserAuthorization(scope, new Uri(redirectUri));

If you use ASP.NET MVC the last line is different:

// Here redirect to authorization site occurs
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
    scope, new Uri(redirectUri));
return response.AsActionResult();

At this point user is redirected to Microsoft to authorize the access:

Outlook_2Confirm

After this step user is redirected back to your website (http://fake-domain-9650932456.com/OAuth2.aspx). Following is this callback code. Its purpose is to get a refresh-token and an access-token:

WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(null);

string accessToken = grantedAccess.AccessToken;

An access token is usually short lived, and allows you to access the user’s data. You also received a refresh token. A refresh token can be used to request a new access token once the previous expired.

Access IMAP/SMTP server

Finally we’ll ask Microsoft for user’s email and use LoginOAUTH2 method to access Outlook.com IMAP server:

OutlookApi api = new OutlookApi(accessToken);
string user = api.GetEmail();

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap-mail.outlook.com");
    imap.LoginOAUTH2(user, accessToken);

    imap.SelectInbox();
    List<long> uids = imap.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        string eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    imap.Close();
}

Refreshing access token

An access token is usually short lived. The main reason behind this is security and prevention of replay attacks. This means that for long-lived applications you need to refresh the access token.

In most cases web applications don’t need to refresh access token (they request new one every time), thus when using WebServerClient refresh token is not sent. To force sending refresh token you need to add “wl.offline_access” to requested scopes:


List<string> scope = new List<string> { OutlookScope.ImapAndSmtp.Name, OutlookScope.EmailAddress.Name, OutlookScope.OfflineAccess.Name };

Your refresh token will be sent only once – don’t loose it!

We recommend storing entire IAuthorizationState object received from WebServerClient.ProcessUserAuthorization method call. This object contains both: refresh token and access token, along with its expiration time.

The process of refreshing access token is simple:

IAuthorizationState grantedAccess = ...
consumer.RefreshAuthorization(grantedAccess, TimeSpan.FromMinutes(20));

In the example above the access token will not be refreshed if its remaining lifetime exceeds 20 minutes.

Apps and services you’ve given access

Users can manage consent for applications and services that can access some of their data on consent panel

OAuth 2.0 with Outlook.com over IMAP for installed applications

$
0
0
You can also read how to use:

Outlook

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

This article describes using OAuth 2.0 to access Outlook.com IMAP and SMTP servers using .NET IMAP component in installed applications scenario. You can also use OAuth 2.0 with Outlook.com for web applications.

DotNetOpenAuth

First download the latest version of DotNetOpenAuth – it’s free, open source library that implements OAuth 2.0: http://www.dotnetopenauth.net

Register Application

Before you can use OAuth 2.0, you must register your application using the application management site. After you’ve registered, go to the My applications and create new application. On “API Settings” page, copy “Client ID” and “Client secret” values and specify “Redirect domain“, which you’ll need later.

Outlook_AddAppKeys

Redirect domain must be valid domain address. For installed applications you can use domain address provided by Microsoft:
“https://login.live.com/oauth20_desktop.srf”

Now we can define clientID, clientSecret and scope variables, as well as Outlook.com OAuth 2.0 server addresses. Scope basically specifies what services we want to have access to. In our case it is user’s email address and IMAP/SMTP access:

string clientID = "000000014810009D";
string clientSecret = "wiRCccXnq1uyKcXnq1uyK";

AuthorizationServerDescription server = new AuthorizationServerDescription
{
    AuthorizationEndpoint = new Uri("https://login.live.com/oauth20_authorize.srf"),
    TokenEndpoint = new Uri("https://login.live.com/oauth20_token.srf"),
    ProtocolVersion = ProtocolVersion.V20,
};
List<string> scope = new List<string> { OutlookScope.ImapAndSmtp.Name, OutlookScope.EmailAddress.Name };

Obtain an OAuth 2.0 access token

As we are using installed applications scenario we’ll use NativeApplicationClient class.

Because of a small issue in DotNetOpenAuth we can not use UserAgentClient directly. NativeApplicationClient inherits UserAgentClient and workarounds this issue. You can find the implementation of NativeApplicationClient on the bottom of this article.

NativeApplicationClient consumer = new NativeApplicationClient(server, clientID, clientSecret);
Uri userAuthorizationUri = consumer.RequestUserAuthorization(scope);

Process.Start(userAuthorizationUri.AbsoluteUri);

We are using Process.Start here, but you can also embed WebBrowser control in your application.

At this point user is redirected to Microsoft to authorize the access:

Outlook_2Confirm

After this step user is presented a code that needs to be pasted to your application:

Outlook_3Redirect

Please note that this code also appears in the title of the browser:

  • It is possible to monitor processes on your machine and act automatically when it is there.
  • If you use embedded WebBrowser control in your application, you can monitor the HTML document title after any redirect.

Following is a code that reads this code and contacts Outlook.com to exchange it for a refresh-token and an access-token:

string authCode = Console.ReadLine();

consumer.ClientCredentialApplicator = 
    ClientCredentialApplicator.PostParameter(clientSecret);

IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(authCode);

string accessToken = grantedAccess.AccessToken;

An access token is usually short lived, and allows you to access the user’s data. You may also received a refresh token. A refresh token can be used to request a new access token once the previous expired.

Access IMAP/SMTP server

Finally we’ll ask Microsoft for user’s email and use LoginOAUTH2 method to access Outlook.com IMAP server:

IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(authCode);
string accessToken = grantedAccess.AccessToken;

OutlookApi api = new OutlookApi(accessToken);
string user = api.GetEmail();

using (Imap client = new Imap())
{
    client.ConnectSSL("imap-mail.outlook.com");

    imap.LoginOAUTH2(user, accessToken);

    imap.SelectInbox();
    List<long> uids = imap.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        string eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    imap.Close();
}

NativeApplicationClient class

The OAuth 2.0 client for use by native applications. It’s main purpose is to build a generic URL containing the auth code, because DotNetOpenAuth library only allows parsing an URL as a method of retrieving the AuthorizationState.

/// <summary>
/// The OAuth2 client for use by native applications. 
/// This is a partial implementation which  should be used until 
/// the feature has been fully implemented in DotNetOpenAuth.
/// </summary>
public class NativeApplicationClient : UserAgentClient
{
    /// <summary>
    /// Represents a callback URL which points to a special out of band page 
    /// used for native OAuth2 authorization. This URL will cause the authorization 
    /// code to appear in the title of the window.
    /// </summary>
    public string OutOfBandCallbackUrl = "https://login.live.com/oauth20_desktop.srf"; // Outlook

    /// <summary>
    /// Initializes a new instance of the <see cref="UserAgentClient"/> class.
    /// </summary>
    /// <param name="authorizationServer">The token issuer.</param>
    /// <param name="clientIdentifier">The client identifier.</param>
    /// <param name="clientSecret">The client secret.</param>
    public NativeApplicationClient(
        AuthorizationServerDescription authorizationServer,
        string clientIdentifier,
        string clientSecret)
        : base(authorizationServer, clientIdentifier, clientSecret)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="UserAgentClient"/> class.
    /// </summary>
    /// <param name="authorizationServer">The token issuer.</param>
    public NativeApplicationClient(
        AuthorizationServerDescription authorizationServer)
        : this(authorizationServer, null, null)
    {
    }

    /// <summary>
    /// Creates the URL which should be used by the user to request the initial 
    /// authorization. Uses the default Out-of-band-URI as a callback.
    /// </summary>
    /// <param name="scope">Set of requested scopes</param>
    /// <returns>URI pointing to the authorization server</returns>
    public Uri RequestUserAuthorization(IEnumerable<string> scope)
    {
        var state = new AuthorizationState(scope);
        state.Callback = new Uri(OutOfBandCallbackUrl);
        return RequestUserAuthorization(state, false, null);
    }

    /// <summary>
    /// Uses the provided authorization code to create an authorization state.
    /// </summary>
    /// <param name="authCode">The authorization code for getting an access token.</param>
    /// <param name="authorizationState">The authorization.  Optional.</param>
    /// <returns>The granted authorization, or <c>null</c> if the authorization was null or rejected.</returns>
    public IAuthorizationState ProcessUserAuthorization(
        string authCode, 
        IAuthorizationState authorizationState)
    {
        if (authorizationState == null)
        {
            authorizationState = new AuthorizationState(null);
            authorizationState.Callback = new Uri(OutOfBandCallbackUrl);
        }

        // Build a generic URL containing the auth code.
        // This is done here as we cannot modify the DotNetOpenAuth library 
        // and the underlying method only allows parsing an URL as a method 
        // of retrieving the AuthorizationState.
        string url = "http://example.com/?code=" + authCode;
        return ProcessUserAuthorization(new Uri(url), authorizationState);
    }

    /// <summary>
    /// Uses the provided authorization code to create an authorization state.
    /// </summary>
    /// <param name="authCode">The authorization code for getting an access token.</param>
    /// <returns>The granted authorization, or <c>null</c> if the authorization was null or rejected.</returns>
    public IAuthorizationState ProcessUserAuthorization(string authCode)
    {
        return ProcessUserAuthorization(authCode, null);
    }
};

Refreshing access token

An access token is usually short lived. The main reason behind this is security and prevention of replay attacks. This means that for long-lived applications you need to refresh the access token. To force sending refresh token you need to add “wl.offline_access” to requested scopes:


List<string> scope = new List<string> { OutlookScope.ImapAndSmtp.Name, OutlookScope.EmailAddress.Name, OutlookScope.OfflineAccess.Name };

Refreshing access token

We recommend storing entire IAuthorizationState object received from WebServerClient.ProcessUserAuthorization method call. This object contains both: refresh token and access token, along with its expiration time.

The process of refreshing access token is simple:

IAuthorizationState grantedAccess = ...
consumer.RefreshAuthorization(grantedAccess, TimeSpan.FromMinutes(20));

In the example above the access token will not be refreshed if its remaining lifetime exceeds 20 minutes.

Apps and services you’ve given access

Users can manage consent for applications and services that can access some of their data on consent panel

Remove attachments from email

$
0
0

First, there is one thing you must be aware of: neither POP3 nor IMAP protocol provide a way to remove attachments from existing emails. This is because email stored on the server is immutable. With IMAP protocol you can copy email message to different folder, you can apply some flags (\SEEN) to it, but you can’t change any part of the message.

The second important thing is, that attachments are not stored separately from the message text and headers – they are embedded inside the email.

Nevertheless Mail.dll provides an easy way to remove attachments from existing email message.

// C#

IMail email = new MailBuilder().CreateFromEml(eml);
email.RemoveAttachments();

' VB.NET

Dim email As IMail = New MailBuilder().CreateFromEml(eml)
email.RemoveAttachments()

RemoveAttachments method has an overloaded version, that allows you to skip visual elements (content-disposition: inline) or/and alternative email representations:

// C#

IMail email = new MailBuilder().CreateFromEml(eml);

AttachmentRemoverConfiguration configuration = new AttachmentRemoverConfiguration();
configuration.RemoveVisuals = false;

email.RemoveAttachments(configuration);

' VB.NET


Dim email As IMail = New MailBuilder().CreateFromEml(eml)

Dim configuration As New AttachmentRemoverConfiguration()
configuration.RemoveVisuals = False

email.RemoveAttachments(configuration)

The following example illustrates the full process of downloading email from IMAP server,
creating new email, with the same information, but without attachments, and finally uploading it, and removing the original message:

// C#

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

    foreach (long uid in imap.GetAll())
    {
        string eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        if (email.Attachments.Count > 0)
        {
            email.RemoveAttachments();

            imap.UploadMessage(email);

            imap.DeleteMessageByUID(uid);
        }
    }
    imap.Close();
}
' VB.NET

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

   For Each uid As Long In imap.GetAll()
      Dim eml As String = imap.GetMessageByUID(uid)
      Dim email As IMail = New MailBuilder().CreateFromEml(eml)
      If email.Attachments.Count > 0 Then
         email.RemoveAttachments()

         imap.UploadMessage(email)

         imap.DeleteMessageByUID(uid)
      End If
   Next
   imap.Close()
End Using

Tried to read a line. Only ” received.

$
0
0

MailForWindowsStore.dll

If you are using MailForWindowsStore.dll: Connect and ConnectSSL methods are asynchronous. You must use await ConnectSSL and await Connect:

// C#

using(Imap imap = new Imap())
{
    await client.ConnectSSL("imap.example.com");
    await client.UseBestLoginAsync("user", "password");
    // ...
}   
' VB.NET

Using imap As New Imap()
    Await client.ConnectSSL("imap.example.com")
    Await client.UseBestLoginAsync("user", "password")
    ' ...
End Using

Please make sure that you have disabled antivirus and firewall software or that you have configured it correctly.

Mail.dll

If you are using Mail.dll:

Please make sure that you have disabled antivirus and firewall software or that you have configured it correctly. This includes Windows Defender – especially if you use SMTP client.

In most cases this error means that the connection was interrupted. It was lost, the server disconnected or your antivirus/firewall cut the connection.

In technical terms, it means exactly what the exception’s message says: component tried to read a line (ending with CRLF), the line was expected in accordance to the protocol in use, but it has not been received during the specified period.

On extremely slow networks you may increase timeout values: ReceiveTimeout and SendTimeout.

In most cases you’ll need to connect again.

Viewing all 120 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>