1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using Microsoft.Win32.SafeHandles;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.ComponentModel;
9 using System.Diagnostics;
10 using System.Net.Security;
11 using System.Runtime.ExceptionServices;
12 using System.Runtime.InteropServices;
13 using System.Security;
14 using System.Security.Authentication.ExtendedProtection;
15 using System.Security.Principal;
16 using System.Text;
17 using System.Threading;
18 using System.Threading.Tasks;
19 
20 namespace System.Net
21 {
22     public sealed unsafe partial class HttpListener
23     {
24         public static bool IsSupported => Interop.HttpApi.s_supported;
25 
26         private static readonly Type s_channelBindingStatusType = typeof(Interop.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS);
27         private static readonly int s_requestChannelBindStatusSize =
28             Marshal.SizeOf(typeof(Interop.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS));
29 
30         // Windows 8 fixed a bug in Http.sys's HttpReceiveClientCertificate method.
31         // Without this fix IOCP callbacks were not being called although ERROR_IO_PENDING was
32         // returned from HttpReceiveClientCertificate when using the
33         // FileCompletionNotificationModes.SkipCompletionPortOnSuccess flag.
34         // This bug was only hit when the buffer passed into HttpReceiveClientCertificate
35         // (1500 bytes initially) is tool small for the certificate.
36         // Due to this bug in downlevel operating systems the FileCompletionNotificationModes.SkipCompletionPortOnSuccess
37         // flag is only used on Win8 and later.
38         internal static readonly bool SkipIOCPCallbackOnSuccess = Environment.OSVersion.Version >= new Version(6, 2);
39 
40         // Mitigate potential DOS attacks by limiting the number of unknown headers we accept.  Numerous header names
41         // with hash collisions will cause the server to consume excess CPU.  1000 headers limits CPU time to under
42         // 0.5 seconds per request.  Respond with a 400 Bad Request.
43         private const int UnknownHeaderLimit = 1000;
44 
45         private static readonly byte[] s_wwwAuthenticateBytes = new byte[]
46         {
47             (byte) 'W', (byte) 'W', (byte) 'W', (byte) '-', (byte) 'A', (byte) 'u', (byte) 't', (byte) 'h',
48             (byte) 'e', (byte) 'n', (byte) 't', (byte) 'i', (byte) 'c', (byte) 'a', (byte) 't', (byte) 'e'
49         };
50 
51         private SafeHandle _requestQueueHandle;
52         private ThreadPoolBoundHandle _requestQueueBoundHandle;
53         private bool _unsafeConnectionNtlmAuthentication;
54 
55         private HttpServerSessionHandle _serverSessionHandle;
56         private ulong _urlGroupId;
57 
58         private bool _V2Initialized;
59         private Dictionary<ulong, DisconnectAsyncResult> _disconnectResults;
60 
61         internal SafeHandle RequestQueueHandle => _requestQueueHandle;
62 
ValidateV2Property()63         private void ValidateV2Property()
64         {
65             // Make sure that calling CheckDisposed and SetupV2Config is an atomic operation. This
66             // avoids race conditions if the listener is aborted/closed after CheckDisposed(), but
67             // before SetupV2Config().
68             lock (_internalLock)
69             {
70                 CheckDisposed();
71                 SetupV2Config();
72             }
73         }
74 
75         public bool UnsafeConnectionNtlmAuthentication
76         {
77             get => _unsafeConnectionNtlmAuthentication;
78             set
79             {
80                 CheckDisposed();
81                 if (_unsafeConnectionNtlmAuthentication == value)
82                 {
83                     return;
84                 }
85                 lock ((DisconnectResults as ICollection).SyncRoot)
86                 {
87                     if (_unsafeConnectionNtlmAuthentication == value)
88                     {
89                         return;
90                     }
91                     _unsafeConnectionNtlmAuthentication = value;
92                     if (!value)
93                     {
94                         foreach (DisconnectAsyncResult result in DisconnectResults.Values)
95                         {
96                             result.AuthenticatedConnection = null;
97                         }
98                     }
99                 }
100             }
101         }
102 
103         private Dictionary<ulong, DisconnectAsyncResult> DisconnectResults =>
104             LazyInitializer.EnsureInitialized(ref _disconnectResults, () => new Dictionary<ulong, DisconnectAsyncResult>());
105 
SetUrlGroupProperty(Interop.HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize)106         private void SetUrlGroupProperty(Interop.HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize)
107         {
108             uint statusCode = Interop.HttpApi.ERROR_SUCCESS;
109 
110             Debug.Assert(_urlGroupId != 0, "SetUrlGroupProperty called with invalid url group id");
111             Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
112 
113             //
114             // Set the url group property using Http Api.
115             //
116             statusCode = Interop.HttpApi.HttpSetUrlGroupProperty(
117                 _urlGroupId, property, info, infosize);
118 
119             if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
120             {
121                 HttpListenerException exception = new HttpListenerException((int)statusCode);
122                 if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"HttpSetUrlGroupProperty:: Property: {property} {exception}");
123                 throw exception;
124             }
125         }
126 
SetServerTimeout(int[] timeouts, uint minSendBytesPerSecond)127         internal void SetServerTimeout(int[] timeouts, uint minSendBytesPerSecond)
128         {
129             ValidateV2Property(); // CheckDispose and initilize HttpListener in the case of app.config timeouts
130 
131             Interop.HttpApi.HTTP_TIMEOUT_LIMIT_INFO timeoutinfo =
132                 new Interop.HttpApi.HTTP_TIMEOUT_LIMIT_INFO();
133 
134             timeoutinfo.Flags = Interop.HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
135             timeoutinfo.DrainEntityBody =
136                 (ushort)timeouts[(int)Interop.HttpApi.HTTP_TIMEOUT_TYPE.DrainEntityBody];
137             timeoutinfo.EntityBody =
138                 (ushort)timeouts[(int)Interop.HttpApi.HTTP_TIMEOUT_TYPE.EntityBody];
139             timeoutinfo.RequestQueue =
140                 (ushort)timeouts[(int)Interop.HttpApi.HTTP_TIMEOUT_TYPE.RequestQueue];
141             timeoutinfo.IdleConnection =
142                 (ushort)timeouts[(int)Interop.HttpApi.HTTP_TIMEOUT_TYPE.IdleConnection];
143             timeoutinfo.HeaderWait =
144                 (ushort)timeouts[(int)Interop.HttpApi.HTTP_TIMEOUT_TYPE.HeaderWait];
145             timeoutinfo.MinSendRate = minSendBytesPerSecond;
146 
147             IntPtr infoptr = new IntPtr(&timeoutinfo);
148 
149             SetUrlGroupProperty(
150                 Interop.HttpApi.HTTP_SERVER_PROPERTY.HttpServerTimeoutsProperty,
151                 infoptr, (uint)Marshal.SizeOf(typeof(Interop.HttpApi.HTTP_TIMEOUT_LIMIT_INFO)));
152         }
153 
154         public HttpListenerTimeoutManager TimeoutManager
155         {
156             get
157             {
158                 ValidateV2Property();
159                 Debug.Assert(_timeoutManager != null, "Timeout manager is not assigned");
160                 return _timeoutManager;
161             }
162         }
163 
DangerousGetHandle()164         private IntPtr DangerousGetHandle()
165         {
166             return ((HttpRequestQueueV2Handle)_requestQueueHandle).DangerousGetHandle();
167         }
168 
169         internal ThreadPoolBoundHandle RequestQueueBoundHandle
170         {
171             get
172             {
173                 if (_requestQueueBoundHandle == null)
174                 {
175                     lock (_internalLock)
176                     {
177                         if (_requestQueueBoundHandle == null)
178                         {
179                             _requestQueueBoundHandle = ThreadPoolBoundHandle.BindHandle(_requestQueueHandle);
180                             if (NetEventSource.IsEnabled) NetEventSource.Info($"ThreadPoolBoundHandle.BindHandle({_requestQueueHandle}) -> {_requestQueueBoundHandle}");
181                         }
182                     }
183                 }
184 
185                 return _requestQueueBoundHandle;
186             }
187         }
188 
SetupV2Config()189         private void SetupV2Config()
190         {
191             uint statusCode = Interop.HttpApi.ERROR_SUCCESS;
192             ulong id = 0;
193 
194             //
195             // If we have already initialized V2 config, then nothing to do.
196             //
197             if (_V2Initialized)
198             {
199                 return;
200             }
201 
202             //
203             // V2 initialization sequence:
204             // 1. Create server session
205             // 2. Create url group
206             // 3. Create request queue - Done in Start()
207             // 4. Add urls to url group - Done in Start()
208             // 5. Attach request queue to url group - Done in Start()
209             //
210 
211             try
212             {
213                 statusCode = Interop.HttpApi.HttpCreateServerSession(
214                     Interop.HttpApi.s_version, &id, 0);
215 
216                 if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
217                 {
218                     throw new HttpListenerException((int)statusCode);
219                 }
220 
221                 Debug.Assert(id != 0, "Invalid id returned by HttpCreateServerSession");
222 
223                 _serverSessionHandle = new HttpServerSessionHandle(id);
224 
225                 id = 0;
226                 statusCode = Interop.HttpApi.HttpCreateUrlGroup(
227                     _serverSessionHandle.DangerousGetServerSessionId(), &id, 0);
228 
229                 if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
230                 {
231                     throw new HttpListenerException((int)statusCode);
232                 }
233 
234                 Debug.Assert(id != 0, "Invalid id returned by HttpCreateUrlGroup");
235                 _urlGroupId = id;
236 
237                 _V2Initialized = true;
238             }
239             catch (Exception exception)
240             {
241                 //
242                 // If V2 initialization fails, we mark object as unusable.
243                 //
244                 _state = State.Closed;
245 
246                 //
247                 // If Url group or request queue creation failed, close server session before throwing.
248                 //
249                 _serverSessionHandle?.Dispose();
250 
251                 if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"SetupV2Config {exception}");
252                 throw;
253             }
254         }
255 
Start()256         public void Start()
257         {
258             if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
259 
260             // Make sure there are no race conditions between Start/Stop/Abort/Close/Dispose and
261             // calls to SetupV2Config: Start needs to setup all resources (esp. in V2 where besides
262             // the request handle, there is also a server session and a Url group. Abort/Stop must
263             // not interfere while Start is allocating those resources. The lock also makes sure
264             // all methods changing state can read and change the state in an atomic way.
265             lock (_internalLock)
266             {
267                 try
268                 {
269                     CheckDisposed();
270                     if (_state == State.Started)
271                     {
272                         return;
273                     }
274 
275                     // SetupV2Config() is not called in the ctor, because it may throw. This would
276                     // be a regression since in v1 the ctor never threw. Besides, ctors should do
277                     // minimal work according to the framework design guidelines.
278                     SetupV2Config();
279                     CreateRequestQueueHandle();
280                     AttachRequestQueueToUrlGroup();
281 
282                     // All resources are set up correctly. Now add all prefixes.
283                     try
284                     {
285                         AddAllPrefixes();
286                     }
287                     catch (HttpListenerException)
288                     {
289                         // If an error occurred while adding prefixes, free all resources allocated by previous steps.
290                         DetachRequestQueueFromUrlGroup();
291                         throw;
292                     }
293 
294                     _state = State.Started;
295                 }
296                 catch (Exception exception)
297                 {
298                     // Make sure the HttpListener instance can't be used if Start() failed.
299                     _state = State.Closed;
300                     CloseRequestQueueHandle();
301                     CleanupV2Config();
302                     if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"Start {exception}");
303                     throw;
304                 }
305                 finally
306                 {
307                     if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
308                 }
309             }
310         }
311 
CleanupV2Config()312         private void CleanupV2Config()
313         {
314             //
315             // If we never setup V2, just return.
316             //
317             if (!_V2Initialized)
318             {
319                 return;
320             }
321 
322             //
323             // V2 stopping sequence:
324             // 1. Detach request queue from url group - Done in Stop()/Abort()
325             // 2. Remove urls from url group - Done in Stop()
326             // 3. Close request queue - Done in Stop()/Abort()
327             // 4. Close Url group.
328             // 5. Close server session.
329 
330             Debug.Assert(_urlGroupId != 0, "HttpCloseUrlGroup called with invalid url group id");
331 
332             uint statusCode = Interop.HttpApi.HttpCloseUrlGroup(_urlGroupId);
333 
334             if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
335             {
336                 if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"CloseV2Config {SR.Format(SR.net_listener_close_urlgroup_error, statusCode)}");
337             }
338             _urlGroupId = 0;
339 
340             Debug.Assert(_serverSessionHandle != null, "ServerSessionHandle is null in CloseV2Config");
341             Debug.Assert(!_serverSessionHandle.IsInvalid, "ServerSessionHandle is invalid in CloseV2Config");
342 
343             _serverSessionHandle.Dispose();
344         }
345 
AttachRequestQueueToUrlGroup()346         private void AttachRequestQueueToUrlGroup()
347         {
348             //
349             // Set the association between request queue and url group. After this, requests for registered urls will
350             // get delivered to this request queue.
351             //
352             Interop.HttpApi.HTTP_BINDING_INFO info = new Interop.HttpApi.HTTP_BINDING_INFO();
353             info.Flags = Interop.HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
354             info.RequestQueueHandle = DangerousGetHandle();
355 
356             IntPtr infoptr = new IntPtr(&info);
357 
358             SetUrlGroupProperty(Interop.HttpApi.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
359                 infoptr, (uint)Marshal.SizeOf(typeof(Interop.HttpApi.HTTP_BINDING_INFO)));
360         }
361 
DetachRequestQueueFromUrlGroup()362         private void DetachRequestQueueFromUrlGroup()
363         {
364             Debug.Assert(_urlGroupId != 0, "DetachRequestQueueFromUrlGroup can't detach using Url group id 0.");
365 
366             //
367             // Break the association between request queue and url group. After this, requests for registered urls
368             // will get 503s.
369             // Note that this method may be called multiple times (Stop() and then Abort()). This
370             // is fine since http.sys allows to set HttpServerBindingProperty multiple times for valid
371             // Url groups.
372             //
373             Interop.HttpApi.HTTP_BINDING_INFO info = new Interop.HttpApi.HTTP_BINDING_INFO();
374             info.Flags = Interop.HttpApi.HTTP_FLAGS.NONE;
375             info.RequestQueueHandle = IntPtr.Zero;
376 
377             IntPtr infoptr = new IntPtr(&info);
378 
379             uint statusCode = Interop.HttpApi.HttpSetUrlGroupProperty(_urlGroupId,
380                 Interop.HttpApi.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
381                 infoptr, (uint)Marshal.SizeOf(typeof(Interop.HttpApi.HTTP_BINDING_INFO)));
382 
383             if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
384             {
385                 if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"DetachRequestQueueFromUrlGroup {SR.Format(SR.net_listener_detach_error, statusCode)}");
386             }
387         }
388 
Stop()389         public void Stop()
390         {
391             if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
392             try
393             {
394                 lock (_internalLock)
395                 {
396                     CheckDisposed();
397                     if (_state == State.Stopped)
398                     {
399                         return;
400                     }
401 
402                     RemoveAll(false);
403                     DetachRequestQueueFromUrlGroup();
404 
405                     // Even though it would be enough to just detach the request queue in v2, in order to
406                     // keep app compat with earlier versions of the framework, we need to close the request queue.
407                     // This will make sure that pending GetContext() calls will complete and throw an exception. Just
408                     // detaching the url group from the request queue would not cause GetContext() to return.
409                     CloseRequestQueueHandle();
410 
411                     _state = State.Stopped;
412                 }
413             }
414             catch (Exception exception)
415             {
416                 if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"Stop {exception}");
417                 throw;
418             }
419             finally
420             {
421                 if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
422             }
423         }
424 
CreateRequestQueueHandle()425         private unsafe void CreateRequestQueueHandle()
426         {
427             uint statusCode = Interop.HttpApi.ERROR_SUCCESS;
428 
429             HttpRequestQueueV2Handle requestQueueHandle = null;
430             statusCode =
431                 Interop.HttpApi.HttpCreateRequestQueue(
432                     Interop.HttpApi.s_version, null, null, 0, out requestQueueHandle);
433 
434             if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
435             {
436                 throw new HttpListenerException((int)statusCode);
437             }
438 
439             // Disabling callbacks when IO operation completes synchronously (returns ErrorCodes.ERROR_SUCCESS)
440             if (SkipIOCPCallbackOnSuccess &&
441                 !Interop.Kernel32.SetFileCompletionNotificationModes(
442                     requestQueueHandle,
443                     Interop.Kernel32.FileCompletionNotificationModes.SkipCompletionPortOnSuccess |
444                     Interop.Kernel32.FileCompletionNotificationModes.SkipSetEventOnHandle))
445             {
446                 throw new HttpListenerException(Marshal.GetLastWin32Error());
447             }
448 
449             _requestQueueHandle = requestQueueHandle;
450         }
451 
CloseRequestQueueHandle()452         private unsafe void CloseRequestQueueHandle()
453         {
454             if ((_requestQueueHandle != null) && (!_requestQueueHandle.IsInvalid))
455             {
456                 if (NetEventSource.IsEnabled) NetEventSource.Info($"Dispose ThreadPoolBoundHandle: {_requestQueueBoundHandle}");
457                 _requestQueueBoundHandle?.Dispose();
458                 _requestQueueHandle.Dispose();
459             }
460         }
461 
Abort()462         public void Abort()
463         {
464             if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
465 
466             lock (_internalLock)
467             {
468                 try
469                 {
470                     if (_state == State.Closed)
471                     {
472                         return;
473                     }
474 
475                     // Just detach and free resources. Don't call Stop (which may throw). Behave like v1: just
476                     // clean up resources.
477                     if (_state == State.Started)
478                     {
479                         DetachRequestQueueFromUrlGroup();
480                         CloseRequestQueueHandle();
481                     }
482                     CleanupV2Config();
483                 }
484                 catch (Exception exception)
485                 {
486                     if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"Abort {exception}");
487                     throw;
488                 }
489                 finally
490                 {
491                     _state = State.Closed;
492                     if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
493                 }
494             }
495         }
496 
Dispose()497         private void Dispose()
498         {
499             if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
500 
501             lock (_internalLock)
502             {
503                 try
504                 {
505                     if (_state == State.Closed)
506                     {
507                         return;
508                     }
509 
510                     Stop();
511                     CleanupV2Config();
512                 }
513                 catch (Exception exception)
514                 {
515                     if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"Dispose {exception}");
516                     throw;
517                 }
518                 finally
519                 {
520                     _state = State.Closed;
521                     if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
522                 }
523             }
524         }
525 
RemovePrefixCore(string uriPrefix)526         private void RemovePrefixCore(string uriPrefix)
527         {
528             Interop.HttpApi.HttpRemoveUrlFromUrlGroup(_urlGroupId, uriPrefix, 0);
529         }
530 
AddAllPrefixes()531         private void AddAllPrefixes()
532         {
533             // go through the uri list and register for each one of them
534             if (_uriPrefixes.Count > 0)
535             {
536                 foreach (string registeredPrefix in _uriPrefixes.Values)
537                 {
538                     AddPrefixCore(registeredPrefix);
539                 }
540             }
541         }
542 
AddPrefixCore(string registeredPrefix)543         private void AddPrefixCore(string registeredPrefix)
544         {
545             if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Calling Interop.HttpApi.HttpAddUrl[ToUrlGroup]");
546 
547             uint statusCode = Interop.HttpApi.HttpAddUrlToUrlGroup(
548                                   _urlGroupId,
549                                   registeredPrefix,
550                                   0,
551                                   0);
552             if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
553             {
554                 if (statusCode == Interop.HttpApi.ERROR_ALREADY_EXISTS)
555                     throw new HttpListenerException((int)statusCode, SR.Format(SR.net_listener_already, registeredPrefix));
556                 else
557                     throw new HttpListenerException((int)statusCode);
558             }
559         }
560 
GetContext()561         public HttpListenerContext GetContext()
562         {
563             if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
564 
565             SyncRequestContext memoryBlob = null;
566             HttpListenerContext httpContext = null;
567             bool stoleBlob = false;
568 
569             try
570             {
571                 CheckDisposed();
572                 if (_state == State.Stopped)
573                 {
574                     throw new InvalidOperationException(SR.Format(SR.net_listener_mustcall, "Start()"));
575                 }
576                 if (_uriPrefixes.Count == 0)
577                 {
578                     throw new InvalidOperationException(SR.Format(SR.net_listener_mustcall, "AddPrefix()"));
579                 }
580                 uint statusCode = Interop.HttpApi.ERROR_SUCCESS;
581                 uint size = 4096;
582                 ulong requestId = 0;
583                 memoryBlob = new SyncRequestContext((int)size);
584                 for (;;)
585                 {
586                     for (;;)
587                     {
588                         if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Calling Interop.HttpApi.HttpReceiveHttpRequest RequestId: {requestId}");
589                         uint bytesTransferred = 0;
590                         statusCode =
591                             Interop.HttpApi.HttpReceiveHttpRequest(
592                                 _requestQueueHandle,
593                                 requestId,
594                                 (uint)Interop.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY,
595                                 memoryBlob.RequestBlob,
596                                 size,
597                                 &bytesTransferred,
598                                 null);
599 
600                         if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Call to Interop.HttpApi.HttpReceiveHttpRequest returned:" + statusCode);
601 
602                         if (statusCode == Interop.HttpApi.ERROR_INVALID_PARAMETER && requestId != 0)
603                         {
604                             // we might get this if somebody stole our RequestId,
605                             // we need to start all over again but we can reuse the buffer we just allocated
606                             requestId = 0;
607                             continue;
608                         }
609                         else if (statusCode == Interop.HttpApi.ERROR_MORE_DATA)
610                         {
611                             // the buffer was not big enough to fit the headers, we need
612                             // to read the RequestId returned, allocate a new buffer of the required size
613                             size = bytesTransferred;
614                             requestId = memoryBlob.RequestBlob->RequestId;
615                             memoryBlob.Reset(checked((int)size));
616                             continue;
617                         }
618                         break;
619                     }
620                     if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
621                     {
622                         // someother bad error, return values are:
623                         // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
624                         throw new HttpListenerException((int)statusCode);
625                     }
626 
627                     if (ValidateRequest(memoryBlob))
628                     {
629                         // We need to hook up our authentication handling code here.
630                         httpContext = HandleAuthentication(memoryBlob, out stoleBlob);
631                     }
632 
633                     if (stoleBlob)
634                     {
635                         // The request has been handed to the user, which means this code can't reuse the blob.  Reset it here.
636                         memoryBlob = null;
637                         stoleBlob = false;
638                     }
639                     if (NetEventSource.IsEnabled) NetEventSource.Info(this, ":HandleAuthentication() returned httpContext" + httpContext);
640                     // if the request survived authentication, return it to the user
641                     if (httpContext != null)
642                     {
643                         return httpContext;
644                     }
645 
646                     // HandleAuthentication may have cleaned this up.
647                     if (memoryBlob == null)
648                     {
649                         memoryBlob = new SyncRequestContext(checked((int)size));
650                     }
651 
652                     requestId = 0;
653                 }
654             }
655             catch (Exception exception)
656             {
657                 if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"{exception}");
658                 throw;
659             }
660             finally
661             {
662                 if (memoryBlob != null && !stoleBlob)
663                 {
664                     memoryBlob.ReleasePins();
665                     memoryBlob.Close();
666                 }
667                 if (NetEventSource.IsEnabled) NetEventSource.Exit(this, "RequestTraceIdentifier: " + (httpContext != null ? httpContext.Request.RequestTraceIdentifier.ToString() : "<null>"));
668             }
669         }
670 
ValidateRequest(RequestContextBase requestMemory)671         internal unsafe bool ValidateRequest(RequestContextBase requestMemory)
672         {
673             // Block potential DOS attacks
674             if (requestMemory.RequestBlob->Headers.UnknownHeaderCount > UnknownHeaderLimit)
675             {
676                 SendError(requestMemory.RequestBlob->RequestId, HttpStatusCode.BadRequest, null);
677                 return false;
678             }
679             return true;
680         }
681 
BeginGetContext(AsyncCallback callback, object state)682         public IAsyncResult BeginGetContext(AsyncCallback callback, object state)
683         {
684             if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
685             ListenerAsyncResult asyncResult = null;
686             try
687             {
688                 CheckDisposed();
689                 if (_state == State.Stopped)
690                 {
691                     throw new InvalidOperationException(SR.Format(SR.net_listener_mustcall, "Start()"));
692                 }
693                 // prepare the ListenerAsyncResult object (this will have it's own
694                 // event that the user can wait on for IO completion - which means we
695                 // need to signal it when IO completes)
696                 asyncResult = new ListenerAsyncResult(this, state, callback);
697                 uint statusCode = asyncResult.QueueBeginGetContext();
698                 if (statusCode != Interop.HttpApi.ERROR_SUCCESS &&
699                     statusCode != Interop.HttpApi.ERROR_IO_PENDING)
700                 {
701                     // someother bad error, return values are:
702                     // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
703                     throw new HttpListenerException((int)statusCode);
704                 }
705             }
706             catch (Exception exception)
707             {
708                 if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"BeginGetContext {exception}");
709                 throw;
710             }
711             finally
712             {
713                 if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
714             }
715 
716             return asyncResult;
717         }
718 
EndGetContext(IAsyncResult asyncResult)719         public HttpListenerContext EndGetContext(IAsyncResult asyncResult)
720         {
721             if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
722             HttpListenerContext httpContext = null;
723             try
724             {
725                 CheckDisposed();
726                 if (asyncResult == null)
727                 {
728                     throw new ArgumentNullException(nameof(asyncResult));
729                 }
730                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"asyncResult: {asyncResult}");
731                 ListenerAsyncResult castedAsyncResult = asyncResult as ListenerAsyncResult;
732                 if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this)
733                 {
734                     throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult));
735                 }
736                 if (castedAsyncResult.EndCalled)
737                 {
738                     throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, nameof(EndGetContext)));
739                 }
740                 castedAsyncResult.EndCalled = true;
741                 httpContext = castedAsyncResult.InternalWaitForCompletion() as HttpListenerContext;
742                 if (httpContext == null)
743                 {
744                     Debug.Assert(castedAsyncResult.Result is Exception, "EndGetContext|The result is neither a HttpListenerContext nor an Exception.");
745                     ExceptionDispatchInfo.Throw(castedAsyncResult.Result as Exception);
746                 }
747             }
748             catch (Exception exception)
749             {
750                 if (NetEventSource.IsEnabled) NetEventSource.Error(this, $"EndGetContext {exception}");
751                 throw;
752             }
753             finally
754             {
755                 if (NetEventSource.IsEnabled) NetEventSource.Exit(this, "EndGetContext " + httpContext == null ? "<no context>" : "HttpListenerContext" + httpContext.ToString() + " RequestTraceIdentifier#" + httpContext.Request.RequestTraceIdentifier);
756             }
757             return httpContext;
758         }
759 
HandleAuthentication(RequestContextBase memoryBlob, out bool stoleBlob)760         internal HttpListenerContext HandleAuthentication(RequestContextBase memoryBlob, out bool stoleBlob)
761         {
762             if (NetEventSource.IsEnabled) NetEventSource.Info(this, "HandleAuthentication() memoryBlob:0x" + ((IntPtr)memoryBlob.RequestBlob).ToString("x"));
763 
764             string challenge = null;
765             stoleBlob = false;
766 
767             // Some things we need right away.  Lift them out now while it's convenient.
768             string verb = Interop.HttpApi.GetVerb(memoryBlob.RequestBlob);
769             string authorizationHeader = Interop.HttpApi.GetKnownHeader(memoryBlob.RequestBlob, (int)HttpRequestHeader.Authorization);
770             ulong connectionId = memoryBlob.RequestBlob->ConnectionId;
771             ulong requestId = memoryBlob.RequestBlob->RequestId;
772             bool isSecureConnection = memoryBlob.RequestBlob->pSslInfo != null;
773 
774             if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"HandleAuthentication() authorizationHeader: ({authorizationHeader})");
775 
776             // if the app has turned on AuthPersistence, an anonymous request might
777             // be authenticated by virtue of it coming on a connection that was
778             // previously authenticated.
779             // assurance that we do this only for NTLM/Negotiate is not here, but in the
780             // code that caches WindowsIdentity instances in the Dictionary.
781             DisconnectAsyncResult disconnectResult;
782             DisconnectResults.TryGetValue(connectionId, out disconnectResult);
783             if (UnsafeConnectionNtlmAuthentication)
784             {
785                 if (authorizationHeader == null)
786                 {
787                     WindowsPrincipal principal = disconnectResult?.AuthenticatedConnection;
788                     if (principal != null)
789                     {
790                         if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Principal: {principal} principal.Identity.Name: {principal.Identity.Name} creating request");
791                         stoleBlob = true;
792                         HttpListenerContext ntlmContext = new HttpListenerContext(this, memoryBlob);
793                         ntlmContext.SetIdentity(principal, null);
794                         ntlmContext.Request.ReleasePins();
795                         return ntlmContext;
796                     }
797                 }
798                 else
799                 {
800                     // They sent an authorization - destroy their previous credentials.
801                     if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Clearing principal cache");
802                     if (disconnectResult != null)
803                     {
804                         disconnectResult.AuthenticatedConnection = null;
805                     }
806                 }
807             }
808 
809             // Figure out what schemes we're allowing, what context we have.
810             stoleBlob = true;
811             HttpListenerContext httpContext = null;
812             NTAuthentication oldContext = null;
813             NTAuthentication newContext = null;
814             NTAuthentication context = null;
815             AuthenticationSchemes headerScheme = AuthenticationSchemes.None;
816             AuthenticationSchemes authenticationScheme = AuthenticationSchemes;
817             ExtendedProtectionPolicy extendedProtectionPolicy = _extendedProtectionPolicy;
818             try
819             {
820                 // Take over handling disconnects for now.
821                 if (disconnectResult != null && !disconnectResult.StartOwningDisconnectHandling())
822                 {
823                     // Just disconnected just then.  Pretend we didn't see the disconnectResult.
824                     disconnectResult = null;
825                 }
826 
827                 // Pick out the old context now.  By default, it'll be removed in the finally, unless context is set somewhere.
828                 if (disconnectResult != null)
829                 {
830                     oldContext = disconnectResult.Session;
831                 }
832 
833                 httpContext = new HttpListenerContext(this, memoryBlob);
834 
835                 AuthenticationSchemeSelector authenticationSelector = _authenticationDelegate;
836                 if (authenticationSelector != null)
837                 {
838                     try
839                     {
840                         httpContext.Request.ReleasePins();
841                         authenticationScheme = authenticationSelector(httpContext.Request);
842                         // Cache the results of authenticationSelector (if any)
843                         httpContext.AuthenticationSchemes = authenticationScheme;
844                         if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"AuthenticationScheme: {authenticationScheme}");
845                     }
846                     catch (Exception exception) when (!ExceptionCheck.IsFatal(exception))
847                     {
848                         if (NetEventSource.IsEnabled)
849                         {
850                             NetEventSource.Error(this, SR.Format(SR.net_log_listener_delegate_exception, exception));
851                             NetEventSource.Info(this, $"authenticationScheme: {authenticationScheme}");
852                         }
853                         SendError(requestId, HttpStatusCode.InternalServerError, null);
854                         httpContext.Close();
855                         return null;
856                     }
857                 }
858                 else
859                 {
860                     // We didn't give the request to the user yet, so we haven't lost control of the unmanaged blob and can
861                     // continue to reuse the buffer.
862                     stoleBlob = false;
863                 }
864 
865                 ExtendedProtectionSelector extendedProtectionSelector = _extendedProtectionSelectorDelegate;
866                 if (extendedProtectionSelector != null)
867                 {
868                     extendedProtectionPolicy = extendedProtectionSelector(httpContext.Request);
869 
870                     if (extendedProtectionPolicy == null)
871                     {
872                         extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never);
873                     }
874                     // Cache the results of extendedProtectionSelector (if any)
875                     httpContext.ExtendedProtectionPolicy = extendedProtectionPolicy;
876                 }
877 
878                 // Then figure out what scheme they're trying (if any are allowed)
879                 int index = -1;
880                 if (authorizationHeader != null && (authenticationScheme & ~AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None)
881                 {
882                     // Find the end of the scheme name.  Trust that HTTP.SYS parsed out just our header ok.
883                     for (index = 0; index < authorizationHeader.Length; index++)
884                     {
885                         if (authorizationHeader[index] == ' ' || authorizationHeader[index] == '\t' ||
886                             authorizationHeader[index] == '\r' || authorizationHeader[index] == '\n')
887                         {
888                             break;
889                         }
890                     }
891 
892                     // Currently only allow one Authorization scheme/header per request.
893                     if (index < authorizationHeader.Length)
894                     {
895                         if ((authenticationScheme & AuthenticationSchemes.Negotiate) != AuthenticationSchemes.None &&
896                             string.Compare(authorizationHeader, 0, AuthenticationTypes.Negotiate, 0, index, StringComparison.OrdinalIgnoreCase) == 0)
897                         {
898                             headerScheme = AuthenticationSchemes.Negotiate;
899                         }
900                         else if ((authenticationScheme & AuthenticationSchemes.Ntlm) != AuthenticationSchemes.None &&
901                             string.Compare(authorizationHeader, 0, AuthenticationTypes.NTLM, 0, index, StringComparison.OrdinalIgnoreCase) == 0)
902                         {
903                             headerScheme = AuthenticationSchemes.Ntlm;
904                         }
905                         else if ((authenticationScheme & AuthenticationSchemes.Basic) != AuthenticationSchemes.None &&
906                             string.Compare(authorizationHeader, 0, AuthenticationTypes.Basic, 0, index, StringComparison.OrdinalIgnoreCase) == 0)
907                         {
908                             headerScheme = AuthenticationSchemes.Basic;
909                         }
910                         else
911                         {
912                             if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_unsupported_authentication_scheme, authorizationHeader, authenticationScheme));
913                         }
914                     }
915                 }
916 
917                 // httpError holds the error we will return if an Authorization header is present but can't be authenticated
918                 HttpStatusCode httpError = HttpStatusCode.InternalServerError;
919                 bool error = false;
920 
921                 // See if we found an acceptable auth header
922                 if (headerScheme == AuthenticationSchemes.None)
923                 {
924                     if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_unmatched_authentication_scheme, authenticationScheme.ToString(), (authorizationHeader == null ? "<null>" : authorizationHeader)));
925 
926                     // If anonymous is allowed, just return the context.  Otherwise go for the 401.
927                     if ((authenticationScheme & AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None)
928                     {
929                         if (!stoleBlob)
930                         {
931                             stoleBlob = true;
932                             httpContext.Request.ReleasePins();
933                         }
934                         return httpContext;
935                     }
936 
937                     httpError = HttpStatusCode.Unauthorized;
938                     httpContext.Request.DetachBlob(memoryBlob);
939                     httpContext.Close();
940                     httpContext = null;
941                 }
942                 else
943                 {
944                     // Perform Authentication
945                     byte[] bytes = null;
946                     byte[] decodedOutgoingBlob = null;
947                     string outBlob = null;
948 
949                     // Find the beginning of the blob.  Trust that HTTP.SYS parsed out just our header ok.
950                     for (index++; index < authorizationHeader.Length; index++)
951                     {
952                         if (authorizationHeader[index] != ' ' && authorizationHeader[index] != '\t' &&
953                             authorizationHeader[index] != '\r' && authorizationHeader[index] != '\n')
954                         {
955                             break;
956                         }
957                     }
958                     string inBlob = index < authorizationHeader.Length ? authorizationHeader.Substring(index) : "";
959 
960                     IPrincipal principal = null;
961                     SecurityStatusPal statusCodeNew;
962                     ChannelBinding binding;
963                     if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Performing Authentication headerScheme: {headerScheme}");
964                     switch (headerScheme)
965                     {
966                         case AuthenticationSchemes.Negotiate:
967                         case AuthenticationSchemes.Ntlm:
968                             if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"context: {oldContext} for connectionId: {connectionId}");
969 
970                             string package = headerScheme == AuthenticationSchemes.Ntlm ? NegotiationInfoClass.NTLM : NegotiationInfoClass.Negotiate;
971                             if (oldContext != null && oldContext.Package == package)
972                             {
973                                 context = oldContext;
974                             }
975                             else
976                             {
977                                 binding = GetChannelBinding(connectionId, isSecureConnection, extendedProtectionPolicy);
978                                 ContextFlagsPal contextFlags = GetContextFlags(extendedProtectionPolicy, isSecureConnection);
979                                 context = new NTAuthentication(true, package, CredentialCache.DefaultNetworkCredentials, null, contextFlags, binding);
980                             }
981 
982                             try
983                             {
984                                 bytes = Convert.FromBase64String(inBlob);
985                             }
986                             catch (FormatException)
987                             {
988                                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"FormatException from FormBase64String");
989                                 httpError = HttpStatusCode.BadRequest;
990                                 error = true;
991                             }
992                             if (!error)
993                             {
994                                 decodedOutgoingBlob = context.GetOutgoingBlob(bytes, false, out statusCodeNew);
995                                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"GetOutgoingBlob returned IsCompleted: {context.IsCompleted} and statusCodeNew: {statusCodeNew}");
996                                 error = !context.IsValidContext;
997                                 if (error)
998                                 {
999                                     // SSPI Workaround
1000                                     // If a client sends up a blob on the initial request, Negotiate returns SEC_E_INVALID_HANDLE
1001                                     // when it should return SEC_E_INVALID_TOKEN.
1002                                     if (statusCodeNew.ErrorCode == SecurityStatusPalErrorCode.InvalidHandle && oldContext == null && bytes != null && bytes.Length > 0)
1003                                     {
1004                                         statusCodeNew = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidToken);
1005                                     }
1006 
1007                                     httpError = HttpStatusFromSecurityStatus(statusCodeNew.ErrorCode);
1008                                 }
1009                             }
1010 
1011                             if (decodedOutgoingBlob != null)
1012                             {
1013                                 outBlob = Convert.ToBase64String(decodedOutgoingBlob);
1014                             }
1015 
1016                             if (!error)
1017                             {
1018                                 if (context.IsCompleted)
1019                                 {
1020                                     SecurityContextTokenHandle userContext = null;
1021                                     try
1022                                     {
1023                                         if (!CheckSpn(context, isSecureConnection, extendedProtectionPolicy))
1024                                         {
1025                                             httpError = HttpStatusCode.Unauthorized;
1026                                         }
1027                                         else
1028                                         {
1029                                             httpContext.Request.ServiceName = context.ClientSpecifiedSpn;
1030 
1031                                             SafeDeleteContext securityContext = context.GetContext(out statusCodeNew);
1032                                             if (statusCodeNew.ErrorCode != SecurityStatusPalErrorCode.OK)
1033                                             {
1034                                                 if (NetEventSource.IsEnabled)
1035                                                 {
1036                                                     NetEventSource.Info(this,
1037                                                         $"HandleAuthentication GetContextToken failed with statusCodeNew: {statusCodeNew}");
1038                                                 }
1039 
1040                                                 httpError = HttpStatusFromSecurityStatus(statusCodeNew.ErrorCode);
1041                                             }
1042                                             else
1043                                             {
1044                                                 SSPIWrapper.QuerySecurityContextToken(GlobalSSPI.SSPIAuth, securityContext, out userContext);
1045 
1046                                                 if (NetEventSource.IsEnabled)
1047                                                 {
1048                                                     NetEventSource.Info(this,
1049                                                         $"HandleAuthentication creating new WindowsIdentity from user context: {userContext.DangerousGetHandle().ToString("x8")}");
1050                                                 }
1051 
1052                                                 WindowsPrincipal windowsPrincipal = new WindowsPrincipal(
1053                                                     new WindowsIdentity(userContext.DangerousGetHandle(), context.ProtocolName));
1054 
1055                                                 principal = windowsPrincipal;
1056                                                 // if appropriate, cache this credential on this connection
1057                                                 if (UnsafeConnectionNtlmAuthentication && context.ProtocolName == NegotiationInfoClass.NTLM)
1058                                                 {
1059                                                     if (NetEventSource.IsEnabled)
1060                                                     {
1061                                                         NetEventSource.Info(this,
1062                                                             $"HandleAuthentication inserting principal: {principal} for connectionId: {connectionId}");
1063                                                     }
1064 
1065                                                     // We may need to call WaitForDisconnect.
1066                                                     if (disconnectResult == null)
1067                                                     {
1068                                                         RegisterForDisconnectNotification(connectionId, ref disconnectResult);
1069                                                     }
1070                                                     if (disconnectResult != null)
1071                                                     {
1072                                                         lock ((DisconnectResults as ICollection).SyncRoot)
1073                                                         {
1074                                                             if (UnsafeConnectionNtlmAuthentication)
1075                                                             {
1076                                                                 disconnectResult.AuthenticatedConnection = windowsPrincipal;
1077                                                             }
1078                                                         }
1079                                                     }
1080                                                     else
1081                                                     {
1082                                                         // Registration failed - UnsafeConnectionNtlmAuthentication ignored.
1083                                                         if (NetEventSource.IsEnabled)
1084                                                         {
1085                                                             NetEventSource.Info(this, $"HandleAuthentication RegisterForDisconnectNotification failed.");
1086                                                         }
1087                                                     }
1088                                                 }
1089                                             }
1090                                         }
1091                                     }
1092                                     finally
1093                                     {
1094                                         if (userContext != null)
1095                                         {
1096                                             userContext.Close();
1097                                         }
1098                                     }
1099                                 }
1100                                 else
1101                                 {
1102                                     // auth incomplete
1103                                     newContext = context;
1104 
1105                                     challenge = (headerScheme == AuthenticationSchemes.Ntlm ? NegotiationInfoClass.NTLM : NegotiationInfoClass.Negotiate);
1106                                     if (!String.IsNullOrEmpty(outBlob))
1107                                     {
1108                                         challenge += " " + outBlob;
1109                                     }
1110                                 }
1111                             }
1112                             break;
1113 
1114                         case AuthenticationSchemes.Basic:
1115                             try
1116                             {
1117                                 bytes = Convert.FromBase64String(inBlob);
1118 
1119                                 inBlob = WebHeaderEncoding.GetString(bytes, 0, bytes.Length);
1120                                 index = inBlob.IndexOf(':');
1121 
1122                                 if (index != -1)
1123                                 {
1124                                     string userName = inBlob.Substring(0, index);
1125                                     string password = inBlob.Substring(index + 1);
1126                                     if (NetEventSource.IsEnabled)
1127                                     {
1128                                         NetEventSource.Info(this, $"Basic Identity found, userName: {userName}");
1129                                     }
1130 
1131                                     principal = new GenericPrincipal(new HttpListenerBasicIdentity(userName, password), null);
1132                                 }
1133                                 else
1134                                 {
1135                                     httpError = HttpStatusCode.BadRequest;
1136                                 }
1137                             }
1138                             catch (FormatException)
1139                             {
1140                                 if (NetEventSource.IsEnabled)
1141                                 {
1142                                     NetEventSource.Info(this, $"FromBase64String threw a FormatException.");
1143                                 }
1144                             }
1145                             break;
1146                     }
1147 
1148                     if (principal != null)
1149                     {
1150                         if (NetEventSource.IsEnabled)
1151                         {
1152                             NetEventSource.Info(this, $"Got principal: {principal}, IdentityName: {principal.Identity.Name} for creating request.");
1153                         }
1154 
1155                         httpContext.SetIdentity(principal, outBlob);
1156                     }
1157                     else
1158                     {
1159                         if (NetEventSource.IsEnabled)
1160                         {
1161                             NetEventSource.Info(this, "Handshake has failed.");
1162                         }
1163 
1164                         httpContext.Request.DetachBlob(memoryBlob);
1165                         httpContext.Close();
1166                         httpContext = null;
1167                     }
1168                 }
1169 
1170                 // if we're not giving a request to the application, we need to send an error
1171                 ArrayList challenges = null;
1172                 if (httpContext == null)
1173                 {
1174                     // If we already have a challenge, just use it.  Otherwise put a challenge for each acceptable scheme.
1175                     if (challenge != null)
1176                     {
1177                         AddChallenge(ref challenges, challenge);
1178                     }
1179                     else
1180                     {
1181                         // We're starting over.  Any context SSPI might have wanted us to keep is useless.
1182                         if (newContext != null)
1183                         {
1184                             if (newContext == context)
1185                             {
1186                                 context = null;
1187                             }
1188 
1189                             if (newContext != oldContext)
1190                             {
1191                                 NTAuthentication toClose = newContext;
1192                                 newContext = null;
1193                                 toClose.CloseContext();
1194                             }
1195                             else
1196                             {
1197                                 newContext = null;
1198                             }
1199                         }
1200 
1201                         // If we're sending something besides 401, do it here.
1202                         if (httpError != HttpStatusCode.Unauthorized)
1203                         {
1204                             if (NetEventSource.IsEnabled) NetEventSource.Info(this, "ConnectionId:" + connectionId + " because of error:" + httpError.ToString());
1205                             SendError(requestId, httpError, null);
1206                             return null;
1207                         }
1208 
1209                         challenges = BuildChallenge(authenticationScheme, connectionId, out newContext,
1210                             extendedProtectionPolicy, isSecureConnection);
1211                     }
1212                 }
1213 
1214                 // Check if we need to call WaitForDisconnect, because if we do and it fails, we want to send a 500 instead.
1215                 if (disconnectResult == null && newContext != null)
1216                 {
1217                     RegisterForDisconnectNotification(connectionId, ref disconnectResult);
1218 
1219                     // Failed - send 500.
1220                     if (disconnectResult == null)
1221                     {
1222                         if (newContext != null)
1223                         {
1224                             if (newContext == context)
1225                             {
1226                                 context = null;
1227                             }
1228 
1229                             if (newContext != oldContext)
1230                             {
1231                                 NTAuthentication toClose = newContext;
1232                                 newContext = null;
1233                                 toClose.CloseContext();
1234                             }
1235                             else
1236                             {
1237                                 newContext = null;
1238                             }
1239                         }
1240 
1241                         if (NetEventSource.IsEnabled) NetEventSource.Info(this, "connectionId:" + connectionId + " because of failed HttpWaitForDisconnect");
1242                         SendError(requestId, HttpStatusCode.InternalServerError, null);
1243                         httpContext.Request.DetachBlob(memoryBlob);
1244                         httpContext.Close();
1245                         return null;
1246                     }
1247                 }
1248 
1249                 // Update Session if necessary.
1250                 if (oldContext != newContext)
1251                 {
1252                     if (oldContext == context)
1253                     {
1254                         // Prevent the finally from closing this twice.
1255                         context = null;
1256                     }
1257 
1258                     NTAuthentication toClose = oldContext;
1259                     oldContext = newContext;
1260                     disconnectResult.Session = newContext;
1261 
1262                     if (toClose != null)
1263                     {
1264                         toClose.CloseContext();
1265                     }
1266                 }
1267 
1268                 // Send the 401 here.
1269                 if (httpContext == null)
1270                 {
1271                     SendError(requestId, challenges != null && challenges.Count > 0 ? HttpStatusCode.Unauthorized : HttpStatusCode.Forbidden, challenges);
1272                     if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Scheme:" + authenticationScheme);
1273                     return null;
1274                 }
1275 
1276                 if (!stoleBlob)
1277                 {
1278                     stoleBlob = true;
1279                     httpContext.Request.ReleasePins();
1280                 }
1281                 return httpContext;
1282             }
1283             catch
1284             {
1285                 if (httpContext != null)
1286                 {
1287                     httpContext.Request.DetachBlob(memoryBlob);
1288                     httpContext.Close();
1289                 }
1290                 if (newContext != null)
1291                 {
1292                     if (newContext == context)
1293                     {
1294                         // Prevent the finally from closing this twice.
1295                         context = null;
1296                     }
1297 
1298                     if (newContext != oldContext)
1299                     {
1300                         NTAuthentication toClose = newContext;
1301                         newContext = null;
1302                         toClose.CloseContext();
1303                     }
1304                     else
1305                     {
1306                         newContext = null;
1307                     }
1308                 }
1309                 throw;
1310             }
1311             finally
1312             {
1313                 try
1314                 {
1315                     // Clean up the previous context if necessary.
1316                     if (oldContext != null && oldContext != newContext)
1317                     {
1318                         // Clear out Session if it wasn't already.
1319                         if (newContext == null && disconnectResult != null)
1320                         {
1321                             disconnectResult.Session = null;
1322                         }
1323 
1324                         oldContext.CloseContext();
1325                     }
1326 
1327                     // Delete any context created but not stored.
1328                     if (context != null && oldContext != context && newContext != context)
1329                     {
1330                         context.CloseContext();
1331                     }
1332                 }
1333                 finally
1334                 {
1335                     // Check if the connection got deleted while in this method, and clear out the hashtables if it did.
1336                     // In a nested finally because if this doesn't happen, we leak.
1337                     if (disconnectResult != null)
1338                     {
1339                         disconnectResult.FinishOwningDisconnectHandling();
1340                     }
1341                 }
1342             }
1343         }
1344 
1345         // Using the configured Auth schemes, populate the auth challenge headers. This is for scenarios where
1346         // Anonymous access is allowed for some resources, but the server later determines that authorization
1347         // is required for this request.
SetAuthenticationHeaders(HttpListenerContext context)1348         internal void SetAuthenticationHeaders(HttpListenerContext context)
1349         {
1350             Debug.Assert(context != null, "Null Context");
1351 
1352             HttpListenerRequest request = context.Request;
1353             HttpListenerResponse response = context.Response;
1354 
1355             // We use the cached results from the delegates so that we don't have to call them again here.
1356             NTAuthentication newContext;
1357             ArrayList challenges = BuildChallenge(context.AuthenticationSchemes, request._connectionId,
1358                 out newContext, context.ExtendedProtectionPolicy, request.IsSecureConnection);
1359 
1360             // Setting 401 without setting WWW-Authenticate is a protocol violation
1361             // but throwing from HttpListener would be a breaking change.
1362             if (challenges != null) // null == Anonymous
1363             {
1364                 // Add the new WWW-Authenticate headers
1365                 foreach (string challenge in challenges)
1366                 {
1367                     response.Headers.Add(HttpKnownHeaderNames.WWWAuthenticate, challenge);
1368                 }
1369             }
1370         }
1371 
GetChannelBinding(ulong connectionId, bool isSecureConnection, ExtendedProtectionPolicy policy)1372         private ChannelBinding GetChannelBinding(ulong connectionId, bool isSecureConnection, ExtendedProtectionPolicy policy)
1373         {
1374             if (policy.PolicyEnforcement == PolicyEnforcement.Never)
1375             {
1376                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, SR.net_log_listener_no_cbt_disabled);
1377                 return null;
1378             }
1379 
1380             if (!isSecureConnection)
1381             {
1382                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, SR.net_log_listener_no_cbt_http);
1383                 return null;
1384             }
1385 
1386             if (policy.ProtectionScenario == ProtectionScenario.TrustedProxy)
1387             {
1388                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, SR.net_log_listener_no_cbt_trustedproxy);
1389                 return null;
1390             }
1391 
1392             ChannelBinding result = GetChannelBindingFromTls(connectionId);
1393 
1394             if (NetEventSource.IsEnabled && result != null)
1395                 NetEventSource.Info(this, "GetChannelBindingFromTls returned null even though OS supposedly supports Extended Protection");
1396             if (NetEventSource.IsEnabled) NetEventSource.Info(this, SR.net_log_listener_cbt);
1397             return result;
1398         }
1399 
CheckSpn(NTAuthentication context, bool isSecureConnection, ExtendedProtectionPolicy policy)1400         private bool CheckSpn(NTAuthentication context, bool isSecureConnection, ExtendedProtectionPolicy policy)
1401         {
1402             // Kerberos does SPN check already in ASC
1403             if (context.IsKerberos)
1404             {
1405                 if (NetEventSource.IsEnabled)
1406                 {
1407                     NetEventSource.Info(this, SR.net_log_listener_no_spn_kerberos);
1408                 }
1409                 return true;
1410             }
1411 
1412             // Don't check the SPN if Extended Protection is off or we already checked the CBT
1413             if (policy.PolicyEnforcement == PolicyEnforcement.Never)
1414             {
1415                 if (NetEventSource.IsEnabled)
1416                 {
1417                     NetEventSource.Info(this, SR.net_log_listener_no_spn_disabled);
1418                 }
1419                 return true;
1420             }
1421 
1422             if (ScenarioChecksChannelBinding(isSecureConnection, policy.ProtectionScenario))
1423             {
1424                 if (NetEventSource.IsEnabled)
1425                 {
1426                     NetEventSource.Info(this, SR.net_log_listener_no_spn_cbt);
1427                 }
1428                 return true;
1429             }
1430 
1431             string clientSpn = context.ClientSpecifiedSpn;
1432 
1433             // An empty SPN is only allowed in the WhenSupported case
1434             if (String.IsNullOrEmpty(clientSpn))
1435             {
1436                 if (policy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
1437                 {
1438                     if (NetEventSource.IsEnabled)
1439                     {
1440                         NetEventSource.Info(this,
1441                             SR.net_log_listener_no_spn_whensupported);
1442                     }
1443                     return true;
1444                 }
1445                 else
1446                 {
1447                     if (NetEventSource.IsEnabled)
1448                     {
1449                         NetEventSource.Info(this,
1450                             SR.net_log_listener_spn_failed_always);
1451                     }
1452                     return false;
1453                 }
1454             }
1455             else if (string.Equals(clientSpn, "http/localhost", StringComparison.OrdinalIgnoreCase))
1456             {
1457                 if (NetEventSource.IsEnabled)
1458                 {
1459                     NetEventSource.Info(this, SR.net_log_listener_no_spn_loopback);
1460                 }
1461 
1462                 return true;
1463             }
1464             else
1465             {
1466                 if (NetEventSource.IsEnabled)
1467                 {
1468                     NetEventSource.Info(this, SR.net_log_listener_spn, clientSpn);
1469                 }
1470 
1471                 ServiceNameCollection serviceNames = GetServiceNames(policy);
1472 
1473                 bool found = serviceNames.Contains(clientSpn);
1474 
1475                 if (NetEventSource.IsEnabled)
1476                 {
1477                     if (found)
1478                     {
1479                         NetEventSource.Info(this, SR.net_log_listener_spn_passed);
1480                     }
1481                     else
1482                     {
1483                         NetEventSource.Info(this, SR.net_log_listener_spn_failed);
1484 
1485                         if (serviceNames.Count == 0)
1486                         {
1487                             if (NetEventSource.IsEnabled)
1488                             {
1489                                 NetEventSource.Info(this, SR.net_log_listener_spn_failed_empty);
1490                             }
1491                         }
1492                         else
1493                         {
1494                             NetEventSource.Info(this, SR.net_log_listener_spn_failed_dump);
1495 
1496                             foreach (string serviceName in serviceNames)
1497                             {
1498                                 NetEventSource.Info(this, "\t" + serviceName);
1499                             }
1500                         }
1501                     }
1502                 }
1503 
1504                 return found;
1505             }
1506         }
1507 
GetServiceNames(ExtendedProtectionPolicy policy)1508         private ServiceNameCollection GetServiceNames(ExtendedProtectionPolicy policy)
1509         {
1510             ServiceNameCollection serviceNames;
1511 
1512             if (policy.CustomServiceNames == null)
1513             {
1514                 if (_defaultServiceNames.ServiceNames.Count == 0)
1515                 {
1516                     throw new InvalidOperationException(SR.net_listener_no_spns);
1517                 }
1518                 serviceNames = _defaultServiceNames.ServiceNames;
1519             }
1520             else
1521             {
1522                 serviceNames = policy.CustomServiceNames;
1523             }
1524             return serviceNames;
1525         }
1526 
ScenarioChecksChannelBinding(bool isSecureConnection, ProtectionScenario scenario)1527         private static bool ScenarioChecksChannelBinding(bool isSecureConnection, ProtectionScenario scenario)
1528         {
1529             return (isSecureConnection && scenario == ProtectionScenario.TransportSelected);
1530         }
1531 
GetContextFlags(ExtendedProtectionPolicy policy, bool isSecureConnection)1532         private ContextFlagsPal GetContextFlags(ExtendedProtectionPolicy policy, bool isSecureConnection)
1533         {
1534             ContextFlagsPal result = ContextFlagsPal.Connection;
1535             if (policy.PolicyEnforcement != PolicyEnforcement.Never)
1536             {
1537                 if (policy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
1538                 {
1539                     result |= ContextFlagsPal.AllowMissingBindings;
1540                 }
1541 
1542                 if (policy.ProtectionScenario == ProtectionScenario.TrustedProxy)
1543                 {
1544                     result |= ContextFlagsPal.ProxyBindings;
1545                 }
1546             }
1547 
1548             return result;
1549         }
1550 
1551         // This only works for context-destroying errors.
HttpStatusFromSecurityStatus(SecurityStatusPalErrorCode statusErrorCode)1552         private HttpStatusCode HttpStatusFromSecurityStatus(SecurityStatusPalErrorCode statusErrorCode)
1553         {
1554             if (IsCredentialFailure(statusErrorCode))
1555             {
1556                 return HttpStatusCode.Unauthorized;
1557             }
1558             if (IsClientFault(statusErrorCode))
1559             {
1560                 return HttpStatusCode.BadRequest;
1561             }
1562             return HttpStatusCode.InternalServerError;
1563         }
1564 
1565         // This only works for context-destroying errors.
IsCredentialFailure(SecurityStatusPalErrorCode error)1566         internal static bool IsCredentialFailure(SecurityStatusPalErrorCode error)
1567         {
1568             return error == SecurityStatusPalErrorCode.LogonDenied ||
1569                 error == SecurityStatusPalErrorCode.UnknownCredentials ||
1570                 error == SecurityStatusPalErrorCode.NoImpersonation ||
1571                 error == SecurityStatusPalErrorCode.NoAuthenticatingAuthority ||
1572                 error == SecurityStatusPalErrorCode.UntrustedRoot ||
1573                 error == SecurityStatusPalErrorCode.CertExpired ||
1574                 error == SecurityStatusPalErrorCode.SmartcardLogonRequired ||
1575                 error == SecurityStatusPalErrorCode.BadBinding;
1576         }
1577 
1578         // This only works for context-destroying errors.
IsClientFault(SecurityStatusPalErrorCode error)1579         internal static bool IsClientFault(SecurityStatusPalErrorCode error)
1580         {
1581             return error == SecurityStatusPalErrorCode.InvalidToken ||
1582                 error == SecurityStatusPalErrorCode.CannotPack ||
1583                 error == SecurityStatusPalErrorCode.QopNotSupported ||
1584                 error == SecurityStatusPalErrorCode.NoCredentials ||
1585                 error == SecurityStatusPalErrorCode.MessageAltered ||
1586                 error == SecurityStatusPalErrorCode.OutOfSequence ||
1587                 error == SecurityStatusPalErrorCode.IncompleteMessage ||
1588                 error == SecurityStatusPalErrorCode.IncompleteCredentials ||
1589                 error == SecurityStatusPalErrorCode.WrongPrincipal ||
1590                 error == SecurityStatusPalErrorCode.TimeSkew ||
1591                 error == SecurityStatusPalErrorCode.IllegalMessage ||
1592                 error == SecurityStatusPalErrorCode.CertUnknown ||
1593                 error == SecurityStatusPalErrorCode.AlgorithmMismatch ||
1594                 error == SecurityStatusPalErrorCode.SecurityQosFailed ||
1595                 error == SecurityStatusPalErrorCode.UnsupportedPreauth;
1596         }
1597 
AddChallenge(ref ArrayList challenges, string challenge)1598         private static void AddChallenge(ref ArrayList challenges, string challenge)
1599         {
1600             if (challenge != null)
1601             {
1602                 challenge = challenge.Trim();
1603                 if (challenge.Length > 0)
1604                 {
1605                     if (NetEventSource.IsEnabled) NetEventSource.Info(null, "challenge:" + challenge);
1606                     if (challenges == null)
1607                     {
1608                         challenges = new ArrayList(4);
1609                     }
1610                     challenges.Add(challenge);
1611                 }
1612             }
1613         }
1614 
BuildChallenge(AuthenticationSchemes authenticationScheme, ulong connectionId, out NTAuthentication newContext, ExtendedProtectionPolicy policy, bool isSecureConnection)1615         private ArrayList BuildChallenge(AuthenticationSchemes authenticationScheme, ulong connectionId,
1616             out NTAuthentication newContext, ExtendedProtectionPolicy policy, bool isSecureConnection)
1617         {
1618             if (NetEventSource.IsEnabled) NetEventSource.Info(this, "AuthenticationScheme:" + authenticationScheme.ToString());
1619             ArrayList challenges = null;
1620             newContext = null;
1621 
1622             if ((authenticationScheme & AuthenticationSchemes.Negotiate) != 0)
1623             {
1624                 AddChallenge(ref challenges, AuthenticationTypes.Negotiate);
1625             }
1626 
1627             if ((authenticationScheme & AuthenticationSchemes.Ntlm) != 0)
1628             {
1629                 AddChallenge(ref challenges, AuthenticationTypes.NTLM);
1630             }
1631 
1632             if ((authenticationScheme & AuthenticationSchemes.Basic) != 0)
1633             {
1634                 AddChallenge(ref challenges, "Basic realm=\"" + Realm + "\"");
1635             }
1636 
1637             return challenges;
1638         }
1639 
RegisterForDisconnectNotification(ulong connectionId, ref DisconnectAsyncResult disconnectResult)1640         private void RegisterForDisconnectNotification(ulong connectionId, ref DisconnectAsyncResult disconnectResult)
1641         {
1642             Debug.Assert(disconnectResult == null);
1643 
1644             try
1645             {
1646                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Calling Interop.HttpApi.HttpWaitForDisconnect");
1647 
1648                 DisconnectAsyncResult result = new DisconnectAsyncResult(this, connectionId);
1649 
1650                 uint statusCode = Interop.HttpApi.HttpWaitForDisconnect(
1651                     _requestQueueHandle,
1652                     connectionId,
1653                     result.NativeOverlapped);
1654 
1655                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Call to Interop.HttpApi.HttpWaitForDisconnect returned:" + statusCode);
1656 
1657                 if (statusCode == Interop.HttpApi.ERROR_SUCCESS ||
1658                     statusCode == Interop.HttpApi.ERROR_IO_PENDING)
1659                 {
1660                     // Need to make sure it's going to get returned before adding it to the hash.  That way it'll be handled
1661                     // correctly in HandleAuthentication's finally.
1662                     disconnectResult = result;
1663                     DisconnectResults[connectionId] = disconnectResult;
1664                 }
1665 
1666                 if (statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess)
1667                 {
1668                     // IO operation completed synchronously - callback won't be called to signal completion.
1669                     result.IOCompleted(statusCode, 0, result.NativeOverlapped);
1670                 }
1671             }
1672             catch (Win32Exception exception)
1673             {
1674                 uint statusCode = (uint)exception.NativeErrorCode;
1675                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Call to Interop.HttpApi.HttpWaitForDisconnect threw, statusCode:" + statusCode);
1676             }
1677         }
1678 
SendError(ulong requestId, HttpStatusCode httpStatusCode, ArrayList challenges)1679         private void SendError(ulong requestId, HttpStatusCode httpStatusCode, ArrayList challenges)
1680         {
1681             if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"RequestId: {requestId}");
1682             Interop.HttpApi.HTTP_RESPONSE httpResponse = new Interop.HttpApi.HTTP_RESPONSE();
1683             httpResponse.Version = new Interop.HttpApi.HTTP_VERSION();
1684             httpResponse.Version.MajorVersion = (ushort)1;
1685             httpResponse.Version.MinorVersion = (ushort)1;
1686             httpResponse.StatusCode = (ushort)httpStatusCode;
1687             string statusDescription = HttpStatusDescription.Get(httpStatusCode);
1688             uint DataWritten = 0;
1689             uint statusCode;
1690             byte[] byteReason = Encoding.Default.GetBytes(statusDescription);
1691             fixed (byte* pReason = byteReason)
1692             {
1693                 httpResponse.pReason = (sbyte*)pReason;
1694                 httpResponse.ReasonLength = (ushort)byteReason.Length;
1695 
1696                 byte[] byteContentLength = Encoding.Default.GetBytes("0");
1697                 fixed (byte* pContentLength = &byteContentLength[0])
1698                 {
1699                     (&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].pRawValue = (sbyte*)pContentLength;
1700                     (&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length;
1701 
1702                     httpResponse.Headers.UnknownHeaderCount = checked((ushort)(challenges == null ? 0 : challenges.Count));
1703                     GCHandle[] challengeHandles = null;
1704                     Interop.HttpApi.HTTP_UNKNOWN_HEADER[] headersArray = null;
1705                     GCHandle headersArrayHandle = new GCHandle();
1706                     GCHandle wwwAuthenticateHandle = new GCHandle();
1707                     if (httpResponse.Headers.UnknownHeaderCount > 0)
1708                     {
1709                         challengeHandles = new GCHandle[httpResponse.Headers.UnknownHeaderCount];
1710                         headersArray = new Interop.HttpApi.HTTP_UNKNOWN_HEADER[httpResponse.Headers.UnknownHeaderCount];
1711                     }
1712 
1713                     try
1714                     {
1715                         if (httpResponse.Headers.UnknownHeaderCount > 0)
1716                         {
1717                             headersArrayHandle = GCHandle.Alloc(headersArray, GCHandleType.Pinned);
1718                             httpResponse.Headers.pUnknownHeaders = (Interop.HttpApi.HTTP_UNKNOWN_HEADER*)Marshal.UnsafeAddrOfPinnedArrayElement(headersArray, 0);
1719                             wwwAuthenticateHandle = GCHandle.Alloc(s_wwwAuthenticateBytes, GCHandleType.Pinned);
1720                             sbyte* wwwAuthenticate = (sbyte*)Marshal.UnsafeAddrOfPinnedArrayElement(s_wwwAuthenticateBytes, 0);
1721 
1722                             for (int i = 0; i < challengeHandles.Length; i++)
1723                             {
1724                                 byte[] byteChallenge = Encoding.Default.GetBytes((string)challenges[i]);
1725                                 challengeHandles[i] = GCHandle.Alloc(byteChallenge, GCHandleType.Pinned);
1726                                 headersArray[i].pName = wwwAuthenticate;
1727                                 headersArray[i].NameLength = (ushort)s_wwwAuthenticateBytes.Length;
1728                                 headersArray[i].pRawValue = (sbyte*)Marshal.UnsafeAddrOfPinnedArrayElement(byteChallenge, 0);
1729                                 headersArray[i].RawValueLength = checked((ushort)byteChallenge.Length);
1730                             }
1731                         }
1732 
1733                         if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendHtthttpResponse");
1734                         statusCode =
1735                             Interop.HttpApi.HttpSendHttpResponse(
1736                                 _requestQueueHandle,
1737                                 requestId,
1738                                 0,
1739                                 &httpResponse,
1740                                 null,
1741                                 &DataWritten,
1742                                 SafeLocalAllocHandle.Zero,
1743                                 0,
1744                                 null,
1745                                 null);
1746                     }
1747                     finally
1748                     {
1749                         if (headersArrayHandle.IsAllocated)
1750                         {
1751                             headersArrayHandle.Free();
1752                         }
1753                         if (wwwAuthenticateHandle.IsAllocated)
1754                         {
1755                             wwwAuthenticateHandle.Free();
1756                         }
1757                         if (challengeHandles != null)
1758                         {
1759                             for (int i = 0; i < challengeHandles.Length; i++)
1760                             {
1761                                 if (challengeHandles[i].IsAllocated)
1762                                 {
1763                                     challengeHandles[i].Free();
1764                                 }
1765                             }
1766                         }
1767                     }
1768                 }
1769             }
1770             if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendHttpResponse returned:" + statusCode);
1771             if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
1772             {
1773                 // if we fail to send a 401 something's seriously wrong, abort the request
1774                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, "SendUnauthorized returned:" + statusCode);
1775                 HttpListenerContext.CancelRequest(_requestQueueHandle, requestId);
1776             }
1777         }
1778 
GetTokenOffsetFromBlob(IntPtr blob)1779         private static unsafe int GetTokenOffsetFromBlob(IntPtr blob)
1780         {
1781             Debug.Assert(blob != IntPtr.Zero);
1782             IntPtr tokenPointer = Marshal.ReadIntPtr((IntPtr)blob, (int)Marshal.OffsetOf(s_channelBindingStatusType, "ChannelToken"));
1783 
1784             Debug.Assert(tokenPointer != IntPtr.Zero);
1785             return (int)((long)tokenPointer - (long)blob);
1786         }
1787 
GetTokenSizeFromBlob(IntPtr blob)1788         private static unsafe int GetTokenSizeFromBlob(IntPtr blob)
1789         {
1790             Debug.Assert(blob != IntPtr.Zero);
1791             return Marshal.ReadInt32(blob, (int)Marshal.OffsetOf(s_channelBindingStatusType, "ChannelTokenSize"));
1792         }
1793 
GetChannelBindingFromTls(ulong connectionId)1794         internal ChannelBinding GetChannelBindingFromTls(ulong connectionId)
1795         {
1796             if (NetEventSource.IsEnabled) NetEventSource.Enter(this, $"connectionId: {connectionId}");
1797 
1798             // +128 since a CBT is usually <128 thus we need to call HRCC just once. If the CBT
1799             // is >128 we will get ERROR_MORE_DATA and call again
1800             int size = s_requestChannelBindStatusSize + 128;
1801 
1802             Debug.Assert(size > 0);
1803 
1804             byte[] blob = null;
1805             Interop.HttpApi.SafeLocalFreeChannelBinding token = null;
1806 
1807             uint bytesReceived = 0;
1808             uint statusCode;
1809 
1810             do
1811             {
1812                 blob = new byte[size];
1813                 fixed (byte* blobPtr = &blob[0])
1814                 {
1815                     // Http.sys team: ServiceName will always be null if
1816                     // HTTP_RECEIVE_SECURE_CHANNEL_TOKEN flag is set.
1817                     statusCode = Interop.HttpApi.HttpReceiveClientCertificate(
1818                         RequestQueueHandle,
1819                         connectionId,
1820                         (uint)Interop.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_SECURE_CHANNEL_TOKEN,
1821                         blobPtr,
1822                         (uint)size,
1823                         &bytesReceived,
1824                         null);
1825 
1826                     if (statusCode == Interop.HttpApi.ERROR_SUCCESS)
1827                     {
1828                         int tokenOffset = GetTokenOffsetFromBlob((IntPtr)blobPtr);
1829                         int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
1830                         Debug.Assert(tokenSize < Int32.MaxValue);
1831 
1832                         token = Interop.HttpApi.SafeLocalFreeChannelBinding.LocalAlloc(tokenSize);
1833                         if (token.IsInvalid)
1834                         {
1835                             throw new OutOfMemoryException();
1836                         }
1837                         Marshal.Copy(blob, tokenOffset, token.DangerousGetHandle(), tokenSize);
1838                     }
1839                     else if (statusCode == Interop.HttpApi.ERROR_MORE_DATA)
1840                     {
1841                         int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
1842                         Debug.Assert(tokenSize < Int32.MaxValue);
1843 
1844                         size = s_requestChannelBindStatusSize + tokenSize;
1845                     }
1846                     else if (statusCode == Interop.HttpApi.ERROR_INVALID_PARAMETER)
1847                     {
1848                         if (NetEventSource.IsEnabled)
1849                         {
1850                             NetEventSource.Error(this, SR.net_ssp_dont_support_cbt);
1851                         }
1852                         return null; // old schannel library which doesn't support CBT
1853                     }
1854                     else
1855                     {
1856                         throw new HttpListenerException((int)statusCode);
1857                     }
1858                 }
1859             } while (statusCode != Interop.HttpApi.ERROR_SUCCESS);
1860 
1861             return token;
1862         }
1863 
1864 
1865         private class DisconnectAsyncResult : IAsyncResult
1866         {
1867             private static readonly IOCompletionCallback s_IOCallback = new IOCompletionCallback(WaitCallback);
1868 
1869             private ulong _connectionId;
1870             private HttpListener _httpListener;
1871             private NativeOverlapped* _nativeOverlapped;
1872             private int _ownershipState;   // 0 = normal, 1 = in HandleAuthentication(), 2 = disconnected, 3 = cleaned up
1873 
1874             private WindowsPrincipal _authenticatedConnection;
1875             private NTAuthentication _session;
1876 
1877             internal NativeOverlapped* NativeOverlapped
1878             {
1879                 get
1880                 {
1881                     return _nativeOverlapped;
1882                 }
1883             }
1884 
1885             public object AsyncState
1886             {
1887                 get
1888                 {
1889                     throw new NotImplementedException(SR.net_PropertyNotImplementedException);
1890                 }
1891             }
1892             public WaitHandle AsyncWaitHandle
1893             {
1894                 get
1895                 {
1896                     throw new NotImplementedException(SR.net_PropertyNotImplementedException);
1897                 }
1898             }
1899             public bool CompletedSynchronously
1900             {
1901                 get
1902                 {
1903                     throw new NotImplementedException(SR.net_PropertyNotImplementedException);
1904                 }
1905             }
1906             public bool IsCompleted
1907             {
1908                 get
1909                 {
1910                     throw new NotImplementedException(SR.net_PropertyNotImplementedException);
1911                 }
1912             }
1913 
DisconnectAsyncResult(HttpListener httpListener, ulong connectionId)1914             internal unsafe DisconnectAsyncResult(HttpListener httpListener, ulong connectionId)
1915             {
1916                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"HttpListener: {httpListener}, ConnectionId: {connectionId}");
1917                 _ownershipState = 1;
1918                 _httpListener = httpListener;
1919                 _connectionId = connectionId;
1920 
1921                 // we can call the Unsafe API here, we won't ever call user code
1922                 _nativeOverlapped = httpListener.RequestQueueBoundHandle.AllocateNativeOverlapped(s_IOCallback, state: this, pinData: null);
1923                 if (NetEventSource.IsEnabled) NetEventSource.Info($"DisconnectAsyncResult: ThreadPoolBoundHandle.AllocateNativeOverlapped({httpListener._requestQueueBoundHandle}) -> {_nativeOverlapped->GetHashCode()}");
1924             }
1925 
StartOwningDisconnectHandling()1926             internal bool StartOwningDisconnectHandling()
1927             {
1928                 int oldValue;
1929 
1930                 SpinWait spin = new SpinWait();
1931                 while ((oldValue = Interlocked.CompareExchange(ref _ownershipState, 1, 0)) == 2)
1932                 {
1933                     // Must block until it equals 3 - we must be in the callback right now.
1934                     spin.SpinOnce();
1935                 }
1936 
1937                 Debug.Assert(oldValue != 1, "StartOwningDisconnectHandling called twice.");
1938                 return oldValue < 2;
1939             }
1940 
FinishOwningDisconnectHandling()1941             internal void FinishOwningDisconnectHandling()
1942             {
1943                 // If it got disconnected, run the disconnect code.
1944                 if (Interlocked.CompareExchange(ref _ownershipState, 0, 1) == 2)
1945                 {
1946                     HandleDisconnect();
1947                 }
1948             }
1949 
IOCompleted(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)1950             internal unsafe void IOCompleted(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
1951             {
1952                 IOCompleted(this, errorCode, numBytes, nativeOverlapped);
1953             }
1954 
IOCompleted(DisconnectAsyncResult asyncResult, uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)1955             private static unsafe void IOCompleted(DisconnectAsyncResult asyncResult, uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
1956             {
1957                 if (NetEventSource.IsEnabled) NetEventSource.Info(null, "_connectionId:" + asyncResult._connectionId);
1958 
1959                 asyncResult._httpListener._requestQueueBoundHandle.FreeNativeOverlapped(nativeOverlapped);
1960                 if (Interlocked.Exchange(ref asyncResult._ownershipState, 2) == 0)
1961                 {
1962                     asyncResult.HandleDisconnect();
1963                 }
1964             }
1965 
WaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)1966             private static unsafe void WaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
1967             {
1968                 if (NetEventSource.IsEnabled) NetEventSource.Info(null, $"errorCode: {errorCode}, numBytes: {numBytes}, nativeOverlapped: {((IntPtr)nativeOverlapped).ToString("x")}");
1969                 // take the DisconnectAsyncResult object from the state
1970                 DisconnectAsyncResult asyncResult = (DisconnectAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped);
1971                 IOCompleted(asyncResult, errorCode, numBytes, nativeOverlapped);
1972             }
1973 
HandleDisconnect()1974             private void HandleDisconnect()
1975             {
1976                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"DisconnectResults {_httpListener.DisconnectResults} removing for _connectionId: {_connectionId}");
1977                 _httpListener.DisconnectResults.Remove(_connectionId);
1978                 if (_session != null)
1979                 {
1980                     _session.CloseContext();
1981                 }
1982 
1983                 // Clean up the identity. This is for scenarios where identity was not cleaned up before due to
1984                 // identity caching for unsafe ntlm authentication
1985 
1986                 IDisposable identity = _authenticatedConnection == null ? null : _authenticatedConnection.Identity as IDisposable;
1987                 if ((identity != null) &&
1988                     (_authenticatedConnection.Identity.AuthenticationType == AuthenticationTypes.NTLM) &&
1989                     (_httpListener.UnsafeConnectionNtlmAuthentication))
1990                 {
1991                     identity.Dispose();
1992                 }
1993 
1994                 int oldValue = Interlocked.Exchange(ref _ownershipState, 3);
1995                 Debug.Assert(oldValue == 2, $"Expected OwnershipState of 2, saw {oldValue}.");
1996             }
1997 
1998             internal WindowsPrincipal AuthenticatedConnection
1999             {
2000                 get
2001                 {
2002                     return _authenticatedConnection;
2003                 }
2004 
2005                 set
2006                 {
2007                     // The previous value can't be disposed because it may be in use by the app.
2008                     _authenticatedConnection = value;
2009                 }
2010             }
2011 
2012             internal NTAuthentication Session
2013             {
2014                 get
2015                 {
2016                     return _session;
2017                 }
2018 
2019                 set
2020                 {
2021                     _session = value;
2022                 }
2023             }
2024         }
2025     }
2026 }
2027