smtpconnection.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / whidbey / NetFXspW7 / ndp / fx / src / Net / System / Net / Mail / smtpconnection.cs / 3 / smtpconnection.cs

                            namespace System.Net.Mail 
{
    using System;
    using System.Net;
    using System.Security.Cryptography.X509Certificates; 
    using System.IO;
    using System.Threading; 
    using System.Globalization; 
    using System.Security.Principal;
    using System.Security.Permissions; 
    using System.Security.Authentication.ExtendedProtection;


 
    class SmtpConnection
    { 
 
        private static PooledStream CreateSmtpPooledStream(ConnectionPool pool)         {
            return (PooledStream)new SmtpPooledStream(pool, TimeSpan.MaxValue, false); 
        }


        private static readonly CreateConnectionDelegate m_CreateConnectionCallback = new CreateConnectionDelegate(CreateSmtpPooledStream); 
        private static readonly ContextCallback s_AuthenticateCallback = new ContextCallback(AuthenticateCallback);
 
        BufferBuilder bufferBuilder = new BufferBuilder(); 
        bool isConnected;
        bool isClosed; 
        bool isStreamOpen;
        bool sawNegotiate;
        EventHandler onCloseHandler;
        internal SmtpTransport parent; 
        internal SmtpClient client;
        SmtpReplyReaderFactory responseReader; 
 
        PooledStream pooledStream;
        ConnectionPool connectionPool; 
        SupportedAuth supportedAuth = SupportedAuth.None;
        bool serverSupportsStartTls = false;
        ISmtpAuthenticationModule[] authenticationModules;
        ICredentialsByHost credentials; 
        int timeout = 100000;
        string[] extensions; 
        private ChannelBinding channelBindingToken = null; 

 
        bool enableSsl;
        X509CertificateCollection clientCertificates;

        internal SmtpConnection(SmtpTransport parent, SmtpClient client, ICredentialsByHost credentials, ISmtpAuthenticationModule[] authenticationModules) 
        {
            this.client = client; 
            this.credentials = credentials; 
            this.authenticationModules = authenticationModules;
            this.parent = parent; 
            onCloseHandler = new EventHandler(OnClose);
        }

        internal BufferBuilder BufferBuilder 
        {
            get 
            { 
                return bufferBuilder;
            } 
        }

        internal bool IsConnected
        { 
            get
            { 
                return isConnected; 
            }
        } 

        internal bool IsStreamOpen
        {
            get 
            {
                return isStreamOpen; 
            } 
        }
 
        internal bool DSNEnabled
        {
            get
            { 
                if (pooledStream != null)
                    return ((SmtpPooledStream)pooledStream).dsnEnabled; 
                else 
                    return false;
            } 
        }

        internal SmtpReplyReaderFactory Reader
        { 
            get
            { 
                return responseReader; 
            }
        } 

        internal bool EnableSsl
        {
            get 
            {
                return enableSsl; 
            } 
            set
            { 
#if !FEATURE_PAL
                enableSsl = value;
#else
                throw new NotImplementedException("ROTORTODO"); 
#endif
            } 
        } 

        internal int Timeout 
        {
            get
            {
                return timeout; 
            }
            set 
            { 
                timeout = value;
            } 
        }


        internal X509CertificateCollection ClientCertificates 
        {
            get 
            { 
                return clientCertificates;
            } 
            set
            {
                clientCertificates = value;
            } 
        }
 
 
        internal IAsyncResult BeginGetConnection(string host, int port, ContextAwareResult outerResult, AsyncCallback callback, object state)
        { 
            ServicePoint servicePoint = ServicePointManager.FindServicePoint(host, port);
            if (Logging.On) Logging.Associate(Logging.Web, this, servicePoint);

            if (EnableSsl && ClientCertificates != null && ClientCertificates.Count > 0) 
                connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, ClientCertificates.GetHashCode().ToString(NumberFormatInfo.InvariantInfo), m_CreateConnectionCallback);
            else 
                connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, "", m_CreateConnectionCallback); 

            ConnectAndHandshakeAsyncResult result = new ConnectAndHandshakeAsyncResult(this, host, port, outerResult, callback, state); 
            result.GetConnection(false);
            return result;
        }
 

        internal IAsyncResult BeginFlush(AsyncCallback callback, object state) 
        { 
            return pooledStream.UnsafeBeginWrite(bufferBuilder.GetBuffer(), 0, bufferBuilder.Length, callback, state);
        } 

        internal void EndFlush(IAsyncResult result)
        {
            pooledStream.EndWrite(result); 
            bufferBuilder.Reset();
        } 
 
        internal void Flush()
        { 
            pooledStream.Write(bufferBuilder.GetBuffer(), 0, bufferBuilder.Length);
            bufferBuilder.Reset();
        }
 
        internal void ReleaseConnection()
        { 
            if (!isClosed) { 
                lock (this) {
 
                    if (!isClosed && pooledStream != null) {

                        //free cbt buffer
                        if (channelBindingToken != null){ 
                            channelBindingToken.Close();
                        } 
 
                        GlobalLog.Print("SmtpConnectiont#" + ValidationHelper.HashString(this) + "::Close Transport#" + ValidationHelper.HashString(parent) + "putting back pooledStream#" + ValidationHelper.HashString(pooledStream));
 
                        ((SmtpPooledStream)pooledStream).previouslyUsed = true;
                        connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout);
                    }
                    isClosed = true; 
                }
            } 
            isConnected = false; 
        }
 
        internal void Abort()
        {
            if (!isClosed) {
                lock (this) { 
                    if (!isClosed && pooledStream != null){
 
                        GlobalLog.Print("SmtpConnectiont#" + ValidationHelper.HashString(this) + "::Close Transport#" + ValidationHelper.HashString(parent) + "closing and putting back pooledStream#" + ValidationHelper.HashString(pooledStream)); 

                        //free CBT buffer 
                        if (this.channelBindingToken != null){
                            channelBindingToken.Close();
                        }
 
                        pooledStream.Close(0);
                        connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout); 
                    } 
                    isClosed = true;
                } 
            }
            isConnected = false;
        }
 
        internal void ParseExtensions(string[] extensions) {
            supportedAuth = SupportedAuth.None; 
            foreach (string extension in extensions) { 
                if (String.Compare(extension, 0, "auth ", 0, 5, StringComparison.OrdinalIgnoreCase) == 0) {
                    string[] authTypes = extension.Split(' '); 
                    foreach (string authType in authTypes) {
                        if (String.Compare(authType, "login", StringComparison.OrdinalIgnoreCase) == 0) {
                            supportedAuth |= SupportedAuth.Login;
                        } 
#if !FEATURE_PAL
                        else if (String.Compare(authType, "ntlm", StringComparison.OrdinalIgnoreCase) == 0) { 
                            supportedAuth |= SupportedAuth.NTLM; 
                        }
                        else if (String.Compare(authType, "gssapi", StringComparison.OrdinalIgnoreCase) == 0) { 
                            supportedAuth |= SupportedAuth.GGSAPI;
                        }
                        else if (String.Compare(authType, "wdigest", StringComparison.OrdinalIgnoreCase) == 0) {
                            supportedAuth |= SupportedAuth.WDigest; 
                        }
#endif // FEATURE_PAL 
                    } 
                }
                else if (String.Compare(extension, 0, "dsn ", 0, 3, StringComparison.OrdinalIgnoreCase) == 0) { 
                    ((SmtpPooledStream)pooledStream).dsnEnabled = true;
                }
                else if (String.Compare(extension, 0, "STARTTLS", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) {
                    serverSupportsStartTls = true; 
                }
            } 
        } 

        internal bool AuthSupported(ISmtpAuthenticationModule module){ 
            if (module is SmtpLoginAuthenticationModule) {
                if ((supportedAuth & SupportedAuth.Login) > 0) {
                    return true;
                } 
            }
#if !FEATURE_PAL 
            else if (module is SmtpNegotiateAuthenticationModule) { 
                if ((supportedAuth & SupportedAuth.GGSAPI) > 0) {
                    sawNegotiate = true; 
                    return true;
                }
            }
            else if (module is SmtpNtlmAuthenticationModule) { 
                //don't try ntlm if negotiate has been tried
                if ((!sawNegotiate && (supportedAuth & SupportedAuth.NTLM) > 0)) { 
                    return true; 
                }
            } 
            else if (module is SmtpDigestAuthenticationModule) {
                if ((supportedAuth & SupportedAuth.WDigest) > 0) {
                    return true;
                } 
            }
#endif // FEATURE_PAL 
 
            return false;
        } 


        internal void GetConnection(string host, int port)
        { 
            if (isConnected)
            { 
                throw new InvalidOperationException(SR.GetString(SR.SmtpAlreadyConnected)); 
            }
 
            ServicePoint servicePoint = ServicePointManager.FindServicePoint(host, port);
            if (Logging.On) Logging.Associate(Logging.Web, this, servicePoint);
            connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, "", m_CreateConnectionCallback);
 
            PooledStream pooledStream = connectionPool.GetConnection((object)this, null, Timeout);
 
            while (((SmtpPooledStream)pooledStream).creds != null && ((SmtpPooledStream)pooledStream).creds != credentials) { 
                pooledStream.Close();
                connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout); 
                pooledStream = connectionPool.GetConnection((object)this, null, Timeout);
            }
            if (Logging.On) Logging.Associate(Logging.Web, this, pooledStream);
 
            lock (this) {
                this.pooledStream = pooledStream; 
            } 

            ((SmtpPooledStream)pooledStream).creds = credentials; 

            responseReader = new SmtpReplyReaderFactory(pooledStream.NetworkStream);

            //set connectionlease 
            pooledStream.UpdateLifetime();
 
            //if the stream was already used, then we've already done the handshake 
            if (((SmtpPooledStream)pooledStream).previouslyUsed == true) {
                isConnected = true; 
                return;
            }

            LineInfo info = responseReader.GetNextReplyReader().ReadLine(); 

            switch (info.StatusCode) 
            { 
                case SmtpStatusCode.ServiceReady:
                    { 
                        break;
                    }
                default:
                    { 
                        throw new SmtpException(info.StatusCode, info.Line, true);
                    } 
            } 

            try 
            {
                extensions = EHelloCommand.Send(this, client.clientDomain);
                ParseExtensions(extensions);
            } 
            catch (SmtpException e)
            { 
                if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized) 
                    && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)) {
                    throw e; 
                }

                HelloCommand.Send(this, client.clientDomain);
                //if ehello isn't supported, assume basic login 
                supportedAuth = SupportedAuth.Login;
            } 
 
