Email component | Blog | Limilabs https://www.limilabs.com/blog Fri, 11 Aug 2023 09:08:33 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.4 Save all attachments to disk using IMAP https://www.limilabs.com/blog/save-all-attachments-to-disk-using-imap https://www.limilabs.com/blog/save-all-attachments-to-disk-using-imap#comments Fri, 28 Jul 2023 15:41:29 +0000 http://www.limilabs.com/blog/?p=1232 Unlock the potential of efficient email processing in .NET with this comprehensive guide on saving email attachments using Mail.dll’s IMAP component and IMAP protocol. In this article, we’ll walk you through the step-by-step process of downloading email messages using Mail.dll’s IMAP component and, more importantly, demonstrate how to effortlessly save all attachments to your disk. […]

The post Save all attachments to disk using IMAP first appeared on Blog | Limilabs.

]]>
Unlock the potential of efficient email processing in .NET with this comprehensive guide on saving email attachments using Mail.dll’s IMAP component and IMAP protocol.

In this article, we’ll walk you through the step-by-step process of downloading email messages using Mail.dll’s IMAP component and, more importantly, demonstrate how to effortlessly save all attachments to your disk.

Email MIME structure

The first thing you need to know is that email attachments are tightly integrated with the email message itself, ensuring that they are conveniently stored and transport together.

In MIME (Multipurpose Internet Mail Extensions), email attachments are stored together with an email message to ensure a structured and standardized way of transmitting and receiving email content with multiple parts.

The primary reason for storing attachments within the email message is to maintain a single cohesive entity that encapsulates all the components of the email, including its text, formatting, and any attached files. By bundling attachments with the message, the entire email becomes a self-contained package, making it easier to handle and process by email clients and servers.

Attachments in Mail.dll

Invoking Imap.GetMessageByUID method is going to download entire email message, including all attachments.

Attachments are stored within the email as part of a mime tree. Usually Quoted-Printable or Base64 encoding is used.

However, with Mail.dll, you don’t need to worry about navigating this mime tree yourself. The library efficiently parses the email’s structure, effortlessly exposing all attachments as familiar .NET collections. This user-friendly approach simplifies the process of handling email attachments, allowing you to focus on building robust and efficient email management solutions with ease.

There are 4 collections that contain attachments:

  • IMail.Attachments – all attachments (includes Visuals, NonVisuals and Alternatives).
  • IMail.Visuals – visual elements, files that should be displayed to the user, such as images embedded in an HTML email.
  • IMail.NonVisuals – non visual elements, “real” attachments.
  • IMail.Alternatives – alternative content representations, for example ical appointment.

How to download all email attachments in .NET

To retrieve all attachments from an email, you can take advantage of the IMail.Attachments collection provided by Mail.dll. This collection serves as a comprehensive repository, housing all the attachments associated with the email.

Each attachment is represented by a dedicated MimeData object, which encapsulates the specific data and metadata of the attachment.

Using the IMail.Attachments collection eliminates the need for manual traversal of the email’s mime tree or deciphering complex structures. Instead, Mail.dll handles the heavy lifting for you, ensuring a streamlined and user-friendly experience when accessing email attachments programmatically.

Installation

The easiest way to install Mail.dll is to download it from nuget via Package Manager:

PM> Install-Package Mail.dll

Alternatively you can download Mail.dll directly from our website.

Download emails and all attachments

The C# code to download all emails and save all attachments using Mail.dll and IMAP is as follows.

using Limilabs.Client.IMAP;
using Limilabs.Mail;
using Limilabs.Mail.MIME
using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        using(Imap imap = new Imap())
        {
            imap.ConnectSSL("imap.example.com");
            imap.UseBestLogin("user", "password");

            imap.SelectInbox();
            List<long>; uids = imap.Search(Flag.Unseen);
            foreach (long uid in uids)
            {
                var eml = imap.GetMessageByUID(uid);
                IMail email = new MailBuilder()
                    .CreateFromEml(eml);
                Console.WriteLine(email.Subject);

                foreach (MimeData mime in email.Attachments)
                {
                    mime.Save(@"c:\" + mime.SafeFileName);
                }
            }
            imap.Close();
        }
    }
};

Below is the VB.NET code for your reference:

Imports Limilabs.Client.IMAP
Imports Limilabs.Mail
Imports Limilabs.Mail.MIME
Imports System
Imports System.Collections.Generic

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

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

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

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

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

    End Sub
End Module

Accessing attachment data

You can also save attachment to a specific stream using MimeData.Save(Stream stream).

You can get direct access to attachment binary data with MimeData.GetMemoryStream().

Finally you can get a byte array (byte[]) using MimeData.Data property.

Downloading only parts of the message

IMAP protocol provides very useful features in regard to working with attachments and all are available in Mail.dll .NET IMAP component.

