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