private void Dispose(bool disposing) { if (_responseState >= ResponseState.Closed) { return; } EnsureResponseStream(); _responseStream.Close(); _responseState = ResponseState.Closed; HttpListenerContext.Close(); }
private void NonBlockingCloseCallback(IAsyncResult asyncResult) { try { m_ResponseStream.EndWrite(asyncResult); } catch (Win32Exception) { } finally { m_ResponseStream.Close(); HttpListenerContext.Close(); m_ResponseState = ResponseState.Closed; } }
public void Close(byte[] responseEntity, bool willBlock) { if (NetEventSource.IsEnabled) { NetEventSource.Enter(this, $"responseEntity={responseEntity},willBlock={willBlock}"); } try { CheckDisposed(); if (responseEntity == null) { throw new ArgumentNullException(nameof(responseEntity)); } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"ResponseState:{_responseState}, BoundaryType:{_boundaryType}, ContentLength:{_contentLength}"); } if (_responseState < ResponseState.SentHeaders && _boundaryType != BoundaryType.Chunked) { ContentLength64 = responseEntity.Length; } EnsureResponseStream(); if (willBlock) { try { _responseStream.Write(responseEntity, 0, responseEntity.Length); } catch (Win32Exception) { } finally { _responseStream.Close(); _responseState = ResponseState.Closed; HttpListenerContext.Close(); } } else { _responseStream.BeginWrite(responseEntity, 0, responseEntity.Length, new AsyncCallback(NonBlockingCloseCallback), null); } } finally { if (NetEventSource.IsEnabled) { NetEventSource.Exit(this); } } }
public void Close(byte[] responseEntity, bool willBlock) { if (Logging.On) { Logging.Enter(Logging.HttpListener, this, "Close", " responseEntity=" + ValidationHelper.HashString(responseEntity) + " willBlock=" + willBlock); } try { CheckDisposed(); if (responseEntity == null) { throw new ArgumentNullException("responseEntity"); } GlobalLog.Print("HttpListenerResponse#" + ValidationHelper.HashString(this) + "::Close() ResponseState:" + m_ResponseState + " BoundaryType:" + m_BoundaryType + " ContentLength:" + m_ContentLength); if (m_ResponseState < ResponseState.SentHeaders && m_BoundaryType != BoundaryType.Chunked) { ContentLength64 = responseEntity.Length; } EnsureResponseStream(); if (willBlock) { try { m_ResponseStream.Write(responseEntity, 0, responseEntity.Length); } catch (Win32Exception) { } finally { m_ResponseStream.Close(); m_ResponseState = ResponseState.Closed; HttpListenerContext.Close(); } } else { // < m_ResponseStream.BeginWrite(responseEntity, 0, responseEntity.Length, new AsyncCallback(NonBlockingCloseCallback), null); } } finally { if (Logging.On) { Logging.Exit(Logging.HttpListener, this, "Close", ""); } } }
internal HttpListenerContext HandleAuthentication(RequestContextBase memoryBlob, out bool stoleBlob) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, "HandleAuthentication() memoryBlob:0x" + ((IntPtr)memoryBlob.RequestBlob).ToString("x")); string challenge = null; stoleBlob = false; // Some things we need right away. Lift them out now while it's convenient. string verb = Interop.HttpApi.GetVerb(memoryBlob.RequestBlob); string authorizationHeader = Interop.HttpApi.GetKnownHeader(memoryBlob.RequestBlob, (int)HttpRequestHeader.Authorization); ulong connectionId = memoryBlob.RequestBlob->ConnectionId; ulong requestId = memoryBlob.RequestBlob->RequestId; bool isSecureConnection = memoryBlob.RequestBlob->pSslInfo != null; if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"HandleAuthentication() authorizationHeader: ({authorizationHeader})"); // if the app has turned on AuthPersistence, an anonymous request might // be authenticated by virtue of it coming on a connection that was // previously authenticated. // assurance that we do this only for NTLM/Negotiate is not here, but in the // code that caches WindowsIdentity instances in the Dictionary. DisconnectAsyncResult disconnectResult; DisconnectResults.TryGetValue(connectionId, out disconnectResult); if (UnsafeConnectionNtlmAuthentication) { if (authorizationHeader == null) { WindowsPrincipal principal = disconnectResult?.AuthenticatedConnection; if (principal != null) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Principal: {principal} principal.Identity.Name: {principal.Identity.Name} creating request"); stoleBlob = true; HttpListenerContext ntlmContext = new HttpListenerContext(this, memoryBlob); ntlmContext.SetIdentity(principal, null); ntlmContext.Request.ReleasePins(); return ntlmContext; } } else { // They sent an authorization - destroy their previous credentials. if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Clearing principal cache"); if (disconnectResult != null) { disconnectResult.AuthenticatedConnection = null; } } } // Figure out what schemes we're allowing, what context we have. stoleBlob = true; HttpListenerContext httpContext = null; NTAuthentication oldContext = null; NTAuthentication newContext = null; NTAuthentication context = null; AuthenticationSchemes headerScheme = AuthenticationSchemes.None; AuthenticationSchemes authenticationScheme = AuthenticationSchemes; ExtendedProtectionPolicy extendedProtectionPolicy = _extendedProtectionPolicy; try { // Take over handling disconnects for now. if (disconnectResult != null && !disconnectResult.StartOwningDisconnectHandling()) { // Just disconnected just then. Pretend we didn't see the disconnectResult. disconnectResult = null; } // Pick out the old context now. By default, it'll be removed in the finally, unless context is set somewhere. if (disconnectResult != null) { oldContext = disconnectResult.Session; } httpContext = new HttpListenerContext(this, memoryBlob); AuthenticationSchemeSelector authenticationSelector = _authenticationDelegate; if (authenticationSelector != null) { try { httpContext.Request.ReleasePins(); authenticationScheme = authenticationSelector(httpContext.Request); // Cache the results of authenticationSelector (if any) httpContext.AuthenticationSchemes = authenticationScheme; if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"AuthenticationScheme: {authenticationScheme}"); } catch (Exception exception) when (!ExceptionCheck.IsFatal(exception)) { if (NetEventSource.IsEnabled) { NetEventSource.Error(this, SR.Format(SR.net_log_listener_delegate_exception, exception)); NetEventSource.Info(this, $"authenticationScheme: {authenticationScheme}"); } SendError(requestId, HttpStatusCode.InternalServerError, null); httpContext.Close(); return null; } } else { // We didn't give the request to the user yet, so we haven't lost control of the unmanaged blob and can // continue to reuse the buffer. stoleBlob = false; } ExtendedProtectionSelector extendedProtectionSelector = _extendedProtectionSelectorDelegate; if (extendedProtectionSelector != null) { extendedProtectionPolicy = extendedProtectionSelector(httpContext.Request); if (extendedProtectionPolicy == null) { extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never); } // Cache the results of extendedProtectionSelector (if any) httpContext.ExtendedProtectionPolicy = extendedProtectionPolicy; } // Then figure out what scheme they're trying (if any are allowed) int index = -1; if (authorizationHeader != null && (authenticationScheme & ~AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None) { // Find the end of the scheme name. Trust that HTTP.SYS parsed out just our header ok. for (index = 0; index < authorizationHeader.Length; index++) { if (authorizationHeader[index] == ' ' || authorizationHeader[index] == '\t' || authorizationHeader[index] == '\r' || authorizationHeader[index] == '\n') { break; } } // Currently only allow one Authorization scheme/header per request. if (index < authorizationHeader.Length) { if ((authenticationScheme & AuthenticationSchemes.Negotiate) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, AuthConstants.Negotiate, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Negotiate; } else if ((authenticationScheme & AuthenticationSchemes.Ntlm) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, AuthConstants.NTLM, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Ntlm; } else if ((authenticationScheme & AuthenticationSchemes.Digest) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, AuthConstants.Digest, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Digest; } else if ((authenticationScheme & AuthenticationSchemes.Basic) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, AuthConstants.Basic, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Basic; } else { if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_unsupported_authentication_scheme, authorizationHeader, authenticationScheme)); } } } // httpError holds the error we will return if an Authorization header is present but can't be authenticated HttpStatusCode httpError = HttpStatusCode.InternalServerError; // See if we found an acceptable auth header if (headerScheme == AuthenticationSchemes.None) { if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_unmatched_authentication_scheme, authenticationScheme.ToString(), (authorizationHeader == null ? "<null>" : authorizationHeader))); // If anonymous is allowed, just return the context. Otherwise go for the 401. if ((authenticationScheme & AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None) { if (!stoleBlob) { stoleBlob = true; httpContext.Request.ReleasePins(); } return httpContext; } httpError = HttpStatusCode.Unauthorized; httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); httpContext = null; } else { throw new NotImplementedException(); } // if we're not giving a request to the application, we need to send an error ArrayList challenges = null; if (httpContext == null) { // If we already have a challenge, just use it. Otherwise put a challenge for each acceptable scheme. if (challenge != null) { AddChallenge(ref challenges, challenge); } else { // We're starting over. Any context SSPI might have wanted us to keep is useless. if (newContext != null) { if (newContext == context) { context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } // If we're sending something besides 401, do it here. if (httpError != HttpStatusCode.Unauthorized) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, "ConnectionId:" + connectionId + " because of error:" + httpError.ToString()); SendError(requestId, httpError, null); return null; } challenges = BuildChallenge(authenticationScheme, connectionId, out newContext, extendedProtectionPolicy, isSecureConnection); } } // Check if we need to call WaitForDisconnect, because if we do and it fails, we want to send a 500 instead. if (disconnectResult == null && newContext != null) { RegisterForDisconnectNotification(connectionId, ref disconnectResult); // Failed - send 500. if (disconnectResult == null) { if (newContext != null) { if (newContext == context) { context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } if (NetEventSource.IsEnabled) NetEventSource.Info(this, "connectionId:" + connectionId + " because of failed HttpWaitForDisconnect"); SendError(requestId, HttpStatusCode.InternalServerError, null); httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); return null; } } // Update Session if necessary. if (oldContext != newContext) { if (oldContext == context) { // Prevent the finally from closing this twice. context = null; } NTAuthentication toClose = oldContext; oldContext = newContext; disconnectResult.Session = newContext; if (toClose != null) { // Save digest context in digest cache, we may need it later because of // subsequest responses to the same req on the same/diff connection if ((authenticationScheme & AuthenticationSchemes.Digest) != 0) { SaveDigestContext(toClose); } else { toClose.CloseContext(); } } } // Send the 401 here. if (httpContext == null) { SendError(requestId, challenges != null && challenges.Count > 0 ? HttpStatusCode.Unauthorized : HttpStatusCode.Forbidden, challenges); if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Scheme:" + authenticationScheme); return null; } if (!stoleBlob) { stoleBlob = true; httpContext.Request.ReleasePins(); } return httpContext; } catch { if (httpContext != null) { httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); } if (newContext != null) { if (newContext == context) { // Prevent the finally from closing this twice. context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } throw; } finally { try { // Clean up the previous context if necessary. if (oldContext != null && oldContext != newContext) { // Clear out Session if it wasn't already. if (newContext == null && disconnectResult != null) { disconnectResult.Session = null; } // Save digest context in digest cache, we may need it later because of // subsequest responses to the same req on the same/diff connection if ((authenticationScheme & AuthenticationSchemes.Digest) != 0) { SaveDigestContext(oldContext); } else { oldContext.CloseContext(); } } // Delete any context created but not stored. if (context != null && oldContext != context && newContext != context) { context.CloseContext(); } } finally { // Check if the connection got deleted while in this method, and clear out the hashtables if it did. // In a nested finally because if this doesn't happen, we leak. if (disconnectResult != null) { disconnectResult.FinishOwningDisconnectHandling(); } } } }
internal unsafe HttpListenerContext HandleAuthentication(RequestContextBase memoryBlob, out bool stoleBlob) { string challenge = null; HttpListenerContext context3; stoleBlob = false; string verb = UnsafeNclNativeMethods.HttpApi.GetVerb(memoryBlob.RequestBlob); string knownHeader = UnsafeNclNativeMethods.HttpApi.GetKnownHeader(memoryBlob.RequestBlob, 0x18); ulong connectionId = memoryBlob.RequestBlob.ConnectionId; ulong requestId = memoryBlob.RequestBlob.RequestId; bool isSecureConnection = memoryBlob.RequestBlob.pSslInfo != null; DisconnectAsyncResult disconnectResult = (DisconnectAsyncResult) this.DisconnectResults[connectionId]; if (this.UnsafeConnectionNtlmAuthentication) { if (knownHeader == null) { WindowsPrincipal principal = (disconnectResult == null) ? null : disconnectResult.AuthenticatedConnection; if (principal != null) { stoleBlob = true; HttpListenerContext context = new HttpListenerContext(this, memoryBlob); context.SetIdentity(principal, null); context.Request.ReleasePins(); return context; } } else if (disconnectResult != null) { disconnectResult.AuthenticatedConnection = null; } } stoleBlob = true; HttpListenerContext context2 = null; NTAuthentication digestContext = null; NTAuthentication newContext = null; NTAuthentication authentication3 = null; System.Net.AuthenticationSchemes none = System.Net.AuthenticationSchemes.None; System.Net.AuthenticationSchemes authenticationSchemes = this.AuthenticationSchemes; System.Security.Authentication.ExtendedProtection.ExtendedProtectionPolicy extendedProtectionPolicy = this.m_ExtendedProtectionPolicy; try { ExtendedProtectionSelector selector; SecurityStatus invalidToken; ChannelBinding binding; string str6; ArrayList list; if ((disconnectResult != null) && !disconnectResult.StartOwningDisconnectHandling()) { disconnectResult = null; } if (disconnectResult != null) { digestContext = disconnectResult.Session; } context2 = new HttpListenerContext(this, memoryBlob); AuthenticationSelectorInfo authenticationDelegate = this.m_AuthenticationDelegate; if (authenticationDelegate != null) { try { context2.Request.ReleasePins(); authenticationSchemes = authenticationDelegate.Delegate(context2.Request); if (!authenticationDelegate.AdvancedAuth && ((authenticationSchemes & (System.Net.AuthenticationSchemes.IntegratedWindowsAuthentication | System.Net.AuthenticationSchemes.Digest)) != System.Net.AuthenticationSchemes.None)) { throw this.m_SecurityException; } goto Label_01A2; } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) { throw; } if (Logging.On) { Logging.PrintError(Logging.HttpListener, this, "HandleAuthentication", SR.GetString("net_log_listener_delegate_exception", new object[] { exception })); } this.SendError(requestId, HttpStatusCode.InternalServerError, null); context2.Close(); return null; } } stoleBlob = false; Label_01A2: selector = this.m_ExtendedProtectionSelectorDelegate; if (selector != null) { extendedProtectionPolicy = selector(context2.Request); if (extendedProtectionPolicy == null) { extendedProtectionPolicy = new System.Security.Authentication.ExtendedProtection.ExtendedProtectionPolicy(PolicyEnforcement.Never); } } int length = -1; if ((knownHeader != null) && ((authenticationSchemes & ~System.Net.AuthenticationSchemes.Anonymous) != System.Net.AuthenticationSchemes.None)) { length = 0; while (length < knownHeader.Length) { if (((knownHeader[length] == ' ') || (knownHeader[length] == '\t')) || ((knownHeader[length] == '\r') || (knownHeader[length] == '\n'))) { break; } length++; } if (length < knownHeader.Length) { if (((authenticationSchemes & System.Net.AuthenticationSchemes.Negotiate) != System.Net.AuthenticationSchemes.None) && (string.Compare(knownHeader, 0, "Negotiate", 0, length, StringComparison.OrdinalIgnoreCase) == 0)) { none = System.Net.AuthenticationSchemes.Negotiate; } else if (((authenticationSchemes & System.Net.AuthenticationSchemes.Ntlm) != System.Net.AuthenticationSchemes.None) && (string.Compare(knownHeader, 0, "NTLM", 0, length, StringComparison.OrdinalIgnoreCase) == 0)) { none = System.Net.AuthenticationSchemes.Ntlm; } else if (((authenticationSchemes & System.Net.AuthenticationSchemes.Digest) != System.Net.AuthenticationSchemes.None) && (string.Compare(knownHeader, 0, "Digest", 0, length, StringComparison.OrdinalIgnoreCase) == 0)) { none = System.Net.AuthenticationSchemes.Digest; } else if (((authenticationSchemes & System.Net.AuthenticationSchemes.Basic) != System.Net.AuthenticationSchemes.None) && (string.Compare(knownHeader, 0, "Basic", 0, length, StringComparison.OrdinalIgnoreCase) == 0)) { none = System.Net.AuthenticationSchemes.Basic; } else if (Logging.On) { Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString("net_log_listener_unsupported_authentication_scheme", new object[] { knownHeader, authenticationSchemes })); } } } HttpStatusCode internalServerError = HttpStatusCode.InternalServerError; bool flag2 = false; if (none == System.Net.AuthenticationSchemes.None) { if (Logging.On) { Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString("net_log_listener_unmatched_authentication_scheme", new object[] { ValidationHelper.ToString(authenticationSchemes), (knownHeader == null) ? "<null>" : knownHeader })); } if ((authenticationSchemes & System.Net.AuthenticationSchemes.Anonymous) != System.Net.AuthenticationSchemes.None) { if (!stoleBlob) { stoleBlob = true; context2.Request.ReleasePins(); } return context2; } internalServerError = HttpStatusCode.Unauthorized; context2.Request.DetachBlob(memoryBlob); context2.Close(); context2 = null; goto Label_07AA; } byte[] bytes = null; byte[] inArray = null; string str4 = null; length++; while (length < knownHeader.Length) { if (((knownHeader[length] != ' ') && (knownHeader[length] != '\t')) && ((knownHeader[length] != '\r') && (knownHeader[length] != '\n'))) { break; } length++; } string incomingBlob = (length < knownHeader.Length) ? knownHeader.Substring(length) : ""; IPrincipal principal2 = null; switch (none) { case System.Net.AuthenticationSchemes.Digest: { binding = this.GetChannelBinding(connectionId, isSecureConnection, extendedProtectionPolicy); authentication3 = new NTAuthentication(true, "WDigest", null, this.GetContextFlags(extendedProtectionPolicy, isSecureConnection), binding); str4 = authentication3.GetOutgoingDigestBlob(incomingBlob, verb, null, this.Realm, false, false, out invalidToken); if (invalidToken == SecurityStatus.OK) { str4 = null; } if (!authentication3.IsValidContext) { break; } SafeCloseHandle contextToken = null; try { if (!this.CheckSpn(authentication3, isSecureConnection, extendedProtectionPolicy)) { internalServerError = HttpStatusCode.Unauthorized; } else { context2.Request.ServiceName = authentication3.ClientSpecifiedSpn; contextToken = authentication3.GetContextToken(out invalidToken); if (invalidToken != SecurityStatus.OK) { internalServerError = this.HttpStatusFromSecurityStatus(invalidToken); } else if (contextToken == null) { internalServerError = HttpStatusCode.Unauthorized; } else { principal2 = new WindowsPrincipal(this.CreateWindowsIdentity(contextToken.DangerousGetHandle(), "Digest", WindowsAccountType.Normal, true)); } } } finally { if (contextToken != null) { contextToken.Close(); } } newContext = authentication3; if (str4 != null) { challenge = "Digest " + str4; } goto Label_0761; } case System.Net.AuthenticationSchemes.Negotiate: case System.Net.AuthenticationSchemes.Ntlm: str6 = (none == System.Net.AuthenticationSchemes.Ntlm) ? "NTLM" : "Negotiate"; if ((digestContext == null) || !(digestContext.Package == str6)) { goto Label_0549; } authentication3 = digestContext; goto Label_056D; case System.Net.AuthenticationSchemes.Basic: try { bytes = Convert.FromBase64String(incomingBlob); incomingBlob = WebHeaderCollection.HeaderEncoding.GetString(bytes, 0, bytes.Length); length = incomingBlob.IndexOf(':'); if (length != -1) { string username = incomingBlob.Substring(0, length); string password = incomingBlob.Substring(length + 1); principal2 = new GenericPrincipal(new HttpListenerBasicIdentity(username, password), null); } else { internalServerError = HttpStatusCode.BadRequest; } } catch (FormatException) { } goto Label_0761; default: goto Label_0761; } internalServerError = this.HttpStatusFromSecurityStatus(invalidToken); goto Label_0761; Label_0549: binding = this.GetChannelBinding(connectionId, isSecureConnection, extendedProtectionPolicy); authentication3 = new NTAuthentication(true, str6, null, this.GetContextFlags(extendedProtectionPolicy, isSecureConnection), binding); Label_056D: try { bytes = Convert.FromBase64String(incomingBlob); } catch (FormatException) { internalServerError = HttpStatusCode.BadRequest; flag2 = true; } if (!flag2) { inArray = authentication3.GetOutgoingBlob(bytes, false, out invalidToken); flag2 = !authentication3.IsValidContext; if (flag2) { if (((invalidToken == SecurityStatus.InvalidHandle) && (digestContext == null)) && ((bytes != null) && (bytes.Length > 0))) { invalidToken = SecurityStatus.InvalidToken; } internalServerError = this.HttpStatusFromSecurityStatus(invalidToken); } } if (inArray != null) { str4 = Convert.ToBase64String(inArray); } if (!flag2) { if (authentication3.IsCompleted) { SafeCloseHandle handle2 = null; try { if (!this.CheckSpn(authentication3, isSecureConnection, extendedProtectionPolicy)) { internalServerError = HttpStatusCode.Unauthorized; } else { context2.Request.ServiceName = authentication3.ClientSpecifiedSpn; handle2 = authentication3.GetContextToken(out invalidToken); if (invalidToken != SecurityStatus.OK) { internalServerError = this.HttpStatusFromSecurityStatus(invalidToken); } else { WindowsPrincipal principal3 = new WindowsPrincipal(this.CreateWindowsIdentity(handle2.DangerousGetHandle(), authentication3.ProtocolName, WindowsAccountType.Normal, true)); principal2 = principal3; if (this.UnsafeConnectionNtlmAuthentication && (authentication3.ProtocolName == "NTLM")) { if (disconnectResult == null) { this.RegisterForDisconnectNotification(connectionId, ref disconnectResult); } if (disconnectResult != null) { lock (this.DisconnectResults.SyncRoot) { if (this.UnsafeConnectionNtlmAuthentication) { disconnectResult.AuthenticatedConnection = principal3; } } } } } } goto Label_0761; } finally { if (handle2 != null) { handle2.Close(); } } } newContext = authentication3; challenge = (none == System.Net.AuthenticationSchemes.Ntlm) ? "NTLM" : "Negotiate"; if (!string.IsNullOrEmpty(str4)) { challenge = challenge + " " + str4; } } Label_0761: if (principal2 != null) { context2.SetIdentity(principal2, str4); } else { if (Logging.On) { Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString("net_log_listener_create_valid_identity_failed")); } context2.Request.DetachBlob(memoryBlob); context2.Close(); context2 = null; } Label_07AA: list = null; if (context2 == null) { if (challenge != null) { AddChallenge(ref list, challenge); } else { if (newContext != null) { if (newContext == authentication3) { authentication3 = null; } if (newContext != digestContext) { NTAuthentication authentication4 = newContext; newContext = null; authentication4.CloseContext(); } else { newContext = null; } } if (internalServerError != HttpStatusCode.Unauthorized) { this.SendError(requestId, internalServerError, null); return null; } list = this.BuildChallenge(authenticationSchemes, connectionId, out newContext, extendedProtectionPolicy, isSecureConnection); } } if ((disconnectResult == null) && (newContext != null)) { this.RegisterForDisconnectNotification(connectionId, ref disconnectResult); if (disconnectResult == null) { if (newContext != null) { if (newContext == authentication3) { authentication3 = null; } if (newContext != digestContext) { NTAuthentication authentication5 = newContext; newContext = null; authentication5.CloseContext(); } else { newContext = null; } } this.SendError(requestId, HttpStatusCode.InternalServerError, null); context2.Request.DetachBlob(memoryBlob); context2.Close(); return null; } } if (digestContext != newContext) { if (digestContext == authentication3) { authentication3 = null; } NTAuthentication authentication6 = digestContext; digestContext = newContext; disconnectResult.Session = newContext; if (authentication6 != null) { if ((authenticationSchemes & System.Net.AuthenticationSchemes.Digest) != System.Net.AuthenticationSchemes.None) { this.SaveDigestContext(authentication6); } else { authentication6.CloseContext(); } } } if (context2 == null) { this.SendError(requestId, ((list != null) && (list.Count > 0)) ? HttpStatusCode.Unauthorized : HttpStatusCode.Forbidden, list); return null; } if (!stoleBlob) { stoleBlob = true; context2.Request.ReleasePins(); } context3 = context2; } catch { if (context2 != null) { context2.Request.DetachBlob(memoryBlob); context2.Close(); } if (newContext != null) { if (newContext == authentication3) { authentication3 = null; } if (newContext != digestContext) { NTAuthentication authentication7 = newContext; newContext = null; authentication7.CloseContext(); } else { newContext = null; } } throw; } finally { try { if ((digestContext != null) && (digestContext != newContext)) { if ((newContext == null) && (disconnectResult != null)) { disconnectResult.Session = null; } if ((authenticationSchemes & System.Net.AuthenticationSchemes.Digest) != System.Net.AuthenticationSchemes.None) { this.SaveDigestContext(digestContext); } else { digestContext.CloseContext(); } } if (((authentication3 != null) && (digestContext != authentication3)) && (newContext != authentication3)) { authentication3.CloseContext(); } } finally { if (disconnectResult != null) { disconnectResult.FinishOwningDisconnectHandling(); } } } return context3; }
internal HttpListenerContext HandleAuthentication(RequestContextBase memoryBlob, out bool stoleBlob) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() memoryBlob:0x" + ((IntPtr) memoryBlob.RequestBlob).ToString("x")); string challenge = null; stoleBlob = false; // Some things we need right away. Lift them out now while it's convenient. string verb = UnsafeNclNativeMethods.HttpApi.GetVerb(memoryBlob.RequestBlob); string authorizationHeader = UnsafeNclNativeMethods.HttpApi.GetKnownHeader(memoryBlob.RequestBlob, (int) HttpRequestHeader.Authorization); ulong connectionId = memoryBlob.RequestBlob->ConnectionId; ulong requestId = memoryBlob.RequestBlob->RequestId; bool isSecureConnection = memoryBlob.RequestBlob->pSslInfo != null; GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() authorizationHeader:" + ValidationHelper.ToString(authorizationHeader)); // if the app has turned on AuthPersistence, an anonymous request might // be authenticated by virtue of it coming on a connection that was // previously authenticated. // assurance that we do this only for NTLM/Negotiate is not here, but in the // code that caches WindowsIdentity instances in the Dictionary. DisconnectAsyncResult disconnectResult = (DisconnectAsyncResult) DisconnectResults[connectionId]; if (UnsafeConnectionNtlmAuthentication) { if (authorizationHeader == null) { WindowsPrincipal principal = disconnectResult == null ? null : disconnectResult.AuthenticatedConnection; if (principal != null) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() got principal:" + ValidationHelper.ToString(principal) + " principal.Identity.Name:" + ValidationHelper.ToString(principal.Identity.Name) + " creating request"); stoleBlob = true; HttpListenerContext ntlmContext = new HttpListenerContext(this, memoryBlob); ntlmContext.SetIdentity(principal, null); ntlmContext.Request.ReleasePins(); return ntlmContext; } } else { // They sent an authorization - destroy their previous credentials. GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() clearing principal cache"); if (disconnectResult != null) { disconnectResult.AuthenticatedConnection = null; } } } // Figure out what schemes we're allowing, what context we have. stoleBlob = true; HttpListenerContext httpContext = null; NTAuthentication oldContext = null; NTAuthentication newContext = null; NTAuthentication context = null; AuthenticationSchemes headerScheme = AuthenticationSchemes.None; AuthenticationSchemes authenticationScheme = AuthenticationSchemes; ExtendedProtectionPolicy extendedProtectionPolicy = m_ExtendedProtectionPolicy; try { // Take over handling disconnects for now. if (disconnectResult != null && !disconnectResult.StartOwningDisconnectHandling()) { // Oops! Just disconnected just then. Pretend we didn't see the disconnectResult. disconnectResult = null; } // Pick out the old context now. By default, it'll be removed in the finally, unless context is set somewhere. if (disconnectResult != null) { oldContext = disconnectResult.Session; } httpContext = new HttpListenerContext(this, memoryBlob); AuthenticationSelectorInfo authenticationSelector = m_AuthenticationDelegate; if (authenticationSelector != null) { try { httpContext.Request.ReleasePins(); authenticationScheme = authenticationSelector.Delegate(httpContext.Request); // Cache the results of authenticationSelector (if any) httpContext.AuthenticationSchemes = authenticationScheme; if (!authenticationSelector.AdvancedAuth && (authenticationScheme & (AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm | AuthenticationSchemes.Digest)) != 0) { throw m_SecurityException; } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() AuthenticationSchemeSelectorDelegate() returned authenticationScheme:" + authenticationScheme); } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; if (Logging.On) Logging.PrintError(Logging.HttpListener, this, "HandleAuthentication", SR.GetString(SR.net_log_listener_delegate_exception, exception)); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() AuthenticationSchemeSelectorDelegate() returned authenticationScheme:" + authenticationScheme); SendError(requestId, HttpStatusCode.InternalServerError, null); httpContext.Close(); return null; } } else { // We didn't give the request to the user yet, so we haven't lost control of the unmanaged blob and can // continue to reuse the buffer. stoleBlob = false; } ExtendedProtectionSelector extendedProtectionSelector = m_ExtendedProtectionSelectorDelegate; if (extendedProtectionSelector != null) { extendedProtectionPolicy = extendedProtectionSelector(httpContext.Request); if (extendedProtectionPolicy == null) { extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never); } // Cache the results of extendedProtectionSelector (if any) httpContext.ExtendedProtectionPolicy = extendedProtectionPolicy; } // Then figure out what scheme they're trying (if any are allowed) int index = -1; if (authorizationHeader != null && (authenticationScheme & ~AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None) { // Find the end of the scheme name. Trust that HTTP.SYS parsed out just our header ok. for (index = 0; index < authorizationHeader.Length; index++) { if (authorizationHeader[index] == ' ' || authorizationHeader[index] == '\t' || authorizationHeader[index] == '\r' || authorizationHeader[index] == '\n') { break; } } // Currently only allow one Authorization scheme/header per request. if (index < authorizationHeader.Length) { if ((authenticationScheme & AuthenticationSchemes.Negotiate) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, NegotiateClient.AuthType, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Negotiate; } else if ((authenticationScheme & AuthenticationSchemes.Ntlm) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, NtlmClient.AuthType, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Ntlm; } else if ((authenticationScheme & AuthenticationSchemes.Digest) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, DigestClient.AuthType, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Digest; } else if ((authenticationScheme & AuthenticationSchemes.Basic) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, BasicClient.AuthType, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Basic; } else { if (Logging.On) Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString(SR.net_log_listener_unsupported_authentication_scheme, authorizationHeader , authenticationScheme)); } } } // httpError holds the error we will return if an Authorization header is present but can't be authenticated HttpStatusCode httpError = HttpStatusCode.InternalServerError; bool error = false; // See if we found an acceptable auth header if (headerScheme == AuthenticationSchemes.None) { if (Logging.On) Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString(SR.net_log_listener_unmatched_authentication_scheme, ValidationHelper.ToString(authenticationScheme), (authorizationHeader == null ? "<null>" : authorizationHeader))); // If anonymous is allowed, just return the context. Otherwise go for the 401. if ((authenticationScheme & AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None) { if (!stoleBlob) { stoleBlob = true; httpContext.Request.ReleasePins(); } return httpContext; } httpError = HttpStatusCode.Unauthorized; httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); httpContext = null; } else { // Perform Authentication byte[] bytes = null; byte[] decodedOutgoingBlob = null; string outBlob = null; // Find the beginning of the blob. Trust that HTTP.SYS parsed out just our header ok. for (index++; index < authorizationHeader.Length; index++) { if (authorizationHeader[index] != ' ' && authorizationHeader[index] != '\t' && authorizationHeader[index] != '\r' && authorizationHeader[index] != '\n') { break; } } string inBlob = index < authorizationHeader.Length ? authorizationHeader.Substring(index) : ""; IPrincipal principal = null; SecurityStatus statusCodeNew; ChannelBinding binding; GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() Performing Authentication headerScheme:" + ValidationHelper.ToString(headerScheme)); switch (headerScheme) { case AuthenticationSchemes.Digest: GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() package:WDigest headerScheme:" + headerScheme); // WDigest had some weird behavior. This is what I have discovered: // Local accounts don't work, only domain accounts. The domain (i.e. REDMOND) is implied. Not sure how it is chosen. // If the domain is specified and the credentials are correct, it works. If they're not (domain, username or password): // AcceptSecurityContext (GetOutgoingDigestBlob) returns success but with a bogus 4k challenge, and // QuerySecurityContextToken (GetContextToken) fails with NoImpersonation. // If the domain isn't specified, AcceptSecurityContext returns NoAuthenticatingAuthority for a bad username, // and LogonDenied for a bad password. // Also interesting is that WDigest requires us to keep a reference to the previous context, but fails if we // actually pass it in! (It't ok to pass it in for the first request, but not if nc > 1.) For Whidbey, // we create a new context and associate it with the connection, just like NTLM, but instead of using it for // the next request on the connection, we always create a new context and swap the old one out. As long // as we keep the old one around until after we authenticate with the new one, it works. For this reason, // we also keep these contexts around past the lifetime of the connection, so that KeepAlive=false works. binding = GetChannelBinding(connectionId, isSecureConnection, extendedProtectionPolicy); context = new NTAuthentication(true, NegotiationInfoClass.WDigest, null, GetContextFlags(extendedProtectionPolicy, isSecureConnection), binding); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() verb:" + verb + " context.IsValidContext:" + context.IsValidContext.ToString()); outBlob = context.GetOutgoingDigestBlob(inBlob, verb, null, Realm, false, false, out statusCodeNew); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() GetOutgoingDigestBlob() returned IsCompleted:" + context.IsCompleted + " statusCodeNew:" + statusCodeNew + " outBlob:[" + outBlob + "]"); // WDigest bug: sometimes when AcceptSecurityContext returns success, it provides a bogus, empty 4k buffer. // Ignore it. (Should find out what's going on here from WDigest people.) if (statusCodeNew == SecurityStatus.OK) { outBlob = null; } if (context.IsValidContext) { SafeCloseHandle userContext = null; try { if (!CheckSpn(context, isSecureConnection, extendedProtectionPolicy)) { httpError = HttpStatusCode.Unauthorized; } else { httpContext.Request.ServiceName = context.ClientSpecifiedSpn; userContext = context.GetContextToken(out statusCodeNew); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() GetContextToken() returned:" + statusCodeNew.ToString()); if (statusCodeNew != SecurityStatus.OK) { httpError = HttpStatusFromSecurityStatus(statusCodeNew); } else if (userContext == null) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() error: GetContextToken() returned:null statusCodeNew:" + statusCodeNew.ToString()); httpError = HttpStatusCode.Unauthorized; } else { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() creating new WindowsIdentity() from userContext:" + userContext.DangerousGetHandle().ToString("x8")); principal = new WindowsPrincipal(CreateWindowsIdentity(userContext.DangerousGetHandle(), DigestClient.AuthType, WindowsAccountType.Normal, true)); } } } finally { if (userContext!=null) { userContext.Close(); } } newContext = context; if (outBlob != null) { challenge = DigestClient.AuthType + " " + outBlob; } } else { httpError = HttpStatusFromSecurityStatus(statusCodeNew); } break; case AuthenticationSchemes.Negotiate: case AuthenticationSchemes.Ntlm: GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() headerScheme:" + headerScheme); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() returned context#" + ValidationHelper.HashString(oldContext) + " for connectionId:" + connectionId); string package = headerScheme == AuthenticationSchemes.Ntlm ? NtlmClient.AuthType : NegotiateClient.AuthType; if (oldContext != null && oldContext.Package == package) { context = oldContext; } else { binding = GetChannelBinding(connectionId, isSecureConnection, extendedProtectionPolicy); context = new NTAuthentication(true, package, null, GetContextFlags(extendedProtectionPolicy, isSecureConnection), binding); } try { bytes = Convert.FromBase64String(inBlob); } catch (FormatException) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() FromBase64String threw a FormatException."); httpError = HttpStatusCode.BadRequest; error = true; } if (!error) { decodedOutgoingBlob = context.GetOutgoingBlob(bytes, false, out statusCodeNew); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() GetOutgoingBlob() returned IsCompleted:" + context.IsCompleted + " statusCodeNew:" + statusCodeNew); error = !context.IsValidContext; if (error) { // Bug #474228: SSPI Workaround // If a client sends up a blob on the initial request, Negotiate returns SEC_E_INVALID_HANDLE // when it should return SEC_E_INVALID_TOKEN. if (statusCodeNew == SecurityStatus.InvalidHandle && oldContext == null && bytes != null && bytes.Length > 0) { statusCodeNew = SecurityStatus.InvalidToken; } httpError = HttpStatusFromSecurityStatus(statusCodeNew); } } if (decodedOutgoingBlob!=null) { outBlob = Convert.ToBase64String(decodedOutgoingBlob); } if (!error) { if (context.IsCompleted) { SafeCloseHandle userContext = null; try { if (!CheckSpn(context, isSecureConnection, extendedProtectionPolicy)) { httpError = HttpStatusCode.Unauthorized; } else { httpContext.Request.ServiceName = context.ClientSpecifiedSpn; userContext = context.GetContextToken(out statusCodeNew); if (statusCodeNew != SecurityStatus.OK) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() GetContextToken() failed with statusCodeNew:" + statusCodeNew.ToString()); httpError = HttpStatusFromSecurityStatus(statusCodeNew); } else { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() creating new WindowsIdentity() from userContext:" + userContext.DangerousGetHandle().ToString("x8")); WindowsPrincipal windowsPrincipal = new WindowsPrincipal(CreateWindowsIdentity(userContext.DangerousGetHandle(), context.ProtocolName, WindowsAccountType.Normal, true)); principal = windowsPrincipal; // if appropriate, cache this credential on this connection if (UnsafeConnectionNtlmAuthentication && context.ProtocolName == NegotiationInfoClass.NTLM) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() inserting principal#" + ValidationHelper.HashString(principal) + " for connectionId:" + connectionId); // We may need to call WaitForDisconnect. if (disconnectResult == null) { RegisterForDisconnectNotification(connectionId, ref disconnectResult); } if (disconnectResult != null) { lock (DisconnectResults.SyncRoot) { if (UnsafeConnectionNtlmAuthentication) { disconnectResult.AuthenticatedConnection = windowsPrincipal; } } } else { // Registration failed - UnsafeConnectionNtlmAuthentication ignored. GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() RegisterForDisconnectNotification() failed."); } } } } } finally { if (userContext!=null) { userContext.Close(); } } } else { // auth incomplete newContext = context; challenge = (headerScheme==AuthenticationSchemes.Ntlm ? NtlmClient.AuthType : NegotiateClient.AuthType); if (!String.IsNullOrEmpty(outBlob)) { challenge += " " + outBlob; } } } break; case AuthenticationSchemes.Basic: try { bytes = Convert.FromBase64String(inBlob); inBlob = WebHeaderCollection.HeaderEncoding.GetString(bytes, 0, bytes.Length); index = inBlob.IndexOf(':'); if (index!=-1) { string userName = inBlob.Substring(0, index); string password = inBlob.Substring(index+1); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() basic identity found, userName:"******"HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() FromBase64String threw a FormatException."); } break; } if (principal != null) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() got principal:" + ValidationHelper.ToString(principal) + " principal.Identity.Name:" + ValidationHelper.ToString(principal.Identity.Name) + " creating request"); httpContext.SetIdentity(principal, outBlob); } else { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() handshake has failed"); if(Logging.On)Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString(SR.net_log_listener_create_valid_identity_failed)); httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); httpContext = null; } } // if we're not giving a request to the application, we need to send an error ArrayList challenges = null; if (httpContext == null) { // If we already have a challenge, just use it. Otherwise put a challenge for each acceptable scheme. if (challenge != null) { AddChallenge(ref challenges, challenge); } else { // We're starting over. Any context SSPI might have wanted us to keep is useless. if (newContext != null) { if (newContext == context) { context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } // If we're sending something besides 401, do it here. if (httpError != HttpStatusCode.Unauthorized) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() failed context#" + ValidationHelper.HashString(context) + " for connectionId:" + connectionId + " because of error:" + httpError.ToString()); SendError(requestId, httpError, null); return null; } challenges = BuildChallenge(authenticationScheme, connectionId, out newContext, extendedProtectionPolicy, isSecureConnection); } } // Check if we need to call WaitForDisconnect, because if we do and it fails, we want to send a 500 instead. if (disconnectResult == null && newContext != null) { RegisterForDisconnectNotification(connectionId, ref disconnectResult); // Failed - send 500. if (disconnectResult == null) { if (newContext != null) { if (newContext == context) { context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() failed context#" + ValidationHelper.HashString(context) + " for connectionId:" + connectionId + " because of failed HttpWaitForDisconnect"); SendError(requestId, HttpStatusCode.InternalServerError, null); httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); return null; } } // Update Session if necessary. if (oldContext != newContext) { if (oldContext == context) { // Prevent the finally from closing this twice. context = null; } NTAuthentication toClose = oldContext; oldContext = newContext; disconnectResult.Session = newContext; if (toClose != null) { // Save digest context in digest cache, we may need it later because of // subsequest responses to the same req on the same/diff connection if ((authenticationScheme & AuthenticationSchemes.Digest) != 0) { SaveDigestContext(toClose); } else { toClose.CloseContext(); } } } // Send the 401 here. if (httpContext == null) { SendError(requestId, challenges != null && challenges.Count > 0 ? HttpStatusCode.Unauthorized : HttpStatusCode.Forbidden, challenges); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() SendUnauthorized(Scheme:" + authenticationScheme + ")"); return null; } if (!stoleBlob) { stoleBlob = true; httpContext.Request.ReleasePins(); } return httpContext; } catch { if (httpContext != null) { httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); } if (newContext != null) { if (newContext == context) { // Prevent the finally from closing this twice. context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } throw; } finally { try { // Clean up the previous context if necessary. if (oldContext != null && oldContext != newContext) { // Clear out Session if it wasn't already. if (newContext == null && disconnectResult != null) { disconnectResult.Session = null; } // Save digest context in digest cache, we may need it later because of // subsequest responses to the same req on the same/diff connection if ((authenticationScheme & AuthenticationSchemes.Digest) != 0) { SaveDigestContext(oldContext); } else { oldContext.CloseContext(); } } // Delete any context created but not stored. if (context != null && oldContext != context && newContext != context) { context.CloseContext(); } } finally { // Check if the connection got deleted while in this method, and clear out the hashtables if it did. // In a nested finally because if this doesn't happen, we leak. if (disconnectResult != null) { disconnectResult.FinishOwningDisconnectHandling(); } } } }
internal HttpListenerContext HandleAuthentication(RequestContextBase memoryBlob, out bool stoleBlob) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, "HandleAuthentication() memoryBlob:0x" + ((IntPtr)memoryBlob.RequestBlob).ToString("x")); string challenge = null; stoleBlob = false; // Some things we need right away. Lift them out now while it's convenient. string verb = Interop.HttpApi.GetVerb(memoryBlob.RequestBlob); string authorizationHeader = Interop.HttpApi.GetKnownHeader(memoryBlob.RequestBlob, (int)HttpRequestHeader.Authorization); ulong connectionId = memoryBlob.RequestBlob->ConnectionId; ulong requestId = memoryBlob.RequestBlob->RequestId; bool isSecureConnection = memoryBlob.RequestBlob->pSslInfo != null; if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"HandleAuthentication() authorizationHeader: ({authorizationHeader})"); // if the app has turned on AuthPersistence, an anonymous request might // be authenticated by virtue of it coming on a connection that was // previously authenticated. // assurance that we do this only for NTLM/Negotiate is not here, but in the // code that caches WindowsIdentity instances in the Dictionary. DisconnectAsyncResult disconnectResult; DisconnectResults.TryGetValue(connectionId, out disconnectResult); if (UnsafeConnectionNtlmAuthentication) { if (authorizationHeader == null) { WindowsPrincipal principal = disconnectResult?.AuthenticatedConnection; if (principal != null) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Principal: {principal} principal.Identity.Name: {principal.Identity.Name} creating request"); stoleBlob = true; HttpListenerContext ntlmContext = new HttpListenerContext(this, memoryBlob); ntlmContext.SetIdentity(principal, null); ntlmContext.Request.ReleasePins(); return ntlmContext; } } else { // They sent an authorization - destroy their previous credentials. if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Clearing principal cache"); if (disconnectResult != null) { disconnectResult.AuthenticatedConnection = null; } } } // Figure out what schemes we're allowing, what context we have. stoleBlob = true; HttpListenerContext httpContext = null; NTAuthentication oldContext = null; NTAuthentication newContext = null; NTAuthentication context = null; AuthenticationSchemes headerScheme = AuthenticationSchemes.None; AuthenticationSchemes authenticationScheme = AuthenticationSchemes; ExtendedProtectionPolicy extendedProtectionPolicy = _extendedProtectionPolicy; try { // Take over handling disconnects for now. if (disconnectResult != null && !disconnectResult.StartOwningDisconnectHandling()) { // Just disconnected just then. Pretend we didn't see the disconnectResult. disconnectResult = null; } // Pick out the old context now. By default, it'll be removed in the finally, unless context is set somewhere. if (disconnectResult != null) { oldContext = disconnectResult.Session; } httpContext = new HttpListenerContext(this, memoryBlob); AuthenticationSchemeSelector authenticationSelector = _authenticationDelegate; if (authenticationSelector != null) { try { httpContext.Request.ReleasePins(); authenticationScheme = authenticationSelector(httpContext.Request); // Cache the results of authenticationSelector (if any) httpContext.AuthenticationSchemes = authenticationScheme; if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"AuthenticationScheme: {authenticationScheme}"); } catch (Exception exception) when (!ExceptionCheck.IsFatal(exception)) { if (NetEventSource.IsEnabled) { NetEventSource.Error(this, SR.Format(SR.net_log_listener_delegate_exception, exception)); NetEventSource.Info(this, $"authenticationScheme: {authenticationScheme}"); } SendError(requestId, HttpStatusCode.InternalServerError, null); httpContext.Close(); return null; } } else { // We didn't give the request to the user yet, so we haven't lost control of the unmanaged blob and can // continue to reuse the buffer. stoleBlob = false; } ExtendedProtectionSelector extendedProtectionSelector = _extendedProtectionSelectorDelegate; if (extendedProtectionSelector != null) { extendedProtectionPolicy = extendedProtectionSelector(httpContext.Request); if (extendedProtectionPolicy == null) { extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never); } // Cache the results of extendedProtectionSelector (if any) httpContext.ExtendedProtectionPolicy = extendedProtectionPolicy; } // Then figure out what scheme they're trying (if any are allowed) int index = -1; if (authorizationHeader != null && (authenticationScheme & ~AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None) { // Find the end of the scheme name. Trust that HTTP.SYS parsed out just our header ok. for (index = 0; index < authorizationHeader.Length; index++) { if (authorizationHeader[index] == ' ' || authorizationHeader[index] == '\t' || authorizationHeader[index] == '\r' || authorizationHeader[index] == '\n') { break; } } // Currently only allow one Authorization scheme/header per request. if (index < authorizationHeader.Length) { if ((authenticationScheme & AuthenticationSchemes.Negotiate) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, AuthenticationTypes.Negotiate, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Negotiate; } else if ((authenticationScheme & AuthenticationSchemes.Ntlm) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, AuthenticationTypes.NTLM, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Ntlm; } else if ((authenticationScheme & AuthenticationSchemes.Basic) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, AuthenticationTypes.Basic, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Basic; } else { if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_unsupported_authentication_scheme, authorizationHeader, authenticationScheme)); } } } // httpError holds the error we will return if an Authorization header is present but can't be authenticated HttpStatusCode httpError = HttpStatusCode.InternalServerError; bool error = false; // See if we found an acceptable auth header if (headerScheme == AuthenticationSchemes.None) { if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_unmatched_authentication_scheme, authenticationScheme.ToString(), (authorizationHeader == null ? "<null>" : authorizationHeader))); // If anonymous is allowed, just return the context. Otherwise go for the 401. if ((authenticationScheme & AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None) { if (!stoleBlob) { stoleBlob = true; httpContext.Request.ReleasePins(); } return httpContext; } httpError = HttpStatusCode.Unauthorized; httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); httpContext = null; } else { // Perform Authentication byte[] bytes = null; byte[] decodedOutgoingBlob = null; string outBlob = null; // Find the beginning of the blob. Trust that HTTP.SYS parsed out just our header ok. for (index++; index < authorizationHeader.Length; index++) { if (authorizationHeader[index] != ' ' && authorizationHeader[index] != '\t' && authorizationHeader[index] != '\r' && authorizationHeader[index] != '\n') { break; } } string inBlob = index < authorizationHeader.Length ? authorizationHeader.Substring(index) : ""; IPrincipal principal = null; SecurityStatusPal statusCodeNew; ChannelBinding binding; if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Performing Authentication headerScheme: {headerScheme}"); switch (headerScheme) { case AuthenticationSchemes.Negotiate: case AuthenticationSchemes.Ntlm: if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"context: {oldContext} for connectionId: {connectionId}"); string package = headerScheme == AuthenticationSchemes.Ntlm ? NegotiationInfoClass.NTLM : NegotiationInfoClass.Negotiate; if (oldContext != null && oldContext.Package == package) { context = oldContext; } else { binding = GetChannelBinding(connectionId, isSecureConnection, extendedProtectionPolicy); ContextFlagsPal contextFlags = GetContextFlags(extendedProtectionPolicy, isSecureConnection); context = new NTAuthentication(true, package, CredentialCache.DefaultNetworkCredentials, null, contextFlags, binding); } try { bytes = Convert.FromBase64String(inBlob); } catch (FormatException) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"FormatException from FormBase64String"); httpError = HttpStatusCode.BadRequest; error = true; } if (!error) { decodedOutgoingBlob = context.GetOutgoingBlob(bytes, false, out statusCodeNew); if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"GetOutgoingBlob returned IsCompleted: {context.IsCompleted} and statusCodeNew: {statusCodeNew}"); error = !context.IsValidContext; if (error) { // SSPI Workaround // If a client sends up a blob on the initial request, Negotiate returns SEC_E_INVALID_HANDLE // when it should return SEC_E_INVALID_TOKEN. if (statusCodeNew.ErrorCode == SecurityStatusPalErrorCode.InvalidHandle && oldContext == null && bytes != null && bytes.Length > 0) { statusCodeNew = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidToken); } httpError = HttpStatusFromSecurityStatus(statusCodeNew.ErrorCode); } } if (decodedOutgoingBlob != null) { outBlob = Convert.ToBase64String(decodedOutgoingBlob); } if (!error) { if (context.IsCompleted) { SecurityContextTokenHandle userContext = null; try { if (!CheckSpn(context, isSecureConnection, extendedProtectionPolicy)) { httpError = HttpStatusCode.Unauthorized; } else { httpContext.Request.ServiceName = context.ClientSpecifiedSpn; SafeDeleteContext securityContext = context.GetContext(out statusCodeNew); if (statusCodeNew.ErrorCode != SecurityStatusPalErrorCode.OK) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"HandleAuthentication GetContextToken failed with statusCodeNew: {statusCodeNew}"); } httpError = HttpStatusFromSecurityStatus(statusCodeNew.ErrorCode); } else { SSPIWrapper.QuerySecurityContextToken(GlobalSSPI.SSPIAuth, securityContext, out userContext); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"HandleAuthentication creating new WindowsIdentity from user context: {userContext.DangerousGetHandle().ToString("x8")}"); } WindowsPrincipal windowsPrincipal = new WindowsPrincipal( new WindowsIdentity(userContext.DangerousGetHandle(), context.ProtocolName)); principal = windowsPrincipal; // if appropriate, cache this credential on this connection if (UnsafeConnectionNtlmAuthentication && context.ProtocolName == NegotiationInfoClass.NTLM) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"HandleAuthentication inserting principal: {principal} for connectionId: {connectionId}"); } // We may need to call WaitForDisconnect. if (disconnectResult == null) { RegisterForDisconnectNotification(connectionId, ref disconnectResult); } if (disconnectResult != null) { lock ((DisconnectResults as ICollection).SyncRoot) { if (UnsafeConnectionNtlmAuthentication) { disconnectResult.AuthenticatedConnection = windowsPrincipal; } } } else { // Registration failed - UnsafeConnectionNtlmAuthentication ignored. if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"HandleAuthentication RegisterForDisconnectNotification failed."); } } } } } } finally { if (userContext != null) { userContext.Close(); } } } else { // auth incomplete newContext = context; challenge = (headerScheme == AuthenticationSchemes.Ntlm ? NegotiationInfoClass.NTLM : NegotiationInfoClass.Negotiate); if (!String.IsNullOrEmpty(outBlob)) { challenge += " " + outBlob; } } } break; case AuthenticationSchemes.Basic: try { bytes = Convert.FromBase64String(inBlob); inBlob = WebHeaderEncoding.GetString(bytes, 0, bytes.Length); index = inBlob.IndexOf(':'); if (index != -1) { string userName = inBlob.Substring(0, index); string password = inBlob.Substring(index + 1); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"Basic Identity found, userName: {userName}"); } principal = new GenericPrincipal(new HttpListenerBasicIdentity(userName, password), null); } else { httpError = HttpStatusCode.BadRequest; } } catch (FormatException) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"FromBase64String threw a FormatException."); } } break; } if (principal != null) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"Got principal: {principal}, IdentityName: {principal.Identity.Name} for creating request."); } httpContext.SetIdentity(principal, outBlob); } else { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Handshake has failed."); } httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); httpContext = null; } } // if we're not giving a request to the application, we need to send an error ArrayList challenges = null; if (httpContext == null) { // If we already have a challenge, just use it. Otherwise put a challenge for each acceptable scheme. if (challenge != null) { AddChallenge(ref challenges, challenge); } else { // We're starting over. Any context SSPI might have wanted us to keep is useless. if (newContext != null) { if (newContext == context) { context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } // If we're sending something besides 401, do it here. if (httpError != HttpStatusCode.Unauthorized) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, "ConnectionId:" + connectionId + " because of error:" + httpError.ToString()); SendError(requestId, httpError, null); return null; } challenges = BuildChallenge(authenticationScheme, connectionId, out newContext, extendedProtectionPolicy, isSecureConnection); } } // Check if we need to call WaitForDisconnect, because if we do and it fails, we want to send a 500 instead. if (disconnectResult == null && newContext != null) { RegisterForDisconnectNotification(connectionId, ref disconnectResult); // Failed - send 500. if (disconnectResult == null) { if (newContext != null) { if (newContext == context) { context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } if (NetEventSource.IsEnabled) NetEventSource.Info(this, "connectionId:" + connectionId + " because of failed HttpWaitForDisconnect"); SendError(requestId, HttpStatusCode.InternalServerError, null); httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); return null; } } // Update Session if necessary. if (oldContext != newContext) { if (oldContext == context) { // Prevent the finally from closing this twice. context = null; } NTAuthentication toClose = oldContext; oldContext = newContext; disconnectResult.Session = newContext; if (toClose != null) { toClose.CloseContext(); } } // Send the 401 here. if (httpContext == null) { SendError(requestId, challenges != null && challenges.Count > 0 ? HttpStatusCode.Unauthorized : HttpStatusCode.Forbidden, challenges); if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Scheme:" + authenticationScheme); return null; } if (!stoleBlob) { stoleBlob = true; httpContext.Request.ReleasePins(); } return httpContext; } catch { if (httpContext != null) { httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); } if (newContext != null) { if (newContext == context) { // Prevent the finally from closing this twice. context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } throw; } finally { try { // Clean up the previous context if necessary. if (oldContext != null && oldContext != newContext) { // Clear out Session if it wasn't already. if (newContext == null && disconnectResult != null) { disconnectResult.Session = null; } oldContext.CloseContext(); } // Delete any context created but not stored. if (context != null && oldContext != context && newContext != context) { context.CloseContext(); } } finally { // Check if the connection got deleted while in this method, and clear out the hashtables if it did. // In a nested finally because if this doesn't happen, we leak. if (disconnectResult != null) { disconnectResult.FinishOwningDisconnectHandling(); } } } }