1 namespace System.Net.Mail
5 using System.Security.Cryptography.X509Certificates;
7 using System.Threading;
8 using System.Globalization;
9 using System.Security.Principal;
10 using System.Security.Permissions;
11 using System.Security.Authentication.ExtendedProtection;
12 using System.Diagnostics;
19 private static PooledStream CreateSmtpPooledStream(ConnectionPool pool) {
20 return (PooledStream)new SmtpPooledStream(pool, TimeSpan.MaxValue, false);
24 private static readonly CreateConnectionDelegate m_CreateConnectionCallback = new CreateConnectionDelegate(CreateSmtpPooledStream);
25 private static readonly ContextCallback s_AuthenticateCallback = new ContextCallback(AuthenticateCallback);
27 BufferBuilder bufferBuilder = new BufferBuilder();
32 EventHandler onCloseHandler;
33 internal SmtpTransport parent;
34 internal SmtpClient client;
35 SmtpReplyReaderFactory responseReader;
37 // accounts for the '=' or ' ' character after AUTH
38 const int sizeOfAuthString = 5;
39 const int sizeOfAuthExtension = 4;
40 // string comparisons for these MUST be case-insensitive
41 const string authExtension = "auth";
42 const string authLogin = "login";
43 const string authNtlm = "ntlm";
44 const string authGssapi = "gssapi";
45 const string authWDigest = "wdigest";
47 PooledStream pooledStream;
48 ConnectionPool connectionPool;
49 SupportedAuth supportedAuth = SupportedAuth.None;
50 bool serverSupportsStartTls = false;
51 ISmtpAuthenticationModule[] authenticationModules;
52 ICredentialsByHost credentials;
55 private ChannelBinding channelBindingToken = null;
58 X509CertificateCollection clientCertificates;
60 internal SmtpConnection(SmtpTransport parent, SmtpClient client, ICredentialsByHost credentials, ISmtpAuthenticationModule[] authenticationModules)
63 this.credentials = credentials;
64 this.authenticationModules = authenticationModules;
66 onCloseHandler = new EventHandler(OnClose);
69 internal BufferBuilder BufferBuilder
77 internal bool IsConnected
85 internal bool IsStreamOpen
93 internal bool DSNEnabled
97 if (pooledStream != null)
98 return ((SmtpPooledStream)pooledStream).dsnEnabled;
104 internal SmtpReplyReaderFactory Reader
108 return responseReader;
112 internal bool EnableSsl
123 throw new NotImplementedException("ROTORTODO");
141 internal X509CertificateCollection ClientCertificates
145 return clientCertificates;
149 clientCertificates = value;
153 internal bool ServerSupportsEai
157 SmtpPooledStream smtpPooledStream = (SmtpPooledStream)pooledStream;
158 Debug.Assert(smtpPooledStream != null, "PooledStream not yet set");
159 return smtpPooledStream.serverSupportsEai;
163 internal IAsyncResult BeginGetConnection(ServicePoint servicePoint, ContextAwareResult outerResult, AsyncCallback callback, object state)
165 if (Logging.On) Logging.Associate(Logging.Web, this, servicePoint);
166 Debug.Assert(servicePoint != null, "servicePoint was null from SmtpTransport");
168 if (EnableSsl && ClientCertificates != null && ClientCertificates.Count > 0)
169 connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, ClientCertificates.GetHashCode().ToString(NumberFormatInfo.InvariantInfo), m_CreateConnectionCallback);
171 connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, "", m_CreateConnectionCallback);
173 ConnectAndHandshakeAsyncResult result = new ConnectAndHandshakeAsyncResult(this, servicePoint.Host, servicePoint.Port, outerResult, callback, state);
174 result.GetConnection(false);
179 internal IAsyncResult BeginFlush(AsyncCallback callback, object state)
181 return pooledStream.UnsafeBeginWrite(bufferBuilder.GetBuffer(), 0, bufferBuilder.Length, callback, state);
184 internal void EndFlush(IAsyncResult result)
186 pooledStream.EndWrite(result);
187 bufferBuilder.Reset();
190 internal void Flush()
192 pooledStream.Write(bufferBuilder.GetBuffer(), 0, bufferBuilder.Length);
193 bufferBuilder.Reset();
196 internal void ReleaseConnection()
201 if (!isClosed && pooledStream != null) {
204 if (channelBindingToken != null){
205 channelBindingToken.Close();
208 GlobalLog.Print("SmtpConnectiont#" + ValidationHelper.HashString(this) + "::Close Transport#" + ValidationHelper.HashString(parent) + "putting back pooledStream#" + ValidationHelper.HashString(pooledStream));
210 ((SmtpPooledStream)pooledStream).previouslyUsed = true;
211 connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout);
219 internal void Abort()
223 if (!isClosed && pooledStream != null){
225 GlobalLog.Print("SmtpConnectiont#" + ValidationHelper.HashString(this) + "::Close Transport#" + ValidationHelper.HashString(parent) + "closing and putting back pooledStream#" + ValidationHelper.HashString(pooledStream));
228 if (this.channelBindingToken != null){
229 channelBindingToken.Close();
232 // must destroy manually since sending a QUIT here might not be
233 // interpreted correctly by the server if it's in the middle of a
234 // DATA command or some similar situation. This may send a RST
235 // but this is ok in this situation. Do not reuse this connection
236 pooledStream.Close(0);
237 connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout, false);
245 internal void ParseExtensions(string[] extensions) {
246 supportedAuth = SupportedAuth.None;
247 foreach (string extension in extensions) {
248 if (String.Compare(extension, 0, authExtension, 0,
249 sizeOfAuthExtension, StringComparison.OrdinalIgnoreCase) == 0) {
250 // remove the AUTH text including the following character
251 // to ensure that split only gets the modules supported
253 extension.Remove(0, sizeOfAuthExtension).Split(new char[] { ' ', '=' },
254 StringSplitOptions.RemoveEmptyEntries);
255 foreach (string authType in authTypes) {
256 if (String.Compare(authType, authLogin, StringComparison.OrdinalIgnoreCase) == 0) {
257 supportedAuth |= SupportedAuth.Login;
260 else if (String.Compare(authType, authNtlm, StringComparison.OrdinalIgnoreCase) == 0) {
261 supportedAuth |= SupportedAuth.NTLM;
263 else if (String.Compare(authType, authGssapi, StringComparison.OrdinalIgnoreCase) == 0) {
264 supportedAuth |= SupportedAuth.GSSAPI;
266 else if (String.Compare(authType, authWDigest, StringComparison.OrdinalIgnoreCase) == 0) {
267 supportedAuth |= SupportedAuth.WDigest;
269 #endif // FEATURE_PAL
272 else if (String.Compare(extension, 0, "dsn ", 0, 3, StringComparison.OrdinalIgnoreCase) == 0) {
273 ((SmtpPooledStream)pooledStream).dsnEnabled = true;
275 else if (String.Compare(extension, 0, "STARTTLS", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) {
276 serverSupportsStartTls = true;
278 else if (String.Compare(extension, 0, "SMTPUTF8", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) {
279 ((SmtpPooledStream)pooledStream).serverSupportsEai = true;
284 internal bool AuthSupported(ISmtpAuthenticationModule module){
285 if (module is SmtpLoginAuthenticationModule) {
286 if ((supportedAuth & SupportedAuth.Login) > 0) {
291 else if (module is SmtpNegotiateAuthenticationModule) {
292 if ((supportedAuth & SupportedAuth.GSSAPI) > 0) {
297 else if (module is SmtpNtlmAuthenticationModule) {
298 //don't try ntlm if negotiate has been tried
299 if ((!sawNegotiate && (supportedAuth & SupportedAuth.NTLM) > 0)) {
303 else if (module is SmtpDigestAuthenticationModule) {
304 if ((supportedAuth & SupportedAuth.WDigest) > 0) {
308 #endif // FEATURE_PAL
314 internal void GetConnection(ServicePoint servicePoint)
318 throw new InvalidOperationException(SR.GetString(SR.SmtpAlreadyConnected));
321 if (Logging.On) Logging.Associate(Logging.Web, this, servicePoint);
322 Debug.Assert(servicePoint != null, "servicePoint was null from SmtpTransport");
323 connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, "", m_CreateConnectionCallback);
325 PooledStream pooledStream = connectionPool.GetConnection((object)this, null, Timeout);
327 while (((SmtpPooledStream)pooledStream).creds != null && ((SmtpPooledStream)pooledStream).creds != credentials) {
328 // destroy this connection so that a new connection can be created
329 // in order to use the proper credentials. Do not just close the
330 // connection since it's in a state where a QUIT could be sent
331 connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout, false);
332 pooledStream = connectionPool.GetConnection((object)this, null, Timeout);
334 if (Logging.On) Logging.Associate(Logging.Web, this, pooledStream);
337 this.pooledStream = pooledStream;
340 ((SmtpPooledStream)pooledStream).creds = credentials;
342 responseReader = new SmtpReplyReaderFactory(pooledStream.NetworkStream);
344 //set connectionlease
345 pooledStream.UpdateLifetime();
347 //if the stream was already used, then we've already done the handshake
348 if (((SmtpPooledStream)pooledStream).previouslyUsed == true) {
353 LineInfo info = responseReader.GetNextReplyReader().ReadLine();
355 switch (info.StatusCode)
357 case SmtpStatusCode.ServiceReady:
363 throw new SmtpException(info.StatusCode, info.Line, true);
369 extensions = EHelloCommand.Send(this, client.clientDomain);
370 ParseExtensions(extensions);
372 catch (SmtpException e)
374 if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized)
375 && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)) {
379 HelloCommand.Send(this, client.clientDomain);
380 //if ehello isn't supported, assume basic login
381 supportedAuth = SupportedAuth.Login;
388 if (!serverSupportsStartTls)
390 // Either TLS is already established or server does not support TLS
391 if (!(pooledStream.NetworkStream is TlsStream))
393 throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
396 StartTlsCommand.Send(this);
397 TlsStream TlsStream = new TlsStream(servicePoint.Host, pooledStream.NetworkStream, clientCertificates, servicePoint, client, null);
399 pooledStream.NetworkStream = TlsStream;
401 //for SMTP, the CBT should be unique
402 this.channelBindingToken = TlsStream.GetChannelBinding(ChannelBindingKind.Unique);
404 responseReader = new SmtpReplyReaderFactory(pooledStream.NetworkStream);
406 // According to RFC 3207: The client SHOULD send an EHLO command
407 // as the first command after a successful TLS negotiation.
408 extensions = EHelloCommand.Send(this, client.clientDomain);
409 ParseExtensions(extensions);
411 #endif // !FEATURE_PAL
413 //if no credentials were supplied, try anonymous
414 //servers don't appear to anounce that they support anonymous login.
415 if (credentials != null) {
417 for (int i = 0; i < authenticationModules.Length; i++)
420 //only authenticate if the auth protocol is supported - Microsoft
421 if (!AuthSupported(authenticationModules[i])) {
425 NetworkCredential credential = credentials.GetCredential(servicePoint.Host,
426 servicePoint.Port, authenticationModules[i].AuthenticationType);
427 if (credential == null)
430 Authorization auth = SetContextAndTryAuthenticate(authenticationModules[i], credential, null);
432 if (auth != null && auth.Message != null)
434 info = AuthCommand.Send(this, authenticationModules[i].AuthenticationType, auth.Message);
436 if (info.StatusCode == SmtpStatusCode.CommandParameterNotImplemented)
441 while ((int)info.StatusCode == 334)
443 auth = authenticationModules[i].Authenticate(info.Line, null, this, this.client.TargetName, this.channelBindingToken);
446 throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
448 info = AuthCommand.Send(this, auth.Message);
450 if ((int)info.StatusCode == 235)
452 authenticationModules[i].CloseContext(this);
464 // We may need to impersonate in this method
466 [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.ControlPrincipal)]
467 private Authorization SetContextAndTryAuthenticate(ISmtpAuthenticationModule module, NetworkCredential credential, ContextAwareResult context)
470 // We may need to restore user thread token here
471 if (credential is SystemNetworkCredential)
475 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));
478 WindowsIdentity w = context == null ? null : context.Identity;
481 IDisposable ctx = w == null ? null : w.Impersonate();
486 return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken);
491 ExecutionContext x = context == null ? null : context.ContextCopy;
494 AuthenticateCallbackContext authenticationContext =
495 new AuthenticateCallbackContext(this, module, credential, this.client.TargetName, this.channelBindingToken);
497 ExecutionContext.Run(x, s_AuthenticateCallback, authenticationContext);
498 return authenticationContext.result;
502 return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken);
508 // Prevent the impersonation from leaking to upstack exception filters.
512 #endif // !FEATURE_PAL
513 return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken);
516 private static void AuthenticateCallback(object state)
518 AuthenticateCallbackContext context = (AuthenticateCallbackContext)state;
519 context.result = context.module.Authenticate(null, context.credential, context.thisPtr, context.spn, context.token);
522 private class AuthenticateCallbackContext
524 internal AuthenticateCallbackContext(SmtpConnection thisPtr, ISmtpAuthenticationModule module, NetworkCredential credential, string spn, ChannelBinding Token)
526 this.thisPtr = thisPtr;
527 this.module = module;
528 this.credential = credential;
535 internal readonly SmtpConnection thisPtr;
536 internal readonly ISmtpAuthenticationModule module;
537 internal readonly NetworkCredential credential;
538 internal readonly string spn;
539 internal readonly ChannelBinding token;
541 internal Authorization result;
544 internal void EndGetConnection(IAsyncResult result)
546 ConnectAndHandshakeAsyncResult.End(result);
549 internal Stream GetClosableStream()
551 ClosableStream cs = new ClosableStream(pooledStream.NetworkStream, onCloseHandler);
556 void OnClose(object sender, EventArgs args)
558 isStreamOpen = false;
560 DataStopCommand.Send(this);
563 class ConnectAndHandshakeAsyncResult : LazyAsyncResult
566 private static readonly GeneralAsyncDelegate m_ConnectionCreatedCallback = new GeneralAsyncDelegate(ConnectionCreatedCallback);
568 SmtpConnection connection;
569 int currentModule = -1;
571 static AsyncCallback handshakeCallback = new AsyncCallback(HandshakeCallback);
572 static AsyncCallback sendEHelloCallback = new AsyncCallback(SendEHelloCallback);
573 static AsyncCallback sendHelloCallback = new AsyncCallback(SendHelloCallback);
574 static AsyncCallback authenticateCallback = new AsyncCallback(AuthenticateCallback);
575 static AsyncCallback authenticateContinueCallback = new AsyncCallback(AuthenticateContinueCallback);
578 private readonly ContextAwareResult m_OuterResult;
581 internal ConnectAndHandshakeAsyncResult(SmtpConnection connection, string host, int port, ContextAwareResult outerResult, AsyncCallback callback, object state) :
582 base(null, state, callback)
584 this.connection = connection;
588 m_OuterResult = outerResult;
592 private static void ConnectionCreatedCallback(object request, object state) {
593 GlobalLog.Enter("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback");
594 ConnectAndHandshakeAsyncResult ConnectAndHandshakeAsyncResult = (ConnectAndHandshakeAsyncResult)request;
595 if (state is Exception) {
596 ConnectAndHandshakeAsyncResult.InvokeCallback((Exception)state);
599 SmtpPooledStream pooledStream = (SmtpPooledStream)(PooledStream)state;
604 while (pooledStream.creds != null && pooledStream.creds != ConnectAndHandshakeAsyncResult.connection.credentials) {
605 GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::Connect pooledStream has wrong creds " + ValidationHelper.HashString(pooledStream));
606 ConnectAndHandshakeAsyncResult.connection.connectionPool.PutConnection(pooledStream,
607 pooledStream.Owner, ConnectAndHandshakeAsyncResult.connection.Timeout, false);
608 pooledStream = (SmtpPooledStream)ConnectAndHandshakeAsyncResult.connection.connectionPool.GetConnection((object)ConnectAndHandshakeAsyncResult, ConnectAndHandshakeAsyncResult.m_ConnectionCreatedCallback, ConnectAndHandshakeAsyncResult.connection.Timeout);
609 if (pooledStream == null) {
610 GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::Connect returning asynchronously");
614 if (Logging.On) Logging.Associate(Logging.Web, ConnectAndHandshakeAsyncResult.connection, pooledStream);
615 pooledStream.Owner = ConnectAndHandshakeAsyncResult.connection; //needs to be updated for gc reasons
616 pooledStream.creds = ConnectAndHandshakeAsyncResult.connection.credentials;
619 lock (ConnectAndHandshakeAsyncResult.connection) {
621 //if we were cancelled while getting the connection, we should close and return
622 if (ConnectAndHandshakeAsyncResult.connection.isClosed) {
623 ConnectAndHandshakeAsyncResult.connection.connectionPool.PutConnection(pooledStream, pooledStream.Owner, ConnectAndHandshakeAsyncResult.connection.Timeout, false);
624 GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback Connect was aborted " + ValidationHelper.HashString(pooledStream));
625 ConnectAndHandshakeAsyncResult.InvokeCallback(null);
628 ConnectAndHandshakeAsyncResult.connection.pooledStream = pooledStream;
631 ConnectAndHandshakeAsyncResult.Handshake();
635 ConnectAndHandshakeAsyncResult.InvokeCallback(e);
637 GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback pooledStream#" + ValidationHelper.HashString(pooledStream));
641 internal static void End(IAsyncResult result)
643 ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result;
644 object connectResult = thisPtr.InternalWaitForCompletion();
645 if (connectResult is Exception){
646 throw (Exception)connectResult;
650 internal void GetConnection(bool synchronous)
653 GlobalLog.Enter("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect: sync=" + (synchronous ? "true" : "false"));
654 if (connection.isConnected)
656 throw new InvalidOperationException(SR.GetString(SR.SmtpAlreadyConnected));
660 SmtpPooledStream pooledStream = (SmtpPooledStream)connection.connectionPool.GetConnection((object)this, (synchronous ? null : m_ConnectionCreatedCallback), connection.Timeout);
661 GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect returned" + ValidationHelper.HashString(this));
663 if (pooledStream != null) {
666 while (pooledStream.creds != null && pooledStream.creds != connection.credentials) {
667 GlobalLog.Print("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect pooledStream has wrong creds " + ValidationHelper.HashString(pooledStream));
668 connection.connectionPool.PutConnection(pooledStream, pooledStream.Owner, connection.Timeout, false);
669 pooledStream = (SmtpPooledStream)connection.connectionPool.GetConnection((object)this, (synchronous ? null : m_ConnectionCreatedCallback), connection.Timeout);
670 if (pooledStream == null) {
671 GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect returning asynchronously");
675 pooledStream.creds = connection.credentials;
676 pooledStream.Owner = this.connection; //needs to be updated for gc reasons
679 connection.pooledStream = pooledStream;
688 GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect pooledStream#" + ValidationHelper.HashString(pooledStream));
694 connection.responseReader = new SmtpReplyReaderFactory(connection.pooledStream.NetworkStream);
697 //if we've already used this stream, then we've already done the handshake
699 //set connectionlease
700 connection.pooledStream.UpdateLifetime();
702 if (((SmtpPooledStream)connection.pooledStream).previouslyUsed == true) {
703 connection.isConnected = true;
709 SmtpReplyReader reader = connection.Reader.GetNextReplyReader();
710 IAsyncResult result = reader.BeginReadLine(handshakeCallback, this);
711 if (!result.CompletedSynchronously)
716 LineInfo info = reader.EndReadLine(result);
718 if (info.StatusCode != SmtpStatusCode.ServiceReady)
720 throw new SmtpException(info.StatusCode, info.Line, true);
738 static void HandshakeCallback(IAsyncResult result) //3
740 if (!result.CompletedSynchronously)
742 ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
747 LineInfo info = thisPtr.connection.Reader.CurrentReader.EndReadLine(result);
748 if (info.StatusCode != SmtpStatusCode.ServiceReady)
750 thisPtr.InvokeCallback(new SmtpException(info.StatusCode, info.Line, true));
753 if (!thisPtr.SendEHello())
758 catch (SmtpException)
760 if (!thisPtr.SendHello())
768 thisPtr.InvokeCallback(e);
775 IAsyncResult result = EHelloCommand.BeginSend(connection, connection.client.clientDomain, sendEHelloCallback, this);
776 if (result.CompletedSynchronously)
778 connection.extensions = EHelloCommand.EndSend(result);
779 connection.ParseExtensions(connection.extensions);
780 // If we already have a TlsStream, this is the second EHLO cmd
781 // that we sent after TLS handshake compelted. So skip TLS and
782 // continue with Authenticate.
783 if (connection.pooledStream.NetworkStream is TlsStream)
789 if (connection.EnableSsl) {
791 if (!connection.serverSupportsStartTls)
793 // Either TLS is already established or server does not support TLS
794 if (!(connection.pooledStream.NetworkStream is TlsStream))
796 throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
802 throw new NotSupportedException("ROTORTODO");
803 #endif // !FEATURE_PAL
813 static void SendEHelloCallback(IAsyncResult result)//5
815 if (!result.CompletedSynchronously)
817 ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
822 thisPtr.connection.extensions = EHelloCommand.EndSend(result);
823 thisPtr.connection.ParseExtensions(thisPtr.connection.extensions);
825 // If we already have a TlsStream, this is the second EHLO cmd
826 // that we sent after TLS handshake compelted. So skip TLS and
827 // continue with Authenticate.
828 if (thisPtr.connection.pooledStream.NetworkStream is TlsStream)
830 thisPtr.Authenticate();
835 catch (SmtpException e)
837 if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized)
838 && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)){
842 if (!thisPtr.SendHello()) {
848 if (thisPtr.connection.EnableSsl) {
850 if (!thisPtr.connection.serverSupportsStartTls)
852 // Either TLS is already established or server does not support TLS
853 if (!(thisPtr.connection.pooledStream.NetworkStream is TlsStream))
855 throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
859 thisPtr.SendStartTls();
861 throw new NotSupportedException("ROTORTODO");
862 #endif // !FEATURE_PAL
865 thisPtr.Authenticate();
870 thisPtr.InvokeCallback(e);
877 IAsyncResult result = HelloCommand.BeginSend(connection, connection.client.clientDomain, sendHelloCallback, this);
878 //if ehello isn't supported, assume basic auth
879 if (result.CompletedSynchronously)
881 connection.supportedAuth = SupportedAuth.Login;
882 HelloCommand.EndSend(result);
889 static void SendHelloCallback(IAsyncResult result) //7
891 if (!result.CompletedSynchronously)
893 ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
896 HelloCommand.EndSend(result);
897 thisPtr.Authenticate();
901 thisPtr.InvokeCallback(e);
907 bool SendStartTls()//6
909 IAsyncResult result = StartTlsCommand.BeginSend(connection, SendStartTlsCallback, this);
910 if (result.CompletedSynchronously)
912 StartTlsCommand.EndSend(result);
913 TlsStream TlsStream = new TlsStream(connection.pooledStream.ServicePoint.Host, connection.pooledStream.NetworkStream, connection.ClientCertificates, connection.pooledStream.ServicePoint, connection.client, m_OuterResult.ContextCopy);
914 connection.pooledStream.NetworkStream = TlsStream;
915 connection.responseReader = new SmtpReplyReaderFactory(connection.pooledStream.NetworkStream);
922 static void SendStartTlsCallback(IAsyncResult result) //7
924 if (!result.CompletedSynchronously)
926 ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
929 StartTlsCommand.EndSend(result);
930 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);
931 thisPtr.connection.pooledStream.NetworkStream = TlsStream;
932 thisPtr.connection.responseReader = new SmtpReplyReaderFactory(thisPtr.connection.pooledStream.NetworkStream);
933 thisPtr.SendEHello();
937 thisPtr.InvokeCallback(e);
941 #endif // !FEATURE_PAL
943 void Authenticate() //8
945 //if no credentials were supplied, try anonymous
946 //servers don't appear to anounce that they support anonymous login.
947 if (connection.credentials != null) {
948 while (++currentModule < connection.authenticationModules.Length)
950 //only authenticate if the auth protocol is supported
951 ISmtpAuthenticationModule module = connection.authenticationModules[currentModule];
952 if (!connection.AuthSupported(module)) {
956 NetworkCredential credential = connection.credentials.GetCredential(host, port, module.AuthenticationType);
957 if (credential == null)
959 Authorization auth = connection.SetContextAndTryAuthenticate(module, credential, m_OuterResult);
961 if (auth != null && auth.Message != null)
963 IAsyncResult result = AuthCommand.BeginSend(connection, connection.authenticationModules[currentModule].AuthenticationType, auth.Message, authenticateCallback, this);
964 if (!result.CompletedSynchronously)
969 LineInfo info = AuthCommand.EndSend(result);
971 if ((int)info.StatusCode == 334)
973 authResponse = info.Line;
974 if (!AuthenticateContinue())
979 else if ((int)info.StatusCode == 235)
981 module.CloseContext(connection);
982 connection.isConnected = true;
988 //try anonymous if didn't authenticate
989 //if (!connection.isConnected) {
990 // throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
994 connection.isConnected = true;
998 static void AuthenticateCallback(IAsyncResult result) //9
1000 if (!result.CompletedSynchronously)
1002 ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
1005 LineInfo info = AuthCommand.EndSend(result);
1007 if ((int)info.StatusCode == 334)
1009 thisPtr.authResponse = info.Line;
1010 if (!thisPtr.AuthenticateContinue())
1015 else if ((int)info.StatusCode == 235)
1017 thisPtr.connection.authenticationModules[thisPtr.currentModule].CloseContext(thisPtr.connection);
1018 thisPtr.connection.isConnected = true;
1019 thisPtr.InvokeCallback();
1023 thisPtr.Authenticate();
1027 thisPtr.InvokeCallback(e);
1032 bool AuthenticateContinue() //10
1036 // We don't need credential on the continued auth assuming they were captured on the first call.
1037 // That should always work, otherwise what if a new credential has been returned?
1038 Authorization auth = connection.authenticationModules[currentModule].Authenticate(authResponse, null, connection, connection.client.TargetName, connection.channelBindingToken);
1041 throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
1044 IAsyncResult result = AuthCommand.BeginSend(connection, auth.Message, authenticateContinueCallback, this);
1045 if (!result.CompletedSynchronously)
1050 LineInfo info = AuthCommand.EndSend(result);
1051 if ((int)info.StatusCode == 235)
1053 connection.authenticationModules[currentModule].CloseContext(connection);
1054 connection.isConnected = true;
1058 else if ((int)info.StatusCode != 334)
1062 authResponse = info.Line;
1066 static void AuthenticateContinueCallback(IAsyncResult result) //11
1068 if (!result.CompletedSynchronously)
1070 ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
1073 LineInfo info = AuthCommand.EndSend(result);
1074 if ((int)info.StatusCode == 235)
1076 thisPtr.connection.authenticationModules[thisPtr.currentModule].CloseContext(thisPtr.connection);
1077 thisPtr.connection.isConnected = true;
1078 thisPtr.InvokeCallback();
1081 else if ((int)info.StatusCode == 334)
1083 thisPtr.authResponse = info.Line;
1084 if (!thisPtr.AuthenticateContinue())
1089 thisPtr.Authenticate();
1093 thisPtr.InvokeCallback(e);