With Mail.dll you can download only parts of email message, which in conjunction with getting basic email information without downloading entire message can make your code extremely fast.

Process emails embedded as attachments

In some situations you’ll receive a message that has another message attached.

You can use Mail.dll to extract all attachments from such inner messages no matter how deep the embedding level is.


Get Mail.dll

The post Save all attachments to disk using IMAP first appeared on Blog | Limilabs.

]]>
https://www.limilabs.com/blog/save-all-attachments-to-disk-using-imap/feed 10
Mail.dll is not affected by Mailsploit https://www.limilabs.com/blog/mail-dll-is-not-affected-by-mailsploit Tue, 05 Dec 2017 15:44:35 +0000 https://www.limilabs.com/blog/?p=5382 The Mailsploit vulnerability stems from how email servers/clients interpret email addresses containing encoded words. Incorrectly handling those, could allow an attacker to spoof email identities. Recent specs (RFC-2822 and RFC-5322) don’t allow using encoded-words for email addresses (addr-spec): 3.4. Address Specification: address = mailbox / group mailbox = name-addr / addr-spec name-addr = [display-name] angle-addr […]

The post Mail.dll is not affected by Mailsploit first appeared on Blog | Limilabs.

]]>
The Mailsploit vulnerability stems from how email servers/clients interpret email addresses containing encoded words. Incorrectly handling those, could allow an attacker to spoof email identities.

Recent specs (RFC-2822 and RFC-5322) don’t allow using encoded-words for email addresses (addr-spec):

3.4. Address Specification:
address = mailbox / group
mailbox = name-addr / addr-spec
name-addr = [display-name] angle-addr
angle-addr = [CFWS] “<" addr-spec ">” [CFWS] / obs-angle-addr
display-name = phrase

Here are the unit test that show how Mail.dll behaves when such malicious emails are parsed. Please note that encoded-words are not decoded when part of email address.

[Test]
public void Test1()
{
    string eml = @"From: =?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=@example.com

Body";

    IMail mail = new MailBuilder().CreateFromEmlASCII(eml);

    Assert.AreEqual(
        "=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=@example.com", 
        mail.Headers["From"]);

    Assert.AreEqual(
        "=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=@example.com", 
        mail.From[0].Address);                                      // Correct

    Assert.AreEqual(
        null, 
        mail.From[0].Name);                                         // Correct
}
[Test]
public void Test2()
{
    string eml = @"From: =?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=

Body";

    IMail mail = new MailBuilder().CreateFromEmlASCII(eml);

    Assert.AreEqual(
        "=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=", 
        mail.Headers["From"]);

    Assert.AreEqual(
        null, 
        mail.From[0].Address);                // Correct

    Assert.AreEqual(
        "potus@whitehouse.gov", 
        mail.From[0].Name);      // Correct - this is correct behavior, 
                                 // sender can put anything in the name field.
}
[Test]
public void Test3()
{
    string eml = @"From: =?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=" 
        + @"=?utf-8?Q?=00?=" 
        + @"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=@example.com

Body";

    IMail mail = new MailBuilder().CreateFromEmlASCII(eml);

    Assert.AreEqual(
        @"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=" 
        + @"=?utf-8?Q?=00?=" 
        + @"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=@example.com", 
        mail.Headers["From"]);

    Assert.AreEqual(
        @"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=" 
        + @"=?utf-8?Q?=00?=" 
        + @"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=@example.com", 
        mail.From[0].Address);            // Correct

    Assert.AreEqual(
        null, 
        mail.From[0].Name);               // Correct
}

Mail.dll allows anything in the name part of the address headers:

[Test]

public void Test4()
{
    string eml = @"From: =?utf-8?Q?=42=45=47=49=4E=20=2F"
        + @"=20=20=2F=20=00=20=50=41=53=53=45=44" 
        + @"=20=4E=55=4C=4C=20=42=59=54=45=20=2F=20=0D=0A" 
        + @"=20=50=41=53=53=45=44=20=43=52" 
        + @"=4C=46=20=2F=20=45=4E=44?= <test@example.com>

Body";

    IMail mail = new MailBuilder().CreateFromEmlASCII(eml);

    Assert.AreEqual(
        "test@example.com", 
        mail.From[0].Address);

    Assert.AreEqual(
        "BEGIN /  / \0 PASSED NULL BYTE / \r\n PASSED CRLF / END", 
        mail.From[0].Name); 

    // Note the \r\n (new line) and \0 (null) characters
}

Specification allow using encoded-words in the name (RFC2047 – 5. Use of encoded-words in message headers. (3) )
Encoded words are used to encode non-ASCII characters, for example national characters like umlauts (ä, ö, ü).

RFC2047 imposes no restrictions what characters can be encoded, which means that zero byte (\0) and new lines (\r\n) are valid characters.