#if !FEATURE_PAL
            // Establish TLS 
            if (enableSsl)
            {
                if (!serverSupportsStartTls)
                { 
                    // Either TLS is already established or server does not support TLS
                    if (!(pooledStream.NetworkStream is TlsStream)) 
                    { 
                        throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
                    } 
                }
                StartTlsCommand.Send(this);
                TlsStream TlsStream = new TlsStream(servicePoint.Host, pooledStream.NetworkStream, clientCertificates, servicePoint, client, null);
 
                pooledStream.NetworkStream = TlsStream;
 
                //for SMTP, the CBT should be unique 
                this.channelBindingToken = TlsStream.GetChannelBinding(ChannelBindingKind.Unique);
 
                responseReader = new SmtpReplyReaderFactory(pooledStream.NetworkStream);

                // According to RFC 3207: The client SHOULD send an EHLO command
                // as the first command after a successful TLS negotiation. 
                extensions = EHelloCommand.Send(this, client.clientDomain);
                ParseExtensions(extensions); 
            } 
#endif // !FEATURE_PAL
 
            //if no credentials were supplied, try anonymous
            //servers don't appear to anounce that they support anonymous login.
            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, null);
 
                    if (auth != null && auth.Message != null)
                    {
                        info = AuthCommand.Send(this, authenticationModules[i].AuthenticationType, auth.Message);
 
                        if (info.StatusCode == SmtpStatusCode.CommandParameterNotImplemented)
                        { 
                            continue; 
                        }
 
                        while ((int)info.StatusCode == 334)
                        {
                            auth = authenticationModules[i].Authenticate(info.Line, null, this, this.client.TargetName, this.channelBindingToken);
                            if (auth == null) 
                            {
                                throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed)); 
                            } 
                            info = AuthCommand.Send(this, auth.Message);
 
                            if ((int)info.StatusCode == 235)
                            {
                                authenticationModules[i].CloseContext(this);
                                isConnected = true; 
                                return;
                            } 
                        } 
                    }
                } 
            }
            isConnected = true;
        }
 
        //
        // We may need to impersonate in this method 
        // 
        [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.ControlPrincipal)]
        private Authorization SetContextAndTryAuthenticate(ISmtpAuthenticationModule module, NetworkCredential credential, ContextAwareResult context) 
        {
#if !FEATURE_PAL
            // We may need to restore user thread token here
            if (credential is SystemNetworkCredential && ComNetOS.IsWinNt) 
            {
                // 
#if DEBUG 
                GlobalLog.Assert(context == null || context.IdentityRequested, "SmtpConnection#{0}::SetContextAndTryAuthenticate|Authentication required when it wasn't expected.  (Maybe Credentials was changed on another thread?)", ValidationHelper.HashString(this));
#endif 

                WindowsIdentity w = context == null ? null : context.Identity;
                try
                { 
                    IDisposable ctx = w == null ? null : w.Impersonate();
                    if (ctx != null) 
                    { 
                        using (ctx)
                        { 
                            return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken);
                        }
                    }
                    else 
                    {
                        ExecutionContext x = context == null ? null : context.ContextCopy; 
                        if (x != null) 
                        {
                            ExecutionContext.Run(x, s_AuthenticateCallback, new AuthenticateCallbackContext(this, module, credential, this.client.TargetName, this.channelBindingToken)); 
                        }
                        else
                        {
                            return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken); 
                        }
                    } 
                } 
                catch
                { 
                    // Prevent the impersonation from leaking to upstack exception filters.
                    throw;
                }
            } 
