Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / whidbey / NetFXspW7 / ndp / fx / src / Net / System / Net / HttpListener.cs / 6 / HttpListener.cs
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
//disabled until BindHandle has an overload that accepts Criticalhandles
#pragma warning disable 618
namespace System.Net {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Security;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Principal;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32;
public class HttpListenerBasicIdentity : GenericIdentity
{
private string m_Password;
public HttpListenerBasicIdentity(string username, string password) :
base(username, BasicClient.AuthType)
{
m_Password = password;
}
public virtual string Password
{
get
{
return m_Password;
}
}
}
internal abstract unsafe class RequestContextBase : IDisposable
{
private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* m_MemoryBlob;
private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* m_OriginalBlobAddress;
private byte[] m_BackingBuffer;
// Must call this from derived class' constructors.
protected void BaseConstruction(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* requestBlob)
{
if (requestBlob == null)
{
GC.SuppressFinalize(this);
}
else
{
m_MemoryBlob = requestBlob;
}
}
// ReleasePins() should be called exactly once. It must be called before Dispose() is called, which means it must be called
// before an object (HttpListenerReqeust) which closes the RequestContext on demand is returned to the application.
internal void ReleasePins()
{
GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::ReleasePins()|ReleasePins() called twice.");
m_OriginalBlobAddress = m_MemoryBlob;
UnsetBlob();
OnReleasePins();
}
protected abstract void OnReleasePins();
public void Close()
{
Dispose();
}
public void Dispose()
{
GlobalLog.Assert(m_MemoryBlob == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins().");
Dispose(true);
}
protected virtual void Dispose(bool disposing) { }
~RequestContextBase()
{
Dispose(false);
}
internal UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* RequestBlob
{
get
{
GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::Dispose()|RequestBlob requested after ReleasePins().");
return m_MemoryBlob;
}
}
internal byte[] RequestBuffer
{
get
{
return m_BackingBuffer;
}
}
internal uint Size
{
get
{
return (uint) m_BackingBuffer.Length;
}
}
internal IntPtr OriginalBlobAddress
{
get
{
UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* blob = m_MemoryBlob;
return (IntPtr) (blob == null ? m_OriginalBlobAddress : blob);
}
}
protected void SetBlob(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* requestBlob)
{
GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::Dispose()|SetBlob() called after ReleasePins().");
if (requestBlob == null)
{
UnsetBlob();
return;
}
if (m_MemoryBlob == null)
{
GC.ReRegisterForFinalize(this);
}
m_MemoryBlob = requestBlob;
}
protected void UnsetBlob()
{
if (m_MemoryBlob != null)
{
GC.SuppressFinalize(this);
}
m_MemoryBlob = null;
}
protected void SetBuffer(int size)
{
m_BackingBuffer = size == 0 ? null : new byte[size];
}
}
internal unsafe class AsyncRequestContext : RequestContextBase
{
private NativeOverlapped* m_NativeOverlapped;
private ListenerAsyncResult m_Result;
internal AsyncRequestContext(ListenerAsyncResult result)
{
m_Result = result;
BaseConstruction(Allocate(0));
}
private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* Allocate(uint size)
{
uint newSize = size != 0 ? size : RequestBuffer == null ? 4096 : Size;
if (m_NativeOverlapped != null && newSize != RequestBuffer.Length)
{
NativeOverlapped* nativeOverlapped = m_NativeOverlapped;
m_NativeOverlapped = null;
Overlapped.Free(nativeOverlapped);
}
if (m_NativeOverlapped == null)
{
SetBuffer(checked((int) newSize));
Overlapped overlapped = new Overlapped();
overlapped.AsyncResult = m_Result;
m_NativeOverlapped = overlapped.Pack(ListenerAsyncResult.IOCallback, RequestBuffer);
return (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST*) Marshal.UnsafeAddrOfPinnedArrayElement(RequestBuffer, 0);
}
return RequestBlob;
}
internal void Reset(ulong requestId, uint size)
{
SetBlob(Allocate(size));
RequestBlob->RequestId = requestId;
}
protected override void OnReleasePins()
{
if (m_NativeOverlapped != null)
{
NativeOverlapped* nativeOverlapped = m_NativeOverlapped;
m_NativeOverlapped = null;
Overlapped.Free(nativeOverlapped);
}
}
protected override void Dispose(bool disposing)
{
if (m_NativeOverlapped != null)
{
GlobalLog.Assert(!disposing, "AsyncRequestContext::Dispose()|Must call ReleasePins() before calling Dispose().");
if (!NclUtilities.HasShutdownStarted || disposing)
{
Overlapped.Free(m_NativeOverlapped);
}
}
base.Dispose(disposing);
}
internal NativeOverlapped* NativeOverlapped
{
get
{
return m_NativeOverlapped;
}
}
}
internal unsafe class SyncRequestContext : RequestContextBase
{
private GCHandle m_PinnedHandle;
internal SyncRequestContext(int size)
{
BaseConstruction(Allocate(size));
}
private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* Allocate(int size)
{
if (m_PinnedHandle.IsAllocated)
{
if (RequestBuffer.Length == size)
{
return RequestBlob;
}
m_PinnedHandle.Free();
}
SetBuffer(size);
if (RequestBuffer == null)
{
return null;
}
m_PinnedHandle = GCHandle.Alloc(RequestBuffer, GCHandleType.Pinned);
return (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST*) Marshal.UnsafeAddrOfPinnedArrayElement(RequestBuffer, 0);
}
internal void Reset(int size)
{
SetBlob(Allocate(size));
}
protected override void OnReleasePins()
{
if (m_PinnedHandle.IsAllocated)
{
m_PinnedHandle.Free();
}
}
protected override void Dispose(bool disposing)
{
if (m_PinnedHandle.IsAllocated)
{
GlobalLog.Assert(!disposing, "AsyncRequestContext::Dispose()|Must call ReleasePins() before calling Dispose().");
if (!NclUtilities.HasShutdownStarted || disposing)
{
m_PinnedHandle.Free();
}
}
base.Dispose(disposing);
}
}
public sealed unsafe class HttpListener : IDisposable
{
private static readonly Type ChannelBindingStatusType = typeof(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS);
private static readonly int RequestChannelBindStatusSize =
Marshal.SizeOf(typeof(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS));
private static byte[] s_WwwAuthenticateBytes = new byte[]
{
(byte) 'W', (byte) 'W', (byte) 'W', (byte) '-', (byte) 'A', (byte) 'u', (byte) 't', (byte) 'h',
(byte) 'e', (byte) 'n', (byte) 't', (byte) 'i', (byte) 'c', (byte) 'a', (byte) 't', (byte) 'e'
};
private class AuthenticationSelectorInfo
{
private AuthenticationSchemeSelector m_SelectorDelegate;
private bool m_CanUseAdvancedAuth;
internal AuthenticationSelectorInfo(AuthenticationSchemeSelector selectorDelegate, bool canUseAdvancedAuth)
{
Debug.Assert(selectorDelegate != null);
m_SelectorDelegate = selectorDelegate;
m_CanUseAdvancedAuth = canUseAdvancedAuth;
}
internal AuthenticationSchemeSelector Delegate
{
get
{
return m_SelectorDelegate;
}
}
internal bool AdvancedAuth
{
get
{
return m_CanUseAdvancedAuth;
}
}
}
private AuthenticationSelectorInfo m_AuthenticationDelegate;
private AuthenticationSchemes m_AuthenticationScheme = AuthenticationSchemes.Anonymous;
private SecurityException m_SecurityException;
private string m_Realm;
private SafeCloseHandle m_RequestQueueHandle;
private bool m_RequestHandleBound;
private State m_State;
private HttpListenerPrefixCollection m_Prefixes;
private bool m_IgnoreWriteExceptions;
private bool m_UnsafeConnectionNtlmAuthentication;
private ExtendedProtectionSelector m_ExtendedProtectionSelectorDelegate;
private ExtendedProtectionPolicy m_ExtendedProtectionPolicy;
private ServiceNameStore m_DefaultServiceNames;
private Hashtable m_DisconnectResults; // ulong -> DisconnectAsyncResult
private object m_InternalLock;
internal Hashtable m_UriPrefixes = new Hashtable();
public delegate ExtendedProtectionPolicy ExtendedProtectionSelector(HttpListenerRequest request);
public HttpListener()
{
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "HttpListener", "");
if (!UnsafeNclNativeMethods.HttpApi.Supported) {
throw new PlatformNotSupportedException();
}
m_State = State.Stopped;
m_InternalLock = new object();
m_DefaultServiceNames = new ServiceNameStore();
// default: no CBT checks on any platform (appcompat reasons); applies also to PolicyEnforcement
// config element
m_ExtendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never);
if (Logging.On) Logging.Exit(Logging.HttpListener, this, "HttpListener", "");
}
internal SafeCloseHandle RequestQueueHandle {
get {
return m_RequestQueueHandle;
}
}
public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
get {
AuthenticationSelectorInfo selector = m_AuthenticationDelegate;
return selector == null ? null : selector.Delegate;
}
set {
CheckDisposed();
try
{
new SecurityPermission(SecurityPermissionFlag.ControlPrincipal).Demand();
m_AuthenticationDelegate = new AuthenticationSelectorInfo(value, true);
}
catch (SecurityException exception)
{
m_SecurityException = exception;
m_AuthenticationDelegate = new AuthenticationSelectorInfo(value, false);
}
}
}
public ExtendedProtectionSelector ExtendedProtectionSelectorDelegate
{
get {
return m_ExtendedProtectionSelectorDelegate;
}
set {
CheckDisposed();
if (value == null) {
throw new ArgumentNullException();
}
m_ExtendedProtectionSelectorDelegate = value;
}
}
public AuthenticationSchemes AuthenticationSchemes {
get {
return m_AuthenticationScheme;
}
set {
CheckDisposed();
// Enabling certain schemes requires special permissions.
if ((value & (AuthenticationSchemes.Digest | AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm)) != 0)
{
new SecurityPermission(SecurityPermissionFlag.ControlPrincipal).Demand();
}
m_AuthenticationScheme = value;
}
}
public ExtendedProtectionPolicy ExtendedProtectionPolicy
{
get {
return m_ExtendedProtectionPolicy;
}
set {
CheckDisposed();
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value.CustomChannelBinding != null)
{
throw new ArgumentException(SR.GetString(SR.net_listener_cannot_set_custom_cbt), "CustomChannelBinding");
}
m_ExtendedProtectionPolicy = value;
}
}
public ServiceNameCollection DefaultServiceNames
{
get {
return m_DefaultServiceNames.ServiceNames;
}
}
public string Realm {
get {
return m_Realm;
}
set {
CheckDisposed();
m_Realm = value;
}
}
public static bool IsSupported {
get {
return UnsafeNclNativeMethods.HttpApi.Supported;
}
}
public bool IsListening {
get {
return m_State==State.Started;
}
}
public bool IgnoreWriteExceptions {
get {
return m_IgnoreWriteExceptions;
}
set {
CheckDisposed();
m_IgnoreWriteExceptions = value;
}
}
public bool UnsafeConnectionNtlmAuthentication {
get {
return m_UnsafeConnectionNtlmAuthentication;
}
set {
CheckDisposed();
if (m_UnsafeConnectionNtlmAuthentication==value) {
return;
}
lock (DisconnectResults.SyncRoot)
{
if (m_UnsafeConnectionNtlmAuthentication == value)
{
return;
}
m_UnsafeConnectionNtlmAuthentication = value;
if (!value)
{
foreach (DisconnectAsyncResult result in DisconnectResults.Values)
{
result.AuthenticatedConnection = null;
}
}
}
}
}
private Hashtable DisconnectResults
{
get
{
if (m_DisconnectResults == null)
{
lock (m_InternalLock)
{
if (m_DisconnectResults == null)
{
m_DisconnectResults = Hashtable.Synchronized(new Hashtable());
}
}
}
return m_DisconnectResults;
}
}
internal void AddPrefix(string uriPrefix)
{
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "AddPrefix", "uriPrefix:" + uriPrefix);
string registeredPrefix = null;
try {
if (uriPrefix==null) {
throw new ArgumentNullException("uriPrefix");
}
(new WebPermission(NetworkAccess.Accept, uriPrefix)).Demand();
CheckDisposed();
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::AddPrefix() uriPrefix:" + uriPrefix);
int i;
if (string.Compare(uriPrefix, 0, "http://", 0, 7, StringComparison.OrdinalIgnoreCase)==0) {
i = 7;
}
else if (string.Compare(uriPrefix, 0, "https://", 0, 8, StringComparison.OrdinalIgnoreCase)==0) {
i = 8;
}
else {
throw new ArgumentException(SR.GetString(SR.net_listener_scheme), "uriPrefix");
}
bool inSquareBrakets = false;
int j = i;
while (j0) {
if (m_State==State.Started) {
foreach (string registeredPrefix in m_UriPrefixes.Values) {
// ignore possible failures
InternalRemovePrefix(registeredPrefix);
}
}
if (clear) {
m_UriPrefixes.Clear();
m_DefaultServiceNames.Clear();
}
}
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "RemoveAll", "");
}
}
[SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)]
internal void EnsureBoundHandle()
{
if (!m_RequestHandleBound)
{
lock (m_InternalLock)
{
if (!m_RequestHandleBound)
{
ThreadPool.BindHandle(m_RequestQueueHandle.DangerousGetHandle());
m_RequestHandleBound = true;
}
}
}
}
public void Start() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Start", "");
try {
CheckDisposed();
if (m_State==State.Started) {
return;
}
m_RequestQueueHandle = SafeCloseHandle.CreateRequestQueueHandle();
AddAll();
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::.ctor() SafeCloseHandle.CreateRequestQueueHandle() returned m_RequestQueueHandle:" + m_RequestQueueHandle);
m_State = State.Started;
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Start", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Start", "");
}
}
public void Stop() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Stop", "");
try {
CheckDisposed();
if (m_State==State.Stopped) {
return;
}
RemoveAll(false);
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Stop() calling SafeCloseHandle.Close()");
m_RequestQueueHandle.Close();
m_RequestHandleBound = false;
m_State = State.Stopped;
ClearDigestCache();
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Stop", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Stop", "");
}
}
public void Abort() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Abort", "");
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Abort()");
try {
if (m_RequestQueueHandle!=null)
{
m_RequestQueueHandle.Abort();
}
m_RequestHandleBound = false;
m_State = State.Closed;
ClearDigestCache();
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Abort", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Abort", "");
}
}
public void Close() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Close", "");
try {
GlobalLog.Print("HttpListenerRequest::Close()");
((IDisposable)this).Dispose();
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Close", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Close", "");
}
}
// old API, now private, and helper methods
private void Dispose(bool disposing) {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Dispose", "");
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Dispose()");
try {
if (m_State == State.Closed){
return;
}
Stop();
m_RequestHandleBound = false;
m_State = State.Closed;
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Dispose", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Dispose", "");
}
}
///
void IDisposable.Dispose() {
Dispose(true);
}
private uint InternalAddPrefix(string uriPrefix) {
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalAddPrefix() uriPrefix:" + uriPrefix + " calling UnsafeNclNativeMethods.HttpApi.HttpAddUrl");
uint statusCode = 0;
fixed (char* pUriPrefix = uriPrefix) {
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpAddUrl(
m_RequestQueueHandle,
(ushort*)pUriPrefix,
null);
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalAddPrefix() call to UnsafeNclNativeMethods.HttpApi.HttpAddUrl returned:" + statusCode);
return statusCode;
}
private bool InternalRemovePrefix(string uriPrefix) {
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalRemovePrefix() uriPrefix:" + uriPrefix + " calling UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl");
uint statusCode = 0;
fixed (char* pUriPrefix = uriPrefix) {
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl(
m_RequestQueueHandle,
(ushort*)pUriPrefix);
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalRemovePrefix() call to UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl returned:" + statusCode);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND)
{
return false;
}
return true;
}
private void AddAll() {
// go through the uri list and register for each one of them
if (m_UriPrefixes.Count>0) {
foreach (string registeredPrefix in m_UriPrefixes.Values) {
uint statusCode = InternalAddPrefix(registeredPrefix);
if (statusCode!=UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) {
Abort();
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS)
throw new HttpListenerException((int)statusCode, SR.GetString(SR.net_listener_already, registeredPrefix));
else
throw new HttpListenerException((int)statusCode);
}
}
}
}
public HttpListenerContext GetContext() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "GetContext", "");
SyncRequestContext memoryBlob = null;
HttpListenerContext httpContext = null;
bool stoleBlob = false;
try {
CheckDisposed();
if (m_State==State.Stopped) {
throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "Start()"));
}
if (m_UriPrefixes.Count==0) {
throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "AddPrefix()"));
}
uint statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
uint size = 4096;
ulong requestId = 0;
memoryBlob = new SyncRequestContext((int) size);
for (;;) {
for (;;) {
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() calling UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest RequestId:" + requestId);
uint bytesTransferred = 0;
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest(
m_RequestQueueHandle,
requestId,
(uint)UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY,
memoryBlob.RequestBlob,
size,
&bytesTransferred,
null);
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() call to UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest returned:" + statusCode);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER && requestId != 0) {
// we might get this if somebody stole our RequestId,
// we need to start all over again but we can reuse the buffer we just allocated
requestId = 0;
continue;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
// the buffer was not big enough to fit the headers, we need
// to read the RequestId returned, allocate a new buffer of the required size
size = bytesTransferred;
requestId = memoryBlob.RequestBlob->RequestId;
memoryBlob.Reset(checked((int) size));
continue;
}
break;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// someother bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
throw new HttpListenerException((int)statusCode);
}
// We need to hook up our authentication handling code here.
httpContext = HandleAuthentication(memoryBlob, out stoleBlob);
if (stoleBlob)
{
// The request has been handed to the user, which means this code can't reuse the blob. Reset it here.
memoryBlob = null;
stoleBlob = false;
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() HandleAuthentication() returned httpContext#" + ValidationHelper.HashString(httpContext));
// if the request survived authentication, return it to the user
if (httpContext!=null) {
return httpContext;
}
// HandleAuthentication may have cleaned this up.
if (memoryBlob == null)
{
memoryBlob = new SyncRequestContext(checked((int) size));
}
requestId = 0;
}
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "GetContext", exception);
throw;
} finally {
if (memoryBlob != null && !stoleBlob)
{
memoryBlob.ReleasePins();
memoryBlob.Close();
}
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "GetContext", "HttpListenerContext#" + ValidationHelper.HashString(httpContext) + " RequestTraceIdentifier#"+httpContext.Request.RequestTraceIdentifier);
}
}
[HostProtection(ExternalThreading=true)]
public IAsyncResult BeginGetContext(AsyncCallback callback, object state) {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "BeginGetContext", "");
ListenerAsyncResult asyncResult = null;
try {
CheckDisposed();
if (m_State==State.Stopped) {
throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "Start()"));
}
// prepare the ListenerAsyncResult object (this will have it's own
// event that the user can wait on for IO completion - which means we
// need to signal it when IO completes)
asyncResult = new ListenerAsyncResult(this, state, callback);
uint statusCode = asyncResult.QueueBeginGetContext();
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// someother bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
throw new HttpListenerException((int)statusCode);
}
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "BeginGetContext", exception);
throw;
} finally {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "BeginGetContext", "IAsyncResult#" + ValidationHelper.HashString(asyncResult));
}
return asyncResult;
}
public HttpListenerContext EndGetContext(IAsyncResult asyncResult) {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "EndGetContext", "IAsyncResult#" + ValidationHelper.HashString(asyncResult));
HttpListenerContext httpContext = null;
try {
CheckDisposed();
if (asyncResult==null) {
throw new ArgumentNullException("asyncResult");
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::EndGetContext() asyncResult:" + ValidationHelper.ToString(asyncResult));
ListenerAsyncResult castedAsyncResult = asyncResult as ListenerAsyncResult;
if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) {
throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
}
if (castedAsyncResult.EndCalled) {
throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndGetContext"));
}
castedAsyncResult.EndCalled = true;
httpContext = castedAsyncResult.InternalWaitForCompletion() as HttpListenerContext;
if (httpContext == null) {
GlobalLog.Assert(castedAsyncResult.Result is Exception, "EndGetContext|The result is neither a HttpListenerContext nor an Exception.");
throw castedAsyncResult.Result as Exception;
}
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "EndGetContext", exception);
throw;
} finally {
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::EndGetContext() returning HttpListenerContext#" + ValidationHelper.ToString(httpContext));
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "EndGetContext", "HttpListenerContext#" + ValidationHelper.HashString(httpContext)+ " RequestTraceIdentifier#"+httpContext.Request.RequestTraceIdentifier);
}
return httpContext;
}
[SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)]
private WindowsIdentity CreateWindowsIdentity(IntPtr userToken, string type, WindowsAccountType acctType, bool isAuthenticated)
{
return new WindowsIdentity(userToken, type, acctType, isAuthenticated);
}
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);
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);
}
}
// 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 ? "" : 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;
bool extendedProtectionFailure = false;
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, out extendedProtectionFailure);
if (!extendedProtectionFailure)
{
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);
}
}
else
{
httpError = HttpStatusCode.Unauthorized;
}
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, out extendedProtectionFailure);
if (!extendedProtectionFailure)
{
context = new NTAuthentication(true, package, null,
GetContextFlags(extendedProtectionPolicy, isSecureConnection), binding);
}
}
if (!extendedProtectionFailure)
{
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;
}
}
}
}
else
{
httpError = HttpStatusCode.Unauthorized;
}
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:" + userName);
principal = new GenericPrincipal(new HttpListenerBasicIdentity(userName, password), null);
}
else
{
httpError = HttpStatusCode.BadRequest;
}
}
catch (FormatException)
{
GlobalLog.Print("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();
}
}
}
}
private static bool ScenarioChecksChannelBinding(bool isSecureConnection, ProtectionScenario scenario)
{
return (isSecureConnection && scenario == ProtectionScenario.TransportSelected);
}
private ChannelBinding GetChannelBinding(ulong connectionId, bool isSecureConnection, ExtendedProtectionPolicy policy, out bool extendedProtectionFailure)
{
extendedProtectionFailure = false;
if (policy.PolicyEnforcement == PolicyEnforcement.Never)
{
if (Logging.On) Logging.PrintInfo(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) + "::GetChannelBinding Extended Protection disabled");
return null;
}
if (!isSecureConnection)
{
if (Logging.On) Logging.PrintInfo(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) + "::GetChannelBinding No Channel Binding check for requests without a secure channel");
return null;
}
ChannelBinding result = GetChannelBindingFromTls(connectionId);
if (result == null)
{
if (Logging.On) Logging.PrintError(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) + "::GetChannelBinding() Policy requires extended protection but platform does not support it!");
extendedProtectionFailure = true;
}
if (policy.ProtectionScenario == ProtectionScenario.TrustedProxy)
{
if (Logging.On) Logging.PrintInfo(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) + "::GetChannelBinding TrustedProxy policy does not require channel binding check");
result = null;
}
return result;
}
private bool CheckSpn(NTAuthentication context, bool isSecureConnection, ExtendedProtectionPolicy policy)
{
// Kerberos does SPN check already in ASC
if (context.IsKerberos)
{
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) +
"::CheckSpn() No SPN check since auth scheme is Kerberos.");
}
return true;
}
// Don't check the SPN if Extended Protection is off or we already checked the CBT
if (policy.PolicyEnforcement == PolicyEnforcement.Never ||
ScenarioChecksChannelBinding(isSecureConnection, policy.ProtectionScenario))
{
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() Returning true because " +
policy == null ? "no extended protection policy set" : "CBT was checked");
}
return true;
}
string clientSpn = context.ClientSpecifiedSpn;
// An empty SPN is only allowed in the WhenSupported case
if (String.IsNullOrEmpty(clientSpn))
{
bool result = false;
if (policy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
{
result = true;
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() No client SPN provided, but PolicyEnforcement " +
"is WhenSupported");
}
}
return result;
}
else if (String.Compare(clientSpn, "http/localhost", StringComparison.OrdinalIgnoreCase) == 0)
{
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() SSP indicates loopback authentication, skipping SPN check");
}
return true;
}
else
{
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() Client provided SPN '" +
clientSpn + "'");
}
ServiceNameCollection serviceNames = GetServiceNames(policy);
bool found = false;
foreach (string serviceName in serviceNames)
{
if (String.Compare(clientSpn, serviceName, StringComparison.OrdinalIgnoreCase) == 0)
{
found = true;
break;
}
}
if (Logging.On && !found)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() SPN check failed");
if (serviceNames.Count == 0)
{
Logging.PrintWarning(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() Service Name collection was emtpy!");
}
else
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() Dumping Acceptable Service Names");
foreach (string serviceName in serviceNames)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() \t" + serviceName);
}
}
}
return found;
}
}
private ServiceNameCollection GetServiceNames(ExtendedProtectionPolicy policy)
{
ServiceNameCollection serviceNames;
if (policy.CustomServiceNames == null) {
if (m_DefaultServiceNames.ServiceNames.Count == 0) {
throw new InvalidOperationException(SR.GetString(SR.net_listener_no_spns));
}
serviceNames = m_DefaultServiceNames.ServiceNames;
}
else {
serviceNames = policy.CustomServiceNames;
}
return serviceNames;
}
private ContextFlags GetContextFlags(ExtendedProtectionPolicy policy, bool isSecureConnection)
{
ContextFlags result = ContextFlags.Connection;
if (policy.PolicyEnforcement != PolicyEnforcement.Never) {
if (policy.PolicyEnforcement == PolicyEnforcement.WhenSupported) {
result |= ContextFlags.AllowMissingBindings;
}
if (policy.ProtectionScenario == ProtectionScenario.TrustedProxy) {
result |= ContextFlags.ProxyBindings;
}
}
return result;
}
private static void AddChallenge(ref ArrayList challenges, string challenge)
{
if (challenge!=null) {
challenge = challenge.Trim();
if (challenge.Length>0) {
GlobalLog.Print("HttpListener:AddChallenge() challenge:" + challenge);
if (challenges == null)
{
challenges = new ArrayList(4);
}
challenges.Add(challenge);
}
}
}
private ArrayList BuildChallenge(AuthenticationSchemes authenticationScheme, ulong connectionId,
out NTAuthentication newContext, ExtendedProtectionPolicy policy, bool isSecureConnection)
{
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() authenticationScheme:" + authenticationScheme.ToString());
ArrayList challenges = null;
newContext = null;
if ((authenticationScheme & AuthenticationSchemes.Negotiate) != 0)
{
AddChallenge(ref challenges, NegotiateClient.AuthType);
}
if ((authenticationScheme & AuthenticationSchemes.Ntlm) != 0)
{
AddChallenge(ref challenges, NtlmClient.AuthType);
}
if ((authenticationScheme & AuthenticationSchemes.Digest) != 0)
{
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() package:WDigest");
NTAuthentication context = null;
try
{
bool extendedProtectionFailure;
string outBlob = null;
ChannelBinding binding = GetChannelBinding(connectionId, isSecureConnection, policy, out extendedProtectionFailure);
if (!extendedProtectionFailure)
{
context = new NTAuthentication(true, NegotiationInfoClass.WDigest, null,
GetContextFlags(policy, isSecureConnection), binding);
SecurityStatus statusCode;
outBlob = context.GetOutgoingDigestBlob(null, null, null, Realm, false, false, out statusCode);
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() GetOutgoingDigestBlob() returned IsCompleted:" + context.IsCompleted + " statusCode:" + statusCode + " outBlob:[" + outBlob + "]");
if (context.IsValidContext)
{
newContext = context;
}
AddChallenge(ref challenges, DigestClient.AuthType + (string.IsNullOrEmpty(outBlob) ? "" : " " + outBlob));
}
}
finally
{
if (context != null && newContext != context)
{
context.CloseContext();
}
}
}
if ((authenticationScheme & AuthenticationSchemes.Basic) != 0)
{
AddChallenge(ref challenges, BasicClient.AuthType + " realm=\"" + Realm + "\"");
}
return challenges;
}
private void RegisterForDisconnectNotification(ulong connectionId, ref DisconnectAsyncResult disconnectResult)
{
GlobalLog.Assert(disconnectResult == null, "HttpListener#{0}::RegisterForDisconnectNotification()|Called with a disconnectResult.", ValidationHelper.HashString(this));
try
{
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() calling UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect");
DisconnectAsyncResult result = new DisconnectAsyncResult(this, connectionId);
EnsureBoundHandle();
uint statusCode = UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect(
m_RequestQueueHandle,
connectionId,
result.NativeOverlapped);
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() call to UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect returned:" + statusCode);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS ||
statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// Need to make sure it's going to get returned before adding it to the hash. That way it'll be handled
// correctly in HandleAuthentication's finally.
disconnectResult = result;
DisconnectResults[connectionId] = disconnectResult;
}
}
catch (Win32Exception exception)
{
uint statusCode = (uint) exception.NativeErrorCode;
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() call to UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect threw. statusCode:" + statusCode);
}
}
private void SendError(ulong requestId, HttpStatusCode httpStatusCode, ArrayList challenges)
{
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() requestId:" + ValidationHelper.ToString(requestId));
UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE httpResponse = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE();
httpResponse.Version = new UnsafeNclNativeMethods.HttpApi.HTTP_VERSION();
httpResponse.Version.MajorVersion = (ushort)1;
httpResponse.Version.MinorVersion = (ushort)1;
httpResponse.StatusCode = (ushort)httpStatusCode;
string statusDescription = HttpListenerResponse.GetStatusDescription((int)httpStatusCode);
uint DataWritten = 0;
uint statusCode;
byte[] byteReason = Encoding.Default.GetBytes(statusDescription);
fixed (byte* pReason = byteReason)
{
httpResponse.pReason = (sbyte*)pReason;
httpResponse.ReasonLength = (ushort)byteReason.Length;
byte[] byteContentLength = Encoding.Default.GetBytes("0");
fixed (byte* pContentLength = byteContentLength)
{
(&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].pRawValue = (sbyte*)pContentLength;
(&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length;
httpResponse.Headers.UnknownHeaderCount = checked((ushort) (challenges == null ? 0 : challenges.Count));
GCHandle[] challengeHandles = null;
UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[] headersArray = null;
GCHandle headersArrayHandle = new GCHandle();
GCHandle wwwAuthenticateHandle = new GCHandle();
if (httpResponse.Headers.UnknownHeaderCount > 0)
{
challengeHandles = new GCHandle[httpResponse.Headers.UnknownHeaderCount];
headersArray = new UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[httpResponse.Headers.UnknownHeaderCount];
}
try
{
if (httpResponse.Headers.UnknownHeaderCount > 0)
{
headersArrayHandle = GCHandle.Alloc(headersArray, GCHandleType.Pinned);
httpResponse.Headers.pUnknownHeaders = (UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER*) Marshal.UnsafeAddrOfPinnedArrayElement(headersArray, 0);
wwwAuthenticateHandle = GCHandle.Alloc(s_WwwAuthenticateBytes, GCHandleType.Pinned);
sbyte* wwwAuthenticate = (sbyte*) Marshal.UnsafeAddrOfPinnedArrayElement(s_WwwAuthenticateBytes, 0);
for (int i = 0; i < challengeHandles.Length; i++)
{
byte[] byteChallenge = Encoding.Default.GetBytes((string) challenges[i]);
challengeHandles[i] = GCHandle.Alloc(byteChallenge, GCHandleType.Pinned);
headersArray[i].pName = wwwAuthenticate;
headersArray[i].NameLength = (ushort) s_WwwAuthenticateBytes.Length;
headersArray[i].pRawValue = (sbyte*) Marshal.UnsafeAddrOfPinnedArrayElement(byteChallenge, 0);
headersArray[i].RawValueLength = checked((ushort) byteChallenge.Length);
}
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() calling UnsafeNclNativeMethods.HttpApi.HttpSendHtthttpResponse");
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse(
m_RequestQueueHandle,
requestId,
0,
&httpResponse,
null,
&DataWritten,
SafeLocalFree.Zero,
0,
null,
null );
}
finally
{
if (headersArrayHandle.IsAllocated)
{
headersArrayHandle.Free();
}
if (wwwAuthenticateHandle.IsAllocated)
{
wwwAuthenticateHandle.Free();
}
if (challengeHandles != null)
{
for (int i = 0; i < challengeHandles.Length; i++)
{
if (challengeHandles[i].IsAllocated)
{
challengeHandles[i].Free();
}
}
}
}
}
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() call to UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse returned:" + statusCode);
if (statusCode!=UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) {
// if we fail to send a 401 something's seriously wrong, abort the request
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() SendUnauthorized() returned:" + statusCode);
HttpListenerContext.CancelRequest(m_RequestQueueHandle, requestId);
}
}
private unsafe static int GetTokenOffsetFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
IntPtr tokenPointer = Marshal.ReadIntPtr((IntPtr)blob, (int)Marshal.OffsetOf(ChannelBindingStatusType, "ChannelToken"));
Debug.Assert(tokenPointer != IntPtr.Zero);
return (int)IntPtrHelper.Subtract(tokenPointer, blob);
}
private unsafe static int GetTokenSizeFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
return Marshal.ReadInt32(blob, (int)Marshal.OffsetOf(ChannelBindingStatusType, "ChannelTokenSize"));
}
internal ChannelBinding GetChannelBindingFromTls(ulong connectionId)
{
if (Logging.On) {
Logging.Enter(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) +
"::GetChannelBindingFromTls() connectionId: " + connectionId.ToString());
}
// +128 since a CBT is usually <128 thus we need to call HRCC just once. If the CBT
// is >128 we will get ERROR_MORE_DATA and call again
int size = RequestChannelBindStatusSize + 128;
Debug.Assert(size >= 0);
byte[] blob = null;
SafeLocalFreeChannelBinding token = null;
uint bytesReceived = 0;
uint statusCode;
do {
blob = new byte[size];
fixed (byte* blobPtr = blob)
{
// Http.sys team: ServiceName will always be null if
// HTTP_RECEIVE_SECURE_CHANNEL_TOKEN flag is set.
statusCode = UnsafeNclNativeMethods.HttpApi.HttpReceiveClientCertificate(
RequestQueueHandle,
connectionId,
(uint)UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_SECURE_CHANNEL_TOKEN,
blobPtr,
(uint)size,
&bytesReceived,
null);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
int tokenOffset = GetTokenOffsetFromBlob((IntPtr)blobPtr);
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
token = SafeLocalFreeChannelBinding.LocalAlloc(tokenSize);
if (token.IsInvalid)
{
throw new OutOfMemoryException();
}
Marshal.Copy(blob, tokenOffset, token.DangerousGetHandle(), tokenSize);
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
size = RequestChannelBindStatusSize + tokenSize;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER)
{
if (Logging.On)
{
Logging.PrintError(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) +
"::GetChannelBindingFromTls() Can't retrieve CBT from TLS: ERROR_INVALID_PARAMETER");
}
return null; // old schannel library which doesn't support CBT
}
else
{
throw new HttpListenerException((int)statusCode);
}
}
} while (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS);
return token;
}
internal void CheckDisposed() {
if (m_State==State.Closed) {
throw new ObjectDisposedException(this.GetType().FullName);
}
}
// This only works for context-destroying errors.
private HttpStatusCode HttpStatusFromSecurityStatus(SecurityStatus status)
{
if (NclUtilities.IsCredentialFailure(status))
{
return HttpStatusCode.Unauthorized;
}
if (NclUtilities.IsClientFault(status))
{
return HttpStatusCode.BadRequest;
}
return HttpStatusCode.InternalServerError;
}
enum State {
Stopped,
Started,
Closed,
}
private const int DigestLifetimeSeconds = 300;
private const int MaximumDigests = 1024; // Must be a power of two.
private const int MinimumDigestLifetimeSeconds = 10;
private struct DigestContext
{
internal NTAuthentication context;
internal int timestamp;
}
private DigestContext[] m_SavedDigests;
private ArrayList m_ExtraSavedDigests;
private ArrayList m_ExtraSavedDigestsBaking;
private int m_ExtraSavedDigestsTimestamp;
private int m_NewestContext;
private int m_OldestContext;
private void SaveDigestContext(NTAuthentication digestContext)
{
if (m_SavedDigests == null)
{
Interlocked.CompareExchange(ref m_SavedDigests, new DigestContext[MaximumDigests], null);
}
// We want to actually close the contexts outside the lock.
NTAuthentication oldContext = null;
ArrayList digestsToClose = null;
lock (m_SavedDigests)
{
// If we're stopped, just throw it away.
if (!IsListening)
{
digestContext.CloseContext();
return;
}
int now = ((now = Environment.TickCount) == 0 ? 1 : now);
m_NewestContext = (m_NewestContext + 1) & (MaximumDigests - 1);
int oldTimestamp = m_SavedDigests[m_NewestContext].timestamp;
oldContext = m_SavedDigests[m_NewestContext].context;
m_SavedDigests[m_NewestContext].timestamp = now;
m_SavedDigests[m_NewestContext].context = digestContext;
// May need to move this up.
if (m_OldestContext == m_NewestContext)
{
m_OldestContext = (m_NewestContext + 1) & (MaximumDigests - 1);
}
// Delete additional contexts older than five minutes.
while (unchecked(now - m_SavedDigests[m_OldestContext].timestamp) >= DigestLifetimeSeconds && m_SavedDigests[m_OldestContext].context != null)
{
if (digestsToClose == null)
{
digestsToClose = new ArrayList();
}
digestsToClose.Add(m_SavedDigests[m_OldestContext].context);
m_SavedDigests[m_OldestContext].context = null;
m_OldestContext = (m_OldestContext + 1) & (MaximumDigests - 1);
}
// If the old context is younger than 10 seconds, put it in the backup pile.
if (oldContext != null && unchecked(now - oldTimestamp) <= MinimumDigestLifetimeSeconds * 1000)
{
// Use a two-tier ArrayList system to guarantee each entry lives at least 10 seconds.
if (m_ExtraSavedDigests == null ||
unchecked(now - m_ExtraSavedDigestsTimestamp) > MinimumDigestLifetimeSeconds * 1000)
{
digestsToClose = m_ExtraSavedDigestsBaking;
m_ExtraSavedDigestsBaking = m_ExtraSavedDigests;
m_ExtraSavedDigestsTimestamp = now;
m_ExtraSavedDigests = new ArrayList();
}
m_ExtraSavedDigests.Add(oldContext);
oldContext = null;
}
}
if (oldContext != null)
{
oldContext.CloseContext();
}
if (digestsToClose != null)
{
for (int i = 0; i < digestsToClose.Count; i++)
{
((NTAuthentication)digestsToClose[i]).CloseContext();
}
}
}
private void ClearDigestCache()
{
if (m_SavedDigests == null)
{
return;
}
ArrayList[] toClose = new ArrayList[3];
lock (m_SavedDigests)
{
toClose[0] = m_ExtraSavedDigestsBaking;
m_ExtraSavedDigestsBaking = null;
toClose[1] = m_ExtraSavedDigests;
m_ExtraSavedDigests = null;
m_NewestContext = 0;
m_OldestContext = 0;
toClose[2] = new ArrayList();
for (int i = 0; i < MaximumDigests; i++)
{
if (m_SavedDigests[i].context != null)
{
toClose[2].Add(m_SavedDigests[i].context);
m_SavedDigests[i].context = null;
}
m_SavedDigests[i].timestamp = 0;
}
}
for (int j = 0; j < toClose.Length; j++)
{
if (toClose[j] != null)
{
for (int k = 0; k < toClose[j].Count; k++)
{
((NTAuthentication) toClose[j][k]).CloseContext();
}
}
}
}
class DisconnectAsyncResult : IAsyncResult {
private static readonly IOCompletionCallback s_IOCallback = new IOCompletionCallback(WaitCallback);
private ulong m_ConnectionId;
private HttpListener m_HttpListener;
NativeOverlapped* m_NativeOverlapped;
private int m_OwnershipState; // 0 = normal, 1 = in HandleAuthentication(), 2 = disconnected, 3 = cleaned up
private WindowsPrincipal m_AuthenticatedConnection;
private NTAuthentication m_Session;
internal const string NTLM = "NTLM";
internal NativeOverlapped* NativeOverlapped{
get{
return m_NativeOverlapped;
}
}
public object AsyncState {
get {
throw ExceptionHelper.PropertyNotImplementedException;
}
}
public WaitHandle AsyncWaitHandle {
get {
throw ExceptionHelper.PropertyNotImplementedException;
}
}
public bool CompletedSynchronously {
get {
throw ExceptionHelper.PropertyNotImplementedException;
}
}
public bool IsCompleted {
get {
throw ExceptionHelper.PropertyNotImplementedException;
}
}
internal unsafe DisconnectAsyncResult(HttpListener httpListener, ulong connectionId) {
GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() httpListener#" + ValidationHelper.HashString(httpListener) + " connectionId:" + connectionId);
m_OwnershipState = 1;
m_HttpListener = httpListener;
m_ConnectionId = connectionId;
Overlapped overlapped = new Overlapped();
overlapped.AsyncResult = this;
// we can call the Unsafe API here, we won't ever call user code
m_NativeOverlapped = overlapped.UnsafePack(s_IOCallback, null);
GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() overlapped#" + ValidationHelper.HashString(overlapped) + " nativeOverlapped:" + ((IntPtr)m_NativeOverlapped).ToString("x"));
}
internal bool StartOwningDisconnectHandling()
{
int oldValue;
while ((oldValue = Interlocked.CompareExchange(ref m_OwnershipState, 1, 0)) == 2)
{
// Must block until it equals 3 - we must be in the callback right now.
Thread.SpinWait(1);
}
GlobalLog.Assert(oldValue != 1, "DisconnectAsyncResult#{0}::HandleDisconnect()|StartOwningDisconnectHandling() called twice.", ValidationHelper.HashString(this));
return oldValue < 2;
}
internal void FinishOwningDisconnectHandling()
{
// If it got disconnected, run the disconnect code.
if (Interlocked.CompareExchange(ref m_OwnershipState, 0, 1) == 2)
{
HandleDisconnect();
}
}
private static unsafe void WaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) {
GlobalLog.Print("DisconnectAsyncResult::WaitCallback() errorCode:" + errorCode + " numBytes:" + numBytes + " nativeOverlapped:" + ((IntPtr)nativeOverlapped).ToString("x"));
// take the DisconnectAsyncResult object from the state
Overlapped callbackOverlapped = Overlapped.Unpack(nativeOverlapped);
DisconnectAsyncResult asyncResult = (DisconnectAsyncResult) callbackOverlapped.AsyncResult;
GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::WaitCallback() callbackOverlapped#" + ValidationHelper.HashString(callbackOverlapped) + " m_ConnectionId:" + asyncResult.m_ConnectionId);
Overlapped.Free(nativeOverlapped);
if (Interlocked.Exchange(ref asyncResult.m_OwnershipState, 2) == 0)
{
asyncResult.HandleDisconnect();
}
}
private void HandleDisconnect()
{
GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::HandleDisconnect() DisconnectResults#" + ValidationHelper.HashString(m_HttpListener.DisconnectResults) + " removing for m_ConnectionId:" + m_ConnectionId);
m_HttpListener.DisconnectResults.Remove(m_ConnectionId);
if (m_Session != null)
{
if (m_Session.Package == NegotiationInfoClass.WDigest)
{
// VSWhidbey #497767
// WDigest doesn't like having the context passed back in on the next request on a connection, but it does want
// the server to keep a reference to it for as long as a client might reuse the nonce. The heuristic we use is,
// keep contexts for five minutes, up to a maximum of 1024, except also keep all contexts at least 10 seconds to avoid
// total DoS (where no handshakes can be completed in time).
m_HttpListener.SaveDigestContext(m_Session);
}
else
{
m_Session.CloseContext();
}
}
// Clean up the identity. This is for scenarios where identity was not cleaned up before due to
// identity caching for unsafe ntlm authentication
IDisposable identity = m_AuthenticatedConnection == null ? null : m_AuthenticatedConnection.Identity as IDisposable;
if ((identity != null) &&
(m_AuthenticatedConnection.Identity.AuthenticationType == NTLM) &&
(m_HttpListener.UnsafeConnectionNtlmAuthentication))
{
identity.Dispose();
}
int oldValue = Interlocked.Exchange(ref m_OwnershipState, 3);
GlobalLog.Assert(oldValue == 2, "DisconnectAsyncResult#{0}::HandleDisconnect()|Expected OwnershipState of 2, saw {1}.", ValidationHelper.HashString(this), oldValue);
}
internal WindowsPrincipal AuthenticatedConnection
{
get
{
return m_AuthenticatedConnection;
}
set
{
// The previous value can't be disposed because it may be in use by the app.
m_AuthenticatedConnection = value;
}
}
internal NTAuthentication Session
{
get
{
return m_Session;
}
set
{
m_Session = value;
}
}
}
}
/* Proposed Future HTTP Base Classes
see \ndp\mb\docs\specs\NetworkFramework\HTTPSYS\ASP.NET\stub.cs
// System.Net exposes base abstract classes that System.Web will inherit from
public abstract class BaseHttpContext {
public virtual IPrincipal User { get; set; }
// it doesn't make sense to make these virtual because we can't override
// and change the returned type for System.Web or people would break.
// the only thing we can do is to declare them normally and hide the
// base implementation like "public new System.Web.HttpRequest Request"
public BaseHttpRequest Request { get; }
public BaseHttpResponse Response { get; }
// these provide plumbing to make the above two methods callable with a BaseHttpContext reference
protected virtual BaseHttpRequest GetRequest();
protected virtual BaseHttpResponse GetResponse();
}
public abstract class BaseHttpRequest {
public virtual string[] AcceptTypes { get; }
public virtual Encoding ContentEncoding { get; set; }
public virtual string ContentType { get; set; }
public virtual NameValueCollection Headers { get; }
public virtual string HttpMethod { get; }
public virtual Stream InputStream { get; }
public virtual bool IsAuthenticated { get; }
public virtual bool IsLocal { get; }
public virtual bool IsSecureConnection { get; }
public virtual NameValueCollection QueryString { get; }
public virtual string RawUrl { get; }
public virtual Uri Url { get; }
public virtual Uri UrlReferrer { get; }
public virtual string UserAgent { get; }
public virtual string UserHostAddress { get; }
public virtual string UserHostName { get; }
public virtual string[] UserLanguages { get; }
// APIs that are in the base class but are new to ASP .NET
public virtual long ContentLengthLong { get; }
public virtual bool HasEntityBody { get; }
public virtual bool KeepAlive { get; }
public virtual IPEndPoint RemoteEndPoint { get; }
public virtual IPEndPoint LocalEndPoint { get; }
}
public abstract class BaseHttpResponse {
public virtual void AppendHeader(string name, string value);
public virtual void Close();
public virtual Encoding ContentEncoding { get; set; }
public virtual string ContentType { get; set; }
public virtual Stream OutputStream { get; }
public virtual string RedirectLocation { get; set; }
public virtual int StatusCode { get; set; }
public virtual string StatusDescription { get; set; }
// APIs that are in the base class but are new to ASP .NET
public virtual bool KeepAlive { get; set; }
}
*/
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
//disabled until BindHandle has an overload that accepts Criticalhandles
#pragma warning disable 618
namespace System.Net {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Security;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Principal;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32;
public class HttpListenerBasicIdentity : GenericIdentity
{
private string m_Password;
public HttpListenerBasicIdentity(string username, string password) :
base(username, BasicClient.AuthType)
{
m_Password = password;
}
public virtual string Password
{
get
{
return m_Password;
}
}
}
internal abstract unsafe class RequestContextBase : IDisposable
{
private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* m_MemoryBlob;
private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* m_OriginalBlobAddress;
private byte[] m_BackingBuffer;
// Must call this from derived class' constructors.
protected void BaseConstruction(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* requestBlob)
{
if (requestBlob == null)
{
GC.SuppressFinalize(this);
}
else
{
m_MemoryBlob = requestBlob;
}
}
// ReleasePins() should be called exactly once. It must be called before Dispose() is called, which means it must be called
// before an object (HttpListenerReqeust) which closes the RequestContext on demand is returned to the application.
internal void ReleasePins()
{
GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::ReleasePins()|ReleasePins() called twice.");
m_OriginalBlobAddress = m_MemoryBlob;
UnsetBlob();
OnReleasePins();
}
protected abstract void OnReleasePins();
public void Close()
{
Dispose();
}
public void Dispose()
{
GlobalLog.Assert(m_MemoryBlob == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins().");
Dispose(true);
}
protected virtual void Dispose(bool disposing) { }
~RequestContextBase()
{
Dispose(false);
}
internal UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* RequestBlob
{
get
{
GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::Dispose()|RequestBlob requested after ReleasePins().");
return m_MemoryBlob;
}
}
internal byte[] RequestBuffer
{
get
{
return m_BackingBuffer;
}
}
internal uint Size
{
get
{
return (uint) m_BackingBuffer.Length;
}
}
internal IntPtr OriginalBlobAddress
{
get
{
UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* blob = m_MemoryBlob;
return (IntPtr) (blob == null ? m_OriginalBlobAddress : blob);
}
}
protected void SetBlob(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* requestBlob)
{
GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::Dispose()|SetBlob() called after ReleasePins().");
if (requestBlob == null)
{
UnsetBlob();
return;
}
if (m_MemoryBlob == null)
{
GC.ReRegisterForFinalize(this);
}
m_MemoryBlob = requestBlob;
}
protected void UnsetBlob()
{
if (m_MemoryBlob != null)
{
GC.SuppressFinalize(this);
}
m_MemoryBlob = null;
}
protected void SetBuffer(int size)
{
m_BackingBuffer = size == 0 ? null : new byte[size];
}
}
internal unsafe class AsyncRequestContext : RequestContextBase
{
private NativeOverlapped* m_NativeOverlapped;
private ListenerAsyncResult m_Result;
internal AsyncRequestContext(ListenerAsyncResult result)
{
m_Result = result;
BaseConstruction(Allocate(0));
}
private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* Allocate(uint size)
{
uint newSize = size != 0 ? size : RequestBuffer == null ? 4096 : Size;
if (m_NativeOverlapped != null && newSize != RequestBuffer.Length)
{
NativeOverlapped* nativeOverlapped = m_NativeOverlapped;
m_NativeOverlapped = null;
Overlapped.Free(nativeOverlapped);
}
if (m_NativeOverlapped == null)
{
SetBuffer(checked((int) newSize));
Overlapped overlapped = new Overlapped();
overlapped.AsyncResult = m_Result;
m_NativeOverlapped = overlapped.Pack(ListenerAsyncResult.IOCallback, RequestBuffer);
return (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST*) Marshal.UnsafeAddrOfPinnedArrayElement(RequestBuffer, 0);
}
return RequestBlob;
}
internal void Reset(ulong requestId, uint size)
{
SetBlob(Allocate(size));
RequestBlob->RequestId = requestId;
}
protected override void OnReleasePins()
{
if (m_NativeOverlapped != null)
{
NativeOverlapped* nativeOverlapped = m_NativeOverlapped;
m_NativeOverlapped = null;
Overlapped.Free(nativeOverlapped);
}
}
protected override void Dispose(bool disposing)
{
if (m_NativeOverlapped != null)
{
GlobalLog.Assert(!disposing, "AsyncRequestContext::Dispose()|Must call ReleasePins() before calling Dispose().");
if (!NclUtilities.HasShutdownStarted || disposing)
{
Overlapped.Free(m_NativeOverlapped);
}
}
base.Dispose(disposing);
}
internal NativeOverlapped* NativeOverlapped
{
get
{
return m_NativeOverlapped;
}
}
}
internal unsafe class SyncRequestContext : RequestContextBase
{
private GCHandle m_PinnedHandle;
internal SyncRequestContext(int size)
{
BaseConstruction(Allocate(size));
}
private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* Allocate(int size)
{
if (m_PinnedHandle.IsAllocated)
{
if (RequestBuffer.Length == size)
{
return RequestBlob;
}
m_PinnedHandle.Free();
}
SetBuffer(size);
if (RequestBuffer == null)
{
return null;
}
m_PinnedHandle = GCHandle.Alloc(RequestBuffer, GCHandleType.Pinned);
return (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST*) Marshal.UnsafeAddrOfPinnedArrayElement(RequestBuffer, 0);
}
internal void Reset(int size)
{
SetBlob(Allocate(size));
}
protected override void OnReleasePins()
{
if (m_PinnedHandle.IsAllocated)
{
m_PinnedHandle.Free();
}
}
protected override void Dispose(bool disposing)
{
if (m_PinnedHandle.IsAllocated)
{
GlobalLog.Assert(!disposing, "AsyncRequestContext::Dispose()|Must call ReleasePins() before calling Dispose().");
if (!NclUtilities.HasShutdownStarted || disposing)
{
m_PinnedHandle.Free();
}
}
base.Dispose(disposing);
}
}
public sealed unsafe class HttpListener : IDisposable
{
private static readonly Type ChannelBindingStatusType = typeof(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS);
private static readonly int RequestChannelBindStatusSize =
Marshal.SizeOf(typeof(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS));
private static byte[] s_WwwAuthenticateBytes = new byte[]
{
(byte) 'W', (byte) 'W', (byte) 'W', (byte) '-', (byte) 'A', (byte) 'u', (byte) 't', (byte) 'h',
(byte) 'e', (byte) 'n', (byte) 't', (byte) 'i', (byte) 'c', (byte) 'a', (byte) 't', (byte) 'e'
};
private class AuthenticationSelectorInfo
{
private AuthenticationSchemeSelector m_SelectorDelegate;
private bool m_CanUseAdvancedAuth;
internal AuthenticationSelectorInfo(AuthenticationSchemeSelector selectorDelegate, bool canUseAdvancedAuth)
{
Debug.Assert(selectorDelegate != null);
m_SelectorDelegate = selectorDelegate;
m_CanUseAdvancedAuth = canUseAdvancedAuth;
}
internal AuthenticationSchemeSelector Delegate
{
get
{
return m_SelectorDelegate;
}
}
internal bool AdvancedAuth
{
get
{
return m_CanUseAdvancedAuth;
}
}
}
private AuthenticationSelectorInfo m_AuthenticationDelegate;
private AuthenticationSchemes m_AuthenticationScheme = AuthenticationSchemes.Anonymous;
private SecurityException m_SecurityException;
private string m_Realm;
private SafeCloseHandle m_RequestQueueHandle;
private bool m_RequestHandleBound;
private State m_State;
private HttpListenerPrefixCollection m_Prefixes;
private bool m_IgnoreWriteExceptions;
private bool m_UnsafeConnectionNtlmAuthentication;
private ExtendedProtectionSelector m_ExtendedProtectionSelectorDelegate;
private ExtendedProtectionPolicy m_ExtendedProtectionPolicy;
private ServiceNameStore m_DefaultServiceNames;
private Hashtable m_DisconnectResults; // ulong -> DisconnectAsyncResult
private object m_InternalLock;
internal Hashtable m_UriPrefixes = new Hashtable();
public delegate ExtendedProtectionPolicy ExtendedProtectionSelector(HttpListenerRequest request);
public HttpListener()
{
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "HttpListener", "");
if (!UnsafeNclNativeMethods.HttpApi.Supported) {
throw new PlatformNotSupportedException();
}
m_State = State.Stopped;
m_InternalLock = new object();
m_DefaultServiceNames = new ServiceNameStore();
// default: no CBT checks on any platform (appcompat reasons); applies also to PolicyEnforcement
// config element
m_ExtendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never);
if (Logging.On) Logging.Exit(Logging.HttpListener, this, "HttpListener", "");
}
internal SafeCloseHandle RequestQueueHandle {
get {
return m_RequestQueueHandle;
}
}
public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
get {
AuthenticationSelectorInfo selector = m_AuthenticationDelegate;
return selector == null ? null : selector.Delegate;
}
set {
CheckDisposed();
try
{
new SecurityPermission(SecurityPermissionFlag.ControlPrincipal).Demand();
m_AuthenticationDelegate = new AuthenticationSelectorInfo(value, true);
}
catch (SecurityException exception)
{
m_SecurityException = exception;
m_AuthenticationDelegate = new AuthenticationSelectorInfo(value, false);
}
}
}
public ExtendedProtectionSelector ExtendedProtectionSelectorDelegate
{
get {
return m_ExtendedProtectionSelectorDelegate;
}
set {
CheckDisposed();
if (value == null) {
throw new ArgumentNullException();
}
m_ExtendedProtectionSelectorDelegate = value;
}
}
public AuthenticationSchemes AuthenticationSchemes {
get {
return m_AuthenticationScheme;
}
set {
CheckDisposed();
// Enabling certain schemes requires special permissions.
if ((value & (AuthenticationSchemes.Digest | AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm)) != 0)
{
new SecurityPermission(SecurityPermissionFlag.ControlPrincipal).Demand();
}
m_AuthenticationScheme = value;
}
}
public ExtendedProtectionPolicy ExtendedProtectionPolicy
{
get {
return m_ExtendedProtectionPolicy;
}
set {
CheckDisposed();
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value.CustomChannelBinding != null)
{
throw new ArgumentException(SR.GetString(SR.net_listener_cannot_set_custom_cbt), "CustomChannelBinding");
}
m_ExtendedProtectionPolicy = value;
}
}
public ServiceNameCollection DefaultServiceNames
{
get {
return m_DefaultServiceNames.ServiceNames;
}
}
public string Realm {
get {
return m_Realm;
}
set {
CheckDisposed();
m_Realm = value;
}
}
public static bool IsSupported {
get {
return UnsafeNclNativeMethods.HttpApi.Supported;
}
}
public bool IsListening {
get {
return m_State==State.Started;
}
}
public bool IgnoreWriteExceptions {
get {
return m_IgnoreWriteExceptions;
}
set {
CheckDisposed();
m_IgnoreWriteExceptions = value;
}
}
public bool UnsafeConnectionNtlmAuthentication {
get {
return m_UnsafeConnectionNtlmAuthentication;
}
set {
CheckDisposed();
if (m_UnsafeConnectionNtlmAuthentication==value) {
return;
}
lock (DisconnectResults.SyncRoot)
{
if (m_UnsafeConnectionNtlmAuthentication == value)
{
return;
}
m_UnsafeConnectionNtlmAuthentication = value;
if (!value)
{
foreach (DisconnectAsyncResult result in DisconnectResults.Values)
{
result.AuthenticatedConnection = null;
}
}
}
}
}
private Hashtable DisconnectResults
{
get
{
if (m_DisconnectResults == null)
{
lock (m_InternalLock)
{
if (m_DisconnectResults == null)
{
m_DisconnectResults = Hashtable.Synchronized(new Hashtable());
}
}
}
return m_DisconnectResults;
}
}
internal void AddPrefix(string uriPrefix)
{
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "AddPrefix", "uriPrefix:" + uriPrefix);
string registeredPrefix = null;
try {
if (uriPrefix==null) {
throw new ArgumentNullException("uriPrefix");
}
(new WebPermission(NetworkAccess.Accept, uriPrefix)).Demand();
CheckDisposed();
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::AddPrefix() uriPrefix:" + uriPrefix);
int i;
if (string.Compare(uriPrefix, 0, "http://", 0, 7, StringComparison.OrdinalIgnoreCase)==0) {
i = 7;
}
else if (string.Compare(uriPrefix, 0, "https://", 0, 8, StringComparison.OrdinalIgnoreCase)==0) {
i = 8;
}
else {
throw new ArgumentException(SR.GetString(SR.net_listener_scheme), "uriPrefix");
}
bool inSquareBrakets = false;
int j = i;
while (j0) {
if (m_State==State.Started) {
foreach (string registeredPrefix in m_UriPrefixes.Values) {
// ignore possible failures
InternalRemovePrefix(registeredPrefix);
}
}
if (clear) {
m_UriPrefixes.Clear();
m_DefaultServiceNames.Clear();
}
}
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "RemoveAll", "");
}
}
[SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)]
internal void EnsureBoundHandle()
{
if (!m_RequestHandleBound)
{
lock (m_InternalLock)
{
if (!m_RequestHandleBound)
{
ThreadPool.BindHandle(m_RequestQueueHandle.DangerousGetHandle());
m_RequestHandleBound = true;
}
}
}
}
public void Start() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Start", "");
try {
CheckDisposed();
if (m_State==State.Started) {
return;
}
m_RequestQueueHandle = SafeCloseHandle.CreateRequestQueueHandle();
AddAll();
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::.ctor() SafeCloseHandle.CreateRequestQueueHandle() returned m_RequestQueueHandle:" + m_RequestQueueHandle);
m_State = State.Started;
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Start", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Start", "");
}
}
public void Stop() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Stop", "");
try {
CheckDisposed();
if (m_State==State.Stopped) {
return;
}
RemoveAll(false);
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Stop() calling SafeCloseHandle.Close()");
m_RequestQueueHandle.Close();
m_RequestHandleBound = false;
m_State = State.Stopped;
ClearDigestCache();
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Stop", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Stop", "");
}
}
public void Abort() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Abort", "");
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Abort()");
try {
if (m_RequestQueueHandle!=null)
{
m_RequestQueueHandle.Abort();
}
m_RequestHandleBound = false;
m_State = State.Closed;
ClearDigestCache();
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Abort", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Abort", "");
}
}
public void Close() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Close", "");
try {
GlobalLog.Print("HttpListenerRequest::Close()");
((IDisposable)this).Dispose();
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Close", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Close", "");
}
}
// old API, now private, and helper methods
private void Dispose(bool disposing) {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Dispose", "");
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Dispose()");
try {
if (m_State == State.Closed){
return;
}
Stop();
m_RequestHandleBound = false;
m_State = State.Closed;
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Dispose", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Dispose", "");
}
}
///
void IDisposable.Dispose() {
Dispose(true);
}
private uint InternalAddPrefix(string uriPrefix) {
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalAddPrefix() uriPrefix:" + uriPrefix + " calling UnsafeNclNativeMethods.HttpApi.HttpAddUrl");
uint statusCode = 0;
fixed (char* pUriPrefix = uriPrefix) {
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpAddUrl(
m_RequestQueueHandle,
(ushort*)pUriPrefix,
null);
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalAddPrefix() call to UnsafeNclNativeMethods.HttpApi.HttpAddUrl returned:" + statusCode);
return statusCode;
}
private bool InternalRemovePrefix(string uriPrefix) {
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalRemovePrefix() uriPrefix:" + uriPrefix + " calling UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl");
uint statusCode = 0;
fixed (char* pUriPrefix = uriPrefix) {
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl(
m_RequestQueueHandle,
(ushort*)pUriPrefix);
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalRemovePrefix() call to UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl returned:" + statusCode);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND)
{
return false;
}
return true;
}
private void AddAll() {
// go through the uri list and register for each one of them
if (m_UriPrefixes.Count>0) {
foreach (string registeredPrefix in m_UriPrefixes.Values) {
uint statusCode = InternalAddPrefix(registeredPrefix);
if (statusCode!=UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) {
Abort();
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS)
throw new HttpListenerException((int)statusCode, SR.GetString(SR.net_listener_already, registeredPrefix));
else
throw new HttpListenerException((int)statusCode);
}
}
}
}
public HttpListenerContext GetContext() {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "GetContext", "");
SyncRequestContext memoryBlob = null;
HttpListenerContext httpContext = null;
bool stoleBlob = false;
try {
CheckDisposed();
if (m_State==State.Stopped) {
throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "Start()"));
}
if (m_UriPrefixes.Count==0) {
throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "AddPrefix()"));
}
uint statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
uint size = 4096;
ulong requestId = 0;
memoryBlob = new SyncRequestContext((int) size);
for (;;) {
for (;;) {
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() calling UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest RequestId:" + requestId);
uint bytesTransferred = 0;
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest(
m_RequestQueueHandle,
requestId,
(uint)UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY,
memoryBlob.RequestBlob,
size,
&bytesTransferred,
null);
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() call to UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest returned:" + statusCode);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER && requestId != 0) {
// we might get this if somebody stole our RequestId,
// we need to start all over again but we can reuse the buffer we just allocated
requestId = 0;
continue;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
// the buffer was not big enough to fit the headers, we need
// to read the RequestId returned, allocate a new buffer of the required size
size = bytesTransferred;
requestId = memoryBlob.RequestBlob->RequestId;
memoryBlob.Reset(checked((int) size));
continue;
}
break;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// someother bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
throw new HttpListenerException((int)statusCode);
}
// We need to hook up our authentication handling code here.
httpContext = HandleAuthentication(memoryBlob, out stoleBlob);
if (stoleBlob)
{
// The request has been handed to the user, which means this code can't reuse the blob. Reset it here.
memoryBlob = null;
stoleBlob = false;
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() HandleAuthentication() returned httpContext#" + ValidationHelper.HashString(httpContext));
// if the request survived authentication, return it to the user
if (httpContext!=null) {
return httpContext;
}
// HandleAuthentication may have cleaned this up.
if (memoryBlob == null)
{
memoryBlob = new SyncRequestContext(checked((int) size));
}
requestId = 0;
}
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "GetContext", exception);
throw;
} finally {
if (memoryBlob != null && !stoleBlob)
{
memoryBlob.ReleasePins();
memoryBlob.Close();
}
if(Logging.On)Logging.Exit(Logging.HttpListener, this, "GetContext", "HttpListenerContext#" + ValidationHelper.HashString(httpContext) + " RequestTraceIdentifier#"+httpContext.Request.RequestTraceIdentifier);
}
}
[HostProtection(ExternalThreading=true)]
public IAsyncResult BeginGetContext(AsyncCallback callback, object state) {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "BeginGetContext", "");
ListenerAsyncResult asyncResult = null;
try {
CheckDisposed();
if (m_State==State.Stopped) {
throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "Start()"));
}
// prepare the ListenerAsyncResult object (this will have it's own
// event that the user can wait on for IO completion - which means we
// need to signal it when IO completes)
asyncResult = new ListenerAsyncResult(this, state, callback);
uint statusCode = asyncResult.QueueBeginGetContext();
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// someother bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
throw new HttpListenerException((int)statusCode);
}
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "BeginGetContext", exception);
throw;
} finally {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "BeginGetContext", "IAsyncResult#" + ValidationHelper.HashString(asyncResult));
}
return asyncResult;
}
public HttpListenerContext EndGetContext(IAsyncResult asyncResult) {
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "EndGetContext", "IAsyncResult#" + ValidationHelper.HashString(asyncResult));
HttpListenerContext httpContext = null;
try {
CheckDisposed();
if (asyncResult==null) {
throw new ArgumentNullException("asyncResult");
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::EndGetContext() asyncResult:" + ValidationHelper.ToString(asyncResult));
ListenerAsyncResult castedAsyncResult = asyncResult as ListenerAsyncResult;
if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) {
throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
}
if (castedAsyncResult.EndCalled) {
throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndGetContext"));
}
castedAsyncResult.EndCalled = true;
httpContext = castedAsyncResult.InternalWaitForCompletion() as HttpListenerContext;
if (httpContext == null) {
GlobalLog.Assert(castedAsyncResult.Result is Exception, "EndGetContext|The result is neither a HttpListenerContext nor an Exception.");
throw castedAsyncResult.Result as Exception;
}
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.HttpListener, this, "EndGetContext", exception);
throw;
} finally {
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::EndGetContext() returning HttpListenerContext#" + ValidationHelper.ToString(httpContext));
if(Logging.On)Logging.Enter(Logging.HttpListener, this, "EndGetContext", "HttpListenerContext#" + ValidationHelper.HashString(httpContext)+ " RequestTraceIdentifier#"+httpContext.Request.RequestTraceIdentifier);
}
return httpContext;
}
[SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)]
private WindowsIdentity CreateWindowsIdentity(IntPtr userToken, string type, WindowsAccountType acctType, bool isAuthenticated)
{
return new WindowsIdentity(userToken, type, acctType, isAuthenticated);
}
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);
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);
}
}
// 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 ? "" : 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;
bool extendedProtectionFailure = false;
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, out extendedProtectionFailure);
if (!extendedProtectionFailure)
{
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);
}
}
else
{
httpError = HttpStatusCode.Unauthorized;
}
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, out extendedProtectionFailure);
if (!extendedProtectionFailure)
{
context = new NTAuthentication(true, package, null,
GetContextFlags(extendedProtectionPolicy, isSecureConnection), binding);
}
}
if (!extendedProtectionFailure)
{
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;
}
}
}
}
else
{
httpError = HttpStatusCode.Unauthorized;
}
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:" + userName);
principal = new GenericPrincipal(new HttpListenerBasicIdentity(userName, password), null);
}
else
{
httpError = HttpStatusCode.BadRequest;
}
}
catch (FormatException)
{
GlobalLog.Print("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();
}
}
}
}
private static bool ScenarioChecksChannelBinding(bool isSecureConnection, ProtectionScenario scenario)
{
return (isSecureConnection && scenario == ProtectionScenario.TransportSelected);
}
private ChannelBinding GetChannelBinding(ulong connectionId, bool isSecureConnection, ExtendedProtectionPolicy policy, out bool extendedProtectionFailure)
{
extendedProtectionFailure = false;
if (policy.PolicyEnforcement == PolicyEnforcement.Never)
{
if (Logging.On) Logging.PrintInfo(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) + "::GetChannelBinding Extended Protection disabled");
return null;
}
if (!isSecureConnection)
{
if (Logging.On) Logging.PrintInfo(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) + "::GetChannelBinding No Channel Binding check for requests without a secure channel");
return null;
}
ChannelBinding result = GetChannelBindingFromTls(connectionId);
if (result == null)
{
if (Logging.On) Logging.PrintError(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) + "::GetChannelBinding() Policy requires extended protection but platform does not support it!");
extendedProtectionFailure = true;
}
if (policy.ProtectionScenario == ProtectionScenario.TrustedProxy)
{
if (Logging.On) Logging.PrintInfo(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) + "::GetChannelBinding TrustedProxy policy does not require channel binding check");
result = null;
}
return result;
}
private bool CheckSpn(NTAuthentication context, bool isSecureConnection, ExtendedProtectionPolicy policy)
{
// Kerberos does SPN check already in ASC
if (context.IsKerberos)
{
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) +
"::CheckSpn() No SPN check since auth scheme is Kerberos.");
}
return true;
}
// Don't check the SPN if Extended Protection is off or we already checked the CBT
if (policy.PolicyEnforcement == PolicyEnforcement.Never ||
ScenarioChecksChannelBinding(isSecureConnection, policy.ProtectionScenario))
{
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() Returning true because " +
policy == null ? "no extended protection policy set" : "CBT was checked");
}
return true;
}
string clientSpn = context.ClientSpecifiedSpn;
// An empty SPN is only allowed in the WhenSupported case
if (String.IsNullOrEmpty(clientSpn))
{
bool result = false;
if (policy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
{
result = true;
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() No client SPN provided, but PolicyEnforcement " +
"is WhenSupported");
}
}
return result;
}
else if (String.Compare(clientSpn, "http/localhost", StringComparison.OrdinalIgnoreCase) == 0)
{
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() SSP indicates loopback authentication, skipping SPN check");
}
return true;
}
else
{
if (Logging.On)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() Client provided SPN '" +
clientSpn + "'");
}
ServiceNameCollection serviceNames = GetServiceNames(policy);
bool found = false;
foreach (string serviceName in serviceNames)
{
if (String.Compare(clientSpn, serviceName, StringComparison.OrdinalIgnoreCase) == 0)
{
found = true;
break;
}
}
if (Logging.On && !found)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() SPN check failed");
if (serviceNames.Count == 0)
{
Logging.PrintWarning(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() Service Name collection was emtpy!");
}
else
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() Dumping Acceptable Service Names");
foreach (string serviceName in serviceNames)
{
Logging.PrintInfo(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) + "::CheckSpn() \t" + serviceName);
}
}
}
return found;
}
}
private ServiceNameCollection GetServiceNames(ExtendedProtectionPolicy policy)
{
ServiceNameCollection serviceNames;
if (policy.CustomServiceNames == null) {
if (m_DefaultServiceNames.ServiceNames.Count == 0) {
throw new InvalidOperationException(SR.GetString(SR.net_listener_no_spns));
}
serviceNames = m_DefaultServiceNames.ServiceNames;
}
else {
serviceNames = policy.CustomServiceNames;
}
return serviceNames;
}
private ContextFlags GetContextFlags(ExtendedProtectionPolicy policy, bool isSecureConnection)
{
ContextFlags result = ContextFlags.Connection;
if (policy.PolicyEnforcement != PolicyEnforcement.Never) {
if (policy.PolicyEnforcement == PolicyEnforcement.WhenSupported) {
result |= ContextFlags.AllowMissingBindings;
}
if (policy.ProtectionScenario == ProtectionScenario.TrustedProxy) {
result |= ContextFlags.ProxyBindings;
}
}
return result;
}
private static void AddChallenge(ref ArrayList challenges, string challenge)
{
if (challenge!=null) {
challenge = challenge.Trim();
if (challenge.Length>0) {
GlobalLog.Print("HttpListener:AddChallenge() challenge:" + challenge);
if (challenges == null)
{
challenges = new ArrayList(4);
}
challenges.Add(challenge);
}
}
}
private ArrayList BuildChallenge(AuthenticationSchemes authenticationScheme, ulong connectionId,
out NTAuthentication newContext, ExtendedProtectionPolicy policy, bool isSecureConnection)
{
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() authenticationScheme:" + authenticationScheme.ToString());
ArrayList challenges = null;
newContext = null;
if ((authenticationScheme & AuthenticationSchemes.Negotiate) != 0)
{
AddChallenge(ref challenges, NegotiateClient.AuthType);
}
if ((authenticationScheme & AuthenticationSchemes.Ntlm) != 0)
{
AddChallenge(ref challenges, NtlmClient.AuthType);
}
if ((authenticationScheme & AuthenticationSchemes.Digest) != 0)
{
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() package:WDigest");
NTAuthentication context = null;
try
{
bool extendedProtectionFailure;
string outBlob = null;
ChannelBinding binding = GetChannelBinding(connectionId, isSecureConnection, policy, out extendedProtectionFailure);
if (!extendedProtectionFailure)
{
context = new NTAuthentication(true, NegotiationInfoClass.WDigest, null,
GetContextFlags(policy, isSecureConnection), binding);
SecurityStatus statusCode;
outBlob = context.GetOutgoingDigestBlob(null, null, null, Realm, false, false, out statusCode);
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() GetOutgoingDigestBlob() returned IsCompleted:" + context.IsCompleted + " statusCode:" + statusCode + " outBlob:[" + outBlob + "]");
if (context.IsValidContext)
{
newContext = context;
}
AddChallenge(ref challenges, DigestClient.AuthType + (string.IsNullOrEmpty(outBlob) ? "" : " " + outBlob));
}
}
finally
{
if (context != null && newContext != context)
{
context.CloseContext();
}
}
}
if ((authenticationScheme & AuthenticationSchemes.Basic) != 0)
{
AddChallenge(ref challenges, BasicClient.AuthType + " realm=\"" + Realm + "\"");
}
return challenges;
}
private void RegisterForDisconnectNotification(ulong connectionId, ref DisconnectAsyncResult disconnectResult)
{
GlobalLog.Assert(disconnectResult == null, "HttpListener#{0}::RegisterForDisconnectNotification()|Called with a disconnectResult.", ValidationHelper.HashString(this));
try
{
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() calling UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect");
DisconnectAsyncResult result = new DisconnectAsyncResult(this, connectionId);
EnsureBoundHandle();
uint statusCode = UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect(
m_RequestQueueHandle,
connectionId,
result.NativeOverlapped);
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() call to UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect returned:" + statusCode);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS ||
statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// Need to make sure it's going to get returned before adding it to the hash. That way it'll be handled
// correctly in HandleAuthentication's finally.
disconnectResult = result;
DisconnectResults[connectionId] = disconnectResult;
}
}
catch (Win32Exception exception)
{
uint statusCode = (uint) exception.NativeErrorCode;
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() call to UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect threw. statusCode:" + statusCode);
}
}
private void SendError(ulong requestId, HttpStatusCode httpStatusCode, ArrayList challenges)
{
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() requestId:" + ValidationHelper.ToString(requestId));
UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE httpResponse = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE();
httpResponse.Version = new UnsafeNclNativeMethods.HttpApi.HTTP_VERSION();
httpResponse.Version.MajorVersion = (ushort)1;
httpResponse.Version.MinorVersion = (ushort)1;
httpResponse.StatusCode = (ushort)httpStatusCode;
string statusDescription = HttpListenerResponse.GetStatusDescription((int)httpStatusCode);
uint DataWritten = 0;
uint statusCode;
byte[] byteReason = Encoding.Default.GetBytes(statusDescription);
fixed (byte* pReason = byteReason)
{
httpResponse.pReason = (sbyte*)pReason;
httpResponse.ReasonLength = (ushort)byteReason.Length;
byte[] byteContentLength = Encoding.Default.GetBytes("0");
fixed (byte* pContentLength = byteContentLength)
{
(&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].pRawValue = (sbyte*)pContentLength;
(&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length;
httpResponse.Headers.UnknownHeaderCount = checked((ushort) (challenges == null ? 0 : challenges.Count));
GCHandle[] challengeHandles = null;
UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[] headersArray = null;
GCHandle headersArrayHandle = new GCHandle();
GCHandle wwwAuthenticateHandle = new GCHandle();
if (httpResponse.Headers.UnknownHeaderCount > 0)
{
challengeHandles = new GCHandle[httpResponse.Headers.UnknownHeaderCount];
headersArray = new UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[httpResponse.Headers.UnknownHeaderCount];
}
try
{
if (httpResponse.Headers.UnknownHeaderCount > 0)
{
headersArrayHandle = GCHandle.Alloc(headersArray, GCHandleType.Pinned);
httpResponse.Headers.pUnknownHeaders = (UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER*) Marshal.UnsafeAddrOfPinnedArrayElement(headersArray, 0);
wwwAuthenticateHandle = GCHandle.Alloc(s_WwwAuthenticateBytes, GCHandleType.Pinned);
sbyte* wwwAuthenticate = (sbyte*) Marshal.UnsafeAddrOfPinnedArrayElement(s_WwwAuthenticateBytes, 0);
for (int i = 0; i < challengeHandles.Length; i++)
{
byte[] byteChallenge = Encoding.Default.GetBytes((string) challenges[i]);
challengeHandles[i] = GCHandle.Alloc(byteChallenge, GCHandleType.Pinned);
headersArray[i].pName = wwwAuthenticate;
headersArray[i].NameLength = (ushort) s_WwwAuthenticateBytes.Length;
headersArray[i].pRawValue = (sbyte*) Marshal.UnsafeAddrOfPinnedArrayElement(byteChallenge, 0);
headersArray[i].RawValueLength = checked((ushort) byteChallenge.Length);
}
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() calling UnsafeNclNativeMethods.HttpApi.HttpSendHtthttpResponse");
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse(
m_RequestQueueHandle,
requestId,
0,
&httpResponse,
null,
&DataWritten,
SafeLocalFree.Zero,
0,
null,
null );
}
finally
{
if (headersArrayHandle.IsAllocated)
{
headersArrayHandle.Free();
}
if (wwwAuthenticateHandle.IsAllocated)
{
wwwAuthenticateHandle.Free();
}
if (challengeHandles != null)
{
for (int i = 0; i < challengeHandles.Length; i++)
{
if (challengeHandles[i].IsAllocated)
{
challengeHandles[i].Free();
}
}
}
}
}
}
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() call to UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse returned:" + statusCode);
if (statusCode!=UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) {
// if we fail to send a 401 something's seriously wrong, abort the request
GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() SendUnauthorized() returned:" + statusCode);
HttpListenerContext.CancelRequest(m_RequestQueueHandle, requestId);
}
}
private unsafe static int GetTokenOffsetFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
IntPtr tokenPointer = Marshal.ReadIntPtr((IntPtr)blob, (int)Marshal.OffsetOf(ChannelBindingStatusType, "ChannelToken"));
Debug.Assert(tokenPointer != IntPtr.Zero);
return (int)IntPtrHelper.Subtract(tokenPointer, blob);
}
private unsafe static int GetTokenSizeFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
return Marshal.ReadInt32(blob, (int)Marshal.OffsetOf(ChannelBindingStatusType, "ChannelTokenSize"));
}
internal ChannelBinding GetChannelBindingFromTls(ulong connectionId)
{
if (Logging.On) {
Logging.Enter(Logging.HttpListener, "HttpListener#" + ValidationHelper.HashString(this) +
"::GetChannelBindingFromTls() connectionId: " + connectionId.ToString());
}
// +128 since a CBT is usually <128 thus we need to call HRCC just once. If the CBT
// is >128 we will get ERROR_MORE_DATA and call again
int size = RequestChannelBindStatusSize + 128;
Debug.Assert(size >= 0);
byte[] blob = null;
SafeLocalFreeChannelBinding token = null;
uint bytesReceived = 0;
uint statusCode;
do {
blob = new byte[size];
fixed (byte* blobPtr = blob)
{
// Http.sys team: ServiceName will always be null if
// HTTP_RECEIVE_SECURE_CHANNEL_TOKEN flag is set.
statusCode = UnsafeNclNativeMethods.HttpApi.HttpReceiveClientCertificate(
RequestQueueHandle,
connectionId,
(uint)UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_SECURE_CHANNEL_TOKEN,
blobPtr,
(uint)size,
&bytesReceived,
null);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
int tokenOffset = GetTokenOffsetFromBlob((IntPtr)blobPtr);
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
token = SafeLocalFreeChannelBinding.LocalAlloc(tokenSize);
if (token.IsInvalid)
{
throw new OutOfMemoryException();
}
Marshal.Copy(blob, tokenOffset, token.DangerousGetHandle(), tokenSize);
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
size = RequestChannelBindStatusSize + tokenSize;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER)
{
if (Logging.On)
{
Logging.PrintError(Logging.HttpListener, "HttpListener#" +
ValidationHelper.HashString(this) +
"::GetChannelBindingFromTls() Can't retrieve CBT from TLS: ERROR_INVALID_PARAMETER");
}
return null; // old schannel library which doesn't support CBT
}
else
{
throw new HttpListenerException((int)statusCode);
}
}
} while (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS);
return token;
}
internal void CheckDisposed() {
if (m_State==State.Closed) {
throw new ObjectDisposedException(this.GetType().FullName);
}
}
// This only works for context-destroying errors.
private HttpStatusCode HttpStatusFromSecurityStatus(SecurityStatus status)
{
if (NclUtilities.IsCredentialFailure(status))
{
return HttpStatusCode.Unauthorized;
}
if (NclUtilities.IsClientFault(status))
{
return HttpStatusCode.BadRequest;
}
return HttpStatusCode.InternalServerError;
}
enum State {
Stopped,
Started,
Closed,
}
private const int DigestLifetimeSeconds = 300;
private const int MaximumDigests = 1024; // Must be a power of two.
private const int MinimumDigestLifetimeSeconds = 10;
private struct DigestContext
{
internal NTAuthentication context;
internal int timestamp;
}
private DigestContext[] m_SavedDigests;
private ArrayList m_ExtraSavedDigests;
private ArrayList m_ExtraSavedDigestsBaking;
private int m_ExtraSavedDigestsTimestamp;
private int m_NewestContext;
private int m_OldestContext;
private void SaveDigestContext(NTAuthentication digestContext)
{
if (m_SavedDigests == null)
{
Interlocked.CompareExchange(ref m_SavedDigests, new DigestContext[MaximumDigests], null);
}
// We want to actually close the contexts outside the lock.
NTAuthentication oldContext = null;
ArrayList digestsToClose = null;
lock (m_SavedDigests)
{
// If we're stopped, just throw it away.
if (!IsListening)
{
digestContext.CloseContext();
return;
}
int now = ((now = Environment.TickCount) == 0 ? 1 : now);
m_NewestContext = (m_NewestContext + 1) & (MaximumDigests - 1);
int oldTimestamp = m_SavedDigests[m_NewestContext].timestamp;
oldContext = m_SavedDigests[m_NewestContext].context;
m_SavedDigests[m_NewestContext].timestamp = now;
m_SavedDigests[m_NewestContext].context = digestContext;
// May need to move this up.
if (m_OldestContext == m_NewestContext)
{
m_OldestContext = (m_NewestContext + 1) & (MaximumDigests - 1);
}
// Delete additional contexts older than five minutes.
while (unchecked(now - m_SavedDigests[m_OldestContext].timestamp) >= DigestLifetimeSeconds && m_SavedDigests[m_OldestContext].context != null)
{
if (digestsToClose == null)
{
digestsToClose = new ArrayList();
}
digestsToClose.Add(m_SavedDigests[m_OldestContext].context);
m_SavedDigests[m_OldestContext].context = null;
m_OldestContext = (m_OldestContext + 1) & (MaximumDigests - 1);
}
// If the old context is younger than 10 seconds, put it in the backup pile.
if (oldContext != null && unchecked(now - oldTimestamp) <= MinimumDigestLifetimeSeconds * 1000)
{
// Use a two-tier ArrayList system to guarantee each entry lives at least 10 seconds.
if (m_ExtraSavedDigests == null ||
unchecked(now - m_ExtraSavedDigestsTimestamp) > MinimumDigestLifetimeSeconds * 1000)
{
digestsToClose = m_ExtraSavedDigestsBaking;
m_ExtraSavedDigestsBaking = m_ExtraSavedDigests;
m_ExtraSavedDigestsTimestamp = now;
m_ExtraSavedDigests = new ArrayList();
}
m_ExtraSavedDigests.Add(oldContext);
oldContext = null;
}
}
if (oldContext != null)
{
oldContext.CloseContext();
}
if (digestsToClose != null)
{
for (int i = 0; i < digestsToClose.Count; i++)
{
((NTAuthentication)digestsToClose[i]).CloseContext();
}
}
}
private void ClearDigestCache()
{
if (m_SavedDigests == null)
{
return;
}
ArrayList[] toClose = new ArrayList[3];
lock (m_SavedDigests)
{
toClose[0] = m_ExtraSavedDigestsBaking;
m_ExtraSavedDigestsBaking = null;
toClose[1] = m_ExtraSavedDigests;
m_ExtraSavedDigests = null;
m_NewestContext = 0;
m_OldestContext = 0;
toClose[2] = new ArrayList();
for (int i = 0; i < MaximumDigests; i++)
{
if (m_SavedDigests[i].context != null)
{
toClose[2].Add(m_SavedDigests[i].context);
m_SavedDigests[i].context = null;
}
m_SavedDigests[i].timestamp = 0;
}
}
for (int j = 0; j < toClose.Length; j++)
{
if (toClose[j] != null)
{
for (int k = 0; k < toClose[j].Count; k++)
{
((NTAuthentication) toClose[j][k]).CloseContext();
}
}
}
}
class DisconnectAsyncResult : IAsyncResult {
private static readonly IOCompletionCallback s_IOCallback = new IOCompletionCallback(WaitCallback);
private ulong m_ConnectionId;
private HttpListener m_HttpListener;
NativeOverlapped* m_NativeOverlapped;
private int m_OwnershipState; // 0 = normal, 1 = in HandleAuthentication(), 2 = disconnected, 3 = cleaned up
private WindowsPrincipal m_AuthenticatedConnection;
private NTAuthentication m_Session;
internal const string NTLM = "NTLM";
internal NativeOverlapped* NativeOverlapped{
get{
return m_NativeOverlapped;
}
}
public object AsyncState {
get {
throw ExceptionHelper.PropertyNotImplementedException;
}
}
public WaitHandle AsyncWaitHandle {
get {
throw ExceptionHelper.PropertyNotImplementedException;
}
}
public bool CompletedSynchronously {
get {
throw ExceptionHelper.PropertyNotImplementedException;
}
}
public bool IsCompleted {
get {
throw ExceptionHelper.PropertyNotImplementedException;
}
}
internal unsafe DisconnectAsyncResult(HttpListener httpListener, ulong connectionId) {
GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() httpListener#" + ValidationHelper.HashString(httpListener) + " connectionId:" + connectionId);
m_OwnershipState = 1;
m_HttpListener = httpListener;
m_ConnectionId = connectionId;
Overlapped overlapped = new Overlapped();
overlapped.AsyncResult = this;
// we can call the Unsafe API here, we won't ever call user code
m_NativeOverlapped = overlapped.UnsafePack(s_IOCallback, null);
GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() overlapped#" + ValidationHelper.HashString(overlapped) + " nativeOverlapped:" + ((IntPtr)m_NativeOverlapped).ToString("x"));
}
internal bool StartOwningDisconnectHandling()
{
int oldValue;
while ((oldValue = Interlocked.CompareExchange(ref m_OwnershipState, 1, 0)) == 2)
{
// Must block until it equals 3 - we must be in the callback right now.
Thread.SpinWait(1);
}
GlobalLog.Assert(oldValue != 1, "DisconnectAsyncResult#{0}::HandleDisconnect()|StartOwningDisconnectHandling() called twice.", ValidationHelper.HashString(this));
return oldValue < 2;
}
internal void FinishOwningDisconnectHandling()
{
// If it got disconnected, run the disconnect code.
if (Interlocked.CompareExchange(ref m_OwnershipState, 0, 1) == 2)
{
HandleDisconnect();
}
}
private static unsafe void WaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) {
GlobalLog.Print("DisconnectAsyncResult::WaitCallback() errorCode:" + errorCode + " numBytes:" + numBytes + " nativeOverlapped:" + ((IntPtr)nativeOverlapped).ToString("x"));
// take the DisconnectAsyncResult object from the state
Overlapped callbackOverlapped = Overlapped.Unpack(nativeOverlapped);
DisconnectAsyncResult asyncResult = (DisconnectAsyncResult) callbackOverlapped.AsyncResult;
GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::WaitCallback() callbackOverlapped#" + ValidationHelper.HashString(callbackOverlapped) + " m_ConnectionId:" + asyncResult.m_ConnectionId);
Overlapped.Free(nativeOverlapped);
if (Interlocked.Exchange(ref asyncResult.m_OwnershipState, 2) == 0)
{
asyncResult.HandleDisconnect();
}
}
private void HandleDisconnect()
{
GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::HandleDisconnect() DisconnectResults#" + ValidationHelper.HashString(m_HttpListener.DisconnectResults) + " removing for m_ConnectionId:" + m_ConnectionId);
m_HttpListener.DisconnectResults.Remove(m_ConnectionId);
if (m_Session != null)
{
if (m_Session.Package == NegotiationInfoClass.WDigest)
{
// VSWhidbey #497767
// WDigest doesn't like having the context passed back in on the next request on a connection, but it does want
// the server to keep a reference to it for as long as a client might reuse the nonce. The heuristic we use is,
// keep contexts for five minutes, up to a maximum of 1024, except also keep all contexts at least 10 seconds to avoid
// total DoS (where no handshakes can be completed in time).
m_HttpListener.SaveDigestContext(m_Session);
}
else
{
m_Session.CloseContext();
}
}
// Clean up the identity. This is for scenarios where identity was not cleaned up before due to
// identity caching for unsafe ntlm authentication
IDisposable identity = m_AuthenticatedConnection == null ? null : m_AuthenticatedConnection.Identity as IDisposable;
if ((identity != null) &&
(m_AuthenticatedConnection.Identity.AuthenticationType == NTLM) &&
(m_HttpListener.UnsafeConnectionNtlmAuthentication))
{
identity.Dispose();
}
int oldValue = Interlocked.Exchange(ref m_OwnershipState, 3);
GlobalLog.Assert(oldValue == 2, "DisconnectAsyncResult#{0}::HandleDisconnect()|Expected OwnershipState of 2, saw {1}.", ValidationHelper.HashString(this), oldValue);
}
internal WindowsPrincipal AuthenticatedConnection
{
get
{
return m_AuthenticatedConnection;
}
set
{
// The previous value can't be disposed because it may be in use by the app.
m_AuthenticatedConnection = value;
}
}
internal NTAuthentication Session
{
get
{
return m_Session;
}
set
{
m_Session = value;
}
}
}
}
/* Proposed Future HTTP Base Classes
see \ndp\mb\docs\specs\NetworkFramework\HTTPSYS\ASP.NET\stub.cs
// System.Net exposes base abstract classes that System.Web will inherit from
public abstract class BaseHttpContext {
public virtual IPrincipal User { get; set; }
// it doesn't make sense to make these virtual because we can't override
// and change the returned type for System.Web or people would break.
// the only thing we can do is to declare them normally and hide the
// base implementation like "public new System.Web.HttpRequest Request"
public BaseHttpRequest Request { get; }
public BaseHttpResponse Response { get; }
// these provide plumbing to make the above two methods callable with a BaseHttpContext reference
protected virtual BaseHttpRequest GetRequest();
protected virtual BaseHttpResponse GetResponse();
}
public abstract class BaseHttpRequest {
public virtual string[] AcceptTypes { get; }
public virtual Encoding ContentEncoding { get; set; }
public virtual string ContentType { get; set; }
public virtual NameValueCollection Headers { get; }
public virtual string HttpMethod { get; }
public virtual Stream InputStream { get; }
public virtual bool IsAuthenticated { get; }
public virtual bool IsLocal { get; }
public virtual bool IsSecureConnection { get; }
public virtual NameValueCollection QueryString { get; }
public virtual string RawUrl { get; }
public virtual Uri Url { get; }
public virtual Uri UrlReferrer { get; }
public virtual string UserAgent { get; }
public virtual string UserHostAddress { get; }
public virtual string UserHostName { get; }
public virtual string[] UserLanguages { get; }
// APIs that are in the base class but are new to ASP .NET
public virtual long ContentLengthLong { get; }
public virtual bool HasEntityBody { get; }
public virtual bool KeepAlive { get; }
public virtual IPEndPoint RemoteEndPoint { get; }
public virtual IPEndPoint LocalEndPoint { get; }
}
public abstract class BaseHttpResponse {
public virtual void AppendHeader(string name, string value);
public virtual void Close();
public virtual Encoding ContentEncoding { get; set; }
public virtual string ContentType { get; set; }
public virtual Stream OutputStream { get; }
public virtual string RedirectLocation { get; set; }
public virtual int StatusCode { get; set; }
public virtual string StatusDescription { get; set; }
// APIs that are in the base class but are new to ASP .NET
public virtual bool KeepAlive { get; set; }
}
*/
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- NativeMethods.cs
- StreamGeometryContext.cs
- TabControlToolboxItem.cs
- JsonEncodingStreamWrapper.cs
- ISCIIEncoding.cs
- DependencyObjectType.cs
- MarshalDirectiveException.cs
- MultiTrigger.cs
- CollectionChangedEventManager.cs
- _HeaderInfoTable.cs
- RuleSettings.cs
- OdbcHandle.cs
- XamlVector3DCollectionSerializer.cs
- UIElement3D.cs
- _TLSstream.cs
- InternalConfigHost.cs
- OverflowException.cs
- FormViewModeEventArgs.cs
- StyleCollection.cs
- GetRecipientListRequest.cs
- ActiveXHelper.cs
- XmlSchemaComplexType.cs
- TextDpi.cs
- PipeSecurity.cs
- Frame.cs
- Component.cs
- EditorPartChrome.cs
- EncryptedType.cs
- ReliableOutputSessionChannel.cs
- EditorPart.cs
- JsonDeserializer.cs
- SessionEndingEventArgs.cs
- IChannel.cs
- AnimatedTypeHelpers.cs
- CrossSiteScriptingValidation.cs
- WebConfigurationHost.cs
- PostBackTrigger.cs
- FactoryId.cs
- DataGridParentRows.cs
- ViewStateModeByIdAttribute.cs
- TreeNodeBindingCollection.cs
- SHA512.cs
- SafeHandles.cs
- AutoCompleteStringCollection.cs
- BitmapEffectRenderDataResource.cs
- Int64KeyFrameCollection.cs
- _CookieModule.cs
- SessionPageStatePersister.cs
- ExceptionHandlerDesigner.cs
- SmtpCommands.cs
- SchemaElement.cs
- XmlSerializerVersionAttribute.cs
- ItemList.cs
- HierarchicalDataBoundControl.cs
- MimeFormImporter.cs
- TransformerInfoCollection.cs
- UnescapedXmlDiagnosticData.cs
- WebResourceAttribute.cs
- BrowserInteropHelper.cs
- ProgressPage.cs
- ProviderConnectionPoint.cs
- ListView.cs
- PointLightBase.cs
- ConnectionInterfaceCollection.cs
- PropertyConverter.cs
- MatrixValueSerializer.cs
- GridProviderWrapper.cs
- GridViewHeaderRowPresenter.cs
- HMACMD5.cs
- processwaithandle.cs
- DynamicResourceExtensionConverter.cs
- OutputCacheProfileCollection.cs
- TypeDependencyAttribute.cs
- BuiltInExpr.cs
- TextEffectResolver.cs
- PerformanceCounterPermissionEntry.cs
- BrowserDefinitionCollection.cs
- IIS7WorkerRequest.cs
- DataGridDesigner.cs
- WCFServiceClientProxyGenerator.cs
- DesignerTextViewAdapter.cs
- NegatedCellConstant.cs
- Freezable.cs
- SchemaReference.cs
- XmlCustomFormatter.cs
- ConnectionStringSettingsCollection.cs
- Single.cs
- FileLogRecordStream.cs
- Utility.cs
- DataBindingList.cs
- EntityDataSourceWrapperCollection.cs
- DiagnosticStrings.cs
- ParserExtension.cs
- SettingsSavedEventArgs.cs
- HierarchicalDataBoundControl.cs
- SQLGuidStorage.cs
- SqlInfoMessageEvent.cs
- EntityDataSourceContainerNameItem.cs
- CompressedStack.cs
- TraceFilter.cs