void Ehlo(CancellationToken cancellationToken) { SmtpResponse response; response = SendEhlo (true, cancellationToken); // Some SMTP servers do not accept an EHLO after authentication (despite the rfc saying it is required). if (response.StatusCode == SmtpStatusCode.BadCommandSequence && capabilities != SmtpCapabilities.None) return; if (response.StatusCode != SmtpStatusCode.Ok) { // Try sending HELO instead... response = SendEhlo (false, cancellationToken); if (response.StatusCode != SmtpStatusCode.Ok) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); } else { // Clear the extensions capabilities = SmtpCapabilities.None; authmechs.Clear(); MaxSize = 0; var lines = response.Response.Split ('\n'); for (int i = 0; i < lines.Length; i++) { var capability = lines[i].Trim (); if (capability.StartsWith ("AUTH", StringComparison.Ordinal)) { int index = 4; capabilities |= SmtpCapabilities.Authentication; if (index < capability.Length && capability[index] == '=') index++; var mechanisms = capability.Substring (index); foreach (var mechanism in mechanisms.Split (new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) authmechs.Add (mechanism); } else if (capability.StartsWith ("SIZE", StringComparison.Ordinal)) { int index = 4; uint size; capabilities |= SmtpCapabilities.Size; while (index < capability.Length && char.IsWhiteSpace (capability[index])) index++; if (uint.TryParse (capability.Substring (index), out size)) MaxSize = size; } else if (capability == "BINARYMIME") { capabilities |= SmtpCapabilities.BinaryMime; } else if (capability == "CHUNKING") { capabilities |= SmtpCapabilities.Chunking; } else if (capability == "ENHANCEDSTATUSCODES") { capabilities |= SmtpCapabilities.EnhancedStatusCodes; } else if (capability == "8BITMIME") { capabilities |= SmtpCapabilities.EightBitMime; } else if (capability == "PIPELINING") { capabilities |= SmtpCapabilities.Pipelining; } else if (capability == "STARTTLS") { capabilities |= SmtpCapabilities.StartTLS; } else if (capability == "SMTPUTF8") { capabilities |= SmtpCapabilities.UTF8; } } } }
/// <summary> /// Establishes a connection to the specified SMTP server. /// </summary> /// <remarks> /// <para>Establishes a connection to an SMTP or SMTP/S server. If the schema /// in the uri is "smtp", a clear-text connection is made and defaults to using /// port 25 if no port is specified in the URI. However, if the schema in the /// uri is "smtps", an SSL connection is made using the /// <see cref="ClientCertificates"/> and defaults to port 465 unless a port /// is specified in the URI.</para> /// <para>It should be noted that when using a clear-text SMTP connection, /// if the server advertizes support for the STARTTLS extension, the client /// will automatically switch into TLS mode before authenticating unless the /// <paramref name="uri"/> contains a query string to disable it.</para> /// If a successful connection is made, the <see cref="AuthenticationMechanisms"/> /// and <see cref="Capabilities"/> properties will be populated. /// </remarks> /// <param name="uri">The server URI. The <see cref="System.Uri.Scheme"/> should either /// be "smtp" to make a clear-text connection or "smtps" to make an SSL connection.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para>The <paramref name="uri"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// The <paramref name="uri"/> is not an absolute URI. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="SmtpClient"/> has been disposed. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The <see cref="SmtpClient"/> is already connected. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="SmtpCommandException"> /// An SMTP command failed. /// </exception> /// <exception cref="SmtpProtocolException"> /// An SMTP protocol error occurred. /// </exception> public void Connect(Uri uri, CancellationToken cancellationToken = default (CancellationToken)) { CheckDisposed (); if (uri == null) throw new ArgumentNullException ("uri"); if (!uri.IsAbsoluteUri) throw new ArgumentException ("The uri must be absolute.", "uri"); if (IsConnected) throw new InvalidOperationException ("The SmtpClient is already connected."); capabilities = SmtpCapabilities.None; authmechs.Clear (); MaxSize = 0; var smtps = uri.Scheme.ToLowerInvariant () == "smtps"; var port = uri.Port > 0 ? uri.Port : (smtps ? 465 : 25); var query = uri.ParsedQuery (); SmtpResponse response = null; string value; var starttls = !smtps && (!query.TryGetValue ("starttls", out value) || Convert.ToBoolean (value)); #if !NETFX_CORE var ipAddresses = Dns.GetHostAddresses (uri.DnsSafeHost); Socket socket = null; for (int i = 0; i < ipAddresses.Length; i++) { socket = new Socket (ipAddresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); cancellationToken.ThrowIfCancellationRequested (); try { socket.Connect (ipAddresses[i], port); localEndPoint = socket.LocalEndPoint; break; } catch { if (i + 1 == ipAddresses.Length) throw; } } if (smtps) { var ssl = new SslStream (new NetworkStream (socket, true), false, ValidateRemoteCertificate); ssl.AuthenticateAsClient (uri.Host, ClientCertificates, SslProtocols.Default, true); stream = ssl; } else { stream = new NetworkStream (socket, true); } #else socket = new StreamSocket (); cancellationToken.ThrowIfCancellationRequested (); socket.ConnectAsync (new HostName (uri.DnsSafeHost), port.ToString (), smtps ? SocketProtectionLevel.Tls12 : SocketProtectionLevel.PlainSocket) .AsTask (cancellationToken) .GetAwaiter () .GetResult (); stream = new DuplexStream (socket.InputStream.AsStreamForRead (), socket.OutputStream.AsStreamForWrite ()); #endif if (stream.CanTimeout) { stream.WriteTimeout = timeout; stream.ReadTimeout = timeout; } host = uri.Host; logger.LogConnect (uri); try { // read the greeting response = ReadResponse (cancellationToken); if (response.StatusCode != SmtpStatusCode.ServiceReady) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); // Send EHLO and get a list of supported extensions Ehlo (cancellationToken); if (starttls && (capabilities & SmtpCapabilities.StartTLS) != 0) { response = SendCommand ("STARTTLS", cancellationToken); if (response.StatusCode != SmtpStatusCode.ServiceReady) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); #if !NETFX_CORE var tls = new SslStream (stream, false, ValidateRemoteCertificate); tls.AuthenticateAsClient (uri.Host, ClientCertificates, SslProtocols.Tls, true); stream = tls; #else socket.UpgradeToSslAsync (SocketProtectionLevel.Tls12, new HostName (uri.DnsSafeHost)) .AsTask (cancellationToken) .GetAwaiter () .GetResult (); #endif // Send EHLO again and get the new list of supported extensions Ehlo (cancellationToken); } IsConnected = true; } catch { stream.Dispose (); stream = null; throw; } }
internal void ReplayConnect(string hostName, Stream replayStream, CancellationToken cancellationToken) { CheckDisposed (); if (hostName == null) throw new ArgumentNullException ("hostName"); if (replayStream == null) throw new ArgumentNullException ("replayStream"); #if !NETFX_CORE localEndPoint = new IPEndPoint (IPAddress.Loopback, 25); #endif capabilities = SmtpCapabilities.None; stream = replayStream; authmechs.Clear (); host = hostName; MaxSize = 0; try { // read the greeting var response = ReadResponse (cancellationToken); if (response.StatusCode != SmtpStatusCode.ServiceReady) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); // Send EHLO and get a list of supported extensions Ehlo (cancellationToken); IsConnected = true; } catch { stream.Dispose (); stream = null; throw; } }
/// <summary> /// Establishes a connection to the specified SMTP or SMTP/S server. /// </summary> /// <remarks> /// <para>Establishes a connection to the specified SMTP or SMTP/S server.</para> /// <para>If the <paramref name="port"/> has a value of <c>0</c>, then the /// <paramref name="options"/> parameter is used to determine the default port to /// connect to. The default port used with <see cref="SecureSocketOptions.SslOnConnect"/> /// is <c>465</c>. All other values will use a default port of <c>25</c>.</para> /// <para>If the <paramref name="options"/> has a value of /// <see cref="SecureSocketOptions.Auto"/>, then the <paramref name="port"/> is used /// to determine the default security options. If the <paramref name="port"/> has a value /// of <c>465</c>, then the default options used will be /// <see cref="SecureSocketOptions.SslOnConnect"/>. All other values will use /// <see cref="SecureSocketOptions.StartTlsWhenAvailable"/>.</para> /// <para>Once a connection is established, properties such as /// <see cref="AuthenticationMechanisms"/> and <see cref="Capabilities"/> will be /// populated.</para> /// <para>Note: The connection established by any of the /// <a href="Overload_MailKit_Net_Smtp_SmtpClient_Connect.htm">Connect</a> /// methods may be re-used if an application wishes to send multiple messages /// to the same SMTP server. Since connecting and authenticating can be expensive /// operations, re-using a connection can significantly improve performance when /// sending a large number of messages to the same SMTP server over a short /// period of time.</para> /// </remarks> /// <example> /// <code language="c#" source="Examples\SmtpExamples.cs" region="SendMessage"/> /// </example> /// <param name="host">The host name to connect to.</param> /// <param name="port">The port to connect to. If the specified port is <c>0</c>, then the default port will be used.</param> /// <param name="options">The secure socket options to when connecting.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="host"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="port"/> is not between <c>0</c> and <c>65535</c>. /// </exception> /// <exception cref="System.ArgumentException"> /// The <paramref name="host"/> is a zero-length string. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="SmtpClient"/> has been disposed. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The <see cref="SmtpClient"/> is already connected. /// </exception> /// <exception cref="System.NotSupportedException"> /// <paramref name="options"/> was set to /// <see cref="MailKit.Security.SecureSocketOptions.StartTls"/> /// and the SMTP server does not support the STARTTLS extension. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="SmtpCommandException"> /// An SMTP command failed. /// </exception> /// <exception cref="SmtpProtocolException"> /// An SMTP protocol error occurred. /// </exception> public override void Connect (string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)) { if (host == null) throw new ArgumentNullException ("host"); if (host.Length == 0) throw new ArgumentException ("The host name cannot be empty.", "host"); if (port < 0 || port > 65535) throw new ArgumentOutOfRangeException ("port"); CheckDisposed (); if (IsConnected) throw new InvalidOperationException ("The SmtpClient is already connected."); capabilities = SmtpCapabilities.None; AuthenticationMechanisms.Clear (); MaxSize = 0; SmtpResponse response; Stream stream; bool starttls; Uri uri; ComputeDefaultValues (host, ref port, ref options, out uri, out starttls); #if !NETFX_CORE var ipAddresses = Dns.GetHostAddresses (host); Socket socket = null; for (int i = 0; i < ipAddresses.Length; i++) { socket = new Socket (ipAddresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); try { cancellationToken.ThrowIfCancellationRequested (); if (LocalEndPoint != null) socket.Bind (LocalEndPoint); socket.Connect (ipAddresses[i], port); break; } catch (OperationCanceledException) { socket.Dispose (); socket = null; throw; } catch { socket.Dispose (); socket = null; if (i + 1 == ipAddresses.Length) throw; } } if (socket == null) throw new IOException (string.Format ("Failed to resolve host: {0}", host)); this.host = host; if (options == SecureSocketOptions.SslOnConnect) { var ssl = new SslStream (new NetworkStream (socket, true), false, ValidateRemoteCertificate); ssl.AuthenticateAsClient (host, ClientCertificates, SslProtocols, true); stream = ssl; } else { stream = new NetworkStream (socket, true); } #else var protection = options == SecureSocketOptions.SslOnConnect ? SocketProtectionLevel.Tls12 : SocketProtectionLevel.PlainSocket; var socket = new StreamSocket (); try { cancellationToken.ThrowIfCancellationRequested (); socket.ConnectAsync (new HostName (host), port.ToString (), protection) .AsTask (cancellationToken) .GetAwaiter () .GetResult (); } catch { socket.Dispose (); throw; } stream = new DuplexStream (socket.InputStream.AsStreamForRead (0), socket.OutputStream.AsStreamForWrite (0)); #endif if (stream.CanTimeout) { stream.WriteTimeout = timeout; stream.ReadTimeout = timeout; } ProtocolLogger.LogConnect (uri); Stream = new SmtpStream (stream, socket, ProtocolLogger); try { // read the greeting response = Stream.ReadResponse (cancellationToken); if (response.StatusCode != SmtpStatusCode.ServiceReady) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); // Send EHLO and get a list of supported extensions Ehlo (cancellationToken); if (options == SecureSocketOptions.StartTls && (capabilities & SmtpCapabilities.StartTLS) == 0) throw new NotSupportedException ("The SMTP server does not support the STARTTLS extension."); if (starttls && (capabilities & SmtpCapabilities.StartTLS) != 0) { response = SendCommand ("STARTTLS", cancellationToken); if (response.StatusCode != SmtpStatusCode.ServiceReady) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); #if !NETFX_CORE var tls = new SslStream (stream, false, ValidateRemoteCertificate); tls.AuthenticateAsClient (host, ClientCertificates, SslProtocols, true); Stream.Stream = tls; #else socket.UpgradeToSslAsync (SocketProtectionLevel.Tls12, new HostName (host)) .AsTask (cancellationToken) .GetAwaiter () .GetResult (); #endif // Send EHLO again and get the new list of supported extensions Ehlo (cancellationToken); } connected = true; } catch { Stream.Dispose (); Stream = null; throw; } OnConnected (); }
/// <summary> /// Establish a connection to the specified SMTP or SMTP/S server using the provided socket. /// </summary> /// <remarks> /// <para>Establishes a connection to the specified SMTP or SMTP/S server.</para> /// <para>If the <paramref name="port"/> has a value of <c>0</c>, then the /// <paramref name="options"/> parameter is used to determine the default port to /// connect to. The default port used with <see cref="SecureSocketOptions.SslOnConnect"/> /// is <c>465</c>. All other values will use a default port of <c>25</c>.</para> /// <para>If the <paramref name="options"/> has a value of /// <see cref="SecureSocketOptions.Auto"/>, then the <paramref name="port"/> is used /// to determine the default security options. If the <paramref name="port"/> has a value /// of <c>465</c>, then the default options used will be /// <see cref="SecureSocketOptions.SslOnConnect"/>. All other values will use /// <see cref="SecureSocketOptions.StartTlsWhenAvailable"/>.</para> /// <para>Once a connection is established, properties such as /// <see cref="AuthenticationMechanisms"/> and <see cref="Capabilities"/> will be /// populated.</para> /// <para>Note: The connection established by any of the /// <a href="Overload_MailKit_Net_Smtp_SmtpClient_Connect.htm">Connect</a> /// methods may be re-used if an application wishes to send multiple messages /// to the same SMTP server. Since connecting and authenticating can be expensive /// operations, re-using a connection can significantly improve performance when /// sending a large number of messages to the same SMTP server over a short /// period of time.</para> /// </remarks> /// <param name="socket">The socket to use for the connection.</param> /// <param name="host">The host name to connect to.</param> /// <param name="port">The port to connect to. If the specified port is <c>0</c>, then the default port will be used.</param> /// <param name="options">The secure socket options to when connecting.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="socket"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="host"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="port"/> is not between <c>0</c> and <c>65535</c>. /// </exception> /// <exception cref="System.ArgumentException"> /// <para><paramref name="socket"/> is not connected.</para> /// <para>-or-</para> /// The <paramref name="host"/> is a zero-length string. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="SmtpClient"/> has been disposed. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The <see cref="SmtpClient"/> is already connected. /// </exception> /// <exception cref="System.NotSupportedException"> /// <paramref name="options"/> was set to /// <see cref="MailKit.Security.SecureSocketOptions.StartTls"/> /// and the SMTP server does not support the STARTTLS extension. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="SmtpCommandException"> /// An SMTP command failed. /// </exception> /// <exception cref="SmtpProtocolException"> /// An SMTP protocol error occurred. /// </exception> public void Connect (Socket socket, string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)) { if (socket == null) throw new ArgumentNullException ("socket"); if (!socket.Connected) throw new ArgumentException ("The socket is not connected.", "socket"); if (host == null) throw new ArgumentNullException ("host"); if (host.Length == 0) throw new ArgumentException ("The host name cannot be empty.", "host"); if (port < 0 || port > 65535) throw new ArgumentOutOfRangeException ("port"); CheckDisposed (); if (IsConnected) throw new InvalidOperationException ("The SmtpClient is already connected."); capabilities = SmtpCapabilities.None; AuthenticationMechanisms.Clear (); MaxSize = 0; SmtpResponse response; Stream stream; bool starttls; Uri uri; ComputeDefaultValues (host, ref port, ref options, out uri, out starttls); this.host = host; if (options == SecureSocketOptions.SslOnConnect) { var ssl = new SslStream (new NetworkStream (socket, true), false, ValidateRemoteCertificate); ssl.AuthenticateAsClient (host, ClientCertificates, SslProtocols, true); stream = ssl; } else { stream = new NetworkStream (socket, true); } if (stream.CanTimeout) { stream.WriteTimeout = timeout; stream.ReadTimeout = timeout; } ProtocolLogger.LogConnect (uri); Stream = new SmtpStream (stream, socket, ProtocolLogger); try { // read the greeting response = Stream.ReadResponse (cancellationToken); if (response.StatusCode != SmtpStatusCode.ServiceReady) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); // Send EHLO and get a list of supported extensions Ehlo (cancellationToken); if (options == SecureSocketOptions.StartTls && (capabilities & SmtpCapabilities.StartTLS) == 0) throw new NotSupportedException ("The SMTP server does not support the STARTTLS extension."); if (starttls && (capabilities & SmtpCapabilities.StartTLS) != 0) { response = SendCommand ("STARTTLS", cancellationToken); if (response.StatusCode != SmtpStatusCode.ServiceReady) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); var tls = new SslStream (stream, false, ValidateRemoteCertificate); tls.AuthenticateAsClient (host, ClientCertificates, SslProtocols, true); Stream.Stream = tls; // Send EHLO again and get the new list of supported extensions Ehlo (cancellationToken); } connected = true; } catch { Stream.Dispose (); Stream = null; throw; } OnConnected (); }
internal void ReplayConnect (string hostName, Stream replayStream, CancellationToken cancellationToken = default (CancellationToken)) { CheckDisposed (); if (hostName == null) throw new ArgumentNullException ("hostName"); if (replayStream == null) throw new ArgumentNullException ("replayStream"); Stream = new SmtpStream (replayStream, null, ProtocolLogger); capabilities = SmtpCapabilities.None; AuthenticationMechanisms.Clear (); host = hostName; MaxSize = 0; try { // read the greeting var response = Stream.ReadResponse (cancellationToken); if (response.StatusCode != SmtpStatusCode.ServiceReady) throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response); // Send EHLO and get a list of supported extensions Ehlo (cancellationToken); connected = true; } catch { Stream.Dispose (); Stream = null; throw; } OnConnected (); }
public ContentTransferEncodingVisitor (SmtpCapabilities capabilities) { Capabilities = capabilities; }
void Disconnect () { capabilities = SmtpCapabilities.None; authenticated = false; connected = false; secure = false; host = null; if (Stream != null) { Stream.Dispose (); Stream = null; } OnDisconnected (); }
public Task <MailSentStatus> Send(EMailEnvelop EMailEnvelop, Byte NumberOfRetries = 3, Boolean AutoStart = true) { var SendMailTask = new Task <MailSentStatus>(() => { lock (this) { switch (Connect()) { case Sockets.TCP.TCPConnectResult.InvalidDomainName: return(MailSentStatus.failed); case Sockets.TCP.TCPConnectResult.NoIPAddressFound: return(MailSentStatus.failed); case Sockets.TCP.TCPConnectResult.UnknownError: return(MailSentStatus.failed); case Sockets.TCP.TCPConnectResult.Ok: // 220 mail.ahzf.de ESMTP Postfix (Debian/GNU) var LoginResponse = this.ReadSMTPResponse(); if (LoginResponse.StatusCode != SMTPStatusCode.ServiceReady) { throw new SMTPClientException("SMTP login error: " + LoginResponse.ToString()); } switch (LoginResponse.StatusCode) { case SMTPStatusCode.ServiceReady: #region Send EHLO var EHLOResponses = SendCommandAndWaits("EHLO " + LocalDomain); // 250-mail.ahzf.de // 250-PIPELINING // 250-SIZE 30720000 // 250-VRFY // 250-ETRN // 250-STARTTLS // 250-AUTH LOGIN DIGEST-MD5 PLAIN CRAM-MD5 // 250-AUTH=LOGIN DIGEST-MD5 PLAIN CRAM-MD5 // 250-ENHANCEDSTATUSCODES // 250-8BITMIME // 250 DSN if (EHLOResponses.Any(v => v.StatusCode != SMTPStatusCode.Ok)) { var Error = EHLOResponses.Where(v => v.StatusCode != SMTPStatusCode.Ok). FirstOrDefault(); if (Error.StatusCode != SMTPStatusCode.Ok) { throw new SMTPClientException("SMTP EHLO command error: " + Error.ToString()); } } #endregion #region Check for STARTTLS if (UseTLS == TLSUsage.STARTTLS) { if (EHLOResponses.Any(v => v.Response == "STARTTLS")) { var StartTLSResponse = SendCommandAndWait("STARTTLS"); if (StartTLSResponse.StatusCode == SMTPStatusCode.ServiceReady) { EnableTLS(); } } else { throw new Exception("TLS is not supported by the SMTP server!"); } // Send EHLO again in order to get the new list of supported extensions! EHLOResponses = SendCommandAndWaits("EHLO " + LocalDomain); } #endregion #region Analyze EHLO responses and set SMTP capabilities // 250-mail.ahzf.de // 250-PIPELINING // 250-SIZE 30720000 // 250-VRFY // 250-ETRN // 250-STARTTLS // 250-AUTH LOGIN DIGEST-MD5 PLAIN CRAM-MD5 // 250-AUTH=LOGIN DIGEST-MD5 PLAIN CRAM-MD5 // 250-ENHANCEDSTATUSCODES // 250-8BITMIME // 250 DSN var MailServerName = EHLOResponses.FirstOrDefault(); EHLOResponses.Skip(1).ForEach(v => { #region PIPELINING if (v.Response == "PIPELINING") { Capabilities |= SmtpCapabilities.Pipelining; } #endregion #region SIZE else if (v.Response.StartsWith("SIZE ")) { Capabilities |= SmtpCapabilities.Size; if (!UInt64.TryParse(v.Response.Substring(5), out _MaxMailSize)) { throw new Exception("Invalid SIZE capability!"); } } #endregion // else if (v.Response == "VRFY") // Capabilities |= SmtpCapabilities.; // else if (v.Response == "ETRN") // Capabilities |= SmtpCapabilities.; #region STARTTLS if (v.Response == "STARTTLS") { Capabilities |= SmtpCapabilities.StartTLS; } #endregion #region AUTH else if (v.Response.StartsWith("AUTH ")) { Capabilities |= SmtpCapabilities.Authentication; SMTPAuthMethods ParsedAuthMethod; var AuthType = v.Response.Substring(4, 1); var AuthMethods = v.Response.Substring(5).Split(' '); // GMail: "AUTH LOGIN PLAIN XOAUTH XOAUTH2 PLAIN-CLIENTTOKEN" foreach (var AuthMethod in AuthMethods) { if (Enum.TryParse <SMTPAuthMethods>(AuthMethod.Replace('-', '_'), true, out ParsedAuthMethod)) { if (AuthType == " ") { _AuthMethods |= ParsedAuthMethod; } } else { _UnknownAuthMethods.Add(AuthMethod); } } } #endregion #region ENHANCEDSTATUSCODES if (v.Response == "ENHANCEDSTATUSCODES") { Capabilities |= SmtpCapabilities.EnhancedStatusCodes; } #endregion #region 8BITMIME if (v.Response == "8BITMIME") { Capabilities |= SmtpCapabilities.EightBitMime; } #endregion #region DSN if (v.Response == "DSN") { Capabilities |= SmtpCapabilities.Dsn; } #endregion #region BINARYMIME if (v.Response == "BINARYMIME") { Capabilities |= SmtpCapabilities.BinaryMime; } #endregion #region CHUNKING if (v.Response == "CHUNKING") { Capabilities |= SmtpCapabilities.Chunking; } #endregion #region UTF8 if (v.Response == "UTF8") { Capabilities |= SmtpCapabilities.UTF8; } #endregion }); #endregion #region Auth PLAIN... if (_AuthMethods.HasFlag(SMTPAuthMethods.PLAIN)) { var AuthPLAINResponse = SendCommandAndWait("AUTH PLAIN " + Convert.ToBase64String(ByteZero. Concat(_Login.ToUTF8Bytes()). Concat(ByteZero). Concat(_Password.ToUTF8Bytes()). ToArray())); } #endregion #region ...or Auth LOGIN... else if (_AuthMethods.HasFlag(SMTPAuthMethods.LOGIN)) { var AuthLOGIN1Response = SendCommandAndWait("AUTH LOGIN"); var AuthLOGIN2Response = SendCommandAndWait(Convert.ToBase64String(_Login.ToUTF8Bytes())); var AuthLOGIN3Response = SendCommandAndWait(Convert.ToBase64String(_Password.ToUTF8Bytes())); } #endregion #region ...or AUTH CRAM-MD5 else if (_AuthMethods.HasFlag(SMTPAuthMethods.CRAM_MD5)) { var AuthCRAMMD5Response = SendCommandAndWait("AUTH CRAM-MD5"); if (AuthCRAMMD5Response.StatusCode == SMTPStatusCode.AuthenticationChallenge) { var aa = Ext2.CRAM_MD5("<*****@*****.**>", "tim", "tanstaaftanstaaf"); var a2 = Convert.ToBase64String(aa) == "dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw"; var bb = Ext2.CRAM_MD5("<*****@*****.**>", "alice", "wonderland"); var b2 = Convert.ToBase64String(bb) == "YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA="; var cc = Ext2.CRAM_MD5("<*****@*****.**>", "ahzf", "ahzf2305!"); var zz = Ext2.CRAM_MD5(Convert.FromBase64String(AuthCRAMMD5Response.Response).ToUTF8String(), _Login, _Password); var AuthPLAINResponse = SendCommandAndWait(Convert.ToBase64String(zz)); } } #endregion #region MAIL FROM: foreach (var MailFrom in EMailEnvelop.MailFrom) { // MAIL FROM:<*****@*****.**> /// 250 2.1.0 Ok var MailFromCommand = "MAIL FROM: <" + MailFrom.Address.ToString() + ">"; if (Capabilities.HasFlag(SmtpCapabilities.EightBitMime)) { MailFromCommand += " BODY=8BITMIME"; } else if (Capabilities.HasFlag(SmtpCapabilities.BinaryMime)) { MailFromCommand += " BODY=BINARYMIME"; } var _MailFromResponse = SendCommandAndWait(MailFromCommand); if (_MailFromResponse.StatusCode != SMTPStatusCode.Ok) { throw new SMTPClientException("SMTP MAIL FROM command error: " + _MailFromResponse.ToString()); } } #endregion #region RCPT TO(s): // RCPT TO:<*****@*****.**> /// 250 2.1.5 Ok EMailEnvelop.RcptTo.ForEach(Rcpt => { var _RcptToResponse = SendCommandAndWait("RCPT TO: <" + Rcpt.Address.ToString() + ">"); switch (_RcptToResponse.StatusCode) { case SMTPStatusCode.UserNotLocalWillForward: case SMTPStatusCode.Ok: break; case SMTPStatusCode.UserNotLocalTryAlternatePath: case SMTPStatusCode.MailboxNameNotAllowed: case SMTPStatusCode.MailboxUnavailable: case SMTPStatusCode.MailboxBusy: // throw new SmtpCommandException(SmtpErrorCode.RecipientNotAccepted, _RcptToResponse.StatusCode, mailbox, _RcptToResponse.Response); case SMTPStatusCode.AuthenticationRequired: throw new UnauthorizedAccessException(_RcptToResponse.Response); //default: // throw new SmtpCommandException(SmtpErrorCode.UnexpectedStatusCode, _RcptToResponse.StatusCode, _RcptToResponse.Response); } //Debug.WriteLine(_RcptToResponse); }); #endregion #region Mail DATA // The encoded MIME text lines must not be longer than 76 characters! /// 354 End data with <CR><LF>.<CR><LF> var _DataResponse = SendCommandAndWait("DATA"); if (_DataResponse.StatusCode != SMTPStatusCode.StartMailInput) { throw new SMTPClientException("SMTP DATA command error: " + _DataResponse.ToString()); } // Send e-mail headers... if (EMailEnvelop.Mail != null) { EMailEnvelop.Mail. Header. Select(header => header.Key + ": " + header.Value). ForEach(line => SendCommand(line)); //SendCommand("Message-Id: <" + (EMailEnvelop.Mail.MessageId != null // ? EMailEnvelop.Mail.MessageId.ToString() // : GenerateMessageId(EMailEnvelop.Mail, RemoteHost).ToString()) + ">"); SendCommand(""); // Send e-mail body(parts)... //if (EMailEnvelop.Mail.MailBody != null) //{ EMailEnvelop.Mail.Body.ToText(false).ForEach(line => SendCommand(line)); SendCommand(""); //} } else if (EMailEnvelop.Mail.ToText != null) { EMailEnvelop.Mail.ToText.ForEach(line => SendCommand(line)); SendCommand(""); } #endregion #region End-of-DATA /// . /// 250 2.0.0 Ok: queued as 83398728027 var _FinishedResponse = SendCommandAndWait("."); if (_FinishedResponse.StatusCode != SMTPStatusCode.Ok) { throw new SMTPClientException("SMTP DATA '.' command error: " + _FinishedResponse.ToString()); } #endregion #region QUIT /// QUIT /// 221 2.0.0 Bye var _QuitResponse = SendCommandAndWait("QUIT"); if (_QuitResponse.StatusCode != SMTPStatusCode.ServiceClosingTransmissionChannel) { throw new SMTPClientException("SMTP QUIT command error: " + _QuitResponse.ToString()); } #endregion break; } return(MailSentStatus.ok); default: return(MailSentStatus.failed); } } }); if (AutoStart) { SendMailTask.Start(); } return(SendMailTask); }
public Task<MailSentStatus> Send(EMailEnvelop EMailEnvelop, Byte NumberOfRetries = 3, Boolean AutoStart = true) { var SendMailTask = new Task<MailSentStatus>(() => { lock (this) { switch (Connect()) { case Sockets.TCP.TCPConnectResult.InvalidDomainName: return MailSentStatus.failed; case Sockets.TCP.TCPConnectResult.NoIPAddressFound: return MailSentStatus.failed; case Sockets.TCP.TCPConnectResult.UnknownError: return MailSentStatus.failed; case Sockets.TCP.TCPConnectResult.Ok: // 220 mail.ahzf.de ESMTP Postfix (Debian/GNU) var LoginResponse = this.ReadSMTPResponse(); if (LoginResponse.StatusCode != SMTPStatusCode.ServiceReady) throw new SMTPClientException("SMTP login error: " + LoginResponse.ToString()); switch (LoginResponse.StatusCode) { case SMTPStatusCode.ServiceReady: #region Send EHLO var EHLOResponses = SendCommandAndWaits("EHLO " + LocalDomain); // 250-mail.ahzf.de // 250-PIPELINING // 250-SIZE 30720000 // 250-VRFY // 250-ETRN // 250-STARTTLS // 250-AUTH LOGIN DIGEST-MD5 PLAIN CRAM-MD5 // 250-AUTH=LOGIN DIGEST-MD5 PLAIN CRAM-MD5 // 250-ENHANCEDSTATUSCODES // 250-8BITMIME // 250 DSN if (EHLOResponses.Any(v => v.StatusCode != SMTPStatusCode.Ok)) { var Error = EHLOResponses.Where(v => v.StatusCode != SMTPStatusCode.Ok). FirstOrDefault(); if (Error.StatusCode != SMTPStatusCode.Ok) throw new SMTPClientException("SMTP EHLO command error: " + Error.ToString()); } #endregion #region Check for STARTTLS if (UseTLS == TLSUsage.STARTTLS) { if (EHLOResponses.Any(v => v.Response == "STARTTLS")) { var StartTLSResponse = SendCommandAndWait("STARTTLS"); if (StartTLSResponse.StatusCode == SMTPStatusCode.ServiceReady) EnableTLS(); } else throw new Exception("TLS is not supported by the SMTP server!"); // Send EHLO again in order to get the new list of supported extensions! EHLOResponses = SendCommandAndWaits("EHLO " + LocalDomain); } #endregion #region Analyze EHLO responses and set SMTP capabilities // 250-mail.ahzf.de // 250-PIPELINING // 250-SIZE 30720000 // 250-VRFY // 250-ETRN // 250-STARTTLS // 250-AUTH LOGIN DIGEST-MD5 PLAIN CRAM-MD5 // 250-AUTH=LOGIN DIGEST-MD5 PLAIN CRAM-MD5 // 250-ENHANCEDSTATUSCODES // 250-8BITMIME // 250 DSN var MailServerName = EHLOResponses.FirstOrDefault(); EHLOResponses.Skip(1).ForEach(v => { #region PIPELINING if (v.Response == "PIPELINING") Capabilities |= SmtpCapabilities.Pipelining; #endregion #region SIZE else if (v.Response.StartsWith("SIZE ")) { Capabilities |= SmtpCapabilities.Size; if (!UInt64.TryParse(v.Response.Substring(5), out _MaxMailSize)) throw new Exception("Invalid SIZE capability!"); } #endregion // else if (v.Response == "VRFY") // Capabilities |= SmtpCapabilities.; // else if (v.Response == "ETRN") // Capabilities |= SmtpCapabilities.; #region STARTTLS if (v.Response == "STARTTLS") Capabilities |= SmtpCapabilities.StartTLS; #endregion #region AUTH else if (v.Response.StartsWith("AUTH ")) { Capabilities |= SmtpCapabilities.Authentication; SMTPAuthMethods ParsedAuthMethod; var AuthType = v.Response.Substring(4, 1); var AuthMethods = v.Response.Substring(5).Split(' '); // GMail: "AUTH LOGIN PLAIN XOAUTH XOAUTH2 PLAIN-CLIENTTOKEN" foreach (var AuthMethod in AuthMethods) { if (Enum.TryParse<SMTPAuthMethods>(AuthMethod.Replace('-', '_'), true, out ParsedAuthMethod)) { if (AuthType == " ") _AuthMethods |= ParsedAuthMethod; } else _UnknownAuthMethods.Add(AuthMethod); } } #endregion #region ENHANCEDSTATUSCODES if (v.Response == "ENHANCEDSTATUSCODES") Capabilities |= SmtpCapabilities.EnhancedStatusCodes; #endregion #region 8BITMIME if (v.Response == "8BITMIME") Capabilities |= SmtpCapabilities.EightBitMime; #endregion #region DSN if (v.Response == "DSN") Capabilities |= SmtpCapabilities.Dsn; #endregion #region BINARYMIME if (v.Response == "BINARYMIME") Capabilities |= SmtpCapabilities.BinaryMime; #endregion #region CHUNKING if (v.Response == "CHUNKING") Capabilities |= SmtpCapabilities.Chunking; #endregion #region UTF8 if (v.Response == "UTF8") Capabilities |= SmtpCapabilities.UTF8; #endregion }); #endregion #region Auth PLAIN... if (_AuthMethods.HasFlag(SMTPAuthMethods.PLAIN)) { var AuthPLAINResponse = SendCommandAndWait("AUTH PLAIN " + Convert.ToBase64String(ByteZero. Concat(_Login. ToUTF8Bytes()). Concat(ByteZero). Concat(_Password.ToUTF8Bytes()). ToArray())); } #endregion #region ...or Auth LOGIN... else if (_AuthMethods.HasFlag(SMTPAuthMethods.LOGIN)) { var AuthLOGIN1Response = SendCommandAndWait("AUTH LOGIN"); var AuthLOGIN2Response = SendCommandAndWait(Convert.ToBase64String(_Login. ToUTF8Bytes())); var AuthLOGIN3Response = SendCommandAndWait(Convert.ToBase64String(_Password.ToUTF8Bytes())); } #endregion #region ...or AUTH CRAM-MD5 else if (_AuthMethods.HasFlag(SMTPAuthMethods.CRAM_MD5)) { var AuthCRAMMD5Response = SendCommandAndWait("AUTH CRAM-MD5"); if (AuthCRAMMD5Response.StatusCode == SMTPStatusCode.AuthenticationChallenge) { var aa = Ext2.CRAM_MD5("<*****@*****.**>", "tim", "tanstaaftanstaaf"); var a2 = Convert.ToBase64String(aa) == "dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw"; var bb = Ext2.CRAM_MD5("<*****@*****.**>", "alice", "wonderland"); var b2 = Convert.ToBase64String(bb) == "YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA="; var cc = Ext2.CRAM_MD5("<*****@*****.**>", "ahzf", "ahzf2305!"); var zz = Ext2.CRAM_MD5(Convert.FromBase64String(AuthCRAMMD5Response.Response).ToUTF8String(), _Login, _Password); var AuthPLAINResponse = SendCommandAndWait(Convert.ToBase64String(zz)); } } #endregion #region MAIL FROM: foreach (var MailFrom in EMailEnvelop.MailFrom) { // MAIL FROM:<*****@*****.**> /// 250 2.1.0 Ok var MailFromCommand = "MAIL FROM: <" + MailFrom.Address.ToString() + ">"; if (Capabilities.HasFlag(SmtpCapabilities.EightBitMime)) MailFromCommand += " BODY=8BITMIME"; else if (Capabilities.HasFlag(SmtpCapabilities.BinaryMime)) MailFromCommand += " BODY=BINARYMIME"; var _MailFromResponse = SendCommandAndWait(MailFromCommand); if (_MailFromResponse.StatusCode != SMTPStatusCode.Ok) throw new SMTPClientException("SMTP MAIL FROM command error: " + _MailFromResponse.ToString()); } #endregion #region RCPT TO(s): // RCPT TO:<*****@*****.**> /// 250 2.1.5 Ok EMailEnvelop.RcptTo.ForEach(Rcpt => { var _RcptToResponse = SendCommandAndWait("RCPT TO: <" + Rcpt.Address.ToString() + ">"); switch (_RcptToResponse.StatusCode) { case SMTPStatusCode.UserNotLocalWillForward: case SMTPStatusCode.Ok: break; case SMTPStatusCode.UserNotLocalTryAlternatePath: case SMTPStatusCode.MailboxNameNotAllowed: case SMTPStatusCode.MailboxUnavailable: case SMTPStatusCode.MailboxBusy: // throw new SmtpCommandException(SmtpErrorCode.RecipientNotAccepted, _RcptToResponse.StatusCode, mailbox, _RcptToResponse.Response); case SMTPStatusCode.AuthenticationRequired: throw new UnauthorizedAccessException(_RcptToResponse.Response); //default: // throw new SmtpCommandException(SmtpErrorCode.UnexpectedStatusCode, _RcptToResponse.StatusCode, _RcptToResponse.Response); } //Debug.WriteLine(_RcptToResponse); }); #endregion #region Mail DATA // The encoded MIME text lines must not be longer than 76 characters! /// 354 End data with <CR><LF>.<CR><LF> var _DataResponse = SendCommandAndWait("DATA"); if (_DataResponse.StatusCode != SMTPStatusCode.StartMailInput) throw new SMTPClientException("SMTP DATA command error: " + _DataResponse.ToString()); // Send e-mail headers... if (EMailEnvelop.Mail != null) { EMailEnvelop.Mail. Header. Select(header => header.Key + ": " + header.Value). ForEach(line => SendCommand(line)); //SendCommand("Message-Id: <" + (EMailEnvelop.Mail.MessageId != null // ? EMailEnvelop.Mail.MessageId.ToString() // : GenerateMessageId(EMailEnvelop.Mail, RemoteHost).ToString()) + ">"); SendCommand(""); // Send e-mail body(parts)... //if (EMailEnvelop.Mail.MailBody != null) //{ EMailEnvelop.Mail.Body.ToText(false).ForEach(line => SendCommand(line)); SendCommand(""); //} } else if (EMailEnvelop.Mail.ToText != null) { EMailEnvelop.Mail.ToText.ForEach(line => SendCommand(line)); SendCommand(""); } #endregion #region End-of-DATA /// . /// 250 2.0.0 Ok: queued as 83398728027 var _FinishedResponse = SendCommandAndWait("."); if (_FinishedResponse.StatusCode != SMTPStatusCode.Ok) throw new SMTPClientException("SMTP DATA '.' command error: " + _FinishedResponse.ToString()); #endregion #region QUIT /// QUIT /// 221 2.0.0 Bye var _QuitResponse = SendCommandAndWait("QUIT"); if (_QuitResponse.StatusCode != SMTPStatusCode.ServiceClosingTransmissionChannel) throw new SMTPClientException("SMTP QUIT command error: " + _QuitResponse.ToString()); #endregion break; } return MailSentStatus.ok; default: return MailSentStatus.failed; }} }); if (AutoStart) SendMailTask.Start(); return SendMailTask; }