#endif // !FEATURE_PAL
            return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken); 
        } 

        private static void AuthenticateCallback(object state) 
        {
            AuthenticateCallbackContext context = (AuthenticateCallbackContext)state;
            context.module.Authenticate(null, context.credential, context.thisPtr, context.spn, context.token);
 
        }
 
        private class AuthenticateCallbackContext 
        {
            internal AuthenticateCallbackContext(SmtpConnection thisPtr, ISmtpAuthenticationModule module, NetworkCredential credential, string spn, ChannelBinding Token) 
            {
                this.thisPtr = thisPtr;
                this.module = module;
                this.credential = credential; 
                this.spn = spn;
                this.token = Token; 
            } 

            internal readonly SmtpConnection thisPtr; 
            internal readonly ISmtpAuthenticationModule module;
            internal readonly NetworkCredential credential;
            internal readonly string spn;
            internal readonly ChannelBinding token; 
        }
 
        internal void EndGetConnection(IAsyncResult result) 
        {
            ConnectAndHandshakeAsyncResult.End(result); 
        }

        internal Stream GetClosableStream()
        { 
            ClosableStream cs = new ClosableStream(pooledStream.NetworkStream, onCloseHandler);
            isStreamOpen = true; 
            return cs; 
        }
 
        void OnClose(object sender, EventArgs args)
        {
            isStreamOpen = false;
 
            DataStopCommand.Send(this);
        } 
 
        class ConnectAndHandshakeAsyncResult : LazyAsyncResult
        { 

            private static readonly GeneralAsyncDelegate m_ConnectionCreatedCallback = new GeneralAsyncDelegate(ConnectionCreatedCallback);
            string authResponse;
            SmtpConnection connection; 
            int currentModule = -1;
            int port; 
            static AsyncCallback handshakeCallback = new AsyncCallback(HandshakeCallback); 
            static AsyncCallback sendEHelloCallback = new AsyncCallback(SendEHelloCallback);
            static AsyncCallback sendHelloCallback = new AsyncCallback(SendHelloCallback); 
            static AsyncCallback authenticateCallback = new AsyncCallback(AuthenticateCallback);
            static AsyncCallback authenticateContinueCallback = new AsyncCallback(AuthenticateContinueCallback);
            string host;
 
            private readonly ContextAwareResult m_OuterResult;
 
 
            internal ConnectAndHandshakeAsyncResult(SmtpConnection connection, string host, int port, ContextAwareResult outerResult, AsyncCallback callback, object state) :
                base(null, state, callback) 
            {
                this.connection = connection;
                this.host = host;
                this.port = port; 

                m_OuterResult = outerResult; 
            } 

 
            private static void ConnectionCreatedCallback(object request, object state) {
                GlobalLog.Enter("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback");
                ConnectAndHandshakeAsyncResult ConnectAndHandshakeAsyncResult = (ConnectAndHandshakeAsyncResult)request;
                if (state is Exception) { 
                    ConnectAndHandshakeAsyncResult.InvokeCallback((Exception)state);
                    return; 
                } 
                SmtpPooledStream pooledStream = (SmtpPooledStream)(PooledStream)state;
 

                try
                {
                    while (pooledStream.creds != null && pooledStream.creds != ConnectAndHandshakeAsyncResult.connection.credentials) { 
                        GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::Connect pooledStream has wrong creds " + ValidationHelper.HashString(pooledStream));
                        pooledStream.Close(); 
                        ConnectAndHandshakeAsyncResult.connection.connectionPool.PutConnection(pooledStream, pooledStream.Owner, ConnectAndHandshakeAsyncResult.connection.Timeout); 
                        pooledStream = (SmtpPooledStream)ConnectAndHandshakeAsyncResult.connection.connectionPool.GetConnection((object)ConnectAndHandshakeAsyncResult, ConnectAndHandshakeAsyncResult.m_ConnectionCreatedCallback, ConnectAndHandshakeAsyncResult.connection.Timeout);
                        if (pooledStream == null) { 
                            GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::Connect returning asynchronously");
                            return;
                        }
                    } 
                    if (Logging.On) Logging.Associate(Logging.Web, ConnectAndHandshakeAsyncResult.connection, pooledStream);
                    pooledStream.Owner = ConnectAndHandshakeAsyncResult.connection; //needs to be updated for gc reasons 
                    pooledStream.creds = ConnectAndHandshakeAsyncResult.connection.credentials; 

 
                    lock (ConnectAndHandshakeAsyncResult.connection) {

                        //if we were cancelled while getting the connection, we should close and return
                        if (ConnectAndHandshakeAsyncResult.connection.isClosed) { 
                            pooledStream.Close();
                            ConnectAndHandshakeAsyncResult.connection.connectionPool.PutConnection(pooledStream, pooledStream.Owner, ConnectAndHandshakeAsyncResult.connection.Timeout); 
                            GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback Connect was aborted " + ValidationHelper.HashString(pooledStream)); 
                            ConnectAndHandshakeAsyncResult.InvokeCallback(null);
                            return; 
                        }
                        ConnectAndHandshakeAsyncResult.connection.pooledStream = pooledStream;
                    }
 
                    ConnectAndHandshakeAsyncResult.Handshake();
                } 
                catch (Exception e) 
                {
                    ConnectAndHandshakeAsyncResult.InvokeCallback(e); 
                }
                GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback pooledStream#" + ValidationHelper.HashString(pooledStream));
            }
 

            internal static void End(IAsyncResult result) 
            { 
                ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result;
                object connectResult = thisPtr.InternalWaitForCompletion(); 
                if (connectResult is Exception){
                    throw (Exception)connectResult;
                }
            } 

            internal void GetConnection(bool synchronous) 
            { 

                GlobalLog.Enter("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect: [....]=" + (synchronous ? "true" : "false")); 
                if (connection.isConnected)
                {
                    throw new InvalidOperationException(SR.GetString(SR.SmtpAlreadyConnected));
                } 

 
                SmtpPooledStream pooledStream = (SmtpPooledStream)connection.connectionPool.GetConnection((object)this, (synchronous ? null : m_ConnectionCreatedCallback), connection.Timeout); 
                GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect returned" + ValidationHelper.HashString(this));
 
                if (pooledStream != null) {
                    try
                    {
                        while (pooledStream.creds != null && pooledStream.creds != connection.credentials) { 
                            GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect pooledStream has wrong creds " + ValidationHelper.HashString(pooledStream));
                            pooledStream.Close(); 
                            connection.connectionPool.PutConnection(pooledStream, pooledStream.Owner, connection.Timeout); 
                            pooledStream = (SmtpPooledStream)connection.connectionPool.GetConnection((object)this, (synchronous ? null : m_ConnectionCreatedCallback), connection.Timeout);
                            if (pooledStream == null) { 
                                GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect returning asynchronously");
                                return;
                            }
                        } 
                        pooledStream.creds = connection.credentials;
                        pooledStream.Owner = this.connection; //needs to be updated for gc reasons 
 
                        lock (connection) {
                            connection.pooledStream = pooledStream; 
                        }
                        Handshake();
                    }
                    catch (Exception e) 
                    {
                        InvokeCallback(e); 
                    } 
                }
                GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect pooledStream#" + ValidationHelper.HashString(pooledStream)); 
            }


            void Handshake() 
            {
                connection.responseReader = new SmtpReplyReaderFactory(connection.pooledStream.NetworkStream); 
 

                //if we've already used this stream, then we've already done the handshake 

                //set connectionlease
                connection.pooledStream.UpdateLifetime();
 
                if (((SmtpPooledStream)connection.pooledStream).previouslyUsed == true) {
                    connection.isConnected = true; 
                    InvokeCallback(); 
                    return;
                } 


                SmtpReplyReader reader = connection.Reader.GetNextReplyReader();
                IAsyncResult result = reader.BeginReadLine(handshakeCallback, this); 
                if (!result.CompletedSynchronously)
                { 
                    return; 
                }
 
                LineInfo info = reader.EndReadLine(result);

                if (info.StatusCode != SmtpStatusCode.ServiceReady)
                { 
                    throw new SmtpException(info.StatusCode, info.Line, true);
                } 
                try 
                {
                    if (!SendEHello()) 
                    {
                        return;
                    }
                } 
                catch
                { 
                    if (!SendHello()) 
                    {
                        return; 
                    }
                }
            }
 
            static void HandshakeCallback(IAsyncResult result)   //3
            { 
                if (!result.CompletedSynchronously) 
                {
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState; 
                    try
                    {
                        try
                        { 
                            LineInfo info = thisPtr.connection.Reader.CurrentReader.EndReadLine(result);
                            if (info.StatusCode != SmtpStatusCode.ServiceReady) 
                            { 
                                thisPtr.InvokeCallback(new SmtpException(info.StatusCode, info.Line, true));
                                return; 
                            }
                            if (!thisPtr.SendEHello())
                            {
                                return; 
                            }
                        } 
                        catch (SmtpException) 
                        {
                            if (!thisPtr.SendHello()) 
                            {
                                return;
                            }
                        } 
                    }
                    catch (Exception e) 
                    { 
                        thisPtr.InvokeCallback(e);
                    } 
                }
            }

            bool SendEHello()//4 
            {
                IAsyncResult result = EHelloCommand.BeginSend(connection, connection.client.clientDomain, sendEHelloCallback, this); 
                if (result.CompletedSynchronously) 
                {
                    connection.extensions = EHelloCommand.EndSend(result); 
                    connection.ParseExtensions(connection.extensions);
                    // If we already have a TlsStream, this is the second EHLO cmd
                    // that we sent after TLS handshake compelted. So skip TLS and
                    // continue with Authenticate. 
                    if (connection.pooledStream.NetworkStream is TlsStream)
                    { 
                        Authenticate(); 
                        return true;
                    } 

                    if (connection.EnableSsl) {
#if !FEATURE_PAL
                        if (!connection.serverSupportsStartTls) 
                        {
                            // Either TLS is already established or server does not support TLS 
                            if (!(connection.pooledStream.NetworkStream is TlsStream)) 
                            {
                                throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls)); 
                            }
                        }

                        SendStartTls(); 
#else // FEATURE_PAL
                        throw new NotSupportedException("ROTORTODO"); 
#endif // !FEATURE_PAL 
                    }
                    else { 
                        Authenticate();
                    }
                    return true;
                } 
                return false;
            } 
 
            static void SendEHelloCallback(IAsyncResult result)//5
            { 
                if (!result.CompletedSynchronously)
                {
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
                    try 
                    {
                        try 
                        { 
                            thisPtr.connection.extensions = EHelloCommand.EndSend(result);
                            thisPtr.connection.ParseExtensions(thisPtr.connection.extensions); 

                            // If we already have a TlsStream, this is the second EHLO cmd
                            // that we sent after TLS handshake compelted. So skip TLS and
                            // continue with Authenticate. 
                            if (thisPtr.connection.pooledStream.NetworkStream is TlsStream)
                            { 
                                thisPtr.Authenticate(); 
                                return;
                            } 
                        }

                        catch (SmtpException e)
                        { 
                            if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized)
                                && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)){ 
                                throw e; 
                            }
 
                            if (!thisPtr.SendHello()) {
                                return;
                            }
                        } 

 
                        if (thisPtr.connection.EnableSsl) { 
#if !FEATURE_PAL
                            if (!thisPtr.connection.serverSupportsStartTls) 
                            {
                                // Either TLS is already established or server does not support TLS
                                if (!(thisPtr.connection.pooledStream.NetworkStream is TlsStream))
                                { 
                                    throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
                                } 
                            } 

                            thisPtr.SendStartTls(); 
#else // FEATURE_PAL
                            throw new NotSupportedException("ROTORTODO");
#endif // !FEATURE_PAL
                        } 
                        else {
                            thisPtr.Authenticate(); 
                        } 
                    }
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e);
                    }
                } 
            }
 
            bool SendHello()//6 
            {
                IAsyncResult result = HelloCommand.BeginSend(connection, connection.client.clientDomain, sendHelloCallback, this); 
                //if ehello isn't supported, assume basic auth
                if (result.CompletedSynchronously)
                {
                    connection.supportedAuth = SupportedAuth.Login; 
                    HelloCommand.EndSend(result);
                    Authenticate(); 
                    return true; 
                }
                return false; 
            }

            static void SendHelloCallback(IAsyncResult result)     //7
            { 
                if (!result.CompletedSynchronously)
                { 
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState; 
                    try
                    { 
                        HelloCommand.EndSend(result);
                        thisPtr.Authenticate();
                    }
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e); 
                    } 
                }
            } 

