Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System / net / System / Net / mail / smtpconnection.cs
1 namespace System.Net.Mail
2 {
3     using System;
4     using System.Net;
5     using System.Security.Cryptography.X509Certificates;
6     using System.IO;
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;
13
14
15
16     class SmtpConnection
17     {
18
19         private static PooledStream CreateSmtpPooledStream(ConnectionPool pool)         {
20             return (PooledStream)new SmtpPooledStream(pool, TimeSpan.MaxValue, false);
21         }
22
23
24         private static readonly CreateConnectionDelegate m_CreateConnectionCallback = new CreateConnectionDelegate(CreateSmtpPooledStream);
25         private static readonly ContextCallback s_AuthenticateCallback = new ContextCallback(AuthenticateCallback);
26
27         BufferBuilder bufferBuilder = new BufferBuilder();
28         bool isConnected;
29         bool isClosed;
30         bool isStreamOpen;
31         bool sawNegotiate;
32         EventHandler onCloseHandler;
33         internal SmtpTransport parent;
34         internal SmtpClient client;
35         SmtpReplyReaderFactory responseReader;
36
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";
46
47         PooledStream pooledStream;
48         ConnectionPool connectionPool;
49         SupportedAuth supportedAuth = SupportedAuth.None;
50         bool serverSupportsStartTls = false;
51         ISmtpAuthenticationModule[] authenticationModules;
52         ICredentialsByHost credentials;
53         int timeout = 100000;
54         string[] extensions;
55         private ChannelBinding channelBindingToken = null;
56
57         bool enableSsl;
58         X509CertificateCollection clientCertificates;
59
60         internal SmtpConnection(SmtpTransport parent, SmtpClient client, ICredentialsByHost credentials, ISmtpAuthenticationModule[] authenticationModules)
61         {
62             this.client = client;
63             this.credentials = credentials;
64             this.authenticationModules = authenticationModules;
65             this.parent = parent;
66             onCloseHandler = new EventHandler(OnClose);
67         }
68
69         internal BufferBuilder BufferBuilder
70         {
71             get
72             {
73                 return bufferBuilder;
74             }
75         }
76
77         internal bool IsConnected
78         {
79             get
80             {
81                 return isConnected;
82             }
83         }
84
85         internal bool IsStreamOpen
86         {
87             get
88             {
89                 return isStreamOpen;
90             }
91         }
92
93         internal bool DSNEnabled
94         {
95             get
96             {
97                 if (pooledStream != null)
98                     return ((SmtpPooledStream)pooledStream).dsnEnabled;
99                 else
100                     return false;
101             }
102         }
103
104         internal SmtpReplyReaderFactory Reader
105         {
106             get
107             {
108                 return responseReader;
109             }
110         }
111
112         internal bool EnableSsl
113         {
114             get
115             {
116                 return enableSsl;
117             }
118             set
119             {
120 #if !FEATURE_PAL
121                 enableSsl = value;
122 #else
123                 throw new NotImplementedException("ROTORTODO");
124 #endif
125             }
126         }
127
128         internal int Timeout
129         {
130             get
131             {
132                 return timeout;
133             }
134             set
135             {
136                 timeout = value;
137             }
138         }
139
140
141         internal X509CertificateCollection ClientCertificates
142         {
143             get
144             {
145                 return clientCertificates;
146             }
147             set
148             {
149                 clientCertificates = value;
150             }
151         }
152
153         internal bool ServerSupportsEai
154         {
155             get 
156             { 
157                 SmtpPooledStream smtpPooledStream = (SmtpPooledStream)pooledStream;
158                 Debug.Assert(smtpPooledStream != null, "PooledStream not yet set");
159                 return smtpPooledStream.serverSupportsEai; 
160             }
161         }
162
163         internal IAsyncResult BeginGetConnection(ServicePoint servicePoint, ContextAwareResult outerResult, AsyncCallback callback, object state)
164         {
165             if (Logging.On) Logging.Associate(Logging.Web, this, servicePoint);
166             Debug.Assert(servicePoint != null, "servicePoint was null from SmtpTransport");
167
168             if (EnableSsl && ClientCertificates != null && ClientCertificates.Count > 0)
169                 connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, ClientCertificates.GetHashCode().ToString(NumberFormatInfo.InvariantInfo), m_CreateConnectionCallback);
170             else
171                 connectionPool = ConnectionPoolManager.GetConnectionPool(servicePoint, "", m_CreateConnectionCallback);
172
173             ConnectAndHandshakeAsyncResult result = new ConnectAndHandshakeAsyncResult(this, servicePoint.Host, servicePoint.Port, outerResult, callback, state);
174             result.GetConnection(false);
175             return result;
176         }
177
178
179         internal IAsyncResult BeginFlush(AsyncCallback callback, object state)
180         {
181             return pooledStream.UnsafeBeginWrite(bufferBuilder.GetBuffer(), 0, bufferBuilder.Length, callback, state);
182         }
183
184         internal void EndFlush(IAsyncResult result)
185         {
186             pooledStream.EndWrite(result);
187             bufferBuilder.Reset();
188         }
189
190         internal void Flush()
191         {
192             pooledStream.Write(bufferBuilder.GetBuffer(), 0, bufferBuilder.Length);
193             bufferBuilder.Reset();
194         }
195
196         internal void ReleaseConnection()
197         {
198             if (!isClosed) {
199                 lock (this) {
200
201                     if (!isClosed && pooledStream != null) {
202                         
203                         //free cbt buffer
204                         if (channelBindingToken != null){
205                             channelBindingToken.Close();
206                         }
207
208                         GlobalLog.Print("SmtpConnectiont#" + ValidationHelper.HashString(this) + "::Close Transport#" + ValidationHelper.HashString(parent) + "putting back pooledStream#" + ValidationHelper.HashString(pooledStream));
209
210                         ((SmtpPooledStream)pooledStream).previouslyUsed = true;
211                         connectionPool.PutConnection(pooledStream, pooledStream.Owner, Timeout);
212                     }
213                     isClosed = true;
214                 }
215             }
216             isConnected = false;
217         }
218
219         internal void Abort()
220         {
221             if (!isClosed) {
222                 lock (this) {
223                     if (!isClosed && pooledStream != null){
224
225                         GlobalLog.Print("SmtpConnectiont#" + ValidationHelper.HashString(this) + "::Close Transport#" + ValidationHelper.HashString(parent) + "closing and putting back pooledStream#" + ValidationHelper.HashString(pooledStream));
226                         
227                         //free CBT buffer
228                         if (this.channelBindingToken != null){
229                             channelBindingToken.Close();
230                         }
231
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);
238                     }
239                     isClosed = true;
240                 }
241             }
242             isConnected = false;
243         }
244
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
252                     string[] authTypes = 
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;
258                         }
259 #if !FEATURE_PAL
260                         else if (String.Compare(authType, authNtlm, StringComparison.OrdinalIgnoreCase) == 0) {
261                             supportedAuth |= SupportedAuth.NTLM;
262                         }
263                         else if (String.Compare(authType, authGssapi, StringComparison.OrdinalIgnoreCase) == 0) {
264                             supportedAuth |= SupportedAuth.GSSAPI;
265                         }
266                         else if (String.Compare(authType, authWDigest, StringComparison.OrdinalIgnoreCase) == 0) {
267                             supportedAuth |= SupportedAuth.WDigest;
268                         }
269 #endif // FEATURE_PAL
270                     }
271                 }
272                 else if (String.Compare(extension, 0, "dsn ", 0, 3, StringComparison.OrdinalIgnoreCase) == 0) {
273                     ((SmtpPooledStream)pooledStream).dsnEnabled = true;
274                 }
275                 else if (String.Compare(extension, 0, "STARTTLS", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) {
276                     serverSupportsStartTls = true;
277                 }
278                 else if (String.Compare(extension, 0, "SMTPUTF8", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) {
279                     ((SmtpPooledStream)pooledStream).serverSupportsEai = true;
280                 }
281             }
282         }
283
284         internal bool AuthSupported(ISmtpAuthenticationModule module){
285             if (module is SmtpLoginAuthenticationModule) {
286                 if ((supportedAuth & SupportedAuth.Login) > 0) {
287                     return true;
288                 }
289             }
290 #if !FEATURE_PAL
291             else if (module is SmtpNegotiateAuthenticationModule) {
292                 if ((supportedAuth & SupportedAuth.GSSAPI) > 0) {
293                     sawNegotiate = true;
294                     return true;
295                 }
296             }
297             else if (module is SmtpNtlmAuthenticationModule) {
298                 //don't try ntlm if negotiate has been tried
299                 if ((!sawNegotiate && (supportedAuth & SupportedAuth.NTLM) > 0)) {
300                     return true;
301                 }
302             }
303             else if (module is SmtpDigestAuthenticationModule) {
304                 if ((supportedAuth & SupportedAuth.WDigest) > 0) {
305                     return true;
306                 }
307             }
308 #endif // FEATURE_PAL
309
310             return false;
311         }
312
313
314         internal void GetConnection(ServicePoint servicePoint)
315         {
316             if (isConnected)
317             {
318                 throw new InvalidOperationException(SR.GetString(SR.SmtpAlreadyConnected));
319             }
320
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);
324
325             PooledStream pooledStream = connectionPool.GetConnection((object)this, null, Timeout);
326
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);
333             }
334             if (Logging.On) Logging.Associate(Logging.Web, this, pooledStream);
335
336             lock (this) {
337                 this.pooledStream = pooledStream;
338             }
339
340             ((SmtpPooledStream)pooledStream).creds = credentials;
341
342             responseReader = new SmtpReplyReaderFactory(pooledStream.NetworkStream);
343
344             //set connectionlease
345             pooledStream.UpdateLifetime();
346
347             //if the stream was already used, then we've already done the handshake
348             if (((SmtpPooledStream)pooledStream).previouslyUsed == true) {
349                 isConnected = true;
350                 return;
351             }
352
353             LineInfo info = responseReader.GetNextReplyReader().ReadLine();
354
355             switch (info.StatusCode) 
356             {
357                 case SmtpStatusCode.ServiceReady: 
358                     {
359                         break;
360                     }
361                 default: 
362                     {
363                         throw new SmtpException(info.StatusCode, info.Line, true);
364                     }
365             }
366
367             try
368             {
369                 extensions = EHelloCommand.Send(this, client.clientDomain);
370                 ParseExtensions(extensions);
371             }
372             catch (SmtpException e)
373             {
374                 if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized)
375                     && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)) {
376                     throw e;
377                 }
378
379                 HelloCommand.Send(this, client.clientDomain);
380                 //if ehello isn't supported, assume basic login
381                 supportedAuth = SupportedAuth.Login;
382             }
383
384 #if !FEATURE_PAL
385             // Establish TLS
386             if (enableSsl) 
387             {
388                 if (!serverSupportsStartTls) 
389                 {
390                     // Either TLS is already established or server does not support TLS
391                     if (!(pooledStream.NetworkStream is TlsStream)) 
392                     {
393                         throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
394                     }
395                 }
396                 StartTlsCommand.Send(this);
397                 TlsStream TlsStream = new TlsStream(servicePoint.Host, pooledStream.NetworkStream, clientCertificates, servicePoint, client, null);
398
399                 pooledStream.NetworkStream = TlsStream;
400
401                 //for SMTP, the CBT should be unique
402                 this.channelBindingToken = TlsStream.GetChannelBinding(ChannelBindingKind.Unique);
403
404                 responseReader = new SmtpReplyReaderFactory(pooledStream.NetworkStream);
405
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);
410             }
411 #endif // !FEATURE_PAL
412
413             //if no credentials were supplied, try anonymous
414             //servers don't appear to anounce that they support anonymous login.
415             if (credentials != null) {
416
417                 for (int i = 0; i < authenticationModules.Length; i++) 
418                 {
419
420                     //only authenticate if the auth protocol is supported  - Microsoft
421                     if (!AuthSupported(authenticationModules[i])) {
422                         continue;
423                     }
424
425                     NetworkCredential credential = credentials.GetCredential(servicePoint.Host, 
426                         servicePoint.Port, authenticationModules[i].AuthenticationType);
427                     if (credential == null)
428                         continue;
429
430                     Authorization auth = SetContextAndTryAuthenticate(authenticationModules[i], credential, null);
431
432                     if (auth != null && auth.Message != null) 
433                     {
434                         info = AuthCommand.Send(this, authenticationModules[i].AuthenticationType, auth.Message);
435
436                         if (info.StatusCode == SmtpStatusCode.CommandParameterNotImplemented) 
437                         {
438                             continue;
439                         }
440
441                         while ((int)info.StatusCode == 334) 
442                         {
443                             auth = authenticationModules[i].Authenticate(info.Line, null, this, this.client.TargetName, this.channelBindingToken);
444                             if (auth == null)
445                             {
446                                 throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
447                             }
448                             info = AuthCommand.Send(this, auth.Message);
449
450                             if ((int)info.StatusCode == 235)
451                             {
452                                 authenticationModules[i].CloseContext(this);
453                                 isConnected = true;
454                                 return;
455                             }
456                         }
457                     }
458                 }
459             }
460             isConnected = true;
461         }
462
463         //
464         // We may need to impersonate in this method
465         //
466         [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.ControlPrincipal)]
467         private Authorization SetContextAndTryAuthenticate(ISmtpAuthenticationModule module, NetworkCredential credential, ContextAwareResult context)
468         {
469 #if !FEATURE_PAL
470             // We may need to restore user thread token here
471             if (credential is SystemNetworkCredential)
472             {
473                 // 
474 #if DEBUG
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));
476 #endif
477
478                 WindowsIdentity w = context == null ? null : context.Identity;
479                 try
480                 {
481                     IDisposable ctx = w == null ? null : w.Impersonate();
482                     if (ctx != null)
483                     {
484                         using (ctx)
485                         {
486                             return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken);
487                         }
488                     }
489                     else
490                     {
491                         ExecutionContext x = context == null ? null : context.ContextCopy;
492                         if (x != null)
493                         {
494                             AuthenticateCallbackContext authenticationContext =
495                                 new AuthenticateCallbackContext(this, module, credential, this.client.TargetName, this.channelBindingToken);
496
497                             ExecutionContext.Run(x, s_AuthenticateCallback, authenticationContext);
498                             return authenticationContext.result;
499                         }
500                         else
501                         {
502                             return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken);
503                         }
504                     }
505                 }
506                 catch
507                 {
508                     // Prevent the impersonation from leaking to upstack exception filters.
509                     throw;
510                 }
511             }
512 #endif // !FEATURE_PAL
513             return module.Authenticate(null, credential, this, this.client.TargetName, this.channelBindingToken);
514         }
515
516         private static void AuthenticateCallback(object state)
517         {
518             AuthenticateCallbackContext context = (AuthenticateCallbackContext)state;
519             context.result = context.module.Authenticate(null, context.credential, context.thisPtr, context.spn, context.token);
520         }
521
522         private class AuthenticateCallbackContext
523         {
524             internal AuthenticateCallbackContext(SmtpConnection thisPtr, ISmtpAuthenticationModule module, NetworkCredential credential, string spn, ChannelBinding Token)
525             {
526                 this.thisPtr = thisPtr;
527                 this.module = module;
528                 this.credential = credential;
529                 this.spn = spn;
530                 this.token = Token;
531
532                 this.result = null;
533             }
534
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;
540
541             internal Authorization result;
542         }
543
544         internal void EndGetConnection(IAsyncResult result)
545         {
546             ConnectAndHandshakeAsyncResult.End(result);
547         }
548
549         internal Stream GetClosableStream()
550         {
551             ClosableStream cs = new ClosableStream(pooledStream.NetworkStream, onCloseHandler);
552             isStreamOpen = true;
553             return cs;
554         }
555
556         void OnClose(object sender, EventArgs args)
557         {
558             isStreamOpen = false;
559
560             DataStopCommand.Send(this);
561         }
562
563         class ConnectAndHandshakeAsyncResult : LazyAsyncResult
564         {
565
566             private static readonly GeneralAsyncDelegate m_ConnectionCreatedCallback = new GeneralAsyncDelegate(ConnectionCreatedCallback);
567             string authResponse;
568             SmtpConnection connection;
569             int currentModule = -1;
570             int port;
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);
576             string host;
577
578             private readonly ContextAwareResult m_OuterResult;
579
580
581             internal ConnectAndHandshakeAsyncResult(SmtpConnection connection, string host, int port, ContextAwareResult outerResult, AsyncCallback callback, object state) :
582                 base(null, state, callback)
583             {
584                 this.connection = connection;
585                 this.host = host;
586                 this.port = port;
587
588                 m_OuterResult = outerResult;
589             }
590
591
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);
597                     return;
598                 }
599                 SmtpPooledStream pooledStream = (SmtpPooledStream)(PooledStream)state;
600
601
602                 try
603                 {
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");
611                             return;
612                         }
613                     }
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;
617
618
619                     lock (ConnectAndHandshakeAsyncResult.connection) {
620
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);
626                             return;
627                         }
628                         ConnectAndHandshakeAsyncResult.connection.pooledStream = pooledStream;
629                     }
630
631                     ConnectAndHandshakeAsyncResult.Handshake();
632                 }
633                 catch (Exception e)
634                 {
635                     ConnectAndHandshakeAsyncResult.InvokeCallback(e);
636                 }
637                 GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(request) + "::ConnectionCreatedCallback pooledStream#" + ValidationHelper.HashString(pooledStream));
638             }
639
640
641             internal static void End(IAsyncResult result)
642             {
643                 ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result;
644                 object connectResult = thisPtr.InternalWaitForCompletion();
645                 if (connectResult is Exception){
646                     throw (Exception)connectResult;
647                 }
648             }
649
650             internal void GetConnection(bool synchronous)
651             {
652
653                 GlobalLog.Enter("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect: sync=" + (synchronous ? "true" : "false"));
654                 if (connection.isConnected)
655                 {
656                     throw new InvalidOperationException(SR.GetString(SR.SmtpAlreadyConnected));
657                 }
658
659
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));
662
663                 if (pooledStream != null) {
664                     try
665                     {
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");
672                                 return;
673                             }
674                         }
675                         pooledStream.creds = connection.credentials;
676                         pooledStream.Owner = this.connection; //needs to be updated for gc reasons
677
678                         lock (connection) {
679                             connection.pooledStream = pooledStream;
680                         }
681                         Handshake();
682                     }
683                     catch (Exception e)
684                     {
685                         InvokeCallback(e);
686                     }
687                 }
688                 GlobalLog.Leave("ConnectAndHandshakeAsyncResult#" + ValidationHelper.HashString(this) + "::Connect pooledStream#" + ValidationHelper.HashString(pooledStream));
689             }
690
691
692             void Handshake()
693             {
694                 connection.responseReader = new SmtpReplyReaderFactory(connection.pooledStream.NetworkStream);
695
696
697                 //if we've already used this stream, then we've already done the handshake
698
699                 //set connectionlease
700                 connection.pooledStream.UpdateLifetime();
701
702                 if (((SmtpPooledStream)connection.pooledStream).previouslyUsed == true) {
703                     connection.isConnected = true;
704                     InvokeCallback();
705                     return;
706                 }
707
708
709                 SmtpReplyReader reader = connection.Reader.GetNextReplyReader();
710                 IAsyncResult result = reader.BeginReadLine(handshakeCallback, this);
711                 if (!result.CompletedSynchronously)
712                 {
713                     return;
714                 }
715
716                 LineInfo info = reader.EndReadLine(result);
717
718                 if (info.StatusCode != SmtpStatusCode.ServiceReady)
719                 {
720                     throw new SmtpException(info.StatusCode, info.Line, true);
721                 }
722                 try
723                 {
724                     if (!SendEHello())
725                     {
726                         return;
727                     }
728                 }
729                 catch
730                 {
731                     if (!SendHello())
732                     {
733                         return;
734                     }
735                 }
736             }
737
738             static void HandshakeCallback(IAsyncResult result)   //3
739             {
740                 if (!result.CompletedSynchronously)
741                 {
742                     ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
743                     try
744                     {
745                         try
746                         {
747                             LineInfo info = thisPtr.connection.Reader.CurrentReader.EndReadLine(result);
748                             if (info.StatusCode != SmtpStatusCode.ServiceReady)
749                             {
750                                 thisPtr.InvokeCallback(new SmtpException(info.StatusCode, info.Line, true));
751                                 return;
752                             }
753                             if (!thisPtr.SendEHello())
754                             {
755                                 return;
756                             }
757                         }
758                         catch (SmtpException)
759                         {
760                             if (!thisPtr.SendHello())
761                             {
762                                 return;
763                             }
764                         }
765                     }
766                     catch (Exception e)
767                     {
768                         thisPtr.InvokeCallback(e);
769                     }
770                 }
771             }
772
773             bool SendEHello()//4
774             {
775                 IAsyncResult result = EHelloCommand.BeginSend(connection, connection.client.clientDomain, sendEHelloCallback, this);
776                 if (result.CompletedSynchronously)
777                 {
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)
784                     {
785                         Authenticate();
786                         return true;
787                     }
788
789                     if (connection.EnableSsl) {
790 #if !FEATURE_PAL
791                         if (!connection.serverSupportsStartTls)
792                         {
793                             // Either TLS is already established or server does not support TLS
794                             if (!(connection.pooledStream.NetworkStream is TlsStream))
795                             {
796                                 throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
797                             }
798                         }
799
800                         SendStartTls();
801 #else // FEATURE_PAL
802                         throw new NotSupportedException("ROTORTODO");
803 #endif // !FEATURE_PAL
804                     }
805                     else {
806                         Authenticate();
807                     }
808                     return true;
809                 }
810                 return false;
811             }
812
813             static void SendEHelloCallback(IAsyncResult result)//5
814             {
815                 if (!result.CompletedSynchronously)
816                 {
817                     ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
818                     try
819                     {
820                         try
821                         {
822                             thisPtr.connection.extensions = EHelloCommand.EndSend(result);
823                             thisPtr.connection.ParseExtensions(thisPtr.connection.extensions);
824
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)
829                             {
830                                 thisPtr.Authenticate();
831                                 return;
832                             }
833                         }
834
835                         catch (SmtpException e)
836                         {
837                             if ((e.StatusCode != SmtpStatusCode.CommandUnrecognized)
838                                 && (e.StatusCode != SmtpStatusCode.CommandNotImplemented)){
839                                 throw e;
840                             }
841
842                             if (!thisPtr.SendHello()) {
843                                 return;
844                             }
845                         }
846
847
848                         if (thisPtr.connection.EnableSsl) {
849 #if !FEATURE_PAL
850                             if (!thisPtr.connection.serverSupportsStartTls)
851                             {
852                                 // Either TLS is already established or server does not support TLS
853                                 if (!(thisPtr.connection.pooledStream.NetworkStream is TlsStream))
854                                 {
855                                     throw new SmtpException(SR.GetString(SR.MailServerDoesNotSupportStartTls));
856                                 }
857                             }
858
859                             thisPtr.SendStartTls();
860 #else // FEATURE_PAL
861                             throw new NotSupportedException("ROTORTODO");
862 #endif // !FEATURE_PAL
863                         }
864                         else {
865                             thisPtr.Authenticate();
866                         }
867                     }
868                     catch (Exception e)
869                     {
870                         thisPtr.InvokeCallback(e);
871                     }
872                 }
873             }
874
875             bool SendHello()//6
876             {
877                 IAsyncResult result = HelloCommand.BeginSend(connection, connection.client.clientDomain, sendHelloCallback, this);
878                 //if ehello isn't supported, assume basic auth
879                 if (result.CompletedSynchronously)
880                 {
881                     connection.supportedAuth = SupportedAuth.Login;
882                     HelloCommand.EndSend(result);
883                     Authenticate();
884                     return true;
885                 }
886                 return false;
887             }
888
889             static void SendHelloCallback(IAsyncResult result)     //7
890             {
891                 if (!result.CompletedSynchronously)
892                 {
893                     ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
894                     try
895                     {
896                         HelloCommand.EndSend(result);
897                         thisPtr.Authenticate();
898                     }
899                     catch (Exception e)
900                     {
901                         thisPtr.InvokeCallback(e);
902                     }
903                 }
904             }
905
906 #if !FEATURE_PAL
907             bool SendStartTls()//6
908             {
909                 IAsyncResult result = StartTlsCommand.BeginSend(connection, SendStartTlsCallback, this);
910                 if (result.CompletedSynchronously)
911                 {
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);
916                     SendEHello();
917                     return true;
918                 }
919                 return false;
920             }
921
922             static void SendStartTlsCallback(IAsyncResult result)     //7
923             {
924                 if (!result.CompletedSynchronously)
925                 {
926                     ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
927                     try
928                     {
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();
934                     }
935                     catch (Exception e)
936                     {
937                         thisPtr.InvokeCallback(e);
938                     }
939                 }
940             }
941 #endif // !FEATURE_PAL
942
943             void Authenticate() //8
944             {
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)
949                     {
950                         //only authenticate if the auth protocol is supported
951                         ISmtpAuthenticationModule module = connection.authenticationModules[currentModule];
952                         if (!connection.AuthSupported(module)) {
953                             continue;
954                         }
955
956                         NetworkCredential credential = connection.credentials.GetCredential(host, port, module.AuthenticationType);
957                         if (credential == null)
958                             continue;
959                         Authorization auth = connection.SetContextAndTryAuthenticate(module, credential, m_OuterResult);
960
961                         if (auth != null && auth.Message != null)
962                         {
963                             IAsyncResult result = AuthCommand.BeginSend(connection, connection.authenticationModules[currentModule].AuthenticationType, auth.Message, authenticateCallback, this);
964                             if (!result.CompletedSynchronously)
965                             {
966                                 return;
967                             }
968
969                             LineInfo info = AuthCommand.EndSend(result);
970
971                             if ((int)info.StatusCode == 334)
972                             {
973                                 authResponse = info.Line;
974                                 if (!AuthenticateContinue())
975                                 {
976                                     return;
977                                 }
978                             }
979                             else if ((int)info.StatusCode == 235)
980                             {
981                                 module.CloseContext(connection);
982                                 connection.isConnected = true;
983                                 break;
984                             }
985                         }
986                     }
987
988                     //try anonymous if didn't authenticate
989                     //if (!connection.isConnected) {
990                     //    throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
991                     // }
992                 }
993
994                 connection.isConnected = true;
995                 InvokeCallback();
996             }
997
998             static void AuthenticateCallback(IAsyncResult result) //9
999             {
1000                 if (!result.CompletedSynchronously)
1001                 {
1002                     ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
1003                     try
1004                     {
1005                         LineInfo info = AuthCommand.EndSend(result);
1006
1007                         if ((int)info.StatusCode == 334)
1008                         {
1009                             thisPtr.authResponse = info.Line;
1010                             if (!thisPtr.AuthenticateContinue())
1011                             {
1012                                 return;
1013                             }
1014                         }
1015                         else if ((int)info.StatusCode == 235)
1016                         {
1017                             thisPtr.connection.authenticationModules[thisPtr.currentModule].CloseContext(thisPtr.connection);
1018                             thisPtr.connection.isConnected = true;
1019                             thisPtr.InvokeCallback();
1020                             return;
1021                         }
1022
1023                         thisPtr.Authenticate();
1024                     }
1025                     catch (Exception e)
1026                     {
1027                         thisPtr.InvokeCallback(e);
1028                     }
1029                 }
1030             }
1031
1032             bool AuthenticateContinue()        //10
1033             {
1034                 for (; ; )
1035                 {
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);
1039                     if (auth == null)
1040                     {
1041                         throw new SmtpException(SR.GetString(SR.SmtpAuthenticationFailed));
1042                     }
1043
1044                     IAsyncResult result = AuthCommand.BeginSend(connection, auth.Message, authenticateContinueCallback, this);
1045                     if (!result.CompletedSynchronously)
1046                     {
1047                         return false;
1048                     }
1049
1050                     LineInfo info = AuthCommand.EndSend(result);
1051                     if ((int)info.StatusCode == 235)
1052                     {
1053                         connection.authenticationModules[currentModule].CloseContext(connection);
1054                         connection.isConnected = true;
1055                         InvokeCallback();
1056                         return false;
1057                     }
1058                     else if ((int)info.StatusCode != 334)
1059                     {
1060                         return true;
1061                     }
1062                     authResponse = info.Line;
1063                 }
1064             }
1065
1066             static void AuthenticateContinueCallback(IAsyncResult result)     //11
1067             {
1068                 if (!result.CompletedSynchronously)
1069                 {
1070                     ConnectAndHandshakeAsyncResult thisPtr = (ConnectAndHandshakeAsyncResult)result.AsyncState;
1071                     try
1072                     {
1073                         LineInfo info = AuthCommand.EndSend(result);
1074                         if ((int)info.StatusCode == 235)
1075                         {
1076                             thisPtr.connection.authenticationModules[thisPtr.currentModule].CloseContext(thisPtr.connection);
1077                             thisPtr.connection.isConnected = true;
1078                             thisPtr.InvokeCallback();
1079                             return;
1080                         }
1081                         else if ((int)info.StatusCode == 334)
1082                         {
1083                             thisPtr.authResponse = info.Line;
1084                             if (!thisPtr.AuthenticateContinue())
1085                             {
1086                                 return;
1087                             }
1088                         }
1089                         thisPtr.Authenticate();
1090                     }
1091                     catch (Exception e)
1092                     {
1093                         thisPtr.InvokeCallback(e);
1094                     }
1095                 }
1096             }
1097
1098         }
1099     }
1100 }