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();
}
}
}
}