#if !FEATURE_PAL
            bool SendStartTls()//6
            { 
                IAsyncResult result = StartTlsCommand.BeginSend(connection, SendStartTlsCallback, this);
                if (result.CompletedSynchronously) 
                { 
                    StartTlsCommand.EndSend(result);
                    TlsStream TlsStream = new TlsStream(connection.pooledStream.ServicePoint.Host, connection.pooledStream.NetworkStream, connection.ClientCertificates, connection.pooledStream.ServicePoint, connection.client, m_OuterResult.ContextCopy); 
                    connection.pooledStream.NetworkStream = TlsStream;
                    connection.responseReader = new SmtpReplyReaderFactory(connection.pooledStream.NetworkStream);
                    SendEHello();
                    return true; 
                }
                return false; 
            } 

            static void SendStartTlsCallback(IAsyncResult result)     //7 
            {
                if (!result.CompletedSynchronously)
                {
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState; 
                    try
                    { 
                        StartTlsCommand.EndSend(result); 
                        TlsStream TlsStream = new TlsStream(thisPtr.connection.pooledStream.ServicePoint.Host, thisPtr.connection.pooledStream.NetworkStream, thisPtr.connection.ClientCertificates, thisPtr.connection.pooledStream.ServicePoint, thisPtr.connection.client, thisPtr.m_OuterResult.ContextCopy);
                        thisPtr.connection.pooledStream.NetworkStream = TlsStream; 
                        thisPtr.connection.responseReader = new SmtpReplyReaderFactory(thisPtr.connection.pooledStream.NetworkStream);
                        thisPtr.SendEHello();
                    }
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e); 
                    } 
                }
            } 
#endif // !FEATURE_PAL

            void Authenticate() //8
            { 
                //if no credentials were supplied, try anonymous
                //servers don't appear to anounce that they support anonymous login. 
                if (connection.credentials != null) { 
                    while (++currentModule < connection.authenticationModules.Length)
                    { 
                        //only authenticate if the auth protocol is supported
                        ISmtpAuthenticationModule module = connection.authenticationModules[currentModule];
                        if (!connection.AuthSupported(module)) {
                            continue; 
                        }
 
                        NetworkCredential credential = connection.credentials.GetCredential(host, port, module.AuthenticationType); 
                        if (credential == null)
                            continue; 
                        Authorization auth = connection.SetContextAndTryAuthenticate(module, credential, m_OuterResult);

                        if (auth != null && auth.Message != null)
                        { 
                            IAsyncResult result = AuthCommand.BeginSend(connection, connection.authenticationModules[currentModule].AuthenticationType, auth.Message, authenticateCallback, this);
                            if (!result.CompletedSynchronously) 
                            { 
                                return;
                            } 

                            LineInfo info = AuthCommand.EndSend(result);

                            if ((int)info.StatusCode == 334) 
                            {
                                authResponse = info.Line; 
                                if (!AuthenticateContinue()) 
                                {
                                    return; 
                                }
                            }
                            else if ((int)info.StatusCode == 235)
                            { 
                                module.CloseContext(connection);
                                connection.isConnected = true; 
                                break; 
                            }
                        } 
                    }

                    //try anonymous if didn't authenticate
                    //if (!connection.isConnected) { 
                    //    throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
                    // } 
                } 

                connection.isConnected = true; 
                InvokeCallback();
            }

            static void AuthenticateCallback(IAsyncResult result) //9 
            {
                if (!result.CompletedSynchronously) 
                { 
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
                    try 
                    {
                        LineInfo info = AuthCommand.EndSend(result);

                        if ((int)info.StatusCode == 334) 
                        {
                            thisPtr.authResponse = info.Line; 
                            if (!thisPtr.AuthenticateContinue()) 
                            {
                                return; 
                            }
                        }
                        else if ((int)info.StatusCode == 235)
                        { 
                            thisPtr.connection.authenticationModules[thisPtr.currentModule].CloseContext(thisPtr.connection);
                            thisPtr.connection.isConnected = true; 
                            thisPtr.InvokeCallback(); 
                            return;
                        } 

                        thisPtr.Authenticate();
                    }
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e); 
                    } 
                }
            } 

            bool AuthenticateContinue()        //10
            {
                for (; ; ) 
                {
                    // We don't need credential on the continued auth assuming they were captured on the first call. 
                    // That should always work, otherwise what if a new credential has been returned? 
                    Authorization auth = connection.authenticationModules[currentModule].Authenticate(authResponse, null, connection, connection.client.TargetName, connection.channelBindingToken);
                    if (auth == null) 
                    {
                        throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
                    }
 
                    IAsyncResult result = AuthCommand.BeginSend(connection, auth.Message, authenticateContinueCallback, this);
                    if (!result.CompletedSynchronously) 
                    { 
                        return false;
                    } 

                    LineInfo info = AuthCommand.EndSend(result);
                    if ((int)info.StatusCode == 235)
                    { 
                        connection.authenticationModules[currentModule].CloseContext(connection);
                        connection.isConnected = true; 
                        InvokeCallback(); 
                        return false;
                    } 
                    else if ((int)info.StatusCode != 334)
                    {
                        return true;
                    } 
                    authResponse = info.Line;
                } 
            } 

            static void AuthenticateContinueCallback(IAsyncResult result)     //11 
            {
                if (!result.CompletedSynchronously)
                {
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState; 
                    try
                    { 
                        LineInfo info = AuthCommand.EndSend(result); 
                        if ((int)info.StatusCode == 235)
                        { 
                            thisPtr.connection.authenticationModules[thisPtr.currentModule].CloseContext(thisPtr.connection);
                            thisPtr.connection.isConnected = true;
                            thisPtr.InvokeCallback();
                            return; 
                        }
                        else if ((int)info.StatusCode == 334) 
                        { 
                            thisPtr.authResponse = info.Line;
                            if (!thisPtr.AuthenticateContinue()) 
                            {
                                return;
                            }
                        } 
                        thisPtr.Authenticate();
                    } 
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e); 
                    }
                }
            }
 
        }
    } 
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
namespace System.Net.Mail 
{
    using System;
    using System.Net;
    using System.Security.Cryptography.X509Certificates; 
    using System.IO;
    using System.Threading; 
    using System.Globalization; 
    using System.Security.Principal;
    using System.Security.Permissions; 
    using System.Security.Authentication.ExtendedProtection;


 
    class SmtpConnection
    { 
 
