File: net\System\Net\mail\SmtpTransport.cs
Project: ndp\fx\src\System.csproj (System)
//-----------------------------------------------------------------------------
// <copyright file="SmtpTransport.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
 
namespace System.Net.Mail
{
    using System;
    using System.Collections;
    using System.IO;
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    using System.Net.Mime;
    using System.Diagnostics;
 
    internal enum SupportedAuth{
        None = 0, Login = 1,
#if !FEATURE_PAL
        NTLM = 2, GSSAPI = 4, WDigest = 8
#endif
    };
 
    internal class SmtpPooledStream:PooledStream{
        internal bool previouslyUsed;
        internal bool dsnEnabled;  //delivery  status notification
        internal bool serverSupportsEai;
        internal ICredentialsByHost creds;
        internal SmtpPooledStream(ConnectionPool connectionPool, TimeSpan lifetime, bool checkLifetime) : base (connectionPool,lifetime,checkLifetime) {
        }
 
        // maximum line length in SMTP response is 76 so this is a bit more conservative
        const int safeBufferLength = 80;
        
        // Cleans up an open connection to an SMTP server by sending the QUIT 
        // response, reading the server's response, and disposing the base stream
        protected override void Dispose(bool disposing)
        {
            if (Logging.On) {
                Logging.Enter(Logging.Web, "SmtpPooledStream::Dispose #" + ValidationHelper.HashString(this));
            }
            if (disposing) {
                if (this.NetworkStream.Connected) {
                    this.Write(SmtpCommands.Quit, 0, SmtpCommands.Quit.Length);
                    this.Flush();
 
                    // read the response - this is a formality since the connection is shut down
                    // immediately by the server so this data can safely be ignored but buffer
                    // must be read to ensure a FIN is sent instead of a RST
                    byte[] buffer = new byte[safeBufferLength];
                    int result = this.Read(buffer, 0, safeBufferLength);
                }
            }
            base.Dispose(disposing);
            if (Logging.On) {
                Logging.Exit(Logging.Web, "SmtpPooledStream::Dispose #" + ValidationHelper.HashString(this));
            }
        }        
    }
 
    internal class SmtpTransport
    {
        internal const int DefaultPort = 25;
        
        ISmtpAuthenticationModule[] authenticationModules;
        SmtpConnection connection;
        SmtpClient client;
        ICredentialsByHost credentials;
        int timeout = 100000; // seconds
        ArrayList failedRecipientExceptions = new ArrayList();
        bool m_IdentityRequired;
 
        bool enableSsl = false;
        X509CertificateCollection clientCertificates = null;
 
        ServicePoint lastUsedServicePoint;
 
        internal SmtpTransport(SmtpClient client) : this(client, SmtpAuthenticationManager.GetModules()) {
        }
 
 
        internal SmtpTransport(SmtpClient client, ISmtpAuthenticationModule[] authenticationModules)
        {
            this.client = client;
 
            if (authenticationModules == null)
            {
                throw new ArgumentNullException("authenticationModules");
            }
 
            this.authenticationModules = authenticationModules;
        }
 
        internal ICredentialsByHost Credentials
        {
            get
            {
                return credentials;
            }
            set
            {
                credentials = value;
            }
        }
 
        internal bool IdentityRequired
        {
            get
            {
                return m_IdentityRequired;
            }
 
            set
            {
                m_IdentityRequired = value;
            }
        }
 
        internal bool IsConnected
        {
            get
            {
                return connection != null && connection.IsConnected;
            }
        }
 
        internal int Timeout
        {
            get
            {
                return timeout;
            }
            set
            {
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException("value");
                }
 
                timeout = value;
            }
        }
 
        internal bool EnableSsl
        {
            get
            {
                return enableSsl;
            }
            set
            {
#if !FEATURE_PAL
                enableSsl = value;
#else
                throw new NotImplementedException("ROTORTODO");
#endif
            }
        }
 
        internal X509CertificateCollection ClientCertificates
        {
            get {
                if (clientCertificates == null) {
                    clientCertificates = new X509CertificateCollection();
                }
                return clientCertificates;
            }
        }
 
        internal bool ServerSupportsEai
        {
            get { return connection != null && connection.ServerSupportsEai; }
        }
 
