private void BeginWriteCallback(IAsyncResult transportResult) { if (!(transportResult.AsyncState is WorkerAsyncResult)) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("StreamFramer::BeginWriteCallback|The state expected to be WorkerAsyncResult, received:{0}.", transportResult.AsyncState.GetType().FullName); } Debug.Fail("StreamFramer::BeginWriteCallback|The state expected to be WorkerAsyncResult, received:" + transportResult.AsyncState.GetType().FullName + "."); } if (transportResult.CompletedSynchronously) { return; } var workerResult = (WorkerAsyncResult)transportResult.AsyncState; try { BeginWriteComplete(transportResult); } catch (Exception e) { if (e is OutOfMemoryException) { throw; } workerResult.InvokeCallback(e); } }
internal SafeDeleteContext GetContext(out SecurityStatusPal status) { status = new SecurityStatusPal(SecurityStatusPalErrorCode.OK); if (!(IsCompleted && IsValidContext)) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("NTAuthentication#{0}::GetContextToken|Should be called only when completed with success, currently is not!", LoggingHash.HashString(this)); } Debug.Fail("NTAuthentication#" + LoggingHash.HashString(this) + "::GetContextToken |Should be called only when completed with success, currently is not!"); } if (!IsServer) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("NTAuthentication#{0}::GetContextToken|The method must not be called by the client side!", LoggingHash.HashString(this)); } Debug.Fail("NTAuthentication#" + LoggingHash.HashString(this) + "::GetContextToken |The method must not be called by the client side!"); } if (!IsValidContext) { status = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidHandle); return(null); } return(_securityContext); }
private bool CheckCompletionBeforeNextRead(int bytes) { if (bytes == 0) { // 0 bytes was requested or EOF in the beginning of a frame, the caller should decide whether it's OK. if (_totalRead == 0) { _request.CompleteRequest(0); return(true); } // EOF in the middle of a frame. throw new IOException(SR.net_io_eof); } if (_totalRead + bytes > _request.Count) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("FixedSizeReader::CheckCompletion()|State got out of range. Total:{0} Count:{1}", _totalRead + bytes, _request.Count); } Debug.Fail("FixedSizeReader::CheckCompletion()|State got out of range. Total:" + (_totalRead + bytes) + " Count:" + _request.Count); } if ((_totalRead += bytes) == _request.Count) { _request.CompleteRequest(_request.Count); return(true); } return(false); }
// This will return a client token when conducted authentication on server side. // This token can be used for impersonation. We use it to create a WindowsIdentity and hand it out to the server app. internal SecurityContextTokenHandle GetContextToken(out Interop.SecurityStatus status) { if ((IsCompleted && IsValidContext) && GlobalLog.IsEnabled) { GlobalLog.AssertFormat("NTAuthentication#{0}::GetContextToken|Should be called only when completed with success, currently is not!", LoggingHash.HashString(this)); } if (IsServer && GlobalLog.IsEnabled) { GlobalLog.AssertFormat("NTAuthentication#{0}::GetContextToken|The method must not be called by the client side!", LoggingHash.HashString(this)); } if (!IsValidContext) { throw new Win32Exception((int)Interop.SecurityStatus.InvalidHandle); } SecurityContextTokenHandle token = null; status = (Interop.SecurityStatus)SSPIWrapper.QuerySecurityContextToken( GlobalSSPI.SSPIAuth, _securityContext, out token); return(token); }
// Allows creating a pre-completed result with less interlockeds. Beware! Constructor calls the callback. // If a derived class ever uses this and overloads Cleanup, this may need to change. internal LazyAsyncResult(object myObject, object myState, AsyncCallback myCallBack, object result) { bool globalLogEnabled = GlobalLog.IsEnabled; if (result == DBNull.Value && globalLogEnabled) { GlobalLog.AssertFormat("LazyAsyncResult#{0}::.ctor()|Result can't be set to DBNull - it's a special internal value.", LoggingHash.HashString(this)); } _asyncObject = myObject; _asyncState = myState; _asyncCallback = myCallBack; _result = result; _intCompleted = 1; if (_asyncCallback != null) { if (globalLogEnabled) { GlobalLog.Print("LazyAsyncResult#" + LoggingHash.HashString(this) + "::Complete() invoking callback"); } _asyncCallback(this); } else if (globalLogEnabled) { GlobalLog.Print("LazyAsyncResult#" + LoggingHash.HashString(this) + "::Complete() no callback to invoke"); } if (globalLogEnabled) { GlobalLog.Print("LazyAsyncResult#" + LoggingHash.HashString(this) + "::.ctor() (pre-completed)"); } }
// If ContextCopy or Identity will be used, the return value should be locked until FinishPostingAsyncOp() is called // or the operation has been aborted (e.g. by BeginXxx throwing). Otherwise, this can be called with false to prevent the lock // object from being created. internal object StartPostingAsyncOp(bool lockCapture) { if (InternalPeekCompleted && GlobalLog.IsEnabled) { GlobalLog.AssertFormat("ContextAwareResult#{0}::StartPostingAsyncOp|Called on completed result.", LoggingHash.HashString(this)); } DebugProtectState(true); _lock = lockCapture ? new object() : null; _flags |= StateFlags.PostBlockStarted; return(_lock); }
/// <summary> /// <para>Creates new timers. This method is thread-safe.</para> /// </summary> internal override Timer CreateTimer(Callback callback, object context) { TimerNode timer = new TimerNode(callback, context, Duration, _timers); // Add this on the tail. (Actually, one before the tail - _timers is the sentinel tail.) bool needProd = false; lock (_timers) { if (!(_timers.Prev.Next == _timers)) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("TimerThread#{0}::CreateTimer()|Tail corruption.", Thread.CurrentThread.ManagedThreadId.ToString()); } Debug.Fail(string.Format("TimerThread#{0}::CreateTimer()|Tail corruption.", Thread.CurrentThread.ManagedThreadId.ToString())); } // If this is the first timer in the list, we need to create a queue handle and prod the timer thread. if (_timers.Next == _timers) { if (_thisHandle == IntPtr.Zero) { _thisHandle = (IntPtr)GCHandle.Alloc(this); } needProd = true; } timer.Next = _timers; timer.Prev = _timers.Prev; _timers.Prev.Next = timer; _timers.Prev = timer; } // If, after we add the new tail, there is a chance that the tail is the next // node to be processed, we need to wake up the timer thread. if (needProd) { TimerThread.Prod(); } return(timer); }
// IO COMPLETION CALLBACK // // This callback is responsible for getting the complete protocol frame. // 1. it reads the header. // 2. it determines the frame size. // 3. loops while not all frame received or an error. // private void ReadFrameComplete(IAsyncResult transportResult) { do { if (!(transportResult.AsyncState is WorkerAsyncResult)) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("StreamFramer::ReadFrameComplete|The state expected to be WorkerAsyncResult, received:{0}.", transportResult.GetType().FullName); } Debug.Fail("StreamFramer::ReadFrameComplete|The state expected to be WorkerAsyncResult, received:" + transportResult.GetType().FullName + "."); } WorkerAsyncResult workerResult = (WorkerAsyncResult)transportResult.AsyncState; int bytesRead = _transportAPM.EndRead(transportResult); workerResult.Offset += bytesRead; if (!(workerResult.Offset <= workerResult.End)) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("StreamFramer::ReadFrameCallback|WRONG: offset - end = {0}", workerResult.Offset - workerResult.End); } Debug.Fail("StreamFramer::ReadFrameCallback|WRONG: offset - end = " + (workerResult.Offset - workerResult.End)); } if (bytesRead <= 0) { // (by design) This indicates the stream has receives EOF // If we are in the middle of a Frame - fail, otherwise - produce EOF object result = null; if (!workerResult.HeaderDone && workerResult.Offset == 0) { result = (object)-1; } else { result = new System.IO.IOException(SR.net_frame_read_io); } workerResult.InvokeCallback(result); return; } if (workerResult.Offset >= workerResult.End) { if (!workerResult.HeaderDone) { workerResult.HeaderDone = true; // This indicates the header has been read succesfully _curReadHeader.CopyFrom(workerResult.Buffer, 0, _readVerifier); int payloadSize = _curReadHeader.PayloadSize; if (payloadSize < 0) { // Let's call user callback and he call us back and we will throw workerResult.InvokeCallback(new System.IO.IOException(SR.Format(SR.net_frame_read_size))); } if (payloadSize == 0) { // report emtpy frame (NOT eof!) to the caller, he might be interested in workerResult.InvokeCallback(0); return; } if (payloadSize > _curReadHeader.MaxMessageSize) { throw new InvalidOperationException(SR.Format(SR.net_frame_size, _curReadHeader.MaxMessageSize.ToString(NumberFormatInfo.InvariantInfo), payloadSize.ToString(NumberFormatInfo.InvariantInfo))); } // Start reading the remaining frame data (note header does not count). byte[] frame = new byte[payloadSize]; // Save the ref of the data block workerResult.Buffer = frame; workerResult.End = frame.Length; workerResult.Offset = 0; // Transport.BeginRead below will pickup those changes. } else { workerResult.HeaderDone = false; // Reset for optional object reuse. workerResult.InvokeCallback(workerResult.End); return; } } // This means we need more data to complete the data block. transportResult = _transportAPM.BeginRead(workerResult.Buffer, workerResult.Offset, workerResult.End - workerResult.Offset, _readFrameCallback, workerResult); } while (transportResult.CompletedSynchronously); }
/// <summary> /// <para>Fires the timer if it is still active and has expired. Returns /// true if it can be deleted, or false if it is still timing.</para> /// </summary> internal bool Fire() { if (_timerState == TimerState.Sentinel) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("TimerThread#{0}::Fire()|TimerQueue tried to Fire a Sentinel.", Thread.CurrentThread.ManagedThreadId.ToString()); } Debug.Fail(string.Format("TimerThread#{0}::Fire()|TimerQueue tried to Fire a Sentinel.", Thread.CurrentThread.ManagedThreadId.ToString())); } if (_timerState != TimerState.Ready) { return(true); } // Must get the current tick count within this method so it is guaranteed not to be before // StartTime, which is set in the constructor. int nowMilliseconds = Environment.TickCount; if (IsTickBetween(StartTime, Expiration, nowMilliseconds)) { if (GlobalLog.IsEnabled) { GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() Not firing (" + StartTime + " <= " + nowMilliseconds + " < " + Expiration + ")"); } return(false); } bool needCallback = false; lock (_queueLock) { if (_timerState == TimerState.Ready) { if (GlobalLog.IsEnabled) { GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() Firing (" + StartTime + " <= " + nowMilliseconds + " >= " + Expiration + ")"); } _timerState = TimerState.Fired; // Remove it from the list. Next.Prev = Prev; Prev.Next = Next; Next = null; Prev = null; needCallback = _callback != null; } } if (needCallback) { try { Callback callback = _callback; object context = _context; _callback = null; _context = null; callback(this, nowMilliseconds, context); } catch (Exception exception) { if (ExceptionCheck.IsFatal(exception)) { throw; } if (NetEventSource.Log.IsEnabled()) { NetEventSource.PrintError(NetEventSource.ComponentType.Web, "TimerThreadTimer#" + StartTime.ToString(NumberFormatInfo.InvariantInfo) + "::Fire() - exception in callback: " + exception); } if (GlobalLog.IsEnabled) { GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() exception in callback: " + exception); } // This thread is not allowed to go into user code, so we should never get an exception here. // So, in debug, throw it up, killing the AppDomain. In release, we'll just ignore it. #if DEBUG throw; #endif } } return(true); }
// This must be called right before returning the result to the user. It might call the callback itself, // to avoid flowing context. Even if the operation completes before this call, the callback won't have been // called. // // Returns whether the operation completed sync or not. private bool CaptureOrComplete(ref ExecutionContext cachedContext, bool returnContext) { if ((_flags & StateFlags.PostBlockStarted) == 0) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("ContextAwareResult#{0}::CaptureOrComplete|Called without calling StartPostingAsyncOp.", LoggingHash.HashString(this)); } Debug.Fail("ContextAwareResult#" + LoggingHash.HashString(this) + "::CaptureOrComplete |Called without calling StartPostingAsyncOp."); } // See if we're going to need to capture the context. bool capturingContext = AsyncCallback != null || (_flags & StateFlags.CaptureContext) != 0; // Peek if we've already completed, but don't fix CompletedSynchronously yet // Capture the identity if requested, unless we're going to capture the context anyway, unless // capturing the context won't be sufficient. if ((_flags & StateFlags.CaptureIdentity) != 0 && !InternalPeekCompleted && (!capturingContext)) { if (GlobalLog.IsEnabled) { GlobalLog.Print("ContextAwareResult#" + LoggingHash.HashString(this) + "::CaptureOrComplete() starting identity capture"); } SafeCaptureIdentity(); } // No need to flow if there's no callback, unless it's been specifically requested. // Note that Capture() can return null, for example if SuppressFlow() is in effect. if (capturingContext && !InternalPeekCompleted) { if (GlobalLog.IsEnabled) { GlobalLog.Print("ContextAwareResult#" + LoggingHash.HashString(this) + "::CaptureOrComplete() starting capture"); } if (cachedContext == null) { cachedContext = ExecutionContext.Capture(); } if (cachedContext != null) { if (!returnContext) { _context = cachedContext; cachedContext = null; } else { _context = cachedContext; } } if (GlobalLog.IsEnabled) { GlobalLog.Print("ContextAwareResult#" + LoggingHash.HashString(this) + "::CaptureOrComplete() _Context:" + LoggingHash.HashString(_context)); } } else { // Otherwise we have to have completed synchronously, or not needed the context. if (GlobalLog.IsEnabled) { GlobalLog.Print("ContextAwareResult#" + LoggingHash.HashString(this) + "::CaptureOrComplete() skipping capture"); } cachedContext = null; if (AsyncCallback != null && !CompletedSynchronously) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("ContextAwareResult#{0}::CaptureOrComplete|Didn't capture context, but didn't complete synchronously!", LoggingHash.HashString(this)); } Debug.Fail("ContextAwareResult#" + LoggingHash.HashString(this) + "::CaptureOrComplete |Didn't capture context, but didn't complete synchronously!"); } } // Now we want to see for sure what to do. We might have just captured the context for no reason. // This has to be the first time the state has been queried "for real" (apart from InvokeCallback) // to guarantee synchronization with Complete() (otherwise, Complete() could try to call the // callback without the context having been gotten). DebugProtectState(false); if (CompletedSynchronously) { if (GlobalLog.IsEnabled) { GlobalLog.Print("ContextAwareResult#" + LoggingHash.HashString(this) + "::CaptureOrComplete() completing synchronously"); } base.Complete(IntPtr.Zero); return(true); } return(false); }
private unsafe static int EncryptDecryptHelper(OP op, SSPIInterface secModule, SafeDeleteContext context, SecurityBuffer[] input, uint sequenceNumber) { Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(input.Length); var unmanagedBuffer = new Interop.SspiCli.SecBuffer[input.Length]; fixed(Interop.SspiCli.SecBuffer *unmanagedBufferPtr = unmanagedBuffer) { sdcInOut.pBuffers = unmanagedBufferPtr; GCHandle[] pinnedBuffers = new GCHandle[input.Length]; byte[][] buffers = new byte[input.Length][]; try { for (int i = 0; i < input.Length; i++) { SecurityBuffer iBuffer = input[i]; unmanagedBuffer[i].cbBuffer = iBuffer.size; unmanagedBuffer[i].BufferType = iBuffer.type; if (iBuffer.token == null || iBuffer.token.Length == 0) { unmanagedBuffer[i].pvBuffer = IntPtr.Zero; } else { pinnedBuffers[i] = GCHandle.Alloc(iBuffer.token, GCHandleType.Pinned); unmanagedBuffer[i].pvBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(iBuffer.token, iBuffer.offset); buffers[i] = iBuffer.token; } } // The result is written in the input Buffer passed as type=BufferType.Data. int errorCode; switch (op) { case OP.Encrypt: errorCode = secModule.EncryptMessage(context, sdcInOut, sequenceNumber); break; case OP.Decrypt: errorCode = secModule.DecryptMessage(context, sdcInOut, sequenceNumber); break; case OP.MakeSignature: errorCode = secModule.MakeSignature(context, sdcInOut, sequenceNumber); break; case OP.VerifySignature: errorCode = secModule.VerifySignature(context, sdcInOut, sequenceNumber); break; default: if (GlobalLog.IsEnabled) { GlobalLog.Assert("SSPIWrapper::EncryptDecryptHelper", "Unknown OP: " + op); } Debug.Fail("SSPIWrapper::EncryptDecryptHelper", "Unknown OP: " + op); throw NotImplemented.ByDesignWithMessage(SR.net_MethodNotImplementedException); } // Marshalling back returned sizes / data. for (int i = 0; i < input.Length; i++) { SecurityBuffer iBuffer = input[i]; iBuffer.size = unmanagedBuffer[i].cbBuffer; iBuffer.type = unmanagedBuffer[i].BufferType; if (iBuffer.size == 0) { iBuffer.offset = 0; iBuffer.token = null; } else { checked { // Find the buffer this is inside of. Usually they all point inside buffer 0. int j; for (j = 0; j < input.Length; j++) { if (buffers[j] == null) { continue; } byte *bufferAddress = (byte *)Marshal.UnsafeAddrOfPinnedArrayElement(buffers[j], 0); if ((byte *)unmanagedBuffer[i].pvBuffer >= bufferAddress && (byte *)unmanagedBuffer[i].pvBuffer + iBuffer.size <= bufferAddress + buffers[j].Length) { iBuffer.offset = (int)((byte *)unmanagedBuffer[i].pvBuffer - bufferAddress); iBuffer.token = buffers[j]; break; } } if (j >= input.Length) { if (GlobalLog.IsEnabled) { GlobalLog.Assert("SSPIWrapper::EncryptDecryptHelper", "Output buffer out of range."); } Debug.Fail("SSPIWrapper::EncryptDecryptHelper", "Output buffer out of range."); iBuffer.size = 0; iBuffer.offset = 0; iBuffer.token = null; } } } // Backup validate the new sizes. if (iBuffer.offset < 0 || iBuffer.offset > (iBuffer.token == null ? 0 : iBuffer.token.Length)) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("SSPIWrapper::EncryptDecryptHelper|'offset' out of range. [{0}]", iBuffer.offset); } Debug.Fail("SSPIWrapper::EncryptDecryptHelper|'offset' out of range. [" + iBuffer.offset + "]"); } if (iBuffer.size < 0 || iBuffer.size > (iBuffer.token == null ? 0 : iBuffer.token.Length - iBuffer.offset)) { if (GlobalLog.IsEnabled) { GlobalLog.AssertFormat("SSPIWrapper::EncryptDecryptHelper|'size' out of range. [{0}]", iBuffer.size); } Debug.Fail("SSPIWrapper::EncryptDecryptHelper|'size' out of range. [" + iBuffer.size + "]"); } } if (errorCode != 0 && NetEventSource.Log.IsEnabled()) { if (errorCode == Interop.SspiCli.SEC_I_RENEGOTIATE) { NetEventSource.PrintError(NetEventSource.ComponentType.Security, SR.Format(SR.event_OperationReturnedSomething, op, "SEC_I_RENEGOTIATE")); } else { NetEventSource.PrintError(NetEventSource.ComponentType.Security, SR.Format(SR.net_log_operation_failed_with_error, op, String.Format(CultureInfo.CurrentCulture, "0X{0:X}", errorCode))); } } return(errorCode); } finally { for (int i = 0; i < pinnedBuffers.Length; ++i) { if (pinnedBuffers[i].IsAllocated) { pinnedBuffers[i].Free(); } } } } }
// Accepts an incoming binary security blob and returns an outgoing binary security blob. internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Interop.SecurityStatus statusCode) { GlobalLog.Enter("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob", ((incomingBlob == null) ? "0" : incomingBlob.Length.ToString(NumberFormatInfo.InvariantInfo)) + " bytes"); var list = new List <SecurityBuffer>(2); if (incomingBlob != null) { list.Add(new SecurityBuffer(incomingBlob, SecurityBufferType.Token)); } if (_channelBinding != null) { list.Add(new SecurityBuffer(_channelBinding)); } SecurityBuffer[] inSecurityBufferArray = null; if (list.Count > 0) { inSecurityBufferArray = list.ToArray(); } var outSecurityBuffer = new SecurityBuffer(_tokenSize, SecurityBufferType.Token); bool firstTime = _securityContext == null; try { if (!_isServer) { // client session statusCode = (Interop.SecurityStatus)SSPIWrapper.InitializeSecurityContext( GlobalSSPI.SSPIAuth, _credentialsHandle, ref _securityContext, _spn, _requestedContextFlags, Interop.SspiCli.Endianness.Network, inSecurityBufferArray, outSecurityBuffer, ref _contextFlags); GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob() SSPIWrapper.InitializeSecurityContext() returns statusCode:0x" + ((int)statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); if (statusCode == Interop.SecurityStatus.CompleteNeeded) { var inSecurityBuffers = new SecurityBuffer[1]; inSecurityBuffers[0] = outSecurityBuffer; statusCode = (Interop.SecurityStatus)SSPIWrapper.CompleteAuthToken( GlobalSSPI.SSPIAuth, ref _securityContext, inSecurityBuffers); GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingDigestBlob() SSPIWrapper.CompleteAuthToken() returns statusCode:0x" + ((int)statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); outSecurityBuffer.token = null; } } else { // Server session. statusCode = (Interop.SecurityStatus)SSPIWrapper.AcceptSecurityContext( GlobalSSPI.SSPIAuth, _credentialsHandle, ref _securityContext, _requestedContextFlags, Interop.SspiCli.Endianness.Network, inSecurityBufferArray, outSecurityBuffer, ref _contextFlags); GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob() SSPIWrapper.AcceptSecurityContext() returns statusCode:0x" + ((int)statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); } } finally { // // Assuming the ISC or ASC has referenced the credential on the first successful call, // we want to decrement the effective ref count by "disposing" it. // The real dispose will happen when the security context is closed. // Note if the first call was not successful the handle is physically destroyed here. // if (firstTime && _credentialsHandle != null) { _credentialsHandle.Dispose(); } } if (((int)statusCode & unchecked ((int)0x80000000)) != 0) { CloseContext(); _isCompleted = true; if (throwOnError) { var exception = new Win32Exception((int)statusCode); GlobalLog.Leave("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob", "Win32Exception:" + exception); throw exception; } GlobalLog.Leave("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob", "null statusCode:0x" + ((int)statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); return(null); } else if (firstTime && _credentialsHandle != null) { // Cache until it is pushed out by newly incoming handles. SSPIHandleCache.CacheCredential(_credentialsHandle); } // The return value from SSPI will tell us correctly if the // handshake is over or not: http://msdn.microsoft.com/library/psdk/secspi/sspiref_67p0.htm // we also have to consider the case in which SSPI formed a new context, in this case we're done as well. if (statusCode == Interop.SecurityStatus.OK) { // Success. if ((statusCode == Interop.SecurityStatus.OK) && GlobalLog.IsEnabled) { GlobalLog.AssertFormat("NTAuthentication#{0}::GetOutgoingBlob()|statusCode:[0x{1:x8}] ({2}) m_SecurityContext#{3}::Handle:[{4}] [STATUS != OK]", LoggingHash.HashString(this), (int)statusCode, statusCode, LoggingHash.HashString(_securityContext), LoggingHash.ObjectToString(_securityContext)); } _isCompleted = true; } else { // We need to continue. GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob() need continue statusCode:[0x" + ((int)statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + "] (" + statusCode.ToString() + ") m_SecurityContext#" + LoggingHash.HashString(_securityContext) + "::Handle:" + LoggingHash.ObjectToString(_securityContext) + "]"); } GlobalLog.Leave("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob", "IsCompleted:" + IsCompleted.ToString()); return(outSecurityBuffer.token); }