        private static PooledStream CreateSmtpPooledStream(ConnectionPool pool)         {
            return (PooledStream)new SmtpPooledStream(pool, TimeSpan.MaxValue, false); 
        }


        private static readonly CreateConnectionDelegate m_CreateConnectionCallback = new CreateConnectionDelegate(CreateSmtpPooledStream); 
        private static readonly ContextCallback s_AuthenticateCallback = new ContextCallback(AuthenticateCallback);
 
        BufferBuilder bufferBuilder = new BufferBuilder(); 
        bool isConnected;
        bool isClosed; 
        bool isStreamOpen;
        bool sawNegotiate;
        EventHandler onCloseHandler;
        internal SmtpTransport parent; 
        internal SmtpClient client;
        SmtpReplyReaderFactory responseReader; 
 
        PooledStream pooledStream;
        ConnectionPool connectionPool; 
        SupportedAuth supportedAuth = SupportedAuth.None;
        bool serverSupportsStartTls = false;
        ISmtpAuthenticationModule[] authenticationModules;
        ICredentialsByHost credentials; 
        int timeout = 100000;
        string[] extensions; 
        private ChannelBinding channelBindingToken = null; 

 
        bool enableSsl;
        X509CertificateCollection clientCertificates;

        internal SmtpConnection(SmtpTransport parent, SmtpClient client, ICredentialsByHost credentials, ISmtpAuthenticationModule[] authenticationModules) 
        {
            this.client = client; 
            this.credentials = credentials; 
            this.authenticationModules = authenticationModules;
            this.parent = parent; 
            onCloseHandler = new EventHandler(OnClose);
        }

        internal BufferBuilder BufferBuilder 
        {
            get 
            { 
                return bufferBuilder;
            } 
        }

        internal bool IsConnected
        { 
            get
            { 
                return isConnected; 
            }
        } 

        internal bool IsStreamOpen
        {
            get 
            {
                return isStreamOpen; 
            } 
        }
 
        internal bool DSNEnabled
        {
            get
            { 
                if (pooledStream != null)
                    return ((SmtpPooledStream)pooledStream).dsnEnabled; 
                else 
                    return false;
            } 
        }

        internal SmtpReplyReaderFactory Reader
        { 
            get
            { 
                return responseReader; 
            }
        } 

        internal bool EnableSsl
        {
            get 
            {
                return enableSsl; 
            } 
            set
            { 
#if !FEATURE_PAL
                enableSsl = value;
#else
                throw new NotImplementedException("ROTORTODO"); 
#endif
            } 
        } 

        internal int Timeout 
        {
            get
            {
                return timeout; 
            }
            set 
            { 
                timeout = value;
            } 
        }


        internal X509CertificateCollection ClientCertificates 
        {
            get 
            { 
                return clientCertificates;
            } 
            set
            {
                clientCertificates = value;
            } 
        }
 
 
        internal IAsyncResult BeginGetConnection(string host, int port, ContextAwareResult outerResult, AsyncCallback callback, object state)
        { 
            ServicePoint servicePoint = ServicePointManager.FindServicePoint(host, port);
            if (Logging.On) Logging.Associate(Logging.Web, this, servicePoint);

            if (EnableSsl && ClientCertificates != null && ClientCertificates.Count > 0) 
                connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, ClientCertificates.GetHashCode().ToString(NumberFormatInfo.InvariantInfo), m_CreateConnectionCallback);
            else 
                connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, "", m_CreateConnectionCallback); 

            ConnectAndHandshakeAsyncResult result = new ConnectAndHandshakeAsyncResult(this, host, port, outerResult, callback, state); 
            result.GetConnection(false);
            return result;
        }
 

        internal IAsyncResult BeginFlush(AsyncCallback callback, object state) 
        { 
            return pooledStream.UnsafeBeginWrite(bufferBuilder.GetBuffer(), 0, bufferBuilder.Length, callback, state);
        } 

        internal void EndFlush(IAsyncResult result)
        {
            pooledStream.EndWrite(result); 
            bufferBuilder.Reset();
        } 
 
        internal void Flush()
        { 
            pooledStream.Write(bufferBuilder.GetBuffer(), 0, bufferBuilder.Length);
            bufferBuilder.Reset();
        }
 
        internal void ReleaseConnection()
        { 
            if (!isClosed) { 
                lock (this) {
 
                    if (!isClosed && pooledStream != null) {

                        //free cbt buffer
                        if (channelBindingToken != null){ 
                            channelBindingToken.Close();
                        } 
 
                        GlobalLog.Print("SmtpConnectiont#" + ValidationHelper.HashString(this) + "::Close Transport#" + ValidationHelper.HashString(parent) + "putting back pooledStream#" + ValidationHelper.HashString(pooledStream));
 
                        ((SmtpPooledStream)pooledStream).previouslyUsed = true;
                        connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout);
                    }
                    isClosed = true; 
                }
            } 
            isConnected = false; 
        }
 
        internal void Abort()
        {
            if (!isClosed) {
                lock (this) { 
                    if (!isClosed && pooledStream != null){
 
                        GlobalLog.Print("SmtpConnectiont#" + ValidationHelper.HashString(this) + "::Close Transport#" + ValidationHelper.HashString(parent) + "closing and putting back pooledStream#" + ValidationHelper.HashString(pooledStream)); 

                        //free CBT buffer 
                        if (this.channelBindingToken != null){
                            channelBindingToken.Close();
                        }
 
                        pooledStream.Close(0);
                        connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout); 
                    } 
                    isClosed = true;
                } 
            }
            isConnected = false;
        }
 
        internal void ParseExtensions(string[] extensions) {
            supportedAuth = SupportedAuth.None; 
            foreach (string extension in extensions) { 
                if (String.Compare(extension, 0, "auth ", 0, 5, StringComparison.OrdinalIgnoreCase) == 0) {
                    string[] authTypes = extension.Split(' '); 
                    foreach (string authType in authTypes) {
                        if (String.Compare(authType, "login", StringComparison.OrdinalIgnoreCase) == 0) {
                            supportedAuth |= SupportedAuth.Login;
                        } 
#if !FEATURE_PAL
                        else if (String.Compare(authType, "ntlm", StringComparison.OrdinalIgnoreCase) == 0) { 
                            supportedAuth |= SupportedAuth.NTLM; 
                        }
                        else if (String.Compare(authType, "gssapi", StringComparison.OrdinalIgnoreCase) == 0) { 
                            supportedAuth |= SupportedAuth.GGSAPI;
                        }
                        else if (String.Compare(authType, "wdigest", StringComparison.OrdinalIgnoreCase) == 0) {
                            supportedAuth |= SupportedAuth.WDigest; 
                        }
#endif // FEATURE_PAL 
                    } 
                }
                else if (String.Compare(extension, 0, "dsn ", 0, 3, StringComparison.OrdinalIgnoreCase) == 0) { 
                    ((SmtpPooledStream)pooledStream).dsnEnabled = true;
                }
                else if (String.Compare(extension, 0, "STARTTLS", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) {
                    serverSupportsStartTls = true; 
                }
            } 
        } 

        internal bool AuthSupported(ISmtpAuthenticationModule module){ 
            if (module is SmtpLoginAuthenticationModule) {
                if ((supportedAuth & SupportedAuth.Login) > 0) {
                    return true;
                } 
            }
#if !FEATURE_PAL 
            else if (module is SmtpNegotiateAuthenticationModule) { 
                if ((supportedAuth & SupportedAuth.GGSAPI) > 0) {
                    sawNegotiate = true; 
                    return true;
                }
            }
            else if (module is SmtpNtlmAuthenticationModule) { 
                //don't try ntlm if negotiate has been tried
                if ((!sawNegotiate && (supportedAuth & SupportedAuth.NTLM) > 0)) { 
                    return true; 
                }
            } 
            else if (module is SmtpDigestAuthenticationModule) {
                if ((supportedAuth & SupportedAuth.WDigest) > 0) {
                    return true;
                } 
            }
#endif // FEATURE_PAL 
 
            return false;
        } 


        internal void GetConnection(string host, int port)
        { 
            if (isConnected)
            { 
                throw new InvalidOperationException(SR.GetString(SR.SmtpAlreadyConnected)); 
            }
 
            ServicePoint servicePoint = ServicePointManager.FindServicePoint(host, port);
            if (Logging.On) Logging.Associate(Logging.Web, this, servicePoint);
            connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, "", m_CreateConnectionCallback);
 
            PooledStream pooledStream = connectionPool.GetConnection((object)this, null, Timeout);
 
            while (((SmtpPooledStream)pooledStream).creds != null && ((SmtpPooledStream)pooledStream).creds != credentials) { 
                pooledStream.Close();
                connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout); 
                pooledStream = connectionPool.GetConnection((object)this, null, Timeout);
            }
            if (Logging.On) Logging.Associate(Logging.Web, this, pooledStream);
 
            lock (this) {
                this.pooledStream = pooledStream; 
            } 

            ((SmtpPooledStream)pooledStream).creds = credentials; 

            responseReader = new SmtpReplyReaderFactory(pooledStream.NetworkStream);

            //set connectionlease 
            pooledStream.UpdateLifetime();
 
            //if the stream was already used, then we've already done the handshake 
            if (((SmtpPooledStream)pooledStream).previouslyUsed == true) {
                isConnected = true; 
                return;
            }

            LineInfo info = responseReader.GetNextReplyReader().ReadLine(); 

            switch (info.StatusCode) 
            { 
                case SmtpStatusCode.ServiceReady:
                    { 
                        break;
                    }
                default:
                    { 
                        throw new SmtpException(info.StatusCode, info.Line, true);
                    } 
            } 

            try 
            {
                extensions = EHelloCommand.Send(this, client.clientDomain);
                ParseExtensions(extensions);
            } 
            catch (SmtpException e)
            { 
                if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized) 
                    && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)) {
                    throw e; 
                }

                HelloCommand.Send(this, client.clientDomain);
                //if ehello isn't supported, assume basic login 
                supportedAuth = SupportedAuth.Login;
            } 
 