        // check to see if we're using a different servicepoint than the last
        // servicepoint used to get a connectionpool
        //
        // preconditions: servicePoint must have valid host and port (checked in SmtpClient)
        //
        // postconditions: if servicePoint is different than the last servicePoint used by this object,
        // the connection pool for the previous servicepoint will be cleaned up and servicePoint will be
        // cached to identify if it has changed in future uses of this SmtpTransport object
        private void UpdateServicePoint(ServicePoint servicePoint)
        {
            if (lastUsedServicePoint == null) {
                lastUsedServicePoint = servicePoint;
            }
            else if (lastUsedServicePoint.Host != servicePoint.Host
               || lastUsedServicePoint.Port != servicePoint.Port) {
                ConnectionPoolManager.CleanupConnectionPool(servicePoint, "");
                lastUsedServicePoint = servicePoint;
            }
        }
 
        internal void GetConnection(ServicePoint servicePoint)
        {           
            try {
                Debug.Assert(servicePoint != null, "no ServicePoint provided by SmtpClient");
                // check to see if we have a different connection than last time
                UpdateServicePoint(servicePoint);
                connection = new SmtpConnection(this, client, credentials, authenticationModules);
                connection.Timeout = timeout;
                if(Logging.On)Logging.Associate(Logging.Web, this, connection);
                
                if (EnableSsl)
                {
                    connection.EnableSsl = true;
                    connection.ClientCertificates = ClientCertificates;
                }
                connection.GetConnection(servicePoint);
            }
            finally {
                
            }
        }
 
 
        internal IAsyncResult BeginGetConnection(ServicePoint servicePoint, ContextAwareResult outerResult, AsyncCallback callback, object state)
        {
            GlobalLog.Enter("SmtpTransport#" + ValidationHelper.HashString(this) + "::BeginConnect");
            IAsyncResult result = null;
            try{
                UpdateServicePoint(servicePoint);
                connection = new SmtpConnection(this, client, credentials, authenticationModules);
                connection.Timeout = timeout;
                if(Logging.On)Logging.Associate(Logging.Web, this, connection);
                if (EnableSsl)
                {
                    connection.EnableSsl = true;
                    connection.ClientCertificates = ClientCertificates;
                }
 
                result = connection.BeginGetConnection(servicePoint, outerResult, callback, state);
            }
            catch(Exception innerException){
                throw new SmtpException(SR.GetString(SR.MailHostNotFound), innerException);
            }
            GlobalLog.Leave("SmtpTransport#" + ValidationHelper.HashString(this) + "::BeginConnect Sync Completion");
            return result;
        }
 
 
        internal void EndGetConnection(IAsyncResult result)
        {
            GlobalLog.Enter("SmtpTransport#" + ValidationHelper.HashString(this) + "::EndGetConnection");
            try {
                connection.EndGetConnection(result);
            }
            finally {
                
                GlobalLog.Leave("SmtpTransport#" + ValidationHelper.HashString(this) + "::EndConnect");
            }
        }
 
 
        internal IAsyncResult BeginSendMail(MailAddress sender, MailAddressCollection recipients, 
            string deliveryNotify, bool allowUnicode, AsyncCallback callback, object state)
        {
            if (sender == null)
            {
                throw new ArgumentNullException("sender");
            }
 
            if (recipients == null)
            {
                throw new ArgumentNullException("recipients");
            }
 
            GlobalLog.Assert(recipients.Count > 0, "SmtpTransport::BeginSendMail()|recepients.Count <= 0");
            
            SendMailAsyncResult result = new SendMailAsyncResult(connection, sender, recipients,
                allowUnicode, connection.DSNEnabled ? deliveryNotify : null, 
                callback, state);
            result.Send();
            return result;
        }
 
        
        internal void ReleaseConnection() {
            if(connection != null){
                connection.ReleaseConnection();
            }
        }
 
        internal void Abort() {
            if(connection != null){
                connection.Abort();
            }
        }
 
 
        internal MailWriter EndSendMail(IAsyncResult result)
        {
            try {
                return SendMailAsyncResult.End(result);
            }
            finally {
                
            }
        }
 