Client applications must ensure that such special charters don’t ‘push’ the actual email address (“”test@example.com”) outside of control, in such way, that it becomes not visible.
It is crucial for them to display the email address (test@example.com) no matter what is in the name field.

The post Mail.dll is not affected by Mailsploit first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for installed applications https://www.limilabs.com/blog/oauth2-gmail-imap-installed-applications Mon, 13 Mar 2017 05:01:09 +0000 http://www.limilabs.com/blog/?p=3359 You can also read how to use:   OAuth 2.0 with Gmail over IMAP for web applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for installed applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for service account (Google.Apis) OAuth 2.0 is an open protocol to allow secure API authorization in a simple and standard method […]

The post OAuth 2.0 with Gmail over IMAP for installed applications first appeared on Blog | Limilabs.

]]>
You can also read how to use:

 

OAuth 2.0 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 Gmail IMAP and SMTP servers using .NET IMAP component in installed applications scenario. You can also use OAuth 2.0 for web applications.

Google.Apis

Use Nuget to download “Google.Apis.Auth” package.

Import namespaces:

// c#

using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Auth.OAuth2.Responses;

using Limilabs.Client.Authentication.Google;

using Limilabs.Client.IMAP;
' VB.NET 

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Flows
Imports Google.Apis.Auth.OAuth2.Requests
Imports Google.Apis.Auth.OAuth2.Responses

Imports Limilabs.Client.Authentication.Google

Imports Limilabs.Client.IMAP

Register Application

Before you can use OAuth 2.0, you must register your application using the Google Cloud Console. After you’ve registered copy the “Client ID” and “Client secret” values which you’ll need later.

At least product name must be specified:

Now create credentials:

After you’ve registered, copy the “Client ID” and “Client secret” values, which you’ll need later:

Now we can define clientID, clientSecret and scope variables, as well as Google 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:

// C#

string clientID = "XXX.apps.googleusercontent.com";
string clientSecret = "IxBs0g5sdaSDUz4Ea7Ix-Ua";

var clientSecrets = new ClientSecrets
{
    ClientId = clientID,
    ClientSecret = clientSecret
};

var credential = new GoogleAuthorizationCodeFlow(
    new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = clientSecrets,
        Scopes = new[] { 
            GoogleScope.ImapAndSmtp.Name, 
            GoogleScope.UserInfoEmailScope.Name}
    });
' VB.NET 

Dim clientID As String = "XXX.apps.googleusercontent.com"
Dim clientSecret As String = "IxBs0g5sdaSDUz4Ea7Ix-Ua"

Dim clientSecrets = New ClientSecrets With { _
	.ClientId = clientID, _
	.ClientSecret = clientSecret _
}

Dim credential = New GoogleAuthorizationCodeFlow( _
    New GoogleAuthorizationCodeFlow.Initializer With { _
    	.ClientSecrets = clientSecrets, _
	    .Scopes = { _
            GoogleScope.ImapAndSmtp.Name,  _
            GoogleScope.UserInfoEmailScope.Name} _
})

Obtain an OAuth 2.0 access token

Now we’ll create authorization url.

As OOB addresses are no longer supported, to receive the authorization code using this URL, your application must be listening on the local web server. This is the recommended mechanism for obtaining the authorization code.

When your app receives the authorization response, for best usability it should respond by displaying an HTML page that instructs the user to close the browser and return to your app.

Your application needs to create a server that listens on this local address:

http://127.0.0.1:port or http://[::1]:port or http://localhost:port

Query your platform for the relevant loopback IP address and start an HTTP listener on a random available port. Substitute port with the actual port number your app listens on.

// C#

AuthorizationCodeRequestUrl url = credential
    .CreateAuthorizationCodeRequest("http://127.0.0.1:1234/auth2callback");

Process.Start(url.Build().ToString());
' VB.NET 

Dim url As AuthorizationCodeRequestUrl = credential _
    .CreateAuthorizationCodeRequest("http://127.0.0.1:1234/auth2callback")

Process.Start(url.Build().ToString())

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

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

After this step user is redirected to the loopback address you provided.

Your application’s local web server, listening on this address, should obtain code from the url parameter.

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

string authCode = "get from the url";

TokenResponse token = await credential.ExchangeCodeForTokenAsync(
    "", 
    authCode, 
    "http://127.0.0.1/auth2callback", 
    CancellationToken.None);

string accessToken = token.AccessToken;
' VB.NET 

Dim authCode As String = "get from the url";

Dim token As TokenResponse = Await credential.ExchangeCodeForTokenAsync( _
    "",  _
    authCode,  _
    "http://127.0.0.1/auth2callback",  _
    CancellationToken.None)

Dim accessToken As String = token.AccessToken

An access token is usually valid for a maximum of one hour, 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 Google for user’s email and use LoginOAUTH2 method to access Gmail’s IMAP server:

// C#

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

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

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

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

