Imports System.Net.Security
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.Logging
Imports Limilabs.Client
Imports Limilabs.Client.IMAP
Imports Limilabs.Client.SMTP
Imports Microsoft.Identity.Client
Namespace Mail
Public Class MailSession
Inherits BaseClass
Public ReadOnly Client As ClientBase
Public ReadOnly OAuth2 As OAuth2
Public Const AUTH_SSL = "SSL"
Public Const AUTH_STARTTLS = "STARTTLS"
Public Const AUTH_SSLTLS = "SSL/TLS"
Public Const AUTH_NONE = "NONE"
Public Const AUTH_OAUTH2 = "OAUTH2"
Private Const SMTP_IGNORED_ERRORS As SslPolicyErrors =
SslPolicyErrors.RemoteCertificateChainErrors Or ' self-signed
SslPolicyErrors.RemoteCertificateNameMismatch ' name mismatch
Private _Session As SessionInfo
Public ReadOnly Property Session As SessionInfo
Get
Return _Session
End Get
End Property
Public Sub New(pLogConfig As LogConfig, pClient As ClientBase)
MyBase.New(pLogConfig)
Client = pClient
End Sub
Public Class SessionInfo
Public Server As String
Public Port As Integer
Public User As String
Public Password As String
Public AuthType As String
Public ClientId As String
Public TenantId As String
Public ClientSecret As String
Public [Error] As Exception
Public Connected As Boolean = False
End Class
Public Class MailSessionOptions
Public Property EnableDefault As Boolean = True
Public Property EnableTls1_1 As Boolean = False
Public Property EnableTls1_2 As Boolean = False
' Not available in .NET 4.6.2
'Public Property EnableTls1_3 As Boolean = False
End Class
'''
''' Start a connection with the specified server and return the session info.
'''
'''
'''
'''
'''
'''
'''
Public Function ConnectToServerWithBasicAuth(pServer As String, pPort As Integer, pUser As String, pPassword As String, pAuthType As String, pOptions As MailSessionOptions) As SessionInfo
Dim oSession = New SessionInfo With {
.Server = pServer,
.Port = pPort,
.User = pUser,
.Password = pPassword,
.AuthType = pAuthType
}
Logger.Debug("Connecting to Server..")
Logger.Debug("Server: [{0}]", oSession.Server)
Logger.Debug("Port: [{0}]", oSession.Port)
Logger.Debug("User: [{0}]", oSession.User)
Logger.Debug("AuthType: [{0}]", oSession.AuthType)
_Session = oSession
Logger.Debug("Initializing Connection with Auth type [{0}].", oSession.AuthType)
Return ConnectToServer(oSession, pOptions)
End Function
Public Function ConnectToServerWithO365OAuth(pUser As String, pClientId As String, pTenantId As String, pClientSecret As String, pOptions As MailSessionOptions) As SessionInfo
Dim oSession = New SessionInfo With {
.Server = OAuth2.O365_SERVER,
.ClientId = pClientId,
.ClientSecret = pClientSecret,
.TenantId = pTenantId,
.User = pUser,
.AuthType = AUTH_OAUTH2
}
Logger.Debug("Connecting to Server..")
Logger.Debug("Server: [{0}]", oSession.Server)
Logger.Debug("User: [{0}]", oSession.User)
Logger.Debug("ClientId: [{0}]", oSession.ClientId)
Logger.Debug("TenantId: [{0}]", oSession.TenantId)
Logger.Debug("AuthType: [{0}]", oSession.AuthType)
_Session = oSession
Logger.Debug("Initializing Connection with Auth type [{0}].", oSession.AuthType)
Return ConnectToServer(oSession, pOptions)
End Function
Private Function ConnectToServer(pSession As SessionInfo, pOptions As MailSessionOptions) As SessionInfo
AddHandler Client.ServerCertificateValidate, AddressOf Session_ServerCertificateValidate
If pOptions.EnableDefault Then
Logger.Debug("Enabling Default TLS Version")
Client.SSLConfiguration.EnabledSslProtocols = Security.Authentication.SslProtocols.Default
Else
Logger.Debug("Disabling Default TLS Version")
Client.SSLConfiguration.EnabledSslProtocols = Security.Authentication.SslProtocols.None
End If
' Set TLS Version manually if requested
If pOptions.EnableTls1_1 Then
Logger.Debug("Enabling TLS Version 1.1")
Client.SSLConfiguration.EnabledSslProtocols = Client.SSLConfiguration.EnabledSslProtocols Or Security.Authentication.SslProtocols.Tls11
End If
If pOptions.EnableTls1_2 Then
Logger.Debug("Enabling TLS Version 1.2")
Client.SSLConfiguration.EnabledSslProtocols = Client.SSLConfiguration.EnabledSslProtocols Or Security.Authentication.SslProtocols.Tls12
End If
' This is not available in .NET 4.6.2, only in .NET 4.7/4.8
'If pOptions.EnableTls1_3 Then
' Logger.Debug("Enabling TLS Version 1.3")
' oSession.SSLConfiguration.EnabledSslProtocols = oSession.SSLConfiguration.EnabledSslProtocols Or Security.Authentication.SslProtocols.Tls13
'End If
Logger.Debug("Enabled Encryption Protocols: [{0}]", Client.SSLConfiguration.EnabledSslProtocols)
If pSession.AuthType = AUTH_OAUTH2 Then
Try
If TypeOf Client Is Imap Then
Dim oClient As Imap = Client
Logger.Debug("Connecting with [ConnectSSL] on [{0}]", pSession.Server)
oClient.ConnectSSL(pSession.Server)
Else
Throw New ApplicationException("Only OAuth2 for IMAP is not yet supported!")
End If
Catch ex As Exception
Logger.Warn("Error while connecting with Auth type OAuth2!")
Logger.Error(ex)
Session.Error = ex
Return Session
End Try
ElseIf pSession.AuthType = AUTH_SSL Then
Try
If pSession.Port = 993 Then
Logger.Debug("Connecting with [ConnectSSL] on [{0}/{1}]", pSession.Server, pSession.Port)
Client.ConnectSSL(pSession.Server, pSession.Port)
Else
Logger.Debug("Connecting with [Connect] on [{0}/{1}]", pSession.Server, pSession.Port)
Client.Connect(pSession.Server, pSession.Port)
End If
Logger.Info("Connection Successful!")
Catch ex As Exception
Logger.Warn("Error while connecting with Auth type SSL!")
Logger.Warn($"Error-message: {ex.Message}")
Logger.Error(ex)
Session.Error = ex
Return Session
End Try
ElseIf Session.AuthType = AUTH_SSLTLS Or Session.AuthType = AUTH_STARTTLS Then
Try
If pSession.Port = 993 Then
Logger.Debug("Connecting with [ConnectSSL] on [{0}/{1}]", pSession.Server, pSession.Port)
Client.ConnectSSL(pSession.Server, pSession.Port)
Else
Logger.Debug("Connecting with [Connect] on [{0}/{1}]", pSession.Server, pSession.Port)
Client.Connect(pSession.Server, pSession.Port)
End If
Logger.Info("Connection Successful!")
Dim oSupportsSTARTTLS As Boolean = SupportsSTARTTLS(Client)
Logger.Debug("Server supports STARTTLS: [{0}]", oSupportsSTARTTLS)
If oSupportsSTARTTLS Then
DoSTARTTLS(Client)
Logger.Info("STARTTLS Successful!")
Else
Logger.Debug("Server does not support STARTTLS. Enabling TLS1.2 instead.")
Client.SSLConfiguration.EnabledSslProtocols = Security.Authentication.SslProtocols.Tls12
End If
Catch ex As Exception
Logger.Warn("Error while connecting with Auth type STARTTLS!")
Logger.Error(ex)
pSession.Error = ex
Return pSession
End Try
Else
Try
Logger.Debug("Auth type [{0}]. Using PLAINTEXT connection.", Session.AuthType)
Client.Connect(pSession.Server, pSession.Port, useSSL:=False)
Logger.Info("Connection Successful!")
Catch ex As Exception
Logger.Warn("Error while connecting with Auth type [{0}]!", Session.AuthType)
Logger.Error(ex)
pSession.Error = ex
Return pSession
End Try
End If
Try
If pSession.User <> String.Empty Then
Logger.Info("Logging in with user [{0}] and Auth Type [{1}]", pSession.User, pSession.AuthType)
If pSession.AuthType = AUTH_OAUTH2 Then
' SessionInfo.Password will be the access token that was obtained
' in the OAuth2 flow before
DoUseBestLogin_OAuth2(Client, pSession)
Else
DoUseBestLogin_BasicAuth(Client, pSession.User, pSession.Password)
End If
End If
Catch ex As Exception
Logger.Warn("Error while connecting with Auth type [{0}]!", pSession.AuthType)
Logger.Error(ex)
pSession.Error = ex
Return pSession
End Try
pSession.Connected = True
Return pSession
End Function
Public Function DisconnectFromServer() As Boolean
Try
If Client IsNot Nothing Then
Logger.Debug("Closing connection to Server [{0}].", Session.Server)
DoClose(Client)
Session.Connected = False
Logger.Info("Connection to Server [{0}] closed.", Session.Server)
Else
Logger.Debug("No connection currently open. Exiting.")
End If
Return True
Catch ex As Exception
Logger.Error(ex)
Return False
End Try
End Function
Public Function TestLogin(pServer As String, pPort As Integer, pUser As String, pPassword As String, pAuthType As String, pOptions As MailSessionOptions) As Boolean
Dim oInfo = ConnectToServerWithBasicAuth(pServer, pPort, pUser, pPassword, pAuthType, pOptions)
If oInfo.Connected Then
If DisconnectFromServer() Then
Return True
Else
Logger.Warn("Login Test failed while disconnecting.")
Return False
End If
Else
Logger.Warn("Login Test Failed while connecting.")
Return False
End If
End Function
Private Function SupportsSTARTTLS(pClient As ClientBase)
If TypeOf pClient Is Smtp Then
Return DirectCast(pClient, Smtp).SupportedExtensions.Contains(SmtpExtension.StartTLS)
ElseIf TypeOf pClient Is Imap Then
Return DirectCast(pClient, Imap).SupportedExtensions.Contains(ImapExtension.StartTLS)
Else
Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
Return False
End If
End Function
Private Sub DoClose(pClient As ClientBase)
If TypeOf pClient Is Smtp Then
DirectCast(pClient, Smtp).Close()
ElseIf TypeOf pClient Is Imap Then
DirectCast(pClient, Imap).Close()
Else
Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
End If
End Sub
Private Sub DoUseBestLogin_BasicAuth(pClient As ClientBase, pUserName As String, pPassword As String)
Logger.Debug("Logging in with Simple Auth")
If TypeOf pClient Is Smtp Then
DirectCast(pClient, Smtp).UseBestLogin(pUserName, pPassword)
ElseIf TypeOf pClient Is Imap Then
DirectCast(pClient, Imap).UseBestLogin(pUserName, pPassword)
Else
Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
End If
End Sub
Private Sub DoUseBestLogin_OAuth2(pClient As ClientBase, pSession As SessionInfo)
Logger.Debug("Logging in with O365 OAuth2")
If TypeOf pClient Is Imap Then
Dim oOAuth = New OAuth2(LogConfig, pSession.TenantId, pSession.ClientId, pSession.ClientSecret)
Dim oAccessToken = oOAuth.GetAccessToken()
DirectCast(pClient, Imap).LoginOAUTH2(pSession.User, oAccessToken)
Else
Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
End If
End Sub
Private Sub DoSTARTTLS(pClient As ClientBase)
If TypeOf pClient Is Smtp Then
DirectCast(pClient, Smtp).StartTLS()
ElseIf TypeOf pClient Is Imap Then
DirectCast(pClient, Imap).StartTLS()
Else
Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
End If
End Sub
Private Sub Session_ServerCertificateValidate(sender As Object, e As ServerCertificateValidateEventArgs)
' i dont know why it works but it does
If (e.SslPolicyErrors And Not SMTP_IGNORED_ERRORS) = SslPolicyErrors.None Then
e.IsValid = True
Else
e.IsValid = False
End If
End Sub
End Class
End Namespace