        internal MailWriter SendMail(MailAddress sender, MailAddressCollection recipients, string deliveryNotify, 
            bool allowUnicode, out SmtpFailedRecipientException exception)
        {
            if (sender == null)
            {
                throw new ArgumentNullException("sender");
            }
 
            if (recipients == null)
            {
                throw new ArgumentNullException("recipients");
            }
 
            GlobalLog.Assert(recipients.Count > 0, "SmtpTransport::SendMail()|recepients.Count <= 0");
 
            MailCommand.Send(connection, SmtpCommands.Mail, sender, allowUnicode);
            failedRecipientExceptions.Clear();
 
            exception = null;
            string response;
            foreach (MailAddress address in recipients) {
                string smtpAddress = address.GetSmtpAddress(allowUnicode);
                string to = smtpAddress + (connection.DSNEnabled ? deliveryNotify : String.Empty);
                if (!RecipientCommand.Send(connection, to, out response)) {
                    failedRecipientExceptions.Add(
                        new SmtpFailedRecipientException(connection.Reader.StatusCode, smtpAddress, response));
                }
            }
 
            if (failedRecipientExceptions.Count > 0)
            {
                if (failedRecipientExceptions.Count == 1)
                {
                    exception = (SmtpFailedRecipientException) failedRecipientExceptions[0];
                }
                else
                {
                    exception = new SmtpFailedRecipientsException(failedRecipientExceptions, failedRecipientExceptions.Count == recipients.Count);
                }
 
                if (failedRecipientExceptions.Count == recipients.Count){
                    exception.fatal = true;
                    throw exception;
                }
            }
 
            DataCommand.Send(connection);
            return new MailWriter(connection.GetClosableStream());
        }
        