Dim api As New GoogleApi(accessToken)
Dim user As String = api.GetEmail()

Using imap As New Imap()
	imap.ConnectSSL("imap.gmail.com")
	imap.LoginOAUTH2(user, accessToken)

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

	For Each uid As Long In uids
		Dim eml = imap.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)
		Console.WriteLine(email.Subject)
	Next
	imap.Close()
End Using

Refreshing access token

An access token is usually short lived and valid for a maximum of one hour. 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.

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

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

The process of refreshing access token is simple:

// c#

TokenResponse refreshed = await credential.RefreshTokenAsync(
    "", 
    token.RefreshToken, 
    CancellationToken.None);
' VB.NET 

Dim refreshed As TokenResponse = Await credential.RefreshTokenAsync( _
    "",  _
    token.RefreshToken,  _
    CancellationToken.None)

The post OAuth 2.0 with Gmail over IMAP for installed applications first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for web applications https://www.limilabs.com/blog/oauth2-gmail-imap-web-applications Mon, 13 Mar 2017 05:00:39 +0000 http://www.limilabs.com/blog/?p=3328 You can also read how to use:   OAuth 2.0 with Gmail over IMAP for web applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for installed applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for service account (Google.Apis) OAuth 2.0 is an open protocol to allow secure API authorization in a simple and standard method […]

The post OAuth 2.0 with Gmail over IMAP for web applications first appeared on Blog | Limilabs.

]]>
You can also read how to use:

 

OAuth 2.0 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 Gmail IMAP and SMTP servers using .NET IMAP component in web application scenario (ASP.NET/ASP.NET MVC). You can also use OAuth 2.0 for installed applications.

Google.Apis

Use Nuget to download “Google.Apis.Auth” package.

Import namespaces:

// c#

using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Auth.OAuth2.Responses;

using Limilabs.Client.Authentication.Google;

using Limilabs.Client.IMAP;
' VB.NET 

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Flows
Imports Google.Apis.Auth.OAuth2.Requests
Imports Google.Apis.Auth.OAuth2.Responses

Imports Limilabs.Client.Authentication.Google

Imports Limilabs.Client.IMAP

Register Application

Before you can use OAuth 2.0, you must register your application using the Google Cloud Console. After you’ve registered, specify “Redirect URI” and copy the “Client ID” and “Client secret” values which you’ll need later.

At least product name must be specified:

Now create credentials:

Specify redirect URI:

After you’ve registered, copy the “Client ID” and “Client secret” values, which you’ll need later:

Now we can define clientID, clientSecret, redirect url and scope variables, as well as Google 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:

// C#

string clientID = "XXX.apps.googleusercontent.com";
string clientSecret = "IxBs0g5sdaSDUz4Ea7Ix-Ua";
string redirectUri = "https://www.example.com/oauth2callback";

var clientSecrets = new ClientSecrets
{
    ClientId = clientID,
    ClientSecret = clientSecret
};

var credential = new GoogleAuthorizationCodeFlow(
    new GoogleAuthorizationCodeFlow.Initializer
        {
            ClientSecrets = clientSecrets,
            Scopes = new[] { 
                GoogleScope.ImapAndSmtp.Name, 
                GoogleScope.UserInfoEmailScope.Name}
        });
' VB.NET 

Dim clientID As String = "XXX.apps.googleusercontent.com"
Dim clientSecret As String = "IxBs0g5sdaSDUz4Ea7Ix-Ua"
Dim redirectUri As String = "https://www.example.com/oauth2callback"

Dim clientSecrets = New ClientSecrets With { _
	.ClientId = clientID, _
	.ClientSecret = clientSecret _
}

Dim credential = New GoogleAuthorizationCodeFlow(New GoogleAuthorizationCodeFlow.Initializer With { _
	.ClientSecrets = clientSecrets, _
	.Scopes = {GoogleScope.ImapAndSmtp.Name, GoogleScope.UserInfoEmailScope.Name} _
})

Obtain an OAuth 2.0 access token

Now we’ll create authorization url:

// C#

AuthorizationCodeRequestUrl url = credential
    .CreateAuthorizationCodeRequest(redirectUri);
' VB.NET 

Dim url As AuthorizationCodeRequestUrl = credential _
    .CreateAuthorizationCodeRequest(redirectUri)

Now we need to redirect the client:

// c#

return new RedirectResult(url.Build().ToString());
' VB.NET 

Return New RedirectResult(url.Build().ToString())

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