#if !FEATURE_PAL
            // Establish TLS 
            if (enableSsl)
            {
                if (!serverSupportsStartTls)
                { 
                    // Either TLS is already established or server does not support TLS
                    if (!(pooledStream.NetworkStream is TlsStream)) 
                    { 
                        throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
                    } 
                }
                StartTlsCommand.Send(this);
                TlsStream TlsStream = new TlsStream(servicePoint.Host, pooledStream.NetworkStream, clientCertificates, servicePoint, client, null);
 
                pooledStream.NetworkStream = TlsStream;
 
                //for SMTP, the CBT should be unique 
                this.channelBindingToken = TlsStream.GetChannelBinding(ChannelBindingKind.Unique);
 
                responseReader = new SmtpReplyReaderFactory(pooledStream.NetworkStream);

                // According to RFC 3207: The client SHOULD send an EHLO command
                // as the first command after a successful TLS negotiation. 
                extensions = EHelloCommand.Send(this, client.clientDomain);
                ParseExtensions(extensions); 
            } 
#endif // !FEATURE_PAL
 
            //if no credentials were supplied, try anonymous
            //servers don't appear to anounce that they support anonymous login.
            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, null);
 
                    if (auth != null && auth.Message != null)
                    {
                        info = AuthCommand.Send(this, authenticationModules[i].AuthenticationType, auth.Message);
 
                        if (info.StatusCode == SmtpStatusCode.CommandParameterNotImplemented)
                        { 
                            continue; 
                        }
 
                        while ((int)info.StatusCode == 334)
                        {
                            auth = authenticationModules[i].Authenticate(info.Line, null, this, this.client.TargetName, this.channelBindingToken);
                            if (auth == null) 
                            {
                                throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed)); 
                            } 
                            info = AuthCommand.Send(this, auth.Message);
 
                            if ((int)info.StatusCode == 235)
                            {
                                authenticationModules[i].CloseContext(this);
                                isConnected = true; 
                                return;
                            } 
                        } 
                    }
                } 
            }
            isConnected = true;
        }
 
        //
        // We may need to impersonate in this method 
        // 
        [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.ControlPrincipal)]
        private Authorization SetContextAndTryAuthenticate(ISmtpAuthenticationModule module, NetworkCredential credential, ContextAwareResult context) 
        {
#if !FEATURE_PAL
            // We may need to restore user thread token here
            if (credential is SystemNetworkCredential && ComNetOS.IsWinNt) 
            {
                // 
#if DEBUG 
                GlobalLog.Assert(context == null || context.IdentityRequested, "SmtpConnection#{0}::SetContextAndTryAuthenticate|Authentication required when it wasn't expected.  (Maybe Credentials was changed on another thread?)", ValidationHelper.HashString(this));
#endif 

                WindowsIdentity w = context == null ? null : context.Identity;
                try
                { 
                    IDisposable ctx = w == null ? null : w.Impersonate();
                    if (ctx != null) 
                    { 
                        using (ctx)
                        { 
                            return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken);
                        }
                    }
                    else 
                    {
                        ExecutionContext x = context == null ? null : context.ContextCopy; 
                        if (x != null) 
                        {
                            ExecutionContext.Run(x, s_AuthenticateCallback, new AuthenticateCallbackContext(this, module, credential, this.client.TargetName, this.channelBindingToken)); 
                        }
                        else
                        {
                            return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken); 
                        }
                    } 
                } 
                catch
                { 
                    // Prevent the impersonation from leaking to upstack exception filters.
                    throw;
                }
            } 