        internal void CloseIdleConnections(ServicePoint servicePoint)
        {
            ConnectionPoolManager.CleanupConnectionPool(servicePoint, "");
        }
    }
 
 
    class SendMailAsyncResult : LazyAsyncResult
    {
        SmtpConnection connection;
        MailAddress from;
        string deliveryNotify;
        static AsyncCallback sendMailFromCompleted = new AsyncCallback(SendMailFromCompleted);
        static AsyncCallback sendToCollectionCompleted = new AsyncCallback(SendToCollectionCompleted);
        static AsyncCallback sendDataCompleted = new AsyncCallback(SendDataCompleted);
        ArrayList failedRecipientExceptions = new ArrayList();
        Stream stream;
        MailAddressCollection toCollection;
        int toIndex;
        private bool allowUnicode;
 
 
        internal SendMailAsyncResult(SmtpConnection connection, MailAddress from, MailAddressCollection toCollection, 
            bool allowUnicode, string deliveryNotify, AsyncCallback callback, object state) 
            : base(null, state, callback)
        {
            this.toCollection = toCollection;
            this.connection = connection;
            this.from = from;
            this.deliveryNotify = deliveryNotify;
            this.allowUnicode = allowUnicode;
        }
 
        internal void Send(){
            SendMailFrom();
        }
 
        internal static MailWriter End(IAsyncResult result)
        {
            SendMailAsyncResult thisPtr = (SendMailAsyncResult)result;
            object sendMailResult = thisPtr.InternalWaitForCompletion();
            
            // Note the difference between the singular and plural FailedRecipient exceptions.
            // Only fail immediately if we couldn't send to any recipients.
            if ((sendMailResult is Exception)
                && (!(sendMailResult is SmtpFailedRecipientException) 
                    || ((SmtpFailedRecipientException)sendMailResult).fatal))
            {
                throw (Exception)sendMailResult;
            }            
            
            return new MailWriter(thisPtr.stream);
        }
        void SendMailFrom()
        {
            IAsyncResult result = MailCommand.BeginSend(connection, SmtpCommands.Mail, from, allowUnicode, 
                sendMailFromCompleted, this);
            if (!result.CompletedSynchronously)
            {
                return;
            }
 
            MailCommand.EndSend(result);
            SendToCollection();
        }
 
        static void SendMailFromCompleted(IAsyncResult result)
        {
            if (!result.CompletedSynchronously)
            {
                SendMailAsyncResult thisPtr = (SendMailAsyncResult)result.AsyncState;
                try
                {
                    MailCommand.EndSend(result);
                    thisPtr.SendToCollection();
                }
                catch (Exception e)
                {
                    thisPtr.InvokeCallback(e);
                }
            }
        }
        
        void SendToCollection()
        {
            while (toIndex < toCollection.Count)
            {
                MultiAsyncResult result = (MultiAsyncResult)RecipientCommand.BeginSend(connection, 
                    toCollection[toIndex++].GetSmtpAddress(allowUnicode) + deliveryNotify, 
                    sendToCollectionCompleted, this);
                if (!result.CompletedSynchronously)
                {
                    return;
                }
                string response;
                if (!RecipientCommand.EndSend(result, out response)){
                    failedRecipientExceptions.Add(new SmtpFailedRecipientException(connection.Reader.StatusCode, 
                        toCollection[toIndex - 1].GetSmtpAddress(allowUnicode), response));
                }
            }
            SendData();
        }
 
        static void SendToCollectionCompleted(IAsyncResult result)
        {
            if (!result.CompletedSynchronously)
            {
                SendMailAsyncResult thisPtr = (SendMailAsyncResult)result.AsyncState;
                try
                {
                    string response;
                    if (!RecipientCommand.EndSend(result, out response))
                    {
                        thisPtr.failedRecipientExceptions.Add(
                            new SmtpFailedRecipientException(thisPtr.connection.Reader.StatusCode,
                                thisPtr.toCollection[thisPtr.toIndex - 1].GetSmtpAddress(thisPtr.allowUnicode), 
                                response));
 
                        if (thisPtr.failedRecipientExceptions.Count == thisPtr.toCollection.Count)
                        {
                            SmtpFailedRecipientException exception = null;
                            if (thisPtr.toCollection.Count == 1)
                            {
                                exception = (SmtpFailedRecipientException)thisPtr.failedRecipientExceptions[0];
                            }
                            else
                            {
                                exception = new SmtpFailedRecipientsException(thisPtr.failedRecipientExceptions, true);
                            }
                            exception.fatal = true;
                            thisPtr.InvokeCallback(exception);
                            return;
                        }
                    }
                    thisPtr.SendToCollection();
                }
                catch (Exception e)
                {
                    thisPtr.InvokeCallback(e);
                }
            }
        }
 
        void SendData()
        {
            IAsyncResult result = DataCommand.BeginSend(connection, sendDataCompleted, this);
            if (!result.CompletedSynchronously)
            {
                return;
            }
            DataCommand.EndSend(result);
            stream = connection.GetClosableStream();
            if (failedRecipientExceptions.Count > 1)
            {
                InvokeCallback(new SmtpFailedRecipientsException(failedRecipientExceptions, failedRecipientExceptions.Count == toCollection.Count));
            }
            else if (failedRecipientExceptions.Count == 1)
            {
                InvokeCallback(failedRecipientExceptions[0]);
            }
            else
            {
                InvokeCallback();
            }
        }
 
        static void SendDataCompleted(IAsyncResult result)
        {
            if (!result.CompletedSynchronously)
            {
                SendMailAsyncResult thisPtr = (SendMailAsyncResult)result.AsyncState;
                try
                {
                    DataCommand.EndSend(result);
                    thisPtr.stream = thisPtr.connection.GetClosableStream();
                    if (thisPtr.failedRecipientExceptions.Count > 1)
                    {
                        thisPtr.InvokeCallback(new SmtpFailedRecipientsException(thisPtr.failedRecipientExceptions, thisPtr.failedRecipientExceptions.Count == thisPtr.toCollection.Count));
                    }
                    else if (thisPtr.failedRecipientExceptions.Count == 1)
                    {
                        thisPtr.InvokeCallback(thisPtr.failedRecipientExceptions[0]);
                    }
                    else
                    {
                        thisPtr.InvokeCallback();
                    }
                }
                catch (Exception e)
                {
                    thisPtr.InvokeCallback(e);
                }
            }
        }
 
        // Return the list of non-terminal failures (some recipients failed but not others).
        internal SmtpFailedRecipientException GetFailedRecipientException()
        {
            if (failedRecipientExceptions.Count == 1)
            {
                return (SmtpFailedRecipientException)failedRecipientExceptions[0];
            }
            else if (failedRecipientExceptions.Count > 1)
            {
                // Aggregate exception, multiple failures
                return new SmtpFailedRecipientsException(failedRecipientExceptions, false);
            }
            return null;
        }
    }
 }