/*++ Copyright (c) 1993-2000 Microsoft Corporation Module Name: ctxtcli.cxx Abstract: API and support routines for handling security contexts. Author: Cliff Van Dyke (CliffV) 13-Jul-1993 Revision History: ChandanS 03-Aug-1996 Stolen from net\svcdlls\ntlmssp\common\context.c --*/ // // Common include files. // #include #include // ALIGN_WCHAR, etc #include #include #include "msp.h" #include "nlp.h" NTSTATUS SsprHandleFirstCall( IN LSA_SEC_HANDLE CredentialHandle, IN OUT PLSA_SEC_HANDLE ContextHandle, IN ULONG ContextReqFlags, IN ULONG InputTokenSize, IN PVOID InputToken, IN PUNICODE_STRING TargetServerName OPTIONAL, IN OUT PULONG OutputTokenSize, OUT PVOID *OutputToken, OUT PULONG ContextAttributes, OUT PTimeStamp ExpirationTime, OUT PUCHAR SessionKey, OUT PULONG NegotiateFlags ) /*++ Routine Description: Handle the First Call part of InitializeSecurityContext. Arguments: All arguments same as for InitializeSecurityContext Return Value: STATUS_SUCCESS -- All OK SEC_I_CONTINUE_NEEDED -- Caller should call again later SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid SEC_E_BUFFER_TOO_SMALL -- Buffer for output token isn't big enough SEC_E_INSUFFICIENT_MEMORY -- Not enough memory --*/ { SspPrint(( SSP_API_MORE, "Entering SsprHandleFirstCall\n" )); NTSTATUS Status = STATUS_SUCCESS; PSSP_CONTEXT Context = NULL; PSSP_CREDENTIAL Credential = NULL; PNEGOTIATE_MESSAGE NegotiateMessage = NULL; ULONG NegotiateMessageSize = 0; PCHAR Where = NULL; ULONG NegotiateFlagsKeyStrength; STRING NtLmLocalOemComputerNameString; STRING NtLmLocalOemPrimaryDomainNameString; // // Initialization // *ContextAttributes = 0; *NegotiateFlags = 0; RtlInitString( &NtLmLocalOemComputerNameString, NULL ); RtlInitString( &NtLmLocalOemPrimaryDomainNameString, NULL ); // // Get a pointer to the credential // Status = SspCredentialReferenceCredential( CredentialHandle, FALSE, &Credential ); if ( !NT_SUCCESS( Status ) ) { SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: invalid credential handle.\n" )); goto Cleanup; } if ( (Credential->CredentialUseFlags & SECPKG_CRED_OUTBOUND) == 0 ) { Status = SEC_E_INVALID_CREDENTIAL_USE; SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: invalid credential use.\n" )); goto Cleanup; } // // Allocate a new context // Context = SspContextAllocateContext( ); if ( Context == NULL) { Status = STATUS_NO_MEMORY; SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: SspContextAllocateContext returned NULL\n")); goto Cleanup; } // // Build a handle to the newly created context. // *ContextHandle = (LSA_SEC_HANDLE) Context; // // We don't support any options. // // Complain about those that require we do something. // if ( (ContextReqFlags & ISC_REQ_PROMPT_FOR_CREDS) != 0 ) { Status = SEC_E_INVALID_CONTEXT_REQ; SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: invalid ContextReqFlags 0x%lx.\n", ContextReqFlags )); goto Cleanup; } // // Capture the default credentials from the credential structure. // if ( Credential->DomainName.Buffer != NULL ) { Status = NtLmDuplicateUnicodeString( &Context->DomainName, &Credential->DomainName ); if (!NT_SUCCESS(Status)) { SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: NtLmDuplicateUnicodeString (DomainName) returned %d\n",Status)); goto Cleanup; } } if ( Credential->UserName.Buffer != NULL ) { Status = NtLmDuplicateUnicodeString( &Context->UserName, &Credential->UserName ); if (!NT_SUCCESS(Status)) { SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: NtLmDuplicateUnicodeString (UserName) returned %d\n", Status )); goto Cleanup; } } Status = SspCredentialGetPassword( Credential, &Context->Password ); if (!NT_SUCCESS(Status)) { SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: SspCredentialGetPassword returned %d\n", Status )); goto Cleanup; } // // save away any marshalled credential info. // Status = CredpExtractMarshalledTargetInfo( TargetServerName, &Context->TargetInfo ); if (!NT_SUCCESS(Status)) { SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: CredpExtractMarshalledTargetInfo returned %d\n", Status )); goto Cleanup; } // // Compute the negotiate flags // // // Supported key strength(s) // NegotiateFlagsKeyStrength = NTLMSSP_NEGOTIATE_56; NegotiateFlagsKeyStrength |= NTLMSSP_NEGOTIATE_128; Context->NegotiateFlags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_OEM | NTLMSSP_NEGOTIATE_NTLM | ((NtLmGlobalLmProtocolSupported != 0) ? NTLMSSP_NEGOTIATE_NTLM2 : 0 ) | NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_ALWAYS_SIGN | NegotiateFlagsKeyStrength | NTLMSSP_NEGOTIATE_VERSION; if ((ContextReqFlags & ISC_REQ_CONFIDENTIALITY) != 0) { if (NtLmGlobalEncryptionEnabled) { // // CONFIDENTIALITY implies INTEGRITY // ContextReqFlags |= ISC_REQ_INTEGRITY; Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL | NTLMSSP_NEGOTIATE_LM_KEY | NTLMSSP_NEGOTIATE_KEY_EXCH ; *ContextAttributes |= ISC_RET_CONFIDENTIALITY; Context->ContextFlags |= ISC_RET_CONFIDENTIALITY; } else { Status = STATUS_NOT_SUPPORTED; SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: NtLmGlobalEncryptionEnabled is FALSE\n")); goto Cleanup; } } // // If the caller specified INTEGRITY, SEQUENCE_DETECT or REPLAY_DETECT, // that means they want to use the MakeSignature/VerifySignature // calls. Add this to the negotiate. // if (ContextReqFlags & (ISC_REQ_INTEGRITY | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT)) { Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_KEY_EXCH | NTLMSSP_NEGOTIATE_LM_KEY; } if ( (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) || (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) ) { // // allows us to support gss-style seal/unseal for LDAP. // Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM2; } if ((ContextReqFlags & ISC_REQ_INTEGRITY) != 0) { *ContextAttributes |= ISC_RET_INTEGRITY; Context->ContextFlags |= ISC_RET_INTEGRITY; } if ((ContextReqFlags & ISC_REQ_SEQUENCE_DETECT) != 0) { *ContextAttributes |= ISC_RET_SEQUENCE_DETECT; Context->ContextFlags |= ISC_RET_SEQUENCE_DETECT; } if ((ContextReqFlags & ISC_REQ_REPLAY_DETECT) != 0) { *ContextAttributes |= ISC_RET_REPLAY_DETECT; Context->ContextFlags |= ISC_RET_REPLAY_DETECT; } if ( (ContextReqFlags & ISC_REQ_NULL_SESSION ) != 0) { *ContextAttributes |= ISC_RET_NULL_SESSION; Context->ContextFlags |= ISC_RET_NULL_SESSION; } if ( (ContextReqFlags & ISC_REQ_CONNECTION ) != 0) { *ContextAttributes |= ISC_RET_CONNECTION; Context->ContextFlags |= ISC_RET_CONNECTION; } // // Check if the caller wants identify level // if ((ContextReqFlags & ISC_REQ_IDENTIFY)!= 0) { Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_IDENTIFY; *ContextAttributes |= ISC_RET_IDENTIFY; Context->ContextFlags |= ISC_RET_IDENTIFY; } IF_DEBUG( USE_OEM ) { Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_UNICODE; } if ( ((ContextReqFlags & ISC_REQ_MUTUAL_AUTH) != 0 ) && (NtLmGlobalMutualAuthLevel < 2 ) ) { *ContextAttributes |= ISC_RET_MUTUAL_AUTH ; if ( NtLmGlobalMutualAuthLevel == 0 ) { Context->ContextFlags |= ISC_RET_MUTUAL_AUTH ; } } // // For connection oriented security, we send a negotiate message to // the server. For datagram, we get back the server's // capabilities in the challenge message. // if ((ContextReqFlags & ISC_REQ_DATAGRAM) == 0) { BOOLEAN CheckForLocal; if ( (Credential->DomainName.Buffer == NULL && Credential->UserName.Buffer == NULL && Credential->Password.Buffer == NULL ) ) { CheckForLocal = TRUE; } else { CheckForLocal = FALSE; } if ( CheckForLocal ) { // // snap up a copy of the globals so we can just take the critsect once. // the old way took the critsect twice, once to read sizes, second time // to grab buffers - bad news if the global got bigger in between. // RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE); if ( NtLmGlobalOemComputerNameString.Buffer == NULL || NtLmGlobalOemPrimaryDomainNameString.Buffer == NULL ) { // // user has picked a computer name or domain name // that failed to convert to OEM. disable the loopback // detection. // Sometime beyond Win2k, Negotiate package should have // a general, robust scheme for detecting loopback. // CheckForLocal = FALSE; } else { Status = NtLmDuplicateString( &NtLmLocalOemComputerNameString, &NtLmGlobalOemComputerNameString ); if ( NT_SUCCESS(Status) ) { Status = NtLmDuplicateString( &NtLmLocalOemPrimaryDomainNameString, &NtLmGlobalOemPrimaryDomainNameString ); } } RtlReleaseResource(&NtLmGlobalCritSect); if (!NT_SUCCESS(Status)) { SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: NtLmDuplicateUnicodeString (GlobalOemComputerName or GlobalOemPrimaryDomainName) returned %d\n", Status )); goto Cleanup; } } // // Allocate a Negotiate message // NegotiateMessageSize = sizeof(*NegotiateMessage) + NtLmLocalOemComputerNameString.Length + NtLmLocalOemPrimaryDomainNameString.Length; if ((ContextReqFlags & ISC_REQ_ALLOCATE_MEMORY) == 0) { if ( NegotiateMessageSize > *OutputTokenSize ) { Status = SEC_E_BUFFER_TOO_SMALL; SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: OutputTokenSize is %d\n", *OutputTokenSize)); goto Cleanup; } } NegotiateMessage = (PNEGOTIATE_MESSAGE) NtLmAllocateLsaHeap( NegotiateMessageSize ); if ( NegotiateMessage == NULL) { Status = STATUS_NO_MEMORY; SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: Error allocating NegotiateMessage.\n")); goto Cleanup; } // // If this is the first call, // build a Negotiate message. // strcpy( (char *) NegotiateMessage->Signature, NTLMSSP_SIGNATURE ); NegotiateMessage->MessageType = NtLmNegotiate; NegotiateMessage->NegotiateFlags = Context->NegotiateFlags; NegotiateMessage->Version = NTLMSSP_ENGINE_VERSION; IF_DEBUG( REQUEST_TARGET ) { NegotiateMessage->NegotiateFlags |= NTLMSSP_REQUEST_TARGET; } // // Copy the DomainName and ComputerName into the negotiate message so // the other side can determine if this is a call from the local system. // // Pass the names in the OEM character set since the character set // hasn't been negotiated yet. // // Skip passing the workstation name if credentials were specified. This // ensures the other side doesn't fall into the case that this is the // local system. We wan't to ensure the new credentials are // authenticated. // Where = (PCHAR)(NegotiateMessage+1); if ( CheckForLocal ) { SspContextCopyString( NegotiateMessage, &NegotiateMessage->OemWorkstationName, &NtLmLocalOemComputerNameString, &Where ); NegotiateMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED; // // OEM_DOMAIN_SUPPLIED used to always be supplied - but the // only case it is ever used is when NTLMSSP_NEGOTIATE_LOCAL_CALL // is set. // SspContextCopyString( NegotiateMessage, &NegotiateMessage->OemDomainName, &NtLmLocalOemPrimaryDomainNameString, &Where ); NegotiateMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED; } if ((ContextReqFlags & ISC_REQ_ALLOCATE_MEMORY) == 0) { RtlCopyMemory( *OutputToken, NegotiateMessage, NegotiateMessageSize ); } else { *OutputToken = NegotiateMessage; NegotiateMessage = NULL; *ContextAttributes |= ISC_RET_ALLOCATED_MEMORY; } *OutputTokenSize = NegotiateMessageSize; } // // Save a reference to the credential in the context. // Context->Credential = Credential; Credential = NULL; // // Check for a caller requesting datagram security. // if ((ContextReqFlags & ISC_REQ_DATAGRAM) != 0) { Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_DATAGRAM; Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_NT_ONLY; Context->ContextFlags |= ISC_RET_DATAGRAM; *ContextAttributes |= ISC_RET_DATAGRAM; // If datagram security is required, then we don't send back a token *OutputTokenSize = 0; // // Generate a session key for this context if sign or seal was // requested. // if ((Context->NegotiateFlags & (NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_SEAL)) != 0) { Status = SspGenerateRandomBits( Context->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH ); if ( !NT_SUCCESS( Status ) ) { SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: SspGenerateRandomBits failed\n")); goto Cleanup; } } RtlCopyMemory( SessionKey, Context->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH ); // // Unless client wants to force its use, // Turn off strong crypt, because we can't negotiate it. // if (!NtLmGlobalDatagramUse128BitEncryption) { Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_128; } // // likewise for 56bit. note that package init handles turning // off 56bit if 128bit is configured for datagram. // if (!NtLmGlobalDatagramUse56BitEncryption) { Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_56; } // // Unless client wants to require NTLM2, can't use its // message processing features because we start using // MD5 sigs, full duplex mode, and datagram rekey before // we know if the server supports NTLM2. // if (!NtLmGlobalRequireNtlm2) { Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_NTLM2; } // // done fiddling with the negotiate flags, output them. // *NegotiateFlags = Context->NegotiateFlags; // // send back the negotiate flags to control signing and sealing // *NegotiateFlags |= NTLMSSP_APP_SEQ; } if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH ) { Status = SspGenerateRandomBits( Context->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH ); if ( !NT_SUCCESS( Status ) ) { SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: SspGenerateRandomBits failed\n")); goto Cleanup; } RtlCopyMemory( SessionKey, Context->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH ); } // // Return output parameters to the caller. // *ExpirationTime = SspContextGetTimeStamp( Context, TRUE ); Status = SEC_I_CONTINUE_NEEDED; Context->State = NegotiateSentState; SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleFirstCall: NegotiateFlags = %lx\n", Context->NegotiateFlags)); // // Check that caller asked for minimum security required. // if (!SsprCheckMinimumSecurity( Context->NegotiateFlags, NtLmGlobalMinimumClientSecurity)) { Status = SEC_E_UNSUPPORTED_FUNCTION; SspPrint(( SSP_CRITICAL, "SsprHandleFirstCall: " "Caller didn't request minimum security requirements (caller=0x%lx wanted=0x%lx).\n", Context->NegotiateFlags, NtLmGlobalMinimumClientSecurity )); goto Cleanup; } // // Free and locally used resources. // Cleanup: if ( Context != NULL ) { // // If we failed, // deallocate the context we allocated above. // // Delinking is a side effect of referencing, so do that. // if ( !NT_SUCCESS(Status) ) { PSSP_CONTEXT LocalContext; SspContextReferenceContext( *ContextHandle, TRUE, &LocalContext ); ASSERT( LocalContext != NULL ); if ( LocalContext != NULL ) { SspContextDereferenceContext( LocalContext ); } } // Always dereference it. SspContextDereferenceContext( Context ); } if ( NegotiateMessage != NULL ) { (VOID) NtLmFreeLsaHeap( NegotiateMessage ); } if ( Credential != NULL ) { SspCredentialDereferenceCredential( Credential ); } if ( NtLmLocalOemComputerNameString.Buffer != NULL ) { (VOID) NtLmFreePrivateHeap( NtLmLocalOemComputerNameString.Buffer ); } if ( NtLmLocalOemPrimaryDomainNameString.Buffer != NULL ) { (VOID) NtLmFreePrivateHeap( NtLmLocalOemPrimaryDomainNameString.Buffer ); } SspPrint(( SSP_API_MORE, "Leaving SsprHandleFirstCall: 0x%lx\n", Status )); return Status; UNREFERENCED_PARAMETER( InputToken ); UNREFERENCED_PARAMETER( InputTokenSize ); } //+------------------------------------------------------------------------- // // Function: SppGenerateExplicitCredAudit // // Synopsis: Generates audit event when a logon is initiated by // explicitly supplying credentials. // // Arguments: pContext -- pointer to SSP context // pChallengeMessage -- pointer to challenge message. the target // information is extracted from this. // ChallengeMessageSize -- size of the above // DoUnicode -- whether the target info is in // unicode or ascii format. // // Returns: NTSTATUS code // //-------------------------------------------------------------------------- NTSTATUS NTAPI SppGenerateExplicitCredAudit( IN PSSP_CONTEXT pContext, IN PCHALLENGE_MESSAGE pChallengeMessage, IN ULONG ChallengeMessageSize, IN BOOLEAN DoUnicode ) { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING TargetName = { 0 }; UNICODE_STRING TargetInfo = { 0 }; PUNICODE_STRING pTargetName = NULL; PUNICODE_STRING pTargetInfo = NULL; PWSTR pszTemp = NULL; ASSERT( DoUnicode && L"SppGenerateExplicitCredAudit: DoUnicode is FALSE" ); // // we do not handle non-unicode for now. // if ( !pContext || !pContext->Credential || !DoUnicode) { goto Cleanup; } // // extract target info from ChallengeMessage // if ((pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO) && (pChallengeMessage->TargetInfo.Length != 0) && (pChallengeMessage->TargetInfo.Buffer != NULL)) { // // target info is present. convert to absolute format now. // if (SspConvertRelativeToAbsolute ( pChallengeMessage, ChallengeMessageSize, &pChallengeMessage->TargetInfo, (PSTRING)&TargetInfo, DoUnicode, TRUE // NULL target info OK )) { // // get the DNS computer name // Status = MsvpAvlToString( &TargetInfo, MsvAvDnsComputerName, &pszTemp ); if ( NT_SUCCESS(Status) && pszTemp ) { // // initialize the target name/info to the same value // RtlInitUnicodeString( &TargetInfo, pszTemp ); RtlInitUnicodeString( &TargetName, pszTemp ); } else { // // DNS name not available, get the Netbios computer name // Status = MsvpAvlToString( &TargetInfo, MsvAvNbComputerName, &pszTemp ); if ( NT_SUCCESS(Status) && pszTemp ) { // // initialize the target name/info to the same value // RtlInitUnicodeString( &TargetInfo, pszTemp ); RtlInitUnicodeString( &TargetName, pszTemp ); } } } else { SspPrint(( SSP_CRITICAL, "SppGenerateExplicitCredAudit: " "ChallengeMessage.TargetInfo size wrong %ld\n", ChallengeMessageSize )); } } if ( TargetInfo.Length && TargetName.Length ) { pTargetInfo = &TargetInfo; pTargetName = &TargetName; } // // target info not available or could not be extracted, // but we may have "domain name\0servername" (or "servername") // in TargetName instead, use that if available. // else if ((pChallengeMessage->TargetName.Length != 0) && (pChallengeMessage->TargetName.Buffer != NULL)) { if (SspConvertRelativeToAbsolute ( pChallengeMessage, ChallengeMessageSize, &pChallengeMessage->TargetName, (PSTRING)&TargetName, DoUnicode, TRUE // NULL target info OK )) { // // determine the TargetName format // "domain name\0servername" or "servername" // PWSTR psz = TargetName.Buffer; USHORT cb = 0; while ((cb < TargetName.Length) && *psz) { psz++; cb += sizeof(WCHAR); } if ( cb < TargetName.Length ) { // // we have "domain name\0servername", skip the // domain name part. // psz++; TargetName.Buffer = psz; TargetName.Length = TargetName.Length - cb; TargetName.MaximumLength = TargetName.Length; } pTargetName = pTargetInfo = &TargetName; } else { SspPrint(( SSP_CRITICAL, "SppGenerateExplicitCredAudit: " "ChallengeMessage.TargetName size wrong %ld\n", ChallengeMessageSize )); } } { SECPKG_CALL_INFO CallInfo; LsaFunctions->GetCallInfo(&CallInfo); Status = I_LsaIAuditLogonUsingExplicitCreds( EVENTLOG_AUDIT_SUCCESS, &pContext->Credential->LogonId, // user1 logon id NULL, // user1 logon guid (HANDLE) (ULONG_PTR) CallInfo.ProcessId, &pContext->UserName, &pContext->DomainName, NULL, // user2 logon guid (NULL for ntlm) pTargetName, pTargetInfo ); } Cleanup: if ( pszTemp ) { NtLmFree( pszTemp ); } return Status; } NTSTATUS SsprHandleChallengeMessage( IN LSA_SEC_HANDLE CredentialHandle, IN OUT PLSA_SEC_HANDLE ContextHandle, IN ULONG ContextReqFlags, IN ULONG InputTokenSize, IN PVOID InputToken, IN ULONG SecondInputTokenSize, IN PVOID SecondInputToken, IN PUNICODE_STRING TargetServerName, IN OUT PULONG OutputTokenSize, OUT PVOID *OutputToken, IN OUT PULONG SecondOutputTokenSize, OUT PVOID *SecondOutputToken, OUT PULONG ContextAttributes, OUT PTimeStamp ExpirationTime, OUT PUCHAR SessionKey, OUT PULONG NegotiateFlags ) /*++ Routine Description: Handle the Challenge message part of InitializeSecurityContext. Arguments: DomainName,UserName,Password - Passed in credentials to be used for this context. DomainNameSize,userNameSize,PasswordSize - length in characters of the credentials to be used for this context. SessionKey - Session key to use for this context NegotiateFlags - Flags negotiated for this context TargetServerName - Target server name, used by CredMgr to associates NT4 servers with domains All other arguments same as for InitializeSecurityContext Return Value: STATUS_SUCCESS - Message handled SEC_I_CONTINUE_NEEDED -- Caller should call again later SEC_E_INVALID_TOKEN -- Token improperly formatted SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid SEC_E_BUFFER_TOO_SMALL -- Buffer for output token isn't big enough SEC_E_NO_CREDENTIALS -- There are no credentials for this client SEC_E_INSUFFICIENT_MEMORY -- Not enough memory --*/ { SECURITY_STATUS SecStatus = STATUS_SUCCESS; PSSP_CONTEXT Context = NULL; PCHALLENGE_MESSAGE ChallengeMessage = NULL; PNTLM_CHALLENGE_MESSAGE NtLmChallengeMessage = NULL; PAUTHENTICATE_MESSAGE AuthenticateMessage = NULL; PNTLM_INITIALIZE_RESPONSE NtLmInitializeResponse = NULL; PMSV1_0_GETCHALLENRESP_RESPONSE ChallengeResponseMessage = NULL; STRING UserName = {0}; STRING DomainName = {0}; STRING Workstation = {0}; STRING LmChallengeResponse = {0}; STRING NtChallengeResponse = {0}; STRING DatagramSessionKey = {0}; BOOLEAN DoUnicode = TRUE; NTSTATUS Status = STATUS_SUCCESS; NTSTATUS ProtocolStatus; NTSTATUS AuditStatus = STATUS_SUCCESS; MSV1_0_GETCHALLENRESP_REQUEST TempChallengeResponse; PMSV1_0_GETCHALLENRESP_REQUEST GetChallengeResponse; ULONG GetChallengeResponseSize; UNICODE_STRING RevealedPassword = {0}; ULONG ChallengeResponseSize; ULONG AuthenticateMessageSize; PCHAR Where; UCHAR DatagramKey[MSV1_0_USER_SESSION_KEY_LENGTH]; PLUID ClientLogonId = NULL; SECURITY_IMPERSONATION_LEVEL ImpersonationLevel = SecurityImpersonation; BOOLEAN UseSuppliedCreds = FALSE; PSSP_CREDENTIAL Credential = NULL; BOOLEAN fCallFromRedir = FALSE; BOOLEAN fShareLevel = FALSE; // is target down-level share based security (no username) ? BOOLEAN fCredmanCredentials = FALSE; // used credman creds? UNICODE_STRING TargetName = {0}; UNICODE_STRING DefectiveTargetName = {0}; // for broken servers. LPWSTR szCredTargetDomain = NULL; LPWSTR szCredTargetServer = NULL; LPWSTR szCredTargetDnsDomain = NULL; LPWSTR szCredTargetDnsServer = NULL; LPWSTR szCredTargetDnsTree = NULL; LPWSTR szCredTargetPreDFSServer = NULL; LUID LogonIdNetworkService = NETWORKSERVICE_LUID; PSSP_CONTEXT ServerContext = NULL; // server context referenced during loopback HANDLE ClientTokenHandle = NULL; // access token associated with client // which called AcquireCredentialsHandle (OUTBOUND) SspPrint((SSP_API_MORE, "Entering SsprHandleChallengeMessage\n")); // // Initialization // *ContextAttributes = 0; *NegotiateFlags = 0; GetChallengeResponse = &TempChallengeResponse; if (*ContextHandle == NULL) { // This is possibly an old style redir call (for 4.0 and before) // so, alloc the context and replace the creds if new ones exists fCallFromRedir = TRUE; SspPrint((SSP_API_MORE, "SsprHandleChallengeMessage: *ContextHandle is NULL (old-style RDR)\n")); if ((ContextReqFlags & ISC_REQ_USE_SUPPLIED_CREDS) != 0) { UseSuppliedCreds = TRUE; } // This is a superflous check since we alloc only if the caller // has asked us too. This is to make sure that the redir always asks us to alloc if (!(ContextReqFlags & ISC_REQ_ALLOCATE_MEMORY)) { SecStatus = STATUS_NOT_SUPPORTED; goto Cleanup; } SecStatus = SspCredentialReferenceCredential( CredentialHandle, FALSE, &Credential ); if ( !NT_SUCCESS( SecStatus ) ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: SspCredentialReferenceCredential returns %x.\n", SecStatus )); goto Cleanup; } // // Allocate a new context // Context = SspContextAllocateContext(); if (Context == NULL) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: SspContextAllocateContext returns NULL.\n" )); SecStatus = STATUS_NO_MEMORY; goto Cleanup; } // We've just added a context, we don't nornally add and then // reference it. SspContextDereferenceContext( Context ); *ContextHandle = (LSA_SEC_HANDLE) Context; // // Capture the default credentials from the credential structure. // if ( Credential->DomainName.Buffer != NULL ) { Status = NtLmDuplicateUnicodeString( &Context->DomainName, &Credential->DomainName ); if (!NT_SUCCESS(Status)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmDuplicateUnicodeString (DomainName) returned %d\n",Status)); SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY); goto Cleanup; } } if ( Credential->UserName.Buffer != NULL ) { Status = NtLmDuplicateUnicodeString( &Context->UserName, &Credential->UserName ); if (!NT_SUCCESS(Status)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmDuplicateUnicodeString (UserName) returned %d\n", Status )); SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY); goto Cleanup; } } SecStatus = SspCredentialGetPassword( Credential, &Context->Password ); if (!NT_SUCCESS(SecStatus)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: SspCredentialGetPassword returned %d\n", SecStatus )); goto Cleanup; } // Assign the Credential Context->Credential = Credential; Credential = NULL; // // fake it // Context->NegotiateFlags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_OEM | NTLMSSP_REQUEST_TARGET | NTLMSSP_REQUEST_INIT_RESPONSE | ((NtLmGlobalLmProtocolSupported != 0) ? NTLMSSP_NEGOTIATE_NTLM2 : 0 ) | NTLMSSP_TARGET_TYPE_SERVER | NTLMSSP_NEGOTIATE_VERSION; *ExpirationTime = SspContextGetTimeStamp(Context, TRUE); Context->State = NegotiateSentState; // If creds are passed in by the RDR, then replace the ones in the context if (UseSuppliedCreds) { if (SecondInputTokenSize < sizeof(NTLM_CHALLENGE_MESSAGE)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: Invalid SecondInputTokensize.\n" )); SecStatus = SEC_E_INVALID_TOKEN; goto Cleanup; } NtLmChallengeMessage = (PNTLM_CHALLENGE_MESSAGE) NtLmAllocatePrivateHeap(SecondInputTokenSize); if (NtLmChallengeMessage == NULL) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: Error while allocating NtLmChallengeMessage\n" )); SecStatus = STATUS_NO_MEMORY; goto Cleanup; } RtlCopyMemory(NtLmChallengeMessage, SecondInputToken, SecondInputTokenSize); // // NULL session is only true if user, domain, and password are all // empty strings in stead of NULLs // if (((NtLmChallengeMessage->Password.Length == 0) && (NtLmChallengeMessage->Password.Buffer != NULL)) && ((NtLmChallengeMessage->UserName.Length == 0) && (NtLmChallengeMessage->UserName.Buffer != NULL)) && ((NtLmChallengeMessage->DomainName.Length == 0) && (NtLmChallengeMessage->DomainName.Buffer != NULL))) { // This could only be a null session request SspPrint(( SSP_WARNING, "SsprHandleChallengeMessage: null session NtLmChallengeMessage\n" )); if (Context->Password.Buffer != NULL) { // free it first NtLmFreePrivateHeap (Context->Password.Buffer); } Context->Password.Buffer = (LPWSTR) NtLmAllocatePrivateHeap(sizeof(WCHAR)); if (Context->Password.Buffer == NULL) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmAllocatePrivateHeap(Password) returns NULL.\n")); SecStatus = SEC_E_INSUFFICIENT_MEMORY; goto Cleanup; } Context->Password.Length = 0; Context->Password.MaximumLength = 0; *(Context->Password.Buffer) = L'\0'; SspHidePassword(&Context->Password); if (Context->UserName.Buffer != NULL) { // free it first NtLmFreePrivateHeap (Context->UserName.Buffer); } Context->UserName.Buffer = (LPWSTR) NtLmAllocatePrivateHeap(sizeof(WCHAR)); if (Context->UserName.Buffer == NULL) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmAllocatePrivateHeap(UserName) returns NULL.\n")); SecStatus = SEC_E_INSUFFICIENT_MEMORY; goto Cleanup; } Context->UserName.Length = 0; Context->UserName.MaximumLength = sizeof(WCHAR); *(Context->UserName.Buffer) = L'\0'; if (Context->DomainName.Buffer != NULL) { // free it first NtLmFreePrivateHeap (Context->DomainName.Buffer); } Context->DomainName.Buffer = (LPWSTR) NtLmAllocatePrivateHeap(sizeof(WCHAR)); if (Context->DomainName.Buffer == NULL) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmAllocatePrivateHeap(DomainName) returns NULL.\n")); SecStatus = SEC_E_INSUFFICIENT_MEMORY; goto Cleanup; } Context->DomainName.Length = 0; Context->DomainName.MaximumLength = sizeof(WCHAR); *(Context->DomainName.Buffer) = L'\0'; } else { ULONG_PTR BufferTail = (ULONG_PTR)NtLmChallengeMessage + SecondInputTokenSize; UNICODE_STRING AbsoluteString; if (NtLmChallengeMessage->Password.Buffer != 0) { AbsoluteString.Buffer = (LPWSTR)((PUCHAR)NtLmChallengeMessage + NtLmChallengeMessage->Password.Buffer); // // verify buffer not out of range. // if ( ( (ULONG_PTR)AbsoluteString.Buffer > BufferTail ) || ( (ULONG_PTR)((PUCHAR)AbsoluteString.Buffer + NtLmChallengeMessage->Password.Length) > BufferTail ) || ( (ULONG_PTR)AbsoluteString.Buffer < (ULONG_PTR)NtLmChallengeMessage ) ) { SecStatus = SEC_E_NO_CREDENTIALS; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: Buffer overflow (Password).\n" )); goto Cleanup; } if (Context->Password.Buffer != NULL) { // free it first NtLmFreePrivateHeap (Context->Password.Buffer); Context->Password.Buffer = NULL; } AbsoluteString.Length = AbsoluteString.MaximumLength = NtLmChallengeMessage->Password.Length; SecStatus = NtLmDuplicatePassword( &Context->Password, &AbsoluteString ); if (!NT_SUCCESS(SecStatus)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmDuplicatePassword returns 0x%lx.\n",SecStatus )); goto Cleanup; } SspHidePassword(&Context->Password); } if (NtLmChallengeMessage->UserName.Length != 0) { AbsoluteString.Buffer = (LPWSTR)((PUCHAR)NtLmChallengeMessage + NtLmChallengeMessage->UserName.Buffer); // // verify buffer not out of range. // if ( ( (ULONG_PTR)AbsoluteString.Buffer > BufferTail ) || ( (ULONG_PTR)((PUCHAR)AbsoluteString.Buffer + NtLmChallengeMessage->UserName.Length) > BufferTail ) || ( (ULONG_PTR)AbsoluteString.Buffer < (ULONG_PTR)NtLmChallengeMessage ) ) { SecStatus = SEC_E_NO_CREDENTIALS; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: Buffer overflow (UserName).\n" )); goto Cleanup; } if (Context->UserName.Buffer != NULL) { // free it first NtLmFreePrivateHeap (Context->UserName.Buffer); } AbsoluteString.Length = AbsoluteString.MaximumLength = NtLmChallengeMessage->UserName.Length; SecStatus = NtLmDuplicateUnicodeString(&Context->UserName, &AbsoluteString); if (!NT_SUCCESS(SecStatus)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmDuplicateUnicodeString(UserName) returns 0x%lx.\n",SecStatus )); goto Cleanup; } } if (NtLmChallengeMessage->DomainName.Length != 0) { AbsoluteString.Buffer = (LPWSTR)((PUCHAR)NtLmChallengeMessage + NtLmChallengeMessage->DomainName.Buffer); // // verify buffer not out of range. // if ( ( (ULONG_PTR)AbsoluteString.Buffer > BufferTail ) || ( (ULONG_PTR)((PUCHAR)AbsoluteString.Buffer + NtLmChallengeMessage->DomainName.Length) > BufferTail ) || ( (ULONG_PTR)AbsoluteString.Buffer < (ULONG_PTR)NtLmChallengeMessage ) ) { SecStatus = SEC_E_NO_CREDENTIALS; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: Buffer overflow (DomainName).\n" )); goto Cleanup; } if (Context->DomainName.Buffer != NULL) { // free it first NtLmFreePrivateHeap (Context->DomainName.Buffer); } AbsoluteString.Length = AbsoluteString.MaximumLength = NtLmChallengeMessage->DomainName.Length; SecStatus = NtLmDuplicateUnicodeString(&Context->DomainName, &AbsoluteString); if (!NT_SUCCESS(SecStatus)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmDuplicateUnicodeString(DomainName) returns 0x%lx.\n",SecStatus )); goto Cleanup; } } } if (NtLmChallengeMessage) { NtLmFreePrivateHeap (NtLmChallengeMessage); NtLmChallengeMessage = NULL; } } // end of special casing if credentials are supplied in the first init call } // end of special casing for the old style redir // // Find the currently existing context. // SecStatus = SspContextReferenceContext( *ContextHandle, FALSE, &Context ); if ( !NT_SUCCESS(SecStatus) ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: invalid context handle.\n" )); goto Cleanup; } // // passing Accept handle to Init can cause AV // if ( Context->Credential == NULL ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: invalid context handle, missing credential.\n" )); SecStatus = STATUS_INVALID_HANDLE; goto Cleanup; } // // If this is not reauthentication (or is datagram reauthentication) // pull the context out of the associated credential. // if ((Context->State != AuthenticateSentState) || (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) != 0) { ClientLogonId = &Context->Credential->LogonId; ImpersonationLevel = Context->Credential->ImpersonationLevel; } // // process the TargetServerName to see if marshalled target info was // passed in. This can happen if the caller passes in marshalled target // info for each call to InitializeSecurityContext(), or, uses the downlevel // RDR path which happened previously in this routine. // Status = CredpExtractMarshalledTargetInfo( TargetServerName, &Context->TargetInfo ); if (!NT_SUCCESS(Status)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: CredpExtractMarshalledTargetInfo returned %d\n", Status )); goto Cleanup; } // // If we have already sent the authenticate message, then this must be // RPC calling Initialize a third time to re-authenticate a connection. // This happens when a new interface is called over an existing // connection. What we do here is build a NULL authenticate message // that the server will recognize and also ignore. // // // That being said, if we are doing datagram style authentication then // the story is different. The server may have dropped this security // context and then the client sent another packet over. The server // will then be trying to restore the context, so we need to build // another authenticate message. // if ( Context->State == AuthenticateSentState ) { AUTHENTICATE_MESSAGE NullMessage; if (((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) == NTLMSSP_NEGOTIATE_DATAGRAM) && (InputTokenSize != 0) && (InputToken != NULL) ) { // // we are doing a reauthentication for datagram, so let this // through. We don't want the security.dll remapping this // context. // *ContextAttributes |= SSP_RET_REAUTHENTICATION; } else { // // To make sure this is the intended meaning of the call, check // that the input token is NULL. // if ( (InputTokenSize != 0) || (InputToken != NULL) ) { SecStatus = SEC_E_INVALID_TOKEN; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: (re-auth) invalid InputTokenSize.\n" )); goto Cleanup; } if ( (*OutputTokenSize < sizeof(NullMessage)) && ((ContextReqFlags & ISC_REQ_ALLOCATE_MEMORY) == 0)) { SecStatus = SEC_E_BUFFER_TOO_SMALL; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: invalid OutputTokenSize.\n" )); } else { RtlZeroMemory( &NullMessage, sizeof(NullMessage) ); strcpy( (char *)NullMessage.Signature, NTLMSSP_SIGNATURE ); NullMessage.MessageType = NtLmAuthenticate; if ((ContextReqFlags & ISC_REQ_ALLOCATE_MEMORY) == 0) { RtlCopyMemory( *OutputToken, &NullMessage, sizeof(NullMessage)); } else { PAUTHENTICATE_MESSAGE NewNullMessage = (PAUTHENTICATE_MESSAGE) NtLmAllocateLsaHeap(sizeof(NullMessage)); if ( NewNullMessage == NULL) { SecStatus = STATUS_NO_MEMORY; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: Error allocating NewNullMessage.\n" )); goto Cleanup; } *OutputToken = NewNullMessage; NewNullMessage = NULL; *ContextAttributes |= ISC_RET_ALLOCATED_MEMORY; } *OutputTokenSize = sizeof(NullMessage); } *ContextAttributes |= SSP_RET_REAUTHENTICATION; goto Cleanup; } } else if ( Context->State != NegotiateSentState ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "Context not in NegotiateSentState\n" )); SecStatus = SEC_E_OUT_OF_SEQUENCE; goto Cleanup; } // // We don't support any options. // Complain about those that require we do something. // if ( (ContextReqFlags & ISC_REQ_PROMPT_FOR_CREDS) != 0 ){ SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: invalid ContextReqFlags 0x%lx.\n", ContextReqFlags )); SecStatus = SEC_E_INVALID_CONTEXT_REQ; goto Cleanup; } // // Ignore the Credential Handle. // // Since this is the second call, // the credential is implied by the Context. // We could double check that the Credential Handle is either NULL or // correct. However, our implementation doesn't maintain a close // association between the two (actually no association) so checking // would require a lot of overhead. // UNREFERENCED_PARAMETER( CredentialHandle ); // // Get the ChallengeMessage. // if ( InputTokenSize < sizeof(OLD_CHALLENGE_MESSAGE) ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "ChallengeMessage size wrong %ld\n", InputTokenSize )); SecStatus = SEC_E_INVALID_TOKEN; goto Cleanup; } SecStatus = SspContextGetMessage( InputToken, InputTokenSize, NtLmChallenge, (PVOID *)&ChallengeMessage ); if ( !NT_SUCCESS(SecStatus) ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "ChallengeMessage GetMessage returns 0x%lx\n", SecStatus )); goto Cleanup; } // // for down-level RDR, EXPORTED_CONTEXT is a hint that we are talking to // share level target. // if ( fCallFromRedir ) { if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_EXPORTED_CONTEXT ) { ChallengeMessage->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_EXPORTED_CONTEXT); fShareLevel = TRUE; SspPrint(( SSP_WARNING, "SsprHandleChallengeMessage: " "downlevel sharelevel security target\n")); } } if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM ) { // // take out any flags we didn't ask for -- self defense from bogus combinations // ChallengeMessage->NegotiateFlags &= ( Context->NegotiateFlags | NTLMSSP_NEGOTIATE_TARGET_INFO | NTLMSSP_TARGET_TYPE_SERVER | NTLMSSP_TARGET_TYPE_DOMAIN | NTLMSSP_NEGOTIATE_LOCAL_CALL); } // // Determine if the caller wants OEM or UNICODE // if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_UNICODE ) { Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE; Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_OEM; DoUnicode = TRUE; } else if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_OEM ){ Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM; Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_UNICODE; DoUnicode = FALSE; } else { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "ChallengeMessage bad NegotiateFlags 0x%lx\n", ChallengeMessage->NegotiateFlags )); SecStatus = SEC_E_INVALID_TOKEN; goto Cleanup; } // // Copy other interesting negotiate flags into the context // if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO ) { Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO; } else { Context->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_TARGET_INFO); } // // if got NTLM2, or if LM_KEY specifically forbidden don't use LM_KEY // if ((ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2)) { if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY ) { SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleChallengeMessage: " "Server support NTLM2 caused LM_KEY to be disabled.\n" )); } ChallengeMessage->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_LM_KEY; Context->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_LM_KEY); } // // if we did not get NTLM2 remove it from context negotiate flags // if (!(ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2)) { if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2 ) { SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleChallengeMessage: " "Server didn't support NTLM2 and client did.\n" )); } Context->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_NTLM2); } if ((ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM) == 0) { if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM ) { SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleChallengeMessage: " "Server didn't support NTLM and client did.\n" )); } Context->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_NTLM); } if ((ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) == 0) { if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH ) { SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleChallengeMessage: " "Server didn't support KEY_EXCH and client did.\n" )); } Context->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_KEY_EXCH); } if ((ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY) == 0) { if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY ) { SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleChallengeMessage: " "Server didn't support LM_KEY and client did.\n" )); } Context->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_LM_KEY); } // // make sure KEY_EXCH is always set if DATAGRAM negotiated and we need a key // this is for local internal use; its now safe because we've got the bits // to go on the wire copied... // if ((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) && (Context->NegotiateFlags & (NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_SEAL))) { Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH; } // // allow negotiate of certain options such as sign/seal when server // asked for it, but client didn't. // #if 0 //// if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL ) { if ( (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) == 0 ) { SspPrint(( SSP_SESSION_KEYS, "SsprHandleChallengeMessage: client didn't request SEAL but server did, adding SEAL.\n")); } Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL; } if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN ) { if ( (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) == 0 ) { SspPrint(( SSP_SESSION_KEYS, "SsprHandleChallengeMessage: client didn't request SIGN but server did, adding SIGN.\n")); } Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN; } //// #endif // // if server didn't support certain crypto strengths, insure they // are disabled. // if ( (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_56) == 0 ) { if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_56 ) { SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleChallengeMessage: Client supported 56, but server didn't.\n")); } Context->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_56); } if ( (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_128) == 0 ) { if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_128 ) { SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleChallengeMessage: Client supported 128, but server didn't.\n")); } Context->NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_128); } // // Check that server gave minimum security required. // not done for legacy down-level case, as, NegotiateFlags are // constructed from incomplete information. // if ( !fCallFromRedir ) { if (!SsprCheckMinimumSecurity( Context->NegotiateFlags, NtLmGlobalMinimumClientSecurity)) { SecStatus = SEC_E_UNSUPPORTED_FUNCTION; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "ChallengeMessage didn't support minimum security requirements.\n" )); goto Cleanup; } } if (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN ) { Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; } else { Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN; } // // Determine that the caller negotiated to NTLM or nothing, but not // NetWare. // if ( (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NETWARE) && ((ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM) == 0) && ((ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) == 0) ) { SecStatus = STATUS_NOT_SUPPORTED; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "ChallengeMessage asked for Netware only.\n" )); goto Cleanup; } if (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) { if (InputTokenSize >= RTL_SIZEOF_THROUGH_FIELD(CHALLENGE_MESSAGE, Version)) { C_ASSERT(sizeof(NTLM_VER_INFO) == sizeof(ULONG64)); RtlCopyMemory(&Context->ServerVersion, &ChallengeMessage->Version, sizeof(ChallengeMessage->Version)); SspPrint(( SSP_VERSION, "SsprHandleChallengeMessage: ServerVersion %#I64x, Major %I64d, Minor %I64d, Build %I64d, Revision %I64d\n", ChallengeMessage->Version, Context->ServerVersion.Major, Context->ServerVersion.Minor, Context->ServerVersion.Build, Context->ServerVersion.Revision )); } else { SecStatus = SEC_E_INVALID_TOKEN; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: ChallengeMessage size too small with NTLMSSP_NEGOTIATE_VERSION\n" )); goto Cleanup; } } // // Check if we negotiated for identify level // if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) { if (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) { Context->ContextFlags |= ISC_REQ_IDENTIFY; *ContextAttributes |= ISC_RET_IDENTIFY; } else { SecStatus = STATUS_NOT_SUPPORTED; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "ChallengeMessage bad NegotiateFlags 0x%lx\n", ChallengeMessage->NegotiateFlags )); goto Cleanup; } } // // If the server is running on this same machine, // just duplicate our caller's token and use it. // while ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL ) { ULONG_PTR ServerContextHandle; static const UCHAR FixedSessionKey[MSV1_0_USER_SESSION_KEY_LENGTH] = { 'S', 'y', 's', 't', 'e', 'm', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'D', 'T', 'C' }; SspPrint(( SSP_MISC, "SsprHandleChallengeMessage: Local Call.\n")); // // Require the new challenge message if we are going to access the // server context handle // if ( InputTokenSize < sizeof(CHALLENGE_MESSAGE) ) { SecStatus = SEC_E_INVALID_TOKEN; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: invalid InputTokenSize.\n")); goto Cleanup; } // // Open the server's context here within this process. // ServerContextHandle = (ULONG_PTR)(ChallengeMessage->ServerContextHandle); SspContextReferenceContext( ServerContextHandle, FALSE, &ServerContext ); if ( ServerContext == NULL ) { // // This means the server has lied about this being a local call or // the server process has exitted. // this can happen if the client and server have not had netbios // machine names set, so, allow this and continue processing // as if this were not loopback. // SspPrint(( SSP_WARNING, "SsprHandleChallengeMessage: " "ChallengeMessage bad ServerContextHandle 0x%p\n", ChallengeMessage->ServerContextHandle)); ChallengeMessage->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_LOCAL_CALL; break; } if (((ServerContext->NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL) == 0) || (ServerContext->State != ChallengeSentState) ) { SspPrint(( SSP_WARNING, "SsprHandleChallengeMessage: " "ChallengeMessage claimed ServerContextHandle in bad state 0x%p\n", ChallengeMessage->ServerContextHandle)); ChallengeMessage->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_LOCAL_CALL; break; } Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_LOCAL_CALL; // // Local loopback for network servcice // if ( (Context->Credential->MutableCredFlags & SSP_CREDENTIAL_FLAG_WAS_NETWORK_SERVICE) ) { SspPrint((SSP_WARNING, "SsprHandleChallengeMessage using networkservice in local loopback\n")); ClientLogonId = &LogonIdNetworkService; } // // open the token associated with the caller at the time of the // AcquireCredentialsHandle() call. // SecStatus = LsaFunctions->OpenTokenByLogonId( ClientLogonId, &ClientTokenHandle ); if (!NT_SUCCESS(SecStatus)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "Could not open client token 0x%lx\n", SecStatus )); goto Cleanup; } if ( ImpersonationLevel < SecurityImpersonation ) { SspPrint(( SSP_WARNING, "Reducing impersonation level %lu to %lu\n", SecurityImpersonation, ImpersonationLevel)); } if ((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) != 0) { ImpersonationLevel = min(SecurityIdentification, ImpersonationLevel); } SecStatus = SspDuplicateToken( ClientTokenHandle, ImpersonationLevel, &ServerContext->TokenHandle ); if (!NT_SUCCESS(SecStatus)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "Could not duplicate client token 0x%lx\n", SecStatus )); goto Cleanup; } // // enable all privileges in the duplicated token, to be consistent // with what a network logon normally looks like // (all privileges held are enabled by default). // if (!SspEnableAllPrivilegesToken(ServerContext->TokenHandle)) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "Could not enable all privileges for loopback.\n")); } // // give local call a hard-coded session key so calls into RDR // to fetch a session key succeed (eg: RtlGetUserSessionKeyClient) // RtlCopyMemory(Context->SessionKey, FixedSessionKey, MSV1_0_USER_SESSION_KEY_LENGTH); // // Don't pass any credentials in the authenticate message. // RtlInitString( &DomainName, NULL ); RtlInitString( &UserName, NULL ); RtlInitString( &Workstation, NULL ); RtlInitString( &NtChallengeResponse, NULL ); RtlInitString( &LmChallengeResponse, NULL ); RtlInitString( &DatagramSessionKey, NULL ); break; } // // If the server is running on a diffent machine, // determine the caller's DomainName, UserName and ChallengeResponse // to pass back in the AuthenicateMessage. // if ( (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL) == 0 ) { // // Build the GetChallengeResponse message to pass to the LSA. // PCHAR MarshalPtr; // marshalling pointer ULONG MarshalBytes; UNICODE_STRING TargetInfo; CREDENTIAL_TARGET_INFORMATIONW CredTargetInfo; // // insure all fields NULL. // ZeroMemory( &CredTargetInfo, sizeof(CredTargetInfo) ); ZeroMemory( GetChallengeResponse, sizeof(*GetChallengeResponse) ); GetChallengeResponseSize = sizeof(*GetChallengeResponse); GetChallengeResponse->MessageType = MsV1_0Lm20GetChallengeResponse; GetChallengeResponse->ParameterControl = 0; if ( Context->Credential ) { PUNICODE_STRING TmpDomainName = &(Context->Credential->DomainName); PUNICODE_STRING TmpUserName = &(Context->Credential->UserName); PUNICODE_STRING TmpPassword = &(Context->Credential->Password); if ( (TmpDomainName->Buffer != NULL) || (TmpUserName->Buffer != NULL) || (TmpPassword->Buffer != NULL) ) { UseSuppliedCreds = TRUE; } } // // if caller specifically asked for non nt session key, give it to them // if ( (ChallengeMessage->NegotiateFlags & NTLMSSP_REQUEST_NON_NT_SESSION_KEY ) || (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY)) { GetChallengeResponse->ParameterControl |= RETURN_NON_NT_USER_SESSION_KEY; } GetChallengeResponse->ParameterControl |= GCR_NTLM3_PARMS; // // if TargetInfo present, use it, otherwise construct it from target name // if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO) { if ( InputTokenSize < RTL_SIZEOF_THROUGH_FIELD(CHALLENGE_MESSAGE, TargetInfo) ) { SspPrint(( SSP_CRITICAL, "SspHandleChallengeMessage: " "ChallengeMessage size wrong when target info flag on %ld\n", InputTokenSize )); SecStatus = SEC_E_INVALID_TOKEN; goto Cleanup; } // // validate and relocate the target info // if (!SspConvertRelativeToAbsolute ( ChallengeMessage, InputTokenSize, &ChallengeMessage->TargetInfo, (PSTRING)&TargetInfo, DoUnicode, TRUE // NULL target info OK )) { SspPrint(( SSP_CRITICAL, "SspHandleChallengeMessage: " "ChallengeMessage.TargetInfo size wrong %ld\n", InputTokenSize )); SecStatus = SEC_E_INVALID_TOKEN; goto Cleanup; } // // Calculate mashal data size for the target info case // if ( UseSuppliedCreds ) { MarshalBytes = TargetInfo.Length + Context->DomainName.Length + Context->UserName.Length + Context->Password.Length + (4*sizeof(WCHAR)); } else { MarshalBytes = TargetInfo.Length + (DNLEN * sizeof(WCHAR)) + (UNLEN * sizeof(WCHAR)) + (PWLEN * sizeof(WCHAR)) + (4*sizeof(WCHAR)); } // // Sets a "reasonable" upper limit on the token size // to avoid unbounded stack allocations. The limit is // NTLMSSP_MAX_MESSAGE_SIZE*4 for historical reasons. // if ((NTLMSSP_MAX_MESSAGE_SIZE*4)ServerName, (PSTRING)&TargetInfo, &MarshalPtr ); GetChallengeResponseSize += GetChallengeResponse->ServerName.Length; // // tell GCR that its an AV list. // GetChallengeResponse->ParameterControl |= GCR_TARGET_INFO; // get various target names if ( !UseSuppliedCreds ) { ULONG AvFlags = 0; // // Uplevel -- get the info from the comprehensive TARGET_INFO // // Status = MsvpAvlToString( &TargetInfo, MsvAvNbDomainName, &szCredTargetDomain ); if (!NT_SUCCESS(Status)) { SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY ); goto Cleanup; } Status = MsvpAvlToString( &TargetInfo, MsvAvNbComputerName, &szCredTargetServer ); if (!NT_SUCCESS(Status)) { SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY ); goto Cleanup; } Status = MsvpAvlToString( &TargetInfo, MsvAvDnsDomainName, &szCredTargetDnsDomain ); if (!NT_SUCCESS(Status)) { SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY ); goto Cleanup; } Status = MsvpAvlToString( &TargetInfo, MsvAvDnsComputerName, &szCredTargetDnsServer ); if (!NT_SUCCESS(Status)) { SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY ); goto Cleanup; } Status = MsvpAvlToString( &TargetInfo, MsvAvDnsTreeName, &szCredTargetDnsTree ); if (!NT_SUCCESS(Status)) { SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY ); goto Cleanup; } if ( TargetServerName && TargetServerName->Length ) { // // check TargetServerName against szTargetServer. If they don't match, we have a DFS share. // Add Pre-DFS ServerName // LPWSTR szTargetServerName = TargetServerName->Buffer; DWORD cchTarget = TargetServerName->Length / sizeof(WCHAR); DWORD IndexSlash; for (IndexSlash = 0 ; IndexSlash < cchTarget; IndexSlash++) { if (TargetServerName->Buffer[IndexSlash] == L'/') { cchTarget -= IndexSlash; szTargetServerName = &TargetServerName->Buffer[ IndexSlash+1 ]; break; } } szCredTargetPreDFSServer = (LPWSTR)NtLmAllocatePrivateHeap( (cchTarget+1) * sizeof(WCHAR) ); if ( szCredTargetPreDFSServer == NULL ) { SecStatus = SEC_E_INSUFFICIENT_MEMORY; goto Cleanup; } CopyMemory( szCredTargetPreDFSServer, szTargetServerName, cchTarget*sizeof(WCHAR) ); szCredTargetPreDFSServer[ cchTarget ] = L'\0'; CredTargetInfo.TargetName = szCredTargetPreDFSServer; } // // see if server enabled the funky guest bit (tm) // Status = MsvpAvlToFlag( &TargetInfo, MsvAvFlags, &AvFlags ); if ( Status == STATUS_SUCCESS ) { if ( AvFlags & MSV1_0_AV_FLAG_FORCE_GUEST ) { CredTargetInfo.Flags |= CRED_TI_ONLY_PASSWORD_REQUIRED; } } } } else { BOOLEAN DefectiveTarget = FALSE; // downlevel - first call may have been handled by redir // // validate and relocate the target name // if (!SspConvertRelativeToAbsolute ( ChallengeMessage, InputTokenSize, &ChallengeMessage->TargetName, (PSTRING)&TargetName, DoUnicode, TRUE // NULL targetname OK )) { SspPrint(( SSP_CRITICAL, "SspHandleChallengeMessage: " "ChallengeMessage.TargetName size wrong %ld\n", InputTokenSize )); SecStatus = SEC_E_INVALID_TOKEN; goto Cleanup; } // // certain flavors of Unix servers set UNICODE and OEM flags, // but supply an OEM buffer. Try to resolve that here. // if ( (DoUnicode) && (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_OEM) ) { if ( IsTextUnicode( TargetName.Buffer, TargetName.Length, NULL ) == 0 ) { DefectiveTarget = TRUE; } } // // convert TargetName to Unicode if needed // if ( !DoUnicode || DefectiveTarget ) { UNICODE_STRING TempString; Status = RtlOemStringToUnicodeString( &TempString, (PSTRING)&TargetName, TRUE); if ( !NT_SUCCESS(Status) ) { SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY ); goto Cleanup; } TargetName = TempString; if ( DefectiveTarget ) { // // save it so we can free it later. // DefectiveTargetName = TargetName; } } if ( !UseSuppliedCreds ) { // ChallengeMessage->TargetName will be the server's netbios domain name if ( TargetName.Buffer && TargetName.Length ) { LPWSTR szTmpTargetName; szTmpTargetName = (PWSTR)NtLmAllocatePrivateHeap( TargetName.Length + sizeof(WCHAR) ); if ( szTmpTargetName == NULL ) { SecStatus = SEC_E_INSUFFICIENT_MEMORY; goto Cleanup; } RtlCopyMemory( szTmpTargetName, TargetName.Buffer, TargetName.Length ); szTmpTargetName[ TargetName.Length/sizeof(WCHAR) ] = L'\0'; if ( ChallengeMessage->NegotiateFlags & NTLMSSP_TARGET_TYPE_SERVER ) { szCredTargetServer = szTmpTargetName; } else if ( ChallengeMessage->NegotiateFlags & NTLMSSP_TARGET_TYPE_DOMAIN ) { szCredTargetDomain = szTmpTargetName; } // TODO: what if TARGET_TYPE not specified, or TARGET_TYPE_SHARE ? } if ( TargetServerName && TargetServerName->Length ) { // // check TargetServerName against szTargetServer. If they don't match, we have a DFS share. // Add Pre-DFS ServerName // LPWSTR szTargetServerName = TargetServerName->Buffer; DWORD cchTarget = TargetServerName->Length / sizeof(WCHAR); DWORD IndexSlash; for (IndexSlash = 0 ; IndexSlash < cchTarget; IndexSlash++) { if (TargetServerName->Buffer[IndexSlash] == L'/') { cchTarget -= IndexSlash; szTargetServerName = &TargetServerName->Buffer[ IndexSlash+1 ]; break; } } szCredTargetPreDFSServer = (LPWSTR)NtLmAllocatePrivateHeap( (cchTarget+1) * sizeof(WCHAR) ); if ( szCredTargetPreDFSServer == NULL ) { SecStatus = SEC_E_INSUFFICIENT_MEMORY; goto Cleanup; } CopyMemory( szCredTargetPreDFSServer, szTargetServerName, cchTarget*sizeof(WCHAR) ); szCredTargetPreDFSServer[ cchTarget ] = L'\0'; CredTargetInfo.TargetName = szCredTargetPreDFSServer; } if ( fShareLevel ) { CredTargetInfo.Flags |= CRED_TI_ONLY_PASSWORD_REQUIRED; } } // // Calculate mashal data size for the target name case // if ( UseSuppliedCreds ) { MarshalBytes = TargetName.Length + Context->DomainName.Length + Context->UserName.Length + Context->Password.Length + (4*sizeof(WCHAR)); } else { MarshalBytes = TargetName.Length + (DNLEN * sizeof(WCHAR)) + (UNLEN * sizeof(WCHAR)) + (PWLEN * sizeof(WCHAR)) + (4*sizeof(WCHAR)); } // // Set a "reasonable" upper limit on the token size // to avoid unbounded stack allocations. The limit is // NTLMSSP_MAX_MESSAGE_SIZE*4 for historical reasons. // if ((NTLMSSP_MAX_MESSAGE_SIZE*4)ServerName, (PSTRING)&TargetName, &MarshalPtr ); GetChallengeResponseSize += GetChallengeResponse->ServerName.Length; } if ( !UseSuppliedCreds ) { ULONG CredTypes[] = {CRED_TYPE_DOMAIN_PASSWORD}; BOOLEAN bIsOwfPassword = FALSE; SECPKG_CALL_INFO CallInfo = {0}; CredTargetInfo.NetbiosDomainName = szCredTargetDomain; CredTargetInfo.NetbiosServerName = szCredTargetServer; CredTargetInfo.DnsDomainName = szCredTargetDnsDomain; CredTargetInfo.DnsServerName = szCredTargetDnsServer; CredTargetInfo.DnsTreeName = szCredTargetDnsTree; CredTargetInfo.PackageName = NTLMSP_NAME; CredTargetInfo.CredTypeCount = COUNTOF(CredTypes); CredTargetInfo.CredTypes = CredTypes; // // if marshalled TargetInfo was supplied, we prefer those fields. // if ( Context->TargetInfo ) { CredTargetInfo.TargetName = Context->TargetInfo->TargetName; CredTargetInfo.NetbiosServerName = Context->TargetInfo->NetbiosServerName; CredTargetInfo.DnsServerName = Context->TargetInfo->DnsServerName; CredTargetInfo.NetbiosDomainName = Context->TargetInfo->NetbiosDomainName; CredTargetInfo.DnsDomainName = Context->TargetInfo->DnsDomainName; CredTargetInfo.DnsTreeName = Context->TargetInfo->DnsTreeName; CredTargetInfo.Flags |= Context->TargetInfo->Flags; } SecStatus = CopyCredManCredentials ( ClientLogonId, &CredTargetInfo, Context, fShareLevel, TRUE, // allow OWF passwords &bIsOwfPassword ); if ( NT_SUCCESS(SecStatus) ) { fCredmanCredentials = TRUE; if (bIsOwfPassword) { GetChallengeResponse->ParameterControl |= GCR_USE_OWF_PASSWORD; } } if ( SecStatus == STATUS_NOT_FOUND ) { SecStatus = STATUS_SUCCESS; } if ( !NT_SUCCESS(SecStatus) ) { goto Cleanup; } // // stow away a copy of the marshalled target info. // if (!LsaFunctions->GetCallInfo(&CallInfo)) { SecStatus = STATUS_INTERNAL_ERROR; goto Cleanup; } if ( fCredmanCredentials || (CallInfo.Attributes & SECPKG_CALL_KERNEL_MODE) ) { CredMarshalTargetInfo( &CredTargetInfo, (PUSHORT*)&(Context->pbMarshalledTargetInfo), &Context->cbMarshalledTargetInfo ); } } else { // // if an explicit cred was supplied, check if it was a marshalled // cred, and if so, reject smartcard attempts. // if ( Context->UserName.Length != 0 && Context->DomainName.Length == 0 // Password may or may not contain a PIN for cert case. ) { UNICODE_STRING UnpickledUserName; UNICODE_STRING UnpickledDomainName; UNICODE_STRING UnpickledPassword; Status = CredpProcessUserNameCredential( &Context->UserName, &UnpickledUserName, &UnpickledDomainName, &UnpickledPassword ); if ( NT_SUCCESS(Status) ) { NtLmFreePrivateHeap( UnpickledUserName.Buffer ); NtLmFreePrivateHeap( UnpickledDomainName.Buffer ); NtLmFreePrivateHeap( UnpickledPassword.Buffer ); } else if ( Status == STATUS_NOT_SUPPORTED ) { goto Cleanup; } Status = STATUS_SUCCESS; } } SspContextCopyStringAbsolute( GetChallengeResponse, (PSTRING)&GetChallengeResponse->LogonDomainName, (PSTRING)&Context->DomainName, &MarshalPtr ); GetChallengeResponseSize += GetChallengeResponse->LogonDomainName.Length; SspContextCopyStringAbsolute( GetChallengeResponse, (PSTRING)&GetChallengeResponse->UserName, (PSTRING)&Context->UserName, &MarshalPtr ); GetChallengeResponseSize += GetChallengeResponse->UserName.Length; // // Check for null session. This is the case if the caller supplies // an empty username, domainname, and password. // // // duplicate the hidden password string, then reveal it into // new buffer. This avoids thread race conditions during hide/reveal // and also allows us to avoid re-hiding the material. // TODO: add flag that indicates to LSA that provided password is encrypted. // SecStatus = NtLmDuplicatePassword( &RevealedPassword, &Context->Password ); if (!NT_SUCCESS( SecStatus ) ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmDuplicatePassword returned %d\n",Status)); goto Cleanup; } SspRevealPassword(&RevealedPassword); if (((Context->ContextFlags & ISC_RET_NULL_SESSION) != 0) || (((Context->DomainName.Length == 0) && (Context->DomainName.Buffer != NULL)) && ((Context->UserName.Length == 0) && (Context->UserName.Buffer != NULL)) && ((Context->Password.Length == 0) && (Context->Password.Buffer != NULL)))) { SspPrint(( SSP_WARNING, "SsprHandleChallengeMessage: null session context\n" )); GetChallengeResponse->ParameterControl |= NULL_SESSION_REQUESTED | USE_PRIMARY_PASSWORD; } else { // // We aren't doing a null session intentionally. MSV may choose // to do a null session if we have no credentials available. // if ( Context->DomainName.Buffer == NULL ) { BOOLEAN FoundAt = FALSE; // // if it's a UPN, don't fill in the domain field. // if ( Context->UserName.Buffer != NULL ) { ULONG i; for (i = 0 ; i < (Context->UserName.Length / sizeof(WCHAR)) ; i++) { if ( Context->UserName.Buffer[i] == '@' ) { FoundAt = TRUE; break; } } } if ( !FoundAt ) { GetChallengeResponse->ParameterControl |= RETURN_PRIMARY_LOGON_DOMAINNAME; } } if ( Context->UserName.Buffer == NULL ) { GetChallengeResponse->ParameterControl |= RETURN_PRIMARY_USERNAME; } // // The password may be a zero length password // GetChallengeResponse->Password = RevealedPassword; if ( Context->Password.Buffer == NULL ) { GetChallengeResponse->ParameterControl |= USE_PRIMARY_PASSWORD; } else { // // MSV needs the password to be 'in' the passed in buffer. // RtlCopyMemory(MarshalPtr, GetChallengeResponse->Password.Buffer, GetChallengeResponse->Password.Length); GetChallengeResponse->Password.Buffer = (LPWSTR)(MarshalPtr); GetChallengeResponseSize += GetChallengeResponse->Password.Length + sizeof(WCHAR); } } // // scrub the cleartext password now to avoid pagefile exposure // during lengthy processing. // if ( RevealedPassword.Buffer != NULL ) { ZeroMemory( RevealedPassword.Buffer, RevealedPassword.Length ); NtLmFreePrivateHeap( RevealedPassword.Buffer ); RevealedPassword.Buffer = NULL; } GetChallengeResponse->LogonId = *ClientLogonId; RtlCopyMemory( &GetChallengeResponse->ChallengeToClient, ChallengeMessage->Challenge, MSV1_0_CHALLENGE_LENGTH ); // // if NTLM2 negotiated, then ask MSV1_0 to mix my challenge with the server's... // if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) { GetChallengeResponse->ParameterControl |= GENERATE_CLIENT_CHALLENGE; } else { // // if it's share level, and: // 1. User only supplied a password, or // 2. Credman returned the creds // allow downgrade to NTLMv1 from NTLMv2. // if ( fShareLevel ) { if ( (GetChallengeResponse->UserName.Length == 0) && (GetChallengeResponse->LogonDomainName.Length == 0) && (GetChallengeResponse->Password.Buffer != NULL) ) { GetChallengeResponse->ParameterControl |= GCR_ALLOW_NTLM; } if ( fCredmanCredentials ) { GetChallengeResponse->ParameterControl |= GCR_ALLOW_NTLM; } } // // DataGram can not negotiate if it is non NTLM2, allow to use LM key // if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) { // // handle smart servers that know to use NtUserSessionKey // if (Context->ServerVersion.Revision >= NTLMSSP_REVISION_W2K3 && NtLmGlobalLmProtocolSupported >= NoLm) { SspPrint((SSP_WARNING, "SsprHandleChallengeMessage turning off LM keys\n")); GetChallengeResponse->ParameterControl &= ~RETURN_NON_NT_USER_SESSION_KEY; Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_LM_KEY; // tell the server we turn off LM key } else // let it through, this might fail if NoLmHash is stored in SAM { GetChallengeResponse->ParameterControl |= GCR_ALLOW_LM; } } } if (!DoUnicode) { GetChallengeResponse->ParameterControl |= GCR_USE_OEM_SET; } // // Get the DomainName, UserName, and ChallengeResponse from the MSV // Status = LsaApCallPackage( (PLSA_CLIENT_REQUEST)(-1), GetChallengeResponse, GetChallengeResponse, GetChallengeResponseSize, (PVOID *)&ChallengeResponseMessage, &ChallengeResponseSize, &ProtocolStatus ); if ( !NT_SUCCESS(Status) ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: " "ChallengeMessage LsaCall to get ChallengeResponse returns 0x%lx\n", Status )); SecStatus = SspNtStatusToSecStatus( Status, SEC_E_NO_CREDENTIALS); goto Cleanup; } if ( !NT_SUCCESS(ProtocolStatus) ) { Status = ProtocolStatus; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: ChallengeMessage LsaCall " "to get ChallengeResponse returns ProtocolStatus 0x%lx\n", Status )); SecStatus = SspNtStatusToSecStatus( Status, SEC_E_NO_CREDENTIALS); goto Cleanup; } // // Check to see if we are doing a null session // if ((ChallengeResponseMessage->CaseSensitiveChallengeResponse.Length == 0) && (ChallengeResponseMessage->CaseInsensitiveChallengeResponse.Length == 1)) { SspPrint(( SSP_WARNING, "SsprHandleChallengeMessage: null session\n" )); *ContextAttributes |= ISC_RET_NULL_SESSION; Context->ContextFlags |= ISC_RET_NULL_SESSION; Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_NULL_SESSION; } else { // // Normalize things by copying the default domain name and user name // into the ChallengeResponseMessage structure. // if ( Context->DomainName.Buffer != NULL ) { ChallengeResponseMessage->LogonDomainName = Context->DomainName; } if ( Context->UserName.Buffer != NULL ) { ChallengeResponseMessage->UserName = Context->UserName; } } // // Convert the domainname/user name to the right character set. // if ( DoUnicode ) { DomainName = *(PSTRING)&ChallengeResponseMessage->LogonDomainName; UserName = *(PSTRING)&ChallengeResponseMessage->UserName; Workstation = *(PSTRING)&NtLmGlobalUnicodeComputerNameString; } else { Status = RtlUpcaseUnicodeStringToOemString( &DomainName, &ChallengeResponseMessage->LogonDomainName, TRUE); if ( !NT_SUCCESS(Status) ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: RtlUpcaseUnicodeToOemString (DomainName) returned 0x%lx.\n", Status)); SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY); goto Cleanup; } Status = RtlUpcaseUnicodeStringToOemString( &UserName, &ChallengeResponseMessage->UserName, TRUE); if ( !NT_SUCCESS(Status) ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: RtlUpcaseUnicodeToOemString (UserName) returned 0x%lx.\n", Status)); SecStatus = SspNtStatusToSecStatus( Status, SEC_E_INSUFFICIENT_MEMORY); goto Cleanup; } Workstation = NtLmGlobalOemComputerNameString; } // // Save the ChallengeResponses // LmChallengeResponse = ChallengeResponseMessage->CaseInsensitiveChallengeResponse; NtChallengeResponse = ChallengeResponseMessage->CaseSensitiveChallengeResponse; // // prepare to send encrypted randomly generated session key // DatagramSessionKey.Buffer = (CHAR*)DatagramKey; DatagramSessionKey.Length = DatagramSessionKey.MaximumLength = 0; // // Generate the session key, or encrypt the previosly generated random one, // from various bits of info. Fill in session key if needed. // SecStatus = SsprMakeSessionKey( Context, &LmChallengeResponse, ChallengeResponseMessage->UserSessionKey, ChallengeResponseMessage->LanmanSessionKey, &DatagramSessionKey ); if (SecStatus != SEC_E_OK) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: SsprMakeSessionKey\n")); goto Cleanup; } } // // If the caller specified SEQUENCE_DETECT or REPLAY_DETECT, // that means they want to use the MakeSignature/VerifySignature // calls. Add this to the returned attributes and the context // negotiate flags. // if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) || (ContextReqFlags & ISC_REQ_REPLAY_DETECT)) { Context->ContextFlags |= ISC_RET_REPLAY_DETECT; *ContextAttributes |= ISC_RET_REPLAY_DETECT; Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN; } if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) || (ContextReqFlags & ISC_REQ_SEQUENCE_DETECT)) { Context->ContextFlags |= ISC_RET_SEQUENCE_DETECT; *ContextAttributes |= ISC_RET_SEQUENCE_DETECT; Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN; } if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) || (ContextReqFlags & ISC_REQ_INTEGRITY)) { Context->ContextFlags |= ISC_RET_INTEGRITY; *ContextAttributes |= ISC_RET_INTEGRITY; Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN; } if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) || (ContextReqFlags & ISC_REQ_CONFIDENTIALITY)) { if (NtLmGlobalEncryptionEnabled) { Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL; Context->ContextFlags |= ISC_REQ_CONFIDENTIALITY; *ContextAttributes |= ISC_REQ_CONFIDENTIALITY; } else { SecStatus = STATUS_NOT_SUPPORTED; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: NtLmGlobalEncryption not enabled.\n")); goto Cleanup; } } if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) == NTLMSSP_NEGOTIATE_DATAGRAM ) { *ContextAttributes |= ISC_RET_DATAGRAM; Context->ContextFlags |= ISC_RET_DATAGRAM; Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_DATAGRAM; } // // Slip in the hacky mutual auth override here: // if ( ((ContextReqFlags & ISC_REQ_MUTUAL_AUTH) != 0 ) && (NtLmGlobalMutualAuthLevel < 2 ) ) { *ContextAttributes |= ISC_RET_MUTUAL_AUTH ; if ( NtLmGlobalMutualAuthLevel == 0 ) { Context->ContextFlags |= ISC_RET_MUTUAL_AUTH ; } } if ( (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL) && (ContextReqFlags & ISC_REQ_DELEGATE) ) { // // for loopback, we can indeed support another hop. // *ContextAttributes |= ISC_RET_DELEGATE; Context->ContextFlags |= ISC_RET_DELEGATE; } // // Allocate an authenticate message // AuthenticateMessageSize = sizeof(*AuthenticateMessage) + LmChallengeResponse.Length + NtChallengeResponse.Length + DomainName.Length + UserName.Length + Workstation.Length + DatagramSessionKey.Length; if ((ContextReqFlags & ISC_REQ_ALLOCATE_MEMORY) == 0) { if ( AuthenticateMessageSize > *OutputTokenSize ) { SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: OutputTokenSize is 0x%lx.\n", *OutputTokenSize)); SecStatus = SEC_E_BUFFER_TOO_SMALL; goto Cleanup; } } AuthenticateMessage = (PAUTHENTICATE_MESSAGE) NtLmAllocateLsaHeap(AuthenticateMessageSize ); if ( AuthenticateMessage == NULL ) { SecStatus = STATUS_NO_MEMORY; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: Error allocating AuthenticateMessage.\n" )); goto Cleanup; } // // Build the authenticate message // strcpy( (char *)AuthenticateMessage->Signature, NTLMSSP_SIGNATURE ); AuthenticateMessage->MessageType = NtLmAuthenticate; AuthenticateMessage->Version = NTLMSSP_ENGINE_VERSION; Where = (PCHAR)(AuthenticateMessage+1); // // Copy the strings needing 2 byte alignment. // SspContextCopyString( AuthenticateMessage, &AuthenticateMessage->DomainName, &DomainName, &Where ); SspContextCopyString( AuthenticateMessage, &AuthenticateMessage->UserName, &UserName, &Where ); SspContextCopyString( AuthenticateMessage, &AuthenticateMessage->Workstation, &Workstation, &Where ); // // Copy the strings not needing special alignment. // SspContextCopyString( AuthenticateMessage, &AuthenticateMessage->LmChallengeResponse, &LmChallengeResponse, &Where ); SspContextCopyString( AuthenticateMessage, &AuthenticateMessage->NtChallengeResponse, &NtChallengeResponse, &Where ); SspContextCopyString( AuthenticateMessage, &AuthenticateMessage->SessionKey, &DatagramSessionKey, &Where ); AuthenticateMessage->NegotiateFlags = Context->NegotiateFlags; ASSERT((AuthenticateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) && L"NTLMSSP_NEGOTIATE_VERSION should be set"); SspPrint(( SSP_NEGOTIATE_FLAGS, "SsprHandleChallengeMessage: ChallengeFlags: %lx AuthenticateFlags: %lx\n", ChallengeMessage->NegotiateFlags, AuthenticateMessage->NegotiateFlags )); // // Copy the AuthenticateMessage to the caller's address space. // if ((ContextReqFlags & ISC_REQ_ALLOCATE_MEMORY) == 0) { RtlCopyMemory( *OutputToken, AuthenticateMessage, AuthenticateMessageSize ); } else { *OutputToken = AuthenticateMessage; AuthenticateMessage = NULL; *ContextAttributes |= ISC_RET_ALLOCATED_MEMORY; } *OutputTokenSize = AuthenticateMessageSize; // we need to send a second token back for the rdr if (fCallFromRedir) { NtLmInitializeResponse = (PNTLM_INITIALIZE_RESPONSE) NtLmAllocateLsaHeap(sizeof(NTLM_INITIALIZE_RESPONSE)); if ( NtLmInitializeResponse == NULL ) { SecStatus = STATUS_NO_MEMORY; SspPrint(( SSP_CRITICAL, "SsprHandleChallengeMessage: Error allocating NtLmInitializeResponse.\n" )); goto Cleanup; } RtlCopyMemory( NtLmInitializeResponse->UserSessionKey, ChallengeResponseMessage->UserSessionKey, MSV1_0_USER_SESSION_KEY_LENGTH ); RtlCopyMemory( NtLmInitializeResponse->LanmanSessionKey, ChallengeResponseMessage->LanmanSessionKey, MSV1_0_LANMAN_SESSION_KEY_LENGTH ); *SecondOutputToken = NtLmInitializeResponse; NtLmInitializeResponse = NULL; *SecondOutputTokenSize = sizeof(NTLM_INITIALIZE_RESPONSE); } // SspPrint((SSP_API_MORE, "Client session key = %p\n", Context->SessionKey)); // // Return output parameters to the caller. // *ExpirationTime = SspContextGetTimeStamp( Context, TRUE ); SecStatus = STATUS_SUCCESS; // // Generate the explicit cred used audit for non null sessions // // LocalLoopBack uses implicit credentials only // if (NtChallengeResponse.Length) // both local loopback and null session have NtChallengeResponse zero length { AuditStatus = SppGenerateExplicitCredAudit( Context, ChallengeMessage, InputTokenSize, DoUnicode ); } // // Free and locally used resources. // Cleanup: if ( RevealedPassword.Buffer != NULL ) { ZeroMemory( RevealedPassword.Buffer, RevealedPassword.Length ); NtLmFreePrivateHeap( RevealedPassword.Buffer ); } if ( ServerContext != NULL ) { SspContextDereferenceContext( ServerContext ); } if ( Context != NULL ) { Context->LastStatus = SecStatus; Context->DownLevel = fCallFromRedir; // // Don't allow this context to be used again. // if ( NT_SUCCESS(SecStatus) ) { Context->State = AuthenticateSentState; } else if ( SecStatus == SEC_I_CALL_NTLMSSP_SERVICE ) { Context->State = PassedToServiceState; } else { Context->State = IdleState; } RtlCopyMemory( SessionKey, Context->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH ); *NegotiateFlags = Context->NegotiateFlags; // If we just created the context (because rdr may be talking to // a pre NT 5.0 server,, we need to dereference it again. if (fCallFromRedir && !NT_SUCCESS(SecStatus)) { PSSP_CONTEXT LocalContext; SspContextReferenceContext( *ContextHandle, TRUE, &LocalContext ); ASSERT(LocalContext != NULL); if (LocalContext != NULL) { SspContextDereferenceContext( LocalContext ); } } SspContextDereferenceContext( Context ); } if (szCredTargetPreDFSServer != NULL) { NtLmFreePrivateHeap( szCredTargetPreDFSServer ); } if (szCredTargetDomain != NULL) { NtLmFreePrivateHeap( szCredTargetDomain ); } if (szCredTargetServer != NULL) { NtLmFreePrivateHeap( szCredTargetServer ); } if (szCredTargetDnsDomain != NULL) { NtLmFreePrivateHeap( szCredTargetDnsDomain ); } if (szCredTargetDnsServer != NULL) { NtLmFreePrivateHeap( szCredTargetDnsServer ); } if (szCredTargetDnsTree != NULL) { NtLmFreePrivateHeap( szCredTargetDnsTree ); } if ( ChallengeMessage != NULL ) { (VOID) NtLmFreePrivateHeap( ChallengeMessage ); } if ( AuthenticateMessage != NULL ) { (VOID) NtLmFreeLsaHeap( AuthenticateMessage ); } if ( ChallengeResponseMessage != NULL ) { (VOID) LsaFunctions->FreeLsaHeap( ChallengeResponseMessage ); } if ( !DoUnicode ) { RtlFreeUnicodeString( &TargetName ); if ( DomainName.Buffer != NULL) { RtlFreeOemString( &DomainName ); } if ( UserName.Buffer != NULL) { RtlFreeOemString( &UserName ); } } RtlFreeUnicodeString( &DefectiveTargetName ); if ( ClientTokenHandle ) { CloseHandle( ClientTokenHandle ); } if (GetChallengeResponse && (GetChallengeResponse!=&TempChallengeResponse)){ SafeAllocaFree(GetChallengeResponse); } SspPrint(( SSP_API_MORE, "Leaving SsprHandleChallengeMessage: 0x%lx\n", SecStatus )); return SecStatus; } NTSTATUS CredpParseUserName( IN OUT LPWSTR ParseName, OUT LPWSTR* pUserName, OUT LPWSTR* pDomainName ) /*++ Routine Description: This routine separates a passed in user name into domain and username. A user name must have one of the following two syntaxes: \ @ The name is considered to have the first syntax if the string contains an \. A string containing a @ is ambiguous since may contain an @. For the second syntax, the last @ in the string is used since may contain an @ but cannot. Arguments: ParseName - Name of user to validate - will be modified pUserName - Returned pointing to canonical name inside of ParseName pDomainName - Returned pointing to domain name inside of ParseName Return Values: The following status codes may be returned: STATUS_INVALID_ACCOUNT_NAME - The user name is not valid. --*/ { LPWSTR SlashPointer; *pUserName = NULL; *pDomainName = NULL; // // NULL is invalid // if ( ParseName == NULL ) { return STATUS_INVALID_ACCOUNT_NAME; } // // Classify the input account name. // // The name is considered to be \ if the string // contains an \. // SlashPointer = wcsrchr( ParseName, L'\\' ); if ( SlashPointer != NULL ) { // // point the output strings // *pDomainName = ParseName; // // Skip the backslash // *SlashPointer = L'\0'; SlashPointer ++; *pUserName = SlashPointer; } else { // // it's a UPN. // leave it intact in the UserName field. // set the DomainName to empty string, so the rest of the logon code // avoids filling in the default. // *pUserName = ParseName; *pDomainName = L""; } return STATUS_SUCCESS; } NTSTATUS CopyCredManCredentials( IN PLUID LogonId, IN CREDENTIAL_TARGET_INFORMATIONW* pTargetInfo, IN OUT PSSP_CONTEXT Context, IN BOOLEAN fShareLevel, IN BOOLEAN bAllowOwfPassword, OUT BOOLEAN* pbIsOwfPassword ) /*++ Routine Description: Look for a keyring credential entry for the specified domain, and copy to Context handle if found Arguments: LogonId -- LogonId of the calling process. pTargetInfo -- Information on target to search for creds. Context - Points to the ContextHandle of the Context to be referenced. fShareLevel - whether it is for sharelevel bAllowOwfPassword - whether to look for OWF password, this prevents username/domainname in Context from being trashed pbIsOwfPassword - whether the cred found consists of OWFs Return Value: STATUS_SUCCESS -- All OK STATUS_NOT_FOUND - Credential couldn't be found. All others are real failures and should be returned to the caller. --*/ { NTSTATUS Status; PENCRYPTED_CREDENTIALW *EncryptedCredentials = NULL; PCREDENTIALW *Credentials = NULL; PCREDENTIALW pPasswordForCertCred = NULL; ULONG CredentialCount; WCHAR* UserName = NULL; WCHAR* DomainName = NULL; UNICODE_STRING TempString = {0}; WCHAR szTempBuffer[UNLEN + UNLEN + 4]; // to hold domain\username or username@dnsdomainname ENCRYPTED_CREDENTIALW* pCertCred = NULL; KERB_QUERY_SUPPLEMENTAL_CREDS_RESPONSE* pQuerySuppCredResp = NULL; if (!Context) // validate context only after call to Lookup { return STATUS_NOT_FOUND; } Status = LsaFunctions->CrediReadDomainCredentials( LogonId, CREDP_FLAGS_IN_PROCESS, // Allow password to be returned pTargetInfo, 0, // no flags &CredentialCount, &EncryptedCredentials ); Credentials = (PCREDENTIALW *)EncryptedCredentials; if (!NT_SUCCESS(Status)) { // // Ideally, only STATUS_NO_SUCH_LOGON_SESSION should be converted to // STATUS_NOT_FOUND. However, swallowing all failures and asserting // these specific two works around a bug in CrediReadDomainCredentials // which returns invalid parameter if the target is a user account name. // Eventually, CrediReadDomainCredentials should return a more // appropriate error in this case. // SspPrint((SSP_WARNING, "CopyCredManCredentials: CrediReadDomainCredentials returned %x, LogonId %#x:%#x\n", Status, LogonId->HighPart, LogonId->LowPart)); ASSERT( (Status == STATUS_NO_SUCH_LOGON_SESSION)|| (Status == STATUS_INVALID_PARAMETER)|| (Status == STATUS_NOT_FOUND) ); return STATUS_NOT_FOUND; } // // NULL terminate scatch buffer // szTempBuffer[COUNTOF(szTempBuffer) - 1] = L'\0'; // // Loop through the list of credentials // for ( ULONG CredIndex = 0; CredIndex < CredentialCount; CredIndex++ ) { // // NTLM only supports password credentials // skip OWF passwords if not allowed // if ( (Credentials[CredIndex]->Type != CRED_TYPE_DOMAIN_PASSWORD) || (!bAllowOwfPassword && (Credentials[CredIndex]->Flags & CRED_FLAGS_OWF_CRED_BLOB)) ) { continue; } // // For Share level connects, don't allow matching against * creds. // // CRED_FLAGS_PASSWORD_FOR_CERT cred is *Session cred // if ( fShareLevel ) { if ( (Credentials[CredIndex]->TargetName) && (wcschr( Credentials[CredIndex]->TargetName, L'*' ) != NULL) ) { continue; } } if ((Credentials[CredIndex]->Flags & CRED_FLAGS_PASSWORD_FOR_CERT)) { pPasswordForCertCred = Credentials[CredIndex]; continue; } if ( Credentials[CredIndex]->Flags & CRED_FLAGS_PROMPT_NOW ) { Status = SEC_E_LOGON_DENIED; goto Cleanup; } // // Sanity check the credential // if ( Credentials[CredIndex]->UserName == NULL ) { Status = STATUS_NOT_FOUND; goto Cleanup; } // // Convert the UserName to domain name and user name // RtlCopyMemory( szTempBuffer, Credentials[CredIndex]->UserName, sizeof(WCHAR) * min(wcslen(Credentials[CredIndex]->UserName) + 1, COUNTOF(szTempBuffer) - 1) ); Status = CredpParseUserName( szTempBuffer, &UserName, &DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Free the existing domain name and add the new one // if (Context->DomainName.Buffer) { NtLmFreePrivateHeap (Context->DomainName.Buffer); Context->DomainName.Buffer = NULL; Context->DomainName.Length = 0; } if ( DomainName ) { RtlInitUnicodeString( &TempString, DomainName ); Status = NtLmDuplicateUnicodeString(&Context->DomainName, &TempString); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } } // // Free the existing user name and add the new one // RtlInitUnicodeString( &TempString, UserName ); if (Context->UserName.Buffer) { NtLmFreePrivateHeap (Context->UserName.Buffer); Context->UserName.Buffer = NULL; } Status = NtLmDuplicateUnicodeString(&Context->UserName, &TempString); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } SspPrint((SSP_CRED, "CopyCredManCredentials copying credmain passwords\n")); TempString.Buffer = (PWSTR) Credentials[CredIndex]->CredentialBlob; TempString.MaximumLength = (USHORT) Credentials[CredIndex]->CredentialBlobSize; TempString.Length = (USHORT) EncryptedCredentials[CredIndex]->ClearCredentialBlobSize; // // zero length password must be treated as blank or NTLM will assume it // should use the password of the currently logged in user. // if ( TempString.Length == 0 ) { TempString.Buffer = L""; } // // Free the existing password and add the new one // if (Context->Password.Buffer) { NtLmFreePrivateHeap (Context->Password.Buffer); Context->Password.Buffer = NULL; } Status = NtLmDuplicatePassword(&Context->Password, &TempString); if (!NT_SUCCESS(Status)) { goto Cleanup; } *pbIsOwfPassword = (BOOLEAN) (Credentials[CredIndex]->Flags & CRED_FLAGS_OWF_CRED_BLOB); goto Cleanup; } // // now call kerberos to get password for cert cred // if (pPasswordForCertCred) { KERB_QUERY_SUPPLEMENTAL_CREDS_REQUEST QuerySuppCred; UNICODE_STRING KerberosPackageName = CONSTANT_UNICODE_STRING((MICROSOFT_KERBEROS_NAME_W)); UNICODE_STRING MsvPackageName = CONSTANT_UNICODE_STRING((NTLMSP_NAME)); ULONG cbResponse = 0; NTSTATUS SubStatus = STATUS_UNSUCCESSFUL; ENCRYPTED_CREDENTIALW* pPasswordCred = NULL; SspPrint((SSP_CRED, "CopyCredManCredentials querying passwords for cert %ws\n", pPasswordForCertCred->TargetName)); Status = LsaFunctions->CrediRead( LogonId, CREDP_FLAGS_IN_PROCESS, // CredFlags pPasswordForCertCred->TargetName, CRED_TYPE_DOMAIN_CERTIFICATE, // CredType 0, // no Flags &pCertCred ); if (!NT_SUCCESS(Status)) { goto Cleanup; } RtlZeroMemory(&QuerySuppCred, sizeof(QuerySuppCred)); QuerySuppCred.MessageType = KerbQuerySupplementalCredentialsMessage; QuerySuppCred.PackageName = MsvPackageName; QuerySuppCred.MarshalledCreds = (CREDENTIALW *) pCertCred; QuerySuppCred.LogonId = *LogonId; Status = LsaFunctions->CallPackage( &KerberosPackageName, &QuerySuppCred, sizeof(QuerySuppCred), reinterpret_cast(&pQuerySuppCredResp), &cbResponse, &SubStatus ); if (!NT_SUCCESS(Status)) { SspPrint((SSP_WARNING, "CopyCredManCredentials CallPackage failed with Status %#x\n", Status)); Status = STATUS_NOT_FOUND; goto Cleanup; } if (!NT_SUCCESS(SubStatus)) { SspPrint((SSP_WARNING, "CopyCredManCredentials CallPackage failed with SubStatus %#x\n", SubStatus)); Status = STATUS_NOT_FOUND; goto Cleanup; } ASSERT( pQuerySuppCredResp ); pPasswordCred = &pQuerySuppCredResp->ReturnedCreds; // // NTLM only supports password credentials and Sanity check the // credential // if ( (pPasswordCred->Cred.Type != CRED_TYPE_DOMAIN_PASSWORD) || (pPasswordCred->Cred.Flags & CRED_FLAGS_PASSWORD_FOR_CERT) || (!bAllowOwfPassword && (pPasswordCred->Cred.Flags & CRED_FLAGS_OWF_CRED_BLOB)) ) { Status = STATUS_NOT_FOUND; goto Cleanup; } if (pPasswordCred->Cred.Flags & CRED_FLAGS_PROMPT_NOW) { Status = SEC_E_LOGON_DENIED; goto Cleanup; } if (pPasswordCred->Cred.UserName == NULL) { Status = STATUS_NOT_FOUND; goto Cleanup; } SspPrint((SSP_CRED, "CopyCredManCredentials using domain\\user %ws\n", pPasswordCred->Cred.UserName)); // // Convert the UserName to domain name and user name // RtlCopyMemory( szTempBuffer, pPasswordCred->Cred.UserName, sizeof(WCHAR) * min(wcslen(pPasswordCred->Cred.UserName) + 1, COUNTOF(szTempBuffer) - 1) ); Status = CredpParseUserName( szTempBuffer, &UserName, &DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Free the existing domain name and add the new one // if (Context->DomainName.Buffer) { NtLmFreePrivateHeap(Context->DomainName.Buffer); Context->DomainName.Buffer = NULL; Context->DomainName.Length = 0; } if ( DomainName ) { RtlInitUnicodeString( &TempString, DomainName ); } Status = NtLmDuplicateUnicodeString(&Context->DomainName, &TempString); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Free the existing user name and add the new one // RtlInitUnicodeString( &TempString, UserName ); if (Context->UserName.Buffer) { NtLmFreePrivateHeap(Context->UserName.Buffer); Context->UserName.Buffer = NULL; } Status = NtLmDuplicateUnicodeString(&Context->UserName, &TempString); if (!NT_SUCCESS(Status)) { goto Cleanup; } TempString.Buffer = (PWSTR) pPasswordCred->Cred.CredentialBlob; TempString.MaximumLength = (USHORT) pPasswordCred->Cred.CredentialBlobSize; TempString.Length = (USHORT) pPasswordCred->ClearCredentialBlobSize; // // Free the existing password and add the new one // if (Context->Password.Buffer) { NtLmFreePrivateHeap(Context->Password.Buffer); Context->Password.Buffer = NULL; } Status = NtLmDuplicatePassword(&Context->Password, &TempString); if (!NT_SUCCESS(Status)) { goto Cleanup; } *pbIsOwfPassword = (BOOLEAN) (pPasswordCred->Cred.Flags & CRED_FLAGS_OWF_CRED_BLOB); // // now this cred is good to go, write it into credman before we return // // // we consider failures of CrediWrite non-fatal, ignore the return code // (VOID) LsaFunctions->CrediWrite( LogonId, CREDP_FLAGS_IN_PROCESS, // CredFlags pPasswordCred, 0 // no Flags ); goto Cleanup; } SspPrint((SSP_CRED, "CopyCredManCredentials no credman credential found\n")); Status = STATUS_NOT_FOUND; Cleanup: // // Free the returned credentials // if (pCertCred) { LsaFunctions->FreeLsaHeap(pCertCred); } if (EncryptedCredentials) { LsaFunctions->CrediFreeCredentials( CredentialCount, EncryptedCredentials ); } if (pQuerySuppCredResp) { LsaFunctions->FreeLsaHeap(pQuerySuppCredResp); } #if DBG if (!NT_SUCCESS(Status)) { SspPrint((SSP_WARNING, "CopyCredManCredentials failed to get credman password Status %#x\n", Status)); } #endif // DBG return Status; } NTSTATUS CredpExtractMarshalledTargetInfo( IN PUNICODE_STRING TargetServerName, OUT CREDENTIAL_TARGET_INFORMATIONW **pTargetInfo ) { CREDENTIAL_TARGET_INFORMATIONW *NewTargetInfo; NTSTATUS Status; // // LSA will set Length to include only the non-marshalled portion, // with MaximumLength trailing data to include marshalled portion. // if ( (TargetServerName == NULL) || (TargetServerName->Buffer == NULL) || (TargetServerName->Length >= TargetServerName->MaximumLength) || ((TargetServerName->MaximumLength - TargetServerName->Length) < CRED_MARSHALED_TI_SIZE_SIZE ) ) { return STATUS_SUCCESS; } // // Unmarshal the target info // Status = CredUnmarshalTargetInfo ( TargetServerName->Buffer, TargetServerName->MaximumLength, &NewTargetInfo, NULL ); if ( !NT_SUCCESS(Status) ) { if ( Status == STATUS_INVALID_PARAMETER ) { Status = STATUS_SUCCESS; } } else { if ( *pTargetInfo != NULL ) { LocalFree( *pTargetInfo ); } *pTargetInfo = NewTargetInfo; } return Status ; } NTSTATUS CredpProcessUserNameCredential( IN PUNICODE_STRING MarshalledUserName, OUT PUNICODE_STRING UserName, OUT PUNICODE_STRING DomainName, OUT PUNICODE_STRING Password ) { WCHAR FastUserName[ UNLEN+1 ]; LPWSTR SlowUserName = NULL; LPWSTR TempUserName; CRED_MARSHAL_TYPE CredMarshalType; PUSERNAME_TARGET_CREDENTIAL_INFO pCredentialUserName = NULL; CREDENTIAL_TARGET_INFORMATIONW TargetInfo; BOOLEAN bIsOwfPassword = FALSE; ULONG CredTypes; SECPKG_CLIENT_INFO ClientInfo; SSP_CONTEXT SspContext; NTSTATUS Status = STATUS_NOT_FOUND; ZeroMemory( &SspContext, sizeof(SspContext) ); if ( (MarshalledUserName->Length+sizeof(WCHAR)) <= sizeof(FastUserName) ) { TempUserName = FastUserName; } else { SlowUserName = (LPWSTR)NtLmAllocatePrivateHeap( MarshalledUserName->Length + sizeof(WCHAR) ); if ( SlowUserName == NULL ) { return STATUS_INSUFFICIENT_RESOURCES; } TempUserName = SlowUserName; } // // copy the input to a NULL terminated string, then attempt to unmarshal it. // RtlCopyMemory( TempUserName, MarshalledUserName->Buffer, MarshalledUserName->Length ); TempUserName[ MarshalledUserName->Length / sizeof(WCHAR) ] = L'\0'; if (!CredUnmarshalCredentialW( TempUserName, &CredMarshalType, (VOID**)&pCredentialUserName )) { goto Cleanup; } if ( (CredMarshalType != UsernameTargetCredential) ) { Status = STATUS_NOT_SUPPORTED; goto Cleanup; } // // now query credential manager for a match. // Status = LsaFunctions->GetClientInfo(&ClientInfo); if (!NT_SUCCESS(Status)) { goto Cleanup; } ZeroMemory( &TargetInfo, sizeof(TargetInfo) ); CredTypes = CRED_TYPE_DOMAIN_PASSWORD; TargetInfo.Flags = CRED_TI_USERNAME_TARGET; TargetInfo.TargetName = pCredentialUserName->UserName; TargetInfo.PackageName = NTLMSP_NAME; TargetInfo.CredTypeCount = 1; TargetInfo.CredTypes = &CredTypes; Status = CopyCredManCredentials( &ClientInfo.LogonId, &TargetInfo, &SspContext, FALSE, FALSE, // do not allow OWF passwords &bIsOwfPassword ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // do not support OWF password via UserNameCredentials // ASSERT(!bIsOwfPassword); *UserName = SspContext.UserName; *DomainName = SspContext.DomainName; *Password = SspContext.Password; SspRevealPassword( Password ); Status = STATUS_SUCCESS; Cleanup: if (!NT_SUCCESS(Status)) { NtLmFreePrivateHeap( SspContext.UserName.Buffer ); NtLmFreePrivateHeap( SspContext.DomainName.Buffer ); NtLmFreePrivateHeap( SspContext.Password.Buffer ); } if ( SlowUserName ) { NtLmFreePrivateHeap( SlowUserName ); } if ( pCredentialUserName != NULL ) { CredFree( pCredentialUserName ); } return Status; } #if 0 //+------------------------------------------------------------------------- // // Function: SpQueryLsaModeContextAttributes // // Synopsis: Querys attributes of the specified context // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- ULONG SavedUseValidated = 0xFF; WCHAR SavedCredentialName[1024] = L"SaveMe"; ULONG SavedCredentialType = 0x22; ULONG SavedCredTypes = CRED_TYPE_DOMAIN_PASSWORD; CREDENTIAL_TARGET_INFORMATIONW SavedTargetInfo = { L"ntdsdc9", L"NTDSDC9", L"NTDSDC9.ntdev.microsoft.com", L"NTDEV", L"ntdev.microsoft.com", L"ntdev.microsoft.com", L"NTLM", 0, 1, &SavedCredTypes }; NTSTATUS NTAPI SpQueryLsaModeContextAttributes( IN LSA_SEC_HANDLE ContextHandle, IN ULONG ContextAttribute, IN OUT PVOID Buffer ) { NTSTATUS Status = STATUS_SUCCESS; DWORD WinStatus; PSSP_CONTEXT Context = NULL; SecPkgContext_CredentialNameW CredentialNameInfo; ULONG CredentialNameSize; LPWSTR UserCredentialName; PCREDENTIAL_TARGET_INFORMATIONW TempTargetInfo = NULL; ULONG TempTargetInfoSize; PCREDENTIAL_TARGET_INFORMATIONW UserTargetInfo; SecPkgContext_TargetInformationW TargetInfo; SspPrint((SSP_API, "Entering SpQueryLsaModeContextAttributes for ctxt:0x%x, Attr:0x%x\n", ContextHandle, ContextAttribute)); // // Find the currently existing context. // Status = SspContextReferenceContext( ContextHandle, FALSE, &Context ); if ( !NT_SUCCESS(Status) ) { SspPrint(( SSP_CRITICAL, "SpQueryLsaModeContextAttributes: invalid context handle.\n" )); goto Cleanup; } // // Return the appropriate information // switch(ContextAttribute) { case SECPKG_ATTR_CREDENTIAL_NAME: CredentialNameSize = (wcslen(SavedCredentialName) + 1) * sizeof(WCHAR); Status = LsaFunctions->AllocateClientBuffer( NULL, CredentialNameSize, (PVOID *) &UserCredentialName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // Copy the name to the user's address space Status = LsaFunctions->CopyToClientBuffer( NULL, CredentialNameSize, UserCredentialName, SavedCredentialName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // Copy the struct itself to the user's address space CredentialNameInfo.CredentialType = SavedCredentialType; CredentialNameInfo.sCredentialName = UserCredentialName; Status = LsaFunctions->CopyToClientBuffer( NULL, sizeof(CredentialNameInfo), Buffer, &CredentialNameInfo ); if (!NT_SUCCESS(Status)) { goto Cleanup; } break; case SECPKG_ATTR_TARGET_INFORMATION: // // Marshall the target info into a single buffer. // WinStatus = CredpConvertTargetInfo ( DoWtoW, &SavedTargetInfo, &TempTargetInfo, &TempTargetInfoSize ); if ( WinStatus != NO_ERROR ) { Status = STATUS_NO_MEMORY; goto Cleanup; } // // Allocate a buffer the same size in the client's address space. // Status = LsaFunctions->AllocateClientBuffer( NULL, TempTargetInfoSize, (PVOID *) &UserTargetInfo ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Relocate all pointers to be user-buffer-specific // YUCK!! // TempTargetInfo->TargetName = (LPWSTR)( ((LPBYTE)(TempTargetInfo->TargetName)) - ((LPBYTE)(TempTargetInfo)) + ((LPBYTE)UserTargetInfo) ); TempTargetInfo->NetbiosServerName = (LPWSTR)( ((LPBYTE)(TempTargetInfo->NetbiosServerName)) - ((LPBYTE)(TempTargetInfo)) + ((LPBYTE)UserTargetInfo) ); TempTargetInfo->DnsServerName = (LPWSTR)( ((LPBYTE)(TempTargetInfo->DnsServerName)) - ((LPBYTE)(TempTargetInfo)) + ((LPBYTE)UserTargetInfo) ); TempTargetInfo->NetbiosDomainName = (LPWSTR)( ((LPBYTE)(TempTargetInfo->NetbiosDomainName)) - ((LPBYTE)(TempTargetInfo)) + ((LPBYTE)UserTargetInfo) ); TempTargetInfo->DnsDomainName = (LPWSTR)( ((LPBYTE)(TempTargetInfo->DnsDomainName)) - ((LPBYTE)(TempTargetInfo)) + ((LPBYTE)UserTargetInfo) ); TempTargetInfo->DnsTreeName = (LPWSTR)( ((LPBYTE)(TempTargetInfo->DnsTreeName)) - ((LPBYTE)(TempTargetInfo)) + ((LPBYTE)UserTargetInfo) ); TempTargetInfo->PackageName = (LPWSTR)( ((LPBYTE)(TempTargetInfo->PackageName)) - ((LPBYTE)(TempTargetInfo)) + ((LPBYTE)UserTargetInfo) ); TempTargetInfo->CredTypes = (LPDWORD)( ((LPBYTE)(TempTargetInfo->CredTypes)) - ((LPBYTE)(TempTargetInfo)) + ((LPBYTE)UserTargetInfo) ); // // Copy the target info to the user's address space // Status = LsaFunctions->CopyToClientBuffer( NULL, TempTargetInfoSize, UserTargetInfo, TempTargetInfo ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Copy the struct itself to the user's address space // TargetInfo.TargetInformation = UserTargetInfo; Status = LsaFunctions->CopyToClientBuffer( NULL, sizeof(TargetInfo), Buffer, &TargetInfo ); if (!NT_SUCCESS(Status)) { goto Cleanup; } break; default: Status = STATUS_NOT_SUPPORTED; break; } Cleanup: if (Context != NULL) { SspContextDereferenceContext( Context ); } if ( TempTargetInfo != NULL ) { CredFree( TempTargetInfo ); } SspPrint((SSP_API, "Leaving SpQueryLsaModeContextAttributes for ctxt:0x%x, Attr:0x%x\n", ContextHandle, ContextAttribute)); return (SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR)); } //+------------------------------------------------------------------------- // // Function: SpSetContextAttributes // // Synopsis: Set attributes of the specified context // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS NTAPI SpSetContextAttributes( IN LSA_SEC_HANDLE ContextHandle, IN ULONG ContextAttribute, IN PVOID Buffer, IN ULONG BufferSize ) { NTSTATUS Status = STATUS_SUCCESS; PSSP_CONTEXT Context = NULL; PSecPkgContext_CredentialNameW CredentialNameInfo; ULONG CredentialNameSize; LPBYTE LocalBuffer = NULL; SspPrint((SSP_API, "Entering SpSetContextAttributes for ctxt:0x%x, Attr:0x%x\n", ContextHandle, ContextAttribute)); // // Find the currently existing context. // Status = SspContextReferenceContext( ContextHandle, FALSE, &Context ); if ( !NT_SUCCESS(Status) ) { SspPrint(( SSP_CRITICAL, "SpSetContextAttributes: invalid context handle.\n" )); goto Cleanup; } // // Grab a local copy of the data // // Sanity check this size before allocating LocalBuffer = (LPBYTE) NtLmAllocatePrivateHeap( BufferSize ); if ( LocalBuffer == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } Status = LsaFunctions->CopyFromClientBuffer( NULL, BufferSize, LocalBuffer, Buffer ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Return the appropriate information // switch(ContextAttribute) { case SECPKG_ATTR_USE_VALIDATED: if ( BufferSize != sizeof(SavedUseValidated) ) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; } SavedUseValidated = *(LPDWORD)LocalBuffer; break; case SECPKG_ATTR_CREDENTIAL_NAME: if ( BufferSize <= sizeof(SecPkgContext_CredentialNameW) ) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; } // Sanity check the pointer and the contained string. CredentialNameInfo = (PSecPkgContext_CredentialNameW) LocalBuffer; SavedCredentialType = CredentialNameInfo->CredentialType; // I'm guessing at the offset of the string. wcscpy( SavedCredentialName, (LPWSTR)(CredentialNameInfo+1) ); break; default: Status = STATUS_NOT_SUPPORTED; break; } Cleanup: if (Context != NULL) { SspContextDereferenceContext( Context ); } if ( LocalBuffer != NULL ) { NtLmFreePrivateHeap( LocalBuffer ); } SspPrint((SSP_API, "Leaving SpSetContextAttributes for ctxt:0x%x, Attr:0x%x\n", ContextHandle, ContextAttribute)); return (SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR)); } #endif