#endif // !FEATURE_PAL
            return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken); 
        } 

        private static void AuthenticateCallback(object state) 
        {
            AuthenticateCallbackContext context = (AuthenticateCallbackContext)state;
            context.module.Authenticate(null, context.credential, context.thisPtr, context.spn, context.token);
 
        }
 
        private class AuthenticateCallbackContext 
        {
            internal AuthenticateCallbackContext(SmtpConnection thisPtr, ISmtpAuthenticationModule module, NetworkCredential credential, string spn, ChannelBinding Token) 
            {
                this.thisPtr = thisPtr;
                this.module = module;
                this.credential = credential; 
                this.spn = spn;
                this.token = Token; 
            } 

            internal readonly SmtpConnection thisPtr; 
            internal readonly ISmtpAuthenticationModule module;
            internal readonly NetworkCredential credential;
            internal readonly string spn;
            internal readonly ChannelBinding token; 
        }
 
        internal void EndGetConnection(IAsyncResult result) 
        {
            ConnectAndHandshakeAsyncResult.End(result); 
        }

        internal Stream GetClosableStream()
        { 
            ClosableStream cs = new ClosableStream(pooledStream.NetworkStream, onCloseHandler);
            isStreamOpen = true; 
            return cs; 
        }
 
        void OnClose(object sender, EventArgs args)
        {
            isStreamOpen = false;
 
            DataStopCommand.Send(this);
        } 
 
        class ConnectAndHandshakeAsyncResult : LazyAsyncResult
        { 

            private static readonly GeneralAsyncDelegate m_ConnectionCreatedCallback = new GeneralAsyncDelegate(ConnectionCreatedCallback);
            string authResponse;
            SmtpConnection connection; 
            int currentModule = -1;
            int port; 
            static AsyncCallback handshakeCallback = new AsyncCallback(HandshakeCallback); 
            static AsyncCallback sendEHelloCallback = new AsyncCallback(SendEHelloCallback);
            static AsyncCallback sendHelloCallback = new AsyncCallback(SendHelloCallback); 
            static AsyncCallback authenticateCallback = new AsyncCallback(AuthenticateCallback);
            static AsyncCallback authenticateContinueCallback = new AsyncCallback(AuthenticateContinueCallback);
            string host;
 
            private readonly ContextAwareResult m_OuterResult;
 
 
            internal ConnectAndHandshakeAsyncResult(SmtpConnection connection, string host, int port, ContextAwareResult outerResult, AsyncCallback callback, object state) :
                base(null, state, callback) 
            {
                this.connection = connection;
                this.host = host;
                this.port = port; 

                m_OuterResult = outerResult; 
            } 

 
            private static void ConnectionCreatedCallback(object request, object state) {
                GlobalLog.Enter("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback");
                ConnectAndHandshakeAsyncResult ConnectAndHandshakeAsyncResult = (ConnectAndHandshakeAsyncResult)request;
                if (state is Exception) { 
                    ConnectAndHandshakeAsyncResult.InvokeCallback((Exception)state);
                    return; 
                } 
                SmtpPooledStream pooledStream = (SmtpPooledStream)(PooledStream)state;
 

                try
                {
                    while (pooledStream.creds != null && pooledStream.creds != ConnectAndHandshakeAsyncResult.connection.credentials) { 
                        GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::Connect pooledStream has wrong creds " + ValidationHelper.HashString(pooledStream));
                        pooledStream.Close(); 
                        ConnectAndHandshakeAsyncResult.connection.connectionPool.PutConnection(pooledStream, pooledStream.Owner, ConnectAndHandshakeAsyncResult.connection.Timeout); 
                        pooledStream = (SmtpPooledStream)ConnectAndHandshakeAsyncResult.connection.connectionPool.GetConnection((object)ConnectAndHandshakeAsyncResult, ConnectAndHandshakeAsyncResult.m_ConnectionCreatedCallback, ConnectAndHandshakeAsyncResult.connection.Timeout);
                        if (pooledStream == null) { 
                            GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::Connect returning asynchronously");
                            return;
                        }
                    } 
                    if (Logging.On) Logging.Associate(Logging.Web, ConnectAndHandshakeAsyncResult.connection, pooledStream);
                    pooledStream.Owner = ConnectAndHandshakeAsyncResult.connection; //needs to be updated for gc reasons 
                    pooledStream.creds = ConnectAndHandshakeAsyncResult.connection.credentials; 

 
                    lock (ConnectAndHandshakeAsyncResult.connection) {

                        //if we were cancelled while getting the connection, we should close and return
                        if (ConnectAndHandshakeAsyncResult.connection.isClosed) { 
                            pooledStream.Close();
                            ConnectAndHandshakeAsyncResult.connection.connectionPool.PutConnection(pooledStream, pooledStream.Owner, ConnectAndHandshakeAsyncResult.connection.Timeout); 
                            GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback Connect was aborted " + ValidationHelper.HashString(pooledStream)); 
                            ConnectAndHandshakeAsyncResult.InvokeCallback(null);
                            return; 
                        }
                        ConnectAndHandshakeAsyncResult.connection.pooledStream = pooledStream;
                    }
 
                    ConnectAndHandshakeAsyncResult.Handshake();
                } 
                catch (Exception e) 
                {
                    ConnectAndHandshakeAsyncResult.InvokeCallback(e); 
                }
                GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback pooledStream#" + ValidationHelper.HashString(pooledStream));
            }
 

            internal static void End(IAsyncResult result) 
            { 
                ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result;
                object connectResult = thisPtr.InternalWaitForCompletion(); 
                if (connectResult is Exception){
                    throw (Exception)connectResult;
                }
            } 

            internal void GetConnection(bool synchronous) 
            { 

                GlobalLog.Enter("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect: [....]=" + (synchronous ? "true" : "false")); 
                if (connection.isConnected)
                {
                    throw new InvalidOperationException(SR.GetString(SR.SmtpAlreadyConnected));
                } 

 
                SmtpPooledStream pooledStream = (SmtpPooledStream)connection.connectionPool.GetConnection((object)this, (synchronous ? null : m_ConnectionCreatedCallback), connection.Timeout); 
                GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect returned" + ValidationHelper.HashString(this));
 
                if (pooledStream != null) {
                    try
                    {
                        while (pooledStream.creds != null && pooledStream.creds != connection.credentials) { 
                            GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect pooledStream has wrong creds " + ValidationHelper.HashString(pooledStream));
                            pooledStream.Close(); 
                            connection.connectionPool.PutConnection(pooledStream, pooledStream.Owner, connection.Timeout); 
                            pooledStream = (SmtpPooledStream)connection.connectionPool.GetConnection((object)this, (synchronous ? null : m_ConnectionCreatedCallback), connection.Timeout);
                            if (pooledStream == null) { 
                                GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect returning asynchronously");
                                return;
                            }
                        } 
                        pooledStream.creds = connection.credentials;
                        pooledStream.Owner = this.connection; //needs to be updated for gc reasons 
 
                        lock (connection) {
                            connection.pooledStream = pooledStream; 
                        }
                        Handshake();
                    }
                    catch (Exception e) 
                    {
                        InvokeCallback(e); 
                    } 
                }
                GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect pooledStream#" + ValidationHelper.HashString(pooledStream)); 
            }


            void Handshake() 
            {
                connection.responseReader = new SmtpReplyReaderFactory(connection.pooledStream.NetworkStream); 
 

                //if we've already used this stream, then we've already done the handshake 

                //set connectionlease
                connection.pooledStream.UpdateLifetime();
 
                if (((SmtpPooledStream)connection.pooledStream).previouslyUsed == true) {
                    connection.isConnected = true; 
                    InvokeCallback(); 
                    return;
                } 


                SmtpReplyReader reader = connection.Reader.GetNextReplyReader();
                IAsyncResult result = reader.BeginReadLine(handshakeCallback, this); 
                if (!result.CompletedSynchronously)
                { 
                    return; 
                }
 
                LineInfo info = reader.EndReadLine(result);

                if (info.StatusCode != SmtpStatusCode.ServiceReady)
                { 
                    throw new SmtpException(info.StatusCode, info.Line, true);
                } 
                try 
                {
                    if (!SendEHello()) 
                    {
                        return;
                    }
                } 
                catch
                { 
                    if (!SendHello()) 
                    {
                        return; 
                    }
                }
            }
 
            static void HandshakeCallback(IAsyncResult result)   //3
            { 
                if (!result.CompletedSynchronously) 
                {
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState; 
                    try
                    {
                        try
                        { 
                            LineInfo info = thisPtr.connection.Reader.CurrentReader.EndReadLine(result);
                            if (info.StatusCode != SmtpStatusCode.ServiceReady) 
                            { 
                                thisPtr.InvokeCallback(new SmtpException(info.StatusCode, info.Line, true));
                                return; 
                            }
                            if (!thisPtr.SendEHello())
                            {
                                return; 
                            }
                        } 
                        catch (SmtpException) 
                        {
                            if (!thisPtr.SendHello()) 
                            {
                                return;
                            }
                        } 
                    }
                    catch (Exception e) 
                    { 
                        thisPtr.InvokeCallback(e);
                    } 
                }
            }

            bool SendEHello()//4 
            {
                IAsyncResult result = EHelloCommand.BeginSend(connection, connection.client.clientDomain, sendEHelloCallback, this); 
                if (result.CompletedSynchronously) 
                {
                    connection.extensions = EHelloCommand.EndSend(result); 
                    connection.ParseExtensions(connection.extensions);
                    // If we already have a TlsStream, this is the second EHLO cmd
                    // that we sent after TLS handshake compelted. So skip TLS and
                    // continue with Authenticate. 
                    if (connection.pooledStream.NetworkStream is TlsStream)
                    { 
                        Authenticate(); 
                        return true;
                    } 

                    if (connection.EnableSsl) {
#if !FEATURE_PAL
                        if (!connection.serverSupportsStartTls) 
                        {
                            // Either TLS is already established or server does not support TLS 
                            if (!(connection.pooledStream.NetworkStream is TlsStream)) 
                            {
                                throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls)); 
                            }
                        }

                        SendStartTls(); 
#else // FEATURE_PAL
                        throw new NotSupportedException("ROTORTODO"); 
#endif // !FEATURE_PAL 
                    }
                    else { 
                        Authenticate();
                    }
                    return true;
                } 
                return false;
            } 
 
            static void SendEHelloCallback(IAsyncResult result)//5
            { 
                if (!result.CompletedSynchronously)
                {
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
                    try 
                    {
                        try 
                        { 
                            thisPtr.connection.extensions = EHelloCommand.EndSend(result);
                            thisPtr.connection.ParseExtensions(thisPtr.connection.extensions); 

                            // If we already have a TlsStream, this is the second EHLO cmd
                            // that we sent after TLS handshake compelted. So skip TLS and
                            // continue with Authenticate. 
                            if (thisPtr.connection.pooledStream.NetworkStream is TlsStream)
                            { 
                                thisPtr.Authenticate(); 
                                return;
                            } 
                        }

                        catch (SmtpException e)
                        { 
                            if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized)
                                && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)){ 
                                throw e; 
                            }
 
                            if (!thisPtr.SendHello()) {
                                return;
                            }
                        } 

 
                        if (thisPtr.connection.EnableSsl) { 
#if !FEATURE_PAL
                            if (!thisPtr.connection.serverSupportsStartTls) 
                            {
                                // Either TLS is already established or server does not support TLS
                                if (!(thisPtr.connection.pooledStream.NetworkStream is TlsStream))
                                { 
                                    throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
                                } 
                            } 

                            thisPtr.SendStartTls(); 
#else // FEATURE_PAL
                            throw new NotSupportedException("ROTORTODO");
#endif // !FEATURE_PAL
                        } 
                        else {
                            thisPtr.Authenticate(); 
                        } 
                    }
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e);
                    }
                } 
            }
 
            bool SendHello()//6 
            {
                IAsyncResult result = HelloCommand.BeginSend(connection, connection.client.clientDomain, sendHelloCallback, this); 
                //if ehello isn't supported, assume basic auth
                if (result.CompletedSynchronously)
                {
                    connection.supportedAuth = SupportedAuth.Login; 
                    HelloCommand.EndSend(result);
                    Authenticate(); 
                    return true; 
                }
                return false; 
            }

            static void SendHelloCallback(IAsyncResult result)     //7
            { 
                if (!result.CompletedSynchronously)
                { 
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState; 
                    try
                    { 
                        HelloCommand.EndSend(result);
                        thisPtr.Authenticate();
                    }
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e); 
                    } 
                }
            } 

