| File: System\Net\Mail\SmtpConnection.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Mail\src\System.Net.Mail.csproj (System.Net.Mail) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Runtime.ExceptionServices; using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; namespace System.Net.Mail { internal sealed partial class SmtpConnection { private readonly BufferBuilder _bufferBuilder = new BufferBuilder(); private bool _isConnected; private bool _isClosed; private bool _isStreamOpen; private readonly EventHandler? _onCloseHandler; internal SmtpTransport? _parent; private readonly SmtpClient? _client; private Stream? _stream; internal TcpClient? _tcpClient; private SmtpReplyReaderFactory? _responseReader; private readonly ICredentialsByHost? _credentials; private string[]? _extensions; private bool _enableSsl; private X509CertificateCollection? _clientCertificates; internal SmtpConnection(SmtpTransport parent, SmtpClient client, ICredentialsByHost? credentials, ISmtpAuthenticationModule[] authenticationModules) { _client = client; _credentials = credentials; _authenticationModules = authenticationModules; _parent = parent; _tcpClient = new TcpClient(); _onCloseHandler = new EventHandler(OnClose); } internal BufferBuilder BufferBuilder => _bufferBuilder; internal bool IsConnected => _isConnected; internal bool IsStreamOpen => _isStreamOpen; internal SmtpReplyReaderFactory? Reader => _responseReader; internal bool EnableSsl { get { return _enableSsl; } set { _enableSsl = value; } } internal X509CertificateCollection? ClientCertificates { get { return _clientCertificates; } set { _clientCertificates = value; } } internal void InitializeConnection(string host, int port) { _tcpClient!.Connect(host, port); _stream = _tcpClient.GetStream(); } internal async Task InitializeConnectionAsync(string host, int port) { await _tcpClient!.ConnectAsync(host, port).ConfigureAwait(false); _stream = _tcpClient.GetStream(); } internal async Task GetConnectionAsync<TIOAdapter>(string host, int port, CancellationToken cancellationToken = default) where TIOAdapter : IReadWriteAdapter { if (_isConnected) { throw new InvalidOperationException(SR.SmtpAlreadyConnected); } bool isAsync = typeof(TIOAdapter) == typeof(AsyncReadWriteAdapter); // Initialize the connection if (isAsync) { await InitializeConnectionAsync(host, port).ConfigureAwait(false); } else { InitializeConnection(host, port); } _responseReader = new SmtpReplyReaderFactory(_stream!); // Read the initial greeting LineInfo info = await _responseReader.GetNextReplyReader().ReadLineAsync<TIOAdapter>(cancellationToken).ConfigureAwait(false); switch (info.StatusCode) { case SmtpStatusCode.ServiceReady: break; default: throw new SmtpException(info.StatusCode, info.Line, true); } // Try EHLO first try { _extensions = await EHelloCommand.SendAsync<TIOAdapter>(this, _client!._clientDomain, cancellationToken).ConfigureAwait(false); ParseExtensions(_extensions); } catch (SmtpException e) { if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized) && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)) { throw; } // Fall back to HELO if EHLO fails await HelloCommand.SendAsync<TIOAdapter>(this, _client!._clientDomain, cancellationToken).ConfigureAwait(false); // If ehello isn't supported, assume basic login _supportedAuth = SupportedAuth.Login; } // Handle SSL/TLS if (_enableSsl) { if (!_serverSupportsStartTls) { // Either TLS is already established or server does not support TLS if (!(_stream is SslStream)) { throw new SmtpException(SR.MailServerDoesNotSupportStartTls); } } await StartTlsCommand.SendAsync<TIOAdapter>(this, cancellationToken).ConfigureAwait(false); #pragma warning disable SYSLIB0014 // ServicePointManager is obsolete SslStream sslStream = new SslStream(_stream!, false, ServicePointManager.ServerCertificateValidationCallback); if (isAsync) { // If we are using async, we need to use the async version of AuthenticateAsClientAsync await sslStream.AuthenticateAsClientAsync( new SslClientAuthenticationOptions { TargetHost = host, ClientCertificates = _clientCertificates, EnabledSslProtocols = (SslProtocols)ServicePointManager.SecurityProtocol, // enums use same values CertificateRevocationCheckMode = ServicePointManager.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck, }, cancellationToken).ConfigureAwait(false); } else { // Synchronous version sslStream.AuthenticateAsClient(host, _clientCertificates, (SslProtocols)ServicePointManager.SecurityProtocol, ServicePointManager.CheckCertificateRevocationList); } #pragma warning restore SYSLIB0014 // ServicePointManager is obsolete _stream = sslStream; _responseReader = new SmtpReplyReaderFactory(_stream); // According to RFC 3207: The client SHOULD send an EHLO command // as the first command after a successful TLS negotiation. _extensions = await EHelloCommand.SendAsync<TIOAdapter>(this, _client!._clientDomain, cancellationToken).ConfigureAwait(false); ParseExtensions(_extensions); } // If credentials were supplied, attempt authentication if (_credentials != null) { for (int i = 0; i < _authenticationModules.Length; i++) { // Only authenticate if the auth protocol is supported if (!AuthSupported(_authenticationModules[i])) { continue; } NetworkCredential? credential = _credentials.GetCredential(host, port, _authenticationModules[i].AuthenticationType); if (credential == null) continue; Authorization? auth = SetContextAndTryAuthenticate(_authenticationModules[i], credential); if (auth != null && auth.Message != null) { info = await AuthCommand.SendAsync<TIOAdapter>(this, _authenticationModules[i].AuthenticationType, auth.Message, cancellationToken).ConfigureAwait(false); if (info.StatusCode == SmtpStatusCode.CommandParameterNotImplemented) { continue; } while ((int)info.StatusCode == 334) { auth = _authenticationModules[i].Authenticate(info.Line, null, this, _client!.TargetName, null); if (auth == null) { throw new SmtpException(SR.SmtpAuthenticationFailed); } info = await AuthCommand.SendAsync<TIOAdapter>(this, auth.Message, cancellationToken).ConfigureAwait(false); if ((int)info.StatusCode == 235) { _authenticationModules[i].CloseContext(this); _isConnected = true; return; } } } } } _isConnected = true; } internal async Task FlushAsync<TIOAdapter>(CancellationToken cancellationToken = default) where TIOAdapter : IReadWriteAdapter { await TIOAdapter.WriteAsync(_stream!, _bufferBuilder.GetBuffer().AsMemory(0, _bufferBuilder.Length), cancellationToken).ConfigureAwait(false); _bufferBuilder.Reset(); } private void ShutdownConnection(bool isAbort) { if (!_isClosed) { lock (this) { if (!_isClosed && _tcpClient != null) { try { try { if (isAbort) { // Must destroy manually since sending a QUIT here might not be // interpreted correctly by the server if it's in the middle of a // DATA command or some similar situation. This may send a RST // but this is ok in this situation. Do not reuse this connection _tcpClient.LingerState = new LingerOption(true, 0); } else { // Gracefully close the transmission channel _tcpClient.Client.Blocking = false; QuitCommand.SendAsync<SyncReadWriteAdapter>(this).GetAwaiter().GetResult(); } } finally { //free cbt buffer _stream?.Close(); _tcpClient.Dispose(); } } catch (IOException) { // Network failure } catch (ObjectDisposedException) { // See https://github.com/dotnet/runtime/issues/30732, and potentially // catch additional exception types here if need demonstrates. } } _isClosed = true; } } _isConnected = false; } internal void ReleaseConnection() { ShutdownConnection(false); } internal void Abort() { ShutdownConnection(true); } private Authorization? SetContextAndTryAuthenticate(ISmtpAuthenticationModule module, NetworkCredential? credential) { return module.Authenticate(null, credential, this, _client!.TargetName, null); } internal Stream GetClosableStream() { ClosableStream cs = new ClosableStream(_stream!, _onCloseHandler); _isStreamOpen = true; return cs; } private void OnClose(object? sender, EventArgs args) { _isStreamOpen = false; DataStopCommand.SendAsync<SyncReadWriteAdapter>(this).GetAwaiter().GetResult(); } } }