After this step user is redirected back to your website (https://www.example.com/oauth2callback), with code request parameter:
https://www.example.com/oauth2callback?code=4/5Y7M4cARD9hrt0nuKnQa0YgasdbwprRtIIjk4Fus#

// C#

public class OAauth2CallbackController : Controller
{
    public ActionResult Index(string code)
    {
        ...
    }
}
' VB.NET 

Public Class OAauth2CallbackController
    Inherits Controller
    Public Function Index(code As String) As ActionResult
        ...
    End Function
End Class

Following is this callback code. Its purpose is to get a refresh-token and an access-token:

// c#

string authCode = code;

TokenResponse token = await credential.ExchangeCodeForTokenAsync(
    "", 
    authCode, 
    redirectUri, 
    CancellationToken.None);

string accessToken = token.AccessToken;
' VB.NET 

Dim authCode As String = code

Dim token As TokenResponse = Await credential.ExchangeCodeForTokenAsync( _
    "",  _
    authCode,  _
    redirectUri,  _
    CancellationToken.None)

Dim accessToken As String = token.AccessToken

An access token is usually valid for a maximum of one hour, 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 Google for user’s email and use LoginOAUTH2 method to access Gmail’s IMAP server:

// c#

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

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

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

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

Dim api As New GoogleApi(accessToken)
Dim user As String = api.GetEmail()

Using imap As New Imap()
	imap.ConnectSSL("imap.gmail.com")
	imap.LoginOAUTH2(user, accessToken)

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

	For Each uid As Long In uids
		Dim eml = imap.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)
		Console.WriteLine(email.Subject)
	Next
	imap.Close()
End Using

Refreshing access token

An access token is usually short lived and valid for a maximum of one hour. 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.

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

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

The process of refreshing access token is simple:

// c#

TokenResponse refreshed = await credential.RefreshTokenAsync(
    "", 
    token.RefreshToken, 
    CancellationToken.None);
' VB.NET 

Dim refreshed As TokenResponse = Await credential.RefreshTokenAsync( _
    "",  _
    token.RefreshToken,  _
    CancellationToken.None)

The post OAuth 2.0 with Gmail over IMAP for web applications first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for installed applications (DotNetOpenAuth) https://www.limilabs.com/blog/oauth2-gmail-imap-installed-applications-dotnetopenauth Mon, 13 Mar 2017 04:00:42 +0000 https://www.limilabs.com/blog/?p=5278 Consider using Google.Apis version for installed applications instead of DotNetOpenAuth version. 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 Gmail IMAP and SMTP servers using .NET IMAP component in installed applications scenario. You can […]