#if !FEATURE_PAL
            bool SendStartTls()//6
            { 
                IAsyncResult result = StartTlsCommand.BeginSend(connection, SendStartTlsCallback, this);
                if (result.CompletedSynchronously) 
                { 
                    StartTlsCommand.EndSend(result);
                    TlsStream TlsStream = new TlsStream(connection.pooledStream.ServicePoint.Host, connection.pooledStream.NetworkStream, connection.ClientCertificates, connection.pooledStream.ServicePoint, connection.client, m_OuterResult.ContextCopy); 
                    connection.pooledStream.NetworkStream = TlsStream;
                    connection.responseReader = new SmtpReplyReaderFactory(connection.pooledStream.NetworkStream);
                    SendEHello();
                    return true; 
                }
                return false; 
            } 

            static void SendStartTlsCallback(IAsyncResult result)     //7 
            {
                if (!result.CompletedSynchronously)
                {
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState; 
                    try
                    { 
                        StartTlsCommand.EndSend(result); 
                        TlsStream TlsStream = new TlsStream(thisPtr.connection.pooledStream.ServicePoint.Host, thisPtr.connection.pooledStream.NetworkStream, thisPtr.connection.ClientCertificates, thisPtr.connection.pooledStream.ServicePoint, thisPtr.connection.client, thisPtr.m_OuterResult.ContextCopy);
                        thisPtr.connection.pooledStream.NetworkStream = TlsStream; 
                        thisPtr.connection.responseReader = new SmtpReplyReaderFactory(thisPtr.connection.pooledStream.NetworkStream);
                        thisPtr.SendEHello();
                    }
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e); 
                    } 
                }
            } 
#endif // !FEATURE_PAL

            void Authenticate() //8
            { 
                //if no credentials were supplied, try anonymous
                //servers don't appear to anounce that they support anonymous login. 
                if (connection.credentials != null) { 
                    while (++currentModule < connection.authenticationModules.Length)
                    { 
                        //only authenticate if the auth protocol is supported
                        ISmtpAuthenticationModule module = connection.authenticationModules[currentModule];
                        if (!connection.AuthSupported(module)) {
                            continue; 
                        }
 
                        NetworkCredential credential = connection.credentials.GetCredential(host, port, module.AuthenticationType); 
                        if (credential == null)
                            continue; 
                        Authorization auth = connection.SetContextAndTryAuthenticate(module, credential, m_OuterResult);

                        if (auth != null && auth.Message != null)
                        { 
                            IAsyncResult result = AuthCommand.BeginSend(connection, connection.authenticationModules[currentModule].AuthenticationType, auth.Message, authenticateCallback, this);
                            if (!result.CompletedSynchronously) 
                            { 
                                return;
                            } 

                            LineInfo info = AuthCommand.EndSend(result);

                            if ((int)info.StatusCode == 334) 
                            {
                                authResponse = info.Line; 
                                if (!AuthenticateContinue()) 
                                {
                                    return; 
                                }
                            }
                            else if ((int)info.StatusCode == 235)
                            { 
                                module.CloseContext(connection);
                                connection.isConnected = true; 
                                break; 
                            }
                        } 
                    }

                    //try anonymous if didn't authenticate
                    //if (!connection.isConnected) { 
                    //    throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
                    // } 
                } 

                connection.isConnected = true; 
                InvokeCallback();
            }

            static void AuthenticateCallback(IAsyncResult result) //9 
            {
                if (!result.CompletedSynchronously) 
                { 
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
                    try 
                    {
                        LineInfo info = AuthCommand.EndSend(result);

                        if ((int)info.StatusCode == 334) 
                        {
                            thisPtr.authResponse = info.Line; 
                            if (!thisPtr.AuthenticateContinue()) 
                            {
                                return; 
                            }
                        }
                        else if ((int)info.StatusCode == 235)
                        { 
                            thisPtr.connection.authenticationModules[thisPtr.currentModule].CloseContext(thisPtr.connection);
                            thisPtr.connection.isConnected = true; 
                            thisPtr.InvokeCallback(); 
                            return;
                        } 

                        thisPtr.Authenticate();
                    }
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e); 
                    } 
                }
            } 

            bool AuthenticateContinue()        //10
            {
                for (; ; ) 
                {
                    // We don't need credential on the continued auth assuming they were captured on the first call. 
                    // That should always work, otherwise what if a new credential has been returned? 
                    Authorization auth = connection.authenticationModules[currentModule].Authenticate(authResponse, null, connection, connection.client.TargetName, connection.channelBindingToken);
                    if (auth == null) 
                    {
                        throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
                    }
 
                    IAsyncResult result = AuthCommand.BeginSend(connection, auth.Message, authenticateContinueCallback, this);
                    if (!result.CompletedSynchronously) 
                    { 
                        return false;
                    } 

                    LineInfo info = AuthCommand.EndSend(result);
                    if ((int)info.StatusCode == 235)
                    { 
                        connection.authenticationModules[currentModule].CloseContext(connection);
                        connection.isConnected = true; 
                        InvokeCallback(); 
                        return false;
                    } 
                    else if ((int)info.StatusCode != 334)
                    {
                        return true;
                    } 
                    authResponse = info.Line;
                } 
            } 

            static void AuthenticateContinueCallback(IAsyncResult result)     //11 
            {
                if (!result.CompletedSynchronously)
                {
                    ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState; 
                    try
                    { 
                        LineInfo info = AuthCommand.EndSend(result); 
                        if ((int)info.StatusCode == 235)
                        { 
                            thisPtr.connection.authenticationModules[thisPtr.currentModule].CloseContext(thisPtr.connection);
                            thisPtr.connection.isConnected = true;
                            thisPtr.InvokeCallback();
                            return; 
                        }
                        else if ((int)info.StatusCode == 334) 
                        { 
                            thisPtr.authResponse = info.Line;
                            if (!thisPtr.AuthenticateContinue()) 
                            {
                                return;
                            }
                        } 
                        thisPtr.Authenticate();
                    } 
                    catch (Exception e) 
                    {
                        thisPtr.InvokeCallback(e); 
                    }
                }
            }
 
        }
    } 
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK