// // MailService.cs // // Author: Jeffrey Stedfast // // Copyright (c) 2013-2020 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Net.Sockets; using System.Net.Security; using System.Threading.Tasks; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using SslProtocols = System.Security.Authentication.SslProtocols; using MailKit.Net; using MailKit.Net.Proxy; using MailKit.Security; namespace MailKit { /// /// An abstract mail service implementation. /// /// /// An abstract mail service implementation. /// public abstract class MailService : IMailService { #if NET48 const SslProtocols DefaultSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; #else const SslProtocols DefaultSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12; #endif /// /// Initializes a new instance of the class. /// /// /// Initializes a new instance of the class. /// /// The protocol logger. /// /// is null. /// protected MailService (IProtocolLogger protocolLogger) { if (protocolLogger == null) throw new ArgumentNullException (nameof (protocolLogger)); SslProtocols = DefaultSslProtocols; CheckCertificateRevocation = true; ProtocolLogger = protocolLogger; } /// /// Initializes a new instance of the class. /// /// /// Initializes a new instance of the class. /// protected MailService () : this (new NullProtocolLogger ()) { } /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// ~MailService () { Dispose (false); } /// /// Gets an object that can be used to synchronize access to the service. /// /// /// Gets an object that can be used to synchronize access to the service. /// /// The sync root. public abstract object SyncRoot { get; } /// /// Gets the protocol supported by the message service. /// /// /// Gets the protocol supported by the message service. /// /// The protocol. protected abstract string Protocol { get; } /// /// Get the protocol logger. /// /// /// Gets the protocol logger. /// /// The protocol logger. public IProtocolLogger ProtocolLogger { get; private set; } /// /// Gets or sets the SSL and TLS protocol versions that the client is allowed to use. /// /// /// Gets or sets the SSL and TLS protocol versions that the client is allowed to use. /// By default, MailKit initializes this value to support only TLS v1.1 and greater and /// does not support TLS v1.0 or any version of SSL due to those protocols no longer being considered /// secure. /// This property should be set before calling any of the /// Connect methods. /// /// The SSL and TLS protocol versions that are supported. public SslProtocols SslProtocols { get; set; } /// /// Gets or sets the client SSL certificates. /// /// /// Some servers may require the client SSL certificates in order /// to allow the user to connect. /// This property should be set before calling any of the /// Connect methods. /// /// The client SSL certificates. public X509CertificateCollection ClientCertificates { get; set; } /// /// Get or set whether connecting via SSL/TLS should check certificate revocation. /// /// /// Gets or sets whether connecting via SSL/TLS should check certificate revocation. /// Normally, the value of this property should be set to true (the default) for security /// reasons, but there are times when it may be necessary to set it to false. /// For example, most Certificate Authorities are probably pretty good at keeping their CRL and/or /// OCSP servers up 24/7, but occasionally they do go down or are otherwise unreachable due to other /// network problems between the client and the Certificate Authority. When this happens, it becomes /// impossible to check the revocation status of one or more of the certificates in the chain /// resulting in an being thrown in the /// Connect method. If this becomes a problem, /// it may become desirable to set to false. /// /// true if certificate revocation should be checked; otherwise, false. public bool CheckCertificateRevocation { get; set; } /// /// Get or sets a callback function to validate the server certificate. /// /// /// Gets or sets a callback function to validate the server certificate. /// This property should be set before calling any of the /// Connect methods. /// /// /// /// /// The server certificate validation callback function. public RemoteCertificateValidationCallback ServerCertificateValidationCallback { get; set; } /// /// Get or set the local IP end point to use when connecting to the remote host. /// /// /// Gets or sets the local IP end point to use when connecting to the remote host. /// /// The local IP end point or null to use the default end point. public IPEndPoint LocalEndPoint { get; set; } /// /// Get or set the proxy client to use when connecting to a remote host. /// /// /// Gets or sets the proxy client to use when connecting to a remote host via any of the /// Connect methods. /// /// The proxy client. public IProxyClient ProxyClient { get; set; } /// /// Gets the authentication mechanisms supported by the mail server. /// /// /// The authentication mechanisms are queried as part of the /// Connect method. /// /// The authentication mechanisms. public abstract HashSet AuthenticationMechanisms { get; } /// /// Gets whether or not the client is currently connected to an mail server. /// /// ///The state is set to true immediately after /// one of the Connect /// methods succeeds and is not set back to false until either the client /// is disconnected via or until a /// is thrown while attempting to read or write to /// the underlying network socket. /// When an is caught, the connection state of the /// should be checked before continuing. /// /// true if the client is connected; otherwise, false. public abstract bool IsConnected { get; } /// /// Get whether or not the connection is secure (typically via SSL or TLS). /// /// /// Gets whether or not the connection is secure (typically via SSL or TLS). /// /// true if the connection is secure; otherwise, false. public abstract bool IsSecure { get; } /// /// Get whether or not the client is currently authenticated with the mail server. /// /// /// Gets whether or not the client is currently authenticated with the mail server. /// To authenticate with the mail server, use one of the /// Authenticate methods /// or any of the Async alternatives. /// /// true if the client is authenticated; otherwise, false. public abstract bool IsAuthenticated { get; } /// /// Gets or sets the timeout for network streaming operations, in milliseconds. /// /// /// Gets or sets the underlying socket stream's /// and values. /// /// The timeout in milliseconds. public abstract int Timeout { get; set; } const string AppleCertificateIssuer = "C=US, O=Apple Inc., OU=Certification Authority, CN=Apple IST CA 2 - G1"; const string GMailCertificateIssuer = "CN=GTS CA 1O1, O=Google Trust Services, C=US"; const string OutlookCertificateIssuer = "CN=DigiCert Cloud Services CA-1, O=DigiCert Inc, C=US"; const string YahooCertificateIssuer = "CN=DigiCert SHA2 High Assurance Server CA, OU=www.digicert.com, O=DigiCert Inc, C=US"; const string GmxCertificateIssuer = "CN=TeleSec ServerPass Extended Validation Class 3 CA, STREET=Untere Industriestr. 20, L=Netphen, OID.2.5.4.17=57250, S=Nordrhein Westfalen, OU=T-Systems Trust Center, O=T-Systems International GmbH, C=DE"; static bool IsKnownMailServerCertificate (X509Certificate2 certificate) { var cn = certificate.GetNameInfo (X509NameType.SimpleName, false); var fingerprint = certificate.Thumbprint; var serial = certificate.SerialNumber; var issuer = certificate.Issuer; switch (cn) { case "imap.gmail.com": return issuer == GMailCertificateIssuer && serial == "0096768414983DDE9C0800000000320A68" && fingerprint == "A53BA86C137D828618540738014F7C3D52F699C7"; case "pop.gmail.com": return issuer == GMailCertificateIssuer && serial == "00D80446EA4406BA970800000000320A6A" && fingerprint == "379A18659C855AE5CD00E24CEBE2C6552235B701"; case "smtp.gmail.com": return issuer == GMailCertificateIssuer && serial == "00A2683EEFC8500CA20800000000320A71" && fingerprint == "8F0A0B43DE223D360C4BBC41725C202B806CED32"; case "outlook.com": return issuer == OutlookCertificateIssuer && serial == "0654F84B6325595A20BC68A6A5851CBB" && fingerprint == "7F0804B4D0A6C83E46A3A00EC98F8343D7308566"; case "imap.mail.me.com": return issuer == AppleCertificateIssuer && serial == "62CBBFC566127C4758E96BDBC38EC9E6" && fingerprint == "E1A5F9D22A810979CACDFC0B4151F561E8D02976"; case "smtp.mail.me.com": return issuer == AppleCertificateIssuer && serial == "3460D64A763D9ACA4B460C25021653C7" && fingerprint == "C262F01E83D6CE0C361E8B049E5BE8FE6E55806B"; case "*.imap.mail.yahoo.com": return issuer == YahooCertificateIssuer && serial == "0B2804C9ED82D14FEFEF111E54A0551C" && fingerprint == "F8047F0F60C4641F718353BE7DDC31665B96B5C0"; case "legacy.pop.mail.yahoo.com": return issuer == YahooCertificateIssuer && serial == "05179AA3E07FA5B4D0FC55A7A950B8D8" && fingerprint == "08E010CBAEFAADD20DB0B222C8B6812E762F28EC"; case "smtp.mail.yahoo.com": return issuer == YahooCertificateIssuer && serial == "0F962C48837807B6556C5B6961FC4671" && fingerprint == "E53995EBA816FB73FD4F4BD55ABED04981DA0F18"; case "mail.gmx.net": return issuer == GmxCertificateIssuer && serial == "218296213149726650EB233346353EEA" && fingerprint == "67DED57393303E005937D5EDECB6A29C136024CA"; default: return false; } } static bool IsUntrustedRoot (X509Chain chain) { foreach (var status in chain.ChainStatus) { if (status.Status == X509ChainStatusFlags.NoError || status.Status == X509ChainStatusFlags.UntrustedRoot) continue; return false; } return true; } internal SslCertificateValidationInfo SslCertificateValidationInfo; /// /// The default server certificate validation callback used when connecting via SSL or TLS. /// /// /// The default server certificate validation callback recognizes and accepts the certificates /// for a list of commonly used mail servers such as gmail.com, outlook.com, mail.me.com, yahoo.com, /// and gmx.net. /// /// true if the certificate is deemed valid; otherwise, false. /// The object that is connecting via SSL or TLS. /// The server's SSL certificate. /// The server's SSL certificate chain. /// The SSL policy errors. protected bool DefaultServerCertificateValidationCallback (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { const SslPolicyErrors mask = SslPolicyErrors.RemoteCertificateNotAvailable | SslPolicyErrors.RemoteCertificateNameMismatch; SslCertificateValidationInfo = null; if (sslPolicyErrors == SslPolicyErrors.None) return true; if ((sslPolicyErrors & mask) == 0) { // At this point, all that is left is SslPolicyErrors.RemoteCertificateChainErrors // If the problem is an untrusted root, then compare the certificate to a list of known mail server certificates. if (IsUntrustedRoot (chain) && certificate is X509Certificate2 certificate2) { if (IsKnownMailServerCertificate (certificate2)) return true; } } SslCertificateValidationInfo = new SslCertificateValidationInfo (sender, certificate, chain, sslPolicyErrors); return false; } internal async Task ConnectSocket (string host, int port, bool doAsync, CancellationToken cancellationToken) { if (ProxyClient != null) { ProxyClient.LocalEndPoint = LocalEndPoint; if (doAsync) return await ProxyClient.ConnectAsync (host, port, Timeout, cancellationToken).ConfigureAwait (false); return ProxyClient.Connect (host, port, Timeout, cancellationToken); } return await SocketUtils.ConnectAsync (host, port, LocalEndPoint, Timeout, doAsync, cancellationToken).ConfigureAwait (false); } /// /// Establish a connection to the specified mail server. /// /// /// Establishes a connection to the specified mail server. /// /// /// /// /// The host name to connect to. /// The port to connect to. If the specified port is 0, then the default port will be used. /// The secure socket options to when connecting. /// The cancellation token. /// /// is null. /// /// /// is not between 0 and 65535. /// /// /// The is a zero-length string. /// /// /// The has been disposed. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// A socket error occurred trying to connect to the remote host. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public abstract void Connect (string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)); /// /// Asynchronously establish a connection to the specified mail server. /// /// /// Asynchronously establishes a connection to the specified mail server. /// /// An asynchronous task context. /// The host name to connect to. /// The port to connect to. If the specified port is 0, then the default port will be used. /// The secure socket options to when connecting. /// The cancellation token. /// /// is null. /// /// /// is not between 0 and 65535. /// /// /// The is a zero-length string. /// /// /// The has been disposed. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// A socket error occurred trying to connect to the remote host. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public abstract Task ConnectAsync (string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)); /// /// Establish a connection to the specified mail server using the provided socket. /// /// /// Establish a connection to the specified mail server using the provided socket. /// If a successful connection is made, the /// property will be populated. /// /// The socket to use for the connection. /// The host name to connect to. /// The port to connect to. If the specified port is 0, then the default port will be used. /// The secure socket options to when connecting. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// is not between 0 and 65535. /// /// /// is not connected. /// -or- /// The is a zero-length string. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The command was rejected by the mail server. /// /// /// The server responded with an unexpected token. /// public abstract void Connect (Socket socket, string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)); /// /// Asynchronously establish a connection to the specified mail server using the provided socket. /// /// /// Asynchronously establishes a connection to the specified mail server using the provided socket. /// If a successful connection is made, the /// property will be populated. /// /// An asynchronous task context. /// The socket to use for the connection. /// The host name to connect to. /// The port to connect to. If the specified port is 0, then the default port will be used. /// The secure socket options to when connecting. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// is not between 0 and 65535. /// /// /// is not connected. /// -or- /// The is a zero-length string. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The command was rejected by the mail server. /// /// /// The server responded with an unexpected token. /// public abstract Task ConnectAsync (Socket socket, string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)); /// /// Establish a connection to the specified mail server using the provided stream. /// /// /// Establish a connection to the specified mail server using the provided stream. /// If a successful connection is made, the /// property will be populated. /// /// The stream to use for the connection. /// The host name to connect to. /// The port to connect to. If the specified port is 0, then the default port will be used. /// The secure socket options to when connecting. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// is not between 0 and 65535. /// /// /// The is a zero-length string. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The command was rejected by the mail server. /// /// /// The server responded with an unexpected token. /// public abstract void Connect (Stream stream, string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)); /// /// Asynchronously establish a connection to the specified mail server using the provided stream. /// /// /// Asynchronously establishes a connection to the specified mail server using the provided stream. /// If a successful connection is made, the /// property will be populated. /// /// An asynchronous task context. /// The stream to use for the connection. /// The host name to connect to. /// The port to connect to. If the specified port is 0, then the default port will be used. /// The secure socket options to when connecting. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// is not between 0 and 65535. /// /// /// The is a zero-length string. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The command was rejected by the mail server. /// /// /// The server responded with an unexpected token. /// public abstract Task ConnectAsync (Stream stream, string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)); internal SecureSocketOptions GetSecureSocketOptions (Uri uri) { var query = uri.ParsedQuery (); var protocol = uri.Scheme; string value; // Note: early versions of MailKit used "pop3" and "pop3s" if (protocol.Equals ("pop3s", StringComparison.OrdinalIgnoreCase)) protocol = "pops"; else if (protocol.Equals ("pop3", StringComparison.OrdinalIgnoreCase)) protocol = "pop"; if (protocol.Equals (Protocol + "s", StringComparison.OrdinalIgnoreCase)) return SecureSocketOptions.SslOnConnect; if (!protocol.Equals (Protocol, StringComparison.OrdinalIgnoreCase)) throw new ArgumentException ("Unknown URI scheme.", nameof (uri)); if (query.TryGetValue ("starttls", out value)) { switch (value.ToLowerInvariant ()) { default: return SecureSocketOptions.StartTlsWhenAvailable; case "always": case "true": case "yes": return SecureSocketOptions.StartTls; case "never": case "false": case "no": return SecureSocketOptions.None; } } return SecureSocketOptions.StartTlsWhenAvailable; } /// /// Establish a connection to the specified mail server. /// /// /// Establishes a connection to the specified mail server. /// /// /// /// /// The server URI. /// The cancellation token. /// /// The is null. /// /// /// The is not an absolute URI. /// /// /// The has been disposed. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// A socket error occurred trying to connect to the remote host. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public void Connect (Uri uri, CancellationToken cancellationToken = default (CancellationToken)) { if (uri == null) throw new ArgumentNullException (nameof (uri)); if (!uri.IsAbsoluteUri) throw new ArgumentException ("The uri must be absolute.", nameof (uri)); var options = GetSecureSocketOptions (uri); Connect (uri.Host, uri.Port < 0 ? 0 : uri.Port, options, cancellationToken); } /// /// Asynchronously establish a connection to the specified mail server. /// /// /// Asynchronously establishes a connection to the specified mail server. /// /// An asynchronous task context. /// The server URI. /// The cancellation token. /// /// The is null. /// /// /// The is not an absolute URI. /// /// /// The has been disposed. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// A socket error occurred trying to connect to the remote host. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public Task ConnectAsync (Uri uri, CancellationToken cancellationToken = default (CancellationToken)) { if (uri == null) throw new ArgumentNullException (nameof (uri)); if (!uri.IsAbsoluteUri) throw new ArgumentException ("The uri must be absolute.", nameof (uri)); var options = GetSecureSocketOptions (uri); return ConnectAsync (uri.Host, uri.Port < 0 ? 0 : uri.Port, options, cancellationToken); } /// /// Establish a connection to the specified mail server. /// /// /// Establishes a connection to the specified mail server. /// /// The argument only controls whether or /// not the client makes an SSL-wrapped connection. In other words, even if the /// parameter is false, SSL/TLS may still be used if /// the mail server supports the STARTTLS extension. /// To disable all use of SSL/TLS, use the /// /// overload with a value of /// SecureSocketOptions.None /// instead. /// /// /// The host to connect to. /// The port to connect to. If the specified port is 0, then the default port will be used. /// true if the client should make an SSL-wrapped connection to the server; otherwise, false. /// The cancellation token. /// /// The is null. /// /// /// is out of range (0 to 65535, inclusive). /// /// /// The is a zero-length string. /// /// /// The has been disposed. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// A socket error occurred trying to connect to the remote host. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public void Connect (string host, int port, bool useSsl, CancellationToken cancellationToken = default (CancellationToken)) { if (host == null) throw new ArgumentNullException (nameof (host)); if (host.Length == 0) throw new ArgumentException ("The host name cannot be empty.", nameof (host)); if (port < 0 || port > 65535) throw new ArgumentOutOfRangeException (nameof (port)); Connect (host, port, useSsl ? SecureSocketOptions.SslOnConnect : SecureSocketOptions.StartTlsWhenAvailable, cancellationToken); } /// /// Asynchronously establish a connection to the specified mail server. /// /// /// Asynchronously establishes a connection to the specified mail server. /// /// The argument only controls whether or /// not the client makes an SSL-wrapped connection. In other words, even if the /// parameter is false, SSL/TLS may still be used if /// the mail server supports the STARTTLS extension. /// To disable all use of SSL/TLS, use the /// /// overload with a value of /// SecureSocketOptions.None /// instead. /// /// /// An asynchronous task context. /// The host to connect to. /// The port to connect to. If the specified port is 0, then the default port will be used. /// true if the client should make an SSL-wrapped connection to the server; otherwise, false. /// The cancellation token. /// /// The is null. /// /// /// is out of range (0 to 65535, inclusive). /// /// /// The is a zero-length string. /// /// /// The has been disposed. /// /// /// The is already connected. /// /// /// The operation was canceled via the cancellation token. /// /// /// A socket error occurred trying to connect to the remote host. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public Task ConnectAsync (string host, int port, bool useSsl, CancellationToken cancellationToken = default (CancellationToken)) { if (host == null) throw new ArgumentNullException (nameof (host)); if (host.Length == 0) throw new ArgumentException ("The host name cannot be empty.", nameof (host)); if (port < 0 || port > 65535) throw new ArgumentOutOfRangeException (nameof (port)); return ConnectAsync (host, port, useSsl ? SecureSocketOptions.SslOnConnect : SecureSocketOptions.StartTlsWhenAvailable, cancellationToken); } /// /// Authenticate using the supplied credentials. /// /// /// If the server supports one or more SASL authentication mechanisms, /// then the SASL mechanisms that both the client and server support are tried /// in order of greatest security to weakest security. Once a SASL /// authentication mechanism is found that both client and server support, /// the credentials are used to authenticate. /// If the server does not support SASL or if no common SASL mechanisms /// can be found, then the default login command is used as a fallback. /// To prevent the usage of certain authentication mechanisms, /// simply remove them from the hash set /// before calling this method. /// /// The encoding to use for the user's credentials. /// The user's credentials. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public abstract void Authenticate (Encoding encoding, ICredentials credentials, CancellationToken cancellationToken = default (CancellationToken)); /// /// Asynchronously authenticate using the supplied credentials. /// /// /// If the server supports one or more SASL authentication mechanisms, /// then the SASL mechanisms that both the client and server support are tried /// in order of greatest security to weakest security. Once a SASL /// authentication mechanism is found that both client and server support, /// the credentials are used to authenticate. /// If the server does not support SASL or if no common SASL mechanisms /// can be found, then the default login command is used as a fallback. /// To prevent the usage of certain authentication mechanisms, /// simply remove them from the hash set /// before calling this method. /// /// An asynchronous task context. /// The encoding to use for the user's credentials. /// The user's credentials. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public abstract Task AuthenticateAsync (Encoding encoding, ICredentials credentials, CancellationToken cancellationToken = default (CancellationToken)); /// /// Authenticate using the supplied credentials. /// /// /// If the server supports one or more SASL authentication mechanisms, /// then the SASL mechanisms that both the client and server support are tried /// in order of greatest security to weakest security. Once a SASL /// authentication mechanism is found that both client and server support, /// the credentials are used to authenticate. /// If the server does not support SASL or if no common SASL mechanisms /// can be found, then the default login command is used as a fallback. /// To prevent the usage of certain authentication mechanisms, /// simply remove them from the hash set /// before calling this method. /// /// The user's credentials. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public void Authenticate (ICredentials credentials, CancellationToken cancellationToken = default (CancellationToken)) { Authenticate (Encoding.UTF8, credentials, cancellationToken); } /// /// Asynchronously authenticate using the supplied credentials. /// /// /// If the server supports one or more SASL authentication mechanisms, /// then the SASL mechanisms that both the client and server support are tried /// in order of greatest security to weakest security. Once a SASL /// authentication mechanism is found that both client and server support, /// the credentials are used to authenticate. /// If the server does not support SASL or if no common SASL mechanisms /// can be found, then the default login command is used as a fallback. /// To prevent the usage of certain authentication mechanisms, /// simply remove them from the hash set /// before calling this method. /// /// An asynchronous task context. /// The user's credentials. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public Task AuthenticateAsync (ICredentials credentials, CancellationToken cancellationToken = default (CancellationToken)) { return AuthenticateAsync (Encoding.UTF8, credentials, cancellationToken); } /// /// Authenticate using the specified user name and password. /// /// /// If the server supports one or more SASL authentication mechanisms, /// then the SASL mechanisms that both the client and server support are tried /// in order of greatest security to weakest security. Once a SASL /// authentication mechanism is found that both client and server support, /// the credentials are used to authenticate. /// If the server does not support SASL or if no common SASL mechanisms /// can be found, then the default login command is used as a fallback. /// To prevent the usage of certain authentication mechanisms, /// simply remove them from the hash set /// before calling this method. /// /// The encoding to use for the user's credentials. /// The user name. /// The password. /// The cancellation token. /// /// is null. /// -or- /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public void Authenticate (Encoding encoding, string userName, string password, CancellationToken cancellationToken = default (CancellationToken)) { if (encoding == null) throw new ArgumentNullException (nameof (encoding)); if (userName == null) throw new ArgumentNullException (nameof (userName)); if (password == null) throw new ArgumentNullException (nameof (password)); var credentials = new NetworkCredential (userName, password); Authenticate (encoding, credentials, cancellationToken); } /// /// Asynchronously authenticate using the specified user name and password. /// /// /// If the server supports one or more SASL authentication mechanisms, /// then the SASL mechanisms that both the client and server support are tried /// in order of greatest security to weakest security. Once a SASL /// authentication mechanism is found that both client and server support, /// the credentials are used to authenticate. /// If the server does not support SASL or if no common SASL mechanisms /// can be found, then the default login command is used as a fallback. /// To prevent the usage of certain authentication mechanisms, /// simply remove them from the hash set /// before calling this method. /// /// An asynchronous task context. /// The encoding to use for the user's credentials. /// The user name. /// The password. /// The cancellation token. /// /// is null. /// -or- /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public Task AuthenticateAsync (Encoding encoding, string userName, string password, CancellationToken cancellationToken = default (CancellationToken)) { if (encoding == null) throw new ArgumentNullException (nameof (encoding)); if (userName == null) throw new ArgumentNullException (nameof (userName)); if (password == null) throw new ArgumentNullException (nameof (password)); var credentials = new NetworkCredential (userName, password); return AuthenticateAsync (encoding, credentials, cancellationToken); } /// /// Authenticate using the specified user name and password. /// /// /// If the server supports one or more SASL authentication mechanisms, /// then the SASL mechanisms that both the client and server support are tried /// in order of greatest security to weakest security. Once a SASL /// authentication mechanism is found that both client and server support, /// the credentials are used to authenticate. /// If the server does not support SASL or if no common SASL mechanisms /// can be found, then the default login command is used as a fallback. /// To prevent the usage of certain authentication mechanisms, /// simply remove them from the hash set /// before calling this method. /// /// /// /// /// The user name. /// The password. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public void Authenticate (string userName, string password, CancellationToken cancellationToken = default (CancellationToken)) { Authenticate (Encoding.UTF8, userName, password, cancellationToken); } /// /// Asynchronously authenticate using the specified user name and password. /// /// /// If the server supports one or more SASL authentication mechanisms, /// then the SASL mechanisms that both the client and server support are tried /// in order of greatest security to weakest security. Once a SASL /// authentication mechanism is found that both client and server support, /// the credentials are used to authenticate. /// If the server does not support SASL or if no common SASL mechanisms /// can be found, then the default login command is used as a fallback. /// To prevent the usage of certain authentication mechanisms, /// simply remove them from the hash set /// before calling this method. /// /// An asynchronous task context. /// The user name. /// The password. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public Task AuthenticateAsync (string userName, string password, CancellationToken cancellationToken = default (CancellationToken)) { return AuthenticateAsync (Encoding.UTF8, userName, password, cancellationToken); } /// /// Authenticate using the specified SASL mechanism. /// /// /// Authenticates using the specified SASL mechanism. /// For a list of available SASL authentication mechanisms supported by the server, /// check the property after the service has been /// connected. /// /// The SASL mechanism. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public abstract void Authenticate (SaslMechanism mechanism, CancellationToken cancellationToken = default (CancellationToken)); /// /// Asynchronously authenticate using the specified SASL mechanism. /// /// /// Authenticates using the specified SASL mechanism. /// For a list of available SASL authentication mechanisms supported by the server, /// check the property after the service has been /// connected. /// /// An asynchronous task context. /// The SASL mechanism. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected or is already authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// Authentication using the supplied credentials has failed. /// /// /// A SASL authentication error occurred. /// /// /// An I/O error occurred. /// /// /// A protocol error occurred. /// public abstract Task AuthenticateAsync (SaslMechanism mechanism, CancellationToken cancellationToken = default (CancellationToken)); /// /// Disconnect the service. /// /// /// If is true, a logout/quit command will be issued in order to disconnect cleanly. /// /// /// /// /// If set to true, a logout/quit command will be issued in order to disconnect cleanly. /// The cancellation token. /// /// The has been disposed. /// public abstract void Disconnect (bool quit, CancellationToken cancellationToken = default (CancellationToken)); /// /// Asynchronously disconnect the service. /// /// /// If is true, a logout/quit command will be issued in order to disconnect cleanly. /// /// An asynchronous task context. /// If set to true, a logout/quit command will be issued in order to disconnect cleanly. /// The cancellation token. /// /// The has been disposed. /// public abstract Task DisconnectAsync (bool quit, CancellationToken cancellationToken = default (CancellationToken)); /// /// Ping the mail server to keep the connection alive. /// /// /// Mail servers, if left idle for too long, will automatically drop the connection. /// /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// -or- /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The command was rejected by the mail server. /// /// /// The server responded with an unexpected token. /// public abstract void NoOp (CancellationToken cancellationToken = default (CancellationToken)); /// /// Asynchronously ping the mail server to keep the connection alive. /// /// /// Mail servers, if left idle for too long, will automatically drop the connection. /// /// An asynchronous task context. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// -or- /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The command was rejected by the mail server. /// /// /// The server responded with an unexpected token. /// public abstract Task NoOpAsync (CancellationToken cancellationToken = default (CancellationToken)); /// /// Occurs when the client has been successfully connected. /// /// /// The event is raised when the client /// successfully connects to the mail server. /// public event EventHandler Connected; /// /// Raise the connected event. /// /// /// Raises the connected event. /// /// The name of the host that the client connected to. /// The port that the client connected to on the remote host. /// The SSL/TLS options that were used when connecting. protected virtual void OnConnected (string host, int port, SecureSocketOptions options) { var handler = Connected; if (handler != null) handler (this, new ConnectedEventArgs (host, port, options)); } /// /// Occurs when the client gets disconnected. /// /// /// The event is raised whenever the client /// gets disconnected. /// public event EventHandler Disconnected; /// /// Raise the disconnected event. /// /// /// Raises the disconnected event. /// /// The name of the host that the client was connected to. /// The port that the client was connected to on the remote host. /// The SSL/TLS options that were used by the client. /// true if the disconnect was explicitly requested; otherwise, false. protected virtual void OnDisconnected (string host, int port, SecureSocketOptions options, bool requested) { var handler = Disconnected; if (handler != null) handler (this, new DisconnectedEventArgs (host, port, options, requested)); } /// /// Occurs when the client has been successfully authenticated. /// /// /// The event is raised whenever the client /// has been authenticated. /// public event EventHandler Authenticated; /// /// Raise the authenticated event. /// /// /// Raises the authenticated event. /// /// The notification sent by the server when the client successfully authenticates. protected virtual void OnAuthenticated (string message) { var handler = Authenticated; if (handler != null) handler (this, new AuthenticatedEventArgs (message)); } /// /// Releases the unmanaged resources used by the and /// optionally releases the managed resources. /// /// /// Releases the unmanaged resources used by the and /// optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; /// false to release only the unmanaged resources. protected virtual void Dispose (bool disposing) { if (disposing) ProtocolLogger.Dispose (); } /// /// Releases all resource used by the object. /// /// Call when you are finished using the . The /// method leaves the in an unusable state. After /// calling , you must release all references to the so /// the garbage collector can reclaim the memory that the was occupying. public void Dispose () { Dispose (true); GC.SuppressFinalize (this); } } }