The post OAuth 2.0 with Gmail over IMAP for installed applications (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
Consider using Google.Apis version for installed applications instead of DotNetOpenAuth version.

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 Gmail IMAP and SMTP servers using .NET IMAP component in installed applications scenario. You can also use OAuth 2.0 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 Google Developers Console.

At least product name must be specified:

Now create credentials:

After you’ve registered, copy the “Client ID” and “Client secret” values, which you’ll need later:

Now we can define clientID, clientSecret and scope variables, as well as Google 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:

const string clientID = "12345.apps.googleusercontent.com";
const string clientSecret = "XXXYYY111";

AuthorizationServerDescription server = new AuthorizationServerDescription
    {
        AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
        TokenEndpoint = new Uri("https://oauth2.googleapis.com/token"),
        ProtocolVersion = ProtocolVersion.V20,
    };
List<string> scope = new List<string>
    {
        GoogleScope.ImapAndSmtp.Name,
        GoogleScope.UserInfoEmailScope.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 Google to authorize the access:

As OOB addresses are no longer supported, to receive the authorization code using this URL, your application must be listening on the local web server. This is the recommended mechanism for obtaining the authorization code.

When your app receives the authorization response, for best usability it should respond by displaying an HTML page that instructs the user to close the browser and return to your app.

Your application needs to create a server that listens on this local address:

http://127.0.0.1:port or http://[::1]:port or http://localhost:port

Query your platform for the relevant loopback IP address and start an HTTP listener on a random available port. Substitute port with the actual port number your app listens on.

In our example we’ll use http://127.0.0.1/auth2callback address.

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

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 Google 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 valid for a maximum of one hour, 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 Google for user’s email and use LoginOAUTH2 method to access Gmail’s IMAP server:

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

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

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

    foreach (long uid in uids)
    {
        var 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>
    /// <remarks>
    /// See http://code.google.com/apis/accounts/docs/OAuth2.html
    /// </remarks>
    public const string OutOfBandCallbackUrl = "http://127.0.0.1/auth2callback";

    /// <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 and valid for a maximum of one hour. 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.

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

We recommend storing entire IAuthorizationState object received from NativeApplicationClient.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.

Retrieving lost refresh token

When your application receives a refresh token, it is important to store that refresh token for future use. If your application loses the refresh token, it will have to re-prompt the user for consent before obtaining another refresh token.

You’ll need to add approval_prompt=force to your parameters:

AuthorizationServerDescription authServer = new AuthorizationServerDescription
{
    AuthorizationEndpoint =
        new Uri("https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force"),
    ...
};

The post OAuth 2.0 with Gmail over IMAP for installed applications (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
Logging in Mail.dll email client https://www.limilabs.com/blog/logging-in-mail-dll Mon, 01 Aug 2016 16:03:54 +0000 http://www.limilabs.com/blog/?p=1478 This article provides a comprehensive guide on how to perform logging within the Mail.dll .NET email client. It outlines step-by-step instructions for implementing robust logging mechanisms, enhancing troubleshooting capabilities, and tracking email communication effectively. By following the methods detailed in the article, VisualBasic and C# developers can seamlessly integrate logging features into their applications, gaining […]

The post Logging in Mail.dll email client first appeared on Blog | Limilabs.

]]>
This article provides a comprehensive guide on how to perform logging within the Mail.dll .NET email client. It outlines step-by-step instructions for implementing robust logging mechanisms, enhancing troubleshooting capabilities, and tracking email communication effectively.

By following the methods detailed in the article, VisualBasic and C# developers can seamlessly integrate logging features into their applications, gaining valuable insights into the Imap, Pop3 and Smtp clients behavior and facilitating streamlined debugging processes.

To enable logging for all Mail.dll .NET clients (Imap, Pop3, Smtp) you only need to add the following line before you connect:

// C# version:

Limilabs.Mail.Log.Enabled = true;
' VB.NET version:

Limilabs.Mail.Log.Enabled = True

You can observe the log output by:

  • looking at the Visual Studio’s output window (View/Output/’Show output from’: Debug)
  • subscribing to Log.WriteLine event
  • defining custom listeners using your application’s config file (App.config or Web.config)
  • using log4net

This is how the log looks like in the Visual Studio’s output window:

For regular .NET framework you can also enable logging using your application’s config file (App.config, Web.config):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>

      <switches>
        <add name="Mail.dll" value="Verbose" />
      </switches>

    </system.diagnostics>
</configuration>

Logging with log4net

If you are using log4net, Mail.dll is going to use log4net instead of standard .NET System.Net.TraceSource class. Please refer to log4net manual on how to capture log entries.

Mail.dll uses logger called “Mail.dll”

_logger = LogManager.GetLogger("Mail.dll")

and level Info _logger.Info(message) to log information.

Please remember that even when using log4net, you need to enable logging by setting “Limilabs.Mail.Log.Enabled = True” or by setting Mail.dll trace switch in the config file (App.config, Web.config) to Verbose as shown above.

Log.WriteLine

Limilabs.Mail.Log class exposes WriteLine event. You can use that event to subscribe your own logging library in both .NET and .NET framework.

// C#

Limilabs.Mail.Log.WriteLine += Console.WriteLine;
' VB.NET

AddHandler Limilabs.Mail.Log.WriteLine, AddressOf Console.WriteLine

Log to file (.NET framework)

You’ll need to define a TextWriterTraceListener that writes to a file and connect it with Mail.dll trace source. The easiest solution is to modify your application’s config file App.config or Web.config accordingly:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>

        <trace autoflush="true"/>

        <switches>
            <add name="Mail.dll" value="Verbose"/>
        </switches>

        <sources>
            <source name="Mail.dll">
                <listeners>
                    <add name="MailLogFile"/>
                </listeners>
            </source>
        </sources>

        <sharedListeners>
            <add
                name="MailLogFile"
                type="System.Diagnostics.TextWriterTraceListener"
                initializeData="c:\folder-with-write-access\mail.log"/>
        </sharedListeners>

    </system.diagnostics>
</configuration>

The post Logging in Mail.dll email client first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) https://www.limilabs.com/blog/oauth2-gmail-imap-service-account-dotnetopenauth Sat, 12 Mar 2016 22:00:00 +0000 http://www.limilabs.com/blog/?p=4810 Consider using Google.Apis version for service account Gmail access instead of DotNetOpenAuth version. In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, .NET IMAP component and service accounts. The basic idea is that domain administrator can use this method to access user email without knowing user’s password. […]

The post OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
Consider using Google.Apis version for service account Gmail access instead of DotNetOpenAuth version.

In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, .NET IMAP component 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

First you need to visit the Google Developers Console and create a service account:

console_0
console_1
console_2

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

console_3

Go to “Service accounts” and click “View Client ID”:

console_4

Make a note of the Client ID and Email address (Service account).

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: 1234567890
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 Google has not updated their code to work with the most recent release. You’ll need to use 4.0 version.

Newtonsoft.Json

The other library you’ll need is Newtonsoft.Json JSON library. You can download it here: https://github.com/JamesNK/Newtonsoft.Json/releases or use nuget.

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 = "name@xxxxxxxxxx.gserviceaccount.com";
const string serviceAccountCertPath = @"c:\XYZ.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "user@your-domain.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://oauth2.googleapis.com/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)
    {
        var 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.

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Limilabs.Client;
using Limilabs.Client.Authentication.SSPI.Ntlm;

/// <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('/', '_');
    }
};

The post OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
Joining message/partial emails https://www.limilabs.com/blog/joining-message-partial-emails Fri, 02 Jan 2015 07:06:13 +0000 http://www.limilabs.com/blog/?p=4864 With Mail.dll you can join a message split across multiple message/partial emails. “Message/partial” emails allow large objects to be delivered as several mail messages and automatically joined by the receiving client. This mechanism can be used when intermediate transport agents, such as SMTP servers, are limiting the size of individual mails that can be sent. […]

The post Joining message/partial emails first appeared on Blog | Limilabs.

]]>
With Mail.dll you can join a message split across multiple message/partial emails.

“Message/partial” emails allow large objects to be delivered as several mail messages and automatically joined by the receiving client. This mechanism can be used when intermediate transport agents, such as SMTP servers, are limiting the size of individual mails that can be sent. Content-Type “message/partial” indicates that the body contains a fragment of a larger email.

// C#

IMail part1 = ...
IMail part2 = ...

PartialMailJoiner joiner = new PartialMailJoiner();
joiner.Add(part1);
joiner.Add(part2);

IMail email = joiner.Join();
var attachments = email.Attachments;
' VB.NET

Dim part1 As IMail = "..."
Dim part2 As IMail = "..."

Dim joiner As New PartialMailJoiner()
joiner.Add(part1)
joiner.Add(part2)

Dim email As IMail = joiner.Join()
Dim attachments = email.Attachments

PartialMailJoiner.Add method checks if all parts of message/partial email are present and you can use PartialMailJoiner.Join method:

// C#

IMail part1 = ...
IMail part2 = ...

List<IMail> parts = new List<IMail>{part1, part2};

PartialMailJoiner joiner = new PartialMailJoiner();

foreach (IMail part in parts)
{
    if (part.IsPartial)
    {
        bool allPartsPresent = joiner.Add(part);

        if (allPartsPresent)
        {
            IMail email = joiner.Join();
            var attachments = email.Attachments;
        }
    }
}

' VB.NET

Dim part1 As IMail = ...
Dim part2 As IMail = ...

Dim parts As New List(Of IMail)() From { _
	part1, _
	part2 _
}

Dim joiner As New PartialMailJoiner()

For Each part As IMail In parts
    If part.IsPartial Then
        Dim allPartsPresent As Boolean = joiner.Add(part)

	If allPartsPresent Then
            Dim email As IMail = joiner.Join()
	    Dim attachments = email.Attachments
        End If
    End If
Next

Joining fragmented message is a bit tricky. Messages filtered using Content-Type’s id property and then sorted by Content-Type’s number property. Note that part numbering begins with 1, not 0.

The headers of the encapsulated message must be merged with the headers of the enclosing entities. Following rules must be used:

  • All of the header fields from the initial enclosing message, except those that start with “Content-” and the specific header fields” “Subject”, “Message-ID”, “Encrypted”, and “MIME-Version”, must be copied, in order, to the new message.
  • The header fields in the enclosed message which start with “Content-“, plus the “Subject”, “Message-ID”, “Encrypted”, and “MIME-Version” fields, must be appended, in order, to the header fields of the new message. all others will be ignored and dropped.
  • All of the header fields from the second and any subsequent enclosing messages are ignored.

The post Joining message/partial emails first appeared on Blog | Limilabs.

]]>
Processing applesingle and appledouble email attachments https://www.limilabs.com/blog/applesingle-appledouble-email-attachments Wed, 01 Oct 2014 15:08:18 +0000 http://www.limilabs.com/blog/?p=4804 Mail.dll now supports applesingle and appledouble email attachments. AppleSingle and AppleDouble are file formats developed to store Mac OS “dual-forked” files (data fork and resource fork) AppleSingle combines both file forks and the related Finder meta-file information into a single file MIME entity, usually with application/applesingle content-type email header. Applesingle files are parsed by Mail.dll […]

The post Processing applesingle and appledouble email attachments first appeared on Blog | Limilabs.

]]>
Mail.dll now supports applesingle and appledouble email attachments.

AppleSingle and AppleDouble are file formats developed to store Mac OS “dual-forked” files (data fork and resource fork)

AppleSingle combines both file forks and the related Finder meta-file information into a single file MIME entity, usually with application/applesingle content-type email header. Applesingle files are parsed by Mail.dll and data fork is extracted as a standard email attachment available through IMail.Attachments collection.

AppleDouble stores both forks as two separate files. Thus it uses multipart mime type – inside two MIME entities are stored: application/applefile, which contains resource fork only, and regular MIME attachment e.g. application/octet-stream content-type.

In both cases you simply use IMail.Attachments collection to access attachments.

byte[] eml = ...;

IMail mail = new MailBuilder().CreateFromEml(eml);
Assert.AreEqual(1, mail.Attachments.Count);
MimeData att = (MimeData) mail.Attachments[0];

byte[] data = att.Data; // data fork

You can control, if apple attachments should be processed using MailBuilder.ProcessAppleAutomatically property. When it is set to false applesingle files are not parsed and data fork is not extracted.

byte[] eml = ...;

MailBuilder builder = new MailBuilder();
builder.ProcessBinHexAutomatically = false;
IMail mail = builder.CreateFromEml(eml);

MimeAppleSingle appleSingle = (MimeAppleSingle) mail.Attachments[0];

Assert.AreEqual(ContentType.ApplicationAppleSingle, appleSingle.ContentType);

byte[] resource = appleSingle.AppleSingle.Resources;
string name = appleSingle.AppleSingle.RealName;

The post Processing applesingle and appledouble email attachments first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for service account https://www.limilabs.com/blog/oauth2-gmail-imap-service-account Thu, 11 Sep 2014 09:36:51 +0000 http://www.limilabs.com/blog/?p=3904 You can also read how to use:   OAuth 2.0 with Gmail over IMAP for web applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for installed applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for service account (Google.Apis) In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, […]

The post OAuth 2.0 with Gmail over IMAP for service account first appeared on Blog | Limilabs.

]]>
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, .NET IMAP component 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

Use Nuget to download “Google.Apis.Auth” package.

Import namespaces:

// c#

using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Auth.OAuth2.Responses;

using System.Security.Cryptography.X509Certificates;

using Limilabs.Client.Authentication.Google;

using Limilabs.Client.IMAP;
' VB.NET 

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Flows
Imports Google.Apis.Auth.OAuth2.Requests
Imports Google.Apis.Auth.OAuth2.Responses

Imports System.Security.Cryptography.X509Certificates

Imports Limilabs.Client.Authentication.Google

Imports Limilabs.Client.IMAP

Google Cloud

First you need to visit Google Cloud Console and create a project:

Now create a new service account:

Add a service name and remember an email address assigned to your service:

Then you need to create a private key for this service:

Download and save this private key (XYZ.p12), you’ll need that later:

Google Domain administration

Final part is to allow this service to access your domain. You’ll perform this steps in your domain administration panel.

Remember the Client ID first, and go to your domain administration panel:

In the main menu select Security / Access and data control / API controls

Then Manage domain wide delegation

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

Alternatively you can use https://www.googleapis.com/auth/gmail.imap_admin scope.

When authorized with this scope, IMAP connections behave differently:

  • All labels are shown via IMAP, even if users disabled “Show in IMAP” for the label in the Gmail settings.
  • All messages are shown via IMAP, regardless of what the user set in “Folder Size Limits” in the Gmail settings.

Access IMAP/SMTP server

// C#

const string serviceAccountEmail = "name@xxxxxxxxxx.gserviceaccount.com";
const string serviceAccountCertPath = @"c:\XYZ.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "user@your-domain.com";

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

ServiceAccountCredential credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail)
    {
        Scopes = new[] { "https://mail.google.com/" },
        // Scopes = new[] { "https://www.googleapis.com/auth/gmail.imap_admin" },

        User = userEmail
    }.FromCertificate(certificate));

bool success = await credential.RequestAccessTokenAsync(
    CancellationToken.None);

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap.gmail.com");
    imap.LoginOAUTH2(userEmail, credential.Token.AccessToken);

    imap.SelectInbox();

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

    imap.Close();
}
' VB.NET

Const serviceAccountEmail As String = "name@xxxxxxxxxx.gserviceaccount.com"
Const serviceAccountCertPath As String = "c:\XYZ.p12"
Const serviceAccountCertPassword As String = "notasecret"
Const userEmail As String = "user@your-domain.com"

Dim certificate As New X509Certificate2(serviceAccountCertPath, serviceAccountCertPassword, X509KeyStorageFlags.Exportable)

Dim credential As New ServiceAccountCredential(New ServiceAccountCredential.Initializer(serviceAccountEmail) With { _
	.Scopes = {"https://mail.google.com/"}, _
	' .Scopes = {"https://www.googleapis.com/auth/gmail.imap_admin"}, _
	.User = userEmail _
}.FromCertificate(certificate))

Dim success As Boolean = credential.RequestAccessTokenAsync(
    CancellationToken.None).Result

Using imap As New Imap()
	imap.ConnectSSL("imap.gmail.com")
	imap.LoginOAUTH2(userEmail, credential.Token.AccessToken)

	imap.SelectInbox()

	For Each uid As Long In uids
		Dim eml = client.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)
		Console.WriteLine(email.Subject)
	Next

	imap.Close()
End Using

The post OAuth 2.0 with Gmail over IMAP for service account first appeared on Blog | Limilabs.

]]>